if not modules then modules = { } end modules ['anch-pos'] = { version = 1.001, comment = "companion to anch-pos.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- 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. -- -- 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. -- -- 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. -- -- 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. -- -- 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, rawget, rawset = tostring, next, setmetatable, tonumber, rawget, rawset local sort, sortedhash = table.sort, table.sortedhash local format, gmatch = string.format, string.gmatch 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 fromposit = posit.fromposit local report = logs.reporter("positions") local scanstring = tokens.scanners.string local implement = interfaces.implement local commands = commands local context = context local ctx_latelua = context.latelua local ctx_doif = commands.doif local ctx_doifelse = commands.doifelse local tex = tex local texgetdimen = tex.getdimen local texgetcount = tex.getcount local texgetinteger = tex.getintegervalue or tex.getcount local texiscount = tex.iscount local texisdimen = tex.isdimen 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 texgetnest = tex.getnest local texgetparstate = tex.getparstate local nuts = nodes.nuts local tonut = nodes.tonut local setlink = nuts.setlink local getlist = nuts.getlist 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 local new_latelua = nuts.pool.latelua local v_text = interfaces.variables.text local v_column = interfaces.variables.column local pt = number.dimenfactors.pt local formatters = string.formatters local collected = allocate() local tobesaved = allocate() local positionsused = nil local jobpositions = { collected = collected, tobesaved = tobesaved, } job.positions = jobpositions local dimension_code = tokens.values.dimension local integer_code = tokens.values.integer local default = { -- not r and paragraphs etc __index = { x = 0, -- x position baseline y = 0, -- y position baseline w = 0, -- width h = 0, -- height d = 0, -- depth p = 0, -- page n = 0, -- paragraph ls = 0, -- leftskip rs = 0, -- rightskip hi = 0, -- hangindent ha = 0, -- hangafter hs = 0, -- hsize pi = 0, -- parindent ps = false, -- parshape dir = 0, -- obsolete r2l = false, -- righttoleft dir = false, } } local f_b_tag = formatters["b:%s"] local f_e_tag = formatters["e:%s"] local f_p_tag = formatters["p:%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 c_realpageno = texiscount("realpageno") local d_strutht = texisdimen("strutht") local d_strutdp = texisdimen("strutdp") local c_inlinelefttoright = texiscount("inlinelefttoright") -- 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 ph, pd, xl, xr = h, d, l, r end end end end end local columndata = { } local freedata = { } -- we can make these weak local syncdata = { } -- we can make these weak local columndone = false if treemode then -- 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 deltapacking = false -- 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 v.i = nil 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) if k ~= true then local prefix, one, two = lpegmatch(p_splitter,k) local list = rawget(t,prefix) if list and type(list) == "table" then local v = list[one] or false if v then if prefix == "p" then -- if deltapacking and type(v) == "number" 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_h_d_hs_list) end 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 = v }, { __index = l }) list[one] = v break end end else 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 end end t[k] = false return false end) -- setmetatableindex(tobesaved,function(t,k) if k then 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 end) -- setmetatablenewindex(tobesaved,function(t,k,v) if k then 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 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) 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.used() if positionsused == nil then positionsused = false for k, v in next, collected do if k ~= "shared" and type(v) == "table" and next(v) then positionsused = true break end end end return positionsused end function jobpositions.getfree(page) return freedata[page] end function jobpositions.getsync(category) return syncdata[category] or { } end local regions = { } local nofregions = 0 local region = nil local columns = { } local nofcolumns = 0 local column = nil local nofpages = nil -- beware ... we're not sparse here as lua will reserve slots for the nilled local getpos, gethpos, getvpos, getrpos function jobpositions.registerhandlers(t) getpos = t and t.getpos or function() return 0, 0 end gethvrpos = t and t.gethvrpos or function() return 0, 0, 0 end gethpos = t and t.gethpos or function() return 0 end getvpos = t and t.getvpos or function() return 0 end getrpos = t and t.getrpos or function() return 0 end end function jobpositions.getpos () return getpos () end function jobpositions.gethvrpos() return gethvrpos() end function jobpositions.gethpos () return gethpos () end function jobpositions.getvpos () return getvpos () end function jobpositions.getrpos () return getrpos () end -------- jobpositions.getcolumn() return column end jobpositions.registerhandlers() local function setall(name,p,x,y,w,h,d,extra) tobesaved[name] = { p = p, x = x ~= 0 and x or nil, y = y ~= 0 and y or nil, w = w ~= 0 and w or nil, h = h ~= 0 and h or nil, d = d ~= 0 and d or nil, e = extra ~= "" and extra or nil, r = region, c = column, r2l = texgetinteger(c_inlinelefttoright) == 1 and true or nil, dir = texget("pardirection") == 1 and true or nil, } end local function enhance(data) if not data then return nil end local r = data.r if r == true then -- or "" data.r = region r = region end if data.x == true then if data.y == true then local x, y = getpos() data.x = x ~= 0 and x or nil data.y = y ~= 0 and y or nil else local x = gethpos() data.x = x ~= 0 and x or nil end elseif data.y == true then local y = getvpos() data.y = y ~= 0 and y or nil end -- if data.r2l then -- print(">>>",data.r2l,getrpos()) -- end -- local r2l = getrpos() -- if r2l == 1 then -- data.r2l = nil -- end -- This is a quick hack. We also need to handel the other text areas -- end. It has to do with backgrounds (see mathincontext-setups.tex) if r then local dr = tobesaved[r] if dr then local sy = dr.sy if sy and sy ~= 1 then data.y = data.y * sy data.h = data.h * sy data.d = data.d * sy end end end -- End of hack. if data.p == true then data.p = texgetcount(c_realpageno) -- we should use a variable set in otr end if data.c == true then data.c = column end if data.w == 0 then data.w = nil end if data.h == 0 then data.h = nil end if data.d == 0 then data.d = nil end return data end -- analyze some files (with lots if margindata) and then when one key optionally -- use that one instead of a table (so, a 3rd / 4th argument: key, e.g. "x") local function set(name,index,value) -- ,key -- officially there should have been a settobesaved local data = enhance(value or {}) if value then container = tobesaved[name] if not container then tobesaved[name] = { [index] = data } else container[index] = data end else tobesaved[name] = data end end local function setspec(specification) local name = specification.name local index = specification.index local value = specification.value local data = enhance(value or {}) if value then container = tobesaved[name] if not container then tobesaved[name] = { [index] = data } else container[index] = data end else tobesaved[name] = data end end local function get(id,index) if index then local container = collected[id] return container and container[index] else return collected[id] end end ------------.setdim = setdim jobpositions.setall = setall jobpositions.set = set jobpositions.setspec = setspec jobpositions.get = get implement { name = "dosaveposition", public = true, protected = true, -- arguments = { "argument", "integerargument", "dimenargument", "dimenargument" }, arguments = { "argument", "integer", "dimension", "dimension" }, actions = setall, -- name p x y } implement { name = "dosavepositionwhd", public = true, protected = true, -- arguments = { "argument", "integerargument", "dimenargument", "dimenargument", "dimenargument", "dimenargument", "dimenargument" }, arguments = { "argument", "integer", "dimension", "dimension", "dimension", "dimension", "dimension" }, actions = setall, -- name p x y w h d } implement { name = "dosavepositionplus", public = true, protected = true, -- arguments = { "argument", "integerargument", "dimenargument", "dimenargument", "dimenargument", "dimenargument", "dimenargument", "argument" }, arguments = { "argument", "integer", "dimension", "dimension", "dimension", "dimension", "dimension", "argument" }, actions = setall, -- name p x y w h d extra } -- will become private table (could also become attribute driven but too nasty -- as attributes can bleed e.g. in margin stuff) -- not much gain in keeping stack (inc/dec instead of insert/remove) local function b_column(specification) local tag = specification.tag local x = gethpos() tobesaved[tag] = { r = true, x = x ~= 0 and x or nil, -- w = 0, } insert(columns,tag) column = tag end local function e_column() local t = tobesaved[column] if not t then -- something's wrong else local x = gethpos() - t.x t.w = x ~= 0 and x or nil t.r = region end remove(columns) column = columns[#columns] end jobpositions.b_column = b_column jobpositions.e_column = e_column implement { name = "bposcolumn", arguments = "argument", actions = function(tag) insert(columns,tag) column = tag end } implement { name = "bposcolumnregistered", arguments = "argument", actions = function(tag) insert(columns,tag) column = tag ctx_latelua { action = b_column, tag = tag } end } implement { name = "eposcolumn", actions = function() remove(columns) column = columns[#columns] end } implement { name = "eposcolumnregistered", actions = function() ctx_latelua { action = e_column } remove(columns) column = columns[#columns] end } -- regions local function b_region(specification) local tag = specification.tag or specification local last = tobesaved[tag] if last then local x, y = getpos() last.x = x ~= 0 and x or nil last.y = y ~= 0 and y or nil last.p = texgetcount(c_realpageno) insert(regions,tag) -- todo: fast stack region = tag end end local function e_region(specification) local last = tobesaved[region] if last then local y = getvpos() if specification.correct then local h = (last.y or 0) - y last.h = h ~= 0 and h or nil end last.y = y ~= 0 and y or nil remove(regions) -- todo: fast stack region = regions[#regions] end end jobpositions.b_region = b_region jobpositions.e_region = e_region local lastregion local function setregionbox(n,tag,index,depth,yscale,column,k,lo,ro,to,bo) -- no correct here if not tag or tag == "" then nofregions = nofregions + 1 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. if depth then d = d + depth end if yscale and (yscale == 0 or yscale == 1) then yscale = nil end tobesaved[tag] = { -- p = texgetcount(c_realpageno), -- we copy them x = 0, y = 0, w = w ~= 0 and w or nil, h = h ~= 0 and h or nil, d = d ~= 0 and d or nil, k = k ~= 0 and k or nil, lo = lo ~= 0 and lo or nil, ro = ro ~= 0 and ro or nil, to = to ~= 0 and to or nil, bo = bo ~= 0 and bo or nil, c = column or nil, sy = yscale } lastregion = tag return tag, box end -- 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,depth,yscale,column,correct,...) -- correct needs checking local tag, box = setregionbox(n,tag,index,depth,yscale,column,...) -- 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 } -- maybe we should construct a hbox first (needs experimenting) so that we can avoid some at the tex end local head = getlist(box) -- no, this fails with \framed[region=...] .. needs thinking -- if getid(box) ~= hlist_code then -- -- report("mark region box assumes a hlist, fix this for %a",tag) -- head = hpack(head) -- end if head then local tail = find_tail(head) setlink(push,head) setlink(tail,pop) else -- we can have a simple push/pop setlink(push,pop) end setlist(box,push) end jobpositions.markregionbox = markregionbox jobpositions.setregionbox = setregionbox function jobpositions.enhance(name) enhance(tobesaved[name]) end function jobpositions.gettobesaved(name,tag) local t = tobesaved[name] if t and tag then return t[tag] else return t end end function jobpositions.settobesaved(name,tag,data) local t = tobesaved[name] if t and tag and data then t[tag] = data end end do local c_anch_positions_paragraph = texiscount("c_anch_positions_paragraph") 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(c_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 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(c_realpageno) if column then data.c = column end if region then data.r = region end end 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 h = texgetdimen(d_strutht) local d = texgetdimen(d_strutdp) -- 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 } 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(c_inlinelefttoright) == 1 or nil, dir = texget("pardirection") == 1 and true or nil, } tobesaved[name] = spec ctx_latelua { action = enhance, specification = spec } end } implement { name = "dosetpositionwhd", -- arguments = { "argument", "dimenargument", "dimenargument", "dimenargument" }, arguments = { "argument", "dimension", "dimension", "dimension" }, 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(c_inlinelefttoright) == 1 or nil, dir = texget("pardirection") == 1 and true or nil, } tobesaved[name] = spec ctx_latelua { action = enhance, specification = spec } end } implement { name = "dosetpositionbox", -- arguments = { "argument", "integerargument" }, arguments = { "argument", "integer" }, 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(c_inlinelefttoright) == 1 or nil, dir = texget("pardirection") == 1 and true or nil, } tobesaved[name] = spec ctx_latelua { action = enhance, specification = spec } end } implement { name = "dosetpositionplus", -- arguments = { "argument", "dimenargument", "dimenargument", "dimenargument" }, arguments = { "argument", "dimension", "dimension", "dimension" }, 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(c_inlinelefttoright) == 1 or nil, dir = texget("pardirection") == 1 and true 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 h = texgetdimen(d_strutht) local d = texgetdimen(d_strutdp) 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(c_inlinelefttoright) == 1 or nil, dir = texget("pardirection") == 1 and true or nil, } tobesaved[name] = spec ctx_latelua { action = enhance, specification = spec } end } implement { name = "dosetpositionstrutkind", -- arguments = { "argument", "integerargument" }, arguments = { "argument", "integer" }, public = true, protected = true, actions = function(name,kind) local h = texgetdimen(d_strutht) local d = texgetdimen(d_strutdp) 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(c_inlinelefttoright) == 1 or nil, dir = texget("pardirection") == 1 and true or nil, } tobesaved[name] = spec ctx_latelua { action = enhance, specification = spec } end } end function jobpositions.getreserved(tag,n) if tag == v_column then local fulltag = f_tag_three(tag,texgetcount(c_realpageno),n or 1) local data = collected[fulltag] if data then return data, fulltag end tag = v_text end if tag == v_text then local fulltag = f_tag_two(tag,texgetcount(c_realpageno)) return collected[fulltag] or false, fulltag end return collected[tag] or false, tag end function jobpositions.copy(target,source) collected[target] = collected[source] end function jobpositions.replace(id,p,x,y,w,h,d) local c = collected[id] if c then c.p = p ; c.x = x ; c.y = y ; c.w = w ; c.h = h ; c.d = d ; -- c g else collected[i] = { p = p, x = x, y = y, w = w, h = h, d = d } -- c g end end local function getpage(id) local jpi = collected[id] return jpi and jpi.p end local function getcolumn(id) local jpi = collected[id] return jpi and jpi.c or false end local function getparagraph(id) local jpi = collected[id] return jpi and jpi.n end local function getregion(id) local jpi = collected[id] if jpi then local r = jpi.r if r then return r end local p = jpi.p if p then return "page:" .. p end end return false end jobpositions.page = getpage jobpositions.column = getcolumn jobpositions.paragraph = getparagraph jobpositions.region = getregion jobpositions.p = getpage -- not used, kind of obsolete jobpositions.c = getcolumn -- idem jobpositions.n = getparagraph -- idem jobpositions.r = getregion -- idem function jobpositions.x(id) local jpi = collected[id] return jpi and jpi.x end function jobpositions.y(id) local jpi = collected[id] return jpi and jpi.y end function jobpositions.width(id) local jpi = collected[id] return jpi and jpi.w end function jobpositions.height(id) local jpi = collected[id] return jpi and jpi.h end function jobpositions.depth(id) local jpi = collected[id] return jpi and jpi.d end function jobpositions.whd(id) local jpi = collected[id] if jpi then return jpi.w, jpi.h, jpi.d end end function jobpositions.leftskip(id) local jpi = collected[id] return jpi and jpi.ls end function jobpositions.rightskip(id) local jpi = collected[id] return jpi and jpi.rs end function jobpositions.hsize(id) local jpi = collected[id] return jpi and jpi.hs end function jobpositions.parindent(id) local jpi = collected[id] return jpi and jpi.pi end function jobpositions.hangindent(id) local jpi = collected[id] return jpi and jpi.hi end function jobpositions.hangafter(id) local jpi = collected[id] return jpi and jpi.ha or 1 end function jobpositions.xy(id) local jpi = collected[id] if jpi then return jpi.x, jpi.y else return 0, 0 end end function jobpositions.lowerleft(id) local jpi = collected[id] if jpi then return jpi.x, jpi.y - jpi.d else return 0, 0 end end function jobpositions.lowerright(id) local jpi = collected[id] if jpi then return jpi.x + jpi.w, jpi.y - jpi.d else return 0, 0 end end function jobpositions.upperright(id) local jpi = collected[id] if jpi then return jpi.x + jpi.w, jpi.y + jpi.h else return 0, 0 end end function jobpositions.upperleft(id) local jpi = collected[id] if jpi then return jpi.x, jpi.y + jpi.h else return 0, 0 end end function jobpositions.position(id) local jpi = collected[id] if jpi then return jpi.p, jpi.x, jpi.y, jpi.w, jpi.h, jpi.d else return 0, 0, 0, 0, 0, 0 end end local splitter = lpeg.splitat(",") function jobpositions.extra(id,n,default) -- assume numbers local jpi = collected[id] if jpi then local e = jpi.e if e then local split = jpi.split if not split then split = lpegmatch(splitter,jpi.e) jpi.split = split end return texsp(split[n]) or default -- watch the texsp here end end return default end local function overlapping(one,two,overlappingmargin) -- hm, strings so this is wrong .. texsp one = collected[one] two = collected[two] if one and two and one.p == two.p then if not overlappingmargin then overlappingmargin = 2 end local x_one = one.x local x_two = two.x local w_two = two.w local llx_one = x_one - overlappingmargin local urx_two = x_two + w_two + overlappingmargin if llx_one > urx_two then return false end local w_one = one.w local urx_one = x_one + w_one + overlappingmargin local llx_two = x_two - overlappingmargin if urx_one < llx_two then return false end local y_one = one.y local y_two = two.y local d_one = one.d local h_two = two.h local lly_one = y_one - d_one - overlappingmargin local ury_two = y_two + h_two + overlappingmargin if lly_one > ury_two then return false end local h_one = one.h local d_two = two.d local ury_one = y_one + h_one + overlappingmargin local lly_two = y_two - d_two - overlappingmargin if ury_one < lly_two then return false end return true end end local function onsamepage(list,page) for id in gmatch(list,"([^,%s]+)") do local jpi = collected[id] if jpi then local p = jpi.p if not p then return false elseif not page then page = p elseif page ~= p then return false end end end return page end local function columnofpos(realpage,xposition) local p = columndata[realpage] if p then for i=1,#p do local c = p[i] local x = c.x or 0 local w = c.w or 0 if xposition >= x and xposition <= (x + w) then return i end end end return 1 end local function getcolumndata(realpage,column) local p = columndata[realpage] if p then return p[column] end end jobpositions.overlapping = overlapping jobpositions.onsamepage = onsamepage jobpositions.columnofpos = columnofpos jobpositions.getcolumndata = getcolumndata -- interface implement { name = "replacepospxywhd", -- arguments = { "argument", "integerargument", "dimenargument", "dimenargument", "dimenargument", "dimenargument", "dimenargument" }, arguments = { "argument", "integer", "dimension", "dimension", "dimension", "dimension", "dimension" }, public = true, protected = true, actions = function(name,page,x,y,w,h,d) local c = collected[name] if c then c.p = page ; c.x = x ; c.y = y ; c.w = w ; c.h = h ; c.d = d ; else collected[name] = { p = page, x = x, y = y, w = w, h = h, d = d } end end } implement { name = "copyposition", arguments = "2 arguments", public = true, protected = true, actions = function(target,source) collected[target] = collected[source] end } implement { name = "MPp", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then local p = jpi.p if p and p ~= true then context(p) return end end context('0') end } implement { name = "jobposp", arguments = "argument", public = true, usage = "value", actions = function(name) local jpi = collected[name] if jpi then local p = jpi.p if p and p ~= true then return integer_code, p end end return integer_code, 0 end } implement { name = "MPx", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then local x = jpi.x if x and x ~= true and x ~= 0 then context("%.5Fpt",x*pt) return end end context('0pt') end } implement { name = "jobposx", arguments = "argument", public = true, usage = "value", actions = function(name) local jpi = collected[name] if jpi then local x = jpi.x if x and x ~= true and x ~= 0 then return dimension_code, x*pt end end return dimension_code, 0 end } implement { name = "MPy", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then local y = jpi.y if y and y ~= true and y ~= 0 then context("%.5Fpt",y*pt) return end end context('0pt') end } implement { name = "jobposy", arguments = "argument", public = true, usage = "value", actions = function(name) local jpi = collected[name] if jpi then local y = jpi.y if y and y ~= true and y ~= 0 then return dimension_code, y*pt end end return dimension_code, 0 end } implement { name = "MPw", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then local w = jpi.w if w and w ~= 0 then context("%.5Fpt",w*pt) return end end context('0pt') end } implement { name = "jobposw", arguments = "argument", public = true, usage = "value", actions = function(name) local jpi = collected[name] if jpi then local w = jpi.w if w and w ~= 0 then return dimension_code, w*pt end end return dimension_code, 0 end } implement { name = "MPh", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then local h = jpi.h if h and h ~= 0 then context("%.5Fpt",h*pt) return end end context('0pt') end } implement { name = "jobposh", arguments = "argument", public = true, usage = "value", actions = function(name) local jpi = collected[name] if jpi then local w = jpi.h if h and h ~= 0 then return dimension_code, h*pt end end return dimension_code, 0 end } implement { name = "MPd", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then local d = jpi.d if d and d ~= 0 then context("%.5Fpt",d*pt) return end end context('0pt') end } implement { name = "jobposd", arguments = "argument", public = true, usage = "value", actions = function(name) local jpi = collected[name] if jpi then local d = jpi.d if d and d ~= 0 then return dimension_code, d*pt end end return dimension_code, 0 end } implement { name = "MPxy", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then context('(%.5Fpt,%.5Fpt)', jpi.x*pt, jpi.y*pt ) else context('(0,0)') end end } implement { name = "MPwhd", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then local w = jpi.w or 0 local h = jpi.h or 0 local d = jpi.d or 0 if w ~= 0 or h ~= 0 or d ~= 0 then context("%.5Fpt,%.5Fpt,%.5Fpt",w*pt,h*pt,d*pt) return end end context('0pt,0pt,0pt') end } implement { name = "MPll", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then context('(%.5Fpt,%.5Fpt)', jpi.x *pt, (jpi.y-jpi.d)*pt ) else context('(0,0)') -- for mp only end end } implement { name = "MPlr", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then context('(%.5Fpt,%.5Fpt)', (jpi.x + jpi.w)*pt, (jpi.y - jpi.d)*pt ) else context('(0,0)') -- for mp only end end } implement { name = "MPur", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then context('(%.5Fpt,%.5Fpt)', (jpi.x + jpi.w)*pt, (jpi.y + jpi.h)*pt ) else context('(0,0)') -- for mp only end end } implement { name = "MPul", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then context('(%.5Fpt,%.5Fpt)', jpi.x *pt, (jpi.y + jpi.h)*pt ) else context('(0,0)') -- for mp only end end } local function MPpos(id) local jpi = collected[id] if jpi then local p = jpi.p if p then context("%s,%.5Fpt,%.5Fpt,%.5Fpt,%.5Fpt,%.5Fpt", p, jpi.x*pt, jpi.y*pt, jpi.w*pt, jpi.h*pt, jpi.d*pt ) return end end context('0,0,0,0,0,0') -- for mp only end implement { name = "MPpos", arguments = "argument", public = true, actions = MPpos } implement { name = "MPn", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then local n = jpi.n if n then context(n) return end end context(0) end } implement { name = "MPc", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then local c = jpi.c if c and c ~= true then context(c) return end end context('0') -- okay ? end } implement { name = "MPr", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then local r = jpi.r if r and r ~= true then context(r) return end local p = jpi.p if p and p ~= true then context("page:" .. p) end end end } local function MPpardata(id) local t = collected[id] if not t then local tag = f_p_tag(id) t = collected[tag] end if t then context("%.5Fpt,%.5Fpt,%.5Fpt,%.5Fpt,%s,%.5Fpt", t.hs*pt, t.ls*pt, t.rs*pt, t.hi*pt, t.ha, t.pi*pt ) else context("0,0,0,0,0,0") -- for mp only end end implement { name = "MPpardata", arguments = "argument", public = true, 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 = "MPls", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then context("%.5Fpt",jpi.ls*pt) else context("0pt") end end } implement { name = "MPrs", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then context("%.5Fpt",jpi.rs*pt) else context("0pt") end end } local splitter = lpeg.tsplitat(",") implement { name = "MPplus", -- arguments = { "argument", "integerargument", "argument" }, arguments = { "argument", "integer", "argument" }, public = true, actions = function(name,n,default) local jpi = collected[name] if jpi then local e = jpi.e if e then local split = jpi.split if not split then split = lpegmatch(splitter,jpi.e) jpi.split = split end context(split[n] or default) return end end context(default) end } implement { name = "MPrest", arguments = "2 arguments", public = true, actions = function(name,default) local jpi = collected[name] context(jpi and jpi.e or default) end } implement { name = "MPxywhd", arguments = "argument", public = true, actions = function(name) local jpi = collected[name] if jpi then context("%.5Fpt,%.5Fpt,%.5Fpt,%.5Fpt,%.5Fpt", jpi.x*pt, jpi.y*pt, jpi.w*pt, jpi.h*pt, jpi.d*pt ) else context("0,0,0,0,0") -- for mp only end end } implement { name = "doifelseposition", arguments = "argument", public = true, protected = true, actions = function(name) ctx_doifelse(collected[name]) end } implement { name = "doifposition", arguments = "argument", public = true, protected = true, actions = function(name) ctx_doif(collected[name]) end } implement { name = "doifelsepositiononpage", -- arguments = { "string", "integerargument" }, arguments = { "string", "integer" }, public = true, protected = true, actions = function(name,p) local c = collected[name] ctx_doifelse(c and c.p == p) end } implement { name = "doifelseoverlapping", arguments = "2 arguments", public = true, protected = true, actions = function(one,two) ctx_doifelse(overlapping(one,two)) end } implement { name = "doifelsepositionsonsamepage", arguments = "argument", public = true, protected = true, actions = function(list) ctx_doifelse(onsamepage(list)) end } implement { name = "doifelsepositionsonthispage", arguments = "argument", public = true, protected = true, actions = function(list) ctx_doifelse(onsamepage(list,tostring(texgetcount(c_realpageno)))) end } implement { name = "doifelsepositionsused", public = true, protected = true, actions = function() ctx_doifelse(jobpositions.used()) end } implement { name = "setregionbox", arguments = "2 integers", actions = setregionbox } implement { name = "setregionboxtagged", arguments = { "integer", "string", "integer", "dimension", "posit" }, actions = function(box,tag,index,depth,yscale) setregionbox(box,tag,index,depth,fromposit(yscale)) -- n text index depth yscale end } implement { name = "markregionbox", -- arguments = "2 integers", arguments = "integer", actions = markregionbox } implement { name = "markregionboxtagged", arguments = { "integer", "string", "integer", "dimension", "posit" }, actions = function(box,tag,index,depth,yscale) markregionbox(box,tag,index,depth,fromposit(yscale),false,false) end } implement { name = "markregionboxtaggedn", arguments = { "integer", "string", "integer", "dimension", "posit", "integer" }, actions = function(box,tag,index,depth,yscale,column) markregionbox(box,tag,index,depth,fromposit(yscale),column,false) end } implement { name = "markregionboxcorrected", arguments = { "integer", "string", "integer", "dimension", "posit" }, actions = function(box,tag,index,depth,yscale) markregionbox(box,tag,index,depth,fromposit(yscale),false,true) end } implement { name = "markregionboxtaggedkind", arguments = { "integer", "string", "integer", "dimension", "posit", "integer", "dimension", "dimension", "dimension", "dimension" }, actions = function(box,tag,index,depth,yscale,kind,d1,d2,d3,d4) markregionbox(box,tag,index,depth,fromposit(yscale),false,false,kind,d1,d2,d3,d4) end } implement { name = "reservedautoregiontag", public = true, actions = function() nofregions = nofregions + 1 context(f_region(nofregions)) end } -- We support the low level positional commands too: local newsavepos = nodes.pool.savepos jobpositions.lastx = 0 jobpositions.lasty = 0 jobpositions.lastr = 0 implement { name = "savepos", protected = true, public = true, actions = function() context(newsavepos()) -- or return a node end } implement { name = "lastxpos", usage = "value", protected = true, public = true, actions = function() return dimension_code, jobpositions.lastx end } implement { name = "lastypos", usage = "value", protected = true, public = true, actions = function() return dimension_code, jobpositions.lasty end } implement { name = "lastrpos", usage = "value", protected = true, public = true, actions = function() return integer_code, jobpositions.lastr end }