\chapter{盒子}

在 \in[drawing-sym] 节中，你已经见过盒子了，只是那时可能你还不知其究竟，本章将揭开它们的一些端倪。也有可能你早已钻研过 Donald Knuth 的《The \TeX\ Book》，对盒子的研究之深已经让我望风而拜，但是也许你未必熟悉 \CONTEXT\ 的盒子，故而本章仍有部分内容值得一观。

\section{\TEX\ 盒子}

之前的章节里，已多次暗示和明示，\TEX\ 系统是 \CONTEXT\ 的底层，二者的关系犹如引擎（发动机）和汽车的关系。对 \TEX\ 引擎丝毫不懂，并不影响你学习和使用 \CONTEXT\ 排版一份精致的文档。不过，懂得一些引擎层面工作原理，虽然貌似未必会有用处，但是实际上你并不能确定将来自己会不会成为一名 \TEX\ 黑客，如同你从前也从未想过有一天会学习 \CONTEXT。

在 \TEX\ 系统中，盒子是很重要的事物。例如，在 \CONTEXT\ 的排版的每一个段落，是一个竖向盒子，即 \type{\vbox}，该盒子之内又有一些横向盒子，即 \type{\hbox}，它们是段落的每一行。我们可以直接用这两种盒子构造一个不甚规整的段落：

\startexample
\vbox{
  \hbox{离离原上草}\hbox{一岁一枯荣}
  \hbox{野火烧不尽}\hbox{春风吹又生}
}
\stopexample
\example[option=TEX][todo-list]{竖向盒子和横向盒子}{\externalfigure[10/vbox-and-hbox.pdf]}

横向盒子可以指定它的长度，竖向盒子可以指定它的高度。例如，

\starttyping[option=TEX]
\hbox to 5cm {赋得古原草送别}
\vbox to 3cm {
  \hbox{离离原上草}\hbox{一岁一枯荣}\hbox{野火烧不尽}\hbox{春风吹又生}
}
\stoptyping

还有一种横向盒子 \type{\line}，其宽度是正文的宽度，该盒子内的文字会向两边伸展并与正文两侧边界对齐，例如

\cmdindex{line}
\starttyping[option=TEX]
\line{\darkred\bf 我能吞下玻璃而不伤身体。}
\stoptyping
\line{\darkred\bf 我能吞下玻璃而不伤身体。}

看到上述示例，想必你想起了 \in[essay] 节在设定文章标题的样式时，汉字之间的粘连被触发后的样子，与\type{\line} 的效果非常相似。使用 \type{\hfill} 或 \type{\hss} 可对横向盒子里的内容进行挤压。例如

\starttyping[option=TEX]
\line{\hfill 我能吞下玻璃而不伤身体。}
\line{我能吞下玻璃而不伤身体。\hfill}
\line{\hfill 我能吞下玻璃而不伤身体。\hfill}
\line{\hss 我能吞下玻璃而不伤身体。}
\line{我能吞下玻璃而不伤身体。\hss}
\line{\hss 我能吞下玻璃而不伤身体。\hss}
\stoptyping

\startframedtext[width=broad]
\vbox{
\line{\hfill 我能吞下玻璃而不伤身体。}
\line{我能吞下玻璃而不伤身体。\hfill}
\line{\hfill 我能吞下玻璃而不伤身体。\hfill}
\line{\hss 我能吞下玻璃而不伤身体。}
\line{我能吞下玻璃而不伤身体。\hss}
\line{\hss 我能吞下玻璃而不伤身体。\hss}
}
\stopframedtext

\index[hfil-hfill-hss]{\type{\hfil}，\type{\hfill} 和 \type{\hss}}
\type{\hfill} 和 \type{\hss}，都是可无限伸缩的粘连，还有一个伸缩能力弱于 \type{\hfill} 的 \type{\hfil}。竖向的可无限伸缩的粘连有 \type{\vfil}，\type{\vfill} 和 \type{\vss}。

\section{\CONTEXT\ 盒子}

在 \CONTEXT\ 层面，通常很少使用 \TEX\ 盒子，而是使用 \type{\inframed} 和 \type{\framed}——前者是后者的特例。与 \TEX\ 盒子相比，\CONTEXT\ 层面的盒子可以显示边框，且有非常多的参数可以定制它们的外观。

\type{\inframed} 用于正文，可用于给一行文字增加边框，例如

\cmdindex{inframed}
\starttyping[option=TEX]
\inframed{\type{\inframed{...}}}
\stoptyping

\noindent 结果为 \inframed{\type{\inframed{...}}}。倘若使用 \type{\framed}，例如

\cmdindex{framed}
\starttyping[option=TEX]
\framed{\type{\framed{...}}}
\stoptyping

\noindent 结果为 \framed{\type{\framed{...}}}。可以发现，\type{\inframed} 更适合在正文中使用，因为它能与文字基线对齐。事实上，\type{\inframed} 与 \type{\framed[location=low]} 等效，故而前者是后者的特例。例如

