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