diff options
Diffstat (limited to 'tex/context/base/mkxl/node-ali.lmt')
-rw-r--r-- | tex/context/base/mkxl/node-ali.lmt | 818 |
1 files changed, 584 insertions, 234 deletions
diff --git a/tex/context/base/mkxl/node-ali.lmt b/tex/context/base/mkxl/node-ali.lmt index 0a7cf30ce..ca3c97536 100644 --- a/tex/context/base/mkxl/node-ali.lmt +++ b/tex/context/base/mkxl/node-ali.lmt @@ -6,50 +6,75 @@ if not modules then modules = { } end modules ['node-ali'] = { license = "see context related readme files" } -local a_alignchar = attributes.private("aligncharacter") - -local nuts = nodes.nuts -local tonut = nuts.tonut -local tonode = nuts.tonode -local getwidth = nuts.getwidth -local setwidth = nuts.setwidth -local getid = nuts.getid -local getattr = nuts.getattr -local setnext = nuts.setnext -local getnext = nuts.getnext -local getprev = nuts.getprev -local setglue = nuts.setglue -local getglue = nuts.getglue -local getlist = nuts.getlist -local setlist = nuts.setlist -local setattrlist = nuts.setattrlist -local getchar = nuts.getchar -local addmargins = nuts.addmargins -local findtail = nuts.tail -local hasglyph = nuts.hasglyph -local getwordrange = nuts.getwordrange -local dimensions = nuts.rangedimensions -local nextrecord = nuts.traversers.alignrecord -local nextunset = nuts.traversers.unset -local nextglyph = nuts.traversers.glyph -local nextglue = nuts.traversers.glue -local nextnode = nuts.traversers.node -local prevnode = nuts.treversers.node -local flushnode = nuts.flush -local hpack = nuts.hpack - -local glyph_code = nodes.nodecodes.glyph -local glue_code = nodes.nodecodes.glue -local kern_code = nodes.nodecodes.kern -local disc_code = nodes.nodecodes.disc - -local spaceskip_code = nodes.gluecodes.spaceskip -local xspaceskip_code = nodes.gluecodes.xspaceskip -local fontkern_code = nodes.kerncodes.fontkern - -local newkern = nuts.pool.kern -local insertbefore = nuts.insertbefore -local insertafter = nuts.insertafter +local setmetatableindex = table.setmetatableindex + +local a_alignchar = attributes.private("aligncharacter") + +local nuts = nodes.nuts +local tonut = nuts.tonut +local tonode = nuts.tonode +local getwidth = nuts.getwidth +local setwidth = nuts.setwidth +local getid = nuts.getid +local getattr = nuts.getattr +local setnext = nuts.setnext +local getnext = nuts.getnext +local getprev = nuts.getprev +local setglue = nuts.setglue +local getglue = nuts.getglue +local setglue = nuts.setglue +local getwhd = nuts.getwhd +local setwhd = nuts.setwhd +local getlist = nuts.getlist +local setlist = nuts.setlist +local setattrlist = nuts.setattrlist +local setprop = nuts.setprop +local getfont = nuts.getfont +local getchar = nuts.getchar +local addmargins = nuts.addmargins +local findtail = nuts.tail +local hasglyph = nuts.hasglyph +local getwordrange = nuts.getwordrange +local dimensions = nuts.rangedimensions +local flushnode = nuts.flush +local hpack = nuts.hpack +local repack = nuts.repack +local insertbefore = nuts.insertbefore +local insertafter = nuts.insertafter +local effectiveglue = nuts.effectiveglue + +local newkern = nuts.pool.kern +local newrule = nuts.pool.rule +local newglue = nuts.pool.glue + +local traversers = nuts.traversers +local nextrecord = traversers.alignrecord +local nextunset = traversers.unset +local nextglyph = traversers.glyph +local nextglue = traversers.glue +local nextboundary = traversers.boundary +local nextnode = traversers.node +local nextlist = traversers.list + +local nodecodes = nodes.nodecodes +local glyph_code = nodecodes.glyph +local glue_code = nodecodes.glue +local kern_code = nodecodes.kern +local disc_code = nodecodes.disc +local unset_code = nodecodes.unset +local alignrecord_code = nodecodes.alignrecord + +local spaceskip_code = nodes.gluecodes.spaceskip +local xspaceskip_code = nodes.gluecodes.xspaceskip +local intermathskip_code = nodes.gluecodes.intermathskip +local fontkern_code = nodes.kerncodes.fontkern +local row_code = nodes.listcodes.alignment -- should be row +local cell_code = nodes.listcodes.cell +local line_code = nodes.listcodes.line + +-- local preamble_pass <const> = tex.alignmentcontextcodes.preamble +-- local preroll_pass <const> = tex.alignmentcontextcodes.preroll +-- local wrapup_pass <const> = tex.alignmentcontextcodes.wrapup -- todo statistics and tracing @@ -57,211 +82,213 @@ local method = 2 local unislots = fonts.hashes.unislots -- todo local chardata = fonts.hashes.characters -function nodes.handlers.aligncharacter(head,attr,preamble) - local attr = getattr(attr,a_alignchar) -- 1 : value doesn't matter (for now) - if attr then - local widths = { } - local data = { } - local rows = 0 - local cols = 0 - for col in nextrecord, preamble do - cols = cols + 1 - local w, s = getwidth(col,true) - widths[cols] = { col, w, s } - end - -- - for row in nextunset, head do - rows = rows + 1 - local c = 0 - local d = { } - data[rows] = d - for col in nextunset, getlist(row) do - c = c + 1 - if widths[c][2] then - local list = getlist(col) - -- if method == 1 then - -- local left = nil - -- local right = nil - -- local middle = nil - -- for g, char in nextglyph, list do - -- if not left then - -- left = g - -- end - -- if char == getattr(g,a_alignchar) then - -- middle = g - -- end - -- right = g - -- end - -- d[c] = middle and { col, left, middle, right, 0, 0, getwidth(middle) } or false - -- elseif method == 2 then - local middle = nil - -- we can either cache unislots or we can cache for this font - for g, char, font in nextglyph, list do - local unicode = getattr(g,a_alignchar) - if unicode then - if char == unicode then - middle = g - elseif unislots[font][char] == unicode then - middle = g +function nodes.handlers.aligncharacter(head,where,attr,preamble) + if where == "preroll" then + local attr = getattr(attr,a_alignchar) -- 1 : value doesn't matter (for now) + if attr then + local widths = { } + local data = { } + local rows = 0 + local cols = 0 + for col in nextrecord, preamble do + cols = cols + 1 + local w, s = getwidth(col,true) + widths[cols] = { col, w, s } + end + -- + for row in nextunset, head do + rows = rows + 1 + local c = 0 + local d = { } + data[rows] = d + for col in nextunset, getlist(row) do + c = c + 1 + if widths[c][2] then + local list = getlist(col) + -- if method == 1 then + -- local left = nil + -- local right = nil + -- local middle = nil + -- for g, char in nextglyph, list do + -- if not left then + -- left = g + -- end + -- if char == getattr(g,a_alignchar) then + -- middle = g + -- end + -- right = g + -- end + -- d[c] = middle and { col, left, middle, right, 0, 0, getwidth(middle) } or false + -- elseif method == 2 then + local middle = nil + -- we can either cache unislots or we can cache for this font + for g, char, font in nextglyph, list do + local unicode = getattr(g,a_alignchar) + if unicode then + if char == unicode then + middle = g + elseif unislots[font][char] == unicode then + middle = g + end end end - end - if middle then - local left, right = getwordrange(middle) -- not real gain but handy anyway (less code too) - -- local left = middle - -- local right = middle - -- for g, id, subtype in nextnode, middle do - -- if id == glyph_code or id == disc_code then - -- right = g - -- elseif id == kern_code and subtype == fontkern_code then - -- right = g - -- else - -- break - -- end - -- end - -- for g, id, subtype in prevnode, middle do - -- if id == glyph_code or id == disc_code then - -- left = g - -- elseif id == kern_code and subtype == fontkern_code then - -- left = g - -- else - -- break - -- end - -- end - d[c] = { col, left, middle, right, 0, 0, getwidth(middle) } - else - d[c] = false - end - -- else - -- local middle = nil - -- for g, char in nextglyph, list do - -- if char == getattr(g,a_alignchar) then - -- middle = g - -- end - -- end - -- if middle then - -- local left = list - -- local right = findtail(list) - -- if getid(left) == glue_code then - -- left = getnext(left) - -- end - -- if getid(right) == glue_code then - -- right = getprev(right) - -- end - -- d[c] = { col, left, middle, right, 0, 0, getwidth(middle) } - -- else - -- d[c] = false - -- end - -- end - else - d[c] = false + if middle then + local left, right = getwordrange(middle) -- not real gain but handy anyway (less code too) + -- local left = middle + -- local right = middle + -- for g, id, subtype in nextnode, middle do + -- if id == glyph_code or id == disc_code then + -- right = g + -- elseif id == kern_code and subtype == fontkern_code then + -- right = g + -- else + -- break + -- end + -- end + -- for g, id, subtype in prevnode, middle do + -- if id == glyph_code or id == disc_code then + -- left = g + -- elseif id == kern_code and subtype == fontkern_code then + -- left = g + -- else + -- break + -- end + -- end + d[c] = { col, left, middle, right, 0, 0, getwidth(middle) } + else + d[c] = false + end + -- else + -- local middle = nil + -- for g, char in nextglyph, list do + -- if char == getattr(g,a_alignchar) then + -- middle = g + -- end + -- end + -- if middle then + -- local left = list + -- local right = findtail(list) + -- if getid(left) == glue_code then + -- left = getnext(left) + -- end + -- if getid(right) == glue_code then + -- right = getprev(right) + -- end + -- d[c] = { col, left, middle, right, 0, 0, getwidth(middle) } + -- else + -- d[c] = false + -- end + -- end + else + d[c] = false + end end end - end - -- - for col=1,cols do - local maxl = 0 - local maxr = 0 - local minm = 0 - local maxm = 0 - local colw = widths[col] - for row=1,rows do - local d = data[row][col] - if d then - local p = d[1] - local l = d[2] - local m = d[3] - local r = d[4] - if m then - local lw = l == m and 0 or dimensions(p,l,m) - local rw = m == r and 0 or dimensions(p,getnext(m),getnext(r)) - d[5] = lw - d[6] = rw - if lw > maxl then - maxl = lw - end - if rw > maxr then - maxr = rw - end - local mw = d[7] - if maxm == 0 then - minm = mw - maxm = mw - else - if mw > maxm then - maxm = mw + -- + for col=1,cols do + local maxl = 0 + local maxr = 0 + local minm = 0 + local maxm = 0 + local colw = widths[col] + for row=1,rows do + local d = data[row][col] + if d then + local p = d[1] + local l = d[2] + local m = d[3] + local r = d[4] + if m then + local lw = l == m and 0 or dimensions(p,l,m) + local rw = m == r and 0 or dimensions(p,getnext(m),getnext(r)) + d[5] = lw + d[6] = rw + if lw > maxl then + maxl = lw end - if mw < minm then + if rw > maxr then + maxr = rw + end + local mw = d[7] + if maxm == 0 then minm = mw + maxm = mw + else + if mw > maxm then + maxm = mw + end + if mw < minm then + minm = mw + end end end end end - end - -- - local fixedwidth = colw[3] ~= 0 - -- - local old = colw[2] - local new = old - for row=1,rows do - local d = data[row][col] - if d then - local p = d[1] - local l = d[2] - local m = d[3] - local r = d[4] - if l and m and r then - local lw = d[5] - local rw = d[6] - local mw = d[7] - dl = maxl - lw - dr = maxr - rw - if dl ~= 0 or dr ~= 0 or mw ~= maxm then - local lst = getlist(p) - local wid = getwidth(p) - if dl ~= 0 then - local k = newkern(dl) - lst = insertbefore(lst,l,k) - setattrlist(k,m) + -- + local fixedwidth = colw[3] ~= 0 + -- + local old = colw[2] + local new = old + for row=1,rows do + local d = data[row][col] + if d then + local p = d[1] + local l = d[2] + local m = d[3] + local r = d[4] + if l and m and r then + local lw = d[5] + local rw = d[6] + local mw = d[7] + dl = maxl - lw + dr = maxr - rw + if dl ~= 0 or dr ~= 0 or mw ~= maxm then + local lst = getlist(p) + local wid = getwidth(p) + if dl ~= 0 then + local k = newkern(dl) + lst = insertbefore(lst,l,k) + setattrlist(k,m) + setlist(p,lst) + wid = wid + dl + end + if dr ~= 0 then + local k = newkern(dr) + insertafter(lst,r,k) + setattrlist(k,m) + wid = wid + dr + end + if mw ~= maxm then + local dw = (maxm - mw) + local dx = dw / 2 + addmargins(m,-dx,-dx) + wid = wid + dw + end + setwidth(p,wid) + if wid > new then + new = wid + end setlist(p,lst) - wid = wid + dl - end - if dr ~= 0 then - local k = newkern(dr) - insertafter(lst,r,k) - setattrlist(k,m) - wid = wid + dr - end - if mw ~= maxm then - local dw = (maxm - mw) - local dx = dw / 2 - addmargins(m,-dx,-dx) - wid = wid + dw - end - setwidth(p,wid) - if wid > new then - new = wid - end - setlist(p,lst) - -- somewhat fuzzy: - if fixedwidth then - local l = hpack(h,getwidth(p),"exactly") - setglue(p,getglue(l)) - setlist(l) - flushnode(l) - else - setglue(p) + -- somewhat fuzzy: + if fixedwidth then + local l = hpack(h,getwidth(p),"exactly") + setglue(p,getglue(l)) + setlist(l) + flushnode(l) + else + setglue(p) + end + -- end - -- end end end - end - if new > old then - if fixedwidth then - -- issue overflow warning - else - setwidth(colw[1],new) + if new > old then + if fixedwidth then + -- issue overflow warning + else + setwidth(colw[1],new) + end end end end @@ -283,3 +310,326 @@ interfaces.implement { end, } +-- Let's put it here (for now): + +do + + local getdata = nuts.getdata + local removenode = nuts.remove + local getwhd = nuts.getwhd + local getheight = nuts.getheight + local getdepth = nuts.getdepth + local setheight = nuts.setheight + local setdepth = nuts.setdepth + local getglue = nuts.getglue + local setoffsets = nuts.setoffsets + local setsubtype = nuts.setsubtype + + local baselineskip_code = nodes.gluecodes.baselineskip + local lineskip_code = nodes.gluecodes.lineskip + + local alignrecord_code = nodecodes.alignrecord + local hlist_code = nodecodes.hlist + local unset_code = nodecodes.unset + + local nextnode = nuts.traversers.node + + local texgetdimen = tex.getdimen + local texgetglue = tex.getglue + local texget = tex.get + + local leftmarker = tex.boundaries.system("c_math_align_l_marker") + local rightmarker = tex.boundaries.system("c_math_align_r_marker") + + local a_location = attributes.system("mathnumberlocation") + local a_threshold = attributes.system("mathnumberthreshold") + + -- Here: + + local function openup(specification,head) + local inbetween = specification.inbetween or 0 + local height = specification.height or 0 + local depth = specification.depth or 0 + local lines = { } + for n, id, subtype, list in nextlist, head do + lines[#lines+1] = { n, subtype, getwhd(n) } + end + local t = #lines + if t > 0 then + local l = 1 + for n, subtype in nextglue, head do + -- one day we can decide what to do with intertext stuff based on the + -- subtype but not now ... on our agenda (intertext etc) + if subtype == baselineskip_code or subtype == lineskip_code then + local amount, stretch, shrink = getglue(n) + local prevdp = lines[l] [5] + local nextht = lines[l+1][4] + local delta = 0 + if prevdp < depth then + setdepth(lines[l][1],depth) + delta = delta + (depth - prevdp) + end + if nextht < height then + setheight(lines[l+1][1],height) + delta = delta + (height - nextht) + end + if subtype == lineskip_code then + setglue(n,inbetween,stretch,shrink) + setsubtype(n,baselineskip_code) + else + setglue(n,amount+inbetween-delta,stretch,shrink) + end + l = l + 1 +-- if l > t then +-- break +-- end + end + end + local firstht = lines[1][4] + local lastdp = lines[t][5] + if firstht < height then + setheight(lines[1],height) + end + if lastdp < depth then + setdepth(lines[t],depth) + end + end + end + + nuts.openup = openup + + -- When present, the number is after the right marker. We need to move the + -- number when we want it at the left. + -- + -- Todo: set a flag in mathalignment and support atttibutes on it so that + -- we can check if this is needed. + + -- [dummy] [left -2] [second -1] [number 0] + + -- In the end it makes more sense to just calculate the alignment in lua + -- but it is kind of fun to see how we can control alignments. + + local totals = { } + local widths = { } + local records = { } + local deltas = { } + local cellwidths = { } + + local a_flushleft <const> = 1 + local a_centered <const> = 2 + local a_flushright <const> = 3 + + local function first_pass(head,attr,preamble) + -- + local width = 0 + local hsize = texget("hsize") + local count = 0 + local overflow = false +totals = { } -- maybe use one table +widths = { } +records = { } +deltas = { } + for n in nextrecord, preamble do + local wd = getwidth(n) + count = count + 1 + width = width + wd + totals [count] = width + widths [count] = wd + records[count] = n + deltas [count] = 0 + end + -- + local lindex = 0 + local rindex = 0 + local lwidth = 0 + local rwidth = 0 + local centered = false + for row in nextunset, head do + local count = 0 + local anchor = nil + local rochan = nil + -- local cellwidths = { } + for cell in nextunset, getlist(row) do + local list = getlist(cell) + count = count + 1 + cellwidths[count] = getwidth(cell) + for bound in nextboundary, list do + local marker = getdata(bound) + if marker == leftmarker then + lindex = count + anchor = bound + rochan = list + elseif marker == rightmarker then + local n = getnext(bound) + if n and getid(n) == hlist_code then + local wd, ht, dp = getwhd(n) + local lc = getattr(n,a_location) + if lc then + -- todo: just store align in the outer attribute so once ... + local align = lc // 0x10 + local location = lc % 0x10 + local threshold = getattr(n,a_threshold) + if location == 1 then + -- number right + local m = 1 + local s = align == a_centered and 2 or 1 +-- if align == 1 then + if align == a_flushleft then + -- flushleft + rwidth = wd +-- elseif align == 3 then + elseif align == a_flushright then + -- flushright + rwidth = wd + elseif wd > lwidth then + lwidth = wd + rwidth = wd + centered = true + end + if totals[count-2] + cellwidths[count-1] + s*wd - s*threshold > hsize then + local total = ht + dp + setdepth(row,getdepth(row) + total) + setoffsets(n,0,-total) + local pr = records[count-1] + local cw = getwidth(pr) + if cw - wd > deltas[count-1] then + deltas[count-1] = cw - wd + end + overflow = true + end + elseif location == 2 then + -- number left +-- if align == 1 then + if align == a_flushleft then + -- flushleft + lwidth = wd +-- elseif align == 3 then + elseif align == a_flushright then + -- flushright + lwidth = wd + end + end + if location == 2 and anchor then + local l, l, n = removenode(list,n) + if l ~= list then + setlist(cell,l) + -- setwidth(cell,0) + end + insertafter(rochan,anchor,n) + end + end + end + rindex = count + end + end + end + end + -- + if overflow then + if deltas[rindex-1] ~= 0 then + setwidth(records[rindex-1],deltas[rindex-1]) + end + end + for count=1,#records do + if count == lindex then + if centered and overflow then + lwidth = lwidth - tex.getdimen("d_math_eqalign_number_distance") + end + setwidth(records[count],lwidth) + elseif count == rindex then + setwidth(records[count],rwidth) + end + end + end + + local function second_pass(head,attr,preamble) + local done = setmetatableindex("table") + local glues = { } + local okay = false + for row, id, subtype in nextlist, head do + if id == hlist_code and subtype == row_code then + for cell, id, subtype in nextlist, getlist(row) do + if id == hlist_code and subtype == cell_code then + for n, s in nextglue, getlist(cell) do + if s == intermathskip_code then + local e = effectiveglue(n,cell) + local g = getglue(n) + local f = getfont(n) + local a = done[f] + local d = a[g] + glues[n] = g + if not d then + a[g] = e + elseif d > e then + a[g] = e + okay = true + end + end + end + end + end + end + end + if okay then + for k, v in next, glues do + local g = done[getfont(k)][v] + if g then + setglue(k,g) + setprop(k,"fixedmathalign",true) + end + end + for row, id, subtype in nextlist, head do + if id == hlist_code and subtype == row_code then + for cell, id, subtype, list in nextlist, getlist(row) do + if list and id == hlist_code and subtype == cell_code then + local wd = getwidth(cell) + repack(cell,wd,"exactly") + end + end + end + end + end + end + + local function third_pass(head,attr,preamble) + local inbetween, stretch, shrink = texgetglue("s_strc_math_alignment_inbetween") + openup({ inbetween = inbetween }, head) + end + + -- maybe zero pass: preamble pass + + function nodes.handlers.fixmathalign(head,where,attr,preamble) + if where == "preroll" then + local signal = getattr(attr,a_location) + if signal == 0x20 or signal == 0x40 then + first_pass(head,attr,preamble) + end + elseif where == "wrapup" then + local signal = getattr(attr,a_location) + if signal == 0x40 then + second_pass(head,attr,preamble) + end + -- maybe also signal + third_pass(head,attr,preamble) + end + end +end + +local report = logs.reporter("alignment","preamble") +local trace trackers.register("alignments.showstates",function(v) trace = v end) + +function nodes.handlers.showpreamble(head,where,attr,preamble) + if trace then + local c = 0 + for n, id in nextnode, preamble do + if id == unset_code or id == alignrecord_code then + c = c + 1 + report("stage %a, cell %i, width %p",where,c,getwidth(n)) + elseif id == glue_code then + report("stage %a, tabskip %s",where,node.direct.gluetostring(n)) + else + report("stage %a, node %a",where,nodecodes[id]) + end + end + end +end |