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