跳转至

LuaTeX-LMTX-CLD学习笔记

欢迎加入本人建的"LuaTeX ConTeXt 学习互助"群:431714622,互助学习LuaTeX ConTeXt LaTeX相关技术,以及通过Lua、Python实现格式化文本、数据的自动排版。

注意:为了避免本博客的jinja引擎误解析,已经在{+#之间,以及对应的}之前加入空格


vscode开发环境设置#

编译配置#

使用wk-j的Save and Run插件运行脚本编译命令,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
"saveAndRun": {
    "commands": [
        {
            "match": "\\.lmtx$",
            "isAsync": true,
            "cmd" : "chcp 65001 && context --autogenerate ${file}",
            // "cmd": "chcp 65001 && context ${file}",
            "useShortcut": false,
            "silent": true
        },
    ]
},

Lua配置#

使用sumneko的Lua插件。配置Lua.workspace.library,添加库,比如(根据你的实际安装位置调整路径,没有实际测试哪些是必要的):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    "Lua.workspace.library": [
        "D:\\venvs\\context-win64\\", //整个安装路径,800多个文件,慢;以下也有700多个文件
        // "D:\\venvs\\context-win64\\bin\\",
        // "D:\\venvs\\context-win64\\tex\\texmf-context\\scripts\\context\\lua\\",
        // "D:\\venvs\\context-win64\\tex\\texmf-context\\tex\\context\\base\\mkiv\\",
        // "D:\\venvs\\context-win64\\tex\\texmf-context\\tex\\context\\base\\mkxl\\",
        // "D:\\venvs\\context-win64\\tex\\texmf-context\\tex\\context\\interface\\mkiv\\",
        // "D:\\venvs\\context-win64\\tex\\texmf-context\\tex\\context\\modules\\mkiv\\",
        // "D:\\venvs\\context-win64\\tex\\texmf-context\\tex\\context\\patterns\\mkiv\\",
        // "D:\\venvs\\context-win64\\tex\\texmf-context\\tex\\generic\\context\\luatex\\",
        // "D:\\venvs\\context-win64\\tex\\texmf-win64\\bin\\",
    ],

LuaTeX#

本部分主要学习如下资料的笔记:

编译命令用:

1
2
lualatex test.tex
context --luatex test.mkiv
LuaTeX使用户可以从根基上控制TEX的行为,因此不只适用于学术排版、数学排版,而可作为任何排版系统的基础,比如数据排版speedata publisher几乎纯用LuaTeX,又如结合ConTeXt使用。

LuaTeX起源pdfTeX,但并未使用pdfTeX的代码,也不会随之更新。

LuaTeX引擎支持两种编程语言:传统的基于TeX的语言,比如LaTeX(LuaLaTeX)宏包,还有Lua脚本语言。

TEX主要关心的是生成段落和页。段落生成器(par builder)把字符列表分行,构成box,行间用glue、penalty等分隔。页面生成器(page builder)累积行,在适当的时机输出。TEX并不构造实际的页面,而是使用能够操纵box的基元(primitive,低级命令)。结果再推给一个文件(通常是pdf)。

TEX引擎是一个黑盒,LuaTEX打开了这个黑盒,让用户可以通过Lua访问TEX引擎约的320个基元和隐藏的部分(LuaTEX新加基元)。在处理过程中的几乎所有合理的结点上,LuaTEX引擎把钩子提供给Lua;也就是说,你可以重载TEX自身的基本行为。 当我们说“回调/回调函数”(callback)的时候,说的就是这些钩子。

数据结构:结点和结点列表#

node类型

结点列表vlist/hlist, 字模glyph, 胶glue, 铅条/铅线rule, 罚分penalty, 小件whatsit, 字间kern,等等。结点列表是结点的一种类型,一种容器结点。

node list

  • 垂直结点列表vlist,表示vbox、vtop、vcenter等垂直盒子中的内容
  • 水平结点列表hlist,表示hbox等水平盒子中的内容
  • 胶是表示弹性占位的特殊特殊盒子(或理解为弹簧)

    • 水平胶: hskip, parfillskip, 等
    • 垂直胶: hskip, baselineskip, 等
  • 结点列表包含

    • 盒子的元数据集合和
    • 取用盒子中的对象的方法;
1
2
3
4
5
6
7
\hskip <natural width> plus <amount to stretch> minus <amount to shrink>

\hbox to100pt{%
A\hskip4pt plus3pt minus 2pt B%
\hskip 0pt plus 2fil C%
\hskip 0pt plus 2fill D%
\hskip 0pt plus 3fill}

box及其排版程序#

  • TEX box: \hbox, \vbox and \vtop
    • \raise \lower (盒子的shift)在水平模式中调整盒子的垂直位置
    • \moveleft \moveright (盒子的shift)在垂直模式中调整盒子的水平位置
    • \hss,等于\hskip 0pt plus 1fil minus 1fil, 它会吸掉h盒子另一侧的所有宽度,无论正负。
    • \vss, 等于\vskip 0cm plus 1fil minus 1fil
    • \strut \hsize
  • 对应的LuaTEX包裹器: \hpack, \vpack and \tpack,不会应用回调
  • LuaTEX相关的四个文本方向,LuaMetaTEX则是正反两个。
    • \righttoleft \lefttoright;\textdirection(ConTeXt中无效)

盒子排版时LuaTEX的行为:

  1. 构造结点列表。在LuaTEX中是双向链表,以便操控;但TEX本身只使用前向链接。
  2. 对结点列表进行断词(hyphenate),即注入分割/断词结点(discretionary node)。这根据字模结点/字符结点的语言属性而定。
  3. 然后构造合字(ligature),如果字体有这样复合字。当应用这个机制时,我们说的是ConTeXt基本模式(base mode)。
  4. 然后应用字间,如果字体提供的话。这也是基本模式的行为。
  5. 最后是盒子打包:
    • 对于水平盒子,列表被打包在hlist结点中,通常是一行,计算并设置好了尺寸。
    • 对于垂直盒子,段落被分成行,没有断词,而有optimal断词,或者在糟糕的情况下应用所谓紧急拉伸(emergency stretch),结果是一个设置好尺寸的vlist结点。

在传统TEX中,前四步交织在一起。但在LuaTEX中是分开的,因为第5步可以通过回调重载;在这种情况下,第3、4步(甚至第2步)也有可能重载,特别是在Lua中处理字体的时候。

结点表示#

node.types()返回类型总表(括号中的数字是类型id):

hlist (0), vlist (1), rule (2), ins (3), mark (4), adjust (5), boundary (6), disc (7), whatsit (8), par (9), dir (10), math (11), glue (12), kern(13), penalty (14), unset (15), style (16), choice (17), noad (18), radical (19), fraction (20), accent (21), fence (22), math_char (23), sub_box (24), sub_mlist (25), math_text_char (26), delim (27), margin_kern (28), glyph (29), align_record (30), pseudo_file (31), pseudo_line(32), page_insert (33), split_insert (34), expr_stack (35), nested_list (36), span (37), attribute (38), glue_spec (39), attribute_list (40), temp (41), align_stack (42), movement_stack (43), if_stack (44), unhyphenated (45), hyphenated (46), delta (47), passive (48), shape (49).

结点视觉化lua库:

pgundlach/viznodelist.lua

主要文本结点#

开头的数字是类型id

9 par(local_par)#

段首插入的结点。一般不要改动。

FIELD TYPE EXPLANATION
attr node list of attributes
pen_inter number local interline penalty (from \localinterlinepenalty)
pen_broken number local broken penalty (from \localbrokenpenalty)
dir string the direction of this par. see 8.2.15
box_left node the \localleftbox
box_left_width number width of the \localleftbox
box_right node the \localrightbox
box_right_width number width of the \localrightbox

0 hlist#

横向的行盒子。

FIELD TYPE EXPLANATION
subtype number 0 = unknown, 1 = line, 2 = box, 3 = indent, 4 = alignment, 5 = cell,6 = equation, 7 = equationnumber, 8 = math, 9 = mathchar, 0 = hextensible, 11 = vextensible, 12 = hdelimiter, 13 = delimiter, 14 =overdelimiter, 15 = underdelimiter, 16 = numerator, 17 = enominator, 18 = limits, 19 = fraction, 20 = nucleus, 21 = sup, 2 = sub,23 = degree, 24 = scripts, 25 = over, 26 = under, 27 = ccent, 28 =radical
attr node 属性结点
width number 盒子的宽度
height number 盒子的高度
depth number 盒子的深度
shift number 垂直于字符前进方向的位移(效果相当于\lower和\raise,对于vlist则相当于\moveleft和\moveright)
glue_order number 数字范围[0, 4],表示胶的序号(0即f,1即fi,2即fil...4即filll??)
glue_set number 胶的设定值:计算后的胶的比率
glue_sign number 0 = 正常, 1 = 拉伸, 2 = 收缩
head/list node 本列表的第一个结点(两个名字等效)
dir string 盒子的方向,参看 8.2.15

LMTX:还可能有字段:attr, class, depth, direction, doffset, glueorder, glueset, gluesign, height, hoffset, list, orientation, shift, state, width, woffset, xoffset and yoffset。其中,orientation, woffset, hoffset, doffset, xoffset and yoffset用于后端旋转/设定朝向和偏置,比如在直排中。

1 vlist#

竖向的列盒子。 与hlist相似。小类只有0,4,5。

29 glyph#

字模结点,输入文本用。

FIELD TYPE EXPLANATION
subtype number 0=character, 1=ligature, 2=ghost, 3=left, 4=right
attr node 属性结点
char number 字符在字体中的索引
font number 字体标识
lang number 语言标识
left number 冻结的\lefthyphenmnin值
right number 冻结的\righthyphenmnin值
uchyph boolean 冻结的\uchyph值
components node 指向带子/合体字(ligature)组件
xoffset number 水平偏置
yoffset number 垂直偏置
width number 字符原始宽度(只读)
height number 字符原始高度(只读)
depth number 字符原始深度(只读)
expansion_factor number 要应用的扩展系数(段落构造时生成,供后端使用)
data number 给用户预留的通用数据空间

is_char函数用于检查结点是不是小类小于256的字符结点。 其变体is_glyph,不检查小类是不是小于256,返回字符值/nil和id。

uses_font函数输入一个结点和字体id,如果字符结点或断行结点引用了字体,则返回true。

属性attr可以携带跨越不同处理阶段的信息,一般用作标记,以便在后面的处理过程中能够识别。参考。比如存储边注标记,然后通过post_linebreak_filter回调,在段落构造好后再构造边注。参考Post_linebreak_filter

n.char也是unicode码点(codepoint)字符,可以通过unicode.utf8.char(n.char)取得字符。

跟结点有关的一些命令(基元):

COMMAND NODE EXPLANATION
\hbox hlist 水平盒子
\vbox vlist 基线在底部的垂直盒子
\vtop vlist 基线在顶部的垂直盒子
\hskip glue horizontal skip with optional stretch and shrink
\vskip glue vertical skip with optional stretch and shrink
\kern kern 水平或垂直调整用的skip
\discretionary disc 分割。断词点 (前结点/pre, 后结点/post, 替换结点/replace)
\char glyph a character
\hrule rule a horizontal rule
\vrule rule a vertical rule
\textdir(ection) dir a change in text direction

打开所有基元命令:

\directlua { tex.enableprimitives('',tex.extraprimitives()) }

12 glue#

胶,表示空格。 在传统的TEX中,skip(即glue)是唯一的复合值数据对象。TEX看到文本流中的空格时会插入skip;也可以通过\hskip和\vskip插入。表示一个skip的glue组件的结构叫glue_spec,有如下字段:

FIELD TYPE EXPLANATION
width number 水平或垂直占位
stretch number 额外的占位,或伸展量
stretchorder number 拉伸量的系数/倍数(0即f,1即fi,2即fil...4即filll ??)
shrink number 额外(负值)占位,或收缩量
shrinkorder number 收缩量的系数/倍数
1
2
3
4
5
6
7
8
local parfillskip = node.new("glue", "parfillskip")
parfillskip.stretch = 2^16 -- 拉伸量65536
parfillskip.stretchorder = 2 --拉伸倍数fil?

-- 不再使用胶性
-- parfillskip.spec = node.new("gluespec")
-- parfillskip.spec.stretch = 2^16
-- parfillskip.spec.stretch_order = 2

glue_spec(胶性)结点在寄存器中存储一套胶值。

胶结点:

FIELD TYPE EXPLANATION
subtype number 0 = userskip, 1 = lineskip, 2 = baselineskip, 3 = parskip, 4 = abovedisplayskip, 5 = belowdisplayskip, 6 = abovedisplayshortskip, 7 = belowdisplayshortskip, 8 = leftskip, 9 = rightskip, 10 = topskip, 11 = splittopskip, 12 = tabskip, 13 = spaceskip, 14 = xspaceskip, 15 = parfillskip, 16 = mathskip, 17 = thinmuskip, 18 = medmuskip, 19 = thickmuskip, 98 = conditionalmathskip, 99 = muglue, 100 = leaders, 101 = cleaders, 102 = xleaders, 103 = gleaders
attr node 属性结点
leader node 指向起头的盒子或条线

还有字段: width, stretch stretchorder, shrink, and shrinkorder

水平胶和垂直胶都用width,这是TEX的传统。

通常的词语间空格也导致一个spaceskip小类(通常是0号的userskip)。

汉字字间胶#

系统会在汉字/中文间插入\hskip 0pt plus 0.5em,即

1
2
3
g = node.new("glue")
g.width=0
g.stretch=0.5 * font.fonts[t.font].size

13 kern#

字间/出格,字模之间的相对偏置关系。 由\kern命令、字体机制、数学机制生成的结点。

FIELD TYPE EXPLANATION
subtype number 0 = fontkern, 1 = userkern, 2 = accentkern, 3 = italiccorrection
attr node 属性结点
kern number 固定的水平或垂直提升量

右端凸排,就是在右端字符(比如)结点后增加;同样,左凸排,就是在右端字符(比如)结点前增加。注意处理这个位置原有的kern。

另有margin_kern(???)

14 penalty#

罚分,控制断行的标记。 由\penalty、nodes.penalty()命令生成。

FIELD TYPE EXPLANATION
subtype number 0 = userpenalty, 1 = linebreakpenalty, 2 = linepenalty, 3 = wordpenalty, 4 = finalpenalty, 5 = noadpenalty, 6 = beforedisplaypenalty, 7 = afterdisplaypenalty, 8 = equationnumberpenalty
attr node 属性结点中
penalty number 惩罚值

小类是非正式的,TEX并不使用它们。当你碰到断行罚分(linebreakpenalty)时,你应该想到,它是club、widow(落单词)和其他相关惩罚的的累加。

罚分用来控制断行(比如某些标点前后),-10000以下表示必须断行,10000以上表示禁止断行,其余中间值表示由引擎决定。

2 rule#

参看ConTeXt-rule学习笔记

条线,铅条,铅线。 LuaTEX会使用条线来存储可重用对象和图像,所以比tex \hrule和\vrule的小类多;user小类结点不可见,可以通过回调截取。支持的 字段有:attr, char, data, depth, font, height, left, right, width, xoffset and yoffset.

FIELD TYPE EXPLANATION
subtype number 0 = normal, 1 = box, 2 = image, 3 = empty, 4 = user, 5 = over, 6 = under, 7 = fraction, 8 = radical, 9 = outline
attr node 属性结点
width number 条线宽度,特殊值−1073741824用来处理胶的尺寸
height number 条线高度 (可以是负值)
depth number 条线深度 (可以是负值)
left number 左端移位 (宽度会减除它)
right number 右端移位 (宽度会减除它)
dir string 条线方向,参看 8.2.15
index number 可用来引用的可选索引
transform number 私有值 (也用来指定轮廓宽度)

3 insert#

浮动对象的插入标记。 与\insert原语相关。

FIELD TYPE EXPLANATION
subtype number 插入(insertion)类
attr node 属性结点
cost number 与本插入关联的惩罚(penalty)
height number 插入的及高度
depth number 插入的深度
head/list node 插入体的第一个结点

还有与关联的胶有关的字段:width, stretch, stretch_order, shrink and shrink_order. 都是数字。

4 mark#

文本标记。 与\mark原语相关联。

FIELD TYPE EXPLANATION
subtype number 未使用
attr node 属性结点
class number mark类
mark table 表示token列表

5 adjust#

垂直间距(两行之间)调整标记。 与\vadjust原语相关联。

FIELD TYPE EXPLANATION
subtype number 0 = normal, 1 = pre
attr node 属性结点
head/list node 调整后的材料

7 disc#

词中的分割结点。与\discretionary and \-原语相关, 即连字符-,断词机制也会生成这样的结点。

FIELD TYPE EXPLANATION
subtype number 0 = discretionary, 1 = explicit, 2 = automatic, 3 = regular, 4 = first, 5 = second
attr node 属性结点
pre node 指向断词前的文本
post node 指向断词后后文本
replace node 指向不断词的文本
penalty number 与断词相关联的惩罚,通常时 \hyphenpenalty 或 \exhyphenpenalty

18 noad#

数学noad结点。

8 whatsit前端小玩意#

小玩意儿,小东西。一种简单的结点,只有一个小类。他甚至比用户结点(user node,实际上很可能是这样)结点还小,使用极少的内存。怎么使用他全由你定,他是极简的。你可以分配一个小类,它可以有一些属性。完全有用户控制。

node.whatsits查询小类:

open(0), write(1), close(2), special(3), save_pos(6), late_lua(7), user_defined(8), pdf_literal(16), pdf_refobj(17), pdf_annot(18), pdf_start_link(19), pdf_end_link(20), pdf_dest(21), pdf_action(22), pdf_thread(23), pdf_start_thread(24), pdf_end_thread(25), pdf_thread_data(26), pdf_link_data(27), pdf_colorstack(28), pdf_setmatrix(29), pdf_save(30), pdf_restore(31), pdf_link_state(32).

有些是通用的,有些与所选后端dvi或pdf相关。

user_defined#

User_defined类小玩意结点,是唯一可以从Lua code生成或操控的。实际上,它们是对扩展机制的一种扩展。LuaTEX引擎将简单地跨过这些小玩意,而不去看其内容。

FIELD TYPE EXPLANATION
attr node list of attributes
user_id number id number
type number type of the value
value number a Lua number
node a node list
string a Lua string
table a Lua table

type可选六个值。可用string.byte("l")代替108。

VALUE MEANING EXPLANATION
97 a list of attributes (a node list)
100 d a Lua number
108 l a Lua value (table, number, boolean, etc)
110 n a node list
115 s a Lua string
116 t a Lua token list in Lua table form (a list of triplets)

108是魔法类型。实际值可以是任何lua type。

1
2
3
4
5
6
    local w = node.new("whatsit","user_defined")
    node.setattribute(w, 3, 333)
    local v = node.hasattribute(w, 3, 333)
    w.type=108
    w.value = "hello world"
    print(w.subtype, w.user_id,w.type, w.value,w.attr, v) --有些不起效

6 boundary#

(单词、凸排的)边界结点。 相关的原语是:\noboundary, \boundary, \protrusionboundary and \wordboundary

FIELD TYPE EXPLANATION
subtype number 0 = cancel, 1 = user, 2 = protrusion, 3 = word
attr node 属性结点
value number values 0–255 are reserved

10 dir#

方向结点,标记正在灌注、需要改变方向的文本的部分,由命令\textdir生成。

FIELD TYPE EXPLANATION
attr node list of attributes
dir string TLT, TRT, RTT, or LTL(3个字母依次表示段落、行、字符的起始方向)
level number nesting level of this direction whatsit

28 margin_kern#

边缘字间,是由突出(protrusion)引起的。

FIELD TYPE EXPLANATION
subtype number 0 = left, 1 = right
attr node list of attributes
width number the advance of the kern
glyph node the glyph to be used

node库#

等于nodes.nuts???

luatex手册 8.7

导语#

结点库中包含一些处理结点和结点列表的便利工具,可用于创建、更换、拷贝、删除和插入luatex结点对象——排版器(typesetter)的核心对象。

在lua中,结点表示为luatex.node元数据类型的用户数据。

所有结点都有以下三个字段,其余字段则根据类型(whatsits根据小类)而定:

1
2
3
4
5
{
    next:...,  -- 下一个结点
    id:...,  -- node type(数值,有些库函数可以接受字符串)
    subtype:..., --小类(数值),比如用于区分whatsits(小玩意儿)
}

两个结点的比较,只是比较索引值,即使已经释放,仍被看作相同。

结点相关回调的一般方法:

  • 假设结点列表没有问题,双向链接正确(应用node.slide可修复)。
  • 插入结点时,确保你插入的解读是先前移除的一个,或是一个新的,或一个拷贝(深拷贝)。不要把一个结点插入两次。
  • 如果永久移除一个结点,请确保也释放结点或列表。
  • 虽然你可以欺骗系统,但通常情况下,当你试图复制一个不存在的结点,或释放一个已经释放的结点时,你会触发一个错误。这种检查涉及一些开销,但目前的折中方案是可以接受的。
  • 当你完成(回调)后,传回结果(如果需要)。你有责任确保列表有正确链接(为安全起见,可以再次应用node.slide)。原则上,在后续动作中不被接受的结点你也可以放在列表中。有些结点会被忽略,有些会触发错误,有时引擎会直接崩溃。

从以上可以明确,结点的内存管理必须由用户显式操作。lua垃圾回收器看不到结点,对于不在使用的结点和列表,用户必须自行调用结点释放函数。结点在列表(结点双向链表)中不能被引用两次。一般情况下通过getter和setter来处理。

有关于已分配的结点内存的统计数据,这对追踪很方便。通常情况下,使用的结点数量并不多。一个页面的排版可能涉及到成千上万的结点,但大多数在页面被运出后就被释放了。与其他程序相比,结点的内存使用量并不是很高。因此,如果由于某种原因,你的应用程序(内存)泄漏了结点,如果在运行结束时,你失去了几百个结点,那也不是什么大问题。事实上,如果你创建了盒子,并制作了副本,但却没有很好地清除它们,你的运行结束时肯定会带着用过的结点,统计数据会提到这点。对于attribute和skips(胶性结点)来说也是如此,请保持当前状态只涉及正在使用的结点。

type结点类型#

  • t = node.types() --大类id:string的映射表(查表用node.type())
  • type = node.type( n) -- 类型id查类型名称;结点转字符串结点(string node)
  • id = node.id( type) --类型名称查类型id
  • subtype = node.subtype( type) -- 小类名称查id
  • t = node.whatsits() --whatsits小类id:string的映射表
  • t = node.is_node( item) --结点索引

fields结点字段#

  • t = node.fields( id) --查某类型的有效字段,也接受名称
  • t = node.fields( id, subtype) --查某小类的有效字段,也接受名称
  • t = node.has_field( n, field) --判断字段有无

打印结点n的字段值:

1
2
3
for _, v in pairs(node.fields(n.id)) do
    print(v, n[v])
end

new新建结点#

  • n =node.new( id)
  • n =node.new( id, subtype) --小东西

也可以接收字符串名称。字段被初始化为nil或0。

请确保这样生成的结点只使用一次;如果不以某种方式回传,则要释放(free)掉。

设置字体:n.font = fnt, 设置字符:n.char = chr

free释放结点内存#

  • next =node.free( n) -- 释放结点内存,返回后继结点
  • flushnode( n) -- 释放结点内存,不返回后继结点
  • node.flushlist( n) --直接冲洗结点n开头的整个列表

请确保没有寄存器、next字段等指向要释放、冲洗的结点、列表,以及列表中的所有元素。

copy拷贝结点#

  • m =node.copy( n) --不会拷贝next字段
  • m =node.copy_list( n) --不会拷贝属性结点(通常也没有必要)
  • m =node.copy_list( n, m) --指定区间

next前后结点#

  • m =node.next( n)
  • m =node.prev( n)

current_attr当前属性结点#

  • m =node.current_attr()

返回前激活的实际属性结点的引用,也就是说,其后可能会改变(比如通过tex.setattribute)。

1
2
3
4
5
local x1 = node.new("glyph")
local x2 = node.new("glyph")
local ca = node.current_attr()
x1.attr = ca
x2.attr = ca

hpack打横包#

这个函数通过把从结点n开始的列表打包成一个水平的盒子来创建一个新的hlist。只有一个参数时,用其组件的自然宽度创建盒子。在三个参数的形式中,info必须是扩充的或精确的,而w是要使用扩充的(\hbox spread)或精确的(\hbox to)宽度。第二个返回值是生成的盒子的坏度(badness)。

  • h, b =node.hpack( n) --使用元素的自然宽度,返回b是新结点的badness
  • h, b =node.hpack( n, w, info) --info: 'additional'|'exactly',w: the additional (\hbox spread) or exact (\hbox to) width to be used
  • h, b =node.hpack( n, w, info, dir)

注意:这个函数可能会有意想不到的副作用,比如更新一些 \marks 和 \inserts。还要注意的是,h的内容是原来的结点列表n:如果你调用node.free(h),你也会释放结点列表本身,除非你事先明确地将list字段设置为nil。而且,以类似的方式,调用node.free(n)也会使h失效!

通常使用node.hpack把列表装入盒子中,这样会正常设置(一些自然属性):

local box, _ = node.hpack(list,0,"exactly")

如果直接把列表绑定到盒子的列表头,则需要自行设置,比如施胶:

1
2
3
4
5
local box = node_new(hlist_id, "box")
box.head = list
box.glueorder = 2
box.glueset = 6.6683349609375
box.gluesign = 2

vpack打竖包#

  • h, b =node.vpack( n)
  • h, b =node.vpack( n, w, info)
  • h, b =node.vpack( n, w, info, dir)

把行间距加入行中#

这是一个实验性的辅助工具,在考虑到基线和行距的情况下,将行距添加到行中。

  • n, delta =node.prepend_prevdepth( n, prevdepth)

dimensions行尺寸#

以结点n开头的行,从头到尾(或到结点t之前)的、经过缩放后的点数(pt,浮点数)。

  • w, h, d=node.dimensions( n)
  • w, h, d=node.dimensions( n, dir)
  • w, h, d=node.dimensions( n, t)
  • w, h, d=node.dimensions( n, t, dir)

或增加胶参数:

  • w, h, d=node.dimensions( glue_set, glue_sign, glue_order, n)
  • w, h, d=node.dimensions( glue_set, glue_sign, glue_order, n, dir)
  • w, h, d=node.dimensions( glue_set, glue_sign, glue_order, n, t)
  • w, h, d=node.dimensions( glue_set, glue_sign, glue_order, n, t, dir)

以上考虑了加胶的情况,特别适用于获取加过盒子的结点子列表的实际宽度,比如:

1
2
3
4
5
6
7
8
\setbox0 = \hbox to 20pt {a b}
\directlua{print (node.dimensions(
    tex.box[0].glue_set,
    tex.box[0].glue_sign,
    tex.box[0].glue_order,
    tex.box[0].head,
    node.tail(tex.box[0].head)
)) }

请记住,这是TEX中少数几个使用浮点数的地方之一,这意味着当你将hpack报告的宽度与尺寸进行比较时,你会得到四舍五入的微小差异。

请注意:node.dimensions、n.width测量的实际上是测量范围内所有元素的标记宽度,而不是视觉看到的实际宽度。也就是说,如果有禁则导致的glue correctionskip(负值,表示刨除不得不附加在盒子边界外的字符宽度)等,算上它们,只是盒子宽度;刨除它们(只计算到最后一个字模)才是视觉宽度,才可以探测overfull。

清除行末负值干扰后,node.dimensions与重新hpack后得到的n.width一致;但此宽度不可用于调整旧行宽度,与视觉宽度不一致,而应记录清除的宽度总数,然后从旧行n.width中扣减。 TODO 有待进一步学习其原理!!!

另有便利的查询(在LMTX中为:node.direct.rangedimensions):

  • w, h, d=node.rangedimensions( parent, first)
  • w, h, d=node.rangedimensions( parent, first, last)

全局设置尺寸参考tex.dimen;本地/当前尺寸参考tex.get;考虑hangafter, hangindent, parindent, leftskip, rightskip等,请参考源码: typo-fln.lua

数学列表转行列表#

h =node.mlist_to_hlist( n, display_type, penalties)

跟回调函数mlist_to_hlist一样。

tail尾结点#

返回结点n开头的结点列表的最后一个结点。

m =node.tail( n)

slide滑到尾结点#

返回结点n开头的结点列表的最后一个结点。同时会在结点之间生成前导各点的反向链条。

m =node.slide( n)

length结点计数#

以结点n开头的结点列表的计数。

  • i =node.length( n)
  • i =node.length( n, m) --终止结点m不计算在内

匹配类型id(字符串名称也可以)的版本:

  • i =node.count( id, n)
  • i =node.count( id, n, m)

是不是字符、是不是字模#

字模结点的子类型预示着该字模是否已经变成了一个字符引用(character reference)。

  • b =node.is_char( n)
  • b =node.is_glyph( n) # node.direct.is_glyph()

似乎与手动检查id不同。

traverse迭代结点列表#

  • t, id, subtype =node.traverse( n)

迭代从结点n开始的结点列表。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- 典型用法
for n in node.traverse(head) do
    ...
end

-- 等价于
do
    local n
    local function f (head,var)
        local t
        if var == nil then
            t = head
        else
            t = var.next
        end
        return t
    end
    while true do
        n = f (head, n)
        if n == nil then break end
        ...
    end
end

traverse_id#

迭代结点列表中特定id的结点。

  • t, subtype =node.traverse_id( id, n)

即在node.traverse()基础上加入类型检查逻辑:

1
2
3
4
5
...
while not t.id == id do
    t = t.next
end
...

traverse_char#

迭代结点列表中的字模结点。

字模结点(glyph nodes):小类(subtype)小于256的结点。

n, font, char =node.traverse_char( n)

返回列表,过滤所有字模(glyph)【???】:

n, font, char =node.traverse_glyph( n)

traverse_list#

迭代列表中的hlist结点和vlist结点。

n, id, subtype, list =node.traverse_list( n)

通过双向链接迭代#

Hans Hagen:不要在traverse中删除结点,以免造成内部的n.nex指向混乱,这样手动遍历:

1
2
3
4
5
local n = head
while n do
   ...
   n = n.next
end

has_glyph#

列表中的第一个字模或分割(disc)结点。

n =node.has_glyph( n)

disc是加入连字符的点。

first_glyph#

返回列表的第一个字模结点。

字模结点:subtype为glyph的结点。

  • n =node.first_glyph( n)
  • n =node.first_glyph( n, m) --包含终点

end_of_math#

下一个数学结点。

t =node.end_of_math( start)

可能是起点。

remove#

从列表中移除当前结点。

head, current =node.remove( head, current)

得自行确定是不是列表的一部分。返回新的head和当前结点(或nil)。

不要在遍历(traverse)中删除结点,会搞乱n.next的指向。参考上面traverse部分。

insert_before, insert_after#

在结点前、后插入结点。

head, new =node.insert_before( head, current, new) head, new =node.insert_after( head, current, new)

head可以是nil。返回新的head。

删除和插入节点时,列表头始终应该更新为返回的新列表头。除非你确定列表头不可能变动!!!

ligaturing应用合体字#

  • h, t, success =node.ligaturing( n)
  • h, t, success =node.ligaturing( n, m)

对指定结点列表应用TEX风格的合体字功能。h,新头;t,新尾。

kerning应用字间#

  • h, t, success =node.kerning( n)
  • h, t, success =node.kerning( n, m)

对指定结点列表应用TEX风格的字距调整功能。h,新头;t,新尾(可能插入了kern node,词语边界可能有特殊的kerning)。

字模与字符结点转换#

  • node.unprotect_glyph( n)
  • node.unprotect_glyphs( n,[ n]) -- 指定终点

把所有字符结点的小类值减去256。在结点处理过程中把字符转换成字模(包含字形设计信息)。

  • node.protect_glyph( n)
  • node.protect_glyphs( n,[ n])

把所有字符结点的小类值加上256(如果值是1则只加255)。

last_node#

弹出当前列表最后一个结点。

n =node.last_node()

write#

在当前列表最后写入一个结点。

node.write( n)

你应该保证当前是水平模式。

结点能否跳过#

skippable =node.protrusion_skippable( n)

为了在字符突出(character protrusion)开启时发现行边界,可能用到此功能。

setglue注胶#

  • node.setglue( n)
  • node.setglue( n,width,stretch,shrink,stretch_order,shrink_order)
    • node.setglue(n,655360,false,65536),只调整 width and shrink.

非数值等于0,即重置属性。

getglue查看胶值#

width, stretch, shrink, stretch_order, shrink_order = node.getglue( n)

is_zero_glue空值胶#

isglue =node.is_zero_glue( n)

结点的两种取用模式#

变量引用(直接模式)和整数索引的互换:

  • d = node.todirect( n))
  • n = node.tonode( d))

