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