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