1if modules then modules = { } end modules ['typo-bld'] = {
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
10
11local next = next
12local insert, remove, sort = table.insert, table.remove, table.sort
13
14builders = builders or { }
15local builders = builders
16
17builders.paragraphs = builders.paragraphs or { }
18local parbuilders = builders.paragraphs
19
20parbuilders.constructors = parbuilders.constructors or { }
21local constructors = parbuilders.constructors
22
23builders.hpack = builders.hpack or { }
24builders.vpack = builders.vpack or { }
25
26local hpackbuilders = builders.hpack
27local vpackbuilders = builders.vpack
28
29constructors.names = constructors.names or { }
30local names = constructors.names
31
32constructors.numbers = constructors.numbers or { }
33local numbers = constructors.numbers
34
35constructors.methods = constructors.methods or { }
36local methods = constructors.methods
37
38local a_parbuilder <const> = attributes.numbers['parbuilder'] or 999
39constructors.attribute = a_parbuilder
40
41local unsetvalue <const> = attributes.unsetvalue
42
43local texsetattribute = tex.setattribute
44local texnest = tex.nest
45local texget = tex.get
46local texset = tex.set
47
48local getspeciallist = nodes.nuts.getspeciallist
49local setspeciallist = nodes.nuts.setspeciallist
50
51local nodes = nodes
52local nodeidstostring = nodes.idstostring
53local nodepool = nodes.pool
54local new_baselineskip = nodepool.baselineskip
55local new_lineskip = nodepool.lineskip
56local insertnodebefore = nodes.insertbefore
57local hpack_node = nodes.hpack
58
59local nuts = nodes.nuts
60local tonode = nodes.tonode
61local tonut = nodes.tonut
62local getattr = nuts.getattr
63local flush = nuts.flush
64
65local starttiming = statistics.starttiming
66local stoptiming = statistics.stoptiming
67
68local registercallback = callbacks.register
69
70storage.register("builders/paragraphs/constructors/names", names, "builders.paragraphs.constructors.names")
71storage.register("builders/paragraphs/constructors/numbers", numbers, "builders.paragraphs.constructors.numbers")
72
73local trace_page_builder = false trackers.register("builders.page", function(v) trace_page_builder = v end)
74local trace_vbox_builder = false trackers.register("builders.vbox", function(v) trace_vbox_builder = v end)
75local trace_post_builder = false trackers.register("builders.post", function(v) trace_post_builder = v end)
76
77local report_page_builder = logs.reporter("builders","page")
78local report_vbox_builder = logs.reporter("builders","vbox")
79local report_par_builder = logs.reporter("builders","par")
80
81local mainconstructor = nil
82local nofconstructors = 0
83local stack = { }
84
85function constructors.define(name)
86 nofconstructors = nofconstructors + 1
87 names[nofconstructors] = name
88 numbers[name] = nofconstructors
89end
90
91function constructors.set(name)
92 if name then
93 mainconstructor = numbers[name] or unsetvalue
94 else
95 mainconstructor = stack[#stack] or unsetvalue
96 end
97 texsetattribute(a_parbuilder,mainconstructor)
98 if mainconstructor ~= unsetvalue then
99 constructors.enable()
100 end
101end
102
103function constructors.start(name)
104 local number = numbers[name]
105 insert(stack,number)
106 mainconstructor = number or unsetvalue
107 texsetattribute(a_parbuilder,mainconstructor)
108 if mainconstructor ~= unsetvalue then
109 constructors.enable()
110 end
111
112end
113
114function constructors.stop()
115 remove(stack)
116 mainconstructor = stack[#stack] or unsetvalue
117 texsetattribute(a_parbuilder,mainconstructor)
118 if mainconstructor == unsetvalue then
119 constructors.disable()
120 end
121
122end
123
124
125
126
127
128
129
130function constructors.handler(head,followed_by_display)
131 if type(head) == "boolean" then
132 return head
133 else
134 local attribute = getattr(head,a_parbuilder)
135 if attribute then
136 local method = names[attribute]
137 if method then
138 local handler = methods[method]
139 if handler then
140 return handler(head,followed_by_display)
141 else
142 report_par_builder("contructor method %a is not defined",tostring(method))
143 return true
144 end
145 end
146 end
147 return true
148 end
149end
150
151
152
153function constructors.methods.default(head,followed_by_display)
154 return true
155end
156
157
158
159function parbuilders.constructors.methods.oneline(head,followed_by_display)
160
161 local t = texnest[texnest.ptr]
162 local h = hpack_node(head)
163 local d = texget("baselineskip",false) - t.prevdepth - h.height
164 t.prevdepth = h.depth
165 t.prevgraf = 1
166 if d < texget("lineskiplimit") then
167 return insertnodebefore(h,h,new_lineskip(texget("lineskip",false)))
168 else
169 return insertnodebefore(h,h,new_baselineskip(d))
170 end
171end
172
173
174
175
176
177
178
179
180local actions = constructors.handler
181local enabled = false
182
183local function processor(head,followed_by_display)
184
185 if enabled then
186 starttiming(parbuilders)
187 head = tonode(actions(tonut(head),followed_by_display))
188 stoptiming(parbuilders)
189 return head
190 else
191 return true
192 end
193end
194
195function constructors.enable () enabled = true end
196function constructors.disable() enabled = false end
197
198registercallback("linebreak_filter", processor, "breaking paragraps into lines")
199
200statistics.register("linebreak processing time", function()
201 return statistics.elapsedseconds(parbuilders)
202end)
203
204
205
206nodes.builders = nodes.builder or { }
207local builders = nodes.builders
208
209local vpackactions = nodes.tasks.actions("vboxbuilders")
210
211function builders.vpack_filter(head,groupcode,size,packtype,maxdepth,direction)
212 if head then
213 starttiming(builders)
214 head = vpackactions(head,groupcode)
215 stoptiming(builders)
216 end
217 return head
218end
219
220
221
222
223local pageactions = nodes.tasks.actions("mvlbuilders")
224
225
226local function report(pagecontext,head)
227 report_page_builder("trigger: %s at level %i",pagecontext,texnest.ptr)
228 report_page_builder(" vsize : %p",texget("vsize"))
229 report_page_builder(" pagegoal : %p",texget("pagegoal"))
230 report_page_builder(" pagetotal: %p",texget("pagetotal"))
231 report_page_builder(" list : %s",head and nodeidstostring(head) or "<empty>")
232end
233
234
235
236
237
238function builders.buildpage_filter(pagecontext)
239 local head, tail = getspeciallist("contributehead")
240 if head then
241 if trace_page_builder then
242 report(pagecontext,head)
243 end
244 starttiming(builders)
245 head = pageactions(head,pagecontext)
246 stoptiming(builders)
247 setspeciallist("contributehead", head)
248 else
249 if trace_page_builder then
250 report(pagecontext)
251 end
252 end
253end
254
255registercallback("vpack_filter", builders.vpack_filter, "vertical spacing etc")
256registercallback("buildpage_filter", builders.buildpage_filter, "vertical spacing etc (mvl)")
257
258local vboxactions = nodes.tasks.actions("vboxhandlers")
259
260function builders.vbox_filter(head,groupcode)
261 if head then
262 starttiming(builders)
263 head = vboxactions(head,groupcode)
264 stoptiming(builders)
265 end
266 return head
267end
268
269registercallback("packed_vbox_filter", builders.vbox_filter, "packed vbox treatments")
270
271statistics.register("v-node processing time", function()
272 return statistics.elapsedseconds(builders)
273end)
274
275local implement = interfaces.implement
276
277implement { name = "defineparbuilder", actions = constructors.define, arguments = "string" }
278implement { name = "setparbuilder", actions = constructors.set, arguments = "string" }
279implement { name = "startparbuilder", actions = constructors.start, arguments = "string" }
280implement { name = "stopparbuilder", actions = constructors.stop }
281implement { name = "enableparbuilder", actions = constructors.enable }
282implement { name = "disableparbuilder", actions = constructors.disable }
283
284
285
286local nuts = nodes.nuts
287local tonut = nodes.tonut
288local setcolor = nodes.tracers.colors.set
289local listtoutf = nodes.listtoutf
290local new_kern = nuts.pool.kern
291local new_rule = nuts.pool.rule
292local hpack = nuts.hpack
293local getheight = nuts.getheight
294local getdepth = nuts.getdepth
295local getdirection = nuts.getdirection
296local getlist = nuts.getlist
297local setwidth = nuts.setwidth
298local setdirection = nuts.setdirection
299local setlink = nuts.setlink
300local tonode = nuts.tonode
301
302local list = { }
303
304local report_quality = logs.reporter("pack quality")
305
306
307
308local report = true trackers.register("builders.vpack.quality", function(v) report = v end)
309local collect = false trackers.register("builders.vpack.collect", function(v) collect = v end)
310
311
312function vpackbuilders.report(how,detail,n,first,last,filename)
313 if report then
314 if last <= 0 then
315 report_quality("%s vbox",how)
316 elseif first > 0 and first < last then
317 report_quality("%s vbox at line %i - %i in file %a",how,first,last,filename or "?")
318 else
319 report_quality("%s vbox at line %i in file %a",how,last,filename or "?")
320 end
321 end
322 if collect then
323 list[#list+1] = { "hbox", how, filename, first, last, how, detail }
324 end
325end
326
327
328
329
330
331
332local report = true trackers.register("builders.hpack.quality", function(v) report = v end)
333local collect = false trackers.register("builders.hpack.collect", function(v) collect = v end)
334local show = false trackers.register("builders.hpack.overflow",function(v) show = v end)
335
336function hpackbuilders.report(how,detail,n,first,last,filename)
337 local str = (report or collect) and listtoutf(getlist(n),"",true,nil,true)
338 if report then
339 local overfull = how == "overfull"
340 if last <= 0 then
341 report_quality(
342 overfull and "%s hbox: %s (%p)" or "%s hbox: %s (badness %i)",
343 how,str,detail
344 )
345 elseif first > 0 and first < last then
346 report_quality(
347 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)",
348 how,first,last,filename or "?",str,detail
349 )
350 else
351 report_quality(
352 overfull and "%s hbox at line %i in file %a: %s (%p)" or "%s hbox at line %i in file %a: %s (badness %i)",
353 how,last,filename or "?",str,detail
354 )
355 end
356 end
357 if collect then
358 list[#list+1] = { "hbox", how, filename, first, last, str, detail }
359 end
360end
361
362function hpackbuilders.show(how,detail,n,first,last,filename,rule)
363 if show then
364 local width = 2*65536
365 local height = getheight(n)
366 local depth = getdepth(n)
367 local direction = getdirection(n)
368 if height < 4*65526 then
369 height = 4*65526
370 end
371 if depth < 2*65526 then
372 depth = 2*65526
373 end
374 if rule then
375 flush(rule)
376 end
377 rule = new_rule(width,height,depth)
378 setdirection(rule,direction)
379 if how == "overfull" then
380 setcolor(rule,"red")
381 local kern = new_kern(-detail)
382 setlink(kern,rule)
383 rule = kern
384 elseif how == "underfull" then
385 setcolor(rule,"blue")
386 elseif how == "loose" then
387 setcolor(rule,"magenta")
388 elseif how == "tight" then
389 setcolor(rule,"cyan")
390 end
391 rule = hpack(rule)
392 setwidth(rule,0)
393 setdirection(rule,direction)
394 else
395 local width = texget("overfullrule")
396 if width > 0 then
397 rule = new_rule(width)
398 end
399 end
400 return rule
401end
402
403
404
405local hqualityactions = nodes.tasks.actions("hquality")
406local vqualityactions = nodes.tasks.actions("vquality")
407
408function hpackbuilders.qualityactions(how,detail,n,first,last,filename)
409 local rul = nil
410
411 rul = hqualityactions(how,detail,n,first,last,filename)
412
413 return rul
414end
415function vpackbuilders.qualityactions(how,detail,n,first,last,filename)
416 local rul = nil
417
418 rul = vqualityactions(how,detail,n,first,last,filename)
419
420 return rul
421end
422
423registercallback("hpack_quality", hpackbuilders.qualityactions, "report horizontal packing quality")
424registercallback("vpack_quality", vpackbuilders.qualityactions, "report vertical packing quality")
425
426statistics.register("quality reports", function()
427 local n = #list
428 if n > 0 then
429 local t = table.setmetatableindex("number")
430 local fw = 0
431 local hw = 0
432 for i=1,n do
433 local f = list[i][1]
434 local h = list[i][2]
435 if #f > fw then
436 fw = #f
437 end
438 if #h > hw then
439 hw = #h
440 end
441 t[h] = t[h] + 1
442 end
443 logs.startfilelogging(report_quality)
444 for i=1,n do
445 local l = list[i]
446 local how = l[2]
447 report_quality(
448 how == "overfull"
449 and "%-" .. fw .. "s [%04i - %04i] : %-" .. hw .. "s %s : %s (%p)"
450 or "%-" .. fw .. "s [%04i - %04i] : %-" .. hw .. "s %s : %s (badness %i)",
451 file.basename(l[3]),l[4],l[5],how,l[1],l[6],l[7]
452 )
453 end
454 logs.stopfilelogging()
455 report_quality()
456 report_quality("%i entries added to the log file : %s",n,table.sequenced(t))
457 report_quality()
458 end
459end)
460
461do
462
463 local loners = { }
464 local report = logs.reporter("loners")
465
466 local texgetcount = tex.getcount
467
468 trackers.register("pages.loners",function(v)
469 callback.register("show_loners",
470 v and function(options,penalty)
471 loners[#loners+1] = { texgetcount("realpageno"), options, penalty }
472 end or nil
473 )
474 end)
475
476 logs.registerfinalactions(function()
477 local n = #loners
478 if n > 0 then
479 local options = tex.getpenaltyoptionvalues()
480 options[0] = nil
481 logs.startfilelogging(report,"page break penalties (loners)")
482 for i=1,n do
483 local l = loners[i]
484 local t = { }
485 local o = l[2]
486 for k, v in next, options do
487 if o & k == k then t[#t+1] = v end
488 end
489 if #t > 0 then
490 sort(t)
491 report(" %4i: penalty %5i, % + t,",l[1],l[3],t)
492 end
493 end
494 logs.stopfilelogging()
495 end
496 end)
497
498 statistics.register("page breaks", function()
499 local n = #loners
500 if n > 0 then
501 local pages = { }
502 for i=1,n do
503 pages[#pages+1] = loners[i][1]
504 end
505 return string.formatters("%i pages have loners reported: % t",n,pages)
506 end
507 end)
508
509end
510 |