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