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