mk-dirtytricks.tex /size: 3551 b    last modification: 2023-12-21 09:43
1% language=us
2
3\startcomponent mk-dirtytricks
4
5\environment mk-environment
6
7\chapter {Dirty tricks}
8
9If you ever laid your hands on the \TeX book, the words \quote {dirty tricks} will
10forever be associated with an appendix of that book. There is no doubt that you need
11to know a bit of the internals of \TEX\ in order to master this kind of trickyness.
12
13In this chaper I will show a few dirty \LUATEX\ tricks. It also gives an impression
14of what kind of discussions Taco and I had when discussing what kind of support should
15be build in the interface.
16
17\subject{afterlua}
18
19When we look at \LUA\ from the \TEX\ end, we can do things like:
20
21\startbuffer
22\def\test#1{%
23    \setbox0=\hbox{\directlua0{tex.sprint(math.pi*#1)}}%
24    pi: \the\wd0\space\the\ht0\space\the\dp0\par
25}
26\stopbuffer
27
28\typebuffer \blank \getbuffer \blank
29
30But what if we are at the \LUA\ end and want to let \TEX\ handle things?  Imagine
31the following call:
32
33\startbuffer
34\setbox0\hbox{} \dimen0=0pt \ctxlua {
35    tex.sprint("\string\\setbox0=\string\\hbox{123}")
36    tex.sprint("\string\\the\string\\wd0")
37}
38\stopbuffer
39
40\typebuffer
41
42This gives: \ignorespaces \getbuffer. This may give you the impression that \TEX\
43kicks in immediately, but the following example demonstrates otherwise:
44
45\startbuffer
46\setbox0\hbox{} \dimen0=0pt \ctxlua {
47    tex.sprint("\string\\setbox0=\string\\hbox{123}")
48    tex.dimen[0] = tex.box[0].width
49    tex.sprint("\string\\the\string\\dimen0")
50}
51\stopbuffer
52
53\typebuffer
54
55This gives: \getbuffer. When still in \LUA, we never get to see the width
56of the box.
57
58A way out of this is the following rather straightforward approach:
59
60\starttyping
61function test(n)
62    function follow_up()
63        tex.sprint(tex.box[0].width)
64    end
65    tex.sprint("\\setbox0=\\hbox{123}\\directlua 0 {follow_up()}")
66end
67\stoptyping
68
69We can provide a more convenient solution for this:
70
71\starttyping
72after_lua = { } -- could also be done with closures
73
74function the_afterlua(...)
75    for _, fun in ipairs(after_lua) do
76        fun(...)
77    end
78    after_lua = { }
79end
80
81function afterlua(f)
82    after_lua[#after_lua+1] = f
83end
84
85function theafterlua(...)
86    tex.sprint("\\directlua 0 {the_afterlua("
87        .. table.concat({...},',') .. ")}")
88end
89\stoptyping
90
91If you look closely, you will see that we can (optionally) pass arguments
92to the function \type {theafterlua}. Usage now becomes:
93
94\starttyping
95function test(n)
96    afterlua(function(...)
97        tex.sprint(string.format("pi: %s %s %s\\par",...     ))
98    end)
99    afterlua(function(wd,ht,dp)
100        tex.sprint(string.format("ip: %s %s %s\\par",dp,ht,wd))
101    end)
102    tex.sprint(string.format("\\setbox0=\\hbox{%s}",math.pi*n))
103    local box_0 = tex.box[0]
104    theafterlua(box_0.width,box_0.height,box_0.depth)
105end
106\stoptyping
107
108The last call may confuse you but since it does a print to \TEX, it is
109in fact a delayed action. A cleaner implementation is the following:
110
111\starttyping
112local delayed = { }
113
114local function flushdelayed(...)
115    delayed = { }
116    for i=1, #t do
117        t[i](...)
118    end
119end
120
121function lua.delay(f)
122    delayed[#delayed+1] = f
123end
124
125function lua.flush(...)
126    tex.sprint("\\directlua{flushdelayed(" ..
127        table.concat({...},',') .. ")}")
128end
129\stoptyping
130
131Usage is similar:
132
133\starttyping
134function test(n)
135    lua.delay(function(...)
136        tex.sprint(string.format("pi: %s %s %s\\par",...))
137    end)
138    tex.sprint(string.format("\\setbox0=\\hbox{%s}",math.pi*n))
139    local box_0 = tex.box[0]
140    lua.flush(box_0.width,box_0.height,box_0.depth)
141end
142\stoptyping
143
144\stopcomponent
145