summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/context/base/mkxl/typo-mar.lmt
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/tex/context/base/mkxl/typo-mar.lmt')
-rw-r--r--Master/texmf-dist/tex/context/base/mkxl/typo-mar.lmt995
1 files changed, 995 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/context/base/mkxl/typo-mar.lmt b/Master/texmf-dist/tex/context/base/mkxl/typo-mar.lmt
new file mode 100644
index 00000000000..7e69162a900
--- /dev/null
+++ b/Master/texmf-dist/tex/context/base/mkxl/typo-mar.lmt
@@ -0,0 +1,995 @@
+if not modules then modules = { } end modules ['typo-mar'] = {
+ version = 1.001,
+ comment = "companion to typo-mar.mkiv",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- todo:
+--
+-- * autoleft/right depending on available space (or distance to margin)
+-- * floating margin data, with close-to-call anchoring
+
+local format, validstring = string.format, string.valid
+local insert, remove, sortedkeys, fastcopy = table.insert, table.remove, table.sortedkeys, table.fastcopy
+local setmetatable, next, tonumber = setmetatable, next, tonumber
+local formatters = string.formatters
+local toboolean = toboolean
+local settings_to_hash = utilities.parsers.settings_to_hash
+
+local attributes = attributes
+local nodes = nodes
+local variables = variables
+local context = context
+
+local trace_margindata = false trackers.register("typesetters.margindata", function(v) trace_margindata = v end)
+local trace_marginstack = false trackers.register("typesetters.margindata.stack", function(v) trace_marginstack = v end)
+local trace_margingroup = false trackers.register("typesetters.margindata.group", function(v) trace_margingroup = v end)
+
+local report_margindata = logs.reporter("margindata")
+
+local tasks = nodes.tasks
+local prependaction = tasks.prependaction
+local disableaction = tasks.disableaction
+local enableaction = tasks.enableaction
+
+local variables = interfaces.variables
+
+local conditionals = tex.conditionals
+local systemmodes = tex.systemmodes
+
+local v_top = variables.top
+local v_depth = variables.depth
+local v_local = variables["local"]
+local v_global = variables["global"]
+local v_left = variables.left
+local v_right = variables.right
+local v_inner = variables.inner
+local v_outer = variables.outer
+local v_margin = variables.margin
+local v_edge = variables.edge
+local v_default = variables.default
+local v_normal = variables.normal
+local v_yes = variables.yes
+local v_continue = variables.continue
+local v_first = variables.first
+local v_text = variables.text
+local v_paragraph = variables.paragraph
+local v_line = variables.line
+
+local nuts = nodes.nuts
+local tonode = nuts.tonode
+
+local hpack_nodes = nuts.hpack
+local traverse_id = nuts.traverse_id
+local flush_node_list = nuts.flush_list
+
+local getnext = nuts.getnext
+local getprev = nuts.getprev
+local getid = nuts.getid
+local getattr = nuts.getattr
+local setattr = nuts.setattr
+local getsubtype = nuts.getsubtype
+local getlist = nuts.getlist
+local getwhd = nuts.getwhd
+local setlist = nuts.setlist
+local setlink = nuts.setlink
+local getshift = nuts.getshift
+local setshift = nuts.setshift
+local getwidth = nuts.getwidth
+local setwidth = nuts.setwidth
+local getheight = nuts.getheight
+
+local setattrlist = nuts.setattrlist
+
+local getbox = nuts.getbox
+local takebox = nuts.takebox
+
+local setprop = nuts.setprop
+local getprop = nuts.getprop
+
+local nodecodes = nodes.nodecodes
+local listcodes = nodes.listcodes
+local whatsitcodes = nodes.whatsitcodes
+
+local hlist_code = nodecodes.hlist
+local vlist_code = nodecodes.vlist
+local whatsit_code = nodecodes.whatsit
+local userdefined_code = whatsitcodes.userdefined
+
+local nodepool = nuts.pool
+
+local new_hlist = nodepool.hlist
+local new_usernode = nodepool.usernode
+local latelua = nodepool.latelua
+
+local texgetdimen = tex.getdimen
+local texgetcount = tex.getcount
+local texget = tex.get
+
+local isleftpage = layouts.status.isleftpage
+local registertogether = builders.paragraphs.registertogether
+
+local paragraphs = typesetters.paragraphs
+local addtoline = paragraphs.addtoline
+local moveinline = paragraphs.moveinline
+local calculatedelta = paragraphs.calculatedelta
+
+local a_linenumber = attributes.private('linenumber')
+
+local inline_mark = nodepool.userids["margins.inline"]
+
+local jobpositions = job.positions
+local getposition = jobpositions.get
+local setposition = jobpositions.set
+local getreserved = jobpositions.getreserved
+
+local margins = { }
+typesetters.margins = margins
+
+local locations = { v_left, v_right, v_inner, v_outer } -- order might change
+local categories = { }
+local displaystore = { } -- [category][location][scope]
+local inlinestore = { } -- [number]
+local nofsaved = 0
+local nofstored = 0
+local nofinlined = 0
+local nofdelayed = 0
+local nofinjected = 0
+local h_anchors = 0
+local v_anchors = 0
+
+local mt1 = {
+ __index = function(t,location)
+ local v = { [v_local] = { }, [v_global] = { } }
+ t[location] = v
+ return v
+ end
+}
+
+local mt2 = {
+ __index = function(stores,category)
+ categories[#categories+1] = category
+ local v = { }
+ setmetatable(v,mt1)
+ stores[category] = v
+ return v
+ end
+}
+
+setmetatable(displaystore,mt2)
+
+local defaults = {
+ __index = {
+ location = v_left,
+ align = v_normal, -- not used
+ method = "",
+ name = "",
+ threshold = 0, -- .25ex
+ margin = v_normal,
+ scope = v_global,
+ distance = 0,
+ hoffset = 0,
+ voffset = 0,
+ category = v_default,
+ line = 0,
+ vstack = 0,
+ dy = 0,
+ baseline = false,
+ inline = false,
+ leftskip = 0,
+ rightskip = 0,
+ option = { }
+ }
+}
+
+local enablelocal, enableglobal -- forward reference (delayed initialization)
+
+local function showstore(store,banner,location)
+ if next(store) then
+ for i, si in table.sortedpairs(store) do
+ local si =store[i]
+ report_margindata("%s: stored in %a at %s: %a => %s",banner,location,i,validstring(si.name,"no name"),nodes.toutf(getlist(si.box)))
+ end
+ else
+ report_margindata("%s: nothing stored in location %a",banner,location)
+ end
+end
+
+function margins.save(t)
+ setmetatable(t,defaults)
+ local content = takebox(t.number)
+ local location = t.location
+ local category = t.category
+ local inline = t.inline
+ local scope = t.scope
+ local name = t.name
+ local option = t.option
+ local stack = t.stack
+ if option then
+ option = settings_to_hash(option)
+ t.option = option
+ end
+ if not content then
+ report_margindata("ignoring empty margin data %a",location or "unknown")
+ return
+ end
+ setprop(content,"specialcontent","margindata")
+ local store
+ if inline then
+ store = inlinestore
+ else
+ store = displaystore[category][location]
+ if not store then
+ report_margindata("invalid location %a",location)
+ return
+ end
+ store = store[scope]
+ end
+ if not store then
+ report_margindata("invalid scope %a",scope)
+ return
+ end
+ if enablelocal and scope == v_local then
+ enablelocal()
+ if enableglobal then
+ enableglobal() -- is the fallback
+ end
+ elseif enableglobal and scope == v_global then
+ enableglobal()
+ end
+ nofsaved = nofsaved + 1
+ nofstored = nofstored + 1
+ if trace_marginstack then
+ showstore(store,"before",location)
+ end
+ if name and name ~= "" then
+ -- this can be used to overload
+ if inlinestore then -- todo: inline store has to be done differently (not sparse)
+ local t = sortedkeys(store) for j=#t,1,-1 do local i = t[j]
+ local si = store[i]
+ if si.name == name then
+ local s = remove(store,i)
+ flush_node_list(s.box)
+ end
+ end
+ else
+ for i=#store,1,-1 do
+ local si = store[i]
+ if si.name == name then
+ local s = remove(store,i)
+ flush_node_list(s.box)
+ end
+ end
+ end
+ if trace_marginstack then
+ showstore(store,"between",location)
+ end
+ end
+ if t.number then
+ local leftmargindistance = texgetdimen("naturalleftmargindistance")
+ local rightmargindistance = texgetdimen("naturalrightmargindistance")
+ local strutbox = getbox("strutbox")
+ local _, strutht, strutdp = getwhd(strutbox)
+ -- better make a new table and make t entry in t
+ t.box = content
+ t.n = nofsaved
+ -- used later (we will clean up this natural mess later)
+ -- nice is to make a special status table mechanism
+ t.strutheight = strutht
+ t.strutdepth = strutdp
+ -- beware: can be different from the applied one (we're not in forgetall)
+ t.leftskip = texget("leftskip",false)
+ t.rightskip = texget("rightskip",false)
+ --
+ t.leftmargindistance = leftmargindistance -- todo:layoutstatus table
+ t.rightmargindistance = rightmargindistance
+ t.leftedgedistance = texgetdimen("naturalleftedgedistance")
+ + texgetdimen("leftmarginwidth")
+ + leftmargindistance
+ t.rightedgedistance = texgetdimen("naturalrightedgedistance")
+ + texgetdimen("rightmarginwidth")
+ + rightmargindistance
+ t.lineheight = texgetdimen("lineheight")
+ --
+ -- t.realpageno = texgetcount("realpageno")
+ if inline then
+ local n = new_usernode(inline_mark,nofsaved)
+ setattrlist(n,true)
+ context(tonode(n)) -- or use a normal node
+ store[nofsaved] = t -- no insert
+ nofinlined = nofinlined + 1
+ else
+ insert(store,t)
+ end
+ end
+ if trace_marginstack then
+ showstore(store,"after",location)
+ end
+ if trace_margindata then
+ report_margindata("saved %a, location %a, scope %a, inline %a",nofsaved,location,scope,inline)
+ end
+end
+
+-- Actually it's an advantage to have them all anchored left (tags and such)
+-- we could keep them in store and flush in stage two but we might want to
+-- do more before that so we need the content to be there unless we can be
+-- sure that we flush this first which might not be the case in the future.
+--
+-- When the prototype inner/outer code that was part of this proved to be
+-- okay it was moved elsewhere.
+
+local function realign(current,candidate)
+ local location = candidate.location
+ local margin = candidate.margin
+ local hoffset = candidate.hoffset
+ local distance = candidate.distance
+ local hsize = candidate.hsize
+ local width = candidate.width
+ local align = candidate.align
+ local inline = candidate.inline
+ local anchor = candidate.anchor
+ local hook = candidate.hook
+ local scope = candidate.scope
+ local option = candidate.option
+ local reverse = hook.reverse
+ local atleft = true
+ local hmove = 0
+ local delta = 0
+ local leftpage = isleftpage()
+ local leftdelta = 0
+ local rightdelta = 0
+ local leftdistance = distance
+ local rightdistance = distance
+ --
+ if not anchor or anchor == "" then
+ anchor = v_text -- this has to become more clever: region:0|column:n|column
+ end
+ if margin == v_normal then
+ --
+ elseif margin == v_local then
+ leftdelta = - candidate.leftskip
+ rightdelta = candidate.rightskip
+ elseif margin == v_margin then
+ leftdistance = candidate.leftmargindistance
+ rightdistance = candidate.rightmargindistance
+ elseif margin == v_edge then
+ leftdistance = candidate.leftedgedistance
+ rightdistance = candidate.rightedgedistance
+ end
+ if leftpage then
+ leftdistance, rightdistance = rightdistance, leftdistance
+ end
+ if location == v_right then
+ atleft = false
+ elseif location == v_inner then
+ if leftpage then
+ atleft = false
+ end
+ elseif location == v_outer then
+ if not leftpage then
+ atleft = false
+ end
+ else
+ -- v_left
+ end
+
+ local islocal = scope == v_local
+ local area = (not islocal or option[v_text]) and anchor or nil
+
+ if atleft then
+ delta = hoffset + leftdelta + leftdistance
+ else
+ delta = hoffset + rightdelta + rightdistance
+ end
+
+ local delta, hmove = calculatedelta (
+ hook, -- the line
+ width, -- width of object
+ delta, -- offset
+ atleft,
+ islocal, -- islocal
+ option[v_paragraph], -- followshape
+ area -- relative to area
+ )
+
+ if hmove ~= 0 then
+ delta = delta + hmove
+ if trace_margindata then
+ report_margindata("realigned %a, location %a, margin %a, move %p",candidate.n,location,margin,hmove)
+ end
+ else
+ if trace_margindata then
+ report_margindata("realigned %a, location %a, margin %a",candidate.n,location,margin)
+ end
+ end
+ moveinline(hook,candidate.node,delta)
+end
+
+local function realigned(current,candidate)
+ realign(current,candidate)
+ nofdelayed = nofdelayed - 1
+ setprop(current,"margindata",false)
+ return true
+end
+
+-- Stacking is done in two ways: the v_yes option stacks per paragraph (or line,
+-- depending on what gets by) and mostly concerns margin data dat got set at more or
+-- less the same time. The v_continue option uses position tracking and works on
+-- larger range. However, crossing pages is not part of it. Anyway, when you have
+-- such messed up margin data you'd better think twice.
+--
+-- The stacked table keeps track (per location) of the offsets (the v_yes case). This
+-- table gets saved when the v_continue case is active. We use a special variant
+-- of position tracking, after all we only need the page number and vertical position.
+
+local validstacknames = {
+ [v_left ] = v_left ,
+ [v_right] = v_right,
+ [v_inner] = v_inner,
+ [v_outer] = v_outer,
+}
+
+local cache = { }
+local stacked = { [v_yes] = { }, [v_continue] = { } }
+local anchors = { [v_yes] = { }, [v_continue] = { } }
+
+local function resetstacked(all)
+ stacked[v_yes] = { }
+ anchors[v_yes] = { }
+ if all then
+ stacked[v_continue] = { }
+ anchors[v_continue] = { }
+ end
+end
+
+-- anchors are only set for lines that have a note
+
+local function sa(specification) -- maybe l/r keys ipv left/right keys
+ local tag = specification.tag
+ local p = cache[tag]
+ if p then
+ if trace_marginstack then
+ report_margindata("updating anchor %a",tag)
+ end
+ p.p = true
+ p.y = true
+ -- maybe settobesaved first
+ setposition("md:v",tag,p)
+ cache[tag] = nil -- do this later, per page a cleanup
+ end
+end
+
+local function setanchor(v_anchor) -- freezes the global here
+ return latelua { action = sa, tag = v_anchor }
+end
+
+local function aa(specification) -- maybe l/r keys ipv left/right keys
+ local tag = specification.tag
+ local n = specification.n
+ local p = jobpositions.gettobesaved('md:v',tag)
+ if p then
+ if trace_marginstack then
+ report_margindata("updating injected %a",tag)
+ end
+ local pages = p.pages
+ if not pages then
+ pages = { }
+ p.pages = pages
+ end
+ pages[n] = texgetcount("realpageno")
+ elseif trace_marginstack then
+ report_margindata("not updating injected %a",tag)
+ end
+end
+
+local function addtoanchor(v_anchor,n) -- freezes the global here
+ return latelua { action = aa, tag = v_anchor, n = n }
+end
+
+local function markovershoot(current) -- todo: alleen als offset > line
+ v_anchors = v_anchors + 1
+ cache[v_anchors] = fastcopy(stacked)
+ local anchor = setanchor(v_anchors)
+ -- local list = hpack_nodes(setlink(anchor,getlist(current))) -- not ok, we need to retain width
+ -- local list = setlink(anchor,getlist(current)) -- why not this ... better play safe
+ local list = hpack_nodes(setlink(anchor,getlist(current)),getwidth(current),"exactly")--
+ if trace_marginstack then
+ report_margindata("marking anchor %a",v_anchors)
+ end
+ setlist(current,list)
+end
+
+local function inject(parent,head,candidate)
+ local box = candidate.box
+ if not box then
+ return head, nil, false -- we can have empty texts
+ end
+ local width, height, depth
+ = getwhd(box)
+ local shift = getshift(box)
+ local stack = candidate.stack
+ local stackname = candidate.stackname
+ local location = candidate.location
+ local method = candidate.method
+ local voffset = candidate.voffset
+ local line = candidate.line
+ local baseline = candidate.baseline
+ local strutheight = candidate.strutheight
+ local strutdepth = candidate.strutdepth
+ local inline = candidate.inline
+ local psubtype = getsubtype(parent)
+ -- This stackname is experimental and therefore undocumented and basically
+ -- unsupported. It was introduced when we needed to support overlapping
+ -- of different anchors.
+ if not stackname or stackname == "" then
+ stackname = location
+ else
+ stackname = validstacknames[stackname] or location
+ end
+ local isstacked = stack == v_continue or stack == v_yes
+ local offset = isstacked and stacked[stack][stackname]
+ local firstonstack = offset == false or offset == nil
+ nofinjected = nofinjected + 1
+ nofdelayed = nofdelayed + 1
+ -- yet untested
+ baseline = tonumber(baseline)
+ if not baseline then
+ baseline = toboolean(baseline)
+ end
+ --
+ if baseline == true then
+ baseline = false
+ else
+ baseline = tonumber(baseline)
+ if not baseline or baseline <= 0 then
+ -- in case we have a box of width 0 that is not analyzed
+ baseline = false -- strutheight -- actually a hack
+ end
+ end
+ candidate.width = width
+ candidate.hsize = getwidth(parent) -- we can also pass textwidth
+ candidate.psubtype = psubtype
+ candidate.stackname = stackname
+ if trace_margindata then
+ report_margindata("processing, index %s, height %p, depth %p, parent %a, method %a",candidate.n,height,depth,listcodes[psubtype],method)
+ end
+ -- Overlap detection is somewhat complex because we have display and inline
+ -- notes mixed as well as inner and outer positioning. We do need to
+ -- handle it in the stream because we also keep lines together so we keep
+ -- track of page numbers of notes.
+
+ if isstacked then
+ firstonstack = true
+ local anchor = getposition("md:v")
+ if anchor and (location == v_inner or location == v_outer) then
+ local pages = anchor.pages
+ if pages then
+ local page = pages[nofinjected]
+ if page then
+ if isleftpage(page) then
+ stackname = location == v_inner and v_right or v_left
+ else
+ stackname = location == v_inner and v_left or v_right
+ end
+ candidate.stackname = stackname
+ offset = stack and stack ~= "" and stacked[stack][stackname]
+ end
+ end
+ end
+ local current = v_anchors + 1
+ local previous = anchors[stack][stackname]
+ if trace_margindata then
+ report_margindata("anchor %i, offset so far %p",current,offset or 0)
+ end
+ local ap = anchor and anchor[previous]
+ local ac = anchor and anchor[current]
+ if not previous then
+ elseif previous == current then
+ firstonstack = false
+ elseif ap and ac and ap.p == ac.p then
+ local distance = (ap.y or 0) - (ac.y or 0)
+ if trace_margindata then
+ report_margindata("distance %p",distance)
+ end
+ if offset > distance then
+ -- we already overflow
+ offset = offset - distance
+ firstonstack = false
+ else
+ offset = 0
+ end
+ else
+ -- what to do
+ end
+ anchors[v_yes] [stackname] = current
+ anchors[v_continue][stackname] = current
+ if firstonstack then
+ offset = 0
+ end
+ offset = offset + candidate.dy -- always
+ shift = shift + offset
+ else
+ if firstonstack then
+ offset = 0
+ end
+ offset = offset + candidate.dy -- always
+ shift = shift + offset
+ end
+ -- Maybe we also need to patch offset when we apply methods, but how ...
+ -- This needs a bit of playing as it depends on the stack setting of the
+ -- following which we don't know yet ... so, consider stacking partially
+ -- experimental.
+ if method == v_top then
+ local delta = height - getheight(parent)
+ if trace_margindata then
+ report_margindata("top aligned by %p",delta)
+ end
+ if delta < candidate.threshold then -- often we need a negative threshold here
+ shift = shift + voffset + delta
+ end
+ elseif method == v_line then
+ local _, ph, pd = getwhd(parent)
+ if pd == 0 then
+ local delta = height - ph
+ if trace_margindata then
+ report_margindata("top aligned by %p (no depth)",delta)
+ end
+ if delta < candidate.threshold then -- often we need a negative threshold here
+ shift = shift + voffset + delta
+ end
+ end
+ elseif method == v_first then
+ if baseline then
+ shift = shift + voffset + height - baseline -- option
+ else
+ shift = shift + voffset -- normal
+ end
+ if trace_margindata then
+ report_margindata("first aligned")
+ end
+ elseif method == v_depth then
+ local delta = strutdepth
+ if trace_margindata then
+ report_margindata("depth aligned by %p",delta)
+ end
+ shift = shift + voffset + delta
+ elseif method == v_height then
+ local delta = - strutheight
+ if trace_margindata then
+ report_margindata("height aligned by %p",delta)
+ end
+ shift = shift + voffset + delta
+ elseif voffset ~= 0 then
+ if trace_margindata then
+ report_margindata("voffset %p applied",voffset)
+ end
+ shift = shift + voffset
+ end
+ -- -- --
+ if line ~= 0 then
+ local delta = line * candidate.lineheight
+ if trace_margindata then
+ report_margindata("offset %p applied to line %s",delta,line)
+ end
+ shift = shift + delta
+ offset = offset + delta
+ end
+ setshift(box,shift)
+ setwidth(box,0) -- not needed when wrapped
+ --
+ if isstacked then
+ setlink(box,addtoanchor(v_anchors,nofinjected))
+ box = new_hlist(box)
+ -- set height / depth ?
+ end
+ --
+ candidate.hook, candidate.node = addtoline(parent,box)
+ --
+ setprop(box,"margindata",candidate)
+ if trace_margindata then
+ report_margindata("injected, location %a, stack %a, shift %p",location,stackname,shift)
+ end
+ -- we need to add line etc to offset as well
+ offset = offset + depth
+ local room = {
+ height = height,
+ depth = offset,
+ slack = candidate.bottomspace, -- todo: 'depth' => strutdepth
+ lineheight = candidate.lineheight, -- only for tracing
+ stacked = inline and isstacked,
+ }
+ offset = offset + height
+ -- we need a restart ... when there is no overlap at all
+ stacked[v_yes] [stackname] = offset
+ stacked[v_continue][stackname] = offset
+ -- todo: if no real depth then zero
+ if trace_margindata then
+ report_margindata("status, offset %s",offset)
+ end
+ return getlist(parent), room, inline and isstacked or (stack == v_continue)
+end
+
+local function flushinline(parent,head)
+ local current = head
+ local done = false
+ local continue = false
+ local room, don, con, list
+ while current and nofinlined > 0 do
+ local id = getid(current)
+ if id == whatsit_code then
+ if getsubtype(current) == userdefined_code and getprop(current,"id") == inline_mark then
+ local n = getprop(current,"data")
+ local candidate = inlinestore[n]
+ if candidate then -- no vpack, as we want to realign
+ inlinestore[n] = nil
+ nofinlined = nofinlined - 1
+ head, room, con = inject(parent,head,candidate) -- maybe return applied offset
+ done = true
+ continue = continue or con
+ nofstored = nofstored - 1
+ if room and room.stacked then
+ -- for now we also check for inline+yes/continue, maybe someday no such check
+ -- will happen; we can assume most inlines are one line heigh; also this
+ -- together feature can become optional
+ registertogether(parent,room)
+ end
+ end
+ end
+ elseif id == hlist_code or id == vlist_code then
+ -- optional (but sometimes needed)
+ list, don, con = flushinline(current,getlist(current))
+ setlist(current,list)
+ continue = continue or con
+ done = done or don
+ end
+ current = getnext(current)
+ end
+ return head, done, continue
+end
+
+local function flushed(scope,parent) -- current is hlist
+ local head = getlist(parent)
+ local done = false
+ local continue = false
+ local room, con, don
+ for c=1,#categories do
+ local category = categories[c]
+ for l=1,#locations do
+ local location = locations[l]
+ local store = displaystore[category][location][scope]
+ if store then
+ while true do
+ local candidate = remove(store,1) -- brr, local stores are sparse
+ if candidate then -- no vpack, as we want to realign
+ head, room, con = inject(parent,head,candidate)
+ done = true
+ continue = continue or con
+ nofstored = nofstored - 1
+ if room then
+ registertogether(parent,room)
+ end
+ else
+ break
+ end
+ end
+ else
+ -- report_margindata("fatal error: invalid category %a",category or "?")
+ end
+ end
+ end
+ if nofinlined > 0 then
+ if done then
+ setlist(parent,head)
+ end
+ head, don, con = flushinline(parent,head)
+ continue = continue or con
+ done = done or don
+ end
+ if done then
+ local a = getattr(head,a_linenumber) -- hack .. we need a more decent critical attribute inheritance mechanism
+ if false then
+ local l = hpack_nodes(head,getwidth(parent),"exactly")
+ setlist(parent,l)
+ if a then
+ setattr(l,a_linenumber,a)
+ end
+ else
+ -- because packing messes up profiling
+ setlist(parent,head)
+ if a then
+ setattr(parent,a_linenumber,a)
+ end
+ end
+ end
+ return done, continue
+end
+
+-- only when group : vbox|vmode_par
+-- only when subtype : line, box (no indent alignment cell)
+
+local function handler(scope,head,group)
+ if nofstored > 0 then
+ if trace_margindata then
+ report_margindata("flushing stage one, stored %s, scope %s, delayed %s, group %a",nofstored,scope,nofdelayed,group)
+ end
+ local current = head
+ local done = false -- for tracing only
+ while current do
+ local id = getid(current)
+ if (id == vlist_code or id == hlist_code) and getprop(current,"margindata") == nil then
+ local don, continue = flushed(scope,current)
+ if don then
+ done = true
+ setprop(current,"margindata",false) -- signal to prevent duplicate processing
+ if continue then
+ markovershoot(current)
+ end
+ if nofstored <= 0 then
+ break
+ end
+ end
+ end
+ current = getnext(current)
+ end
+ if trace_margindata then
+ if done then
+ report_margindata("flushing stage one, done, %s left",nofstored)
+ else
+ report_margindata("flushing stage one, nothing done, %s left",nofstored)
+ end
+ end
+ resetstacked()
+ end
+ return head
+end
+
+local trialtypesetting = context.trialtypesetting
+
+-- maybe change this to an action applied to the to be shipped out box (that is
+-- the mvl list in there so that we don't need to traverse global
+
+function margins.localhandler(head,group) -- sometimes group is "" which is weird
+
+ if trialtypesetting() then
+ return head
+ end
+
+ local inhibit = conditionals.inhibitmargindata
+ if inhibit then
+ if trace_margingroup then
+ report_margindata("ignored 3, group %a, stored %s, inhibit %a",group,nofstored,inhibit)
+ end
+ return head
+ end
+ if nofstored > 0 then
+ return handler(v_local,head,group)
+ end
+ if trace_margingroup then
+ report_margindata("ignored 4, group %a, stored %s, inhibit %a",group,nofstored,inhibit)
+ end
+ return head
+end
+
+function margins.globalhandler(head,group) -- check group
+
+ if trialtypesetting() then
+ return head, false
+ end
+
+ local inhibit = conditionals.inhibitmargindata
+ if inhibit or nofstored == 0 then
+ if trace_margingroup then
+ report_margindata("ignored 1, group %a, stored %s, inhibit %a",group,nofstored,inhibit)
+ end
+ return head
+ elseif group == "hmode_par" then
+ return handler(v_global,head,group)
+ elseif group == "vmode_par" then -- experiment (for alignments)
+ return handler(v_global,head,group)
+ -- this needs checking as we then get quite some one liners to process and
+ -- we cannot look ahead then:
+ elseif group == "box" then -- experiment (for alignments)
+ return handler(v_global,head,group)
+ elseif group == "alignment" then -- experiment (for alignments)
+ return handler(v_global,head,group)
+ else
+ if trace_margingroup then
+ report_margindata("ignored 2, group %a, stored %s, inhibit %a",group,nofstored,inhibit)
+ end
+ return head
+ end
+end
+
+local function finalhandler(head)
+ if nofdelayed > 0 then
+ local current = head
+ while current and nofdelayed > 0 do -- traverse_list
+ local id = getid(current)
+ if id == hlist_code then -- only lines?
+ local a = getprop(current,"margindata")
+ if not a then
+ finalhandler(getlist(current))
+ elseif realigned(current,a) then
+ if nofdelayed == 0 then
+ return head, true
+ end
+ end
+ elseif id == vlist_code then
+ finalhandler(getlist(current))
+ end
+ current = getnext(current)
+ end
+ end
+ return head
+end
+
+function margins.finalhandler(head)
+ if nofdelayed > 0 then
+ if trace_margindata then
+ report_margindata("flushing stage two, instore: %s, delayed: %s",nofstored,nofdelayed)
+ end
+ head = finalhandler(head)
+ resetstacked(nofdelayed==0)
+ else
+ resetstacked()
+ end
+ return head
+end
+
+-- Somehow the vbox builder (in combinations) gets pretty confused and decides to
+-- go horizontal. So this needs more testing.
+
+enablelocal = function()
+ enableaction("finalizers", "typesetters.margins.localhandler")
+ enableaction("shipouts", "typesetters.margins.finalhandler")
+ enablelocal = nil
+end
+
+enableglobal = function()
+ enableaction("mvlbuilders", "typesetters.margins.globalhandler")
+ enableaction("shipouts", "typesetters.margins.finalhandler")
+ enableglobal = nil
+end
+
+statistics.register("margin data", function()
+ if nofsaved > 0 then
+ return format("%s entries, %s pending",nofsaved,nofdelayed)
+ else
+ return nil
+ end
+end)
+
+interfaces.implement {
+ name = "savemargindata",
+ actions = margins.save,
+ arguments = {
+ {
+ { "location" },
+ { "method" },
+ { "category" },
+ { "name" },
+ { "scope" },
+ { "number", "integer" },
+ { "margin" },
+ { "distance", "dimen" },
+ { "hoffset", "dimen" },
+ { "voffset", "dimen" },
+ { "dy", "dimen" },
+ { "bottomspace", "dimen" },
+ { "baseline"}, -- dimen or string or
+ { "threshold", "dimen" },
+ { "inline", "boolean" },
+ { "anchor" },
+ -- { "leftskip", "dimen" },
+ -- { "rightskip", "dimen" },
+ { "align" },
+ { "option" },
+ { "line", "integer" },
+ { "index", "integer" },
+ { "stackname" },
+ { "stack" },
+ }
+ }
+}