typo-bld.lmt /size: 15 Kb    last modification: 2024-01-16 09:03
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
22builders.hpack           = builders.hpack or { }
23builders.vpack           = builders.vpack or { }
24
25local hpackbuilders      = builders.hpack
26local vpackbuilders      = builders.vpack
27
28constructors.names       = constructors.names or { }
29local names              = constructors.names
30
31constructors.numbers     = constructors.numbers or { }
32local numbers            = constructors.numbers
33
34constructors.methods     = constructors.methods or { }
35local methods            = constructors.methods
36
37local a_parbuilder       = attributes.numbers['parbuilder'] or 999 -- why 999
38constructors.attribute   = a_parbuilder
39
40local unsetvalue         = attributes.unsetvalue
41local texsetattribute    = tex.setattribute
42local texnest            = tex.nest
43local texget             = tex.get
44local texset             = tex.set
45
46local getspeciallist     = nodes.nuts.getspeciallist
47local setspeciallist     = nodes.nuts.setspeciallist
48
49local nodes              = nodes
50local nodeidstostring    = nodes.idstostring
51local nodepool           = nodes.pool
52local new_baselineskip   = nodepool.baselineskip
53local new_lineskip       = nodepool.lineskip
54local insertnodebefore   = nodes.insertbefore
55local hpack_node         = nodes.hpack
56
57local nuts               = nodes.nuts
58local tonode             = nodes.tonode
59local tonut              = nodes.tonut
60local getattr            = nuts.getattr
61local flush              = nuts.flush
62
63local starttiming        = statistics.starttiming
64local stoptiming         = statistics.stoptiming
65
66local registercallback   = callbacks.register
67
68storage.register("builders/paragraphs/constructors/names",   names,   "builders.paragraphs.constructors.names")
69storage.register("builders/paragraphs/constructors/numbers", numbers, "builders.paragraphs.constructors.numbers")
70
71local trace_page_builder = false  trackers.register("builders.page", function(v) trace_page_builder = v end)
72local trace_vbox_builder = false  trackers.register("builders.vbox", function(v) trace_vbox_builder = v end)
73local trace_post_builder = false  trackers.register("builders.post", function(v) trace_post_builder = v end)
74
75local report_page_builder = logs.reporter("builders","page")
76local report_vbox_builder = logs.reporter("builders","vbox")
77local report_par_builder  = logs.reporter("builders","par")
78
79local mainconstructor = nil -- not stored in format
80local nofconstructors = 0
81local stack           = { }
82
83function constructors.define(name)
84    nofconstructors = nofconstructors + 1
85    names[nofconstructors] = name
86    numbers[name] = nofconstructors
87end
88
89function constructors.set(name) --- will go
90    if name then
91        mainconstructor = numbers[name] or unsetvalue
92    else
93        mainconstructor = stack[#stack] or unsetvalue
94    end
95    texsetattribute(a_parbuilder,mainconstructor)
96    if mainconstructor ~= unsetvalue then
97        constructors.enable()
98    end
99end
100
101function constructors.start(name)
102    local number = numbers[name]
103    insert(stack,number)
104    mainconstructor = number or unsetvalue
105    texsetattribute(a_parbuilder,mainconstructor)
106    if mainconstructor ~= unsetvalue then
107        constructors.enable()
108    end
109 -- report_par_builder("start %a",name)
110end
111
112function constructors.stop()
113    remove(stack)
114    mainconstructor = stack[#stack] or unsetvalue
115    texsetattribute(a_parbuilder,mainconstructor)
116    if mainconstructor == unsetvalue then
117        constructors.disable()
118    end
119 -- report_par_builder("stop")
120end
121
122-- return values:
123--
124-- true  : tex will break itself
125-- false : idem but dangerous
126-- head  : list of valid vmode nodes with last being hlist
127
128function constructors.handler(head,followed_by_display)
129    if type(head) == "boolean" then
130        return head
131    else
132        local attribute = getattr(head,a_parbuilder) -- or mainconstructor
133        if attribute then
134            local method = names[attribute]
135            if method then
136                local handler = methods[method]
137                if handler then
138                    return handler(head,followed_by_display)
139                else
140                    report_par_builder("contructor method %a is not defined",tostring(method))
141                    return true -- let tex break
142                end
143            end
144        end
145        return true -- let tex break
146    end
147end
148
149-- just for testing
150
151function constructors.methods.default(head,followed_by_display)
152    return true -- let tex break
153end
154
155-- also for testing (now also surrounding spacing done)
156
157function parbuilders.constructors.methods.oneline(head,followed_by_display)
158    -- when needed we will turn this into a helper
159    local t = texnest[texnest.ptr]
160    local h = hpack_node(head)
161    local d = texget("baselineskip",false) - t.prevdepth - h.height
162    t.prevdepth = h.depth
163    t.prevgraf  = 1
164    if d < texget("lineskiplimit") then
165        return insertnodebefore(h,h,new_lineskip(texget("lineskip",false))) -- no stretch etc
166    else
167        return insertnodebefore(h,h,new_baselineskip(d))
168    end
169end
170
171-- It makes no sense to have a sequence here as we already have
172-- pre and post hooks and only one parbuilder makes sense, so no:
173--
174-- local actions = nodes.tasks.actions("parbuilders")
175--
176-- yet ... maybe some day.
177
178local actions = constructors.handler
179local enabled = false
180
181local function processor(head,followed_by_display)
182    -- todo: not again in otr so we need to flag
183    if enabled then
184        starttiming(parbuilders)
185        head = tonode(actions(tonut(head),followed_by_display))
186        stoptiming(parbuilders)
187        return head
188    else
189        return true -- let tex do the work
190    end
191end
192
193function constructors.enable () enabled = true  end
194function constructors.disable() enabled = false end
195
196registercallback("linebreak_filter", processor, "breaking paragraps into lines")
197
198statistics.register("linebreak processing time", function()
199    return statistics.elapsedseconds(parbuilders)
200end)
201
202-- todo: move from nodes.builders to builders
203
204nodes.builders = nodes.builder or { }
205local builders = nodes.builders
206
207local vpackactions = nodes.tasks.actions("vboxbuilders")
208
209function builders.vpack_filter(head,groupcode,size,packtype,maxdepth,direction)
210    if head then
211        starttiming(builders)
212        head = vpackactions(head,groupcode)
213        stoptiming(builders)
214    end
215    return head
216end
217
218-- This one is special in the sense that it has no head and we operate on the mlv. Also,
219-- we need to do the vspacing last as it removes items from the mvl.
220
221local pageactions = nodes.tasks.actions("mvlbuilders")
222----- lineactions = nodes.tasks.actions("linebuilders")
223
224local function report(pagecontext,head)
225    report_page_builder("trigger: %s at level %i",pagecontext,texnest.ptr)
226    report_page_builder("  vsize    : %p",texget("vsize"))
227    report_page_builder("  pagegoal : %p",texget("pagegoal"))
228    report_page_builder("  pagetotal: %p",texget("pagetotal"))
229    report_page_builder("  list     : %s",head and nodeidstostring(head) or "<empty>")
230end
231
232-- Watch out: contributehead can be any head (kind of) not per se the page one
233-- but that needs to be intercepted when needed by groupcode and level. At some
234-- point this one might be split by group.
235
236function builders.buildpage_filter(pagecontext)
237    local head, tail = getspeciallist("contributehead")
238    if head then
239        if trace_page_builder then
240            report(pagecontext,head)
241        end
242        starttiming(builders)
243        head = pageactions(head,pagecontext) -- todo: tail
244        stoptiming(builders)
245        setspeciallist("contributehead", head)
246    else
247        if trace_page_builder then
248            report(pagecontext)
249        end
250    end
251end
252
253registercallback("vpack_filter",     builders.vpack_filter,     "vertical spacing etc")
254registercallback("buildpage_filter", builders.buildpage_filter, "vertical spacing etc (mvl)")
255
256local vboxactions = nodes.tasks.actions("vboxhandlers")
257
258function builders.vbox_filter(head,groupcode)
259    if head then
260        starttiming(builders)
261        head = vboxactions(head,groupcode)
262        stoptiming(builders)
263    end
264    return head
265end
266
267registercallback("packed_vbox_filter", builders.vbox_filter, "packed vbox treatments")
268
269statistics.register("v-node processing time", function()
270    return statistics.elapsedseconds(builders)
271end)
272
273local implement = interfaces.implement
274
275implement { name = "defineparbuilder",  actions = constructors.define, arguments = "string" }
276implement { name = "setparbuilder",     actions = constructors.set,    arguments = "string" }
277implement { name = "startparbuilder",   actions = constructors.start,  arguments = "string" }
278implement { name = "stopparbuilder",    actions = constructors.stop    }
279implement { name = "enableparbuilder",  actions = constructors.enable  }
280implement { name = "disableparbuilder", actions = constructors.disable }
281
282-- Here are some tracers:
283
284local nuts         = nodes.nuts
285local tonut        = nodes.tonut
286local setcolor     = nodes.tracers.colors.set
287local listtoutf    = nodes.listtoutf
288local new_kern     = nuts.pool.kern
289local new_rule     = nuts.pool.rule
290local hpack        = nuts.hpack
291local getheight    = nuts.getheight
292local getdepth     = nuts.getdepth
293local getdirection = nuts.getdirection
294local getlist      = nuts.getlist
295local setwidth     = nuts.setwidth
296local setdirection = nuts.setdirection
297local setlink      = nuts.setlink
298local tonode       = nuts.tonode
299
300local list         = { }
301
302local report_quality = logs.reporter("pack quality")
303
304-- overflow|badness w h d dir
305
306local report  = true   trackers.register("builders.vpack.quality", function(v) report  = v end)
307local collect = false  trackers.register("builders.vpack.collect", function(v) collect = v end)
308----  show    = false  trackers.register("builders.vpack.overflow",function(v) show    = v end)
309
310function vpackbuilders.report(how,detail,n,first,last,filename) -- no return value
311    if report then
312        if last <= 0 then
313            report_quality("%s vbox",how)
314        elseif first > 0 and first < last then
315            report_quality("%s vbox at line %i - %i in file %a",how,first,last,filename or "?")
316        else
317            report_quality("%s vbox at line %i in file %a",how,last,filename or "?")
318        end
319    end
320    if collect then
321        list[#list+1] = { "hbox", how, filename, first, last, how, detail }
322    end
323end
324
325-- function builders.vpack.show(how,detail,n,first,last,filename) -- return value
326--     if show then
327--     end
328-- end
329
330local report  = true   trackers.register("builders.hpack.quality", function(v) report  = v end)
331local collect = false  trackers.register("builders.hpack.collect", function(v) collect = v end)
332local show    = false  trackers.register("builders.hpack.overflow",function(v) show    = v end)
333
334function hpackbuilders.report(how,detail,n,first,last,filename) -- no return value
335    local str = (report or collect) and listtoutf(getlist(n),"",true,nil,true)
336    if report then
337        local overfull = how == "overfull"
338        if last <= 0 then
339            report_quality(
340                overfull and "%s hbox: %s (%p)" or "%s hbox: %s (badness %i)",
341                how,str,detail
342            )
343        elseif first > 0 and first < last then
344            report_quality(
345                overfull and "%s hbox at line %i - %i in file %a: %s (%p)" or "%s hbox at line %i - %i in file %a: %s (badness %i)",
346                how,first,last,filename or "?",str,detail
347            )
348        else
349            report_quality(
350                overfull and "%s hbox at line %i in file %a: %s (%p)" or "%s hbox at line %i in file %a: %s (badness %i)",
351                how,last,filename or "?",str,detail
352            )
353        end
354    end
355    if collect then
356        list[#list+1] = { "hbox", how, filename, first, last, str, detail }
357    end
358end
359
360function hpackbuilders.show(how,detail,n,first,last,filename,rule) -- return value
361    if show then
362        local width     = 2*65536
363        local height    = getheight(n)
364        local depth     = getdepth(n)
365        local direction = getdirection(n)
366        if height < 4*65526 then
367            height = 4*65526
368        end
369        if depth < 2*65526 then
370            depth = 2*65526
371        end
372        if rule then
373            flush(rule)
374        end
375        rule = new_rule(width,height,depth)
376        setdirection(rule,direction)
377        if how == "overfull" then
378            setcolor(rule,"red")
379            local kern = new_kern(-detail)
380            setlink(kern,rule)
381            rule = kern
382        elseif how == "underfull" then
383            setcolor(rule,"blue")
384        elseif how == "loose" then
385            setcolor(rule,"magenta")
386        elseif how == "tight" then
387            setcolor(rule,"cyan")
388        end
389        rule = hpack(rule)
390        setwidth(rule,0) -- maybe better whd all zero
391        setdirection(rule,direction)
392    else
393        local width = texget("overfullrule")
394        if width > 0 then
395            rule = new_rule(width)
396        end
397    end
398    return rule
399end
400
401--
402
403local hqualityactions = nodes.tasks.actions("hquality")
404local vqualityactions = nodes.tasks.actions("vquality")
405
406function hpackbuilders.qualityactions(how,detail,n,first,last,filename)
407    local rul = nil
408 -- starttiming(builders)
409    rul = hqualityactions(how,detail,n,first,last,filename)
410 -- stoptiming(builders)
411    return rul
412end
413function vpackbuilders.qualityactions(how,detail,n,first,last,filename)
414    local rul = nil
415 -- starttiming(builders)
416    rul = vqualityactions(how,detail,n,first,last,filename)
417 -- stoptiming(builders)
418    return rul
419end
420
421registercallback("hpack_quality", hpackbuilders.qualityactions, "report horizontal packing quality")
422registercallback("vpack_quality", vpackbuilders.qualityactions, "report vertical packing quality")
423
424statistics.register("quality reports", function()
425    local n = #list
426    if n > 0 then
427        local t = table.setmetatableindex("number")
428        local fw = 0
429        local hw = 0
430        for i=1,n do
431            local f = list[i][1]
432            local h = list[i][2]
433            if #f > fw then
434                fw = #f
435            end
436            if #h > hw then
437                hw = #h
438            end
439            t[h] = t[h] + 1
440        end
441        logs.startfilelogging(report_quality)
442        for i=1,n do
443            local l   = list[i]
444            local how = l[2]
445            report_quality(
446                how == "overfull"
447                    and "%-" .. fw .. "s [%04i - %04i] : %-" .. hw .. "s %s : %s (%p)"
448                     or "%-" .. fw .. "s [%04i - %04i] : %-" .. hw .. "s %s : %s (badness %i)",
449                file.basename(l[3]),l[4],l[5],how,l[1],l[6],l[7]
450            )
451        end
452        logs.stopfilelogging()
453        report_quality()
454        report_quality("%i entries added to the log file : %s",n,table.sequenced(t))
455        report_quality()
456    end
457end)
458