diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/page-mix.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/page-mix.lua | 587 |
1 files changed, 415 insertions, 172 deletions
diff --git a/Master/texmf-dist/tex/context/base/page-mix.lua b/Master/texmf-dist/tex/context/base/page-mix.lua index cf009478736..0fbaa4e30b4 100644 --- a/Master/texmf-dist/tex/context/base/page-mix.lua +++ b/Master/texmf-dist/tex/context/base/page-mix.lua @@ -15,48 +15,79 @@ if not modules then modules = { } end modules ["page-mix"] = { local concat = table.concat -local nodecodes = nodes.nodecodes -local gluecodes = nodes.gluecodes -local nodepool = nodes.pool - -local hlist_code = nodecodes.hlist -local vlist_code = nodecodes.vlist -local kern_code = nodecodes.kern -local glue_code = nodecodes.glue -local penalty_code = nodecodes.penalty -local insert_code = nodecodes.ins -local mark_code = nodecodes.mark - -local new_hlist = nodepool.hlist -local new_vlist = nodepool.vlist -local new_glue = nodepool.glue - -local hpack = node.hpack -local vpack = node.vpack -local freenode = node.free - -local texbox = tex.box -local texskip = tex.skip -local texdimen = tex.dimen -local points = number.points -local settings_to_hash = utilities.parsers.settings_to_hash - -local variables = interfaces.variables -local v_yes = variables.yes -local v_global = variables["global"] -local v_local = variables["local"] -local v_columns = variables.columns - local trace_state = false trackers.register("mixedcolumns.trace", function(v) trace_state = v end) local trace_detail = false trackers.register("mixedcolumns.detail", function(v) trace_detail = v end) local report_state = logs.reporter("mixed columns") +local nodecodes = nodes.nodecodes +local gluecodes = nodes.gluecodes + +local hlist_code = nodecodes.hlist +local vlist_code = nodecodes.vlist +local kern_code = nodecodes.kern +local glue_code = nodecodes.glue +local penalty_code = nodecodes.penalty +local insert_code = nodecodes.ins +local mark_code = nodecodes.mark +local rule_code = nodecodes.rule + +local topskip_code = gluecodes.topskip +local lineskip_code = gluecodes.lineskip +local baselineskip_code = gluecodes.baselineskip +local userskip_code = gluecodes.userskip + +local nuts = nodes.nuts +local tonode = nuts.tonode +local nodetostring = nuts.tostring +local listtoutf = nodes.listtoutf + +local hpack = nuts.hpack +local vpack = nuts.vpack +local freenode = nuts.free +local concatnodes = nuts.concat +local slidenodes = nuts.slide -- ok here as we mess with prev links intermediately +local traversenodes = nuts.traverse + +local getfield = nuts.getfield +local setfield = nuts.setfield +local getnext = nuts.getnext +local getprev = nuts.getprev +local getid = nuts.getid +local getlist = nuts.getlist +local getsubtype = nuts.getsubtype +local getbox = nuts.getbox +local setbox = nuts.setbox +local getskip = nuts.getskip +local getattribute = nuts.getattribute + +local nodepool = nuts.pool + +local new_hlist = nodepool.hlist +local new_vlist = nodepool.vlist +local new_glue = nodepool.glue + +local points = number.points + +local settings_to_hash = utilities.parsers.settings_to_hash + +local variables = interfaces.variables +local v_yes = variables.yes +local v_global = variables["global"] +local v_local = variables["local"] +local v_columns = variables.columns +local v_fixed = variables.fixed +local v_auto = variables.auto +local v_none = variables.none +local v_more = variables.more +local v_less = variables.less + pagebuilders = pagebuilders or { } pagebuilders.mixedcolumns = pagebuilders.mixedcolumns or { } local mixedcolumns = pagebuilders.mixedcolumns -local forcedbreak = -123 +local a_checkedbreak = attributes.private("checkedbreak") +local forcedbreak = -123 -- initializesplitter(specification) -- cleanupsplitter() @@ -73,13 +104,13 @@ local function collectinserts(result,nxt,nxtid) local inserts, currentskips, nextskips, inserttotal = { }, 0, 0, 0 while nxt do if nxtid == insert_code then - inserttotal = inserttotal + nxt.height + nxt.depth - local s = nxt.subtype + inserttotal = inserttotal + getfield(nxt,"height") + getfield(nxt,"depth") + local s = getsubtype(nxt) local c = inserts[s] if not c then c = { } inserts[s] = c - local width = texskip[s].width + local width = getfield(getskip(s),"width") if not result.inserts[s] then currentskips = currentskips + width end @@ -96,9 +127,9 @@ local function collectinserts(result,nxt,nxtid) else break end - nxt = nxt.next + nxt = getnext(nxt) if nxt then - nxtid = nxt.id + nxtid = getid(nxt) else break end @@ -124,33 +155,31 @@ end local function discardtopglue(current,discarded) local size = 0 while current do - local id = current.id + local id = getid(current) if id == glue_code then - size = size + current.spec.width + size = size + getfield(getfield(current,"spec"),"width") discarded[#discarded+1] = current - current = current.next + current = getnext(current) elseif id == penalty_code then - if current.penalty == forcedbreak then + if getfield(current,"penalty") == forcedbreak then discarded[#discarded+1] = current - current = current.next - while current do - local id = current.id - if id == glue_code then - size = size + current.spec.width - discarded[#discarded+1] = current - current = current.next - else - break - end + current = getnext(current) + while current and getid(current) == glue_code do + size = size + getfield(getfield(current,"spec"),"width") + discarded[#discarded+1] = current + current = getnext(current) end else discarded[#discarded+1] = current - current = current.next + current = getnext(current) end else break end end + if current then + setfield(current,"prev",nil) -- prevent look back + end return current, size end @@ -160,13 +189,13 @@ local function stripbottomglue(results,discarded) local r = results[i] local t = r.tail while t and t ~= r.head do - local prev = t.prev + local prev = getprev(t) if not prev then break end - local id = t.id + local id = getid(t) if id == penalty_code then - if t.penalty == forcedbreak then + if getfield(t,"penalty") == forcedbreak then break else discarded[#discarded+1] = t @@ -175,7 +204,7 @@ local function stripbottomglue(results,discarded) end elseif id == glue_code then discarded[#discarded+1] = t - local width = t.spec.width + local width = getfield(getfield(t,"spec"),"width") if trace_state then report_state("columns %s, discarded bottom glue %p",i,width) end @@ -199,25 +228,30 @@ local function setsplit(specification) -- a rather large function report_state("fatal error, no box") return end - local list = texbox[box] + local list = getbox(box) if not list then report_state("fatal error, no list") return end - local head = list.head or specification.originalhead + local head = getlist(list) or specification.originalhead if not head then report_state("fatal error, no head") return end + slidenodes(head) -- we can have set prev's to nil to prevent backtracking local discarded = { } local originalhead = head - local originalwidth = specification.originalwidth or list.width - local originalheight = specification.originalheight or list.height + local originalwidth = specification.originalwidth or getfield(list,"width") + local originalheight = specification.originalheight or getfield(list,"height") local current = head local skipped = 0 local height = 0 local depth = 0 local skip = 0 + local splitmethod = specification.splitmethod or false + if splitmethod == v_none then + splitmethod = false + end local options = settings_to_hash(specification.option or "") local stripbottom = specification.alternative == v_local local cycle = specification.cycle or 1 @@ -253,12 +287,104 @@ local function setsplit(specification) -- a rather large function delta = 0, } end - local column = 1 - local line = 0 - local result = results[column] - local lasthead = nil - local rest = nil + + local column = 1 + local line = 0 + local result = results[1] + local lasthead = nil + local rest = nil + local lastlocked = nil + local lastcurrent = nil + local lastcontent = nil + local backtracked = false + + if trace_state then + report_state("setting collector to column %s",column) + end + + local function unlock(penalty) + if lastlocked then + if trace_state then + report_state("penalty %s, unlocking in column %s",penalty or "-",column) + end + lastlocked = nil + end + lastcurrent = nil + lastcontent = nil + end + + local function lock(penalty,current) + if trace_state then + report_state("penalty %s, locking in column %s",penalty,column) + end + lastlocked = penalty + lastcurrent = current or lastcurrent + lastcontent = nil + end + + local function backtrack(start) + local current = start + -- first skip over glue and penalty + while current do + local id = getid(current) + if id == glue_code then + if trace_state then + report_state("backtracking over %s in column %s","glue",column) + end + current = getprev(current) + elseif id == penalty_code then + if trace_state then + report_state("backtracking over %s in column %s","penalty",column) + end + current = getprev(current) + else + break + end + end + -- then skip over content + while current do + local id = getid(current) + if id == glue_code then + if trace_state then + report_state("quitting at %s in column %s","glue",column) + end + break + elseif id == penalty_code then + if trace_state then + report_state("quitting at %s in column %s","penalty",column) + end + break + else + current = getprev(current) + end + end + if not current then + if trace_state then + report_state("no effective backtracking in column %s",column) + end + current = start + end + return current + end + local function gotonext() + if lastcurrent then + if current ~= lastcurrent then + if trace_state then + report_state("backtracking to preferred break in column %s",column) + end + -- todo: also remember height/depth + current = backtrack(lastcurrent) + backtracked = true + end + lastcurrent = nil + if lastlocked then + if trace_state then + report_state("unlocking in column %s",column) + end + lastlocked = nil + end + end if head == lasthead then if trace_state then report_state("empty column %s, needs more work",column) @@ -271,7 +397,7 @@ local function setsplit(specification) -- a rather large function if current == head then result.tail = head else - result.tail = current.prev + result.tail = getprev(current) end result.height = height result.depth = depth @@ -282,19 +408,24 @@ local function setsplit(specification) -- a rather large function if column == nofcolumns then column = 0 -- nicer in trace rest = head - -- lasthead = head return false, 0 else local skipped column = column + 1 result = results[column] + if trace_state then + report_state("setting collector to column %s",column) + end current, skipped = discardtopglue(current,discarded) + if trace_detail and skipped ~= 0 then + report_state("check > column 1, discarded %p",skipped) + end head = current - -- lasthead = head return true, skipped end end - local function checked(advance,where) + + local function checked(advance,where,locked) local total = skip + height + depth + advance local delta = total - target local state = "same" @@ -311,115 +442,226 @@ local function setsplit(specification) -- a rather large function end end if trace_detail then - report_state("%-7s > column %s, delta %p, threshold %p, advance %p, total %p, target %p, discarded %p => %a (height %p, depth %p, skip %p)", + report_state("%-7s > column %s, delta %p, threshold %p, advance %p, total %p, target %p => %a (height %p, depth %p, skip %p)", where,curcol,delta,threshold,advance,total,target,state,skipped,height,depth,skip) end return state, skipped end + current, skipped = discardtopglue(current,discarded) if trace_detail and skipped ~= 0 then report_state("check > column 1, discarded %p",skipped) end + + -- problem: when we cannot break after a list (and we only can expect same-page situations as we don't + -- care too much about weighted breaks here) we should sort of look ahead or otherwise be able to push + -- back inserts and so + -- + -- ok, we could use vsplit but we don't have that one opened up yet .. maybe i should look into the c-code + -- .. something that i try to avoid so let's experiment more before we entry dirty trick mode + head = current - while current do - local id = current.id - local nxt = current.next -local lastcolumn = column - if id == hlist_code or id == vlist_code then - line = line + 1 - local nxtid = nxt and nxt.id - local inserts, currentskips, nextskips, inserttotal = nil, 0, 0, 0 - local advance = current.height -- + current.depth - if nxt and (nxtid == insert_code or nxtid == mark_code) then - nxt, inserts, localskips, insertskips, inserttotal = collectinserts(result,nxt,nxtid) - end - local state, skipped = checked(advance+inserttotal+currentskips,"line") + + local function process_skip(current,nxt) + local advance = getfield(getfield(current,"spec"),"width") + if advance ~= 0 then + local state, skipped = checked(advance,"glue") if trace_state then - report_state("%-7s > column %s, state %a, line %s, advance %p, insert %p, height %p","line",column,state,line,advance,inserttotal,height) + report_state("%-7s > column %s, state %a, advance %p, height %p","glue",column,state,advance,height) if skipped ~= 0 then - report_state("%-7s > column %s, discarded %p","line",column,skipped) + report_state("%-7s > column %s, discarded %p","glue",column,skipped) end end if state == "quit" then - break - else - height = height + depth + skip + advance + inserttotal - if state == "next" then - height = height + nextskips - else - height = height + currentskips - end + return true end - depth = current.depth - skip = 0 - if inserts then - appendinserts(result.inserts,inserts) + height = height + depth + skip + depth = 0 + skip = height > 0 and advance or 0 + if trace_state then + report_state("%-7s > column %s, height %p, depth %p, skip %p","glue",column,height,depth,skip) end - elseif id == glue_code then - local advance = current.spec.width - if advance ~= 0 then - local state, skipped = checked(advance,"glue") - if trace_state then - report_state("%-7s > column %s, state %a, advance %p, height %p","glue",column,state,advance,height) - if skipped ~= 0 then - report_state("%-7s > column %s, discarded %p","glue",column,skipped) - end + else + -- what else? ignore? treat as valid as usual? + end + if lastcontent then + unlock() + end + end + + local function process_kern(current,nxt) + local advance = getfield(current,"kern") + if advance ~= 0 then + local state, skipped = checked(advance,"kern") + if trace_state then + report_state("%-7s > column %s, state %a, advance %p, height %p, state %a","kern",column,state,advance,height) + if skipped ~= 0 then + report_state("%-7s > column %s, discarded %p","kern",column,skipped) end - if state == "quit" then - break + end + if state == "quit" then + return true + end + height = height + depth + skip + advance + depth = 0 + skip = 0 + if trace_state then + report_state("%-7s > column %s, height %p, depth %p, skip %p","kern",column,height,depth,skip) + end + end + end + + local function process_rule(current,nxt) + -- simple variant of h|vlist + local advance = getfield(current,"height") -- + getfield(current,"depth") + if advance ~= 0 then + local state, skipped = checked(advance,"rule") + if trace_state then + report_state("%-7s > column %s, state %a, rule, advance %p, height %p","rule",column,state,advance,inserttotal,height) + if skipped ~= 0 then + report_state("%-7s > column %s, discarded %p","rule",column,skipped) end - height = height + depth + skip - depth = 0 - skip = height > 0 and advance or 0 end - elseif id == kern_code then - local advance = current.kern - if advance ~= 0 then - local state, skipped = checked(advance,"kern") + if state == "quit" then + return true + end + height = height + depth + skip + advance + -- if state == "next" then + -- height = height + nextskips + -- else + -- height = height + currentskips + -- end + depth = getfield(current,"depth") + skip = 0 + end + lastcontent = current + end + + -- okay, here we could do some badness like magic but we want something + -- predictable and even better: strategies .. so eventually this will + -- become installable + -- + -- [chapter] [penalty] [section] [penalty] [first line] + + local function process_penalty(current,nxt) + local penalty = getfield(current,"penalty") + if penalty == 0 then + unlock(penalty) + elseif penalty == forcedbreak then + local needed = getattribute(current,a_checkedbreak) + local proceed = not needed or needed == 0 + if not proceed then + local available = target - height + proceed = needed >= available if trace_state then - report_state("%-7s > column %s, state %a, advance %p, height %p, state %a","kern",column,state,advance,height) - if skipped ~= 0 then - report_state("%-7s > column %s, discarded %p","kern",column,skipped) - end - end - if state == "quit" then - break + report_state("cycle: %s, column %s, available %p, needed %p, %s break",cycle,column,available,needed,proceed and "forcing" or "ignoring") end - height = height + depth + skip + advance - depth = 0 - skip = 0 end - elseif id == penalty_code then - local penalty = current.penalty - if penalty == 0 then - -- don't bother - elseif penalty == forcedbreak then + if proceed then + unlock(penalty) local okay, skipped = gotonext() if okay then if trace_state then - report_state("cycle: %s, forced column break (same page)",cycle) + report_state("cycle: %s, forced column break, same page",cycle) if skipped ~= 0 then report_state("%-7s > column %s, discarded %p","penalty",column,skipped) end end else if trace_state then - report_state("cycle: %s, forced column break (next page)",cycle) + report_state("cycle: %s, forced column break, next page",cycle) if skipped ~= 0 then report_state("%-7s > column %s, discarded %p","penalty",column,skipped) end end - break + return true end - else - -- todo: nobreak etc ... we might need to backtrack so we need to remember - -- the last acceptable break - -- club and widow and such i.e. resulting penalties (if we care) end + elseif penalty < 0 then + -- we don't care too much + unlock(penalty) + elseif penalty >= 10000 then + if not lastcurrent then + lock(penalty,current) + elseif penalty > lastlocked then + lock(penalty) + end + else + unlock(penalty) end -if lastcolumn == column then - nxt = current.next -- can have changed -end + end + + local function process_list(current,nxt) + local nxtid = nxt and getid(nxt) + line = line + 1 + local inserts, currentskips, nextskips, inserttotal = nil, 0, 0, 0 + local advance = getfield(current,"height") -- + getfield(current,"depth") + if trace_state then + report_state("%-7s > column %s, content: %s","line",column,listtoutf(getlist(current),true,true)) + end + if nxt and (nxtid == insert_code or nxtid == mark_code) then + nxt, inserts, localskips, insertskips, inserttotal = collectinserts(result,nxt,nxtid) + end + local state, skipped = checked(advance+inserttotal+currentskips,"line",lastlocked) + if trace_state then + report_state("%-7s > column %s, state %a, line %s, advance %p, insert %p, height %p","line",column,state,line,advance,inserttotal,height) + if skipped ~= 0 then + report_state("%-7s > column %s, discarded %p","line",column,skipped) + end + end + if state == "quit" then + return true + end + height = height + depth + skip + advance + inserttotal + if state == "next" then + height = height + nextskips + else + height = height + currentskips + end + depth = getfield(current,"depth") + skip = 0 + if inserts then + -- so we already collect them ... makes backtracking tricky ... alternatively + -- we can do that in a separate loop ... no big deal either + appendinserts(result.inserts,inserts) + end + if trace_state then + report_state("%-7s > column %s, height %p, depth %p, skip %p","line",column,height,depth,skip) + end + lastcontent = current + end + +local kept = head + + while current do + + local id = getid(current) + local nxt = getnext(current) + + backtracked = false + + -- print("process",nodetostring(current)) + + if id == hlist_code or id == vlist_code then + if process_list(current,nxt) then break end + elseif id == glue_code then + if process_skip(current,nxt) then break end + elseif id == kern_code then + if process_kern(current,nxt) then break end + elseif id == penalty_code then + if process_penalty(current,nxt) then break end + elseif id == rule_code then + if process_rule(current,nxt) then break end + else + end + + if backtracked then + -- print("pickup",nodetostring(current)) + nxt = current + else + -- print("move on",nodetostring(current)) + end + if nxt then current = nxt elseif head == lasthead then @@ -437,16 +679,19 @@ end break end end + if not current then if trace_state then - report_state("nilling rest") + report_state("nothing left") end - rest = nil - elseif rest == lasthead then + -- needs well defined case + -- rest = nil + elseif rest == lasthead then if trace_state then - report_state("nilling rest as rest is lasthead") + report_state("rest equals lasthead") end - rest = nil + -- test case: x\index{AB} \index{AA}x \blank \placeindex + -- makes line disappear: rest = nil end if stripbottom then @@ -466,7 +711,7 @@ end specification.overflow = overflow specification.discarded = discarded - texbox[specification.box].head = nil + setfield(getbox(specification.box),"list",nil) return specification end @@ -478,12 +723,12 @@ function mixedcolumns.finalize(result) local r = results[i] local h = r.head if h then - h.prev = nil + setfield(h,"prev",nil) local t = r.tail if t then - t.next = nil + setfield(t,"next",nil) else - h.next = nil + setfield(h,"next",nil) r.tail = h end for c, list in next, r.inserts do @@ -492,13 +737,13 @@ function mixedcolumns.finalize(result) local l = list[i] local h = new_hlist() t[i] = h - h.head = l.head - h.height = l.height - h.depth = l.depth - l.head = nil + setfield(h,"list",getfield(l,"head")) + setfield(h,"height",getfield(l,"height")) + setfield(h,"depth",getfield(l,"depth")) + setfield(l,"head",nil) end - t[1].prev = nil -- needs checking - t[#t].next = nil -- needs checking + setfield(t[1],"prev",nil) -- needs checking + setfield(t[#t],"next",nil) -- needs checking r.inserts[c] = t end end @@ -556,9 +801,6 @@ function mixedcolumns.setsplit(specification) end end -local topskip_code = gluecodes.topskip -local baselineskip_code = gluecodes.baselineskip - function mixedcolumns.getsplit(result,n) if not result then report_state("flush, column %s, no result",n) @@ -573,13 +815,13 @@ function mixedcolumns.getsplit(result,n) return new_glue(result.originalwidth) end - h.prev = nil -- move up + setfield(h,"prev",nil) -- move up local strutht = result.strutht local strutdp = result.strutdp local lineheight = strutht + strutdp local v = new_vlist() - v.head = h + setfield(v,"list",h) -- local v = vpack(h,"exactly",height) @@ -601,23 +843,24 @@ function mixedcolumns.getsplit(result,n) dp = result.depth end - v.width = wd - v.height = ht - v.depth = dp + setfield(v,"width",wd) + setfield(v,"height",ht) + setfield(v,"depth",dp) if trace_state then - local id = h.id + local id = getid(h) if id == hlist_code then - report_state("flush, column %s, grid %a, width %p, height %p, depth %p, %s: %s",n,grid,wd,ht,dp,"top line",nodes.toutf(h.list)) + report_state("flush, column %s, grid %a, width %p, height %p, depth %p, %s: %s",n,grid,wd,ht,dp,"top line",listtoutf(getlist(h))) else report_state("flush, column %s, grid %a, width %p, height %p, depth %p, %s: %s",n,grid,wd,ht,dp,"head node",nodecodes[id]) end end for c, list in next, r.inserts do - -- tex.setbox("global",c,vpack(nodes.concat(list))) - -- tex.setbox(c,vpack(nodes.concat(list))) - texbox[c] = vpack(nodes.concat(list)) + local l = concatnodes(list) + local b = vpack(l) -- multiple arguments, todo: fastvpack + -- setbox("global",c,b) + setbox(c,b) r.inserts[c] = nil end @@ -661,7 +904,7 @@ end function commands.mixgetsplit(n) if result then - context(mixedcolumns.getsplit(result,n)) + context(tonode(mixedcolumns.getsplit(result,n))) end end @@ -673,13 +916,13 @@ end function commands.mixflushrest() if result then - context(mixedcolumns.getrest(result)) + context(tonode(mixedcolumns.getrest(result))) end end function commands.mixflushlist() if result then - context(mixedcolumns.getlist(result)) + context(tonode(mixedcolumns.getlist(result))) end end |