if not modules then modules = { } end modules ['typo-lin'] = { version = 1.001, comment = "companion to typo-lin.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- This is experimental code. The idea is to create an anchor point in a line but there are -- some considerations: -- -- * if we normalize in order to have more easy access later on, we need to normalize all -- lines and we cannot catch all without losing efficiency -- -- * if we normalize too soon, we might have issues with changed properties later on -- -- * if we normalize too late, we have no knowledge of the current hsize -- -- * if we always create an anchor we also create unwanted overhead if it is not used later -- on (more nodes) -- -- The question is: do we mind of at the first access an anchor is created? As we cannot know -- that now, I choose some middle ground but this might change. if we don't assume direct -- access but only helpers, we can decide later. -- -- Because of right2left mess it makes sense to use helpers so that we only need to deal with -- this mess once, in helpers. The more abstraction there the better. And so, after a week of -- experimenting, yet another abstraction was introduced. -- -- The danger of adding the anchor later is that we adapt the head and so the caller needs to -- check that ... real messy. On the other hand, we soldom traverse the line. And other -- mechanisms can push stuff in front too. Actually that alone can mess up analysis when we -- delay too much. So in the end we need to accept the slow down. -- -- We only need to normalize the left side because when we mess around we keep the page stream -- order (and adding content to the right of the line is a no-go for tagged etc. For the same -- reason we don't use two left anchors (each side fo leftskip) because there can be stretch. -- But, maybe there are good reasons for having just that anchor (mostly for educational purposes -- I guess.) -- -- At this stage the par node is no longer of any use so we remove it (each line has the -- direction attached). We might at some point also strip the disc nodes as they no longer serve -- a purpose but that can better be a helper. Anchoring left has advantage of keeping page -- stream. -- -- This looks a bit messy but we want to keep the box as it is so \showboxes still visualizes as -- expected. Normally left and rightskips end up in the line while hangindents become shifts and -- hsize corrections. We could normalize this to a line with -- indent : hlist type 3 -- hangindent : shift and width local type = type local trace_anchors = false trackers.register("paragraphs.anchors", function(v) trace_anchors = v end) local report = logs.reporter("anchors") local nuts = nodes.nuts local nodecodes = nodes.nodecodes local listcodes = nodes.listcodes local hlist_code = nodecodes.hlist local kern_code = nodecodes.kern local linelist_code = listcodes.line local tonut = nodes.tonut local tonode = nodes.tonode local nexthlist = nuts.traversers.hlist local insertbefore = nuts.insertbefore local insertafter = nuts.insertafter local getlist = nuts.getlist local setlist = nuts.setlist local getid = nuts.getid local getboth = nuts.getboth local setlink = nuts.setlink local setkern = nuts.setkern local getkern = nuts.getkern local getdirection = nuts.getdirection local getshift = nuts.getshift local setshift = nuts.setshift local getwidth = nuts.getwidth local setwidth = nuts.setwidth local getnormalizedline = nuts.getnormalizedline local setprop = nuts.setprop local getprop = nuts.rawprop -- getprop local effectiveglue = nuts.effectiveglue local nodepool = nuts.pool local new_kern = nodepool.kern local new_hlist = nodepool.hlist local new_rule = nodepool.rule local new_glue = nodepool.glue local righttoleft_code = tex.directioncodes.righttoleft local setmetatableindex = table.setmetatableindex local jobpositions = job.positions local getposition = jobpositions.get local getreserved = jobpositions.getreserved local paragraphs = { } typesetters.paragraphs = paragraphs local noflines = 0 local function finalize(prop,key) -- delayed calculations local line = prop.line local hsize = prop.hsize local width = prop.width local shift = getshift(line) -- dangerous as it can be vertical as well local reverse = getdirection(line) == righttoleft_code or false local pack = new_hlist() local head = getlist(line) local delta = 0 if reverse then delta = - shift + (hsize - width) else delta = shift end local kern1 = new_kern(delta) local kern2 = new_kern(-delta) head = insertbefore(head,head,kern1) -- setlink head = insertbefore(head,head,pack) head = insertbefore(head,head,kern2) setlist(line,head) local where = { pack = pack, head = nil, tail = nil, } prop.where = where prop.reverse = reverse -- prop.shift = shift setmetatableindex(prop,nil) return prop[key] end local function normalize(line,islocal) -- assumes prestine lines, nothing pre/appended local prop = getnormalizedline(line) -- we also can have "lineproperties" set but for a different purpose local width = getwidth(line) local hsize = islocal and width or tex.hsize noflines = noflines + 1 if prop then prop.width = width prop.hsize = hsize prop.line = line prop.number = noflines else -- can be a vbox ... we could use a metatable with zeros prop = { width = width, hsize = hsize, leftskip = 0, rightskip = 0, lefthangskip = 0, righthangskip = 0, parfillleftskip = 0, parfillrightskip = 0, line = line, number = noflines, } end setprop(line,"line",prop) setmetatableindex(prop,finalize) return prop end function paragraphs.checkline(n) return getprop(n,"line") or normalize(n,true) end -- print(nodes.idstostring(head)) -- We do only basic positioning and leave compensation for directions and distances -- to the caller as that one knows the circumstances better. -- todo: only in mvl or explicitly, e.g. framed or so, not in all lines local function addtoline(n,list,option) local line = getprop(n,"line") if not line then line = normalize(n,true) end if line then if trace_anchors and not line.traced then line.traced = true local rule = new_rule(2*65536,2*65536,1*65536) local list = insertbefore(rule,rule,new_kern(-1*65536)) addtoline(n,list) local rule = new_rule(2*65536,6*65536,-3*65536) local list = insertbefore(rule,rule,new_kern(-1*65536)) addtoline(n,list,"internal") else line.traced = true end local list = tonut(list) local where = line.where local head = where.head local tail = where.tail local blob = new_hlist(list) local delta = 0 if option == "internal" then if line.reverse then delta = line.lefthangskip - line.leftskip - (line.hsize - line.width) else delta = line.lefthangskip + line.leftskip end end -- this is a quick hack for line numbering in aligned math but maybe we need -- a signal in the properties: if getprops(n,"repositioned") local xoffset = nuts.getoffsets(n) delta = delta - xoffset -- always kerns, also when 0 so that we can adapt but we can optimize if needed -- by keeping a hash as long as we use the shiftinline helper .. no need to -- optimize now .. we can also decide to put each blob in a hlist local kern = new_kern(delta) if tail then head, tail = insertafter(head,tail,kern) else head, tail = kern, kern setlist(where.pack,head) end head, tail = insertafter(head,tail,blob) local kern = new_kern(-delta) head, tail = insertafter(head,tail,kern) -- where.head = head where.tail = tail return line, blob else -- report("unknown anchor") end end local function addanchortoline(n,anchor) local line = type(n) ~= "table" and getprop(n,"line") or n if not line then line = normalize(n,true) end if line then local anchor = tonut(anchor) local where = line.where if trace_anchors then anchor = new_hlist(setlink( anchor, new_kern(-65536/4), new_rule(65536/2,4*65536,4*65536), new_kern(-65536/4-4*65536), new_rule(8*65536,65536/4,65536/4) )) setwidth(anchor,0) end if where.tail then local head = where.head insertbefore(head,head,anchor) else where.tail = anchor end setlist(where.pack,anchor) where.head = anchor return line end end paragraphs.addtoline = addtoline paragraphs.addanchortoline = addanchortoline function paragraphs.moveinline(n,blob,dx,dy) if not blob then return end if not dx then dx = 0 end if not dy then dy = 0 end if dx ~= 0 or dy ~= 0 then local line = type(n) ~= "table" and getprop(n,"line") or n if line then if dx ~= 0 then local prev, next = getboth(blob) if prev and getid(prev) == kern_code then setkern(prev,getkern(prev) + dx) end if next and getid(next) == kern_code then setkern(next,getkern(next) - dx) end end if dy ~= 0 then if getid(blob) == hlist_code then setshift(blob,getshift(blob) + dy) end end else -- report("no line") end end end local latelua = nodepool.latelua local setposition = jobpositions.setspec local function setanchor(h_anchor) return latelua { action = setposition, name = "md:h", index = h_anchor, value = { x = true, c = true }, } end -- needs to be adapted ! function paragraphs.calculatedelta(n,width,delta,atleft,islocal,followshape,area) local line = type(n) ~= "table" and getprop(n,"line") or n if not line then line = normalize(n,true) end local hmove = 0 if line then local reverse = line.reverse -- basic hsize based anchoring if atleft then if reverse then -- delta = delta else delta = - delta - width end else if reverse then delta = - delta - width - line.hsize else delta = delta + line.hsize end end if islocal then -- relative to hsize with leftskip / rightskip compensation if atleft then if reverse then delta = delta - line.leftskip else delta = delta + line.leftskip end else if reverse then delta = delta + line.rightskip else delta = delta - line.rightskip end end if followshape then -- shape compensation (compare to mkiv where we have shift!) if atleft then if reverse then delta = delta + line.lefthangskip - line.hsize + line.width -- needs testing: + line.parfillleftskip else delta = delta + line.lefthangskip -- needs testing: - line.parfillleftskip end else if reverse then delta = delta + line.lefthangskip + line.parfillrightskip else delta = delta + line.lefthangskip - line.hsize + line.width - line.parfillrightskip end end end end if area then local number = line.number if not line.hanchor then addanchortoline(line,setanchor(number)) line.hanchor = true end local blob = getposition("md:h",number) if blob then local reference = getreserved(area,blob.c) if reference then hmove = (reference.x or 0) - (blob.x or 0) if atleft then if reverse then hmove = hmove + (reference.w or 0) else -- hmove = hmove end else if reverse then hmove = hmove + line.hsize else hmove = hmove + (reference.w or 0) - line.hsize end end end end end end return delta, hmove end