summaryrefslogtreecommitdiff
path: root/tex/context/base/mkiv/spac-ver.lmt
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/mkiv/spac-ver.lmt')
-rw-r--r--tex/context/base/mkiv/spac-ver.lmt2623
1 files changed, 2623 insertions, 0 deletions
diff --git a/tex/context/base/mkiv/spac-ver.lmt b/tex/context/base/mkiv/spac-ver.lmt
new file mode 100644
index 000000000..bfbcdf772
--- /dev/null
+++ b/tex/context/base/mkiv/spac-ver.lmt
@@ -0,0 +1,2623 @@
+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 = math.ceil, math.floor
+local lpegmatch = lpeg.match
+local unpack = unpack or table.unpack
+local allocate = utilities.storage.allocate
+local todimen = string.todimen
+local formatters = string.formatters
+local abs = math.abs
+
+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 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 tonode = nuts.tonode
+
+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
+
+ do -- old variant
+
+ local ctx_fixedblankskip = context.fixedblankskip
+ local ctx_flexibleblankskip = context.flexibleblankskip
+ local ctx_setblankcategory = context.setblankcategory
+ local ctx_setblankorder = context.setblankorder
+ local ctx_setblankpenalty = context.setblankpenalty
+ ----- ctx_setblankhandling = context.setblankhandling
+ local ctx_flushblankhandling = context.flushblankhandling
+ local ctx_addpredefinedblankskip = context.addpredefinedblankskip
+ local ctx_addaskedblankskip = context.addaskedblankskip
+ local ctx_setblankpacked = context.setblankpacked
+
+ local ctx_pushlogger = context.pushlogger
+ local ctx_startblankhandling = context.startblankhandling
+ local ctx_stopblankhandling = context.stopblankhandling
+ local ctx_poplogger = context.poplogger
+
+ local pattern = nil
+
+ local packed = categories.packed
+
+ local function handler(amount, 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
+ ctx_fixedblankskip()
+ elseif keyword == k_flexible then
+ ctx_flexibleblankskip()
+ elseif keyword == k_category then
+ local category = tonumber(detail)
+ if category == packed then
+ ctx_setblankpacked()
+ elseif category then
+ ctx_setblankcategory(category)
+ ctx_flushblankhandling()
+ end
+ elseif keyword == k_order and detail then
+ local order = tonumber(detail)
+ if order then
+ ctx_setblankorder(order)
+ end
+ elseif keyword == k_penalty and detail then
+ local penalty = tonumber(detail)
+ if penalty then
+ ctx_setblankpenalty(penalty)
+ end
+ else
+ amount = tonumber(amount) or 1
+ local sk = skip[keyword]
+ if sk then
+ ctx_addpredefinedblankskip(amount,keyword)
+ else -- no check
+ ctx_addaskedblankskip(amount,keyword)
+ end
+ end
+ end
+ end
+
+ local splitter = ((multiplier + singlefier) * keyword * (category + Cc(false))) / handler
+ pattern = (splitter + separator^1)^0
+
+ function vspacing.analyze(str)
+ if trace_vspacing then
+ ctx_pushlogger(report_vspacing)
+ ctx_startblankhandling()
+ lpegmatch(pattern,str)
+ ctx_stopblankhandling()
+ ctx_poplogger()
+ else
+ ctx_startblankhandling()
+ lpegmatch(pattern,str)
+ ctx_stopblankhandling()
+ end
+ end
+
+ end
+
+ do -- new variant
+
+ 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 ctx_vspacingfromtempstring = context.core.vspacingfromtempstring
+ -- local ctx_vspacingfromscratchtoks = context.core.vspacingfromscratchtoks
+
+ -- local ctx_vspacingpredefinedvalue = context.core.vspacingpredefinedvalue
+ --
+ -- local cache = table.setmetatableindex(function(t,k)
+ -- local v = function() ctx_vspacingpredefinedvalue(k) end
+ -- t[k] = v
+ -- return v
+ -- end)
+
+ 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 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)
+ -- if b_category then
+ -- setattr(n,a_skipcategory,b_category)
+ -- end
+ -- if b_penalty then
+ -- setattr(n,a_skippenalty,b_penalty)
+ -- end
+ -- if b_order then
+ -- setattr(n,a_skiporder,b_order or 1)
+ -- end
+ -- write_node(n)
+ -- end
+
+ 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")
+
+ local function handler(amount, 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
+ amount = tonumber(amount) or 1
+ local sk = skip[keyword]
+ if sk then
+ -- amount, keyword
+ b_done = true
+ -- 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)
+ --
+ b_amount = b_amount + amount * texgetdimen("scratchdimen")
+ else -- no check
+ b_done = true
+ b_amount = b_amount + amount * toscaled(keyword)
+ end
+ 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 = gridsnapping
+ 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
+
+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
+
+ -- old variant
+
+ implement {
+ name = "vspacing",
+ actions = vspacing.analyze,
+ scope = "private",
+ arguments = "string"
+ }
+
+ -- new variant
+
+ 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