summaryrefslogtreecommitdiff
path: root/tex/context/base/mkxl/anch-pos.lmt
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/mkxl/anch-pos.lmt')
-rw-r--r--tex/context/base/mkxl/anch-pos.lmt1381
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