if not modules then modules = { } end modules ['spac-pas'] = { version = 1.001, comment = "companion to spac-pas.mkxl", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- My usual usical timestamp: early September 2024, Albion live in Alphen NL -- (as impressive as expected). local rawget, tonumber, type, unpack = rawget, tonumber, type, unpack local sortedhash = table.sortedhash local formatters = string.formatters local report = logs.reporter("linebreak","quality") local currentfile = luatex.currentfile local integer_value = tokens.values.integer local toobad = 20 local tracing = false ----- passnames = { } ----- nofidentifiers = 0 builders.passnames = utilities.storage.mark(builders.passnames or { [0] = "traditional" }) local passnames = builders.passnames local nofidentifiers = #passnames storage.register("builders/passnames", passnames, "builders.passnames") table.setmetatableindex(passnames,function(t,k) if type(k) == "number" and k >= 0 then return k else local i = rawget(t,k) if i then return i else nofidentifiers = nofidentifiers + 1 t[k] = nofidentifiers t[nofidentifiers] = k return nofidentifiers end end end) interfaces.implement { name = "parpassidentifier", public = true, -- protected = true, usage = "value", arguments = "string", actions = function(s) return integer_value, passnames[s] end, } local function parpassidentifier(n) return rawget(passnames,n) or n end builders.parpassidentifier = parpassidentifier local nuts = nodes.nuts local tonut = nuts.tonut local tonode = nuts.tonode local insertafter = nuts.insertafter local linebreakstates = tex.linebreakstatecodes local content_state = linebreakstates.content local text_state = linebreakstates.text local disc_state = linebreakstates.disc local math_state = linebreakstates.math local function tostate(state) return ((state & text_state ~= 0) and "t" or "-") .. ((state & disc_state ~= 0) and "d" or "-") .. ((state & math_state ~= 0) and "m" or "-") end local visualizepasses = false local summary = { } local f_3 = false local noffirst = 0 local nofsecond = 0 local nofthird = 0 local nofsubpasses = 0 local nofsubdone = 0 local function marker(par,pass,subpass,subpasses,state,identifier,name,collect) par = tonut(par) if not visualizepasses then visualizepasses = nodes.visualizers.register("passes","trace:dr","trace:dr",-1.5) nodes.visualizers.definelayer("passes") f_3 = formatters["subpass %02i"] end if pass == 1 then pass = "pretolerance" elseif pass == 2 then pass = "tolerance" elseif pass == 3 then -- pass = "emergency" pass = "final" elseif subpass == -2 then pass = "subpass pre" elseif subpass == -1 then pass = "subpass tol" else pass = f_3(subpass) end if collect then local s = summary[identifier] if not s then s = { nofpasses = subpasses, name = name, passes = { }, states = table.setmetatableindex("table"), } summary[identifier] = s end local p = s.passes ; p[pass] = (p[pass] or 0) + 1 local s = s.states ; s[pass][state] = (s[pass][state] or 0) + 1 end if type(name) == "string" or name > 0 then pass = name .. ":" .. pass end return tonode(visualizepasses(pass)) end local function linebreak_quality_console(par,id,pass,subpass,subpasses,state,overfull,underfull,verdict,classified,line) if (state & content_state) ~= 0 then local filename = currentfile() local passname = parpassidentifier(id) 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) return marker(par,pass,subpass,subpasses,state,id,passname,false) end end local function linebreak_quality_simple(par,id,pass,subpass,subpasses,state,overfull,underfull,verdict,classified,line) if (state & content_state) ~= 0 then local passname = parpassidentifier(id) return marker(par,pass,subpass,subpasses,state,id,passname,false) end end local function linebreak_quality_summary(par,id,pass,subpass,subpasses,state,overfull,underfull,verdict,classified,line) if (state & content_state) ~= 0 then local passname = parpassidentifier(id) if pass == 1 then noffirst = noffirst + 1 elseif pass == 2 then nofsecond = nofsecond + 1 elseif pass == 3 then nofthird = nofthird + 1 else nofsubpasses = nofsubpasses + 1 nofsubdone = nofsubdone + subpass end return marker(par,pass,subpass,subpasses,state,id,passname,true) end end logs.registerfinalactions(function() if tracing and next(summary) then logs.startfilelogging(report,"linebreak passes") report("") for identifier, s in sortedhash(summary) do local name = s.name or "" local nofpasses = s.nofpasses or 0 local passes = s.passes local states = s.states for pass, final in sortedhash(passes) do local t = { } for state, n in sortedhash(states[pass]) do t[#t+1] = n .. ":" .. tostate(state) end report(" identifier %i, name %a, pass %a, passes %i, count %04i, states % t",identifier,name,pass,nofpasses,final,t) end end report("") logs.stopfilelogging() end end) statistics.register("linebreak quality",function() if tracing then return string.formatters["%i first, %i second, %i third, %i subpasses, %i subruns"](noffirst, nofsecond, nofthird,nofsubpasses,nofsubdone) end end) local registercallback = callback.register trackers.register("paragraphs.passes", function(v) if not v then tracing = nil elseif v == "summary" then tracing = linebreak_quality_summary elseif v == "console" then tracing = linebreak_quality_console else tracing = linebreak_quality_simple end registercallback("linebreak_quality",tracing) end) -- trackers.register("paragraphs.passes.console", function(v) -- tracing = v and linebreak_quality_console or nil -- registercallback("linebreak_quality",tracing) -- end) -- trackers.register("paragraphs.passes.summary", function(v) -- tracing = v and linebreak_quality_summary or nil -- registercallback("linebreak_quality",tracing) -- end) -- -- -- -- local nuts = nodes.nuts local tonut = nodes.tonut local setprop = nuts.setprop local getprop = nuts.getprop local trace_callbacks = false trackers.register("paragraphs.passes.callbacks", function(v) trace_callbacks = v end) local actions = { } callback.register("paragraph_pass", function( head,identifier,subpass,callback, overfull,underfull,verdict,classified, threshold,badness,classes ) local action = actions[callback] if action then local head = tonut(head) local step = getprop(head,"par_pass_done") or 1 setprop(head,"par_pass_done",step+1) local result, again = action( head,parpassidentifier(identifier) or identifier,callback,step, overfull,underfull,verdict,classified, threshold,badness,classes ) if trace_callbacks then if type(result) == "table" then report("identifier %a, subpass %i, callback %a, %s",identifier,subpass,callback,"process with data") elseif result then report("identifier %a, subpass %i, callback %a, %s",identifier,subpass,callback,"process") else report("identifier %a, subpass %i, callback %a, %s",identifier,subpass,callback,"quit") end end return result, again else report("identifier %a, subpass %i, unknown callback %a",identifier,subpass,callback) return false end end) function builders.registerpaspasscallback(identifier,action) local index = parpassidentifier(identifier) actions[index] = action return index end interfaces.implement { name = "parpasscallback", public = true, usage = "value", arguments = "string", actions = function(s) return integer_value, passnames[s] end, }