Compacted PS and PDF output files by removing redundant reiterations
[sgt/halibut] / bk_paper.c
1 /*
2 * Paper printing pre-backend for Halibut.
3 *
4 * This module does all the processing common to both PostScript
5 * and PDF output: selecting fonts, line wrapping and page breaking
6 * in accordance with font metrics, laying out the contents and
7 * index pages, generally doing all the page layout. After this,
8 * bk_ps.c and bk_pdf.c should only need to do linear translations
9 * into their literal output format.
10 */
11
12 /*
13 * TODO in future work:
14 *
15 * - linearised PDF, perhaps?
16 *
17 * - I'm uncertain of whether I need to include a ToUnicode CMap
18 * in each of my font definitions in PDF. Currently things (by
19 * which I mean cut and paste out of acroread) seem to be
20 * working fairly happily without it, but I don't know.
21 *
22 * - rather than the ugly aux_text mechanism for rendering chapter
23 * titles, we could actually build the correct word list and
24 * wrap it as a whole.
25 *
26 * - get vertical font metrics and use them to position the PDF
27 * xref boxes more pleasantly
28 *
29 * - configurability
30 * * all the measurements in `conf' should be configurable
31 * + notably paper size/shape
32 * * page header and footer should be configurable; we should
33 * be able to shift the page number elsewhere, and add other
34 * things such as the current chapter/section title and fixed
35 * text
36 * * remove the fixed mapping from heading levels to heading
37 * styles; offer a menu of styles from which the user can
38 * choose at every heading level
39 * * first-line indent in paragraphs
40 * * fixed text: `Contents', `Index', bullet, quotes, the
41 * colon-space and full stop in chapter title constructions
42 * * configurable location of contents?
43 * * certainly configurably _remove_ the contents, and possibly
44 * also the index
45 * * double-sided document switch?
46 * + means you have two header/footer formats which
47 * alternate
48 * + and means that mandatory page breaks before chapter
49 * titles should include a blank page if necessary to
50 * start the next section to a right-hand page
51 *
52 * - title pages
53 *
54 * - ability to import other Type 1 fonts
55 * * we need to parse the font to extract its metrics
56 * * then we pass the font bodily to both PS and PDF so it can
57 * be included in the output file
58 *
59 * - character substitution for better typography?
60 * * fi, fl, ffi, ffl ligatures
61 * * use real ellipsis rather than ...
62 * * a hyphen in a word by itself might prefer to be an en-dash
63 * * (Americans might even want a convenient way to use an
64 * em-dash)
65 * * DON'T DO ANY OF THE ABOVE WITHIN \c OR \cw!
66 * * substituting `minus' for `hyphen' in the standard encoding
67 * is probably preferable in Courier, though certainly not in
68 * the main text font
69 * * if I do do this lot, I'm rather inclined to at least try
70 * to think up a configurable way to do it so that Americans
71 * can do em-dash tricks without my intervention and other
72 * people can do other odd things too.
73 */
74
75 #include <assert.h>
76 #include <stdio.h>
77
78 #include "halibut.h"
79 #include "paper.h"
80
81 typedef struct paper_conf_Tag paper_conf;
82 typedef struct paper_idx_Tag paper_idx;
83
84 struct paper_conf_Tag {
85 int paper_width;
86 int paper_height;
87 int left_margin;
88 int top_margin;
89 int right_margin;
90 int bottom_margin;
91 int indent_list_bullet;
92 int indent_list;
93 int indent_quote;
94 int base_leading;
95 int base_para_spacing;
96 int chapter_top_space;
97 int sect_num_left_space;
98 int chapter_underline_depth;
99 int chapter_underline_thickness;
100 int rule_thickness;
101 int base_font_size;
102 int contents_indent_step;
103 int contents_margin;
104 int leader_separation;
105 int index_gutter;
106 int index_cols;
107 int index_minsep;
108 int pagenum_fontsize;
109 int footer_distance;
110 /* These are derived from the above */
111 int base_width;
112 int page_height;
113 int index_colwidth;
114 /* Fonts used in the configuration */
115 font_data *tr, *ti, *hr, *hi, *cr, *co, *cb;
116 };
117
118 struct paper_idx_Tag {
119 /*
120 * Word list giving the page numbers on which this index entry
121 * appears. Also the last word in the list, for ease of
122 * construction.
123 */
124 word *words;
125 word *lastword;
126 /*
127 * The last page added to the list (so we can ensure we don't
128 * add one twice).
129 */
130 page_data *lastpage;
131 };
132
133 enum {
134 word_PageXref = word_NotWordType + 1
135 };
136
137 static font_data *make_std_font(font_list *fontlist, char const *name);
138 static void wrap_paragraph(para_data *pdata, word *words,
139 int w, int i1, int i2);
140 static page_data *page_breaks(line_data *first, line_data *last,
141 int page_height, int ncols, int headspace);
142 static int render_string(page_data *page, font_data *font, int fontsize,
143 int x, int y, wchar_t *str);
144 static int render_line(line_data *ldata, int left_x, int top_y,
145 xref_dest *dest, keywordlist *keywords, indexdata *idx);
146 static void render_para(para_data *pdata, paper_conf *conf,
147 keywordlist *keywords, indexdata *idx,
148 paragraph *index_placeholder, page_data *index_page);
149 static int string_width(font_data *font, wchar_t const *string, int *errs);
150 static int paper_width_simple(para_data *pdata, word *text);
151 static para_data *code_paragraph(int indent, word *words, paper_conf *conf);
152 static para_data *rule_paragraph(int indent, paper_conf *conf);
153 static void add_rect_to_page(page_data *page, int x, int y, int w, int h);
154 static para_data *make_para_data(int ptype, int paux, int indent, int rmargin,
155 word *pkwtext, word *pkwtext2, word *pwords,
156 paper_conf *conf);
157 static void standard_line_spacing(para_data *pdata, paper_conf *conf);
158 static wchar_t *prepare_outline_title(word *first, wchar_t *separator,
159 word *second);
160 static word *fake_word(wchar_t *text);
161 static word *fake_space_word(void);
162 static word *fake_page_ref(page_data *page);
163 static word *fake_end_ref(void);
164 static word *prepare_contents_title(word *first, wchar_t *separator,
165 word *second);
166 static void fold_into_page(page_data *dest, page_data *src, int right_shift);
167
168 void *paper_pre_backend(paragraph *sourceform, keywordlist *keywords,
169 indexdata *idx) {
170 paragraph *p;
171 document *doc;
172 int indent, used_contents;
173 para_data *pdata, *firstpara = NULL, *lastpara = NULL;
174 para_data *firstcont, *lastcont;
175 line_data *firstline, *lastline, *firstcontline, *lastcontline;
176 page_data *pages;
177 font_list *fontlist;
178 paper_conf *conf;
179 int has_index;
180 int pagenum;
181 paragraph index_placeholder_para;
182 page_data *first_index_page;
183
184 /*
185 * FIXME: All these things ought to become configurable.
186 */
187 conf = mknew(paper_conf);
188 conf->paper_width = 595 * 4096;
189 conf->paper_height = 841 * 4096;
190 conf->left_margin = 72 * 4096;
191 conf->top_margin = 72 * 4096;
192 conf->right_margin = 72 * 4096;
193 conf->bottom_margin = 108 * 4096;
194 conf->indent_list_bullet = 6 * 4096;
195 conf->indent_list = 24 * 4096;
196 conf->indent_quote = 18 * 4096;
197 conf->base_leading = 4096;
198 conf->base_para_spacing = 10 * 4096;
199 conf->chapter_top_space = 72 * 4096;
200 conf->sect_num_left_space = 12 * 4096;
201 conf->chapter_underline_depth = 14 * 4096;
202 conf->chapter_underline_thickness = 3 * 4096;
203 conf->rule_thickness = 1 * 4096;
204 conf->base_font_size = 12;
205 conf->contents_indent_step = 24 * 4096;
206 conf->contents_margin = 84 * 4096;
207 conf->leader_separation = 12 * 4096;
208 conf->index_gutter = 36 * 4096;
209 conf->index_cols = 2;
210 conf->index_minsep = 18 * 4096;
211 conf->pagenum_fontsize = 12;
212 conf->footer_distance = 32 * 4096;
213
214 conf->base_width =
215 conf->paper_width - conf->left_margin - conf->right_margin;
216 conf->page_height =
217 conf->paper_height - conf->top_margin - conf->bottom_margin;
218 conf->index_colwidth =
219 (conf->base_width - (conf->index_cols-1) * conf->index_gutter)
220 / conf->index_cols;
221
222 /*
223 * First, set up some font structures.
224 */
225 fontlist = mknew(font_list);
226 fontlist->head = fontlist->tail = NULL;
227 conf->tr = make_std_font(fontlist, "Times-Roman");
228 conf->ti = make_std_font(fontlist, "Times-Italic");
229 conf->hr = make_std_font(fontlist, "Helvetica-Bold");
230 conf->hi = make_std_font(fontlist, "Helvetica-BoldOblique");
231 conf->cr = make_std_font(fontlist, "Courier");
232 conf->co = make_std_font(fontlist, "Courier-Oblique");
233 conf->cb = make_std_font(fontlist, "Courier-Bold");
234
235 /*
236 * Set up a data structure to collect page numbers for each
237 * index entry.
238 */
239 {
240 int i;
241 indexentry *entry;
242
243 has_index = FALSE;
244
245 for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) {
246 paper_idx *pi = mknew(paper_idx);
247
248 has_index = TRUE;
249
250 pi->words = pi->lastword = NULL;
251 pi->lastpage = NULL;
252
253 entry->backend_data = pi;
254 }
255 }
256
257 /*
258 * Format the contents entry for each heading.
259 */
260 {
261 word *contents_title;
262 contents_title = fake_word(L"Contents");
263
264 firstcont = make_para_data(para_UnnumberedChapter, 0, 0, 0,
265 NULL, NULL, contents_title, conf);
266 lastcont = firstcont;
267 lastcont->next = NULL;
268 firstcontline = firstcont->first;
269 lastcontline = lastcont->last;
270 for (p = sourceform; p; p = p->next) {
271 word *words;
272 int indent;
273
274 switch (p->type) {
275 case para_Chapter:
276 case para_Appendix:
277 case para_UnnumberedChapter:
278 case para_Heading:
279 case para_Subsect:
280 switch (p->type) {
281 case para_Chapter:
282 case para_Appendix:
283 words = prepare_contents_title(p->kwtext, L": ", p->words);
284 indent = 0;
285 break;
286 case para_UnnumberedChapter:
287 words = prepare_contents_title(NULL, NULL, p->words);
288 indent = 0;
289 break;
290 case para_Heading:
291 case para_Subsect:
292 words = prepare_contents_title(p->kwtext2, L" ", p->words);
293 indent = (p->aux + 1) * conf->contents_indent_step;
294 break;
295 }
296 pdata = make_para_data(para_Normal, p->aux, indent,
297 conf->contents_margin,
298 NULL, NULL, words, conf);
299 pdata->next = NULL;
300 pdata->contents_entry = p;
301 lastcont->next = pdata;
302 lastcont = pdata;
303
304 /*
305 * Link all contents line structures together into
306 * a big list.
307 */
308 if (pdata->first) {
309 if (lastcontline) {
310 lastcontline->next = pdata->first;
311 pdata->first->prev = lastcontline;
312 } else {
313 firstcontline = pdata->first;
314 pdata->first->prev = NULL;
315 }
316 lastcontline = pdata->last;
317 lastcontline->next = NULL;
318 }
319
320 break;
321 }
322 }
323
324 /*
325 * And one extra one, for the index.
326 */
327 if (has_index) {
328 pdata = make_para_data(para_Normal, 0, 0,
329 conf->contents_margin,
330 NULL, NULL, fake_word(L"Index"), conf);
331 pdata->next = NULL;
332 pdata->contents_entry = &index_placeholder_para;
333 lastcont->next = pdata;
334 lastcont = pdata;
335
336 if (pdata->first) {
337 if (lastcontline) {
338 lastcontline->next = pdata->first;
339 pdata->first->prev = lastcontline;
340 } else {
341 firstcontline = pdata->first;
342 pdata->first->prev = NULL;
343 }
344 lastcontline = pdata->last;
345 lastcontline->next = NULL;
346 }
347 }
348 }
349
350 /*
351 * Do the main paragraph formatting.
352 */
353 indent = 0;
354 used_contents = FALSE;
355 firstline = lastline = NULL;
356 for (p = sourceform; p; p = p->next) {
357 p->private_data = NULL;
358
359 switch (p->type) {
360 /*
361 * These paragraph types are either invisible or don't
362 * define text in the normal sense. Either way, they
363 * don't require wrapping.
364 */
365 case para_IM:
366 case para_BR:
367 case para_Biblio:
368 case para_NotParaType:
369 case para_Config:
370 case para_VersionID:
371 case para_NoCite:
372 break;
373
374 /*
375 * These paragraph types don't require wrapping, but
376 * they do affect the line width to which we wrap the
377 * rest of the paragraphs, so we need to pay attention.
378 */
379 case para_LcontPush:
380 indent += conf->indent_list; break;
381 case para_LcontPop:
382 indent -= conf->indent_list; assert(indent >= 0); break;
383 case para_QuotePush:
384 indent += conf->indent_quote; break;
385 case para_QuotePop:
386 indent -= conf->indent_quote; assert(indent >= 0); break;
387
388 /*
389 * This paragraph type is special. Process it
390 * specially.
391 */
392 case para_Code:
393 pdata = code_paragraph(indent, p->words, conf);
394 p->private_data = pdata;
395 if (pdata->first != pdata->last) {
396 pdata->first->penalty_after += 100000;
397 pdata->last->penalty_before += 100000;
398 }
399 break;
400
401 /*
402 * This paragraph is also special.
403 */
404 case para_Rule:
405 pdata = rule_paragraph(indent, conf);
406 p->private_data = pdata;
407 break;
408
409 /*
410 * All of these paragraph types require wrapping in the
411 * ordinary way. So we must supply a set of fonts, a
412 * line width and auxiliary information (e.g. bullet
413 * text) for each one.
414 */
415 case para_Chapter:
416 case para_Appendix:
417 case para_UnnumberedChapter:
418 case para_Heading:
419 case para_Subsect:
420 case para_Normal:
421 case para_BiblioCited:
422 case para_Bullet:
423 case para_NumberedList:
424 case para_DescribedThing:
425 case para_Description:
426 case para_Copyright:
427 case para_Title:
428 pdata = make_para_data(p->type, p->aux, indent, 0,
429 p->kwtext, p->kwtext2, p->words, conf);
430
431 p->private_data = pdata;
432
433 break;
434 }
435
436 if (p->private_data) {
437 pdata = (para_data *)p->private_data;
438
439 /*
440 * If this is the first non-title heading, we link the
441 * contents section in before it.
442 */
443 if (!used_contents && pdata->outline_level > 0) {
444 used_contents = TRUE;
445 if (lastpara)
446 lastpara->next = firstcont;
447 else
448 firstpara = firstcont;
449 lastpara = lastcont;
450 assert(lastpara->next == NULL);
451
452 if (lastline) {
453 lastline->next = firstcontline;
454 firstcontline->prev = lastline;
455 } else {
456 firstline = firstcontline;
457 firstcontline->prev = NULL;
458 }
459 assert(lastcontline != NULL);
460 lastline = lastcontline;
461 lastline->next = NULL;
462 }
463
464 /*
465 * Link all line structures together into a big list.
466 */
467 if (pdata->first) {
468 if (lastline) {
469 lastline->next = pdata->first;
470 pdata->first->prev = lastline;
471 } else {
472 firstline = pdata->first;
473 pdata->first->prev = NULL;
474 }
475 lastline = pdata->last;
476 lastline->next = NULL;
477 }
478
479 /*
480 * Link all paragraph structures together similarly.
481 */
482 pdata->next = NULL;
483 if (lastpara)
484 lastpara->next = pdata;
485 else
486 firstpara = pdata;
487 lastpara = pdata;
488 }
489 }
490
491 /*
492 * Now we have an enormous linked list of every line of text in
493 * the document. Break it up into pages.
494 */
495 pages = page_breaks(firstline, lastline, conf->page_height, 0, 0);
496
497 /*
498 * Number the pages.
499 */
500 {
501 char buf[40];
502 page_data *page;
503
504 pagenum = 0;
505
506 for (page = pages; page; page = page->next) {
507 sprintf(buf, "%d", ++pagenum);
508 page->number = ufroma_dup(buf);
509 }
510
511 if (has_index) {
512 first_index_page = mknew(page_data);
513 first_index_page->next = first_index_page->prev = NULL;
514 first_index_page->first_line = NULL;
515 first_index_page->last_line = NULL;
516 first_index_page->first_text = first_index_page->last_text = NULL;
517 first_index_page->first_xref = first_index_page->last_xref = NULL;
518 first_index_page->first_rect = first_index_page->last_rect = NULL;
519
520 /* And don't forget the as-yet-uncreated index. */
521 sprintf(buf, "%d", ++pagenum);
522 first_index_page->number = ufroma_dup(buf);
523 }
524 }
525
526 /*
527 * Now we're ready to actually lay out the pages. We do this by
528 * looping over _paragraphs_, since we may need to track cross-
529 * references between lines and even across pages.
530 */
531 for (pdata = firstpara; pdata; pdata = pdata->next)
532 render_para(pdata, conf, keywords, idx,
533 &index_placeholder_para, first_index_page);
534
535 /*
536 * Now we've laid out the main body pages, we should have
537 * acquired a full set of page numbers for the index.
538 */
539 if (has_index) {
540 int i;
541 indexentry *entry;
542 word *index_title;
543 para_data *firstidx, *lastidx;
544 line_data *firstidxline, *lastidxline, *ldata;
545 page_data *ipages, *ipages2, *page;
546
547 /*
548 * Create a set of paragraphs for the index.
549 */
550 index_title = fake_word(L"Index");
551
552 firstidx = make_para_data(para_UnnumberedChapter, 0, 0, 0,
553 NULL, NULL, index_title, conf);
554 lastidx = firstidx;
555 lastidx->next = NULL;
556 firstidxline = firstidx->first;
557 lastidxline = lastidx->last;
558 for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) {
559 paper_idx *pi = (paper_idx *)entry->backend_data;
560 para_data *text, *pages;
561
562 if (!pi->words)
563 continue;
564
565 text = make_para_data(para_Normal, 0, 0,
566 conf->base_width - conf->index_colwidth,
567 NULL, NULL, entry->text, conf);
568
569 pages = make_para_data(para_Normal, 0, 0,
570 conf->base_width - conf->index_colwidth,
571 NULL, NULL, pi->words, conf);
572
573 text->justification = LEFT;
574 pages->justification = RIGHT;
575 text->last->space_after = pages->first->space_before =
576 conf->base_leading / 2;
577
578 pages->last->space_after = text->first->space_before =
579 conf->base_leading;
580
581 assert(text->first);
582 assert(pages->first);
583 assert(lastidxline);
584 assert(lastidx);
585
586 /*
587 * If feasible, fold the two halves of the index entry
588 * together.
589 */
590 if (text->last->real_shortfall + pages->first->real_shortfall >
591 conf->index_colwidth + conf->index_minsep) {
592 text->last->space_after = -1;
593 pages->first->space_before = -pages->first->line_height+1;
594 }
595
596 lastidx->next = text;
597 text->next = pages;
598 pages->next = NULL;
599 lastidx = pages;
600
601 /*
602 * Link all index line structures together into
603 * a big list.
604 */
605 text->last->next = pages->first;
606 pages->first->prev = text->last;
607
608 lastidxline->next = text->first;
609 text->first->prev = lastidxline;
610
611 lastidxline = pages->last;
612
613 /*
614 * Breaking an index entry anywhere is so bad that I
615 * think I'm going to forbid it totally.
616 */
617 for (ldata = text->first; ldata && ldata->next;
618 ldata = ldata->next) {
619 ldata->next->space_before += ldata->space_after + 1;
620 ldata->space_after = -1;
621 }
622 }
623
624 /*
625 * Now break the index into pages.
626 */
627 ipages = page_breaks(firstidxline, firstidxline, conf->page_height,
628 0, 0);
629 ipages2 = page_breaks(firstidxline->next, lastidxline,
630 conf->page_height,
631 conf->index_cols,
632 firstidxline->space_before +
633 firstidxline->line_height +
634 firstidxline->space_after);
635
636 /*
637 * This will have put each _column_ of the index on a
638 * separate page, which isn't what we want. Fold the pages
639 * back together.
640 */
641 page = ipages2;
642 while (page) {
643 int i;
644
645 for (i = 1; i < conf->index_cols; i++)
646 if (page->next) {
647 page_data *tpage;
648
649 fold_into_page(page, page->next,
650 i * (conf->index_colwidth +
651 conf->index_gutter));
652 tpage = page->next;
653 page->next = page->next->next;
654 if (page->next)
655 page->next->prev = page;
656 sfree(tpage);
657 }
658
659 page = page->next;
660 }
661 /* Also fold the heading on to the same page as the index items. */
662 fold_into_page(ipages, ipages2, 0);
663 ipages->next = ipages2->next;
664 if (ipages->next)
665 ipages->next->prev = ipages;
666 sfree(ipages2);
667 fold_into_page(first_index_page, ipages, 0);
668 first_index_page->next = ipages->next;
669 if (first_index_page->next)
670 first_index_page->next->prev = first_index_page;
671 sfree(ipages);
672 ipages = first_index_page;
673
674 /*
675 * Number the index pages, except the already-numbered
676 * first one.
677 */
678 for (page = ipages->next; page; page = page->next) {
679 char buf[40];
680 sprintf(buf, "%d", ++pagenum);
681 page->number = ufroma_dup(buf);
682 }
683
684 /*
685 * Render the index pages.
686 */
687 for (pdata = firstidx; pdata; pdata = pdata->next)
688 render_para(pdata, conf, keywords, idx,
689 &index_placeholder_para, first_index_page);
690
691 /*
692 * Link the index page list on to the end of the main page
693 * list.
694 */
695 if (!pages)
696 pages = ipages;
697 else {
698 for (page = pages; page->next; page = page->next);
699 page->next = ipages;
700 }
701
702 /*
703 * Same with the paragraph list, which will cause the index
704 * to be mentioned in the document outline.
705 */
706 if (!firstpara)
707 firstpara = firstidx;
708 else
709 lastpara->next = firstidx;
710 lastpara = lastidx;
711 }
712
713 /*
714 * Draw the headers and footers.
715 *
716 * FIXME: this should be fully configurable, but for the moment
717 * I'm just going to put in page numbers in the centre of a
718 * footer and leave it at that.
719 */
720 {
721 page_data *page;
722
723 for (page = pages; page; page = page->next) {
724 int width;
725
726 width = conf->pagenum_fontsize *
727 string_width(conf->tr, page->number, NULL);
728
729 render_string(page, conf->tr, conf->pagenum_fontsize,
730 conf->left_margin + (conf->base_width - width)/2,
731 conf->bottom_margin - conf->footer_distance,
732 page->number);
733 }
734 }
735
736 /*
737 * Start putting together the overall document structure we're
738 * going to return.
739 */
740 doc = mknew(document);
741 doc->fonts = fontlist;
742 doc->pages = pages;
743 doc->paper_width = conf->paper_width;
744 doc->paper_height = conf->paper_height;
745
746 /*
747 * Collect the section heading paragraphs into a document
748 * outline. This is slightly fiddly because the Title paragraph
749 * isn't required to be at the start, although all the others
750 * must be in order.
751 */
752 {
753 int osize = 20;
754
755 doc->outline_elements = mknewa(outline_element, osize);
756 doc->n_outline_elements = 0;
757
758 /* First find the title. */
759 for (pdata = firstpara; pdata; pdata = pdata->next) {
760 if (pdata->outline_level == 0) {
761 doc->outline_elements[0].level = 0;
762 doc->outline_elements[0].pdata = pdata;
763 doc->n_outline_elements++;
764 break;
765 }
766 }
767
768 /* Then collect the rest. */
769 for (pdata = firstpara; pdata; pdata = pdata->next) {
770 if (pdata->outline_level > 0) {
771 if (doc->n_outline_elements >= osize) {
772 osize += 20;
773 doc->outline_elements =
774 resize(doc->outline_elements, osize);
775 }
776
777 doc->outline_elements[doc->n_outline_elements].level =
778 pdata->outline_level;
779 doc->outline_elements[doc->n_outline_elements].pdata = pdata;
780 doc->n_outline_elements++;
781 }
782 }
783 }
784
785 sfree(conf);
786
787 return doc;
788 }
789
790 static para_data *make_para_data(int ptype, int paux, int indent, int rmargin,
791 word *pkwtext, word *pkwtext2, word *pwords,
792 paper_conf *conf)
793 {
794 para_data *pdata;
795 line_data *ldata;
796 int extra_indent, firstline_indent, aux_indent;
797 word *aux, *aux2;
798
799 pdata = mknew(para_data);
800 pdata->outline_level = -1;
801 pdata->outline_title = NULL;
802 pdata->rect_type = RECT_NONE;
803 pdata->contents_entry = NULL;
804 pdata->justification = JUST;
805
806 /*
807 * Choose fonts for this paragraph.
808 *
809 * FIXME: All of this ought to be completely
810 * user-configurable.
811 */
812 switch (ptype) {
813 case para_Title:
814 pdata->fonts[FONT_NORMAL] = conf->hr;
815 pdata->sizes[FONT_NORMAL] = 24;
816 pdata->fonts[FONT_EMPH] = conf->hi;
817 pdata->sizes[FONT_EMPH] = 24;
818 pdata->fonts[FONT_CODE] = conf->cb;
819 pdata->sizes[FONT_CODE] = 24;
820 pdata->outline_level = 0;
821 break;
822
823 case para_Chapter:
824 case para_Appendix:
825 case para_UnnumberedChapter:
826 pdata->fonts[FONT_NORMAL] = conf->hr;
827 pdata->sizes[FONT_NORMAL] = 20;
828 pdata->fonts[FONT_EMPH] = conf->hi;
829 pdata->sizes[FONT_EMPH] = 20;
830 pdata->fonts[FONT_CODE] = conf->cb;
831 pdata->sizes[FONT_CODE] = 20;
832 pdata->outline_level = 1;
833 break;
834
835 case para_Heading:
836 case para_Subsect:
837 pdata->fonts[FONT_NORMAL] = conf->hr;
838 pdata->fonts[FONT_EMPH] = conf->hi;
839 pdata->fonts[FONT_CODE] = conf->cb;
840 pdata->sizes[FONT_NORMAL] =
841 pdata->sizes[FONT_EMPH] =
842 pdata->sizes[FONT_CODE] =
843 (paux == 0 ? 16 : paux == 1 ? 14 : 13);
844 pdata->outline_level = 2 + paux;
845 break;
846
847 case para_Normal:
848 case para_BiblioCited:
849 case para_Bullet:
850 case para_NumberedList:
851 case para_DescribedThing:
852 case para_Description:
853 case para_Copyright:
854 pdata->fonts[FONT_NORMAL] = conf->tr;
855 pdata->sizes[FONT_NORMAL] = 12;
856 pdata->fonts[FONT_EMPH] = conf->ti;
857 pdata->sizes[FONT_EMPH] = 12;
858 pdata->fonts[FONT_CODE] = conf->cr;
859 pdata->sizes[FONT_CODE] = 12;
860 break;
861 }
862
863 /*
864 * Also select an indentation level depending on the
865 * paragraph type (list paragraphs other than
866 * para_DescribedThing need extra indent).
867 *
868 * (FIXME: Perhaps at some point we might even arrange
869 * for the user to be able to request indented first
870 * lines in paragraphs.)
871 */
872 if (ptype == para_Bullet ||
873 ptype == para_NumberedList ||
874 ptype == para_Description) {
875 extra_indent = firstline_indent = conf->indent_list;
876 } else {
877 extra_indent = firstline_indent = 0;
878 }
879
880 /*
881 * Find the auxiliary text for this paragraph.
882 */
883 aux = aux2 = NULL;
884 aux_indent = 0;
885
886 switch (ptype) {
887 case para_Chapter:
888 case para_Appendix:
889 case para_Heading:
890 case para_Subsect:
891 /*
892 * For some heading styles (FIXME: be able to
893 * configure which), the auxiliary text contains
894 * the chapter number and is arranged to be
895 * right-aligned a few points left of the primary
896 * margin. For other styles, the auxiliary text is
897 * the full chapter _name_ and takes up space
898 * within the (wrapped) chapter title, meaning that
899 * we must move the first line indent over to make
900 * space for it.
901 */
902 if (ptype == para_Heading || ptype == para_Subsect) {
903 int len;
904
905 aux = pkwtext2;
906 len = paper_width_simple(pdata, pkwtext2);
907 aux_indent = -len - conf->sect_num_left_space;
908
909 pdata->outline_title =
910 prepare_outline_title(pkwtext2, L" ", pwords);
911 } else {
912 aux = pkwtext;
913 aux2 = fake_word(L": ");
914 aux_indent = 0;
915
916 firstline_indent += paper_width_simple(pdata, aux);
917 firstline_indent += paper_width_simple(pdata, aux2);
918
919 pdata->outline_title =
920 prepare_outline_title(pkwtext, L": ", pwords);
921 }
922 break;
923
924 case para_Bullet:
925 /*
926 * Auxiliary text consisting of a bullet. (FIXME:
927 * configurable bullet.)
928 */
929 aux = fake_word(L"\x2022");
930 aux_indent = indent + conf->indent_list_bullet;
931 break;
932
933 case para_NumberedList:
934 /*
935 * Auxiliary text consisting of the number followed
936 * by a (FIXME: configurable) full stop.
937 */
938 aux = pkwtext;
939 aux2 = fake_word(L".");
940 aux_indent = indent + conf->indent_list_bullet;
941 break;
942
943 case para_BiblioCited:
944 /*
945 * Auxiliary text consisting of the bibliography
946 * reference text, and a trailing space.
947 */
948 aux = pkwtext;
949 aux2 = fake_word(L" ");
950 aux_indent = indent;
951 firstline_indent += paper_width_simple(pdata, aux);
952 firstline_indent += paper_width_simple(pdata, aux2);
953 break;
954 }
955
956 if (pdata->outline_level >= 0 && !pdata->outline_title) {
957 pdata->outline_title =
958 prepare_outline_title(NULL, NULL, pwords);
959 }
960
961 wrap_paragraph(pdata, pwords, conf->base_width - rmargin,
962 indent + firstline_indent,
963 indent + extra_indent);
964
965 pdata->first->aux_text = aux;
966 pdata->first->aux_text_2 = aux2;
967 pdata->first->aux_left_indent = aux_indent;
968
969 /*
970 * Line breaking penalties.
971 */
972 switch (ptype) {
973 case para_Chapter:
974 case para_Appendix:
975 case para_Heading:
976 case para_Subsect:
977 case para_UnnumberedChapter:
978 /*
979 * Fixed and large penalty for breaking straight
980 * after a heading; corresponding bonus for
981 * breaking straight before.
982 */
983 pdata->first->penalty_before = -500000;
984 pdata->last->penalty_after = 500000;
985 for (ldata = pdata->first; ldata; ldata = ldata->next)
986 ldata->penalty_after = 500000;
987 break;
988
989 case para_DescribedThing:
990 /*
991 * This is treated a bit like a small heading:
992 * there's a penalty for breaking after it (i.e.
993 * between it and its description), and a bonus for
994 * breaking before it (actually _between_ list
995 * items).
996 */
997 pdata->first->penalty_before = -200000;
998 pdata->last->penalty_after = 200000;
999 break;
1000
1001 default:
1002 /*
1003 * Most paragraph types: widow/orphan control by
1004 * discouraging breaking one line from the end of
1005 * any paragraph.
1006 */
1007 if (pdata->first != pdata->last) {
1008 pdata->first->penalty_after = 100000;
1009 pdata->last->penalty_before = 100000;
1010 }
1011 break;
1012 }
1013
1014 standard_line_spacing(pdata, conf);
1015
1016 /*
1017 * Some kinds of section heading require a page break before
1018 * them and an underline after.
1019 */
1020 if (ptype == para_Title ||
1021 ptype == para_Chapter ||
1022 ptype == para_Appendix ||
1023 ptype == para_UnnumberedChapter) {
1024 pdata->first->page_break = TRUE;
1025 pdata->first->space_before = conf->chapter_top_space;
1026 pdata->last->space_after +=
1027 (conf->chapter_underline_depth +
1028 conf->chapter_underline_thickness);
1029 pdata->rect_type = RECT_CHAPTER_UNDERLINE;
1030 }
1031
1032 return pdata;
1033 }
1034
1035 static void standard_line_spacing(para_data *pdata, paper_conf *conf)
1036 {
1037 line_data *ldata;
1038
1039 /*
1040 * Set the line spacing for each line in this paragraph.
1041 */
1042 for (ldata = pdata->first; ldata; ldata = ldata->next) {
1043 if (ldata == pdata->first)
1044 ldata->space_before = conf->base_para_spacing / 2;
1045 else
1046 ldata->space_before = conf->base_leading / 2;
1047 if (ldata == pdata->last)
1048 ldata->space_after = conf->base_para_spacing / 2;
1049 else
1050 ldata->space_after = conf->base_leading / 2;
1051 ldata->page_break = FALSE;
1052 }
1053 }
1054
1055 static font_encoding *new_font_encoding(font_data *font)
1056 {
1057 font_encoding *fe;
1058 int i;
1059
1060 fe = mknew(font_encoding);
1061 fe->next = NULL;
1062
1063 if (font->list->tail)
1064 font->list->tail->next = fe;
1065 else
1066 font->list->head = fe;
1067 font->list->tail = fe;
1068
1069 fe->font = font;
1070 fe->free_pos = 0x21;
1071
1072 for (i = 0; i < 256; i++) {
1073 fe->vector[i] = NULL;
1074 fe->indices[i] = -1;
1075 fe->to_unicode[i] = 0xFFFF;
1076 }
1077
1078 return fe;
1079 }
1080
1081 static font_data *make_std_font(font_list *fontlist, char const *name)
1082 {
1083 const int *widths;
1084 int nglyphs;
1085 font_data *f;
1086 font_encoding *fe;
1087 int i;
1088
1089 widths = ps_std_font_widths(name);
1090 if (!widths)
1091 return NULL;
1092
1093 for (nglyphs = 0; ps_std_glyphs[nglyphs] != NULL; nglyphs++);
1094
1095 f = mknew(font_data);
1096
1097 f->list = fontlist;
1098 f->name = name;
1099 f->nglyphs = nglyphs;
1100 f->glyphs = ps_std_glyphs;
1101 f->widths = widths;
1102 f->subfont_map = mknewa(subfont_map_entry, nglyphs);
1103
1104 /*
1105 * Our first subfont will contain all of US-ASCII. This isn't
1106 * really necessary - we could just create custom subfonts
1107 * precisely as the whim of render_string dictated - but
1108 * instinct suggests that it might be nice to have the text in
1109 * the output files look _marginally_ recognisable.
1110 */
1111 fe = new_font_encoding(f);
1112 fe->free_pos = 0xA1; /* only the top half is free */
1113 f->latest_subfont = fe;
1114
1115 for (i = 0; i < (int)lenof(f->bmp); i++)
1116 f->bmp[i] = 0xFFFF;
1117
1118 for (i = 0; i < nglyphs; i++) {
1119 wchar_t ucs;
1120 ucs = ps_glyph_to_unicode(f->glyphs[i]);
1121 assert(ucs != 0xFFFF);
1122 f->bmp[ucs] = i;
1123 if (ucs >= 0x20 && ucs <= 0x7E) {
1124 fe->vector[ucs] = f->glyphs[i];
1125 fe->indices[ucs] = i;
1126 fe->to_unicode[ucs] = ucs;
1127 f->subfont_map[i].subfont = fe;
1128 f->subfont_map[i].position = ucs;
1129 } else {
1130 /*
1131 * This character is not yet assigned to a subfont.
1132 */
1133 f->subfont_map[i].subfont = NULL;
1134 f->subfont_map[i].position = 0;
1135 }
1136 }
1137
1138 return f;
1139 }
1140
1141 static int string_width(font_data *font, wchar_t const *string, int *errs)
1142 {
1143 int width = 0;
1144
1145 if (errs)
1146 *errs = 0;
1147
1148 for (; *string; string++) {
1149 int index;
1150
1151 index = font->bmp[(unsigned short)*string];
1152 if (index == 0xFFFF) {
1153 if (errs)
1154 *errs = 1;
1155 } else {
1156 width += font->widths[index];
1157 }
1158 }
1159
1160 return width;
1161 }
1162
1163 static int paper_width_internal(void *vctx, word *word, int *nspaces);
1164
1165 struct paper_width_ctx {
1166 int minspacewidth;
1167 para_data *pdata;
1168 };
1169
1170 static int paper_width_list(void *vctx, word *text, word *end, int *nspaces) {
1171 int w = 0;
1172 while (text && text != end) {
1173 w += paper_width_internal(vctx, text, nspaces);
1174 text = text->next;
1175 }
1176 return w;
1177 }
1178
1179 static int paper_width_internal(void *vctx, word *word, int *nspaces)
1180 {
1181 struct paper_width_ctx *ctx = (struct paper_width_ctx *)vctx;
1182 int style, type, findex, width, errs;
1183 wchar_t *str;
1184
1185 switch (word->type) {
1186 case word_HyperLink:
1187 case word_HyperEnd:
1188 case word_UpperXref:
1189 case word_LowerXref:
1190 case word_PageXref:
1191 case word_XrefEnd:
1192 case word_IndexRef:
1193 return 0;
1194 }
1195
1196 style = towordstyle(word->type);
1197 type = removeattr(word->type);
1198
1199 findex = (style == word_Normal ? FONT_NORMAL :
1200 style == word_Emph ? FONT_EMPH :
1201 FONT_CODE);
1202
1203 if (type == word_Normal) {
1204 str = word->text;
1205 } else if (type == word_WhiteSpace) {
1206 if (findex != FONT_CODE) {
1207 if (nspaces)
1208 (*nspaces)++;
1209 return ctx->minspacewidth;
1210 } else
1211 str = L" ";
1212 } else /* if (type == word_Quote) */ {
1213 if (word->aux == quote_Open)
1214 str = L"\x2018"; /* FIXME: configurability! */
1215 else
1216 str = L"\x2019"; /* FIXME: configurability! */
1217 }
1218
1219 width = string_width(ctx->pdata->fonts[findex], str, &errs);
1220
1221 if (errs && word->alt)
1222 return paper_width_list(vctx, word->alt, NULL, nspaces);
1223 else
1224 return ctx->pdata->sizes[findex] * width;
1225 }
1226
1227 static int paper_width(void *vctx, word *word)
1228 {
1229 return paper_width_internal(vctx, word, NULL);
1230 }
1231
1232 static int paper_width_simple(para_data *pdata, word *text)
1233 {
1234 struct paper_width_ctx ctx;
1235
1236 ctx.pdata = pdata;
1237 ctx.minspacewidth =
1238 (pdata->sizes[FONT_NORMAL] *
1239 string_width(pdata->fonts[FONT_NORMAL], L" ", NULL));
1240
1241 return paper_width_list(&ctx, text, NULL, NULL);
1242 }
1243
1244 static void wrap_paragraph(para_data *pdata, word *words,
1245 int w, int i1, int i2)
1246 {
1247 wrappedline *wrapping, *p;
1248 int spacewidth;
1249 struct paper_width_ctx ctx;
1250 int line_height;
1251
1252 /*
1253 * We're going to need to store the line height in every line
1254 * structure we generate.
1255 */
1256 {
1257 int i;
1258 line_height = 0;
1259 for (i = 0; i < NFONTS; i++)
1260 if (line_height < pdata->sizes[i])
1261 line_height = pdata->sizes[i];
1262 line_height *= 4096;
1263 }
1264
1265 spacewidth = (pdata->sizes[FONT_NORMAL] *
1266 string_width(pdata->fonts[FONT_NORMAL], L" ", NULL));
1267 if (spacewidth == 0) {
1268 /*
1269 * A font without a space?! Disturbing. I hope this never
1270 * comes up, but I'll make a random guess anyway and set my
1271 * space width to half the point size.
1272 */
1273 spacewidth = pdata->sizes[FONT_NORMAL] * 4096 / 2;
1274 }
1275
1276 /*
1277 * I'm going to set the _minimum_ space width to 3/5 of the
1278 * standard one, and use the standard one as the optimum.
1279 */
1280 ctx.minspacewidth = spacewidth * 3 / 5;
1281 ctx.pdata = pdata;
1282
1283 wrapping = wrap_para(words, w - i1, w - i2, paper_width, &ctx, spacewidth);
1284
1285 /*
1286 * Having done the wrapping, we now concoct a set of line_data
1287 * structures.
1288 */
1289 pdata->first = pdata->last = NULL;
1290
1291 for (p = wrapping; p; p = p->next) {
1292 line_data *ldata;
1293 word *wd;
1294 int len, wid, spaces;
1295
1296 ldata = mknew(line_data);
1297
1298 ldata->pdata = pdata;
1299 ldata->first = p->begin;
1300 ldata->end = p->end;
1301 ldata->line_height = line_height;
1302
1303 ldata->xpos = (p == wrapping ? i1 : i2);
1304
1305 if (pdata->last) {
1306 pdata->last->next = ldata;
1307 ldata->prev = pdata->last;
1308 } else {
1309 pdata->first = ldata;
1310 ldata->prev = NULL;
1311 }
1312 ldata->next = NULL;
1313 pdata->last = ldata;
1314
1315 spaces = 0;
1316 len = paper_width_list(&ctx, ldata->first, ldata->end, &spaces);
1317 wid = (p == wrapping ? w - i1 : w - i2);
1318 wd = ldata->first;
1319
1320 ldata->hshortfall = wid - len;
1321 ldata->nspaces = spaces;
1322 /*
1323 * This tells us how much the space width needs to
1324 * change from _min_spacewidth. But we want to store
1325 * its difference from the _natural_ space width, to
1326 * make the text rendering easier.
1327 */
1328 ldata->hshortfall += ctx.minspacewidth * spaces;
1329 ldata->hshortfall -= spacewidth * spaces;
1330 ldata->real_shortfall = ldata->hshortfall;
1331 /*
1332 * Special case: on the last line of a paragraph, we
1333 * never stretch spaces.
1334 */
1335 if (ldata->hshortfall > 0 && !p->next)
1336 ldata->hshortfall = 0;
1337
1338 ldata->aux_text = NULL;
1339 ldata->aux_text_2 = NULL;
1340 ldata->aux_left_indent = 0;
1341 ldata->penalty_before = ldata->penalty_after = 0;
1342 }
1343
1344 }
1345
1346 static page_data *page_breaks(line_data *first, line_data *last,
1347 int page_height, int ncols, int headspace)
1348 {
1349 line_data *l, *m;
1350 page_data *ph, *pt;
1351 int n, n1, this_height;
1352
1353 /*
1354 * Page breaking is done by a close analogue of the optimal
1355 * paragraph wrapping algorithm used by wrap_para(). We work
1356 * backwards from the end of the document line by line; for
1357 * each line, we contemplate every possible number of lines we
1358 * could put on a page starting with that line, determine a
1359 * cost function for each one, add it to the pre-computed cost
1360 * function for optimally page-breaking everything after that
1361 * page, and pick the best option.
1362 *
1363 * This is made slightly more complex by the fact that we have
1364 * a multi-column index with a heading at the top of the
1365 * _first_ page, meaning that the first _ncols_ pages must have
1366 * a different length. Hence, we must do the wrapping ncols+1
1367 * times over, hypothetically trying to put every subsequence
1368 * on every possible page.
1369 *
1370 * Since my line_data structures are only used for this
1371 * purpose, I might as well just store the algorithm data
1372 * directly in them.
1373 */
1374
1375 for (l = last; l; l = l->prev) {
1376 l->bestcost = mknewa(int, ncols+1);
1377 l->vshortfall = mknewa(int, ncols+1);
1378 l->text = mknewa(int, ncols+1);
1379 l->space = mknewa(int, ncols+1);
1380 l->page_last = mknewa(line_data *, ncols+1);
1381
1382 for (n = 0; n <= ncols; n++) {
1383 int minheight, text = 0, space = 0;
1384 int cost;
1385
1386 n1 = (n < ncols ? n+1 : ncols);
1387 if (n < ncols)
1388 this_height = page_height - headspace;
1389 else
1390 this_height = page_height;
1391
1392 l->bestcost[n] = -1;
1393 for (m = l; m; m = m->next) {
1394 if (m != l && m->page_break)
1395 break; /* we've gone as far as we can */
1396
1397 if (m != l) {
1398 if (m->prev->space_after > 0)
1399 space += m->prev->space_after;
1400 else
1401 text += m->prev->space_after;
1402 }
1403 if (m != l || m->page_break) {
1404 if (m->space_before > 0)
1405 space += m->space_before;
1406 else
1407 text += m->space_before;
1408 }
1409 text += m->line_height;
1410 minheight = text + space;
1411
1412 if (m != l && minheight > this_height)
1413 break;
1414
1415 /*
1416 * If the space after this paragraph is _negative_
1417 * (which means the next line is folded on to this
1418 * one, which happens in the index), we absolutely
1419 * cannot break here.
1420 */
1421 if (m->space_after >= 0) {
1422
1423 /*
1424 * Compute the cost of this arrangement, as the
1425 * square of the amount of wasted space on the
1426 * page. Exception: if this is the last page
1427 * before a mandatory break or the document
1428 * end, we don't penalise a large blank area.
1429 */
1430 if (m != last && m->next && !m->next->page_break)
1431 {
1432 int x = this_height - minheight;
1433 int xf;
1434
1435 xf = x & 0xFF;
1436 x >>= 8;
1437
1438 cost = x*x;
1439 cost += (x * xf) >> 8;
1440 } else
1441 cost = 0;
1442
1443 if (m != last && m->next && !m->next->page_break) {
1444 cost += m->penalty_after;
1445 cost += m->next->penalty_before;
1446 }
1447
1448 if (m != last && m->next && !m->next->page_break)
1449 cost += m->next->bestcost[n1];
1450 if (l->bestcost[n] == -1 || l->bestcost[n] > cost) {
1451 /*
1452 * This is the best option yet for this
1453 * starting point.
1454 */
1455 l->bestcost[n] = cost;
1456 if (m != last && m->next && !m->next->page_break)
1457 l->vshortfall[n] = this_height - minheight;
1458 else
1459 l->vshortfall[n] = 0;
1460 l->text[n] = text;
1461 l->space[n] = space;
1462 l->page_last[n] = m;
1463 }
1464 }
1465
1466 if (m == last)
1467 break;
1468 }
1469 }
1470 }
1471
1472 /*
1473 * Now go through the line list forwards and assemble the
1474 * actual pages.
1475 */
1476 ph = pt = NULL;
1477
1478 l = first;
1479 n = 0;
1480 while (l) {
1481 page_data *page;
1482 int text, space, head;
1483
1484 page = mknew(page_data);
1485 page->next = NULL;
1486 page->prev = pt;
1487 if (pt)
1488 pt->next = page;
1489 else
1490 ph = page;
1491 pt = page;
1492
1493 page->first_line = l;
1494 page->last_line = l->page_last[n];
1495
1496 page->first_text = page->last_text = NULL;
1497 page->first_xref = page->last_xref = NULL;
1498 page->first_rect = page->last_rect = NULL;
1499
1500 /*
1501 * Now assign a y-coordinate to each line on the page.
1502 */
1503 text = space = 0;
1504 head = (n < ncols ? headspace : 0);
1505 for (l = page->first_line; l; l = l->next) {
1506 if (l != page->first_line) {
1507 if (l->prev->space_after > 0)
1508 space += l->prev->space_after;
1509 else
1510 text += l->prev->space_after;
1511 }
1512 if (l != page->first_line || l->page_break) {
1513 if (l->space_before > 0)
1514 space += l->space_before;
1515 else
1516 text += l->space_before;
1517 }
1518 text += l->line_height;
1519
1520 l->page = page;
1521 l->ypos = text + space + head +
1522 space * (float)page->first_line->vshortfall[n] /
1523 page->first_line->space[n];
1524
1525 if (l == page->last_line)
1526 break;
1527 }
1528
1529 l = page->last_line;
1530 if (l == last)
1531 break;
1532 l = l->next;
1533
1534 n = (n < ncols ? n+1 : ncols);
1535 }
1536
1537 return ph;
1538 }
1539
1540 static void add_rect_to_page(page_data *page, int x, int y, int w, int h)
1541 {
1542 rect *r = mknew(rect);
1543
1544 r->next = NULL;
1545 if (page->last_rect)
1546 page->last_rect->next = r;
1547 else
1548 page->first_rect = r;
1549 page->last_rect = r;
1550
1551 r->x = x;
1552 r->y = y;
1553 r->w = w;
1554 r->h = h;
1555 }
1556
1557 static void add_string_to_page(page_data *page, int x, int y,
1558 font_encoding *fe, int size, char *text,
1559 int width)
1560 {
1561 text_fragment *frag;
1562
1563 frag = mknew(text_fragment);
1564 frag->next = NULL;
1565
1566 if (page->last_text)
1567 page->last_text->next = frag;
1568 else
1569 page->first_text = frag;
1570 page->last_text = frag;
1571
1572 frag->x = x;
1573 frag->y = y;
1574 frag->fe = fe;
1575 frag->fontsize = size;
1576 frag->text = dupstr(text);
1577 frag->width = width;
1578 }
1579
1580 /*
1581 * Returns the updated x coordinate.
1582 */
1583 static int render_string(page_data *page, font_data *font, int fontsize,
1584 int x, int y, wchar_t *str)
1585 {
1586 char *text;
1587 int textpos, textwid, glyph;
1588 font_encoding *subfont = NULL, *sf;
1589
1590 text = mknewa(char, 1 + ustrlen(str));
1591 textpos = textwid = 0;
1592
1593 while (*str) {
1594 glyph = font->bmp[*str];
1595
1596 if (glyph == 0xFFFF) {
1597 str++;
1598 continue; /* nothing more we can do here */
1599 }
1600
1601 /*
1602 * Find which subfont this character is going in.
1603 */
1604 sf = font->subfont_map[glyph].subfont;
1605
1606 if (!sf) {
1607 int c;
1608
1609 /*
1610 * This character is not yet in a subfont. Assign one.
1611 */
1612 if (font->latest_subfont->free_pos >= 0x100)
1613 font->latest_subfont = new_font_encoding(font);
1614
1615 c = font->latest_subfont->free_pos++;
1616 if (font->latest_subfont->free_pos == 0x7F)
1617 font->latest_subfont->free_pos = 0xA1;
1618
1619 font->subfont_map[glyph].subfont = font->latest_subfont;
1620 font->subfont_map[glyph].position = c;
1621 font->latest_subfont->vector[c] = font->glyphs[glyph];
1622 font->latest_subfont->indices[c] = glyph;
1623 font->latest_subfont->to_unicode[c] = *str;
1624
1625 sf = font->latest_subfont;
1626 }
1627
1628 if (!subfont || sf != subfont) {
1629 if (subfont) {
1630 text[textpos] = '\0';
1631 add_string_to_page(page, x, y, subfont, fontsize, text,
1632 textwid);
1633 x += textwid;
1634 } else {
1635 assert(textpos == 0);
1636 }
1637 textpos = 0;
1638 subfont = sf;
1639 }
1640
1641 text[textpos++] = font->subfont_map[glyph].position;
1642 textwid += font->widths[glyph] * fontsize;
1643
1644 str++;
1645 }
1646
1647 if (textpos > 0) {
1648 text[textpos] = '\0';
1649 add_string_to_page(page, x, y, subfont, fontsize, text, textwid);
1650 x += textwid;
1651 }
1652
1653 return x;
1654 }
1655
1656 /*
1657 * Returns the updated x coordinate.
1658 */
1659 static int render_text(page_data *page, para_data *pdata, line_data *ldata,
1660 int x, int y, word *text, word *text_end, xref **xr,
1661 int shortfall, int nspaces, int *nspace,
1662 keywordlist *keywords, indexdata *idx)
1663 {
1664 while (text && text != text_end) {
1665 int style, type, findex, errs;
1666 wchar_t *str;
1667 xref_dest dest;
1668
1669 switch (text->type) {
1670 /*
1671 * Start a cross-reference.
1672 */
1673 case word_HyperLink:
1674 case word_UpperXref:
1675 case word_LowerXref:
1676 case word_PageXref:
1677
1678 if (text->type == word_HyperLink) {
1679 dest.type = URL;
1680 dest.url = utoa_dup(text->text);
1681 dest.page = NULL;
1682 } else if (text->type == word_PageXref) {
1683 dest.type = PAGE;
1684 dest.url = NULL;
1685 dest.page = (page_data *)text->private_data;
1686 } else {
1687 keyword *kwl = kw_lookup(keywords, text->text);
1688 para_data *pdata;
1689
1690 if (kwl) {
1691 assert(kwl->para->private_data);
1692 pdata = (para_data *) kwl->para->private_data;
1693 dest.type = PAGE;
1694 dest.page = pdata->first->page;
1695 dest.url = NULL;
1696 } else {
1697 /*
1698 * Shouldn't happen, but *shrug*
1699 */
1700 dest.type = NONE;
1701 dest.page = NULL;
1702 dest.url = NULL;
1703 }
1704 }
1705 if (dest.type != NONE) {
1706 *xr = mknew(xref);
1707 (*xr)->dest = dest; /* structure copy */
1708 if (page->last_xref)
1709 page->last_xref->next = *xr;
1710 else
1711 page->first_xref = *xr;
1712 page->last_xref = *xr;
1713 (*xr)->next = NULL;
1714
1715 /*
1716 * FIXME: Ideally we should have, and use, some
1717 * vertical font metric information here so that
1718 * our cross-ref rectangle can take account of
1719 * descenders and the font's cap height. This will
1720 * do for the moment, but it isn't ideal.
1721 */
1722 (*xr)->lx = (*xr)->rx = x;
1723 (*xr)->by = y;
1724 (*xr)->ty = y + ldata->line_height;
1725 }
1726 goto nextword;
1727
1728 /*
1729 * Finish extending a cross-reference box.
1730 */
1731 case word_HyperEnd:
1732 case word_XrefEnd:
1733 *xr = NULL;
1734 goto nextword;
1735
1736 /*
1737 * Add the current page number to the list of pages
1738 * referenced by an index entry.
1739 */
1740 case word_IndexRef:
1741 /*
1742 * We don't create index references in contents entries.
1743 */
1744 if (!pdata->contents_entry) {
1745 indextag *tag;
1746 int i;
1747
1748 tag = index_findtag(idx, text->text);
1749 if (!tag)
1750 goto nextword;
1751
1752 for (i = 0; i < tag->nrefs; i++) {
1753 indexentry *entry = tag->refs[i];
1754 paper_idx *pi = (paper_idx *)entry->backend_data;
1755
1756 /*
1757 * If the same index term is indexed twice
1758 * within the same section, we only want to
1759 * mention it once in the index.
1760 */
1761 if (pi->lastpage != page) {
1762 word **wp;
1763
1764 if (pi->lastword) {
1765 pi->lastword = pi->lastword->next =
1766 fake_word(L",");
1767 pi->lastword = pi->lastword->next =
1768 fake_space_word();
1769 wp = &pi->lastword->next;
1770 } else
1771 wp = &pi->words;
1772
1773 pi->lastword = *wp =
1774 fake_page_ref(page);
1775 pi->lastword = pi->lastword->next =
1776 fake_word(page->number);
1777 pi->lastword = pi->lastword->next =
1778 fake_end_ref();
1779 }
1780
1781 pi->lastpage = page;
1782 }
1783 }
1784 goto nextword;
1785 }
1786
1787 style = towordstyle(text->type);
1788 type = removeattr(text->type);
1789
1790 findex = (style == word_Normal ? FONT_NORMAL :
1791 style == word_Emph ? FONT_EMPH :
1792 FONT_CODE);
1793
1794 if (type == word_Normal) {
1795 str = text->text;
1796 } else if (type == word_WhiteSpace) {
1797 x += pdata->sizes[findex] *
1798 string_width(pdata->fonts[findex], L" ", NULL);
1799 if (nspaces && findex != FONT_CODE) {
1800 x += (*nspace+1) * shortfall / nspaces;
1801 x -= *nspace * shortfall / nspaces;
1802 (*nspace)++;
1803 }
1804 goto nextword;
1805 } else /* if (type == word_Quote) */ {
1806 if (text->aux == quote_Open)
1807 str = L"\x2018"; /* FIXME: configurability! */
1808 else
1809 str = L"\x2019"; /* FIXME: configurability! */
1810 }
1811
1812 (void) string_width(pdata->fonts[findex], str, &errs);
1813
1814 if (errs && text->alt)
1815 x = render_text(page, pdata, ldata, x, y, text->alt, NULL,
1816 xr, shortfall, nspaces, nspace, keywords, idx);
1817 else
1818 x = render_string(page, pdata->fonts[findex],
1819 pdata->sizes[findex], x, y, str);
1820
1821 if (*xr)
1822 (*xr)->rx = x;
1823
1824 nextword:
1825 text = text->next;
1826 }
1827
1828 return x;
1829 }
1830
1831 /*
1832 * Returns the last x position used on the line.
1833 */
1834 static int render_line(line_data *ldata, int left_x, int top_y,
1835 xref_dest *dest, keywordlist *keywords, indexdata *idx)
1836 {
1837 int nspace;
1838 xref *xr;
1839 int ret = 0;
1840
1841 if (ldata->aux_text) {
1842 int x;
1843 xr = NULL;
1844 nspace = 0;
1845 x = render_text(ldata->page, ldata->pdata, ldata,
1846 left_x + ldata->aux_left_indent,
1847 top_y - ldata->ypos,
1848 ldata->aux_text, NULL, &xr, 0, 0, &nspace,
1849 keywords, idx);
1850 if (ldata->aux_text_2)
1851 render_text(ldata->page, ldata->pdata, ldata,
1852 x, top_y - ldata->ypos,
1853 ldata->aux_text_2, NULL, &xr, 0, 0, &nspace,
1854 keywords, idx);
1855 }
1856 nspace = 0;
1857
1858 if (ldata->first) {
1859 /*
1860 * There might be a cross-reference carried over from a
1861 * previous line.
1862 */
1863 if (dest->type != NONE) {
1864 xr = mknew(xref);
1865 xr->next = NULL;
1866 xr->dest = *dest; /* structure copy */
1867 if (ldata->page->last_xref)
1868 ldata->page->last_xref->next = xr;
1869 else
1870 ldata->page->first_xref = xr;
1871 ldata->page->last_xref = xr;
1872 xr->lx = xr->rx = left_x + ldata->xpos;
1873 xr->by = top_y - ldata->ypos;
1874 xr->ty = top_y - ldata->ypos + ldata->line_height;
1875 } else
1876 xr = NULL;
1877
1878 {
1879 int extra_indent, shortfall, spaces;
1880 int just = ldata->pdata->justification;
1881
1882 /*
1883 * All forms of justification become JUST when we have
1884 * to squeeze the paragraph.
1885 */
1886 if (ldata->hshortfall < 0)
1887 just = JUST;
1888
1889 switch (just) {
1890 case JUST:
1891 shortfall = ldata->hshortfall;
1892 spaces = ldata->nspaces;
1893 extra_indent = 0;
1894 break;
1895 case LEFT:
1896 shortfall = spaces = extra_indent = 0;
1897 break;
1898 case RIGHT:
1899 shortfall = spaces = 0;
1900 extra_indent = ldata->real_shortfall;
1901 break;
1902 }
1903
1904 ret = render_text(ldata->page, ldata->pdata, ldata,
1905 left_x + ldata->xpos + extra_indent,
1906 top_y - ldata->ypos, ldata->first, ldata->end,
1907 &xr, shortfall, spaces, &nspace,
1908 keywords, idx);
1909 }
1910
1911 if (xr) {
1912 /*
1913 * There's a cross-reference continued on to the next line.
1914 */
1915 *dest = xr->dest;
1916 } else
1917 dest->type = NONE;
1918 }
1919
1920 return ret;
1921 }
1922
1923 static void render_para(para_data *pdata, paper_conf *conf,
1924 keywordlist *keywords, indexdata *idx,
1925 paragraph *index_placeholder, page_data *index_page)
1926 {
1927 int last_x;
1928 xref *cxref;
1929 page_data *cxref_page;
1930 xref_dest dest;
1931 para_data *target;
1932 line_data *ldata;
1933
1934 dest.type = NONE;
1935 cxref = NULL;
1936 cxref_page = NULL;
1937
1938 for (ldata = pdata->first; ldata; ldata = ldata->next) {
1939 /*
1940 * If this is a contents entry, we expect to have a single
1941 * enormous cross-reference rectangle covering the whole
1942 * thing. (Unless, of course, it spans multiple pages.)
1943 */
1944 if (pdata->contents_entry && ldata->page != cxref_page) {
1945 cxref_page = ldata->page;
1946 cxref = mknew(xref);
1947 cxref->next = NULL;
1948 cxref->dest.type = PAGE;
1949 if (pdata->contents_entry == index_placeholder) {
1950 cxref->dest.page = index_page;
1951 } else {
1952 assert(pdata->contents_entry->private_data);
1953 target = (para_data *)pdata->contents_entry->private_data;
1954 cxref->dest.page = target->first->page;
1955 }
1956 cxref->dest.url = NULL;
1957 if (ldata->page->last_xref)
1958 ldata->page->last_xref->next = cxref;
1959 else
1960 ldata->page->first_xref = cxref;
1961 ldata->page->last_xref = cxref;
1962 cxref->lx = conf->left_margin;
1963 cxref->rx = conf->paper_width - conf->right_margin;
1964 cxref->ty = conf->paper_height - conf->top_margin
1965 - ldata->ypos + ldata->line_height;
1966 }
1967 if (pdata->contents_entry) {
1968 assert(cxref != NULL);
1969 cxref->by = conf->paper_height - conf->top_margin
1970 - ldata->ypos;
1971 }
1972
1973 last_x = render_line(ldata, conf->left_margin,
1974 conf->paper_height - conf->top_margin,
1975 &dest, keywords, idx);
1976 if (ldata == pdata->last)
1977 break;
1978 }
1979
1980 /*
1981 * If this is a contents entry, add leaders and a page
1982 * number.
1983 */
1984 if (pdata->contents_entry) {
1985 word *w;
1986 wchar_t *num;
1987 int wid;
1988 int x;
1989
1990 if (pdata->contents_entry == index_placeholder) {
1991 num = index_page->number;
1992 } else {
1993 assert(pdata->contents_entry->private_data);
1994 target = (para_data *)pdata->contents_entry->private_data;
1995 num = target->first->page->number;
1996 }
1997
1998 w = fake_word(num);
1999 wid = paper_width_simple(pdata, w);
2000 sfree(w);
2001
2002 render_string(pdata->last->page,
2003 pdata->fonts[FONT_NORMAL],
2004 pdata->sizes[FONT_NORMAL],
2005 conf->paper_width - conf->right_margin - wid,
2006 (conf->paper_height - conf->top_margin -
2007 pdata->last->ypos), num);
2008
2009 for (x = 0; x < conf->base_width; x += conf->leader_separation)
2010 if (x - conf->leader_separation > last_x - conf->left_margin &&
2011 x + conf->leader_separation < conf->base_width - wid)
2012 render_string(pdata->last->page,
2013 pdata->fonts[FONT_NORMAL],
2014 pdata->sizes[FONT_NORMAL],
2015 conf->left_margin + x,
2016 (conf->paper_height - conf->top_margin -
2017 pdata->last->ypos), L".");
2018 }
2019
2020 /*
2021 * Render any rectangle (chapter title underline or rule)
2022 * that goes with this paragraph.
2023 */
2024 switch (pdata->rect_type) {
2025 case RECT_CHAPTER_UNDERLINE:
2026 add_rect_to_page(pdata->last->page,
2027 conf->left_margin,
2028 (conf->paper_height - conf->top_margin -
2029 pdata->last->ypos -
2030 conf->chapter_underline_depth),
2031 conf->base_width,
2032 conf->chapter_underline_thickness);
2033 break;
2034 case RECT_RULE:
2035 add_rect_to_page(pdata->first->page,
2036 conf->left_margin + pdata->first->xpos,
2037 (conf->paper_height - conf->top_margin -
2038 pdata->last->ypos -
2039 pdata->last->line_height),
2040 conf->base_width - pdata->first->xpos,
2041 pdata->last->line_height);
2042 break;
2043 default: /* placate gcc */
2044 break;
2045 }
2046 }
2047
2048 static para_data *code_paragraph(int indent, word *words, paper_conf *conf)
2049 {
2050 para_data *pdata = mknew(para_data);
2051
2052 /*
2053 * For code paragraphs, I'm going to hack grievously and
2054 * pretend the three normal fonts are the three code paragraph
2055 * fonts.
2056 */
2057 pdata->fonts[FONT_NORMAL] = conf->cb;
2058 pdata->fonts[FONT_EMPH] = conf->co;
2059 pdata->fonts[FONT_CODE] = conf->cr;
2060 pdata->sizes[FONT_NORMAL] =
2061 pdata->sizes[FONT_EMPH] =
2062 pdata->sizes[FONT_CODE] = 12;
2063
2064 pdata->first = pdata->last = NULL;
2065 pdata->outline_level = -1;
2066 pdata->rect_type = RECT_NONE;
2067 pdata->contents_entry = NULL;
2068 pdata->justification = LEFT;
2069
2070 for (; words; words = words->next) {
2071 wchar_t *t, *e, *start;
2072 word *lhead = NULL, *ltail = NULL, *w;
2073 line_data *ldata;
2074 int prev = -1, curr;
2075
2076 t = words->text;
2077 if (words->next && words->next->type == word_Emph) {
2078 e = words->next->text;
2079 words = words->next;
2080 } else
2081 e = NULL;
2082
2083 start = t;
2084
2085 while (*start) {
2086 while (*t) {
2087 if (!e || !*e)
2088 curr = 0;
2089 else if (*e == L'i')
2090 curr = 1;
2091 else if (*e == L'b')
2092 curr = 2;
2093 else
2094 curr = 0;
2095
2096 if (prev < 0)
2097 prev = curr;
2098
2099 if (curr != prev)
2100 break;
2101
2102 t++;
2103 if (e && *e)
2104 e++;
2105 }
2106
2107 /*
2108 * We've isolated a maximal subsequence of the line
2109 * which has the same emphasis. Form it into a word
2110 * structure.
2111 */
2112 w = mknew(word);
2113 w->next = NULL;
2114 w->alt = NULL;
2115 w->type = (prev == 0 ? word_WeakCode :
2116 prev == 1 ? word_Emph : word_Normal);
2117 w->text = mknewa(wchar_t, t-start+1);
2118 memcpy(w->text, start, (t-start) * sizeof(wchar_t));
2119 w->text[t-start] = '\0';
2120 w->breaks = FALSE;
2121
2122 if (ltail)
2123 ltail->next = w;
2124 else
2125 lhead = w;
2126 ltail = w;
2127
2128 start = t;
2129 prev = -1;
2130 }
2131
2132 ldata = mknew(line_data);
2133
2134 ldata->pdata = pdata;
2135 ldata->first = lhead;
2136 ldata->end = NULL;
2137 ldata->line_height = conf->base_font_size * 4096;
2138
2139 ldata->xpos = indent;
2140
2141 if (pdata->last) {
2142 pdata->last->next = ldata;
2143 ldata->prev = pdata->last;
2144 } else {
2145 pdata->first = ldata;
2146 ldata->prev = NULL;
2147 }
2148 ldata->next = NULL;
2149 pdata->last = ldata;
2150
2151 ldata->hshortfall = 0;
2152 ldata->nspaces = 0;
2153 ldata->aux_text = NULL;
2154 ldata->aux_text_2 = NULL;
2155 ldata->aux_left_indent = 0;
2156 /* General opprobrium for breaking in a code paragraph. */
2157 ldata->penalty_before = ldata->penalty_after = 50000;
2158 }
2159
2160 standard_line_spacing(pdata, conf);
2161
2162 return pdata;
2163 }
2164
2165 static para_data *rule_paragraph(int indent, paper_conf *conf)
2166 {
2167 para_data *pdata = mknew(para_data);
2168 line_data *ldata;
2169
2170 ldata = mknew(line_data);
2171
2172 ldata->pdata = pdata;
2173 ldata->first = NULL;
2174 ldata->end = NULL;
2175 ldata->line_height = conf->rule_thickness;
2176
2177 ldata->xpos = indent;
2178
2179 ldata->prev = NULL;
2180 ldata->next = NULL;
2181
2182 ldata->hshortfall = 0;
2183 ldata->nspaces = 0;
2184 ldata->aux_text = NULL;
2185 ldata->aux_text_2 = NULL;
2186 ldata->aux_left_indent = 0;
2187
2188 /*
2189 * Better to break after a rule than before it
2190 */
2191 ldata->penalty_after += 100000;
2192 ldata->penalty_before += -100000;
2193
2194 pdata->first = pdata->last = ldata;
2195 pdata->outline_level = -1;
2196 pdata->rect_type = RECT_RULE;
2197 pdata->contents_entry = NULL;
2198 pdata->justification = LEFT;
2199
2200 standard_line_spacing(pdata, conf);
2201
2202 return pdata;
2203 }
2204
2205 /*
2206 * Plain-text-like formatting for outline titles.
2207 */
2208 static void paper_rdaddw(rdstring *rs, word *text) {
2209 for (; text; text = text->next) switch (text->type) {
2210 case word_HyperLink:
2211 case word_HyperEnd:
2212 case word_UpperXref:
2213 case word_LowerXref:
2214 case word_XrefEnd:
2215 case word_IndexRef:
2216 break;
2217
2218 case word_Normal:
2219 case word_Emph:
2220 case word_Code:
2221 case word_WeakCode:
2222 case word_WhiteSpace:
2223 case word_EmphSpace:
2224 case word_CodeSpace:
2225 case word_WkCodeSpace:
2226 case word_Quote:
2227 case word_EmphQuote:
2228 case word_CodeQuote:
2229 case word_WkCodeQuote:
2230 assert(text->type != word_CodeQuote &&
2231 text->type != word_WkCodeQuote);
2232 if (towordstyle(text->type) == word_Emph &&
2233 (attraux(text->aux) == attr_First ||
2234 attraux(text->aux) == attr_Only))
2235 rdadd(rs, L'_'); /* FIXME: configurability */
2236 else if (towordstyle(text->type) == word_Code &&
2237 (attraux(text->aux) == attr_First ||
2238 attraux(text->aux) == attr_Only))
2239 rdadd(rs, L'\''); /* FIXME: configurability */
2240 if (removeattr(text->type) == word_Normal) {
2241 rdadds(rs, text->text);
2242 } else if (removeattr(text->type) == word_WhiteSpace) {
2243 rdadd(rs, L' ');
2244 } else if (removeattr(text->type) == word_Quote) {
2245 rdadd(rs, L'\''); /* fixme: configurability */
2246 }
2247 if (towordstyle(text->type) == word_Emph &&
2248 (attraux(text->aux) == attr_Last ||
2249 attraux(text->aux) == attr_Only))
2250 rdadd(rs, L'_'); /* FIXME: configurability */
2251 else if (towordstyle(text->type) == word_Code &&
2252 (attraux(text->aux) == attr_Last ||
2253 attraux(text->aux) == attr_Only))
2254 rdadd(rs, L'\''); /* FIXME: configurability */
2255 break;
2256 }
2257 }
2258
2259 static wchar_t *prepare_outline_title(word *first, wchar_t *separator,
2260 word *second)
2261 {
2262 rdstring rs = {0, 0, NULL};
2263
2264 if (first)
2265 paper_rdaddw(&rs, first);
2266 if (separator)
2267 rdadds(&rs, separator);
2268 if (second)
2269 paper_rdaddw(&rs, second);
2270
2271 return rs.text;
2272 }
2273
2274 static word *fake_word(wchar_t *text)
2275 {
2276 word *ret = mknew(word);
2277 ret->next = NULL;
2278 ret->alt = NULL;
2279 ret->type = word_Normal;
2280 ret->text = ustrdup(text);
2281 ret->breaks = FALSE;
2282 ret->aux = 0;
2283 return ret;
2284 }
2285
2286 static word *fake_space_word(void)
2287 {
2288 word *ret = mknew(word);
2289 ret->next = NULL;
2290 ret->alt = NULL;
2291 ret->type = word_WhiteSpace;
2292 ret->text = NULL;
2293 ret->breaks = TRUE;
2294 ret->aux = 0;
2295 return ret;
2296 }
2297
2298 static word *fake_page_ref(page_data *page)
2299 {
2300 word *ret = mknew(word);
2301 ret->next = NULL;
2302 ret->alt = NULL;
2303 ret->type = word_PageXref;
2304 ret->text = NULL;
2305 ret->breaks = FALSE;
2306 ret->aux = 0;
2307 ret->private_data = page;
2308 return ret;
2309 }
2310
2311 static word *fake_end_ref(void)
2312 {
2313 word *ret = mknew(word);
2314 ret->next = NULL;
2315 ret->alt = NULL;
2316 ret->type = word_XrefEnd;
2317 ret->text = NULL;
2318 ret->breaks = FALSE;
2319 ret->aux = 0;
2320 return ret;
2321 }
2322
2323 static word *prepare_contents_title(word *first, wchar_t *separator,
2324 word *second)
2325 {
2326 word *ret;
2327 word **wptr, *w;
2328
2329 wptr = &ret;
2330
2331 if (first) {
2332 w = dup_word_list(first);
2333 *wptr = w;
2334 while (w->next)
2335 w = w->next;
2336 wptr = &w->next;
2337 }
2338
2339 if (separator) {
2340 w = fake_word(separator);
2341 *wptr = w;
2342 wptr = &w->next;
2343 }
2344
2345 if (second) {
2346 *wptr = dup_word_list(second);
2347 }
2348
2349 return ret;
2350 }
2351
2352 static void fold_into_page(page_data *dest, page_data *src, int right_shift)
2353 {
2354 line_data *ldata;
2355
2356 if (!src->first_line)
2357 return;
2358
2359 if (dest->last_line) {
2360 dest->last_line->next = src->first_line;
2361 src->first_line->prev = dest->last_line;
2362 }
2363 dest->last_line = src->last_line;
2364
2365 for (ldata = src->first_line; ldata; ldata = ldata->next) {
2366 ldata->page = dest;
2367 ldata->xpos += right_shift;
2368
2369 if (ldata == src->last_line)
2370 break;
2371 }
2372 }