Cut-and-paste error was preventing PS and PDF filename config
[sgt/halibut] / bk_paper.c
CommitLineData
43341922 1/*
2 * Paper printing pre-backend for Halibut.
3 *
4 * This module does all the processing common to both PostScript
5 * and PDF output: selecting fonts, line wrapping and page breaking
6 * in accordance with font metrics, laying out the contents and
7 * index pages, generally doing all the page layout. After this,
8 * bk_ps.c and bk_pdf.c should only need to do linear translations
9 * into their literal output format.
10 */
11
12/*
9a8dc6b1 13 * TODO in future work:
43341922 14 *
15 * - linearised PDF, perhaps?
16 *
e48dc052 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 *
43341922 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 *
9a8dc6b1 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 *
43341922 34 * - configurability
9a8dc6b1 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
dd567011 43 * * fixed text: `Contents', `Index', the colon-space and full
44 * stop in chapter title constructions
9a8dc6b1 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
43341922 54 *
55 * - title pages
9a8dc6b1 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
266a539f 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)
09358aa7 68 * * DON'T DO ANY OF THE ABOVE WITHIN \c OR \cw!
266a539f 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.
43341922 76 */
77
78#include <assert.h>
79#include <stdio.h>
dd567011 80#include <stdarg.h>
43341922 81
82#include "halibut.h"
83#include "paper.h"
84
be76d597 85typedef struct paper_conf_Tag paper_conf;
c6536773 86typedef struct paper_idx_Tag paper_idx;
be76d597 87
88struct 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;
dd567011 96 int indent_list_after;
be76d597 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;
2bfd1b76 107 int contents_indent_step;
108 int contents_margin;
109 int leader_separation;
c6536773 110 int index_gutter;
111 int index_cols;
112 int index_minsep;
9a8dc6b1 113 int pagenum_fontsize;
114 int footer_distance;
dd567011 115 wchar_t *lquote, *rquote, *bullet;
be76d597 116 /* These are derived from the above */
117 int base_width;
118 int page_height;
c6536773 119 int index_colwidth;
be76d597 120 /* Fonts used in the configuration */
121 font_data *tr, *ti, *hr, *hi, *cr, *co, *cb;
122};
123
c6536773 124struct 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
3f3d1acc 139enum {
140 word_PageXref = word_NotWordType + 1
141};
142
43341922 143static font_data *make_std_font(font_list *fontlist, char const *name);
144static void wrap_paragraph(para_data *pdata, word *words,
dd567011 145 int w, int i1, int i2, paper_conf *conf);
43341922 146static page_data *page_breaks(line_data *first, line_data *last,
c6536773 147 int page_height, int ncols, int headspace);
2bfd1b76 148static int render_string(page_data *page, font_data *font, int fontsize,
149 int x, int y, wchar_t *str);
150static int render_line(line_data *ldata, int left_x, int top_y,
dd567011 151 xref_dest *dest, keywordlist *keywords, indexdata *idx,
152 paper_conf *conf);
c6536773 153static void render_para(para_data *pdata, paper_conf *conf,
154 keywordlist *keywords, indexdata *idx,
155 paragraph *index_placeholder, page_data *index_page);
9a8dc6b1 156static int string_width(font_data *font, wchar_t const *string, int *errs);
dd567011 157static int paper_width_simple(para_data *pdata, word *text, paper_conf *conf);
be76d597 158static para_data *code_paragraph(int indent, word *words, paper_conf *conf);
159static para_data *rule_paragraph(int indent, paper_conf *conf);
23765aeb 160static void add_rect_to_page(page_data *page, int x, int y, int w, int h);
2bfd1b76 161static para_data *make_para_data(int ptype, int paux, int indent, int rmargin,
be76d597 162 word *pkwtext, word *pkwtext2, word *pwords,
163 paper_conf *conf);
164static void standard_line_spacing(para_data *pdata, paper_conf *conf);
165static wchar_t *prepare_outline_title(word *first, wchar_t *separator,
166 word *second);
2bfd1b76 167static word *fake_word(wchar_t *text);
c6536773 168static word *fake_space_word(void);
3f3d1acc 169static word *fake_page_ref(page_data *page);
170static word *fake_end_ref(void);
2bfd1b76 171static word *prepare_contents_title(word *first, wchar_t *separator,
172 word *second);
c6536773 173static void fold_into_page(page_data *dest, page_data *src, int right_shift);
43341922 174
dd567011 175static 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
195static 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
43341922 386void *paper_pre_backend(paragraph *sourceform, keywordlist *keywords,
387 indexdata *idx) {
388 paragraph *p;
389 document *doc;
2bfd1b76 390 int indent, used_contents;
be76d597 391 para_data *pdata, *firstpara = NULL, *lastpara = NULL;
2bfd1b76 392 para_data *firstcont, *lastcont;
c6536773 393 line_data *firstline, *lastline, *firstcontline, *lastcontline;
43341922 394 page_data *pages;
395 font_list *fontlist;
dd567011 396 paper_conf *conf, ourconf;
c6536773 397 int has_index;
398 int pagenum;
399 paragraph index_placeholder_para;
400 page_data *first_index_page;
43341922 401
f1530049 402 fontlist = snew(font_list);
43341922 403 fontlist->head = fontlist->tail = NULL;
dd567011 404
405 ourconf = paper_configure(sourceform, fontlist);
406 conf = &ourconf;
43341922 407
408 /*
c6536773 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++) {
f1530049 419 paper_idx *pi = snew(paper_idx);
c6536773 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 /*
2bfd1b76 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 }
c6536773 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 }
2bfd1b76 521 }
522
523 /*
524 * Do the main paragraph formatting.
43341922 525 */
526 indent = 0;
2bfd1b76 527 used_contents = FALSE;
43341922 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:
43341922 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:
be76d597 553 indent += conf->indent_list; break;
43341922 554 case para_LcontPop:
be76d597 555 indent -= conf->indent_list; assert(indent >= 0); break;
43341922 556 case para_QuotePush:
be76d597 557 indent += conf->indent_quote; break;
43341922 558 case para_QuotePop:
be76d597 559 indent -= conf->indent_quote; assert(indent >= 0); break;
43341922 560
561 /*
562 * This paragraph type is special. Process it
563 * specially.
564 */
565 case para_Code:
be76d597 566 pdata = code_paragraph(indent, p->words, conf);
515d216b 567 p->private_data = pdata;
39a0cfb9 568 if (pdata->first != pdata->last) {
569 pdata->first->penalty_after += 100000;
570 pdata->last->penalty_before += 100000;
571 }
43341922 572 break;
573
574 /*
87bd6353 575 * This paragraph is also special.
576 */
577 case para_Rule:
be76d597 578 pdata = rule_paragraph(indent, conf);
87bd6353 579 p->private_data = pdata;
580 break;
581
582 /*
43341922 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:
2bfd1b76 601 pdata = make_para_data(p->type, p->aux, indent, 0,
be76d597 602 p->kwtext, p->kwtext2, p->words, conf);
43341922 603
43341922 604 p->private_data = pdata;
605
515d216b 606 break;
607 }
608
609 if (p->private_data) {
610 pdata = (para_data *)p->private_data;
611
43341922 612 /*
2bfd1b76 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 /*
515d216b 638 * Link all line structures together into a big list.
639 */
43341922 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;
2bfd1b76 649 lastline->next = NULL;
43341922 650 }
be76d597 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;
43341922 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 */
c6536773 668 pages = page_breaks(firstline, lastline, conf->page_height, 0, 0);
43341922 669
670 /*
2bfd1b76 671 * Number the pages.
672 */
673 {
c6536773 674 char buf[40];
2bfd1b76 675 page_data *page;
c6536773 676
677 pagenum = 0;
678
2bfd1b76 679 for (page = pages; page; page = page->next) {
c6536773 680 sprintf(buf, "%d", ++pagenum);
e4ea58f8 681 page->number = ufroma_dup(buf, CS_ASCII);
2bfd1b76 682 }
c6536773 683
684 if (has_index) {
f1530049 685 first_index_page = snew(page_data);
c6536773 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);
e4ea58f8 695 first_index_page->number = ufroma_dup(buf, CS_ASCII);
c6536773 696 }
2bfd1b76 697 }
698
699 /*
43341922 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 */
c6536773 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;
a0768d17 719
c6536773 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
34d8cc1c 735 if (!pi->words)
736 continue;
737
c6536773 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);
a0768d17 758
a0768d17 759 /*
c6536773 760 * If feasible, fold the two halves of the index entry
761 * together.
a0768d17 762 */
c6536773 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;
a0768d17 767 }
768
c6536773 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 }
be76d597 795 }
87bd6353 796
be76d597 797 /*
c6536773 798 * Now break the index into pages.
2bfd1b76 799 */
c6536773 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);
2bfd1b76 808
c6536773 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 }
2bfd1b76 831
c6536773 832 page = page->next;
2bfd1b76 833 }
c6536773 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;
2bfd1b76 846
847 /*
c6536773 848 * Number the index pages, except the already-numbered
849 * first one.
be76d597 850 */
c6536773 851 for (page = ipages->next; page; page = page->next) {
852 char buf[40];
853 sprintf(buf, "%d", ++pagenum);
e4ea58f8 854 page->number = ufroma_dup(buf, CS_ASCII);
43341922 855 }
c6536773 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;
43341922 884 }
885
f0e51ce1 886 /*
9a8dc6b1 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 /*
f0e51ce1 910 * Start putting together the overall document structure we're
911 * going to return.
912 */
f1530049 913 doc = snew(document);
43341922 914 doc->fonts = fontlist;
915 doc->pages = pages;
be76d597 916 doc->paper_width = conf->paper_width;
917 doc->paper_height = conf->paper_height;
f0e51ce1 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
f1530049 928 doc->outline_elements = snewn(osize, outline_element);
f0e51ce1 929 doc->n_outline_elements = 0;
930
931 /* First find the title. */
be76d597 932 for (pdata = firstpara; pdata; pdata = pdata->next) {
933 if (pdata->outline_level == 0) {
f0e51ce1 934 doc->outline_elements[0].level = 0;
be76d597 935 doc->outline_elements[0].pdata = pdata;
f0e51ce1 936 doc->n_outline_elements++;
937 break;
938 }
939 }
940
941 /* Then collect the rest. */
be76d597 942 for (pdata = firstpara; pdata; pdata = pdata->next) {
943 if (pdata->outline_level > 0) {
f0e51ce1 944 if (doc->n_outline_elements >= osize) {
945 osize += 20;
946 doc->outline_elements =
f1530049 947 sresize(doc->outline_elements, osize, outline_element);
f0e51ce1 948 }
949
be76d597 950 doc->outline_elements[doc->n_outline_elements].level =
951 pdata->outline_level;
952 doc->outline_elements[doc->n_outline_elements].pdata = pdata;
f0e51ce1 953 doc->n_outline_elements++;
f0e51ce1 954 }
955 }
956 }
957
43341922 958 return doc;
959}
960
2bfd1b76 961static para_data *make_para_data(int ptype, int paux, int indent, int rmargin,
be76d597 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
f1530049 970 pdata = snew(para_data);
be76d597 971 pdata->outline_level = -1;
972 pdata->outline_title = NULL;
973 pdata->rect_type = RECT_NONE;
2bfd1b76 974 pdata->contents_entry = NULL;
c6536773 975 pdata->justification = JUST;
be76d597 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;
dd567011 1077 len = paper_width_simple(pdata, pkwtext2, conf);
be76d597 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;
2bfd1b76 1084 aux2 = fake_word(L": ");
be76d597 1085 aux_indent = 0;
1086
dd567011 1087 firstline_indent += paper_width_simple(pdata, aux, conf);
1088 firstline_indent += paper_width_simple(pdata, aux2, conf);
be76d597 1089
1090 pdata->outline_title =
1091 prepare_outline_title(pkwtext, L": ", pwords);
1092 }
1093 break;
1094
1095 case para_Bullet:
1096 /*
dd567011 1097 * Auxiliary text consisting of a bullet.
be76d597 1098 */
dd567011 1099 aux = fake_word(conf->bullet);
be76d597 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;
2bfd1b76 1109 aux2 = fake_word(L".");
be76d597 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;
2bfd1b76 1119 aux2 = fake_word(L" ");
be76d597 1120 aux_indent = indent;
dd567011 1121 firstline_indent += paper_width_simple(pdata, aux, conf);
1122 firstline_indent += paper_width_simple(pdata, aux2, conf);
be76d597 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
2bfd1b76 1131 wrap_paragraph(pdata, pwords, conf->base_width - rmargin,
be76d597 1132 indent + firstline_indent,
dd567011 1133 indent + extra_indent, conf);
be76d597 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
1205static 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
43341922 1225static font_encoding *new_font_encoding(font_data *font)
1226{
1227 font_encoding *fe;
1228 int i;
1229
f1530049 1230 fe = snew(font_encoding);
43341922 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
1251static 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
f1530049 1265 f = snew(font_data);
43341922 1266
1267 f->list = fontlist;
1268 f->name = name;
1269 f->nglyphs = nglyphs;
1270 f->glyphs = ps_std_glyphs;
1271 f->widths = widths;
f1530049 1272 f->subfont_map = snewn(nglyphs, subfont_map_entry);
43341922 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
1311static 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
faad4952 1333static int paper_width_internal(void *vctx, word *word, int *nspaces);
43341922 1334
1335struct paper_width_ctx {
1336 int minspacewidth;
1337 para_data *pdata;
dd567011 1338 paper_conf *conf;
43341922 1339};
1340
faad4952 1341static int paper_width_list(void *vctx, word *text, word *end, int *nspaces) {
43341922 1342 int w = 0;
faad4952 1343 while (text && text != end) {
1344 w += paper_width_internal(vctx, text, nspaces);
43341922 1345 text = text->next;
1346 }
1347 return w;
1348}
1349
faad4952 1350static int paper_width_internal(void *vctx, word *word, int *nspaces)
43341922 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:
3f3d1acc 1361 case word_PageXref:
43341922 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) {
faad4952 1377 if (findex != FONT_CODE) {
1378 if (nspaces)
1379 (*nspaces)++;
43341922 1380 return ctx->minspacewidth;
faad4952 1381 } else
43341922 1382 str = L" ";
1383 } else /* if (type == word_Quote) */ {
1384 if (word->aux == quote_Open)
dd567011 1385 str = ctx->conf->lquote;
43341922 1386 else
dd567011 1387 str = ctx->conf->rquote;
43341922 1388 }
1389
1390 width = string_width(ctx->pdata->fonts[findex], str, &errs);
1391
1392 if (errs && word->alt)
faad4952 1393 return paper_width_list(vctx, word->alt, NULL, nspaces);
43341922 1394 else
1395 return ctx->pdata->sizes[findex] * width;
1396}
1397
faad4952 1398static int paper_width(void *vctx, word *word)
1399{
1400 return paper_width_internal(vctx, word, NULL);
1401}
1402
dd567011 1403static int paper_width_simple(para_data *pdata, word *text, paper_conf *conf)
515d216b 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));
dd567011 1411 ctx.conf = conf;
515d216b 1412
1413 return paper_width_list(&ctx, text, NULL, NULL);
1414}
1415
43341922 1416static void wrap_paragraph(para_data *pdata, word *words,
dd567011 1417 int w, int i1, int i2, paper_conf *conf)
43341922 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;
dd567011 1454 ctx.conf = conf;
43341922 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
f1530049 1469 ldata = snew(line_data);
43341922 1470
1471 ldata->pdata = pdata;
1472 ldata->first = p->begin;
faad4952 1473 ldata->end = p->end;
43341922 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
43341922 1488 spaces = 0;
faad4952 1489 len = paper_width_list(&ctx, ldata->first, ldata->end, &spaces);
1490 wid = (p == wrapping ? w - i1 : w - i2);
43341922 1491 wd = ldata->first;
43341922 1492
faad4952 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;
c6536773 1503 ldata->real_shortfall = ldata->hshortfall;
faad4952 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;
43341922 1510
1511 ldata->aux_text = NULL;
515d216b 1512 ldata->aux_text_2 = NULL;
43341922 1513 ldata->aux_left_indent = 0;
39a0cfb9 1514 ldata->penalty_before = ldata->penalty_after = 0;
43341922 1515 }
1516
1517}
1518
1519static page_data *page_breaks(line_data *first, line_data *last,
c6536773 1520 int page_height, int ncols, int headspace)
43341922 1521{
1522 line_data *l, *m;
1523 page_data *ph, *pt;
c6536773 1524 int n, n1, this_height;
43341922 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 *
c6536773 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 *
43341922 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) {
f1530049 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 *);
c6536773 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;
43341922 1584
c6536773 1585 if (m != l && minheight > this_height)
1586 break;
43341922 1587
c6536773 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) {
43341922 1595
c6536773 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 }
43341922 1620
c6536773 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 }
43341922 1638
c6536773 1639 if (m == last)
1640 break;
43341922 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;
c6536773 1652 n = 0;
43341922 1653 while (l) {
1654 page_data *page;
c6536773 1655 int text, space, head;
43341922 1656
f1530049 1657 page = snew(page_data);
43341922 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;
c6536773 1667 page->last_line = l->page_last[n];
43341922 1668
1669 page->first_text = page->last_text = NULL;
138d7ffb 1670 page->first_xref = page->last_xref = NULL;
23765aeb 1671 page->first_rect = page->last_rect = NULL;
138d7ffb 1672
43341922 1673 /*
1674 * Now assign a y-coordinate to each line on the page.
1675 */
1676 text = space = 0;
c6536773 1677 head = (n < ncols ? headspace : 0);
43341922 1678 for (l = page->first_line; l; l = l->next) {
c6536773 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 }
43341922 1691 text += l->line_height;
1692
1693 l->page = page;
c6536773 1694 l->ypos = text + space + head +
1695 space * (float)page->first_line->vshortfall[n] /
1696 page->first_line->space[n];
43341922 1697
1698 if (l == page->last_line)
1699 break;
1700 }
1701
c6536773 1702 l = page->last_line;
1703 if (l == last)
1704 break;
1705 l = l->next;
1706
1707 n = (n < ncols ? n+1 : ncols);
43341922 1708 }
1709
1710 return ph;
1711}
1712
23765aeb 1713static void add_rect_to_page(page_data *page, int x, int y, int w, int h)
1714{
f1530049 1715 rect *r = snew(rect);
23765aeb 1716
1717 r->next = NULL;
1718 if (page->last_rect)
1719 page->last_rect->next = r;
1720 else
1721 page->first_rect = r;
1722 page->last_rect = r;
1723
1724 r->x = x;
1725 r->y = y;
1726 r->w = w;
1727 r->h = h;
1728}
1729
43341922 1730static void add_string_to_page(page_data *page, int x, int y,
7c8c4239 1731 font_encoding *fe, int size, char *text,
1732 int width)
43341922 1733{
1734 text_fragment *frag;
1735
f1530049 1736 frag = snew(text_fragment);
43341922 1737 frag->next = NULL;
1738
1739 if (page->last_text)
1740 page->last_text->next = frag;
1741 else
1742 page->first_text = frag;
1743 page->last_text = frag;
1744
1745 frag->x = x;
1746 frag->y = y;
1747 frag->fe = fe;
1748 frag->fontsize = size;
1749 frag->text = dupstr(text);
7c8c4239 1750 frag->width = width;
43341922 1751}
1752
1753/*
1754 * Returns the updated x coordinate.
1755 */
1756static int render_string(page_data *page, font_data *font, int fontsize,
1757 int x, int y, wchar_t *str)
1758{
1759 char *text;
1760 int textpos, textwid, glyph;
1761 font_encoding *subfont = NULL, *sf;
1762
f1530049 1763 text = snewn(1 + ustrlen(str), char);
43341922 1764 textpos = textwid = 0;
1765
1766 while (*str) {
1767 glyph = font->bmp[*str];
1768
4cc00cdd 1769 if (glyph == 0xFFFF) {
1770 str++;
43341922 1771 continue; /* nothing more we can do here */
4cc00cdd 1772 }
43341922 1773
1774 /*
1775 * Find which subfont this character is going in.
1776 */
1777 sf = font->subfont_map[glyph].subfont;
1778
1779 if (!sf) {
1780 int c;
1781
1782 /*
1783 * This character is not yet in a subfont. Assign one.
1784 */
1785 if (font->latest_subfont->free_pos >= 0x100)
1786 font->latest_subfont = new_font_encoding(font);
1787
1788 c = font->latest_subfont->free_pos++;
1789 if (font->latest_subfont->free_pos == 0x7F)
1790 font->latest_subfont->free_pos = 0xA1;
1791
1792 font->subfont_map[glyph].subfont = font->latest_subfont;
1793 font->subfont_map[glyph].position = c;
1794 font->latest_subfont->vector[c] = font->glyphs[glyph];
1795 font->latest_subfont->indices[c] = glyph;
1796 font->latest_subfont->to_unicode[c] = *str;
1797
1798 sf = font->latest_subfont;
1799 }
1800
1801 if (!subfont || sf != subfont) {
1802 if (subfont) {
1803 text[textpos] = '\0';
7c8c4239 1804 add_string_to_page(page, x, y, subfont, fontsize, text,
1805 textwid);
43341922 1806 x += textwid;
1807 } else {
1808 assert(textpos == 0);
1809 }
1810 textpos = 0;
1811 subfont = sf;
1812 }
1813
1814 text[textpos++] = font->subfont_map[glyph].position;
1815 textwid += font->widths[glyph] * fontsize;
1816
1817 str++;
1818 }
1819
1820 if (textpos > 0) {
1821 text[textpos] = '\0';
7c8c4239 1822 add_string_to_page(page, x, y, subfont, fontsize, text, textwid);
43341922 1823 x += textwid;
1824 }
1825
1826 return x;
1827}
1828
1829/*
1830 * Returns the updated x coordinate.
1831 */
138d7ffb 1832static int render_text(page_data *page, para_data *pdata, line_data *ldata,
1833 int x, int y, word *text, word *text_end, xref **xr,
1834 int shortfall, int nspaces, int *nspace,
dd567011 1835 keywordlist *keywords, indexdata *idx, paper_conf *conf)
43341922 1836{
faad4952 1837 while (text && text != text_end) {
43341922 1838 int style, type, findex, errs;
1839 wchar_t *str;
138d7ffb 1840 xref_dest dest;
43341922 1841
1842 switch (text->type) {
138d7ffb 1843 /*
1844 * Start a cross-reference.
1845 */
43341922 1846 case word_HyperLink:
43341922 1847 case word_UpperXref:
1848 case word_LowerXref:
3f3d1acc 1849 case word_PageXref:
138d7ffb 1850
1851 if (text->type == word_HyperLink) {
1852 dest.type = URL;
e4ea58f8 1853 dest.url = utoa_dup(text->text, CS_ASCII);
138d7ffb 1854 dest.page = NULL;
3f3d1acc 1855 } else if (text->type == word_PageXref) {
1856 dest.type = PAGE;
1857 dest.url = NULL;
1858 dest.page = (page_data *)text->private_data;
138d7ffb 1859 } else {
1860 keyword *kwl = kw_lookup(keywords, text->text);
1861 para_data *pdata;
1862
1863 if (kwl) {
1864 assert(kwl->para->private_data);
1865 pdata = (para_data *) kwl->para->private_data;
1866 dest.type = PAGE;
1867 dest.page = pdata->first->page;
1868 dest.url = NULL;
1869 } else {
1870 /*
1871 * Shouldn't happen, but *shrug*
1872 */
1873 dest.type = NONE;
1874 dest.page = NULL;
1875 dest.url = NULL;
1876 }
1877 }
1878 if (dest.type != NONE) {
f1530049 1879 *xr = snew(xref);
138d7ffb 1880 (*xr)->dest = dest; /* structure copy */
1881 if (page->last_xref)
1882 page->last_xref->next = *xr;
1883 else
1884 page->first_xref = *xr;
1885 page->last_xref = *xr;
23765aeb 1886 (*xr)->next = NULL;
138d7ffb 1887
1888 /*
1889 * FIXME: Ideally we should have, and use, some
1890 * vertical font metric information here so that
1891 * our cross-ref rectangle can take account of
1892 * descenders and the font's cap height. This will
1893 * do for the moment, but it isn't ideal.
1894 */
1895 (*xr)->lx = (*xr)->rx = x;
1896 (*xr)->by = y;
1897 (*xr)->ty = y + ldata->line_height;
1898 }
1899 goto nextword;
1900
1901 /*
1902 * Finish extending a cross-reference box.
1903 */
1904 case word_HyperEnd:
43341922 1905 case word_XrefEnd:
138d7ffb 1906 *xr = NULL;
1907 goto nextword;
1908
43341922 1909 /*
c6536773 1910 * Add the current page number to the list of pages
1911 * referenced by an index entry.
43341922 1912 */
c6536773 1913 case word_IndexRef:
34d8cc1c 1914 /*
1915 * We don't create index references in contents entries.
1916 */
1917 if (!pdata->contents_entry) {
c6536773 1918 indextag *tag;
1919 int i;
1920
1921 tag = index_findtag(idx, text->text);
1922 if (!tag)
1923 goto nextword;
1924
1925 for (i = 0; i < tag->nrefs; i++) {
1926 indexentry *entry = tag->refs[i];
1927 paper_idx *pi = (paper_idx *)entry->backend_data;
1928
1929 /*
1930 * If the same index term is indexed twice
1931 * within the same section, we only want to
1932 * mention it once in the index.
1933 */
1934 if (pi->lastpage != page) {
3f3d1acc 1935 word **wp;
1936
c6536773 1937 if (pi->lastword) {
1938 pi->lastword = pi->lastword->next =
1939 fake_word(L",");
1940 pi->lastword = pi->lastword->next =
1941 fake_space_word();
3f3d1acc 1942 wp = &pi->lastword->next;
1943 } else
1944 wp = &pi->words;
1945
1946 pi->lastword = *wp =
1947 fake_page_ref(page);
1948 pi->lastword = pi->lastword->next =
1949 fake_word(page->number);
1950 pi->lastword = pi->lastword->next =
1951 fake_end_ref();
c6536773 1952 }
1953
1954 pi->lastpage = page;
1955 }
1956 }
1957 goto nextword;
43341922 1958 }
1959
1960 style = towordstyle(text->type);
1961 type = removeattr(text->type);
1962
1963 findex = (style == word_Normal ? FONT_NORMAL :
1964 style == word_Emph ? FONT_EMPH :
1965 FONT_CODE);
1966
1967 if (type == word_Normal) {
1968 str = text->text;
1969 } else if (type == word_WhiteSpace) {
1970 x += pdata->sizes[findex] *
1971 string_width(pdata->fonts[findex], L" ", NULL);
faad4952 1972 if (nspaces && findex != FONT_CODE) {
1973 x += (*nspace+1) * shortfall / nspaces;
1974 x -= *nspace * shortfall / nspaces;
1975 (*nspace)++;
1976 }
43341922 1977 goto nextword;
1978 } else /* if (type == word_Quote) */ {
1979 if (text->aux == quote_Open)
dd567011 1980 str = conf->lquote;
43341922 1981 else
dd567011 1982 str = conf->rquote;
43341922 1983 }
1984
1985 (void) string_width(pdata->fonts[findex], str, &errs);
1986
1987 if (errs && text->alt)
138d7ffb 1988 x = render_text(page, pdata, ldata, x, y, text->alt, NULL,
dd567011 1989 xr, shortfall, nspaces, nspace, keywords, idx,
1990 conf);
43341922 1991 else
1992 x = render_string(page, pdata->fonts[findex],
1993 pdata->sizes[findex], x, y, str);
1994
138d7ffb 1995 if (*xr)
1996 (*xr)->rx = x;
1997
43341922 1998 nextword:
43341922 1999 text = text->next;
2000 }
2001
2002 return x;
2003}
2004
2bfd1b76 2005/*
2006 * Returns the last x position used on the line.
2007 */
2008static int render_line(line_data *ldata, int left_x, int top_y,
dd567011 2009 xref_dest *dest, keywordlist *keywords, indexdata *idx,
2010 paper_conf *conf)
43341922 2011{
faad4952 2012 int nspace;
138d7ffb 2013 xref *xr;
2bfd1b76 2014 int ret = 0;
138d7ffb 2015
faad4952 2016 if (ldata->aux_text) {
515d216b 2017 int x;
138d7ffb 2018 xr = NULL;
faad4952 2019 nspace = 0;
515d216b 2020 x = render_text(ldata->page, ldata->pdata, ldata,
2021 left_x + ldata->aux_left_indent,
2022 top_y - ldata->ypos,
c6536773 2023 ldata->aux_text, NULL, &xr, 0, 0, &nspace,
dd567011 2024 keywords, idx, conf);
515d216b 2025 if (ldata->aux_text_2)
2026 render_text(ldata->page, ldata->pdata, ldata,
2027 x, top_y - ldata->ypos,
c6536773 2028 ldata->aux_text_2, NULL, &xr, 0, 0, &nspace,
dd567011 2029 keywords, idx, conf);
faad4952 2030 }
2031 nspace = 0;
138d7ffb 2032
87bd6353 2033 if (ldata->first) {
138d7ffb 2034 /*
87bd6353 2035 * There might be a cross-reference carried over from a
2036 * previous line.
138d7ffb 2037 */
87bd6353 2038 if (dest->type != NONE) {
f1530049 2039 xr = snew(xref);
87bd6353 2040 xr->next = NULL;
2041 xr->dest = *dest; /* structure copy */
2042 if (ldata->page->last_xref)
2043 ldata->page->last_xref->next = xr;
2044 else
2045 ldata->page->first_xref = xr;
2046 ldata->page->last_xref = xr;
2047 xr->lx = xr->rx = left_x + ldata->xpos;
2048 xr->by = top_y - ldata->ypos;
2049 xr->ty = top_y - ldata->ypos + ldata->line_height;
2050 } else
2051 xr = NULL;
2052
c6536773 2053 {
2054 int extra_indent, shortfall, spaces;
2055 int just = ldata->pdata->justification;
2056
2057 /*
2058 * All forms of justification become JUST when we have
2059 * to squeeze the paragraph.
2060 */
2061 if (ldata->hshortfall < 0)
2062 just = JUST;
2063
2064 switch (just) {
2065 case JUST:
2066 shortfall = ldata->hshortfall;
2067 spaces = ldata->nspaces;
2068 extra_indent = 0;
2069 break;
2070 case LEFT:
2071 shortfall = spaces = extra_indent = 0;
2072 break;
2073 case RIGHT:
2074 shortfall = spaces = 0;
2075 extra_indent = ldata->real_shortfall;
2076 break;
2077 }
2078
2079 ret = render_text(ldata->page, ldata->pdata, ldata,
2080 left_x + ldata->xpos + extra_indent,
2081 top_y - ldata->ypos, ldata->first, ldata->end,
2082 &xr, shortfall, spaces, &nspace,
dd567011 2083 keywords, idx, conf);
c6536773 2084 }
87bd6353 2085
2086 if (xr) {
2087 /*
2088 * There's a cross-reference continued on to the next line.
2089 */
2090 *dest = xr->dest;
2091 } else
2092 dest->type = NONE;
2093 }
2bfd1b76 2094
2095 return ret;
43341922 2096}
515d216b 2097
c6536773 2098static void render_para(para_data *pdata, paper_conf *conf,
2099 keywordlist *keywords, indexdata *idx,
2100 paragraph *index_placeholder, page_data *index_page)
2101{
2102 int last_x;
2103 xref *cxref;
2104 page_data *cxref_page;
2105 xref_dest dest;
2106 para_data *target;
2107 line_data *ldata;
2108
2109 dest.type = NONE;
2110 cxref = NULL;
2111 cxref_page = NULL;
2112
2113 for (ldata = pdata->first; ldata; ldata = ldata->next) {
2114 /*
2115 * If this is a contents entry, we expect to have a single
2116 * enormous cross-reference rectangle covering the whole
2117 * thing. (Unless, of course, it spans multiple pages.)
2118 */
2119 if (pdata->contents_entry && ldata->page != cxref_page) {
2120 cxref_page = ldata->page;
f1530049 2121 cxref = snew(xref);
c6536773 2122 cxref->next = NULL;
2123 cxref->dest.type = PAGE;
2124 if (pdata->contents_entry == index_placeholder) {
2125 cxref->dest.page = index_page;
2126 } else {
2127 assert(pdata->contents_entry->private_data);
2128 target = (para_data *)pdata->contents_entry->private_data;
2129 cxref->dest.page = target->first->page;
2130 }
2131 cxref->dest.url = NULL;
2132 if (ldata->page->last_xref)
2133 ldata->page->last_xref->next = cxref;
2134 else
2135 ldata->page->first_xref = cxref;
2136 ldata->page->last_xref = cxref;
2137 cxref->lx = conf->left_margin;
2138 cxref->rx = conf->paper_width - conf->right_margin;
2139 cxref->ty = conf->paper_height - conf->top_margin
2140 - ldata->ypos + ldata->line_height;
2141 }
2142 if (pdata->contents_entry) {
2143 assert(cxref != NULL);
2144 cxref->by = conf->paper_height - conf->top_margin
2145 - ldata->ypos;
2146 }
2147
2148 last_x = render_line(ldata, conf->left_margin,
2149 conf->paper_height - conf->top_margin,
dd567011 2150 &dest, keywords, idx, conf);
c6536773 2151 if (ldata == pdata->last)
2152 break;
2153 }
2154
2155 /*
2156 * If this is a contents entry, add leaders and a page
2157 * number.
2158 */
2159 if (pdata->contents_entry) {
2160 word *w;
2161 wchar_t *num;
2162 int wid;
2163 int x;
2164
2165 if (pdata->contents_entry == index_placeholder) {
2166 num = index_page->number;
2167 } else {
2168 assert(pdata->contents_entry->private_data);
2169 target = (para_data *)pdata->contents_entry->private_data;
2170 num = target->first->page->number;
2171 }
2172
2173 w = fake_word(num);
dd567011 2174 wid = paper_width_simple(pdata, w, conf);
c6536773 2175 sfree(w);
2176
c6536773 2177 for (x = 0; x < conf->base_width; x += conf->leader_separation)
2178 if (x - conf->leader_separation > last_x - conf->left_margin &&
2179 x + conf->leader_separation < conf->base_width - wid)
2180 render_string(pdata->last->page,
2181 pdata->fonts[FONT_NORMAL],
2182 pdata->sizes[FONT_NORMAL],
2183 conf->left_margin + x,
2184 (conf->paper_height - conf->top_margin -
2185 pdata->last->ypos), L".");
adbcaa16 2186
2187 render_string(pdata->last->page,
2188 pdata->fonts[FONT_NORMAL],
2189 pdata->sizes[FONT_NORMAL],
2190 conf->paper_width - conf->right_margin - wid,
2191 (conf->paper_height - conf->top_margin -
2192 pdata->last->ypos), num);
c6536773 2193 }
2194
2195 /*
2196 * Render any rectangle (chapter title underline or rule)
2197 * that goes with this paragraph.
2198 */
2199 switch (pdata->rect_type) {
2200 case RECT_CHAPTER_UNDERLINE:
2201 add_rect_to_page(pdata->last->page,
2202 conf->left_margin,
2203 (conf->paper_height - conf->top_margin -
2204 pdata->last->ypos -
2205 conf->chapter_underline_depth),
2206 conf->base_width,
2207 conf->chapter_underline_thickness);
2208 break;
2209 case RECT_RULE:
2210 add_rect_to_page(pdata->first->page,
2211 conf->left_margin + pdata->first->xpos,
2212 (conf->paper_height - conf->top_margin -
2213 pdata->last->ypos -
2214 pdata->last->line_height),
2215 conf->base_width - pdata->first->xpos,
2216 pdata->last->line_height);
2217 break;
2218 default: /* placate gcc */
2219 break;
2220 }
2221}
2222
be76d597 2223static para_data *code_paragraph(int indent, word *words, paper_conf *conf)
515d216b 2224{
f1530049 2225 para_data *pdata = snew(para_data);
be76d597 2226
515d216b 2227 /*
2228 * For code paragraphs, I'm going to hack grievously and
2229 * pretend the three normal fonts are the three code paragraph
2230 * fonts.
2231 */
be76d597 2232 pdata->fonts[FONT_NORMAL] = conf->cb;
2233 pdata->fonts[FONT_EMPH] = conf->co;
36e64ed4 2234 pdata->fonts[FONT_CODE] = conf->cr;
515d216b 2235 pdata->sizes[FONT_NORMAL] =
2236 pdata->sizes[FONT_EMPH] =
be76d597 2237 pdata->sizes[FONT_CODE] = 12;
515d216b 2238
2239 pdata->first = pdata->last = NULL;
be76d597 2240 pdata->outline_level = -1;
2241 pdata->rect_type = RECT_NONE;
2bfd1b76 2242 pdata->contents_entry = NULL;
c6536773 2243 pdata->justification = LEFT;
515d216b 2244
2245 for (; words; words = words->next) {
2246 wchar_t *t, *e, *start;
2247 word *lhead = NULL, *ltail = NULL, *w;
2248 line_data *ldata;
2249 int prev = -1, curr;
2250
2251 t = words->text;
2252 if (words->next && words->next->type == word_Emph) {
2253 e = words->next->text;
2254 words = words->next;
2255 } else
2256 e = NULL;
2257
2258 start = t;
2259
2260 while (*start) {
2261 while (*t) {
2262 if (!e || !*e)
2263 curr = 0;
2264 else if (*e == L'i')
2265 curr = 1;
2266 else if (*e == L'b')
2267 curr = 2;
2268 else
2269 curr = 0;
2270
2271 if (prev < 0)
2272 prev = curr;
2273
2274 if (curr != prev)
2275 break;
2276
2277 t++;
2278 if (e && *e)
2279 e++;
2280 }
2281
2282 /*
2283 * We've isolated a maximal subsequence of the line
2284 * which has the same emphasis. Form it into a word
2285 * structure.
2286 */
f1530049 2287 w = snew(word);
515d216b 2288 w->next = NULL;
2289 w->alt = NULL;
2290 w->type = (prev == 0 ? word_WeakCode :
2291 prev == 1 ? word_Emph : word_Normal);
f1530049 2292 w->text = snewn(t-start+1, wchar_t);
515d216b 2293 memcpy(w->text, start, (t-start) * sizeof(wchar_t));
2294 w->text[t-start] = '\0';
2295 w->breaks = FALSE;
2296
2297 if (ltail)
2298 ltail->next = w;
2299 else
2300 lhead = w;
2301 ltail = w;
2302
2303 start = t;
2304 prev = -1;
2305 }
2306
f1530049 2307 ldata = snew(line_data);
515d216b 2308
2309 ldata->pdata = pdata;
2310 ldata->first = lhead;
2311 ldata->end = NULL;
be76d597 2312 ldata->line_height = conf->base_font_size * 4096;
515d216b 2313
2314 ldata->xpos = indent;
2315
2316 if (pdata->last) {
2317 pdata->last->next = ldata;
2318 ldata->prev = pdata->last;
2319 } else {
2320 pdata->first = ldata;
2321 ldata->prev = NULL;
2322 }
2323 ldata->next = NULL;
2324 pdata->last = ldata;
2325
2326 ldata->hshortfall = 0;
2327 ldata->nspaces = 0;
2328 ldata->aux_text = NULL;
2329 ldata->aux_text_2 = NULL;
2330 ldata->aux_left_indent = 0;
39a0cfb9 2331 /* General opprobrium for breaking in a code paragraph. */
2332 ldata->penalty_before = ldata->penalty_after = 50000;
515d216b 2333 }
be76d597 2334
2335 standard_line_spacing(pdata, conf);
2336
2337 return pdata;
515d216b 2338}
87bd6353 2339
be76d597 2340static para_data *rule_paragraph(int indent, paper_conf *conf)
87bd6353 2341{
f1530049 2342 para_data *pdata = snew(para_data);
87bd6353 2343 line_data *ldata;
2344
f1530049 2345 ldata = snew(line_data);
87bd6353 2346
2347 ldata->pdata = pdata;
2348 ldata->first = NULL;
2349 ldata->end = NULL;
be76d597 2350 ldata->line_height = conf->rule_thickness;
87bd6353 2351
2352 ldata->xpos = indent;
2353
2354 ldata->prev = NULL;
2355 ldata->next = NULL;
2356
2357 ldata->hshortfall = 0;
2358 ldata->nspaces = 0;
2359 ldata->aux_text = NULL;
2360 ldata->aux_text_2 = NULL;
2361 ldata->aux_left_indent = 0;
2362
2363 /*
2364 * Better to break after a rule than before it
2365 */
2366 ldata->penalty_after += 100000;
2367 ldata->penalty_before += -100000;
2368
2369 pdata->first = pdata->last = ldata;
be76d597 2370 pdata->outline_level = -1;
2371 pdata->rect_type = RECT_RULE;
2bfd1b76 2372 pdata->contents_entry = NULL;
c6536773 2373 pdata->justification = LEFT;
be76d597 2374
2375 standard_line_spacing(pdata, conf);
2376
2377 return pdata;
2378}
2379
2380/*
2381 * Plain-text-like formatting for outline titles.
2382 */
2383static void paper_rdaddw(rdstring *rs, word *text) {
2384 for (; text; text = text->next) switch (text->type) {
2385 case word_HyperLink:
2386 case word_HyperEnd:
2387 case word_UpperXref:
2388 case word_LowerXref:
2389 case word_XrefEnd:
2390 case word_IndexRef:
2391 break;
2392
2393 case word_Normal:
2394 case word_Emph:
2395 case word_Code:
2396 case word_WeakCode:
2397 case word_WhiteSpace:
2398 case word_EmphSpace:
2399 case word_CodeSpace:
2400 case word_WkCodeSpace:
2401 case word_Quote:
2402 case word_EmphQuote:
2403 case word_CodeQuote:
2404 case word_WkCodeQuote:
2405 assert(text->type != word_CodeQuote &&
2406 text->type != word_WkCodeQuote);
2407 if (towordstyle(text->type) == word_Emph &&
2408 (attraux(text->aux) == attr_First ||
2409 attraux(text->aux) == attr_Only))
2410 rdadd(rs, L'_'); /* FIXME: configurability */
2411 else if (towordstyle(text->type) == word_Code &&
2412 (attraux(text->aux) == attr_First ||
2413 attraux(text->aux) == attr_Only))
2414 rdadd(rs, L'\''); /* FIXME: configurability */
2415 if (removeattr(text->type) == word_Normal) {
2416 rdadds(rs, text->text);
2417 } else if (removeattr(text->type) == word_WhiteSpace) {
2418 rdadd(rs, L' ');
2419 } else if (removeattr(text->type) == word_Quote) {
2420 rdadd(rs, L'\''); /* fixme: configurability */
2421 }
2422 if (towordstyle(text->type) == word_Emph &&
2423 (attraux(text->aux) == attr_Last ||
2424 attraux(text->aux) == attr_Only))
2425 rdadd(rs, L'_'); /* FIXME: configurability */
2426 else if (towordstyle(text->type) == word_Code &&
2427 (attraux(text->aux) == attr_Last ||
2428 attraux(text->aux) == attr_Only))
2429 rdadd(rs, L'\''); /* FIXME: configurability */
2430 break;
2431 }
2432}
2433
2434static wchar_t *prepare_outline_title(word *first, wchar_t *separator,
2435 word *second)
2436{
2437 rdstring rs = {0, 0, NULL};
2438
2439 if (first)
2440 paper_rdaddw(&rs, first);
2441 if (separator)
2442 rdadds(&rs, separator);
2443 if (second)
2444 paper_rdaddw(&rs, second);
2445
2446 return rs.text;
87bd6353 2447}
2bfd1b76 2448
2449static word *fake_word(wchar_t *text)
2450{
f1530049 2451 word *ret = snew(word);
2bfd1b76 2452 ret->next = NULL;
2453 ret->alt = NULL;
2454 ret->type = word_Normal;
2455 ret->text = ustrdup(text);
2456 ret->breaks = FALSE;
2457 ret->aux = 0;
2458 return ret;
2459}
2460
c6536773 2461static word *fake_space_word(void)
2462{
f1530049 2463 word *ret = snew(word);
c6536773 2464 ret->next = NULL;
2465 ret->alt = NULL;
2466 ret->type = word_WhiteSpace;
2467 ret->text = NULL;
2468 ret->breaks = TRUE;
2469 ret->aux = 0;
2470 return ret;
2471}
2472
3f3d1acc 2473static word *fake_page_ref(page_data *page)
2474{
f1530049 2475 word *ret = snew(word);
3f3d1acc 2476 ret->next = NULL;
2477 ret->alt = NULL;
2478 ret->type = word_PageXref;
2479 ret->text = NULL;
2480 ret->breaks = FALSE;
2481 ret->aux = 0;
2482 ret->private_data = page;
2483 return ret;
2484}
2485
2486static word *fake_end_ref(void)
2487{
f1530049 2488 word *ret = snew(word);
3f3d1acc 2489 ret->next = NULL;
2490 ret->alt = NULL;
2491 ret->type = word_XrefEnd;
2492 ret->text = NULL;
2493 ret->breaks = FALSE;
2494 ret->aux = 0;
2495 return ret;
2496}
2497
2bfd1b76 2498static word *prepare_contents_title(word *first, wchar_t *separator,
2499 word *second)
2500{
2501 word *ret;
2502 word **wptr, *w;
2503
2504 wptr = &ret;
2505
2506 if (first) {
2507 w = dup_word_list(first);
2508 *wptr = w;
2509 while (w->next)
2510 w = w->next;
2511 wptr = &w->next;
2512 }
2513
2514 if (separator) {
2515 w = fake_word(separator);
2516 *wptr = w;
2517 wptr = &w->next;
2518 }
2519
2520 if (second) {
2521 *wptr = dup_word_list(second);
2522 }
2523
2524 return ret;
2525}
c6536773 2526
2527static void fold_into_page(page_data *dest, page_data *src, int right_shift)
2528{
2529 line_data *ldata;
2530
2531 if (!src->first_line)
2532 return;
2533
2534 if (dest->last_line) {
2535 dest->last_line->next = src->first_line;
2536 src->first_line->prev = dest->last_line;
2537 }
2538 dest->last_line = src->last_line;
2539
2540 for (ldata = src->first_line; ldata; ldata = ldata->next) {
2541 ldata->page = dest;
2542 ldata->xpos += right_shift;
2543
2544 if (ldata == src->last_line)
2545 break;
2546 }
2547}