page-flt.lua /size: 12 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['page-flt'] = {
2    version   = 1.001,
3    comment   = "companion to page-flt.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-- floats -> managers.floats
10-- some functions are a tex/lua mix so we need a separation
11
12local next = next
13local tostring = tostring
14local insert, remove = table.insert, table.remove
15local find, topattern = string.find, string.topattern
16local abs = math.abs
17
18local trace_floats     = false  trackers.register("floats.caching",    function(v) trace_floats     = v end)
19local trace_collecting = false  trackers.register("floats.collecting", function(v) trace_collecting = v end)
20
21local report_floats     = logs.reporter("floats","caching")
22local report_collecting = logs.reporter("floats","collecting")
23
24local C, Cc, S, P, lpegmatch = lpeg.C, lpeg.Cc, lpeg.S, lpeg.P, lpeg.match
25
26-- we use floatbox, floatwidth, floatheight
27-- text page leftpage rightpage (todo: top, bottom, margin, order)
28
29local setdimen         = tex.setdimen
30local getdimen         = tex.getdimen
31local setcount         = tex.setcount
32local texsetbox        = tex.setbox
33local textakebox       = nodes.takebox
34
35floats                 = floats or { }
36local floats           = floats
37
38local context          = context
39local commands         = commands
40local interfaces       = interfaces
41local showmessage      = interfaces.showmessage
42local implement        = interfaces.implement
43local setmacro         = interfaces.setmacro
44
45local noffloats        = 0
46local last             = nil
47local default          = "text"
48local pushed           = { }
49
50local function initialize()
51    return {
52        text      = { },
53        page      = { },
54        top       = { },
55        bottom    = { },
56        leftpage  = { },
57        rightpage = { },
58        somewhere = { },
59    }
60end
61
62local stacks = initialize()
63
64-- list location
65
66function floats.stacked(which) -- floats.thenofstacked
67    return #stacks[which or default]
68end
69
70function floats.push()
71    insert(pushed,stacks)
72    stacks = initialize()
73    setcount("global","savednoffloats",0)
74end
75
76function floats.pop()
77    local popped = remove(pushed)
78    if popped then
79        for which, stack in next, stacks do
80            for i=1,#stack do
81                insert(popped[which],stack[i])
82            end
83        end
84        stacks = popped
85        setcount("global","savednoffloats",#stacks[default])
86    end
87end
88
89local function setdimensions(t,b)
90    local bw, bh, bd = 0, 0, 0
91    local nw, nh, nd = 0, 0, 0
92    local cw, ch, cd = 0, 0, 0
93    if b then
94        bw = b.width
95        bh = b.height
96        bd = b.depth
97        cw = b.cwidth
98        ch = b.cheight
99        cd = b.cdepth
100    end
101    if t then
102        nw = t.width   or bw
103        nh = t.height  or bh
104        nd = t.depth   or bd
105        cw = t.cwidth  or cw
106        ch = t.cheight or ch
107        cd = t.cdepth  or cd
108    end
109    setdimen("global","floatwidth",     bw)
110    setdimen("global","floatheight",    bh+bd)
111    setdimen("global","naturalfloatwd", nw)
112    setdimen("global","naturalfloatht", nh)
113    setdimen("global","naturalfloatdp", nd)
114    setdimen("global","floatcaptionwd", cw)
115    setdimen("global","floatcaptionht", ch)
116    setdimen("global","floatcaptiondp", cd)
117    return bw, bh, bd, nw, nh, dp, cw, xh, xp
118end
119
120local function get(stack,n,bylabel)
121    if bylabel then
122        for i=1,#stack do
123            local s = stack[i]
124            local n = topattern(tostring(n)) -- to be sure
125            if find(s.data.label,n) then
126                return s, s.box, i
127            end
128        end
129    else
130        n = n or #stack
131        if n > 0 then
132            local t = stack[n]
133            if t then
134                return t, t.box, n
135            end
136        end
137    end
138end
139
140function floats.save(which,data) -- todo: just pass
141    which = which or default
142    local b = textakebox("floatbox")
143    if b then
144        local stack = stacks[which]
145        noffloats = noffloats + 1
146        local t = {
147            n       = noffloats,
148            data    = data or { },
149            width   = getdimen("naturalfloatwd"),
150            height  = getdimen("naturalfloatht"),
151            depth   = getdimen("naturalfloatdp"),
152            cwidth  = getdimen("floatcaptionwd"),
153            cheight = getdimen("floatcaptionht"),
154            cdepth  = getdimen("floatcaptiondp"),
155            box     = b,
156        }
157        insert(stack,t)
158-- inspect(stacks)
159        setcount("global","savednoffloats",#stacks[default])
160        if trace_floats then
161            report_floats("%s, category %a, number %a, slot %a, width %p, height %p, depth %p","saving",
162                which,noffloats,#stack,b.width,b.height,b.depth)
163        else
164            showmessage("floatblocks",2,noffloats)
165        end
166    else
167        report_floats("ignoring empty, category %a, number %a",which,noffloats)
168    end
169end
170
171function floats.resave(which)
172    if last then
173        which = which or default
174        local stack = stacks[which]
175        local b = textakebox("floatbox")
176        if not b then
177            report_floats("resaved float is empty")
178        end
179        last.box = b
180        insert(stack,1,last)
181        setcount("global","savednoffloats",#stacks[default])
182        if trace_floats then
183            report_floats("%s, category %a, number %a, slot %a width %p, height %p, depth %p","resaving",
184                which,noffloats,#stack,b.width,b.height,b.depth)
185        else
186            showmessage("floatblocks",2,noffloats)
187        end
188    else
189        report_floats("unable to resave float")
190    end
191end
192
193function floats.flush(which,n,bylabel)
194    which = which or default
195    local stack = stacks[which]
196    local t, b, n = get(stack,n or 1,bylabel)
197    if t then
198        if not b then
199            showmessage("floatblocks",1,t.n)
200        end
201        local w, h, d = setdimensions(t,b)
202        if trace_floats then
203            report_floats("%s, category %a, number %a, slot %a width %p, height %p, depth %p","flushing",
204                which,t.n,n,w,h,d)
205        else
206            showmessage("floatblocks",3,t.n)
207        end
208        texsetbox("floatbox",b)
209        last = remove(stack,n)
210        last.box = nil
211        setcount("global","savednoffloats",#stacks[which]) -- default?
212    else
213        setdimensions()
214    end
215end
216
217function floats.consult(which,n)
218    which = which or default
219    local stack = stacks[which]
220    local t, b, n = get(stack,n)
221    if t then
222        local w, h, d = setdimensions(t,b)
223        if trace_floats then
224            report_floats("%s, category %a, number %a, slot %a width %p, height %p, depth %p","consulting",
225                which,t.n,n,w,h,d)
226        end
227        return t, b, n
228    else
229        if trace_floats then
230            report_floats("nothing to consult")
231        end
232        setdimensions()
233    end
234end
235
236function floats.collect(which,maxwidth,distance)
237    local usedwhich = which or default
238    local stack     = stacks[usedwhich]
239    local stacksize = #stack
240    local collected = 0
241    local maxheight = 0
242    local maxdepth  = 0
243
244    local function register()
245        collected = collected + 1
246        maxwidth  = rest
247        if h > maxheight then
248            maxheight = h
249        end
250        if d > maxdepth then
251            maxdepth = d
252        end
253    end
254
255    for i=1,stacksize do
256        local t, b, n = get(stack,i)
257        if t then
258            local w, h, d, nw, nh, nd, cw, ch, cd = setdimensions(t,b)
259            -- we use the real width
260            if cw > nw then
261                w = cw
262            else
263                w = nw
264            end
265            -- which could be an option
266            local rest = maxwidth - w - distance
267            local fits = rest > -10
268            if trace_collecting then
269                report_collecting("%s, category %a, number %a, slot %a width %p, rest %p, fit %a","collecting",
270                    usedwhich,t.n,n,w,rest,fits)
271            end
272            if fits then
273                collected = collected + 1
274                maxwidth  = rest
275                if h > maxheight then
276                    maxheight = h
277                end
278                if d > maxdepth then
279                    maxdepth = d
280                end
281            else
282                break
283            end
284        else
285            break
286        end
287    end
288    setcount("global","nofcollectedfloats",collected)
289    setdimen("global","maxcollectedfloatstotal",maxheight+maxdepth)
290end
291
292function floats.getvariable(name,default)
293    local value = last and last.data[name] or default
294    return value ~= "" and value
295end
296
297function floats.checkedpagefloat(packed)
298    if structures.pages.is_odd() then
299        if #stacks.rightpage > 0 then
300            return "rightpage"
301        elseif #stacks.page > 0 then
302            return "page"
303        elseif #stacks.leftpage > 0 then
304            if packed then
305                return "leftpage"
306            end
307        end
308    else
309        if #stacks.leftpage > 0 then
310            return "leftpage"
311        elseif #stacks.page > 0 then
312            return "page"
313        elseif #stacks.rightpage > 0 then
314            if packed then
315                return "rightpage"
316            end
317        end
318    end
319end
320
321function floats.nofstacked(which)
322    local s = stacks[which or default]
323    return s and #s or 0
324end
325
326function floats.hasstacked(which)
327    local s = stacks[which or default]
328    return (s and #s or 0) > 0
329end
330
331-- todo: check for digits !
332
333local digits   = lpeg.patterns.digits
334local nothing  = Cc("")
335local method   = C((1-S(", :"))^1)
336local position = P(":") * C(digits^1) * (P("*") * C(digits^1) + nothing)
337local label    = P(":") * C((1-S(",*: "))^0)
338
339local pattern = method * (
340    label * position
341  + nothing * position
342  + label * nothing * nothing
343  + nothing * nothing * nothing
344) + nothing * nothing * nothing * nothing
345
346-- inspect { lpegmatch(pattern,"somewhere:blabla,crap") }
347-- inspect { lpegmatch(pattern,"somewhere:1*2") }
348-- inspect { lpegmatch(pattern,"somewhere:blabla:1*2") }
349-- inspect { lpegmatch(pattern,"somewhere::1*2") }
350-- inspect { lpegmatch(pattern,"somewhere,") }
351-- inspect { lpegmatch(pattern,"somewhere") }
352-- inspect { lpegmatch(pattern,"") }
353
354function floats.analysemethod(str) -- will become a more extensive parser
355    return lpegmatch(pattern,str or "")
356end
357
358-- interface
359
360implement {
361    name      = "flushfloat",
362    actions   = floats.flush,
363    arguments = { "string", "integer" },
364}
365
366implement {
367    name      = "flushlabeledfloat",
368    actions   = floats.flush,
369    arguments = { "string", "string", true },
370}
371
372implement {
373    name      = "savefloat",
374    actions   = floats.save,
375    arguments = "string"
376}
377
378implement {
379    name      = "savespecificfloat",
380    actions   = floats.save,
381    arguments = {
382        "string",
383        {
384            { "specification" },
385            { "label" },
386        }
387    }
388}
389
390implement {
391    name      = "resavefloat",
392    actions   = floats.resave,
393    arguments = "string"
394}
395
396implement {
397    name      = "pushfloat",
398    actions   = floats.push
399}
400
401implement {
402    name      = "popfloat",
403    actions   = floats.pop
404}
405
406implement {
407    name      = "consultfloat",
408    actions   = floats.consult,
409    arguments = "string",
410}
411
412implement {
413    name      = "collectfloat",
414    actions   = floats.collect,
415    arguments = { "string", "dimen", "dimen" }
416}
417
418implement {
419    name      = "getfloatvariable",
420    actions   = { floats.getvariable, context },
421    arguments = "string"
422}
423
424implement {
425    name      = "checkedpagefloat",
426    actions   = { floats.checkedpagefloat, context },
427    arguments = "string"
428}
429
430implement {
431    name      = "nofstackedfloats",
432    actions   = { floats.nofstacked, context },
433    arguments = "string"
434}
435
436implement {
437    name      = "doifelsestackedfloats",
438    actions   = { floats.hasstacked, commands.doifelse },
439    arguments = "string"
440}
441
442implement {
443    name    = "analysefloatmethod",
444    actions = function(str)
445        local method, label, column, row = floats.analysemethod(str)
446        setmacro("floatmethod",method or "")
447        setmacro("floatlabel", label  or "")
448        setmacro("floatrow",   row    or "")
449        setmacro("floatcolumn",column or "")
450    end,
451    arguments = "string"
452}
453