直接模式使用键值操作属性:nodeobject.char。整数索引模式使用get... set... 方法,速度更快:getfield(nodenumber,"char")

常用取用器(设置器是配套的):

  • getnext
  • getprev
  • getboth
  • getid
  • getsubtype
  • getfont
  • getchar
  • getwhd
  • getdisc
  • getlist
  • getleader
  • getfield
  • getbox
  • getoffsets

界面node.direct中还有更多辅助工具。

属性的处理#

ltmx

属性是将额外的信息与节点联系起来的一种方便方式。你可以在TEX端和Lua端给它们赋值,并在Lua端查阅它们。一个很大的优点是它们服从分组。它们是链接在一起的列表,通常对它们的检查是相当有效的,即使它们的用量很大。一个宏包必须在TEX端提供一些方法来管理这些属性,否则可能会发生冲突。类似的,还可以使用资产表。

对属性寄存器的赋值,会导致给结点设置过属性的列表赋值;这个应用很重要,因为附加到结点上的值基本上是一个(排序的)键值对散列数组。一般来说,通过使用node库中的专用函数来处理属性列表和属性是最容易的。

当前激活的属性列表:

m = node.currentattr()

1
2
3
4
5
local x1 = node.new("glyph")
local x2 = node.new("glyph")
local ca = node.currentattr()
x1.attr = ca
x2.attr = ca
  • v = node.hasattribute( n, id)
  • v = node.hasattribute( n, id, val)

  • v = node.getattribute( n, id)

