typo-pag.lua /size: 6989 b    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['typo-pag'] = {
2    version   = 1.001,
3    comment   = "companion to typo-pag.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
10builders                  = builders or { }
11local builders            = builders
12
13builders.paragraphs       = builders.paragraphs or { }
14local parbuilders         = builders.paragraphs
15
16local nodes               = nodes
17local nodecodes           = nodes.nodecodes
18
19local hlist_code          = nodecodes.hlist
20local vlist_code          = nodecodes.vlist
21local glue_code           = nodecodes.glue
22local kern_code           = nodecodes.kern
23local penalty_code        = nodecodes.penalty
24
25local unsetvalue          = attributes.unsetvalue
26local a_keeptogether      = attributes.private("keeptogether")
27
28local nuts                = nodes.nuts
29
30local getnext             = nuts.getnext
31local getprev             = nuts.getprev
32local getid               = nuts.getid
33local getattr             = nuts.getattr
34local takeattr            = nuts.takeattr
35local setattr             = nuts.setattr
36local getwhd              = nuts.getwhd
37local getkern             = nuts.getkern
38local setpenalty          = nuts.setpenalty
39local getwidth            = nuts.getwidth
40local getdepth            = nuts.getdepth
41
42local insertnodeafter     = nuts.insertafter
43local new_penalty         = nuts.pool.penalty
44
45local trace_keeptogether  = false
46local report_keeptogether = logs.reporter("parbuilders","keeptogether")
47
48local enableaction        = nodes.tasks.enableaction
49
50local cache               = { }
51local last                = 0
52local enabled             = false
53
54trackers.register("parbuilders.keeptogether", function(v) trace_keeptogether  = v end)
55
56-- todo: also support lines = 3 etc (e.g. dropped caps) but how to set that
57-- when no hlists are there ?
58
59function parbuilders.registertogether(line,specification) -- might change
60    if not specification then
61        return
62    end
63    if not enabled then
64        enableaction("finalizers","builders.paragraphs.keeptogether")
65    end
66    local a = getattr(line,a_keeptogether)
67    local c = a and cache[a]
68    if c then
69        local height = specification.height
70        local depth  = specification.depth
71        local slack  = specification.slack
72        if height and height > c.height then
73            c.height = height
74        end
75        if depth and depth > c.depth then
76            c.depth = depth
77        end
78        if slack and slack > c.slack then
79            c.slack = slack
80        end
81    else
82        last = last + 1
83        cache[last] = specification
84        if not specification.height then
85            specification.height = 0
86        end
87        if not specification.depth then
88            specification.depth = 0
89        end
90        if not specification.slack then
91            specification.slack = 0
92        end
93        setattr(line,a_keeptogether,last)
94    end
95    if trace_keeptogether then
96        local a = a or last
97        local c = cache[a]
98        local noflines = specification.lineheight
99        local height = c.height
100        local depth = c.depth
101        local slack = c.slack
102        if not noflines or noflines == 0 then
103            noflines = "unknown"
104        else
105            noflines = math.round((height + depth - slack) / noflines)
106        end
107        report_keeptogether("registered, index %s, height %p, depth %p, slack %p, noflines %a",a,height,depth,slack,noflines)
108    end
109end
110
111local function keeptogether(start,a,specification)
112    local current   = getnext(start)
113    local previous  = start
114    local total     = getdepth(previous)
115    local slack     = specification.slack
116    local threshold = specification.depth - slack
117    if trace_keeptogether then
118        report_keeptogether("%s, index %s, total %p, threshold %p, slack %p","list",a,total,threshold,slack)
119    end
120    while current do
121        local id = getid(current)
122        if id == vlist_code or id == hlist_code then
123            local wd, ht, dp = getwhd(current)
124            total = total + ht + dp
125            if trace_keeptogether then
126                report_keeptogether("%s, index %s, total %p, threshold %p","list",a,total,threshold)
127            end
128            if total <= threshold then
129                if getid(previous) == penalty_code then
130                    setpenalty(previous,10000)
131                else
132                    insertnodeafter(head,previous,new_penalty(10000))
133                end
134            else
135                break
136            end
137        elseif id == glue_code then
138            -- hm, breakpoint, maybe turn this into kern
139            total = total + getwidth(current)
140            if trace_keeptogether then
141                report_keeptogether("%s, index %s, total %p, threshold %p","glue",a,total,threshold)
142            end
143            if total <= threshold then
144                if getid(previous) == penalty_code then
145                    setpenalty(previous,10000)
146                else
147                    insertnodeafter(head,previous,new_penalty(10000))
148                end
149            else
150                break
151            end
152        elseif id == kern_code then
153            total = total + getkern(current)
154            if trace_keeptogether then
155                report_keeptogether("%s, index %s, total %s, threshold %s","kern",a,total,threshold)
156            end
157            if total <= threshold then
158                if getid(previous) == penalty_code then
159                    setpenalty(previous,10000)
160                else
161                    insertnodeafter(head,previous,new_penalty(10000))
162                end
163            else
164                break
165            end
166        elseif id == penalty_code then
167            if total <= threshold then
168                if getid(previous) == penalty_code then
169                    setpenalty(previous,10000)
170                end
171                setpenalty(current,10000)
172            else
173                break
174            end
175        end
176        previous = current
177        current = getnext(current)
178    end
179end
180
181-- also look at first non glue/kern node e.g for a dropped caps
182
183function parbuilders.keeptogether(head)
184    local done    = false -- can go
185    local current = head
186    while current do
187        if getid(current) == hlist_code then
188            local a = takeattr(current,a_keeptogether)
189            if a and a > 0 then
190                local specification = cache[a]
191                if specification then
192                    keeptogether(current,a,specification)
193                    -- this is tricky ... we need a better resetter, maybe some
194                    -- injected latelua or a gc method on a property (interesting
195                    -- experiment)
196                    cache[a] = nil
197                    done = true
198                end
199            end
200        end
201        current = getnext(current)
202    end
203    return head, done
204end
205