\starttyping[option=TEX]
\framed[location=low]{\type{\framed[location=low]{...}}}
\stoptyping

\noindent 结果为 \framed[location=low]{\type{\framed[location=low]{...}}}。

如果不希望 \type{\framed} 显示边框，只需 \type{\framed[frame=off]{...}}，也可以单独显示某条边线，并设定边线粗度和颜色：

\starttyping[option=TEX]
\line{
  \framed[frame=off,leftframe=on,rulethickness=4pt,framecolor=red]{foo}
  \framed[frame=off,topframe=on,rulethickness=4pt,framecolor=green]{foo}
  \framed[frame=off,rightframe=on,rulethickness=4pt,framecolor=blue]{foo}
  \framed[frame=off,bottomframe=on,rulethickness=4pt,framecolor=magenta]{foo}
}
\stoptyping
\line{
  \framed[frame=off,leftframe=on,rulethickness=4pt,framecolor=red]{foo}
  \framed[frame=off,topframe=on,rulethickness=4pt,framecolor=green]{foo}
  \framed[frame=off,rightframe=on,rulethickness=4pt,framecolor=blue]{foo}
  \framed[frame=off,bottomframe=on,rulethickness=4pt,framecolor=magenta]{foo}
}

\blank

可以设定盒子的宽度和高度，例如宽 10cm，高 2 cm 的盒子：

\starttyping[option=TEX]
\hbox to \textwidth{\hfill\framed[width=10cm,height=2cm]{foo}\hfill}
\stoptyping
\hbox to \textwidth{\hfill\framed[width=10cm,height=2cm]{foo}\hfill}

\section{对齐}

\CONTEXT\ 盒子的内容默认居中，即 \type{align=center}，此外还有 8 种对齐方式：

\starttyping[option=TEX]
\line{
  \setupframed[width=1.75cm,height=1.75cm]
  \framed[align={flushleft,high}]{A}
  \framed[align={middle,high}]{A}
  \framed[align={flushright,high}]{A}
  \framed[align={flushright,lohi}]{A}
  \framed[align={flushright,low}]{A}
  \framed[align={middle,low}]{A}
  \framed[align={flushleft,low}]{A}
  \framed[align={flushleft,lohi}]{A}
}
\stoptyping
\line{
  \setupframed[width=1.75cm,height=1.75cm]
  \framed[align={flushleft,high}]{A}
  \framed[align={middle,high}]{A}
  \framed[align={flushright,high}]{A}
  \framed[align={flushright,lohi}]{A}
  \framed[align={flushright,low}]{A}
  \framed[align={middle,low}]{A}
  \framed[align={flushleft,low}]{A}
  \framed[align={flushleft,lohi}]{A}
}

\noindent 注意，上述代码中的 \tex{setupframed}\index[setupframed]{\tex{setupframed}} 命令可以设定 \tex{framed} 盒子的样式，所作设定会影响到该命令之后的所有 \type{\framed} 盒子，但是，如果是在编组内设定盒子样式，所作设定不会影响编组之外的盒子。上述代码中的 \tex{line} 命令之后跟随的便是编组。

\section[framebg]{背景}

可将颜色作为 \type{\framed} 的背景。例如

\starttyping[option=TEX]
\inframed
  [background=color,
   backgroundcolor=lightgray,
   width=2cm,
   frame=off]{\bf foo}
\stoptyping

\noindent 结果为 \inframed[background=color,backgroundcolor=lightgray,width=2cm,frame=off]{\bf foo}。

通过 overlay，可将一些排版元素作为 \type{\framed} 的背景。例如

\index[overlay]{overlay}
\starttyping[option=TEX]
\defineoverlay[foo][{\framed[width=3cm,frame=off,bottomframe=on]{}}]
\midaligned{\inframed[background=foo,frame=off]{你好啊！}}
\stoptyping

\defineoverlay
  [foo]
  [{\framed[width=3cm,frame=off,bottomframe=on]{}}]
\midaligned{\inframed[background=foo,frame=off]{你好啊！}}
\blank

\noindent 也能在 overlay 里插入 \METAPOST\ 代码绘制的矢量图形，将其作为盒子的背景，例如

\starttyping[option=TEX]
\startuseMPgraphic{foo}
path p;
p := fullcircle scaled OverlayWidth;
draw p withpen pencircle scaled .4pt withcolor darkred;
\stopuseMPgraphic
\defineoverlay[circle][\useMPgraphic{foo}]
\def\fooframe#1{%
  \inframed[frame=off,background=circle]{#1}%
}
\stoptyping
\startuseMPgraphic{foo}
path p;
p := fullcircle scaled OverlayWidth;
draw p withpen pencircle scaled .4pt withcolor darkred;
\stopuseMPgraphic
\defineoverlay[circle][\useMPgraphic{foo}]
\def\fooframe#1{%
  \inframed[frame=off,background=circle]{#1}%
}

