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