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