1if not modules then modules = { } end modules ['trac-brk'] = {
2 version = 1.001,
3 optimize = true,
4 comment = "companion to trac-brk.mkxl",
5 author = "Hans Hagen & Mikael Sundqvist",
6 copyright = "PRAGMA ADE / ConTeXt Development Team",
7 license = "see context related readme files"
8}
9
10local next, tonumber, tostring, type = next, tonumber, tostring, type
11local round, abs = math.round, math.abs
12local formatters = string.formatters
13
14local settings_to_array = utilities.parsers.settings_to_array
15local settings_to_hash = utilities.parsers.settings_to_hash
16
17local nuts = nodes.nuts
18local tonut = nodes.tonut
19
20local newrule = nuts.pool.virtualrule
21local insertbefore = nuts.insertbefore
22local insertafter = nuts.insertafter
23local hpack_string = nuts.typesetters.tohpack
24local getwidth = nuts.getwidth
25local getid = nuts.getid
26local getprev = nuts.getprev
27local getnext = nuts.getnext
28local setwhd = nuts.setwhd
29local getglue = nuts.getglue
30local getlist = nuts.getlist
31local setoffsets = nuts.setoffsets
32local setattr = nuts.setattr
33
34local setcolor = nodes.tracers.colors.set
35local settransparency = nodes.tracers.transparencies.set
36
37local texgetnest = tex.getnest
38
39local nodecodes = nodes.nodecodes
40
41
42local penalty_code <const> = nodecodes.penalty
43local glyph_code <const> = nodecodes.glyph
44local glue_code <const> = nodecodes.glue
45local disc_code <const> = nodecodes.disc
46local kern_code <const> = nodecodes.kern
47local math_code <const> = nodecodes.math
48
49local a_tagged <const> = attributes.private('tagged')
50
51local getnormalizedline = nuts.getnormalizedline
52local getdimensions = nuts.dimensions
53local getnaturalhsize = nuts.naturalhsize
54local setruledimensions = nuts.setruledimensions
55local getruledimensions = nuts.getruledimensions
56
57local breakpoints = tracers.breakpoints or { }
58tracers.breakpoints = breakpoints
59
60local width = 65536 / 2
61local height = 9 * 65536
62local depth = 3 * 65536
63local xoffset = 1 * 65536
64local yoffset = -3 * 65536
65
66local serials = false
67local serial = false
68local results = false
69local usedfont = false
70local nestlevel = false
71local overloads = false
72local overload = false
73local breakpass = 0
74local breaksubpass = 0
75local breaksubpasses = 0
76local linenumber = 0
77local decentfit = 0
78
79local fitnesscodes = {
80 [4] = {
81 "veryloose",
82 "loose",
83 "decent",
84 "tight",
85 },
86 [5] = {
87 "veryloose",
88 "loose",
89 "decent",
90 "tight",
91 "verytight",
92 },
93 [9] = {
94 "veryloose",
95 "loose",
96 "almostloose",
97 "barelyloose",
98 "decent",
99 "barelytight",
100 "almosttight",
101 "tight",
102 "verytight",
103 },
104}
105
106local report = logs.reporter("linebreaks")
107local breakcodes = tex.breakcodes
108
109local f_detail = formatters["[%02i] b=%i d=%p p=%0.3f r=%0.3f (dt=%i) (%p) from s=%i b=%i"]
110local f_simple = formatters["[%02i] b=%i from s=%i b=%i"]
111local f_badness = formatters["[%02i] b=%i"]
112
113local maxbadness <const> = tex.magicconstants.maxcalculatedbadness
114local marginoffset = 0
115local showdetails = false
116local showsimple = false
117local showrefcount = true
118
119local showactions = {
120 [breakcodes.initialize] = function(checks,subpasses)
121 if texgetnest("ptr") == nestlevel then
122 report("initialize")
123 usedfont = nodes.visualizers.getusedfont()
124 breaksubpasses = subpasses
125 end
126 end,
127 [breakcodes.start] = function(checks,pass,subpass,classes,decent)
128 if texgetnest("ptr") == nestlevel then
129 report("start pass %i, subpass %i",pass,subpass)
130 if breakpass ~= pass and breaksubpass ~= subpass then
131 breakpass = pass
132 breaksubpass = subpass
133 linenumber = 0
134 decentfit = decent
135 serials = { }
136 results = { }
137 end
138 serial = { }
139 serials[#serials+1] = serial
140 if overloads then
141 overload = overloads[#serials]
142 end
143 end
144 end,
145 [breakcodes.report] = function(checks,pass,subpass,currentserial,previousserial,line,kind,class,classes,badness,demerits,breakpoint,short,glue,width)
146 if texgetnest("ptr") == nestlevel then
147 if breakpoint then
148 local s = {
149 serial = currentserial,
150 previous = previousserial,
151 line = line,
152 kind = kind,
153 class = class,
154 classes = classes,
155 badness = badness,
156 demerits = demerits,
157 breakpoint = breakpoint,
158 short = short,
159 width = width,
160 spillover = 0,
161 refcount = 0,
162 }
163 if short < 0 then
164 short = -short
165 if short > glue then
166 local spillover = short - glue
167 report("pass %i, subpass %i, line %i, %s by %p",pass,subpass,line,"overfull",spillover)
168 s.spillover = splillover
169 end
170 elseif short > 0 then
171 if short > glue then
172 local spillover = short - glue
173 report("pass %i, subpass %i, line %i, %s by %p",pass,subpass,line,"underfull",spillover)
174 s.spillover = splillover
175 end
176 end
177 serial[currentserial] = s
178 local found = overload and overload[currentserial]
179 if found then
180 report("pass %i, subpass %i, overloading serial %i demerits from %i to %i",pass,subpass,currentserial,demerits,found)
181 s.overload = found
182 return found
183 end
184 end
185 end
186 return demerits
187 end,
188 [breakcodes.stop] = function(checks,demerits)
189 if texgetnest("ptr") == nestlevel then
190 report("stop with demerits %i",demerits)
191 end
192 end,
193 [breakcodes.collect] = function()
194 if texgetnest("ptr") == nestlevel then
195 report("collect")
196 for currentserial=1,#serial do
197 local data = serial[currentserial]
198 local breakpoint = data.breakpoint
199 local current = tonut(breakpoint)
200 local trigger = getid(current)
201 while current do
202 local id = getid(current)
203 if id == penalty_code or id == glue_code or id == kern_code or id == math_code then
204 current = getprev(current)
205 else
206 break
207 end
208 end
209 if current then
210 local rule = newrule(width,height,depth,currentserial)
211 data.rule = rule
212 data.trigger = trigger
213 insertafter(current,current,rule)
214 end
215 end
216 end
217 end,
218 [breakcodes.line] = function(checks,line,badness,overshoot,shrink,stretch,linum,ser)
219 if showdetails and texgetnest("ptr") == nestlevel then
220 linenumber = linenumber + 1
221 line = tonut(line)
222 local linedata = getnormalizedline(line,true)
223 local natural = linedata.size
224 local linewd = linedata.width
225 local deltawd = linewd - natural
226 local splillover = linedata.spillover or 0
227 local ratiob = 0
228 local ratio, order, sign
229 = getglue(line)
230 local yoffset = 2*xoffset
231 local xoffset = 5*xoffset + linedata.right + marginoffset
232 local serialdata = serial[ser]
233 if order == 0 then
234 if deltawd ~= 0 and ratio == 0 and sign == 0 then
235 xoffset = xoffset + deltawd
236 ratiob = 10000
237 elseif deltawd < 0 and ratio == 1 then
238 ratiob = 1000000
239 else
240 ratiob = round(100*abs(ratio)^3)
241 if ratiob > maxbadness then
242 ratiob = 10000
243 end
244 end
245 end
246
247
248
249 local sbadness = serialdata and serialdata.badness or 0
250 local text = showsimple == 1 and f_simple(linenumber,badness,ser,sbadness) or
251 showsimple == 2 and f_badness(linenumber,sbadness) or
252 f_detail(linenumber,badness,deltawd,100*deltawd/linewd,ratio,ratiob,spillover,ser,sbadness)
253 local location = linedata.last
254 local head = linedata.head
255 text = hpack_string(text,usedfont,spillover)
256 setattr(text,a_tagged,0)
257 setwhd(text,0,0,0)
258 setoffsets(text,xoffset,yoffset)
259 insertbefore(head,location,text)
260 deltawd = deltawd - linedata.parinitrightskip - linedata.parfillrightskip
261 local rule = newrule(deltawd < 0 and - deltawd or deltawd,0,depth)
262 setoffsets(rule,deltawd > 0 and -deltawd or 0,yoffset+depth)
263 setcolor(rule,"trace:6")
264 settransparency(rule,"trace:6")
265 insertbefore(head,location,rule)
266 end
267 end,
268 [breakcodes.list] = function(checks,currentserial,refcount)
269 if texgetnest("ptr") == nestlevel then
270 local s = serial[currentserial]
271 if s then
272 s.final = true
273
274 end
275 end
276 end,
277 [breakcodes.delete] = function(checks,currentserial,refcount)
278 if texgetnest("ptr") == nestlevel then
279 local s = serial[currentserial]
280 if s then
281 s.refcount = refcount
282 end
283 end
284 end,
285 [breakcodes.wrapup] = function(checks,demerits,looseness)
286 if texgetnest("ptr") == nestlevel then
287 report("wrapup")
288 if serial then
289 local finals = { }
290 local result = {
291 looseness = looseness,
292 demerits = demerits,
293 finals = finals,
294 pass = breakpass,
295 subpass = breaksubpass,
296 subpasses = breaksubpasses,
297 }
298 results[#results+1] = result
299 for currentserial=1,#serial do
300 local data = serial[currentserial]
301 local rule = data.rule
302 if rule then
303 local text = hpack_string(tostring(currentserial),usedfont)
304 local size = getwidth(text)
305 local trigger = data.trigger
306 local final = data.final
307 local class = data.class
308 local codes = fitnesscodes[data.classes]
309 local color = "trace:0"
310 if trigger == penalty_code then
311 color = "trace:1"
312 elseif trigger == glue_code then
313 color = "trace:2"
314 elseif trigger == disc_code then
315 color = "trace:3"
316 end
317 if final then
318 local width, height, depth = getruledimensions(rule)
319 setruledimensions(rule,2*width,height,depth)
320 end
321 setattr(text,a_tagged,0)
322 setcolor(rule,color)
323 setwhd(text,0,0,0)
324 setoffsets(text,-size-xoffset,yoffset)
325 insertafter(rule,rule,text)
326 result[#result+1] = data
327 data.trigger = nodecodes[trigger]
328 data.class = codes and codes[class] or ("class " .. class)
329 data.color = color
330 end
331 end
332
333 if #serial > 0 then
334 local lastline = serial[#serial].line
335 for currentserial=#serial,1,-1 do
336 local data = serial[currentserial]
337 if data.refcount == 0 then
338 local list = { }
339 local done = {
340 serial = currentserial,
341 line = data.line,
342 list = list,
343 final = data.final,
344 color = data.color
345 }
346 finals[#finals+1] = done
347 while true do
348 local previous = data.previous
349 if previous == 0 then
350 break
351 else
352 list[#list+1] = previous
353 data = serial[previous]
354 end
355 end
356 else
357 break
358 end
359 end
360 end
361 table.reverse(finals)
362
363 serial = false
364 end
365 end
366 end,
367}
368
369
370
371
372
373local function check_options(option)
374 if option then
375 option = type(option) == "table" and option or settings_to_hash(option or "")
376 if option.margin then
377 showdetails = true
378 end
379 if option.simple then
380 showsimple = 1
381 elseif option.badness then
382 showsimple = 2
383 end
384 if option.compact then
385 showrefcount = false
386 end
387 end
388end
389
390function breakpoints.start(specification)
391 local list = specification.list
392 breakpass = 0
393 linenumber = 0
394 showdetails = false
395 showsimple = false
396 showrefcount = true
397 serials = { }
398 results = { }
399 nestlevel = texgetnest("ptr") + (tonumber(specification.level) or 0)
400 marginoffset = specification.offset or 0
401 check_options(specification.option)
402 if list then
403 overloads = type(list) == "table" and list or settings_to_array(list or "")
404 for i=1,#overloads do
405 local s = overloads[i]
406 local t = settings_to_hash(s)
407 local o = { }
408 for k, v in next, t do
409 o[tonumber(k)] = tonumber(v) or -1
410 end
411 overloads[i] = o
412 end
413 end
414end
415
416function breakpoints.stop()
417
418end
419
420nodes.handlers.linebreakchecks[1] = function(what,...)
421 return showactions[what](...)
422end
423
424function breakpoints.reset()
425 serials = false
426 serial = false
427 results = false
428 nestlevel = false
429 breakpass = 0
430 linenumber = 0
431end
432
433function breakpoints.getresults()
434 return results or { }
435end
436
437function breakpoints.nofresults()
438 return results and #results or 0
439end
440
441function breakpoints.typesetresult(n,option)
442 local context, ctx_NC, ctx_NR, ctx_EQ, ctx_color = context, context.NC, context.NR, context.EQ, context.color
443
444 check_options(option)
445
446 local function typeset(result)
447 if result then
448 local lastline = 0
449 context.starttabulate { showrefcount and "|r|rS|r|r|r|r|l|l|c|" or "|r|r|r|r|r|l|l|c|" }
450 for i=1,#result do
451 local r = result[i]
452 local serial = r.serial
453 local previous = r.previous
454 local line = r.line
455 local final = r.final
456 local refcount = r.refcount or 0
457 local overload = r.overload
458 local demerits = overload or r.demerits
459 local badness = r.badness
460 local trigger = r.trigger
461 local class = r.class
462 local short = r.short
463 local color = { r.color }
464 ctx_NC() if line ~= lastline then lastline = line context(line) end
465 if showrefcount then
466 ctx_NC() if refcount > 0 then if final then ctx_color(color,refcount) else context(refcount) end end
467 end
468 ctx_NC() if final then ctx_color(color,serial) else context(serial) end
469 ctx_NC() if final then ctx_color(color,previous) else context(previous) end
470 ctx_NC() if final then ctx_color(color,badness) else context(badness) end
471 ctx_NC() if final then ctx_color(color,demerits) else context(demerits) end
472 ctx_NC() if final then ctx_color(color,class) else context(class) end
473 ctx_NC() ctx_color(color,trigger)
474 ctx_NC() if overload then if final then ctx_color(color,"!") else context("!") end end
475 ctx_NC() ctx_NR()
476 end
477 context.stoptabulate()
478
479 local finals = result.finals
480 if finals then
481
482 context.starttabulate { "|r|lp|" }
483 for i=1,#finals do
484 local data = finals[i]
485 local final = data.final
486 local serial = data.serial
487 local path = table.concat(data.list," ")
488 local color = final and data.color
489 if color then
490 color = { color }
491 end
492 ctx_NC() if color then ctx_color(color,serial) else context(serial) end
493 ctx_NC() if color then ctx_color(color,path) else context(path) end
494 ctx_NC() ctx_NR()
495 end
496 context.stoptabulate()
497 local passname = {
498 [-2] = "P",
499 [-1] = "T",
500 [ 0] = "E",
501 }
502 context.starttabulate { "|l|r|l|r|" }
503 local pass = result.pass
504 local subpasses = result.subpasses
505 local subpass = result.subpass
506 if subpasses == 0 then
507 subpass = passname[subpass] or subpass
508 end
509 ctx_NC() context("pass") ctx_EQ() context(pass)
510 ctx_NC() context("demerits") ctx_EQ() context(result.demerits) ctx_NC() ctx_NR()
511 ctx_NC() context("subpass") ctx_EQ() context(subpass)
512 ctx_NC() context("looseness") ctx_EQ() context(result.looseness) ctx_NC() ctx_NR()
513 ctx_NC() context("subpasses") ctx_EQ() context(subpasses)
514 ctx_NC() ctx_NC() ctx_NC() ctx_NR()
515 context.stoptabulate()
516 end
517 end
518 end
519
520 local results = breakpoints.getresults()
521 if n then
522 typeset(results[n])
523 else
524 for i=1,#results do
525 typeset(results[i])
526 end
527 end
528end
529
530
531
532function mp.show_breakpoints(dx,dy,sx,sy,lw)
533 local results = breakpoints.getresults()[1]
534 local breaks = { }
535 local colors = {
536 none = "black",
537 glue = "darkgreen",
538 disc = "darkblue",
539 penalty = "darkred",
540 math = "darkgray",
541 lines = "darkgray",
542 }
543 local everything = {
544 formatters["numeric dx ; dx := %N ;"](dx),
545 formatters["numeric dy ; dy := %N ;"](dy),
546 formatters["numeric sx ; sx := %N ;"](sx),
547 formatters["numeric sy ; sy := %N ;"](sy),
548 formatters["numeric lw ; lw := %N ;"](lw),
549 formatters["pickup pencircle scaled lw ;"](),
550 }
551 results[0] = {
552 final = true,
553 serial = 0,
554 trigger = "none"
555 }
556 do
557 local line = 0
558 local slot = 0
559 breaks[0] = { 0, 0 }
560 for i=1,#results do
561 local r = results[i]
562 local l = r.line
563 if l ~= line then
564 slot = 0
565 line = l
566 else
567 slot = slot + 1
568 end
569 breaks[i] = { slot, -l }
570 end
571 end
572 do
573 local result = { }
574 for i=1,#results do
575 local r = results[i]
576 local previous = r.previous
577 local p1 = breaks[i]
578 local p2 = breaks[previous]
579 if p1 and p2 then
580 result[#result+1] = formatters["((%N,%N)--(%N,%N))"](p1[1],p1[2],p2[1],p2[2])
581 end
582 end
583 everything[#everything+1] = formatters[ [[draw (% && t) xyscaled (dx,dy) withcolor "%s" ;]] ](result,colors.lines)
584 end
585 do
586 local result = { }
587 for i=0,#results do
588 local r = results[i]
589 if r.final then
590 local previous = r.previous
591 if previous and results[previous] and results[previous].final then
592 local p1 = breaks[i]
593 local p2 = breaks[previous]
594 if p1 and p2 then
595 result[#result+1] = formatters["((%N,%N)--(%N,%N))"](p1[1],p1[2],p2[1],p2[2])
596 end
597 end
598 end
599 end
600 everything[#everything+1] = formatters[ [[draw (% && t) xyscaled (dx,dy) withcolor "white" ;]] ](result)
601 everything[#everything+1] = formatters[ [[draw (% && t) xyscaled (dx,dy) dashed (evenly scaled .5lw) withcolor "black" ;]] ](result)
602 end
603 for i=0,#results do
604 local p = breaks[i]
605 if p then
606 local r = results[i]
607 everything[#everything+1] = formatters[ [[draw (%N*dx,%N*dy) withpen pencircle xyscaled(sx,sy) withcolor "%s" ;]] ](p[1],p[2],colors[r.trigger])
608 everything[#everything+1] = formatters[ [[draw textext("\ttxx%i") shifted (%N*dx,%N*dy) withcolor "white" ;]] ](r.serial,p[1],p[2])
609 end
610 end
611
612 return table.concat(everything,"\n")
613end
614 |