typo-lin.lmt /size: 13 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['typo-lin'] = {
2    version   = 1.001,
3    comment   = "companion to typo-lin.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- This is experimental code. The idea is to create an anchor point in a line but there are
10-- some considerations:
11--
12-- * if we normalize in order to have more easy access later on, we need to normalize all
13--   lines and we cannot catch all without losing efficiency
14--
15-- * if we normalize too soon, we might have issues with changed properties later on
16--
17-- * if we normalize too late, we have no knowledge of the current hsize
18--
19-- * if we always create an anchor we also create unwanted overhead if it is not used later
20--   on (more nodes)
21--
22-- The question is: do we mind of at the first access an anchor is created? As we cannot know
23-- that now, I choose some middle ground but this might change. if we don't assume direct
24-- access but only helpers, we can decide later.
25--
26-- Because of right2left mess it makes sense to use helpers so that we only need to deal with
27-- this mess once, in helpers. The more abstraction there the better. And so, after a week of
28-- experimenting, yet another abstraction was introduced.
29--
30-- The danger of adding the anchor later is that we adapt the head and so the caller needs to
31-- check that ... real messy. On the other hand, we soldom traverse the line. And other
32-- mechanisms can push stuff in front too. Actually that alone can mess up analysis when we
33-- delay too much. So in the end we need to accept the slow down.
34--
35-- We only need to normalize the left side because when we mess around we keep the page stream
36-- order (and adding content to the right of the line is a no-go for tagged etc. For the same
37-- reason we don't use two left anchors (each side fo leftskip) because there can be stretch.
38-- But, maybe there are good reasons for having just that anchor (mostly for educational purposes
39-- I guess.)
40--
41-- At this stage the par node is no longer of any use so we remove it (each line has the
42-- direction attached). We might at some point also strip the disc nodes as they no longer serve
43-- a purpose but that can better be a helper. Anchoring left has advantage of keeping page
44-- stream.
45--
46-- This looks a bit messy but we want to keep the box as it is so \showboxes still visualizes as
47-- expected. Normally left and rightskips end up in the line while hangindents become shifts and
48-- hsize corrections. We could normalize this to a line with
49
50-- indent     : hlist type 3
51-- hangindent : shift and width
52
53local type = type
54
55local trace_anchors  = false  trackers.register("paragraphs.anchors", function(v) trace_anchors = v end)
56
57local report = logs.reporter("anchors")
58
59local nuts              = nodes.nuts
60local nodecodes         = nodes.nodecodes
61local listcodes         = nodes.listcodes
62
63local hlist_code        = nodecodes.hlist
64local kern_code         = nodecodes.kern
65local linelist_code     = listcodes.line
66
67local tonut             = nodes.tonut
68local tonode            = nodes.tonode
69
70local nexthlist         = nuts.traversers.hlist
71local insertbefore      = nuts.insertbefore
72local insertafter       = nuts.insertafter
73
74local getlist           = nuts.getlist
75local setlist           = nuts.setlist
76local getid             = nuts.getid
77local getboth           = nuts.getboth
78local setlink           = nuts.setlink
79local setkern           = nuts.setkern
80local getkern           = nuts.getkern
81local getdirection      = nuts.getdirection
82local getshift          = nuts.getshift
83local setshift          = nuts.setshift
84local getwidth          = nuts.getwidth
85local setwidth          = nuts.setwidth
86
87local getnormalizedline = nuts.getnormalizedline
88
89local setprop           = nuts.setprop
90local getprop           = nuts.rawprop -- getprop
91
92local effectiveglue     = nuts.effectiveglue
93
94local nodepool          = nuts.pool
95local new_kern          = nodepool.kern
96local new_hlist         = nodepool.hlist
97local new_rule          = nodepool.rule
98local new_glue          = nodepool.glue
99
100local righttoleft_code  = tex.directioncodes.righttoleft
101
102local setmetatableindex = table.setmetatableindex
103
104local jobpositions      = job.positions
105local getposition       = jobpositions.get
106local getreserved       = jobpositions.getreserved
107
108local paragraphs        = { }
109typesetters.paragraphs  = paragraphs
110
111local noflines          = 0
112
113local function finalize(prop,key) -- delayed calculations
114    local line     = prop.line
115    local hsize    = prop.hsize
116    local width    = prop.width
117    local shift    = getshift(line) -- dangerous as it can be vertical as well
118    local reverse  = getdirection(line) == righttoleft_code or false
119    local pack     = new_hlist()
120    local head     = getlist(line)
121    local delta    = 0
122    if reverse then
123        delta = - shift + (hsize - width)
124    else
125        delta =   shift
126    end
127    local kern1 = new_kern(delta)
128    local kern2 = new_kern(-delta)
129    head = insertbefore(head,head,kern1) -- setlink
130    head = insertbefore(head,head,pack)
131    head = insertbefore(head,head,kern2)
132    setlist(line,head)
133    local where = {
134        pack = pack,
135        head = nil,
136        tail = nil,
137    }
138    prop.where   = where
139    prop.reverse = reverse
140 -- prop.shift   = shift
141    setmetatableindex(prop,nil)
142    return prop[key]
143end
144
145local function normalize(line,islocal) -- assumes prestine lines, nothing pre/appended
146    local prop  = getnormalizedline(line) -- we also can have "lineproperties" set but for a different purpose
147    local width = getwidth(line)
148    local hsize = islocal and width or tex.hsize
149    noflines    = noflines + 1
150    if prop then
151        prop.width  = width
152        prop.hsize  = hsize
153        prop.line   = line
154        prop.number = noflines
155    else
156        -- can be a vbox ... we could use a metatable with zeros
157        prop = {
158            width            = width,
159            hsize            = hsize,
160            leftskip         = 0,
161            rightskip        = 0,
162            lefthangskip     = 0,
163            righthangskip    = 0,
164            parfillleftskip  = 0,
165            parfillrightskip = 0,
166            line             = line,
167            number           = noflines,
168        }
169    end
170    setprop(line,"line",prop)
171    setmetatableindex(prop,finalize)
172    return prop
173end
174
175function paragraphs.checkline(n)
176    return getprop(n,"line") or normalize(n,true)
177end
178
179-- print(nodes.idstostring(head))
180
181-- We do only basic positioning and leave compensation for directions and distances
182-- to the caller as that one knows the circumstances better.
183
184-- todo: only in mvl or explicitly, e.g. framed or so, not in all lines
185
186local function addtoline(n,list,option)
187    local line = getprop(n,"line")
188    if not line then
189        line = normalize(n,true)
190    end
191    if line then
192        if trace_anchors and not line.traced then
193            line.traced = true
194            local rule = new_rule(2*65536,2*65536,1*65536)
195            local list = insertbefore(rule,rule,new_kern(-1*65536))
196            addtoline(n,list)
197            local rule = new_rule(2*65536,6*65536,-3*65536)
198            local list = insertbefore(rule,rule,new_kern(-1*65536))
199            addtoline(n,list,"internal")
200        else
201            line.traced = true
202        end
203        local list  = tonut(list)
204        local where = line.where
205        local head  = where.head
206        local tail  = where.tail
207        local blob  = new_hlist(list)
208        local delta = 0
209        if option == "internal" then
210            if line.reverse then
211                delta = line.lefthangskip - line.leftskip - (line.hsize - line.width)
212            else
213                delta = line.lefthangskip + line.leftskip
214            end
215        end
216
217        -- this is a quick hack for line numbering in aligned math but maybe we need
218        -- a signal in the properties: if getprops(n,"repositioned")
219        local xoffset = nuts.getoffsets(n)
220        delta = delta - xoffset
221
222        -- always kerns, also when 0 so that we can adapt but we can optimize if needed
223        -- by keeping a hash as long as we use the shiftinline helper .. no need to
224        -- optimize now .. we can also decide to put each blob in a hlist
225        local kern = new_kern(delta)
226        if tail then
227            head, tail = insertafter(head,tail,kern)
228        else
229            head, tail = kern, kern
230            setlist(where.pack,head)
231        end
232        head, tail = insertafter(head,tail,blob)
233        local kern = new_kern(-delta)
234        head, tail = insertafter(head,tail,kern)
235        --
236        where.head = head
237        where.tail = tail
238        return line, blob
239    else
240     -- report("unknown anchor")
241    end
242end
243
244local function addanchortoline(n,anchor)
245    local line = type(n) ~= "table" and getprop(n,"line") or n
246    if not line then
247        line = normalize(n,true)
248    end
249    if line then
250        local anchor = tonut(anchor)
251        local where  = line.where
252        if trace_anchors then
253            anchor = new_hlist(setlink(
254                anchor,
255                new_kern(-65536/4),
256                new_rule(65536/2,4*65536,4*65536),
257                new_kern(-65536/4-4*65536),
258                new_rule(8*65536,65536/4,65536/4)
259            ))
260            setwidth(anchor,0)
261        end
262        if where.tail then
263            local head = where.head
264            insertbefore(head,head,anchor)
265        else
266            where.tail = anchor
267        end
268        setlist(where.pack,anchor)
269        where.head = anchor
270        return line
271    end
272end
273
274paragraphs.addtoline       = addtoline
275paragraphs.addanchortoline = addanchortoline
276
277function paragraphs.moveinline(n,blob,dx,dy)
278    if not blob then
279        return
280    end
281    if not dx then
282        dx = 0
283    end
284    if not dy then
285        dy = 0
286    end
287    if dx ~= 0 or dy ~= 0 then
288        local line = type(n) ~= "table" and getprop(n,"line") or n
289        if line then
290            if dx ~= 0 then
291                local prev, next = getboth(blob)
292                if prev and getid(prev) == kern_code then
293                    setkern(prev,getkern(prev) + dx)
294                end
295                if next and getid(next) == kern_code then
296                    setkern(next,getkern(next) - dx)
297                end
298            end
299            if dy ~= 0 then
300                if getid(blob) == hlist_code then
301                    setshift(blob,getshift(blob) + dy)
302                end
303            end
304        else
305         -- report("no line")
306        end
307    end
308end
309
310local latelua     = nodepool.latelua
311local setposition = jobpositions.setspec
312
313local function setanchor(h_anchor)
314    return latelua {
315        action = setposition,
316        name   = "md:h",
317        index  = h_anchor,
318        value  = { x = true, c = true },
319    }
320end
321
322-- needs to be adapted !
323
324function paragraphs.calculatedelta(n,width,delta,atleft,islocal,followshape,area)
325    local line = type(n) ~= "table" and getprop(n,"line") or n
326    if not line then
327        line = normalize(n,true)
328    end
329    local hmove = 0
330    if line then
331        local reverse = line.reverse
332        -- basic hsize based anchoring
333        if atleft then
334            if reverse then
335             -- delta =   delta
336            else
337                delta = - delta - width
338            end
339        else
340            if reverse then
341                delta = - delta - width - line.hsize
342            else
343                delta =   delta         + line.hsize
344            end
345        end
346        if islocal then
347            -- relative to hsize with leftskip / rightskip compensation
348            if atleft then
349                if reverse then
350                    delta = delta - line.leftskip
351                else
352                    delta = delta + line.leftskip
353                end
354            else
355                if reverse then
356                    delta = delta + line.rightskip
357                else
358                    delta = delta - line.rightskip
359                end
360            end
361            if followshape then
362                -- shape compensation (compare to mkiv where we have shift!)
363                if atleft then
364                    if reverse then
365                        delta = delta + line.lefthangskip - line.hsize + line.width -- needs testing: + line.parfillleftskip
366                    else
367                        delta = delta + line.lefthangskip                           -- needs testing: - line.parfillleftskip
368                    end
369                else
370                    if reverse then
371                        delta = delta + line.lefthangskip                           + line.parfillrightskip
372                    else
373                        delta = delta + line.lefthangskip - line.hsize + line.width - line.parfillrightskip
374                    end
375                end
376            end
377        end
378        if area then
379            local number = line.number
380            if not line.hanchor then
381                addanchortoline(line,setanchor(number))
382                line.hanchor = true
383            end
384            local blob = getposition("md:h",number)
385            if blob then
386                local reference = getreserved(area,blob.c)
387                if reference then
388                    hmove = (reference.x or 0) - (blob.x or 0)
389                    if atleft then
390                        if reverse then
391                            hmove = hmove + (reference.w or 0)
392                        else
393                         -- hmove = hmove
394                        end
395                    else
396                        if reverse then
397                            hmove = hmove                      + line.hsize
398                        else
399                            hmove = hmove + (reference.w or 0) - line.hsize
400                        end
401                    end
402                end
403            end
404        end
405    end
406    return delta, hmove
407end
408