Stupid bug: "t" was leaving the y-coordinate on the stack, which confused
[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 */
17c71b41 202 ret.paper_width = 595 * UNITS_PER_PT;
203 ret.paper_height = 841 * UNITS_PER_PT;
204 ret.left_margin = 72 * UNITS_PER_PT;
205 ret.top_margin = 72 * UNITS_PER_PT;
206 ret.right_margin = 72 * UNITS_PER_PT;
207 ret.bottom_margin = 108 * UNITS_PER_PT;
208 ret.indent_list_bullet = 6 * UNITS_PER_PT;
209 ret.indent_list_after = 18 * UNITS_PER_PT;
210 ret.indent_quote = 18 * UNITS_PER_PT;
211 ret.base_leading = UNITS_PER_PT;
212 ret.base_para_spacing = 10 * UNITS_PER_PT;
213 ret.chapter_top_space = 72 * UNITS_PER_PT;
214 ret.sect_num_left_space = 12 * UNITS_PER_PT;
215 ret.chapter_underline_depth = 14 * UNITS_PER_PT;
216 ret.chapter_underline_thickness = 3 * UNITS_PER_PT;
217 ret.rule_thickness = 1 * UNITS_PER_PT;
dd567011 218 ret.base_font_size = 12;
17c71b41 219 ret.contents_indent_step = 24 * UNITS_PER_PT;
220 ret.contents_margin = 84 * UNITS_PER_PT;
221 ret.leader_separation = 12 * UNITS_PER_PT;
222 ret.index_gutter = 36 * UNITS_PER_PT;
dd567011 223 ret.index_cols = 2;
17c71b41 224 ret.index_minsep = 18 * UNITS_PER_PT;
dd567011 225 ret.pagenum_fontsize = 12;
17c71b41 226 ret.footer_distance = 32 * UNITS_PER_PT;
dd567011 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 =
17c71b41 260 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 261 } else if (!ustricmp(p->keyword, L"paper-page-height")) {
262 ret.paper_height =
17c71b41 263 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 264 } else if (!ustricmp(p->keyword, L"paper-left-margin")) {
265 ret.left_margin =
17c71b41 266 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 267 } else if (!ustricmp(p->keyword, L"paper-top-margin")) {
268 ret.top_margin =
17c71b41 269 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 270 } else if (!ustricmp(p->keyword, L"paper-right-margin")) {
271 ret.right_margin =
17c71b41 272 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 273 } else if (!ustricmp(p->keyword, L"paper-bottom-margin")) {
274 ret.bottom_margin =
17c71b41 275 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 276 } else if (!ustricmp(p->keyword, L"paper-list-indent")) {
277 ret.indent_list_bullet =
17c71b41 278 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 279 } else if (!ustricmp(p->keyword, L"paper-listitem-indent")) {
280 ret.indent_list =
17c71b41 281 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 282 } else if (!ustricmp(p->keyword, L"paper-quote-indent")) {
283 ret.indent_quote =
17c71b41 284 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 285 } else if (!ustricmp(p->keyword, L"paper-base-leading")) {
286 ret.base_leading =
17c71b41 287 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 288 } else if (!ustricmp(p->keyword, L"paper-base-para-spacing")) {
289 ret.base_para_spacing =
17c71b41 290 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 291 } else if (!ustricmp(p->keyword, L"paper-chapter-top-space")) {
292 ret.chapter_top_space =
17c71b41 293 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 294 } else if (!ustricmp(p->keyword, L"paper-sect-num-left-space")) {
295 ret.sect_num_left_space =
17c71b41 296 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 297 } else if (!ustricmp(p->keyword, L"paper-chapter-underline-depth")) {
298 ret.chapter_underline_depth =
17c71b41 299 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 300 } else if (!ustricmp(p->keyword, L"paper-chapter-underline-thickness")) {
301 ret.chapter_underline_thickness =
17c71b41 302 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 303 } else if (!ustricmp(p->keyword, L"paper-rule-thickness")) {
304 ret.rule_thickness =
17c71b41 305 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 306 } else if (!ustricmp(p->keyword, L"paper-contents-indent-step")) {
307 ret.contents_indent_step =
17c71b41 308 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 309 } else if (!ustricmp(p->keyword, L"paper-contents-margin")) {
310 ret.contents_margin =
17c71b41 311 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 312 } else if (!ustricmp(p->keyword, L"paper-leader-separation")) {
313 ret.leader_separation =
17c71b41 314 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 315 } else if (!ustricmp(p->keyword, L"paper-index-gutter")) {
316 ret.index_gutter =
17c71b41 317 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 318 } else if (!ustricmp(p->keyword, L"paper-index-minsep")) {
319 ret.index_minsep =
17c71b41 320 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 321 } else if (!ustricmp(p->keyword, L"paper-footer-distance")) {
322 ret.footer_distance =
17c71b41 323 (int) 0.5 + FUNITS_PER_PT * utof(uadv(p->keyword));
dd567011 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
5333269a 1321 index = (*string < 0 || *string > 0xFFFF ? 0xFFFF :
1322 font->bmp[*string]);
1323
43341922 1324 if (index == 0xFFFF) {
1325 if (errs)
1326 *errs = 1;
1327 } else {
1328 width += font->widths[index];
1329 }
1330 }
1331
1332 return width;
1333}
1334
faad4952 1335static int paper_width_internal(void *vctx, word *word, int *nspaces);
43341922 1336
1337struct paper_width_ctx {
1338 int minspacewidth;
1339 para_data *pdata;
dd567011 1340 paper_conf *conf;
43341922 1341};
1342
faad4952 1343static int paper_width_list(void *vctx, word *text, word *end, int *nspaces) {
43341922 1344 int w = 0;
faad4952 1345 while (text && text != end) {
1346 w += paper_width_internal(vctx, text, nspaces);
43341922 1347 text = text->next;
1348 }
1349 return w;
1350}
1351
faad4952 1352static int paper_width_internal(void *vctx, word *word, int *nspaces)
43341922 1353{
1354 struct paper_width_ctx *ctx = (struct paper_width_ctx *)vctx;
1355 int style, type, findex, width, errs;
1356 wchar_t *str;
1357
1358 switch (word->type) {
1359 case word_HyperLink:
1360 case word_HyperEnd:
1361 case word_UpperXref:
1362 case word_LowerXref:
3f3d1acc 1363 case word_PageXref:
43341922 1364 case word_XrefEnd:
1365 case word_IndexRef:
1366 return 0;
1367 }
1368
1369 style = towordstyle(word->type);
1370 type = removeattr(word->type);
1371
1372 findex = (style == word_Normal ? FONT_NORMAL :
1373 style == word_Emph ? FONT_EMPH :
1374 FONT_CODE);
1375
1376 if (type == word_Normal) {
1377 str = word->text;
1378 } else if (type == word_WhiteSpace) {
faad4952 1379 if (findex != FONT_CODE) {
1380 if (nspaces)
1381 (*nspaces)++;
43341922 1382 return ctx->minspacewidth;
faad4952 1383 } else
43341922 1384 str = L" ";
1385 } else /* if (type == word_Quote) */ {
1386 if (word->aux == quote_Open)
dd567011 1387 str = ctx->conf->lquote;
43341922 1388 else
dd567011 1389 str = ctx->conf->rquote;
43341922 1390 }
1391
1392 width = string_width(ctx->pdata->fonts[findex], str, &errs);
1393
1394 if (errs && word->alt)
faad4952 1395 return paper_width_list(vctx, word->alt, NULL, nspaces);
43341922 1396 else
1397 return ctx->pdata->sizes[findex] * width;
1398}
1399
faad4952 1400static int paper_width(void *vctx, word *word)
1401{
1402 return paper_width_internal(vctx, word, NULL);
1403}
1404
dd567011 1405static int paper_width_simple(para_data *pdata, word *text, paper_conf *conf)
515d216b 1406{
1407 struct paper_width_ctx ctx;
1408
1409 ctx.pdata = pdata;
1410 ctx.minspacewidth =
1411 (pdata->sizes[FONT_NORMAL] *
1412 string_width(pdata->fonts[FONT_NORMAL], L" ", NULL));
dd567011 1413 ctx.conf = conf;
515d216b 1414
1415 return paper_width_list(&ctx, text, NULL, NULL);
1416}
1417
43341922 1418static void wrap_paragraph(para_data *pdata, word *words,
dd567011 1419 int w, int i1, int i2, paper_conf *conf)
43341922 1420{
1421 wrappedline *wrapping, *p;
1422 int spacewidth;
1423 struct paper_width_ctx ctx;
1424 int line_height;
1425
1426 /*
1427 * We're going to need to store the line height in every line
1428 * structure we generate.
1429 */
1430 {
1431 int i;
1432 line_height = 0;
1433 for (i = 0; i < NFONTS; i++)
1434 if (line_height < pdata->sizes[i])
1435 line_height = pdata->sizes[i];
17c71b41 1436 line_height *= UNITS_PER_PT;
43341922 1437 }
1438
1439 spacewidth = (pdata->sizes[FONT_NORMAL] *
1440 string_width(pdata->fonts[FONT_NORMAL], L" ", NULL));
1441 if (spacewidth == 0) {
1442 /*
1443 * A font without a space?! Disturbing. I hope this never
1444 * comes up, but I'll make a random guess anyway and set my
1445 * space width to half the point size.
1446 */
17c71b41 1447 spacewidth = pdata->sizes[FONT_NORMAL] * UNITS_PER_PT / 2;
43341922 1448 }
1449
1450 /*
1451 * I'm going to set the _minimum_ space width to 3/5 of the
1452 * standard one, and use the standard one as the optimum.
1453 */
1454 ctx.minspacewidth = spacewidth * 3 / 5;
1455 ctx.pdata = pdata;
dd567011 1456 ctx.conf = conf;
43341922 1457
1458 wrapping = wrap_para(words, w - i1, w - i2, paper_width, &ctx, spacewidth);
1459
1460 /*
1461 * Having done the wrapping, we now concoct a set of line_data
1462 * structures.
1463 */
1464 pdata->first = pdata->last = NULL;
1465
1466 for (p = wrapping; p; p = p->next) {
1467 line_data *ldata;
1468 word *wd;
1469 int len, wid, spaces;
1470
f1530049 1471 ldata = snew(line_data);
43341922 1472
1473 ldata->pdata = pdata;
1474 ldata->first = p->begin;
faad4952 1475 ldata->end = p->end;
43341922 1476 ldata->line_height = line_height;
1477
1478 ldata->xpos = (p == wrapping ? i1 : i2);
1479
1480 if (pdata->last) {
1481 pdata->last->next = ldata;
1482 ldata->prev = pdata->last;
1483 } else {
1484 pdata->first = ldata;
1485 ldata->prev = NULL;
1486 }
1487 ldata->next = NULL;
1488 pdata->last = ldata;
1489
43341922 1490 spaces = 0;
faad4952 1491 len = paper_width_list(&ctx, ldata->first, ldata->end, &spaces);
1492 wid = (p == wrapping ? w - i1 : w - i2);
43341922 1493 wd = ldata->first;
43341922 1494
faad4952 1495 ldata->hshortfall = wid - len;
1496 ldata->nspaces = spaces;
1497 /*
1498 * This tells us how much the space width needs to
1499 * change from _min_spacewidth. But we want to store
1500 * its difference from the _natural_ space width, to
1501 * make the text rendering easier.
1502 */
1503 ldata->hshortfall += ctx.minspacewidth * spaces;
1504 ldata->hshortfall -= spacewidth * spaces;
c6536773 1505 ldata->real_shortfall = ldata->hshortfall;
faad4952 1506 /*
1507 * Special case: on the last line of a paragraph, we
1508 * never stretch spaces.
1509 */
1510 if (ldata->hshortfall > 0 && !p->next)
1511 ldata->hshortfall = 0;
43341922 1512
1513 ldata->aux_text = NULL;
515d216b 1514 ldata->aux_text_2 = NULL;
43341922 1515 ldata->aux_left_indent = 0;
39a0cfb9 1516 ldata->penalty_before = ldata->penalty_after = 0;
43341922 1517 }
1518
1519}
1520
1521static page_data *page_breaks(line_data *first, line_data *last,
c6536773 1522 int page_height, int ncols, int headspace)
43341922 1523{
1524 line_data *l, *m;
1525 page_data *ph, *pt;
c6536773 1526 int n, n1, this_height;
43341922 1527
1528 /*
1529 * Page breaking is done by a close analogue of the optimal
1530 * paragraph wrapping algorithm used by wrap_para(). We work
1531 * backwards from the end of the document line by line; for
1532 * each line, we contemplate every possible number of lines we
1533 * could put on a page starting with that line, determine a
1534 * cost function for each one, add it to the pre-computed cost
1535 * function for optimally page-breaking everything after that
1536 * page, and pick the best option.
1537 *
c6536773 1538 * This is made slightly more complex by the fact that we have
1539 * a multi-column index with a heading at the top of the
1540 * _first_ page, meaning that the first _ncols_ pages must have
1541 * a different length. Hence, we must do the wrapping ncols+1
1542 * times over, hypothetically trying to put every subsequence
1543 * on every possible page.
1544 *
43341922 1545 * Since my line_data structures are only used for this
1546 * purpose, I might as well just store the algorithm data
1547 * directly in them.
1548 */
1549
1550 for (l = last; l; l = l->prev) {
f1530049 1551 l->bestcost = snewn(ncols+1, int);
1552 l->vshortfall = snewn(ncols+1, int);
1553 l->text = snewn(ncols+1, int);
1554 l->space = snewn(ncols+1, int);
1555 l->page_last = snewn(ncols+1, line_data *);
c6536773 1556
1557 for (n = 0; n <= ncols; n++) {
1558 int minheight, text = 0, space = 0;
1559 int cost;
1560
1561 n1 = (n < ncols ? n+1 : ncols);
1562 if (n < ncols)
1563 this_height = page_height - headspace;
1564 else
1565 this_height = page_height;
1566
1567 l->bestcost[n] = -1;
1568 for (m = l; m; m = m->next) {
1569 if (m != l && m->page_break)
1570 break; /* we've gone as far as we can */
1571
1572 if (m != l) {
1573 if (m->prev->space_after > 0)
1574 space += m->prev->space_after;
1575 else
1576 text += m->prev->space_after;
1577 }
1578 if (m != l || m->page_break) {
1579 if (m->space_before > 0)
1580 space += m->space_before;
1581 else
1582 text += m->space_before;
1583 }
1584 text += m->line_height;
1585 minheight = text + space;
43341922 1586
c6536773 1587 if (m != l && minheight > this_height)
1588 break;
43341922 1589
c6536773 1590 /*
1591 * If the space after this paragraph is _negative_
1592 * (which means the next line is folded on to this
1593 * one, which happens in the index), we absolutely
1594 * cannot break here.
1595 */
1596 if (m->space_after >= 0) {
43341922 1597
c6536773 1598 /*
1599 * Compute the cost of this arrangement, as the
1600 * square of the amount of wasted space on the
1601 * page. Exception: if this is the last page
1602 * before a mandatory break or the document
1603 * end, we don't penalise a large blank area.
1604 */
1605 if (m != last && m->next && !m->next->page_break)
1606 {
1607 int x = this_height - minheight;
1608 int xf;
1609
1610 xf = x & 0xFF;
1611 x >>= 8;
1612
1613 cost = x*x;
1614 cost += (x * xf) >> 8;
1615 } else
1616 cost = 0;
1617
1618 if (m != last && m->next && !m->next->page_break) {
1619 cost += m->penalty_after;
1620 cost += m->next->penalty_before;
1621 }
43341922 1622
c6536773 1623 if (m != last && m->next && !m->next->page_break)
1624 cost += m->next->bestcost[n1];
1625 if (l->bestcost[n] == -1 || l->bestcost[n] > cost) {
1626 /*
1627 * This is the best option yet for this
1628 * starting point.
1629 */
1630 l->bestcost[n] = cost;
1631 if (m != last && m->next && !m->next->page_break)
1632 l->vshortfall[n] = this_height - minheight;
1633 else
1634 l->vshortfall[n] = 0;
1635 l->text[n] = text;
1636 l->space[n] = space;
1637 l->page_last[n] = m;
1638 }
1639 }
43341922 1640
c6536773 1641 if (m == last)
1642 break;
43341922 1643 }
1644 }
1645 }
1646
1647 /*
1648 * Now go through the line list forwards and assemble the
1649 * actual pages.
1650 */
1651 ph = pt = NULL;
1652
1653 l = first;
c6536773 1654 n = 0;
43341922 1655 while (l) {
1656 page_data *page;
c6536773 1657 int text, space, head;
43341922 1658
f1530049 1659 page = snew(page_data);
43341922 1660 page->next = NULL;
1661 page->prev = pt;
1662 if (pt)
1663 pt->next = page;
1664 else
1665 ph = page;
1666 pt = page;
1667
1668 page->first_line = l;
c6536773 1669 page->last_line = l->page_last[n];
43341922 1670
1671 page->first_text = page->last_text = NULL;
138d7ffb 1672 page->first_xref = page->last_xref = NULL;
23765aeb 1673 page->first_rect = page->last_rect = NULL;
138d7ffb 1674
43341922 1675 /*
1676 * Now assign a y-coordinate to each line on the page.
1677 */
1678 text = space = 0;
c6536773 1679 head = (n < ncols ? headspace : 0);
43341922 1680 for (l = page->first_line; l; l = l->next) {
c6536773 1681 if (l != page->first_line) {
1682 if (l->prev->space_after > 0)
1683 space += l->prev->space_after;
1684 else
1685 text += l->prev->space_after;
1686 }
1687 if (l != page->first_line || l->page_break) {
1688 if (l->space_before > 0)
1689 space += l->space_before;
1690 else
1691 text += l->space_before;
1692 }
43341922 1693 text += l->line_height;
1694
1695 l->page = page;
416dfe17 1696 l->ypos = text + space + head;
1697 if (page->first_line->space[n]) {
1698 l->ypos += space * (float)page->first_line->vshortfall[n] /
1699 page->first_line->space[n];
1700 }
43341922 1701
1702 if (l == page->last_line)
1703 break;
1704 }
1705
c6536773 1706 l = page->last_line;
1707 if (l == last)
1708 break;
1709 l = l->next;
1710
1711 n = (n < ncols ? n+1 : ncols);
43341922 1712 }
1713
1714 return ph;
1715}
1716
23765aeb 1717static void add_rect_to_page(page_data *page, int x, int y, int w, int h)
1718{
f1530049 1719 rect *r = snew(rect);
23765aeb 1720
1721 r->next = NULL;
1722 if (page->last_rect)
1723 page->last_rect->next = r;
1724 else
1725 page->first_rect = r;
1726 page->last_rect = r;
1727
1728 r->x = x;
1729 r->y = y;
1730 r->w = w;
1731 r->h = h;
1732}
1733
43341922 1734static void add_string_to_page(page_data *page, int x, int y,
7c8c4239 1735 font_encoding *fe, int size, char *text,
1736 int width)
43341922 1737{
1738 text_fragment *frag;
1739
f1530049 1740 frag = snew(text_fragment);
43341922 1741 frag->next = NULL;
1742
1743 if (page->last_text)
1744 page->last_text->next = frag;
1745 else
1746 page->first_text = frag;
1747 page->last_text = frag;
1748
1749 frag->x = x;
1750 frag->y = y;
1751 frag->fe = fe;
1752 frag->fontsize = size;
1753 frag->text = dupstr(text);
7c8c4239 1754 frag->width = width;
43341922 1755}
1756
1757/*
1758 * Returns the updated x coordinate.
1759 */
1760static int render_string(page_data *page, font_data *font, int fontsize,
1761 int x, int y, wchar_t *str)
1762{
1763 char *text;
1764 int textpos, textwid, glyph;
1765 font_encoding *subfont = NULL, *sf;
1766
f1530049 1767 text = snewn(1 + ustrlen(str), char);
43341922 1768 textpos = textwid = 0;
1769
1770 while (*str) {
5333269a 1771 glyph = (*str < 0 || *str > 0xFFFF ? 0xFFFF :
1772 font->bmp[*str]);
43341922 1773
4cc00cdd 1774 if (glyph == 0xFFFF) {
1775 str++;
43341922 1776 continue; /* nothing more we can do here */
4cc00cdd 1777 }
43341922 1778
1779 /*
1780 * Find which subfont this character is going in.
1781 */
1782 sf = font->subfont_map[glyph].subfont;
1783
1784 if (!sf) {
1785 int c;
1786
1787 /*
1788 * This character is not yet in a subfont. Assign one.
1789 */
1790 if (font->latest_subfont->free_pos >= 0x100)
1791 font->latest_subfont = new_font_encoding(font);
1792
1793 c = font->latest_subfont->free_pos++;
1794 if (font->latest_subfont->free_pos == 0x7F)
1795 font->latest_subfont->free_pos = 0xA1;
1796
1797 font->subfont_map[glyph].subfont = font->latest_subfont;
1798 font->subfont_map[glyph].position = c;
1799 font->latest_subfont->vector[c] = font->glyphs[glyph];
1800 font->latest_subfont->indices[c] = glyph;
1801 font->latest_subfont->to_unicode[c] = *str;
1802
1803 sf = font->latest_subfont;
1804 }
1805
1806 if (!subfont || sf != subfont) {
1807 if (subfont) {
1808 text[textpos] = '\0';
7c8c4239 1809 add_string_to_page(page, x, y, subfont, fontsize, text,
1810 textwid);
43341922 1811 x += textwid;
1812 } else {
1813 assert(textpos == 0);
1814 }
1815 textpos = 0;
1816 subfont = sf;
1817 }
1818
1819 text[textpos++] = font->subfont_map[glyph].position;
1820 textwid += font->widths[glyph] * fontsize;
1821
1822 str++;
1823 }
1824
1825 if (textpos > 0) {
1826 text[textpos] = '\0';
7c8c4239 1827 add_string_to_page(page, x, y, subfont, fontsize, text, textwid);
43341922 1828 x += textwid;
1829 }
1830
1831 return x;
1832}
1833
1834/*
1835 * Returns the updated x coordinate.
1836 */
138d7ffb 1837static int render_text(page_data *page, para_data *pdata, line_data *ldata,
1838 int x, int y, word *text, word *text_end, xref **xr,
1839 int shortfall, int nspaces, int *nspace,
dd567011 1840 keywordlist *keywords, indexdata *idx, paper_conf *conf)
43341922 1841{
faad4952 1842 while (text && text != text_end) {
43341922 1843 int style, type, findex, errs;
1844 wchar_t *str;
138d7ffb 1845 xref_dest dest;
43341922 1846
1847 switch (text->type) {
138d7ffb 1848 /*
1849 * Start a cross-reference.
1850 */
43341922 1851 case word_HyperLink:
43341922 1852 case word_UpperXref:
1853 case word_LowerXref:
3f3d1acc 1854 case word_PageXref:
138d7ffb 1855
1856 if (text->type == word_HyperLink) {
1857 dest.type = URL;
e4ea58f8 1858 dest.url = utoa_dup(text->text, CS_ASCII);
138d7ffb 1859 dest.page = NULL;
3f3d1acc 1860 } else if (text->type == word_PageXref) {
1861 dest.type = PAGE;
1862 dest.url = NULL;
1863 dest.page = (page_data *)text->private_data;
138d7ffb 1864 } else {
1865 keyword *kwl = kw_lookup(keywords, text->text);
1866 para_data *pdata;
1867
1868 if (kwl) {
1869 assert(kwl->para->private_data);
1870 pdata = (para_data *) kwl->para->private_data;
1871 dest.type = PAGE;
1872 dest.page = pdata->first->page;
1873 dest.url = NULL;
1874 } else {
1875 /*
1876 * Shouldn't happen, but *shrug*
1877 */
1878 dest.type = NONE;
1879 dest.page = NULL;
1880 dest.url = NULL;
1881 }
1882 }
1883 if (dest.type != NONE) {
f1530049 1884 *xr = snew(xref);
138d7ffb 1885 (*xr)->dest = dest; /* structure copy */
1886 if (page->last_xref)
1887 page->last_xref->next = *xr;
1888 else
1889 page->first_xref = *xr;
1890 page->last_xref = *xr;
23765aeb 1891 (*xr)->next = NULL;
138d7ffb 1892
1893 /*
1894 * FIXME: Ideally we should have, and use, some
1895 * vertical font metric information here so that
1896 * our cross-ref rectangle can take account of
1897 * descenders and the font's cap height. This will
1898 * do for the moment, but it isn't ideal.
1899 */
1900 (*xr)->lx = (*xr)->rx = x;
1901 (*xr)->by = y;
1902 (*xr)->ty = y + ldata->line_height;
1903 }
1904 goto nextword;
1905
1906 /*
1907 * Finish extending a cross-reference box.
1908 */
1909 case word_HyperEnd:
43341922 1910 case word_XrefEnd:
138d7ffb 1911 *xr = NULL;
1912 goto nextword;
1913
43341922 1914 /*
c6536773 1915 * Add the current page number to the list of pages
1916 * referenced by an index entry.
43341922 1917 */
c6536773 1918 case word_IndexRef:
34d8cc1c 1919 /*
1920 * We don't create index references in contents entries.
1921 */
1922 if (!pdata->contents_entry) {
c6536773 1923 indextag *tag;
1924 int i;
1925
1926 tag = index_findtag(idx, text->text);
1927 if (!tag)
1928 goto nextword;
1929
1930 for (i = 0; i < tag->nrefs; i++) {
1931 indexentry *entry = tag->refs[i];
1932 paper_idx *pi = (paper_idx *)entry->backend_data;
1933
1934 /*
1935 * If the same index term is indexed twice
1936 * within the same section, we only want to
1937 * mention it once in the index.
1938 */
1939 if (pi->lastpage != page) {
3f3d1acc 1940 word **wp;
1941
c6536773 1942 if (pi->lastword) {
1943 pi->lastword = pi->lastword->next =
1944 fake_word(L",");
1945 pi->lastword = pi->lastword->next =
1946 fake_space_word();
3f3d1acc 1947 wp = &pi->lastword->next;
1948 } else
1949 wp = &pi->words;
1950
1951 pi->lastword = *wp =
1952 fake_page_ref(page);
1953 pi->lastword = pi->lastword->next =
1954 fake_word(page->number);
1955 pi->lastword = pi->lastword->next =
1956 fake_end_ref();
c6536773 1957 }
1958
1959 pi->lastpage = page;
1960 }
1961 }
1962 goto nextword;
43341922 1963 }
1964
1965 style = towordstyle(text->type);
1966 type = removeattr(text->type);
1967
1968 findex = (style == word_Normal ? FONT_NORMAL :
1969 style == word_Emph ? FONT_EMPH :
1970 FONT_CODE);
1971
1972 if (type == word_Normal) {
1973 str = text->text;
1974 } else if (type == word_WhiteSpace) {
1975 x += pdata->sizes[findex] *
1976 string_width(pdata->fonts[findex], L" ", NULL);
faad4952 1977 if (nspaces && findex != FONT_CODE) {
1978 x += (*nspace+1) * shortfall / nspaces;
1979 x -= *nspace * shortfall / nspaces;
1980 (*nspace)++;
1981 }
43341922 1982 goto nextword;
1983 } else /* if (type == word_Quote) */ {
1984 if (text->aux == quote_Open)
dd567011 1985 str = conf->lquote;
43341922 1986 else
dd567011 1987 str = conf->rquote;
43341922 1988 }
1989
1990 (void) string_width(pdata->fonts[findex], str, &errs);
1991
1992 if (errs && text->alt)
138d7ffb 1993 x = render_text(page, pdata, ldata, x, y, text->alt, NULL,
dd567011 1994 xr, shortfall, nspaces, nspace, keywords, idx,
1995 conf);
43341922 1996 else
1997 x = render_string(page, pdata->fonts[findex],
1998 pdata->sizes[findex], x, y, str);
1999
138d7ffb 2000 if (*xr)
2001 (*xr)->rx = x;
2002
43341922 2003 nextword:
43341922 2004 text = text->next;
2005 }
2006
2007 return x;
2008}
2009
2bfd1b76 2010/*
2011 * Returns the last x position used on the line.
2012 */
2013static int render_line(line_data *ldata, int left_x, int top_y,
dd567011 2014 xref_dest *dest, keywordlist *keywords, indexdata *idx,
2015 paper_conf *conf)
43341922 2016{
faad4952 2017 int nspace;
138d7ffb 2018 xref *xr;
2bfd1b76 2019 int ret = 0;
138d7ffb 2020
faad4952 2021 if (ldata->aux_text) {
515d216b 2022 int x;
138d7ffb 2023 xr = NULL;
faad4952 2024 nspace = 0;
515d216b 2025 x = render_text(ldata->page, ldata->pdata, ldata,
2026 left_x + ldata->aux_left_indent,
2027 top_y - ldata->ypos,
c6536773 2028 ldata->aux_text, NULL, &xr, 0, 0, &nspace,
dd567011 2029 keywords, idx, conf);
515d216b 2030 if (ldata->aux_text_2)
2031 render_text(ldata->page, ldata->pdata, ldata,
2032 x, top_y - ldata->ypos,
c6536773 2033 ldata->aux_text_2, NULL, &xr, 0, 0, &nspace,
dd567011 2034 keywords, idx, conf);
faad4952 2035 }
2036 nspace = 0;
138d7ffb 2037
87bd6353 2038 if (ldata->first) {
138d7ffb 2039 /*
87bd6353 2040 * There might be a cross-reference carried over from a
2041 * previous line.
138d7ffb 2042 */
87bd6353 2043 if (dest->type != NONE) {
f1530049 2044 xr = snew(xref);
87bd6353 2045 xr->next = NULL;
2046 xr->dest = *dest; /* structure copy */
2047 if (ldata->page->last_xref)
2048 ldata->page->last_xref->next = xr;
2049 else
2050 ldata->page->first_xref = xr;
2051 ldata->page->last_xref = xr;
2052 xr->lx = xr->rx = left_x + ldata->xpos;
2053 xr->by = top_y - ldata->ypos;
2054 xr->ty = top_y - ldata->ypos + ldata->line_height;
2055 } else
2056 xr = NULL;
2057
c6536773 2058 {
2059 int extra_indent, shortfall, spaces;
2060 int just = ldata->pdata->justification;
2061
2062 /*
2063 * All forms of justification become JUST when we have
2064 * to squeeze the paragraph.
2065 */
2066 if (ldata->hshortfall < 0)
2067 just = JUST;
2068
2069 switch (just) {
2070 case JUST:
2071 shortfall = ldata->hshortfall;
2072 spaces = ldata->nspaces;
2073 extra_indent = 0;
2074 break;
2075 case LEFT:
2076 shortfall = spaces = extra_indent = 0;
2077 break;
2078 case RIGHT:
2079 shortfall = spaces = 0;
2080 extra_indent = ldata->real_shortfall;
2081 break;
2082 }
2083
2084 ret = render_text(ldata->page, ldata->pdata, ldata,
2085 left_x + ldata->xpos + extra_indent,
2086 top_y - ldata->ypos, ldata->first, ldata->end,
2087 &xr, shortfall, spaces, &nspace,
dd567011 2088 keywords, idx, conf);
c6536773 2089 }
87bd6353 2090
2091 if (xr) {
2092 /*
2093 * There's a cross-reference continued on to the next line.
2094 */
2095 *dest = xr->dest;
2096 } else
2097 dest->type = NONE;
2098 }
2bfd1b76 2099
2100 return ret;
43341922 2101}
515d216b 2102
c6536773 2103static void render_para(para_data *pdata, paper_conf *conf,
2104 keywordlist *keywords, indexdata *idx,
2105 paragraph *index_placeholder, page_data *index_page)
2106{
2107 int last_x;
2108 xref *cxref;
2109 page_data *cxref_page;
2110 xref_dest dest;
2111 para_data *target;
2112 line_data *ldata;
2113
2114 dest.type = NONE;
2115 cxref = NULL;
2116 cxref_page = NULL;
2117
2118 for (ldata = pdata->first; ldata; ldata = ldata->next) {
2119 /*
2120 * If this is a contents entry, we expect to have a single
2121 * enormous cross-reference rectangle covering the whole
2122 * thing. (Unless, of course, it spans multiple pages.)
2123 */
2124 if (pdata->contents_entry && ldata->page != cxref_page) {
2125 cxref_page = ldata->page;
f1530049 2126 cxref = snew(xref);
c6536773 2127 cxref->next = NULL;
2128 cxref->dest.type = PAGE;
2129 if (pdata->contents_entry == index_placeholder) {
2130 cxref->dest.page = index_page;
2131 } else {
2132 assert(pdata->contents_entry->private_data);
2133 target = (para_data *)pdata->contents_entry->private_data;
2134 cxref->dest.page = target->first->page;
2135 }
2136 cxref->dest.url = NULL;
2137 if (ldata->page->last_xref)
2138 ldata->page->last_xref->next = cxref;
2139 else
2140 ldata->page->first_xref = cxref;
2141 ldata->page->last_xref = cxref;
2142 cxref->lx = conf->left_margin;
2143 cxref->rx = conf->paper_width - conf->right_margin;
2144 cxref->ty = conf->paper_height - conf->top_margin
2145 - ldata->ypos + ldata->line_height;
2146 }
2147 if (pdata->contents_entry) {
2148 assert(cxref != NULL);
2149 cxref->by = conf->paper_height - conf->top_margin
2150 - ldata->ypos;
2151 }
2152
2153 last_x = render_line(ldata, conf->left_margin,
2154 conf->paper_height - conf->top_margin,
dd567011 2155 &dest, keywords, idx, conf);
c6536773 2156 if (ldata == pdata->last)
2157 break;
2158 }
2159
2160 /*
2161 * If this is a contents entry, add leaders and a page
2162 * number.
2163 */
2164 if (pdata->contents_entry) {
2165 word *w;
2166 wchar_t *num;
2167 int wid;
2168 int x;
2169
2170 if (pdata->contents_entry == index_placeholder) {
2171 num = index_page->number;
2172 } else {
2173 assert(pdata->contents_entry->private_data);
2174 target = (para_data *)pdata->contents_entry->private_data;
2175 num = target->first->page->number;
2176 }
2177
2178 w = fake_word(num);
dd567011 2179 wid = paper_width_simple(pdata, w, conf);
c6536773 2180 sfree(w);
2181
c6536773 2182 for (x = 0; x < conf->base_width; x += conf->leader_separation)
2183 if (x - conf->leader_separation > last_x - conf->left_margin &&
2184 x + conf->leader_separation < conf->base_width - wid)
2185 render_string(pdata->last->page,
2186 pdata->fonts[FONT_NORMAL],
2187 pdata->sizes[FONT_NORMAL],
2188 conf->left_margin + x,
2189 (conf->paper_height - conf->top_margin -
2190 pdata->last->ypos), L".");
adbcaa16 2191
2192 render_string(pdata->last->page,
2193 pdata->fonts[FONT_NORMAL],
2194 pdata->sizes[FONT_NORMAL],
2195 conf->paper_width - conf->right_margin - wid,
2196 (conf->paper_height - conf->top_margin -
2197 pdata->last->ypos), num);
c6536773 2198 }
2199
2200 /*
2201 * Render any rectangle (chapter title underline or rule)
2202 * that goes with this paragraph.
2203 */
2204 switch (pdata->rect_type) {
2205 case RECT_CHAPTER_UNDERLINE:
2206 add_rect_to_page(pdata->last->page,
2207 conf->left_margin,
2208 (conf->paper_height - conf->top_margin -
2209 pdata->last->ypos -
2210 conf->chapter_underline_depth),
2211 conf->base_width,
2212 conf->chapter_underline_thickness);
2213 break;
2214 case RECT_RULE:
2215 add_rect_to_page(pdata->first->page,
2216 conf->left_margin + pdata->first->xpos,
2217 (conf->paper_height - conf->top_margin -
2218 pdata->last->ypos -
2219 pdata->last->line_height),
2220 conf->base_width - pdata->first->xpos,
2221 pdata->last->line_height);
2222 break;
2223 default: /* placate gcc */
2224 break;
2225 }
2226}
2227
be76d597 2228static para_data *code_paragraph(int indent, word *words, paper_conf *conf)
515d216b 2229{
f1530049 2230 para_data *pdata = snew(para_data);
be76d597 2231
515d216b 2232 /*
2233 * For code paragraphs, I'm going to hack grievously and
2234 * pretend the three normal fonts are the three code paragraph
2235 * fonts.
2236 */
be76d597 2237 pdata->fonts[FONT_NORMAL] = conf->cb;
2238 pdata->fonts[FONT_EMPH] = conf->co;
36e64ed4 2239 pdata->fonts[FONT_CODE] = conf->cr;
515d216b 2240 pdata->sizes[FONT_NORMAL] =
2241 pdata->sizes[FONT_EMPH] =
be76d597 2242 pdata->sizes[FONT_CODE] = 12;
515d216b 2243
2244 pdata->first = pdata->last = NULL;
be76d597 2245 pdata->outline_level = -1;
2246 pdata->rect_type = RECT_NONE;
2bfd1b76 2247 pdata->contents_entry = NULL;
c6536773 2248 pdata->justification = LEFT;
515d216b 2249
2250 for (; words; words = words->next) {
2251 wchar_t *t, *e, *start;
2252 word *lhead = NULL, *ltail = NULL, *w;
2253 line_data *ldata;
2254 int prev = -1, curr;
2255
2256 t = words->text;
2257 if (words->next && words->next->type == word_Emph) {
2258 e = words->next->text;
2259 words = words->next;
2260 } else
2261 e = NULL;
2262
2263 start = t;
2264
2265 while (*start) {
2266 while (*t) {
2267 if (!e || !*e)
2268 curr = 0;
2269 else if (*e == L'i')
2270 curr = 1;
2271 else if (*e == L'b')
2272 curr = 2;
2273 else
2274 curr = 0;
2275
2276 if (prev < 0)
2277 prev = curr;
2278
2279 if (curr != prev)
2280 break;
2281
2282 t++;
2283 if (e && *e)
2284 e++;
2285 }
2286
2287 /*
2288 * We've isolated a maximal subsequence of the line
2289 * which has the same emphasis. Form it into a word
2290 * structure.
2291 */
f1530049 2292 w = snew(word);
515d216b 2293 w->next = NULL;
2294 w->alt = NULL;
2295 w->type = (prev == 0 ? word_WeakCode :
2296 prev == 1 ? word_Emph : word_Normal);
f1530049 2297 w->text = snewn(t-start+1, wchar_t);
515d216b 2298 memcpy(w->text, start, (t-start) * sizeof(wchar_t));
2299 w->text[t-start] = '\0';
2300 w->breaks = FALSE;
2301
2302 if (ltail)
2303 ltail->next = w;
2304 else
2305 lhead = w;
2306 ltail = w;
2307
2308 start = t;
2309 prev = -1;
2310 }
2311
f1530049 2312 ldata = snew(line_data);
515d216b 2313
2314 ldata->pdata = pdata;
2315 ldata->first = lhead;
2316 ldata->end = NULL;
17c71b41 2317 ldata->line_height = conf->base_font_size * UNITS_PER_PT;
515d216b 2318
2319 ldata->xpos = indent;
2320
2321 if (pdata->last) {
2322 pdata->last->next = ldata;
2323 ldata->prev = pdata->last;
2324 } else {
2325 pdata->first = ldata;
2326 ldata->prev = NULL;
2327 }
2328 ldata->next = NULL;
2329 pdata->last = ldata;
2330
2331 ldata->hshortfall = 0;
2332 ldata->nspaces = 0;
2333 ldata->aux_text = NULL;
2334 ldata->aux_text_2 = NULL;
2335 ldata->aux_left_indent = 0;
39a0cfb9 2336 /* General opprobrium for breaking in a code paragraph. */
2337 ldata->penalty_before = ldata->penalty_after = 50000;
515d216b 2338 }
be76d597 2339
2340 standard_line_spacing(pdata, conf);
2341
2342 return pdata;
515d216b 2343}
87bd6353 2344
be76d597 2345static para_data *rule_paragraph(int indent, paper_conf *conf)
87bd6353 2346{
f1530049 2347 para_data *pdata = snew(para_data);
87bd6353 2348 line_data *ldata;
2349
f1530049 2350 ldata = snew(line_data);
87bd6353 2351
2352 ldata->pdata = pdata;
2353 ldata->first = NULL;
2354 ldata->end = NULL;
be76d597 2355 ldata->line_height = conf->rule_thickness;
87bd6353 2356
2357 ldata->xpos = indent;
2358
2359 ldata->prev = NULL;
2360 ldata->next = NULL;
2361
2362 ldata->hshortfall = 0;
2363 ldata->nspaces = 0;
2364 ldata->aux_text = NULL;
2365 ldata->aux_text_2 = NULL;
2366 ldata->aux_left_indent = 0;
2367
2368 /*
2369 * Better to break after a rule than before it
2370 */
2371 ldata->penalty_after += 100000;
2372 ldata->penalty_before += -100000;
2373
2374 pdata->first = pdata->last = ldata;
be76d597 2375 pdata->outline_level = -1;
2376 pdata->rect_type = RECT_RULE;
2bfd1b76 2377 pdata->contents_entry = NULL;
c6536773 2378 pdata->justification = LEFT;
be76d597 2379
2380 standard_line_spacing(pdata, conf);
2381
2382 return pdata;
2383}
2384
2385/*
2386 * Plain-text-like formatting for outline titles.
2387 */
2388static void paper_rdaddw(rdstring *rs, word *text) {
2389 for (; text; text = text->next) switch (text->type) {
2390 case word_HyperLink:
2391 case word_HyperEnd:
2392 case word_UpperXref:
2393 case word_LowerXref:
2394 case word_XrefEnd:
2395 case word_IndexRef:
2396 break;
2397
2398 case word_Normal:
2399 case word_Emph:
2400 case word_Code:
2401 case word_WeakCode:
2402 case word_WhiteSpace:
2403 case word_EmphSpace:
2404 case word_CodeSpace:
2405 case word_WkCodeSpace:
2406 case word_Quote:
2407 case word_EmphQuote:
2408 case word_CodeQuote:
2409 case word_WkCodeQuote:
2410 assert(text->type != word_CodeQuote &&
2411 text->type != word_WkCodeQuote);
2412 if (towordstyle(text->type) == word_Emph &&
2413 (attraux(text->aux) == attr_First ||
2414 attraux(text->aux) == attr_Only))
2415 rdadd(rs, L'_'); /* FIXME: configurability */
2416 else if (towordstyle(text->type) == word_Code &&
2417 (attraux(text->aux) == attr_First ||
2418 attraux(text->aux) == attr_Only))
2419 rdadd(rs, L'\''); /* FIXME: configurability */
2420 if (removeattr(text->type) == word_Normal) {
2421 rdadds(rs, text->text);
2422 } else if (removeattr(text->type) == word_WhiteSpace) {
2423 rdadd(rs, L' ');
2424 } else if (removeattr(text->type) == word_Quote) {
2425 rdadd(rs, L'\''); /* fixme: configurability */
2426 }
2427 if (towordstyle(text->type) == word_Emph &&
2428 (attraux(text->aux) == attr_Last ||
2429 attraux(text->aux) == attr_Only))
2430 rdadd(rs, L'_'); /* FIXME: configurability */
2431 else if (towordstyle(text->type) == word_Code &&
2432 (attraux(text->aux) == attr_Last ||
2433 attraux(text->aux) == attr_Only))
2434 rdadd(rs, L'\''); /* FIXME: configurability */
2435 break;
2436 }
2437}
2438
2439static wchar_t *prepare_outline_title(word *first, wchar_t *separator,
2440 word *second)
2441{
2442 rdstring rs = {0, 0, NULL};
2443
2444 if (first)
2445 paper_rdaddw(&rs, first);
2446 if (separator)
2447 rdadds(&rs, separator);
2448 if (second)
2449 paper_rdaddw(&rs, second);
2450
2451 return rs.text;
87bd6353 2452}
2bfd1b76 2453
2454static word *fake_word(wchar_t *text)
2455{
f1530049 2456 word *ret = snew(word);
2bfd1b76 2457 ret->next = NULL;
2458 ret->alt = NULL;
2459 ret->type = word_Normal;
2460 ret->text = ustrdup(text);
2461 ret->breaks = FALSE;
2462 ret->aux = 0;
2463 return ret;
2464}
2465
c6536773 2466static word *fake_space_word(void)
2467{
f1530049 2468 word *ret = snew(word);
c6536773 2469 ret->next = NULL;
2470 ret->alt = NULL;
2471 ret->type = word_WhiteSpace;
2472 ret->text = NULL;
2473 ret->breaks = TRUE;
2474 ret->aux = 0;
2475 return ret;
2476}
2477
3f3d1acc 2478static word *fake_page_ref(page_data *page)
2479{
f1530049 2480 word *ret = snew(word);
3f3d1acc 2481 ret->next = NULL;
2482 ret->alt = NULL;
2483 ret->type = word_PageXref;
2484 ret->text = NULL;
2485 ret->breaks = FALSE;
2486 ret->aux = 0;
2487 ret->private_data = page;
2488 return ret;
2489}
2490
2491static word *fake_end_ref(void)
2492{
f1530049 2493 word *ret = snew(word);
3f3d1acc 2494 ret->next = NULL;
2495 ret->alt = NULL;
2496 ret->type = word_XrefEnd;
2497 ret->text = NULL;
2498 ret->breaks = FALSE;
2499 ret->aux = 0;
2500 return ret;
2501}
2502
2bfd1b76 2503static word *prepare_contents_title(word *first, wchar_t *separator,
2504 word *second)
2505{
2506 word *ret;
2507 word **wptr, *w;
2508
2509 wptr = &ret;
2510
2511 if (first) {
2512 w = dup_word_list(first);
2513 *wptr = w;
2514 while (w->next)
2515 w = w->next;
2516 wptr = &w->next;
2517 }
2518
2519 if (separator) {
2520 w = fake_word(separator);
2521 *wptr = w;
2522 wptr = &w->next;
2523 }
2524
2525 if (second) {
2526 *wptr = dup_word_list(second);
2527 }
2528
2529 return ret;
2530}
c6536773 2531
2532static void fold_into_page(page_data *dest, page_data *src, int right_shift)
2533{
2534 line_data *ldata;
2535
2536 if (!src->first_line)
2537 return;
2538
2539 if (dest->last_line) {
2540 dest->last_line->next = src->first_line;
2541 src->first_line->prev = dest->last_line;
2542 }
2543 dest->last_line = src->last_line;
2544
2545 for (ldata = src->first_line; ldata; ldata = ldata->next) {
2546 ldata->page = dest;
2547 ldata->xpos += right_shift;
2548
2549 if (ldata == src->last_line)
2550 break;
2551 }
2552}