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