1% buildpage.w 2% 3% Copyright 2009-2010 Taco Hoekwater <taco@@luatex.org> 4% 5% This file is part of LuaTeX. 6% 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. 11% 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. 16% 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/>. 19 20@ @c 21 22 23#include "ptexlib.h" 24 25@ @c 26#define box(A) eqtb[box_base+(A)].hh.rh 27#define count(A) eqtb[count_base+(A)].hh.rh 28#undef skip 29#define skip(A) eqtb[skip_base+(A)].hh.rh 30#define dimen(A) eqtb[scaled_base+(A)].hh.rh 31 32#define vbadness int_par(vbadness_code) 33#define max_dead_cycles int_par(max_dead_cycles_code) 34#define output_box int_par(output_box_code) 35#define body_direction int_par(body_direction_code) 36#define holding_inserts int_par(holding_inserts_code) 37 38#define vsize dimen_par(vsize_code) 39#define vfuzz dimen_par(vfuzz_code) 40#define max_depth dimen_par(max_depth_code) 41#define pdf_ignored_dimen dimen_par(pdf_ignored_dimen_code) 42 43#define output_routine equiv(output_routine_loc) 44#define split_top_skip glue_par(split_top_skip_code) 45 46#define prev_depth cur_list.prev_depth_field 47#define mode_line cur_list.ml_field 48#define mode cur_list.mode_field 49#define tail cur_list.tail_field 50#define head cur_list.head_field 51 52@ When \TeX\ appends new material to its main vlist in vertical mode, it uses 53a method something like |vsplit| to decide where a page ends, except that 54the calculations are done ``on line'' as new items come in. 55The main complication in this process is that insertions must be put 56into their boxes and removed from the vlist, in a more-or-less optimum manner. 57 58We shall use the term ``current page'' for that part of the main vlist that 59is being considered as a candidate for being broken off and sent to the 60user's output routine. The current page starts at |vlink(page_head)|, and 61it ends at |page_tail|. We have |page_head=page_tail| if this list is empty. 62@^current page@> 63 64Utter chaos would reign if the user kept changing page specifications 65while a page is being constructed, so the page builder keeps the pertinent 66specifications frozen as soon as the page receives its first box or 67insertion. The global variable |page_contents| is |empty| when the 68current page contains only mark nodes and content-less whatsit nodes; it 69is |inserts_only| if the page contains only insertion nodes in addition to 70marks and whatsits. Glue nodes, kern nodes, and penalty nodes are 71discarded until a box or rule node appears, at which time |page_contents| 72changes to |box_there|. As soon as |page_contents| becomes non-|empty|, 73the current |vsize| and |max_depth| are squirreled away into |page_goal| 74and |page_max_depth|; the latter values will be used until the page has 75been forwarded to the user's output routine. The \.{\\topskip} adjustment 76is made when |page_contents| changes to |box_there|. 77 78Although |page_goal| starts out equal to |vsize|, it is decreased by the 79scaled natural height-plus-depth of the insertions considered so far, and by 80the \.{\\skip} corrections for those insertions. Therefore it represents 81the size into which the non-inserted material should fit, assuming that 82all insertions in the current page have been made. 83 84The global variables |best_page_break| and |least_page_cost| correspond 85respectively to the local variables |best_place| and |least_cost| in the 86|vert_break| routine that we have already studied; i.e., they record the 87location and value of the best place currently known for breaking the 88current page. The value of |page_goal| at the time of the best break is 89stored in |best_size|. 90 91@c 92halfword page_tail; /* the final node on the current page */ 93int page_contents; /* what is on the current page so far? */ 94scaled page_max_depth; /* maximum box depth on page being built */ 95halfword best_page_break; /* break here to get the best page known so far */ 96int least_page_cost; /* the score for this currently best page */ 97scaled best_size; /* its |page_goal| */ 98 99 100@ The page builder has another data structure to keep track of insertions. 101This is a list of four-word nodes, starting and ending at |page_ins_head|. 102That is, the first element of the list is node |r@t$_1$@>=vlink(page_ins_head)|; 103node $r_j$ is followed by |r@t$_{j+1}$@>=vlink(r@t$_j$@>)|; and if there are 104|n| items we have |r@t$_{n+1}$@>=page_ins_head|. The |subtype| field of 105each node in this list refers to an insertion number; for example, `\.{\\insert 106250}' would correspond to a node whose |subtype| is |qi(250)| 107(the same as the |subtype| field of the relevant |ins_node|). These |subtype| 108fields are in increasing order, and |subtype(page_ins_head)=65535|, so 109|page_ins_head| serves as a convenient sentinel 110at the end of the list. A record is present for each insertion number that 111appears in the current page. 112 113The |type| field in these nodes distinguishes two possibilities that 114might occur as we look ahead before deciding on the optimum page break. 115If |type(r)=inserting_node|, then |height(r)| contains the total of the 116height-plus-depth dimensions of the box and all its inserts seen so far. 117 |type(r)=split_up_node|, then no more insertions will be made into this box, 118because at least one previous insertion was too big to fit on the current 119page; |broken_ptr(r)| points to the node where that insertion will be 120split, if \TeX\ decides to split it, |broken_ins(r)| points to the 121insertion node that was tentatively split, and |height(r)| includes also the 122natural height plus depth of the part that would be split off. 123 124In both cases, |last_ins_ptr(r)| points to the last |ins_node| 125encountered for box |qo(subtype(r))| that would be at least partially 126inserted on the next page; and |best_ins_ptr(r)| points to the last 127such |ins_node| that should actually be inserted, to get the page with 128minimum badness among all page breaks considered so far. We have 129|best_ins_ptr(r)=null| if and only if no insertion for this box should 130be made to produce this optimum page. 131 132 133 134@ Pages are built by appending nodes to the current list in \TeX's 135vertical mode, which is at the outermost level of the semantic nest. This 136vlist is split into two parts; the ``current page'' that we have been 137talking so much about already, and the ``contribution list'' that receives 138new nodes as they are created. The current page contains everything that 139the page builder has accounted for in its data structures, as described 140above, while the contribution list contains other things that have been 141generated by other parts of \TeX\ but have not yet been 142seen by the page builder. 143The contribution list starts at |vlink(contrib_head)|, and it ends at the 144current node in \TeX's vertical mode. 145 146When \TeX\ has appended new material in vertical mode, it calls the procedure 147|build_page|, which tries to catch up by moving nodes from the contribution 148list to the current page. This procedure will succeed in its goal of 149emptying the contribution list, unless a page break is discovered, i.e., 150unless the current page has grown to the point where the optimum next 151page break has been determined. In the latter case, the nodes after the 152optimum break will go back onto the contribution list, and control will 153effectively pass to the user's output routine. 154 155We make |type(page_head)=glue_node|, so that an initial glue node on 156the current page will not be considered a valid breakpoint. 157 158@c 159void initialize_buildpage(void) 160{ 161 subtype(page_ins_head) = 65535; 162 type(page_ins_head) = split_up_node; 163 vlink(page_ins_head) = page_ins_head; 164 165 type(page_head) = glue_node; 166 subtype(page_head) = normal; 167} 168 169 170@ An array |page_so_far| records the heights and depths of everything 171on the current page. This array contains six |scaled| numbers, like the 172similar arrays already considered in |line_break| and |vert_break|; and it 173also contains |page_goal| and |page_depth|, since these values are 174all accessible to the user via |set_page_dimen| commands. The 175value of |page_so_far[1]| is also called |page_total|. The stretch 176and shrink components of the \.{\\skip} corrections for each insertion are 177included in |page_so_far|, but the natural space components of these 178corrections are not, since they have been subtracted from |page_goal|. 179 180The variable |page_depth| records the depth of the current page; it has been 181adjusted so that it is at most |page_max_depth|. The variable 182|last_glue| points to the glue specification of the most recent node 183contributed from the contribution list, if this was a glue node; otherwise 184|last_glue=max_halfword|. (If the contribution list is nonempty, 185however, the value of |last_glue| is not necessarily accurate.) 186The variables |last_penalty|, |last_kern|, and |last_node_type| 187are similar. And 188finally, |insert_penalties| holds the sum of the penalties associated with 189all split and floating insertions. 190 191@c 192scaled page_so_far[8]; /* height and glue of the current page */ 193halfword last_glue; /* used to implement \.{\\lastskip} */ 194int last_penalty; /* used to implement \.{\\lastpenalty} */ 195scaled last_kern; /* used to implement \.{\\lastkern} */ 196int last_node_type; /* used to implement \.{\\lastnodetype} */ 197int insert_penalties; /* sum of the penalties for held-over insertions */ 198 199#define print_plus(A,B) do { \ 200 if (page_so_far[(A)]!=0) { \ 201 tprint(" plus "); \ 202 print_scaled(page_so_far[(A)]); \ 203 tprint((B)); \ 204 } \ 205 } while (0) 206 207void print_totals(void) 208{ 209 print_scaled(page_total); 210 print_plus(2, ""); 211 print_plus(3, "fil"); 212 print_plus(4, "fill"); 213 print_plus(5, "filll"); 214 if (page_shrink != 0) { 215 tprint(" minus "); 216 print_scaled(page_shrink); 217 } 218} 219 220 221@ Here is a procedure that is called when the |page_contents| is changing 222from |empty| to |inserts_only| or |box_there|. 223 224@c 225#define do_all_six(A) A(1);A(2);A(3);A(4);A(5);A(6);A(7) 226#define set_page_so_far_zero(A) page_so_far[(A)]=0 227 228void freeze_page_specs(int s) 229{ 230 page_contents = s; 231 page_goal = vsize; 232 page_max_depth = max_depth; 233 page_depth = 0; 234 do_all_six(set_page_so_far_zero); 235 least_page_cost = awful_bad; 236 if (int_par(tracing_pages_code) > 0) { 237 begin_diagnostic(); 238 tprint_nl("%% goal height="); 239 print_scaled(page_goal); 240 tprint(", max depth="); 241 print_scaled(page_max_depth); 242 end_diagnostic(false); 243 } 244} 245 246 247@ The global variable |output_active| is true during the time the 248user's output routine is driving \TeX. 249 250@c 251boolean output_active; /* are we in the midst of an output routine? */ 252 253 254@ The page builder is ready to start a fresh page if we initialize 255the following state variables. (However, the page insertion list is initialized 256elsewhere.) 257 258@c 259void start_new_page(void) 260{ 261 page_contents = empty; 262 page_tail = page_head; 263 vlink(page_head) = null; 264 last_glue = max_halfword; 265 last_penalty = 0; 266 last_kern = 0; 267 last_node_type = -1; 268 page_depth = 0; 269 page_max_depth = 0; 270} 271 272 273@ At certain times box \.{\\outputbox} is supposed to be void (i.e., |null|), 274or an insertion box is supposed to be ready to accept a vertical list. 275If not, an error message is printed, and the following subroutine 276flushes the unwanted contents, reporting them to the user. 277 278@c 279static void box_error(int n) 280{ 281 error(); 282 begin_diagnostic(); 283 tprint_nl("The following box has been deleted:"); 284 show_box(box(n)); 285 end_diagnostic(true); 286 flush_node_list(box(n)); 287 box(n) = null; 288} 289 290 291@ The following procedure guarantees that a given box register 292does not contain an \.{\\hbox}. 293 294@c 295static void ensure_vbox(int n) 296{ 297 halfword p; /* the box register contents */ 298 p = box(n); 299 if (p != null && type(p) == hlist_node) { 300 print_err("Insertions can only be added to a vbox"); 301 help3("Tut tut: You're trying to \\insert into a", 302 "\\box register that now contains an \\hbox.", 303 "Proceed, and I'll discard its present contents."); 304 box_error(n); 305 } 306} 307 308 309@ \TeX\ is not always in vertical mode at the time |build_page| 310is called; the current mode reflects what \TeX\ should return to, after 311the contribution list has been emptied. A call on |build_page| should 312be immediately followed by `|goto big_switch|', which is \TeX's central 313control point. 314 315@c 316void build_page(void) 317{ /* append contributions to the current page */ 318 halfword p; /* the node being appended */ 319 halfword q, r; /* nodes being examined */ 320 int b, c; /* badness and cost of current page */ 321 int pi; /* penalty to be added to the badness */ 322 int n; /* insertion box number */ 323 scaled delta, h, w; /* sizes used for insertion calculations */ 324 pi = 0; 325 if ((vlink(contrib_head) == null) || output_active) 326 return; 327 do { 328 CONTINUE: 329 p = vlink(contrib_head); 330 /* Update the values of |last_glue|, |last_penalty|, and |last_kern| */ 331 if (last_glue != max_halfword) { 332 delete_glue_ref(last_glue); 333 last_glue = max_halfword; 334 } 335 last_penalty = 0; 336 last_kern = 0; 337 last_node_type = type(p) + 1; 338 if (type(p) == glue_node) { 339 last_glue = glue_ptr(p); 340 add_glue_ref(last_glue); 341 } else if (type(p) == penalty_node) { 342 last_penalty = penalty(p); 343 } else if (type(p) == kern_node) { 344 last_kern = width(p); 345 } 346 347 /* Move node |p| to the current page; if it is time for a page break, 348 put the nodes following the break back onto the contribution list, 349 and |return| to the users output routine if there is one */ 350 351 /* The code here is an example of a many-way switch into routines that 352 merge together in different places. Some people call this unstructured 353 programming, but the author doesn't see much wrong with it, as long as 354 the various labels have a well-understood meaning. 355 */ 356 /* If the current page is empty and node |p| is to be deleted, |goto done1|; 357 otherwise use node |p| to update the state of the current page; 358 if this node is an insertion, |goto contribute|; otherwise if this node 359 is not a legal breakpoint, |goto contribute| or |update_heights|; 360 otherwise set |pi| to the penalty associated with this breakpoint */ 361 /* The title of this section is already so long, it seems best to avoid 362 making it more accurate but still longer, by mentioning the fact that a 363 kern node at the end of the contribution list will not be contributed until 364 we know its successor. */ 365 switch (type(p)) { 366 case hlist_node: 367 case vlist_node: 368 case rule_node: 369 if (page_contents < box_there) { 370 /* Initialize the current page, insert the \.{\\topskip} glue 371 ahead of |p|, and |goto continue| */ 372 if (page_contents == empty) 373 freeze_page_specs(box_there); 374 else 375 page_contents = box_there; 376 q = new_skip_param(top_skip_code); /* now |temp_ptr=glue_ptr(q)| */ 377 if ((type(p) == hlist_node) && is_mirrored(body_direction)) { 378 if (width(temp_ptr) > depth(p)) 379 width(temp_ptr) = width(temp_ptr) - depth(p); 380 else 381 width(temp_ptr) = 0; 382 } else { 383 if (width(temp_ptr) > height(p)) 384 width(temp_ptr) = width(temp_ptr) - height(p); 385 else 386 width(temp_ptr) = 0; 387 } 388 couple_nodes(q, p); 389 couple_nodes(contrib_head, q); 390 goto CONTINUE; 391 392 } else { 393 /* Prepare to move a box or rule node to the current page, 394 then |goto contribute| */ 395 if ((type(p) == hlist_node) && is_mirrored(body_direction)) { 396 page_total = page_total + page_depth + depth(p); 397 page_depth = height(p); 398 } else { 399 page_total = page_total + page_depth + height(p); 400 page_depth = depth(p); 401 } 402 goto CONTRIBUTE; 403 404 } 405 break; 406 case whatsit_node: 407 /* Prepare to move whatsit |p| to the current page, 408 then |goto contribute| */ 409 if ((subtype(p) == pdf_refxform_node) 410 || (subtype(p) == pdf_refximage_node)) { 411 page_total = page_total + page_depth + height(p); 412 page_depth = depth(p); 413 } 414 goto CONTRIBUTE; 415 416 break; 417 case glue_node: 418 if (page_contents < box_there) 419 goto DONE1; 420 else if (precedes_break(page_tail)) 421 pi = 0; 422 else 423 goto UPDATE_HEIGHTS; 424 break; 425 case kern_node: 426 if (page_contents < box_there) 427 goto DONE1; 428 else if (vlink(p) == null) 429 goto EXIT; 430 else if (type(vlink(p)) == glue_node) 431 pi = 0; 432 else 433 goto UPDATE_HEIGHTS; 434 break; 435 case penalty_node: 436 if (page_contents < box_there) 437 goto DONE1; 438 else 439 pi = penalty(p); 440 break; 441 case mark_node: 442 goto CONTRIBUTE; 443 break; 444 case ins_node: 445 /* Append an insertion to the current page and |goto contribute| */ 446 if (page_contents == empty) 447 freeze_page_specs(inserts_only); 448 n = subtype(p); 449 r = page_ins_head; 450 while (n >= subtype(vlink(r))) 451 r = vlink(r); 452 if (subtype(r) != n) { 453 /* Create a page insertion node with |subtype(r)=qi(n)|, and 454 include the glue correction for box |n| in the 455 current page state */ 456 /* We take note of the value of \.{\\skip} |n| and the height plus depth 457 of \.{\\box}~|n| only when the first \.{\\insert}~|n| node is 458 encountered for a new page. A user who changes the contents of \.{\\box}~|n| 459 after that first \.{\\insert}~|n| had better be either extremely careful 460 or extremely lucky, or both. */ 461 462 q = new_node(inserting_node, n); 463 try_couple_nodes(q, vlink(r)); 464 couple_nodes(r, q); 465 r = q; 466 ensure_vbox(n); 467 if (box(n) == null) 468 height(r) = 0; 469 else 470 height(r) = height(box(n)) + depth(box(n)); 471 best_ins_ptr(r) = null; 472 q = skip(n); 473 if (count(n) == 1000) 474 h = height(r); 475 else 476 h = x_over_n(height(r), 1000) * count(n); 477 page_goal = page_goal - h - width(q); 478 if (stretch_order(q) > 1) 479 page_so_far[1 + stretch_order(q)] = 480 page_so_far[1 + stretch_order(q)] + stretch(q); 481 else 482 page_so_far[2 + stretch_order(q)] = 483 page_so_far[2 + stretch_order(q)] + stretch(q); 484 page_shrink = page_shrink + shrink(q); 485 if ((shrink_order(q) != normal) && (shrink(q) != 0)) { 486 print_err("Infinite glue shrinkage inserted from \\skip"); 487 print_int(n); 488 help3 489 ("The correction glue for page breaking with insertions", 490 "must have finite shrinkability. But you may proceed,", 491 "since the offensive shrinkability has been made finite."); 492 error(); 493 } 494 495 } 496 if (type(r) == split_up_node) { 497 insert_penalties = insert_penalties + float_cost(p); 498 } else { 499 last_ins_ptr(r) = p; 500 delta = page_goal - page_total - page_depth + page_shrink; 501 /* this much room is left if we shrink the maximum */ 502 if (count(n) == 1000) 503 h = height(p); 504 else 505 h = x_over_n(height(p), 1000) * count(n); /* this much room is needed */ 506 if (((h <= 0) || (h <= delta)) 507 && (height(p) + height(r) <= dimen(n))) { 508 page_goal = page_goal - h; 509 height(r) = height(r) + height(p); 510 } else { 511 /* Find the best way to split the insertion, and change 512 |type(r)| to |split_up_node| */ 513 /* Here is the code that will split a long footnote between pages, in an 514 emergency. The current situation deserves to be recapitulated: Node |p| 515 is an insertion into box |n|; the insertion will not fit, in its entirety, 516 either because it would make the total contents of box |n| greater than 517 \.{\\dimen} |n|, or because it would make the incremental amount of growth 518 |h| greater than the available space |delta|, or both. (This amount |h| has 519 been weighted by the insertion scaling factor, i.e., by \.{\\count} |n| 520 over 1000.) Now we will choose the best way to break the vlist of the 521 insertion, using the same criteria as in the \.{\\vsplit} operation. 522 */ 523 if (count(n) <= 0) { 524 w = max_dimen; 525 } else { 526 w = page_goal - page_total - page_depth; 527 if (count(n) != 1000) 528 w = x_over_n(w, count(n)) * 1000; 529 } 530 if (w > dimen(n) - height(r)) 531 w = dimen(n) - height(r); 532 q = vert_break(ins_ptr(p), w, depth(p)); 533 height(r) = height(r) + best_height_plus_depth; 534 if (int_par(tracing_pages_code) > 0) { 535 /* Display the insertion split cost */ 536 begin_diagnostic(); 537 tprint_nl("% split"); 538 print_int(n); 539 tprint(" to "); 540 print_scaled(w); 541 print_char(','); 542 print_scaled(best_height_plus_depth); 543 tprint(" p="); 544 if (q == null) 545 print_int(eject_penalty); 546 else if (type(q) == penalty_node) 547 print_int(penalty(q)); 548 else 549 print_char('0'); 550 end_diagnostic(false); 551 552 } 553 if (count(n) != 1000) 554 best_height_plus_depth = 555 x_over_n(best_height_plus_depth, 1000) * count(n); 556 page_goal = page_goal - best_height_plus_depth; 557 type(r) = split_up_node; 558 broken_ptr(r) = q; 559 broken_ins(r) = p; 560 if (q == null) 561 insert_penalties = insert_penalties + eject_penalty; 562 else if (type(q) == penalty_node) 563 insert_penalties = insert_penalties + penalty(q); 564 } 565 } 566 goto CONTRIBUTE; 567 568 break; 569 default: 570 fprintf(stderr, "type(p)=%d\n", type(p)); 571 confusion("page"); 572 break; 573 } 574 575 /* Check if node |p| is a new champion breakpoint; then if it is time for 576 a page break, prepare for output, and either fire up the users 577 output routine and |return| or ship out the page and |goto done| */ 578 579 if (pi < inf_penalty) { 580 /* Compute the badness, |b|, of the current page, 581 using |awful_bad| if the box is too full */ 582 if (page_total < page_goal) { 583 if ((page_so_far[3] != 0) || (page_so_far[4] != 0) || 584 (page_so_far[5] != 0)) 585 b = 0; 586 else 587 b = badness(page_goal - page_total, page_so_far[2]); 588 } else if (page_total - page_goal > page_shrink) { 589 b = awful_bad; 590 } else { 591 b = badness(page_total - page_goal, page_shrink); 592 } 593 594 if (b < awful_bad) { 595 if (pi <= eject_penalty) 596 c = pi; 597 else if (b < inf_bad) 598 c = b + pi + insert_penalties; 599 else 600 c = deplorable; 601 } else { 602 c = b; 603 } 604 if (insert_penalties >= 10000) 605 c = awful_bad; 606 if (int_par(tracing_pages_code) > 0) { 607 /* Display the page break cost */ 608 begin_diagnostic(); 609 tprint_nl("%"); 610 tprint(" t="); 611 print_totals(); 612 tprint(" g="); 613 print_scaled(page_goal); 614 tprint(" b="); 615 if (b == awful_bad) 616 print_char('*'); 617 else 618 print_int(b); 619 tprint(" p="); 620 print_int(pi); 621 tprint(" c="); 622 if (c == awful_bad) 623 print_char('*'); 624 else 625 print_int(c); 626 if (c <= least_page_cost) 627 print_char('#'); 628 end_diagnostic(false); 629 630 } 631 if (c <= least_page_cost) { 632 best_page_break = p; 633 best_size = page_goal; 634 least_page_cost = c; 635 r = vlink(page_ins_head); 636 while (r != page_ins_head) { 637 best_ins_ptr(r) = last_ins_ptr(r); 638 r = vlink(r); 639 } 640 } 641 if ((c == awful_bad) || (pi <= eject_penalty)) { 642 fire_up(p); /* output the current page at the best place */ 643 if (output_active) 644 goto EXIT; /* user's output routine will act */ 645 goto DONE; /* the page has been shipped out by default output routine */ 646 } 647 } 648 649 if ((type(p) < glue_node) || (type(p) > kern_node)) 650 goto CONTRIBUTE; 651 652 UPDATE_HEIGHTS: /* go here to record glue in the |active_height| table */ 653 654 /* Update the current page measurements with respect to the 655 glue or kern specified by node~|p| */ 656 if (type(p) == kern_node) { 657 q = p; 658 } else { 659 q = glue_ptr(p); 660 if (stretch_order(q) > 1) 661 page_so_far[1 + stretch_order(q)] = 662 page_so_far[1 + stretch_order(q)] + stretch(q); 663 else 664 page_so_far[2 + stretch_order(q)] = 665 page_so_far[2 + stretch_order(q)] + stretch(q); 666 page_shrink = page_shrink + shrink(q); 667 if ((shrink_order(q) != normal) && (shrink(q) != 0)) { 668 print_err("Infinite glue shrinkage found on current page"); 669 help4("The page about to be output contains some infinitely", 670 "shrinkable glue, e.g., `\\vss' or `\\vskip 0pt minus 1fil'.", 671 "Such glue doesn't belong there; but you can safely proceed,", 672 "since the offensive shrinkability has been made finite."); 673 error(); 674 r = new_spec(q); 675 shrink_order(r) = normal; 676 delete_glue_ref(q); 677 glue_ptr(p) = r; 678 q = r; 679 } 680 } 681 page_total = page_total + page_depth + width(q); 682 page_depth = 0; 683 684 CONTRIBUTE: /* go here to link a node into the current page */ 685 686 /* Make sure that |page_max_depth| is not exceeded */ 687 if (page_depth > page_max_depth) { 688 page_total = page_total + page_depth - page_max_depth; 689 page_depth = page_max_depth; 690 } 691 692 /* Link node |p| into the current page and |goto done| */ 693 couple_nodes(page_tail, p); 694 page_tail = p; 695 try_couple_nodes(contrib_head,vlink(p)); 696 vlink(p) = null; 697 goto DONE; 698 DONE1: 699 /* Recycle node |p| */ 700 try_couple_nodes(contrib_head,vlink(p)); 701 vlink(p) = null; 702 if (int_par(saving_vdiscards_code) > 0) { 703 if (page_disc == null) { 704 page_disc = p; 705 } else { 706 couple_nodes(tail_page_disc, p); 707 } 708 tail_page_disc = p; 709 } else { 710 flush_node_list(p); 711 } 712 DONE: 713 ; 714 } while (vlink(contrib_head) != null); 715 /* Make the contribution list empty by setting its tail to |contrib_head| */ 716 contrib_tail = contrib_head; 717 EXIT: 718 ; 719} 720 721@ When the page builder has looked at as much material as could appear before 722the next page break, it makes its decision. The break that gave minimum 723badness will be used to put a completed ``page'' into box \.{\\outputbox}, with insertions 724appended to their other boxes. 725 726We also set the values of |top_mark|, |first_mark|, and |bot_mark|. The 727program uses the fact that |bot_mark(x)<>null| implies |first_mark(x)<>null|; 728it also knows that |bot_mark(x)=null| implies |top_mark(x)=first_mark(x)=null|. 729 730The |fire_up| subroutine prepares to output the current page at the best 731place; then it fires up the user's output routine, if there is one, 732or it simply ships out the page. There is one parameter, |c|, which represents 733the node that was being contributed to the page when the decision to 734force an output was made. 735 736@c 737void fire_up(halfword c) 738{ 739 halfword p, q, r, s; /* nodes being examined and/or changed */ 740 halfword prev_p; /* predecessor of |p| */ 741 int n; /* insertion box number */ 742 boolean wait; /* should the present insertion be held over? */ 743 int save_vbadness; /* saved value of |vbadness| */ 744 scaled save_vfuzz; /* saved value of |vfuzz| */ 745 halfword save_split_top_skip; /* saved value of |split_top_skip| */ 746 halfword i; /* for looping through the marks */ 747 748 /* Set the value of |output_penalty| */ 749 if (type(best_page_break) == penalty_node) { 750 geq_word_define(int_base + output_penalty_code, 751 penalty(best_page_break)); 752 penalty(best_page_break) = inf_penalty; 753 } else { 754 geq_word_define(int_base + output_penalty_code, inf_penalty); 755 } 756 757 for (i = 0; i <= biggest_used_mark; i++) { 758 if (bot_mark(i) != null) { 759 if (top_mark(i) != null) 760 delete_token_ref(top_mark(i)); 761 set_top_mark(i, bot_mark(i)); 762 add_token_ref(top_mark(i)); 763 delete_first_mark(i); 764 } 765 } 766 /* Put the optimal current page into box |output_box|, update |first_mark| and 767 |bot_mark|, append insertions to their boxes, and put the 768 remaining nodes back on the contribution list; */ 769 770 /* As the page is finally being prepared for output, 771 pointer |p| runs through the vlist, with |prev_p| trailing behind; 772 pointer |q| is the tail of a list of insertions that 773 are being held over for a subsequent page. */ 774 775 if (c == best_page_break) 776 best_page_break = null; /* |c| not yet linked in */ 777 /* Ensure that box |output_box| is empty before output */ 778 if (box(output_box) != null) { 779 print_err("\\box"); 780 print_int(output_box); 781 tprint(" is not void"); 782 help2("You shouldn't use \\box\\outputbox except in \\output routines.", 783 "Proceed, and I'll discard its present contents."); 784 box_error(output_box); 785 } 786 787 insert_penalties = 0; /* this will count the number of insertions held over */ 788 save_split_top_skip = split_top_skip; 789 if (holding_inserts <= 0) { 790 /* Prepare all the boxes involved in insertions to act as queues */ 791 /* If many insertions are supposed to go into the same box, we want to know 792 the position of the last node in that box, so that we don't need to waste time 793 when linking further information into it. The |last_ins_ptr| fields of the 794 page insertion nodes are therefore used for this purpose during the 795 packaging phase. */ 796 797 r = vlink(page_ins_head); 798 while (r != page_ins_head) { 799 if (best_ins_ptr(r) != null) { 800 n = subtype(r); 801 ensure_vbox(n); 802 if (box(n) == null) 803 box(n) = new_null_box(); 804 p = box(n) + list_offset; 805 while (vlink(p) != null) 806 p = vlink(p); 807 last_ins_ptr(r) = p; 808 } 809 r = vlink(r); 810 } 811 812 } 813 q = hold_head; 814 vlink(q) = null; 815 prev_p = page_head; 816 p = vlink(prev_p); 817 while (p != best_page_break) { 818 if (type(p) == ins_node) { 819 if (holding_inserts <= 0) { 820 /* Either insert the material specified by node |p| into the 821 appropriate box, or hold it for the next page; 822 also delete node |p| from the current page */ 823 /* We will set |best_ins_ptr:=null| and package the box corresponding to 824 insertion node~|r|, just after making the final insertion into that box. 825 If this final insertion is `|split_up_node|', the remainder after splitting 826 and pruning (if any) will be carried over to the next page. */ 827 r = vlink(page_ins_head); 828 while (subtype(r) != subtype(p)) 829 r = vlink(r); 830 if (best_ins_ptr(r) == null) { 831 wait = true; 832 } else { 833 wait = false; 834 s = last_ins_ptr(r); 835 vlink(s) = ins_ptr(p); 836 if (best_ins_ptr(r) == p) { 837 /* Wrap up the box specified by node |r|, splitting node |p| if 838 called for; set |wait:=true| if node |p| holds a remainder after 839 splitting */ 840 if (type(r) == split_up_node) { 841 if ((broken_ins(r) == p) && (broken_ptr(r) != null)) { 842 while (vlink(s) != broken_ptr(r)) 843 s = vlink(s); 844 vlink(s) = null; 845 split_top_skip = split_top_ptr(p); 846 ins_ptr(p) = 847 prune_page_top(broken_ptr(r), false); 848 if (ins_ptr(p) != null) { 849 temp_ptr = 850 vpack(ins_ptr(p), 0, additional, -1); 851 height(p) = 852 height(temp_ptr) + depth(temp_ptr); 853 list_ptr(temp_ptr) = null; 854 flush_node(temp_ptr); 855 wait = true; 856 } 857 } 858 } 859 best_ins_ptr(r) = null; 860 n = subtype(r); 861 temp_ptr = list_ptr(box(n)); 862 list_ptr(box(n)) = null; 863 flush_node(box(n)); 864 box(n) = vpack(temp_ptr, 0, additional, body_direction); 865 866 } else { 867 while (vlink(s) != null) 868 s = vlink(s); 869 last_ins_ptr(r) = s; 870 } 871 } 872 /* Either append the insertion node |p| after node |q|, and remove it 873 from the current page, or delete |node(p)| */ 874 try_couple_nodes(prev_p, vlink(p)); 875 vlink(p) = null; 876 if (wait) { 877 couple_nodes(q, p); 878 q = p; 879 incr(insert_penalties); 880 } else { 881 ins_ptr(p) = null; 882 flush_node(p); 883 } 884 p = prev_p; 885 886 } 887 } else if (type(p) == mark_node) { 888 /* Update the values of |first_mark| and |bot_mark| */ 889 if (first_mark(mark_class(p)) == null) { 890 set_first_mark(mark_class(p), mark_ptr(p)); 891 add_token_ref(first_mark(mark_class(p))); 892 } 893 if (bot_mark(mark_class(p)) != null) 894 delete_token_ref(bot_mark(mark_class(p))); 895 set_bot_mark(mark_class(p), mark_ptr(p)); 896 add_token_ref(bot_mark(mark_class(p))); 897 898 } 899 prev_p = p; 900 p = vlink(prev_p); 901 } 902 split_top_skip = save_split_top_skip; 903 /* Break the current page at node |p|, put it in box~|output_box|, 904 and put the remaining nodes on the contribution list */ 905 /* When the following code is executed, the current page runs from node 906 |vlink(page_head)| to node |prev_p|, and the nodes from |p| to |page_tail| 907 are to be placed back at the front of the contribution list. Furthermore 908 the heldover insertions appear in a list from |vlink(hold_head)| to |q|; we 909 will put them into the current page list for safekeeping while the user's 910 output routine is active. We might have |q=hold_head|; and |p=null| if 911 and only if |prev_p=page_tail|. Error messages are suppressed within 912 |vpackage|, since the box might appear to be overfull or underfull simply 913 because the stretch and shrink from the \.{\\skip} registers for inserts 914 are not actually present in the box. */ 915 916 if (p != null) { 917 if (vlink(contrib_head) == null) { 918 contrib_tail = page_tail; 919 } 920 couple_nodes(page_tail,vlink(contrib_head)); 921 couple_nodes(contrib_head, p); 922 vlink(prev_p) = null; 923 } 924 save_vbadness = vbadness; 925 vbadness = inf_bad; 926 save_vfuzz = vfuzz; 927 vfuzz = max_dimen; /* inhibit error messages */ 928 box(output_box) = 929 filtered_vpackage(vlink(page_head), best_size, exactly, page_max_depth, 930 output_group, body_direction); 931 vbadness = save_vbadness; 932 vfuzz = save_vfuzz; 933 if (last_glue != max_halfword) 934 delete_glue_ref(last_glue); 935 /* Start a new current page */ 936 start_new_page(); /* this sets |last_glue:=max_halfword| */ 937 if (q != hold_head) { 938 vlink(page_head) = vlink(hold_head); 939 page_tail = q; 940 } 941 942 /* Delete the page-insertion nodes */ 943 r = vlink(page_ins_head); 944 while (r != page_ins_head) { 945 q = vlink(r); 946 flush_node(r); 947 r = q; 948 } 949 vlink(page_ins_head) = page_ins_head; 950 951 for (i = 0; i <= biggest_used_mark; i++) { 952 if ((top_mark(i) != null) && (first_mark(i) == null)) { 953 set_first_mark(i, top_mark(i)); 954 add_token_ref(top_mark(i)); 955 } 956 } 957 if (output_routine != null) { 958 if (dead_cycles >= max_dead_cycles) { 959 /* Explain that too many dead cycles have occurred in a row */ 960 print_err("Output loop---"); 961 print_int(dead_cycles); 962 tprint(" consecutive dead cycles"); 963 help3("I've concluded that your \\output is awry; it never does a", 964 "\\shipout, so I'm shipping \\box\\outputbox out myself. Next time", 965 "increase \\maxdeadcycles if you want me to be more patient!"); 966 error(); 967 968 } else { 969 /* Fire up the users output routine and |return| */ 970 output_active = true; 971 incr(dead_cycles); 972 push_nest(); 973 mode = -vmode; 974 prev_depth = pdf_ignored_dimen; 975 mode_line = -line; 976 begin_token_list(output_routine, output_text); 977 new_save_level(output_group); 978 normal_paragraph(); 979 scan_left_brace(); 980 return; 981 982 } 983 } 984 /* Perform the default output routine */ 985 /* The list of heldover insertions, running from |vlink(page_head)| to 986 |page_tail|, must be moved to the contribution list when the user has 987 specified no output routine. */ 988 if (vlink(page_head) != null) { 989 if (vlink(contrib_head) == null) { 990 contrib_tail = page_tail; 991 } else { 992 vlink(page_tail) = vlink(contrib_head); 993 } 994 vlink(contrib_head) = vlink(page_head); 995 vlink(page_head) = null; 996 page_tail = page_head; 997 } 998 flush_node_list(page_disc); 999 page_disc = null; 1000 ship_out(static_pdf, box(output_box), SHIPPING_PAGE); 1001 box(output_box) = null; 1002} 1003 1004 1005@ When the user's output routine finishes, it has constructed a vlist 1006in internal vertical mode, and \TeX\ will do the following: 1007 1008@c 1009void resume_after_output(void) 1010{ 1011 if ((iloc != null) 1012 || ((token_type != output_text) && (token_type != backed_up))) { 1013 /* Recover from an unbalanced output routine */ 1014 print_err("Unbalanced output routine"); 1015 help2("Your sneaky output routine has problematic {'s and/or }'s.", 1016 "I can't handle that very well; good luck."); 1017 error(); 1018 do { 1019 get_token(); 1020 } while (iloc != null); 1021 /* loops forever if reading from a file, since |null=min_halfword<=0| */ 1022 1023 } 1024 end_token_list(); /* conserve stack space in case more outputs are triggered */ 1025 end_graf(bottom_level); 1026 unsave(); 1027 output_active = false; 1028 insert_penalties = 0; 1029 /* Ensure that box |output_box| is empty after output */ 1030 if (box(output_box) != null) { 1031 print_err("Output routine didn't use all of \\box"); 1032 print_int(output_box); 1033 help3("Your \\output commands should empty \\box\\outputbox,", 1034 "e.g., by saying `\\shipout\\box\\outputbox'.", 1035 "Proceed; I'll discard its present contents."); 1036 box_error(output_box); 1037 } 1038 1039 if (tail != head) { /* current list goes after heldover insertions */ 1040 try_couple_nodes(page_tail, vlink(head)); 1041 page_tail = tail; 1042 } 1043 if (vlink(page_head) != null) { /* and both go before heldover contributions */ 1044 if (vlink(contrib_head) == null) 1045 contrib_tail = page_tail; 1046 try_couple_nodes(page_tail, vlink(contrib_head)); 1047 try_couple_nodes(contrib_head, vlink(page_head)); 1048 vlink(page_head) = null; 1049 page_tail = page_head; 1050 } 1051 flush_node_list(page_disc); 1052 page_disc = null; 1053 pop_nest(); 1054 //lua_node_filter_s(buildpage_filter_callback, "after_output"); 1055 lua_node_filter_s(buildpage_filter_callback,lua_key_index(after_output)); 1056 build_page(); 1057 1058} 1059