if not modules then modules = { } end modules ['spac-ver'] = { version = 1.001, comment = "companion to spac-ver.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- we also need to call the spacer for inserts! -- this code dates from the beginning and is kind of experimental; it -- will be optimized and improved soon -- -- the collapser will be redone with user nodes; also, we might get make -- parskip into an attribute and appy it explicitly thereby getting rid -- of automated injections; eventually i want to get rid of the currently -- still needed tex -> lua -> tex > lua chain (needed because we can have -- expandable settings at the tex end -- todo: strip baselineskip around display math local next, type, tonumber = next, type, tonumber local format, gmatch, concat, match = string.format, string.gmatch, table.concat, string.match local ceil, floor, max, min, round = math.ceil, math.floor, math.max, math.min, math.round local texsprint, texlists, texdimen, texbox = tex.sprint, tex.lists, tex.dimen, tex.box local lpegmatch = lpeg.match local unpack = unpack or table.unpack local ctxcatcodes = tex.ctxcatcodes local variables = interfaces.variables local starttiming, stoptiming = statistics.starttiming, statistics.stoptiming -- vertical space handler local trace_vbox_vspacing = false trackers.register("nodes.vbox_vspacing", function(v) trace_vbox_vspacing = v end) local trace_page_vspacing = false trackers.register("nodes.page_vspacing", function(v) trace_page_vspacing = v end) local trace_collect_vspacing = false trackers.register("nodes.collect_vspacing", function(v) trace_collect_vspacing = v end) local trace_vspacing = false trackers.register("nodes.vspacing", function(v) trace_vspacing = v end) local trace_vsnapping = false trackers.register("nodes.vsnapping", function(v) trace_vsnapping = v end) local skip_category = attributes.private('skip-category') local skip_penalty = attributes.private('skip-penalty') local skip_order = attributes.private('skip-order') local snap_category = attributes.private('snap-category') local display_math = attributes.private('display-math') local snap_method = attributes.private('snap-method') local snap_done = attributes.private('snap-done') local has_attribute = node.has_attribute local unset_attribute = node.unset_attribute local set_attribute = node.set_attribute local find_node_tail = node.tail local free_node = node.free local copy_node = node.copy local traverse_nodes = node.traverse local traverse_nodes_id = node.traverse_id local insert_node_before = node.insert_before local insert_node_after = node.insert_after local remove_node = nodes.remove local make_penalty_node = nodes.penalty local make_kern_node = nodes.kern local make_rule_node = nodes.rule local count_nodes = nodes.count local node_ids_to_string = nodes.ids_to_string local hpack_node = node.hpack local vpack_node = node.vpack local writable_spec = nodes.writable_spec local glyph = node.id("glyph") local penalty = node.id("penalty") local kern = node.id("kern") local glue = node.id('glue') local hlist = node.id('hlist') local vlist = node.id('vlist') local adjust = node.id('adjust') vspacing = vspacing or { } vspacing.data = vspacing.data or { } vspacing.data.snapmethods = vspacing.data.snapmethods or { } storage.register("vspacing/data/snapmethods", vspacing.data.snapmethods, "vspacing.data.snapmethods") local snapmethods = vspacing.data.snapmethods --maybe some older code can go local default = { maxheight = true, maxdepth = true, strut = true, hfraction = 1, dfraction = 1, } local fractions = { minheight = "hfraction", maxheight = "hfraction", mindepth = "dfraction", maxdepth = "dfraction", top = "tlines", bottom = "blines", } local values = { offset = "offset" } local colonsplitter = lpeg.splitat(":") local function listtohash(str) local t = { } for s in gmatch(str,"[^, ]+") do local key, detail = lpegmatch(colonsplitter,s) local v = variables[key] if v then t[v] = true if detail then local k = fractions[key] if k then detail = tonumber("0" .. detail) if detail then t[k] = detail end else k = values[key] if k then detail = string.todimen(detail) if detail then t[k] = detail end end end end else detail = tonumber("0" .. key) if detail then t.hfraction, t.dfraction = detail, detail end end end if next(t) then t.hfraction = t.hfraction or 1 t.dfraction = t.dfraction or 1 return t else return default end end function vspacing.define_snap_method(name,method) local n = #snapmethods + 1 local t = listtohash(method) snapmethods[n] = t t.name, t.specification = name, method tex.write(n) end --~ local rule_id = node.id("rule") --~ local vlist_id = node.id("vlist") --~ function nodes.makevtop(n) --~ if n.id == vlist_id then --~ local list = n.list --~ local height = (list and list.id <= rule_id and list.height) or 0 --~ n.depth = n.depth - height + n.height --~ n.height = height --~ end --~ end local function snap_hlist(current,method,height,depth) -- method.strut is default local snapht, snapdp --~ print(table.serialize(method)) if method["local"] then -- snapping is done immediately here snapht, snapdp = texdimen.bodyfontstrutheight, texdimen.bodyfontstrutdepth elseif method["global"] then snapht, snapdp = texdimen.globalbodyfontstrutheight, texdimen.globalbodyfontstrutdepth else -- maybe autolocal -- snapping might happen later in the otr snapht, snapdp = texdimen.globalbodyfontstrutheight, texdimen.globalbodyfontstrutdepth local lsnapht, lsnapdp = texdimen.bodyfontstrutheight, texdimen.bodyfontstrutdepth if snapht ~= lsnapht and snapdp ~= lsnapdp then snapht, snapdp = lsnapht, lsnapdp end end local h, d = height or current.height, depth or current.depth local hr, dr, ch, cd = method.hfraction or 1, method.dfraction or 1, h, d local tlines, blines = method.tlines or 1, method.blines or 1 local done, plusht, plusdp = false, snapht, snapdp local snaphtdp = snapht + snapdp if method.none then plusht, plusdp = 0, 0 end if method.halfline then plusht, plusdp = plusht + snaphtdp/2, plusdp + snaphtdp/2 end if method.line then plusht, plusdp = plusht + snaphtdp, plusdp + snaphtdp end if method.first then if current.id == vlist then local list, lh, ld = current.list for n in traverse_nodes_id(hlist,list) do lh, ld = n.height, n.depth break end if lh then local x = max(ceil((lh-hr*snapht)/snaphtdp),0)*snaphtdp + plusht local n = make_kern_node(x-lh) n.next, list.prev, current.list = list, n, n ch = x + snaphtdp cd = max(ceil((d+h-lh-dr*snapdp-hr*snapht)/snaphtdp),0)*snaphtdp + plusdp done = true end end elseif method.last then if current.id == vlist then local list, lh, ld = current.list for n in traverse_nodes_id(hlist,list) do lh, ld = n.height, n.depth end if lh then local baseline_till_top = h + d - ld local x = max(ceil((baseline_till_top-hr*snapht)/snaphtdp),0)*snaphtdp + plusht local n = make_kern_node(x-baseline_till_top) n.next, list.prev, current.list = list, n, n ch = x cd = max(ceil((ld-dr*snapdp)/snaphtdp),0)*snaphtdp + plusdp done = true end end end if done then -- first or last elseif method.minheight then ch = max(floor((h-hr*snapht)/snaphtdp),0)*snaphtdp + plusht elseif method.maxheight then ch = max(ceil((h-hr*snapht)/snaphtdp),0)*snaphtdp + plusht else ch = plusht end if done then -- first or last elseif method.mindepth then cd = max(floor((d-dr*snapdp)/snaphtdp),0)*snaphtdp + plusdp elseif method.maxdepth then cd = max(ceil((d-dr*snapdp)/snaphtdp),0)*snaphtdp + plusdp else cd = plusdp end if method.top then ch = ch + tlines * snaphtdp end if method.bottom then cd = cd + blines * snaphtdp end local offset = method.offset if offset then -- we need to set the attr local shifted = vpack_node(current.list) shifted.shift = offset current.list = shifted end if not height then current.height = ch end if not depth then current.depth = cd end -- set_attribute(current,snap_method,0) return h, d, ch, cd, (ch+cd)/snaphtdp end --~ local function snap_topskip(current,method) --~ local spec = current.spec --~ local w = spec.width --~ local wd = w --~ if spec then --~ wd = 0 --~ spec = writable_spec(current) --~ spec.width = wd --~ end --~ return w, wd --~ end local function snap_topskip(current,method) local spec = current.spec local w = spec.width local wd = w if spec.writable then spec.width, wd = 0, 0 end return w, wd end vspacing.categories = { [0] = 'discard', [1] = 'largest', [2] = 'force' , [3] = 'penalty', [4] = 'add' , [5] = 'disable', [6] = 'nowhite', [7] = 'goback', [8] = 'together' } local categories = vspacing.categories function vspacing.tocategories(str) local t = { } for s in gmatch(str,"[^, ]") do local n = tonumber(s) if n then t[categories[n]] = true else t[b] = true end end return t end function vspacing.tocategory(str) if type(str) == "string" then return set.tonumber(vspacing.tocategories(str)) else return set.tonumber({ [categories[str]] = true }) end end vspacing.data.map = vspacing.data.map or { } vspacing.data.skip = vspacing.data.skip or { } storage.register("vspacing/data/map", vspacing.data.map, "vspacing.data.map") storage.register("vspacing/data/skip", vspacing.data.skip, "vspacing.data.skip") do -- todo: interface.variables local function logger(c,...) logs.report("vspacing",concat {...}) texsprint(c,...) end vspacing.fixed = false local map = vspacing.data.map local skip = vspacing.data.skip local multiplier = lpeg.C(lpeg.S("+-")^0 * lpeg.R("09")^1) * lpeg.P("*") local category = lpeg.P(":") * lpeg.C(lpeg.P(1)^1) local keyword = lpeg.C((1-category)^1) local splitter = (multiplier + lpeg.Cc(1)) * keyword * (category + lpeg.Cc(false)) local k_fixed, k_flexible, k_category, k_penalty, k_order = variables.fixed, variables.flexible, "category", "penalty", "order" -- This will change: just node.write and we can store the values in skips which -- then obeys grouping local function analyse(str,oldcategory,texsprint) -- we could use shorter names for s in gmatch(str,"([^ ,]+)") do local amount, keyword, detail = lpegmatch(splitter,s) if not keyword then logs.report("vspacing","unknown directive: %s",s) else local mk = map[keyword] if mk then category = analyse(mk,category,texsprint) elseif keyword == k_fixed then texsprint(ctxcatcodes,"\\fixedblankskip") elseif keyword == k_flexible then texsprint(ctxcatcodes,"\\flexibleblankskip") elseif keyword == k_category then local category = tonumber(detail) if category then texsprint(ctxcatcodes,"\\setblankcategory{",category,"}") if category ~= oldcategory then texsprint(ctxcatcodes,"\\flushblankhandling") oldcategory = category end end elseif keyword == k_order and detail then local order = tonumber(detail) if order then texsprint(ctxcatcodes,"\\setblankorder{",order,"}") end elseif keyword == k_penalty and detail then local penalty = tonumber(detail) if penalty then texsprint(ctxcatcodes,"\\setblankpenalty{",penalty,"}") end else amount = tonumber(amount) or 1 local sk = skip[keyword] if sk then texsprint(ctxcatcodes,"\\addpredefinedblankskip{",amount,"}{",keyword,"}") else -- no check texsprint(ctxcatcodes,"\\addaskedblankskip{",amount,"}{",keyword,"}") end end end end return category end function vspacing.analyse(str) local texsprint = (trace_vspacing and logger) or texsprint texsprint(ctxcatcodes,"\\startblankhandling") analyse(str,1,texsprint) texsprint(ctxcatcodes,"\\stopblankhandling") end -- function vspacing.setmap(from,to) map[from] = to end function vspacing.setskip(key,value,grid) if value ~= "" then if grid == "" then grid = value end skip[key] = { value, grid } end end end -- implementation --~ nodes.snapvalues = { } --~ function nodes.setsnapvalue(n,ht,dp) --~ nodes.snapvalues[n] = { ht, dp, ht+dp } --~ end local trace_list, tracing_info, before, after = { }, false, "", "" local function glue_to_string(glue) local spec = glue.spec local t = { } t[#t+1] = aux.strip_zeros(number.topoints(spec.width)) if spec.stretch_order and spec.stretch_order ~= 0 then t[#t+1] = format("plus -%sfi%s",spec.stretch/65536,string.rep("l",math.abs(spec.stretch_order)-1)) elseif spec.stretch and spec.stretch ~= 0 then t[#t+1] = format("plus %s",aux.strip_zeros(number.topoints(spec.stretch))) end if spec.shrink_order and spec.shrink_order ~= 0 then t[#t+1] = format("minus -%sfi%s",spec.shrink/65536,string.rep("l",math.abs(spec.shrink_order)-1)) elseif spec.shrink and spec.shrink ~= 0 then t[#t+1] = format("minus %s",aux.strip_zeros(number.topoints(spec.shrink))) end return concat(t," ") end local function nodes_to_string(head) local current, t = head, { } while current do local id = current.id local ty = node.type(id) if id == penalty then t[#t+1] = format("%s:%s",ty,current.penalty) elseif id == glue then t[#t+1] = format("%s:%s",ty,aux.strip_zeros(number.topoints(current.spec.width))) elseif id == kern then t[#t+1] = format("%s:%s",ty,aux.strip_zeros(number.topoints(current.kern))) else t[#t+1] = ty end current = current.next end return concat(t," + ") end local function reset_tracing(head) trace_list, tracing_info, before, after = { }, false, nodes_to_string(head), "" end local function trace_skip(str,sc,so,sp,data) trace_list[#trace_list+1] = { "skip", format("%s | %s | category %s | order %s | penalty %s", str, glue_to_string(data), sc or "-", so or "-", sp or "-") } tracing_info = true end local function trace_natural(str,data) trace_list[#trace_list+1] = { "skip", format("%s | %s", str, glue_to_string(data)) } tracing_info = true end local function trace_info(message, where, what) trace_list[#trace_list+1] = { "info", format("%s: %s/%s",message,where,what) } end local function trace_node(what) local nt = node.type(what.id) local tl = trace_list[#trace_list] if tl and tl[1] == "node" then trace_list[#trace_list] = { "node", tl[2] .. " + " .. nt } else trace_list[#trace_list+1] = { "node", nt } end end local function trace_done(str,data) if data.id == penalty then trace_list[#trace_list+1] = { "penalty", format("%s | %s", str, data.penalty) } else trace_list[#trace_list+1] = { "glue", format("%s | %s", str, glue_to_string(data)) } end tracing_info = true end local function show_tracing(head) if tracing_info then after = nodes_to_string(head) for i=1,#trace_list do local tag, text = unpack(trace_list[i]) if tag == "info" then logs.report("collapse",text) else logs.report("collapse"," %s: %s",tag,text) end end logs.report("collapse","before: %s",before) logs.report("collapse","after : %s",after) end end -- alignment box begin_of_par vmode_par hmode_par insert penalty before_display after_display local user_skip = 0 local line_skip = 1 local baseline_skip = 2 local par_skip = 3 local above_display_skip = 4 local below_display_skip = 5 local above_display_short_skip = 6 local below_display_short_skip = 7 local left_skip_code = 8 local right_skip_code = 9 local top_skip_code = 10 local split_top_skip_code = 11 local tab_skip_code = 12 local space_skip_code = 13 local xspace_skip_code = 14 local par_fill_skip_code = 15 local thin_mu_skip_code = 16 local med_mu_skip_code = 17 local thick_mu_skip_code = 18 local skips = { [ 0] = "user_skip", [ 1] = "line_skip", [ 2] = "baseline_skip", [ 3] = "par_skip", [ 4] = "above_display_skip", [ 5] = "below_display_skip", [ 6] = "above_display_short_skip", [ 7] = "below_display_short_skip", [ 8] = "left_skip_code", [ 9] = "right_skip_code", [10] = "top_skip_code", [11] = "split_top_skip_code", [12] = "tab_skip_code", [13] = "space_skip_code", [14] = "xspace_skip_code", [15] = "par_fill_skip_code", [16] = "thin_mu_skip_code", [17] = "med_mu_skip_code", [18] = "thick_mu_skip_code", } local free_glue_node = free_node local discard, largest, force, penalty, add, disable, nowhite, goback, together = 0, 1, 2, 3, 4, 5, 6, 7, 8 --~ local function free_glue_node(n) free_node(n.spec) free_node(n) end function vspacing.snap_box(n,how) local sv = snapmethods[how] if sv then local box = texbox[n] local list = box.list --~ if list and (list.id == hlist or list.id == vlist) then if list then local s = has_attribute(list,snap_method) if s == 0 then if trace_vsnapping then -- logs.report("snapper", "hlist not snapped, already done") end else local h, d, ch, cd, lines = snap_hlist(box,sv,box.height,box.depth) box.height, box.depth = ch, cd if trace_vsnapping then logs.report("snapper", "hlist snapped from (%s,%s) to (%s,%s) using method '%s' (%s) for '%s' (%s lines)",h,d,ch,cd,sv.name,sv.specification,"direct",lines) end set_attribute(list,snap_method,0) end end end end local function forced_skip(head,current,width,where,trace) if where == "after" then head, current = insert_node_after(head,current,make_rule_node(0,0,0)) head, current = insert_node_after(head,current,make_kern_node(width)) head, current = insert_node_after(head,current,make_rule_node(0,0,0)) else local c = current head, current = insert_node_before(head,current,make_rule_node(0,0,0)) head, current = insert_node_before(head,current,make_kern_node(width)) head, current = insert_node_before(head,current,make_rule_node(0,0,0)) current = c end if trace then logs.report("vspacing", "inserting forced skip of %s",width) end return head, current end local function collapser(head,where,what,trace,snap) -- maybe also pass tail if trace then reset_tracing(head) end local current, oldhead = head, head local glue_order, glue_data, force_glue = 0, nil, false local penalty_order, penalty_data, natural_penalty = 0, nil, nil local parskip, ignore_parskip, ignore_following, ignore_whitespace, keep_together = nil, false, false, false, false -- -- todo: keep_together: between headers -- local function flush(why) if penalty_data then local p = make_penalty_node(penalty_data) if trace then trace_done("flushed due to " .. why,p) end head, _ = insert_node_before(head,current,p) end if glue_data then if force_glue then if trace then trace_done("flushed due to " .. why,glue_data) end head, _ = forced_skip(head,current,glue_data.spec.width,"before",trace) free_glue_node(glue_data) elseif glue_data.spec.writable then if trace then trace_done("flushed due to " .. why,glue_data) end head, _ = insert_node_before(head,current,glue_data) else free_glue_node(glue_data) end end if trace then trace_node(current) end glue_order, glue_data, force_glue = 0, nil, false penalty_order, penalty_data, natural_penalty = 0, nil, nil parskip, ignore_parskip, ignore_following, ignore_whitespace = nil, false, false, false end if trace_vsnapping then logs.report("snapper", "global ht/dp = %s/%s, local ht/dp = %s/%s", texdimen.globalbodyfontstrutheight, texdimen.globalbodyfontstrutdepth, texdimen.bodyfontstrutheight, texdimen.bodyfontstrutdepth) end if trace then trace_info("start analyzing",where,what) end while current do local id, subtype = current.id, current.subtype if id == hlist or id == vlist then -- needs checking, why so many calls if snap then local s = has_attribute(current,snap_method) if not s then -- if trace_vsnapping then -- logs.report("snapper", "hlist not snapped") -- end elseif s == 0 then if trace_vsnapping then -- logs.report("snapper", "hlist not snapped, already done") end else local sv = snapmethods[s] if sv then local h, d, ch, cd, lines = snap_hlist(current,sv) if trace_vsnapping then logs.report("snapper", "hlist snapped from (%s,%s) to (%s,%s) using method '%s' (%s) for '%s' (%s lines)",h,d,ch,cd,sv.name,sv.specification,where,lines) end elseif trace_vsnapping then logs.report("snapper", "hlist not snapped due to unknown snap specification") end set_attribute(current,snap_method,0) end else -- end -- tex.prevdepth = 0 flush("list") current = current.next elseif id == penalty then --~ natural_penalty = current.penalty --~ if trace then trace_done("removed penalty",current) end --~ head, current = remove_node(head, current, true) current = current.next elseif id == kern then if snap and trace_vsnapping and current.kern ~= 0 then --~ current.kern = 0 logs.report("snapper", "kern of %s (kept)",current.kern) end flush("kern") current = current.next elseif id ~= glue then flush("something else") current = current.next elseif subtype == user_skip then -- todo, other subtypes, like math local sc = has_attribute(current,skip_category) -- has no default, no unset (yet) local so = has_attribute(current,skip_order ) or 1 -- has 1 default, no unset (yet) local sp = has_attribute(current,skip_penalty ) -- has no default, no unset (yet) if sp and sc == penalty then if not penalty_data then penalty_data = sp elseif penalty_order < so then penalty_order, penalty_data = so, sp elseif penalty_order == so and sp > penalty_data then penalty_data = sp end if trace then trace_skip('penalty in skip',sc,so,sp,current) end head, current = remove_node(head, current, true) elseif not sc then -- if not sc then if glue_data then if trace then trace_done("flush",glue_data) end head, current = nodes.before(head,current,glue_data) if trace then trace_natural("natural",current) end current = current.next else -- not look back across head local previous = current.prev if previous and previous.id == glue and previous.subtype == 0 then local ps = previous.spec if ps.writable then local cs = current.spec if cs.writable and ps.stretch_order == 0 and ps.shrink_order == 0 and cs.stretch_order == 0 and cs.shrink_order == 0 then local pw, pp, pm = ps.width, ps.stretch, ps.shrink local cw, cp, cm = cs.width, cs.stretch, cs.shrink ps = writable_spec(previous) -- no writable needed here ps.width, ps.stretch, ps.shrink = pw + cw, pp + cp, pm + cm if trace then trace_natural("removed",current) end head, current = remove_node(head, current, true) -- current = previous if trace then trace_natural("collapsed",previous) end -- current = current.next else if trace then trace_natural("filler",current) end current = current.next end else if trace then trace_natural("natural (no prev spec)",current) end current = current.next end else if trace then trace_natural("natural (no prev)",current) end current = current.next end end glue_order, glue_data = 0, nil elseif sc == disable then ignore_following = true if trace then trace_skip("disable",sc,so,sp,current) end head, current = remove_node(head, current, true) elseif sc == together then keep_together = true if trace then trace_skip("together",sc,so,sp,current) end head, current = remove_node(head, current, true) elseif sc == nowhite then ignore_whitespace = true head, current = remove_node(head, current, true) elseif sc == discard then if trace then trace_skip("discard",sc,so,sp,current) end head, current = remove_node(head, current, true) elseif ignore_following then if trace then trace_skip("disabled",sc,so,sp,current) end head, current = remove_node(head, current, true) elseif not glue_data then if trace then trace_skip("assign",sc,so,sp,current) end glue_order = so head, current, glue_data = remove_node(head, current) elseif glue_order < so then if trace then trace_skip("force",sc,so,sp,current) end glue_order = so free_glue_node(glue_data) head, current, glue_data = remove_node(head, current) elseif glue_order == so then -- is now exclusive, maybe support goback as combi, else why a set if sc == largest then local cs, gs = current.spec, glue_data.spec local cw, gw = cs.width, gs.width if cw > gw then if trace then trace_skip('largest',sc,so,sp,current) end free_glue_node(glue_data) -- also free spec head, current, glue_data = remove_node(head, current) else if trace then trace_skip('remove smallest',sc,so,sp,current) end head, current = remove_node(head, current, true) end elseif sc == goback then if trace then trace_skip('goback',sc,so,sp,current) end free_glue_node(glue_data) -- also free spec head, current, glue_data = remove_node(head, current) elseif sc == force then -- last one counts, some day we can provide an accumulator and largest etc -- but not now if trace then trace_skip('force',sc,so,sp,current) end free_glue_node(glue_data) -- also free spec head, current, glue_data = remove_node(head, current) elseif sc == penalty then -- ? ? ? ? if trace then trace_skip('penalty',sc,so,sp,current) end free_glue_node(glue_data) -- also free spec glue_data = nil head, current = remove_node(head, current, true) elseif sc == add then if trace then trace_skip('add',sc,so,sp,current) end --~ local old, new = glue_data.spec, current.spec local old, new = writable_spec(glue_data), current.spec old.width = old.width + new.width old.stretch = old.stretch + new.stretch old.shrink = old.shrink + new.shrink -- toto: order head, current = remove_node(head, current, true) else if trace then trace_skip("unknown",sc,so,sp,current) end head, current = remove_node(head, current, true) end else if trace then trace_skip("unknown",sc,so,sp,current) end head, current = remove_node(head, current, true) end if sc == force then force_glue = true end elseif subtype == line_skip then if snap then local s = has_attribute(current,snap_method) if s and s ~= 0 then set_attribute(current,snap_method,0) if current.spec.writable then local spec = writable_spec(current) spec.width = 0 if trace_vsnapping then logs.report("snapper", "lineskip set to zero") end end else if trace then trace_skip("lineskip",sc,so,sp,current) end flush("lineskip") end else if trace then trace_skip("lineskip",sc,so,sp,current) end flush("lineskip") end current = current.next elseif subtype == baseline_skip then if snap then local s = has_attribute(current,snap_method) if s and s ~= 0 then set_attribute(current,snap_method,0) if current.spec.writable then local spec = writable_spec(current) spec.width = 0 if trace_vsnapping then logs.report("snapper", "baselineskip set to zero") end end else if trace then trace_skip("baselineskip",sc,so,sp,current) end flush("baselineskip") end else if trace then trace_skip("baselineskip",sc,so,sp,current) end flush("baselineskip") end current = current.next elseif subtype == par_skip then -- parskip always comes later if ignore_whitespace then if trace then trace_natural("ignored parskip",current) end head, current = remove_node(head, current, true) elseif glue_data then local ps, gs = current.spec, glue_data.spec if ps.writable and gs.writable and ps.width > gs.width then glue_data.spec = copy_node(ps) if trace then trace_natural("taking parskip",current) end else if trace then trace_natural("removed parskip",current) end end head, current = remove_node(head, current, true) else if trace then trace_natural("honored parskip",current) end head, current, glue_data = remove_node(head, current) end elseif subtype == top_skip_code or subtype == split_top_skip_code then if snap then local s = has_attribute(current,snap_method) if s and s ~= 0 then set_attribute(current,snap_method,0) local sv = snapmethods[s] local w, cw = snap_topskip(current,sv) if trace_vsnapping then logs.report("snapper", "topskip snapped from %s to %s for '%s'",w,cw,where) end else if trace then trace_skip("topskip",sc,so,sp,current) end flush("topskip") end else if trace then trace_skip("topskip",sc,so,sp,current) end flush("topskip") end current = current.next elseif subtype == above_display_skip then -- if trace then trace_skip("above display skip (normal)",sc,so,sp,current) end flush("above display skip (normal)") current = current.next -- elseif subtype == below_display_skip then -- if trace then trace_skip("below display skip (normal)",sc,so,sp,current) end flush("below display skip (normal)") current = current.next -- elseif subtype == above_display_short_skip then -- if trace then trace_skip("above display skip (short)",sc,so,sp,current) end flush("above display skip (short)") current = current.next -- elseif subtype == below_display_short_skip then -- if trace then trace_skip("below display skip (short)",sc,so,sp,current) end flush("below display skip (short)") current = current.next -- else -- other glue if snap and trace_vsnapping and current.spec.writable and current.spec.width ~= 0 then logs.report("snapper", "%s of %s (kept)",skips[subtype],current.spec.width) --~ current.spec.width = 0 end if trace then trace_skip(format("some glue (%s)",subtype),sc,so,sp,current) end flush("some glue") current = current.next end end if trace then trace_info("stop analyzing",where,what) end --~ if natural_penalty and (not penalty_data or natural_penalty > penalty_data) then --~ penalty_data = natural_penalty --~ end if trace and (glue_data or penalty_data) then trace_info("start flushing",where,what) end local tail if penalty_data then tail = find_node_tail(head) local p = make_penalty_node(penalty_data) if trace then trace_done("result",p) end head, tail = insert_node_after(head,tail,p) end if glue_data then if not tail then tail = find_node_tail(head) end if trace then trace_done("result",glue_data) end if force_glue then head, tail = forced_skip(head,tail,glue_data.spec.width,"after",trace) free_glue_node(glue_data) else head, tail = insert_node_after(head,tail,glue_data) end end if trace then if glue_data or penalty_data then trace_info("stop flushing",where,what) end show_tracing(head) if oldhead ~= head then trace_info("head has been changed from '%s' to '%s'",node.type(oldhead.id),node.type(head.id)) end end return head, true end -- alignment after_output end box new_graf vmode_par hmode_par insert penalty before_display after_display -- \par -> vmode_par -- -- status.best_page_break -- tex.lists.best_page_break -- tex.lists.best_size (natural size to best_page_break) -- tex.lists.least_page_cost (badness of best_page_break) -- tex.lists.page_head -- tex.lists.contrib_head local stackhead, stacktail, stackhack = nil, nil, false local function report(message,lst) logs.report("vspacing",message,count_nodes(lst,true),node_ids_to_string(lst)) end function nodes.handle_page_spacing(newhead,where) --~ local newhead = texlists.contrib_head if newhead then --~ starttiming(vspacing) local newtail = find_node_tail(newhead) local flush = false stackhack = true -- todo: only when grid snapping once enabled for n in traverse_nodes(newhead) do -- we could just look for glue nodes local id = n.id if id == glue then if n.subtype == 0 then if has_attribute(n,skip_category) then stackhack = true else flush = true end else -- tricky end else flush = true end end if flush then if stackhead then if trace_collect_vspacing then report("appending %s nodes to stack (final): %s",newhead) end stacktail.next = newhead newhead.prev = stacktail newhead = stackhead stackhead, stacktail = nil, nil end if stackhack then stackhack = false if trace_collect_vspacing then report("processing %s nodes: %s",newhead) end --~ texlists.contrib_head = collapser(newhead,"page",where,trace_page_vspacing,true) newhead = collapser(newhead,"page",where,trace_page_vspacing,true) else if trace_collect_vspacing then report("flushing %s nodes: %s",newhead) end --~ texlists.contrib_head = newhead end else if stackhead then if trace_collect_vspacing then report("appending %s nodes to stack (intermediate): %s",newhead) end stacktail.next = newhead newhead.prev = stacktail else if trace_collect_vspacing then report("storing %s nodes in stack (initial): %s",newhead) end stackhead = newhead end stacktail = newtail --~ texlists.contrib_head = nil newhead = nil end --~ stoptiming(vspacing) end return newhead end local ignore = table.tohash { "split_keep", "split_off", -- "vbox", } function nodes.handle_vbox_spacing(head,where) if head and not ignore[where] and head.next then -- starttiming(vspacing) head = collapser(head,"vbox",where,trace_vbox_vspacing,false) -- stoptiming(vspacing) end return head end function nodes.collapse_vbox(n) -- for boxes local list = texbox[n].list if list then -- starttiming(vspacing) texbox[n].list = vpack_node(collapser(list,"snapper","vbox",trace_vbox_vspacing,true)) -- stoptiming(vspacing) end end -- we will split this module hence the locals local attribute = attributes.private('graphicvadjust') local hlist = node.id('hlist') local vlist = node.id('vlist') local remove_node = nodes.remove local hpack_node = node.hpack local vpack_node = node.vpack local has_attribute = node.has_attribute function nodes.repackage_graphicvadjust(head,groupcode) -- we can make an actionchain for mvl only if groupcode == "" then -- mvl only local h, p, done = head, nil, false while h do local id = h.id if id == hlist or id == vlist then local a = has_attribute(h,attribute) if a then if p then local n head, h, n = remove_node(head,h) local pl = p.list if n.width ~= 0 then n = hpack_node(n,0,'exactly') -- todo: dir end if pl then pl.prev = n n.next = pl end p.list = n done = true else -- can't happen end else p = h h = h.next end else h = h.next end end return head, done else return head, false end end --~ function nodes.repackage_graphicvadjust(head,groupcode) -- we can make an actionchain for mvl only --~ if groupcode == "" then -- mvl only --~ return head, false --~ else --~ return head, false --~ end --~ end --~ tasks.appendaction("finalizers", "lists", "nodes.repackage_graphicvadjust") nodes.builders = nodes.builder or { } local builders = nodes.builders local actions = tasks.actions("vboxbuilders",5) function nodes.builders.vpack_filter(head,groupcode,size,packtype,maxdepth,direction) local done = false if head then starttiming(builders) if trace_callbacks then local before = nodes.count(head) head, done = actions(head,groupcode,size,packtype,maxdepth,direction) local after = nodes.count(head) if done then tracer("vpack","changed",head,groupcode,before,after,true) else tracer("vpack","unchanged",head,groupcode,before,after,true) end stoptiming(builders) else head, done = actions(head,groupcode) stoptiming(builders) end end return head, done 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 actions = tasks.actions("mvlbuilders",1) function nodes.builders.buildpage_filter(groupcode) starttiming(builders) local head = texlists.contrib_head local head, done = actions(head,groupcode) texlists.contrib_head = head stoptiming(builders) return (done and head) or true end callbacks.register('vpack_filter', nodes.builders.vpack_filter, "vertical spacing etc") callbacks.register('buildpage_filter', nodes.builders.buildpage_filter, "vertical spacing etc (mvl)") statistics.register("v-node processing time", function() return statistics.elapsedseconds(builders) end)