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
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
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
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
176local new_rule = nodepool.rule
177
178local nodecodes = nodes.nodecodes
179local gluecodes = nodes.gluecodes
180
181
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
288 t.specification = method
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
313 else
314 return nil
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
324 end
325end
326
327
328
329local function already_done(parentid,list,a_snapmethod)
330
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
342
343
344
345
346
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
352
353 else
354 return false
355 end
356 end
357 end
358 return false
359end
360
361
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
380
381local function fixedprofile(current)
382 local profiling = builders.profiling
383 return profiling and profiling.fixedprofile(current)
384end
385
386
387
388
389
390
391
392
393
394
395
396
397local function snap_hlist(where,current,method,height,depth)
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)
406 t[#t+1] = formatters["specification: %s"](method.specification)
407 end
408 local snapht, snapdp
409 if method[v_local] then
410
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
424
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
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
505
506 if method[v_halfline] then
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
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
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
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
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
692
693
694
695
696
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
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
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)
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 { }
753vspacingdata.skip = vspacingdata.skip or { }
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
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
782
783
784
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
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
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
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
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
968 end
969 else
970 local wd, ht, dp = getwhd(box)
971 if false then
972
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)
994 end
995 end
996 end
997 end
998end
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011do
1012
1013 local w, h, d = 0, 0, 0
1014
1015
1016 local function forced_skip(head,current,width,where,trace)
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
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
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
1057
1058 local special_penalty_min = 32250
1059 local special_penalty_max = 35000
1060 local special_penalty_xxx = 0
1061
1062
1063
1064
1065
1066
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
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
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
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
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
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
1174
1175
1176
1177
1178
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)
1205
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
1219 while c do
1220 local id = getid(c)
1221 if id == glue_code or id == penalty_code or id == kern_code then
1222
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
1233 c = current
1234 while c do
1235 local id = getid(c)
1236 if id == glue_code or id == penalty_code then
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
1248 else
1249
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
1267 return remove_node(head,current,true)
1268 end
1269
1270 local function collapser(head,where,what,trace,snap,a_snapmethod)
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
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)
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
1315 local h = p.ch
1316
1317
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
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")
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
1377 local prev = getprev(current)
1378 if getid(prev) == glue_code then
1379
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
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
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
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
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
1456
1457
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
1466 local done = list and already_done(id,list,a_snapmethod)
1467 if done then
1468
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
1498 flush("list")
1499 current = getnext(current)
1500 elseif id == penalty_code then
1501
1502
1503
1504
1505
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)
1517 local so = getattr(current,a_skiporder) or 1
1518 local sp = getattr(current,a_skippenalty)
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
1525
1526
1527
1528
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
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
1556
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
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
1616 if trace then
1617 trace_skip("overlay",sc,so,sp,current)
1618 end
1619
1620
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
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
1665
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
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
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)
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
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
1854
1855
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
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
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
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
1917
1918
1919 local forceflush = false
1920
1921 function vspacing.pagehandler(newhead,where)
1922 if newhead then
1923 local newtail = find_node_tail(newhead)
1924 local flush = false
1925 stackhack = true
1926
1927 for n, id, subtype in nextnode, newhead do
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
1938 if texgetcount("c_spac_vspacing_ignore_parskip") > 0 then
1939
1940 setglue(n)
1941
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
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)
2009 end
2010 return head
2011 end
2012
2013 function vspacing.collapsevbox(n,aslist)
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)
2021 else
2022 setlist(box,vpack_node(list))
2023 end
2024 end
2025 end
2026 end
2027
2028end
2029
2030
2031
2032
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
2053
2054
2055
2056
2057
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
2081 elseif subtype == parskip_code then
2082 if getwidth(tail) > 0 then
2083 break
2084 else
2085
2086 end
2087 end
2088 elseif id == penalty_code then
2089
2090 elseif id == rule_code or id == kern_code then
2091 break
2092 else
2093
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
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
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
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
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
2239 if last then
2240 nuts.setnext(getprev(last))
2241 nuts.setprev(last)
2242 end
2243 end
2244
2245 function vspacing.popatsame()
2246
2247 nuts.write(last)
2248 end
2249
2250end
2251
2252
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
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
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
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",
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 |