if modules then modules = { } end modules ['typo-bld'] = { -- was node-par version = 1.001, comment = "companion to typo-bld.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- no need for nuts in the one-line demo (that might move anyway) local insert, remove = table.insert, table.remove builders = builders or { } local builders = builders builders.paragraphs = builders.paragraphs or { } local parbuilders = builders.paragraphs parbuilders.constructors = parbuilders.constructors or { } local constructors = parbuilders.constructors builders.hpack = builders.hpack or { } builders.vpack = builders.vpack or { } local hpackbuilders = builders.hpack local vpackbuilders = builders.vpack constructors.names = constructors.names or { } local names = constructors.names constructors.numbers = constructors.numbers or { } local numbers = constructors.numbers constructors.methods = constructors.methods or { } local methods = constructors.methods local a_parbuilder = attributes.numbers['parbuilder'] or 999 -- why 999 constructors.attribute = a_parbuilder local unsetvalue = attributes.unsetvalue local texsetattribute = tex.setattribute local texnest = tex.nest local texget = tex.get local texset = tex.set local getspeciallist = nodes.nuts.getspeciallist local setspeciallist = nodes.nuts.setspeciallist local nodes = nodes local nodeidstostring = nodes.idstostring local nodepool = nodes.pool local new_baselineskip = nodepool.baselineskip local new_lineskip = nodepool.lineskip local insertnodebefore = nodes.insertbefore local hpack_node = nodes.hpack local nuts = nodes.nuts local tonode = nodes.tonode local tonut = nodes.tonut local getattr = nuts.getattr local flush = nuts.flush local starttiming = statistics.starttiming local stoptiming = statistics.stoptiming local registercallback = callbacks.register storage.register("builders/paragraphs/constructors/names", names, "builders.paragraphs.constructors.names") storage.register("builders/paragraphs/constructors/numbers", numbers, "builders.paragraphs.constructors.numbers") local trace_page_builder = false trackers.register("builders.page", function(v) trace_page_builder = v end) local trace_vbox_builder = false trackers.register("builders.vbox", function(v) trace_vbox_builder = v end) local trace_post_builder = false trackers.register("builders.post", function(v) trace_post_builder = v end) local report_page_builder = logs.reporter("builders","page") local report_vbox_builder = logs.reporter("builders","vbox") local report_par_builder = logs.reporter("builders","par") local mainconstructor = nil -- not stored in format local nofconstructors = 0 local stack = { } function constructors.define(name) nofconstructors = nofconstructors + 1 names[nofconstructors] = name numbers[name] = nofconstructors end function constructors.set(name) --- will go if name then mainconstructor = numbers[name] or unsetvalue else mainconstructor = stack[#stack] or unsetvalue end texsetattribute(a_parbuilder,mainconstructor) if mainconstructor ~= unsetvalue then constructors.enable() end end function constructors.start(name) local number = numbers[name] insert(stack,number) mainconstructor = number or unsetvalue texsetattribute(a_parbuilder,mainconstructor) if mainconstructor ~= unsetvalue then constructors.enable() end -- report_par_builder("start %a",name) end function constructors.stop() remove(stack) mainconstructor = stack[#stack] or unsetvalue texsetattribute(a_parbuilder,mainconstructor) if mainconstructor == unsetvalue then constructors.disable() end -- report_par_builder("stop") end -- return values: -- -- true : tex will break itself -- false : idem but dangerous -- head : list of valid vmode nodes with last being hlist function constructors.handler(head,followed_by_display) if type(head) == "boolean" then return head else local attribute = getattr(head,a_parbuilder) -- or mainconstructor if attribute then local method = names[attribute] if method then local handler = methods[method] if handler then return handler(head,followed_by_display) else report_par_builder("contructor method %a is not defined",tostring(method)) return true -- let tex break end end end return true -- let tex break end end -- just for testing function constructors.methods.default(head,followed_by_display) return true -- let tex break end -- also for testing (now also surrounding spacing done) function parbuilders.constructors.methods.oneline(head,followed_by_display) -- when needed we will turn this into a helper local t = texnest[texnest.ptr] local h = hpack_node(head) local d = texget("baselineskip",false) - t.prevdepth - h.height t.prevdepth = h.depth t.prevgraf = 1 if d < texget("lineskiplimit") then return insertnodebefore(h,h,new_lineskip(texget("lineskip",false))) -- no stretch etc else return insertnodebefore(h,h,new_baselineskip(d)) end end -- It makes no sense to have a sequence here as we already have -- pre and post hooks and only one parbuilder makes sense, so no: -- -- local actions = nodes.tasks.actions("parbuilders") -- -- yet ... maybe some day. local actions = constructors.handler local enabled = false local function processor(head,followed_by_display) -- todo: not again in otr so we need to flag if enabled then starttiming(parbuilders) head = tonode(actions(tonut(head),followed_by_display)) stoptiming(parbuilders) return head else return true -- let tex do the work end end function constructors.enable () enabled = true end function constructors.disable() enabled = false end registercallback("linebreak_filter", processor, "breaking paragraps into lines") statistics.register("linebreak processing time", function() return statistics.elapsedseconds(parbuilders) end) -- todo: move from nodes.builders to builders nodes.builders = nodes.builder or { } local builders = nodes.builders local vpackactions = nodes.tasks.actions("vboxbuilders") function builders.vpack_filter(head,groupcode,size,packtype,maxdepth,direction) if head then starttiming(builders) head = vpackactions(head,groupcode) stoptiming(builders) end return head end -- This one is special in the sense that it has no head and we operate on the mlv. Also, -- we need to do the vspacing last as it removes items from the mvl. local pageactions = nodes.tasks.actions("mvlbuilders") ----- lineactions = nodes.tasks.actions("linebuilders") local function report(pagecontext,head) report_page_builder("trigger: %s at level %i",pagecontext,texnest.ptr) report_page_builder(" vsize : %p",texget("vsize")) report_page_builder(" pagegoal : %p",texget("pagegoal")) report_page_builder(" pagetotal: %p",texget("pagetotal")) report_page_builder(" list : %s",head and nodeidstostring(head) or "") end -- Watch out: contributehead can be any head (kind of) not per se the page one -- but that needs to be intercepted when needed by groupcode and level. At some -- point this one might be split by group. function builders.buildpage_filter(pagecontext) local head, tail = getspeciallist("contributehead") if head then if trace_page_builder then report(pagecontext,head) end starttiming(builders) head = pageactions(head,pagecontext) -- todo: tail stoptiming(builders) setspeciallist("contributehead", head) else if trace_page_builder then report(pagecontext) end end end registercallback("vpack_filter", builders.vpack_filter, "vertical spacing etc") registercallback("buildpage_filter", builders.buildpage_filter, "vertical spacing etc (mvl)") local vboxactions = nodes.tasks.actions("vboxhandlers") function builders.vbox_filter(head,groupcode) if head then starttiming(builders) head = vboxactions(head,groupcode) stoptiming(builders) end return head end registercallback("packed_vbox_filter", builders.vbox_filter, "packed vbox treatments") statistics.register("v-node processing time", function() return statistics.elapsedseconds(builders) end) local implement = interfaces.implement implement { name = "defineparbuilder", actions = constructors.define, arguments = "string" } implement { name = "setparbuilder", actions = constructors.set, arguments = "string" } implement { name = "startparbuilder", actions = constructors.start, arguments = "string" } implement { name = "stopparbuilder", actions = constructors.stop } implement { name = "enableparbuilder", actions = constructors.enable } implement { name = "disableparbuilder", actions = constructors.disable } -- Here are some tracers: local nuts = nodes.nuts local tonut = nodes.tonut local setcolor = nodes.tracers.colors.set local listtoutf = nodes.listtoutf local new_kern = nuts.pool.kern local new_rule = nuts.pool.rule local hpack = nuts.hpack local getheight = nuts.getheight local getdepth = nuts.getdepth local getdirection = nuts.getdirection local getlist = nuts.getlist local setwidth = nuts.setwidth local setdirection = nuts.setdirection local setlink = nuts.setlink local tonode = nuts.tonode local list = { } local report_quality = logs.reporter("pack quality") -- overflow|badness w h d dir local report = true trackers.register("builders.vpack.quality", function(v) report = v end) local collect = false trackers.register("builders.vpack.collect", function(v) collect = v end) ---- show = false trackers.register("builders.vpack.overflow",function(v) show = v end) function vpackbuilders.report(how,detail,n,first,last,filename) -- no return value if report then if last <= 0 then report_quality("%s vbox",how) elseif first > 0 and first < last then report_quality("%s vbox at line %i - %i in file %a",how,first,last,filename or "?") else report_quality("%s vbox at line %i in file %a",how,last,filename or "?") end end if collect then list[#list+1] = { "hbox", how, filename, first, last, how, detail } end end -- function builders.vpack.show(how,detail,n,first,last,filename) -- return value -- if show then -- end -- end local report = true trackers.register("builders.hpack.quality", function(v) report = v end) local collect = false trackers.register("builders.hpack.collect", function(v) collect = v end) local show = false trackers.register("builders.hpack.overflow",function(v) show = v end) function hpackbuilders.report(how,detail,n,first,last,filename) -- no return value local str = (report or collect) and listtoutf(getlist(n),"",true,nil,true) if report then local overfull = how == "overfull" if last <= 0 then report_quality( overfull and "%s hbox: %s (%p)" or "%s hbox: %s (badness %i)", how,str,detail ) elseif first > 0 and first < last then report_quality( overfull and "%s hbox at line %i - %i in file %a: %s (%p)" or "%s hbox at line %i - %i in file %a: %s (badness %i)", how,first,last,filename or "?",str,detail ) else report_quality( overfull and "%s hbox at line %i in file %a: %s (%p)" or "%s hbox at line %i in file %a: %s (badness %i)", how,last,filename or "?",str,detail ) end end if collect then list[#list+1] = { "hbox", how, filename, first, last, str, detail } end end function hpackbuilders.show(how,detail,n,first,last,filename,rule) -- return value if show then local width = 2*65536 local height = getheight(n) local depth = getdepth(n) local direction = getdirection(n) if height < 4*65526 then height = 4*65526 end if depth < 2*65526 then depth = 2*65526 end if rule then flush(rule) end rule = new_rule(width,height,depth) setdirection(rule,direction) if how == "overfull" then setcolor(rule,"red") local kern = new_kern(-detail) setlink(kern,rule) rule = kern elseif how == "underfull" then setcolor(rule,"blue") elseif how == "loose" then setcolor(rule,"magenta") elseif how == "tight" then setcolor(rule,"cyan") end rule = hpack(rule) setwidth(rule,0) -- maybe better whd all zero setdirection(rule,direction) else local width = texget("overfullrule") if width > 0 then rule = new_rule(width) end end return rule end -- local hqualityactions = nodes.tasks.actions("hquality") local vqualityactions = nodes.tasks.actions("vquality") function hpackbuilders.qualityactions(how,detail,n,first,last,filename) local rul = nil -- starttiming(builders) rul = hqualityactions(how,detail,n,first,last,filename) -- stoptiming(builders) return rul end function vpackbuilders.qualityactions(how,detail,n,first,last,filename) local rul = nil -- starttiming(builders) rul = vqualityactions(how,detail,n,first,last,filename) -- stoptiming(builders) return rul end registercallback("hpack_quality", hpackbuilders.qualityactions, "report horizontal packing quality") registercallback("vpack_quality", vpackbuilders.qualityactions, "report vertical packing quality") statistics.register("quality reports", function() local n = #list if n > 0 then local t = table.setmetatableindex("number") local fw = 0 local hw = 0 for i=1,n do local f = list[i][1] local h = list[i][2] if #f > fw then fw = #f end if #h > hw then hw = #h end t[h] = t[h] + 1 end logs.startfilelogging(report_quality) for i=1,n do local l = list[i] local how = l[2] report_quality( how == "overfull" and "%-" .. fw .. "s [%04i - %04i] : %-" .. hw .. "s %s : %s (%p)" or "%-" .. fw .. "s [%04i - %04i] : %-" .. hw .. "s %s : %s (badness %i)", file.basename(l[3]),l[4],l[5],how,l[1],l[6],l[7] ) end logs.stopfilelogging() report_quality() report_quality("%i entries added to the log file : %s",n,table.sequenced(t)) report_quality() end end)