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