1local next, tonumber, tostring, type = next, tonumber, tostring, type
2local round, abs = math.round, math.abs
3
4local settings_to_array = utilities.parsers.settings_to_array
5local settings_to_hash = utilities.parsers.settings_to_hash
6
7local nuts = nodes.nuts
8local tonut = nodes.tonut
9
10local newrule = nuts.pool.virtualrule
11local insertbefore = nuts.insertbefore
12local insertafter = nuts.insertafter
13local hpack_string = nuts.typesetters.tohpack
14local getwidth = nuts.getwidth
15local getid = nuts.getid
16local getprev = nuts.getprev
17local getnext = nuts.getnext
18local setwhd = nuts.setwhd
19local getglue = nuts.getglue
20local getlist = nuts.getlist
21local setoffsets = nuts.setoffsets
22
23local setcolor = nodes.tracers.colors.set
24local settransparency = nodes.tracers.transparencies.set
25
26local texgetnest = tex.getnest
27
28local nodecodes = nodes.nodecodes
29local fitnesscodes = tex.fitnesscodes
30
31local penalty_code = nodecodes.penalty
32local glue_code = nodecodes.glue
33local disc_code = nodecodes.disc
34local kern_code = nodecodes.kern
35local math_code = nodecodes.math
36
37local getnormalizedline = nuts.getnormalizedline
38local getdimensions = nuts.dimensions
39local getnaturalhsize = nuts.naturalhsize
40local setruledimensions = nuts.setruledimensions
41local getruledimensions = nuts.getruledimensions
42
43local breakpoints = tracers.breakpoints or { }
44tracers.breakpoints = breakpoints
45
46local width = 65536 / 2
47local height = 9 * 65536
48local depth = 3 * 65536
49local xoffset = 1 * 65536
50local yoffset = -3 * 65536
51
52local serials = false
53local serial = false
54local results = false
55local usedfont = false
56local nestlevel = false
57local overloads = false
58local overload = false
59local breakpass = 0
60local linenumber = 0
61
62local report = logs.reporter("linebreaks")
63
64local breakcodes = tex.breakcodes
65
66local f_status = string.formatters["[% 2i] b=%i d=%p p=%0.3f r=%0.3f (dt=%i) (%p)"]
67
68local maxbadness <const> = tex.magicconstants.maxcalculatedbadness
69local marginoffset = 0
70local showdetails = false
71
72local actions = {
73 [breakcodes.initialize] = function()
74 if texgetnest("ptr") == nestlevel then
75 report("start registering")
76 usedfont = nodes.visualizers.getusedfont()
77 end
78 end,
79 [breakcodes.start] = function(pass)
80 if texgetnest("ptr") == nestlevel then
81 report("tracing pass %i",pass)
82 if breakpass ~= pass then
83 breakpass = pass
84 linenumber = 0
85 serials = { }
86 results = { }
87 end
88 serial = { }
89 serials[#serials+1] = serial
90 if overloads then
91 overload = overloads[#serials]
92 end
93 end
94 end,
95 [breakcodes.report] = function(pass,currentserial,previousserial,line,kind,class,demerits,breakpoint,short,glue,width)
96 if texgetnest("ptr") == nestlevel then
97 if breakpoint then
98 local s = {
99 serial = currentserial,
100 previous = previousserial,
101 line = line,
102 kind = kind,
103 class = class,
104 demerits = demerits,
105 breakpoint = breakpoint,
106 short = short,
107 width = width,
108 spillover = 0,
109 }
110 if short < 0 then
111 short = -short
112 if short > glue then
113 local spillover = short - glue
114 report("pass %i, line %i, %s by %p",pass,line,"overfull",spillover)
115 s.spillover = splillover
116 end
117 elseif short > 0 then
118 if short > glue then
119 local spillover = short - glue
120 report("pass %i, line %i, %s by %p",pass,line,"underfull",spillover)
121 s.spillover = splillover
122 end
123 end
124 serial[currentserial] = s
125 local found = overload and overload[currentserial]
126 if found then
127 report("pass %i, overloading serial %i demerits from %i to %i",breakpass,currentserial,demerits,found)
128 s.overload = found
129 return found
130 end
131 end
132 end
133 return demerits
134 end,
135 [breakcodes.stop] = function()
136 if texgetnest("ptr") == nestlevel then
137 report("stop registering")
138 end
139 end,
140 [breakcodes.collect] = function()
141 if texgetnest("ptr") == nestlevel then
142 report("collecting")
143 for currentserial=1,#serial do
144 local data = serial[currentserial]
145 local breakpoint = data.breakpoint
146 local current = tonut(breakpoint)
147 local trigger = getid(current)
148 while current do
149 local id = getid(current)
150 if id == penalty_code or id == glue_code or id == kern_code or id == math_code then
151 current = getprev(current)
152 else
153 break
154 end
155 end
156 if current then
157 local rule = newrule(width,height,depth,currentserial)
158 data.rule = rule
159 data.trigger = trigger
160 insertafter(current,current,rule)
161 end
162 end
163 end
164 end,
165 [breakcodes.line] = function(line,badness,overshoot,shrink,stretch)
166 if showdetails and texgetnest("ptr") == nestlevel then
167 linenumber = linenumber + 1
168 line = tonut(line)
169 local linedata = getnormalizedline(line,true)
170 local natural = linedata.size
171 local linewd = linedata.width
172 local deltawd = linewd - natural
173 local splillover = linedata.spillover or 0
174 local ratiob = 0
175 local ratio, order, sign
176 = getglue(line)
177 local yoffset = 2*xoffset
178 local xoffset = 5*xoffset + linedata.right + marginoffset
179 if order == 0 then
180 if deltawd ~= 0 and ratio == 0 and sign == 0 then
181 xoffset = xoffset + deltawd
182 ratiob = 10000
183 elseif deltawd < 0 and ratio == 1 then
184 ratiob = 1000000
185 else
186 ratiob = round(100*abs(ratio)^3)
187 if ratiob > maxbadness then
188 ratiob = 10000
189 end
190 end
191 end
192
193
194 local text = hpack_string(f_status(linenumber,badness,deltawd,100*deltawd/linewd,ratio,ratiob),usedfont,spillover)
195 local location = linedata.last
196 local head = linedata.head
197 setwhd(text,0,0,0)
198 setoffsets(text,xoffset,yoffset)
199 insertbefore(head,location,text)
200 deltawd = deltawd - linedata.parinitrightskip - linedata.parfillrightskip
201 local rule = newrule(deltawd < 0 and - deltawd or deltawd,0,depth)
202 setoffsets(rule,deltawd > 0 and -deltawd or 0,yoffset+depth)
203 setcolor(rule,"trace:dy")
204 settransparency(rule,"trace:dy")
205 insertbefore(head,location,rule)
206 end
207 end,
208 [breakcodes.list] = function(currentserial)
209 if texgetnest("ptr") == nestlevel then
210 local s = serial[currentserial]
211 if s then
212 s.final = true
213 end
214 end
215 end,
216 [breakcodes.delete] = function(currentserial)
217
218 if texgetnest("ptr") == nestlevel then
219 local s = serial[currentserial]
220 if s then
221 s.deleted = true
222 end
223 end
224 end,
225 [breakcodes.wrapup] = function()
226 if texgetnest("ptr") == nestlevel then
227 report("wrapping up")
228 if serial then
229 local result = { }
230 results[#results+1] = result
231 for currentserial=1,#serial do
232 local data = serial[currentserial]
233 local rule = data.rule
234 if rule then
235 local text = hpack_string(tostring(currentserial),usedfont)
236 local size = getwidth(text)
237 local trigger = data.trigger
238 local final = data.final
239 local class = data.class
240 local color = "trace:ds"
241 if trigger == penalty_code then
242 color = "trace:dr"
243 elseif trigger == glue_code then
244 color = "trace:dg"
245 elseif trigger == disc_code then
246 color = "trace:db"
247 end
248 if final then
249 local width, height, depth = getruledimensions(rule)
250 setruledimensions(rule,2*width,height,depth)
251 end
252 setcolor(rule,color)
253 setwhd(text,0,0,0)
254 setoffsets(text,-size-xoffset,yoffset)
255 insertafter(rule,rule,text)
256 result[#result+1] = data
257 data.trigger = nodecodes[trigger]
258 data.class = fitnesscodes[class]
259 data.color = color
260 end
261 end
262
263
264
265 local finals = { }
266 for currentserial=#serial,1,-1 do
267 if serial[currentserial].final then
268 for finalserial=currentserial-1,currentserial+1 do
269 local data = serial[finalserial]
270 if data then
271 local line = data.line
272 local list = { }
273 local done = {
274 serial = finalserial,
275 line = line,
276 list = list,
277 final = data.final,
278 color = data.color
279 }
280 finals[#finals+1] = done
281 while true do
282 local previous = data.previous
283 if previous == 0 then
284 break
285 else
286 list[#list+1] = previous
287 data = serial[previous]
288 end
289 end
290 end
291 end
292 break
293 end
294 end
295
296 result.finals = finals
297
298 serial = false
299 end
300 end
301 end,
302}
303
304
305
306function breakpoints.start(specification)
307 callback.register("show_break", function(what,...)
308 return actions[what](...)
309 end)
310
311 local list = specification.list
312 local option = specification.option
313
314 breakpass = 0
315 linenumber = 0
316 showdetails = false
317 serials = { }
318 results = { }
319 nestlevel = texgetnest("ptr")
320 marginoffset = specification.offset or 0
321
322 if option then
323 option = type(options) == "table" and option or settings_to_hash(option or "")
324 if option.margin then
325 showdetails = true
326 end
327 end
328 if list then
329 overloads = type(list) == "table" and list or settings_to_array(list or "")
330 for i=1,#overloads do
331 local s = overloads[i]
332 local t = settings_to_hash(s)
333 local o = { }
334 for k, v in next, t do
335 o[tonumber(k)] = tonumber(v) or -1
336 end
337 overloads[i] = o
338 end
339 end
340end
341
342function breakpoints.stop()
343 callback.register("show_break")
344end
345
346function breakpoints.reset()
347 serials = false
348 serial = false
349 results = false
350 nestlevel = false
351 breakpass = 0
352 linenumber = 0
353end
354
355function breakpoints.getresults()
356 return results or { }
357end
358
359function breakpoints.nofresults()
360 return results and #results or 0
361end
362
363function breakpoints.typesetresult(n)
364 local context, ctx_NC, ctx_NR, ctx_color = context, context.NC, context.NR, context.color
365
366 local function typeset(result)
367 if result then
368 local lastline = 0
369
370 context.starttabulate { "|r|r|r|r|l|l|c|" }
371 for i=1,#result do
372 local r = result[i]
373 local serial = r.serial
374 local previous = r.previous
375 local line = r.line
376 local final = r.final
377
378 local overload = r.overload
379 local demerits = overload or r.demerits
380 local trigger = r.trigger
381 local class = r.class
382 local short = r.short
383 local color = { r.color }
384 ctx_NC() if line ~= lastline then context(line) lastline = line end
385
386 ctx_NC() if final then ctx_color(color,serial) else context(serial) end
387 ctx_NC() context(previous)
388 ctx_NC() if final then ctx_color(color,demerits) else context(demerits) end
389 ctx_NC() if final then ctx_color(color,class) else context(class) end
390 ctx_NC() ctx_color(color,trigger)
391 ctx_NC() if overload then if final then ctx_color(color,"!") else context("!") end end
392 ctx_NC() ctx_NR()
393 end
394 context.stoptabulate()
395
396 local finals = result.finals
397 if finals then
398 context.starttabulate { "|r|l|" }
399 for i=1,#finals do
400 local data = finals[i]
401 local final = data.final
402 local serial = data.serial
403 local path = table.concat(data.list," ")
404 local color = final and data.color
405 if color then
406 color = { color }
407 end
408 ctx_NC() if color then ctx_color(color,serial) else context(serial) end
409 ctx_NC() if color then ctx_color(color,path) else context(path) end
410 ctx_NC() ctx_NR()
411 end
412 context.stoptabulate()
413 end
414 end
415 end
416
417 local results = breakpoints.getresults()
418 if n then
419 typeset(results[n])
420 else
421 for i=1,#results do
422 typeset(results[i])
423 end
424 end
425end
426 |