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