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
31local next, type, tonumber = next, type, tonumber
32local gmatch, concat = string.gmatch, table.concat
33local lpegmatch = lpeg.match
34local unpack = unpack or table.unpack
35local allocate = utilities.storage.allocate
36local todimen = string.todimen
37local formatters = string.formatters
38
39local nodes = nodes
40local trackers = trackers
41local attributes = attributes
42local context = context
43local tex = tex
44
45local texget = tex.get
46local texgetcount = tex.getcount
47local texgetdimen = tex.getdimen
48local texgetglue = tex.getglue
49local texset = tex.set
50local texsetdimen = tex.setdimen
51local texsetcount = tex.setcount
52local texgetbox = tex.getbox
53
54local texgetlist = tex.getlist
55local texsetlist = tex.setlist
56local texgetnest = tex.getnest
57
58local variables = interfaces.variables
59
60local v_local <const> = variables["local"]
61local v_global <const> = variables["global"]
62local v_box <const> = variables.box
63
64local v_split <const> = variables.split
65local v_min <const> = variables.min
66local v_max <const> = variables.max
67local v_none <const> = variables.none
68local v_first <const> = variables.first
69local v_last <const> = variables.last
70local v_top <const> = variables.top
71local v_bottom <const> = variables.bottom
72local v_maxheight <const> = variables.maxheight
73local v_minheight <const> = variables.minheight
74local v_mindepth <const> = variables.mindepth
75local v_maxdepth <const> = variables.maxdepth
76local v_offset <const> = variables.offset
77local v_strut <const> = variables.strut
78
79local v_hfraction <const> = variables.hfraction
80local v_dfraction <const> = variables.dfraction
81local v_bfraction <const> = variables.bfraction
82local v_tlines <const> = variables.tlines
83local v_blines <const> = variables.blines
84
85
86
87local trace_vbox_vspacing = false trackers.register("vspacing.vbox", function(v) trace_vbox_vspacing = v end)
88local trace_page_vspacing = false trackers.register("vspacing.page", function(v) trace_page_vspacing = v end)
89local trace_collect_vspacing = false trackers.register("vspacing.collect", function(v) trace_collect_vspacing = v end)
90local trace_vspacing = false trackers.register("vspacing.spacing", function(v) trace_vspacing = v end)
91local trace_vsnapping = false trackers.register("vspacing.snapping", function(v) trace_vsnapping = v end)
92local trace_specials = false trackers.register("vspacing.specials", function(v) trace_specials = v end)
93
94local remove_math_skips = true directives.register("vspacing.removemathskips", function(v) remnove_math_skips = v end)
95
96local report_vspacing = logs.reporter("vspacing","spacing")
97local report_collapser = logs.reporter("vspacing","collapsing")
98local report_snapper = logs.reporter("vspacing","snapping")
99local report_specials = logs.reporter("vspacing","specials")
100
101local a_skipcategory <const> = attributes.private('skipcategory')
102local a_skippenalty <const> = attributes.private('skippenalty')
103local a_skiporder <const> = attributes.private('skiporder')
104local a_snapmethod <const> = attributes.private('snapmethod')
105local a_snapvbox <const> = attributes.private('snapvbox')
106
107local d_bodyfontstrutheight <const> = tex.isdimen("bodyfontstrutheight")
108local d_bodyfontstrutdepth <const> = tex.isdimen("bodyfontstrutdepth")
109local d_globalbodyfontstrutheight <const> = tex.isdimen("globalbodyfontstrutheight")
110local d_globalbodyfontstrutdepth <const> = tex.isdimen("globalbodyfontstrutdepth")
111
112local d_strutdp <const> = tex.isdimen("strutdp")
113local d_spac_overlay <const> = tex.isdimen("d_spac_overlay")
114
115local c_spac_vspacing_ignore_parskip <const> = tex.iscount("c_spac_vspacing_ignore_parskip")
116
117local nuts = nodes.nuts
118local tonut = nuts.tonut
119
120local getnext = nuts.getnext
121local setlink = nuts.setlink
122local getprev = nuts.getprev
123local getid = nuts.getid
124local getlist = nuts.getlist
125local setlist = nuts.setlist
126local getattr = nuts.getattr
127local setattr = nuts.setattr
128local getsubtype = nuts.getsubtype
129local getbox = nuts.getbox
130local getwhd = nuts.getwhd
131local setwhd = nuts.setwhd
132local getprop = nuts.getprop
133local setprop = nuts.setprop
134local getglue = nuts.getglue
135local setglue = nuts.setglue
136local getkern = nuts.getkern
137local getpenalty = nuts.getpenalty
138local setshift = nuts.setshift
139local setwidth = nuts.setwidth
140local getwidth = nuts.getwidth
141local setheight = nuts.setheight
142local getheight = nuts.getheight
143local setdepth = nuts.setdepth
144local getdepth = nuts.getdepth
145local setnext = nuts.setnext
146local setprev = nuts.setprev
147local setoptions = nuts.setoptions
148
149local find_node_tail = nuts.tail
150local flushnode = nuts.flushnode
151local remove_node = nuts.remove
152local count_nodes = nuts.countall
153local hpack_node = nuts.hpack
154local vpack_node = nuts.vpack
155
156local startofpar = nuts.startofpar
157
158local write_node = nuts.write
159
160local nextnode = nuts.traversers.node
161local nexthlist = nuts.traversers.hlist
162
163local listtoutf = nodes.listtoutf
164local nodeidstostring = nodes.idstostring
165
166local nodepool = nuts.pool
167
168local new_penalty = nodepool.penalty
169local new_kern = nodepool.kern
170local new_glue = nodepool.glue
171local new_rule = nodepool.rule
172
173local nodecodes = nodes.nodecodes
174local gluecodes = nodes.gluecodes
175
176
177
178local penalty_code <const> = nodecodes.penalty
179local kern_code <const> = nodecodes.kern
180local glue_code <const> = nodecodes.glue
181local hlist_code <const> = nodecodes.hlist
182local vlist_code <const> = nodecodes.vlist
183local rule_code <const> = nodecodes.rule
184local par_code <const> = nodecodes.par
185local boundary_code <const> = nodecodes.boundary
186
187local userskip_code <const> = gluecodes.userskip
188local lineskip_code <const> = gluecodes.lineskip
189local baselineskip_code <const> = gluecodes.baselineskip
190local parskip_code <const> = gluecodes.parskip
191local topskip_code <const> = gluecodes.topskip
192local splittopskip_code <const> = gluecodes.splittopskip
193
194local linelist_code <const> = nodes.listcodes.line
195
196local setvisual = function(...) setvisual = nuts.setvisual return setvisual(...) end
197
198local properties = nodes.properties.data
199
200local vspacing = builders.vspacing or { }
201builders.vspacing = vspacing
202
203local vspacingdata = vspacing.data or { }
204vspacing.data = vspacingdata
205
206local snapmethods = vspacingdata.snapmethods or { }
207vspacingdata.snapmethods = snapmethods
208
209storage.register("builders/vspacing/data/snapmethods", snapmethods, "builders.vspacing.data.snapmethods")
210
211do
212
213 local default = {
214 [v_maxheight] = true,
215 [v_maxdepth] = true,
216 [v_strut] = true,
217 [v_hfraction] = 1,
218 [v_dfraction] = 1,
219 [v_bfraction] = 0.25,
220 }
221
222 local fractions = {
223 [v_minheight] = v_hfraction, [v_maxheight] = v_hfraction,
224 [v_mindepth] = v_dfraction, [v_maxdepth] = v_dfraction,
225 [v_box] = v_bfraction,
226 [v_top] = v_tlines, [v_bottom] = v_blines,
227 }
228
229 local values = {
230 offset = "offset"
231 }
232
233 local colonsplitter = lpeg.splitat(":")
234
235 local function listtohash(str)
236 local t = { }
237 for s in gmatch(str,"[^, ]+") do
238 local key, detail = lpegmatch(colonsplitter,s)
239 local v = variables[key]
240 if v then
241 t[v] = true
242 if detail then
243 local k = fractions[key]
244 if k then
245 detail = tonumber("0" .. detail)
246 if detail then
247 t[k] = detail
248 end
249 else
250 k = values[key]
251 if k then
252 detail = todimen(detail)
253 if detail then
254 t[k] = detail
255 end
256 end
257 end
258 end
259 else
260 detail = tonumber("0" .. key)
261 if detail then
262 t[v_hfraction] = detail
263 t[v_dfraction] = detail
264 end
265 end
266 end
267 if next(t) then
268 t[v_hfraction] = t[v_hfraction] or 1
269 t[v_dfraction] = t[v_dfraction] or 1
270 return t
271 else
272 return default
273 end
274 end
275
276 function vspacing.definesnapmethod(name,method)
277 local n = #snapmethods + 1
278 local t = listtohash(method)
279 snapmethods[n] = t
280 t.name = name
281 t.specification = method
282 return n
283 end
284
285end
286
287local function validvbox(parentid,list)
288 if parentid == hlist_code then
289 local id = getid(list)
290 if id == par_code and startofpar(list) then
291 list = getnext(list)
292 if not next then
293 return nil
294 end
295 end
296 local done = nil
297 for n, id in nextnode, list do
298 if id == vlist_code or id == hlist_code then
299 if done then
300 return nil
301 else
302 done = n
303 end
304 elseif id == glue_code or id == penalty_code then
305
306 else
307 return nil
308 end
309 end
310 if done then
311 local id = getid(done)
312 if id == hlist_code then
313 return validvbox(id,getlist(done))
314 end
315 end
316 return done
317 end
318end
319
320local function already_done(parentid,list,a_snapmethod)
321
322 if list and parentid == hlist_code then
323 local id = getid(list)
324 if id == par_code and startofpar(list) then
325 list = getnext(list)
326 if not list then
327 return false
328 end
329 end
330 for n, id in nextnode, list do
331 if id == hlist_code or id == vlist_code then
332
333
334
335
336
337
338 local p = getprop(n,"snapper")
339 if p then
340 return p
341 end
342 elseif id == glue_code or id == penalty_code then
343
344 else
345 return false
346 end
347 end
348 end
349 return false
350end
351
352
353
354local snap_hlist do
355
356 local v_noheight <const> = variables.noheight
357 local v_nodepth <const> = variables.nodepth
358 local v_line <const> = variables.line
359 local v_halfline <const> = variables.halfline
360 local v_line_m <const> = "-" .. v_line
361 local v_halfline_m <const> = "-" .. v_halfline
362
363 local floor = math.floor
364 local ceil = math.ceil
365
366 local function fixedprofile(current)
367 local profiling = builders.profiling
368 return profiling and profiling.fixedprofile(current)
369 end
370
371
372
373 local function ceiled(n)
374 if n < 0 or n < 0.01 then
375 return 0
376 else
377 return ceil(n)
378 end
379 end
380
381 local function floored(n)
382 if n < 0 or n < 0.01 then
383 return 0
384 else
385 return floor(n)
386 end
387 end
388
389 snap_hlist = function(where,current,method,height,depth)
390 if fixedprofile(current) then
391 return
392 end
393 local list = getlist(current)
394 local t = trace_vsnapping and { }
395 if t then
396 t[#t+1] = formatters["list content: %s"](listtoutf(list))
397 t[#t+1] = formatters["snap method: %s"](method.name)
398 t[#t+1] = formatters["specification: %s"](method.specification)
399 end
400 local snapht, snapdp
401 if method[v_local] then
402
403 snapht = texgetdimen(d_bodyfontstrutheight)
404 snapdp = texgetdimen(d_bodyfontstrutdepth)
405 if t then
406 t[#t+1] = formatters["local: snapht %p snapdp %p"](snapht,snapdp)
407 end
408 elseif method[v_global] then
409 snapht = texgetdimen(d_globalbodyfontstrutheight)
410 snapdp = texgetdimen(d_globalbodyfontstrutdepth)
411 if t then
412 t[#t+1] = formatters["global: snapht %p snapdp %p"](snapht,snapdp)
413 end
414 else
415
416
417 snapht = texgetdimen(d_globalbodyfontstrutheight)
418 snapdp = texgetdimen(d_globalbodyfontstrutdepth)
419 local lsnapht = texgetdimen(d_bodyfontstrutheight)
420 local lsnapdp = texgetdimen(d_bodyfontstrutdepth)
421 if snapht ~= lsnapht and snapdp ~= lsnapdp then
422 snapht, snapdp = lsnapht, lsnapdp
423 end
424 if t then
425 t[#t+1] = formatters["auto: snapht %p snapdp %p"](snapht,snapdp)
426 end
427 end
428
429 local wd, ht, dp = getwhd(current)
430
431 local h = (method[v_noheight] and 0) or height or ht
432 local d = (method[v_nodepth] and 0) or depth or dp
433 local hr = method[v_hfraction] or 1
434 local dr = method[v_dfraction] or 1
435 local br = method[v_bfraction] or 0
436 local ch = h
437 local cd = d
438 local tlines = method[v_tlines] or 1
439 local blines = method[v_blines] or 1
440 local done = false
441 local plusht = snapht
442 local plusdp = snapdp
443 local snaphtdp = snapht + snapdp
444 local extra = 0
445
446 if t then
447 t[#t+1] = formatters["hlist: wd %p ht %p (used %p) dp %p (used %p)"](wd,ht,h,dp,d)
448 t[#t+1] = formatters["fractions: hfraction %s dfraction %s bfraction %s tlines %s blines %s"](hr,dr,br,tlines,blines)
449 end
450
451 if method[v_box] then
452 local br = 1 - br
453 if br < 0 then
454 br = 0
455 elseif br > 1 then
456 br = 1
457 end
458 local n = ceiled((h+d-br*snapht-br*snapdp)/snaphtdp)
459 local x = n * snaphtdp - h - d
460 plusht = h + x / 2
461 plusdp = d + x / 2
462 if t then
463 t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_box,plusht,plusdp)
464 end
465 elseif method[v_max] then
466 local n = ceiled((h+d)/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_max,plusht,plusdp)
472 end
473 elseif method[v_min] then
474
475 if method.specification ~= v_min then
476 local n = floored((h+d)/snaphtdp)
477 local x = n * snaphtdp - h - d
478 plusht = h + x / 2
479 plusdp = d + x / 2
480 if plusht < 0 then
481 plusht = 0
482 end
483 if plusdp < 0 then
484 plusdp = 0
485 end
486 end
487 if t then
488 t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_min,plusht,plusdp)
489 end
490 elseif method[v_none] then
491 plusht, plusdp = 0, 0
492 if t then
493 t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_none,0,0)
494 end
495 end
496
497
498 if method[v_halfline] then
499 extra = snaphtdp/2
500 plusht = plusht + extra
501 plusdp = plusdp + extra
502 if t then
503 t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_halfline,plusht,plusdp)
504 end
505 end
506 if method[v_line] then
507 extra = snaphtdp
508 plusht = plusht + extra
509 plusdp = plusdp + extra
510 if t then
511 t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_line,plusht,plusdp)
512 end
513 end
514 if method[v_halfline_m] then
515 extra = - snaphtdp/2
516 plusht = plusht + extra
517 plusdp = plusdp + extra
518 if t then
519 t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_halfline_m,plusht,plusdp)
520 end
521 end
522 if method[v_line_m] then
523 extra = - snaphtdp
524 plusht = plusht + extra
525 plusdp = plusdp + extra
526 if t then
527 t[#t+1] = formatters["%s: plusht %p plusdp %p"](v_line_m,plusht,plusdp)
528 end
529 end
530 if method[v_first] then
531 local thebox = current
532 local id = getid(thebox)
533 if id == hlist_code then
534 thebox = validvbox(id,getlist(thebox))
535 id = thebox and getid(thebox)
536 end
537 if thebox and id == vlist_code then
538 local list = getlist(thebox)
539 local lw, lh, ld
540 for n in nexthlist, list do
541 lw, lh, ld = getwhd(n)
542 break
543 end
544 if lh then
545 local wd, ht, dp = getwhd(thebox)
546 if t then
547 t[#t+1] = formatters["first line: height %p depth %p"](lh,ld)
548 t[#t+1] = formatters["dimensions: height %p depth %p"](ht,dp)
549 end
550 local delta = h - lh
551 ch, cd = lh, delta + d
552 h, d = ch, cd
553 local shifted = hpack_node(getlist(current))
554 setshift(shifted,delta)
555 setlist(current,shifted)
556 done = true
557 if t then
558 t[#t+1] = formatters["first: height %p depth %p shift %p"](ch,cd,delta)
559 end
560 elseif t then
561 t[#t+1] = "first: not done, no content"
562 end
563 elseif t then
564 t[#t+1] = "first: not done, no vbox"
565 end
566 elseif method[v_last] then
567 local thebox = current
568 local id = getid(thebox)
569 if id == hlist_code then
570 thebox = validvbox(id,getlist(thebox))
571 id = thebox and getid(thebox)
572 end
573 if thebox and id == vlist_code then
574 local list = getlist(thebox)
575 local lw, lh, ld
576 for n in nexthlist, list do
577 lw, lh, ld = getwhd(n)
578 end
579 if lh then
580 local wd, ht, dp = getwhd(thebox)
581 if t then
582 t[#t+1] = formatters["last line: height %p depth %p" ](lh,ld)
583 t[#t+1] = formatters["dimensions: height %p depth %p"](ht,dp)
584 end
585 local delta = d - ld
586 cd, ch = ld, delta + h
587 h, d = ch, cd
588 local shifted = hpack_node(getlist(current))
589 setshift(shifted,delta)
590 setlist(current,shifted)
591 done = true
592 if t then
593 t[#t+1] = formatters["last: height %p depth %p shift %p"](ch,cd,delta)
594 end
595 elseif t then
596 t[#t+1] = "last: not done, no content"
597 end
598 elseif t then
599 t[#t+1] = "last: not done, no vbox"
600 end
601 end
602 if method[v_minheight] then
603 ch = floored((h-hr*snapht)/snaphtdp)*snaphtdp + plusht
604 if t then
605 t[#t+1] = formatters["minheight: %p"](ch)
606 end
607 elseif method[v_maxheight] then
608 ch = ceiled((h-hr*snapht)/snaphtdp)*snaphtdp + plusht
609 if t then
610 t[#t+1] = formatters["maxheight: %p"](ch)
611 end
612 else
613 ch = plusht
614 if t then
615 t[#t+1] = formatters["set height: %p"](ch)
616 end
617 end
618 if method[v_mindepth] then
619 cd = floored((d-dr*snapdp)/snaphtdp)*snaphtdp + plusdp
620 if t then
621 t[#t+1] = formatters["mindepth: %p"](cd)
622 end
623 elseif method[v_maxdepth] then
624 cd = ceiled((d-dr*snapdp)/snaphtdp)*snaphtdp + plusdp
625 if t then
626 t[#t+1] = formatters["maxdepth: %p"](cd)
627 end
628 else
629 cd = plusdp
630 if t then
631 t[#t+1] = formatters["set depth: %p"](cd)
632 end
633 end
634 if method[v_top] then
635 ch = ch + tlines * snaphtdp
636 if t then
637 t[#t+1] = formatters["top height: %p"](ch)
638 end
639 end
640 if method[v_bottom] then
641 cd = cd + blines * snaphtdp
642 if t then
643 t[#t+1] = formatters["bottom depth: %p"](cd)
644 end
645 end
646 local offset = method[v_offset]
647 if offset then
648
649 if t then
650 local wd, ht, dp = getwhd(current)
651 t[#t+1] = formatters["before offset: %p (width %p height %p depth %p)"](offset,wd,ht,dp)
652 end
653 local shifted = hpack_node(getlist(current))
654 setshift(shifted,offset)
655 setlist(current,shifted)
656 if t then
657 local wd, ht, dp = getwhd(current)
658 t[#t+1] = formatters["after offset: %p (width %p height %p depth %p)"](offset,wd,ht,dp)
659 end
660 setattr(shifted,a_snapmethod,0)
661 setattr(current,a_snapmethod,0)
662 end
663 if not height then
664 setheight(current,ch)
665 if t then
666 t[#t+1] = formatters["forced height: %p"](ch)
667 end
668 end
669 if not depth then
670 setdepth(current,cd)
671 if t then
672 t[#t+1] = formatters["forced depth: %p"](cd)
673 end
674 end
675 local lines = (ch+cd)/snaphtdp
676 if t then
677 local original = (h+d)/snaphtdp
678 local whatever = (ch+cd)/(texgetdimen(d_globalbodyfontstrutheight) + texgetdimen(d_globalbodyfontstrutdepth))
679 t[#t+1] = formatters["final lines : %p -> %p (%p)"](original,lines,whatever)
680 t[#t+1] = formatters["final height: %p -> %p"](h,ch)
681 t[#t+1] = formatters["final depth : %p -> %p"](d,cd)
682 end
683
684
685
686
687
688
689 if t then
690 report_snapper("trace: %s type %s\n\t%\n\tt",where,nodecodes[getid(current)],t)
691 end
692 if not method[v_split] then
693
694 extra = 0
695 end
696 return h, d, ch, cd, lines, extra
697 end
698
699end
700
701local categories = { [0] =
702 "discard",
703 "largest",
704 "force",
705 "penalty",
706 "add",
707 "disable",
708 "nowhite",
709 "goback",
710 "packed",
711 "overlay",
712 "enable",
713 "notopskip",
714 "keep",
715}
716
717categories = allocate(table.swapped(categories,categories))
718vspacing.categories = categories
719
720function vspacing.tocategories(str)
721 local t = { }
722 for s in gmatch(str,"[^, ]") do
723 local n = tonumber(s)
724 if n then
725 t[categories[n]] = true
726 else
727 t[b] = true
728 end
729 end
730 return t
731end
732
733function vspacing.tocategory(str)
734 if type(str) == "string" then
735 return set.tonumber(vspacing.tocategories(str))
736 else
737 return set.tonumber({ [categories[str]] = true })
738 end
739end
740
741vspacingdata.map = vspacingdata.map or { }
742vspacingdata.skip = vspacingdata.skip or { }
743
744storage.register("builders/vspacing/data/map", vspacingdata.map, "builders.vspacing.data.map")
745storage.register("builders/vspacing/data/skip", vspacingdata.skip, "builders.vspacing.data.skip")
746
747local setspecification, getspecification
748
749
750
751
752
753
754
755
756local method = 1
757
758
759
760
761
762
763if method == 1 then
764
765 local registervalue = attributes.registervalue
766 local getvalue = attributes.getvalue
767 local values = attributes.values
768
769 setspecification = function(n,category,penalty,order)
770 local detail = { category, penalty, order or 1 }
771 local value = registervalue(a_skipcategory,detail)
772 setattr(n,a_skipcategory,value)
773 end
774
775 getspecification = function(n)
776 local value = getattr(n,a_skipcategory)
777 if value then
778 local detail = getvalue(a_skipcategory,value)
779
780
781 if detail then
782 return detail[1], detail[2], detail[3]
783 end
784 end
785 return false, false, 1
786 end
787
788elseif method == 2 then
789
790
791
792 local setattrs = nuts.setattrs
793 local getattrs = nuts.getattrs
794
795 setspecification = function(n,category,penalty,order)
796 setattrs(n,false,a_skipcategory,category or nil,a_skippenalty,penalty or nil,a_skiporder,order or 1)
797 end
798
799 getspecification = function(n)
800 local c, p, o = getattrs(n,a_skipcategory,a_skippenalty,a_skiporder)
801 return c or false, p or false, o or 1
802 end
803
804elseif method == 3 then
805
806
807
808 setspecification = function(n,category,penalty,order)
809
810 properties[n] = {
811 [a_skipcategory] = category,
812 [a_skippenalty] = penalty,
813 [a_skiporder] = order or 1,
814 }
815 end
816
817 getspecification = function(n)
818 local p = properties[n]
819 if p then
820 return p[a_skipcategory], p[a_skippenalty], p[a_skiporder]
821 end
822 end
823
824elseif method == 4 then
825
826
827
828 local getdata = nuts.getdata
829 local setdata = nuts.setdata
830
831 setspecification = function(n,category,penalty,order)
832 if not category or category > 0xF then
833 category = 0xF
834 end
835 if not order or order > 0xFF then
836 order = 0xFF
837 end
838 if not penalty or penalty > 0x7FFFF then
839 penalty = 0x7FFFF
840 elseif penalty < -0x7FFFF then
841 penalty = -0x7FFFF
842 end
843
844 setdata(n, (penalty << 12) + (order << 4) + category)
845 end
846
847 getspecification = function(n)
848 local data = getdata(n)
849 if data and data ~= 0 then
850 local category = data & 0x0F
851 local order = (data >> 4) & 0xFF
852 local penalty = data >> 12
853 if category == 0xF then
854 category = nil
855 end
856 if order == 0xFF then
857 order = nil
858 end
859 if penalty == 0x7FFFF then
860 penalty = nil
861 end
862 return category, penalty, order
863 else
864 return nil, nil, nil
865 end
866 end
867
868end
869
870do
871
872 local P, C, R, S, Cc, Cs = lpeg.P, lpeg.C, lpeg.R, lpeg.S, lpeg.Cc, lpeg.Cs
873
874 vspacing.fixed = false
875
876 local map = vspacingdata.map
877 local skip = vspacingdata.skip
878
879 local sign = S("+-")^0
880 local multiplier = C(sign * R("09")^1) * P("*")
881 local singlefier = Cs(sign * Cc(1))
882 local separator = S(", ")
883 local category = P(":") * C((1-separator)^1)
884 local keyword = C((1-category-separator)^1)
885 local splitter = (multiplier + Cc(1)) * keyword * (category + Cc(false))
886
887 local k_fixed <const> = variables.fixed
888 local k_flexible <const> = variables.flexible
889 local k_limit <const> = variables.limit
890
891 local k_category <const> = "category"
892 local k_penalty <const> = "penalty"
893 local k_order <const> = "order"
894
895 function vspacing.setmap(from,to)
896 map[from] = to
897 end
898
899 function vspacing.setskip(key,value,grid)
900 if value ~= "" then
901 if grid == "" then grid = value end
902 skip[key] = { value, grid }
903 end
904 end
905
906 local expandmacro = token.expandmacro
907
908
909
910 local toscaled = tex.toscaled
911
912 local b_done = false
913 local b_packed = false
914 local b_keep = false
915
916 local b_amount = 0
917 local b_stretch = 0
918 local b_shrink = 0
919 local b_category = false
920 local b_penalty = false
921 local b_order = false
922 local b_fixed = false
923 local b_grid = false
924 local b_limit = false
925
926 local pattern = nil
927
928 local packed = categories.packed
929 local keep = categories.keep
930
931 local gluefactor = .25
932
933 local ctx_ignoreparskip = context.core.ignoreparskip
934
935 local function before()
936 b_amount = 0
937 b_stretch = 0
938 b_shrink = 0
939 b_category = 1
940 b_penalty = false
941 b_order = false
942 b_fixed = b_grid
943 b_limit = false
944 end
945
946 local function after()
947 if b_fixed then
948 b_stretch = 0
949 b_shrink = 0
950 else
951 b_stretch = gluefactor * b_amount
952 b_shrink = gluefactor * b_amount
953 end
954 end
955
956
957
958 local function inject()
959 local n = new_glue(b_amount,b_stretch,b_shrink)
960 setspecification(n,b_category,b_penalty,b_order or 1)
961 setvisual(n)
962 write_node(n)
963 if b_limit then
964 setoptions(n,tex.glueoptioncodes.limit)
965 end
966
967 end
968
969 local function flush()
970 after()
971 if b_done then
972 inject()
973 b_done = false
974 end
975 before()
976 end
977
978
979
980
981
982 local s_predefined = tex.isskip("s_spac_vspacing_predefined")
983
984 local function handler(multiplier, keyword, detail)
985 if not keyword then
986 report_vspacing("unknown directive %a",s)
987 else
988 local mk = map[keyword]
989 if mk then
990 lpegmatch(pattern,mk)
991 elseif keyword == k_fixed then
992 b_fixed = true
993 elseif keyword == k_flexible then
994 b_flexible = false
995 elseif keyword == k_category then
996 local category = tonumber(detail)
997 if category == packed then
998 b_packed = true
999 elseif category then
1000 b_category = category
1001 b_done = true
1002 flush()
1003 end
1004 elseif keyword == k_order and detail then
1005 local order = tonumber(detail)
1006 if order then
1007 b_order = order
1008 end
1009 elseif keyword == k_penalty and detail then
1010 local penalty = tonumber(detail)
1011 if penalty then
1012 flush()
1013 b_done = true
1014 b_category = 3
1015 b_penalty = penalty
1016 flush()
1017 end
1018 elseif keyword == k_limit then
1019 b_limit = true
1020 else
1021 local amount, stretch, shrink
1022 multiplier = tonumber(multiplier) or 1
1023 local sk = skip[keyword]
1024 if sk then
1025
1026
1027 expandmacro("vspacingpredefinedvalue",true,keyword)
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040 amount, stretch, shrink = texgetglue(s_predefined)
1041 if not stretch then
1042 stretch = 0
1043 end
1044 if not shrink then
1045 shrink = 0
1046 end
1047 if stretch == 0 and shrink == 0 then
1048 stretch = gluefactor * amount
1049 shrink = stretch
1050 end
1051 else
1052 amount = toscaled(keyword)
1053 stretch = gluefactor * amount
1054 shrink = stretch
1055 end
1056
1057 b_amount = b_amount + multiplier * amount
1058 b_stretch = b_stretch + multiplier * stretch
1059 b_shrink = b_shrink + multiplier * shrink
1060 b_done = true
1061 end
1062 end
1063 end
1064
1065
1066
1067
1068 local splitter = ((multiplier + singlefier) * keyword * (category + Cc(false))) / handler
1069 pattern = (splitter + separator^1)^0
1070
1071 function vspacing.inject(grid,str)
1072 if trace_vspacing then
1073
1074 end
1075 b_done = false
1076 b_packed = false
1077 b_keep = false
1078 b_grid = grid == true or grid == 1
1079 before()
1080 lpegmatch(pattern,str)
1081 after()
1082 if b_done then
1083 inject()
1084 end
1085 if b_packed then
1086 ctx_ignoreparskip()
1087 end
1088 if trace_vspacing then
1089
1090 end
1091 end
1092
1093 function vspacing.injectpenalty(penalty)
1094 local n = new_glue()
1095
1096 setspecification(n,categories.penalty,penalty,1)
1097 setvisual(k)
1098 write_node(n)
1099 end
1100
1101 function vspacing.injectskip(amount)
1102 local n = new_glue(amount)
1103
1104 setspecification(n,categories.largest,false,1)
1105 setvisual(k)
1106 write_node(n)
1107 end
1108
1109 function vspacing.injectdisable(amount)
1110 local n = new_glue()
1111
1112 setspecification(n,categories.disable,false,1)
1113 setvisual(k)
1114 write_node(n)
1115 end
1116
1117end
1118
1119
1120
1121
1122
1123function vspacing.snapbox(n,how)
1124 local sv = snapmethods[how]
1125 if sv then
1126 local box = getbox(n)
1127 local list = getlist(box)
1128 if list then
1129 local s = getattr(list,a_snapmethod)
1130 if s == 0 then
1131 if trace_vsnapping then
1132
1133 end
1134 else
1135 local wd, ht, dp = getwhd(box)
1136 if false then
1137
1138 if trace_vsnapping then
1139 report_snapper("box list already snapped at (%p,%p): %s",
1140 ht,dp,listtoutf(list))
1141 end
1142 else
1143 local h, d, ch, cd, lines, extra = snap_hlist("box",box,sv,ht,dp)
1144 setprop(box,"snapper",{
1145 ht = h,
1146 dp = d,
1147 ch = ch,
1148 cd = cd,
1149 extra = extra,
1150 current = current,
1151 })
1152 setwhd(box,wd,ch,cd)
1153 if trace_vsnapping then
1154 report_snapper("box list snapped from (%p,%p) to (%p,%p) using method %a (%s) for %a (%s lines): %s",
1155 h,d,ch,cd,sv.name,sv.specification,"direct",lines,listtoutf(list))
1156 end
1157 setattr(box,a_snapmethod,0)
1158 setattr(list,a_snapmethod,0)
1159 end
1160 end
1161 end
1162 end
1163end
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176do
1177
1178 local insertnodeafter = nuts.insertafter
1179 local insertnodebefore = nuts.insertbefore
1180
1181 local abovedisplayskip_code <const> = gluecodes.abovedisplayskip
1182 local belowdisplayskip_code <const> = gluecodes.belowdisplayskip
1183 local abovedisplayshortskip_code <const> = gluecodes.abovedisplayshortskip
1184 local belowdisplayshortskip_code <const> = gluecodes.belowdisplayshortskip
1185
1186 local w, h, d = 0, 0, 0
1187
1188
1189 local trace_list = { }
1190 local tracing_info = { }
1191 local before = ""
1192 local after = ""
1193
1194 local texnest = tex.nest
1195
1196 local function nodes_to_string(head)
1197 local current = head
1198 local t = { }
1199 while current do
1200 local id = getid(current)
1201 local ty = nodecodes[id]
1202 if id == penalty_code then
1203 t[#t+1] = formatters["%s:%s"](ty,getpenalty(current))
1204 elseif id == glue_code then
1205 t[#t+1] = formatters["%s:%s:%p"](ty,gluecodes[getsubtype(current)],getwidth(current))
1206 elseif id == kern_code then
1207 t[#t+1] = formatters["%s:%p"](ty,getkern(current))
1208 else
1209 t[#t+1] = ty
1210 end
1211 current = getnext(current)
1212 end
1213 return concat(t," + ")
1214 end
1215
1216 local function reset_tracing(head)
1217 trace_list, tracing_info, before, after = { }, false, nodes_to_string(head), ""
1218 end
1219
1220 local function trace_skip(str,sc,so,sp,data)
1221 trace_list[#trace_list+1] = { "skip", formatters["%s | %p | category %s | order %s | penalty %s | subtype %s"](str, getwidth(data), sc or "-", so or "-", sp or "-", gluecodes[getsubtype(data)]) }
1222 tracing_info = true
1223 end
1224
1225 local function trace_natural(str,data)
1226 trace_list[#trace_list+1] = { "skip", formatters["%s | %p"](str, getwidth(data)) }
1227 tracing_info = true
1228 end
1229
1230 local function trace_info(message, where, what)
1231 trace_list[#trace_list+1] = { "info", formatters["%s: %s/%s"](message,where,what) }
1232 end
1233
1234 local function trace_node(what)
1235 local nt = nodecodes[getid(what)]
1236 local tl = trace_list[#trace_list]
1237 if tl and tl[1] == "node" then
1238 trace_list[#trace_list] = { "node", formatters["%s + %s"](tl[2],nt) }
1239 else
1240 trace_list[#trace_list+1] = { "node", nt }
1241 end
1242 end
1243
1244 local function show_tracing(head)
1245 if tracing_info then
1246 after = nodes_to_string(head)
1247 for i=1,#trace_list do
1248 local tag, text = unpack(trace_list[i])
1249 if tag == "info" then
1250 report_collapser(text)
1251 else
1252 report_collapser(" %s: %s",tag,text)
1253 end
1254 end
1255 report_collapser("before: %s",before)
1256 report_collapser("after : %s",after)
1257 end
1258 end
1259
1260 local function trace_done(str,data)
1261 if getid(data) == penalty_code then
1262 trace_list[#trace_list+1] = { "penalty", formatters["%s | %s"](str,getpenalty(data)) }
1263 else
1264 trace_list[#trace_list+1] = { "glue", formatters["%s | %p"](str,getwidth(data)) }
1265 end
1266 tracing_info = true
1267 end
1268
1269 local function forced_skip(head,current,width,where,trace)
1270 if head == current then
1271 if getsubtype(head) == baselineskip_code then
1272 width = width - getwidth(head)
1273 end
1274 end
1275 if width == 0 then
1276
1277 else
1278 local b = new_rule(w,h,d)
1279 local k = new_kern(width)
1280 local a = new_rule(w,h,d)
1281 setvisual(k)
1282 if where == "after" then
1283 head, current = insertnodeafter(head,current,b)
1284 head, current = insertnodeafter(head,current,k)
1285 head, current = insertnodeafter(head,current,a)
1286 else
1287 local c = current
1288 head, current = insertnodebefore(head,current,b)
1289 head, current = insertnodebefore(head,current,k)
1290 head, current = insertnodebefore(head,current,a)
1291 current = c
1292 end
1293 end
1294 if trace then
1295 report_vspacing("inserting forced skip of %p",width)
1296 end
1297 return head, current
1298 end
1299
1300
1301
1302 local discard <const> = categories.discard
1303 local largest <const> = categories.largest
1304 local force <const> = categories.force
1305 local penalty <const> = categories.penalty
1306 local add <const> = categories.add
1307 local disable <const> = categories.disable
1308 local nowhite <const> = categories.nowhite
1309 local goback <const> = categories.goback
1310 local packed <const> = categories.packed
1311 local overlay <const> = categories.overlay
1312 local enable <const> = categories.enable
1313 local notopskip <const> = categories.notopskip
1314 local keep <const> = categories.keep
1315
1316
1317
1318 local special_penalty_min <const> = 32250
1319 local special_penalty_max <const> = 35000
1320 local special_penalty_xxx <const> = 0
1321
1322
1323
1324
1325
1326
1327
1328 local specialmethods = { }
1329 local specialmethod = 1
1330
1331 specialmethods[1] = function(pagehead,pagetail,start,penalty)
1332
1333 if not pagehead or penalty < special_penalty_min or penalty > special_penalty_max then
1334 return
1335 end
1336 local current = pagetail
1337
1338
1339
1340 if trace_specials then
1341 report_specials("checking penalty %a",penalty)
1342 end
1343 while current do
1344 local id = getid(current)
1345 if id == penalty_code then
1346 local p = properties[current]
1347 if p then
1348 local p = p.special_penalty
1349 if not p then
1350 if trace_specials then
1351 report_specials(" regular penalty, continue")
1352 end
1353 elseif p == penalty then
1354 if trace_specials then
1355 report_specials(" context penalty %a, same level, overloading",p)
1356 end
1357 return special_penalty_xxx
1358 elseif p > special_penalty_min and p < special_penalty_max then
1359 if penalty < p then
1360 if trace_specials then
1361 report_specials(" context penalty %a, lower level, overloading",p)
1362 end
1363 return special_penalty_xxx
1364 else
1365 if trace_specials then
1366 report_specials(" context penalty %a, higher level, quitting",p)
1367 end
1368 return
1369 end
1370 elseif trace_specials then
1371 report_specials(" context penalty %a, higher level, continue",p)
1372 end
1373 else
1374 local p = getpenalty(current)
1375 if p < 10000 then
1376
1377 if trace_specials then
1378 report_specials(" regular penalty %a, quitting",p)
1379 end
1380 break
1381 else
1382 if trace_specials then
1383 report_specials(" regular penalty %a, continue",p)
1384 end
1385 end
1386 end
1387 end
1388 current = getprev(current)
1389 end
1390
1391 if trace_specials then
1392 if pagetail then
1393 report_specials(" context penalty, discarding, nothing special")
1394 else
1395 report_specials(" context penalty, discarding, nothing preceding")
1396 end
1397 end
1398 return special_penalty_xxx
1399 end
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424 local function snap_topskip(current,method)
1425 local w = getwidth(current)
1426 setwidth(current,0)
1427 return w, 0
1428 end
1429
1430 local function check_experimental_overlay(head,current)
1431 local p = nil
1432 local c = current
1433 local n = nil
1434 local function overlay(p,n,mvl)
1435 local p_wd, p_ht, p_dp = getwhd(p)
1436 local n_wd, n_ht, n_dp = getwhd(n)
1437 local skips = 0
1438
1439
1440
1441
1442
1443
1444
1445
1446 local c = getnext(p)
1447 local l = c
1448 while c and c ~= n do
1449 local id = getid(c)
1450 if id == glue_code then
1451 local w = getwidth(c)
1452 skips = skips + w
1453 setglue(c,w)
1454 elseif id == kern_code then
1455 skips = skips + getkern(c)
1456 end
1457 l = c
1458 c = getnext(c)
1459 end
1460 local c = getprev(n)
1461 while c and c ~= n and c ~= l do
1462 local id = getid(c)
1463 if id == glue_code then
1464 local w = getwidth(c)
1465 skips = skips + w
1466 setglue(c,w)
1467 elseif id == kern_code then
1468 skips = skips + getkern(c)
1469 end
1470 c = getprev(c)
1471 end
1472
1473 local delta = n_ht + skips + p_dp
1474 texsetdimen("global",d_spac_overlay,-delta)
1475
1476 local k = new_kern(-delta)
1477 setvisual(k)
1478 head = insertnodebefore(head,n,k)
1479 if n_ht > p_ht then
1480 local k = new_kern(n_ht-p_ht)
1481 setvisual(k)
1482 head = insertnodebefore(head,p,k)
1483 end
1484 if trace_vspacing then
1485 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)
1486 end
1487 return remove_node(head,current,true)
1488 end
1489
1490
1491 while c do
1492 local id = getid(c)
1493 if id == glue_code or id == penalty_code or id == kern_code then
1494
1495 c = getnext(c)
1496 elseif id == hlist_code then
1497 n = c
1498 break
1499 else
1500 break
1501 end
1502 end
1503 if n then
1504
1505 c = current
1506 while c do
1507 local id = getid(c)
1508 if id == glue_code or id == penalty_code then
1509 c = getprev(c)
1510 elseif id == hlist_code then
1511 p = c
1512 break
1513 else
1514 break
1515 end
1516 end
1517 if not p then
1518 if a_snapmethod == a_snapvbox then
1519
1520 else
1521
1522 local c = tonut(texgetlist("pagehead"))
1523 while c and c ~= n do
1524 local id = getid(c)
1525 if id == hlist_code then
1526 p = c
1527 end
1528 c = getnext(c)
1529 end
1530 if p and p ~= n then
1531 return overlay(p,n,true)
1532 end
1533 end
1534 elseif p ~= n then
1535 return overlay(p,n,false)
1536 end
1537 end
1538
1539 return remove_node(head,current,true)
1540 end
1541
1542
1543
1544
1545 local checkslide = false
1546
1547 directives.register("vspacing.checkslide", function(v)
1548 if v then
1549 checkslide = function(head,where,what)
1550 nuts.checkslide(head,where .. " : " .. what)
1551 end
1552 else
1553 checkslide = false
1554 end
1555 end)
1556
1557 local function collapser(head,where,what,trace,snap,a_snapmethod)
1558 if trace then
1559 reset_tracing(head)
1560 end
1561 local current = head
1562 local oldhead = head
1563 local glue_order = 0
1564 local glue_data
1565 local force_glue = false
1566 local penalty_order = 0
1567 local penalty_data
1568 local natural_penalty
1569 local special_penalty
1570 local parskip
1571 local ignore_parskip = false
1572 local ignore_following = false
1573 local ignore_whitespace = false
1574 local keep_together = false
1575 local dont_discard = false
1576 local lastsnap
1577 local pagehead
1578 local pagetail
1579
1580
1581
1582 local function getpagelist()
1583 if not pagehead then
1584 pagehead, pagetail = nuts.getmvllist()
1585 end
1586 end
1587
1588 local setdiscardable <const> = tex.glueoptioncodes.setdiscardable
1589 local resetdiscardable <const> = tex.glueoptioncodes.resetdiscardable
1590
1591 local function checkoptions(n,where)
1592 if getid(n) ~= glue_code then
1593
1594
1595 elseif dont_discard then
1596
1597 setoptions(n,tex.glueoptioncodes.setdiscardable)
1598 else
1599
1600 setoptions(n,tex.glueoptioncodes.resetdiscardable)
1601 end
1602 end
1603
1604 local function compensate(n)
1605 local g = 0
1606 while n and getid(n) == glue_code do
1607 g = g + getwidth(n)
1608 n = getnext(n)
1609 end
1610 if n then
1611 local p = getprop(n,"snapper")
1612 if p then
1613 local extra = p.extra
1614 if extra and extra < 0 then
1615 local h = p.ch
1616
1617
1618 setheight(n,h-2*extra)
1619 p.extra = 0
1620 if trace_vsnapping then
1621 report_snapper("removed extra space at top: %p",extra)
1622 end
1623
1624 end
1625 end
1626 return n
1627 end
1628 end
1629
1630 local function removetopsnap()
1631 getpagelist()
1632 if pagehead then
1633 local n = pagehead and compensate(pagehead)
1634 if n and n ~= pagetail then
1635 local p = getprop(pagetail,"snapper")
1636 if p then
1637 local e = p.extra
1638 if e and e < 0 then
1639
1640 local t = texget("pagetotal")
1641 if t > 0 then
1642 local g = texget("pagegoal")
1643 local d = g - t
1644 if d < -e then
1645 local penalty = new_penalty(1000000)
1646 setvisual(penalty)
1647 setlink(penalty,head)
1648 head = penalty
1649 report_snapper("force pagebreak due to extra space at bottom: %p",e)
1650 end
1651 end
1652 end
1653 end
1654 end
1655 elseif head then
1656 compensate(head)
1657 end
1658 end
1659
1660 local function getavailable()
1661 getpagelist()
1662 if pagehead then
1663
1664 local t = texget("pagetotal")
1665 if t > 0 then
1666 local g = texget("pagegoal")
1667 return g - t
1668 end
1669 end
1670 return false
1671 end
1672
1673 local function flush(why)
1674 if penalty_data then
1675 local p = new_penalty(penalty_data)
1676 setvisual(p)
1677 if trace then
1678 trace_done("flushed due to " .. why,p)
1679 end
1680 if penalty_data >= 10000 then
1681 local prev = getprev(current)
1682 if getid(prev) == glue_code then
1683
1684 head = insertnodebefore(head,prev,p)
1685 else
1686 head = insertnodebefore(head,current,p)
1687 end
1688 else
1689 head = insertnodebefore(head,current,p)
1690 end
1691
1692 local props = properties[p]
1693 if props then
1694 props.special_penalty = special_penalty or penalty_data
1695 else
1696 properties[p] = {
1697 special_penalty = special_penalty or penalty_data
1698 }
1699 end
1700
1701 end
1702 if glue_data then
1703 if force_glue then
1704 if trace then
1705 trace_done("flushed due to forced " .. why,glue_data)
1706 end
1707 checkoptions(head,1)
1708 head = forced_skip(head,current,getwidth(glue_data,width),"before",trace)
1709 flushnode(glue_data)
1710 else
1711 local width, stretch, shrink = getglue(glue_data)
1712 if width ~= 0 then
1713 if trace then
1714 trace_done("flushed due to non zero " .. why,glue_data)
1715 end
1716 checkoptions(glue_data,2)
1717 head = insertnodebefore(head,current,glue_data)
1718 elseif stretch ~= 0 or shrink ~= 0 then
1719 if trace then
1720 trace_done("flushed due to stretch/shrink in" .. why,glue_data)
1721 end
1722 head = insertnodebefore(head,current,glue_data)
1723 else
1724
1725 checkoptions(glue_data,3)
1726 flushnode(glue_data)
1727 end
1728 end
1729 end
1730
1731 if trace then
1732 trace_node(current)
1733 end
1734 glue_order = 0
1735 glue_data = nil
1736 force_glue = false
1737 penalty_order = 0
1738 penalty_data = nil
1739 natural_penalty = nil
1740 parskip = nil
1741 ignore_parskip = false
1742 ignore_following = false
1743 ignore_whitespace = false
1744 dont_discard = false
1745 end
1746
1747 if trace_vsnapping then
1748 report_snapper("global ht/dp = %p/%p, local ht/dp = %p/%p",
1749 texgetdimen(d_globalbodyfontstrutheight),
1750 texgetdimen(d_globalbodyfontstrutdepth),
1751 texgetdimen(d_bodyfontstrutheight),
1752 texgetdimen(d_bodyfontstrutdepth)
1753 )
1754 end
1755 if trace then
1756 trace_info("start analyzing",where,what)
1757 end
1758 if snap and where == "page" then
1759 removetopsnap()
1760 end
1761 if checkslide then
1762 checkslide(head,where,what)
1763 end
1764 while current do
1765 local id = getid(current)
1766 if id == hlist_code or id == vlist_code then
1767
1768 if snap then
1769 lastsnap = nil
1770 local list = getlist(current)
1771 local s = getattr(current,a_snapmethod)
1772 if not s then
1773
1774
1775
1776 elseif s == 0 then
1777 if trace_vsnapping then
1778 report_snapper("mvl %a not snapped, already done: %s",nodecodes[id],listtoutf(list))
1779 end
1780 else
1781 local sv = snapmethods[s]
1782 if sv then
1783
1784 local done = list and already_done(id,list,a_snapmethod)
1785 if done then
1786
1787 if trace_vsnapping then
1788 local w, h, d = getwhd(current)
1789 report_snapper("mvl list already snapped at (%p,%p): %s",h,d,listtoutf(list))
1790 end
1791 else
1792 local h, d, ch, cd, lines, extra = snap_hlist("mvl",current,sv,false,false)
1793 lastsnap = {
1794 ht = h,
1795 dp = d,
1796 ch = ch,
1797 cd = cd,
1798 extra = extra,
1799 current = current,
1800 }
1801 setprop(current,"snapper",lastsnap)
1802 if trace_vsnapping then
1803 report_snapper("mvl %a snapped from (%p,%p) to (%p,%p) using method %a (%s) for %a (%s lines): %s",
1804 nodecodes[id],h,d,ch,cd,sv.name,sv.specification,where,lines,listtoutf(list))
1805 end
1806 end
1807 elseif trace_vsnapping then
1808 report_snapper("mvl %a not snapped due to unknown snap specification: %s",nodecodes[id],listtoutf(list))
1809 end
1810 setattr(current,a_snapmethod,0)
1811 end
1812 else
1813
1814 end
1815
1816 flush("list")
1817 current = getnext(current)
1818 elseif id == penalty_code then
1819
1820
1821
1822
1823
1824 if trace then
1825 trace_done("kept penalty",current)
1826 end
1827 current = getnext(current)
1828 elseif id == kern_code then
1829 if snap and trace_vsnapping and getkern(current) ~= 0 then
1830 report_snapper("kern of %p kept",getkern(current))
1831 end
1832 flush("kern")
1833 current = getnext(current)
1834 elseif id == glue_code then
1835 local subtype = getsubtype(current)
1836 if subtype == userskip_code then
1837
1838 local sc, sp, so = getspecification(current)
1839 if not so then
1840 so = 1
1841 end
1842 if sp and sc == penalty then
1843 if where == "page" then
1844 getpagelist()
1845 local p = specialmethods[specialmethod](pagehead,pagetail,current,sp)
1846 if p then
1847
1848
1849
1850
1851
1852 special_penalty = sp
1853 sp = p
1854 end
1855 end
1856 if not penalty_data then
1857 penalty_data = sp
1858 elseif penalty_order < so then
1859 penalty_order, penalty_data = so, sp
1860 elseif penalty_order == so and sp > penalty_data then
1861 penalty_data = sp
1862 end
1863 if trace then
1864 trace_skip("penalty in skip",sc,so,sp,current)
1865 end
1866 head, current = remove_node(head,current,true)
1867 elseif not sc then
1868 if glue_data then
1869 if trace then
1870 trace_done("flush",glue_data)
1871 end
1872 head = insertnodebefore(head,current,glue_data)
1873 if trace then
1874 trace_natural("natural",current)
1875 end
1876 current = getnext(current)
1877 glue_data = nil
1878 else
1879
1880
1881 local previous = getprev(current)
1882 if previous and getid(previous) == glue_code and getsubtype(previous) == userskip_code then
1883 local pwidth, pstretch, pshrink, pstretch_order, pshrink_order = getglue(previous)
1884 local cwidth, cstretch, cshrink, cstretch_order, cshrink_order = getglue(current)
1885 if pstretch_order == 0 and pshrink_order == 0 and cstretch_order == 0 and cshrink_order == 0 then
1886 setglue(previous,pwidth + cwidth, pstretch + cstretch, pshrink + cshrink)
1887 if trace then
1888 trace_natural("removed",current)
1889 end
1890 head, current = remove_node(head,current,true)
1891 if trace then
1892 trace_natural("collapsed",previous)
1893 end
1894 else
1895 if trace then
1896 trace_natural("filler",current)
1897 end
1898 current = getnext(current)
1899 end
1900 else
1901 if trace then
1902 trace_natural("natural (no prev)",current)
1903 end
1904 current = getnext(current)
1905 end
1906 end
1907 glue_order = 0
1908 elseif sc == disable or sc == enable then
1909 local next = getnext(current)
1910 if next then
1911 ignore_following = sc == disable
1912 if trace then
1913 trace_skip(sc == disable and "disable" or "enable",sc,so,sp,current)
1914 end
1915 head, current = remove_node(head,current,true)
1916 else
1917 current = next
1918 end
1919 elseif sc == packed then
1920 if trace then
1921 trace_skip("packed",sc,so,sp,current)
1922 end
1923
1924 head, current = remove_node(head,current,true)
1925 elseif sc == nowhite then
1926 local next = getnext(current)
1927 if next then
1928 ignore_whitespace = true
1929 head, current = remove_node(head,current,true)
1930 else
1931 current = next
1932 end
1933 elseif sc == discard then
1934 if trace then
1935 trace_skip("discard",sc,so,sp,current)
1936 end
1937 head, current = remove_node(head,current,true)
1938 elseif sc == overlay then
1939
1940 if trace then
1941 trace_skip("overlay",sc,so,sp,current)
1942 end
1943
1944
1945 head, current = check_experimental_overlay(head,current,a_snapmethod)
1946 elseif ignore_following then
1947 if trace then
1948 trace_skip("disabled",sc,so,sp,current)
1949 end
1950 head, current = remove_node(head,current,true)
1951 elseif not glue_data then
1952 if trace then
1953 trace_skip("assign",sc,so,sp,current)
1954 end
1955 if sc == keep then
1956 if trace then
1957 trace_skip("keep 1",sc,so,sp,current)
1958 end
1959 dont_discard = true
1960 end
1961 glue_order = so
1962 head, current, glue_data = remove_node(head,current)
1963 elseif glue_order < so then
1964 if trace then
1965 trace_skip("force",sc,so,sp,current)
1966 end
1967 glue_order = so
1968 flushnode(glue_data)
1969 head, current, glue_data = remove_node(head,current)
1970 if sc == keep then
1971 if trace then
1972 trace_skip("keep 2",sc,so,sp,current)
1973 end
1974 dont_discard = true
1975 end
1976 elseif glue_order == so then
1977
1978 if sc == largest then
1979 local cw = getwidth(current)
1980 local gw = getwidth(glue_data)
1981 if cw > gw then
1982 if trace then
1983 trace_skip("largest",sc,so,sp,current)
1984 end
1985 flushnode(glue_data)
1986 head, current, glue_data = remove_node(head,current)
1987 else
1988 if trace then
1989 trace_skip("remove smallest",sc,so,sp,current)
1990 end
1991 head, current = remove_node(head,current,true)
1992 end
1993
1994
1995
1996
1997
1998
1999 elseif sc == goback then
2000 if trace then
2001 trace_skip("goback",sc,so,sp,current)
2002 end
2003 flushnode(glue_data)
2004 head, current, glue_data = remove_node(head,current)
2005 elseif sc == force then
2006
2007
2008 if trace then
2009 trace_skip("force",sc,so,sp,current)
2010 end
2011 flushnode(glue_data)
2012 head, current, glue_data = remove_node(head,current)
2013 elseif sc == penalty then
2014 if trace then
2015 trace_skip("penalty",sc,so,sp,current)
2016 end
2017 flushnode(glue_data)
2018 glue_data = nil
2019 head, current = remove_node(head,current,true)
2020 elseif sc == add then
2021 if trace then
2022 trace_skip("add",sc,so,sp,current)
2023 end
2024 local cwidth, cstretch, cshrink = getglue(current)
2025 local gwidth, gstretch, gshrink = getglue(glue_data)
2026 setglue(glue_data,gwidth + cwidth, gstretch + cstretch,gshrink + cshrink)
2027
2028 head, current = remove_node(head,current,true)
2029 else
2030 if trace then
2031 trace_skip("unknown 1",sc,so,sp,current)
2032 end
2033 head, current = remove_node(head,current,true)
2034if sc == keep then
2035 if trace then
2036 trace_skip("keep 4",sc,so,sp,current)
2037 end
2038 dont_discard = true
2039end
2040 end
2041 else
2042 if trace then
2043 trace_skip("unknown 2",sc,so,sp,current)
2044 end
2045 head, current = remove_node(head,current,true)
2046 end
2047 if sc == force then
2048 force_glue = true
2049 end
2050 elseif subtype == lineskip_code then
2051 if snap then
2052 local s = getattr(current,a_snapmethod)
2053 if s and s ~= 0 then
2054 setattr(current,a_snapmethod,0)
2055 setwidth(current,0)
2056 if trace_vsnapping then
2057 report_snapper("lineskip set to zero")
2058 end
2059 else
2060 if trace then
2061 trace_skip("lineskip",sc,so,sp,current)
2062 end
2063 flush("lineskip")
2064 end
2065 else
2066 if trace then
2067 trace_skip("lineskip",sc,so,sp,current)
2068 end
2069 flush("lineskip")
2070 end
2071 current = getnext(current)
2072 elseif subtype == baselineskip_code then
2073 if snap then
2074 local s = getattr(current,a_snapmethod)
2075 if s and s ~= 0 then
2076 setattr(current,a_snapmethod,0)
2077 setwidth(current,0)
2078 if trace_vsnapping then
2079 report_snapper("baselineskip set to zero")
2080 end
2081 else
2082 if trace then
2083 trace_skip("baselineskip",sc,so,sp,current)
2084 end
2085 flush("baselineskip")
2086 end
2087 else
2088 if trace then
2089 trace_skip("baselineskip",sc,so,sp,current)
2090 end
2091 flush("baselineskip")
2092 end
2093 current = getnext(current)
2094 elseif subtype == parskip_code then
2095
2096 if ignore_whitespace then
2097 if trace then
2098 trace_natural("ignored parskip",current)
2099 end
2100 head, current = remove_node(head,current,true)
2101 elseif glue_data then
2102 local w = getwidth(current)
2103 if w ~= 0 and w > getwidth(glue_data) then
2104 flushnode(glue_data)
2105 glue_data = current
2106 if trace then
2107 trace_natural("taking parskip",current)
2108 end
2109 head, current = remove_node(head,current)
2110 else
2111 if trace then
2112 trace_natural("removed parskip",current)
2113 end
2114 head, current = remove_node(head,current,true)
2115 end
2116 else
2117 if trace then
2118 trace_natural("honored parskip",current)
2119 end
2120 head, current, glue_data = remove_node(head,current)
2121 end
2122 elseif subtype == topskip_code or subtype == splittopskip_code then
2123 local next = getnext(current)
2124
2125 if next and getspecification(next) == notopskip then
2126 setglue(current)
2127 end
2128 if snap then
2129 local s = getattr(current,a_snapmethod)
2130 if s and s ~= 0 then
2131 setattr(current,a_snapmethod,0)
2132 local sv = snapmethods[s]
2133 local w, cw = snap_topskip(current,sv)
2134 if trace_vsnapping then
2135 report_snapper("topskip snapped from %p to %p for %a",w,cw,where)
2136 end
2137 else
2138 if trace then
2139 trace_skip("topskip",sc,so,sp,current)
2140 end
2141 flush("topskip")
2142 end
2143 else
2144 if trace then
2145 trace_skip("topskip",sc,so,sp,current)
2146 end
2147 flush("topskip")
2148 end
2149 current = getnext(current)
2150
2151 elseif subtype == abovedisplayskip_code and remove_math_skips then
2152
2153 if trace then
2154 trace_skip("above display skip (normal)",sc,so,sp,current)
2155 end
2156 flush("above display skip (normal)")
2157 current = getnext(current)
2158
2159 elseif subtype == belowdisplayskip_code and remove_math_skips then
2160
2161 if trace then
2162 trace_skip("below display skip (normal)",sc,so,sp,current)
2163 end
2164 flush("below display skip (normal)")
2165 current = getnext(current)
2166
2167 elseif subtype == abovedisplayshortskip_code and remove_math_skips then
2168
2169 if trace then
2170 trace_skip("above display skip (short)",sc,so,sp,current)
2171 end
2172 flush("above display skip (short)")
2173 current = getnext(current)
2174
2175 elseif subtype == belowdisplayshortskip_code and remove_math_skips then
2176
2177 if trace then
2178 trace_skip("below display skip (short)",sc,so,sp,current)
2179 end
2180 flush("below display skip (short)")
2181 current = getnext(current)
2182
2183
2184 else
2185 if snap and trace_vsnapping then
2186 local w = getwidth(current)
2187 if w ~= 0 then
2188 report_snapper("glue %p of type %a kept",w,gluecodes[subtype])
2189 end
2190 end
2191 if trace then
2192 trace_skip(formatters["glue of type %a"](subtype),sc,so,sp,current)
2193 end
2194 flush("some glue")
2195 current = getnext(current)
2196 end
2197 else
2198 flush(trace and formatters["node with id %a"](id) or "other node")
2199 current = getnext(current)
2200 end
2201 end
2202 if trace then
2203 trace_info("stop analyzing",where,what)
2204 end
2205
2206
2207
2208 if trace and (glue_data or penalty_data) then
2209 trace_info("start flushing",where,what)
2210 end
2211 local tail
2212 if penalty_data then
2213 tail = find_node_tail(head)
2214 local p = new_penalty(penalty_data)
2215 setvisual(p)
2216 if trace then
2217 trace_done("result",p)
2218 end
2219 setlink(tail,p)
2220
2221 local props = properties[p]
2222 if props then
2223 props.special_penalty = special_penalty or penalty_data
2224 else
2225 properties[p] = {
2226 special_penalty = special_penalty or penalty_data
2227 }
2228 end
2229
2230 end
2231 if glue_data then
2232 if not tail then tail = find_node_tail(head) end
2233 if trace then
2234 trace_done("result",glue_data)
2235 end
2236 if force_glue then
2237 checkoptions(head,4)
2238 head, tail = forced_skip(head,tail,getwidth(glue_data),"after",trace)
2239 flushnode(glue_data)
2240 glue_data = nil
2241 elseif tail then
2242 setlink(tail,glue_data)
2243 setnext(glue_data)
2244 checkoptions(glue_data,5)
2245 else
2246 head = glue_data
2247 checkoptions(glue_data,6)
2248 end
2249
2250 texnest[texnest.ptr].prevdepth = 0
2251 end
2252 if trace then
2253 if glue_data or penalty_data then
2254 trace_info("stop flushing",where,what)
2255 end
2256 show_tracing(head)
2257 if oldhead ~= head then
2258 trace_info("head has been changed from %a to %a",nodecodes[getid(oldhead)],nodecodes[getid(head)])
2259 end
2260 end
2261 return head
2262 end
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290 local stackhead = { }
2291 local stacktail = { }
2292 local stackhack = { }
2293
2294 local forceflush = false
2295 local stackmvl = 0
2296
2297 local function report(message,where,lst)
2298 if lst and where then
2299 report_vspacing(message,where,count_nodes(lst,true),nodeidstostring(lst))
2300 else
2301 report_vspacing(message,count_nodes(lst,true),nodeidstostring(lst))
2302 end
2303 end
2304
2305 function vspacing.pagehandler(newhead,where)
2306 if newhead then
2307
2308 local mvl = tex.getcurrentmvl()
2309 if mvl ~= stackmvl then
2310 if stackhead[stackmvl] then
2311 report_vspacing("pending stack entries for mvl %i, changing to %i",stackmvl,mvl)
2312 end
2313
2314
2315
2316
2317
2318 stackmvl = mvl
2319 end
2320
2321 local newtail = find_node_tail(newhead)
2322 local flush = false
2323 stackhack[stackmvl] = true
2324 for n, id, subtype in nextnode, newhead do
2325 if id ~= glue_code then
2326 flush = true
2327 elseif subtype == userskip_code then
2328
2329 local sc = getspecification(n)
2330 if sc then
2331 stackhack[stackmvl] = true
2332 else
2333 flush = true
2334 end
2335 elseif subtype == parskip_code then
2336
2337 if texgetcount(c_spac_vspacing_ignore_parskip) > 0 then
2338 setglue(n)
2339
2340 end
2341 end
2342 end
2343 texsetcount(c_spac_vspacing_ignore_parskip,0)
2344
2345 if forceflush then
2346 forceflush = false
2347 flush = true
2348 end
2349
2350 if flush then
2351 if stackhead[stackmvl] then
2352 if trace_collect_vspacing then report("%s > appending %s nodes to stack (final): %s",where,newhead) end
2353 setlink(stacktail[stackmvl],newhead)
2354 newhead = stackhead[stackmvl]
2355 stackhead[stackmvl] = nil
2356 stacktail[stackmvl] = nil
2357 end
2358 if stackhack[stackmvl] then
2359 stackhack[stackmvl] = false
2360 if trace_collect_vspacing then report("%s > processing %s nodes: %s",where,newhead) end
2361 newhead = collapser(newhead,"page",where,trace_page_vspacing,true,a_snapmethod)
2362 else
2363 if trace_collect_vspacing then report("%s > flushing %s nodes: %s",where,newhead) end
2364 end
2365 return newhead
2366 else
2367 if stackhead[stackmvl] then
2368 if trace_collect_vspacing then report("%s > appending %s nodes to stack (intermediate): %s",where,newhead) end
2369 setlink(stacktail[stackmvl],newhead)
2370 else
2371 if trace_collect_vspacing then report("%s > storing %s nodes in stack (initial): %s",where,newhead) end
2372 stackhead[stackmvl] = newhead
2373 end
2374 stacktail[stackmvl] = newtail
2375 end
2376 end
2377 return nil
2378 end
2379
2380 function vspacing.pageoverflow()
2381 local h = 0
2382 if stackhead[stackmvl] then
2383 for n, id in nextnode, stackhead[stackmvl] do
2384 if id == glue_code then
2385 h = h + getwidth(n)
2386 elseif id == kern_code then
2387 h = h + getkern(n)
2388 end
2389 end
2390 end
2391 return h
2392 end
2393
2394 function vspacing.forcepageflush()
2395 forceflush = true
2396 end
2397
2398 local ignored = table.tohash {
2399 "splitkeep",
2400 "splitoff",
2401
2402 }
2403
2404 function vspacing.vboxhandler(head,where)
2405 if head and not ignored[where] and getnext(head) then
2406 head = collapser(head,"vbox",where,trace_vbox_vspacing,true,a_snapvbox)
2407 end
2408 return head
2409 end
2410
2411 function vspacing.collapsevbox(n,aslist)
2412 local box = getbox(n)
2413 if box then
2414 local list = getlist(box)
2415 if list then
2416 list = collapser(list,"snapper","vbox",trace_vbox_vspacing,true,a_snapmethod)
2417 if aslist then
2418 setlist(box,list)
2419 else
2420 setlist(box,vpack_node(list))
2421 end
2422 end
2423 end
2424 end
2425
2426end
2427
2428
2429
2430
2431
2432do
2433
2434 local outer = tex.getnest(0)
2435
2436 local enabled = true
2437 local trace = false
2438 local report = logs.reporter("vspacing")
2439
2440 trackers.register("vspacing.synchronizepage",function(v)
2441 trace = v
2442 end)
2443
2444 directives.register("vspacing.synchronizepage",function(v)
2445 enabled = v
2446 end)
2447
2448 local function ignoredepth()
2449 return texgetdimen("ignoredepthcriterion")
2450 end
2451
2452
2453
2454
2455
2456
2457
2458
2459 function vspacing.getnofpreviouslines(head)
2460 if enabled then
2461 if not thead then
2462 head = texgetlist("pagehead")
2463 end
2464 local noflines = 0
2465 if head then
2466 local tail = find_node_tail(tonut(head))
2467 while tail do
2468 local id = getid(tail)
2469 if id == hlist_code then
2470 if getsubtype(tail) == linelist_code then
2471 noflines = noflines + 1
2472 else
2473 break
2474 end
2475 elseif id == vlist_code then
2476 break
2477 elseif id == glue_code then
2478 local subtype = getsubtype(tail)
2479 if subtype == baselineskip_code or subtype == lineskip_code then
2480
2481 elseif subtype == parskip_code then
2482 if getwidth(tail) > 0 then
2483 break
2484 else
2485
2486 end
2487 end
2488 elseif id == penalty_code then
2489
2490 elseif id == rule_code or id == kern_code then
2491 break
2492 else
2493
2494 end
2495 tail = getprev(tail)
2496 end
2497 end
2498 return noflines
2499 end
2500 end
2501
2502 interfaces.implement {
2503 name = "getnofpreviouslines",
2504 public = true,
2505 actions = vspacing.getnofpreviouslines,
2506 }
2507
2508 function vspacing.synchronizepage()
2509 if enabled then
2510 if trace then
2511 local newdepth = outer.prevdepth
2512 local olddepth = newdepth
2513 if not texgetlist("pagehead") then
2514 newdepth = ignoredepth()
2515 texset("prevdepth",newdepth)
2516 outer.prevdepth = newdepth
2517 end
2518 report("page %i, prevdepth %p => %p",texgetcount("realpageno"),olddepth,newdepth)
2519
2520 else
2521 if not texgetlist("pagehead") then
2522 local newdepth = ignoredepth()
2523 texset("prevdepth",newdepth)
2524 outer.prevdepth = newdepth
2525 end
2526 end
2527 end
2528 end
2529
2530 local trace = false
2531 local abs = math.abs
2532
2533 local getnodetail = nodes.tail
2534
2535 local vmode_code <const> = tex.modelevels.vertical
2536 local temp_code <const> = nodecodes.temp
2537
2538
2539
2540
2541
2542 function vspacing.checkstrutdepth(depth)
2543 local nest = texgetnest()
2544 if abs(nest.mode) == vmode_code and nest.head then
2545 local tail = nest.tail
2546 local id = tail.id
2547 if id == hlist_code then
2548 if tail.depth < depth then
2549 tail.depth = depth
2550 end
2551 nest.prevdepth = depth
2552 elseif id == temp_code and texgetnest("ptr") == 0 then
2553
2554 local head = texgetlist("pagehead")
2555 if head then
2556 tail = getnodetail(head)
2557 if tail and tail.id == hlist_code then
2558 if tail.depth < depth then
2559 tail.depth = depth
2560 end
2561 nest.prevdepth = depth
2562
2563 texset("pagedepth",depth)
2564 end
2565 end
2566 end
2567 end
2568 end
2569
2570 interfaces.implement {
2571 name = "checkstrutdepth",
2572 arguments = "dimension",
2573 actions = vspacing.checkstrutdepth,
2574 }
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639 local hlist_code <const> = nodes.nodecodes.hlist
2640 local insert_code <const> = nodes.nodecodes.insert
2641 local mark_code <const> = nodes.nodecodes.mark
2642 local line_code <const> = nodes.listcodes.line
2643
2644
2645
2646
2647
2648
2649 local gettotal = nuts.gettotal
2650 local getspeciallist = nuts.getspeciallist
2651 local setspeciallist = nuts.setspeciallist
2652 local getmvllist = nuts.getmvllist
2653 local setmvllist = nuts.setmvllist
2654
2655 local triggerbuildpage = tex.triggerbuildpage
2656
2657
2658
2659 local texgetnest = tex.getnest
2660
2661
2662
2663 local trace = false trackers.register("otr.forcestrutdepth", function(v)
2664 trace = v and function(n)
2665 setvisual(nuts.tonut(n),nodes.visualizers.modes.depth)
2666 end
2667 end)
2668
2669 local treversenode = nuts.treversers.node
2670
2671 local function flushcontributions()
2672 if texgetnest("ptr") == 0 then
2673
2674 local prev = nil
2675 local cycle = 1
2676 while cycle <= 10 do
2677 local head = getspeciallist("contributehead")
2678 if head == prev then
2679
2680
2681 cycle = cycle + 1
2682 else
2683 triggerbuildpage()
2684 prev = head
2685 end
2686 end
2687 return true
2688 else
2689 return false
2690 end
2691 end
2692
2693 vspacing.flushcontributions = flushcontributions
2694
2695 function vspacing.forcestrutdepth()
2696
2697 if flushcontributions() then
2698
2699 local head, tail = getspeciallist("pagehead")
2700 if tail then
2701 for n, id, subtype in treversenode, tail do
2702 if id == hlist_code then
2703 if subtype == line_code then
2704 local strutdp = texgetdimen(d_strutdp)
2705 local delta = strutdp - getdepth(n)
2706 if delta > 0 then
2707
2708 setdepth(n,strutdp)
2709 texset("pagetotal",texget("pagetotal") + delta)
2710 texset("pagedepth",strutdp)
2711 if trace then
2712 trace(n)
2713 end
2714 end
2715 end
2716 break
2717 elseif id == insert_code or id == mark_code then
2718
2719 else
2720
2721
2722
2723 break
2724
2725 end
2726 end
2727 end
2728 else
2729 local nest = texgetnest()
2730
2731 local tail = nest.tail
2732 if tail.id == hlist_code and tail.subtype == line_code then
2733 local strutdp = texgetdimen(d_strutdp)
2734 if tail.depth < strutdp then
2735 tail.depth = strutdp
2736 end
2737 nest.prevdepth = strutdp
2738 if trace then
2739 trace(tail)
2740 end
2741 end
2742
2743 end
2744 end
2745
2746
2747
2748 local setbox = nuts.setbox
2749
2750 function vspacing.interceptsamepagecontent(box)
2751 if vspacing.flushcontributions() then
2752
2753
2754local head, tail = getmvllist()
2755 if tail and getid(tail) == glue_code then
2756 local prev = getprev(tail)
2757 if prev and getid(prev) == penalty_code then
2758 if getpenalty(prev) >= 10000 then
2759 local state = nil
2760 local first = nil
2761 local last = tail
2762 local c = getprev(prev)
2763 while c do
2764 if getid(c) == glue_code then
2765 local p = getprev(c)
2766 if p and getid(p) == penalty_code then
2767 if getpenalty(p) < 10000 then
2768 state = 1
2769 end
2770 else
2771 state = 2
2772 break
2773 end
2774 end
2775 first = c
2776 c = getprev(c)
2777 end
2778 if first and first ~= head then
2779 setnext(getprev(first))
2780 setprev(first)
2781 local vbox = vpack_node(first)
2782 setvisual(vbox)
2783 setbox(box,vbox)
2784 report_vspacing("same page intercept, case %i")
2785 end
2786 end
2787 end
2788 end
2789 end
2790 end
2791
2792 interfaces.implement {
2793 name = "interceptsamepagecontent",
2794 arguments = "integer",
2795 actions = vspacing.interceptsamepagecontent,
2796 }
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812 function vspacing.pushatsame()
2813
2814 if last then
2815 nuts.setnext(getprev(last))
2816 nuts.setprev(last)
2817 end
2818 end
2819
2820 function vspacing.popatsame()
2821
2822 nuts.write(last)
2823 end
2824
2825end
2826
2827
2828
2829do
2830
2831 local implement = interfaces.implement
2832
2833 implement {
2834 name = "injectvspacing",
2835 actions = vspacing.inject,
2836 arguments = { "integer", "string" },
2837 }
2838
2839 implement {
2840 name = "injectvpenalty",
2841 actions = vspacing.injectpenalty,
2842 arguments = "integer",
2843 }
2844
2845 implement {
2846 name = "injectvskip",
2847 actions = vspacing.injectskip,
2848 arguments = "dimension",
2849 }
2850
2851 implement {
2852 name = "injectdisable",
2853 actions = vspacing.injectdisable,
2854 }
2855
2856
2857
2858 implement {
2859 name = "synchronizepage",
2860 actions = vspacing.synchronizepage,
2861 scope = "private"
2862 }
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878 implement {
2879 name = "forcestrutdepth",
2880 public = true,
2881 protected = true,
2882 actions = vspacing.forcestrutdepth,
2883 }
2884
2885 implement {
2886 name = "pushatsame",
2887 actions = vspacing.pushatsame,
2888 scope = "private"
2889 }
2890
2891 implement {
2892 name = "popatsame",
2893 actions = vspacing.popatsame,
2894 scope = "private"
2895 }
2896
2897 implement {
2898 name = "vspacingsetamount",
2899 actions = vspacing.setskip,
2900 scope = "private",
2901 arguments = "string",
2902 }
2903
2904 implement {
2905 name = "vspacingdefine",
2906 actions = vspacing.setmap,
2907 scope = "private",
2908 arguments = "2 strings",
2909 }
2910
2911 implement {
2912 name = "vspacingcollapse",
2913 actions = vspacing.collapsevbox,
2914 scope = "private",
2915 arguments = "integer"
2916 }
2917
2918 implement {
2919 name = "vspacingcollapseonly",
2920 actions = vspacing.collapsevbox,
2921 scope = "private",
2922 arguments = { "integer", true }
2923 }
2924
2925 implement {
2926 name = "vspacingsnap",
2927 actions = vspacing.snapbox,
2928 scope = "private",
2929 arguments = "2 integers",
2930 }
2931
2932 local integer_value = tokens.values.integer
2933
2934 implement {
2935 name = "definesnapmethod",
2936 usage = "value",
2937 actions = function(key,value)
2938 return integer_value, vspacing.definesnapmethod(key,value)
2939 end,
2940 scope = "private",
2941 arguments = "2 strings",
2942 }
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959 local buildpage = tex.triggerbuildpage
2960
2961 implement {
2962 name = "removelastline",
2963 actions = function()
2964 local head = texgetlist("pagehead")
2965 if head then
2966 local tail = find_node_tail(head)
2967 if tail then
2968
2969 local head = remove_node(head,tail,true)
2970 texsetlist("pagehead", head)
2971 buildpage()
2972 end
2973 end
2974 end
2975 }
2976
2977 implement {
2978 name = "showpagelist",
2979 actions = function()
2980 local head = texgetlist("pagehead")
2981 if head then
2982 print("start")
2983 while head do
2984 print(" " .. tostring(head))
2985 head = head.next
2986 end
2987 end
2988 end
2989 }
2990
2991 implement {
2992 name = "pageoverflow",
2993 actions = { vspacing.pageoverflow, context }
2994 }
2995
2996 implement {
2997 name = "forcepageflush",
2998 actions = vspacing.forcepageflush
2999 }
3000
3001 implement {
3002 name = "injectzerobaselineskip",
3003 protected = true,
3004 public = true,
3005 actions = { nodes.pool.baselineskip, context },
3006 }
3007
3008end
3009 |