spac-ver.lua /size: 87 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['spac-ver'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to spac-ver.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-- we also need to call the spacer for inserts!
11
12-- somehow lists still don't always have proper prev nodes so i need to
13-- check all of the luatex code some day .. maybe i should replece the
14-- whole mvl handler by lua code .. why not
15
16-- todo: use lua nodes with lua data (>0.79)
17-- see ** can go when 0.79
18
19-- needs to be redone, too many calls and tests now ... still within some
20-- luatex limitations
21
22-- this code dates from the beginning and is kind of experimental; it
23-- will be optimized and improved soon .. it's way too complex now but
24-- dates from less possibilities
25--
26-- the collapser will be redone with user nodes; also, we might get make
27-- parskip into an attribute and appy it explicitly thereby getting rid
28-- of automated injections; eventually i want to get rid of the currently
29-- still needed tex -> lua -> tex > lua chain (needed because we can have
30-- expandable settings at the tex end
31
32-- todo: strip baselineskip around display math
33
34local next, type, tonumber = next, type, tonumber
35local gmatch, concat = string.gmatch, table.concat
36local ceil, floor = math.ceil, math.floor
37local lpegmatch = lpeg.match
38local unpack = unpack or table.unpack
39local allocate = utilities.storage.allocate
40local todimen = string.todimen
41local formatters = string.formatters
42local abs = math.abs
43
44local nodes        =  nodes
45local trackers     =  trackers
46local attributes   =  attributes
47local context      =  context
48local tex          =  tex
49
50local texlists     = tex.lists
51local texget       = tex.get
52local texgetcount  = tex.getcount
53local texgetdimen  = tex.getdimen
54local texset       = tex.set
55local texsetdimen  = tex.setdimen
56local texsetcount  = tex.setcount
57local texnest      = tex.nest
58local texgetbox    = tex.getbox
59
60local buildpage    = tex.triggerbuildpage
61
62local variables    = interfaces.variables
63local implement    = interfaces.implement
64
65local v_local      = variables["local"]
66local v_global     = variables["global"]
67local v_box        = variables.box
68----- v_page       = variables.page -- reserved for future use
69local v_split      = variables.split
70local v_min        = variables.min
71local v_max        = variables.max
72local v_none       = variables.none
73local v_line       = variables.line
74local v_noheight   = variables.noheight
75local v_nodepth    = variables.nodepth
76local v_line       = variables.line
77local v_halfline   = variables.halfline
78local v_line_m     = "-" .. v_line
79local v_halfline_m = "-" .. v_halfline
80local v_first      = variables.first
81local v_last       = variables.last
82local v_top        = variables.top
83local v_bottom     = variables.bottom
84local v_minheight  = variables.minheight
85local v_maxheight  = variables.maxheight
86local v_mindepth   = variables.mindepth
87local v_maxdepth   = variables.maxdepth
88local v_offset     = variables.offset
89local v_strut      = variables.strut
90
91local v_hfraction  = variables.hfraction
92local v_dfraction  = variables.dfraction
93local v_bfraction  = variables.bfraction
94local v_tlines     = variables.tlines
95local v_blines     = variables.blines
96
97-- vertical space handler
98
99local trace_vbox_vspacing    = false  trackers.register("vspacing.vbox",     function(v) trace_vbox_vspacing    = v end)
100local trace_page_vspacing    = false  trackers.register("vspacing.page",     function(v) trace_page_vspacing    = v end)
101local trace_page_builder     = false  trackers.register("builders.page",     function(v) trace_page_builder     = v end)
102local trace_collect_vspacing = false  trackers.register("vspacing.collect",  function(v) trace_collect_vspacing = v end)
103local trace_vspacing         = false  trackers.register("vspacing.spacing",  function(v) trace_vspacing         = v end)
104local trace_vsnapping        = false  trackers.register("vspacing.snapping", function(v) trace_vsnapping        = v end)
105local trace_specials         = false  trackers.register("vspacing.specials", function(v) trace_specials         = v end)
106
107local remove_math_skips   = true  directives.register("vspacing.removemathskips", function(v) remnove_math_skips = v end)
108
109local report_vspacing     = logs.reporter("vspacing","spacing")
110local report_collapser    = logs.reporter("vspacing","collapsing")
111local report_snapper      = logs.reporter("vspacing","snapping")
112local report_specials     = logs.reporter("vspacing","specials")
113
114local a_skipcategory      = attributes.private('skipcategory')
115local a_skippenalty       = attributes.private('skippenalty')
116local a_skiporder         = attributes.private('skiporder')
117local a_snapmethod        = attributes.private('snapmethod')
118local a_snapvbox          = attributes.private('snapvbox')
119
120local nuts                = nodes.nuts
121local tonut               = nuts.tonut
122local tonode              = nuts.tonode
123
124local getnext             = nuts.getnext
125local setlink             = nuts.setlink
126local getprev             = nuts.getprev
127local getid               = nuts.getid
128local getlist             = nuts.getlist
129local setlist             = nuts.setlist
130local getattr             = nuts.getattr
131local setattr             = nuts.setattr
132local getsubtype          = nuts.getsubtype
133local getbox              = nuts.getbox
134local getwhd              = nuts.getwhd
135local setwhd              = nuts.setwhd
136local getprop             = nuts.getprop
137local setprop             = nuts.setprop
138local getglue             = nuts.getglue
139local setglue             = nuts.setglue
140local getkern             = nuts.getkern
141local getpenalty          = nuts.getpenalty
142local setshift            = nuts.setshift
143local setwidth            = nuts.setwidth
144local getwidth            = nuts.getwidth
145local setheight           = nuts.setheight
146local getheight           = nuts.getheight
147local setdepth            = nuts.setdepth
148local getdepth            = nuts.getdepth
149
150local find_node_tail      = nuts.tail
151local flushnode           = nuts.flushnode
152local insertnodeafter     = nuts.insertafter
153local insertnodebefore    = nuts.insertbefore
154local remove_node         = nuts.remove
155local count_nodes         = nuts.countall
156local hpack_node          = nuts.hpack
157local vpack_node          = nuts.vpack
158
159local startofpar          = nuts.startofpar
160
161local nextnode            = nuts.traversers.node
162local nexthlist           = nuts.traversers.hlist
163
164local nodereference       = nuts.reference
165
166local theprop             = nuts.theprop
167
168local listtoutf           = nodes.listtoutf
169local nodeidstostring     = nodes.idstostring
170
171local nodepool            = nuts.pool
172
173local new_penalty         = nodepool.penalty
174local new_kern            = nodepool.kern
175----- new_glue            = nodepool.glue
176local new_rule            = nodepool.rule
177
178local nodecodes           = nodes.nodecodes
179local gluecodes           = nodes.gluecodes
180----- penaltycodes        = nodes.penaltycodes
181----- listcodes           = nodes.listcodes
182
183local penalty_code        = nodecodes.penalty
184local kern_code           = nodecodes.kern
185local glue_code           = nodecodes.glue
186local hlist_code          = nodecodes.hlist
187local vlist_code          = nodecodes.vlist
188local rule_code           = nodecodes.rule
189local par_code            = nodecodes.par
190
191local userskip_code       = gluecodes.userskip
192local lineskip_code       = gluecodes.lineskip
193local baselineskip_code   = gluecodes.baselineskip
194local parskip_code        = gluecodes.parskip
195local topskip_code        = gluecodes.topskip
196local splittopskip_code   = gluecodes.splittopskip
197
198local linelist_code       = nodes.listcodes.line
199
200local abovedisplayskip_code      = gluecodes.abovedisplayskip
201local belowdisplayskip_code      = gluecodes.belowdisplayskip
202local abovedisplayshortskip_code = gluecodes.abovedisplayshortskip
203local belowdisplayshortskip_code = gluecodes.belowdisplayshortskip
204
205local properties          = nodes.properties.data
206
207local vspacing            = builders.vspacing or { }
208builders.vspacing         = vspacing
209
210local vspacingdata        = vspacing.data or { }
211vspacing.data             = vspacingdata
212
213local snapmethods         = vspacingdata.snapmethods or { }
214vspacingdata.snapmethods  = snapmethods
215
216storage.register("builders/vspacing/data/snapmethods", snapmethods, "builders.vspacing.data.snapmethods")
217
218do
219
220    local default = {
221        [v_maxheight] = true,
222        [v_maxdepth]  = true,
223        [v_strut]     = true,
224        [v_hfraction] = 1,
225        [v_dfraction] = 1,
226        [v_bfraction] = 0.25,
227    }
228
229    local fractions = {
230        [v_minheight] = v_hfraction, [v_maxheight] = v_hfraction,
231        [v_mindepth]  = v_dfraction, [v_maxdepth]  = v_dfraction,
232        [v_box]       = v_bfraction,
233        [v_top]       = v_tlines,    [v_bottom]    = v_blines,
234    }
235
236    local values = {
237        offset = "offset"
238    }
239
240    local colonsplitter = lpeg.splitat(":")
241
242    local function listtohash(str)
243        local t = { }
244        for s in gmatch(str,"[^, ]+") do
245            local key, detail = lpegmatch(colonsplitter,s)
246            local v = variables[key]
247            if v then
248                t[v] = true
249                if detail then
250                    local k = fractions[key]
251                    if k then
252                        detail = tonumber("0" .. detail)
253                        if detail then
254                            t[k] = detail
255                        end
256                    else
257                        k = values[key]
258                        if k then
259                            detail = todimen(detail)
260                            if detail then
261                                t[k] = detail
262                            end
263                        end
264                    end
265                end
266            else
267                detail = tonumber("0" .. key)
268                if detail then
269                    t[v_hfraction] = detail
270                    t[v_dfraction] = detail
271                end
272            end
273        end
274        if next(t) then
275            t[v_hfraction] = t[v_hfraction] or 1
276            t[v_dfraction] = t[v_dfraction] or 1
277            return t
278        else
279            return default
280        end
281    end
282
283    function vspacing.definesnapmethod(name,method)
284        local n = #snapmethods + 1
285        local t = listtohash(method)
286        snapmethods[n] = t
287        t.name          = name   -- not interfaced
288        t.specification = method -- not interfaced
289        context(n)
290    end
291
292end
293
294local function validvbox(parentid,list)
295    if parentid == hlist_code then
296        local id = getid(list)
297        if id == par_code and startofpar(list) then
298            list = getnext(list)
299            if not next then
300                return nil
301            end
302        end
303        local done = nil
304        for n, id in nextnode, list do
305            if id == vlist_code or id == hlist_code then
306                if done then
307                    return nil
308                else
309                    done = n
310                end
311            elseif id == glue_code or id == penalty_code then
312                -- go on
313            else
314                return nil -- whatever
315            end
316        end
317        if done then
318            local id = getid(done)
319            if id == hlist_code then
320                return validvbox(id,getlist(done))
321            end
322        end
323        return done -- only one vbox
324    end
325end
326
327-- we can use a property
328
329local function already_done(parentid,list,a_snapmethod) -- todo: done when only boxes and all snapped
330    -- problem: any snapped vbox ends up in a line
331    if list and parentid == hlist_code then
332        local id = getid(list)
333        if id == par_code and startofpar(list) then
334            list = getnext(list)
335            if not list then
336                return false
337            end
338        end
339        for n, id in nextnode, list do
340            if id == hlist_code or id == vlist_code then
341             -- local a = getattr(n,a_snapmethod)
342             -- if not a then
343             --  -- return true -- not snapped at all
344             -- elseif a == 0 then
345             --     return true -- already snapped
346             -- end
347                local p = getprop(n,"snapper")
348                if p then
349                    return p
350                end
351            elseif id == glue_code or id == penalty_code then -- or id == kern_code then
352                -- go on
353            else
354                return false -- whatever
355            end
356        end
357    end
358    return false
359end
360
361-- quite tricky: ceil(-something) => -0
362
363local function ceiled(n)
364    if n < 0 or n < 0.01 then
365        return 0
366    else
367        return ceil(n)
368    end
369end
370
371local function floored(n)
372    if n < 0 or n < 0.01 then
373        return 0
374    else
375        return floor(n)
376    end
377end
378
379-- check variables.none etc
380
381local function fixedprofile(current)
382    local profiling = builders.profiling
383    return profiling and profiling.fixedprofile(current)
384end
385
386-- local function onlyoneentry(t)
387--     local n = 1
388--     for k, v in next, t do
389--         if n > 1 then
390--             return false
391--         end
392--         n = n + 1
393--     end
394--     return true
395-- end
396
397local function snap_hlist(where,current,method,height,depth) -- method[v_strut] is default
398    if fixedprofile(current) then
399        return
400    end
401    local list = getlist(current)
402    local t = trace_vsnapping and { }
403    if t then
404        t[#t+1] = formatters["list content: %s"](listtoutf(list))
405        t[#t+1] = formatters["snap method: %s"](method.name) -- not interfaced
406        t[#t+1] = formatters["specification: %s"](method.specification) -- not interfaced
407    end
408    local snapht, snapdp
409    if method[v_local] then
410        -- snapping is done immediately here
411        snapht = texgetdimen("bodyfontstrutheight")
412        snapdp = texgetdimen("bodyfontstrutdepth")
413        if t then
414            t[#t+1] = formatters["local: snapht %p snapdp %p"](snapht,snapdp)
415        end
416    elseif method[v_global] then
417        snapht = texgetdimen("globalbodyfontstrutheight")
418        snapdp = texgetdimen("globalbodyfontstrutdepth")
419        if t then
420            t[#t+1] = formatters["global: snapht %p snapdp %p"](snapht,snapdp)
421        end
422    else
423        -- maybe autolocal
424        -- snapping might happen later in the otr
425        snapht = texgetdimen("globalbodyfontstrutheight")
426        snapdp = texgetdimen("globalbodyfontstrutdepth")
427        local lsnapht = texgetdimen("bodyfontstrutheight")
428        local lsnapdp = texgetdimen("bodyfontstrutdepth")
429        if snapht ~= lsnapht and snapdp ~= lsnapdp then
430            snapht, snapdp = lsnapht, lsnapdp
431        end
432        if t then
433            t[#t+1] = formatters["auto: snapht %p snapdp %p"](snapht,snapdp)
434        end
435    end
436
437    local wd, ht, dp = getwhd(current)
438
439    local h        = (method[v_noheight] and 0) or height or ht
440    local d        = (method[v_nodepth]  and 0) or depth  or dp
441    local hr       = method[v_hfraction] or 1
442    local dr       = method[v_dfraction] or 1
443    local br       = method[v_bfraction] or 0
444    local ch       = h
445    local cd       = d
446    local tlines   = method[v_tlines] or 1
447    local blines   = method[v_blines] or 1
448    local done     = false
449    local plusht   = snapht
450    local plusdp   = snapdp
451    local snaphtdp = snapht + snapdp
452    local extra    = 0
453
454    if t then
455        t[#t+1] = formatters["hlist: wd %p ht %p (used %p) dp %p (used %p)"](wd,ht,h,dp,d)
456        t[#t+1] = formatters["fractions: hfraction %s dfraction %s bfraction %s tlines %s blines %s"](hr,dr,br,tlines,blines)
457    end
458
459    if method[v_box] then
460        local br = 1 - br
461        if br < 0 then
462            br = 0
463        elseif br > 1 then
464            br = 1
465        end
466        local n = ceiled((h+d-br*snapht-br*snapdp)/snaphtdp)
467        local x = n * snaphtdp - h - d
468        plusht = h + x / 2
469        plusdp = d + x / 2
470        if t then
471            t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_box,plusht,plusdp)
472        end
473    elseif method[v_max] then
474        local n = ceiled((h+d)/snaphtdp)
475        local x = n * snaphtdp - h - d
476        plusht = h + x / 2
477        plusdp = d + x / 2
478        if t then
479            t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_max,plusht,plusdp)
480        end
481    elseif method[v_min] then
482        -- we catch a lone min
483        if method.specification ~= v_min then
484            local n = floored((h+d)/snaphtdp)
485            local x = n * snaphtdp - h - d
486            plusht = h + x / 2
487            plusdp = d + x / 2
488            if plusht < 0 then
489                plusht = 0
490            end
491            if plusdp < 0 then
492                plusdp = 0
493            end
494        end
495        if t then
496            t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_min,plusht,plusdp)
497        end
498    elseif method[v_none] then
499        plusht, plusdp = 0, 0
500        if t then
501            t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_none,0,0)
502        end
503    end
504    -- for now, we actually need to tag a box and then check at several points if something ended up
505    -- at the top of a page
506    if method[v_halfline] then -- extra halfline
507        extra  = snaphtdp/2
508        plusht = plusht + extra
509        plusdp = plusdp + extra
510        if t then
511            t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_halfline,plusht,plusdp)
512        end
513    end
514    if method[v_line] then -- extra line
515        extra  = snaphtdp
516        plusht = plusht + extra
517        plusdp = plusdp + extra
518        if t then
519            t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_line,plusht,plusdp)
520        end
521    end
522    if method[v_halfline_m] then -- extra halfline
523        extra  = - snaphtdp/2
524        plusht = plusht + extra
525        plusdp = plusdp + extra
526        if t then
527            t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_halfline_m,plusht,plusdp)
528        end
529    end
530    if method[v_line_m] then -- extra line
531        extra  = - snaphtdp
532        plusht = plusht + extra
533        plusdp = plusdp + extra
534        if t then
535            t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_line_m,plusht,plusdp)
536        end
537    end
538    if method[v_first] then
539        local thebox = current
540        local id = getid(thebox)
541        if id == hlist_code then
542            thebox = validvbox(id,getlist(thebox))
543            id = thebox and getid(thebox)
544        end
545        if thebox and id == vlist_code then
546            local list = getlist(thebox)
547            local lw, lh, ld
548            for n in nexthlist, list do
549                lw, lh, ld = getwhd(n)
550                break
551            end
552            if lh then
553                local wd, ht, dp = getwhd(thebox)
554                if t then
555                    t[#t+1] = formatters["first line: height %p depth %p"](lh,ld)
556                    t[#t+1] = formatters["dimensions: height %p depth %p"](ht,dp)
557                end
558                local delta = h - lh
559                ch, cd = lh, delta + d
560                h, d = ch, cd
561                local shifted = hpack_node(getlist(current))
562                setshift(shifted,delta)
563                setlist(current,shifted)
564                done = true
565                if t then
566                    t[#t+1] = formatters["first: height %p depth %p shift %p"](ch,cd,delta)
567                end
568            elseif t then
569                t[#t+1] = "first: not done, no content"
570            end
571        elseif t then
572            t[#t+1] = "first: not done, no vbox"
573        end
574    elseif method[v_last] then
575        local thebox = current
576        local id = getid(thebox)
577        if id == hlist_code then
578            thebox = validvbox(id,getlist(thebox))
579            id = thebox and getid(thebox)
580        end
581        if thebox and id == vlist_code then
582            local list = getlist(thebox)
583            local lw, lh, ld
584            for n in nexthlist, list do
585                lw, lh, ld = getwhd(n)
586            end
587            if lh then
588                local wd, ht, dp = getwhd(thebox)
589                if t then
590                    t[#t+1] = formatters["last line: height %p depth %p" ](lh,ld)
591                    t[#t+1] = formatters["dimensions: height %p depth %p"](ht,dp)
592                end
593                local delta = d - ld
594                cd, ch = ld, delta + h
595                h, d = ch, cd
596                local shifted = hpack_node(getlist(current))
597                setshift(shifted,delta)
598                setlist(current,shifted)
599                done = true
600                if t then
601                    t[#t+1] = formatters["last: height %p depth %p shift %p"](ch,cd,delta)
602                end
603            elseif t then
604                t[#t+1] = "last: not done, no content"
605            end
606        elseif t then
607            t[#t+1] = "last: not done, no vbox"
608        end
609    end
610    if method[v_minheight] then
611        ch = floored((h-hr*snapht)/snaphtdp)*snaphtdp + plusht
612        if t then
613            t[#t+1] = formatters["minheight: %p"](ch)
614        end
615    elseif method[v_maxheight] then
616        ch = ceiled((h-hr*snapht)/snaphtdp)*snaphtdp + plusht
617        if t then
618            t[#t+1] = formatters["maxheight: %p"](ch)
619        end
620    else
621        ch = plusht
622        if t then
623            t[#t+1] = formatters["set height: %p"](ch)
624        end
625    end
626    if method[v_mindepth] then
627        cd = floored((d-dr*snapdp)/snaphtdp)*snaphtdp + plusdp
628        if t then
629            t[#t+1] = formatters["mindepth: %p"](cd)
630        end
631    elseif method[v_maxdepth] then
632        cd = ceiled((d-dr*snapdp)/snaphtdp)*snaphtdp + plusdp
633        if t then
634            t[#t+1] = formatters["maxdepth: %p"](cd)
635        end
636    else
637        cd = plusdp
638        if t then
639            t[#t+1] = formatters["set depth: %p"](cd)
640        end
641    end
642    if method[v_top] then
643        ch = ch + tlines * snaphtdp
644        if t then
645            t[#t+1] = formatters["top height: %p"](ch)
646        end
647    end
648    if method[v_bottom] then
649        cd = cd + blines * snaphtdp
650        if t then
651            t[#t+1] = formatters["bottom depth: %p"](cd)
652        end
653    end
654    local offset = method[v_offset]
655    if offset then
656        -- we need to set the attr
657        if t then
658            local wd, ht, dp = getwhd(current)
659            t[#t+1] = formatters["before offset: %p (width %p height %p depth %p)"](offset,wd,ht,dp)
660        end
661        local shifted = hpack_node(getlist(current))
662        setshift(shifted,offset)
663        setlist(current,shifted)
664        if t then
665            local wd, ht, dp = getwhd(current)
666            t[#t+1] = formatters["after offset: %p (width %p height %p depth %p)"](offset,wd,ht,dp)
667        end
668        setattr(shifted,a_snapmethod,0)
669        setattr(current,a_snapmethod,0)
670    end
671    if not height then
672        setheight(current,ch)
673        if t then
674            t[#t+1] = formatters["forced height: %p"](ch)
675        end
676    end
677    if not depth then
678        setdepth(current,cd)
679        if t then
680            t[#t+1] = formatters["forced depth: %p"](cd)
681        end
682    end
683    local lines = (ch+cd)/snaphtdp
684    if t then
685        local original = (h+d)/snaphtdp
686        local whatever = (ch+cd)/(texgetdimen("globalbodyfontstrutheight") + texgetdimen("globalbodyfontstrutdepth"))
687        t[#t+1] = formatters["final lines : %p -> %p (%p)"](original,lines,whatever)
688        t[#t+1] = formatters["final height: %p -> %p"](h,ch)
689        t[#t+1] = formatters["final depth : %p -> %p"](d,cd)
690    end
691-- todo:
692--
693--     if h < 0 or d < 0 then
694--         h = 0
695--         d = 0
696--     end
697    if t then
698        report_snapper("trace: %s type %s\n\t%\n\tt",where,nodecodes[getid(current)],t)
699    end
700    if not method[v_split] then
701        -- so extra will not be compensated at the top of a page
702        extra = 0
703    end
704    return h, d, ch, cd, lines, extra
705end
706
707local function snap_topskip(current,method)
708    local w = getwidth(current)
709    setwidth(current,0)
710    return w, 0
711end
712
713local categories = { [0] =
714    "discard",
715    "largest",
716    "force",
717    "penalty",
718    "add",
719    "disable",
720    "nowhite",
721    "goback",
722    "packed",
723    "overlay",
724    "enable",
725    "notopskip",
726}
727
728categories          = allocate(table.swapped(categories,categories))
729vspacing.categories = categories
730
731function vspacing.tocategories(str)
732    local t = { }
733    for s in gmatch(str,"[^, ]") do -- use lpeg instead
734        local n = tonumber(s)
735        if n then
736            t[categories[n]] = true
737        else
738            t[b] = true
739        end
740    end
741    return t
742end
743
744function vspacing.tocategory(str) -- can be optimized
745    if type(str) == "string" then
746        return set.tonumber(vspacing.tocategories(str))
747    else
748        return set.tonumber({ [categories[str]] = true })
749    end
750end
751
752vspacingdata.map  = vspacingdata.map  or { } -- allocate ?
753vspacingdata.skip = vspacingdata.skip or { } -- allocate ?
754
755storage.register("builders/vspacing/data/map",  vspacingdata.map,  "builders.vspacing.data.map")
756storage.register("builders/vspacing/data/skip", vspacingdata.skip, "builders.vspacing.data.skip")
757
758do -- todo: interface.variables and properties
759
760    local P, C, R, S, Cc, Cs = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.Cc, lpeg.Cs
761
762    vspacing.fixed   = false
763
764    local map        = vspacingdata.map
765    local skip       = vspacingdata.skip
766
767    local sign       = S("+-")^0
768    local multiplier = C(sign * R("09")^1) * P("*")
769    local singlefier = Cs(sign * Cc(1))
770    local separator  = S(", ")
771    local category   = P(":") * C((1-separator)^1)
772    local keyword    = C((1-category-separator)^1)
773    local splitter   = (multiplier + Cc(1)) * keyword * (category + Cc(false))
774
775    local k_fixed    = variables.fixed
776    local k_flexible = variables.flexible
777    local k_category = "category"
778    local k_penalty  = "penalty"
779    local k_order    = "order"
780
781    -- This will change: just node.write and we can store the values in skips which
782    -- then obeys grouping .. but .. we miss the amounts then as they live at the tex
783    -- end so we then also need to change that bit ... it would be interesting if we
784    -- could store in properties
785
786    local ctx_fixedblankskip         = context.fixedblankskip
787    local ctx_flexibleblankskip      = context.flexibleblankskip
788    local ctx_setblankcategory       = context.setblankcategory
789    local ctx_setblankorder          = context.setblankorder
790    local ctx_setblankpenalty        = context.setblankpenalty
791    ----- ctx_setblankhandling       = context.setblankhandling
792    local ctx_flushblankhandling     = context.flushblankhandling
793    local ctx_addpredefinedblankskip = context.addpredefinedblankskip
794    local ctx_addaskedblankskip      = context.addaskedblankskip
795    local ctx_setblankpacked         = context.setblankpacked
796
797    local ctx_pushlogger             = context.pushlogger
798    local ctx_startblankhandling     = context.startblankhandling
799    local ctx_stopblankhandling      = context.stopblankhandling
800    local ctx_poplogger              = context.poplogger
801
802    local pattern = nil
803
804    local packed  = categories.packed
805
806    local function handler(amount, keyword, detail)
807        if not keyword then
808            report_vspacing("unknown directive %a",s)
809        else
810            local mk = map[keyword]
811            if mk then
812                lpegmatch(pattern,mk)
813            elseif keyword == k_fixed then
814                ctx_fixedblankskip()
815            elseif keyword == k_flexible then
816                ctx_flexibleblankskip()
817            elseif keyword == k_category then
818                local category = tonumber(detail)
819                if category == packed then
820                    ctx_setblankpacked()
821                elseif category then
822                    ctx_setblankcategory(category)
823                    ctx_flushblankhandling()
824                end
825            elseif keyword == k_order and detail then
826                local order = tonumber(detail)
827                if order then
828                    ctx_setblankorder(order)
829                end
830            elseif keyword == k_penalty and detail then
831                local penalty = tonumber(detail)
832                if penalty then
833                    ctx_setblankpenalty(penalty)
834                end
835            else
836                amount = tonumber(amount) or 1
837                local sk = skip[keyword]
838                if sk then
839                    ctx_addpredefinedblankskip(amount,keyword)
840                else -- no check
841                    ctx_addaskedblankskip(amount,keyword)
842                end
843            end
844        end
845    end
846
847    local splitter = ((multiplier + singlefier) * keyword * (category + Cc(false))) / handler
848          pattern  = (splitter + separator^1)^0
849
850    function vspacing.analyze(str)
851        if trace_vspacing then
852            ctx_pushlogger(report_vspacing)
853            ctx_startblankhandling()
854            lpegmatch(pattern,str)
855            ctx_stopblankhandling()
856            ctx_poplogger()
857        else
858            ctx_startblankhandling()
859            lpegmatch(pattern,str)
860            ctx_stopblankhandling()
861        end
862    end
863
864    --
865
866    function vspacing.setmap(from,to)
867        map[from] = to
868    end
869
870    function vspacing.setskip(key,value,grid)
871        if value ~= "" then
872            if grid == "" then grid = value end
873            skip[key] = { value, grid }
874        end
875    end
876
877end
878
879-- implementation
880
881local trace_list, tracing_info, before, after = { }, false, "", ""
882
883local function nodes_to_string(head)
884    local current = head
885    local t       = { }
886    while current do
887        local id = getid(current)
888        local ty = nodecodes[id]
889        if id == penalty_code then
890            t[#t+1] = formatters["%s:%s"](ty,getpenalty(current))
891        elseif id == glue_code then
892            t[#t+1] = formatters["%s:%s:%p"](ty,gluecodes[getsubtype(current)],getwidth(current))
893        elseif id == kern_code then
894            t[#t+1] = formatters["%s:%p"](ty,getkern(current))
895        else
896            t[#t+1] = ty
897        end
898        current = getnext(current)
899    end
900    return concat(t," + ")
901end
902
903local function reset_tracing(head)
904    trace_list, tracing_info, before, after = { }, false, nodes_to_string(head), ""
905end
906
907local function trace_skip(str,sc,so,sp,data)
908    trace_list[#trace_list+1] = { "skip", formatters["%s | %p | category %s | order %s | penalty %s"](str, getwidth(data), sc or "-", so or "-", sp or "-") }
909    tracing_info = true
910end
911
912local function trace_natural(str,data)
913    trace_list[#trace_list+1] = { "skip", formatters["%s | %p"](str, getwidth(data)) }
914    tracing_info = true
915end
916
917local function trace_info(message, where, what)
918    trace_list[#trace_list+1] = { "info", formatters["%s: %s/%s"](message,where,what) }
919end
920
921local function trace_node(what)
922    local nt = nodecodes[getid(what)]
923    local tl = trace_list[#trace_list]
924    if tl and tl[1] == "node" then
925        trace_list[#trace_list] = { "node", formatters["%s + %s"](tl[2],nt) }
926    else
927        trace_list[#trace_list+1] = { "node", nt }
928    end
929end
930
931local function trace_done(str,data)
932    if getid(data) == penalty_code then
933        trace_list[#trace_list+1] = { "penalty", formatters["%s | %s"](str,getpenalty(data)) }
934    else
935        trace_list[#trace_list+1] = { "glue", formatters["%s | %p"](str,getwidth(data)) }
936    end
937    tracing_info = true
938end
939
940local function show_tracing(head)
941    if tracing_info then
942        after = nodes_to_string(head)
943        for i=1,#trace_list do
944            local tag, text = unpack(trace_list[i])
945            if tag == "info" then
946                report_collapser(text)
947            else
948                report_collapser("  %s: %s",tag,text)
949            end
950        end
951        report_collapser("before: %s",before)
952        report_collapser("after : %s",after)
953    end
954end
955
956-- alignment box begin_of_par vmode_par hmode_par insert penalty before_display after_display
957
958function vspacing.snapbox(n,how)
959    local sv = snapmethods[how]
960    if sv then
961        local box = getbox(n)
962        local list = getlist(box)
963        if list then
964            local s = getattr(list,a_snapmethod)
965            if s == 0 then
966                if trace_vsnapping then
967                --  report_snapper("box list not snapped, already done")
968                end
969            else
970                local wd, ht, dp = getwhd(box)
971                if false then -- todo: already_done
972                    -- assume that the box is already snapped
973                    if trace_vsnapping then
974                        report_snapper("box list already snapped at (%p,%p): %s",
975                            ht,dp,listtoutf(list))
976                    end
977                else
978                    local h, d, ch, cd, lines, extra = snap_hlist("box",box,sv,ht,dp)
979                    setprop(box,"snapper",{
980                        ht = h,
981                        dp = d,
982                        ch = ch,
983                        cd = cd,
984                        extra = extra,
985                        current = current,
986                    })
987                    setwhd(box,wd,ch,cd)
988                    if trace_vsnapping then
989                        report_snapper("box list snapped from (%p,%p) to (%p,%p) using method %a (%s) for %a (%s lines): %s",
990                            h,d,ch,cd,sv.name,sv.specification,"direct",lines,listtoutf(list))
991                    end
992                    setattr(box,a_snapmethod,0)  --
993                    setattr(list,a_snapmethod,0) -- yes or no
994                end
995            end
996        end
997    end
998end
999
1000-- I need to figure out how to deal with the prevdepth that crosses pages. In fact,
1001-- prevdepth is often quite interfering (even over a next paragraph) so I need to
1002-- figure out a trick. Maybe use something other than a rule. If we visualize we'll
1003-- see the baselineskip in action:
1004--
1005-- \blank[force,5*big] { \baselineskip1cm xxxxxxxxx \par } \page
1006-- \blank[force,5*big] { \baselineskip1cm xxxxxxxxx \par } \page
1007-- \blank[force,5*big] { \baselineskip5cm xxxxxxxxx \par } \page
1008
1009-- We can register and copy the rule instead.
1010
1011do
1012
1013    local w, h, d = 0, 0, 0
1014    ----- w, h, d = 100*65536, 65536, 65536
1015
1016    local function forced_skip(head,current,width,where,trace) -- looks old ... we have other tricks now
1017        if head == current then
1018            if getsubtype(head) == baselineskip_code then
1019                width = width - getwidth(head)
1020            end
1021        end
1022        if width == 0 then
1023            -- do nothing
1024        elseif where == "after" then
1025            head, current = insertnodeafter(head,current,new_rule(w,h,d))
1026            head, current = insertnodeafter(head,current,new_kern(width))
1027            head, current = insertnodeafter(head,current,new_rule(w,h,d))
1028        else
1029            local c = current
1030            head, current = insertnodebefore(head,current,new_rule(w,h,d))
1031            head, current = insertnodebefore(head,current,new_kern(width))
1032            head, current = insertnodebefore(head,current,new_rule(w,h,d))
1033            current = c
1034        end
1035        if trace then
1036            report_vspacing("inserting forced skip of %p",width)
1037        end
1038        return head, current
1039    end
1040
1041    -- penalty only works well when before skip
1042
1043    local discard   = categories.discard
1044    local largest   = categories.largest
1045    local force     = categories.force
1046    local penalty   = categories.penalty
1047    local add       = categories.add
1048    local disable   = categories.disable
1049    local nowhite   = categories.nowhite
1050    local goback    = categories.goback
1051    local packed    = categories.packed
1052    local overlay   = categories.overlay
1053    local enable    = categories.enable
1054    local notopskip = categories.notopskip
1055
1056    -- [whatsits][hlist][glue][glue][penalty]
1057
1058    local special_penalty_min = 32250
1059    local special_penalty_max = 35000
1060    local special_penalty_xxx =     0
1061
1062    -- this is rather messy and complex: we want to make sure that successive
1063    -- header don't break but also make sure that we have at least a decent
1064    -- break when we have succesive ones (often when testing)
1065
1066    -- todo: mark headers as such so that we can recognize them
1067
1068    local specialmethods = { }
1069    local specialmethod  = 1
1070
1071    specialmethods[1] = function(pagehead,pagetail,start,penalty)
1072        --
1073        if not pagehead or penalty < special_penalty_min or penalty > special_penalty_max then
1074            return
1075        end
1076        local current  = pagetail
1077        --
1078        -- nodes.showsimplelist(pagehead,0)
1079        --
1080        if trace_specials then
1081            report_specials("checking penalty %a",penalty)
1082        end
1083        while current do
1084            local id = getid(current)
1085            if id == penalty_code then
1086                local p = properties[current]
1087                if p then
1088                    local p = p.special_penalty
1089                    if not p then
1090                        if trace_specials then
1091                            report_specials("  regular penalty, continue")
1092                        end
1093                    elseif p == penalty then
1094                        if trace_specials then
1095                            report_specials("  context penalty %a, same level, overloading",p)
1096                        end
1097                        return special_penalty_xxx
1098                    elseif p > special_penalty_min and p < special_penalty_max then
1099                        if penalty < p then
1100                            if trace_specials then
1101                                report_specials("  context penalty %a, lower level, overloading",p)
1102                            end
1103                            return special_penalty_xxx
1104                        else
1105                            if trace_specials then
1106                                report_specials("  context penalty %a, higher level, quitting",p)
1107                            end
1108                            return
1109                        end
1110                    elseif trace_specials then
1111                        report_specials("  context penalty %a, higher level, continue",p)
1112                    end
1113                else
1114                    local p = getpenalty(current)
1115                    if p < 10000 then
1116                        -- assume some other mechanism kicks in so we seem to have content
1117                        if trace_specials then
1118                            report_specials("  regular penalty %a, quitting",p)
1119                        end
1120                        break
1121                    else
1122                        if trace_specials then
1123                            report_specials("  regular penalty %a, continue",p)
1124                        end
1125                    end
1126                end
1127            end
1128            current = getprev(current)
1129        end
1130        -- none found, so no reson to be special
1131        if trace_specials then
1132            if pagetail then
1133                report_specials("  context penalty, discarding, nothing special")
1134            else
1135                report_specials("  context penalty, discarding, nothing preceding")
1136            end
1137        end
1138        return special_penalty_xxx
1139    end
1140
1141    -- This will be replaced after 0.80+ when we have a more robust look-back and
1142    -- can look at the bigger picture.
1143
1144    -- todo: look back and when a special is there before a list is seen penalty keep ut
1145
1146    -- we now look back a lot, way too often
1147
1148    -- userskip
1149    -- lineskip
1150    -- baselineskip
1151    -- parskip
1152    -- abovedisplayskip
1153    -- belowdisplayskip
1154    -- abovedisplayshortskip
1155    -- belowdisplayshortskip
1156    -- topskip
1157    -- splittopskip
1158
1159    -- we could inject a vadjust to force a recalculation .. a mess
1160    --
1161    -- So, the next is far from robust and okay but for the moment this overlaying
1162    -- has to do. Always test this with the examples in spac-ver.mkvi!
1163
1164    local function check_experimental_overlay(head,current)
1165        local p = nil
1166        local c = current
1167        local n = nil
1168        local function overlay(p,n,mvl)
1169            local p_wd, p_ht, p_dp = getwhd(p)
1170            local n_wd, n_ht, n_dp = getwhd(n)
1171            local skips = 0
1172            --
1173            -- We deal with this at the tex end .. we don't see spacing .. enabling this code
1174            -- is probably harmless but then we need to test it.
1175            --
1176            -- we could calculate this before we call
1177            --
1178            -- problem: prev list and next list can be unconnected
1179            --
1180            local c = getnext(p)
1181            local l = c
1182            while c and c ~= n do
1183                local id = getid(c)
1184                if id == glue_code then
1185                    skips = skips + getwidth(c)
1186                elseif id == kern_code then
1187                    skips = skips + getkern(c)
1188                end
1189                l = c
1190                c = getnext(c)
1191            end
1192            local c = getprev(n)
1193            while c and c ~= n and c ~= l do
1194                local id = getid(c)
1195                if id == glue_code then
1196                    skips = skips + getwidth(c)
1197                elseif id == kern_code then
1198                    skips = skips + getkern(c)
1199                end
1200                c = getprev(c)
1201            end
1202            --
1203            local delta = n_ht + skips + p_dp
1204            texsetdimen("global","d_spac_overlay",-delta) -- for tracing
1205            -- we should adapt pagetotal ! (need a hook for that) .. now we have the wrong pagebreak
1206            local k = new_kern(-delta)
1207            head = insertnodebefore(head,n,k)
1208            if n_ht > p_ht then
1209                local k = new_kern(n_ht-p_ht)
1210                head = insertnodebefore(head,p,k)
1211            end
1212            if trace_vspacing then
1213                report_vspacing("overlaying, prev height: %p, prev depth: %p, next height: %p, skips: %p, move up: %p",p_ht,p_dp,n_ht,skips,delta)
1214            end
1215            return remove_node(head,current,true)
1216        end
1217
1218        -- goto next line
1219        while c do
1220            local id = getid(c)
1221            if id == glue_code or id == penalty_code or id == kern_code then
1222                -- skip (actually, remove)
1223                c = getnext(c)
1224            elseif id == hlist_code then
1225                n = c
1226                break
1227            else
1228                break
1229            end
1230        end
1231        if n then
1232            -- we have a next line, goto prev line
1233            c = current
1234            while c do
1235                local id = getid(c)
1236                if id == glue_code or id == penalty_code then -- kern ?
1237                    c = getprev(c)
1238                elseif id == hlist_code then
1239                    p = c
1240                    break
1241                else
1242                    break
1243                end
1244            end
1245            if not p then
1246                if a_snapmethod == a_snapvbox then
1247                    -- quit, we're not on the mvl
1248                else
1249                    -- inefficient when we're at the end of a page
1250                    local c = tonut(texlists.page_head)
1251                    while c and c ~= n do
1252                        local id = getid(c)
1253                        if id == hlist_code then
1254                            p = c
1255                        end
1256                        c = getnext(c)
1257                    end
1258                    if p and p ~= n then
1259                        return overlay(p,n,true)
1260                    end
1261                end
1262            elseif p ~= n then
1263                return overlay(p,n,false)
1264            end
1265        end
1266        -- in fact, we could try again later ... so then no remove (a few tries)
1267        return remove_node(head,current,true)
1268    end
1269
1270    local function collapser(head,where,what,trace,snap,a_snapmethod) -- maybe also pass tail
1271        if trace then
1272            reset_tracing(head)
1273        end
1274        local current           = head
1275        local oldhead           = head
1276        local glue_order        = 0
1277        local glue_data
1278        local force_glue        = false
1279        local penalty_order     = 0
1280        local penalty_data
1281        local natural_penalty
1282        local special_penalty
1283        local parskip
1284        local ignore_parskip    = false
1285        local ignore_following  = false
1286        local ignore_whitespace = false
1287        local keep_together     = false
1288        local lastsnap
1289        local pagehead
1290        local pagetail
1291        --
1292        -- todo: keep_together: between headers
1293        --
1294        local function getpagelist()
1295            if not pagehead then
1296                pagehead = texlists.page_head
1297                if pagehead then
1298                    pagehead = tonut(pagehead)
1299                    pagetail = find_node_tail(pagehead) -- no texlists.page_tail yet-- no texlists.page_tail yet
1300                end
1301            end
1302        end
1303        --
1304        local function compensate(n)
1305            local g = 0
1306            while n and getid(n) == glue_code do
1307                g = g + getwidth(n)
1308                n = getnext(n)
1309            end
1310            if n then
1311                local p = getprop(n,"snapper")
1312                if p then
1313                    local extra = p.extra
1314                    if extra and extra < 0 then -- hm, extra can be unset ... needs checking
1315                        local h = p.ch -- getheight(n)
1316                        -- maybe an extra check
1317                     -- if h - extra < g then
1318                            setheight(n,h-2*extra)
1319                            p.extra = 0
1320                            if trace_vsnapping then
1321                                report_snapper("removed extra space at top: %p",extra)
1322                            end
1323                     -- end
1324                    end
1325                end
1326                return n
1327            end
1328        end
1329        --
1330        local function removetopsnap()
1331            getpagelist()
1332            if pagehead then
1333                local n = pagehead and compensate(pagehead)
1334                if n and n ~= pagetail then
1335                    local p = getprop(pagetail,"snapper")
1336                    if p then
1337                        local e = p.extra
1338                        if e and e < 0 then
1339                            local t = texget("pagetotal")
1340                            if t > 0 then
1341                                local g = texget("pagegoal") -- 1073741823 is signal
1342                                local d = g - t
1343                                if d < -e then
1344                                    local penalty = new_penalty(1000000)
1345                                    setlink(penalty,head)
1346                                    head = penalty
1347                                    report_snapper("force pagebreak due to extra space at bottom: %p",e)
1348                                end
1349                            end
1350                        end
1351                    end
1352                end
1353            elseif head then
1354                compensate(head)
1355            end
1356        end
1357        --
1358        local function getavailable()
1359            getpagelist()
1360            if pagehead then
1361                local t = texget("pagetotal")
1362                if t > 0 then
1363                    local g = texget("pagegoal")
1364                    return g - t
1365                end
1366            end
1367            return false
1368        end
1369        --
1370        local function flush(why)
1371            if penalty_data then
1372                local p = new_penalty(penalty_data)
1373                if trace then
1374                    trace_done("flushed due to " .. why,p)
1375                end
1376                if penalty_data >= 10000 then -- or whatever threshold?
1377                    local prev = getprev(current)
1378                    if getid(prev) == glue_code then -- maybe go back more, or maybe even push back before any glue
1379                        -- tricky case: spacing/grid-007.tex: glue penalty glue
1380                        head = insertnodebefore(head,prev,p)
1381                    else
1382                        head = insertnodebefore(head,current,p)
1383                    end
1384                else
1385                    head = insertnodebefore(head,current,p)
1386                end
1387             -- if penalty_data > special_penalty_min and penalty_data < special_penalty_max then
1388                local props = properties[p]
1389                if props then
1390                    props.special_penalty = special_penalty or penalty_data
1391                else
1392                    properties[p] = {
1393                        special_penalty = special_penalty or penalty_data
1394                    }
1395                end
1396             -- end
1397            end
1398            if glue_data then
1399                if force_glue then
1400                    if trace then
1401                        trace_done("flushed due to forced " .. why,glue_data)
1402                    end
1403                    head = forced_skip(head,current,getwidth(glue_data,width),"before",trace)
1404                    flushnode(glue_data)
1405                else
1406                    local width, stretch, shrink = getglue(glue_data)
1407                    if width ~= 0 then
1408                        if trace then
1409                            trace_done("flushed due to non zero " .. why,glue_data)
1410                        end
1411                        head = insertnodebefore(head,current,glue_data)
1412                    elseif stretch ~= 0 or shrink ~= 0 then
1413                        if trace then
1414                            trace_done("flushed due to stretch/shrink in" .. why,glue_data)
1415                        end
1416                        head = insertnodebefore(head,current,glue_data)
1417                    else
1418                     -- report_vspacing("needs checking (%s): %p",gluecodes[getsubtype(glue_data)],w)
1419                        flushnode(glue_data)
1420                    end
1421                end
1422            end
1423
1424            if trace then
1425                trace_node(current)
1426            end
1427            glue_order, glue_data, force_glue = 0, nil, false
1428            penalty_order, penalty_data, natural_penalty = 0, nil, nil
1429            parskip, ignore_parskip, ignore_following, ignore_whitespace = nil, false, false, false
1430        end
1431        --
1432        if trace_vsnapping then
1433            report_snapper("global ht/dp = %p/%p, local ht/dp = %p/%p",
1434                texgetdimen("globalbodyfontstrutheight"),
1435                texgetdimen("globalbodyfontstrutdepth"),
1436                texgetdimen("bodyfontstrutheight"),
1437                texgetdimen("bodyfontstrutdepth")
1438            )
1439        end
1440        if trace then
1441            trace_info("start analyzing",where,what)
1442        end
1443        if snap and where == "page" then
1444            removetopsnap()
1445        end
1446        while current do
1447            local id = getid(current)
1448            if id == hlist_code or id == vlist_code then
1449                -- needs checking, why so many calls
1450                if snap then
1451                    lastsnap = nil
1452                    local list = getlist(current)
1453                    local s = getattr(current,a_snapmethod)
1454                    if not s then
1455                    --  if trace_vsnapping then
1456                    --      report_snapper("mvl list not snapped")
1457                    --  end
1458                    elseif s == 0 then
1459                        if trace_vsnapping then
1460                            report_snapper("mvl %a not snapped, already done: %s",nodecodes[id],listtoutf(list))
1461                        end
1462                    else
1463                        local sv = snapmethods[s]
1464                        if sv then
1465                            -- check if already snapped
1466                            local done = list and already_done(id,list,a_snapmethod)
1467                            if done then
1468                                -- assume that the box is already snapped
1469                                if trace_vsnapping then
1470                                    local w, h, d = getwhd(current)
1471                                    report_snapper("mvl list already snapped at (%p,%p): %s",h,d,listtoutf(list))
1472                                end
1473                            else
1474                                local h, d, ch, cd, lines, extra = snap_hlist("mvl",current,sv,false,false)
1475                                lastsnap = {
1476                                    ht = h,
1477                                    dp = d,
1478                                    ch = ch,
1479                                    cd = cd,
1480                                    extra = extra,
1481                                    current = current,
1482                                }
1483                                setprop(current,"snapper",lastsnap)
1484                                if trace_vsnapping then
1485                                    report_snapper("mvl %a snapped from (%p,%p) to (%p,%p) using method %a (%s) for %a (%s lines): %s",
1486                                        nodecodes[id],h,d,ch,cd,sv.name,sv.specification,where,lines,listtoutf(list))
1487                                end
1488                            end
1489                        elseif trace_vsnapping then
1490                            report_snapper("mvl %a not snapped due to unknown snap specification: %s",nodecodes[id],listtoutf(list))
1491                        end
1492                        setattr(current,a_snapmethod,0)
1493                    end
1494                else
1495                    --
1496                end
1497            --  tex.prevdepth = 0
1498                flush("list")
1499                current = getnext(current)
1500            elseif id == penalty_code then
1501             -- natural_penalty = getpenalty(current)
1502             -- if trace then
1503             --     trace_done("removed penalty",current)
1504             -- end
1505             -- head, current = remove_node(head,current,true)
1506                current = getnext(current)
1507            elseif id == kern_code then
1508                if snap and trace_vsnapping and getkern(current) ~= 0 then
1509                    report_snapper("kern of %p kept",getkern(current))
1510                end
1511                flush("kern")
1512                current = getnext(current)
1513            elseif id == glue_code then
1514                local subtype = getsubtype(current)
1515                if subtype == userskip_code then
1516                    local sc = getattr(current,a_skipcategory)   -- has no default, no unset (yet)
1517                    local so = getattr(current,a_skiporder) or 1 -- has  1 default, no unset (yet)
1518                    local sp = getattr(current,a_skippenalty)    -- has no default, no unset (yet)
1519                    if sp and sc == penalty then
1520                        if where == "page" then
1521                            getpagelist()
1522                            local p = specialmethods[specialmethod](pagehead,pagetail,current,sp)
1523                            if p then
1524                             -- todo: other tracer
1525                             --
1526                             -- if trace then
1527                             --     trace_skip("previous special penalty %a is changed to %a using method %a",sp,p,specialmethod)
1528                             -- end
1529                                special_penalty = sp
1530                                sp = p
1531                            end
1532                        end
1533                        if not penalty_data then
1534                            penalty_data = sp
1535                        elseif penalty_order < so then
1536                            penalty_order, penalty_data = so, sp
1537                        elseif penalty_order == so and sp > penalty_data then
1538                            penalty_data = sp
1539                        end
1540                        if trace then
1541                            trace_skip("penalty in skip",sc,so,sp,current)
1542                        end
1543                        head, current = remove_node(head,current,true)
1544                    elseif not sc then  -- if not sc then
1545                        if glue_data then
1546                            if trace then
1547                                trace_done("flush",glue_data)
1548                            end
1549                            head = insertnodebefore(head,current,glue_data)
1550                            if trace then
1551                                trace_natural("natural",current)
1552                            end
1553                            current = getnext(current)
1554                        else
1555                            -- not look back across head
1556                            -- todo: prev can be whatsit (latelua)
1557                            local previous = getprev(current)
1558                            if previous and getid(previous) == glue_code and getsubtype(previous) == userskip_code then
1559                                local pwidth, pstretch, pshrink, pstretch_order, pshrink_order = getglue(previous)
1560                                local cwidth, cstretch, cshrink, cstretch_order, cshrink_order = getglue(current)
1561                                if pstretch_order == 0 and pshrink_order == 0 and cstretch_order == 0 and cshrink_order == 0 then
1562                                    setglue(previous,pwidth + cwidth, pstretch + cstretch, pshrink  + cshrink)
1563                                    if trace then
1564                                        trace_natural("removed",current)
1565                                    end
1566                                    head, current = remove_node(head,current,true)
1567                                    if trace then
1568                                        trace_natural("collapsed",previous)
1569                                    end
1570                                else
1571                                    if trace then
1572                                        trace_natural("filler",current)
1573                                    end
1574                                    current = getnext(current)
1575                                end
1576                            else
1577                                if trace then
1578                                    trace_natural("natural (no prev)",current)
1579                                end
1580                                current = getnext(current)
1581                            end
1582                        end
1583                        glue_order, glue_data = 0, nil
1584                    elseif sc == disable or sc == enable then
1585                        local next = getnext(current)
1586                        if next then
1587                            ignore_following = sc == disable
1588                            if trace then
1589                                trace_skip(sc == disable and "disable" or "enable",sc,so,sp,current)
1590                            end
1591                            head, current = remove_node(head,current,true)
1592                        else
1593                            current = next
1594                        end
1595                    elseif sc == packed then
1596                        if trace then
1597                            trace_skip("packed",sc,so,sp,current)
1598                        end
1599                        -- can't happen !
1600                        head, current = remove_node(head,current,true)
1601                    elseif sc == nowhite then
1602                        local next = getnext(current)
1603                        if next then
1604                            ignore_whitespace = true
1605                            head, current = remove_node(head,current,true)
1606                        else
1607                            current = next
1608                        end
1609                    elseif sc == discard then
1610                        if trace then
1611                            trace_skip("discard",sc,so,sp,current)
1612                        end
1613                        head, current = remove_node(head,current,true)
1614                    elseif sc == overlay then
1615                        -- todo (overlay following line over previous
1616                        if trace then
1617                            trace_skip("overlay",sc,so,sp,current)
1618                        end
1619                            -- beware: head can actually be after the affected nodes as
1620                            -- we look back ... some day head will the real head
1621                        head, current = check_experimental_overlay(head,current,a_snapmethod)
1622                    elseif ignore_following then
1623                        if trace then
1624                            trace_skip("disabled",sc,so,sp,current)
1625                        end
1626                        head, current = remove_node(head,current,true)
1627                    elseif not glue_data then
1628                        if trace then
1629                            trace_skip("assign",sc,so,sp,current)
1630                        end
1631                        glue_order = so
1632                        head, current, glue_data = remove_node(head,current)
1633                    elseif glue_order < so then
1634                        if trace then
1635                            trace_skip("force",sc,so,sp,current)
1636                        end
1637                        glue_order = so
1638                        flushnode(glue_data)
1639                        head, current, glue_data = remove_node(head,current)
1640                    elseif glue_order == so then
1641                        -- is now exclusive, maybe support goback as combi, else why a set
1642                        if sc == largest then
1643                            local cw = getwidth(current)
1644                            local gw = getwidth(glue_data)
1645                            if cw > gw then
1646                                if trace then
1647                                    trace_skip("largest",sc,so,sp,current)
1648                                end
1649                                flushnode(glue_data)
1650                                head, current, glue_data = remove_node(head,current)
1651                            else
1652                                if trace then
1653                                    trace_skip("remove smallest",sc,so,sp,current)
1654                                end
1655                                head, current = remove_node(head,current,true)
1656                            end
1657                        elseif sc == goback then
1658                            if trace then
1659                                trace_skip("goback",sc,so,sp,current)
1660                            end
1661                            flushnode(glue_data)
1662                            head, current, glue_data = remove_node(head,current)
1663                        elseif sc == force then
1664                            -- last one counts, some day we can provide an accumulator and largest etc
1665                            -- but not now
1666                            if trace then
1667                                trace_skip("force",sc,so,sp,current)
1668                            end
1669                            flushnode(glue_data)
1670                            head, current, glue_data = remove_node(head,current)
1671                        elseif sc == penalty then
1672                            if trace then
1673                                trace_skip("penalty",sc,so,sp,current)
1674                            end
1675                            flushnode(glue_data)
1676                            glue_data = nil
1677                            head, current = remove_node(head,current,true)
1678                        elseif sc == add then
1679                            if trace then
1680                                trace_skip("add",sc,so,sp,current)
1681                            end
1682                            local cwidth, cstretch, cshrink = getglue(current)
1683                            local gwidth, gstretch, gshrink = getglue(glue_data)
1684                            setglue(glue_data,gwidth + cwidth, gstretch + cstretch,gshrink + cshrink)
1685                            -- toto: order
1686                            head, current = remove_node(head,current,true)
1687                        else
1688                            if trace then
1689                                trace_skip("unknown",sc,so,sp,current)
1690                            end
1691                            head, current = remove_node(head,current,true)
1692                        end
1693                    else
1694                        if trace then
1695                            trace_skip("unknown",sc,so,sp,current)
1696                        end
1697                        head, current = remove_node(head,current,true)
1698                    end
1699                    if sc == force then
1700                        force_glue = true
1701                    end
1702                elseif subtype == lineskip_code then
1703                    if snap then
1704                        local s = getattr(current,a_snapmethod)
1705                        if s and s ~= 0 then
1706                            setattr(current,a_snapmethod,0)
1707                            setwidth(current,0)
1708                            if trace_vsnapping then
1709                                report_snapper("lineskip set to zero")
1710                            end
1711                        else
1712                            if trace then
1713                                trace_skip("lineskip",sc,so,sp,current)
1714                            end
1715                            flush("lineskip")
1716                        end
1717                    else
1718                        if trace then
1719                            trace_skip("lineskip",sc,so,sp,current)
1720                        end
1721                        flush("lineskip")
1722                    end
1723                    current = getnext(current)
1724                elseif subtype == baselineskip_code then
1725                    if snap then
1726                        local s = getattr(current,a_snapmethod)
1727                        if s and s ~= 0 then
1728                            setattr(current,a_snapmethod,0)
1729                            setwidth(current,0)
1730                            if trace_vsnapping then
1731                                report_snapper("baselineskip set to zero")
1732                            end
1733                        else
1734                            if trace then
1735                                trace_skip("baselineskip",sc,so,sp,current)
1736                            end
1737                            flush("baselineskip")
1738                        end
1739                    else
1740                        if trace then
1741                            trace_skip("baselineskip",sc,so,sp,current)
1742                        end
1743                        flush("baselineskip")
1744                    end
1745                    current = getnext(current)
1746                elseif subtype == parskip_code then
1747                    -- parskip always comes later
1748                    if ignore_whitespace then
1749                        if trace then
1750                            trace_natural("ignored parskip",current)
1751                        end
1752                        head, current = remove_node(head,current,true)
1753                    elseif glue_data then
1754                        local w = getwidth(current)
1755                        if (w ~= 0) and (w > getwidth(glue_data)) then
1756                            glue_data = current
1757                            if trace then
1758                                trace_natural("taking parskip",current)
1759                            end
1760                            head, current = remove_node(head,current)
1761                        else
1762                            if trace then
1763                                trace_natural("removed parskip",current)
1764                            end
1765                            head, current = remove_node(head,current,true)
1766                        end
1767                    else
1768                        if trace then
1769                            trace_natural("honored parskip",current)
1770                        end
1771                        head, current, glue_data = remove_node(head,current)
1772                    end
1773                elseif subtype == topskip_code or subtype == splittopskip_code then
1774                    local next = getnext(current)
1775                    if next and getattr(next,a_skipcategory) == notopskip then
1776                        nuts.setglue(current) -- zero
1777                    end
1778                    if snap then
1779                        local s = getattr(current,a_snapmethod)
1780                        if s and s ~= 0 then
1781                            setattr(current,a_snapmethod,0)
1782                            local sv = snapmethods[s]
1783                            local w, cw = snap_topskip(current,sv)
1784                            if trace_vsnapping then
1785                                report_snapper("topskip snapped from %p to %p for %a",w,cw,where)
1786                            end
1787                        else
1788                            if trace then
1789                                trace_skip("topskip",sc,so,sp,current)
1790                            end
1791                            flush("topskip")
1792                        end
1793                    else
1794                        if trace then
1795                            trace_skip("topskip",sc,so,sp,current)
1796                        end
1797                        flush("topskip")
1798                    end
1799                    current = getnext(current)
1800                elseif subtype == abovedisplayskip_code and remove_math_skips then
1801                    --
1802                    if trace then
1803                        trace_skip("above display skip (normal)",sc,so,sp,current)
1804                    end
1805                    flush("above display skip (normal)")
1806                    current = getnext(current)
1807                    --
1808                elseif subtype == belowdisplayskip_code and remove_math_skips then
1809                    --
1810                    if trace then
1811                        trace_skip("below display skip (normal)",sc,so,sp,current)
1812                    end
1813                    flush("below display skip (normal)")
1814                    current = getnext(current)
1815                   --
1816                elseif subtype == abovedisplayshortskip_code and remove_math_skips then
1817                    --
1818                    if trace then
1819                        trace_skip("above display skip (short)",sc,so,sp,current)
1820                    end
1821                    flush("above display skip (short)")
1822                    current = getnext(current)
1823                    --
1824                elseif subtype == belowdisplayshortskip_code and remove_math_skips then
1825                    --
1826                    if trace then
1827                        trace_skip("below display skip (short)",sc,so,sp,current)
1828                    end
1829                    flush("below display skip (short)")
1830                    current = getnext(current)
1831                    --
1832                else -- other glue
1833                    if snap and trace_vsnapping then
1834                        local w = getwidth(current)
1835                        if w ~= 0 then
1836                            report_snapper("glue %p of type %a kept",w,gluecodes[subtype])
1837                        end
1838                    end
1839                    if trace then
1840                        trace_skip(formatters["glue of type %a"](subtype),sc,so,sp,current)
1841                    end
1842                    flush("some glue")
1843                    current = getnext(current)
1844                end
1845            else
1846                flush(formatters["node with id %a"](id))
1847                current = getnext(current)
1848            end
1849        end
1850        if trace then
1851            trace_info("stop analyzing",where,what)
1852        end
1853     -- if natural_penalty and (not penalty_data or natural_penalty > penalty_data) then
1854     --     penalty_data = natural_penalty
1855     -- end
1856        if trace and (glue_data or penalty_data) then
1857            trace_info("start flushing",where,what)
1858        end
1859        local tail
1860        if penalty_data then
1861            tail = find_node_tail(head)
1862            local p = new_penalty(penalty_data)
1863            if trace then
1864                trace_done("result",p)
1865            end
1866            setlink(tail,p)
1867         -- if penalty_data > special_penalty_min and penalty_data < special_penalty_max then
1868                local props = properties[p]
1869                if props then
1870                    props.special_penalty = special_penalty or penalty_data
1871                else
1872                    properties[p] = {
1873                        special_penalty = special_penalty or penalty_data
1874                    }
1875                end
1876         -- end
1877        end
1878        if glue_data then
1879            if not tail then tail = find_node_tail(head) end
1880            if trace then
1881                trace_done("result",glue_data)
1882            end
1883            if force_glue then
1884                head, tail = forced_skip(head,tail,getwidth(glue_data),"after",trace)
1885                flushnode(glue_data)
1886                glue_data = nil
1887            elseif tail then
1888                setlink(tail,glue_data)
1889            else
1890                head = glue_data
1891            end
1892            texnest[texnest.ptr].prevdepth = 0 -- appending to the list bypasses tex's prevdepth handler
1893        end
1894        if trace then
1895            if glue_data or penalty_data then
1896                trace_info("stop flushing",where,what)
1897            end
1898            show_tracing(head)
1899            if oldhead ~= head then
1900                trace_info("head has been changed from %a to %a",nodecodes[getid(oldhead)],nodecodes[getid(head)])
1901            end
1902        end
1903        return head
1904    end
1905
1906    local stackhead, stacktail, stackhack = nil, nil, false
1907
1908    local function report(message,where,lst)
1909        if lst and where then
1910            report_vspacing(message,where,count_nodes(lst,true),nodeidstostring(lst))
1911        else
1912            report_vspacing(message,count_nodes(lst,true),nodeidstostring(lst))
1913        end
1914    end
1915
1916    -- ugly code: we get partial lists (check if this stack is still okay) ... and we run
1917    -- into temp nodes (sigh)
1918
1919    local forceflush = false
1920
1921    function vspacing.pagehandler(newhead,where)
1922        if newhead then
1923            local newtail = find_node_tail(newhead) -- best pass that tail, known anyway
1924            local flush = false
1925            stackhack = true -- todo: only when grid snapping once enabled
1926            -- todo: fast check if head = tail
1927            for n, id, subtype in nextnode, newhead do -- we could just look for glue nodes
1928                if id ~= glue_code then
1929                    flush = true
1930                elseif subtype == userskip_code then
1931                    if getattr(n,a_skipcategory) then
1932                        stackhack = true
1933                    else
1934                        flush = true
1935                    end
1936                elseif subtype == parskip_code then
1937                    -- if where == new_graf then ... end
1938                    if texgetcount("c_spac_vspacing_ignore_parskip") > 0 then
1939                     -- texsetcount("c_spac_vspacing_ignore_parskip",0)
1940                        setglue(n)
1941                     -- maybe removenode
1942                    end
1943                end
1944            end
1945            texsetcount("c_spac_vspacing_ignore_parskip",0)
1946
1947            if forceflush then
1948                forceflush = false
1949                flush      = true
1950            end
1951
1952            if flush then
1953                if stackhead then
1954                    if trace_collect_vspacing then report("%s > appending %s nodes to stack (final): %s",where,newhead) end
1955                    setlink(stacktail,newhead)
1956                    newhead   = stackhead
1957                    stackhead = nil
1958                    stacktail = nil
1959                end
1960                if stackhack then
1961                    stackhack = false
1962                    if trace_collect_vspacing then report("%s > processing %s nodes: %s",where,newhead) end
1963                    newhead = collapser(newhead,"page",where,trace_page_vspacing,true,a_snapmethod)
1964                else
1965                    if trace_collect_vspacing then report("%s > flushing %s nodes: %s",where,newhead) end
1966                end
1967                return newhead
1968            else
1969                if stackhead then
1970                    if trace_collect_vspacing then report("%s > appending %s nodes to stack (intermediate): %s",where,newhead) end
1971                    setlink(stacktail,newhead)
1972                else
1973                    if trace_collect_vspacing then report("%s > storing %s nodes in stack (initial): %s",where,newhead) end
1974                    stackhead = newhead
1975                end
1976                stacktail = newtail
1977            end
1978        end
1979        return nil
1980    end
1981
1982    function vspacing.pageoverflow()
1983        local h = 0
1984        if stackhead then
1985            for n, id in nextnode, stackhead do
1986                if id == glue_code then
1987                    h = h + getwidth(n)
1988                elseif id == kern_code then
1989                    h = h + getkern(n)
1990                end
1991            end
1992        end
1993        return h
1994    end
1995
1996    function vspacing.forcepageflush()
1997        forceflush = true
1998    end
1999
2000    local ignore = table.tohash {
2001        "split_keep",
2002        "split_off",
2003     -- "vbox",
2004    }
2005
2006    function vspacing.vboxhandler(head,where)
2007        if head and not ignore[where] and getnext(head) then
2008            head = collapser(head,"vbox",where,trace_vbox_vspacing,true,a_snapvbox) -- todo: local snapper
2009        end
2010        return head
2011    end
2012
2013    function vspacing.collapsevbox(n,aslist) -- for boxes but using global a_snapmethod
2014        local box = getbox(n)
2015        if box then
2016            local list = getlist(box)
2017            if list then
2018                list = collapser(list,"snapper","vbox",trace_vbox_vspacing,true,a_snapmethod)
2019                if aslist then
2020                    setlist(box,list) -- beware, dimensions of box are wrong now
2021                else
2022                    setlist(box,vpack_node(list))
2023                end
2024            end
2025        end
2026    end
2027
2028end
2029
2030-- This one is needed to prevent bleeding of prevdepth to the next page
2031-- which doesn't work well with forced skips. I'm not that sure if the
2032-- following is a good way out.
2033
2034do
2035
2036    local outer   = texnest[0]
2037
2038    local enabled = true
2039    local trace   = false
2040    local report  = logs.reporter("vspacing")
2041
2042    trackers.register("vspacing.synchronizepage",function(v)
2043        trace = v
2044    end)
2045
2046    directives.register("vspacing.synchronizepage",function(v)
2047        enabled = v
2048    end)
2049
2050    local ignoredepth = -65536000
2051
2052    -- A previous version analyzed the number of lines moved to the next page in
2053    -- synchronizepage because prevgraf is unreliable in that case. However, we cannot
2054    -- tweak that parameter because it is also used in postlinebreak and hangafter, so
2055    -- there is a danger for interference. Therefore we now do it dynamically.
2056
2057    -- We can also support other lists but there prevgraf probably is ok.
2058
2059    function vspacing.getnofpreviouslines(head)
2060        if enabled then
2061            if not thead then
2062                head = texlists.page_head
2063            end
2064            local noflines = 0
2065            if head then
2066                local tail = find_node_tail(tonut(head))
2067                while tail do
2068                    local id = getid(tail)
2069                    if id == hlist_code then
2070                        if getsubtype(tail) == linelist_code then
2071                            noflines = noflines + 1
2072                        else
2073                            break
2074                        end
2075                    elseif id == vlist_code then
2076                        break
2077                    elseif id == glue_code then
2078                        local subtype = getsubtype(tail)
2079                        if subtype == baselineskip_code or subtype == lineskip_code then
2080                            -- we're ok
2081                        elseif subtype == parskip_code then
2082                            if getwidth(tail) > 0 then
2083                                break
2084                            else
2085                                -- we assume we're ok
2086                            end
2087                        end
2088                    elseif id == penalty_code then
2089                        -- we're probably ok
2090                    elseif id == rule_code or id == kern_code then
2091                        break
2092                    else
2093                        -- ins, mark, boundary, whatsit
2094                    end
2095                    tail = getprev(tail)
2096                end
2097            end
2098            return noflines
2099        end
2100    end
2101
2102    interfaces.implement {
2103        name    = "getnofpreviouslines",
2104        public  = true,
2105        actions = vspacing.getnofpreviouslines,
2106    }
2107
2108    function vspacing.synchronizepage()
2109        if enabled then
2110            if trace then
2111                local newdepth = outer.prevdepth
2112                local olddepth = newdepth
2113                if not texlists.page_head then
2114                    newdepth = ignoredepth
2115                    texset("prevdepth",ignoredepth)
2116                    outer.prevdepth = ignoredepth
2117                end
2118                report("page %i, prevdepth %p => %p",texgetcount("realpageno"),olddepth,newdepth)
2119             -- report("list %s",nodes.idsandsubtypes(head))
2120            else
2121                if not texlists.page_head then
2122                    texset("prevdepth",ignoredepth)
2123                    outer.prevdepth = ignoredepth
2124                end
2125            end
2126        end
2127    end
2128
2129    local trace       = false
2130    local last        = nil
2131    local vmode_code  = tex.modelevels.vertical
2132    local temp_code   = nodecodes.temp
2133    local texgetnest  = tex.getnest
2134    local texgetlist  = tex.getlist
2135    local getnodetail = nodes.tail
2136
2137    trackers.register("vspacing.forcestrutdepth",function(v) trace = v end)
2138
2139    -- abs : negative is inner
2140
2141    function vspacing.checkstrutdepth(depth)
2142        local nest = texgetnest()
2143        if abs(nest.mode) == vmode_code and nest.head then
2144            local tail = nest.tail
2145            local id   = tail.id
2146            if id == hlist_code then
2147                if tail.depth < depth then
2148                    tail.depth = depth
2149                end
2150                nest.prevdepth = depth
2151            elseif id == temp_code and texgetnest("ptr") == 0 then
2152                local head = texgetlist("page_head")
2153                if head then
2154                    tail = getnodetail(head)
2155                    if tail and tail.id == hlist_code then
2156                        if tail.depth < depth then
2157                            tail.depth = depth
2158                        end
2159                        nest.prevdepth = depth
2160                        -- only works in lmtx
2161                        texset("pagedepth",depth)
2162                    end
2163                end
2164            end
2165        end
2166    end
2167
2168    interfaces.implement {
2169        name      = "checkstrutdepth",
2170        arguments = "dimension",
2171        actions   = vspacing.checkstrutdepth,
2172    }
2173
2174    function vspacing.forcestrutdepth(n,depth,trace_mode,plus)
2175        local box = texgetbox(n)
2176        if box then
2177            box = tonut(box)
2178            local head = getlist(box)
2179            if head then
2180                local tail = find_node_tail(head)
2181                if tail then
2182                    if getid(tail) == hlist_code then
2183                        local dp = getdepth(tail)
2184                        if dp < depth then
2185                            setdepth(tail,depth)
2186                            outer.prevdepth = depth
2187                            if trace or trace_mode > 0 then
2188                                nuts.setvisual(tail,"depth")
2189                            end
2190                        end
2191                    end
2192                    last = nil
2193                    if plus then
2194                        -- penalty / skip ...
2195                        local height = 0
2196                        local sofar  = 0
2197                        local same   = false
2198                        local seen   = false
2199                        local list   = { }
2200                              last   = nil
2201                        while tail do
2202                            local id = getid(tail)
2203                            if id == hlist_code or id == vlist_code then
2204                                local w, h, d = getwhd(tail)
2205                                height = height + h + d + sofar
2206                                sofar  = 0
2207                                last   = tail
2208                            elseif id == kern_code then
2209                                sofar = sofar + getkern(tail)
2210                            elseif id == glue_code then
2211                                if seen then
2212                                    sofar = sofar + getwidth(tail)
2213                                    seen  = false
2214                                else
2215                                    break
2216                                end
2217                            elseif id == penalty_code then
2218                                local p = getpenalty(tail)
2219                                if p >= 10000 then
2220                                    same = true
2221                                    seen = true
2222                                else
2223                                    break
2224                                end
2225                            else
2226                                break
2227                            end
2228                            tail = getprev(tail)
2229                        end
2230                        texsetdimen("global","d_spac_prevcontent",same and height or 0)
2231                    end
2232                end
2233            end
2234        end
2235    end
2236
2237    function vspacing.pushatsame()
2238        -- needs better checking !
2239        if last then -- setsplit
2240            nuts.setnext(getprev(last))
2241            nuts.setprev(last)
2242        end
2243    end
2244
2245    function vspacing.popatsame()
2246        -- needs better checking !
2247        nuts.write(last)
2248    end
2249
2250end
2251
2252-- interface
2253
2254do
2255
2256    implement {
2257        name      = "vspacing",
2258        actions   = vspacing.analyze,
2259        scope     = "private",
2260        arguments = "string"
2261    }
2262
2263    implement {
2264        name      = "synchronizepage",
2265        actions   = vspacing.synchronizepage,
2266        scope     = "private"
2267    }
2268
2269    implement {
2270        name      = "forcestrutdepth",
2271        arguments = { "integer", "dimension", "integer" },
2272        actions   = vspacing.forcestrutdepth,
2273        scope     = "private"
2274    }
2275
2276    implement {
2277        name      = "forcestrutdepthplus",
2278        arguments = { "integer", "dimension", "integer", true },
2279        actions   = vspacing.forcestrutdepth,
2280        scope     = "private"
2281    }
2282
2283    implement {
2284        name      = "pushatsame",
2285        actions   = vspacing.pushatsame,
2286        scope     = "private"
2287    }
2288
2289    implement {
2290        name      = "popatsame",
2291        actions   = vspacing.popatsame,
2292        scope     = "private"
2293    }
2294
2295    implement {
2296        name      = "vspacingsetamount",
2297        actions   = vspacing.setskip,
2298        scope     = "private",
2299        arguments = "string",
2300    }
2301
2302    implement {
2303        name      = "vspacingdefine",
2304        actions   = vspacing.setmap,
2305        scope     = "private",
2306        arguments = "2 strings",
2307    }
2308
2309    implement {
2310        name      = "vspacingcollapse",
2311        actions   = vspacing.collapsevbox,
2312        scope     = "private",
2313        arguments = "integer"
2314    }
2315
2316    implement {
2317        name      = "vspacingcollapseonly",
2318        actions   = vspacing.collapsevbox,
2319        scope     = "private",
2320        arguments = { "integer", true }
2321    }
2322
2323    implement {
2324        name      = "vspacingsnap",
2325        actions   = vspacing.snapbox,
2326        scope     = "private",
2327        arguments = { "integer", "integer" }
2328    }
2329
2330    implement {
2331        name      = "definesnapmethod",
2332        actions   = vspacing.definesnapmethod,
2333        scope     = "private",
2334        arguments = "2 strings",
2335    }
2336
2337 -- local remove_node    = nodes.remove
2338 -- local find_node_tail = nodes.tail
2339 --
2340 -- interfaces.implement {
2341 --     name    = "fakenextstrutline",
2342 --     actions = function()
2343 --         local head = texlists.page_head
2344 --         if head then
2345 --             local head = remove_node(head,find_node_tail(head),true)
2346 --             texlists.page_head = head
2347 --             buildpage()
2348 --         end
2349 --     end
2350 -- }
2351
2352    implement {
2353        name    = "removelastline",
2354        actions = function()
2355            local head = texlists.page_head
2356            if head then
2357                local tail = find_node_tail(head)
2358                if tail then
2359                    -- maybe check for hlist subtype 1
2360                    local head = remove_node(head,tail,true)
2361                    texlists.page_head = head
2362                    buildpage()
2363                end
2364            end
2365        end
2366    }
2367
2368    implement {
2369        name    = "showpagelist", -- will improve
2370        actions = function()
2371            local head = texlists.page_head
2372            if head then
2373                print("start")
2374                while head do
2375                    print("  " .. tostring(head))
2376                    head = head.next
2377                end
2378            end
2379        end
2380    }
2381
2382    implement {
2383        name    = "pageoverflow",
2384        actions = { vspacing.pageoverflow, context }
2385    }
2386
2387    implement {
2388        name    = "forcepageflush",
2389        actions = vspacing.forcepageflush
2390    }
2391
2392end
2393