page-ins.lmt /size: 8688 b    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['page-ins'] = {
2    version   = 1.001,
3    comment   = "companion to page-mix.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
9local next = next
10local remove = table.remove
11
12structures           = structures or { }
13structures.inserts   = structures.inserts or { }
14local inserts        = structures.inserts
15
16local allocate       = utilities.storage.allocate
17
18inserts.stored       = inserts.stored or allocate { } -- combining them in one is inefficient in the
19inserts.data         = inserts.data   or allocate { } -- bytecode storage pool
20
21local variables      = interfaces.variables
22local v_page         <const> = variables.page
23local v_auto         <const> = variables.auto
24
25local context        = context
26local implement      = interfaces.implement
27
28storage.register("structures/inserts/stored", inserts.stored, "structures.inserts.stored")
29
30local data           = inserts.data
31local stored         = inserts.stored
32
33for name, specification in next, stored do
34    data[specification.number] = specification
35    data[name]                 = specification
36end
37
38function inserts.define(name,specification)
39    specification.name= name
40    local number = specification.number or 0
41    data[name]   = specification
42    data[number] = specification
43    -- only needed at runtime as this get stored in a bytecode register
44    stored[name] = specification
45    if not specification.location then
46        specification.location = v_page
47    end
48    return specification
49end
50
51function inserts.setup(name,settings)
52    local specification = data[name]
53    for k, v in next, settings do
54        -- maybe trace change
55        specification[k] = v
56    end
57    return specification
58end
59
60function inserts.setlocation(name,location) -- a practical fast one
61    data[name].location = location
62end
63
64function inserts.getlocation(name,location)
65    return data[name].location or v_page
66end
67
68function inserts.getdata(name) -- or number
69    return data[name]
70end
71
72function inserts.getname(number)
73    return data[name].name
74end
75
76function inserts.getnumber(name)
77    return data[name].number
78end
79
80-- interface
81
82implement {
83    name      = "defineinsertion",
84    actions   = inserts.define,
85    arguments = {
86        "string",
87        {
88            { "number", "integer" }
89        }
90    }
91}
92
93implement {
94    name      = "setupinsertion",
95    actions   = inserts.setup,
96    arguments = {
97        "string",
98        {
99            { "location" }
100        }
101    }
102}
103
104implement {
105    name      = "setinsertionlocation",
106    actions   = inserts.setlocation,
107    arguments = "2 strings",
108}
109
110implement {
111    name      = "insertionnumber",
112    actions   = function(name) context(data[name].number or 0) end,
113    arguments = "string"
114}
115
116implement {
117    name      = "setinsertmigration",
118    arguments = "string",
119    actions   = function(state)
120        nodes.migrations.setinserts(state == v_auto)
121    end
122}
123
124
125-- nasty
126
127do
128
129    local insert_code   <const> = nodes.nodecodes.insert
130    local integer_value <const> = tokens.values.integer
131
132    local nuts       = nodes.nuts
133    local tonode     = nodes.tonode
134
135    local getpost     = nuts.getpost
136    local setpost     = nuts.setpost
137    local getlist     = nuts.getlist
138    local setlist     = nuts.setlist
139 -- local setboth     = nuts.setboth
140    local getbox      = nuts.getbox
141    local getid       = nuts.getid
142    local getindex    = nuts.getindex
143    local getnext     = nuts.getnext
144    local flushlist   = nuts.flushlist
145    local traverseid  = nuts.traverseid
146
147    local detached    = false
148    local nofdetached = false
149
150    local function detach(n)
151        local box = getbox(n)
152        if box then
153            local post = getpost(box)
154            if post then
155                setpost(box)
156                if detached then
157                    flushlist(detached)
158                end
159                detached = post
160                nofdetached = 0
161                for d in traverseid(insert_code,detached) do
162                    nofdetached = nofdetached + 1
163                end
164                if nofdetached == 0 then
165                    flushlist(detached)
166                    detached = false
167                end
168            end
169        end
170    end
171
172    local function attach(index,cs)
173        if detached then
174            for d in traverseid(insert_code,detached) do
175                local idx = getindex(d)
176                if not index or idx == index or index == 0 then
177                    local list = getlist(d)
178                    context[cs](tonode(list))
179                    setlist(d)
180                    nofdetached = nofdetached - 1
181                end
182            end
183            if nofdetached == 0 then
184                flushlist(detached)
185                detached = false
186            end
187        end
188    end
189
190    local function nofdetached(index)
191        local n = 0
192        if detached then
193            for d in traverseid(insert_code,detached) do
194                local idx = getindex(d)
195                if not index or idx == index or index == 0 then
196                    n = n + 1
197                end
198            end
199        end
200        return integer_value, n
201    end
202
203    implement {
204        name      = "detachinsertions",
205        arguments = "integer",
206        public    = true,
207        protected = true,
208        actions   = detach,
209    }
210
211    implement {
212        name      = "attachinsertions",
213        arguments = "csname",
214        public    = true,
215        protected = true,
216        actions   = function(cs) attach(0,cs) end,
217    }
218
219    implement {
220        name      = "attachinsertion",
221        arguments = { "integer", "csname" },
222        public    = true,
223        protected = true,
224        actions   = attach,
225    }
226
227    implement {
228        name      = "nofdetachedinsertions",
229        actions   = nofdetached,
230        public    = true,
231     -- protected = true,
232        usage     = "value",
233        arguments = "integer",
234    }
235
236    inserts.attach      = attach
237    inserts.detach      = detach
238    inserts.nofdetached = nofdetached
239
240end
241
242-- Maybe this will get its own module.
243
244local tostring = tostring
245local abs = math.abs
246
247local nuts         = nodes.nuts
248local tonut        = nuts.tonut
249local tonode       = nuts.tonode
250local traverselist = nuts.traverselist
251local getlist      = nuts.getlist
252local setlist      = nuts.setlist
253local getattr      = nuts.getattr
254local getheight    = nuts.getheight
255local setheight    = nuts.setheight
256local hpack        = nuts.hpack
257local setattrlist  = nuts.setattrlist
258
259local setbox       = nuts.setbox
260local takebox      = nuts.takebox
261local expandmacro  = token.expandmacro
262
263do
264    -- we actually get a split one
265
266    local report = logs.reporter("balance","uinsert")
267
268    local trace  = false  trackers.register("columnsets.uinsert", function(v) trace = v end)
269
270    local a_balancescaled <const> = attributes.private("balancescaled")
271
272    local threshold <const> = 1.01
273
274    local packlist = { } -- can be reused
275
276    local function locate(p)
277        for n in traverselist(p) do
278            if getattr(n,a_balancescaled) then
279                packlist[#packlist+1] = n
280                return n
281            else
282                packlist[#packlist+1] = p
283                return locate(getlist(p),t)
284            end
285        end
286    end
287
288    local function processuinsert(l,scale)
289        setbox("b_page_inserts_uinsert",hpack(l))
290        expandmacro("page_inserts_handle_uinsert",true,tostring(scale))
291        return takebox("b_page_inserts_uinsert")
292    end
293
294    callback.register("handle_uinsert", function(callback,index,order,packed,height,amount)
295        if (callback == 1 or callback == 2) and amount ~= 0 then
296            packed = tonut(packed)
297            local p = locate(getlist(packed))
298            if p then
299                local oldheight = getheight(p)
300                local newheight = oldheight + amount
301                local scale     = newheight/oldheight
302                if abs(scale) >= threshold then
303                    if trace then
304                        report("index %i, order %i, scaling %.3f",index,order,scale)
305                    end
306                    local l = processuinsert(getlist(p),scale)
307                    setlist(p,l)
308                    setattrlist(l,insert)
309                    local h = getheight(l)
310                    for i=#packlist,1,-1 do
311                        setheight(packlist[i],h)
312                    end
313                end
314            end
315            packlist = { }
316            return tonode(packed)
317        else
318            return packed
319        end
320    end)
321
322end
323