beta-0.89.2
[luatex.git] / source / texk / web2c / luatexdir / tex / packaging.w
blob7541339dc36f2bf61b6729cdfee01956962ea8be
1 % packaging.w
3 % Copyright 2009-2010 Taco Hoekwater <taco@@luatex.org>
5 % This file is part of LuaTeX.
7 % LuaTeX is free software; you can redistribute it and/or modify it under
8 % the terms of the GNU General Public License as published by the Free
9 % Software Foundation; either version 2 of the License, or (at your
10 % option) any later version.
12 % LuaTeX is distributed in the hope that it will be useful, but WITHOUT
13 % ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 % FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
15 % License for more details.
17 % You should have received a copy of the GNU General Public License along
18 % with LuaTeX; if not, see <http://www.gnu.org/licenses/>.
20 @ @c
22 #include "ptexlib.h"
24 @ @c
25 #define scan_normal_dimen() scan_dimen(false,false,false)
27 #define prev_depth cur_list.prev_depth_field
28 #define space_factor cur_list.space_factor_field
29 #define box(A) eqtb[box_base+(A)].hh.rh
31 #define every_hbox equiv(every_hbox_loc)
32 #define every_vbox equiv(every_vbox_loc)
33 #define box_max_depth dimen_par(box_max_depth_code)
35 @ We're essentially done with the parts of \TeX\ that are concerned with the
36 input (|get_next|) and the output (|ship_out|). So it's time to get heavily into
37 the remaining part, which does the real work of typesetting.
39 After lists are constructed, \TeX\ wraps them up and puts them into boxes. Two
40 major subroutines are given the responsibility for this task: |hpack| applies to
41 horizontal lists (hlists) and |vpack| applies to vertical lists (vlists). The
42 main duty of |hpack| and |vpack| is to compute the dimensions of the resulting
43 boxes, and to adjust the glue if one of those dimensions is pre-specified. The
44 computed sizes normally enclose all of the material inside the new box; but some
45 items may stick out if negative glue is used, if the box is overfull, or if a
46 \.{\\vbox} includes other boxes that have been shifted left.
48 The subroutine call |hpack(p,w,m)| returns a pointer to an |hlist_node| for a box
49 containing the hlist that starts at |p|. Parameter |w| specifies a width; and
50 parameter |m| is either `|exactly|' or `|additional|'. Thus, |hpack(p,w,exactly)|
51 produces a box whose width is exactly |w|, while |hpack(p,w,additional)| yields a
52 box whose width is the natural width plus |w|. It is convenient to define a macro
53 called `|natural|' to cover the most common case, so that we can say
54 |hpack(p,natural)| to get a box that has the natural width of list |p|.
56 Similarly, |vpack(p,w,m)| returns a pointer to a |vlist_node| for a box
57 containing the vlist that starts at |p|. In this case |w| represents a height
58 instead of a width; the parameter |m| is interpreted as in |hpack|.
60 @ The parameters to |hpack| and |vpack| correspond to \TeX's primitives like
61 `\.{\\hbox} \.{to} \.{300pt}', `\.{\\hbox} \.{spread} \.{10pt}'; note that
62 `\.{\\hbox}' with no dimension following it is equivalent to `\.{\\hbox}
63 \.{spread} \.{0pt}'. The |scan_spec| subroutine scans such constructions in the
64 user's input, including the mandatory left brace that follows them, and it puts
65 the specification onto |save_stack| so that the desired box can later be obtained
66 by executing the following code: $$\vbox{\halign{#\hfil\cr
67 |save_ptr:=save_ptr-1;|\cr |hpack(p,saved_value(0),saved_level(0)).|\cr}}$$
71 void scan_spec(group_code c)
73 int spec_code;
74 if (scan_keyword("to")) {
75 spec_code = exactly;
76 scan_normal_dimen();
77 } else if (scan_keyword("spread")) {
78 spec_code = additional;
79 scan_normal_dimen();
80 } else {
81 spec_code = additional;
82 cur_val = 0;
84 set_saved_record(0, saved_boxspec, spec_code, cur_val);
85 save_ptr++;
86 new_save_level(c);
87 scan_left_brace();
91 void scan_spec(group_code c)
92 { /* scans a box specification and left brace */
93 int spec_code;
94 boolean done = false ;
95 do {
96 get_x_token();
97 } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));
98 if (cur_cmd == left_brace_cmd) {
99 spec_code = additional;
100 cur_val = 0;
101 done = true;
102 } else {
103 /* todo: attr */
104 back_input();
105 if (scan_keyword("to")) {
106 spec_code = exactly;
107 scan_normal_dimen();
108 } else if (scan_keyword("spread")) {
109 spec_code = additional;
110 scan_normal_dimen();
111 } else {
112 spec_code = additional;
113 cur_val = 0;
116 set_saved_record(0, saved_boxspec, spec_code, cur_val);
117 save_ptr++;
118 new_save_level(c);
119 if (!done) {
120 scan_left_brace();
124 @ When scanning, special care is necessary to ensure that the special
125 |save_stack| codes are placed just below the new group code, because scanning can
126 change |save_stack| when \.{\\csname} appears.
128 This coincides with the text on |dir| and |attr| keywords, as these are exaclty
129 the uses of \.{\\hbox}, \.{\\vbox}, and \.{\\vtop} in the input stream (the
130 others are \.{\\vcenter}, \.{\\valign}, and \.{\\halign}).
134 void scan_full_spec(group_code c, int spec_direction)
136 int s;
137 int i;
138 int v;
139 int spec_code;
140 halfword attr_list;
141 if (attr_list_cache == cache_disabled)
142 update_attribute_cache();
143 attr_list = attr_list_cache;
144 s = saved_value(0);
145 CONTINUE:
146 while (cur_cmd == relax_cmd || cur_cmd == spacer_cmd) {
147 get_x_token();
148 if (cur_cmd != relax_cmd && cur_cmd != spacer_cmd)
149 back_input();
151 if (scan_keyword("attr")) {
152 scan_register_num();
153 i = cur_val;
154 scan_optional_equals();
155 scan_int();
156 v = cur_val;
157 if ((attr_list != null) && (attr_list == attr_list_cache)) {
158 attr_list = copy_attribute_list(attr_list_cache);
159 add_node_attr_ref(attr_list);
161 attr_list = do_set_attribute(attr_list, i, v);
162 goto CONTINUE;
164 if (scan_keyword("dir")) {
165 scan_direction();
166 spec_direction = cur_val;
167 goto CONTINUE;
169 if (attr_list == attr_list_cache) {
170 add_node_attr_ref(attr_list);
172 if (scan_keyword("to")) {
173 spec_code = exactly;
174 } else if (scan_keyword("spread")) {
175 spec_code = additional;
176 } else {
177 spec_code = additional;
178 cur_val = 0;
179 goto FOUND;
181 scan_normal_dimen();
182 FOUND:
183 set_saved_record(0, saved_boxcontext, 0, s);
184 set_saved_record(1, saved_boxspec, spec_code, cur_val);
185 if (spec_direction != -1) {
186 set_saved_record(2, saved_boxdir, spec_direction, text_dir_ptr);
187 text_dir_ptr = new_dir(spec_direction);
188 } else {
189 set_saved_record(2, saved_boxdir, spec_direction, null);
191 set_saved_record(3, saved_boxattr, 0, attr_list);
192 save_ptr += 4;
193 new_save_level(c);
194 scan_left_brace();
195 eq_word_define(int_base + body_direction_code, spec_direction);
196 eq_word_define(int_base + par_direction_code, spec_direction);
197 eq_word_define(int_base + text_direction_code, spec_direction);
201 /* scans a box specification and left brace */
203 void scan_full_spec(group_code c, int spec_direction, int just_pack)
205 int s;
206 int i;
207 int v;
208 int spec_code;
209 boolean done = false ;
210 halfword attr_list;
211 if (attr_list_cache == cache_disabled)
212 update_attribute_cache();
213 attr_list = attr_list_cache;
214 s = saved_value(0); /* the box context */
215 do {
216 get_x_token();
217 } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));
218 if (cur_cmd == left_brace_cmd) {
219 goto QUICK;
220 } else {
221 back_input();
222 goto KEYWORDS;
224 CONTINUE:
225 while (cur_cmd == relax_cmd || cur_cmd == spacer_cmd) {
226 get_x_token();
227 if (cur_cmd == left_brace_cmd) {
228 goto QUICK;
229 } else if (cur_cmd != relax_cmd && cur_cmd != spacer_cmd) {
230 back_input();
231 break;
234 KEYWORDS:
235 if (scan_keyword("attr")) {
236 scan_register_num();
237 i = cur_val;
238 scan_optional_equals();
239 scan_int();
240 v = cur_val;
241 if ((attr_list != null) && (attr_list == attr_list_cache)) {
242 attr_list = copy_attribute_list(attr_list_cache);
243 add_node_attr_ref(attr_list); /* will be used once */
245 attr_list = do_set_attribute(attr_list, i, v);
246 goto CONTINUE;
248 if (scan_keyword("dir")) {
249 scan_direction();
250 spec_direction = cur_val;
251 goto CONTINUE;
253 if (attr_list == attr_list_cache) {
254 add_node_attr_ref(attr_list);
256 if (scan_keyword("to")) {
257 spec_code = exactly;
258 } else if (scan_keyword("spread")) {
259 spec_code = additional;
260 } else {
261 spec_code = additional;
262 cur_val = 0;
263 goto FOUND;
265 scan_normal_dimen();
266 goto FOUND;
267 QUICK:
268 spec_code = additional;
269 cur_val = 0;
270 add_node_attr_ref(attr_list);
271 done = true;
272 FOUND:
273 set_saved_record(0, saved_boxcontext, 0, s);
274 set_saved_record(1, saved_boxspec, spec_code, cur_val);
275 /* DIR: Adjust |text_dir_ptr| for |scan_spec| */
276 if (spec_direction != -1) {
277 set_saved_record(2, saved_boxdir, spec_direction, text_dir_ptr);
278 text_dir_ptr = new_dir(spec_direction);
279 } else {
280 set_saved_record(2, saved_boxdir, spec_direction, null);
282 set_saved_record(3, saved_boxattr, 0, attr_list);
283 set_saved_record(4, saved_boxpack, 0, just_pack);
284 save_ptr += 5;
285 new_save_level(c);
286 if (! done) {
287 scan_left_brace();
289 /* no gain: if (body_direction != spec_direction) etc */
290 eq_word_define(int_base + body_direction_code, spec_direction);
291 eq_word_define(int_base + par_direction_code, spec_direction);
292 eq_word_define(int_base + text_direction_code, spec_direction);
295 @ To figure out the glue setting, |hpack| and |vpack| determine how much
296 stretchability and shrinkability are present, considering all four orders of
297 infinity. The highest order of infinity that has a nonzero coefficient is then
298 used as if no other orders were present.
300 For example, suppose that the given list contains six glue nodes with the
301 respective stretchabilities 3pt, 8fill, 5fil, 6pt, $-3$fil, $-8$fill. Then the
302 total is essentially 2fil; and if a total additional space of 6pt is to be
303 achieved by stretching, the actual amounts of stretch will be 0pt, 0pt, 15pt,
304 0pt, $-9$pt, and 0pt, since only `fil' glue will be considered. (The `fill' glue
305 is therefore not really stretching infinitely with respect to `fil'; nobody would
306 actually want that to happen.)
308 The arrays |total_stretch| and |total_shrink| are used to determine how much glue
309 of each kind is present. A global variable |last_badness| is used to implement
310 \.{\\badness}.
313 scaled total_stretch[5];
314 scaled total_shrink[5]; /* glue found by |hpack| or |vpack| */
315 int last_badness; /* badness of the most recently packaged box */
317 @ If the global variable |adjust_tail| is non-null, the |hpack| routine also
318 removes all occurrences of |ins_node|, |mark_node|, and |adjust_node| items and
319 appends the resulting material onto the list that ends at location |adjust_tail|.
322 halfword adjust_tail; /* tail of adjustment list */
325 @ Materials in \.{\\vadjust} used with \.{pre} keyword will be appended to
326 |pre_adjust_tail| instead of |adjust_tail|.
329 halfword pre_adjust_tail;
331 halfword last_leftmost_char;
332 halfword last_rightmost_char;
334 halfword next_char_p; /* pointer to the next char of an implicit kern */
335 halfword prev_char_p; /* pointer to the previous char of an implicit kern */
337 @ This procedure is called repeatedly from inside the line break algorithm.
339 void set_prev_char_p(halfword p)
341 prev_char_p = p;
344 @ @c
345 scaled char_stretch(halfword p)
347 internal_font_number f = font(p);
348 int m = font_max_stretch(f);
349 if (m > 0) {
350 int c = character(p);
351 int ef = get_ef_code(f, c);
352 if (ef > 0) {
353 scaled dw = calc_char_width(f, c, m) - char_width(f, c);
354 if (dw > 0) {
355 return round_xn_over_d(dw, ef, 1000);
359 return 0;
362 @ @c
363 scaled char_shrink(halfword p)
365 internal_font_number f = font(p);
366 int m = font_max_shrink(f);
367 if (m > 0) {
368 int c = character(p);
369 int ef = get_ef_code(f, c);
370 if (ef > 0) {
371 scaled dw = char_width(f, c) - calc_char_width(f, c, -m);
372 if (dw > 0) {
373 return round_xn_over_d(dw, ef, 1000);
377 return 0;
380 @ @c
381 scaled kern_stretch(halfword p)
383 halfword l, r;
384 scaled d;
385 int m;
386 if ((prev_char_p == null) || (vlink(prev_char_p) != p) || (vlink(p) == null))
387 return 0;
388 l = prev_char_p;
389 /* we need a left char */
390 if (!is_char_node(l))
391 return 0;
392 r = vlink(p);
393 /* and a right char */
394 if (!is_char_node(r))
395 return 0;
396 /* and a reason to kern */
397 if ((font(l) != font(r)) || (font_max_stretch(font(l)) == 0))
398 return 0;
399 m = font_max_stretch(font(l));
400 d = get_kern(font(l), character(l), character(r));
401 d = round_xn_over_d(d, 1000 + m, 1000);
402 return round_xn_over_d(d - width(p), get_ef_code(font(l), character(l)), 1000);
405 @ @c
406 scaled kern_shrink(halfword p)
408 halfword l, r;
409 scaled d;
410 int m;
411 if ((prev_char_p == null) || (vlink(prev_char_p) != p) || (vlink(p) == null))
412 return 0;
413 l = prev_char_p;
414 /* we need a left char */
415 if (!is_char_node(l))
416 return 0;
417 r = vlink(p);
418 /* and a right char */
419 if (!is_char_node(r))
420 return 0;
421 /* and a reason to kern */
422 if ((font(l) != font(r)) || (font_max_shrink(font(l)) == 0))
423 return 0;
424 m = font_max_stretch(font(l));
425 d = get_kern(font(l), character(l), character(r));
426 d = round_xn_over_d(d, 1000 - m, 1000);
427 return round_xn_over_d(width(p) - d, get_ef_code(font(l), character(l)), 1000);
430 @ @c
431 void do_subst_font(halfword p, int ex_ratio)
433 if (type(p) == disc_node) {
434 halfword r = vlink(pre_break(p));
435 while (r != null) {
436 if (is_char_node(r))
437 do_subst_font(r, ex_ratio);
438 r = vlink(r);
440 r = vlink(post_break(p));
441 while (r != null) {
442 if (is_char_node(r))
443 do_subst_font(r, ex_ratio);
444 r = vlink(r);
446 r = vlink(no_break(p));
447 while (r != null) {
448 if (is_char_node(r))
449 do_subst_font(r, ex_ratio);
450 r = vlink(r);
452 return;
454 if (! is_char_node(p)) {
455 normal_error("font expansion", "invalid node type");
456 return;
457 } else {
458 internal_font_number f = font(p);
459 int ef = get_ef_code(f, character(p));
460 if (ef == 0)
461 return;
462 if ((font_max_stretch(f) > 0) && (ex_ratio > 0)) {
463 int ex_stretch = ext_xn_over_d(ex_ratio * ef, font_max_stretch(f), 1000000);
464 ex_glyph(p) = fix_expand_value(f, ex_stretch)*1000;
465 } else if ((font_max_shrink(f) > 0) && (ex_ratio < 0)) {
466 int ex_shrink = ext_xn_over_d(ex_ratio * ef, font_max_shrink(f), 1000000);
467 ex_glyph(p) = fix_expand_value(f, ex_shrink)*1000;
472 @ @c
473 scaled char_pw(halfword p, int side)
475 internal_font_number f;
476 int c, w;
477 if (side == left_side)
478 last_leftmost_char = null;
479 else
480 last_rightmost_char = null;
481 if (p == null)
482 return 0;
483 if (!is_char_node(p))
484 return 0;
485 f = font(p);
486 if (side == left_side) {
487 c = get_lp_code(f, character(p));
488 last_leftmost_char = p;
489 } else {
490 c = get_rp_code(f, character(p));
491 last_rightmost_char = p;
493 if (c == 0)
494 return 0;
495 w = quad(f);
496 return round_xn_over_d(w, c, 1000);
499 @ @c
500 halfword new_margin_kern(scaled w, halfword p, int side)
502 halfword k, q;
503 k = new_node(margin_kern_node, side);
504 width(k) = w;
505 if (p == null)
506 normal_error("margin kerning", "invalid pointer to marginal char node");
507 q = new_char(font(p), character(p));
508 margin_char(k) = q;
509 return k;
512 @ Here is |hpack|, which is place where we do font substituting when font
513 expansion is being used.
516 halfword hpack(halfword p, scaled w, int m, int pack_direction)
518 halfword r; /* the box node that will be returned */
519 halfword q; /* trails behind |p| */
520 scaled h = 0; /* height */
521 scaled d = 0; /* depth */
522 scaled x = 0; /* natural width */
523 scaled_whd whd;
524 scaled s; /* shift amount */
525 halfword g; /* points to a glue specification */
526 int o; /* order of infinity */
527 halfword dir_ptr1 = null; /* for managing the direction stack */
528 int hpack_dir; /* the current direction */
529 int disc_level = 0;
530 halfword pack_interrupt[8];
531 scaled font_stretch = 0;
532 scaled font_shrink = 0;
533 int font_expand_ratio = 0; /* current expansion ratio */
534 scaled k = 0;
535 last_badness = 0;
536 r = new_node(hlist_node, min_quarterword); /* the box node that will be returned */
537 if (pack_direction == -1) {
538 hpack_dir = text_direction;
539 } else {
540 hpack_dir = pack_direction;
542 box_dir(r) = hpack_dir;
544 potential optimimization, save a little but neglectable in practice (not so
545 many empty boxes are used)
547 if (p == null) {
548 width(r) = w;
549 return r;
552 push_dir(dir_ptr1,hpack_dir); /* push null */
553 q = r + list_offset; /* hm, adding something to a node address? */
554 vlink(q) = p;
555 if (m == cal_expand_ratio) {
556 prev_char_p = null; /* why not always */
558 total_stretch[normal] = 0;
559 total_shrink[normal] = 0;
560 total_stretch[sfi] = 0;
561 total_shrink[sfi] = 0;
562 total_stretch[fil] = 0;
563 total_shrink[fil] = 0;
564 total_stretch[fill] = 0;
565 total_shrink[fill] = 0;
566 total_stretch[filll] = 0;
567 total_shrink[filll] = 0;
569 RESWITCH:
570 while ((p != null) || (disc_level > 0)) {
571 if (p == null) {
572 decr(disc_level);
573 p = pack_interrupt[disc_level];
574 goto RESWITCH;
577 Examine node |p| in the hlist, taking account of its effect
578 on the dimensions of the new box, or moving it to the adjustment list;
579 then advance |p| to the next node
581 while (is_char_node(p)) {
583 Incorporate character dimensions into the dimensions of the hbox
584 that will contain~it, then move to the next node.
586 The following code is part of \TeX's inner loop; i.e., adding
587 another character of text to the user's input will cause each of
588 these instructions to be exercised one more time.
590 if (m >= cal_expand_ratio) {
591 prev_char_p = p;
592 if (m == cal_expand_ratio) {
593 font_stretch += char_stretch(p);
594 font_shrink += char_shrink(p);
595 } else if (m == subst_ex_font) {
596 do_subst_font(p, font_expand_ratio);
599 whd = pack_width_height_depth(hpack_dir, dir_TRT, p, true);
600 x += whd.wd;
601 if (whd.ht > h)
602 h = whd.ht;
603 if (whd.dp > d)
604 d = whd.dp;
605 p = vlink(p);
607 if (p != null) {
608 switch (type(p)) {
609 case hlist_node:
610 case vlist_node:
612 Incorporate box dimensions into the dimensions of the hbox
613 that will contain~it
615 The code here implicitly uses the fact that running dimensions are
616 indicated by |null_flag|, which will be ignored in the calculations
617 because it is a highly negative number.
619 s = shift_amount(p);
620 whd = pack_width_height_depth(hpack_dir, box_dir(p), p, false);
621 x += whd.wd;
622 if (whd.ht - s > h)
623 h = whd.ht - s;
624 if (whd.dp + s > d)
625 d = whd.dp + s;
626 break;
628 case rule_node:
629 case unset_node:
630 x += width(p);
631 if (type(p) >= rule_node) // always
632 s = 0;
633 else
634 s = shift_amount(p);
635 if (height(p) - s > h)
636 h = height(p) - s;
637 if (depth(p) + s > d)
638 d = depth(p) + s;
639 break;
641 case rule_node:
642 case unset_node:
643 x += width(p);
644 if (height(p) > h)
645 h = height(p);
646 if (depth(p) > d)
647 d = depth(p);
648 break;
649 /* */
650 case glue_node:
651 /* Incorporate glue into the horizontal totals */
652 g = glue_ptr(p);
653 x += width(g);
654 o = stretch_order(g);
655 total_stretch[o] = total_stretch[o] + stretch(g);
656 o = shrink_order(g);
657 total_shrink[o] = total_shrink[o] + shrink(g);
658 if (subtype(p) >= a_leaders) {
659 g = leader_ptr(p);
660 if (height(g) > h)
661 h = height(g);
662 if (depth(g) > d)
663 d = depth(g);
665 break;
666 case kern_node:
667 if (subtype(p) == normal) {
668 if (m == cal_expand_ratio) {
669 font_stretch = font_stretch + kern_stretch(p);
670 font_shrink = font_shrink + kern_shrink(p);
671 } else if (m == subst_ex_font) {
672 if (font_expand_ratio > 0)
673 k = kern_stretch(p);
674 else if (font_expand_ratio < 0)
675 k = kern_shrink(p);
676 if (k != 0)
677 width(p) = get_kern(font(prev_char_p), character(prev_char_p), character(vlink(p)));
680 x += width(p);
681 break;
682 case disc_node:
683 if (m == subst_ex_font)
684 do_subst_font(p, font_expand_ratio);
685 if ((subtype(p) != select_disc) && (vlink(no_break(p)) != null)) {
686 pack_interrupt[disc_level] = vlink(p);
687 incr(disc_level);
688 p = no_break(p);
690 break;
691 case dir_node:
692 /* Adjust the dir stack for the |hpack| routine */
693 if (dir_dir(p) >= 0) {
694 hpack_dir = dir_dir(p);
695 push_dir_node(dir_ptr1,p);
696 } else {
697 pop_dir_node(dir_ptr1);
698 if (dir_ptr1 != null)
699 hpack_dir = dir_dir(dir_ptr1);
701 break;
702 case math_node:
703 /* begin mathskip code */
704 if (glue_ptr(p) == zero_glue) {
705 x += surround(p);
706 break;
707 } else {
708 /* fall through: mathskip */
710 /* end mathskip code */
711 case margin_kern_node:
712 if (m == cal_expand_ratio) {
713 int f = font(margin_char(p));
714 do_subst_font(margin_char(p), 1000);
715 if (f != font(margin_char(p)))
716 font_stretch = font_stretch - width(p) - char_pw(margin_char(p), subtype(p));
717 font(margin_char(p)) = f;
718 do_subst_font(margin_char(p), -1000);
719 if (f != font(margin_char(p)))
720 font_shrink = font_shrink - width(p) - char_pw(margin_char(p), subtype(p));
721 font(margin_char(p)) = f;
722 } else if (m == subst_ex_font) {
723 do_subst_font(margin_char(p), font_expand_ratio);
724 width(p) = -char_pw(margin_char(p), subtype(p));
726 x += width(p);
727 break;
728 case ins_node:
729 case mark_node:
730 case adjust_node:
732 Transfer node |p| to the adjustment list.
734 Although node |q| is not necessarily the immediate predecessor of node |p|,
735 it always points to some node in the list preceding |p|. Thus, we can delete
736 nodes by moving |q| when necessary. The algorithm takes linear time, and the
737 extra computation does not intrude on the inner loop unless it is necessary
738 to make a deletion.
740 if (adjust_tail != null || pre_adjust_tail != null) {
741 while (vlink(q) != p)
742 q = vlink(q);
743 if (type(p) == adjust_node) {
744 if (adjust_pre(p) != 0)
745 update_adjust_list(pre_adjust_tail);
746 else
747 update_adjust_list(adjust_tail);
748 p = vlink(p);
749 adjust_ptr(vlink(q)) = null;
750 flush_node(vlink(q));
751 } else {
752 vlink(adjust_tail) = p;
753 adjust_tail = p;
754 p = vlink(p);
756 vlink(q) = p;
757 p = q;
759 break;
760 /* */
761 default:
762 break;
764 p = vlink(p);
769 if (adjust_tail != null)
770 vlink(adjust_tail) = null;
771 if (pre_adjust_tail != null)
772 vlink(pre_adjust_tail) = null;
773 height(r) = h;
774 depth(r) = d;
776 Determine the value of |width(r)| and the appropriate glue setting; then
777 |return| or |goto common_ending|.
779 When we get to the present part of the program, |x| is the natural width
780 of the box being packaged.
782 if (m == additional)
783 w = x + w;
784 width(r) = w;
785 x = w - x;
786 /* now |x| is the excess to be made up */
787 if (x == 0) {
788 glue_sign(r) = normal;
789 glue_order(r) = normal;
790 set_glue_ratio_zero(glue_set(r));
791 goto EXIT;
792 } else if (x > 0) {
794 Determine horizontal glue stretch setting, then |return|
795 or \hbox{|goto common_ending|}.
797 If |hpack| is called with |m=cal_expand_ratio| we calculate
798 |font_expand_ratio| and return without checking for overfull or
799 underfull box.
801 if (total_stretch[filll] != 0)
802 o = filll;
803 else if (total_stretch[fill] != 0)
804 o = fill;
805 else if (total_stretch[fil] != 0)
806 o = fil;
807 else if (total_stretch[sfi] != 0)
808 o = sfi;
809 else
810 o = normal;
812 if ((m == cal_expand_ratio) && (o == normal) && (font_stretch > 0)) {
813 font_expand_ratio = divide_scaled_n(x, font_stretch, 1000.0);
814 goto EXIT;
816 glue_order(r) = (quarterword) o;
817 glue_sign(r) = stretching;
818 if (total_stretch[o] != 0) {
819 glue_set(r) = unfloat((double) x / total_stretch[o]);
820 } else {
821 /* there's nothing to stretch */
822 glue_sign(r) = normal;
823 set_glue_ratio_zero(glue_set(r));
825 if (o == normal) {
826 if (list_ptr(r) != null) {
828 Report an underfull hbox and |goto common_ending|, if this box
829 is sufficiently bad.
831 last_badness = badness(x, total_stretch[normal]);
832 if (last_badness > int_par(hbadness_code)) {
833 int callback_id = callback_defined(hpack_quality_callback);
834 if (callback_id > 0) {
835 halfword rule = null;
836 if (last_badness > 100) {
837 run_callback(callback_id, "SdNdd->N","underfull",last_badness,r,abs(pack_begin_line),line,&rule);
838 } else {
839 run_callback(callback_id, "SdNdd->N","loose",last_badness,r,abs(pack_begin_line),line,&rule);
841 if (rule != null) {
842 while (vlink(q) != null) {
843 q = vlink(q);
845 couple_nodes(q,rule);
847 } else {
848 print_ln();
849 if (last_badness > 100) {
850 tprint_nl("Underfull \\hbox (badness ");
851 } else {
852 tprint_nl("Loose \\hbox (badness ");
854 print_int(last_badness);
855 goto COMMON_ENDING;
860 goto EXIT;
861 } else {
863 Determine horizontal glue shrink setting, then |return|
864 or \hbox{|goto common_ending|},
866 if (total_shrink[filll] != 0)
867 o = filll;
868 else if (total_shrink[fill] != 0)
869 o = fill;
870 else if (total_shrink[fil] != 0)
871 o = fil;
872 else if (total_shrink[sfi] != 0)
873 o = sfi;
874 else
875 o = normal;
877 if ((m == cal_expand_ratio) && (o == normal) && (font_shrink > 0)) {
878 font_expand_ratio = divide_scaled_n(x, font_shrink, 1000.0);
879 goto EXIT;
881 glue_order(r) = (quarterword) o;
882 glue_sign(r) = shrinking;
883 if (total_shrink[o] != 0) {
884 glue_set(r) = unfloat((double) (-x) / (double) total_shrink[o]);
885 } else {
886 /* there's nothing to shrink */
887 glue_sign(r) = normal;
888 set_glue_ratio_zero(glue_set(r));
890 if ((total_shrink[o] < -x) && (o == normal) && (list_ptr(r) != null)) {
891 int overshoot = -x - total_shrink[normal] ;
892 last_badness = 1000000;
893 /* use the maximum shrinkage */
894 set_glue_ratio_one(glue_set(r));
896 Report an overfull hbox and |goto common_ending|, if this box
897 is sufficiently bad.
899 if ((overshoot > dimen_par(hfuzz_code)) || (int_par(hbadness_code) < 100)) {
900 int callback_id = callback_defined(hpack_quality_callback);
901 halfword rule = null;
902 if (callback_id > 0) {
903 run_callback(callback_id, "SdNdd->N","overfull",overshoot,r,abs(pack_begin_line),line,&rule);
904 } else if (dimen_par(overfull_rule_code) > 0) {
905 rule = new_rule(normal_rule);
906 rule_dir(rule) = box_dir(r);
907 width(rule) = dimen_par(overfull_rule_code);
909 if (rule != null) {
910 while (vlink(q) != null) {
911 q = vlink(q);
913 couple_nodes(q,rule);
915 if (callback_id == 0) {
916 print_ln();
917 tprint_nl("Overfull \\hbox (");
918 print_scaled(overshoot);
919 tprint("pt too wide");
920 goto COMMON_ENDING;
923 } else if (o == normal) {
924 if (list_ptr(r) != null) {
926 Report a tight hbox and |goto common_ending|, if this box is
927 sufficiently bad.
929 last_badness = badness(-x, total_shrink[normal]);
930 if (last_badness > int_par(hbadness_code)) {
931 int callback_id = callback_defined(hpack_quality_callback);
932 if (callback_id > 0) {
933 halfword rule = null;
934 run_callback(callback_id, "SdNdd->N","tight",last_badness,r,abs(pack_begin_line),line,&rule);
935 if (rule != null) {
936 while (vlink(q) != null) {
937 q = vlink(q);
939 couple_nodes(q,rule);
941 } else {
942 print_ln();
943 tprint_nl("Tight \\hbox (badness ");
944 print_int(last_badness);
945 goto COMMON_ENDING;
950 goto EXIT;
953 COMMON_ENDING:
955 Finish issuing a diagnostic message for an overfull or underfull
956 hbox.
958 if (output_active) {
959 tprint(") has occurred while \\output is active");
960 } else {
961 if (pack_begin_line != 0) {
962 if (pack_begin_line > 0) {
963 tprint(") in paragraph at lines ");
964 } else {
965 tprint(") in alignment at lines ");
967 print_int(abs(pack_begin_line));
968 tprint("--");
969 } else {
970 tprint(") detected at line ");
972 print_int(line);
975 print_ln();
976 font_in_short_display = null_font;
977 short_display(list_ptr(r));
978 print_ln();
979 begin_diagnostic();
980 show_box(r);
981 end_diagnostic(true);
982 EXIT:
983 if ((m == cal_expand_ratio) && (font_expand_ratio != 0)) {
984 font_expand_ratio = fix_int(font_expand_ratio, -1000, 1000);
985 q = list_ptr(r);
986 list_ptr(r) = null;
987 flush_node(r);
988 r = hpack(q, w, subst_ex_font, hpack_dir);
990 while (dir_ptr1 != null)
991 pop_dir_node(dir_ptr1);
992 return r;
995 @ @c
997 halfword filtered_hpack(halfword p, halfword qt, scaled w, int m, int grp, int pac)
999 halfword q;
1000 new_hyphenation(p, qt);
1001 (void) new_ligkern(p, qt); // we don't care about the tail in this case
1002 q = vlink(p);
1003 q = lua_hpack_filter(q, w, m, grp, pac);
1004 return hpack(q, w, m, pac);
1008 halfword filtered_hpack(halfword p, halfword qt, scaled w, int m, int grp, int pac, int just_pack)
1010 halfword q;
1011 if (just_pack) {
1012 q = vlink(p);
1013 } else if (type(p) == temp_node && vlink(p) == null) {
1014 q = vlink(p);
1016 q = new_node(hlist_node, min_quarterword);
1017 box_dir(q) = (pac == -1) ? text_direction : pac;
1018 width(q) = w;
1019 return q;
1021 } else {
1022 new_hyphenation(p, qt);
1023 (void) new_ligkern(p, qt); /* we don't care about the tail in this case */
1024 q = vlink(p);
1025 /* maybe here: alink(p) = null */
1026 q = lua_hpack_filter(q, w, m, grp, pac); /* ignores empty anyway */ /* maybe also pass tail */
1028 return hpack(q, w, m, pac);
1031 @ here is a function to calculate the natural whd of a (horizontal) node list
1034 scaled_whd natural_sizes(halfword p, halfword pp, glue_ratio g_mult,
1035 int g_sign, int g_order, int pack_direction)
1037 scaled s; /* shift amount */
1038 halfword g; /* points to a glue specification */
1039 int hpack_dir;
1040 scaled_whd xx; /* for recursion */
1041 scaled_whd whd, siz = { 0, 0, 0 };
1042 if (pack_direction == -1) {
1043 hpack_dir = text_direction;
1044 } else {
1045 hpack_dir = pack_direction;
1047 while (p != pp && p != null) {
1048 while (is_char_node(p) && p != pp) {
1049 whd = pack_width_height_depth(hpack_dir, dir_TRT, p, true);
1050 siz.wd += whd.wd;
1051 if (whd.ht > siz.ht)
1052 siz.ht = whd.ht;
1053 if (whd.dp > siz.dp)
1054 siz.dp = whd.dp;
1055 p = vlink(p);
1057 if (p != pp && p != null) {
1058 switch (type(p)) {
1059 case hlist_node:
1060 case vlist_node:
1061 s = shift_amount(p);
1062 whd = pack_width_height_depth(hpack_dir, box_dir(p), p, false);
1063 siz.wd += whd.wd;
1064 if (whd.ht - s > siz.ht)
1065 siz.ht = whd.ht - s;
1066 if (whd.dp + s > siz.dp)
1067 siz.dp = whd.dp + s;
1068 break;
1070 case rule_node:
1071 case unset_node:
1072 siz.wd += width(p);
1073 if (type(p) >= rule_node) // always true
1074 s = 0;
1075 else
1076 s = shift_amount(p);
1077 if (height(p) - s > siz.ht)
1078 siz.ht = height(p) - s;
1079 if (depth(p) + s > siz.dp)
1080 siz.dp = depth(p) + s;
1081 break;
1083 case rule_node:
1084 case unset_node:
1085 siz.wd += width(p);
1086 if (height(p) > siz.ht)
1087 siz.ht = height(p);
1088 if (depth(p) > siz.dp)
1089 siz.dp = depth(p);
1090 break;
1091 /* */
1092 case math_node:
1093 /* begin mathskip code */
1094 if (glue_ptr(p) == zero_glue) {
1095 siz.wd += surround(p);
1096 break;
1097 } else {
1098 /* fall through: mathskip */
1100 /* end mathskip code */
1101 case glue_node:
1102 g = glue_ptr(p);
1103 siz.wd += width(g);
1104 if (g_sign != normal) {
1105 if (g_sign == stretching) {
1106 if (stretch_order(g) == g_order) {
1107 siz.wd += float_round(float_cast(g_mult) * float_cast(stretch(g)));
1109 } else if (shrink_order(g) == g_order) {
1110 siz.wd -= float_round(float_cast(g_mult) * float_cast(shrink(g)));
1113 if (subtype(p) >= a_leaders) {
1114 g = leader_ptr(p);
1115 if (height(g) > siz.ht)
1116 siz.ht = height(g);
1117 if (depth(g) > siz.dp)
1118 siz.dp = depth(g);
1120 break;
1121 case margin_kern_node:
1122 case kern_node:
1123 siz.wd += width(p);
1124 break;
1125 case disc_node:
1126 xx = natural_sizes(no_break(p), null, g_mult, g_sign, g_order, hpack_dir);
1127 siz.wd += xx.wd;
1128 if (xx.ht > siz.ht)
1129 siz.ht = xx.ht;
1130 if (xx.dp > siz.dp)
1131 siz.dp = xx.dp;
1132 break;
1133 default:
1134 break;
1136 p = vlink(p);
1140 return siz;
1143 @ In order to provide a decent indication of where an overfull or underfull box
1144 originated, we use a global variable |pack_begin_line| that is set nonzero only
1145 when |hpack| is being called by the paragraph builder or the alignment finishing
1146 routine.
1148 @ The source file line where the current paragraph or alignment began; a negative
1149 value denotes alignment:
1152 int pack_begin_line;
1154 @ The |vpack| subroutine is actually a special case of a slightly more general
1155 routine called |vpackage|, which has four parameters. The fourth parameter, which
1156 is |max_dimen| in the case of |vpack|, specifies the maximum depth of the page
1157 box that is constructed. The depth is first computed by the normal rules; if it
1158 exceeds this limit, the reference point is simply moved down until the limiting
1159 depth is attained.
1162 halfword vpackage(halfword p, scaled h, int m, scaled l, int pack_direction)
1164 halfword r; /* the box node that will be returned */
1165 scaled w = 0; /* width */
1166 scaled d = 0; /* depth */
1167 scaled x = 0; /* natural height */
1168 scaled_whd whd;
1169 scaled s; /* shift amount */
1170 halfword g; /* points to a glue specification */
1171 int o; /* order of infinity */
1172 last_badness = 0;
1173 r = new_node(vlist_node, 0);
1174 if (pack_direction == -1) {
1175 box_dir(r) = body_direction;
1176 } else {
1177 box_dir(r) = pack_direction;
1179 subtype(r) = min_quarterword;
1180 shift_amount(r) = 0;
1181 list_ptr(r) = p;
1182 total_stretch[normal] = 0;
1183 total_shrink[normal] = 0;
1184 total_stretch[sfi] = 0;
1185 total_shrink[sfi] = 0;
1186 total_stretch[fil] = 0;
1187 total_shrink[fil] = 0;
1188 total_stretch[fill] = 0;
1189 total_shrink[fill] = 0;
1190 total_stretch[filll] = 0;
1191 total_shrink[filll] = 0;
1193 while (p != null) {
1195 Examine node |p| in the vlist, taking account of its effect
1196 on the dimensions of the new box; then advance |p| to the next
1197 node.
1199 if (is_char_node(p)) {
1200 confusion("vpack");
1201 } else {
1202 switch (type(p)) {
1203 case hlist_node:
1204 case vlist_node:
1206 Incorporate box dimensions into the dimensions of
1207 the vbox that will contain it.
1209 s = shift_amount(p);
1210 whd = pack_width_height_depth(box_dir(r), box_dir(p), p, false);
1211 if (whd.wd + s > w)
1212 w = whd.wd + s;
1213 x += d + whd.ht;
1214 d = whd.dp;
1215 break;
1217 case rule_node:
1218 case unset_node:
1219 x += d + height(p);
1220 d = depth(p);
1221 if (type(p) >= rule_node) // always
1222 s = 0;
1223 else
1224 s = shift_amount(p);
1225 if (width(p) + s > w)
1226 w = width(p) + s;
1227 break;
1229 case rule_node:
1230 case unset_node:
1231 x += d + height(p);
1232 d = depth(p);
1233 if (width(p) > w)
1234 w = width(p);
1235 break;
1236 /* */
1237 case glue_node:
1238 /* Incorporate glue into the vertical totals */
1239 x += d;
1240 d = 0;
1241 g = glue_ptr(p);
1242 x += width(g);
1243 o = stretch_order(g);
1244 total_stretch[o] = total_stretch[o] + stretch(g);
1245 o = shrink_order(g);
1246 total_shrink[o] = total_shrink[o] + shrink(g);
1247 if (subtype(p) >= a_leaders) {
1248 g = leader_ptr(p);
1249 if (width(g) > w)
1250 w = width(g);
1252 break;
1253 case kern_node:
1254 x += d + width(p);
1255 d = 0;
1256 break;
1257 default:
1258 break;
1261 p = vlink(p);
1263 width(r) = w;
1264 if (d > l) {
1265 x += d - l;
1266 depth(r) = l;
1267 } else {
1268 depth(r) = d;
1271 Determine the value of |height(r)| and the appropriate glue setting;
1272 then |return| or |goto common_ending|.
1274 When we get to the present part of the program, |x| is the natural
1275 height of the box being packaged.
1277 if (m == additional)
1278 h = x + h;
1279 height(r) = h;
1280 x = h - x;
1281 /* now |x| is the excess to be made up */
1282 if (x == 0) {
1283 glue_sign(r) = normal;
1284 glue_order(r) = normal;
1285 set_glue_ratio_zero(glue_set(r));
1286 return r;
1287 } else if (x > 0) {
1289 Determine vertical glue stretch setting, then |return|
1290 or \hbox{|goto common_ending|}.
1292 if (total_stretch[filll] != 0)
1293 o = filll;
1294 else if (total_stretch[fill] != 0)
1295 o = fill;
1296 else if (total_stretch[fil] != 0)
1297 o = fil;
1298 else if (total_stretch[sfi] != 0)
1299 o = sfi;
1300 else
1301 o = normal;
1303 glue_order(r) = (quarterword) o;
1304 glue_sign(r) = stretching;
1305 if (total_stretch[o] != 0) {
1306 glue_set(r) = unfloat((double) x / total_stretch[o]);
1307 } else {
1308 glue_sign(r) = normal;
1309 set_glue_ratio_zero(glue_set(r)); /* there's nothing to stretch */
1311 if (o == normal) {
1312 if (list_ptr(r) != null) {
1314 Report an underfull vbox and |goto common_ending|, if this box
1315 is sufficiently bad.
1317 last_badness = badness(x, total_stretch[normal]);
1318 if (last_badness > int_par(vbadness_code)) {
1319 int callback_id = callback_defined(vpack_quality_callback);
1320 if (callback_id > 0) {
1321 if (last_badness > 100) {
1322 run_callback(callback_id, "SdNdd->","underfull",last_badness,r,abs(pack_begin_line),line);
1323 } else {
1324 run_callback(callback_id, "SdNdd->","loose",last_badness,r,abs(pack_begin_line),line);
1326 goto EXIT;
1327 } else {
1328 print_ln();
1329 if (last_badness > 100) {
1330 tprint_nl("Underfull \\vbox (badness ");
1331 } else {
1332 tprint_nl("Loose \\vbox (badness ");
1334 print_int(last_badness);
1335 goto COMMON_ENDING;
1340 return r;
1342 } else {
1344 Determine vertical glue shrink setting, then |return|
1345 or \hbox{|goto common_ending|}.
1347 if (total_shrink[filll] != 0)
1348 o = filll;
1349 else if (total_shrink[fill] != 0)
1350 o = fill;
1351 else if (total_shrink[fil] != 0)
1352 o = fil;
1353 else if (total_shrink[sfi] != 0)
1354 o = sfi;
1355 else
1356 o = normal;
1358 glue_order(r) = (quarterword) o;
1359 glue_sign(r) = shrinking;
1360 if (total_shrink[o] != 0) {
1361 glue_set(r) = unfloat((double) (-x) / total_shrink[o]);
1362 } else {
1363 /* there's nothing to shrink */
1364 glue_sign(r) = normal;
1365 set_glue_ratio_zero(glue_set(r));
1367 if ((total_shrink[o] < -x) && (o == normal) && (list_ptr(r) != null)) {
1368 int overshoot = -x - total_shrink[normal];
1369 last_badness = 1000000;
1370 /* use the maximum shrinkage */
1371 set_glue_ratio_one(glue_set(r));
1373 Report an overfull vbox and |goto common_ending|, if this box
1374 is sufficiently bad.
1376 if ((overshoot > dimen_par(vfuzz_code)) || (int_par(vbadness_code) < 100)) {
1377 int callback_id = callback_defined(vpack_quality_callback);
1378 if (callback_id > 0) {
1379 run_callback(callback_id, "SdNdd->","overfull",overshoot,r,abs(pack_begin_line),line);
1380 goto EXIT;
1381 } else {
1382 print_ln();
1383 tprint_nl("Overfull \\vbox (");
1384 print_scaled(-x - total_shrink[normal]);
1385 tprint("pt too high");
1386 goto COMMON_ENDING;
1389 } else if (o == normal) {
1390 if (list_ptr(r) != null) {
1392 Report a tight vbox and |goto common_ending|, if this box is
1393 sufficiently bad.
1395 last_badness = badness(-x, total_shrink[normal]);
1396 if (last_badness > int_par(vbadness_code)) {
1397 int callback_id = callback_defined(vpack_quality_callback);
1398 if (callback_id > 0) {
1399 run_callback(callback_id, "SdNdd->","tight",last_badness,r,abs(pack_begin_line),line);
1400 goto EXIT;
1401 } else {
1402 print_ln();
1403 tprint_nl("Tight \\vbox (badness ");
1404 print_int(last_badness);
1405 goto COMMON_ENDING;
1410 return r;
1413 COMMON_ENDING:
1414 /* Finish issuing a diagnostic message or an overfull or underfull vbox */
1415 if (output_active) {
1416 tprint(") has occurred while \\output is active");
1417 } else {
1418 if (pack_begin_line != 0) {
1419 /* it's actually negative */
1420 tprint(") in alignment at lines ");
1421 print_int(abs(pack_begin_line));
1422 tprint("--");
1423 } else {
1424 tprint(") detected at line ");
1426 print_int(line);
1427 print_ln();
1429 begin_diagnostic();
1430 show_box(r);
1431 end_diagnostic(true);
1432 EXIT:
1433 return r;
1436 @ @c
1437 halfword filtered_vpackage(halfword p, scaled h, int m, scaled l, int grp, int pack_direction, int just_pack)
1439 halfword q = p;
1440 if (!just_pack)
1441 /* if (q != null) */
1442 q = lua_vpack_filter(q, h, m, l, grp, pack_direction);
1443 return vpackage(q, h, m, l, pack_direction);
1446 @ @c
1447 void finish_vcenter(void)
1449 halfword p;
1450 unsave();
1451 save_ptr--;
1452 p = vpack(vlink(cur_list.head_field), saved_value(0), saved_level(0), -1);
1453 pop_nest();
1454 p = math_vcenter_group(p);
1455 tail_append(p);
1458 @ @c
1459 void package(int c)
1461 halfword saved0, saved2, saved3, saved4;
1462 int grp = cur_group;
1463 scaled d = box_max_depth; /* max depth */
1464 unsave();
1465 save_ptr -= 5;
1466 saved0 = saved_value(0);
1467 saved2 = saved_value(2);
1468 saved3 = saved_value(3);
1469 saved4 = saved_value(4);
1470 if (cur_list.mode_field == -hmode) {
1471 cur_box = filtered_hpack(cur_list.head_field, cur_list.tail_field,
1472 saved_value(1), saved_level(1), grp, saved_level(2), saved4);
1473 subtype(cur_box) = hbox_list;
1474 } else {
1475 cur_box = filtered_vpackage(vlink(cur_list.head_field),
1476 saved_value(1), saved_level(1), d, grp, saved_level(2), saved4);
1477 if (c == vtop_code) {
1479 Read just the height and depth of |cur_box|, for \.{\\vtop}. The
1480 height of a `\.{\\vtop}' box is inherited from the first item on
1481 its list, if that item is an |hlist_node|, |vlist_node|, or
1482 |rule_node|; otherwise the \.{\\vtop} height is zero.
1484 scaled h = 0;
1485 halfword p = list_ptr(cur_box);
1486 if ((p != null) && (type(p) <= rule_node)) {
1487 /* hlist, vlist, rule */
1488 h = height(p);
1490 depth(cur_box) = depth(cur_box) - h + height(cur_box);
1491 height(cur_box) = h;
1494 if (saved2 != null) {
1495 /* DIR: Adjust back |text_dir_ptr| for |scan_spec| */
1496 flush_node_list(text_dir_ptr);
1497 text_dir_ptr = saved2;
1500 replace_attribute_list(cur_box, saved3);
1501 pop_nest();
1502 box_end(saved0);
1505 @ When a box is being appended to the current vertical list, the baselineskip
1506 calculation is handled by the |append_to_vlist| routine.
1509 void append_to_vlist(halfword b, int location)
1511 scaled d; /* deficiency of space between baselines */
1512 halfword p; /* a new glue node */
1513 boolean mirrored = (type(b) == hlist_node) && is_mirrored(box_dir(b)) ;
1514 halfword result = null;
1515 halfword next_depth = ignore_depth;
1516 boolean prev_set = false ;
1517 if (lua_appendtovlist_callback(b,location,prev_depth,mirrored,&result,&next_depth,&prev_set)) {
1518 while (result != null) {
1519 couple_nodes(cur_list.tail_field, result);
1520 cur_list.tail_field = result;
1521 result = vlink(result);
1523 if (prev_set) {
1524 prev_depth = next_depth;
1526 } else {
1527 if (prev_depth > ignore_depth) {
1528 if (mirrored) {
1529 d = width(glue_par(baseline_skip_code)) - prev_depth - depth(b);
1530 } else {
1531 d = width(glue_par(baseline_skip_code)) - prev_depth - height(b);
1533 if (d < dimen_par(line_skip_limit_code)) {
1534 p = new_param_glue(line_skip_code);
1535 } else {
1536 p = new_skip_param(baseline_skip_code);
1537 /* |temp_ptr=glue_ptr(p)| */
1538 width(temp_ptr) = d;
1540 couple_nodes(cur_list.tail_field, p);
1541 cur_list.tail_field = p;
1543 couple_nodes(cur_list.tail_field, b);
1544 cur_list.tail_field = b;
1545 if (mirrored) {
1546 prev_depth = height(b);
1547 } else {
1548 prev_depth = depth(b);
1553 @ When |saving_vdiscards| is positive then the glue, kern, and penalty nodes
1554 removed by the page builder or by \.{\\vsplit} from the top of a vertical list
1555 are saved in special lists instead of being discarded.
1558 #define tail_page_disc disc_ptr[copy_code] /* last item removed by page builder */
1559 #define page_disc disc_ptr[last_box_code] /* first item removed by page builder */
1560 #define split_disc disc_ptr[vsplit_code] /* first item removed by \.{\\vsplit} */
1562 halfword disc_ptr[(vsplit_code + 1)]; /* list pointers */
1564 @ The |vsplit| procedure, which implements \TeX's \.{\\vsplit} operation, is
1565 considerably simpler than |line_break| because it doesn't have to worry about
1566 hyphenation, and because its mission is to discover a single break instead of an
1567 optimum sequence of breakpoints. But before we get into the details of |vsplit|,
1568 we need to consider a few more basic things.
1570 A subroutine called |prune_page_top| takes a pointer to a vlist and returns a
1571 pointer to a modified vlist in which all glue, kern, and penalty nodes have been
1572 deleted before the first box or rule node. However, the first box or rule is
1573 actually preceded by a newly created glue node designed so that the topmost
1574 baseline will be at distance |split_top_skip| from the top, whenever this is
1575 possible without backspacing.
1577 When the second argument |s| is |false| the deleted nodes are destroyed,
1578 otherwise they are collected in a list starting at |split_disc|.
1581 halfword prune_page_top(halfword p, boolean s)
1583 halfword q;
1584 halfword prev_p = temp_head; /* lags one step behind |p| */
1585 halfword r = null;
1586 vlink(temp_head) = p;
1587 while (p != null) {
1588 switch (type(p)) {
1589 case hlist_node:
1590 case vlist_node:
1591 case rule_node:
1592 /* Insert glue for |split_top_skip| and set~|p:=null| */
1593 q = new_skip_param(split_top_skip_code);
1594 vlink(prev_p) = q;
1595 vlink(q) = p;
1596 /* now |temp_ptr=glue_ptr(q)| */
1597 if (width(temp_ptr) > height(p))
1598 width(temp_ptr) = width(temp_ptr) - height(p);
1599 else
1600 width(temp_ptr) = 0;
1601 p = null;
1602 break;
1603 case boundary_node:
1604 case whatsit_node:
1605 case mark_node:
1606 case ins_node:
1607 prev_p = p;
1608 p = vlink(prev_p);
1609 break;
1610 case glue_node:
1611 case kern_node:
1612 case penalty_node:
1613 q = p;
1614 p = vlink(q);
1615 vlink(q) = null;
1616 vlink(prev_p) = p;
1617 if (s) {
1618 if (split_disc == null)
1619 split_disc = q;
1620 else
1621 vlink(r) = q;
1622 r = q;
1623 } else {
1624 flush_node_list(q);
1626 break;
1627 default:
1628 confusion("pruning");
1629 break;
1632 return vlink(temp_head);
1635 @ The next subroutine finds the best place to break a given vertical list so as
1636 to obtain a box of height~|h|, with maximum depth~|d|. A pointer to the beginning
1637 of the vertical list is given, and a pointer to the optimum breakpoint is
1638 returned. The list is effectively followed by a forced break, i.e., a penalty
1639 node with the |eject_penalty|; if the best break occurs at this artificial node,
1640 the value |null| is returned.
1643 scaled active_height[10]; /* distance from first active node to~|cur_p| */
1645 @ An array of six |scaled| distances is used to keep track of the height from the
1646 beginning of the list to the current place, just as in |line_break|. In fact, we
1647 use one of the same arrays, only changing its name to reflect its new
1648 significance.
1651 #define do_all_six(A) A(1);A(2);A(3);A(4);A(5);A(6);A(7)
1652 #define set_height_zero(A) active_height[A]=0 /* initialize the height to zero */
1654 @ A global variable |best_height_plus_depth| will be set to the natural size of
1655 the box that corresponds to the optimum breakpoint found by |vert_break|. (This
1656 value is used by the insertion-splitting algorithm of the page builder.)
1658 @ height of the best box, without stretching or shrinking
1661 scaled best_height_plus_depth;
1663 /* finds optimum page break */
1665 halfword vert_break(halfword p, scaled h, scaled d)
1667 halfword prev_p = p; /* if |p| is a glue node, |type(prev_p)| determines whether |p| is a
1668 legal breakpoint, an initial glue node is not a legal breakpoint */
1669 halfword q, r; /* glue specifications */
1670 int pi = 0; /* penalty value */
1671 int b; /* badness at a trial breakpoint */
1672 int t; /* |type| of the node following a kern */
1673 int least_cost; /* the smallest badness plus penalties found so far */
1674 halfword best_place = null; /* the most recent break that leads to |least_cost| */
1675 scaled prev_dp = 0; /* depth of previous box in the list */
1676 least_cost = awful_bad;
1677 do_all_six(set_height_zero);
1678 while (1) {
1679 /* If node |p| is a legal breakpoint, check if this break is
1680 the best known, and |goto done| if |p| is null or
1681 if the page-so-far is already too full to accept more stuff */
1682 /* A subtle point to be noted here is that the maximum depth~|d| might be
1683 negative, so |cur_height| and |prev_dp| might need to be corrected even
1684 after a glue or kern node. */
1686 if (p == null) {
1687 pi = eject_penalty;
1688 } else {
1689 /* Use node |p| to update the current height and depth measurements;
1690 if this node is not a legal breakpoint, |goto not_found|
1691 or |update_heights|,
1692 otherwise set |pi| to the associated penalty at the break */
1693 switch (type(p)) {
1694 case hlist_node:
1695 case vlist_node:
1696 case rule_node:
1697 cur_height = cur_height + prev_dp + height(p);
1698 prev_dp = depth(p);
1699 goto NOT_FOUND;
1700 break;
1701 case boundary_node:
1702 case whatsit_node:
1703 goto NOT_FOUND;
1704 break;
1705 case glue_node:
1706 if (precedes_break(prev_p))
1707 pi = 0;
1708 else
1709 goto UPDATE_HEIGHTS;
1710 break;
1711 case kern_node:
1712 if (vlink(p) == null)
1713 t = penalty_node;
1714 else
1715 t = type(vlink(p));
1716 if (t == glue_node)
1717 pi = 0;
1718 else
1719 goto UPDATE_HEIGHTS;
1720 break;
1721 case penalty_node:
1722 pi = penalty(p);
1723 break;
1724 case mark_node:
1725 case ins_node:
1726 goto NOT_FOUND;
1727 break;
1728 default:
1729 confusion("vertbreak");
1730 break;
1733 /* Check if node |p| is a new champion breakpoint; then |goto done|
1734 if |p| is a forced break or if the page-so-far is already too full */
1735 if (pi < inf_penalty) {
1736 /* Compute the badness, |b|, using |awful_bad| if the box is too full */
1737 if (cur_height < h) {
1738 if ((active_height[3] != 0) || (active_height[4] != 0) ||
1739 (active_height[5] != 0) || (active_height[6] != 0))
1740 b = 0;
1741 else
1742 b = badness(h - cur_height, active_height[2]);
1743 } else if (cur_height - h > active_height[7]) {
1744 b = awful_bad;
1745 } else {
1746 b = badness(cur_height - h, active_height[7]);
1749 if (b < awful_bad) {
1750 if (pi <= eject_penalty)
1751 b = pi;
1752 else if (b < inf_bad)
1753 b = b + pi;
1754 else
1755 b = deplorable;
1757 if (b <= least_cost) {
1758 best_place = p;
1759 least_cost = b;
1760 best_height_plus_depth = cur_height + prev_dp;
1762 if ((b == awful_bad) || (pi <= eject_penalty))
1763 goto DONE;
1766 if ((type(p) < glue_node) || (type(p) > kern_node))
1767 goto NOT_FOUND;
1768 UPDATE_HEIGHTS:
1769 /* Update the current height and depth measurements with
1770 respect to a glue or kern node~|p| */
1771 /* Vertical lists that are subject to the |vert_break| procedure should not
1772 contain infinite shrinkability, since that would permit any amount of
1773 information to ``fit'' on one page. */
1775 if (type(p) == kern_node) {
1776 q = p;
1777 } else {
1778 q = glue_ptr(p);
1779 active_height[2 + stretch_order(q)] += stretch(q);
1780 active_height[7] += shrink(q);
1781 if ((shrink_order(q) != normal) && (shrink(q) != 0)) {
1782 print_err("Infinite glue shrinkage found in box being split");
1783 help4("The box you are \\vsplitting contains some infinitely",
1784 "shrinkable glue, e.g., `\\vss' or `\\vskip 0pt minus 1fil'.",
1785 "Such glue doesn't belong there; but you can safely proceed,",
1786 "since the offensive shrinkability has been made finite.");
1787 error();
1788 r = new_spec(q);
1789 shrink_order(r) = normal;
1790 delete_glue_ref(q);
1791 glue_ptr(p) = r;
1792 q = r;
1795 cur_height = cur_height + prev_dp + width(q);
1796 prev_dp = 0;
1797 NOT_FOUND:
1798 if (prev_dp > d) {
1799 cur_height = cur_height + prev_dp - d;
1800 prev_dp = d;
1802 prev_p = p;
1803 p = vlink(prev_p);
1805 DONE:
1806 return best_place;
1809 @ Now we are ready to consider |vsplit| itself. Most of its work is accomplished
1810 by the two subroutines that we have just considered.
1812 Given the number of a vlist box |n|, and given a desired page height |h|, the
1813 |vsplit| function finds the best initial segment of the vlist and returns a box
1814 for a page of height~|h|. The remainder of the vlist, if any, replaces the
1815 original box, after removing glue and penalties and adjusting for
1816 |split_top_skip|. Mark nodes in the split-off box are used to set the values of
1817 |split_first_mark| and |split_bot_mark|; we use the fact that
1818 |split_first_mark(x)=null| if and only if |split_bot_mark(x)=null|.
1820 The original box becomes ``void'' if and only if it has been entirely extracted.
1821 The extracted box is ``void'' if and only if the original box was void (or if it
1822 was, erroneously, an hlist box).
1825 /* extracts a page of height |h| from box |n| */
1827 halfword vsplit(halfword n, scaled h, int m)
1829 halfword v; /* the box to be split */
1830 int vdir; /* the direction of the box to be split */
1831 halfword p; /* runs through the vlist */
1832 halfword q; /* points to where the break occurs */
1833 halfword i; /* for traversing marks lists */
1834 v = box(n);
1835 vdir = box_dir(v);
1836 flush_node_list(split_disc);
1837 split_disc = null;
1838 for (i = 0; i <= biggest_used_mark; i++) {
1839 delete_split_first_mark(i);
1840 delete_split_bot_mark(i);
1842 /* Dispense with trivial cases of void or bad boxes */
1843 if (v == null) {
1844 return null;
1846 if (type(v) != vlist_node) {
1847 print_err("\\vsplit needs a \\vbox");
1848 help2("The box you are trying to split is an \\hbox.",
1849 "i can't split such a box, so I''ll leave it alone.");
1850 error();
1851 return null;
1853 q = vert_break(list_ptr(v), h, dimen_par(split_max_depth_code));
1855 Look at all the marks in nodes before the break, and set the final
1856 link to |null| at the break. It's possible that the box begins with
1857 a penalty node that is the ``best'' break, so we must be careful to
1858 handle this special case correctly.
1860 p = list_ptr(v);
1861 if (p == q) {
1862 list_ptr(v) = null;
1863 } else {
1864 while (1) {
1865 if (type(p) == mark_node) {
1866 if (split_first_mark(mark_class(p)) == null) {
1867 set_split_first_mark(mark_class(p), mark_ptr(p));
1868 set_split_bot_mark(mark_class(p), split_first_mark(mark_class(p)));
1869 set_token_ref_count(split_first_mark(mark_class(p)),
1870 token_ref_count(split_first_mark(mark_class(p))) + 2);
1871 } else {
1872 delete_token_ref(split_bot_mark(mark_class(p)));
1873 set_split_bot_mark(mark_class(p), mark_ptr(p));
1874 add_token_ref(split_bot_mark(mark_class(p)));
1877 if (vlink(p) == q) {
1878 vlink(p) = null;
1879 break;
1881 p = vlink(p);
1884 q = prune_page_top(q, int_par(saving_vdiscards_code) > 0);
1885 p = list_ptr(v);
1886 list_ptr(v) = null;
1887 flush_node(v);
1888 if (q == null) {
1889 /* the |eq_level| of the box stays the same */
1890 box(n) = null;
1891 } else {
1892 box(n) = filtered_vpackage(q, 0, additional, dimen_par(max_depth_code), split_keep_group, vdir, 0);
1894 if (m == exactly) {
1895 return filtered_vpackage(p, h, exactly, dimen_par(split_max_depth_code), split_off_group, vdir, 0);
1896 } else {
1897 return filtered_vpackage(p, 0, additional, dimen_par(max_depth_code), split_off_group, vdir, 0);
1901 @ Now that we can see what eventually happens to boxes, we can consider the first
1902 steps in their creation. The |begin_box| routine is called when |box_context| is
1903 a context specification, |cur_chr| specifies the type of box desired, and
1904 |cur_cmd=make_box|.
1907 void begin_box(int box_context)
1909 halfword q; /* run through the current list */
1910 halfword k; /* 0 or |vmode| or |hmode| */
1911 int n; /* a box number */
1912 int spec_direction = -1;
1913 int just_pack = 0;
1914 switch (cur_chr) {
1915 case box_code:
1916 scan_register_num();
1917 cur_box = box(cur_val);
1918 /* the box becomes void, at the same level */
1919 box(cur_val) = null;
1920 break;
1921 case copy_code:
1922 scan_register_num();
1923 cur_box = copy_node_list(box(cur_val));
1924 break;
1925 case last_box_code:
1927 If the current list ends with a box node, delete it from
1928 the list and make |cur_box| point to it; otherwise set
1929 |cur_box:=null|.
1931 cur_box = null;
1932 if (abs(cur_list.mode_field) == mmode) {
1933 you_cant();
1934 help1("Sorry; this \\lastbox will be void.");
1935 error();
1936 } else if ((cur_list.mode_field == vmode) && (cur_list.head_field == cur_list.tail_field)) {
1937 you_cant();
1938 help2("Sorry...I usually can't take things from the current page.",
1939 "This \\lastbox will therefore be void.");
1940 error();
1941 } else {
1942 if (cur_list.head_field != cur_list.tail_field) {
1943 /* todo: new code, needs testing */
1945 /* maybe: ((type(cur_list.tail_field) == hlist_node) < rule_node) */
1947 if ((type(cur_list.tail_field) == hlist_node) || (type(cur_list.tail_field) == vlist_node)) {
1948 /* Remove the last box ... */
1949 q = alink(cur_list.tail_field);
1950 if (q == null || vlink(q) != cur_list.tail_field) {
1951 q = cur_list.head_field;
1952 while (vlink(q) != cur_list.tail_field)
1953 q = vlink(q);
1955 uncouple_node(cur_list.tail_field);
1956 cur_box = cur_list.tail_field;
1957 shift_amount(cur_box) = 0;
1958 cur_list.tail_field = q;
1959 vlink(cur_list.tail_field) = null;
1963 break;
1964 case vsplit_code:
1966 Split off part of a vertical box, make |cur_box| point to it. Here we
1967 deal with things like `\.{\\vsplit 13 to 100pt}'.
1969 scan_register_num();
1970 n = cur_val;
1971 if (!scan_keyword("to")) {
1972 print_err("Missing `to' inserted");
1973 help2("I'm working on `\\vsplit<box number> to <dimen>';",
1974 "will look for the <dimen> next.");
1975 error();
1977 scan_normal_dimen();
1978 cur_box = vsplit(n, cur_val, additional);
1979 break;
1980 default:
1982 Initiate the construction of an hbox or vbox, then |return|. Here is
1983 where we enter restricted horizontal mode or internal vertical mode,
1984 in order to make a box.
1986 switch (cur_chr) {
1987 case tpack_code:
1988 cur_chr = vtop_code;
1989 just_pack = 1;
1990 break;
1991 case vpack_code:
1992 cur_chr = vtop_code + vmode;
1993 just_pack = 1;
1994 break;
1995 case hpack_code:
1996 cur_chr = vtop_code + hmode;
1997 just_pack = 1;
1998 break;
2000 /* */
2001 k = cur_chr - vtop_code;
2002 set_saved_record(0, saved_boxcontext, 0, box_context);
2003 switch (abs(cur_list.mode_field)) {
2004 case vmode:
2005 spec_direction = body_direction;
2006 break;
2007 case hmode:
2008 spec_direction = text_direction;
2009 break;
2010 case mmode:
2011 spec_direction = math_direction;
2012 break;
2014 if (k == hmode) {
2015 if ((box_context < box_flag) && (abs(cur_list.mode_field) == vmode))
2016 scan_full_spec(adjusted_hbox_group, spec_direction,just_pack);
2017 else
2018 scan_full_spec(hbox_group, spec_direction,just_pack);
2019 } else {
2020 if (k == vmode) {
2021 scan_full_spec(vbox_group, spec_direction,just_pack);
2022 } else {
2023 scan_full_spec(vtop_group, spec_direction,just_pack);
2024 k = vmode;
2026 normal_paragraph();
2028 push_nest();
2029 cur_list.mode_field = -k;
2030 if (k == vmode) {
2031 prev_depth = ignore_depth;
2032 if (every_vbox != null)
2033 begin_token_list(every_vbox, every_vbox_text);
2034 } else {
2035 space_factor = 1000;
2036 if (every_hbox != null)
2037 begin_token_list(every_hbox, every_hbox_text);
2039 return;
2040 break;
2042 /* in simple cases, we use the box immediately */
2043 box_end(box_context);