spac-pas.lmt /size: 9046 b    last modification: 2025-02-21 11:03
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-- My usual usical timestamp: early September 2024, Albion live in Alphen NL
10-- (as impressive as expected).
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----- passnames      = { }
26----- nofidentifiers = 0
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 -- protected = true,
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     -- pass = "emergency"
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-- trackers.register("paragraphs.passes.console", function(v)
213--     tracing = v and linebreak_quality_console or nil
214--     registercallback("linebreak_quality",tracing)
215-- end)
216
217-- trackers.register("paragraphs.passes.summary", function(v)
218--     tracing = v and linebreak_quality_summary or nil
219--     registercallback("linebreak_quality",tracing)
220-- end)
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