diff options
Diffstat (limited to 'tex/context/base/core-spa.lua')
-rw-r--r-- | tex/context/base/core-spa.lua | 1979 |
1 files changed, 504 insertions, 1475 deletions
diff --git a/tex/context/base/core-spa.lua b/tex/context/base/core-spa.lua index 5f4df72a2..0f308b19c 100644 --- a/tex/context/base/core-spa.lua +++ b/tex/context/base/core-spa.lua @@ -6,13 +6,43 @@ if not modules then modules = { } end modules ['core-spa'] = { license = "see context related readme files" } --- todo: test without unset +-- this code dates from the beginning and is kind of experimental; it +-- will be optimized and improved soon -local format, insert = string.format, table.insert -local utfchar = utf.char +local next, type = next, type +local format, gmatch, concat = string.format, string.gmatch, table.concat +local texsprint, texlists = tex.sprint, tex.lists + +local ctxcatcodes = tex.ctxcatcodes -- vertical space handler +local trace_vbox_vspacing = false trackers.register("nodes.vbox_vspacing", function(v) trace_vbox_vspacing = v end) +local trace_page_vspacing = false trackers.register("nodes.page_vspacing", function(v) trace_page_vspacing = v end) +local trace_collect_vspacing = false trackers.register("nodes.collect_vspacing", function(v) trace_collect_vspacing = v end) +local trace_vspacing = false trackers.register("nodes.vspacing", function(v) trace_vspacing = v end) + +local has_attribute = node.has_attribute +local unset_attribute = node.unset_attribute +local set_attribute = node.set_attribute +local slide_node_list = node.slide +local free_node = node.free +local copy_node = node.copy +local traverse_nodes = node.traverse +local insert_node_before = node.insert_before +local insert_node_after = node.insert_after +local remove_node = nodes.remove +local make_penalty_node = nodes.penalty +local count_nodes = nodes.count +local node_ids_to_string = nodes.ids_to_string + +local glyph = node.id("glyph") +local penalty = node.id("penalty") +local kern = node.id("kern") +local glue = node.id('glue') +local hlist = node.id('hlist') +local vlist = node.id('vlist') + vspacing = vspacing or { } vspacing.categories = { @@ -26,12 +56,14 @@ vspacing.categories = { [7] = 'goback', } +local categories = vspacing.categories + function vspacing.tocategories(str) local t = { } - for s in str:gmatch("[^, ]") do + for s in gmatch(str,"[^, ]") do local n = tonumber(s) if n then - t[vspacing.categories[n]] = true + t[categories[n]] = true else t[b] = true end @@ -43,7 +75,7 @@ function vspacing.tocategory(str) if type(str) == "string" then return set.tonumber(vspacing.tocategories(str)) else - return set.tonumber({ [vspacing.categories[str]] = true }) + return set.tonumber({ [categories[str]] = true }) end end @@ -51,90 +83,87 @@ vspacing.data = vspacing.data or { } vspacing.data.map = vspacing.data.map or { } vspacing.data.skip = vspacing.data.skip or { } -input.storage.register(false, "vspacing/data/map", vspacing.data.map, "vspacing.data.map") -input.storage.register(false, "vspacing/data/skip", vspacing.data.skip, "vspacing.data.skip") +storage.register("vspacing/data/map", vspacing.data.map, "vspacing.data.map") +storage.register("vspacing/data/skip", vspacing.data.skip, "vspacing.data.skip") do + vspacing.fixed = false + local map = vspacing.data.map local skip = vspacing.data.skip - vspacing.fixed = false - vspacing.trace = false - local multiplier = lpeg.C(lpeg.S("+-")^0 * lpeg.R("09")^1) * lpeg.P("*") local category = lpeg.P(":") * lpeg.C(lpeg.P(1)^1) local keyword = lpeg.C((1-category)^1) - local splitter = (multiplier + lpeg.Cc(1)) * keyword * (category + lpeg.Cc(false)) - function vspacing.analyse(str) - local category, order, penalty, command, fixed = { }, 0, 0, { }, vspacing.fixed - local function analyse(str) - for s in str:gmatch("([^ ,]+)") do - local amount, keyword, detail = splitter:match(s) - if keyword then - local mk = map[keyword] - if mk then - analyse(mk) - elseif keyword == "fixed" then - fixed = true - elseif keyword == "flexible" then - fixed = false - elseif keyword == "category" then - -- is a set - local n = tonumber(detail) - if n then - category[vspacing.categories[n]] = true - else - category[detail] = true - end - elseif keyword == "order" then - -- last one counts - order = tonumber(detail) or 0 - elseif keyword == "penalty" then - -- last one counts - penalty = tonumber(detail) or 0 - elseif keyword == "skip" then - -- last one counts - command[#command+1] = { 1, tonumber(detail or 1) or 1} + local function analyse(str,category,order,penalty,command,fixed) + for s in gmatch(str,"([^ ,]+)") do + local amount, keyword, detail = splitter:match(s) + if keyword then + local mk = map[keyword] + if mk then + analyse(mk,category,order,penalty,command,fixed) + elseif keyword == "fixed" then + fixed = true + elseif keyword == "flexible" then + fixed = false + elseif keyword == "category" then + -- is a set + local n = tonumber(detail) + if n then + category[categories[n]] = true else - amount = tonumber(amount) or 1 - local sk = skip[keyword] - if sk then - command[#command+1] = { amount, sk[1], sk[2] or sk[1]} - else -- no check - command[#command+1] = { amount, keyword, keyword, keyword} - end + category[detail] = true end + elseif keyword == "order" then + -- last one counts + order = tonumber(detail) or 0 + elseif keyword == "penalty" then + -- last one counts + penalty = tonumber(detail) or 0 + elseif keyword == "skip" then + -- last one counts + command[#command+1] = { 1, tonumber(detail or 1) or 1} else - logs.report("vspacing","unknown directive: %s",str) + amount = tonumber(amount) or 1 + local sk = skip[keyword] + if sk then + command[#command+1] = { amount, sk[1], sk[2] or sk[1]} + else -- no check + command[#command+1] = { amount, keyword, keyword, keyword} + end end + else + logs.report("vspacing","unknown directive: %s",str) end end - analyse(str) + end + + local function logger(c,s) + logs.report("vspacing",s) + texsprint(c,s) + end + + function vspacing.analyse(str) + local texsprint = (trace_vspacing and logger) or texsprint + local category, order, penalty, command, fixed = { }, 0, 0, { }, vspacing.fixed + analyse(str,category,order,penalty,command,fixed) category = set.tonumber(category) - local texsprint, ctxcatcodes = tex.sprint, tex.ctxcatcodes - if vspacing.trace then - -- quick and dirty - texsprint = function(c,s) - logs.report("vspacing",s) - tex.sprint(c,s) - end - end texsprint(ctxcatcodes,"\\startblankhandling") if category > 0 then - texsprint(ctxcatcodes,("\\setblankcategory{%s}"):format(category)) + texsprint(ctxcatcodes,format("\\setblankcategory{%s}",category)) end if order > 0 then - texsprint(ctxcatcodes,("\\setblankorder{%s}"):format(order)) + texsprint(ctxcatcodes,format("\\setblankorder{%s}",order)) end if penalty > 0 then - texsprint(ctxcatcodes,("\\setblankpenalty{%s}"):format(penalty)) + texsprint(ctxcatcodes,format("\\setblankpenalty{%s}",penalty)) end for i=1,#command do local c = command[i] - texsprint(ctxcatcodes,("\\addblankskip{%s}{%s}{%s}"):format(c[1],c[2],c[3] or c[2])) + texsprint(ctxcatcodes,format("\\addblankskip{%s}{%s}{%s}",c[1],c[2],c[3] or c[2])) end if fixed then texsprint(ctxcatcodes,"\\fixedblankskip") @@ -165,1488 +194,488 @@ function nodes.setsnapvalue(n,ht,dp) nodes.snapvalues[n] = { ht, dp, ht+dp } end -do - - nodes.trace_vbox_spacing = false - nodes.trace_page_spacing = false +local trace_list, tracing_info, before, after = { }, false, "", "" - local kern, glue, penalty, hlist = node.id('kern'), node.id('glue'), node.id('penalty'), node.id('hlist') - - local has_attribute = node.has_attribute - local unset_attribute = node.unset_attribute - local set_attribute = node.set_attribute - local has_field = node.has_field - - local trace_list, tracing_info, before, after = { }, false, "", "" - - local function glue_to_string(glue) - local spec = glue.spec - if spec then - local t = { } - t[#t+1] = aux.strip_zeros(number.topoints(spec.width)) - if spec.stretch_order and spec.stretch_order ~= 0 then - t[#t+1] = format("plus -%sfi%s",spec.stretch/65536,string.rep("l",math.abs(spec.stretch_order)-1)) - elseif spec.stretch and spec.stretch ~= 0 then - t[#t+1] = format("plus %s",aux.strip_zeros(number.topoints(spec.stretch))) - end - if spec.shrink_order and spec.shrink_order ~= 0 then - t[#t+1] = format("minus -%sfi%s",spec.shrink/65536,string.rep("l",math.abs(spec.shrink_order)-1)) - elseif spec.shrink and spec.shrink ~= 0 then - t[#t+1] = format("minus %s",aux.strip_zeros(number.topoints(spec.shrink))) - end - return table.concat(t," ") - else - return "[0pt]" - end - end - local function nodes_to_string(head) - local current, t = head, { } - while current do - local id = current.id - local ty = node.type(id) - if id == penalty then - t[#t+1] = format("%s:%s",ty,current.penalty) - elseif id == glue then - if current.spec then - t[#t+1] = format("%s:%s",ty,aux.strip_zeros(number.topoints(current.spec.width))) - else - t[#t+1] = format("%s:[0pt]",ty) - end - elseif id == kern then - t[#t+1] = format("%s:%s",ty,aux.strip_zeros(number.topoints(current.kern))) - else - t[#t+1] = ty - end - current = current.next - end - return table.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", ("%s | %s | category %s | order %s | penalty %s"):format(str, glue_to_string(data), sc or "-", so or "-", sp or "-") } - tracing_info = true - end - local function trace_natural(str,data) - trace_list[#trace_list+1] = { "skip", ("%s | %s"):format(str, glue_to_string(data)) } - tracing_info = true - end - local function trace_info(message, where, what) - trace_list[#trace_list+1] = { "info", format("%s: %s/%s",message,where,what) } - end - local function trace_node(what) - local nt = node.type(what.id) - local tl = trace_list[#trace_list] - if tl[1] == "node" then - trace_list[#trace_list] = { "node", tl[2] .. " + " .. nt } - else - trace_list[#trace_list+1] = { "node", nt } +local function glue_to_string(glue) + local spec = glue.spec + if spec then + local t = { } + t[#t+1] = aux.strip_zeros(number.topoints(spec.width)) + if spec.stretch_order and spec.stretch_order ~= 0 then + t[#t+1] = format("plus -%sfi%s",spec.stretch/65536,string.rep("l",math.abs(spec.stretch_order)-1)) + elseif spec.stretch and spec.stretch ~= 0 then + t[#t+1] = format("plus %s",aux.strip_zeros(number.topoints(spec.stretch))) end - end - local function trace_done(str,data) - if data.id == penalty then - trace_list[#trace_list+1] = { "penalty", ("%s | %s"):format(str, data.penalty) } - else - trace_list[#trace_list+1] = { "glue", ("%s | %s"):format(str, glue_to_string(data)) } - end - tracing_info = true - end - local function show_tracing(head) - if tracing_info then - after = nodes_to_string(head) - for i=1,#trace_list do - local tag, text = unpack(trace_list[i]) - if tag == "info" then - logs.report("collapse",text) - else - logs.report("collapse"," %s: %s",tag,text) - end - end - logs.report("collapse","before: %s",before) - logs.report("collapse","after : %s",after) + if spec.shrink_order and spec.shrink_order ~= 0 then + t[#t+1] = format("minus -%sfi%s",spec.shrink/65536,string.rep("l",math.abs(spec.shrink_order)-1)) + elseif spec.shrink and spec.shrink ~= 0 then + t[#t+1] = format("minus %s",aux.strip_zeros(number.topoints(spec.shrink))) end + return concat(t," ") + else + return "[0pt]" end +end - -- we assume that these are defined - - local skip_category = attributes.numbers['skip-category'] or 101 - local skip_penalty = attributes.numbers['skip-penalty'] or 102 - local skip_order = attributes.numbers['skip-order'] or 103 - local snap_category = attributes.numbers['snap-category'] or 111 - local display_math = attributes.numbers['display-math'] or 121 - - -- alignment box begin_of_par vmode_par hmode_par insert penalty before_display after_display - - local user_skip = 0 - local line_skip = 1 - local baseline_skip = 2 - local par_skip = 3 - local above_display_skip = 4 - local below_display_skip = 5 - local above_display_short_skip = 6 - local below_display_short_skip = 7 - local left_skip_code = 8 - local right_skip_code = 9 - local top_skip_code = 10 - local split_top_skip_code = 11 - local tab_skip_code = 12 - local space_skip_code = 13 - local xspace_skip_code = 14 - local par_fill_skip_code = 15 - local thin_mu_skip_code = 16 - local med_mu_skip_code = 17 - local thick_mu_skip_code = 18 - - local skips = { - [ 0] = "user_skip", - [ 1] = "line_skip", - [ 2] = "baseline_skip", - [ 3] = "par_skip", - [ 4] = "above_display_skip", - [ 5] = "below_display_skip", - [ 6] = "above_display_short_skip", - [ 7] = "below_display_short_skip", - [ 8] = "left_skip_code", - [ 9] = "right_skip_code", - [10] = "top_skip_code", - [11] = "split_top_skip_code", - [12] = "tab_skip_code", - [13] = "space_skip_code", - [14] = "xspace_skip_code", - [15] = "par_fill_skip_code", - [16] = "thin_mu_skip_code", - [17] = "med_mu_skip_code", - [18] = "thick_mu_skip_code", - } - - function nodes.is_display_math(head) - local n = head.prev - while n do - local id = n.id - if id == penalty then - elseif id == glue then - if n.subtype == 6 then -- above_display_short_skip - return true - end - else - break - end - n = n.prev - end - n = head.next - while n do - local id = n.id - if id == penalty then - elseif id == glue then - if n.subtype == 7 then -- below_display_short_skip - return true - end +local function nodes_to_string(head) + local current, t = head, { } + while current do + local id = current.id + local ty = node.type(id) + if id == penalty then + t[#t+1] = format("%s:%s",ty,current.penalty) + elseif id == glue then + if current.spec then + t[#t+1] = format("%s:%s",ty,aux.strip_zeros(number.topoints(current.spec.width))) else - break - end - n = n.next - end - return false - end - - local function collapser(head,where,what,trace,preceding) - if head then - input.starttiming(nodes) - if trace then reset_tracing(head) end - if trace then trace_info("start analyzing",where,what) end - node.slide(head) -- hm, why - local current, tail = head, nil - local glue_order, glue_data = 0, nil - local penalty_order, penalty_data, natural_penalty = 0, nil, nil - local parskip, ignore_parskip, ignore_following, ignore_whitespace = nil, false, false, false - while current do - local id = current.id - if id == glue and current.subtype == 0 then -- todo, other subtypes, like math - local sc = has_attribute(current,skip_category) - local so = has_attribute(current,skip_order) - local sp = has_attribute(current,skip_penalty) ---~ if sc then unset_attribute(current,skip_category) end ---~ if so then unset_attribute(current,skip_order) end ---~ if sp then unset_attribute(current,skip_penalty) end - so = so or 1 - if not sc then - if glue_data then - if trace then trace_done("flush",glue_data) end - head, current = nodes.before(head,current,glue_data) - if trace then trace_natural("natural",current) end - else - -- not look back across head - local previous = current.prev - if previous and previous.id == glue and previous.subtype == 0 then - local ps = previous.spec - if ps then - local cs = current.spec - if cs and ps.stretch_order == 0 and ps.shrink_order == 0 and cs.stretch_order == 0 and cs.shrink_order == 0 then - local pw, pp, pm = ps.width, ps.stretch, ps.shrink - local cw, cp, cm = cs.width, cs.stretch, cs.shrink - ps.width, ps.stretch, ps.shrink = pw + cw, pp + cp, pm + cm - if trace then trace_natural("removed",current) end - head, current = nodes.remove(head, current, true) - current = previous - if trace then trace_natural("collapsed",current) end - else - if trace then trace_natural("filler",current) end - end - else - if trace then trace_natural("natural (no prev spec)",current) end - end - else - if trace then trace_natural("natural (no prev)",current) end - end - end - glue_order, glue_data = 0, nil - if current then - current = current.next - end - else - local sct = vspacing.categories[sc] -- or 'unknown' - if sct == 'disable' then - ignore_following = true - if trace then trace_skip(sct,sc,so,sp,current) end - head, current = nodes.remove(head, current, true) - elseif sct == 'nowhite' then - ignore_whitespace = true - head, current = nodes.remove(head, current, true) - elseif sct == 'discard' then - if trace then trace_skip(sct,sc,so,sp,current) end - head, current = nodes.remove(head, current, true) - else - if sp then - if not penalty_data then - penalty_data = sp - elseif penalty_order < so then - penalty_order, penalty_data = so, sp - elseif penalty_order == so and sp > penalty_data then - penalty_data = sp - end - end - if ignore_following then - if trace then trace_skip("disabled",sc,so,sp,current) end - head, current = nodes.remove(head, current, true) - elseif not glue_data then - if trace then trace_skip("assign " .. sct,sc,so,sp,current) end - glue_order = so - head, current, glue_data = nodes.remove(head, current) - elseif glue_order < so then - if trace then trace_skip("force",sc,so,sp,current) end - glue_order = so - node.free(glue_data) - head, current, glue_data = nodes.remove(head, current) - elseif glue_order == so then - if sct == 'largest' then - local cs, gs = current.spec, glue_data.spec - local cw = (cs and cs.width) or 0 - local gw = (gs and gs.width) or 0 - if cw > gw then - if trace then trace_skip(sct,sc,so,sp,current) end - node.free(glue_data) -- also free spec - head, current, glue_data = nodes.remove(head, current) - else - if trace then trace_skip('remove smallest',sc,so,sp,current) end - head, current = nodes.remove(head, current, true) - end - elseif sct == 'goback' then - if trace then trace_skip(sct,sc,so,sp,current) end - node.free(glue_data) -- also free spec - head, current, glue_data = nodes.remove(head, current) - elseif sct == 'force' then - -- todo: inject kern - if trace then trace_skip(sct,sc,so,sp,current) end - node.free(glue_data) -- also free spec - head, current, glue_data = nodes.remove(head, current) - elseif sct == 'penalty' then - if trace then trace_skip(sct,sc,so,sp,current) end - node.free(glue_data) -- also free spec - head, current = nodes.remove(head, current, true) - elseif sct == 'add' then - if trace then trace_skip(sct,sc,so,sp,current) end - local old, new = glue_data.spec, current.spec - old.width = old.width + new.width - old.stretch = old.stretch + new.stretch - old.shrink = old.shrink + new.shrink - -- toto: order - head, current = nodes.remove(head, current, true) - else - if trace then trace_skip("unknown",sc,so,sp,current) end - head, current = nodes.remove(head, current, true) - end - else - if trace then trace_skip("unknown",sc,so,sp,current) end - head, current = nodes.remove(head, current, true) - end - end - end - elseif id == penalty then ---~ natural_penalty = current.penalty ---~ if trace then trace_done("removed penalty",current) end ---~ head, current = nodes.remove(head, current, true) -current = current.next - elseif id == glue and current.subtype == 2 then - local sn = has_attribute(current,snap_category) - if sn then - -- local sv = nodes.snapvalues[sn] - -- if sv then - if trace then trace_natural("removed baselineskip",current) end - head, current = nodes.remove(head, current, true) - -- else - -- current = current.next - -- end - else - if trace then trace_natural("keep baselineskip",current) end - current = current.next - end - elseif id == glue and current.subtype == 3 then - -- parskip always comes later - if ignore_whitespace then - if trace then trace_natural("ignored parskip",current) end - head, current = nodes.remove(head,current,true) - elseif glue_data then - local ps, gs = current.spec, glue_data.spec - if ps and gs and ps.width > gs.width then - node.free(glue_data.spec) - glue_data.spec = ps - if trace then trace_natural("taking parskip",current) end - else - if trace then trace_natural("removed parskip",current) end - end - head, current = nodes.remove(head, current,true) - else - if trace then trace_natural("honored parskip",current) end - head, current, glue_data = nodes.remove(head, current) - end ---~ if trace then trace_natural("removed parskip",current) end ---~ current.spec = nil ---~ current = current.next - else - if glue_data then - if trace then trace_done("flushed",glue_data) end - head, current = node.insert_before(head,current,glue_data) - glue_order, glue_data = 0, nil - end - if penalty_data then - local p = nodes.penalty(penalty_data) - if trace then trace_done("flushed",p) end - head, current = node.insert_before(head,current,p) - penalty_data = nil - end - if trace then trace_node(current) end - if id == hlist and where == 'hmode_par' then - local list = current.list - if list then - local sn = has_attribute(list,snap_category) - if sn then - local sv = nodes.snapvalues[sn] - if sv then - local height, depth, lineheight = sv[1], sv[2], sv[3] - current.height = math.ceil((current.height-height)/lineheight)*lineheight + height - current.depth = math.ceil((current.depth -depth )/lineheight)*lineheight + depth - end - end - end - end - current = current.next - end - end - tail = node.slide(head) - 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 - if penalty_data then - local p = nodes.penalty(penalty_data) - if trace then trace_done("result",p) end - head, tail = node.insert_after(head,tail,p) - end - if glue_data then - if trace then trace_done("result",glue_data) end - head, tail = node.insert_after(head,tail,glue_data) + t[#t+1] = format("%s:[0pt]",ty) end - if trace and (glue_data or penalty_data) then trace_info("stop flushing",where,what) end - input.stoptiming(nodes) - if trace then show_tracing(head) end + elseif id == kern then + t[#t+1] = format("%s:%s",ty,aux.strip_zeros(number.topoints(current.kern))) + else + t[#t+1] = ty end - return head, true - end - - -- alignment after_output end box new_graf vmode_par hmode_par insert penalty before_display after_display - - function nodes.handle_page_spacing(where) -- no arguments - --~ status.best_page_break - --~ tex.lists.best_page_break - --~ tex.lists.best_size (natural size to best_page_break) - --~ tex.lists.least_page_cost ( badness van best_page_break) - --~ tex.lists.page_head - --~ tex.lists.contrib_head - local head, done= collapser(tex.lists.contrib_head,"page",where,nodes.trace_page_spacing,tex.lists.page_head) - tex.lists.contrib_head = head + current = current.next end - - -- split_keep, split_off, vbox - - local not_needed = table.tohash { - "split_keep", - "split_off", - } - - function nodes.handle_vbox_spacing(t,where) - return (t and not not_needed[where] and t.next and collapser(t,"vbox",where,nodes.trace_vbox_spacing)) or t - end - + return concat(t," + ") end --- experimental callback definitions will be moved elsewhere --- --- this will become a chain - -function vspacing.enable() ---~ callback.register('vpack_filter', nodes.handle_vbox_spacing) - callback.register('buildpage_filter', nodes.handle_page_spacing) +local function reset_tracing(head) + trace_list, tracing_info, before, after = { }, false, nodes_to_string(head), "" end -function vspacing.disable() - callback.register('vpack_filter', nil) - callback.register('buildpage_filter', nil) -end - --- horizontal stuff --- probably a has_glyphs is rather fast too - -do -- maybe just share these locals - - local has_attribute = node.has_attribute - local unset_attribute = node.unset_attribute - local set_attribute = node.set_attribute - local traverse_id = node.traverse_id - ---~ local function unset_attribute(n,attribute) ---~ set_attribute(n,attribute,0) ---~ end - - local glyph = node.id("glyph") - local whatsit = node.id("whatsit") - local penalty = node.id("penalty") - local kern = node.id("kern") - local disc = node.id('disc') - local glue = node.id('glue') - local hlist = node.id('hlist') - local vlist = node.id('vlist') - - spacings = spacings or { } - spacings.mapping = spacings.mapping or { } - spacings.enabled = false - - input.storage.register(false,"spacings/mapping", spacings.mapping, "spacings.mapping") +local function trace_skip(str,sc,so,sp,data) + trace_list[#trace_list+1] = { "skip", format("%s | %s | category %s | order %s | penalty %s", str, glue_to_string(data), sc or "-", so or "-", sp or "-") } + tracing_info = true +end - function spacings.setspacing(id,char,left,right,alternative) - local mapping = spacings.mapping[id] - if not mapping then - mapping = { } - spacings.mapping[id] = mapping - end - local map = mapping[char] - if not map then - map = { } - mapping[char] = map - end - map.left, map.right, map.alternative = left, right, alternative - end +local function trace_natural(str,data) + trace_list[#trace_list+1] = { "skip", format("%s | %s", str, glue_to_string(data)) } + tracing_info = true +end - -- todo: no ligatures +local function trace_info(message, where, what) + trace_list[#trace_list+1] = { "info", format("%s: %s/%s",message,where,what) } +end - function nodes.somespace(n,all) - if n then - local id = n.id - if id == glue then - return (all or (n.spec.width ~= 0)) and glue - elseif id == kern then - return (all or (n.kern ~= 0)) and kern - elseif id == glyph then - local category = characters.data[n.char].category - -- maybe more category checks are needed - return (category == "zs") and glyph - end - end - return false +local function trace_node(what) + local nt = node.type(what.id) + local tl = trace_list[#trace_list] + if tl[1] == "node" then + trace_list[#trace_list] = { "node", tl[2] .. " + " .. nt } + else + trace_list[#trace_list+1] = { "node", nt } end +end - function nodes.somepenalty(n,value) - if n then - local id = n.id - if id == penalty then - if value then - return n.penalty == value - else - return true - end - end - end - return false +local function trace_done(str,data) + if data.id == penalty then + trace_list[#trace_list+1] = { "penalty", format("%s | %s", str, data.penalty) } + else + trace_list[#trace_list+1] = { "glue", format("%s | %s", str, glue_to_string(data)) } end + tracing_info = true +end - spacings.trace = false - - function spacings.process(namespace,attribute,head) - local done, mapping, fontids = false, spacings.mapping, fonts.tfm.id - local start = head - -- head is always begin of par (whatsit), so we have at least two prev nodes - -- penalty followed by glue - while start do - if start.id == glyph then - local attr = has_attribute(start,attribute) - if attr and attr > 0 then - local map = mapping[attr] - if map then - map = map[start.char] - unset_attribute(start,attribute) - if map then - local trace = spacings.trace - local left, right, alternative = map.left, map.right, map.alternative - local quad = fontids[start.font].parameters.quad - local prev = start.prev - if left and left ~= 0 and prev then - local ok = false - if alternative == 1 then - local somespace = nodes.somespace(prev,true) - if somespace then - local prevprev = prev.prev - local somepenalty = nodes.somepenalty(prevprev,10000) - if somepenalty then - if trace then - logs.report("spacing","removing penalty and space before %s", utfchar(start.char)) - end - head, _ = nodes.remove(head,prev,true) - head, _ = nodes.remove(head,prevprev,true) - else - local somespace = nodes.somespace(prev,true) - if somespace then - if trace then - logs.report("spacing","removing space before %s", utfchar(start.char)) - end - head, _ = nodes.remove(head,prev,true) - end - end - end - ok = true - else - ok = not (nodes.somespace(prev,true) and nodes.somepenalty(prev.prev,true)) or nodes.somespace(prev,true) - end - if ok then - if trace then - logs.report("spacing","inserting penalty and space before %s", utfchar(start.char)) - end - node.insert_before(head,start,nodes.penalty(10000)) - node.insert_before(head,start,nodes.glue(tex.scale(quad,left))) - done = true - end - end - local next = start.next - if right and right ~= 0 and next then - local ok = false - if alternative == 1 then - local somepenalty = nodes.somepenalty(next,10000) - if somepenalty then - local nextnext = next.next - local somespace = nodes.somespace(nextnext,true) - if somespace then - if trace then - logs.report("spacing","removing penalty and space after %s", utfchar(start.char)) - end - head, _ = nodes.remove(head,next,true) - head, _ = nodes.remove(head,nextnext,true) - end - else - local somespace = nodes.somespace(next,true) - if somespace then - if trace then - logs.report("spacing","removing space after %s", utfchar(start.char)) - end - head, _ = nodes.remove(head,next,true) - end - end - ok = true - else - ok = not (nodes.somepenalty(next,10000) and nodes.somespace(next.next,true)) or nodes.somespace(next,true) - end - if ok then - if trace then - logs.report("spacing","inserting penalty and space after %s", utfchar(start.char)) - end - node.insert_after(head,start,nodes.glue(tex.scale(quad,right))) - node.insert_after(head,start,nodes.penalty(10000)) - done = true - end - end - end - 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 + logs.report("collapse",text) + else + logs.report("collapse"," %s: %s",tag,text) end - start = start.next end - return head, done - end - - lists.handle_spacing = nodes.install_attribute_handler { - name = "spacing", - namespace = spacings, - processor = spacings.process, - } - - kerns = kerns or { } - kerns.mapping = kerns.mapping or { } - kerns.enabled = false - - input.storage.register(false, "kerns/mapping", kerns.mapping, "kerns.mapping") - - function kerns.setspacing(id,factor) - kerns.mapping[id] = factor + logs.report("collapse","before: %s",before) + logs.report("collapse","after : %s",after) end +end - -- local marks = fti[font].shared.otfdata.luatex.marks - -- if not marks[tchar] then - - -- todo: use node.* functions +-- we assume that these are defined + +local skip_category = attributes.private('skip-category') +local skip_penalty = attributes.private('skip-penalty') +local skip_order = attributes.private('skip-order') +local snap_category = attributes.private('snap-category') +local display_math = attributes.private('display-math') + +-- alignment box begin_of_par vmode_par hmode_par insert penalty before_display after_display + +local user_skip = 0 +local line_skip = 1 +local baseline_skip = 2 +local par_skip = 3 +local above_display_skip = 4 +local below_display_skip = 5 +local above_display_short_skip = 6 +local below_display_short_skip = 7 +local left_skip_code = 8 +local right_skip_code = 9 +local top_skip_code = 10 +local split_top_skip_code = 11 +local tab_skip_code = 12 +local space_skip_code = 13 +local xspace_skip_code = 14 +local par_fill_skip_code = 15 +local thin_mu_skip_code = 16 +local med_mu_skip_code = 17 +local thick_mu_skip_code = 18 + +local skips = { + [ 0] = "user_skip", + [ 1] = "line_skip", + [ 2] = "baseline_skip", + [ 3] = "par_skip", + [ 4] = "above_display_skip", + [ 5] = "below_display_skip", + [ 6] = "above_display_short_skip", + [ 7] = "below_display_short_skip", + [ 8] = "left_skip_code", + [ 9] = "right_skip_code", + [10] = "top_skip_code", + [11] = "split_top_skip_code", + [12] = "tab_skip_code", + [13] = "space_skip_code", + [14] = "xspace_skip_code", + [15] = "par_fill_skip_code", + [16] = "thin_mu_skip_code", + [17] = "med_mu_skip_code", + [18] = "thick_mu_skip_code", +} - function kerns.process(namespace,attribute,head) -- todo interchar kerns / disc nodes / can be made faster - local fti, scale = fonts.tfm.id, tex.scale - local start, done, mapping, fontids, lastfont = head, false, kerns.mapping, fonts.tfm.id, nil - while start do - -- faster to test for attr first - local attr = has_attribute(start,attribute) - if attr and attr > 0 then - unset_attribute(start,attribute) - local krn = mapping[attr] - if krn and krn ~= 0 then - local id = start.id - if id == glyph then - lastfont = start.font - local c = start.components - if c then - local s = start - local tail = node.slide(c) - if s.prev then - s.prev.next = c - c.prev = s.prev +local free_glue_node = free_node +local free_glue_spec = free_node +--~ local free_glue_node = function(n) free_node(n) end +--~ local free_glue_spec = function(n) end + +local function collapser(head,where,what,trace) -- maybe also pass tail + if trace then + reset_tracing(head) + trace_info("start analyzing",where,what) + end + local current = head + local glue_order, glue_data = 0, nil + local penalty_order, penalty_data, natural_penalty = 0, nil, nil + local parskip, ignore_parskip, ignore_following, ignore_whitespace = nil, false, false, false + while current do + local id = current.id + if id == glue and current.subtype == 0 then -- todo, other subtypes, like math + local sc = has_attribute(current,skip_category) -- has no default, no unset (yet) + local so = has_attribute(current,skip_order ) or 1 -- has 1 default, no unset (yet) + local sp = has_attribute(current,skip_penalty ) -- has no degault, no unset (yet) + if not sc then + if glue_data then + if trace then trace_done("flush",glue_data) end + head, current = nodes.before(head,current,glue_data) + if trace then trace_natural("natural",current) end + else + -- not look back across head + local previous = current.prev + if previous and previous.id == glue and previous.subtype == 0 then + local ps = previous.spec + if ps then + local cs = current.spec + if cs and ps.stretch_order == 0 and ps.shrink_order == 0 and cs.stretch_order == 0 and cs.shrink_order == 0 then + local pw, pp, pm = ps.width, ps.stretch, ps.shrink + local cw, cp, cm = cs.width, cs.stretch, cs.shrink + ps.width, ps.stretch, ps.shrink = pw + cw, pp + cp, pm + cm + if trace then trace_natural("removed",current) end + head, current = remove_node(head, current, true) + current = previous + if trace then trace_natural("collapsed",current) end else - head = c - end - if s.next then - s.next.prev = tail - end - tail.next = s.next - start = c - start.attr = s.attr - s.attr = nil - s.components = nil - node.free(s) - done = true - end - local prev = start.prev - if prev then - local pid = prev.id - if not pid then - -- nothing - elseif pid == kern and prev.subtype == 0 then - prev.subtype = 1 - prev.kern = prev.kern + scale(fontids[lastfont].parameters.quad,krn) - done = true - elseif pid == glyph then - -- fontdata access can be done more efficient - if prev.font == lastfont then - local prevchar, lastchar = prev.char, start.char - local tfm = fti[lastfont].characters[prevchar] - local ickern = tfm.kerns - if ickern and ickern[lastchar] then - krn = scale(ickern[lastchar]+fontids[lastfont].parameters.quad,krn) - else - krn = scale(fontids[lastfont].parameters.quad,krn) - end - else - krn = scale(fontids[lastfont].parameters.quad,krn) - end - node.insert_before(head,start,nodes.kern(krn)) - done = true - elseif pid == disc then - local disc = start.prev -- disc - local pre, post, replace = disc.pre, disc.post, disc.replace - if pre then -- must pair with start.prev - local before = node.copy(disc.prev) - pre.prev = before - before.next = pre - before.prev = nil - pre = kerns.process(namespace,attribute,before) - pre = pre.next - pre.prev = nil - disc.pre = pre - node.free(before) - end - if post then -- must pair with start - local after = node.copy(disc.next) - local tail = node.slide(post) - tail.next = after - after.prev = tail - after.next = nil - post = kerns.process(namespace,attribute,post) - tail.next = nil - disc.post = post - node.free(after) - end - if replace then -- must pair with start and start.prev - local before = node.copy(disc.prev) - local after = node.copy(disc.next) - local tail = node.slide(post) - replace.prev = before - before.next = replace - before.prev = nil - tail.next = after - after.prev = tail - after.next = nil - replace = kerns.process(namespace,attribute,before) - replace = replace.next - replace.prev = nil - tail.next = nil - disc.replace = replace - node.free(after) - node.free(before) - end + if trace then trace_natural("filler",current) end end + else + if trace then trace_natural("natural (no prev spec)",current) end end - elseif id == glue and start.subtype == 0 then - local s = start.spec - local w = s.width - if w > 0 then - local width, stretch, shrink = w+2*scale(w,krn), s.stretch, s.shrink - start.spec = nodes.glue_spec(width,scale(stretch,width/w),scale(shrink,width/w)) - -- local width, stretch, shrink = w+2*w*krn, s.stretch, s.shrink - -- start.spec = nodes.glue_spec(width,stretch*width/w,shrink*width/w)) - done = true - end - elseif false and id == kern and start.subtype == 0 then -- handle with glyphs - local sk = start.kern - if sk > 0 then - -- start.kern = scale(sk,krn) - start.kern = sk*krn - done = true - end - elseif lastfont and (id == hlist or id == vlist) then -- todo: lookahead - if start.prev then - node.insert_before(head,start,nodes.kern(scale(fontids[lastfont].parameters.quad,krn))) - done = true - end - if start.next then - node.insert_after(head,start,nodes.kern(scale(fontids[lastfont].parameters.quad,krn))) - done = true - end - end - end - end - if start then - start = start.next - end - end - return head, done - end - - lists.handle_kerning = nodes.install_attribute_handler { - name = "kern", - namespace = kerns, - processor = kerns.process, - } - - -- spacing == attributename !! does not belong here but we will - -- relocate node and attribute stuff once it's more complete !! - - -- experimental, we may extend or change this - - --~ Analysis by Idris: - --~ - --~ 1. Assuming the reading- vs word-order distinction (bidi-char types) is governing; - --~ 2. Assuming that 'ARAB' represents an actual arabic string in raw input order, not word-order; - --~ 3. Assuming that 'BARA' represent the correct RL word order; - --~ - --~ Then we have, with input: LATIN ARAB - --~ - --~ \textdir TLT LATIN ARAB => LATIN BARA - --~ \textdir TRT LATIN ARAB => LATIN BARA - --~ \textdir TRT LRO LATIN ARAB => LATIN ARAB - --~ \textdir TLT LRO LATIN ARAB => LATIN ARAB - --~ \textdir TLT RLO LATIN ARAB => NITAL ARAB - --~ \textdir TRT RLO LATIN ARAB => NITAL ARAB - - -- elseif d == "es" then -- European Number Separator - -- elseif d == "et" then -- European Number Terminator - -- elseif d == "cs" then -- Common Number Separator - -- elseif d == "nsm" then -- Non-Spacing Mark - -- elseif d == "bn" then -- Boundary Neutral - -- elseif d == "b" then -- Paragraph Separator - -- elseif d == "s" then -- Segment Separator - -- elseif d == "ws" then -- Whitespace - -- elseif d == "on" then -- Other Neutrals - - mirror = mirror or { } - mirror.enabled = false - mirror.trace = false - mirror.strip = false - - local state = attributes.numbers['state'] or 100 - - function mirror.process(namespace,attribute,head) - local done, data, directions, trace = false, characters.data, characters.directions, mirror.trace - local current, inserted, obsolete = head, nil, { } - local override, embedded, autodir = 0, 0, 0 - local list, glyphs = trace and { }, false - local stack, top, finished, finidir, finipos = { }, 0, nil, nil, 1 - local finish = nil - local lro, rlo, prevattr = false, false, 0 - -- todo: delayed inserts here - local function finish_auto_before() - head, inserted = node.insert_before(head,current,nodes.textdir("-"..finish)) - finished, finidir = inserted, finish - if trace then insert(list,#list,format("finish %s",finish)) ; finipos = #list-1 end - finish, autodir, done = nil, 0, true - end - local function finish_auto_after() - head, current = node.insert_after(head,current,nodes.textdir("-"..finish)) - finished, finidir = current, finish - if trace then list[#list+1] = format("finish %s",finish) ; finipos = #list end - finish, autodir, done = nil, 0, true - end - local function force_auto_left_before() - if finish then - finish_auto_before() - end - if embedded >= 0 then - finish, autodir, done = "TLT", 1, true - else - finish, autodir, done = "TRT", -1, true - end - if finidir == finish then - nodes.remove(head,finished,true) - if trace then list[finipos] = list[finipos].." (deleted)" end - if trace then insert(list,#list,format("start %s (deleted)",finish)) end - else - head, inserted = node.insert_before(head,current,nodes.textdir("+"..finish)) - if trace then insert(list,#list,format("start %s",finish)) end - end - end - local function force_auto_right_before() - if finish then - finish_auto_before() - end - if embedded <= 0 then - finish, autodir, done = "TRT", -1, true - else - finish, autodir, done = "TLT", 1, true - end - if finidir == finish then - nodes.remove(head,finished,true) - if trace then list[finipos] = list[finipos].." (deleted)" end - if trace then insert(list,#list,format("start %s (deleted)",finish)) end - else - head, inserted = node.insert_before(head,current,nodes.textdir("+"..finish)) - if trace then insert(list,#list,format("start %s",finish)) end - end - end - local function is_right(n) - if n then - local id = n.id - if id == glyph then - local attr = has_attribute(n,attribute) - if attr and attr > 0 then - local d = directions[n.char] - if d == "r" or d == "al" then -- override - return true - end - end - end - end - return false - end - while current do - local id = current.id - local attr = has_attribute(current,attribute) - if attr and attr > 0 then - unset_attribute(current,attribute) - if attr == 1 then - -- bidi parsing mode - elseif attr ~= prevattr then - -- no pop, grouped driven (2=normal,3=lro,4=rlo) - if attr == 3 then - if trace then list[#list+1] = format("override right -> left (lro) (bidi=%s)",attr) end - lro, rlo = true, false - elseif attr == 4 then - if trace then list[#list+1] = format("override left -> right (rlo) (bidi=%s)",attr) end - lro, rlo = false, true else - if trace and current ~= head then list[#list+1] = format("override reset (bidi=%s)",attr) end - lro, rlo = false, false + if trace then trace_natural("natural (no prev)",current) end end - prevattr = attr end - end - if id == glyph then - glyphs = true - if attr and attr > 0 then - local char = current.char - local d = directions[char] - if rlo or override > 0 then - if d == "l" then - if trace then list[#list+1] = format("char %s of class %s overidden to r (bidi=%s)",utf.char(char),d,attr) end - d = "r" - elseif trace then - if d == "lro" or d == "rlo" or d == "pdf" then -- else side effects on terminal - list[#list+1] = format("override char of class %s (bidi=%s)",d,attr) - else -- todo: rle lre - list[#list+1] = format("char %s of class %s (bidi=%s)",utf.char(char),d,attr) - end - end - elseif lro or override < 0 then - if d == "r" or d == "al" then - set_attribute(current,state,4) -- maybe better have a special bidi attr value -> override (9) -> todo - if trace then list[#list+1] = format("char %s of class %s overidden to l (bidi=%s) (state=isol)",utf.char(char),d,attr) end - d = "l" - elseif trace then - if d == "lro" or d == "rlo" or d == "pdf" then -- else side effects on terminal - list[#list+1] = format("override char of class %s (bidi=%s)",d,attr) - else -- todo: rle lre - list[#list+1] = format("char %s of class %s (bidi=%s)",utf.char(char),d,attr) - end - end - elseif trace then - if d == "lro" or d == "rlo" or d == "pdf" then -- else side effects on terminal - list[#list+1] = format("override char of class %s (bidi=%s)",d,attr) - else -- todo: rle lre - list[#list+1] = format("char %s of class %s (bidi=%s)",utf.char(char),d,attr) + glue_order, glue_data = 0, nil + if current then + current = current.next + end + else + local sct = categories[sc] -- or 'unknown' + if sct == 'disable' then + ignore_following = true + if trace then trace_skip(sct,sc,so,sp,current) end + head, current = remove_node(head, current, true) + elseif sct == 'nowhite' then + ignore_whitespace = true + head, current = remove_node(head, current, true) + elseif sct == 'discard' then + if trace then trace_skip(sct,sc,so,sp,current) end + head, current = remove_node(head, current, true) + else + if sp then + if not penalty_data then + penalty_data = sp + elseif penalty_order < so then + penalty_order, penalty_data = so, sp + elseif penalty_order == so and sp > penalty_data then + penalty_data = sp end end - if d == "on" then - local mirror = data[char].mirror - if mirror and fonts.tfm.id[current.font].characters[mirror] then - -- todo: set attribute - if autodir < 0 then - current.char = mirror - done = true - --~ elseif left or autodir > 0 then - --~ if not is_right(current.prev) then - --~ current.char = mirror - --~ done = true - --~ end + if 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 " .. sct,sc,so,sp,current) end + glue_order = so + head, current, glue_data = remove_node(head, current) + elseif glue_order < so then + if trace then trace_skip("force",sc,so,sp,current) end + glue_order = so + free_glue_node(glue_data) + head, current, glue_data = remove_node(head, current) + elseif glue_order == so then + if sct == 'largest' then + local cs, gs = current.spec, glue_data.spec + local cw = (cs and cs.width) or 0 + local gw = (gs and gs.width) or 0 + if cw > gw then + if trace then trace_skip('largest',sc,so,sp,current) end + free_glue_node(glue_data) -- also free spec + head, current, glue_data = remove_node(head, current) + else + if trace then trace_skip('remove smallest',sc,so,sp,current) end + head, current = remove_node(head, current, true) end - end - elseif d == "l" or d == "en" then -- european number - if autodir <= 0 then - force_auto_left_before() - end - elseif d == "r" or d == "al" or d == "an" then -- arabic left, arabic number - if autodir >= 0 then - force_auto_right_before() - end - elseif d == "lro" then -- Left-to-Right Override -> right becomes left - if trace then list[#list+1] = "override right -> left" end - top = top + 1 - stack[top] = { override, embedded } - override = -1 - obsolete[#obsolete+1] = current - elseif d == "rlo" then -- Right-to-Left Override -> left becomes right - if trace then list[#list+1] = "override left -> right" end - top = top + 1 - stack[top] = { override, embedded } - override = 1 - obsolete[#obsolete+1] = current - elseif d == "lre" then -- Left-to-Right Embedding -> TLT - if trace then list[#list+1] = "embedding left -> right" end - top = top + 1 - stack[top] = { override, embedded } - embedded = 1 - obsolete[#obsolete+1] = current - elseif d == "rle" then -- Right-to-Left Embedding -> TRT - if trace then list[#list+1] = "embedding right -> left" end - top = top + 1 - stack[top] = { override, embedded } - embedded = 1 - obsolete[#obsolete+1] = current - elseif d == "pdf" then -- Pop Directional Format - -- override = 0 - if top > 0 then - local s = stack[top] - override, embedded = s[1], s[2] - top = top - 1 - if trace then list[#list+1] = format("state: override: %s, embedded: %s, autodir: %s",override,embedded,autodir) end - else - if trace then list[#list+1] = "pop (error, too many pops)" end - end - obsolete[#obsolete+1] = current - end - else - if trace then - local char = current.char - local d = directions[char] - list[#list+1] = format("char %s of class %s (no bidi)",utf.char(char),d) - end - end - elseif id == whatsit then - if finish then - finish_auto_before() - end - local subtype = current.subtype - if subtype == 6 then - local dir = current.dir - local d = dir:sub(2,2) - if dir:find(".R.") then - autodir = -1 - else - autodir = 1 - end - embeddded = autodir - if trace then list[#list+1] = format("pardir %s",dir) end - elseif subtype == 7 then - local dir = current.dir - local sign = dir:sub(1,1) - local dire = dir:sub(3,3) - if dire == "R" then - if sign == "+" then - finish, autodir = "TRT", -1 + elseif sct == 'goback' then + if trace then trace_skip('goback',sc,so,sp,current) end + free_glue_node(glue_data) -- also free spec + head, current, glue_data = remove_node(head, current) + elseif sct == 'force' then + -- todo: inject kern + if trace then trace_skip('force',sc,so,sp,current) end + free_glue_node(glue_data) -- also free spec + head, current, glue_data = remove_node(head, current) + elseif sct == 'penalty' then + if trace then trace_skip('penalty',sc,so,sp,current) end + free_glue_node(glue_data) -- also free spec + glue_data = nil + head, current = remove_node(head, current, true) + elseif sct == 'add' then + if trace then trace_skip('add',sc,so,sp,current) end + local old, new = glue_data.spec, current.spec + old.width = old.width + new.width + old.stretch = old.stretch + new.stretch + old.shrink = old.shrink + new.shrink + -- toto: order + head, current = remove_node(head, current, true) else - finish, autodir = nil, 0 + if trace then trace_skip("unknown",sc,so,sp,current) end + head, current = remove_node(head, current, true) end else - if sign == "+" then - finish, autodir = "TLT", 1 - else - finish, autodir = nil, 0 - end + if trace then trace_skip("unknown",sc,so,sp,current) end + head, current = remove_node(head, current, true) end - if trace then list[#list+1] = format("textdir %s",dir) end - end - else - if trace then list[#list+1] = format("node %s",node.type(id)) end - if finish then - finish_auto_before() end end - local cn = current.next - if not cn then - if finish then - finish_auto_after() + elseif id == penalty then + --~ natural_penalty = current.penalty + --~ if trace then trace_done("removed penalty",current) end + --~ head, current = remove_node(head, current, true) + current = current.next + elseif id == glue and current.subtype == 2 then + local sn = has_attribute(current,snap_category) + if sn then + -- local sv = nodes.snapvalues[sn] + -- if sv then + if trace then trace_natural("removed baselineskip",current) end + head, current = remove_node(head, current, true) + -- else + -- current = current.next + -- end + else + if trace then trace_natural("keep baselineskip",current) end + current = current.next + end + elseif id == glue and current.subtype == 3 then + -- parskip always comes later + if ignore_whitespace then + if trace then trace_natural("ignored parskip",current) end + head, current = remove_node(head,current,true) + elseif glue_data then + local ps, gs = current.spec, glue_data.spec + if ps and gs and ps.width > gs.width then + -- free_glue_spec(glue_data.spec) -- result in double free + glue_data.spec = copy_node(ps) + if trace then trace_natural("taking parskip",current) end + else + if trace then trace_natural("removed parskip",current) end end + head, current = remove_node(head, current,true) + else + if trace then trace_natural("honored parskip",current) end + head, current, glue_data = remove_node(head, current) end - current = cn - end - if trace and glyphs then - logs.report("bidi","start log") - for i=1,#list do - logs.report("bidi","%02i: %s",i,list[i]) - end - logs.report("bidi","stop log") - end - if done and mirror.strip then - local n = #obsolete - if n > 0 then - for i=1,n do - nodes.remove(head,obsolete[i],true) - end - logs.report("bidi","%s character nodes removed",n) + --~ if trace then trace_natural("removed parskip",current) end + --~ current.spec = nil + --~ current = current.next + else + if glue_data then + if trace then trace_done("flushed",glue_data) end + head, current = insert_node_before(head,current,glue_data) + glue_order, glue_data = 0, nil end - end - return head, done - end - - chars.handle_mirroring = nodes.install_attribute_handler { - name = "mirror", - namespace = mirror, - processor = mirror.process, - } - - cases = cases or { } - cases.enabled = false - cases.actions = { } - - local function helper(start, code, codes) - local data, char = characters.data, start.char - local dc = data[char] - if dc then - local fnt = start.font - local ifc = fonts.tfm.id[fnt].characters - local ucs = dc[codes] - if ucs then - local ok = true - for i=1,#ucs do - ok = ok and ifc[ucs[i]] - end - if ok then - local prev, original, copy = start, start, node.copy - for i=1,#ucs do - local chr = ucs[i] - prev = start - if i == 1 then - start.char = chr - else - local g = copy(original) - g.char = chr - local next = start.next - g.prev = start - if next then - g.next = next - start.next = g - next.prev = g - end - start = g + if penalty_data then + local p = make_penalty_node(penalty_data) + if trace then trace_done("flushed",p) end + head, current = insert_node_before(head,current,p) + penalty_data = nil + end + if trace then trace_node(current) end + if id == hlist and where == 'hmode_par' then + local list = current.list + if list then + local sn = has_attribute(list,snap_category) + if sn then + local sv = nodes.snapvalues[sn] + if sv then + local height, depth, lineheight = sv[1], sv[2], sv[3] + -- is math.ceil really needed? + current.height = math.ceil((current.height-height)/lineheight)*lineheight + height + current.depth = math.ceil((current.depth -depth )/lineheight)*lineheight + depth end end - return prev, true end - return start, false - end - local uc = dc[code] - if uc and ifc[uc] then - start.char = uc - return start, true end + current = current.next end - return start, false end - - local function upper(start) - return helper(start,'uccode','uccodes') + local tail = slide_node_list(head) -- still needed, check previous code ? + 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 function lower(start) - return helper(start,'lccode','lccodes') + if penalty_data then + local p = make_penalty_node(penalty_data) + if trace then trace_done("result",p) end + head, tail = insert_node_after(head,tail,p) end - - cases.actions[1], cases.actions[2] = upper, lower - - cases.actions[3] = function(start,attribute) - local prev = start.prev - if prev and prev.id == kern and prev.subtype == 0 then - prev = prev.prev - end - if not prev or prev.id ~= glyph then - --- only the first character is treated - for n in traverse_id(glyph,start.next) do - if has_attribute(n,attribute) then - unset_attribute(n,attribute) - end - end - return upper(start) - else - return start, false - end + if glue_data then + if trace then trace_done("result",glue_data) end + head, tail = insert_node_after(head,tail,glue_data) end - - cases.actions[4] = function(start,attribute) - local prev = start.prev - if prev and prev.id == kern and prev.subtype == 0 then - prev = prev.prev - end - if not prev or prev.id ~= glyph then - return upper(start) - else - return start, false + if trace then + if glue_data or penalty_data then + trace_info("stop flushing",where,what) end + show_tracing(head) end + return head, true +end + +-- alignment after_output end box new_graf vmode_par hmode_par insert penalty before_display after_display +-- \par -> vmode_par +-- +-- status.best_page_break +-- tex.lists.best_page_break +-- tex.lists.best_size (natural size to best_page_break) +-- tex.lists.least_page_cost (badness of best_page_break) +-- tex.lists.page_head +-- tex.lists.contrib_head + +local stackhead, stacktail, stackhack = nil, nil, false - -- cases.actions[5] = function(start) - -- local prev, next = start.prev, start.next - -- if prev and prev.id == kern and prev.subtype == 0 then - -- prev = prev.prev - -- end - -- if next and next.id == kern and next.subtype == 0 then - -- next = next.next - -- end - -- if (not prev or prev.id ~= glyph) and next and next.id == glyph then - -- return upper(start) - -- else - -- return start, false - -- end - -- end +local function report(message,lst) + logs.report("vspacing",message,count_nodes(lst,true),node_ids_to_string(lst)) +end - cases.actions[8] = function(start) - local data = characters.data - local ch = start.char - local mr = math.random - local tfm = fonts.tfm.id[start.font].characters - if data[ch].lccode then - while true do - local d = data[mr(1,0xFFFF)] - if d then - local uc = d.uccode - if uc and tfm[uc] then - start.char = uc - return start, true +function nodes.handle_page_spacing(where) + local newhead = texlists.contrib_head + if newhead then + statistics.starttiming(vspacing) + local newtail = slide_node_list(newhead) + local flush = false + for n in traverse_nodes(newhead) do + local id = n.id + if id == glue then + if n.subtype == 0 then + if has_attribute(n,skip_category) then + stackhack = true + else + flush = true end + else + -- tricky end + else + flush = true end - elseif data[ch].uccode then - while true do - local d = data[mr(1,0xFFFF)] - if d then - local lc = d.lccode - if lc and tfm[lc] then - start.char = lc - return start, true - end - end + end + if flush then + if stackhead then + if trace_collect_vspacing then report("appending %s nodes to stack (final): %s",newhead) end + stacktail.next = newhead + newhead.prev = stacktail + newhead = stackhead + stackhead, stacktail = nil, nil + end + if stackhack then + stackhack = false + if trace_collect_vspacing then report("processing %s nodes: %s",newhead) end + texlists.contrib_head = collapser(newhead,"page",where,trace_page_vspacing) + else + if trace_collect_vspacing then report("flushing %s nodes: %s",newhead) end + texlists.contrib_head = newhead end else - return start, false - end - end - - -- node.traverse_id_attr - - function cases.process(namespace,attribute,head) -- not real fast but also not used on much data - local done, actions = false, cases.actions - for start in traverse_id(glyph,head) do - local attr = has_attribute(start,attribute) - if attr and attr > 0 then - unset_attribute(start,attribute) - local action = actions[attr] - if action then - local _, ok = action(start,attribute) - done = done and ok - end + if stackhead then + if trace_collect_vspacing then report("appending %s nodes to stack (intermediate): %s",newhead) end + stacktail.next = newhead + newhead.prev = stacktail + else + if trace_collect_vspacing then report("storing %s nodes in stack (initial): %s",newhead) end + stackhead = newhead end + stacktail = newtail + texlists.contrib_head = nil end - return head, done + statistics.stoptiming(vspacing) end +end - chars.handle_casing = nodes.install_attribute_handler { - name = "case", - namespace = cases, - processor = cases.process, - } - - breakpoints = breakpoints or { } - breakpoints.mapping = breakpoints.mapping or { } - breakpoints.methods = breakpoints.methods or { } - breakpoints.enabled = false - - input.storage.register(false,"breakpoints/mapping", breakpoints.mapping, "breakpoints.mapping") +local ignore = table.tohash { + "split_keep", + "split_off", + -- "vbox", +} - function breakpoints.setreplacement(id,char,kind,before,after) - local mapping = breakpoints.mapping[id] - if not mapping then - mapping = { } - breakpoints.mapping[id] = mapping - end - mapping[char] = { kind or 1, before or 1, after or 1 } +function nodes.handle_vbox_spacing(head,where) + if head and not ignore[where] and head.next then + statistics.starttiming(vspacing) + head = collapser(slide_node_list(head),"vbox",where,trace_vbox_vspacing) + statistics.stoptiming(vspacing) end + return head +end - breakpoints.methods[1] = function(head,start) - if start.prev and start.next then - node.insert_before(head,start,nodes.penalty(10000)) - node.insert_before(head,start,nodes.glue(0)) - node.insert_after(head,start,nodes.glue(0)) - node.insert_after(head,start,nodes.penalty(0)) - end - return head, start - end - breakpoints.methods[2] = function(head,start) -- ( => (- - if start.prev and start.next then - local tmp = start - start = nodes.disc() - start.prev, start.next = tmp.prev, tmp.next - tmp.prev.next, tmp.next.prev = start, start - tmp.prev, tmp.next = nil, nil - start.replace = tmp - local tmp, hyphen = node.copy(tmp), node.copy(tmp) - hyphen.char = languages.prehyphenchar(tmp.lang) - tmp.next, hyphen.prev = hyphen, tmp - start.post = tmp - node.insert_before(head,start,nodes.penalty(10000)) - node.insert_before(head,start,nodes.glue(0)) - node.insert_after(head,start,nodes.glue(0)) - node.insert_after(head,start,nodes.penalty(10000)) - end - return head, start - end - breakpoints.methods[3] = function(head,start) -- ) => -) - if start.prev and start.next then - local tmp = start - start = nodes.disc() - start.prev, start.next = tmp.prev, tmp.next - tmp.prev.next, tmp.next.prev = start, start - tmp.prev, tmp.next = nil, nil - start.replace = tmp - local tmp, hyphen = node.copy(tmp), node.copy(tmp) - hyphen.char = languages.prehyphenchar(tmp.lang) - tmp.prev, hyphen.next = hyphen, tmp - start.pre = hyphen - node.insert_before(head,start,nodes.penalty(10000)) - node.insert_before(head,start,nodes.glue(0)) - node.insert_after(head,start,nodes.glue(0)) - node.insert_after(head,start,nodes.penalty(10000)) - end - return head, start +statistics.register("v-node processing time", function() + if statistics.elapsedindeed(vspacing) then + return format("%s seconds", statistics.elapsedtime(vspacing)) end +end) - function breakpoints.process(namespace,attribute,head) - local done, mapping, fontids = false, breakpoints.mapping, fonts.tfm.id - local start, n = head, 0 - while start do - local id = start.id - if id == glyph then - local attr = has_attribute(start,attribute) - if attr and attr > 0 then - unset_attribute(start,attribute) -- maybe test for subtype > 256 (faster) - -- look ahead and back n chars - local map = mapping[attr] - if map then - local smap = map[start.char] - if smap then - if n >= smap[2] then - local m = smap[3] - local next = start.next - while next do -- gamble on same attribute - local id = next.id - if id == glyph then -- gamble on same attribute - if map[next.char] then - break - elseif m == 1 then - local method = breakpoints.methods[smap[1]] - if method then - head, start = method(head,start) - done = true - end - break - else - m = m - 1 - next = next.next - end - elseif id == kern and next.subtype == 0 then - next = next.next - -- ignore intercharacter kerning, will go way - else - -- we can do clever and set n and jump ahead but ... not now - break - end - end - end - n = 0 - else - n = n + 1 - end - else - n = 0 - end - end - elseif id == kern and start.subtype == 0 then - -- ignore intercharacter kerning, will go way - else - n = 0 - end - start = start.next - end - return head, done - end - - chars.handle_breakpoints = nodes.install_attribute_handler { - name = "breakpoint", - namespace = breakpoints, - processor = breakpoints.process, - } +-- these are experimental callback definitions that definitely will +-- be moved elsewhere as part of a chain of vnode handling +function vspacing.enable() +--~ callback.register('vpack_filter', nodes.handle_vbox_spacing) + callback.register('buildpage_filter', nodes.handle_page_spacing) end --- educational: snapper - ---~ function demo_snapper(head,where) -- snap_category 105 / nodes.snapvalue = { [1] = { 8*65536, 4*65536, 12*65536 } } ---~ if head then ---~ local current, tail, dummy = head, nil, nil ---~ while current do ---~ local id = current.id ---~ if id == glue and current.subtype == 2 then ---~ local sn = has_attribute(current,snap_category) ---~ if sn then ---~ local sv = nodes.snapvalues[sn] ---~ if sv then ---~ head, current, dummy = node.delete(head, current) ---~ node.free(dummy) ---~ else ---~ current = current.next ---~ end ---~ else ---~ current = current.next ---~ end ---~ else ---~ if id == hlist and where == 'hmode_par' and current.list then ---~ local sn = has_attribute(current.list,snap_category) ---~ if sn then ---~ local sv = nodes.snapvalues[sn] ---~ if sv then ---~ local height, depth, lineheight = sv[1], sv[2], sv[3] ---~ current.height = math.ceil((current.height-height)/lineheight)*lineheight + height ---~ current.depth = math.ceil((current.depth -depth )/lineheight)*lineheight + depth ---~ end ---~ end ---~ end ---~ current = current.next ---~ end ---~ tail = current ---~ end ---~ end ---~ return head ---~ end - ---~ callback.register('buildpage_filter', demo_snapper) - --- obsolete, callback changed - ---~ local head, tail = nil, nil - ---~ function nodes.flush_vertical_spacing() ---~ if head and head.next then ---~ local t = collapser(head,'flush') ---~ head = nil ---~ -- tail = nil ---~ return t ---~ else ---~ return head ---~ end ---~ end - ---~ function nodes.handle_page_spacing(t, where) ---~ where = where or "page" ---~ -- we need to add the latest t too, else we miss skips and such ---~ if t then ---~ --~ node.slide(t) -- redunant ---~ if t.next then ---~ local tt = node.slide(t) ---~ local id = tt.id ---~ if id == glue then -- or id == penalty then -- or maybe: if not hlist or vlist ---~ if head then ---~ t.prev = tail ---~ tail.next = t ---~ else ---~ head = t ---~ end ---~ tail = tt ---~ t = nil ---~ elseif head then ---~ t.prev = tail ---~ tail.next = t ---~ t = collapser(head,"page",where) ---~ head = nil ---~ else ---~ t = collapser(t,"page",where) ---~ end ---~ elseif head then ---~ t.prev = tail ---~ tail.next = t ---~ t = collapser(head,"page",where) ---~ head = nil ---~ else ---~ t = collapser(t,"page",where) ---~ end ---~ elseif head then ---~ t = collapser(head,"page",where) ---~ head = nil ---~ end ---~ return t ---~ end +function vspacing.disable() + callback.register('vpack_filter', nil) + callback.register('buildpage_filter', nil) +end |