diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkxl/spac-ver.lmt')
-rw-r--r-- | Master/texmf-dist/tex/context/base/mkxl/spac-ver.lmt | 2516 |
1 files changed, 2516 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkxl/spac-ver.lmt b/Master/texmf-dist/tex/context/base/mkxl/spac-ver.lmt new file mode 100644 index 00000000000..2d4aace2437 --- /dev/null +++ b/Master/texmf-dist/tex/context/base/mkxl/spac-ver.lmt @@ -0,0 +1,2516 @@ +if not modules then modules = { } end modules ['spac-ver'] = { + version = 1.001, + optimize = true, + 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! + +-- somehow lists still don't always have proper prev nodes so i need to +-- check all of the luatex code some day .. maybe i should replece the +-- whole mvl handler by lua code .. why not + +-- todo: use lua nodes with lua data (>0.79) +-- see ** can go when 0.79 + +-- needs to be redone, too many calls and tests now ... still within some +-- luatex limitations + +-- this code dates from the beginning and is kind of experimental; it +-- will be optimized and improved soon .. it's way too complex now but +-- dates from less possibilities +-- +-- 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 gmatch, concat = string.gmatch, table.concat +local ceil, floor, abs = math.ceil, math.floor, math.abs +local lpegmatch = lpeg.match +local unpack = unpack or table.unpack +local allocate = utilities.storage.allocate +local todimen = string.todimen +local formatters = string.formatters + +local nodes = nodes +local trackers = trackers +local attributes = attributes +local context = context +local tex = tex + +local texlists = tex.lists +local texget = tex.get +local texgetcount = tex.getcount +local texgetdimen = tex.getdimen +local texgetglue = tex.getglue +local texset = tex.set +local texsetdimen = tex.setdimen +local texsetcount = tex.setcount +local texnest = tex.nest +local texgetbox = tex.getbox + +local buildpage = tex.triggerbuildpage + +local variables = interfaces.variables +local implement = interfaces.implement + +local v_local = variables["local"] +local v_global = variables["global"] +local v_box = variables.box +----- v_page = variables.page -- reserved for future use +local v_split = variables.split +local v_min = variables.min +local v_max = variables.max +local v_none = variables.none +local v_line = variables.line +local v_noheight = variables.noheight +local v_nodepth = variables.nodepth +local v_line = variables.line +local v_halfline = variables.halfline +local v_line_m = "-" .. v_line +local v_halfline_m = "-" .. v_halfline +local v_first = variables.first +local v_last = variables.last +local v_top = variables.top +local v_bottom = variables.bottom +local v_minheight = variables.minheight +local v_maxheight = variables.maxheight +local v_mindepth = variables.mindepth +local v_maxdepth = variables.maxdepth +local v_offset = variables.offset +local v_strut = variables.strut + +local v_hfraction = variables.hfraction +local v_dfraction = variables.dfraction +local v_bfraction = variables.bfraction +local v_tlines = variables.tlines +local v_blines = variables.blines + +-- vertical space handler + +local trace_vbox_vspacing = false trackers.register("vspacing.vbox", function(v) trace_vbox_vspacing = v end) +local trace_page_vspacing = false trackers.register("vspacing.page", function(v) trace_page_vspacing = v end) +local trace_page_builder = false trackers.register("builders.page", function(v) trace_page_builder = v end) +local trace_collect_vspacing = false trackers.register("vspacing.collect", function(v) trace_collect_vspacing = v end) +local trace_vspacing = false trackers.register("vspacing.spacing", function(v) trace_vspacing = v end) +local trace_vsnapping = false trackers.register("vspacing.snapping", function(v) trace_vsnapping = v end) +local trace_specials = false trackers.register("vspacing.specials", function(v) trace_specials = v end) + +local remove_math_skips = true directives.register("vspacing.removemathskips", function(v) remnove_math_skips = v end) + +local report_vspacing = logs.reporter("vspacing","spacing") +local report_collapser = logs.reporter("vspacing","collapsing") +local report_snapper = logs.reporter("vspacing","snapping") +local report_specials = logs.reporter("vspacing","specials") + +local a_skipcategory = attributes.private('skipcategory') +local a_skippenalty = attributes.private('skippenalty') +local a_skiporder = attributes.private('skiporder') +local a_snapmethod = attributes.private('snapmethod') +local a_snapvbox = attributes.private('snapvbox') + +local nuts = nodes.nuts +local tonut = nuts.tonut + +local getnext = nuts.getnext +local setlink = nuts.setlink +local getprev = nuts.getprev +local getid = nuts.getid +local getlist = nuts.getlist +local setlist = nuts.setlist +local getattr = nuts.getattr +local getattrs = nuts.getattrs +local setattr = nuts.setattr +local getsubtype = nuts.getsubtype +local getbox = nuts.getbox +local getwhd = nuts.getwhd +local setwhd = nuts.setwhd +local getprop = nuts.getprop +local setprop = nuts.setprop +local getglue = nuts.getglue +local setglue = nuts.setglue +local getkern = nuts.getkern +local getpenalty = nuts.getpenalty +local setshift = nuts.setshift +local setwidth = nuts.setwidth +local getwidth = nuts.getwidth +local setheight = nuts.setheight +local getheight = nuts.getheight +local setdepth = nuts.setdepth +local getdepth = nuts.getdepth +local setnext = nuts.setnext + +local find_node_tail = nuts.tail +local flush_node = nuts.flush_node +local remove_node = nuts.remove +local count_nodes = nuts.countall +local hpack_node = nuts.hpack +local vpack_node = nuts.vpack +local start_of_par = nuts.start_of_par + +local write_node = nuts.write + +local nextnode = nuts.traversers.node +local nexthlist = nuts.traversers.hlist + +local nodereference = nuts.reference + +local theprop = nuts.theprop + +local listtoutf = nodes.listtoutf +local nodeidstostring = nodes.idstostring + +local nodepool = nuts.pool + +local new_penalty = nodepool.penalty +local new_kern = nodepool.kern +local new_glue = nodepool.glue +local new_rule = nodepool.rule + +local nodecodes = nodes.nodecodes +local gluecodes = nodes.gluecodes +----- penaltycodes = nodes.penaltycodes +----- listcodes = nodes.listcodes + +local penalty_code = nodecodes.penalty +local kern_code = nodecodes.kern +local glue_code = nodecodes.glue +local hlist_code = nodecodes.hlist +local vlist_code = nodecodes.vlist +local rule_code = nodecodes.rule +local par_code = nodecodes.par + +local userskip_code = gluecodes.userskip +local lineskip_code = gluecodes.lineskip +local baselineskip_code = gluecodes.baselineskip +local parskip_code = gluecodes.parskip +local topskip_code = gluecodes.topskip +local splittopskip_code = gluecodes.splittopskip + +local linelist_code = nodes.listcodes.line + +local properties = nodes.properties.data + +local vspacing = builders.vspacing or { } +builders.vspacing = vspacing + +local vspacingdata = vspacing.data or { } +vspacing.data = vspacingdata + +local snapmethods = vspacingdata.snapmethods or { } +vspacingdata.snapmethods = snapmethods + +storage.register("builders/vspacing/data/snapmethods", snapmethods, "builders.vspacing.data.snapmethods") + +do + + local default = { + [v_maxheight] = true, + [v_maxdepth] = true, + [v_strut] = true, + [v_hfraction] = 1, + [v_dfraction] = 1, + [v_bfraction] = 0.25, + } + + local fractions = { + [v_minheight] = v_hfraction, [v_maxheight] = v_hfraction, + [v_mindepth] = v_dfraction, [v_maxdepth] = v_dfraction, + [v_box] = v_bfraction, + [v_top] = v_tlines, [v_bottom] = v_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 = todimen(detail) + if detail then + t[k] = detail + end + end + end + end + else + detail = tonumber("0" .. key) + if detail then + t[v_hfraction] = detail + t[v_dfraction] = detail + end + end + end + if next(t) then + t[v_hfraction] = t[v_hfraction] or 1 + t[v_dfraction] = t[v_dfraction] or 1 + return t + else + return default + end + end + + function vspacing.definesnapmethod(name,method) + local n = #snapmethods + 1 + local t = listtohash(method) + snapmethods[n] = t + t.name = name -- not interfaced + t.specification = method -- not interfaced + context(n) + end + +end + +local function validvbox(parentid,list) + if parentid == hlist_code then + local id = getid(list) + if id == par_code and start_of_par(list) then + list = getnext(list) + if not next then + return nil + end + end + local done = nil + for n, id in nextnode, list do + if id == vlist_code or id == hlist_code then + if done then + return nil + else + done = n + end + elseif id == glue_code or id == penalty_code then + -- go on + else + return nil -- whatever + end + end + if done then + local id = getid(done) + if id == hlist_code then + return validvbox(id,getlist(done)) + end + end + return done -- only one vbox + end +end + +local function already_done(parentid,list,a_snapmethod) -- todo: done when only boxes and all snapped + -- problem: any snapped vbox ends up in a line + if list and parentid == hlist_code then + local id = getid(list) + if id == par_code and start_of_par(list) then + list = getnext(list) + if not list then + return false + end + end + for n, id in nextnode, list do + if id == hlist_code or id == vlist_code then + -- local a = getattr(n,a_snapmethod) + -- if not a then + -- -- return true -- not snapped at all + -- elseif a == 0 then + -- return true -- already snapped + -- end + local p = getprop(n,"snapper") + if p then + return p + end + elseif id == glue_code or id == penalty_code then -- or id == kern_code then + -- go on + else + return false -- whatever + end + end + end + return false +end + +-- quite tricky: ceil(-something) => -0 + +local function ceiled(n) + if n < 0 or n < 0.01 then + return 0 + else + return ceil(n) + end +end + +local function floored(n) + if n < 0 or n < 0.01 then + return 0 + else + return floor(n) + end +end + +-- check variables.none etc + +local function fixedprofile(current) + local profiling = builders.profiling + return profiling and profiling.fixedprofile(current) +end + +local function snap_hlist(where,current,method,height,depth) -- method[v_strut] is default + if fixedprofile(current) then + return + end + local list = getlist(current) + local t = trace_vsnapping and { } + if t then + t[#t+1] = formatters["list content: %s"](listtoutf(list)) + t[#t+1] = formatters["snap method: %s"](method.name) -- not interfaced + t[#t+1] = formatters["specification: %s"](method.specification) -- not interfaced + end + local snapht, snapdp + if method[v_local] then + -- snapping is done immediately here + snapht = texgetdimen("bodyfontstrutheight") + snapdp = texgetdimen("bodyfontstrutdepth") + if t then + t[#t+1] = formatters["local: snapht %p snapdp %p"](snapht,snapdp) + end + elseif method[v_global] then + snapht = texgetdimen("globalbodyfontstrutheight") + snapdp = texgetdimen("globalbodyfontstrutdepth") + if t then + t[#t+1] = formatters["global: snapht %p snapdp %p"](snapht,snapdp) + end + else + -- maybe autolocal + -- snapping might happen later in the otr + snapht = texgetdimen("globalbodyfontstrutheight") + snapdp = texgetdimen("globalbodyfontstrutdepth") + local lsnapht = texgetdimen("bodyfontstrutheight") + local lsnapdp = texgetdimen("bodyfontstrutdepth") + if snapht ~= lsnapht and snapdp ~= lsnapdp then + snapht, snapdp = lsnapht, lsnapdp + end + if t then + t[#t+1] = formatters["auto: snapht %p snapdp %p"](snapht,snapdp) + end + end + + local wd, ht, dp = getwhd(current) + + local h = (method[v_noheight] and 0) or height or ht + local d = (method[v_nodepth] and 0) or depth or dp + local hr = method[v_hfraction] or 1 + local dr = method[v_dfraction] or 1 + local br = method[v_bfraction] or 0 + local ch = h + local cd = d + local tlines = method[v_tlines] or 1 + local blines = method[v_blines] or 1 + local done = false + local plusht = snapht + local plusdp = snapdp + local snaphtdp = snapht + snapdp + local extra = 0 + + if t then + t[#t+1] = formatters["hlist: wd %p ht %p (used %p) dp %p (used %p)"](wd,ht,h,dp,d) + t[#t+1] = formatters["fractions: hfraction %s dfraction %s bfraction %s tlines %s blines %s"](hr,dr,br,tlines,blines) + end + + if method[v_box] then + local br = 1 - br + if br < 0 then + br = 0 + elseif br > 1 then + br = 1 + end + local n = ceiled((h+d-br*snapht-br*snapdp)/snaphtdp) + local x = n * snaphtdp - h - d + plusht = h + x / 2 + plusdp = d + x / 2 + if t then + t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_box,plusht,plusdp) + end + elseif method[v_max] then + local n = ceiled((h+d)/snaphtdp) + local x = n * snaphtdp - h - d + plusht = h + x / 2 + plusdp = d + x / 2 + if t then + t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_max,plusht,plusdp) + end + elseif method[v_min] then + -- we catch a lone min + if method.specification ~= v_min then + local n = floored((h+d)/snaphtdp) + local x = n * snaphtdp - h - d + plusht = h + x / 2 + plusdp = d + x / 2 + if plusht < 0 then + plusht = 0 + end + if plusdp < 0 then + plusdp = 0 + end + end + if t then + t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_min,plusht,plusdp) + end + elseif method[v_none] then + plusht, plusdp = 0, 0 + if t then + t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_none,0,0) + end + end + -- for now, we actually need to tag a box and then check at several points if something ended up + -- at the top of a page + if method[v_halfline] then -- extra halfline + extra = snaphtdp/2 + plusht = plusht + extra + plusdp = plusdp + extra + if t then + t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_halfline,plusht,plusdp) + end + end + if method[v_line] then -- extra line + extra = snaphtdp + plusht = plusht + extra + plusdp = plusdp + extra + if t then + t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_line,plusht,plusdp) + end + end + if method[v_halfline_m] then -- extra halfline + extra = - snaphtdp/2 + plusht = plusht + extra + plusdp = plusdp + extra + if t then + t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_halfline_m,plusht,plusdp) + end + end + if method[v_line_m] then -- extra line + extra = - snaphtdp + plusht = plusht + extra + plusdp = plusdp + extra + if t then + t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_line_m,plusht,plusdp) + end + end + if method[v_first] then + local thebox = current + local id = getid(thebox) + if id == hlist_code then + thebox = validvbox(id,getlist(thebox)) + id = thebox and getid(thebox) + end + if thebox and id == vlist_code then + local list = getlist(thebox) + local lw, lh, ld + for n in nexthlist, list do + lw, lh, ld = getwhd(n) + break + end + if lh then + local wd, ht, dp = getwhd(thebox) + if t then + t[#t+1] = formatters["first line: height %p depth %p"](lh,ld) + t[#t+1] = formatters["dimensions: height %p depth %p"](ht,dp) + end + local delta = h - lh + ch, cd = lh, delta + d + h, d = ch, cd + local shifted = hpack_node(getlist(current)) + setshift(shifted,delta) + setlist(current,shifted) + done = true + if t then + t[#t+1] = formatters["first: height %p depth %p shift %p"](ch,cd,delta) + end + elseif t then + t[#t+1] = "first: not done, no content" + end + elseif t then + t[#t+1] = "first: not done, no vbox" + end + elseif method[v_last] then + local thebox = current + local id = getid(thebox) + if id == hlist_code then + thebox = validvbox(id,getlist(thebox)) + id = thebox and getid(thebox) + end + if thebox and id == vlist_code then + local list = getlist(thebox) + local lw, lh, ld + for n in nexthlist, list do + lw, lh, ld = getwhd(n) + end + if lh then + local wd, ht, dp = getwhd(thebox) + if t then + t[#t+1] = formatters["last line: height %p depth %p" ](lh,ld) + t[#t+1] = formatters["dimensions: height %p depth %p"](ht,dp) + end + local delta = d - ld + cd, ch = ld, delta + h + h, d = ch, cd + local shifted = hpack_node(getlist(current)) + setshift(shifted,delta) + setlist(current,shifted) + done = true + if t then + t[#t+1] = formatters["last: height %p depth %p shift %p"](ch,cd,delta) + end + elseif t then + t[#t+1] = "last: not done, no content" + end + elseif t then + t[#t+1] = "last: not done, no vbox" + end + end + if method[v_minheight] then + ch = floored((h-hr*snapht)/snaphtdp)*snaphtdp + plusht + if t then + t[#t+1] = formatters["minheight: %p"](ch) + end + elseif method[v_maxheight] then + ch = ceiled((h-hr*snapht)/snaphtdp)*snaphtdp + plusht + if t then + t[#t+1] = formatters["maxheight: %p"](ch) + end + else + ch = plusht + if t then + t[#t+1] = formatters["set height: %p"](ch) + end + end + if method[v_mindepth] then + cd = floored((d-dr*snapdp)/snaphtdp)*snaphtdp + plusdp + if t then + t[#t+1] = formatters["mindepth: %p"](cd) + end + elseif method[v_maxdepth] then + cd = ceiled((d-dr*snapdp)/snaphtdp)*snaphtdp + plusdp + if t then + t[#t+1] = formatters["maxdepth: %p"](cd) + end + else + cd = plusdp + if t then + t[#t+1] = formatters["set depth: %p"](cd) + end + end + if method[v_top] then + ch = ch + tlines * snaphtdp + if t then + t[#t+1] = formatters["top height: %p"](ch) + end + end + if method[v_bottom] then + cd = cd + blines * snaphtdp + if t then + t[#t+1] = formatters["bottom depth: %p"](cd) + end + end + local offset = method[v_offset] + if offset then + -- we need to set the attr + if t then + local wd, ht, dp = getwhd(current) + t[#t+1] = formatters["before offset: %p (width %p height %p depth %p)"](offset,wd,ht,dp) + end + local shifted = hpack_node(getlist(current)) + setshift(shifted,offset) + setlist(current,shifted) + if t then + local wd, ht, dp = getwhd(current) + t[#t+1] = formatters["after offset: %p (width %p height %p depth %p)"](offset,wd,ht,dp) + end + setattr(shifted,a_snapmethod,0) + setattr(current,a_snapmethod,0) + end + if not height then + setheight(current,ch) + if t then + t[#t+1] = formatters["forced height: %p"](ch) + end + end + if not depth then + setdepth(current,cd) + if t then + t[#t+1] = formatters["forced depth: %p"](cd) + end + end + local lines = (ch+cd)/snaphtdp + if t then + local original = (h+d)/snaphtdp + local whatever = (ch+cd)/(texgetdimen("globalbodyfontstrutheight") + texgetdimen("globalbodyfontstrutdepth")) + t[#t+1] = formatters["final lines : %p -> %p (%p)"](original,lines,whatever) + t[#t+1] = formatters["final height: %p -> %p"](h,ch) + t[#t+1] = formatters["final depth : %p -> %p"](d,cd) + end +-- todo: +-- +-- if h < 0 or d < 0 then +-- h = 0 +-- d = 0 +-- end + if t then + report_snapper("trace: %s type %s\n\t%\n\tt",where,nodecodes[getid(current)],t) + end + if not method[v_split] then + -- so extra will not be compensated at the top of a page + extra = 0 + end + return h, d, ch, cd, lines, extra +end + +local categories = { + [0] = "discard", + [1] = "largest", + [2] = "force", + [3] = "penalty", + [4] = "add", + [5] = "disable", + [6] = "nowhite", + [7] = "goback", + [8] = "packed", + [9] = "overlay", + [10] = "enable", + [11] = "notopskip", +} + +categories = allocate(table.swapped(categories,categories)) +vspacing.categories = categories + +function vspacing.tocategories(str) + local t = { } + for s in gmatch(str,"[^, ]") do -- use lpeg instead + local n = tonumber(s) + if n then + t[categories[n]] = true + else + t[b] = true + end + end + return t +end + +function vspacing.tocategory(str) -- can be optimized + if type(str) == "string" then + return set.tonumber(vspacing.tocategories(str)) + else + return set.tonumber({ [categories[str]] = true }) + end +end + +vspacingdata.map = vspacingdata.map or { } -- allocate ? +vspacingdata.skip = vspacingdata.skip or { } -- allocate ? + +storage.register("builders/vspacing/data/map", vspacingdata.map, "builders.vspacing.data.map") +storage.register("builders/vspacing/data/skip", vspacingdata.skip, "builders.vspacing.data.skip") + +do + + local P, C, R, S, Cc, Cs = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.Cc, lpeg.Cs + + vspacing.fixed = false + + local map = vspacingdata.map + local skip = vspacingdata.skip + + local sign = S("+-")^0 + local multiplier = C(sign * R("09")^1) * P("*") + local singlefier = Cs(sign * Cc(1)) + local separator = S(", ") + local category = P(":") * C((1-separator)^1) + local keyword = C((1-category-separator)^1) + local splitter = (multiplier + Cc(1)) * keyword * (category + Cc(false)) + + local k_fixed = variables.fixed + local k_flexible = variables.flexible + local k_category = "category" + local k_penalty = "penalty" + local k_order = "order" + + 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 + + local expandmacro = token.expand_macro + -- local runlocal = tex.runlocal + -- local setmacro = tokens.setters.macro + -- local settoks = tex.settoks + local toscaled = tex.toscaled + + local setattrs = nuts.setattrs + + local b_done = false + local b_packed = false + + local b_amount = 0 + local b_stretch = 0 + local b_shrink = 0 + local b_category = false + local b_penalty = false + local b_order = false + local b_fixed = false + local b_grid = false + + local pattern = nil + + local packed = categories.packed + + local gluefactor = .25 + + local ctx_ignoreparskip = context.core.ignoreparskip + + local function before() + b_amount = 0 + b_stretch = 0 + b_shrink = 0 + b_category = 1 + b_penalty = false + b_order = false + b_fixed = b_grid + end + + local function after() + if b_fixed then + b_stretch = 0 + b_shrink = 0 + else + b_stretch = gluefactor * b_amount + b_shrink = gluefactor * b_amount + end + end + + -- use a cache for predefined ones + + local function inject() + local n = new_glue(b_amount,b_stretch,b_shrink) + setattrs(n,false,a_skipcategory,b_category,a_skippenalty,b_penalty,a_skiporder,b_order or 1) + write_node(n) + end + + local function flush() + after() + if b_done then + inject() + b_done = false + end + before() + end + + -- local cmd = token.create("vspacingfromtempstring") + -- local cmd = token.create("vspacingpredefinedvalue") -- not yet known + + local function handler(multiplier, keyword, detail) + if not keyword then + report_vspacing("unknown directive %a",s) + else + local mk = map[keyword] + if mk then + lpegmatch(pattern,mk) + elseif keyword == k_fixed then + b_fixed = true + elseif keyword == k_flexible then + b_flexible = false + elseif keyword == k_category then + local category = tonumber(detail) + if category == packed then + b_packed = true + elseif category then + b_category = category + b_done = true + flush() + end + elseif keyword == k_order and detail then + local order = tonumber(detail) + if order then + b_order = order + end + elseif keyword == k_penalty and detail then + local penalty = tonumber(detail) + if penalty then + flush() + b_done = true + b_category = 3 + b_penalty = penalty + flush() + end + else + local amount, stretch, shrink + multiplier = tonumber(multiplier) or 1 + local sk = skip[keyword] + if sk then + -- multiplier, keyword + -- best, for now, todo: runlocal with arguments + expandmacro("vspacingpredefinedvalue",true,keyword) + -- expandmacro(cmd,true,keyword) + -- setmacro("tempstring",keyword) + -- runlocal(cmd) + -- nicest + -- runlocal(cache[keyword]) + -- fast + -- settoks("scratchtoks",keyword) + -- runlocal("vspacingfromscratchtoks") + -- middleground + -- setmacro("tempstring",keyword) + -- runlocal(ctx_vspacingfromtempstring) + -- + amount, stretch, shrink = texgetglue("scratchskip") + if not stretch then + stretch = 0 + end + if not shrink then + shrink = 0 + end + if stretch == 0 and shrink == 0 then + stretch = gluefactor * amount -- always unless grid + shrink = stretch -- always unless grid + end + else -- no check, todo: parse plus and minus + amount = toscaled(keyword) + stretch = gluefactor * amount -- always unless grid + shrink = stretch -- always unless grid + end + -- we look at fixed later + b_amount = b_amount + multiplier * amount + b_stretch = b_stretch + multiplier * stretch + b_shrink = b_shrink + multiplier * shrink + b_done = true + end + end + end + + -- alternatively we can make a table and have a keyword -> split cache but this is probably + -- not really a bottleneck + + local splitter = ((multiplier + singlefier) * keyword * (category + Cc(false))) / handler + pattern = (splitter + separator^1)^0 + + function vspacing.inject(grid,str) + if trace_vspacing then + -- ctx_pushlogger(report_vspacing) + end + b_done = false + b_packed = false + b_grid = grid + before() + lpegmatch(pattern,str) + after() + if b_done then + inject() + end + if b_packed then + ctx_ignoreparskip() + end + if trace_vspacing then + -- ctx_poplogger() + end + end + + function vspacing.injectpenalty(penalty) + local n = new_glue() + setattrs(n,false,a_skipcategory,categories.penalty,a_skippenalty,penalty,a_skiporder,1) + write_node(n) + end + + function vspacing.injectskip(amount) + local n = new_glue(amount) + setattrs(n,false,a_skipcategory,categories.largest,a_skippenalty,false,a_skiporder,1) + write_node(n) + end + + function vspacing.injectdisable(amount) + local n = new_glue() + setattrs(n,false,a_skipcategory,categories.disable,a_skippenalty,false,a_skiporder,1) + write_node(n) + end + +end + +-- implementation + +-- alignment box begin_of_par vmode_par hmode_par insert penalty before_display after_display + +function vspacing.snapbox(n,how) + local sv = snapmethods[how] + if sv then + local box = getbox(n) + local list = getlist(box) + if list then + local s = getattr(list,a_snapmethod) + if s == 0 then + if trace_vsnapping then + -- report_snapper("box list not snapped, already done") + end + else + local wd, ht, dp = getwhd(box) + if false then -- todo: already_done + -- assume that the box is already snapped + if trace_vsnapping then + report_snapper("box list already snapped at (%p,%p): %s", + ht,dp,listtoutf(list)) + end + else + local h, d, ch, cd, lines, extra = snap_hlist("box",box,sv,ht,dp) + setprop(box,"snapper",{ + ht = h, + dp = d, + ch = ch, + cd = cd, + extra = extra, + current = current, + }) + setwhd(box,wd,ch,cd) + if trace_vsnapping then + report_snapper("box list snapped from (%p,%p) to (%p,%p) using method %a (%s) for %a (%s lines): %s", + h,d,ch,cd,sv.name,sv.specification,"direct",lines,listtoutf(list)) + end + setattr(box,a_snapmethod,0) -- + setattr(list,a_snapmethod,0) -- yes or no + end + end + end + end +end + +-- I need to figure out how to deal with the prevdepth that crosses pages. In fact, +-- prevdepth is often quite interfering (even over a next paragraph) so I need to +-- figure out a trick. Maybe use something other than a rule. If we visualize we'll +-- see the baselineskip in action: +-- +-- \blank[force,5*big] { \baselineskip1cm xxxxxxxxx \par } \page +-- \blank[force,5*big] { \baselineskip1cm xxxxxxxxx \par } \page +-- \blank[force,5*big] { \baselineskip5cm xxxxxxxxx \par } \page + +-- We can register and copy the rule instead. + +do + + local insert_node_after = nuts.insert_after + local insert_node_before = nuts.insert_before + + local abovedisplayskip_code = gluecodes.abovedisplayskip + local belowdisplayskip_code = gluecodes.belowdisplayskip + local abovedisplayshortskip_code = gluecodes.abovedisplayshortskip + local belowdisplayshortskip_code = gluecodes.belowdisplayshortskip + + local w, h, d = 0, 0, 0 + ----- w, h, d = 100*65536, 65536, 65536 + + local trace_list = { } + local tracing_info = { } + local before = "" + local after = "" + + local function nodes_to_string(head) + local current = head + local t = { } + while current do + local id = getid(current) + local ty = nodecodes[id] + if id == penalty_code then + t[#t+1] = formatters["%s:%s"](ty,getpenalty(current)) + elseif id == glue_code then + t[#t+1] = formatters["%s:%s:%p"](ty,gluecodes[getsubtype(current)],getwidth(current)) + elseif id == kern_code then + t[#t+1] = formatters["%s:%p"](ty,getkern(current)) + else + t[#t+1] = ty + end + current = getnext(current) + 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", formatters["%s | %p | category %s | order %s | penalty %s"](str, getwidth(data), sc or "-", so or "-", sp or "-") } + tracing_info = true + end + + local function trace_natural(str,data) + trace_list[#trace_list+1] = { "skip", formatters["%s | %p"](str, getwidth(data)) } + tracing_info = true + end + + local function trace_info(message, where, what) + trace_list[#trace_list+1] = { "info", formatters["%s: %s/%s"](message,where,what) } + end + + local function trace_node(what) + local nt = nodecodes[getid(what)] + local tl = trace_list[#trace_list] + if tl and tl[1] == "node" then + trace_list[#trace_list] = { "node", formatters["%s + %s"](tl[2],nt) } + else + trace_list[#trace_list+1] = { "node", nt } + end + 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 + report_collapser(text) + else + report_collapser(" %s: %s",tag,text) + end + end + report_collapser("before: %s",before) + report_collapser("after : %s",after) + end + end + + local function trace_done(str,data) + if getid(data) == penalty_code then + trace_list[#trace_list+1] = { "penalty", formatters["%s | %s"](str,getpenalty(data)) } + else + trace_list[#trace_list+1] = { "glue", formatters["%s | %p"](str,getwidth(data)) } + end + tracing_info = true + end + + local function forced_skip(head,current,width,where,trace) -- looks old ... we have other tricks now + if head == current then + if getsubtype(head) == baselineskip_code then + width = width - getwidth(head) + end + end + if width == 0 then + -- do nothing + elseif where == "after" then + head, current = insert_node_after(head,current,new_rule(w,h,d)) + head, current = insert_node_after(head,current,new_kern(width)) + head, current = insert_node_after(head,current,new_rule(w,h,d)) + else + local c = current + head, current = insert_node_before(head,current,new_rule(w,h,d)) + head, current = insert_node_before(head,current,new_kern(width)) + head, current = insert_node_before(head,current,new_rule(w,h,d)) + current = c + end + if trace then + report_vspacing("inserting forced skip of %p",width) + end + return head, current + end + + -- penalty only works well when before skip + + local discard = categories.discard + local largest = categories.largest + local force = categories.force + local penalty = categories.penalty + local add = categories.add + local disable = categories.disable + local nowhite = categories.nowhite + local goback = categories.goback + local packed = categories.packed + local overlay = categories.overlay + local enable = categories.enable + local notopskip = categories.notopskip + + -- [whatsits][hlist][glue][glue][penalty] + + local special_penalty_min = 32250 + local special_penalty_max = 35000 + local special_penalty_xxx = 0 + + -- this is rather messy and complex: we want to make sure that successive + -- header don't break but also make sure that we have at least a decent + -- break when we have succesive ones (often when testing) + + -- todo: mark headers as such so that we can recognize them + + local specialmethods = { } + local specialmethod = 1 + + specialmethods[1] = function(pagehead,pagetail,start,penalty) + -- + if not pagehead or penalty < special_penalty_min or penalty > special_penalty_max then + return + end + local current = pagetail + -- + -- nodes.showsimplelist(pagehead,0) + -- + if trace_specials then + report_specials("checking penalty %a",penalty) + end + while current do + local id = getid(current) + if id == penalty_code then + local p = properties[current] + if p then + local p = p.special_penalty + if not p then + if trace_specials then + report_specials(" regular penalty, continue") + end + elseif p == penalty then + if trace_specials then + report_specials(" context penalty %a, same level, overloading",p) + end + return special_penalty_xxx + elseif p > special_penalty_min and p < special_penalty_max then + if penalty < p then + if trace_specials then + report_specials(" context penalty %a, lower level, overloading",p) + end + return special_penalty_xxx + else + if trace_specials then + report_specials(" context penalty %a, higher level, quitting",p) + end + return + end + elseif trace_specials then + report_specials(" context penalty %a, higher level, continue",p) + end + else + local p = getpenalty(current) + if p < 10000 then + -- assume some other mechanism kicks in so we seem to have content + if trace_specials then + report_specials(" regular penalty %a, quitting",p) + end + break + else + if trace_specials then + report_specials(" regular penalty %a, continue",p) + end + end + end + end + current = getprev(current) + end + -- none found, so no reson to be special + if trace_specials then + if pagetail then + report_specials(" context penalty, discarding, nothing special") + else + report_specials(" context penalty, discarding, nothing preceding") + end + end + return special_penalty_xxx + end + + -- This will be replaced after 0.80+ when we have a more robust look-back and + -- can look at the bigger picture. + + -- todo: look back and when a special is there before a list is seen penalty keep ut + + -- we now look back a lot, way too often + + -- userskip + -- lineskip + -- baselineskip + -- parskip + -- abovedisplayskip + -- belowdisplayskip + -- abovedisplayshortskip + -- belowdisplayshortskip + -- topskip + -- splittopskip + + -- we could inject a vadjust to force a recalculation .. a mess + -- + -- So, the next is far from robust and okay but for the moment this overlaying + -- has to do. Always test this with the examples in spac-ver.mkvi! + + local function snap_topskip(current,method) + local w = getwidth(current) + setwidth(current,0) + return w, 0 + end + + local function check_experimental_overlay(head,current) + local p = nil + local c = current + local n = nil + local function overlay(p,n,mvl) + local p_wd, p_ht, p_dp = getwhd(p) + local n_wd, n_ht, n_dp = getwhd(n) + local skips = 0 + -- + -- We deal with this at the tex end .. we don't see spacing .. enabling this code + -- is probably harmless but then we need to test it. + -- + -- we could calculate this before we call + -- + -- problem: prev list and next list can be unconnected + -- + local c = getnext(p) + local l = c + while c and c ~= n do + local id = getid(c) + if id == glue_code then + skips = skips + getwidth(c) + elseif id == kern_code then + skips = skips + getkern(c) + end + l = c + c = getnext(c) + end + local c = getprev(n) + while c and c ~= n and c ~= l do + local id = getid(c) + if id == glue_code then + skips = skips + getwidth(c) + elseif id == kern_code then + skips = skips + getkern(c) + end + c = getprev(c) + end + -- + local delta = n_ht + skips + p_dp + texsetdimen("global","d_spac_overlay",-delta) -- for tracing + -- we should adapt pagetotal ! (need a hook for that) .. now we have the wrong pagebreak + local k = new_kern(-delta) + head = insert_node_before(head,n,k) + if n_ht > p_ht then + local k = new_kern(n_ht-p_ht) + head = insert_node_before(head,p,k) + end + if trace_vspacing then + report_vspacing("overlaying, prev height: %p, prev depth: %p, next height: %p, skips: %p, move up: %p",p_ht,p_dp,n_ht,skips,delta) + end + return remove_node(head,current,true) + end + + -- goto next line + while c do + local id = getid(c) + if id == glue_code or id == penalty_code or id == kern_code then + -- skip (actually, remove) + c = getnext(c) + elseif id == hlist_code then + n = c + break + else + break + end + end + if n then + -- we have a next line, goto prev line + c = current + while c do + local id = getid(c) + if id == glue_code or id == penalty_code then -- kern ? + c = getprev(c) + elseif id == hlist_code then + p = c + break + else + break + end + end + if not p then + if a_snapmethod == a_snapvbox then + -- quit, we're not on the mvl + else + -- inefficient when we're at the end of a page + local c = tonut(texlists.page_head) + while c and c ~= n do + local id = getid(c) + if id == hlist_code then + p = c + end + c = getnext(c) + end + if p and p ~= n then + return overlay(p,n,true) + end + end + elseif p ~= n then + return overlay(p,n,false) + end + end + -- in fact, we could try again later ... so then no remove (a few tries) + return remove_node(head,current,true) + end + + local function collapser(head,where,what,trace,snap,a_snapmethod) -- maybe also pass tail + if trace then + reset_tracing(head) + end + local current = head + local oldhead = head + local glue_order = 0 + local glue_data + local force_glue = false + local penalty_order = 0 + local penalty_data + local natural_penalty + local special_penalty + local parskip + local ignore_parskip = false + local ignore_following = false + local ignore_whitespace = false + local keep_together = false + local lastsnap + local pagehead + local pagetail + -- + -- todo: keep_together: between headers + -- + local function getpagelist() + if not pagehead then + pagehead = texlists.page_head + if pagehead then + pagehead = tonut(pagehead) + pagetail = find_node_tail(pagehead) -- no texlists.page_tail yet-- no texlists.page_tail yet + end + end + end + -- + local function compensate(n) + local g = 0 + while n and getid(n) == glue_code do + g = g + getwidth(n) + n = getnext(n) + end + if n then + local p = getprop(n,"snapper") + if p then + local extra = p.extra + if extra and extra < 0 then -- hm, extra can be unset ... needs checking + local h = p.ch -- getheight(n) + -- maybe an extra check + -- if h - extra < g then + setheight(n,h-2*extra) + p.extra = 0 + if trace_vsnapping then + report_snapper("removed extra space at top: %p",extra) + end + -- end + end + end + return n + end + end + -- + local function removetopsnap() + getpagelist() + if pagehead then + local n = pagehead and compensate(pagehead) + if n and n ~= pagetail then + local p = getprop(pagetail,"snapper") + if p then + local e = p.extra + if e and e < 0 then + local t = texget("pagetotal") + if t > 0 then + local g = texget("pagegoal") -- 1073741823 is signal + local d = g - t + if d < -e then + local penalty = new_penalty(1000000) + setlink(penalty,head) + head = penalty + report_snapper("force pagebreak due to extra space at bottom: %p",e) + end + end + end + end + end + elseif head then + compensate(head) + end + end + -- + local function getavailable() + getpagelist() + if pagehead then + local t = texget("pagetotal") + if t > 0 then + local g = texget("pagegoal") + return g - t + end + end + return false + end + -- + local function flush(why) + if penalty_data then + local p = new_penalty(penalty_data) + if trace then + trace_done("flushed due to " .. why,p) + end + if penalty_data >= 10000 then -- or whatever threshold? + local prev = getprev(current) + if getid(prev) == glue_code then -- maybe go back more, or maybe even push back before any glue + -- tricky case: spacing/grid-007.tex: glue penalty glue + head = insert_node_before(head,prev,p) + else + head = insert_node_before(head,current,p) + end + else + head = insert_node_before(head,current,p) + end + -- if penalty_data > special_penalty_min and penalty_data < special_penalty_max then + local props = properties[p] + if props then + props.special_penalty = special_penalty or penalty_data + else + properties[p] = { + special_penalty = special_penalty or penalty_data + } + end + -- end + end + if glue_data then + if force_glue then + if trace then + trace_done("flushed due to forced " .. why,glue_data) + end + head = forced_skip(head,current,getwidth(glue_data,width),"before",trace) + flush_node(glue_data) + else + local width, stretch, shrink = getglue(glue_data) + if width ~= 0 then + if trace then + trace_done("flushed due to non zero " .. why,glue_data) + end + head = insert_node_before(head,current,glue_data) + elseif stretch ~= 0 or shrink ~= 0 then + if trace then + trace_done("flushed due to stretch/shrink in" .. why,glue_data) + end + head = insert_node_before(head,current,glue_data) + else + -- report_vspacing("needs checking (%s): %p",gluecodes[getsubtype(glue_data)],w) + flush_node(glue_data) + end + 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 + report_snapper("global ht/dp = %p/%p, local ht/dp = %p/%p", + texgetdimen("globalbodyfontstrutheight"), + texgetdimen("globalbodyfontstrutdepth"), + texgetdimen("bodyfontstrutheight"), + texgetdimen("bodyfontstrutdepth") + ) + end + if trace then + trace_info("start analyzing",where,what) + end + if snap and where == "page" then + removetopsnap() + end + while current do + local id = getid(current) + if id == hlist_code or id == vlist_code then + -- needs checking, why so many calls + if snap then + lastsnap = nil + local list = getlist(current) + local s = getattr(current,a_snapmethod) + if not s then + -- if trace_vsnapping then + -- report_snapper("mvl list not snapped") + -- end + elseif s == 0 then + if trace_vsnapping then + report_snapper("mvl %a not snapped, already done: %s",nodecodes[id],listtoutf(list)) + end + else + local sv = snapmethods[s] + if sv then + -- check if already snapped + local done = list and already_done(id,list,a_snapmethod) + if done then + -- assume that the box is already snapped + if trace_vsnapping then + local w, h, d = getwhd(current) + report_snapper("mvl list already snapped at (%p,%p): %s",h,d,listtoutf(list)) + end + else + local h, d, ch, cd, lines, extra = snap_hlist("mvl",current,sv,false,false) + lastsnap = { + ht = h, + dp = d, + ch = ch, + cd = cd, + extra = extra, + current = current, + } + setprop(current,"snapper",lastsnap) + if trace_vsnapping then + report_snapper("mvl %a snapped from (%p,%p) to (%p,%p) using method %a (%s) for %a (%s lines): %s", + nodecodes[id],h,d,ch,cd,sv.name,sv.specification,where,lines,listtoutf(list)) + end + end + elseif trace_vsnapping then + report_snapper("mvl %a not snapped due to unknown snap specification: %s",nodecodes[id],listtoutf(list)) + end + setattr(current,a_snapmethod,0) + end + else + -- + end + -- tex.prevdepth = 0 + flush("list") + current = getnext(current) + elseif id == penalty_code then + -- natural_penalty = getpenalty(current) + -- if trace then + -- trace_done("removed penalty",current) + -- end + -- head, current = remove_node(head,current,true) + current = getnext(current) + elseif id == kern_code then + if snap and trace_vsnapping and getkern(current) ~= 0 then + report_snapper("kern of %p kept",getkern(current)) + end + flush("kern") + current = getnext(current) + elseif id == glue_code then + local subtype = getsubtype(current) + if subtype == userskip_code then + local sc, so, sp = getattrs(current,a_skipcategory,a_skiporder,a_skippenalty) + if not so then + so = 1 -- the others have no default value + end + if sp and sc == penalty then + if where == "page" then + getpagelist() + local p = specialmethods[specialmethod](pagehead,pagetail,current,sp) + if p then + -- todo: other tracer + -- + -- if trace then + -- trace_skip("previous special penalty %a is changed to %a using method %a",sp,p,specialmethod) + -- end + special_penalty = sp + sp = p + end + end + 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 = insert_node_before(head,current,glue_data) + if trace then + trace_natural("natural",current) + end + current = getnext(current) + glue_data = nil + else + -- not look back across head + -- todo: prev can be whatsit (latelua) + local previous = getprev(current) + if previous and getid(previous) == glue_code and getsubtype(previous) == userskip_code then + local pwidth, pstretch, pshrink, pstretch_order, pshrink_order = getglue(previous) + local cwidth, cstretch, cshrink, cstretch_order, cshrink_order = getglue(current) + if pstretch_order == 0 and pshrink_order == 0 and cstretch_order == 0 and cshrink_order == 0 then + setglue(previous,pwidth + cwidth, pstretch + cstretch, pshrink + cshrink) + if trace then + trace_natural("removed",current) + end + head, current = remove_node(head,current,true) + if trace then + trace_natural("collapsed",previous) + end + else + if trace then + trace_natural("filler",current) + end + current = getnext(current) + end + else + if trace then + trace_natural("natural (no prev)",current) + end + current = getnext(current) + end + end + glue_order = 0 + elseif sc == disable or sc == enable then + local next = getnext(current) + if next then + ignore_following = sc == disable + if trace then + trace_skip(sc == disable and "disable" or "enable",sc,so,sp,current) + end + head, current = remove_node(head,current,true) + else + current = next + end + elseif sc == packed then + if trace then + trace_skip("packed",sc,so,sp,current) + end + -- can't happen ! + head, current = remove_node(head,current,true) + elseif sc == nowhite then + local next = getnext(current) + if next then + ignore_whitespace = true + head, current = remove_node(head,current,true) + else + current = next + end + elseif sc == discard then + if trace then + trace_skip("discard",sc,so,sp,current) + end + head, current = remove_node(head,current,true) + elseif sc == overlay then + -- todo (overlay following line over previous + if trace then + trace_skip("overlay",sc,so,sp,current) + end + -- beware: head can actually be after the affected nodes as + -- we look back ... some day head will the real head + head, current = check_experimental_overlay(head,current,a_snapmethod) + 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 + flush_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 cw = getwidth(current) + local gw = getwidth(glue_data) + if cw > gw then + if trace then + trace_skip("largest",sc,so,sp,current) + end + flush_node(glue_data) + 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 + flush_node(glue_data) + 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 + flush_node(glue_data) + head, current, glue_data = remove_node(head,current) + elseif sc == penalty then + if trace then + trace_skip("penalty",sc,so,sp,current) + end + flush_node(glue_data) + 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 cwidth, cstretch, cshrink = getglue(current) + local gwidth, gstretch, gshrink = getglue(glue_data) + setglue(glue_data,gwidth + cwidth, gstretch + cstretch,gshrink + cshrink) + -- 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 == lineskip_code then + if snap then + local s = getattr(current,a_snapmethod) + if s and s ~= 0 then + setattr(current,a_snapmethod,0) + setwidth(current,0) + if trace_vsnapping then + report_snapper("lineskip set to zero") + 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 = getnext(current) + elseif subtype == baselineskip_code then + if snap then + local s = getattr(current,a_snapmethod) + if s and s ~= 0 then + setattr(current,a_snapmethod,0) + setwidth(current,0) + if trace_vsnapping then + report_snapper("baselineskip set to zero") + 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 = getnext(current) + elseif subtype == parskip_code 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 w = getwidth(current) + if w ~= 0 and w > getwidth(glue_data) then +flush_node(glue_data) + glue_data = current + if trace then + trace_natural("taking parskip",current) + end + head, current = remove_node(head,current) + else + if trace then + trace_natural("removed parskip",current) + end + head, current = remove_node(head,current,true) + end + else + if trace then + trace_natural("honored parskip",current) + end + head, current, glue_data = remove_node(head,current) + end + elseif subtype == topskip_code or subtype == splittopskip_code then + local next = getnext(current) + if next and getattr(next,a_skipcategory) == notopskip then + nuts.setglue(current) -- zero + end + if snap then + local s = getattr(current,a_snapmethod) + if s and s ~= 0 then + setattr(current,a_snapmethod,0) + local sv = snapmethods[s] + local w, cw = snap_topskip(current,sv) + if trace_vsnapping then + report_snapper("topskip snapped from %p to %p for %a",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 = getnext(current) + elseif subtype == abovedisplayskip_code and remove_math_skips then + -- + if trace then + trace_skip("above display skip (normal)",sc,so,sp,current) + end + flush("above display skip (normal)") + current = getnext(current) + -- + elseif subtype == belowdisplayskip_code and remove_math_skips then + -- + if trace then + trace_skip("below display skip (normal)",sc,so,sp,current) + end + flush("below display skip (normal)") + current = getnext(current) + -- + elseif subtype == abovedisplayshortskip_code and remove_math_skips then + -- + if trace then + trace_skip("above display skip (short)",sc,so,sp,current) + end + flush("above display skip (short)") + current = getnext(current) + -- + elseif subtype == belowdisplayshortskip_code and remove_math_skips then + -- + if trace then + trace_skip("below display skip (short)",sc,so,sp,current) + end + flush("below display skip (short)") + current = getnext(current) + -- + else -- other glue + if snap and trace_vsnapping then + local w = getwidth(current) + if w ~= 0 then + report_snapper("glue %p of type %a kept",w,gluecodes[subtype]) + end + end + if trace then + trace_skip(formatters["glue of type %a"](subtype),sc,so,sp,current) + end + flush("some glue") + current = getnext(current) + end + else + flush(trace and formatters["node with id %a"](id) or "other node") + current = getnext(current) + 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 = new_penalty(penalty_data) + if trace then + trace_done("result",p) + end + setlink(tail,p) + -- if penalty_data > special_penalty_min and penalty_data < special_penalty_max then + local props = properties[p] + if props then + props.special_penalty = special_penalty or penalty_data + else + properties[p] = { + special_penalty = special_penalty or penalty_data + } + end + -- end + 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,getwidth(glue_data),"after",trace) + flush_node(glue_data) + glue_data = nil + elseif tail then + setlink(tail,glue_data) +setnext(glue_data) + else + head = glue_data + end + texnest[texnest.ptr].prevdepth = 0 -- appending to the list bypasses tex's prevdepth handler + 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 %a to %a",nodecodes[getid(oldhead)],nodecodes[getid(head)]) + end + end + return head + end + + local stackhead, stacktail, stackhack = nil, nil, false + + local function report(message,where,lst) + if lst and where then + report_vspacing(message,where,count_nodes(lst,true),nodeidstostring(lst)) + else + report_vspacing(message,count_nodes(lst,true),nodeidstostring(lst)) + end + end + + -- ugly code: we get partial lists (check if this stack is still okay) ... and we run + -- into temp nodes (sigh) + + local forceflush = false + + function vspacing.pagehandler(newhead,where) + if newhead then + local newtail = find_node_tail(newhead) -- best pass that tail, known anyway + local flush = false + stackhack = true -- todo: only when grid snapping once enabled + -- todo: fast check if head = tail + for n, id, subtype in nextnode, newhead do -- we could just look for glue nodes + if id ~= glue_code then + flush = true + elseif subtype == userskip_code then + if getattr(n,a_skipcategory) then + stackhack = true + else + flush = true + end + elseif subtype == parskip_code then + -- if where == new_graf then ... end + if texgetcount("c_spac_vspacing_ignore_parskip") > 0 then + -- texsetcount("c_spac_vspacing_ignore_parskip",0) + setglue(n) + -- maybe removenode + end + end + end + texsetcount("c_spac_vspacing_ignore_parskip",0) + + if forceflush then + forceflush = false + flush = true + end + + if flush then + if stackhead then + if trace_collect_vspacing then report("%s > appending %s nodes to stack (final): %s",where,newhead) end + setlink(stacktail,newhead) + newhead = stackhead + stackhead = nil + stacktail = nil + end + if stackhack then + stackhack = false + if trace_collect_vspacing then report("%s > processing %s nodes: %s",where,newhead) end + newhead = collapser(newhead,"page",where,trace_page_vspacing,true,a_snapmethod) + else + if trace_collect_vspacing then report("%s > flushing %s nodes: %s",where,newhead) end + end + return newhead + else + if stackhead then + if trace_collect_vspacing then report("%s > appending %s nodes to stack (intermediate): %s",where,newhead) end + setlink(stacktail,newhead) + else + if trace_collect_vspacing then report("%s > storing %s nodes in stack (initial): %s",where,newhead) end + stackhead = newhead + end + stacktail = newtail + end + end + return nil + end + + function vspacing.pageoverflow() + local h = 0 + if stackhead then + for n, id in nextnode, stackhead do + if id == glue_code then + h = h + getwidth(n) + elseif id == kern_code then + h = h + getkern(n) + end + end + end + return h + end + + function vspacing.forcepageflush() + forceflush = true + end + + local ignore = table.tohash { + "split_keep", + "split_off", + -- "vbox", + } + + function vspacing.vboxhandler(head,where) + if head and not ignore[where] and getnext(head) then + if getnext(head) then -- what if a one liner and snapping? + head = collapser(head,"vbox",where,trace_vbox_vspacing,true,a_snapvbox) -- todo: local snapper + return head + end + end + return head + end + + function vspacing.collapsevbox(n,aslist) -- for boxes but using global a_snapmethod + local box = getbox(n) + if box then + local list = getlist(box) + if list then + list = collapser(list,"snapper","vbox",trace_vbox_vspacing,true,a_snapmethod) + if aslist then + setlist(box,list) -- beware, dimensions of box are wrong now + else + setlist(box,vpack_node(list)) + end + end + end + end + +end + +-- This one is needed to prevent bleeding of prevdepth to the next page +-- which doesn't work well with forced skips. I'm not that sure if the +-- following is a good way out. + +do + + local outer = texnest[0] + + local enabled = true + local trace = false + local report = logs.reporter("vspacing") + + trackers.register("vspacing.synchronizepage",function(v) + trace = v + end) + + directives.register("vspacing.synchronizepage",function(v) + enabled = v + end) + + local ignoredepth = -65536000 + + -- A previous version analyzed the number of lines moved to the next page in + -- synchronizepage because prevgraf is unreliable in that case. However, we cannot + -- tweak that parameter because it is also used in postlinebreak and hangafter, so + -- there is a danger for interference. Therefore we now do it dynamically. + + -- We can also support other lists but there prevgraf probably is ok. + + function vspacing.getnofpreviouslines(head) + if enabled then + if not thead then + head = texlists.page_head + end + local noflines = 0 + if head then + local tail = find_node_tail(tonut(head)) + while tail do + local id = getid(tail) + if id == hlist_code then + if getsubtype(tail) == linelist_code then + noflines = noflines + 1 + else + break + end + elseif id == vlist_code then + break + elseif id == glue_code then + local subtype = getsubtype(tail) + if subtype == baselineskip_code or subtype == lineskip_code then + -- we're ok + elseif subtype == parskip_code then + if getwidth(tail) > 0 then + break + else + -- we assume we're ok + end + end + elseif id == penalty_code then + -- we're probably ok + elseif id == rule_code or id == kern_code then + break + else + -- ins, mark, boundary, whatsit + end + tail = getprev(tail) + end + end + return noflines + end + end + + interfaces.implement { + name = "getnofpreviouslines", + public = true, + actions = vspacing.getnofpreviouslines, + } + + function vspacing.synchronizepage() + if enabled then + if trace then + local newdepth = outer.prevdepth + local olddepth = newdepth + if not texlists.page_head then + newdepth = ignoredepth + texset("prevdepth",ignoredepth) + outer.prevdepth = ignoredepth + end + report("page %i, prevdepth %p => %p",texgetcount("realpageno"),olddepth,newdepth) + -- report("list %s",nodes.idsandsubtypes(head)) + else + if not texlists.page_head then + texset("prevdepth",ignoredepth) + outer.prevdepth = ignoredepth + end + end + end + end + + local trace = false + local last = nil + local vmode_code = tex.modelevels.vertical + local temp_code = nodecodes.temp + local getnest = tex.getnest + local getlist = tex.getlist + + trackers.register("vspacing.forcestrutdepth",function(v) trace = v end) + + -- abs : negative is inner + + function vspacing.checkstrutdepth(depth) + local nest = getnest() + if abs(nest.mode) == vmode_code and nest.head then + local tail = nest.tail + local id = tail.id + if id == hlist_code then + if tail.depth < depth then + tail.depth = depth + end + nest.prevdepth = depth + elseif id == temp_code and getnest("ptr") == 0 then + local head = getlist("page_head") + if head then + tail = nodes.tail(head) + if tail and tail.id == hlist_code then + if tail.depth < depth then + tail.depth = depth + end + nest.prevdepth = depth + -- only works in lmtx + texset("pagedepth",depth) + end + end + end + end + end + + interfaces.implement { + name = "checkstrutdepth", + arguments = "dimension", + actions = vspacing.checkstrutdepth, + } + + function vspacing.forcestrutdepth(n,depth,trace_mode,plus) + local box = texgetbox(n) + if box then + box = tonut(box) + local head = getlist(box) + if head then + local tail = find_node_tail(head) + if tail then + if getid(tail) == hlist_code then + local dp = getdepth(tail) + if dp < depth then + setdepth(tail,depth) + outer.prevdepth = depth + if trace or trace_mode > 0 then + nuts.setvisual(tail,"depth") + end + end + end + last = nil + if plus then + -- penalty / skip ... + local height = 0 + local sofar = 0 + local same = false + local seen = false + local list = { } + last = nil + while tail do + local id = getid(tail) + if id == hlist_code or id == vlist_code then + local w, h, d = getwhd(tail) + height = height + h + d + sofar + sofar = 0 + last = tail + elseif id == kern_code then + sofar = sofar + getkern(tail) + elseif id == glue_code then + if seen then + sofar = sofar + getwidth(tail) + seen = false + else + break + end + elseif id == penalty_code then + local p = getpenalty(tail) + if p >= 10000 then + same = true + seen = true + else + break + end + else + break + end + tail = getprev(tail) + end + texsetdimen("global","d_spac_prevcontent",same and height or 0) + end + end + end + end + end + + function vspacing.pushatsame() + -- needs better checking ! + if last then -- setsplit + nuts.setnext(getprev(last)) + nuts.setprev(last) + end + end + + function vspacing.popatsame() + -- needs better checking ! + nuts.write(last) + end + +end + +-- interface + +do + + interfaces.implement { + name = "injectvspacing", + actions = vspacing.inject, + arguments = { "integer", "string" }, + } + + interfaces.implement { + name = "injectvpenalty", + actions = vspacing.injectpenalty, + arguments = "integer", + } + + interfaces.implement { + name = "injectvskip", + actions = vspacing.injectskip, + arguments = "dimension", + } + + interfaces.implement { + name = "injectdisable", + actions = vspacing.injectdisable, + } + + -- + + implement { + name = "synchronizepage", + actions = vspacing.synchronizepage, + scope = "private" + } + + implement { + name = "forcestrutdepth", + arguments = { "integer", "dimension", "integer" }, + actions = vspacing.forcestrutdepth, + scope = "private" + } + + implement { + name = "forcestrutdepthplus", + arguments = { "integer", "dimension", "integer", true }, + actions = vspacing.forcestrutdepth, + scope = "private" + } + + implement { + name = "pushatsame", + actions = vspacing.pushatsame, + scope = "private" + } + + implement { + name = "popatsame", + actions = vspacing.popatsame, + scope = "private" + } + + implement { + name = "vspacingsetamount", + actions = vspacing.setskip, + scope = "private", + arguments = "string", + } + + implement { + name = "vspacingdefine", + actions = vspacing.setmap, + scope = "private", + arguments = "2 strings", + } + + implement { + name = "vspacingcollapse", + actions = vspacing.collapsevbox, + scope = "private", + arguments = "integer" + } + + implement { + name = "vspacingcollapseonly", + actions = vspacing.collapsevbox, + scope = "private", + arguments = { "integer", true } + } + + implement { + name = "vspacingsnap", + actions = vspacing.snapbox, + scope = "private", + arguments = { "integer", "integer" } + } + + implement { + name = "definesnapmethod", + actions = vspacing.definesnapmethod, + scope = "private", + arguments = "2 strings", + } + + -- local remove_node = nodes.remove + -- local find_node_tail = nodes.tail + -- + -- interfaces.implement { + -- name = "fakenextstrutline", + -- actions = function() + -- local head = texlists.page_head + -- if head then + -- local head = remove_node(head,find_node_tail(head),true) + -- texlists.page_head = head + -- buildpage() + -- end + -- end + -- } + + implement { + name = "removelastline", + actions = function() + local head = texlists.page_head + if head then + local tail = find_node_tail(head) + if tail then + -- maybe check for hlist subtype 1 + local head = remove_node(head,tail,true) + texlists.page_head = head + buildpage() + end + end + end + } + + implement { + name = "showpagelist", -- will improve + actions = function() + local head = texlists.page_head + if head then + print("start") + while head do + print(" " .. tostring(head)) + head = head.next + end + end + end + } + + implement { + name = "pageoverflow", + actions = { vspacing.pageoverflow, context } + } + + implement { + name = "forcepageflush", + actions = vspacing.forcepageflush + } + +end |