node-bck.lmt /size: 9552 b    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['node-bck'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to node-bck.mkiv",
5    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6    copyright = "PRAGMA ADE / ConTeXt Development Team",
7    license   = "see context related readme files"
8}
9
10-- beware, this one takes quite some runtime, so we need a status flag
11-- maybe some page related state
12
13-- todo: done (or just get rid of done altogether) ... saves no purpose
14-- any longer
15
16local attributes, nodes, node = attributes, nodes, node
17
18local enableaction       = nodes.tasks.enableaction
19
20local nodecodes          = nodes.nodecodes
21local listcodes          = nodes.listcodes
22
23local hlist_code         = nodecodes.hlist
24local vlist_code         = nodecodes.vlist
25
26local alignmentlist_code = listcodes.alignment
27local celllist_code      = listcodes.cell
28
29local nuts               = nodes.nuts
30local nodepool           = nuts.pool
31
32local getnext            = nuts.getnext
33local getprev            = nuts.getprev
34local getid              = nuts.getid
35local getlist            = nuts.getlist
36local getattr            = nuts.getattr
37local getsubtype         = nuts.getsubtype
38local getwhd             = nuts.getwhd
39local getwidth           = nuts.getwidth
40local getprop            = nuts.getprop
41
42local setattr            = nuts.setattr
43local setlink            = nuts.setlink
44local setlist            = nuts.setlist
45local setattributelist   = nuts.setattributelist
46local setprop            = nuts.setprop
47
48local takebox            = nuts.takebox
49
50local nextnode           = nuts.traversers.node
51local nexthlist          = nuts.traversers.hlist
52local nextlist           = nuts.traversers.list
53
54local flushnodelist      = nuts.flushlist
55local flushnode          = nuts.flush
56
57local new_rule           = nodepool.rule
58local new_kern           = nodepool.kern
59local new_hlist          = nodepool.hlist
60
61local privateattributes  = attributes.private
62local unsetvalue         = attributes.unsetvalue
63
64local getvalue           = attributes.getvalue
65local hasvalues          = attributes.hasvalues
66
67local linefillers        = nodes.linefillers
68
69local a_background       = privateattributes("background")
70local a_alignbackground  = privateattributes("alignbackground")
71local a_linefiller       = privateattributes("linefiller")
72----- a_ruled            = privateattributes("ruled")
73
74local trace_alignment    = false
75local report_alignment   = logs.reporter("backgrounds","alignment")
76
77trackers.register("backgrounds.alignments",function(v) trace_alignment = v end)
78
79-- We can't use listbuilders with where=alignment because at that stage we have
80-- unset boxes. Also, post_linebreak is unsuitable for nested processing as we
81-- get the same stuff many times (wrapped again and again).
82--
83-- After many experiments with different callbacks the shipout is still the best
84-- place but then we need to store some settings longer or save them with the node.
85-- For color only we can get away with it with an extra attribute flagging a row
86-- but for more complex stuff we can better do as we do here now.
87
88local overshoot = math.floor(65781/5) -- could be an option per table (just also store it)
89
90local function colored_a(current,list,template,id)
91    local width, height, depth = getwhd(current)
92    local total = height + depth
93    if width > 0 and total > 0 then
94        local rule = nil
95        --
96        local a = getattr(template,a_linefiller)
97        if a then
98            local d = getvalue(a_linefiller,a)
99            if d then
100                rule = linefillers.filler(template,d,width,height,depth)
101            end
102        end
103        --
104        if not rule then
105            rule = new_rule(width,height,depth)
106            setattributelist(rule,template)
107        end
108        local back = new_kern(-((id == vlist_code and total) or width))
109        return setlink(rule,back,list)
110    end
111end
112
113local function colored_b(current,list,template,id,indent)
114    local width, height, depth = getwhd(current)
115    local total = height + depth
116    if width > 0 and total > 0 then
117        local fore = (indent ~= 0) and new_kern(indent)
118        local rule = nil
119        --
120        local a = getattr(template,a_linefiller)
121        if a then
122            local d = getvalue(a_linefiller,a)
123            if d then
124                rule = linefillers.filler(template,d,width-indent,height,depth)
125            end
126        end
127        --
128        if not rule then
129            rule = new_rule(width-indent,height+overshoot,depth+overshoot)
130            setattributelist(rule,template)
131        end
132        if overshoot == 0 then
133            local back = new_kern(-((id == vlist_code and total) or width))
134            return setlink(fore,rule,back,list)
135        else
136            rule = new_hlist(rule)
137            return setlink(fore,rule,list)
138        end
139    end
140end
141
142local templates  = { }
143local currentrow = 0
144local enabled    = false
145local alignments = false
146
147-- todo: more control over cell attributes
148
149local function add_alignbackgrounds(head,list)
150    for current, id, subtype, list in nextlist, list do
151        if list and id == hlist_code and subtype == celllist_code then
152            for template in nexthlist, list do
153                local background = getattr(template,a_alignbackground)
154                if background then
155                    local list = colored_a(current,list,template,id)
156                    if list then
157                        setlist(current,list)
158                    end
159                    -- not that efficient:
160                    setattr(template,a_alignbackground,unsetvalue)
161                end
162                break
163            end
164        end
165    end
166    -- we can store this differently now
167    local template = getprop(head,"alignmentchecked")
168    if template then
169        list = colored_b(head,list,template[1],hlist_code,template[2])
170        flushnodelist(template[1])
171        templates[currentrow] = false
172        return list
173    end
174end
175
176local function add_backgrounds(head,id,list)
177    if list then
178        for current, id, subtype, list in nextlist, list do
179            if list then
180                if alignments and subtype == alignmentlist_code then
181                    local l = add_alignbackgrounds(current,list)
182                    if l then
183                        list = l
184                        setlist(current,list)
185                    end
186                end
187                local l = add_backgrounds(current,id,list)
188                if l then
189                    list = l
190                    setlist(current,l)
191                end
192            end
193        end
194    end
195    if id == hlist_code or id == vlist_code then
196        local background = getattr(head,a_background)
197        if background then
198            list = colored_a(head,list,head,id)
199            -- not needed
200            setattr(head,a_background,unsetvalue) -- or property
201            return list
202        end
203    end
204end
205
206function nodes.handlers.backgrounds(head)
207    add_backgrounds(head,getid(head),getlist(head))
208    return head
209end
210
211function nodes.handlers.backgroundspage(head,where)
212    if head and where == "alignment" then
213        for n in nexthlist, head do
214            local p = getprop(n,"alignmentchecked")
215            if not p and getsubtype(n) == alignmentlist_code then
216                currentrow = currentrow + 1
217                local template = templates[currentrow]
218                if trace_alignment then
219                    report_alignment("%03i %s %s",currentrow,"page",template and "+" or "-")
220                end
221                setprop(n,"alignmentchecked",template)
222            end
223        end
224    end
225    return head
226end
227
228function nodes.handlers.backgroundsvbox(head,where)
229    if head and where == "vbox" then
230        local list = getlist(head)
231        if list then
232            for n in nexthlist, list do
233                local p = getprop(n,"alignmentchecked")
234                if not p and getsubtype(n) == alignmentlist_code then
235                    currentrow = currentrow + 1
236                    local template = templates[currentrow]
237                    if trace_alignment then
238                        report_alignment("%03i %s %s",currentrow,"vbox",template and "+" or "-")
239                    end
240                    setprop(n,"alignmentchecked",template)
241                end
242            end
243        end
244    end
245    return head
246end
247
248local function enable(alignmentstoo)
249    if not enabled then
250        enabled = true
251        enableaction("shipouts","nodes.handlers.backgrounds")
252    end
253    if not alignments and alignmentstoo then
254        alignments = true
255        enableaction("vboxbuilders","nodes.handlers.backgroundsvbox")
256        enableaction("mvlbuilders", "nodes.handlers.backgroundspage")
257    end
258end
259
260interfaces.implement {
261    name      = "enablebackgroundboxes",
262    onlyonce  = true,
263    actions   = enable,
264}
265
266interfaces.implement {
267    name      = "enablebackgroundalign", --- move into next one
268    onlyonce  = true,
269    actions   = function()
270        enable(true)
271    end,
272}
273
274interfaces.implement {
275    name      = "setbackgroundrowdata",
276    arguments = { "integer", "integer", "dimension" }, -- todo: "box"
277    actions   = function(row,box,indent)
278        row = row -1 -- better here than in tex
279        if box == 0 then
280            templates[row] = false
281        else
282            templates[row] = { takebox(box), indent }
283        end
284    end,
285}
286
287interfaces.implement {
288    name      = "resetbackgroundrowdata",
289    actions   = function()
290        currentrow = 0
291    end,
292}
293