1if not modules then modules = { } end modules ['spac-pas'] = {
2 version = 1.001,
3 comment = "companion to spac-pas.mkxl",
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
11
12local rawget, tonumber, type, unpack = rawget, tonumber, type, unpack
13local sortedhash = table.sortedhash
14local formatters = string.formatters
15
16local report = logs.reporter("linebreak","quality")
17
18local currentfile = luatex.currentfile
19
20local integer_value <const> = tokens.values.integer
21
22local toobad <const> = 20
23local tracing = false
24
25
26
27
28builders.passnames = utilities.storage.mark(builders.passnames or { [0] = "traditional" })
29local passnames = builders.passnames
30local nofidentifiers = #passnames
31
32storage.register("builders/passnames", passnames, "builders.passnames")
33
34table.setmetatableindex(passnames,function(t,k)
35 if type(k) == "number" and k >= 0 then
36 return k
37 else
38 local i = rawget(t,k)
39 if i then
40 return i
41 else
42 nofidentifiers = nofidentifiers + 1
43 t[k] = nofidentifiers
44 t[nofidentifiers] = k
45 return nofidentifiers
46 end
47 end
48end)
49
50interfaces.implement {
51 name = "parpassidentifier",
52 public = true,
53
54 usage = "value",
55 arguments = "string",
56 actions = function(s)
57 return integer_value, passnames[s]
58 end,
59}
60
61local function parpassidentifier(n)
62 return rawget(passnames,n) or n
63end
64
65builders.parpassidentifier = parpassidentifier
66
67local nuts = nodes.nuts
68local tonut = nuts.tonut
69local tonode = nuts.tonode
70local insertafter = nuts.insertafter
71
72local linebreakstates = tex.linebreakstatecodes
73local content_state <const> = linebreakstates.content
74local text_state <const> = linebreakstates.text
75local disc_state <const> = linebreakstates.disc
76local math_state <const> = linebreakstates.math
77
78local function tostate(state)
79 return
80 ((state & text_state ~= 0) and "t" or "-") ..
81 ((state & disc_state ~= 0) and "d" or "-") ..
82 ((state & math_state ~= 0) and "m" or "-")
83end
84
85local visualizepasses = false
86local summary = { }
87local f_3 = false
88
89local noffirst = 0
90local nofsecond = 0
91local nofthird = 0
92local nofsubpasses = 0
93local nofsubdone = 0
94
95local function marker(par,pass,subpass,subpasses,state,identifier,name,collect)
96 par = tonut(par)
97 if not visualizepasses then
98 visualizepasses = nodes.visualizers.register("passes","trace:dr","trace:dr",-1.5)
99 nodes.visualizers.definelayer("passes")
100 f_3 = formatters["subpass %02i"]
101 end
102 if pass == 1 then
103 pass = "pretolerance"
104 elseif pass == 2 then
105 pass = "tolerance"
106 elseif pass == 3 then
107
108 pass = "final"
109 elseif subpass == -2 then
110 pass = "subpass pre"
111 elseif subpass == -1 then
112 pass = "subpass tol"
113 else
114 pass = f_3(subpass)
115 end
116 if collect then
117 local s = summary[identifier]
118 if not s then
119 s = {
120 nofpasses = subpasses,
121 name = name,
122 passes = { },
123 states = table.setmetatableindex("table"),
124 }
125 summary[identifier] = s
126 end
127 local p = s.passes ; p[pass] = (p[pass] or 0) + 1
128 local s = s.states ; s[pass][state] = (s[pass][state] or 0) + 1
129 end
130 if type(name) == "string" or name > 0 then
131 pass = name .. ":" .. pass
132 end
133 return tonode(visualizepasses(pass))
134end
135
136local function linebreak_quality_console(par,id,pass,subpass,subpasses,state,overfull,underfull,verdict,classified,line)
137 if (state & content_state) ~= 0 then
138 local filename = currentfile()
139 local passname = parpassidentifier(id)
140 report("file %a, line %i, pass %i, subpass %i of %i, state %s, id %a, overfull %p, underfull %p, verdict %i, classified 0x%X",filename,line,pass,subpass,subpasses,tostate(state),passname,overfull,underfull,verdict,classified)
141 return marker(par,pass,subpass,subpasses,state,id,passname,false)
142 end
143end
144
145local function linebreak_quality_simple(par,id,pass,subpass,subpasses,state,overfull,underfull,verdict,classified,line)
146 if (state & content_state) ~= 0 then
147 local passname = parpassidentifier(id)
148 return marker(par,pass,subpass,subpasses,state,id,passname,false)
149 end
150end
151
152local function linebreak_quality_summary(par,id,pass,subpass,subpasses,state,overfull,underfull,verdict,classified,line)
153 if (state & content_state) ~= 0 then
154 local passname = parpassidentifier(id)
155 if pass == 1 then
156 noffirst = noffirst + 1
157 elseif pass == 2 then
158 nofsecond = nofsecond + 1
159 elseif pass == 3 then
160 nofthird = nofthird + 1
161 else
162 nofsubpasses = nofsubpasses + 1
163 nofsubdone = nofsubdone + subpass
164 end
165 return marker(par,pass,subpass,subpasses,state,id,passname,true)
166 end
167end
168
169logs.registerfinalactions(function()
170 if tracing and next(summary) then
171 logs.startfilelogging(report,"linebreak passes")
172 report("")
173 for identifier, s in sortedhash(summary) do
174 local name = s.name or ""
175 local nofpasses = s.nofpasses or 0
176 local passes = s.passes
177 local states = s.states
178 for pass, final in sortedhash(passes) do
179 local t = { }
180 for state, n in sortedhash(states[pass]) do
181 t[#t+1] = n .. ":" .. tostate(state)
182 end
183 report(" identifier %i, name %a, pass %a, passes %i, count %04i, states % t",identifier,name,pass,nofpasses,final,t)
184 end
185 end
186 report("")
187 logs.stopfilelogging()
188 end
189end)
190
191statistics.register("linebreak quality",function()
192 if tracing then
193 return string.formatters["%i first, %i second, %i third, %i subpasses, %i subruns"](noffirst, nofsecond, nofthird,nofsubpasses,nofsubdone)
194 end
195end)
196
197local registercallback = callback.register
198
199trackers.register("paragraphs.passes", function(v)
200 if not v then
201 tracing = nil
202 elseif v == "summary" then
203 tracing = linebreak_quality_summary
204 elseif v == "console" then
205 tracing = linebreak_quality_console
206 else
207 tracing = linebreak_quality_simple
208 end
209 registercallback("linebreak_quality",tracing)
210end)
211
212
213
214
215
216
217
218
219
220
221
222
223
224local nuts = nodes.nuts
225local tonut = nodes.tonut
226local setprop = nuts.setprop
227local getprop = nuts.getprop
228
229local trace_callbacks = false
230
231trackers.register("paragraphs.passes.callbacks", function(v)
232 trace_callbacks = v
233end)
234
235local actions = { }
236
237callback.register("paragraph_pass", function(
238 head,identifier,subpass,callback,
239 overfull,underfull,verdict,classified,
240 threshold,badness,classes
241 )
242 local action = actions[callback]
243 if action then
244 local head = tonut(head)
245 local step = getprop(head,"par_pass_done") or 1
246 setprop(head,"par_pass_done",step+1)
247 local result, again = action(
248 head,parpassidentifier(identifier) or identifier,callback,step,
249 overfull,underfull,verdict,classified,
250 threshold,badness,classes
251 )
252 if trace_callbacks then
253 if type(result) == "table" then
254 report("identifier %a, subpass %i, callback %a, %s",identifier,subpass,callback,"process with data")
255 elseif result then
256 report("identifier %a, subpass %i, callback %a, %s",identifier,subpass,callback,"process")
257 else
258 report("identifier %a, subpass %i, callback %a, %s",identifier,subpass,callback,"quit")
259 end
260 end
261 return result, again
262 else
263 report("identifier %a, subpass %i, unknown callback %a",identifier,subpass,callback)
264 return false
265 end
266end)
267
268function builders.registerpaspasscallback(identifier,action)
269 local index = parpassidentifier(identifier)
270 actions[index] = action
271 return index
272end
273
274interfaces.implement {
275 name = "parpasscallback",
276 public = true,
277 usage = "value",
278 arguments = "string",
279 actions = function(s)
280 return integer_value, passnames[s]
281 end,
282}
283
284 |