文本
文本节点是唯一携带字符的 Ebox 节点;所有盒子最终都是为排布它们而存在。本章描述文本节点如何创建、接受哪些属性,:wrap-mode 的各种断行模式如何填满可用宽度,CJK 文本、字符簇与 emoji 如何保持完整,文本装饰如何绘制,以及测量结果如何缓存。属性本身由样式属性参考定义;盒与常规流描述这些节点参与的段落,渲染模型描述它们解出的像素与行高。
Function: ebox-text-create (string &optional props)
Section titled “Function: ebox-text-create (string &optional props)”创建一个显示 STRING、带作者属性 PROPS 的 detached 文本节点。
参数。 string 是载荷,复制进节点;空字符串合法。props 是可选的作者属性列表,只限文本节点接受的绘制与排版属性::color、:background-color、:font-family、:font-size、:font-weight、:font-style、:text-decoration-line、:text-decoration-color 与 :text-decoration-style。布局属性属于拥有段落的盒子,不属于文本节点。
语义。 节点在作为其他节点的子节点传入之前保持 detached,传入后成为那个盒子的段落的一部分。文本节点是叶子,永远不接受子节点。它没有自己的盒子几何,由所在段落测量与换行::wrap-mode 与 :text-align 从拥有的盒子(或最近声明它们的祖先)读取,节点的排版属性沿与其他盒子相同的计算样式链继承。ebox-node-text-p 识别文本节点,ebox-node-set-text 之后可替换其载荷。
返回。 新建的 detached 文本节点。
错误。 string 不是字符串时报 ebox-error,消息为 "Ebox text payload must be a string"。不支持的属性或非法值在创建节点时报 ebox-error,例如 "Ebox text does not accept :width"。
示例。 (ebox-text-create "hello") 建立文本为 hello 的节点;把它挂到 3 像素宽的盒子里渲染,默认 word 模式下会断成 hel 与 lo。
参见。 ebox-node-create、ebox-node-set-text。
段落把行内片段断成不超过包含盒内容宽度的行。断行的单位是一个字符簇及其分类:换行符、空格、CJK 字符,或单词的一部分。:wrap-mode 选择其中哪些单位可以开始新行。
初值 word 模式整体移动单词:放不下的单词从下一行开始,它前面的空格结束当前行并被丢弃。比整行还宽的单词无处可移,于是逐字符簇切开。char 模式把每个字符簇都当作断点机会,于是每行填到像素。none 模式只在显式换行处断开;宽于段落的文本不换行,超出可用宽度的部分不显示,因为段落从不水平滚动。整段片段能放进一行时,断行计算整体跳过。
\n 在任何模式下都开始新行,也是 none 模式下唯一的断点。放不下的一串空格结束当前行:空格被丢弃,而不是推到下一行。只由空格组成的段落渲染为空行。
每行按 :text-align 在段落宽度内垫空格:left 把剩余像素留在末尾,center 平分,right 放在开头。行比段落还宽时没有可垫的空间,行在段落边缘被切。
软断点遵守标点规则。收尾标点(如 。、、、)、」、.、,)不能开始新行,开启标点(如 (、「、')不能结束一行;软断点会违反其中任何一条时,断点回退到前一个字符簇或单词,把标点拉回当前行、使该行变短。回退会清空该行时放弃规则,所以极窄的段落仍能推进。
示例。 4 像素宽、默认 word 模式下,中文。汉字 断成 中 / 文。 / 汉字:。 不能开始一行,于是被拉回上一行,中 独占一行。3 像素宽时每行一个字,。 可以开始一行,因为所需的回退会清空该行。ab(cd) 同理断成 ab / (cd / ),开括号不会落在行尾。
kp 断行模式
Section titled “kp 断行模式”kp 是 :wrap-mode 的合法取值,但 Ebox 尚未实现 Knuth–Plass 断行:布局落到 word 规则,所以 kp 段落与 word 段落断得完全一样,包括超宽单词的切法。这个模式预留给 EKP 排版包 单独实现的段落最优断行;Ebox 不链接 EKP,也没有任何 Ebox 构建会把 kp 变成最优算法。如今需要该行为的应用要用 EKP 排好段落,再把结果挂到别处。
CJK、字符簇与 emoji
Section titled “CJK、字符簇与 emoji”CJK 字符在 word 模式下像空格一样断:下一个字放不下时当前字就结束一行,所以中、日文段落可以在任何字符边界换行,无需额外的断词机会。CJK 标点同样参与上面的软断点规则,所以全角逗号或句号在行内有余地时绝不会落在行首。CJK 字符簇按自身宽度测量——回退度量下全角字符 2 像素,图形 frame 上按字体步进。
字符簇是测量、切分与断行的最小单位。组合记号跟随基字符,变体选择符与零宽连接符扩展它前面的簇,一对区域指示符构成一个旗帜簇;断行永远不会拆开这些部分。用连接符构成的 emoji 序列因此像单个表意文字一样:在 word 模式下整体移动,在 char 模式下算一个断点机会。超宽单词的默认切分也只在字符簇边界切,不会切进簇内。
:text-decoration-line 的值(underline、overline、line-through,或它们的列表)会投影到绘制文本的 face 上,装饰因此像 face 属性一样精确跟随字形。:text-decoration-color 选择线条颜色,默认 currentColor;:text-decoration-style 当前支持 solid 与 wavy,后者画波浪线。文本节点自己的装饰会替换 face 上的其他装饰,而不是叠加。
装饰向子孙传播。声明了装饰的盒子会把它沿树向下传过中间的盒子与行内盒,作用于下方每个没有自己声明装饰的文本节点;文本节点无法取消继承来的装饰,因为声明 none 只是让继承来的装饰继续生效。后代声明不同的线型时,该子树的继承装饰被替换,而不是叠加。
用 :visibility hidden 隐藏的子树中的文本保留字符与断行:文本绘制为等宽空白,因此隐藏文本占据的空间与显示时完全相同。visibility 能用作布局占位正是靠这一点。
示例。 给一个盒子加下划线、渲染 ab 与 cd 两段文本,两段都得到带 :underline 的 face;把 cd 设为 line-through 后,该段的 face 变成删除线。6 个字符、3 像素宽的隐藏文本仍然渲染两行空白。
插图: 字重、倾斜与两种文字装饰
(etaf-view (column :width '(ch 40) :item-gap '(lh 0) (text :font-weight 'bold "加粗") (text :font-style 'italic "倾斜") (text :text-decoration-line '(underline) "下划线") (text :text-decoration-line '(line-through) "删除线") (text :font-family (or (face-attribute 'fixed-pitch :family nil t) "Monospace") "等宽文本")))文本节点缓存自己的测量结果:测量时用的 face、载荷、wrap 模式、是否含换行、最长一行的宽度、最长不可断段的宽度,以及——在段落第一次需要断行时惰性计算——它的显示单位。载荷、face、wrap 模式或全局宽度世代的任何变化都会重算测量;未变化的节点直接复用。段落同样缓存断好的行,并在其中任何行内节点的文本、计算样式或交互变化时重建。
宽度按字符簇测量并按 face 记忆,所以重复字符的段落每个簇只量一次。度量不变的字体重排——例如等宽字体族下可继承的字重变化——只重绘不重测。整树字体变化使测量失效时,ebox-clear-cache 清空宽度缓存,下一次布局重新测量;渲染模型定义驱动这一切的世代。
Function: etaf-string-pixel-width (string)
Section titled “Function: etaf-string-pixel-width (string)”返回 STRING 第一行的显示像素宽。
语义。 量度会考虑 STRING 的 face 与 display 文本属性,所以带样式文本与纯文本可能量出不同结果;换行结束测量。应用用它把内容对齐到字形宽度,量度与渲染器一致。
返回。 像素宽;空字符串为零。
错误。 STRING 不是字符串时 error→ wrong-type-argument,数据点名 stringp。
参见。 ebox-text-create。
用非字符串载荷创建文本节点会报 ebox-error,不支持的属性或非法值同样如此:属性错误在构造时暴露,早于任何布局。空字符串是合法的,渲染为空行,这也是在块里刻意留一条空行的办法。
无法断开的文本仍会被处理:比行还宽的单词在 word 模式下按字符簇边界切开,none 模式下溢出部分直接不显示。当溢出的内容而不是装饰时,给段落更宽的盒子、改用 word 或 char,或把文本放进可滚动容器——滚动一章会讲。变窄的段落不会静默丢字符,none 除外;其他模式总会断开。