找到第一个有某属性的列表:

  • v, n = node.findattribute( n, id)

  • node.setattribute( n, id, val)

  • v = node.unsetattribute( n, id)

  • v = node.unsetattribute( n, id, val)

以下参考

Attribute寄存器遵循计数器的规则,可以通过\the等命令来使用。

定义:

  • \attribute ⟨16-bit number⟩ ⟨optional equals⟩ ⟨32-bit number⟩
  • \attributedef ⟨csname⟩ ⟨optional equals⟩ ⟨16-bit number⟩

-"7FFFFFFF −2147483647 表示unset。

相同scope中生成的结点通过链接持有相同的Attribute键和值,这使得它很方便用于扩展。

使用结点Attributes给TeX和Lua传递信息,遵循分组规则,大量使用仍然相当高效。

\attributedef\myattribute=333 \myattribute=1

tex.getattribute() tex.setattribute(["global",] , )

\attribute0=1 \setbox0=\hbox attr 2 = 3 {contents}

这个盒子(结点)及其内容的每个结点,具有attribute 0,值为1;但只有盒子结点具有attribute 2,值3。它的内容具有当前分配给该属性的任何值(很可能该属性未被设置)。

attr关键字应该在to和spread之前。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- 给盒子设置资产表,携带字符char(效率较低,但可以使用复杂数据,比如表)
local n_char = n.char
local p = node.getproperty(hlist)
if not p then
    p = {}
    node.setproperty(hlist, p)
end
p.char = n_char

--给盒子设置属性{1:n.char}(效率更高,只能用整数)
node.setattribute(hlist, 1, n_char)

结点资产Property表#

除了属性,每个节点也可以有一个资产表(property table),你可以用setproperty函数给这个表赋值,用getproperty函数获取资产。管理资产比管理属性效率低,但方便使用复杂数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
\directlua {
    local n = node.new("glyph")
    local t = node.getproperty(n)
    if not t then
        t = { }
        node.setproperty(n,t)
    end
    t.one = "foo"
    t.two = "bar"
    print(node.getproperty(n).one)
    print(node.getproperty(n).two)
    node.free(n)
}

There are a few helper functions that you normally should not touch as user: getpropertiestable and will give the table that stores properties (using direct entries) and you can best notmess too much with that one either because LuaTEX itself will make sure that entries related tonodes will get wiped when nodes get freed, so that the Lua garbage collector can do its job. Infact, the main reason why we have this mechanism is that it saves the user (or macro package)some work. One can easily write a property mechanism in Lua where after a shipout propertiesgets cleaned up but it's not entirely trivial to make sure that with each freed node also its properties get freed, due to the fact that there can be nodes left over for a next page. And having acallback bound to the node deallocator would add way to much overhead.

When we copy a node list that has a table as property, there are several possibilities: we do thesame as a new node, we copy the entry to the table in properties (a reference), we do a deep copyof a table in the properties, we create a new table and give it the original one as a metatable.After some experiments (that also included timing) with these scenarios we decided that a deepcopy made no sense, nor did nilling. In the end both the shallow copy and the metatable variantwere both ok, although the second one is slower. The most important aspect to keep in mind isthat references to other nodes in properties no longer can be valid for that copy. We could usetwo tables (one unique and one shared) or metatables but that only complicates matters.

When defining a new node, we could already allocate a table but it is rather easy to do that atthe lua end e.g. using a metatable __index method. That way it is under macro package control.When deleting a node, we could keep the slot (e.g. setting it to false) but it could make memoryconsumption raise unneeded when we have temporary large node lists and after that only smalllists. Both are not done because in the end this is what happens now: when a node is copied,and it has a table as property, the new node will share that table. The copy gets its own tablewith the original table as metatable.

A few more experiments were done. For instance: copy attributes to the properties so that wehave fast access at the Lua end. In the end the overhead is not compensated by speed andconvenience, in fact, attributes are not that slow when it comes to accessing them. So this wasrejected.

Another experiment concerned a bitset in the node but again the gain compared to attributeswas neglectable and given the small amount of available bits it also demands a pretty strongagreement over what bit represents what, and this is unlikely to succeed in the TEX community. It doesn't pay off.

Just in case one wonders why properties make sense: it is not so much speed that we gain,but more convenience: storing all kinds of (temporary) data in attributes is no fun and thismechanism makes sure that properties are cleaned up when a node is freed. Also, the advantageof a more or less global properties table is that we stay at the Lua end. An alternative is to storea reference in the node itself but that is complicated by the fact that the register has somelimitations (no numeric keys) and we also don't want to mess with it too much.

TeX相关库#

tex库#

简介#

