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