diff options
Diffstat (limited to 'tex/context/base/mkxl/anch-pos.lmt')
-rw-r--r-- | tex/context/base/mkxl/anch-pos.lmt | 1381 |
1 files changed, 1030 insertions, 351 deletions
diff --git a/tex/context/base/mkxl/anch-pos.lmt b/tex/context/base/mkxl/anch-pos.lmt index 94eb443c6..5e53406da 100644 --- a/tex/context/base/mkxl/anch-pos.lmt +++ b/tex/context/base/mkxl/anch-pos.lmt @@ -6,33 +6,46 @@ if not modules then modules = { } end modules ['anch-pos'] = { license = "see context related readme files" } ---[[ldx-- -<p>We save positional information in the main utility table. Not only -can we store much more information in <l n='lua'/> but it's also -more efficient.</p> ---ldx]]-- - --- plus (extra) is obsolete but we will keep it for a while +-- We save positional information in the main utility table. Not only can we store +-- much more information in Lua but it's also more efficient. In the meantime these +-- files have become quite large. In some cases that get noticed by a hickup in the +-- start and/or finish, but that is the price we pay for progress. +-- +-- This was the last module that got rid of directly setting scanners, with a little +-- performance degradation but not that noticeable. It is also a module that has been +-- on the (partial) redo list for a while. -- --- maybe replace texsp by our own converter (stay at the lua end) --- eventually mp will have large numbers so we can use sp there too +-- We can gain a little when we group positions but then we still have to deal with +-- regions and cells so we either end up with lots of extra small tables pointing to +-- them and/or assembling/disassembling. I played with that and rejected the idea +-- until I ran into a test case where we had 50.000 one line paragraphs in an eight +-- columns setup, and there we save 25 M on a 75 M tuc file. So, I played a bit more +-- and we can have a solution that is of similar performance for regular documents +-- (in spite of the extra overhead) but also works ok for the large files. In normal +-- documents it is never a problem, but there are always exceptions to the normal and +-- often these are also cases where positions are not really used but end up in the +-- tuc file anyway. -- --- this is one of the first modules using scanners and we need to replace it by --- implement and friends +-- Currently (because we never had split tags) we do splitting at access time, which +-- is sort of inefficient but still ok. Much of this mechanism comes from MkII where +-- TeX was the bottleneck. -- --- we could have namespaces, like p, page, region, columnarea, textarea but then --- we need virtual table accessors as well as have tag/id accessors ... we don't --- save much here (at least not now) +-- By grouping we need to set more metatable so in the end that is also overhead +-- but on the average we're okay because excessive serialization also comes at a price +-- and this way we also delay some activities till the moment it is realy used (which +-- is not always the case with positional information. We will make this transition +-- stepwise so till we're done there will be inefficiencies and overhead. -- --- This was the last module that got rid of directly setting scanners, with a little --- performance degradation but not that noticeable. +-- The pure hash based variant combined with filtering is in anch-pos.lua and previous +-- lmt versions! That is the reference. -local tostring, next, setmetatable, tonumber = tostring, next, setmetatable, tonumber -local sort = table.sort +local tostring, next, setmetatable, tonumber, rawget, rawset = tostring, next, setmetatable, tonumber, rawget, rawset +local sort, sortedhash = table.sort, table.sortedhash local format, gmatch = string.format, string.gmatch -local lpegmatch = lpeg.match +local P, R, C, Cc, lpegmatch = lpeg.P, lpeg.R, lpeg.C, lpeg.Cc, lpeg.match local insert, remove = table.insert, table.remove local allocate = utilities.storage.allocate +local setmetatableindex, setmetatablenewindex = table.setmetatableindex, table.setmetatablenewindex local report = logs.reporter("positions") @@ -55,11 +68,11 @@ local texsetcount = tex.setcount local texget = tex.get local texsp = tex.sp ----- texsp = string.todimen -- because we cache this is much faster but no rounding - -local setmetatableindex = table.setmetatableindex -local setmetatablenewindex = table.setmetatablenewindex +local texgetnest = tex.getnest +local texgetparstate = tex.getparstate local nuts = nodes.nuts +local tonut = nodes.tonut local setlink = nuts.setlink local getlist = nuts.getlist @@ -67,8 +80,12 @@ local setlist = nuts.setlist local getbox = nuts.getbox local getid = nuts.getid local getwhd = nuts.getwhd +local setprop = nuts.setprop + +local getparstate = nuts.getparstate local hlist_code = nodes.nodecodes.hlist +local par_code = nodes.nodecodes.par local find_tail = nuts.tail ----- hpack = nuts.hpack @@ -85,6 +102,7 @@ local formatters = string.formatters local collected = allocate() local tobesaved = allocate() +local positionsused = false local jobpositions = { collected = collected, @@ -109,138 +127,707 @@ local default = { -- not r and paragraphs etc hs = 0, -- hsize pi = 0, -- parindent ps = false, -- parshape - dir = 0, + dir = 0, -- obsolete + r2l = false, -- righttoleft } } local f_b_tag = formatters["b:%s"] local f_e_tag = formatters["e:%s"] local f_p_tag = formatters["p:%s"] -local f_w_tag = formatters["w:%s"] +----- f_w_tag = formatters["w:%s"] local f_region = formatters["region:%s"] local f_tag_three = formatters["%s:%s:%s"] local f_tag_two = formatters["%s:%s"] -local nofregular = 0 -local nofspecial = 0 -local splitter = lpeg.splitat(":",true) - -local pagedata = { } -local columndata = setmetatableindex("table") -- per page -local freedata = setmetatableindex("table") -- per page - -local function initializer() - tobesaved = jobpositions.tobesaved - collected = jobpositions.collected - for tag, data in next, collected do - local prefix, rest = lpegmatch(splitter,tag) - if prefix == "p" then - nofregular = nofregular + 1 - elseif prefix == "page" then - nofregular = nofregular + 1 - pagedata[tonumber(rest) or 0] = data - elseif prefix == "free" then - nofspecial = nofspecial + 1 - local t = freedata[data.p or 0] - t[#t+1] = data - elseif prefix == "columnarea" then - columndata[data.p or 0][data.c or 0] = data - end - setmetatable(data,default) - end - -- - local pages = structures.pages.collected - if pages then - local last = nil - for p=1,#pages do - local region = "page:" .. p - local data = pagedata[p] - local free = freedata[p] - if free then - sort(free,function(a,b) return b.y < a.y end) -- order matters ! - end - if data then - last = data - last.free = free - elseif last then - local t = setmetatableindex({ free = free, p = p },last) - if not collected[region] then - collected[region] = t +-- Because positions are set with a delay we cannot yet make the tree -- so that +-- is a finalizer step. But, we already have a dual split. + +-- local treemode = false +local treemode = true + +local function checkshapes(s) + for p, data in next, s do + local n = #data + if n > 1 then + local d1 = data[1] + local ph = d1[2] + local pd = d1[3] + local xl = d1[4] + local xr = d1[5] + for i=2,n do + local di = data[i] + local h = di[2] + local d = di[3] + local l = di[4] + local r = di[5] + if r == xr then + di[5] = nil + if l == xl then + di[4] = nil + if d == pd then + di[3] = nil + if h == ph then + di[2] = nil + else + ph = h + end + else + pd, ph = d, h + end + else + ph, pd, xl = h, d, l + end else - -- something is wrong + ph, pd, xl, xr = h, d, l, r end - pagedata[p] = t end end end - jobpositions.pagedata = pagedata end -function jobpositions.used() - return next(collected) -- we can safe it -end +local columndata = { } +local freedata = { } -- we can make these weak +local syncdata = { } -- we can make these weak +local columndone = false -function jobpositions.getfree(page) - return freedata[page] -end +if treemode then --- we can gain a little when we group positions but then we still have to --- deal with regions and cells so we either end up with lots of extra small --- tables pointing to them and/or assembling/disassembling so in the end --- it makes no sense to do it (now) and still have such a mix --- --- proof of concept code removed ... see archive - -local function finalizer() - -- We make the (possible extensive) shape lists sparse working - -- from the end. We could also drop entries here that have l and - -- r the same which saves testing later on. - for k, v in next, tobesaved do - local s = v.s - if s then - for p, data in next, s do - local n = #data - if n > 1 then - local ph = data[1][2] - local pd = data[1][3] - local xl = data[1][4] - local xr = data[1][5] - for i=2,n do - local di = data[i] - local h = di[2] - local d = di[3] - local l = di[4] - local r = di[5] - if r == xr then - di[5] = nil - if l == xl then - di[4] = nil - if d == pd then - di[3] = nil - if h == ph then - di[2] = nil - else - ph = h + -- At some point we can install extra ones. I actually was halfway making a more + -- general installer but we have quite some distinct handling down here and it + -- became messy. So I rolled that back. Also, users and modules will quite likely + -- stay in the "user" namespace. + + -- syncpos : indirect access via helper, todo after we switch: direct setters + -- free : indirect access via helper, todo after we switch: direct setters + -- columnarea : indirect access via helper, todo after we switch: direct setters + + -- todo: keep track of total and check that against # (sanity check) + + local prefix_number = { "text", "textarea", "page", "p", "free", "columnarea" } + local prefix_label_number = { "syncpos" } + local prefix_number_rest = { "region", "b", "e" } + + -- no need to split: syncpos free columnarea (textarea?) + + local function splitter_pattern() + local p_number = R("09")^1/tonumber + local p_colon = P(":") + local p_label = C(P(1 - p_colon)^0) + local p_rest = C(P(1)^0) + return + C(lpeg.utfchartabletopattern(prefix_number )) * p_colon * p_number * P(-1) + + C(lpeg.utfchartabletopattern(prefix_label_number)) * p_colon * (p_number + p_label) * p_colon * p_number * P(-1) + + C(lpeg.utfchartabletopattern(prefix_number_rest )) * p_colon * (p_number + p_rest) + + Cc("user") * p_rest + end + + -- In the end these metatable entries are not more efficient than copying + -- but it's all about making sure that the tuc file doesn't explode. + + columndata = { } + columndone = false + + local deltapacking = true -- so we can see the difference + + local function checkcommondata(v,common) + if common then + local i = v.i + local t = common[i] + if t then + local m = t.mt + if not m then + setmetatable(t,default) + m = { __index = t } + t.mt = m + end + setmetatable(v,m) + return + end + end + setmetatable(v,default) + end + + local function initializer() + tobesaved = jobpositions.tobesaved + collected = jobpositions.collected + -- + local p_splitter = splitter_pattern() + -- + local list = nil + -- + local shared = setmetatableindex(rawget(collected,"shared"),"table") + local x_y_w_h_list = shared.x_y_w_h + local y_w_h_d_list = shared.y_w_h_d + local x_h_d_list = shared.x_h_d + local x_h_d_hs_list = shared.x_h_d_hs + -- + columndata = setmetatableindex(function(t,k) + setmetatableindex(t,"table") + list = rawget(collected,"columnarea") + if list then + -- for tag, data in next, list do + for i=1,#list do + local data = list[i] + columndata[data.p or 0][data.c or 0] = data + checkcommondata(data,y_w_h_d_list) + end + end + columndone = true + return t[k] + end) + -- + -- todo: use a raw collected and a weak proxy + -- + setmetatableindex(collected,function(t,k) + local prefix, one, two = lpegmatch(p_splitter,k) + local list = rawget(t,prefix) + if list and type(list) == "table" then + v = list[one] or false + if v then + if prefix == "p" then + if deltapacking and type(v) == "number" then + for i=one,1,-1 do + local l = list[i] + if type(l) ~= "number" then + if not getmetatable(l) then + checkcommondata(l,x_h_d_hs_list) end - else - pd, ph = d, h + v = setmetatable({ y = v }, { __index = l }) + list[one] = v + break + end + end + else + checkcommondata(v,x_h_d_hs_list) + end + elseif prefix == "text" or prefix == "textarea" then + if type(v) == "number" then + for i=one,1,-1 do + local l = list[i] + if type(l) ~= "number" then + if not getmetatable(l) then + checkcommondata(l,x_y_w_h_list) + end + v = setmetatable({ p = p }, { __index = l }) + list[one] = v + break end - else - ph, pd, xl = h, d, l end else - ph, pd, xl, xr = h, d, l, r + checkcommondata(v,x_y_w_h_list) + end + elseif prefix == "columnarea" then + if not columndone then + checkcommondata(v,y_w_h_d_list) + end + elseif prefix == "syncpos" then + -- will become an error + if two then + -- v = syncdata[one][two] or { } + v = v[two] or { } + else + v = { } + end + -- for j=1,#v do + -- checkcommondata(v[j],x_h_d_list) + -- end + elseif prefix == "free" then + -- will become an error + elseif prefix == "page" then + checkcommondata(v) + else + checkcommondata(v) + end + else + if prefix == "page" then + for i=one,1,-1 do + local data = list[i] + if data then + v = setmetatableindex({ free = free or false, p = p },last) + list[one] = v + break + end + end + end + end + t[k] = v + return v + else + t[k] = false + return false + end + end) + -- + setmetatableindex(tobesaved,function(t,k) + local prefix, one, two = lpegmatch(p_splitter,k) + local v = rawget(t,prefix) + if v and type(v) == "table" then + v = v[one] + if v and two then + v = v[two] + end + return v -- or default + else + -- return default + end + end) + -- + setmetatablenewindex(tobesaved,function(t,k,v) + local prefix, one, two = lpegmatch(p_splitter,k) + local p = rawget(t,prefix) + if not p then + p = { } + rawset(t,prefix,p) + end + if type(one) == "number" then -- maybe Cc(0 1 2) + if #p < one then + for i=#p+1,one-1 do + p[i] = { } -- false + end + end + end + if two then + local pone = p[one] + if not pone then + pone = { } + p[one] = pone + end + if type(two) == "number" then -- maybe Cc(0 1 2) + if #pone < two then + for i=#pone+1,two-1 do + pone[i] = { } -- false + end + end + end + pone[two] = v + else + p[one] = v + end + end) + -- + syncdata = setmetatableindex(function(t,category) + -- p's and y's are not shared so no need to resolve + local list = rawget(collected,"syncpos") + local tc = list and rawget(list,category) + if tc then + sort(tc,function(a,b) + local ap = a.p + local bp = b.p + if ap == bp then + return b.y < a.y + else + return ap < bp + end + end) + tc.start = 1 + for i=1,#tc do + checkcommondata(tc[i],x_h_d_list) + end + else + tc = { } + end + t[category] = tc + return tc + end) + -- + for k, v in next, collected do + if k ~= "shared" and next(v) then + positionsused = true + break + end + end + end + + function jobpositions.used() + return positionsused + end + + local function finalizer() + + -- We make the (possible extensive) shape lists sparse working from the end. We + -- could also drop entries here that have l and r the same which saves testing + -- later on. + + local nofpositions = 0 + local nofpartials = 0 + local nofdeltas = 0 + -- + local x_y_w_h_size = 0 + local x_y_w_h_list = { } + local x_y_w_h_hash = setmetatableindex(function(t,x) + local y = setmetatableindex(function(t,y) + local w = setmetatableindex(function(t,w) + local h = setmetatableindex(function(t,h) + x_y_w_h_size = x_y_w_h_size + 1 + t[h] = x_y_w_h_size + x_y_w_h_list[x_y_w_h_size] = { x = x, y = y, w = w, h = h } + return x_y_w_h_size + end) + t[w] = h + return h + end) + t[y] = w + return w + end) + t[x] = y + return y + end) + -- + local y_w_h_d_size = 0 + local y_w_h_d_list = { } + local y_w_h_d_hash = setmetatableindex(function(t,y) + local w = setmetatableindex(function(t,w) + local h = setmetatableindex(function(t,h) + local d = setmetatableindex(function(t,d) + y_w_h_d_size = y_w_h_d_size + 1 + t[d] = y_w_h_d_size + y_w_h_d_list[y_w_h_d_size] = { y = y, w = w, h = h, d = d } + return y_w_h_d_size + end) + t[h] = d + return d + end) + t[w] = h + return h + end) + t[y] = w + return w + end) + -- + local x_h_d_size = 0 + local x_h_d_list = { } + local x_h_d_hash = setmetatableindex(function(t,x) + local h = setmetatableindex(function(t,h) + local d = setmetatableindex(function(t,d) + x_h_d_size = x_h_d_size + 1 + t[d] = x_h_d_size + x_h_d_list[x_h_d_size] = { x = x, h = h, d = d } + return x_h_d_size + end) + t[h] = d + return d + end) + t[x] = h + return h + end) + -- + local x_h_d_hs_size = 0 + local x_h_d_hs_list = { } + local x_h_d_hs_hash = setmetatableindex(function(t,x) + local h = setmetatableindex(function(t,h) + local d = setmetatableindex(function(t,d) + local hs = setmetatableindex(function(t,hs) + x_h_d_hs_size = x_h_d_hs_size + 1 + t[hs] = x_h_d_hs_size + x_h_d_hs_list[x_h_d_hs_size] = { x = x, h = h, d = d, hs = hs } + return x_h_d_hs_size + end) + t[d] = hs + return hs + end) + t[h] = d + return d + end) + t[x] = h + return h + end) + -- + rawset(tobesaved,"shared", { + x_y_w_h = x_y_w_h_list, + y_w_h_d = y_w_h_d_list, + x_h_d = x_h_d_list, + x_h_d_hs = x_h_d_hs_list, + }) + -- + -- If fonts can use crazy and hard to grasp packing tricks so can we. The "i" field + -- refers to a shared set of values. In addition we pack some sequences. + -- + -- how about free + -- + for k, v in sortedhash(tobesaved) do + if k == "p" then + -- numeric + local n = #v + for i=1,n do + local t = v[i] + local hsh = x_h_d_hs_hash[t.x or 0][t.h or 0][t.d or 0][t.hs or 0] + t.x = nil + t.h = nil + t.d = nil + t.hs = nil -- not in syncpos + t.i = hsh + local s = t.s + if s then + checkshapes(s) + end + end + if deltapacking then + -- delta packing (y) + local last + local current + for i=1,n do + current = v[i] + if last then + for k, v in next, last do + if k ~= "y" and v ~= current[k] then + goto DIFFERENT + end + end + for k, v in next, current do + if k ~= "y" and v ~= last[k] then + goto DIFFERENT + end + end + v[i] = current.y or 0 + nofdeltas = nofdeltas + 1 + goto CONTINUE + end + ::DIFFERENT:: + last = current + ::CONTINUE:: + end + end + -- + nofpositions = nofpositions + n + nofpartials = nofpartials + n + elseif k == "syncpos" then + -- hash + for k, t in next, v do + -- numeric + local n = #t + for j=1,n do + local t = t[j] + local hsh = x_h_d_hash[t.x or 0][t.h or 0][t.d or 0] + t.x = nil + t.h = nil + t.d = nil + t.i = hsh + end + nofpositions = nofpositions + n + nofpartials = nofpartials + n + end + elseif k == "text" or k == "textarea" then + -- numeric + local n = #v + for i=1,n do + local t = v[i] + local hsh = x_y_w_h_hash[t.x or 0][t.y or 0][t.w or 0][t.h or 0] + t.x = nil + t.y = nil + t.w = nil + t.h = nil + t.i = hsh + end + nofpositions = nofpositions + n + nofpartials = nofpartials + n + if deltapacking then + -- delta packing (p) + local last + local current + for i=1,n do + current = v[i] + if last then + for k, v in next, last do + if k ~= "p" and v ~= current[k] then + goto DIFFERENT + end + end + for k, v in next, current do + if k ~= "p" and v ~= last[k] then + goto DIFFERENT + end + end + v[i] = current.p or 0 + nofdeltas = nofdeltas + 1 + goto CONTINUE end + ::DIFFERENT:: + last = current + ::CONTINUE:: end end + elseif k == "columnarea" then + -- numeric + local n = #v + for i=1,n do + local t = v[i] + local hsh = y_w_h_d_hash[t.y or 0][t.w or 0][t.h or 0][t.d or 0] + t.y = nil + t.w = nil + t.h = nil + t.d = nil + t.i = hsh + end + nofpositions = nofpositions + n + nofpartials = nofpartials + n + else -- probably only b has shapes + for k, t in next, v do -- no need to sort + local s = t.s + if s then + checkshapes(s) + end + nofpositions = nofpositions + 1 + end end end + + statistics.register("positions", function() + if nofpositions > 0 then + return format("%s collected, %i deltas, %i shared partials, %i partial entries", + nofpositions, nofdeltas, nofpartials, + x_y_w_h_size + y_w_h_d_size + x_h_d_size + x_h_d_hs_size + ) + else + return nil + end + end) + end + + freedata = setmetatableindex(function(t,page) + local list = rawget(collected,"free") + local free = { } + if list then + local size = 0 + for i=1,#list do + local l = list[i] + if l.p == page then + size = size + 1 + free[size] = l + checkcommondata(l) + end + end + sort(free,function(a,b) return b.y < a.y end) -- order matters ! + end + t[page] = free + return free + end) + + job.register('job.positions.collected', tobesaved, initializer, finalizer) + +else + + columndata = setmetatableindex("table") -- per page + freedata = setmetatableindex("table") -- per page + + local function initializer() + tobesaved = jobpositions.tobesaved + collected = jobpositions.collected + -- + local pagedata = { } + local p_splitter = lpeg.splitat(":",true) + + for tag, data in next, collected do + local prefix, rest = lpegmatch(p_splitter,tag) + if prefix == "page" then + pagedata[tonumber(rest) or 0] = data + elseif prefix == "free" then + local t = freedata[data.p or 0] + t[#t+1] = data + elseif prefix == "columnarea" then + columndata[data.p or 0][data.c or 0] = data + end + setmetatable(data,default) + end + local pages = structures.pages.collected + if pages then + local last = nil + for p=1,#pages do + local region = "page:" .. p + local data = pagedata[p] + local free = freedata[p] + if free then + sort(free,function(a,b) return b.y < a.y end) -- order matters ! + end + if data then + last = data + last.free = free + elseif last then + local t = setmetatableindex({ free = free, p = p },last) + if not collected[region] then + collected[region] = t + else + -- something is wrong + end + pagedata[p] = t + end + end + end + jobpositions.pagedata = pagedata -- never used + + end + + local function finalizer() + + -- We make the (possible extensive) shape lists sparse working from the end. We + -- could also drop entries here that have l and r the same which saves testing + -- later on. + + local nofpositions = 0 + + for k, v in next, tobesaved do + local s = v.s + if s then + checkshapes(s) + end + nofpositions = nofpositions + 1 + end + + statistics.register("positions", function() + if nofpositions > 0 then + return format("%s collected",nofpositions) + else + return nil + end + end) + + end + + local p_number = lpeg.patterns.cardinal/tonumber + local p_tag = P("syncpos:") * p_number * P(":") * p_number + + syncdata = setmetatableindex(function(t,category) + setmetatable(t,nil) + for tag, pos in next, collected do + local c, n = lpegmatch(p_tag,tag) + if c then + local tc = t[c] + if tc then + tc[n] = pos + else + t[c] = { [n] = pos } + end + end + end + for k, list in next, t do + sort(list,function(a,b) + local ap = a.p + local bp = b.p + if ap == bp then + return b.y < a.y + else + return ap < bp + end + end) + list.start = 1 + end + return t[category] + end) + + job.register('job.positions.collected', tobesaved, initializer, finalizer) + +end + +function jobpositions.getfree(page) + return freedata[page] end -job.register('job.positions.collected', tobesaved, initializer, finalizer) +function jobpositions.getsync(category) + return syncdata[category] or { } +end local regions = { } local nofregions = 0 @@ -491,7 +1078,6 @@ local function e_region(specification) local last = tobesaved[region] if last then local y = getvpos() - local x, y = getpos() if specification.correct then local h = (last.y or 0) - y last.h = h ~= 0 and h or nil @@ -507,13 +1093,22 @@ jobpositions.e_region = e_region local lastregion -local function setregionbox(n,tag,k,lo,ro,to,bo,column) -- kind +local function setregionbox(n,tag,index,k,lo,ro,to,bo,column) -- kind if not tag or tag == "" then nofregions = nofregions + 1 - tag = f_region(nofregions) + tag = "region" + index = nofregions + elseif index ~= 0 then + -- So we can cheat and pass a zero index and enforce tag as is needed in + -- cases where we fallback on automated region tagging (framed). + tag = tag .. ":" .. index end local box = getbox(n) local w, h, d = getwhd(box) + -- We could set directly but then we also need to check for gaps but as this + -- is direct is is unlikely that we get a gap. We then also need to intecept + -- these auto regions (comning from framed). Too messy and the split in the + -- setter is fast enough. tobesaved[tag] = { -- p = texgetcount("realpageno"), -- we copy them x = 0, @@ -532,8 +1127,18 @@ local function setregionbox(n,tag,k,lo,ro,to,bo,column) -- kind return tag, box end -local function markregionbox(n,tag,correct,...) -- correct needs checking - local tag, box = setregionbox(n,tag,...) +-- we can have a finalizer property that we catch in the backend but that demands +-- a check for property for each list .. what is the impact + +-- textarea operates *inside* a box so experiments with pre/post hooks in the +-- backend driver didn't work out (because a box can be larger) +-- +-- it also gives no gain to split prefix and number here because in the end we +-- push and pop tags as strings, but it save a little on expansion so we do it +-- in the interface + +local function markregionbox(n,tag,index,correct,...) -- correct needs checking + local tag, box = setregionbox(n,tag,index,...) -- todo: check if tostring is needed with formatter local push = new_latelua { action = b_region, tag = tag } local pop = new_latelua { action = e_region, correct = correct } @@ -577,195 +1182,290 @@ function jobpositions.settobesaved(name,tag,data) end end -local nofparagraphs = 0 +do -implement { - name = "parpos", - actions = function() - nofparagraphs = nofparagraphs + 1 - texsetcount("global","c_anch_positions_paragraph",nofparagraphs) - local box = getbox("strutbox") - local w, h, d = getwhd(box) - local t = { - p = true, - c = true, - r = true, - x = true, - y = true, - h = h, - d = d, - hs = texget("hsize"), -- never 0 - } - local leftskip = texget("leftskip",false) - local rightskip = texget("rightskip",false) - local hangindent = texget("hangindent") - local hangafter = texget("hangafter") - local parindent = texget("parindent") - local parshape = texget("parshape") - if leftskip ~= 0 then - t.ls = leftskip - end - if rightskip ~= 0 then - t.rs = rightskip - end - if hangindent ~= 0 then - t.hi = hangindent + local nofparagraphs = 0 + + local function enhancepar_1(data) + if data then + local par = data.par -- we can pass twice when we copy + local state = par and getparstate(data.par,true) + if state then + local x, y = getpos() + if x ~= 0 then + data.x = x + end + if y ~= 0 then + data.y = y + end + data.p = texgetcount("realpageno") -- we should use a variable set in otr + if column then + data.c = column + end + if region then + data.r = region + end + -- + data.par = nil + local leftskip = state.leftskip + local rightskip = state.rightskip + local hangindent = state.hangindent + local hangafter = state.hangafter + local parindent = state.parindent + local parshape = state.parshape + if hangafter ~= 0 and hangafter ~= 1 then + data.ha = hangafter + end + if hangindent ~= 0 then + data.hi = hangindent + end + data.hs = state.hsize + if leftskip ~= 0 then + data.ls = leftskip + end + if parindent ~= 0 then + data.pi = parindent + end + if rightskip ~= 0 then + data.rs = rightskip + end + if parshape and #parshape > 0 then + data.ps = parshape + end + end end - if hangafter ~= 1 and hangafter ~= 0 then -- can not be zero .. so it needs to be 1 if zero - t.ha = hangafter + return data + end + + local function enhancepar_2(data) + if data then + local x, y = getpos() + if x ~= 0 then + data.x = x + end + if y ~= 0 then + data.y = y + end + data.p = texgetcount("realpageno") -- we should use a variable set in otr + if column then + data.c = column + end + if region then + data.r = region + end end - if parindent ~= 0 then - t.pi = parindent + return data + end + + implement { + name = "parpos", + actions = function() + nofparagraphs = nofparagraphs + 1 + texsetcount("global","c_anch_positions_paragraph",nofparagraphs) + local name = f_p_tag(nofparagraphs) + local box = getbox("strutbox") + local w, h, d = getwhd(box) + -- + local top = texgetnest("top","head") + local nxt = top.next + if nxt then + nxt = tonut(nxt) + end + local data + if nxt and getid(nxt) == par_code then -- todo: check node type + local t = { + h = h, + d = d, + par = nxt, + } + tobesaved[name] = t + ctx_latelua { action = enhancepar_1, specification = t } + else + -- This is kind of weird but it happens in tables (rows) so we probably + -- need less. + local state = texgetparstate() + local leftskip = state.leftskip + local rightskip = state.rightskip + local hangindent = state.hangindent + local hangafter = state.hangafter + local parindent = state.parindent + local parshape = state.parshape + local t = { + p = true, + c = true, + r = true, + x = true, + y = true, + h = h, + d = d, + hs = state.hsize, -- never 0 + } + if leftskip ~= 0 then + t.ls = leftskip + end + if rightskip ~= 0 then + t.rs = rightskip + end + if hangindent ~= 0 then + t.hi = hangindent + end + if hangafter ~= 1 and hangafter ~= 0 then -- can not be zero .. so it needs to be 1 if zero + t.ha = hangafter + end + if parindent ~= 0 then + t.pi = parindent + end + if parshape and #parshape > 0 then + t.ps = parshape + end + tobesaved[name] = t + ctx_latelua { action = enhancepar_2, specification = t } + end end - if parshape and #parshape > 0 then - t.ps = parshape + } + + implement { + name = "dosetposition", + arguments = "argument", + public = true, + protected = true, + actions = function(name) + local spec = { + p = true, + c = column, + r = true, + x = true, + y = true, + n = nofparagraphs > 0 and nofparagraphs or nil, + r2l = texgetinteger("inlinelefttoright") == 1 or nil, + } + tobesaved[name] = spec + ctx_latelua { action = enhance, specification = spec } end - local name = f_p_tag(nofparagraphs) - tobesaved[name] = t - ctx_latelua { action = enhance, specification = t } - end -} + } -implement { - name = "dosetposition", - arguments = "argument", - public = true, - protected = true, - actions = function(name) - local spec = { - p = true, - c = column, - r = true, - x = true, - y = true, - n = nofparagraphs > 0 and nofparagraphs or nil, - r2l = texgetinteger("inlinelefttoright") == 1 or nil, - } - tobesaved[name] = spec - ctx_latelua { action = enhance, specification = spec } - end -} + implement { + name = "dosetpositionwhd", + arguments = { "argument", "dimenargument", "dimenargument", "dimenargument" }, + public = true, + protected = true, + actions = function(name,w,h,d) + local spec = { + p = true, + c = column, + r = true, + x = true, + y = true, + w = w ~= 0 and w or nil, + h = h ~= 0 and h or nil, + d = d ~= 0 and d or nil, + n = nofparagraphs > 0 and nofparagraphs or nil, + r2l = texgetinteger("inlinelefttoright") == 1 or nil, + } + tobesaved[name] = spec + ctx_latelua { action = enhance, specification = spec } + end + } -implement { - name = "dosetpositionwhd", - arguments = { "argument", "dimenargument", "dimenargument", "dimenargument" }, - public = true, - protected = true, - actions = function(name,w,h,d) - local spec = { - p = true, - c = column, - r = true, - x = true, - y = true, - w = w ~= 0 and w or nil, - h = h ~= 0 and h or nil, - d = d ~= 0 and d or nil, - n = nofparagraphs > 0 and nofparagraphs or nil, - r2l = texgetinteger("inlinelefttoright") == 1 or nil, - } - tobesaved[name] = spec - ctx_latelua { action = enhance, specification = spec } - end -} + implement { + name = "dosetpositionbox", + arguments = { "argument", "integerargument" }, + public = true, + protected = true, + actions = function(name,n) + local box = getbox(n) + local w, h, d = getwhd(box) + local spec = { + p = true, + c = column, + r = true, + x = true, + y = true, + w = w ~= 0 and w or nil, + h = h ~= 0 and h or nil, + d = d ~= 0 and d or nil, + n = nofparagraphs > 0 and nofparagraphs or nil, + r2l = texgetinteger("inlinelefttoright") == 1 or nil, + } + tobesaved[name] = spec + ctx_latelua { action = enhance, specification = spec } + end + } -implement { - name = "dosetpositionbox", - arguments = { "argument", "integerargument" }, - public = true, - protected = true, - actions = function(name,n) - local box = getbox(n) - local w, h, d = getwhd(box) - local spec = { - p = true, - c = column, - r = true, - x = true, - y = true, - w = w ~= 0 and w or nil, - h = h ~= 0 and h or nil, - d = d ~= 0 and d or nil, - n = nofparagraphs > 0 and nofparagraphs or nil, - r2l = texgetinteger("inlinelefttoright") == 1 or nil, - } - tobesaved[name] = spec - ctx_latelua { action = enhance, specification = spec } - end -} + implement { + name = "dosetpositionplus", + arguments = { "argument", "dimenargument", "dimenargument", "dimenargument" }, + public = true, + protected = true, + actions = function(name,w,h,d) + local spec = { + p = true, + c = column, + r = true, + x = true, + y = true, + w = w ~= 0 and w or nil, + h = h ~= 0 and h or nil, + d = d ~= 0 and d or nil, + n = nofparagraphs > 0 and nofparagraphs or nil, + e = scanstring(), + r2l = texgetinteger("inlinelefttoright") == 1 or nil, + } + tobesaved[name] = spec + ctx_latelua { action = enhance, specification = spec } + end + } -implement { - name = "dosetpositionplus", - arguments = { "argument", "dimenargument", "dimenargument", "dimenargument" }, - public = true, - protected = true, - actions = function(name,w,h,d) - local spec = { - p = true, - c = column, - r = true, - x = true, - y = true, - w = w ~= 0 and w or nil, - h = h ~= 0 and h or nil, - d = d ~= 0 and d or nil, - n = nofparagraphs > 0 and nofparagraphs or nil, - e = scanstring(), - r2l = texgetinteger("inlinelefttoright") == 1 or nil, - } - tobesaved[name] = spec - ctx_latelua { action = enhance, specification = spec } - end -} + implement { + name = "dosetpositionstrut", + arguments = "argument", + public = true, + protected = true, + actions = function(name) + local box = getbox("strutbox") + local w, h, d = getwhd(box) + local spec = { + p = true, + c = column, + r = true, + x = true, + y = true, + h = h ~= 0 and h or nil, + d = d ~= 0 and d or nil, + n = nofparagraphs > 0 and nofparagraphs or nil, + r2l = texgetinteger("inlinelefttoright") == 1 or nil, + } + tobesaved[name] = spec + ctx_latelua { action = enhance, specification = spec } + end + } -implement { - name = "dosetpositionstrut", - arguments = "argument", - public = true, - protected = true, - actions = function(name) - local box = getbox("strutbox") - local w, h, d = getwhd(box) - local spec = { - p = true, - c = column, - r = true, - x = true, - y = true, - h = h ~= 0 and h or nil, - d = d ~= 0 and d or nil, - n = nofparagraphs > 0 and nofparagraphs or nil, - r2l = texgetinteger("inlinelefttoright") == 1 or nil, - } - tobesaved[name] = spec - ctx_latelua { action = enhance, specification = spec } - end -} + implement { + name = "dosetpositionstrutkind", + arguments = { "argument", "integerargument" }, + public = true, + protected = true, + actions = function(name,kind) + local box = getbox("strutbox") + local w, h, d = getwhd(box) + local spec = { + k = kind, + p = true, + c = column, + r = true, + x = true, + y = true, + h = h ~= 0 and h or nil, + d = d ~= 0 and d or nil, + n = nofparagraphs > 0 and nofparagraphs or nil, + r2l = texgetinteger("inlinelefttoright") == 1 or nil, + } + tobesaved[name] = spec + ctx_latelua { action = enhance, specification = spec } + end + } -implement { - name = "dosetpositionstrutkind", - arguments = { "argument", "integerargument" }, - public = true, - protected = true, - actions = function(name,kind) - local box = getbox("strutbox") - local w, h, d = getwhd(box) - local spec = { - k = kind, - p = true, - c = column, - r = true, - x = true, - y = true, - h = h ~= 0 and h or nil, - d = d ~= 0 and d or nil, - n = nofparagraphs > 0 and nofparagraphs or nil, - r2l = texgetinteger("inlinelefttoright") == 1 or nil, - } - tobesaved[name] = spec - ctx_latelua { action = enhance, specification = spec } - end -} +end function jobpositions.getreserved(tag,n) if tag == v_column then @@ -1384,18 +2084,18 @@ implement { actions = MPpardata } -implement { - name = "MPposset", - arguments = "argument", - public = true, - actions = function(name) - local b = f_b_tag(name) - local e = f_e_tag(name) - local w = f_w_tag(name) - local p = f_p_tag(getparagraph(b)) - MPpos(b) context(",") MPpos(e) context(",") MPpos(w) context(",") MPpos(p) context(",") MPpardata(p) - end -} +-- implement { +-- name = "MPposset", +-- arguments = "argument", +-- public = true, +-- actions = function(name) +-- local b = f_b_tag(name) +-- local e = f_e_tag(name) +-- local w = f_w_tag(name) +-- local p = f_p_tag(getparagraph(b)) +-- MPpos(b) context(",") MPpos(e) context(",") MPpos(w) context(",") MPpos(p) context(",") MPpardata(p) +-- end +-- } implement { name = "MPls", @@ -1451,7 +2151,7 @@ implement { implement { name = "MPrest", - arguments = { "argument", "argument" }, + arguments = "2 arguments", public = true, actions = function(name,default) local jpi = collected[name] @@ -1515,7 +2215,7 @@ implement { implement { name = "doifelseoverlapping", - arguments = { "argument", "argument" }, + arguments = "2 arguments", public = true, protected = true, actions = function(one,two) @@ -1548,53 +2248,53 @@ implement { public = true, protected = true, actions = function() - doifelse(next(collected)) + doifelse(positionsused) end } implement { name = "markregionbox", - arguments = "integer", + arguments = { "integer", "integer" }, actions = markregionbox } implement { name = "setregionbox", - arguments = "integer", + arguments = { "integer", "integer" }, actions = setregionbox } implement { name = "markregionboxtagged", - arguments = { "integer", "string" }, + arguments = { "integer", "string", "integer" }, actions = markregionbox } implement { name = "markregionboxtaggedn", - arguments = { "integer", "string", "integer" }, - actions = function(box,tag,n) - markregionbox(box,tag,nil,nil,nil,nil,nil,nil,n) + arguments = { "integer", "string", "integer", "integer" }, + actions = function(box,tag,index,n) + markregionbox(box,tag,index,nil,nil,nil,nil,nil,nil,n) end } implement { name = "setregionboxtagged", - arguments = { "integer", "string" }, + arguments = { "integer", "string", "integer" }, actions = setregionbox } implement { name = "markregionboxcorrected", - arguments = { "integer", "string", true }, + arguments = { "integer", "string", "integer", true }, actions = markregionbox } implement { name = "markregionboxtaggedkind", - arguments = { "integer", "string", "integer", "dimen", "dimen", "dimen", "dimen" }, - actions = function(box,tag,n,d1,d2,d3,d4) - markregionbox(box,tag,nil,n,d1,d2,d3,d4) + arguments = { "integer", "string", "integer", "integer", "dimen", "dimen", "dimen", "dimen" }, + actions = function(box,tag,index,n,d1,d2,d3,d4) + markregionbox(box,tag,index,nil,n,d1,d2,d3,d4) end } @@ -1607,27 +2307,6 @@ implement { end } --- statistics (at least for the moment, when testing) - --- statistics.register("positions", function() --- local total = nofregular + nofusedregions + nofmissingregions --- if total > 0 then --- return format("%s collected, %s regulars, %s regions, %s unresolved regions", --- total, nofregular, nofusedregions, nofmissingregions) --- else --- return nil --- end --- end) - -statistics.register("positions", function() - local total = nofregular + nofspecial - if total > 0 then - return format("%s collected, %s regular, %s special",total,nofregular,nofspecial) - else - return nil - end -end) - -- We support the low level positional commands too: local newsavepos = nodes.pool.savepos |