tex包含了一大串虚拟的TEX内部参数,这些参数部分可以写入。这里说的 "虚拟 "意味着,这些条目在Lua中没有严格定义,而只是由一个元表(metatable)来控制的一个前端,元表操作实际的TEX值。因此,大多数的Lua表操作符(如pairs#)对这类条目不起作用。目前,可以访问几乎所有你可以在\the之后使用的参数,单一标记(token)或TEX特殊标记。这不包括那些需要额外参数的参数,如\the\scriptfont。包含简单的整数和尺寸寄存器的子集是可写、可读的(如\tracingcommands\parindent)。

内部参数值的set、get#

本节中的所有参数,可以使用名字作为tex表中的索引直接访问到,或者使用tex.get和tex.set访问。确切的参数和返回值取决于实际的参数,tex.set是否有任何作用也是如此。对于可以设置的参数,可以使用global作为tex.set的第一个参数;这使得赋值是全局的而不是局部的。

tex.set (["global",] <string> n, ...)

... = tex.get (<string> n)

Glue有点特别,因为涉及到五个值。返回值是一个glue_specnode,但是当你把false作为最后一个参数传给tex.get时,你会得到glue的宽度,当你传给true时,你会得到所有五个值。否则你会得到一个结点,它是内部值的一个副本,所以你要负责在Lua端释放它。当你设置胶数量时,你可以传递一个glue_spec或者最多五个数字。如果你传入true,你会得到5个胶值,而当你传入false时,你只得到宽度。

整数参数,可读可写:

  • tex.adjdemerits
  • tex.binoppenalty
  • tex.brokenpenalty
  • tex.catcodetable
  • tex.clubpenalty
  • tex.day
  • tex.defaulthyphenchar
  • tex.defaultskewchar
  • tex.delimiterfactor
  • tex.displaywidowpenalty
  • tex.doublehyphendemerits
  • tex.endlinechar
  • tex.errorcontextlines
  • tex.escapechar
  • tex.exhyphenpenalty
  • tex.fam
  • tex.finalhyphendemerits
  • tex.floatingpenalty
  • tex.globaldefs
  • tex.hangafter
  • tex.hbadness
  • tex.holdinginserts
  • tex.hyphenpenalty
  • tex.interlinepenalty
  • tex.language
  • tex.lastlinefit
  • tex.lefthyphenmin
  • tex.linepenalty
  • tex.localbrokenpenalty
  • tex.localinterlinepenalty
  • tex.looseness
  • tex.mag
  • tex.maxdeadcycles
  • tex.month
  • tex.newlinechar
  • tex.outputpenalty
  • tex.pausing
  • tex.postdisplaypenalty
  • tex.predisplaydirection
  • tex.predisplaypenalty
  • tex.pretolerance
  • tex.relpenalty
  • tex.righthyphenmin
  • tex.savinghyphcodes
  • tex.savingvdiscards
  • tex.showboxbreadth
  • tex.showboxdepth
  • tex.time
  • tex.tolerance
  • tex.tracingassigns
  • tex.tracingcommands
  • tex.tracinggroups
  • tex.tracingifs
  • tex.tracinglostchars
  • tex.tracingmacros
  • tex.tracingnesting
  • tex.tracingonline
  • tex.tracingoutput
  • tex.tracingpages
  • tex.tracingparagraphs
  • tex.tracingrestores
  • tex.tracingscantokens
  • tex.tracingstats
  • tex.uchyph
  • tex.vbadness
  • tex.widowpenalty
  • tex.year

整数参数,只读:

  • tex.deadcycles
  • tex.insertpenalties
  • tex.parshape
  • tex.interlinepenalties
  • tex.clubpenalties
  • tex.widowpenalties
  • tex.displaywidowpenalties
  • tex.prevgraf
  • tex.spacefactor

尺寸参数:

尺寸参数接受Lua数字(表示缩放过的点数)或字符串(包含次数)。其结果总是缩放过的点数。以下可读可写:

  • tex.boxmaxdepth
  • tex.delimitershortfall
  • tex.displayindent
  • tex.displaywidth
  • tex.emergencystretch
  • tex.hangindent
  • tex.hfuzz
  • tex.hoffset
  • tex.hsize
  • tex.lineskiplimit
  • tex.mathsurround
  • tex.maxdepth
  • tex.nulldelimiterspace
  • tex.overfullrule
  • tex.pagebottomoffset
  • tex.pageheight
  • tex.pageleftoffset
  • tex.pagerightoffset
  • tex.pagetopoffset
  • tex.pagewidth
  • tex.parindent
  • tex.predisplaysize
  • tex.scriptspace
  • tex.splitmaxdepth
  • tex.vfuzz
  • tex.voffset
  • tex.vsize
  • tex.prevdepth
  • tex.prevgraf
  • tex.spacefactor

以下只读:

  • tex.pagedepth
  • tex.pagefilllstretch
  • tex.pagefillstretch
  • tex.pagefilstretch
  • tex.pagegoal
  • tex.pageshrink
  • tex.pagestretch
  • tex.pagetotal

对只读参数进行设置会覆盖原参数。

对于prevdepth, prevgraf and spacefactor的操作,通常通过tex.nest表来操作。

方向参数:

  • tex.bodydir
  • tex.mathdir
  • tex.pagedir
  • tex.pardir
  • tex.textdir

胶参数:

  • tex.abovedisplayshortskip
  • tex.abovedisplayskip
  • tex.baselineskip
  • tex.belowdisplayshortskip
  • tex.belowdisplayskip
  • tex.leftskip
  • tex.lineskip
  • tex.parfillskip
  • tex.parskip
  • tex.rightskip
  • tex.spaceskip
  • tex.splittopskip
  • tex.tabskip
  • tex.topskip
  • tex.xspaceskip

Muglue胶参数:

  • tex.medmuskip
  • tex.thickmuskip
  • tex.thinmuskip

tocken列表参数:

  • tex.errhelp
  • tex.everycr
  • tex.everydisplay
  • tex.everyeof
  • tex.everyhbox
  • tex.everyjob
  • tex.everymath
  • tex.everypar
  • tex.everyvbox
  • tex.output
转换命令#

只读,返回字符串。

  • tex.eTeXVersion
  • tex.eTeXrevision
  • tex.formatname
  • tex.jobname
  • tex.luatexbanner
  • tex.luatexrevision
  • tex.fontname(number)
  • tex.uniformdeviate(number)
  • tex.number(number)
  • tex.romannumeral(number)
  • tex.fontidentifier(number)
last item命令#

只读,返回数字。

  • tex.lastpenalty
  • tex.lastkern
  • tex.lastskip
  • tex.lastnodetype
  • tex.inputlineno
  • tex.lastxpos
  • tex.lastypos
  • tex.randomseed
  • tex.luatexversion
  • tex.eTeXminorversion
  • tex.eTeXversion
  • tex.currentgrouplevel
  • tex.currentgrouptype
  • tex.currentiflevel
  • tex.currentiftype
  • tex.currentifbranch
取用寄存器:set, get and is*#

TEX的属性(\attribute)的寄存器可以使用tex表的2X5个虚拟子表来访问和写入:

  • tex.attribute
  • tex.count
  • tex.dimen,全局设置;而当前/本地值通过 tex.get("key")获得;key:
  • textwidth
  • columnwidth
  • linewidth(没有全局值?)
  • tex.skip
  • tex.glue
  • tex.muskip
  • tex.muglue
  • tex.toks

可以使用与控制序列\attributedef, \countdef, \dimendef, \skipdef, \toksdef相关的名字作为这些表的索引(LuaTeX启动时会查询):

1
2
tex.count.scratchcounter = 0
enormous = tex.dimen['maxdimen']

此功能最终还可用于和展开为数字的宏。

count和attribute寄存器接受、返回Lua数字。

dimension寄存器Lua数字(缩放后的点数)或字符串(内含绝对尺寸,禁用em and ex and px),结果始终是缩放后的点数。

除了以上的数字描述形式,还有一一对应set、get函数形式,比如:

1
2
3
4
tex.setskip (["global",] <number> n, <node> s)
tex.setskip (["global",] <string> s, <node> s)
<node> s = tex.getskip (<number> n)
<node> s = tex.getskip (<string> s)
字符code寄存器: [get|set]*code[s]#

通过tex的六个虚拟字表读写TEX字符code表(\lccode, \uccode, \sfcode, \catcode, \mathcode, \delcode):

  • tex.lccode
  • tex.uccode
  • tex.sfcode
  • tex.catcode
  • tex.mathcode
  • tex.delcode

对应的get、set形式:

1
2
tex.setsfcode (["global",] <number> n, <number> s)
<number> s = tex.getsfcode (<number> n)
Box寄存器: [get|set]box#

以数组形式设置和查询由\hbox, \vbox or \vtop生成的box实例:

tex.box

对应的set、get形式:

1
2
3
4
tex.setbox(["global",] <number> n, <node> s)
tex.setbox(["global",] <string> cs, <node> s)
<node> n = tex.getbox(<number> n)
<node> n = tex.getbox(<string> cs)

通过赋值方式引用盒子,要保证被引用盒子始终有效。或用深拷贝:

tex.box[0] = node.copy_list(tex.box[2])

重用boxe: [use|save]boxresource and getboxresourcedimensions#

寄存盒子以便使用、重用:

1
local index = tex.saveboxresource(n,attributes,resources,immediate,type,margin)

生成引用:

local reused = tex.useboxresource(n,wd,ht,dp)

获取尺寸(width, height, depth and margin):

local w, h, d, m = tex.getboxresourcedimensions(n)

triggerbuildpage#

在有东西要建立的情况下,调用内部函数来建立一个页面。

splitbox#

分割盒子:

local vlist = tex.splitbox(n,height,mode)

剩余部分留在原盒子中,返回一个包装过的vlist。本操作与\vsplit对应。mode可以是additional or exactly,关联分割出的盒子。

Accessing math parameters: [get|set]math#
Special list heads: [get|set]list#
Semantic nest levels: getnest and ptr#
辅助函数#

n = tex.sp( o) n = tex.sp( s)

用数字或字符串表示的公开尺寸,转换为缩放过的点数。

Functions for dealing with primitives#

开启基元命令(置于前缀表中):

  • tex.enableprimitives( prefix, primitive names)

提取基元命令:

  • t = tex.extraprimitives( s, ...)
  • t = tex.primitives() 所有命令的表

例子:

tex.enableprimitives("", tex.extraprimitives( "omega", "aleph", "luatex"))

tex.enableprimitives("luatex", tex.extraprimitives( "omega", "aleph", "luatex"))

tex.enableprimitives("luatex",tex.extraprimitives("luatex"))

t = tex.extraprimitives("tex")

查询所有基元:

1
2
3
4
t = tex.primitives()
for i, v in ipairs(t) do
    print (i, v)
end
内核功能界面#
badness#

b = tex.badness( t, s)

用于断行时进行计算劣质/坏性(badness)。

t and s是缩放值。the function returns the badness for when total t is supposed to be made from amounts that sum to s. 返回数近似\(100(𝑡/𝑠)^3\)

tex.resetparagraph#

重置段落。

linebreak分行#

见源码node-ltp.lua

1
local <node> nodelist, <table> info = tex.linebreak(<node> listhead, <table> parameters)

2022-07-26,在使用tex.linebreak()前,可以使用准备函数tex.preparelinebreak(),为你的结点列表准备必要的东西:

1
2
3
4
5
6
7
8
<par vmodepar> -- 用户必须实现自行准备,否则无法找到插入点
<glue parinitleftskip>
<glue parinitrightskip>
<glue indentskip> -- 用户必须实现自行准备,否则无法找到插入点
...
<penalty linepenalty> -- infinite penalty,原列表最后一个胶会被删除
<glue parfillleftskip>
<glue parfillskip> -- 与parfillrightskip相同
1
2
3
local h, t, parinitleftskip, parinitrightskip, parfillleftskip, parfillrightskip = tex.preparelinebreak(head)
local new_head = tex.linebreak(head)
tex.show(new_head) -- 查看结果

表参数parameters:

NAME TYPE EXPLANATION
tracingparagraphs number 如果是正值,则生成断行算法跟踪,见log
hsize number 在垂直盒子中排版时所用的行宽。以sp计
emergencystretch number (为前两遍断行失败后尝试第三遍时)段落各行应用的额外拉伸。以sp计,比如0.1 * hsize
pardir string 段落方向
hangafter number 如果是正数,表示缩进开始前的行数;如果是负数,其绝对值是指从段落的第一行开始的缩进行数。默认值为1,在每个段落后恢复默认值1。
hangindent number 如果是正数,表示从左页边距开始缩进;如果是负数,是从右页边距开始缩进的负数。默认值为0pt,在每个段落之后都会恢复。以sp计
leftskip glue_spec node 行左胶的胶性结点
rightskip glue_spec node 行右胶的胶性结点
parshape table 段落形状表
pretolerance number 不断词段落的容忍值(坏性),tex默认100
tolerance number 断词段落中各行的容忍值(坏性),tex默认200
looseness number 本段必须比理想行数多出这么多行
lastlinefit number 段落末行的调整率,乘以1000
pdfadjustspacing number
pdfprotrudechars number
interlinepenalty number or table 行间罚点,TEX默认为0,可以是像 \interlinepenalties 样的数组构成的表
linepenalty number 每个断行的罚点,tex默认10
hyphenpenalty number 在一个断词点/连字符处断行的罚点,TEX默认为50
exhyphenpenalty number 当断点前文本为空时,在水平行连字符条目处分行的罚点,TEX默认为50
clubpenalty number or table 段首孤行罚点,TEX默认为150,可以是像 \clubpenalties 样的数组构成的表
widowpenalty number or table 断尾孤行罚点,TEX默认为150,可以是像 \widowpenalties 样的数组构成的表
brokenpenalty number 在断词行后分页的额外罚点,TEX默认为100
finalhyphendemerits number 倒数第二行有连字时附加的罚点,TEX默认为5000
doublehyphendemerits number 连续两行以连字符结束的罚点,TEX默认为10000
adjdemerits number 毗连两行的视觉不协调(Badness分类号差别大于1)的罚点,TEX默认为10000

你必须自行确保 listhead 是一个合适的段落列表,这个函数不会向它添加任何结点。确切地说,如果你想替换内核的断行行为,你可能需要做以下工作(如果你没有在pre_linebreak_filter或linebreak_filter回调中工作,或者当从listhead开始的原始列表是以水平模式生成的):

  1. 添加一个"缩进盒子indent box",或许在开始时再添加一个par结点(仅当你需要它们时);
  2. 用一个无限罚分来替换最后的胶(如果最后一个结点不是胶,则添加这样一个罚分);
  3. 在该罚分结点之后添加一个胶结点表示\parfillskip;黄注:此时是各行拉伸胶宽、两端对齐模式。如果要模仿系统一般模式——各行平均收缩胶宽、末行左对齐,则要在parfillskip前再添加一个hskip,宽度和收缩均同textwidth(或hsize??)行宽;反之则宽度为0,拉伸为textwidth。
  4. 确保所有的prev指针都是正确的。

结果是一个结点列表,如果你想把它分配给一个\vbox,它仍然需要vpack。返回的info信息表包含四个值,都是数字:

  • prevdepth: 断行后段落最后一行的深度;
  • prevgraf: 断行后段落的行数;
  • looseness: 断行后段落的实际松性looseness(比理想行数多出的行数)
  • demerits: 所选方案的总缺点demerits

请注意,有几件事你不能用这个函数来干预。除了通过pdfadjustspacing之外,你不能影响字体的伸缩,因为它的设置是在其他地方进行的。hbadness和hfuzz等同此。所有这些都在hpack程序中,它通过globals获取自己的变量。

例:

1
2
-- 断行宽度10em
local new_head, info = tex.linebreak(copy_head, { hsize =  tex.sp("10em")})

断行后:

1
2
3
4
5
6
7
# 一般行末尾是两个胶:
<node :   5582 <=   5594 =>   6210 : glue rightskip>
<node :   5594 <=   6210 =>    nil : glue righthangskip>
# 末行末尾是三个胶:
<node :   6157 <=   6162 =>   6198 : glue parfillleftskip>
<node :   6162 <=   6198 =>   6180 : glue rightskip>
<node :   6198 <=   6180 =>    nil : glue righthangskip>

配置示例(可能比较老旧):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
local main_text_tbl = {
    hsize = text_width,

    --The header will be centered.
    leftskip                 = util.filll(),
    rightskip                = util.filll(),

    parindent                = tex.parindent,
    parfillskipstretch       = 2^16,
    parfillskipstretch_order = 2,

    font           = main_text_font,
    space          = font.fonts[main_text_font].parameters.space,
    space_stretch  = font.fonts[main_text_font].parameters.space_stretch,
    space_shrink   = font.fonts[main_text_font].parameters.space_shrink,

    lang           = tex.language,
    lefthyphenmin  = tex.lefthyphenmin,
    righthyphenmin = tex.righthyphenmin,
}

段落形状parshape

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- 系统段落形状 tex.parshape ???空值

function do_linebreak( nodelist,hsize,parameters )
  parameters = parameters or {}

  local pdfignoreddimen
  pdfignoreddimen    = -65536000

  local default_parameters = {
    hsize = hsize,
    emergencystretch = 0.1 * hsize,
    hyphenpenalty = 0,
    linepenalty = 10,
    pretolerance = 0,
    tolerance = 2000,
    doublehyphendemerits = 1000,
    pdfeachlineheight = pdfignoreddimen,
    pdfeachlinedepth  = pdfignoreddimen,
    pdflastlinedepth  = pdfignoreddimen,
    pdfignoreddimen   = pdfignoreddimen,
    parshape = { {0,40*2^16},{0,hsize} } -- {{缩进,行宽},..}
  }
  setmetatable(parameters,{__index=default_parameters})
  local j = tex.linebreak(nodelist,parameters)

...
end
shipout#

tex.shipout( n)

把n号盒子送到输出文件,清除盒子寄存器。

getpagestate#

以整数值报告当前页的阶段(state): empty, box_there or inserts_only。

getlocallevel#

以整数值报告局部循环的当前水平。出错用。

Randomizers#

token库#

扫描器#

参见CLD的宏

token库提供了拦截输入并在Lua级别处理它的方法。该库提供了一个基本的扫描器基础设施,可以用来编写接受各种参数的宏。这个接口的目的是保持通用性,并且性能相当好。人们可以在没有太多开销的情况下建立额外的分析器。由于LuaTEX的主要原则是提供一套最小的工具,而不是解决方案,所以要看宏包编写者如何从中受益。扫描器函数可能是最吸引人的。

FUNCTION ARGUMENT RESULT
scan_keyword string returns true if the given keyword is gobbled; as with the regular TEX keyword scanner this is case insensitive (and ascii based)
scan_keywordcs string returns true if the given keyword is gobbled; this variant is case sensitive and also suitable for utf8
scan_int returns an integer
scan_real returns a number from e.g. 1, 1.1, .1 with optional collapsed signs
scan_float returns a number from e.g. 1, 1.1, .1, 1.1E10, , .1e-10 with optional collapsed signs
scan_dimen infinity, mu-units returns a number representing a dimension and or two numbers being the filler and order
scan_glue mu-units returns a glue spec node
scan_toks definer, expand returns a table of tokens tokens
scan_code bitset returns a character if its category is in the given bitset(representing catcodes)
scan_string returns a string given between {}, as \macro or as sequence of characters with catcode 11 or 12
scan_argument boolean this one is simular to scanstring but also accepts a \cs
scan_word returns a sequence of characters with catcode 11 or 12 as string
scan_csname returns foo after scanning \foo
scan_list picks up a box specification and returns a [h

当扫描token时,表示我们我们正在定义一个宏,在这种情况下,结果也将提供关于预期的参数的信息,在结果中,这将由一个表示异议的分隔符分开。expansion旗标决定了列表是否会被扩展。

scan_argument函数会扩展给定的参数。当扫描一个带括号的参数时,可以通过传递false(默认为true)来禁止扩展。在控制序列的情况下,通过false将导致单级扩展(宏的含义)。

lua界面,以字符串的形式传送参数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
\directlua {
function mymacro(n)
    ...
end
}
\define[1]\mymacro{%
    \directlua {
        mymacro(\number\dimexpr#1)
    }%
}
\mymacro{12pt}
\mymacro{\dimen0}

从输入流中扫描参数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
\directlua {
function mymacro()
    --直接扫描命令后参数,不需要再转换,对于数字和尺寸会高效一些,但也有限
    local d = token.scan_dimen()
    ...
end
}
\define[1]\mymacro{%
    \directlua {
        mymacro()
    }%
}
\mymacro 12pt
\mymacro \dimen0
生成token#

local t = token.create("relax")

生成一个带有\relax基元属性的token。可用的token属性包括:

NAME EXPLANATION
command 一个代表内部命令的数字
cmdname 命令的类型,比如catcode字符,确定内部处理的分类器
csname 控制序列(如果可用)
id token的id
tok TEX中存储的完整的token数字
active 布尔值,表示token的激活状态
expandable 布尔值,表示表示token(宏)能否展开
protected 布尔值,表示token(宏)受保护
mode 数值,表示一个字符或实体
index 数值,从0x0000到0xFFFF,表示一个TEX寄存器索引

可以通过getter get_ 获取token的这些属性。

#

参见CLD的宏

token.set_macro函数可以接受四个参数:

token.set_macro("csname","content") token.set_macro("csname","content","global") token.set_macro("csname")

第一个参数可以是catcodetable标识符:

token.set_macro(catcodetable,"csname","content") token.set_macro(catcodetable,"csname","content","global") token.set_macro(catcodetable,"csname")

等价于:

\def\csname{content} \gdef\csname{content} \def\csname{}

1
\directlua{token.set_macro('foo', 'GREEN')}

等价于:

1
\newcommand*\foo{GREEN}

宏输出命令的转义:

1
token.set_macro("mymacro",[-[\string\emph{content}]-])

\chardef(无效者会被忽略):

1
2
set_char("csname",number)
set_char("csname",number,"global")

生成指向Lua函数(存在表中)的token,可用lua.get_functions_table来取用。这是\luadef的伴侣。

1
2
set_lua("mycode",id)
set_lua("mycode",id,"global","protected")

PDF后端物件(whatsit)#

获取位置:pdf.getpos()

Fonts 字体#

更多细节参看font manual(s)

当前字体id/设置当前字体idfont.current() fonts.currentid()

当前字体数据 fonts.current()

指定id的字体数据 local tfmdata = fonts.identifiers[number]

字体数据表如下(有些是下级表的快捷方式):

keys type 描述
ascender number the height of a line conforming the font
descender number the depth of a line conforming the font
italicangle number the angle of the italic shapes (if present)
designsize number the design size of the font (if known)
size number the size in scaled points if the font instance
factor number the multiplication factor for unscaled dimensions
hfactor number the horizontal multiplication factor
vfactor number the vertical multiplication factor
extend number the horizontal scaling to be used by the backend
slant number the slanting to be applied by the backend
characters table the scaled character (glyph) information (tfm)
descriptions table 未缩放的原始字模信息(otf, afm, tfm)
indices table the mapping from unicode slot to glyph index
unicodes table the mapoing from glyph names to unicode
marks table a hash table with glyphs that are marks as entry
parameters table the font parameters as TEX likes them
mathconstants table the OPENTYPE math parameters
mathparameters table a reference to the MathConstants table
shared table a table with information shared between instances
unique table a table with information unique for this instance
unscaled table the unscaled (intermediate) table
goodies table the CONTEXT specific extra font information
fonts table the table with references to other fonts
cidinfo table a table with special information for the backend
filename string the full path of the loaded font
fontname string the font name as specified in the font (limited in size)
fullname string the complete font name as specified in the font
name string the (short) name of the font
psname string the (unique) name of the font as used by the backend
hash string the hash that makes this instance unique

获取结点的字体名称和字符

1
2
print(fonts.hashes.identifiers[n.font].properties.filename,
    fonts.hashes.identifiers[n.font].characters[n.char].tounicode)

luatex字体常识#

见于 Fonts out of ConTEXt: explaining luatex and mkiv

在TEX中,字体是抽象的,引擎只关心:

  • 字符如何映射到字模 mapping to glyphs
  • 如何以及何时构建合体字 ligature building
  • 每个字模的尺寸 dimensions
    • 基线 baseline,行内各字模间对齐的位置
    • 高 height,基线上的高度
    • 深 depth,基线下的高度
    • 字间 kerning

字模外框(bounding box)的宽度并不是字模定义上的宽度。Type1和OpenType字体(泛指otf、ttf、ttc)每个字模有advance width,这才是所用的宽度。OpenType字体没有高度和深度,需要从外框推导。

LuaTEX因为有旧引擎的包袱,从输入字符到后端字体应用需要经过多重映射;但ConTEXt MkIV使用utf,输入直接映射到字模。

TEX中没有空格,输入的空格会转换成胶 glue。

用户通过语言和脚本(languages and scripts)来控制字体特性。

虚拟字体是使用Lua表来定义的。又因为字体往往比较大,因此预先以Lua表的形式进行缓存处理,在安装新版字体或更新字体加载器时自动更新缓存。在内存中,每个缩放尺寸的字体实例需要单独生成。

定义字体时会定义从字符转换为字模的模式(mode),四种模式:none,base,node,auto。

LuaTEX构造器的基本/base模式如下:

  1. 给结点列表断词(加入分割/disc/discretionary结点) hyphenate the node list(传统TEX在分行过程中断词)
  2. 构建合字 build ligatures
  3. 注入字间 inject kerns
  4. 可能要分行 optionally break into lines

强制使用基本模式:

1
2
3
4
5
% 定义base模式,应用ligatures and kerns
% OpenType字体还可应用其他特性
\definefontfeature[basemode][mode=base,kern=yes,liga=yes]
\definefont[MyTitleFont][SerifBold*basemode at 12pt]
\definedfont[Serif*default at 36pt] %定义并立即运用

在结点/Node模式中,引起不处理字间,不构造合字;而是通过Lua巡视结点列表,根据需要处理。

如果无法决断使用何种模式,为安全起见,请全都使用结点/node模式,功能强大,但比较慢。在 ConTEXt中,还可使用自动/auto模式,会自动根据script and language进行分析,决定使用base模式还是node模式。

有时,比如为了呈现字面文本(verbatim)0,可用none模式,不会应用合字、字间和替换。

node模式可以动态地添加、减去、替换、重置:

  • \addfeature
  • \subtractfeature
  • \replacefeature
  • \resetfeature
  • \feature[more|less|new|local|old|reset][] % 通用命令,双参数

分割结点(discretionary nodes)是由断词引擎(hyphenation engine)加入的,或由用户直接加入或通过宏加入。

通常说来,cjk字体比较大,但规则简单(比如无需替换)。cjk脚本比较复杂,但另一方面也是简单的:因为不需要应用特性,所以字体部分是相当闲逸的。因为字模大,信息浓度高,每页的处理时间跟拉丁文字相差不多。对于大多数cjk,基本模式(Base mode)已经够用。

阿拉伯脚本则相反,需要分析、替换、定位,都是时效最低的。比如用Husayni字体,开启30个特性,平均每个特性5个规则,一个300个字符的段落就要执行45,000个动作。如果是多字体,就更不用说了。

可以定义Lua fonts,使用ConTEXt提供的各种辅助函数。

字体特性(features,如字间、合字)包括字体本身提供的,也包括ConTEXt提供的。

通常通过\definefontfeature定义特性集,再绑定到字体实例,如此来使用特性,而不是显式应用某个具体的特性。

字体信息存储在fonts.hashes中,打印结点n的字符字体包框信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
-- 打印结点n的字体描述信息
local fonthashes = fonts.hashes
local font_id_table = fonthashes.identifiers

print(":::n 包含字符:::", nodes.toutf(n))
-- 打印结点的所有字段信息
print ('::::::node.fields(n.id):::::')
for _, v in pairs(node.fields(n.id)) do
    print(v, n[v])
end
-- 打印结点字模描述信息
desc = font_id_table[n.font].descriptions[n.char]
print ('::::::n.char font desc:::::')
for i, v in pairs(desc) do
    print(i,v)
end
print (':::::BoundingBox:::::')
for i, k in pairs (desc.boundingbox) do
    -- 以基线左点为原点,依次是x1、y1,x2、y2
    print (i,k)
end

结点n(字符为,宋体12pt)详情:

:::n 包含字符::: ,

::::::node.fields(n.id):::::

  • id 28
  • subtype 32768
  • attr <node : nil <= 1220 => 1222 : attribute list>
  • char 65292
  • font 1 # 字体
  • language 56
  • lhmin 2
  • rhmin 2
  • uchyph 1
  • state 0
  • left 0
  • right 0
  • xoffset 0
  • yoffset 0
  • xscale 1000 # x标尺
  • yscale 1000 # y标尺
  • width 786000 # 字体原始宽度1000的786倍,以下尺寸同理
  • height 80958 # 高
  • depth 145410 # 深
  • total 226368 # 净高(height+depth)
  • expansion 0
  • data 0
  • script 1
  • hyphenate 499519
  • options 128
  • next <node : 1233 <= 550 => 1013 : kern userkern>
  • prev <node : 684 <= 512 => 1233 : kern userkern>

fonts.hashes.quads[n.font] # 方铅/字框宽度 78600,等于n.width

::::fonts.hashes.identifiers[n.font]字体详情::::

  • parameters table: 000004431ff4b000 # 处理后的字体参数表
    • units 1000 # 每个em的单元数(units_per_em)
    • factor 786 # 缩放因子/倍数
    • vfactor 786 # 垂直缩放因子/倍数
    • hfactor 786 # 高度缩放因子/倍数
    • quad 786000 # 方铅宽度(descender + ascender; units * hfactor)
    • descender 157200 # 字模格子的深度
    • ascender 628800 # 字模格子的高度
    • size 786432 # 尺寸(???)
    • scaledpoints 786432 # 伸缩后pt
    • textsize 786432
    • forcedsize 0
    • slantfactor 0.0
    • extrafactor 1
    • extendfactor 1.0
    • squeezefactor 1.0
    • designsize 655360.0
    • minsize 655360.0
    • maxsize 655360.0
    • extraspace 65500.0
    • spaceshrink 65500.0 # 收缩空(等于左右空白???)
    • spacestretch 98250.0 # 拉伸空 spaceshrink * 1.5
    • space 196500 # 等于spacestretch * 2
    • mathsize 0
    • expansion table: 0000050c3f0e5dc0
    • mode 0
    • xheight 301824 # 小写x高度
    • slant 0.0
    • width 0
  • descriptions table: 000004431ff4afc0 # 各字模的原始描述表
    • 65292 # 字符号码,即n.char
      • unicode 65292 # 字符号码
      • index 7629
      • height 103 # 高
      • depth 185 # 深
      • width 1000 # 外框宽度
      • vheight 1301 # 外框高度
      • tsb 883 # topsidebearing 上字肩(字上的承条/留空高度)
      • boundingbox table: 00000241d041c3c0 # 内框两个顶点(左下、右上)的坐标,原点为基线左端
        • 1 181 # x1
        • 2 -185 # y1
        • 3 330 # x2
        • 4 103 # y2
  • characters table: 000004431ff4af80
    • 所包含的各字符
  • specification table: 000004431b506ec0
    • hash stsong @ @ unknown
    • cs 3>mschinese-12pt-rm-tf-0--0
    • forcedname stsong.ttf
    • method *
    • global true
    • mathsize 0
    • scalemode 2
    • detail chinese
    • source ttf # 字体源格式
    • id 1
    • size 786432
    • name stsong # 字体名称
    • filename c:/windows/fonts/stsong.ttf
    • goodies
    • lookup file
    • features table: 000002c736ce6600
    • specification Serif sa 1
    • sub
    • resolved
    • forced ttf
    • textsize 786432
  • properties table: 000004431ff4b080
    • finalized true
    • hasmath false
    • fontname STSong
    • privates table: 000003c60ece5480
    • psname STSong
    • hash stsong @ @ unknown @ 786432
    • id 1
    • format truetype
    • language dflt
    • writingmode horizontal
    • mode base
    • filename c:/windows/fonts/stsong.ttf # 字体文件名
    • name stsong
    • subfont 1
    • script dflt
    • fullname STSong-2
  • shared table: 000004431ff4ad80
    • rawdata table: 00000549d5478d80
    • features table: 00000549d734ad40
    • processes table: 00000549d734af00
    • dynamics table: 00000549d734adc0
  • original Serif sa 1
  • fonts table: 00000443200e5340
    • 1 table: 00000234148e5380
  • writingmode horizontal
  • resources table: 000004431c343980
    • features table: 00000206d5724440
    • classes table: 00000206d56a1f00
    • foundtables table: 00000206d5724540
    • private 983040
    • marks table: 00000206d57246c0
    • alreadydone table: 00000206d4bab500
    • sublookups table: 00000206d5724a80
    • filename c:/windows/fonts/stsong.ttf
    • foundfilename c:/windows/fonts/stsong.ttf
    • marksets table: 00000206d5724700
    • version Version 1.02
    • sequences table: 00000206d5724740
    • unicodes table: 00000206d5724b00
    • duplicates table: 00000206d5724400
    • markclasses table: 00000206d5724680
  • unscaled table: 000004431ff4ad00
    • characters table: 0000030837362a00
    • cache_version 1.01
    • resources table: 0000030838843a40
    • changed table: 000003083bb49d00
    • properties table: 00000308373627c0
    • descriptions table: 0000030837362840
    • parameters table: 0000030837362a40
    • shared table: 000003083bb49c80
    • goodies table: 0000030837362900
    • mathparameters table: 0000030837362a80
  • goodies table: 000004431ce15f80

实际使用的字体文件,第一次加载时会生成缓存lua表字体文件名.tma,包含一般数据和每个字符的bounding box数据。

合字(略)

Goodies(略)

Tracing

缺失字符时跟踪信息:

  • \enabletrackers[fonts.missing=replace]
  • \enabletrackers[fonts.missing=remove]
  • \enabletrackers[fonts.missing]

拼字Composing(略)

执行lua代码#

代码使用方式

  • 行内代码
  • 用\catcode、dofile()引入代码文件

代码入口基元

  • \directlua{tex.print("Hi there")}
  • \latelua{}
  • \lateluafunction{}
  • \luaescapestring{}

引用函数表与定义表:

  • \luafunction
  • \luafunctioncall
  • \luadef
1
2
3
4
5
6
7
\directlua {
    local t = lua.get_functions_table()
    t[1] = function() tex.print("!") end
    t[2] = function() tex.print("?") end
}
\luafunction1
\luafunction2

\luabytecode and \luabytecodecall

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
\directlua {
    lua.bytecode[9998] = function(s)
    tex.sprint(s*token.scan_int())
    end
    lua.bytecode[5555] = function(s)
    tex.sprint(s*token.scan_dimen())
    end
}


\luabytecode 9998 5
\luabytecode 5555 5sp
\luabytecodecall9998 5
\luabytecodecall5555 5sp

预先展开:\directlua代码执行机制#

  • category codes and TeX tokens: converting text to tokens and tokens to text;

category codes and TeX tokens

  • TeX’s expansion process (and preventing expansion);

TeX’s expansion process

LuaTeX引擎会把代码预先交由TeX展开/求值,结果文本再作为lua代码交给lua引擎解释

1
2
3
4
5
6
7
8
9
\def\aa{tex}
\def\bb{.}
\def\cc{print}
\def\dd{("Hello")}
\directlua{
    \aa\bb\cc\dd
}
% 等价于
\directlua{tex.print("Hello")}

展开实际上是LuaTeX的一个API,即Lua与TeX交换数据的方式

1
2
3
4
5
6
7
\count75=1564 % TeX世界中的数据
\directlua{
    local x=\number\count75 \space % 等号右边会展开,即把TeX数据传给Lua;\space是输入空格的命令,也可以省略
    tex.print("x= "..x)
    local y = (2*x-65)/5
    tex.print(" and y = "..y)
}

展开会生成的Lua代码:

1
local x=1564 tex.print("x= "..x) local y = (2*x-65)/5 tex.print(" and y = "..y)

在控制台打印:

1
2
3
4
5
6
7
\directlua{
    local foo=[-[local x=\number\count75
    tex.print("x= "..x)
    local y = (2*x-65)/5
    tex.print(" and y = "..y)]-]
    print(foo)
}

Overleaf的控制台打印用texio.write(foo)

\directlua预处理(scan_toks())中不展开的权标(token)#

  • 速记命令权标/shorthand command tokens: 此类命令权标源于用TeX基元\chardef, \mathchardef, \countdef, \dimendef, \skipdef, \muskipdef, \toksdef所定义的控制序列(下表的command,表示数值):
    • \chardef ⟨command⟩ = ⟨numeric value⟩
    • \mathchardef ⟨command⟩ = ⟨numeric value⟩
    • \countdef ⟨command⟩ = ⟨numeric value⟩
    • \dimendef ⟨command⟩ = ⟨numeric value⟩
    • \skipdef ⟨command⟩ = ⟨numeric value⟩
    • \muskipdef ⟨command⟩ = ⟨numeric value⟩
    • \toksdef ⟨command⟩ = ⟨numeric value⟩
  • 不展开的权标: 此类命令权标源于会展开的命令,但\directlua已经对它们:
    • 显式指令不予展开,比如\noexpand\unexpanded命令压制展开。
    • 通过处理序列予以注入权标,如\the\toks等。
1
2
3
4
5
6
\chardef\mydollar=`\$ 
\directlua{
    local x =[-[I paid \mydollar30.]-] 
    texio.write(x)
}
% I paid \mydollar 30

注意: TeX中的\%, \&, \#, \$是用\chardef定义的,因此不可展开:

1
2
3
4
5
\chardef\#=`\#
\chardef\$=`\$
\chardef\%=`\%
\chardef\&=`\&
% I paid \mydollar 30

`\表示取得其后代码值的数字字符。但在LaTeX中这些都是宏,可展开。

避免lua代码展开导致的问题#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
% TeX宏会吞掉后面的空格
\directlua{
    myvar = \macro
  anothervar = 2
}
% 得到`yvar = 1anothervar = 2`

% Lua注释的短路问题
\directlua{
  myvar = 1
--  anothervar = 2
  onelastvar = 3
}
% 得到`myvar = 1 -- anothervar = 2 onelastvar = 3`
% 导致注释后的所有行无效

% 以上续行问题可以用 `\endlinechar=10` 重新定义换行符

更多

  • \noexpand⟨token⟩: 阻止单个⟨token⟩展开;
  • \unexpanded{⟨material⟩}: 阻止所有权标展开;
  • \protected: 宏定义前缀,阻止宏在某些环境,如\directlua, \write, \edef中展开。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
\directlua{local x= "\noexpand\TeX"}
% 传给Lua的是:
% local x= "\TeX "
% **空格是\directlua的tokenlist_to_cstring()生成的**

\def\macroA{"This unprotected macro contains a string"}
\protected\def\macroB{"This protected macro also contains a string"}
\directlua{
    local x=\macroA..\macroB
}
% local x="This unprotected macro contains a string"..\macroB

\let\\\relax %重定义\\的意义以避免展开问题

\directlua中的\the\toks#

\toks指向权标寄存器暂存的权标列表,列表是原样保存的,用\the\toks插入前不会预先展开。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
\def\mymacro{\TeX}
\directlua{
    local x="\mymacro"
}
% 得到:
% local x = "T\kern -.1667em\lower .5ex\hbox {E}\kern -.125emX"

\toks100={\TeX}
\directlua{
    local x="\the\toks100"
}
% 得到:
% local x = "\TeX "

其他展开技术#

  • \string
  • \detokenize{}, \string的多权标版本
1
2
3
4
5
6
7
8
\directlua{
    local x="I will use \string\newcommand" 
    print(x)
}
% 有转义问题:
% local x="I will use \newcommand" print(x)
% I will use
% ewcommand

Lua转义序列#

  • 标准序列:\n (newline), \r (carriage return), \\ (backslash), \" (double quote), \t (horizontal tab), \v (vertical tab) and \' (single quote);
  • \xXX, XX是十六进制数;
  • \ddd, where ddd is a sequence of up to three decimal digits;
  • \u{XXX},用十六进制数表述的UTF-8字符

长括号来救急:

  • "this is a string.\nI'll now start on a new line."
  • 长括号文本不会转义: [-[I am a long brackets\n string]-]
1
2
3
4
5
\protected\def\newtest#1{The argument: #1}
\directlua{
    tex.print([-[\newtest{Hello}]-])
}
% The argument: Hello

以页面为单位的信息收集与统计#

  1. 通过标签与实际页码的关系确定信息位置 (需要多次编译才准确,并注意核对)
  2. 通过页面引用取得页码,不能使用在文档中不准确的\thepage
  3. 通过\refcount宏包的可展开命令\getrefnumber, \getpagerefnumber取得页码等可操作的数据,而不是通过\pageref(无法展开,仅文档中呈现,而无法作为值保存、处理)
  4. 埋设标签和收集信息可以包装成一个命令 (不能双重包装)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
\documentclass[a4paper]{ctexrep}%

% 在页眉页脚中通过\thepage过滤、使用表中数据
\usepackage{fancyhdr}
\fancyfoot[C]{\thepage{}页共%
    \directlua{%
        sum = t[\thepage] or "??" % 设置默认值方便核对
        tex.print(sum) %
    }%
    题,做对\_\_\_\_%
} %
\pagestyle{fancy} % 应用页面样式
%
\usepackage{refcount}%
\setrefcountdefault{??} % 未定义标签的默认值,方便核对
% 定义表、计数器
\directlua{ t = {} }
%
% 按页的标签计数
\newcommand*{\countup}[1]{
    \directlua{
        t[#1] = t[#1] or 0;
        t[#1] = t[#1] + 1;
    }%
}
%
\begin{document}%

% 标签一般埋设在特定类别的条目,在一般文本行首、末可能会出错????
这里有标签 \label{ 1 } 标签所在页码: \getpagerefnumber{ 1 }\pageref{ 1 }%

% 收集  标签id,存入表中键值为页码的单元
\directlua{t[\getpagerefnumber{ 1 }] = t[\getpagerefnumber{ 1 }] or 0; t[\getpagerefnumber{ 1 }] = t[\getpagerefnumber{ 1 }] + 1 }%
% pylatex代码:
% doc.append(NoEscape(fr'''\directlua{{
%     t[\getpagerefnumber{{ {label_id} }}] = t[\getpagerefnumber{{ {label_id} }}] or 0;
%     t[\getpagerefnumber{{ {label_id} }}] = t[\getpagerefnumber{{ {label_id} }}] + 1;
%     }}'''))

% 或包装成命令(不能装入\getpagerefnumber形成多重包装)
% \countup{ \getpagerefnumber{ 1 } }%
% pylatex代码:
% doc.append(NoEscape(fr"\countup{{ \getpagerefnumber{{ {label_id} }} }}"))

\par%

\end{document}%

\directlua包装成\command时要避免多重嵌套。 \command\directlua\command双重包装会引起第三遍编译(pdf能观察到): 第一遍编译,因引用变动而引起第二遍编译,此时正确取数; 但是又意外地引发第三次编译,无法取数,如下:

1
2
3
4
5
6
7
8
9
\newcommand*{\countup}[1]{
    % 以下lua包装可以直接放在文档中,但不能再包装成命令
    \directlua{
        t[\getpagerefnumber{ #1 }] = t[\getpagerefnumber{ #1 }] or 0;
        t[\getpagerefnumber{ #1 }] = t[\getpagerefnumber{ #1 }] + 1;
    }%
}
% 这样使用
\countup{ 1 }%

可以这样:

1
2
3
4
5
6
7
8
\newcommand*{\countup}[1]{
    \directlua{
        t[#1] = t[#1] or 0;
        t[#1] = t[#1] + 1;
    }%
}
% 这样使用:
\countup{ \getpagerefnumber{ 1 } }%

不可能在主文件中精确引用页码计数器#

vid Carlisle:你几乎不可能在主文件中精确引用页码计数器(page counter),只有在输出进程(output routine,此时输出页面)中才能精确地知道。Luatex没有改变TeX的这种基本模式。

可以通过埋设标签的方式实现,每处用一个\label{id}关联,第二轮运行时就可以通过\pageref{id}从Lua或TeX引用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
\documentclass{article}
\directlua{j=0}
\newcommand\printluapage{%
    \directlua{
       for i = 1, 60 do
         j=j+1
         tex.print('\string\\pageref{foo' .. j .. '}  \string\\label{foo' .. j .. '}') 
         tex.print([-[\par]-])
       end
       }%
}

\begin{document}
\printluapage

New command

\printluapage

New command

\printluapage
\end{document}

标点压缩临时方案#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
\usepackage{luacode}
%%% 消除中文問號及驚嘆號後面多餘的空白。
\def\exsp#1{\unskip#1\hspace{0em}}
\begin{luacode}
function dosub(s)
    s = string.gsub(s, '!', '\\exsp!')
    s = string.gsub(s, '?', '\\exsp?')
    return(s)
end
\end{luacode}
% 对输入调用查找替换
\AtBeginDocument{%
    \luaexec{luatexbase.add_to_callback("process_input_buffer", dosub, "dosub")}%
}

速度问题#

使用本地变量而不是原始名字:

nodetraverseid = node.traverse_id

如果少一些LaTeX/宏展开,多一些lua,会快得多

by svat on Sept 1, 2017

The book A Gentle Introduction to TeX is 97 pages long (number of pages in the output). To produce the typeset output from gentle.tex, on my laptop,

  • tex takes 0.15 seconds
  • pdftex takes 0.37 seconds
  • luatex (1.0.4) takes 0.46 seconds

All of these are reasonably fast, and I think getting a full book in less than half a second is acceptable. (My browser takes longer than that to render most things.)

What's the catch? This book is written in plain TeX. The slowness comes when you use LaTeX: it's not unusual to have even single-page documents that take longer for LaTeX to generate. The whole LaTeX approach is to have a package for everything, load everything you may want, providing all the features, etc — and all of this implemented in TeX macro expansion in painful and hard-to-refactor ways.

This is something LuaTeX will help with actually: LuaTeX makes TeX less monolithic by providing hooks (callbacks), and many things can now be implemented in easier-to-read Lua code as hooks into the right parts of TeX's execution, rather than setting everything up at the start so that after macros are expanded the right thing happens. If there's less use of LaTeX and elaborate macros, things should get faster.

TeX_without_TeX_revised_and_expanded#

LuaMetaTEX(LMTX)#

本部分主要学习:LuaMetaTEX Reference Manual

引言#

参见见官网LMTX

LuaMetaTEX是更精瘦和干练的(leaner and meaner)LuaTEX,移除了大量的部件和依赖。主要为ConTeXt所用,可以说是新版的ConTeXt。目前兼容LuaTEX。

LuaMetaTeX(lmtx)引擎整合了TEX文本渲染引擎、MetaPost图形引擎和Lua脚本解释器,或可联想Lua, MetaPost, TeX, XML。LuaMetaTeX是主要为ConTeXt创建的引擎,基于并兼容LuaTeX,而有优化:

  • 除了三个核心libc和libm运行时外,没有外部依赖。
  • 没有PDF后端代码,而由ConTeXt的Lua代码生成pdf。
  • 没有字体加载器lua界面库,(OpenType)字体加载由ConTeXt的Lua代码处理。
  • 没有(poppler-based)图像和pdf文件的lua界面库(后者用epdf代替)
  • 各种扩展已经做成lua界面库

需要安装最新的ConTeXt实现CONTEXT LMTX(ConTeXt Minimals > ConTeXt Standalone > LMTX),很慢。在看到下载压缩包的时候,可以用下载工具手动下载,解压到相应的位置,再重启安装程序。重新安装即可更新到最新版。 预计TeX Live 2022中会加入LuaMetaTeX。

安装包中的设置系统路径的工具setpath.bat似乎无效,需要手动设置。

查看版本:

1
context --version && luametatex --version

编译时,context test.mkiv 默认使用LuaMetaTeX。可指定使用luatex: context --luatex test.mkiv

字体#

给前端传送字体时只是传送尺寸。

与LuaTEX不同,LuaMetaTeX没有内建后端,所以不使用虚拟字体信息。

对于Lua代码来说,所有字体都表示为表(table),内部则是C结构;包含如下键:

KEY VALUE TYPE DESCRIPTION
name string 度量 (文件) 名称
original string 日志和反馈中使用的名字
designsize number 期望的尺寸 (默认: 655360 == 10pt)
size number 要求的缩放 (默认同 designsize)
characters table 本字体定义的字模
fonts table 本地使用的字体
parameters hash 默认7个参数均为0
stretch number the ‘stretch’
shrink number the ‘shrink’
step number the ‘step’
textcontrol bitset this controls various code paths in the text engine
hyphenchar number 默认: TEX's \hyphenchar
skewchar number 默认: TEX's \skewchar
nomath boolean this key allows a minor speedup for text fonts; if it is present and true, then LuaTEX will not check the charac­ter entries for math-specific keys
oldmath boolean this key flags a font as representing an old school TEX math font and disables the OpenType code path
mathcontrol bitset this controls various code paths in the math engine, like enforcing the traditional code path
compactmath boolean experimental: use the smaller chain to locate a character
textscale number 数学文本中的缩放
scriptscale number 数学脚本中的缩放
scriptscriptscale number 数学脚本的缩放脚本

其中,parameters是哈希表,但包含7个便捷索引:slant, space, spacestretch, spaceshrink, xheight(小写x高度), quad(方铅宽度,即em), extraspace。比如,通过结点字体(n.font)这样取得最终属性:

1
fonts.hashes.identifiers[n.font].parameters.xheight

其中,characters表(字体定义的字模),整数键,字模结点的字符代码会引用这些键。包括如下键:

KEY TYPE DESCRIPTION
width number width in sp (default 0)
height number height in sp (default 0)
depth number depth in sp (default 0)
italic number italic correction in sp (default 0)
topaccent number top accent alignment place in sp (default zero)
botaccent number bottom accent alignment place, in sp (default zero)
leftprotruding number left protruding factor (\lpcode)
rightprotruding number right protruding factor (\rpcode)
expansion number expansion factor (\efcode)
next number ‘next larger’ character index
extensible table constituent parts of an extensible recipe
vvariants table constituent parts of a vertical variant set
hvariants table constituent parts of a horizontal variant set
kerns table kerning information
ligatures table ligaturing information
mathkern table math cut-in specifications
smaller number the next smaller math size character

比如字符‘f’(整数102)在字体cmr10中,10pt时(缩放后的尺寸):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[102] = {
    ["width"]    = 200250,
    ["height"] = 455111,
    ["depth"]    = 0,
    ["italic"] = 50973,
    ["kerns"] = {
        [63] = 50973,
        [93] = 50973,
        [39] = 50973,
        [33] = 50973,
        [41] = 50973
    },
    ["ligatures"] = {
        [102] = { ["char"] = 11, ["type"] = 0 },
        [108] = { ["char"] = 13, ["type"] = 0 },
        [105] = { ["char"] = 12, ["type"] = 0 }
    }
}

等宽字体没有带子和字间(ligatures and kerns),一般不处理这两个问题。

Lua中的font库#

参看LuaTEX manual

朝向与旋转(竖排、直书)#

参见开发文档followingup的第6节,Directions。

LMTX使用两个结点列表dir(direction,列表流的方向),从左到右和从右到左,而不是luatex的四个(TLT, TRT, RTT, LTL);而主要使用box的6种orientation(朝向、方向)实现特殊方向的排版。

模块开发#

参考

  • 社区文档
  • 模块参数
    • \currentmoduleparameter{color} 模块内展开
    • \moduleparameter{bgcolor}{color} 全局使用,比如在定义中

Lua in ConTeXt#

本部分主要学习如下资料的笔记:

引言#

ConTeXt Lua 的两种运行方式:

  • 镶嵌在ConTeXt文档中执行
  • 用context直接执行.cld文档

ConTeXt文档中Lua代码的主要问题是:代码先由TeX展开,再给Lua处理。这意味着,所有Lua代码,甚至分行和注释,首先必须在TeX中合法;其次,展开后要在Lua中合法!因此要注意:

  • 断行视作空格;
  • 特殊TeX字符,如&, #, $, {, }等,需要转义。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
\ctxlua
  {-- A Lua comment
   tex.print("This is not printed")}
\ctxlua
  {% A Tex comment
   tex.print("This is printed")}

% This doesn't work:
%\ctxlua
%  {local t = {1,2,3,4}
%   tex.print("length " .. #t)}
% should be:
\ctxlua
  {local t = {1,2,3,4}
   tex.print("length " .. \string#t)}

ConTeXt文档中的代码执行界面 Interfacing#

  • \startluacode...\stopluacode,模块式,如定义函数
  • \ctxlua,或\ctxluacode(更多准备),行内式,如执行函数

以上都是在LuaTeX基元\directlua之上包装的;绝不要直接使用\directlua

命名空间commands的快捷方式:

  • \ctxcommand{thisorthat("...")},等于\ctxlua{commands.thisorthat("...")}

命名空间context的快捷方式:

  • \cldcontext{myfunction(5)}直接获得函数返回值,等价于\ctxlua{context(myfunction(5))}
  • \cldprocessfile#1{\directlua{context.runfile("#1")}}
  • \cldloadfile#1 {\directlua{context.loadfile("#1")}}
  • \cldcontext#1 {\directlua{context(#1)}}
  • \cldcommand#1 {\directlua{context.#1}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
\startluacode
    -- The unknown command \undefined will cause this entire block to fail.

    -- Print a countdown '10, 8, ..., 0!'
    -- `..` is Lua for string concatenation
    for i = 10, 2, -2 do
        context(i .. ", ")
    end
    context("0!")

    -- \\par is equivalent to a blank line in the input
    -- (Notice the escaped backslash: TeX won't mind the above comment.)
    context.par()

    -- Look! we can use # and $ with impunity!
    context("Unless we print them, then we must \\#\\$\\& print the escape characters, too.")
\stopluacode

ctx函数与ctx宏/ctx定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
% ctx函数
\startctxfunction MyFunctionA
    context(" A1 ")
\stopctxfunction
\ctxfunction{MyFunctionA}

% ctx函数定义(宏)
\startctxfunctiondefinition MyFunctionB
    context(" B2 ")
\stopctxfunctiondefinition
\MyFunctionB

引入外部代码#

1
2
3
4
\startluacode
-- include the file my-lua-lib.lua
require("my-lua-lib")
\stopluacode

使用命名空间#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
\startluacode
    -- if userdata doesn't exist yet, create it
    userdata = userdata or {}
    -- define a shorter synonym
    u = userdata

    -- create my custom function inside the userdata namespace
    function u.myfunction()
        -- do stuff
    end        
\stopluacode
  • userdata = userdata or { } -- for users (e.g. functions etc)
  • documentdata = documentdata or { } -- for users (e.g. raw data)
  • moduledata = moduledata or { } -- only for development team
  • parametersets = parametersets or { } -- experimental for team
  • thirddata = thirddata or { } -- only for third party modules

用子命名空间向用户暴露自己的数据:

1
2
3
4
5
moduledata['mymodule'] = { }
mm = moduledata.mymodule
function mm.mainfunction()
    -- do stuff
end

#

用表组织在一起的功能。

Lua库:string, table, lpeg, math, io and os
LUATEX库:node, tex and texio.

输出文本#

一般使用context(...),等价于tex.print(string.format(...))

1
2
3
4
5
6
\startluacode
    name = "Jane"
    date = "today"
    context("Hello %s, how are you %s?", name, date)
    -- Becomes 'Hello Jane, how are you today?'
\stopluacode

更原始的:

  • tex.print()(多个参数,或一个表,每项一行)
  • tex.sprint()(多个参数,或一个表,所有项合成一个string)

显示输入文件及其路径。参考: https://tex.stackexchange.com/questions/132303/is-there-a-context-command-to-print-the-path-of-the-file-in-which-it-is-evoked

  • \ctxlua{context(environment.inputfilename)}
  • \ctxlua{context(lfs.currentdir())}

拼接成完整路径:

1
2
3
4
local fullname = file.join(lfs.currentdir(), environment.inputfilename)
print(fullname)
local pwd = file.dirname(fullname)
print(pwd)

ConTeXt commands#

大部分ConTeXt命令可以通过context.command的形式取得,方括号参数改用表来表示,花括号参数改用字符串。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
\startluacode
    context.chapter({first}, "Some title")
    context.startcolumns({n = 3, rule = "on"})
        context("Hello one")
    context.column()
        context("Hello two")
    context.column()
        context("Hello three")
    context.stopcolumns()
\stopluacode

等价于:

1
2
3
4
5
6
7
8
\chapter[first]{Some title}
\startcolumns[n=3, rule=on]
    Hello one
\column
    Hello two
\column
    Hello three
\stopcolumns

Calling TeX from Lua#

ConTeXt命令传递参数和缓存给Lua#

参考Programming_in_LuaTeX

先定义Lua函数:

1
2
3
4
5
6
7
8
\startluacode
    -- remember, using the userdata namespace prevents conflicts
    userdata = userdata or {}

    function userdata.surroundwithdashes(str)
        context("--" .. str .. "--")
    end
\stopluacode

然后定义同一个TeX命令来展开\ctxlua回调:

1
2
\def\surroundwd#1%
    {\ctxlua{userdata.surroundwithdashes([==[#1]==])}}

[==[#1]==]长字符串比引号更健壮。

先定义Lua函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
\startluacode
    userdata = userdata or {}

    function userdata.verynarrow(buffer)
        -- equivalent to \startnarrower[10em]
        context.startnarrower({"10em"})
            context(buffer)
        context.stopnarrower()
    end
\stopluacode

定义缓存的开始命令:

https://wiki.contextgarden.net/Buffers_in_LuaTeX

1
2
3
4
5
6
\def\startverynarrow%
  {\dostartbuffer
    [verynarrow]      % buffer name
    [startverynarrow] % command where buffer starts
    [stopverynarrow]} % command where buffer ends
                      % also: command invoked when buffer stops

定义结束命令\stopverynarrow,并当前完成的缓存传递给Lua函数verynarrow:

1
2
3
\def\stopverynarrow
  {\ctxlua
     {userdata.verynarrow(buffers.getcontent('verynarrow'))}}

用例#

计算正弦

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
\def\COSINE#1%
  {\ctxlua{context(math.cos(#1*2*math.pi/360))}}

$\pi = \ctxlua{context(math.pi)}$ 

$\pi = \ctxlua{context("\letterpercent.6f", math.pi)}$

\setupcolors[state=start]
\setupTABLE[each][each][width=2em,height=2em,align={middle,middle}]  
\setupTABLE[r][1][background=color,backgroundcolor=gray]  
\setupTABLE[c][1][background=color,backgroundcolor=gray]

循环而无需担忧展开:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
\startluacode
context.bTABLE()
  context.bTR()
    context.bTD() context("$(+)$") context.eTD()
    for j=1,6 do
      context.bTD() context(j) context.eTD()
    end
  context.eTR()
  for i=1,6 do
    context.bTR()
    context.bTD() context(i) context.eTD()
    for j=1,6 do
      context.bTD() context(i+j) context.eTD()
    end
    context.eTR()
  end
context.eTABLE()
\stopluacode

通用例子:定义ConTeXt命令,而用lua函数解析参数、实现业务逻辑并输出。(来自 Command handling, Interface between TeX and Lua, by Bruce Horrocks)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
\startluacode
-- 定义lua函数,存储在给用户分配的userdata中
userdata = userdata or { }
function userdata.mycommand(keywords, keyvals, str)
    -- 解析位置参数
    keyword_options = utilities.parsers.settings_to_array(keywords)
    -- 解析命名参数
    named_values = utilities.parsers.settings_to_hash(keyvals)

    -- 回输位置参数
    context('First option = ' .. keyword_options[1])
    context('\\par')
    -- 回输出命名参数
    context('Color chosen = ' .. named_values['color'])
    context('\\par')
    -- 回输内容参数
    context('Curly braces = ' .. str)
    context('\\par')
end
\stopluacode
1
2
3
4
5
6
7
8
% 定义命令,向lua函数传递三种类型的参数
\def\mycommand[#1][#2]#3{\ctxlua{
    userdata.mycommand('#1', '#2', [==[#3]==])}}

\starttext
% 使用命令
\mycommand[top, inmargin, now][color=green, roof=gabled]{Anne of Green Gables?}
\stoptext

字符串操作#

文本操作,文本控制,文本分行,参数解析,字符串格式化

https://wiki.contextgarden.net/String_manipulation

表格操作#

https://wiki.contextgarden.net/Table_manipulation

IO#

https://wiki.contextgarden.net/Extensions_to_the_Lua_I/O_library

在ConTeXt外部运行Lua(可使用ConTeXt环境)#

https://wiki.contextgarden.net/Running_Lua_Code_Externally

mtxrun --script dummy.lua

CLD: ConTeXt Lua Documents#

ConTeXt Lua Documents (CLD)是从Lua脚本中使用TeX的方式。可以实现纯Lua、不需要TeX代码的排版。适合自动排版。

本部分主要学习如下资料的笔记:

执行方式#

极简文件test.cld

1
2
3
context.starttext()
context.chapter("Hello There!")
context.stoptext()

cld文件的执行:context test.cld,或自定后缀而加运行选项--forcecld。其余选项:

  • --forcecld
  • --forcelua
  • --forcexml
  • --forcemp

常用函数#

cld函数与文档命令往往一一对应:

  • context.chapter("Some title") 对应\chapter{Some title}
  • context.chapter({ "first" }, "Some title") 对应\chapter[first]{Some title}
  • context.startchapter({ title = "Some title", label = "first" }) 对应\startchapter[title={Some title},label=first]
  • context.pagenumber("pagenumber")

文本被看作tex输入:

  • context.mathematics([-[\sqrt{2^3}]-])

分行、分段:

1
2
3
4
5
6
7
8
context.startlines()
context("line 1") context(true)
context("line 2") context(true)
context("line 3") context(true)
context("line 4\n")
context.stoplines()
-- 分段
context.par()

插入pdf的一部分:

1
2
3
4
5
\startluacode
for s=5, 17 do
    context.externalfigure({"filename.pdf"},{page=s})
end
\stopluacode

所有命令:

https://source.contextgarden.net/tex/context/base/mkiv/cldf-ini.lua

传递函数#

TeX中定义的命令\mycommand{Myvariable},可以在Lua中命名空间context下直接使用context.mycommand("Myvariable")

CLD双向传递变量#

参考: https://wiki.contextgarden.net/CLD_passing_variables#Variables

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
\startTEXpage[offset=5mm]

\setvariable{Namespace}{Key}{30}

\startluacode
local value = tokens.getters.macro(tokens.getters.macro("??variables") .. "Namespace:Key")
value = 3*value
context("Type: " .. type(value))
context.par()
context("Result is " .. value)
\stopluacode

\stopTEXpage

Result is \getvariable{Namespace}{Key}.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
\startTEXpage[offset=5mm]

\startluacode
local value = 30
value = 3 * value
context("Type: " .. type(value))
context.par()
context.setvariable("Namespace", "Key", value)
\stopluacode

Result is \getvariable{Namespace}{Key}.

\stopTEXpage

多过/多遍处理所需数据的存取参考\setdataset

直接输出#

仅在特殊情况下使用。

仅仅是(给环境)传递参数,不做操作:context.direct("one","two"),等同于context(false,"one","two")(无可选参数,而只有输入内容)

特种码 Catcodes#

输出文本$x=1$而不是数学公式:

1
2
3
context.pushcatcodes("text")
context("$x=1$")
context.popcatcodes()

catcode regimes:

  • ctx, ctxcatcodes, context: the normal CONTEXT catcode regime
  • prt, prtcatcodes, protect: the CONTEXT protected regime, used for modules
  • tex, texcatcodes, plain: the traditional (plain) TEX regime
  • txt, txtcatcodes, text: the CONTEXT regime but with less special characters
  • vrb, vrbcatcodes, verbatim: a regime specially meant for verbatim
  • xml, xmlcatcodes: a regime specially meant for XML processing

以函数而不是语句作为参数#

context.framed( { frame = "on", offset = "5mm", align = "middle" }, -- 不用context.input("knuth"),而用: function() context.input("knuth") end )

试排 Trial typesetting#

试排状态(Trial typesetting,比如处理天头、地脚、表格单元,寻找最佳答案),退出时会清空缓存。但可用return true转存:

1
2
3
4
function()
    context("whatever")
    return true
end

只要碰到结果不满意的情况,即可return true。但这样可能导致缓存过大,这时可以强制清空:context.restart()

判断试排状态:

b = context.trialtypesetting()

Steppers 从Lua临时进入TeX#

未中断Lua

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
context.stepwise (function()
    context.startitemize()
        context.startitem()
            context.step("BEFORE 1")
        context.stopitem()
            context.step("\\setbox0\\hbox{!!!!}")
        context.startitem()
            context.step("%p",tex.getbox(0).width)
        context.stopitem()
        -- ...
    context.stopitemize()
end)

Modes 模式匹配#

processing time 测试:

1
2
3
4
5
6
7
8
context.doifmodeelse( "screen",
    function()
        ... -- mode == screen
    end,
    function()
        ... -- mode ~= screen
    end
)

每个run只测试一次:

1
2
3
4
5
if tex.modes["screen"] then
...
else
...
end

常用模式:

  • tex.modes["mymode"]
  • tex.modes["*mymode"]
  • tex.systemmodes["mymode"]
  • tex.constants["someconstant'] 非公开,可能改变

Token列表#

TeX:\toks0 = {Don't get \inframed{framed}!}

lua:context(tex.toks[0])

实际上是以文本形式保存的:context("< %s >",tex.toks[0])

但却不能反过来,存入文本形式的内容:tex.toks[0] = [-[\framed{oeps}]-],这样这能得到文本

Node列表#

TeX:\setbox0 = \hbox{Don't get \inframed{framed}!},然后用\box0输出,或用\copy0持有拷贝。

lua端则:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-- 引用、拷贝(结点必须free)
context.copy()
context.direct(0)
--或
context.copy(false,0)
--或
context(node.copy_list(tex.box[0]))
-- 输出
context(tex.box[0])
context(tex.box[0].width)

遍历node和任务挂载:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
\startluacode
local hlist_code = nodes.nodecodes.hlist
local glyphs = nodes.nodecodes.glyph
function userdata.processmystuff(head)
    -- 遍历结点
    for n in node.traverse(head) do
        print(n.subtype)
    end
    -- 遍历glyphs结点id
    for n in node.traverse_id(glyphs,head) do
        print(n)
        --% nodes.tracers.colors.set(, "red")
   end
   return head, true
end

--把`userdata.processmystuff`函数挂载到processors回调的normalizers类别中。
nodes.tasks.appendaction("processors", "after", "userdata.processmystuff")
--停用
nodes.tasks.disableaction("processors", "userdata.processmystuff")
\stopluacode

用例:

\startluacode
--启用
nodes.tasks.enableaction("processors", "userdata.processmystuff")
\stopluacode

我和你。

\startluacode
--停用
nodes.tasks.disableaction("processors", "userdata.processmystuff")
\stopluacode

回调函数#

本节内容见LuaTeX回调游乐场

页面布局与框架布局#

设置版面布局,随机生成图形:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
\startluacode
--数字转尺寸
local todimen, random = number.todimen, math.random

-- 开始一个页面
context.startTEXpage()

--纸张尺寸
local paperwidth = tex.dimen.paperwidth
local paperheight = tex.dimen.paperheight
local nofsteps = 25
local firstcolor = "darkblue"
local secondcolor = "white"

--定义和设置整体框架布局
context.definelayer({ "titlepage" })
context.setuplayer(
    { "titlepage" },
    {
        width = todimen(paperwidth),
        height = todimen(paperheight),
})
context.setlayerframed(
    { "titlepage" },
    { offset = "-5pt" },
    {
        width = todimen(paperwidth),
        height = todimen(paperheight),
        background = "color",
        backgroundcolor = firstcolor,
        backgroundoffset = "10pt",
        frame = "off",
    },
    ""
)
--定义和设置小图框架布局
local settings = {
    frame = "off",
    background = "color",
    backgroundcolor = secondcolor,
    foregroundcolor = firstcolor,
    foregroundstyle = "type",
}
for i=1, nofsteps do
    for j=1, nofsteps do
        context.setlayerframed(
            { "titlepage" },
            {
                x = todimen((i-1) * paperwidth /nofsteps),
                y = todimen((j-1) * paperheight/nofsteps),
                --旋转角度
                rotation = random(360),
            },
            settings,
            "CLD"
        )
    end
end
--???
context.tightlayer({ "titlepage" })
context.stopTEXpage()
return true
\stopluacode

列表#

1
2
3
4
5
6
7
context.startitemize { "a", "packed", "two" }
for i=1,10 do
    context.startitem()
    context("this is item %i",i)
    context.stopitem()
end
context.stopitemize()

表格#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
local one = {
    align = "middle",
    style = "type",
}
local two = {
    align = "middle",
    style = "type",
    background = "color",
    backgroundcolor = "darkblue",
    foregroundcolor = "white",
}
local random = math.random
-- 表格开始
context.bTABLE { framecolor = "darkblue" }
for i=1,10 do
    -- 表行开始
    context.bTR()
    for i=1,20 do
        local r = random(99)
        -- 单元格开始
        context.bTD(r < 50 and one or two)
        context("%2i",r)
        context.eTD()
    end
    context.eTR()
end
context.eTABLE()

放置图片#

1
2
3
4
5
context.placefigure(
    "caption",
    --这里也可以生成表格,或表格中嵌入图像
    function() context.externalfigure( { "cow.pdf" } ) end
)

风格#

  • 粗体 context.bold("important")
  • 粗体加颜色 context.style( { style = "bold", color = "red" }, "important")
    • context.bold(function() context.color( { "red" }, "important") end)
    • context.bold(context.delayed.color( { "red" }, "important"))

包装成命令:

1
2
3
4
local function mycommands.important(str)
    context.style( { style = "bold", color = "red" }, str )
end
mycommands.important( "important")

或设置、定义一种风格:

1
2
3
4
5
6
7
8
context.setupstyle( { "important" }, { style = "bold", color = "red" } )
context.style( { "important" }, "important")

context.definestyle( { "important" }, { style = "bold", color = "red" } )
context.important("important")
context.startimportant()
context.inframed("important")
context.stopimportant()

ForLorien 打印数学题的完整例子#

Interfacing TeX界面和Unicode的处理#

Formatters#

Graphics#

#

可能有用的界面定义,效率不是最高。一般用TeX定义宏。参见token库

TEX命令: \setupsomething[key=value] 等价于LUA调用: context.setupsomething { key = value }

但Lua中的关键字需要先翻译成界面预设的相应的值,比如yes要转换成interfaces.variables.yes

TeX的宏定义和使用:

  • \def\test#1#2{.. #1 .. #2 .. } \test{a}{b}
  • \def\test[#1]#2{.. #1 .. #2 .. } \test[a]{b}
  • \def\test(#1><#2){.. #1 .. #2 .. } \test(a><b) (定界符号可以灵活处理)

In MkIV \define is like \unexpanded\def in TEX and \defineexpandable is like \def.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
\startluacode
function test(opt_1, opt_2, arg_1)
    context.startnarrower()
    context("options 1: %s",interfaces.tolist(opt_1))
    context.par()
    context("options 2: %s",interfaces.tolist(opt_2))
    context.par()
    context("argument 1: %s",arg_1)
    context.stopnarrower()
end

interfaces.definecommand {
    name = "test",
    arguments = {
        { "option", "list" },
        { "option", "hash" },
        { "content", "string" },
    },
    macro = test,
}
\stopluacode

使用test: \test[1][a=3]{whatever}

在lua中操控宏:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
\def\TestA{A}
\def\TestB{\def\TestC{c}}
\def\TestC{C}

\startluacode
    context(tokens.getters.macro("TestA"))
    context(tokens.getters.macro("TestB"))
    context(tokens.getters.macro("TestC"))
    tokens.setters.macro("TestA","a")
    context(tokens.getters.macro("TestA"))
    context(function()
        context(tokens.getters.macro("TestA"))
        context(tokens.getters.macro("TestB"))
        context(tokens.getters.macro("TestC"))
    end)
\stopluacode

ACaac

\startluacode
if tokens.getters.macro("fontstyle") == "rm" then
    context("serif")
else
    context("unknown")
end
\stopluacode

serif

定义环境:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
\startluacode
local function startmore(opt_1)
    context.startnarrower()
    context("start more, options: %s",interfaces.tolist(opt_1))
    context.startnarrower()
end
local function stopmore()
    context.stopnarrower()
    context("stop more")
    context.stopnarrower()
end

interfaces.definecommand ( "more", {
    environment = true,
    arguments = {
        { "option", "list" },
    },
    starter = startmore,
    stopper = stopmore,
} )
\stopluacode

使用more: \startmore[1] one \startmore[2] two \stopmore one \stopmore

Verbatim 字符字面值#

获得公式:

  • $e=mc^2$
  • \mathematics{e=mc^2}
  • context.mathematics("e=mc^2")

获得字面值:

  • context.verbatim("$e=mc^2$")
  • context.verbatim.bold("$e=mc^2$")
  • context.verbatim.inframed({ offset = "0pt" }, "$e=mc^2$")

context.verbatimtype宏更方便、快、准确。

利用缓存处理多行文本:

  • context.tobuffer("temp",str)
  • context.getbuffer("temp")

利用虚拟文件处理多行文本:

  • context.tolines(str)
  • context.viafile(str)

使用lpeg和visualizers完美打印:

  • spaces should honoured and can be visualized
  • newlines and empty lins need to be honoured as well
  • optionally lines have to be numbered but
  • wrapped around lines should not be numbered

Logging#

关闭日志:

  • context --silent
  • context --silent=structure,resolve,font*

禁用控制台:

  • context --noconsole

在CONTEXT使用指令:

  • \enabledirectives[logs.blocked=structure,resolve,font*]
  • \enabledirectives[logs.target=file]

Lua界面:

  • local report_whatever = logs.reporter("modules","whatever")
  • report_whatever("not found: %s","this or that")

TeX中的信息同步,还需要配置logs.messenger

Lua 函数#

table#

LuaTeX的函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- 表项拼接为文本
local str = table.concat(t)
local str = table.concat(t,separator)
local str = table.concat(t,separator,first)
local str = table.concat(t,separator,first,last)
table.concat({"a","b","c","d","e"},"+")
-- a+b+c+d+e
table.concat({"a","b","c","d","e"},"+",2,3)
-- b+c

-- 插入和移除
table.insert(t,value)
table.insert(t,value,position)
value = table.remove(t,position)

-- 解包
local a, b, c = table.unpack(t)

-- 排序
table.sort(t)
table.sort(t,comparefunction)
t={"a","b","c"}
table.sort(t,function(x,y) return x > y end)
-- t={ "c", "b", "a" }

内置函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
-- 排序
local a = table.sorted(b)

-- 取键值
local s = table.keys (t)
local s = table.sortedkeys (t)
local s = table.sortedhashkeys (t)

-- 追踪/打印/查看表格内容
inspect(t)

-- 迭代器(与sortedpairs同义,以便与pairs、ipairs相对) 
for key, value in table.sortedhash(t) do
    print(key,value)
end

table.serialize (root, name, reduce, noquotes, hexify)
table.print (root, name, reduce, noquotes, hexify)
table.tofile (filename, root, name, reduce, noquotes, hexify)
table.tohandle (handle, root, name, reduce, noquotes, hexify)
table.serialize({ a = 2, [3] = "b", [true] = "6" }, nil, true)
-- t={[3]="b",["a"]=2,[true]="6",}

-- 比较
local b = table.identical (one, two)
local b = table.are_equal (one, two)

-- 哈希
local hashed = table.tohash(indexed)
local indexed = table.fromhash(hashed)
table.tohash({ "a", "b", "c" })
-- t={["a"]=true,["b"]=true,["c"]=true,}
table.fromhash({ a = true, b = false, c = true })
-- t={ "c", "a" }

-- 
local swapped = table.swapped(indexedtable)
table.swapped({ "a", "b", "c" })
-- t={["a"]=1,["b"]=2,["c"]=3,}

-- 正反
local reversed = table.reversed (indexedtable)
local reverse = table.reverse(indexedtable)

-- 镜像
local mirrored = table.mirrored (hashedtable)
table.mirrored({ a = "x", b = "y", c = "z" })
-- t={["a"]="x",["b"]="y",["c"]="z",["x"]="a",["y"]="b",["z"]="c",}

-- 增添
table.append (one, two)
table.prepend(one, two)

-- 原位合并
table.merge(one, two, ...)
table.imerge(one, two, ...)
-- 拷贝合并
local merged = table.merged(one, two, ...)
local merged = table.imerged (one, two, ...)

-- 拷贝
local copy = table.copy(t)
local copy = table.fastcopy(t) --不检查循环引用

-- 展平(一层)嵌套,用merge也可以做到
local flattened = table.flatten(t)

-- 键值小写
local normalized = table.loweredkeys { a = "a", A = "b", b = "c" }

-- 包含判断
if table.contains(t, 5 ) then ... else ... end
if table.contains(t,"5") then ... else ... end

-- 去重、唯一化
local t = table.unique { 1, 2, 3, 4, 3, 2, 5, 6 }

-- 计算长度、元素个数(索引表用#t更快)
local n = table.count(t)

-- 序列化(比如供打印)
print(table.sequenced(t, separator))

math#

除了内置函数,还提供:round, odd, even, div, mod, sind, cosd and tand

boolean#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-- 转0、1
local state = boolean.tonumber(str)

-- 文本转布尔(全局也可以用,或string.toboolean,如果tolerant=true,字符串true, yes, on, 1, t和数字1都转为布尔true)
local b = toboolean(str)
local b = toboolean(str,tolerant)

-- 判断是布尔,或string.is_boolean
if is_boolean(str)then ... end
if is_boolean(str,default) then ... end

string 字符串处理#

Lua:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
string.byte("luatex") --108
string.char(65,66,67) --ABC  utf8.char(n.char)

-- 切片
local slice = string.sub(str,first,last)

-- 替换
local new, count = string.gsub(old,pattern,replacement,howoften)
string.gsub("abcdef","[bc]",string.upper) --aBCdef

-- 查找
local first, last = find(str,pattern)
if find("luatex","tex") then ... end

-- 逐一匹配
local a, b, c, ... = string.match(str,pattern)
for s in string.gmatch(str,"([^,%s])+") do
    print(s)
end

-- 大写、小写
string.lower("LOW")
string.upper("upper")

-- 模板格式化
local s = format(format, str, ...)

CONTEXT:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
-- 行修剪
utilities.strings.striplines(str,"collapse")
utilities.strings.striplines(str,"prune and collapse")
utilities.strings.striplines(str,"prune and no empty")
utilities.strings.striplines(str,"prune and to space")
utilities.strings.striplines(str,"retain")

-- 强大的格式化formatter(略)

-- 修剪头尾
local s = string.strip(str)

-- 分切
local t = string.split(str, pattern)
local t = string.split(str, lpeg) --多个分切符号`lpeg.S(",.")`
local t = string.checkedsplit (str, lpeg)
local t = string.splitlines(str) --分行

-- 引号
local q = string.quoted(str)
local u = string.unquoted(str)

-- 计数
local n = count(str,pattern)
string.count("test me", "e") -- 2

-- 限制长度
print(limit(str,max,sentinel)
string.limit("too long", 6) --too...

-- 判断为空
if is_empty(str) then ... end

-- 转义
local e = escapedpattern(str, simple)
local p = topattern(str, lowercase, strict)

UTF#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
local b = utf.byte("å")
local c = utf.char(0xE5)

-- 切片
utf.sub("123456àáâãäå",1,7)

-- 长度
local l = utf.len("ÀÁÂÃÄÅàáâãäå")

-- 迭代
for u in utf.values(str) do
    ... -- u is a number
end
for c in utf.characters(str) do
    ... -- c is a string
end

-- 转码
utf.ustring("ù")
-- U+000F9
utf.xstring(0xE6)
-- 0x000E6
utf.xstring("à")
-- 0x000E0
utf.tocodes("òóö","+")
-- 0x00F2+0x00F3+0x00F6

-- 切分成表
utf.split utf.splitlines utf.totable

-- 计数
utf.count("äáàa",lpeg.P("á") + lpeg.P("à")) --2

-- 映射器、替换器remapper replacer substituter
local remap = utf.remapper { a = 'd', b = "c", c = "b", d = "a" }
print(remap("abcd 1234 abcd"))

-- 判断值有效(主要是外部输入)
if utf.is_valid(data) do ... end

number#

1
2
3
4
5
6
-- 转比特
number.tobitstring(2013)

-- 检查数字有效
number.valid(12)
number.valid("ab",56)

LPEG patterns 格式#

类似于正则表达式,细节见l-lpeg.lua

1
2
3
local splitter = lpeg.splitat("*")
local n, m = lpeg.match(splitter,"2*4")
local n, m = lpeg.match(splitter,"2*4")

IO#

1
2
3
4
5
6
7
8
9
local f = io.open(filename)
f:read(...)
f:write(...)
f:lines()
f:close()

io.loaddata(filename,textmode)
io.savedata(filename,str)
io.savedata(filename,tab,joiner)

File、Dir、URL、OS#

1
2
3
os.time()
os.gettimeofday()
os.runtime()

LUA 界面代码 interface code#

字体#

Nodes#

引擎提供的所有node助手都映射在nodes命名空间下。node详情参见 LUATEX manual

由于历史原因,nodes下的一些助手与node下的近似。

结点和结点列表追踪:

1
\setbox0\hbox{我在这里。It's in \hbox{\bf all} those nodes.}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
\startluacode
--基本信息:类型(node)、编号、前后结点编号、类型id、??
print(n)
--<node :   1605 <=   1612 =>   1667 : glyph unset>

--结点转utf(仅限能转的如字模结点)
print(nodes.toutf(tex.box[0]))
print(nodes.toutf(tex.box[0].list))
--我你他!

--结点列表转utf(非字模用`[-]`表示)
print(nodes.listtoutf(tex.box[0])) 
print(nodes.listtoutf(tex.box[0].list))
--[-][-]我[-]你[-]他[-][-]![-]

--结点序列化(结点类型、字模的utf代码和字符)
print(nodes.tosequence(tex.box[0]))
print(nodes.tosequence(tex.box[0].list))

--结点id转文本(同id合并)
print(nodes.idstostring(tex.box[0]))
print(nodes.idstostring(tex.box[0].list))

--结点计数
print(nodes.countall(tex.box[0]))
print(nodes.countall(tex.box[0].list))

--查看结点类型、小类和id
print(node.type(n.id))
print(node.subtypes(n.id)[n.subtype])
print(node.id("vlist"))

查看字模结点n的字符:

local c = utf8.char(n.char)

跟踪结点明细:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
local function show_detail(n, label) 
    print(">>>>>>>>>"..label.."<<<<<<<<<<")
    print(nodes.toutf(n))
    for i in node.traverse(n) do
        local char
        if i.id == glyph_id then
            char = utf8.char(i.char)
            print(i, char)
        elseif i.id == penalty_id then
            print(i, i.penalty)
        elseif i.id == glue_id then
            print(i, i.width, i.stretch, i.shrink)
        elseif i.id == hlist_id then
            print(i, nodes.toutf(i.list))
        else
            print(i)
        end
    end
end

结点类型的id与字符串映射表,大类:

  • nodes.nodecodes regular nodes, some fo them are sort of private to the engine. underscores:
    • nodes.nodecodes.glyph
    • nodes.nodecodes.kern
    • nodes.nodecodes.glue
  • nodes.noadcodes math nodes that later on are converted into regular nodes

小类:

  • nodes.listcodes subtypes of hlist and vlist nodes
  • nodes.kerncodes subtypes of kern nodes
  • nodes.gluecodes subtypes of glue nodes (skips)
  • nodes.glyphcodes subtypes of glyph nodes, the subtype can change
  • nodes.mathcodes math specific subtypes
  • nodes.fillcodes these are not really subtypes but indicate the strength of the filler
  • nodes.whatsitcodes subtypes of a rather large group of extension nodes

遍历并判断结点类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
-- 本地变量要比原变量快得多
local glyph_code = nodes.nodecodes.glyph
local kern_code = nodes.nodecodes.kern
local glue_code = nodes.nodecodes.glue
--for n in node.traverse_id(glyph_code,list) do
for n in nodes.traverse(list) do
    local id == n.id
    if id == glyph_code then
        ...
    elseif id == kern_code then
        ...
    elseif id == glue_code then
        ...
    else
        ...
    end
end

新建结点、结点池操控函数:

  • node.new
  • local g = nodes.pool.glyph(fnt,chr) --根据字体id取字符

Resolvers、math、grph、MetaPost、LuaTEX、Tracing#

Scanners#

定义直接与Lua交互的宏。

实现一个界面:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
\startluacode
interfaces.implement {
    name    = "MyMacroA",
    public    = true,
    permanent = false,
    arguments = "string",
    actions    = function(s)
        context("(%s)",s)
    end,
}
\stopluacode
-- \def\MyMacro #1 {... \clf_MyMacroA{ #1 } ...}

\startluacode
interfaces.implement {
    name    = "MyMacroJ",
    public    = true,
    arguments = "box",
    actions    = function(b)
        context(b)
    end,
}
\stopluacode
-- \MyMacroJ \hbox{\strut Test 1}
-- \MyMacroJ \hbox to 4cm {\strut Test 2}

\startluacode
    interfaces.implement {
    name    = "MyMacroL",
    public    = true,
    arguments = {
        "hbox",
        "vbox",
    },
    actions    = function(h,v)
        context(h)
        context(v)
    end,
}
\stopluacode
-- \MyMacroL{\strut Test 1h} to 10mm {\vfill Test 1v\vfill}
-- \MyMacroL spread 1cm {\strut Test 2h} to 15mm {\vfill Test 2v\vfill}

控制台print中文乱码的问题#

临时改变code page为utf8chcp 65001

样例#

hyphenation#

导出luatex的断词信息。

This will write to hyphens-\jobname.txt a dump of all the characters that luatex has been asked to add hyphenation points to from this point on in the source, with the output of each callback call on a single line.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{\catcode`\#=12
\directlua {
local words = io.open('hyphens-' .. tex.jobname .. '.txt', 'w');
local outchar = unicode.utf8.char
local function dumphyphens (head)
   local data = {}
   for v in node.traverse(head) do
       if v.id == node.id('glyph') then
         data[#data+1] = outchar(v.char);
       elseif v.id == node.id('disc') then
          data[#data+1] = '-'
       elseif v.id == node.id('glue') then
         data[#data+1] = outchar(32)
       elseif v.id == node.id('hlist') then
         data[#data+1] = dumphyphens(v.list)
       end
   end
   return table.concat(data)
end
callback.register ('hyphenate', function (head,tail)
   lang.hyphenate(head, tail) 
   words:write (dumphyphens(head) .. outchar(10))
   end)
}}

\input knuth
\bye

答朋友问#

【问】

原生的 tex 是 web 和 pascal 语言写的,latex 进行了扩展,那么问题来了,latex 是扩展的宏集,那么问题来了,有没有完全用新的语言重新实现的 tex 和 latex 呢

【答】

我以前也想过luatex是用什么写的这个问题,但没查证。刚才查了查,它目前是C写的(早期用过一点pascal)。

LaTeX只是宏包,也就是说,是对tex接口的进一步包装。plain tex、ConTeXt也是宏包。

而tex、xetex、pdftex、LaTeX是tex引擎(即都支持tex原语和语法)。其中xetex、pdftex都是在tex上扩展的,不是完全重写。

LuaTeX因为绑定了lua,所以在LuaTeX基础上使用LaTeX、plain tex、ConTeXt可以使用lua,ConTeXt本身包含很多lua代码。

非tex系的Typst等,则主要是重新实现tex效果(尤其是断行组段算法),而不支持tex原语和语法。

参考:

评论