上例定义了一个宏 \tex{fooframe}，它能为盒子里的文字套上一个圆，例如 \type{\fooframe{123}}，结果为 \fooframe{12345}，还记得 \in[lua] 节实现带圈数字的方案吗？现在又多了一个方案，而且该方案不依赖于所用字体是否提供带圈字符。

\section[box-depth]{盒子的深度}

如果你仔细观察，\CONTEXT\ 盒子里的内容在水平方向是精确居中的，但是在竖直方向上却并非如此。例如

\starttyping[option=TEX]
\inframed{\framed{我看到一棵樱桃树}}。
\stoptyping

\noindent 结果为 \inframed{\framed{我看到一棵樱桃树}}。可见 \type{\inframed} 内部的 \type{\framed} 的底部有着看似多余的空白。使用盒子的参数 \type{depth} 可以消除这些空白，但问题在于这处空白从何而来及其高度是多少。该问题与底层 \TEX\ 的西文排版机制有关。

无论是 \TEX\ 还是 \CONTEXT\ 盒子，它们本身是没有深度的，但是当它们里面的文字或盒子有深度时，它们便有了深度。至于深度值具体是多大，可以借助 \TEX\ 的盒子寄存器进行测量。例如，定义一个盒子寄存器 \type{box0}：

\cmdindex{setbox}
\starttyping[option=TEX]
\setbox0\hbox{\inframed{\framed{我看到一棵樱桃树}}}
\stoptyping
\setbox0\hbox{\inframed{\framed{我看到一棵樱桃树}}}

\noindent 现在我们有了一个 0 号盒子，使用 \type{\wd}，\type{\ht} 和 \type{\dp} 可分别测量该盒子的宽度、高度和深度……顺便复习一下表格的用法：

\startexample
\starttabulate[|c|c|c|]
\TL[3]
\NC 宽度 \NC 高度 \NC 深度 \NR
\HL
\NC \the\wd0 \NC \the\ht0 \NC \the\dp0 \NR
\BL[3]
\stoptabulate
\stopexample
\simpleexample[option=TEX]{\getexample}

将上述所得深度信息取负作为 \type{\inframed} 的参数 \type{depth} 的值，即

\starttyping[option=TEX]
\inframed[depth=-\dp0]{\framed{我看到一棵樱桃树}}
\stoptyping

\noindent 便可消除深度，结果为 \inframed[depth=-\dp0]{\framed{我看到一棵樱桃树}}。

\section{段落盒子}

上文的示例，盒子里的内容都非常简单。事实上，\tex{framed} 能够容纳段落，只是默认情况下，它不具备段落断行功能，需要将其参数 \type{align} 的值设为 \type{normal} 方能断行，见下例。

\startexample
\framed[width=6cm, align=normal]{%
第一段 ... ... ...\par
第二段 ... ... ...
}
\stopexample
\simpleexample[option=TEX]{\getexample}

\noindent 注意，上述代码中，\tex{framed} 的 \type!{! 后面的注释符 \type{%} 是必要的，否则花括号后的换行符会被视为一个不可忽略的空格，从而导致第一段开头是一个空格，请以下例作为对比。

\startexample
\framed[width=6cm, align=normal]{
第一段 ... ... ...\par
第二段 ... ... ...
}
\stopexample
\simpleexample[option=TEX]{\getexample}

由于 \tex{framed} 用于容纳段落需要注意一些细节，故而对于此类任务，通常用语法形式封闭的 \type{framedtext} 环境来做，例如

\startexample
\startframedtext[width=\textwidth, rulethickness=4pt, framecolor=darkgray]
第一段 ... ... ...\par
第二段 ... ... ...
\stopframedtext
\stopexample
\simpleexample[option=TEX]{\null}
\getexample

\section{自定义盒子}

类似于 \tex{type} 和 \type{typing} 环境支持用户自定义一些专用的命令，\tex{framed} 盒子也可如此。下例定义了一个专用的盒子，并演示了其样式的设定方法。

\startexample
\defineframed[mybox]
\setupframed[mybox][rulethickness=4pt,framecolor=darkred]
\mybox{自定义盒子}
\stopexample
\simpleexample[option=TEX]{\getexample}

类似地，\type{framedtext} 环境也支持定义专用的段落盒子，例如

\startexample
\defineframedtext[bluebox]
\setupframedtext[bluebox][width=\textwidth, rulethickness=4pt,framecolor=blue]
\startbluebox
自定义盒子
\stopbluebox
\stopexample
\simpleexample[option=TEX]{\null}
\getexample

\subject{结语}

\TEX\ 盒子是无形的。\CONTEXT\ 盒子是有形的。老子曾说过，恒无欲，以观其妙；恒有欲，以观其所徼。故而，\TEX\ 要懂一些，\CONTEXT\ 也要懂一些。
