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