if not modules then modules = { } end modules ['trac-brk'] = { version = 1.001, optimize = true, comment = "companion to trac-brk.mkxl", author = "Hans Hagen & Mikael Sundqvist", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local next, tonumber, tostring, type = next, tonumber, tostring, type local round, abs = math.round, math.abs local formatters = string.formatters local settings_to_array = utilities.parsers.settings_to_array local settings_to_hash = utilities.parsers.settings_to_hash local nuts = nodes.nuts local tonut = nodes.tonut local newrule = nuts.pool.virtualrule local insertbefore = nuts.insertbefore local insertafter = nuts.insertafter local hpack_string = nuts.typesetters.tohpack local getwidth = nuts.getwidth local getid = nuts.getid local getprev = nuts.getprev local getnext = nuts.getnext local setwhd = nuts.setwhd local getglue = nuts.getglue local getlist = nuts.getlist local setoffsets = nuts.setoffsets local setattr = nuts.setattr local setcolor = nodes.tracers.colors.set local settransparency = nodes.tracers.transparencies.set local texgetnest = tex.getnest local nodecodes = nodes.nodecodes ----- fitnesscodes = tex.fitnesscodes local penalty_code = nodecodes.penalty local glyph_code = nodecodes.glyph local glue_code = nodecodes.glue local disc_code = nodecodes.disc local kern_code = nodecodes.kern local math_code = nodecodes.math local a_tagged = attributes.private('tagged') local getnormalizedline = nuts.getnormalizedline local getdimensions = nuts.dimensions local getnaturalhsize = nuts.naturalhsize local setruledimensions = nuts.setruledimensions local getruledimensions = nuts.getruledimensions local breakpoints = tracers.breakpoints or { } tracers.breakpoints = breakpoints local width = 65536 / 2 local height = 9 * 65536 local depth = 3 * 65536 local xoffset = 1 * 65536 local yoffset = -3 * 65536 local serials = false local serial = false local results = false local usedfont = false local nestlevel = false local overloads = false local overload = false local breakpass = 0 local breaksubpass = 0 local breaksubpasses = 0 local linenumber = 0 local decentfit = 0 local fitnesscodes = { [4] = { "veryloose", "loose", "decent", "tight", }, [5] = { "veryloose", "loose", "decent", "tight", "verytight", }, [9] = { "veryloose", "loose", "almostloose", "barelyloose", "decent", "barelytight", "almosttight", "tight", "verytight", }, } local report = logs.reporter("linebreaks") local breakcodes = tex.breakcodes local f_detail = formatters["[%02i] b=%i d=%p p=%0.3f r=%0.3f (dt=%i) (%p) from s=%i b=%i"] local f_simple = formatters["[%02i] b=%i from s=%i b=%i"] local f_badness = formatters["[%02i] b=%i"] local maxbadness = tex.magicconstants.maxcalculatedbadness local marginoffset = 0 local showdetails = false local showsimple = false local showrefcount = true local showactions = { [breakcodes.initialize] = function(checks,subpasses) if texgetnest("ptr") == nestlevel then report("initialize") usedfont = nodes.visualizers.getusedfont() breaksubpasses = subpasses end end, [breakcodes.start] = function(checks,pass,subpass,classes,decent) if texgetnest("ptr") == nestlevel then report("start pass %i, subpass %i",pass,subpass) if breakpass ~= pass and breaksubpass ~= subpass then breakpass = pass breaksubpass = subpass linenumber = 0 decentfit = decent serials = { } results = { } end serial = { } serials[#serials+1] = serial if overloads then overload = overloads[#serials] end end end, [breakcodes.report] = function(checks,pass,subpass,currentserial,previousserial,line,kind,class,classes,badness,demerits,breakpoint,short,glue,width) if texgetnest("ptr") == nestlevel then if breakpoint then local s = { serial = currentserial, previous = previousserial, line = line, kind = kind, class = class, classes = classes, badness = badness, demerits = demerits, breakpoint = breakpoint, short = short, width = width, spillover = 0, refcount = 0, } if short < 0 then short = -short if short > glue then local spillover = short - glue report("pass %i, subpass %i, line %i, %s by %p",pass,subpass,line,"overfull",spillover) s.spillover = splillover end elseif short > 0 then if short > glue then local spillover = short - glue report("pass %i, subpass %i, line %i, %s by %p",pass,subpass,line,"underfull",spillover) s.spillover = splillover end end serial[currentserial] = s local found = overload and overload[currentserial] if found then report("pass %i, subpass %i, overloading serial %i demerits from %i to %i",pass,subpass,currentserial,demerits,found) s.overload = found return found end end end return demerits end, [breakcodes.stop] = function(checks,demerits) if texgetnest("ptr") == nestlevel then report("stop with demerits %i",demerits) end end, [breakcodes.collect] = function() if texgetnest("ptr") == nestlevel then report("collect") for currentserial=1,#serial do local data = serial[currentserial] local breakpoint = data.breakpoint local current = tonut(breakpoint) local trigger = getid(current) while current do local id = getid(current) if id == penalty_code or id == glue_code or id == kern_code or id == math_code then current = getprev(current) else break end end if current then local rule = newrule(width,height,depth,currentserial) data.rule = rule data.trigger = trigger insertafter(current,current,rule) end end end end, [breakcodes.line] = function(checks,line,badness,overshoot,shrink,stretch,linum,ser) if showdetails and texgetnest("ptr") == nestlevel then linenumber = linenumber + 1 line = tonut(line) local linedata = getnormalizedline(line,true) -- get more details local natural = linedata.size local linewd = linedata.width local deltawd = linewd - natural local splillover = linedata.spillover or 0 local ratiob = 0 local ratio, order, sign = getglue(line) local yoffset = 2*xoffset local xoffset = 5*xoffset + linedata.right + marginoffset local serialdata = serial[ser] -- what if we have multiple pars if order == 0 then if deltawd ~= 0 and ratio == 0 and sign == 0 then xoffset = xoffset + deltawd ratiob = 10000 elseif deltawd < 0 and ratio == 1 then ratiob = 1000000 else -- or test for ratio as maxbadness is 8189 ratiob = round(100*abs(ratio)^3) if ratiob > maxbadness then ratiob = 10000 end end end -- Here ratio is the value from Digital Typography and a difference -- indicates a sensitive area. It is just a playground for MS and HH. -- For instance, applied expansion can influence the badness. local sbadness = serialdata and serialdata.badness or 0 local text = showsimple == 1 and f_simple(linenumber,badness,ser,sbadness) or showsimple == 2 and f_badness(linenumber,sbadness) or f_detail(linenumber,badness,deltawd,100*deltawd/linewd,ratio,ratiob,spillover,ser,sbadness) local location = linedata.last local head = linedata.head text = hpack_string(text,usedfont,spillover) setattr(text,a_tagged,0) setwhd(text,0,0,0) setoffsets(text,xoffset,yoffset) insertbefore(head,location,text) deltawd = deltawd - linedata.parinitrightskip - linedata.parfillrightskip local rule = newrule(deltawd < 0 and - deltawd or deltawd,0,depth) setoffsets(rule,deltawd > 0 and -deltawd or 0,yoffset+depth) setcolor(rule,"trace:6") -- darkyellow settransparency(rule,"trace:6") -- darkyellow insertbefore(head,location,rule) end end, [breakcodes.list] = function(checks,currentserial,refcount) if texgetnest("ptr") == nestlevel then local s = serial[currentserial] if s then s.final = true -- s.refcount = refcount -- can be messed up so we set it again end end end, [breakcodes.delete] = function(checks,currentserial,refcount) if texgetnest("ptr") == nestlevel then local s = serial[currentserial] if s then s.refcount = refcount end end end, [breakcodes.wrapup] = function(checks,demerits,looseness) if texgetnest("ptr") == nestlevel then report("wrapup") if serial then local finals = { } local result = { looseness = looseness, demerits = demerits, finals = finals, pass = breakpass, subpass = breaksubpass, subpasses = breaksubpasses, } results[#results+1] = result for currentserial=1,#serial do local data = serial[currentserial] local rule = data.rule if rule then local text = hpack_string(tostring(currentserial),usedfont) local size = getwidth(text) local trigger = data.trigger local final = data.final local class = data.class local codes = fitnesscodes[data.classes] local color = "trace:0" -- darkgray if trigger == penalty_code then color = "trace:1" -- darkred elseif trigger == glue_code then color = "trace:2" -- darkgreen elseif trigger == disc_code then color = "trace:3" -- darkblue end if final then local width, height, depth = getruledimensions(rule) setruledimensions(rule,2*width,height,depth) end setattr(text,a_tagged,0) setcolor(rule,color) setwhd(text,0,0,0) setoffsets(text,-size-xoffset,yoffset) insertafter(rule,rule,text) result[#result+1] = data data.trigger = nodecodes[trigger] data.class = codes and codes[class] or ("class " .. class) data.color = color end end -- if #serial > 0 then local lastline = serial[#serial].line for currentserial=#serial,1,-1 do local data = serial[currentserial] if data.refcount == 0 then local list = { } local done = { serial = currentserial, line = data.line, list = list, final = data.final, color = data.color } finals[#finals+1] = done while true do local previous = data.previous if previous == 0 then break else list[#list+1] = previous data = serial[previous] end end else break end end end table.reverse(finals) -- serial = false end end end, } -- -- -- -- local enabled = false local function check_options(option) if option then option = type(option) == "table" and option or settings_to_hash(option or "") if option.margin then showdetails = true end if option.simple then showsimple = 1 elseif option.badness then showsimple = 2 end if option.compact then showrefcount = false end end end function breakpoints.start(specification) local list = specification.list breakpass = 0 linenumber = 0 showdetails = false showsimple = false showrefcount = true serials = { } results = { } nestlevel = texgetnest("ptr") + (tonumber(specification.level) or 0) -- hack marginoffset = specification.offset or 0 check_options(specification.option) if list then overloads = type(list) == "table" and list or settings_to_array(list or "") for i=1,#overloads do local s = overloads[i] local t = settings_to_hash(s) local o = { } for k, v in next, t do o[tonumber(k)] = tonumber(v) or -1 end overloads[i] = o end end end function breakpoints.stop() -- enabled = false end nodes.handlers.linebreakchecks[1] = function(what,...) return showactions[what](...) end function breakpoints.reset() serials = false serial = false results = false nestlevel = false breakpass = 0 linenumber = 0 end function breakpoints.getresults() return results or { } end function breakpoints.nofresults() return results and #results or 0 end function breakpoints.typesetresult(n,option) local context, ctx_NC, ctx_NR, ctx_EQ, ctx_color = context, context.NC, context.NR, context.EQ, context.color check_options(option) local function typeset(result) if result then local lastline = 0 context.starttabulate { showrefcount and "|r|rS|r|r|r|r|l|l|c|" or "|r|r|r|r|r|l|l|c|" } for i=1,#result do local r = result[i] local serial = r.serial local previous = r.previous local line = r.line local final = r.final local refcount = r.refcount or 0 local overload = r.overload local demerits = overload or r.demerits local badness = r.badness local trigger = r.trigger local class = r.class local short = r.short local color = { r.color } -- cache ctx_NC() if line ~= lastline then lastline = line context(line) end if showrefcount then ctx_NC() if refcount > 0 then if final then ctx_color(color,refcount) else context(refcount) end end end ctx_NC() if final then ctx_color(color,serial) else context(serial) end ctx_NC() if final then ctx_color(color,previous) else context(previous) end ctx_NC() if final then ctx_color(color,badness) else context(badness) end ctx_NC() if final then ctx_color(color,demerits) else context(demerits) end ctx_NC() if final then ctx_color(color,class) else context(class) end ctx_NC() ctx_color(color,trigger) ctx_NC() if overload then if final then ctx_color(color,"!") else context("!") end end ctx_NC() ctx_NR() end context.stoptabulate() -- local finals = result.finals if finals then -- We could limit the number ... let's see what MS asks for that feature. context.starttabulate { "|r|lp|" } for i=1,#finals do local data = finals[i] local final = data.final local serial = data.serial local path = table.concat(data.list," ") local color = final and data.color if color then color = { color } end ctx_NC() if color then ctx_color(color,serial) else context(serial) end ctx_NC() if color then ctx_color(color,path) else context(path) end ctx_NC() ctx_NR() end context.stoptabulate() local passname = { [-2] = "P", [-1] = "T", [ 0] = "E", } context.starttabulate { "|l|r|l|r|" } local pass = result.pass local subpasses = result.subpasses local subpass = result.subpass if subpasses == 0 then subpass = passname[subpass] or subpass end ctx_NC() context("pass") ctx_EQ() context(pass) ctx_NC() context("demerits") ctx_EQ() context(result.demerits) ctx_NC() ctx_NR() ctx_NC() context("subpass") ctx_EQ() context(subpass) ctx_NC() context("looseness") ctx_EQ() context(result.looseness) ctx_NC() ctx_NR() ctx_NC() context("subpasses") ctx_EQ() context(subpasses) ctx_NC() ctx_NC() ctx_NC() ctx_NR() context.stoptabulate() end end end local results = breakpoints.getresults() if n then typeset(results[n]) else for i=1,#results do typeset(results[i]) end end end -- bonus function mp.show_breakpoints(dx,dy,sx,sy,lw) local results = breakpoints.getresults()[1] local breaks = { } local colors = { none = "black", glue = "darkgreen", disc = "darkblue", penalty = "darkred", math = "darkgray", lines = "darkgray", } local everything = { formatters["numeric dx ; dx := %N ;"](dx), formatters["numeric dy ; dy := %N ;"](dy), formatters["numeric sx ; sx := %N ;"](sx), formatters["numeric sy ; sy := %N ;"](sy), formatters["numeric lw ; lw := %N ;"](lw), formatters["pickup pencircle scaled lw ;"](), } results[0] = { final = true, serial = 0, trigger = "none" } do local line = 0 local slot = 0 breaks[0] = { 0, 0 } for i=1,#results do local r = results[i] local l = r.line if l ~= line then slot = 0 line = l else slot = slot + 1 end breaks[i] = { slot, -l } end end do local result = { } for i=1,#results do local r = results[i] local previous = r.previous local p1 = breaks[i] local p2 = breaks[previous] if p1 and p2 then result[#result+1] = formatters["((%N,%N)--(%N,%N))"](p1[1],p1[2],p2[1],p2[2]) end end everything[#everything+1] = formatters[ [[draw (% && t) xyscaled (dx,dy) withcolor "%s" ;]] ](result,colors.lines) end do local result = { } for i=0,#results do local r = results[i] if r.final then local previous = r.previous if previous and results[previous] and results[previous].final then local p1 = breaks[i] local p2 = breaks[previous] if p1 and p2 then result[#result+1] = formatters["((%N,%N)--(%N,%N))"](p1[1],p1[2],p2[1],p2[2]) end end end end everything[#everything+1] = formatters[ [[draw (% && t) xyscaled (dx,dy) withcolor "white" ;]] ](result) everything[#everything+1] = formatters[ [[draw (% && t) xyscaled (dx,dy) dashed (evenly scaled .5lw) withcolor "black" ;]] ](result) end for i=0,#results do local p = breaks[i] if p then local r = results[i] everything[#everything+1] = formatters[ [[draw (%N*dx,%N*dy) withpen pencircle xyscaled(sx,sy) withcolor "%s" ;]] ](p[1],p[2],colors[r.trigger]) everything[#everything+1] = formatters[ [[draw textext("\ttxx%i") shifted (%N*dx,%N*dy) withcolor "white" ;]] ](r.serial,p[1],p[2]) end end -- return table.concat(everything,"\n") end