typo-lin.lua /size: 17 Kb    last modification: 2021-10-28 13:50
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 gluecodes         = nodes.gluecodes
62local listcodes         = nodes.listcodes
63
64local hlist_code        = nodecodes.hlist
65local glue_code         = nodecodes.glue
66local kern_code         = nodecodes.kern
67local linelist_code     = listcodes.line
68----- par_code          = nodecodes.par
69local leftskip_code     = gluecodes.leftskip
70local rightskip_code    = gluecodes.rightskip
71local parfillskip_code  = gluecodes.parfillskip
72
73local tonut             = nodes.tonut
74local tonode            = nodes.tonode
75
76local nexthlist         = nuts.traversers.hlist
77local insertbefore      = nuts.insertbefore
78local insertafter       = nuts.insertafter
79local find_tail         = nuts.tail
80local rehpack           = nuts.rehpack
81----- remove_node       = nuts.remove
82
83local getsubtype        = nuts.getsubtype
84local getlist           = nuts.getlist
85local setlist           = nuts.setlist
86local getid             = nuts.getid
87local getnext           = nuts.getnext
88local getprev           = nuts.getprev
89local getboth           = nuts.getboth
90local setlink           = nuts.setlink
91local setkern           = nuts.setkern
92local getkern           = nuts.getkern
93local getdirection      = nuts.getdirection
94local getshift          = nuts.getshift
95local setshift          = nuts.setshift
96local getwidth          = nuts.getwidth
97local setwidth          = nuts.setwidth
98
99local setprop           = nuts.setprop
100local getprop           = nuts.rawprop -- getprop
101
102local effectiveglue     = nuts.effectiveglue
103
104local nodepool          = nuts.pool
105local new_kern          = nodepool.kern
106local new_leftskip      = nodepool.leftskip
107local new_rightskip     = nodepool.rightskip
108local new_hlist         = nodepool.hlist
109local new_rule          = nodepool.rule
110local new_glue          = nodepool.glue
111
112local righttoleft_code  = nodes.dirvalues.righttoleft
113
114local texgetcount       = tex.getcount
115local texgetglue        = tex.getglue
116local setmetatableindex = table.setmetatableindex
117local formatters        = string.formatters
118
119local jobpositions      = job.positions
120local getposition       = jobpositions.get
121local getreserved       = jobpositions.getreserved
122
123local paragraphs        = { }
124typesetters.paragraphs  = paragraphs
125
126local addskips          = false -- todo: use engine normalizer
127local noflines          = 0
128
129-- This is the third version, a mix between immediate (prestine lines) and delayed
130-- as we don't want anchors that are not used.
131
132-- I will make a better variant once lmtx is stable i.e. less clutter.
133
134local function finalize(prop,key) -- delayed calculations
135    local line     = prop.line
136    local hsize    = prop.hsize
137    local width    = prop.width
138    local shift    = getshift(line) -- dangerous as it can be vertical as well
139    local reverse  = getdirection(line) == righttoleft_code or false
140    local pack     = new_hlist()
141    local head     = getlist(line)
142    local delta    = 0
143    if reverse then
144        delta = - shift + (hsize - width)
145    else
146        delta =   shift
147    end
148    local kern1 = new_kern(delta)
149    local kern2 = new_kern(-delta)
150    head = insertbefore(head,head,kern1)
151    head = insertbefore(head,head,pack)
152    head = insertbefore(head,head,kern2)
153    setlist(line,head)
154    local where = {
155        pack = pack,
156        head = nil,
157        tail = nil,
158    }
159    prop.where   = where
160    prop.reverse = reverse
161    prop.shift   = shift
162    setmetatableindex(prop,nil)
163    return prop[key]
164end
165
166local function normalize(line,islocal) -- assumes prestine lines, nothing pre/appended
167    local oldhead   = getlist(line)
168    local head      = oldhead
169    local leftskip  = nil
170    local rightskip = nil
171    local width     = getwidth(line)
172    local hsize     = islocal and width or tex.hsize
173    local lskip     = 0
174    local rskip     = 0
175    local pskip     = 0
176    local current   = head
177    local id        = getid(current)
178    if id == glue_code then
179        local subtype = getsubtype(head)
180        if subtype == leftskip_code then
181            leftskip = head
182            lskip    = getwidth(head) or 0
183        end
184        current = getnext(head)
185        id      = getid(current)
186    end
187    -- no:
188 -- if id == par_code then
189 --     head = remove_node(head,head,true)
190 -- end
191    local tail    = find_tail(head)
192    local current = tail
193    local id      = getid(current)
194    if id == glue_code then
195        if getsubtype(current) == rightskip_code then
196            rightskip  = tail
197            rskip      = getwidth(current) or 0
198            current    = getprev(tail)
199            id         = getid(current)
200        end
201        if id == glue_code then
202            if getsubtype(current) == parfillskip_code then
203                pskip = effectiveglue(current,line)
204            end
205        end
206    end
207    if addskips then
208        if rightskip and not leftskip then
209            leftskip = new_leftskip(lskip)
210            head     = insertbefore(head,head,leftskip)
211        end
212        if leftskip and not rightskip then
213            rightskip = new_rightskip(0)
214            head, tail = insertafter(head,tail,rightskip)
215        end
216    end
217    if head ~= oldhead then
218        setlist(line,head)
219    end
220    noflines = noflines + 1
221    local prop = {
222        width       = width,
223        hsize       = hsize,
224        leftskip    = lskip,
225        rightskip   = rskip,
226        parfillskip = pskip,
227        line        = line,
228        number      = noflines,
229    }
230    setprop(line,"line",prop)
231    setmetatableindex(prop,finalize)
232    return prop
233end
234
235function paragraphs.checkline(n)
236    return getprop(n,"line") or normalize(n,true)
237end
238
239-- do we still need this:
240
241function paragraphs.normalize(head,islocal)
242    if texgetcount("pagebodymode") > 0 then
243        -- can be an option, maybe we need a proper state in lua itself ... is this check still needed?
244        return head, false
245    end
246    -- this can become a separate handler but it makes sense to integrate it here
247    local mode = texgetcount("parfillleftmode")
248    if mode > 0 then
249        local l_width, l_stretch, l_shrink = texgetglue("parfillleftskip")
250        if l_width ~= 0 or l_stretch ~= 0 or l_shrink ~= 0 then
251            local last = nil -- a nut
252            local done = mode == 2 -- false
253            for line, subtype in nexthlist, head do
254                if subtype == linelist_code and not getprop(line,"line") then
255                    if done then
256                        last = line
257                    else
258                        done = true
259                    end
260                end
261            end
262            if last then -- only if we have more than one line
263                local head    = getlist(last)
264                local current = head
265                if current then
266                    if getid(current) == glue_code and getsubtype(current,leftskip_code) then
267                        current = getnext(current)
268                    end
269                    if current then
270                        head, current = insertbefore(head,current,new_glue(l_width,l_stretch,l_shrink))
271                        if head == current then
272                            setlist(last,head)
273                        end
274                        -- can be a 'rehpack(h  )'
275                        rehpack(last)
276                    end
277                end
278            end
279        end
280    end
281    -- normalizer
282    for line, subtype in nexthlist, head do
283        if subtype == linelist_code and not getprop(line,"line") then
284            normalize(line)
285        end
286    end
287    return head, true -- true is obsolete
288end
289
290-- print(nodes.idstostring(head))
291
292-- We do only basic positioning and leave compensation for directions and distances
293-- to the caller as that one knows the circumstances better.
294
295-- todo: only in mvl or explicitly, e.g. framed or so, not in all lines
296
297local function addtoline(n,list,option)
298    local line = getprop(n,"line")
299    if not line then
300        line = normalize(n,true)
301    end
302    if line then
303        if trace_anchors and not line.traced then
304            line.traced = true
305            local rule = new_rule(2*65536,2*65536,1*65536)
306            local list = insertbefore(rule,rule,new_kern(-1*65536))
307            addtoline(n,list)
308            local rule = new_rule(2*65536,6*65536,-3*65536)
309            local list = insertbefore(rule,rule,new_kern(-1*65536))
310            addtoline(n,list,"internal")
311        else
312            line.traced = true
313        end
314        local list  = tonut(list)
315        local where = line.where
316        local head  = where.head
317        local tail  = where.tail
318        local blob  = new_hlist(list)
319        local delta = 0
320        if option == "internal" then
321            if line.reverse then
322                delta = line.shift - line.leftskip - (line.hsize - line.width)
323            else
324                delta = line.shift + line.leftskip
325            end
326        end
327        -- always kerns, also when 0 so that we can adapt but we can optimize if needed
328        -- by keeping a hash as long as we use the shiftinline helper .. no need to
329        -- optimize now .. we can also decide to put each blob in a hlist
330        local kern = new_kern(delta)
331        if tail then
332            head, tail = insertafter(head,tail,kern)
333        else
334            head, tail = kern, kern
335            setlist(where.pack,head)
336        end
337        head, tail = insertafter(head,tail,blob)
338        local kern = new_kern(-delta)
339        head, tail = insertafter(head,tail,kern)
340        --
341        where.head = head
342        where.tail = tail
343        return line, blob
344    else
345     -- report("unknown anchor")
346    end
347end
348
349local function addanchortoline(n,anchor)
350    local line = type(n) ~= "table" and getprop(n,"line") or n
351    if not line then
352        line = normalize(n,true)
353    end
354    if line then
355        local anchor = tonut(anchor)
356        local where  = line.where
357        if trace_anchors then
358            anchor = new_hlist(setlink(
359                anchor,
360                new_kern(-65536/4),
361                new_rule(65536/2,4*65536,4*65536),
362                new_kern(-65536/4-4*65536),
363                new_rule(8*65536,65536/4,65536/4)
364            ))
365            setwidth(anchor,0)
366        end
367        if where.tail then
368            local head = where.head
369            insertbefore(head,head,anchor)
370        else
371            where.tail = anchor
372        end
373        setlist(where.pack,anchor)
374        where.head = anchor
375        return line
376    end
377end
378
379paragraphs.addtoline       = addtoline
380paragraphs.addanchortoline = addanchortoline
381
382function paragraphs.moveinline(n,blob,dx,dy)
383    if not blob then
384        return
385    end
386    if not dx then
387        dx = 0
388    end
389    if not dy then
390        dy = 0
391    end
392    if dx ~= 0 or dy ~= 0 then
393        local line = type(n) ~= "table" and getprop(n,"line") or n
394        if line then
395            if dx ~= 0 then
396                local prev, next = getboth(blob)
397                if prev and getid(prev) == kern_code then
398                    setkern(prev,getkern(prev) + dx)
399                end
400                if next and getid(next) == kern_code then
401                    setkern(next,getkern(next) - dx)
402                end
403            end
404            if dy ~= 0 then
405                if getid(blob) == hlist_code then
406                    setshift(blob,getshift(blob) + dy)
407                end
408            end
409        else
410--             report("no line")
411        end
412    end
413end
414
415local latelua     = nodepool.latelua
416local setposition = jobpositions.setspec
417
418local function setanchor(h_anchor)
419    return latelua {
420        action = setposition,
421        name   = "md:h",
422        index  = h_anchor,
423        value  = { x = true, c = true },
424    }
425end
426
427function paragraphs.calculatedelta(n,width,delta,atleft,islocal,followshape,area)
428    local line = type(n) ~= "table" and getprop(n,"line") or n
429    if not line then
430        line = normalize(n,true)
431    end
432    local hmove = 0
433    if line then
434        local reverse = line.reverse
435        -- basic hsize based anchoring
436        if atleft then
437            if reverse then
438             -- delta =   delta
439            else
440                delta = - delta - width
441            end
442        else
443            if reverse then
444                delta = - delta - width - line.hsize
445            else
446                delta =   delta         + line.hsize
447            end
448        end
449        if islocal then
450            -- relative to hsize with leftskip / rightskip compensation
451            if atleft then
452                if reverse then
453                    delta = delta - line.leftskip
454                else
455                    delta = delta + line.leftskip
456                end
457            else
458                if reverse then
459                    delta = delta + line.rightskip
460                else
461                    delta = delta - line.rightskip
462                end
463            end
464            if followshape then
465                -- shape compensation
466                if atleft then
467                    if reverse then
468                        delta = delta + line.shift - line.hsize + line.width
469                    else
470                        delta = delta + line.shift
471                    end
472                else
473                    if reverse then
474                        delta = delta + line.shift                           + line.parfillskip
475                    else
476                        delta = delta + line.shift - line.hsize + line.width - line.parfillskip
477                    end
478                end
479            end
480        end
481        if area then
482            local number = line.number
483            if not line.hanchor then
484                addanchortoline(line,setanchor(number))
485                line.hanchor = true
486            end
487            local blob = getposition("md:h",number)
488            if blob then
489                local reference = getreserved(area,blob.c)
490                if reference then
491                    hmove = (reference.x or 0) - (blob.x or 0)
492                    if atleft then
493                        if reverse then
494                            hmove = hmove + (reference.w or 0)
495                        else
496                         -- hmove = hmove
497                        end
498                    else
499                        if reverse then
500                            hmove = hmove                      + line.hsize
501                        else
502                            hmove = hmove + (reference.w or 0) - line.hsize
503                        end
504                    end
505                end
506            end
507        end
508    end
509    return delta, hmove
510end
511