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