typo-bld.lua /size: 12 Kb    last modification: 2021-10-28 13:50
1if modules then modules = { } end modules ['typo-bld'] = { -- was node-par
2    version   = 1.001,
3    comment   = "companion to typo-bld.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- no need for nuts in the one-line demo (that might move anyway)
10
11local insert, remove = table.insert, table.remove
12
13builders                 = builders or { }
14local builders           = builders
15
16builders.paragraphs      = builders.paragraphs or { }
17local parbuilders        = builders.paragraphs
18
19parbuilders.constructors = parbuilders.constructors or { }
20local       constructors = parbuilders.constructors
21
22constructors.names       = constructors.names or { }
23local names              = constructors.names
24
25constructors.numbers     = constructors.numbers or { }
26local numbers            = constructors.numbers
27
28constructors.methods     = constructors.methods or { }
29local methods            = constructors.methods
30
31local a_parbuilder       = attributes.numbers['parbuilder'] or 999 -- why 999
32constructors.attribute   = a_parbuilder
33
34local unsetvalue         = attributes.unsetvalue
35local texsetattribute    = tex.setattribute
36local texnest            = tex.nest
37local texlists           = tex.lists
38
39local texget             = tex.get
40local texset             = tex.set
41
42local texgetdimen        = tex.getdimen
43
44local nodes              = nodes
45local nodeidstostring    = nodes.idstostring
46local nodepool           = nodes.pool
47local new_baselineskip   = nodepool.baselineskip
48local new_lineskip       = nodepool.lineskip
49local insertnodebefore   = nodes.insertbefore
50local hpack_node         = nodes.hpack
51
52local nuts               = nodes.nuts
53local tonode             = nodes.tonode
54local tonut              = nodes.tonut
55local count_nodes        = nuts.countall
56local getattr            = nuts.getattr
57
58local starttiming        = statistics.starttiming
59local stoptiming         = statistics.stoptiming
60
61local registercallback   = callbacks.register
62
63storage.register("builders/paragraphs/constructors/names",   names,   "builders.paragraphs.constructors.names")
64storage.register("builders/paragraphs/constructors/numbers", numbers, "builders.paragraphs.constructors.numbers")
65
66local trace_page_builder = false  trackers.register("builders.page", function(v) trace_page_builder = v end)
67local trace_vbox_builder = false  trackers.register("builders.vbox", function(v) trace_vbox_builder = v end)
68local trace_post_builder = false  trackers.register("builders.post", function(v) trace_post_builder = v end)
69
70local report_page_builder = logs.reporter("builders","page")
71local report_vbox_builder = logs.reporter("builders","vbox")
72local report_par_builder  = logs.reporter("builders","par")
73
74local mainconstructor = nil -- not stored in format
75local nofconstructors = 0
76local stack           = { }
77
78function constructors.define(name)
79    nofconstructors = nofconstructors + 1
80    names[nofconstructors] = name
81    numbers[name] = nofconstructors
82end
83
84function constructors.set(name) --- will go
85    if name then
86        mainconstructor = numbers[name] or unsetvalue
87    else
88        mainconstructor = stack[#stack] or unsetvalue
89    end
90    texsetattribute(a_parbuilder,mainconstructor)
91    if mainconstructor ~= unsetvalue then
92        constructors.enable()
93    end
94end
95
96function constructors.start(name)
97    local number = numbers[name]
98    insert(stack,number)
99    mainconstructor = number or unsetvalue
100    texsetattribute(a_parbuilder,mainconstructor)
101    if mainconstructor ~= unsetvalue then
102        constructors.enable()
103    end
104 -- report_par_builder("start %a",name)
105end
106
107function constructors.stop()
108    remove(stack)
109    mainconstructor = stack[#stack] or unsetvalue
110    texsetattribute(a_parbuilder,mainconstructor)
111    if mainconstructor == unsetvalue then
112        constructors.disable()
113    end
114 -- report_par_builder("stop")
115end
116
117-- return values:
118--
119-- true  : tex will break itself
120-- false : idem but dangerous
121-- head  : list of valid vmode nodes with last being hlist
122
123function constructors.handler(head,followed_by_display)
124    if type(head) == "boolean" then
125        return head
126    else
127        local attribute = getattr(head,a_parbuilder) -- or mainconstructor
128        if attribute then
129            local method = names[attribute]
130            if method then
131                local handler = methods[method]
132                if handler then
133                    return handler(head,followed_by_display)
134                else
135                    report_par_builder("contructor method %a is not defined",tostring(method))
136                    return true -- let tex break
137                end
138            end
139        end
140        return true -- let tex break
141    end
142end
143
144-- just for testing
145
146function constructors.methods.default(head,followed_by_display)
147    return true -- let tex break
148end
149
150-- also for testing (now also surrounding spacing done)
151
152function parbuilders.constructors.methods.oneline(head,followed_by_display)
153    -- when needed we will turn this into a helper
154    local t = texnest[texnest.ptr]
155    local h = hpack_node(head)
156    local d = texget("baselineskip",false) - t.prevdepth - h.height
157    t.prevdepth = h.depth
158    t.prevgraf  = 1
159    if d < texget("lineskiplimit") then
160        return insertnodebefore(h,h,new_lineskip(texget("lineskip",false))) -- no stretch etc
161    else
162        return insertnodebefore(h,h,new_baselineskip(d))
163    end
164end
165
166-- It makes no sense to have a sequence here as we already have
167-- pre and post hooks and only one parbuilder makes sense, so no:
168--
169-- local actions = nodes.tasks.actions("parbuilders")
170--
171-- yet ... maybe some day.
172
173local actions = constructors.handler
174local enabled = false
175
176local function processor(head,followed_by_display)
177    -- todo: not again in otr so we need to flag
178    if enabled then
179        starttiming(parbuilders)
180        head = tonut(head)
181        head = actions(head,followed_by_display)
182        head = tonode(head)
183        stoptiming(parbuilders)
184        return head
185    else
186        return true -- let tex do the work
187    end
188end
189
190function constructors.enable () enabled = true  end
191function constructors.disable() enabled = false end
192
193registercallback('linebreak_filter', processor, "breaking paragraps into lines")
194
195statistics.register("linebreak processing time", function()
196    return statistics.elapsedseconds(parbuilders)
197end)
198
199-- todo: move from nodes.builders to builders
200
201nodes.builders = nodes.builder or { }
202local builders = nodes.builders
203
204local vboxactions = nodes.tasks.actions("vboxbuilders")
205
206function builders.vpack_filter(head,groupcode,size,packtype,maxdepth,direction)
207    local done = false
208    if head then
209        starttiming(builders)
210        head = tonut(head)
211        if trace_vbox_builder then
212            local before = count_nodes(head)
213            head, done = vboxactions(head,groupcode,size,packtype,maxdepth,direction)
214            local after = count_nodes(head)
215            nodes.processors.tracer("vpack",head,groupcode,before,after,done)
216        else
217            head, done = vboxactions(head,groupcode)
218        end
219        head = tonode(head)
220        stoptiming(builders)
221    end
222    return head, done
223end
224
225-- This one is special in the sense that it has no head and we operate on the mlv. Also,
226-- we need to do the vspacing last as it removes items from the mvl.
227
228local pageactions = nodes.tasks.actions("mvlbuilders")
229----- lineactions = nodes.tasks.actions("linebuilders")
230
231local function report(groupcode,head)
232    report_page_builder("trigger: %s",groupcode)
233    report_page_builder("  vsize    : %p",texget("vsize"))
234    report_page_builder("  pagegoal : %p",texget("pagegoal"))
235    report_page_builder("  pagetotal: %p",texget("pagetotal"))
236    report_page_builder("  list     : %s",head and nodeidstostring(head) or "<empty>")
237end
238
239-- check why box is called before after_linebreak .. maybe make categories and
240-- call 'm less
241
242-- this will be split into contribute_filter for these 4 so at some point
243-- the check can go away
244
245function builders.buildpage_filter(groupcode)
246    local head = texlists.contrib_head
247    if head then
248        local done = false
249        -- called quite often ... maybe time to remove timing
250        starttiming(builders)
251        if trace_page_builder then
252            report(groupcode,head)
253        end
254        head, done = pageactions(head,groupcode)
255        stoptiming(builders)
256     -- -- doesn't work here (not passed on?)
257     -- texset("pagegoal,texget("vsize") - texgetdimen("d_page_floats_inserted_top") - texgetdimen("d_page_floats_inserted_bottom")
258        texlists.contrib_head = head or nil -- needs checking
259     -- tex.setlist("contrib_head",head,head and nodes.tail(head))
260        return done and head or true -- no return value needed
261    else
262        -- happens quite often
263        if trace_page_builder then
264            report(groupcode)
265        end
266--         return nil, false -- no return value needed
267        return nil
268    end
269end
270
271registercallback('vpack_filter',          builders.vpack_filter,      "vertical spacing etc")
272registercallback('buildpage_filter',      builders.buildpage_filter,  "vertical spacing etc (mvl)")
273
274statistics.register("v-node processing time", function()
275    return statistics.elapsedseconds(builders)
276end)
277
278local implement = interfaces.implement
279
280implement { name = "defineparbuilder",  actions = constructors.define, arguments = "string" }
281implement { name = "setparbuilder",     actions = constructors.set,    arguments = "string" }
282implement { name = "startparbuilder",   actions = constructors.start,  arguments = "string" }
283implement { name = "stopparbuilder",    actions = constructors.stop    }
284implement { name = "enableparbuilder",  actions = constructors.enable  }
285implement { name = "disableparbuilder", actions = constructors.disable }
286
287-- Here are some tracers:
288
289local nuts         = nodes.nuts
290local tonut        = nodes.tonut
291local setcolor     = nodes.tracers.colors.set
292local listtoutf    = nodes.listtoutf
293local new_kern     = nuts.pool.kern
294local new_rule     = nuts.pool.rule
295local hpack        = nuts.hpack
296local getheight    = nuts.getheight
297local getdepth     = nuts.getdepth
298local getdirection = nuts.getdirection
299local getlist      = nuts.getlist
300local setwidth     = nuts.setwidth
301local setdirection = nuts.setdirection
302local setlink      = nuts.setlink
303local tonode       = nuts.tonode
304
305local report_hpack = logs.reporter("hpack routine")
306local report_vpack = logs.reporter("vpack routine")
307
308-- overflow|badness w h d dir
309
310local function vpack_quality(how,n,detail,first,last)
311    if last <= 0 then
312        report_vpack("%s vbox",how)
313    elseif first > 0 and first < last then
314        report_vpack("%s vbox at line %i - %i",how,first,last)
315    else
316        report_vpack("%s vbox at line %i",how,last)
317    end
318end
319
320trackers.register("builders.vpack.quality",function(v)
321    registercallback("vpack_quality",v and report_vpack_quality or nil,"check vpack quality")
322end)
323
324local report, show = false, false
325
326local function hpack_quality(how,detail,n,first,last)
327    n = tonut(n)
328    if report then
329        local str = listtoutf(getlist(n),"",true,nil,true)
330        if last <= 0 then
331            report_hpack("%s hbox: %s",how,str)
332        elseif first > 0 and first < last then
333            report_hpack("%s hbox at line %i - %i: %s",how,first,last,str)
334        else
335            report_hpack("%s hbox at line %i: %s",how,last,str)
336        end
337    end
338    if show then
339        local width     = 2*65536
340        local height    = getheight(n)
341        local depth     = getdepth(n)
342        local direction = getdirection(n)
343        if height < 4*65526 then
344            height = 4*65526
345        end
346        if depth < 2*65526 then
347            depth = 2*65526
348        end
349        local rule = new_rule(width,height,depth)
350        setdirection(rule,direction)
351        if how == "overfull" then
352            setcolor(rule,"red")
353            local kern = new_kern(-detail)
354            setlink(kern,rule)
355            rule = kern
356        elseif how == "underfull" then
357            setcolor(rule,"blue")
358        elseif how == "loose" then
359            setcolor(rule,"magenta")
360        elseif how == "tight" then
361            setcolor(rule,"cyan")
362        end
363        rule = hpack(rule)
364        setwidth(rule,0)
365        setdirection(rule,direction)
366        return tonode(rule) -- can be a nut
367    end
368end
369
370trackers.register("builders.hpack.quality",function(v)
371    report = v
372    registercallback("hpack_quality",(report or show) and hpack_quality or nil,"check hpack quality")
373end)
374
375trackers.register("builders.hpack.overflow",function(v)
376    show = v
377    registercallback("hpack_quality",(report or show) and hpack_quality or nil,"check hpack quality")
378end)
379