Restructuring to remove the requirement for a printed paragraph to
[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/*
13 * To be done:
14 *
43341922 15 * - set up contents section now we know what sections begin on
16 * which pages
17 *
43341922 18 * - index
19 *
a8070b46 20 * - header/footer? Page numbers at least would be handy. Fully
21 * configurable footer can wait, though.
22 *
43341922 23 * That should bring us to the same level of functionality that
24 * original-Halibut had, and the same in PDF plus the obvious
25 * interactive navigation features. After that, in future work:
26 *
27 * - linearised PDF, perhaps?
28 *
29 * - I'm uncertain of whether I need to include a ToUnicode CMap
30 * in each of my font definitions in PDF. Currently things (by
31 * which I mean cut and paste out of acroread) seem to be
32 * working fairly happily without it, but I don't know.
33 *
34 * - configurability
35 *
36 * - title pages
37 */
38
39#include <assert.h>
40#include <stdio.h>
41
42#include "halibut.h"
43#include "paper.h"
44
be76d597 45typedef struct paper_conf_Tag paper_conf;
46
47struct paper_conf_Tag {
48 int paper_width;
49 int paper_height;
50 int left_margin;
51 int top_margin;
52 int right_margin;
53 int bottom_margin;
54 int indent_list_bullet;
55 int indent_list;
56 int indent_quote;
57 int base_leading;
58 int base_para_spacing;
59 int chapter_top_space;
60 int sect_num_left_space;
61 int chapter_underline_depth;
62 int chapter_underline_thickness;
63 int rule_thickness;
64 int base_font_size;
65 /* These are derived from the above */
66 int base_width;
67 int page_height;
68 /* Fonts used in the configuration */
69 font_data *tr, *ti, *hr, *hi, *cr, *co, *cb;
70};
71
43341922 72static font_data *make_std_font(font_list *fontlist, char const *name);
73static void wrap_paragraph(para_data *pdata, word *words,
74 int w, int i1, int i2);
75static page_data *page_breaks(line_data *first, line_data *last,
76 int page_height);
138d7ffb 77static void render_line(line_data *ldata, int left_x, int top_y,
78 xref_dest *dest, keywordlist *keywords);
515d216b 79static int paper_width_simple(para_data *pdata, word *text);
be76d597 80static para_data *code_paragraph(int indent, word *words, paper_conf *conf);
81static para_data *rule_paragraph(int indent, paper_conf *conf);
23765aeb 82static void add_rect_to_page(page_data *page, int x, int y, int w, int h);
be76d597 83static para_data *make_para_data(int ptype, int paux, int indent,
84 word *pkwtext, word *pkwtext2, word *pwords,
85 paper_conf *conf);
86static void standard_line_spacing(para_data *pdata, paper_conf *conf);
87static wchar_t *prepare_outline_title(word *first, wchar_t *separator,
88 word *second);
43341922 89
90void *paper_pre_backend(paragraph *sourceform, keywordlist *keywords,
91 indexdata *idx) {
92 paragraph *p;
93 document *doc;
be76d597 94 int indent;
95 para_data *pdata, *firstpara = NULL, *lastpara = NULL;
43341922 96 line_data *ldata, *firstline, *lastline;
43341922 97 page_data *pages;
98 font_list *fontlist;
be76d597 99 paper_conf *conf;
43341922 100
101 /*
102 * FIXME: All these things ought to become configurable.
103 */
be76d597 104 conf = mknew(paper_conf);
105 conf->paper_width = 595 * 4096;
106 conf->paper_height = 841 * 4096;
107 conf->left_margin = 72 * 4096;
108 conf->top_margin = 72 * 4096;
109 conf->right_margin = 72 * 4096;
110 conf->bottom_margin = 108 * 4096;
111 conf->indent_list_bullet = 6 * 4096;
112 conf->indent_list = 24 * 4096;
113 conf->indent_quote = 18 * 4096;
114 conf->base_leading = 4096;
115 conf->base_para_spacing = 10 * 4096;
116 conf->chapter_top_space = 72 * 4096;
117 conf->sect_num_left_space = 12 * 4096;
118 conf->chapter_underline_depth = 14 * 4096;
119 conf->chapter_underline_thickness = 3 * 4096;
120 conf->rule_thickness = 1 * 4096;
121 conf->base_font_size = 12;
122
123 conf->base_width =
124 conf->paper_width - conf->left_margin - conf->right_margin;
125 conf->page_height =
126 conf->paper_height - conf->top_margin - conf->bottom_margin;
43341922 127
43341922 128 IGNORE(idx); /* FIXME */
43341922 129
130 /*
131 * First, set up some font structures.
132 */
133 fontlist = mknew(font_list);
134 fontlist->head = fontlist->tail = NULL;
be76d597 135 conf->tr = make_std_font(fontlist, "Times-Roman");
136 conf->ti = make_std_font(fontlist, "Times-Italic");
137 conf->hr = make_std_font(fontlist, "Helvetica-Bold");
138 conf->hi = make_std_font(fontlist, "Helvetica-BoldOblique");
139 conf->cr = make_std_font(fontlist, "Courier");
140 conf->co = make_std_font(fontlist, "Courier-Oblique");
141 conf->cb = make_std_font(fontlist, "Courier-Bold");
43341922 142
143 /*
144 * Go through and break up each paragraph into lines.
145 */
146 indent = 0;
147 firstline = lastline = NULL;
148 for (p = sourceform; p; p = p->next) {
149 p->private_data = NULL;
150
151 switch (p->type) {
152 /*
153 * These paragraph types are either invisible or don't
154 * define text in the normal sense. Either way, they
155 * don't require wrapping.
156 */
157 case para_IM:
158 case para_BR:
43341922 159 case para_Biblio:
160 case para_NotParaType:
161 case para_Config:
162 case para_VersionID:
163 case para_NoCite:
164 break;
165
166 /*
167 * These paragraph types don't require wrapping, but
168 * they do affect the line width to which we wrap the
169 * rest of the paragraphs, so we need to pay attention.
170 */
171 case para_LcontPush:
be76d597 172 indent += conf->indent_list; break;
43341922 173 case para_LcontPop:
be76d597 174 indent -= conf->indent_list; assert(indent >= 0); break;
43341922 175 case para_QuotePush:
be76d597 176 indent += conf->indent_quote; break;
43341922 177 case para_QuotePop:
be76d597 178 indent -= conf->indent_quote; assert(indent >= 0); break;
43341922 179
180 /*
181 * This paragraph type is special. Process it
182 * specially.
183 */
184 case para_Code:
be76d597 185 pdata = code_paragraph(indent, p->words, conf);
515d216b 186 p->private_data = pdata;
39a0cfb9 187 if (pdata->first != pdata->last) {
188 pdata->first->penalty_after += 100000;
189 pdata->last->penalty_before += 100000;
190 }
43341922 191 break;
192
193 /*
87bd6353 194 * This paragraph is also special.
195 */
196 case para_Rule:
be76d597 197 pdata = rule_paragraph(indent, conf);
87bd6353 198 p->private_data = pdata;
199 break;
200
201 /*
43341922 202 * All of these paragraph types require wrapping in the
203 * ordinary way. So we must supply a set of fonts, a
204 * line width and auxiliary information (e.g. bullet
205 * text) for each one.
206 */
207 case para_Chapter:
208 case para_Appendix:
209 case para_UnnumberedChapter:
210 case para_Heading:
211 case para_Subsect:
212 case para_Normal:
213 case para_BiblioCited:
214 case para_Bullet:
215 case para_NumberedList:
216 case para_DescribedThing:
217 case para_Description:
218 case para_Copyright:
219 case para_Title:
be76d597 220 pdata = make_para_data(p->type, p->aux, indent,
221 p->kwtext, p->kwtext2, p->words, conf);
43341922 222
43341922 223 p->private_data = pdata;
224
515d216b 225 break;
226 }
227
228 if (p->private_data) {
229 pdata = (para_data *)p->private_data;
230
43341922 231 /*
515d216b 232 * Link all line structures together into a big list.
233 */
43341922 234 if (pdata->first) {
235 if (lastline) {
236 lastline->next = pdata->first;
237 pdata->first->prev = lastline;
238 } else {
239 firstline = pdata->first;
240 pdata->first->prev = NULL;
241 }
242 lastline = pdata->last;
243 }
be76d597 244
245 /*
246 * Link all paragraph structures together similarly.
247 */
248 pdata->next = NULL;
249 if (lastpara)
250 lastpara->next = pdata;
251 else
252 firstpara = pdata;
253 lastpara = pdata;
43341922 254 }
255 }
256
257 /*
258 * Now we have an enormous linked list of every line of text in
259 * the document. Break it up into pages.
260 */
be76d597 261 pages = page_breaks(firstline, lastline, conf->page_height);
43341922 262
263 /*
264 * Now we're ready to actually lay out the pages. We do this by
265 * looping over _paragraphs_, since we may need to track cross-
266 * references between lines and even across pages.
267 */
be76d597 268 for (pdata = firstpara; pdata; pdata = pdata->next) {
269 xref_dest dest;
270 dest.type = NONE;
271 for (ldata = pdata->first; ldata; ldata = ldata->next) {
272 render_line(ldata, conf->left_margin,
273 conf->paper_height - conf->top_margin,
274 &dest, keywords);
275 if (ldata == pdata->last)
276 break;
277 }
87bd6353 278
be76d597 279 /*
280 * Render any rectangle (chapter title underline or rule)
281 * that goes with this paragraph.
282 */
283 switch (pdata->rect_type) {
284 case RECT_CHAPTER_UNDERLINE:
285 add_rect_to_page(pdata->last->page,
286 conf->left_margin,
287 (conf->paper_height - conf->top_margin -
288 pdata->last->ypos -
289 conf->chapter_underline_depth),
290 conf->base_width,
291 conf->chapter_underline_thickness);
292 break;
293 case RECT_RULE:
294 add_rect_to_page(pdata->first->page,
295 conf->left_margin + pdata->first->xpos,
296 (conf->paper_height - conf->top_margin -
297 pdata->last->ypos -
298 pdata->last->line_height),
299 conf->base_width - pdata->first->xpos,
300 pdata->last->line_height);
301 break;
302 default: /* placate gcc */
303 break;
43341922 304 }
305 }
306
f0e51ce1 307 /*
308 * Start putting together the overall document structure we're
309 * going to return.
310 */
43341922 311 doc = mknew(document);
312 doc->fonts = fontlist;
313 doc->pages = pages;
be76d597 314 doc->paper_width = conf->paper_width;
315 doc->paper_height = conf->paper_height;
f0e51ce1 316
317 /*
318 * Collect the section heading paragraphs into a document
319 * outline. This is slightly fiddly because the Title paragraph
320 * isn't required to be at the start, although all the others
321 * must be in order.
322 */
323 {
324 int osize = 20;
325
326 doc->outline_elements = mknewa(outline_element, osize);
327 doc->n_outline_elements = 0;
328
329 /* First find the title. */
be76d597 330 for (pdata = firstpara; pdata; pdata = pdata->next) {
331 if (pdata->outline_level == 0) {
f0e51ce1 332 doc->outline_elements[0].level = 0;
be76d597 333 doc->outline_elements[0].pdata = pdata;
f0e51ce1 334 doc->n_outline_elements++;
335 break;
336 }
337 }
338
339 /* Then collect the rest. */
be76d597 340 for (pdata = firstpara; pdata; pdata = pdata->next) {
341 if (pdata->outline_level > 0) {
f0e51ce1 342 if (doc->n_outline_elements >= osize) {
343 osize += 20;
344 doc->outline_elements =
345 resize(doc->outline_elements, osize);
346 }
347
be76d597 348 doc->outline_elements[doc->n_outline_elements].level =
349 pdata->outline_level;
350 doc->outline_elements[doc->n_outline_elements].pdata = pdata;
f0e51ce1 351 doc->n_outline_elements++;
f0e51ce1 352 }
353 }
354 }
355
be76d597 356 sfree(conf);
357
43341922 358 return doc;
359}
360
be76d597 361static para_data *make_para_data(int ptype, int paux, int indent,
362 word *pkwtext, word *pkwtext2, word *pwords,
363 paper_conf *conf)
364{
365 para_data *pdata;
366 line_data *ldata;
367 int extra_indent, firstline_indent, aux_indent;
368 word *aux, *aux2;
369
370 pdata = mknew(para_data);
371 pdata->outline_level = -1;
372 pdata->outline_title = NULL;
373 pdata->rect_type = RECT_NONE;
374
375 /*
376 * Choose fonts for this paragraph.
377 *
378 * FIXME: All of this ought to be completely
379 * user-configurable.
380 */
381 switch (ptype) {
382 case para_Title:
383 pdata->fonts[FONT_NORMAL] = conf->hr;
384 pdata->sizes[FONT_NORMAL] = 24;
385 pdata->fonts[FONT_EMPH] = conf->hi;
386 pdata->sizes[FONT_EMPH] = 24;
387 pdata->fonts[FONT_CODE] = conf->cb;
388 pdata->sizes[FONT_CODE] = 24;
389 pdata->outline_level = 0;
390 break;
391
392 case para_Chapter:
393 case para_Appendix:
394 case para_UnnumberedChapter:
395 pdata->fonts[FONT_NORMAL] = conf->hr;
396 pdata->sizes[FONT_NORMAL] = 20;
397 pdata->fonts[FONT_EMPH] = conf->hi;
398 pdata->sizes[FONT_EMPH] = 20;
399 pdata->fonts[FONT_CODE] = conf->cb;
400 pdata->sizes[FONT_CODE] = 20;
401 pdata->outline_level = 1;
402 break;
403
404 case para_Heading:
405 case para_Subsect:
406 pdata->fonts[FONT_NORMAL] = conf->hr;
407 pdata->fonts[FONT_EMPH] = conf->hi;
408 pdata->fonts[FONT_CODE] = conf->cb;
409 pdata->sizes[FONT_NORMAL] =
410 pdata->sizes[FONT_EMPH] =
411 pdata->sizes[FONT_CODE] =
412 (paux == 0 ? 16 : paux == 1 ? 14 : 13);
413 pdata->outline_level = 2 + paux;
414 break;
415
416 case para_Normal:
417 case para_BiblioCited:
418 case para_Bullet:
419 case para_NumberedList:
420 case para_DescribedThing:
421 case para_Description:
422 case para_Copyright:
423 pdata->fonts[FONT_NORMAL] = conf->tr;
424 pdata->sizes[FONT_NORMAL] = 12;
425 pdata->fonts[FONT_EMPH] = conf->ti;
426 pdata->sizes[FONT_EMPH] = 12;
427 pdata->fonts[FONT_CODE] = conf->cr;
428 pdata->sizes[FONT_CODE] = 12;
429 break;
430 }
431
432 /*
433 * Also select an indentation level depending on the
434 * paragraph type (list paragraphs other than
435 * para_DescribedThing need extra indent).
436 *
437 * (FIXME: Perhaps at some point we might even arrange
438 * for the user to be able to request indented first
439 * lines in paragraphs.)
440 */
441 if (ptype == para_Bullet ||
442 ptype == para_NumberedList ||
443 ptype == para_Description) {
444 extra_indent = firstline_indent = conf->indent_list;
445 } else {
446 extra_indent = firstline_indent = 0;
447 }
448
449 /*
450 * Find the auxiliary text for this paragraph.
451 */
452 aux = aux2 = NULL;
453 aux_indent = 0;
454
455 switch (ptype) {
456 case para_Chapter:
457 case para_Appendix:
458 case para_Heading:
459 case para_Subsect:
460 /*
461 * For some heading styles (FIXME: be able to
462 * configure which), the auxiliary text contains
463 * the chapter number and is arranged to be
464 * right-aligned a few points left of the primary
465 * margin. For other styles, the auxiliary text is
466 * the full chapter _name_ and takes up space
467 * within the (wrapped) chapter title, meaning that
468 * we must move the first line indent over to make
469 * space for it.
470 */
471 if (ptype == para_Heading || ptype == para_Subsect) {
472 int len;
473
474 aux = pkwtext2;
475 len = paper_width_simple(pdata, pkwtext2);
476 aux_indent = -len - conf->sect_num_left_space;
477
478 pdata->outline_title =
479 prepare_outline_title(pkwtext2, L" ", pwords);
480 } else {
481 aux = pkwtext;
482 aux2 = mknew(word);
483 aux2->next = NULL;
484 aux2->alt = NULL;
485 aux2->type = word_Normal;
486 aux2->text = ustrdup(L": ");
487 aux2->breaks = FALSE;
488 aux2->aux = 0;
489 aux_indent = 0;
490
491 firstline_indent += paper_width_simple(pdata, aux);
492 firstline_indent += paper_width_simple(pdata, aux2);
493
494 pdata->outline_title =
495 prepare_outline_title(pkwtext, L": ", pwords);
496 }
497 break;
498
499 case para_Bullet:
500 /*
501 * Auxiliary text consisting of a bullet. (FIXME:
502 * configurable bullet.)
503 */
504 aux = mknew(word);
505 aux->next = NULL;
506 aux->alt = NULL;
507 aux->type = word_Normal;
508 aux->text = ustrdup(L"\x2022");
509 aux->breaks = FALSE;
510 aux->aux = 0;
511 aux_indent = indent + conf->indent_list_bullet;
512 break;
513
514 case para_NumberedList:
515 /*
516 * Auxiliary text consisting of the number followed
517 * by a (FIXME: configurable) full stop.
518 */
519 aux = pkwtext;
520 aux2 = mknew(word);
521 aux2->next = NULL;
522 aux2->alt = NULL;
523 aux2->type = word_Normal;
524 aux2->text = ustrdup(L".");
525 aux2->breaks = FALSE;
526 aux2->aux = 0;
527 aux_indent = indent + conf->indent_list_bullet;
528 break;
529
530 case para_BiblioCited:
531 /*
532 * Auxiliary text consisting of the bibliography
533 * reference text, and a trailing space.
534 */
535 aux = pkwtext;
536 aux2 = mknew(word);
537 aux2->next = NULL;
538 aux2->alt = NULL;
539 aux2->type = word_Normal;
540 aux2->text = ustrdup(L" ");
541 aux2->breaks = FALSE;
542 aux2->aux = 0;
543 aux_indent = indent;
544 firstline_indent += paper_width_simple(pdata, aux);
545 firstline_indent += paper_width_simple(pdata, aux2);
546 break;
547 }
548
549 if (pdata->outline_level >= 0 && !pdata->outline_title) {
550 pdata->outline_title =
551 prepare_outline_title(NULL, NULL, pwords);
552 }
553
554 wrap_paragraph(pdata, pwords, conf->base_width,
555 indent + firstline_indent,
556 indent + extra_indent);
557
558 pdata->first->aux_text = aux;
559 pdata->first->aux_text_2 = aux2;
560 pdata->first->aux_left_indent = aux_indent;
561
562 /*
563 * Line breaking penalties.
564 */
565 switch (ptype) {
566 case para_Chapter:
567 case para_Appendix:
568 case para_Heading:
569 case para_Subsect:
570 case para_UnnumberedChapter:
571 /*
572 * Fixed and large penalty for breaking straight
573 * after a heading; corresponding bonus for
574 * breaking straight before.
575 */
576 pdata->first->penalty_before = -500000;
577 pdata->last->penalty_after = 500000;
578 for (ldata = pdata->first; ldata; ldata = ldata->next)
579 ldata->penalty_after = 500000;
580 break;
581
582 case para_DescribedThing:
583 /*
584 * This is treated a bit like a small heading:
585 * there's a penalty for breaking after it (i.e.
586 * between it and its description), and a bonus for
587 * breaking before it (actually _between_ list
588 * items).
589 */
590 pdata->first->penalty_before = -200000;
591 pdata->last->penalty_after = 200000;
592 break;
593
594 default:
595 /*
596 * Most paragraph types: widow/orphan control by
597 * discouraging breaking one line from the end of
598 * any paragraph.
599 */
600 if (pdata->first != pdata->last) {
601 pdata->first->penalty_after = 100000;
602 pdata->last->penalty_before = 100000;
603 }
604 break;
605 }
606
607 standard_line_spacing(pdata, conf);
608
609 /*
610 * Some kinds of section heading require a page break before
611 * them and an underline after.
612 */
613 if (ptype == para_Title ||
614 ptype == para_Chapter ||
615 ptype == para_Appendix ||
616 ptype == para_UnnumberedChapter) {
617 pdata->first->page_break = TRUE;
618 pdata->first->space_before = conf->chapter_top_space;
619 pdata->last->space_after +=
620 (conf->chapter_underline_depth +
621 conf->chapter_underline_thickness);
622 pdata->rect_type = RECT_CHAPTER_UNDERLINE;
623 }
624
625 return pdata;
626}
627
628static void standard_line_spacing(para_data *pdata, paper_conf *conf)
629{
630 line_data *ldata;
631
632 /*
633 * Set the line spacing for each line in this paragraph.
634 */
635 for (ldata = pdata->first; ldata; ldata = ldata->next) {
636 if (ldata == pdata->first)
637 ldata->space_before = conf->base_para_spacing / 2;
638 else
639 ldata->space_before = conf->base_leading / 2;
640 if (ldata == pdata->last)
641 ldata->space_after = conf->base_para_spacing / 2;
642 else
643 ldata->space_after = conf->base_leading / 2;
644 ldata->page_break = FALSE;
645 }
646}
647
43341922 648static font_encoding *new_font_encoding(font_data *font)
649{
650 font_encoding *fe;
651 int i;
652
653 fe = mknew(font_encoding);
654 fe->next = NULL;
655
656 if (font->list->tail)
657 font->list->tail->next = fe;
658 else
659 font->list->head = fe;
660 font->list->tail = fe;
661
662 fe->font = font;
663 fe->free_pos = 0x21;
664
665 for (i = 0; i < 256; i++) {
666 fe->vector[i] = NULL;
667 fe->indices[i] = -1;
668 fe->to_unicode[i] = 0xFFFF;
669 }
670
671 return fe;
672}
673
674static font_data *make_std_font(font_list *fontlist, char const *name)
675{
676 const int *widths;
677 int nglyphs;
678 font_data *f;
679 font_encoding *fe;
680 int i;
681
682 widths = ps_std_font_widths(name);
683 if (!widths)
684 return NULL;
685
686 for (nglyphs = 0; ps_std_glyphs[nglyphs] != NULL; nglyphs++);
687
688 f = mknew(font_data);
689
690 f->list = fontlist;
691 f->name = name;
692 f->nglyphs = nglyphs;
693 f->glyphs = ps_std_glyphs;
694 f->widths = widths;
695 f->subfont_map = mknewa(subfont_map_entry, nglyphs);
696
697 /*
698 * Our first subfont will contain all of US-ASCII. This isn't
699 * really necessary - we could just create custom subfonts
700 * precisely as the whim of render_string dictated - but
701 * instinct suggests that it might be nice to have the text in
702 * the output files look _marginally_ recognisable.
703 */
704 fe = new_font_encoding(f);
705 fe->free_pos = 0xA1; /* only the top half is free */
706 f->latest_subfont = fe;
707
708 for (i = 0; i < (int)lenof(f->bmp); i++)
709 f->bmp[i] = 0xFFFF;
710
711 for (i = 0; i < nglyphs; i++) {
712 wchar_t ucs;
713 ucs = ps_glyph_to_unicode(f->glyphs[i]);
714 assert(ucs != 0xFFFF);
715 f->bmp[ucs] = i;
716 if (ucs >= 0x20 && ucs <= 0x7E) {
717 fe->vector[ucs] = f->glyphs[i];
718 fe->indices[ucs] = i;
719 fe->to_unicode[ucs] = ucs;
720 f->subfont_map[i].subfont = fe;
721 f->subfont_map[i].position = ucs;
722 } else {
723 /*
724 * This character is not yet assigned to a subfont.
725 */
726 f->subfont_map[i].subfont = NULL;
727 f->subfont_map[i].position = 0;
728 }
729 }
730
731 return f;
732}
733
734static int string_width(font_data *font, wchar_t const *string, int *errs)
735{
736 int width = 0;
737
738 if (errs)
739 *errs = 0;
740
741 for (; *string; string++) {
742 int index;
743
744 index = font->bmp[(unsigned short)*string];
745 if (index == 0xFFFF) {
746 if (errs)
747 *errs = 1;
748 } else {
749 width += font->widths[index];
750 }
751 }
752
753 return width;
754}
755
faad4952 756static int paper_width_internal(void *vctx, word *word, int *nspaces);
43341922 757
758struct paper_width_ctx {
759 int minspacewidth;
760 para_data *pdata;
761};
762
faad4952 763static int paper_width_list(void *vctx, word *text, word *end, int *nspaces) {
43341922 764 int w = 0;
faad4952 765 while (text && text != end) {
766 w += paper_width_internal(vctx, text, nspaces);
43341922 767 text = text->next;
768 }
769 return w;
770}
771
faad4952 772static int paper_width_internal(void *vctx, word *word, int *nspaces)
43341922 773{
774 struct paper_width_ctx *ctx = (struct paper_width_ctx *)vctx;
775 int style, type, findex, width, errs;
776 wchar_t *str;
777
778 switch (word->type) {
779 case word_HyperLink:
780 case word_HyperEnd:
781 case word_UpperXref:
782 case word_LowerXref:
783 case word_XrefEnd:
784 case word_IndexRef:
785 return 0;
786 }
787
788 style = towordstyle(word->type);
789 type = removeattr(word->type);
790
791 findex = (style == word_Normal ? FONT_NORMAL :
792 style == word_Emph ? FONT_EMPH :
793 FONT_CODE);
794
795 if (type == word_Normal) {
796 str = word->text;
797 } else if (type == word_WhiteSpace) {
faad4952 798 if (findex != FONT_CODE) {
799 if (nspaces)
800 (*nspaces)++;
43341922 801 return ctx->minspacewidth;
faad4952 802 } else
43341922 803 str = L" ";
804 } else /* if (type == word_Quote) */ {
805 if (word->aux == quote_Open)
806 str = L"\x2018"; /* FIXME: configurability! */
807 else
808 str = L"\x2019"; /* FIXME: configurability! */
809 }
810
811 width = string_width(ctx->pdata->fonts[findex], str, &errs);
812
813 if (errs && word->alt)
faad4952 814 return paper_width_list(vctx, word->alt, NULL, nspaces);
43341922 815 else
816 return ctx->pdata->sizes[findex] * width;
817}
818
faad4952 819static int paper_width(void *vctx, word *word)
820{
821 return paper_width_internal(vctx, word, NULL);
822}
823
515d216b 824static int paper_width_simple(para_data *pdata, word *text)
825{
826 struct paper_width_ctx ctx;
827
828 ctx.pdata = pdata;
829 ctx.minspacewidth =
830 (pdata->sizes[FONT_NORMAL] *
831 string_width(pdata->fonts[FONT_NORMAL], L" ", NULL));
832
833 return paper_width_list(&ctx, text, NULL, NULL);
834}
835
43341922 836static void wrap_paragraph(para_data *pdata, word *words,
837 int w, int i1, int i2)
838{
839 wrappedline *wrapping, *p;
840 int spacewidth;
841 struct paper_width_ctx ctx;
842 int line_height;
843
844 /*
845 * We're going to need to store the line height in every line
846 * structure we generate.
847 */
848 {
849 int i;
850 line_height = 0;
851 for (i = 0; i < NFONTS; i++)
852 if (line_height < pdata->sizes[i])
853 line_height = pdata->sizes[i];
854 line_height *= 4096;
855 }
856
857 spacewidth = (pdata->sizes[FONT_NORMAL] *
858 string_width(pdata->fonts[FONT_NORMAL], L" ", NULL));
859 if (spacewidth == 0) {
860 /*
861 * A font without a space?! Disturbing. I hope this never
862 * comes up, but I'll make a random guess anyway and set my
863 * space width to half the point size.
864 */
865 spacewidth = pdata->sizes[FONT_NORMAL] * 4096 / 2;
866 }
867
868 /*
869 * I'm going to set the _minimum_ space width to 3/5 of the
870 * standard one, and use the standard one as the optimum.
871 */
872 ctx.minspacewidth = spacewidth * 3 / 5;
873 ctx.pdata = pdata;
874
875 wrapping = wrap_para(words, w - i1, w - i2, paper_width, &ctx, spacewidth);
876
877 /*
878 * Having done the wrapping, we now concoct a set of line_data
879 * structures.
880 */
881 pdata->first = pdata->last = NULL;
882
883 for (p = wrapping; p; p = p->next) {
884 line_data *ldata;
885 word *wd;
886 int len, wid, spaces;
887
888 ldata = mknew(line_data);
889
890 ldata->pdata = pdata;
891 ldata->first = p->begin;
faad4952 892 ldata->end = p->end;
43341922 893 ldata->line_height = line_height;
894
895 ldata->xpos = (p == wrapping ? i1 : i2);
896
897 if (pdata->last) {
898 pdata->last->next = ldata;
899 ldata->prev = pdata->last;
900 } else {
901 pdata->first = ldata;
902 ldata->prev = NULL;
903 }
904 ldata->next = NULL;
905 pdata->last = ldata;
906
43341922 907 spaces = 0;
faad4952 908 len = paper_width_list(&ctx, ldata->first, ldata->end, &spaces);
909 wid = (p == wrapping ? w - i1 : w - i2);
43341922 910 wd = ldata->first;
43341922 911
faad4952 912 ldata->hshortfall = wid - len;
913 ldata->nspaces = spaces;
914 /*
915 * This tells us how much the space width needs to
916 * change from _min_spacewidth. But we want to store
917 * its difference from the _natural_ space width, to
918 * make the text rendering easier.
919 */
920 ldata->hshortfall += ctx.minspacewidth * spaces;
921 ldata->hshortfall -= spacewidth * spaces;
922 /*
923 * Special case: on the last line of a paragraph, we
924 * never stretch spaces.
925 */
926 if (ldata->hshortfall > 0 && !p->next)
927 ldata->hshortfall = 0;
43341922 928
929 ldata->aux_text = NULL;
515d216b 930 ldata->aux_text_2 = NULL;
43341922 931 ldata->aux_left_indent = 0;
39a0cfb9 932 ldata->penalty_before = ldata->penalty_after = 0;
43341922 933 }
934
935}
936
937static page_data *page_breaks(line_data *first, line_data *last,
938 int page_height)
939{
940 line_data *l, *m;
941 page_data *ph, *pt;
942
943 /*
944 * Page breaking is done by a close analogue of the optimal
945 * paragraph wrapping algorithm used by wrap_para(). We work
946 * backwards from the end of the document line by line; for
947 * each line, we contemplate every possible number of lines we
948 * could put on a page starting with that line, determine a
949 * cost function for each one, add it to the pre-computed cost
950 * function for optimally page-breaking everything after that
951 * page, and pick the best option.
952 *
953 * Since my line_data structures are only used for this
954 * purpose, I might as well just store the algorithm data
955 * directly in them.
956 */
957
958 for (l = last; l; l = l->prev) {
959 int minheight, text = 0, space = 0;
960 int cost;
961
962 l->bestcost = -1;
963 for (m = l; m; m = m->next) {
964 if (m != l && m->page_break)
965 break; /* we've gone as far as we can */
966
967 if (m != l)
968 space += m->prev->space_after;
969 if (m != l || m->page_break)
970 space += m->space_before;
971 text += m->line_height;
972 minheight = text + space;
973
974 if (m != l && minheight > page_height)
975 break;
976
977 /*
978 * Compute the cost of this arrangement, as the square
979 * of the amount of wasted space on the page.
980 * Exception: if this is the last page before a
981 * mandatory break or the document end, we don't
982 * penalise a large blank area.
983 */
984 if (m->next && !m->next->page_break)
985 {
986 int x = page_height - minheight;
987 int xf;
988
989 xf = x & 0xFF;
990 x >>= 8;
991
992 cost = x*x;
993 cost += (x * xf) >> 8;
994 } else
995 cost = 0;
996
39a0cfb9 997 if (m->next && !m->next->page_break) {
998 cost += m->penalty_after;
999 cost += m->next->penalty_before;
1000 }
43341922 1001
1002 if (m->next && !m->next->page_break)
1003 cost += m->next->bestcost;
43341922 1004 if (l->bestcost == -1 || l->bestcost > cost) {
1005 /*
1006 * This is the best option yet for this starting
1007 * point.
1008 */
1009 l->bestcost = cost;
1010 if (m->next && !m->next->page_break)
faad4952 1011 l->vshortfall = page_height - minheight;
43341922 1012 else
faad4952 1013 l->vshortfall = 0;
43341922 1014 l->text = text;
1015 l->space = space;
1016 l->page_last = m;
1017 }
1018 }
1019 }
1020
1021 /*
1022 * Now go through the line list forwards and assemble the
1023 * actual pages.
1024 */
1025 ph = pt = NULL;
1026
1027 l = first;
1028 while (l) {
1029 page_data *page;
1030 int text, space;
1031
1032 page = mknew(page_data);
1033 page->next = NULL;
1034 page->prev = pt;
1035 if (pt)
1036 pt->next = page;
1037 else
1038 ph = page;
1039 pt = page;
1040
1041 page->first_line = l;
1042 page->last_line = l->page_last;
1043
1044 page->first_text = page->last_text = NULL;
138d7ffb 1045 page->first_xref = page->last_xref = NULL;
23765aeb 1046 page->first_rect = page->last_rect = NULL;
138d7ffb 1047
43341922 1048 /*
1049 * Now assign a y-coordinate to each line on the page.
1050 */
1051 text = space = 0;
1052 for (l = page->first_line; l; l = l->next) {
1053 if (l != page->first_line)
1054 space += l->prev->space_after;
1055 if (l != page->first_line || l->page_break)
1056 space += l->space_before;
1057 text += l->line_height;
1058
1059 l->page = page;
1060 l->ypos = text + space +
faad4952 1061 space * (float)page->first_line->vshortfall /
43341922 1062 page->first_line->space;
1063
1064 if (l == page->last_line)
1065 break;
1066 }
1067
1068 l = page->last_line->next;
1069 }
1070
1071 return ph;
1072}
1073
23765aeb 1074static void add_rect_to_page(page_data *page, int x, int y, int w, int h)
1075{
1076 rect *r = mknew(rect);
1077
1078 r->next = NULL;
1079 if (page->last_rect)
1080 page->last_rect->next = r;
1081 else
1082 page->first_rect = r;
1083 page->last_rect = r;
1084
1085 r->x = x;
1086 r->y = y;
1087 r->w = w;
1088 r->h = h;
1089}
1090
43341922 1091static void add_string_to_page(page_data *page, int x, int y,
1092 font_encoding *fe, int size, char *text)
1093{
1094 text_fragment *frag;
1095
1096 frag = mknew(text_fragment);
1097 frag->next = NULL;
1098
1099 if (page->last_text)
1100 page->last_text->next = frag;
1101 else
1102 page->first_text = frag;
1103 page->last_text = frag;
1104
1105 frag->x = x;
1106 frag->y = y;
1107 frag->fe = fe;
1108 frag->fontsize = size;
1109 frag->text = dupstr(text);
1110}
1111
1112/*
1113 * Returns the updated x coordinate.
1114 */
1115static int render_string(page_data *page, font_data *font, int fontsize,
1116 int x, int y, wchar_t *str)
1117{
1118 char *text;
1119 int textpos, textwid, glyph;
1120 font_encoding *subfont = NULL, *sf;
1121
1122 text = mknewa(char, 1 + ustrlen(str));
1123 textpos = textwid = 0;
1124
1125 while (*str) {
1126 glyph = font->bmp[*str];
1127
1128 if (glyph == 0xFFFF)
1129 continue; /* nothing more we can do here */
1130
1131 /*
1132 * Find which subfont this character is going in.
1133 */
1134 sf = font->subfont_map[glyph].subfont;
1135
1136 if (!sf) {
1137 int c;
1138
1139 /*
1140 * This character is not yet in a subfont. Assign one.
1141 */
1142 if (font->latest_subfont->free_pos >= 0x100)
1143 font->latest_subfont = new_font_encoding(font);
1144
1145 c = font->latest_subfont->free_pos++;
1146 if (font->latest_subfont->free_pos == 0x7F)
1147 font->latest_subfont->free_pos = 0xA1;
1148
1149 font->subfont_map[glyph].subfont = font->latest_subfont;
1150 font->subfont_map[glyph].position = c;
1151 font->latest_subfont->vector[c] = font->glyphs[glyph];
1152 font->latest_subfont->indices[c] = glyph;
1153 font->latest_subfont->to_unicode[c] = *str;
1154
1155 sf = font->latest_subfont;
1156 }
1157
1158 if (!subfont || sf != subfont) {
1159 if (subfont) {
1160 text[textpos] = '\0';
1161 add_string_to_page(page, x, y, subfont, fontsize, text);
1162 x += textwid;
1163 } else {
1164 assert(textpos == 0);
1165 }
1166 textpos = 0;
1167 subfont = sf;
1168 }
1169
1170 text[textpos++] = font->subfont_map[glyph].position;
1171 textwid += font->widths[glyph] * fontsize;
1172
1173 str++;
1174 }
1175
1176 if (textpos > 0) {
1177 text[textpos] = '\0';
1178 add_string_to_page(page, x, y, subfont, fontsize, text);
1179 x += textwid;
1180 }
1181
1182 return x;
1183}
1184
1185/*
1186 * Returns the updated x coordinate.
1187 */
138d7ffb 1188static int render_text(page_data *page, para_data *pdata, line_data *ldata,
1189 int x, int y, word *text, word *text_end, xref **xr,
1190 int shortfall, int nspaces, int *nspace,
1191 keywordlist *keywords)
43341922 1192{
faad4952 1193 while (text && text != text_end) {
43341922 1194 int style, type, findex, errs;
1195 wchar_t *str;
138d7ffb 1196 xref_dest dest;
43341922 1197
1198 switch (text->type) {
138d7ffb 1199 /*
1200 * Start a cross-reference.
1201 */
43341922 1202 case word_HyperLink:
43341922 1203 case word_UpperXref:
1204 case word_LowerXref:
138d7ffb 1205
1206 if (text->type == word_HyperLink) {
1207 dest.type = URL;
1208 dest.url = utoa_dup(text->text);
1209 dest.page = NULL;
1210 } else {
1211 keyword *kwl = kw_lookup(keywords, text->text);
1212 para_data *pdata;
1213
1214 if (kwl) {
1215 assert(kwl->para->private_data);
1216 pdata = (para_data *) kwl->para->private_data;
1217 dest.type = PAGE;
1218 dest.page = pdata->first->page;
1219 dest.url = NULL;
1220 } else {
1221 /*
1222 * Shouldn't happen, but *shrug*
1223 */
1224 dest.type = NONE;
1225 dest.page = NULL;
1226 dest.url = NULL;
1227 }
1228 }
1229 if (dest.type != NONE) {
1230 *xr = mknew(xref);
1231 (*xr)->dest = dest; /* structure copy */
1232 if (page->last_xref)
1233 page->last_xref->next = *xr;
1234 else
1235 page->first_xref = *xr;
1236 page->last_xref = *xr;
23765aeb 1237 (*xr)->next = NULL;
138d7ffb 1238
1239 /*
1240 * FIXME: Ideally we should have, and use, some
1241 * vertical font metric information here so that
1242 * our cross-ref rectangle can take account of
1243 * descenders and the font's cap height. This will
1244 * do for the moment, but it isn't ideal.
1245 */
1246 (*xr)->lx = (*xr)->rx = x;
1247 (*xr)->by = y;
1248 (*xr)->ty = y + ldata->line_height;
1249 }
1250 goto nextword;
1251
1252 /*
1253 * Finish extending a cross-reference box.
1254 */
1255 case word_HyperEnd:
43341922 1256 case word_XrefEnd:
138d7ffb 1257 *xr = NULL;
1258 goto nextword;
1259
43341922 1260 case word_IndexRef:
1261 goto nextword;
1262 /*
a8070b46 1263 * FIXME: we should do something with this.
43341922 1264 */
1265 }
1266
1267 style = towordstyle(text->type);
1268 type = removeattr(text->type);
1269
1270 findex = (style == word_Normal ? FONT_NORMAL :
1271 style == word_Emph ? FONT_EMPH :
1272 FONT_CODE);
1273
1274 if (type == word_Normal) {
1275 str = text->text;
1276 } else if (type == word_WhiteSpace) {
1277 x += pdata->sizes[findex] *
1278 string_width(pdata->fonts[findex], L" ", NULL);
faad4952 1279 if (nspaces && findex != FONT_CODE) {
1280 x += (*nspace+1) * shortfall / nspaces;
1281 x -= *nspace * shortfall / nspaces;
1282 (*nspace)++;
1283 }
43341922 1284 goto nextword;
1285 } else /* if (type == word_Quote) */ {
1286 if (text->aux == quote_Open)
1287 str = L"\x2018"; /* FIXME: configurability! */
1288 else
1289 str = L"\x2019"; /* FIXME: configurability! */
1290 }
1291
1292 (void) string_width(pdata->fonts[findex], str, &errs);
1293
1294 if (errs && text->alt)
138d7ffb 1295 x = render_text(page, pdata, ldata, x, y, text->alt, NULL,
1296 xr, shortfall, nspaces, nspace, keywords);
43341922 1297 else
1298 x = render_string(page, pdata->fonts[findex],
1299 pdata->sizes[findex], x, y, str);
1300
138d7ffb 1301 if (*xr)
1302 (*xr)->rx = x;
1303
43341922 1304 nextword:
43341922 1305 text = text->next;
1306 }
1307
1308 return x;
1309}
1310
138d7ffb 1311static void render_line(line_data *ldata, int left_x, int top_y,
1312 xref_dest *dest, keywordlist *keywords)
43341922 1313{
faad4952 1314 int nspace;
138d7ffb 1315 xref *xr;
1316
faad4952 1317 if (ldata->aux_text) {
515d216b 1318 int x;
138d7ffb 1319 xr = NULL;
faad4952 1320 nspace = 0;
515d216b 1321 x = render_text(ldata->page, ldata->pdata, ldata,
1322 left_x + ldata->aux_left_indent,
1323 top_y - ldata->ypos,
1324 ldata->aux_text, NULL, &xr, 0, 0, &nspace, keywords);
1325 if (ldata->aux_text_2)
1326 render_text(ldata->page, ldata->pdata, ldata,
1327 x, top_y - ldata->ypos,
1328 ldata->aux_text_2, NULL, &xr, 0, 0, &nspace, keywords);
faad4952 1329 }
1330 nspace = 0;
138d7ffb 1331
87bd6353 1332 if (ldata->first) {
138d7ffb 1333 /*
87bd6353 1334 * There might be a cross-reference carried over from a
1335 * previous line.
138d7ffb 1336 */
87bd6353 1337 if (dest->type != NONE) {
1338 xr = mknew(xref);
1339 xr->next = NULL;
1340 xr->dest = *dest; /* structure copy */
1341 if (ldata->page->last_xref)
1342 ldata->page->last_xref->next = xr;
1343 else
1344 ldata->page->first_xref = xr;
1345 ldata->page->last_xref = xr;
1346 xr->lx = xr->rx = left_x + ldata->xpos;
1347 xr->by = top_y - ldata->ypos;
1348 xr->ty = top_y - ldata->ypos + ldata->line_height;
1349 } else
1350 xr = NULL;
1351
1352 render_text(ldata->page, ldata->pdata, ldata, left_x + ldata->xpos,
1353 top_y - ldata->ypos, ldata->first, ldata->end, &xr,
1354 ldata->hshortfall, ldata->nspaces, &nspace, keywords);
1355
1356 if (xr) {
1357 /*
1358 * There's a cross-reference continued on to the next line.
1359 */
1360 *dest = xr->dest;
1361 } else
1362 dest->type = NONE;
1363 }
43341922 1364}
515d216b 1365
be76d597 1366static para_data *code_paragraph(int indent, word *words, paper_conf *conf)
515d216b 1367{
be76d597 1368 para_data *pdata = mknew(para_data);
1369
515d216b 1370 /*
1371 * For code paragraphs, I'm going to hack grievously and
1372 * pretend the three normal fonts are the three code paragraph
1373 * fonts.
1374 */
be76d597 1375 pdata->fonts[FONT_NORMAL] = conf->cb;
1376 pdata->fonts[FONT_EMPH] = conf->co;
1377 pdata->fonts[FONT_CODE] = conf->cb;
515d216b 1378 pdata->sizes[FONT_NORMAL] =
1379 pdata->sizes[FONT_EMPH] =
be76d597 1380 pdata->sizes[FONT_CODE] = 12;
515d216b 1381
1382 pdata->first = pdata->last = NULL;
be76d597 1383 pdata->outline_level = -1;
1384 pdata->rect_type = RECT_NONE;
515d216b 1385
1386 for (; words; words = words->next) {
1387 wchar_t *t, *e, *start;
1388 word *lhead = NULL, *ltail = NULL, *w;
1389 line_data *ldata;
1390 int prev = -1, curr;
1391
1392 t = words->text;
1393 if (words->next && words->next->type == word_Emph) {
1394 e = words->next->text;
1395 words = words->next;
1396 } else
1397 e = NULL;
1398
1399 start = t;
1400
1401 while (*start) {
1402 while (*t) {
1403 if (!e || !*e)
1404 curr = 0;
1405 else if (*e == L'i')
1406 curr = 1;
1407 else if (*e == L'b')
1408 curr = 2;
1409 else
1410 curr = 0;
1411
1412 if (prev < 0)
1413 prev = curr;
1414
1415 if (curr != prev)
1416 break;
1417
1418 t++;
1419 if (e && *e)
1420 e++;
1421 }
1422
1423 /*
1424 * We've isolated a maximal subsequence of the line
1425 * which has the same emphasis. Form it into a word
1426 * structure.
1427 */
1428 w = mknew(word);
1429 w->next = NULL;
1430 w->alt = NULL;
1431 w->type = (prev == 0 ? word_WeakCode :
1432 prev == 1 ? word_Emph : word_Normal);
1433 w->text = mknewa(wchar_t, t-start+1);
1434 memcpy(w->text, start, (t-start) * sizeof(wchar_t));
1435 w->text[t-start] = '\0';
1436 w->breaks = FALSE;
1437
1438 if (ltail)
1439 ltail->next = w;
1440 else
1441 lhead = w;
1442 ltail = w;
1443
1444 start = t;
1445 prev = -1;
1446 }
1447
1448 ldata = mknew(line_data);
1449
1450 ldata->pdata = pdata;
1451 ldata->first = lhead;
1452 ldata->end = NULL;
be76d597 1453 ldata->line_height = conf->base_font_size * 4096;
515d216b 1454
1455 ldata->xpos = indent;
1456
1457 if (pdata->last) {
1458 pdata->last->next = ldata;
1459 ldata->prev = pdata->last;
1460 } else {
1461 pdata->first = ldata;
1462 ldata->prev = NULL;
1463 }
1464 ldata->next = NULL;
1465 pdata->last = ldata;
1466
1467 ldata->hshortfall = 0;
1468 ldata->nspaces = 0;
1469 ldata->aux_text = NULL;
1470 ldata->aux_text_2 = NULL;
1471 ldata->aux_left_indent = 0;
39a0cfb9 1472 /* General opprobrium for breaking in a code paragraph. */
1473 ldata->penalty_before = ldata->penalty_after = 50000;
515d216b 1474 }
be76d597 1475
1476 standard_line_spacing(pdata, conf);
1477
1478 return pdata;
515d216b 1479}
87bd6353 1480
be76d597 1481static para_data *rule_paragraph(int indent, paper_conf *conf)
87bd6353 1482{
be76d597 1483 para_data *pdata = mknew(para_data);
87bd6353 1484 line_data *ldata;
1485
1486 ldata = mknew(line_data);
1487
1488 ldata->pdata = pdata;
1489 ldata->first = NULL;
1490 ldata->end = NULL;
be76d597 1491 ldata->line_height = conf->rule_thickness;
87bd6353 1492
1493 ldata->xpos = indent;
1494
1495 ldata->prev = NULL;
1496 ldata->next = NULL;
1497
1498 ldata->hshortfall = 0;
1499 ldata->nspaces = 0;
1500 ldata->aux_text = NULL;
1501 ldata->aux_text_2 = NULL;
1502 ldata->aux_left_indent = 0;
1503
1504 /*
1505 * Better to break after a rule than before it
1506 */
1507 ldata->penalty_after += 100000;
1508 ldata->penalty_before += -100000;
1509
1510 pdata->first = pdata->last = ldata;
be76d597 1511 pdata->outline_level = -1;
1512 pdata->rect_type = RECT_RULE;
1513
1514 standard_line_spacing(pdata, conf);
1515
1516 return pdata;
1517}
1518
1519/*
1520 * Plain-text-like formatting for outline titles.
1521 */
1522static void paper_rdaddw(rdstring *rs, word *text) {
1523 for (; text; text = text->next) switch (text->type) {
1524 case word_HyperLink:
1525 case word_HyperEnd:
1526 case word_UpperXref:
1527 case word_LowerXref:
1528 case word_XrefEnd:
1529 case word_IndexRef:
1530 break;
1531
1532 case word_Normal:
1533 case word_Emph:
1534 case word_Code:
1535 case word_WeakCode:
1536 case word_WhiteSpace:
1537 case word_EmphSpace:
1538 case word_CodeSpace:
1539 case word_WkCodeSpace:
1540 case word_Quote:
1541 case word_EmphQuote:
1542 case word_CodeQuote:
1543 case word_WkCodeQuote:
1544 assert(text->type != word_CodeQuote &&
1545 text->type != word_WkCodeQuote);
1546 if (towordstyle(text->type) == word_Emph &&
1547 (attraux(text->aux) == attr_First ||
1548 attraux(text->aux) == attr_Only))
1549 rdadd(rs, L'_'); /* FIXME: configurability */
1550 else if (towordstyle(text->type) == word_Code &&
1551 (attraux(text->aux) == attr_First ||
1552 attraux(text->aux) == attr_Only))
1553 rdadd(rs, L'\''); /* FIXME: configurability */
1554 if (removeattr(text->type) == word_Normal) {
1555 rdadds(rs, text->text);
1556 } else if (removeattr(text->type) == word_WhiteSpace) {
1557 rdadd(rs, L' ');
1558 } else if (removeattr(text->type) == word_Quote) {
1559 rdadd(rs, L'\''); /* fixme: configurability */
1560 }
1561 if (towordstyle(text->type) == word_Emph &&
1562 (attraux(text->aux) == attr_Last ||
1563 attraux(text->aux) == attr_Only))
1564 rdadd(rs, L'_'); /* FIXME: configurability */
1565 else if (towordstyle(text->type) == word_Code &&
1566 (attraux(text->aux) == attr_Last ||
1567 attraux(text->aux) == attr_Only))
1568 rdadd(rs, L'\''); /* FIXME: configurability */
1569 break;
1570 }
1571}
1572
1573static wchar_t *prepare_outline_title(word *first, wchar_t *separator,
1574 word *second)
1575{
1576 rdstring rs = {0, 0, NULL};
1577
1578 if (first)
1579 paper_rdaddw(&rs, first);
1580 if (separator)
1581 rdadds(&rs, separator);
1582 if (second)
1583 paper_rdaddw(&rs, second);
1584
1585 return rs.text;
87bd6353 1586}