node-bck.lua /size: 9294 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
49local findtail           = nuts.tail
50
51local nextnode           = nuts.traversers.node
52local nexthlist          = nuts.traversers.hlist
53local nextlist           = nuts.traversers.list
54
55local flushnodelist      = nuts.flushlist
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 linefillers        = nodes.linefillers
65
66local a_background       = privateattributes("background")
67local a_alignbackground  = privateattributes("alignbackground")
68local a_linefiller       = privateattributes("linefiller")
69local a_ruled            = privateattributes("ruled")
70
71local trace_alignment    = false
72local report_alignment   = logs.reporter("backgrounds","alignment")
73
74trackers.register("backgrounds.alignments",function(v) trace_alignment = v end)
75
76-- We can't use listbuilders with where=alignment because at that stage we have
77-- unset boxes. Also, post_linebreak is unsuitable for nested processing as we
78-- get the same stuff many times (wrapped again and again).
79--
80-- After many experiments with different callbacks the shipout is still the best
81-- place but then we need to store some settings longer or save them with the node.
82-- For color only we can get away with it with an extra attribute flagging a row
83-- but for more complex stuff we can better do as we do here now.
84
85local overshoot = math.floor(65781/5) -- could be an option per table (just also store it)
86
87local function colored_a(current,list,template,id)
88    local width, height, depth = getwhd(current)
89    local total = height + depth
90    if width > 0 and total > 0 then
91        local rule = nil
92        --
93        local a = getattr(template,a_linefiller)
94        if a then
95            local d = linefillers.data[a%1000]
96            if d then
97                rule = linefillers.filler(template,d,width,height,depth)
98            end
99        end
100        --
101        if not rule then
102            rule = new_rule(width,height,depth)
103        end
104        setattributelist(rule,template)
105        local back = new_kern(-((id == vlist_code and total) or width))
106        return setlink(rule,back,list)
107    end
108end
109
110local function colored_b(current,list,template,id,indent)
111    local width, height, depth = getwhd(current)
112    local total = height + depth
113    if width > 0 and total > 0 then
114        local fore = (indent ~= 0) and new_kern(indent)
115        local rule = nil
116        --
117        local a = getattr(template,a_linefiller)
118        if a then
119            local d = linefillers.data[a%1000]
120            if d then
121                rule = linefillers.filler(template,d,width-indent,height,depth)
122            end
123        end
124        --
125        if not rule then
126            rule = new_rule(width-indent,height+overshoot,depth+overshoot)
127            setattributelist(rule,template)
128        end
129        if overshoot == 0 then
130            local back = new_kern(-((id == vlist_code and total) or width))
131            return setlink(fore,rule,back,list)
132        else
133            rule = new_hlist(rule)
134            return setlink(fore,rule,list)
135        end
136    end
137end
138
139local templates  = { }
140local currentrow = 0
141local enabled    = false
142local alignments = false
143
144local function add_alignbackgrounds(head,list)
145    for current, id, subtype, list in nextlist, list do
146        if list and id == hlist_code and subtype == celllist_code then
147            for template in nexthlist, list do
148                local background = getattr(template,a_alignbackground)
149                if background then
150                    local list = colored_a(current,list,template)
151                    if list then
152                        setlist(current,list)
153                    end
154                    setattr(template,a_alignbackground,unsetvalue) -- or property
155                end
156                break
157            end
158        end
159    end
160    local template = getprop(head,"alignmentchecked")
161    if template then
162        list = colored_b(head,list,template[1],hlist_code,template[2])
163        flushnodelist(template)
164        templates[currentrow] = false
165        return list
166    end
167end
168
169local function add_backgrounds(head,id,list)
170    if list then
171        for current, id, subtype, list in nextlist, list do
172            if list then
173                if alignments and subtype == alignmentlist_code then
174                    local l = add_alignbackgrounds(current,list)
175                    if l then
176                        list = l
177                        setlist(current,list)
178                    end
179                end
180                local l = add_backgrounds(current,id,list)
181                if l then
182                    list = l
183                    setlist(current,l)
184                end
185            end
186        end
187    end
188    if id == hlist_code or id == vlist_code then
189        local background = getattr(head,a_background)
190        if background then
191            list = colored_a(head,list,head,id)
192            -- not needed
193            setattr(head,a_background,unsetvalue) -- or property
194            return list
195        end
196    end
197end
198
199function nodes.handlers.backgrounds(head)
200    add_backgrounds(head,getid(head),getlist(head))
201    return head
202end
203
204function nodes.handlers.backgroundspage(head,where)
205    if head and where == "alignment" then
206        for n in nexthlist, head do
207            local p = getprop(n,"alignmentchecked")
208            if not p and getsubtype(n) == alignmentlist_code then
209                currentrow = currentrow + 1
210                local template = templates[currentrow]
211                if trace_alignment then
212                    report_alignment("%03i %s %s",currentrow,"page",template and "+" or "-")
213                end
214                setprop(n,"alignmentchecked",template)
215            end
216        end
217    end
218    return head
219end
220
221function nodes.handlers.backgroundsvbox(head,where)
222    if head and where == "vbox" then
223        local list = getlist(head)
224        if list then
225            for n in nexthlist, list do
226                local p = getprop(n,"alignmentchecked")
227                if not p and getsubtype(n) == alignmentlist_code then
228                    currentrow = currentrow + 1
229                    local template = templates[currentrow]
230                    if trace_alignment then
231                        report_alignment("%03i %s %s",currentrow,"vbox",template and "+" or "-")
232                    end
233                    setprop(n,"alignmentchecked",template)
234                end
235            end
236        end
237    end
238    return head
239end
240
241local function enable(alignmentstoo)
242    if not enabled then
243        enabled = true
244        enableaction("shipouts","nodes.handlers.backgrounds")
245    end
246    if not alignments and alignmentstoo then
247        alignments = true
248        enableaction("vboxbuilders","nodes.handlers.backgroundsvbox")
249        enableaction("mvlbuilders", "nodes.handlers.backgroundspage")
250    end
251end
252
253interfaces.implement {
254    name      = "enablebackgroundboxes",
255    onlyonce  = true,
256    actions   = enable,
257}
258
259interfaces.implement {
260    name      = "enablebackgroundalign",
261    onlyonce  = true,
262    actions   = function()
263        enable(true)
264    end,
265}
266
267interfaces.implement {
268    name      = "setbackgroundrowdata",
269    arguments = { "integer", "integer", "dimension" },
270    actions   = function(row,box,indent)
271        row = row -1 -- better here than in tex
272        if box == 0 then
273            templates[row] = false
274        else
275            templates[row] = { takebox(box), indent }
276        end
277    end,
278}
279
280interfaces.implement {
281    name      = "resetbackgroundrowdata",
282    actions   = function()
283        currentrow = 0
284    end,
285}
286