Aha, _that's_ why paragraphs weren't properly justified. Confusion
[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 * To be done:
14 *
15 * - set up contents section now we know what sections begin on
16 * which pages
17 *
18 * - do cross-reference rectangles
19 *
20 * - do PDF outline
21 *
22 * - all the missing features in text rendering (code paragraphs,
23 * list bullets, indentation, section heading styles)
24 *
25 * - index
26 *
27 * That should bring us to the same level of functionality that
28 * original-Halibut had, and the same in PDF plus the obvious
29 * interactive navigation features. After that, in future work:
30 *
31 * - linearised PDF, perhaps?
32 *
33 * - I'm uncertain of whether I need to include a ToUnicode CMap
34 * in each of my font definitions in PDF. Currently things (by
35 * which I mean cut and paste out of acroread) seem to be
36 * working fairly happily without it, but I don't know.
37 *
38 * - configurability
39 *
40 * - title pages
41 */
42
43 #include <assert.h>
44 #include <stdio.h>
45
46 #include "halibut.h"
47 #include "paper.h"
48
49 static font_data *make_std_font(font_list *fontlist, char const *name);
50 static void wrap_paragraph(para_data *pdata, word *words,
51 int w, int i1, int i2);
52 static page_data *page_breaks(line_data *first, line_data *last,
53 int page_height);
54 static void render_line(line_data *ldata, int left_x, int top_y);
55
56 void *paper_pre_backend(paragraph *sourceform, keywordlist *keywords,
57 indexdata *idx) {
58 paragraph *p;
59 document *doc;
60 int indent, extra_indent, firstline_indent;
61 para_data *pdata;
62 line_data *ldata, *firstline, *lastline;
63 font_data *tr, *ti, *cr;
64 page_data *pages;
65 font_list *fontlist;
66
67 /*
68 * FIXME: All these things ought to become configurable.
69 */
70 int paper_width = 595 * 4096;
71 int paper_height = 841 * 4096;
72 int left_margin = 72 * 4096;
73 int top_margin = 72 * 4096;
74 int right_margin = 72 * 4096;
75 int bottom_margin = 108 * 4096;
76 int indent_list_bullet = 6 * 4096;
77 int indent_list = 24 * 4096;
78 int indent_quote = 18 * 4096;
79 int base_leading = 4096;
80 int base_para_spacing = 10 * 4096;
81
82 int base_width = paper_width - left_margin - right_margin;
83 int page_height = paper_height - top_margin - bottom_margin;
84
85 IGNORE(keywords); /* FIXME */
86 IGNORE(idx); /* FIXME */
87 IGNORE(indent_list_bullet); /* FIXME */
88
89 /*
90 * First, set up some font structures.
91 */
92 fontlist = mknew(font_list);
93 fontlist->head = fontlist->tail = NULL;
94 tr = make_std_font(fontlist, "Times-Roman");
95 ti = make_std_font(fontlist, "Times-Italic");
96 cr = make_std_font(fontlist, "Courier");
97
98 /*
99 * Go through and break up each paragraph into lines.
100 */
101 indent = 0;
102 firstline = lastline = NULL;
103 for (p = sourceform; p; p = p->next) {
104 p->private_data = NULL;
105
106 switch (p->type) {
107 /*
108 * These paragraph types are either invisible or don't
109 * define text in the normal sense. Either way, they
110 * don't require wrapping.
111 */
112 case para_IM:
113 case para_BR:
114 case para_Rule:
115 case para_Biblio:
116 case para_NotParaType:
117 case para_Config:
118 case para_VersionID:
119 case para_NoCite:
120 break;
121
122 /*
123 * These paragraph types don't require wrapping, but
124 * they do affect the line width to which we wrap the
125 * rest of the paragraphs, so we need to pay attention.
126 */
127 case para_LcontPush:
128 indent += indent_list; break;
129 case para_LcontPop:
130 indent -= indent_list; assert(indent >= 0); break;
131 case para_QuotePush:
132 indent += indent_quote; break;
133 case para_QuotePop:
134 indent -= indent_quote; assert(indent >= 0); break;
135
136 /*
137 * This paragraph type is special. Process it
138 * specially.
139 */
140 case para_Code:
141 /* FIXME */
142 break;
143
144 /*
145 * All of these paragraph types require wrapping in the
146 * ordinary way. So we must supply a set of fonts, a
147 * line width and auxiliary information (e.g. bullet
148 * text) for each one.
149 */
150 case para_Chapter:
151 case para_Appendix:
152 case para_UnnumberedChapter:
153 case para_Heading:
154 case para_Subsect:
155 case para_Normal:
156 case para_BiblioCited:
157 case para_Bullet:
158 case para_NumberedList:
159 case para_DescribedThing:
160 case para_Description:
161 case para_Copyright:
162 case para_Title:
163 pdata = mknew(para_data);
164
165 /*
166 * FIXME: Subsidiary switch on paragraph type to decide
167 * what font set to use for this paragraph.
168 */
169 pdata->fonts[FONT_NORMAL] = tr;
170 pdata->sizes[FONT_NORMAL] = 12;
171 pdata->fonts[FONT_EMPH] = ti;
172 pdata->sizes[FONT_EMPH] = 12;
173 pdata->fonts[FONT_CODE] = cr;
174 pdata->sizes[FONT_CODE] = 12;
175
176 /*
177 * FIXME: Also select an indentation level depending on
178 * the paragraph type (list paragraphs other than
179 * para_DescribedThing need extra indent).
180 *
181 * Perhaps at some point we might even arrange for the
182 * user to be able to request indented first lines in
183 * paragraphs.
184 */
185 extra_indent = 0;
186 firstline_indent = 0;
187
188 wrap_paragraph(pdata, p->words, base_width,
189 indent + firstline_indent,
190 indent + extra_indent);
191
192 /*
193 * FIXME: Also find the auxiliary data for this
194 * paragraph. For para_Bullet it's a bullet; for
195 * para_NumberedList it's the number; for some section
196 * headings (depending on the style of section heading
197 * selected) it's the section number.
198 *
199 * Assign into pdata->first->aux_*.
200 */
201
202 p->private_data = pdata;
203
204 /*
205 * Set the line spacing for each line in this paragraph.
206 */
207 for (ldata = pdata->first; ldata; ldata = ldata->next) {
208 if (ldata == pdata->first)
209 ldata->space_before = base_para_spacing / 2;
210 else
211 ldata->space_before = base_leading / 2;
212 if (ldata == pdata->last)
213 ldata->space_after = base_para_spacing / 2;
214 else
215 ldata->space_after = base_leading / 2;
216 ldata->page_break = FALSE;
217 }
218
219 /*
220 * FIXME: some kinds of section heading do require a
221 * page break before them.
222 */
223
224 break;
225 }
226
227 /*
228 * Link all line structures together into a big list.
229 */
230 if (p->private_data) {
231 pdata = (para_data *)p->private_data;
232 if (pdata->first) {
233 if (lastline) {
234 lastline->next = pdata->first;
235 pdata->first->prev = lastline;
236 } else {
237 firstline = pdata->first;
238 pdata->first->prev = NULL;
239 }
240 lastline = pdata->last;
241 }
242 }
243 }
244
245 /*
246 * Now we have an enormous linked list of every line of text in
247 * the document. Break it up into pages.
248 */
249 pages = page_breaks(firstline, lastline, page_height);
250
251 /*
252 * Now we're ready to actually lay out the pages. We do this by
253 * looping over _paragraphs_, since we may need to track cross-
254 * references between lines and even across pages.
255 */
256 for (p = sourceform; p; p = p->next) {
257 pdata = (para_data *)p->private_data;
258
259 if (pdata) {
260 for (ldata = pdata->first; ldata; ldata = ldata->next) {
261 render_line(ldata, left_margin, paper_height - top_margin);
262 if (ldata == pdata->last)
263 break;
264 }
265 }
266 }
267
268 doc = mknew(document);
269 doc->fonts = fontlist;
270 doc->pages = pages;
271 doc->paper_width = paper_width;
272 doc->paper_height = paper_height;
273 return doc;
274 }
275
276 static font_encoding *new_font_encoding(font_data *font)
277 {
278 font_encoding *fe;
279 int i;
280
281 fe = mknew(font_encoding);
282 fe->next = NULL;
283
284 if (font->list->tail)
285 font->list->tail->next = fe;
286 else
287 font->list->head = fe;
288 font->list->tail = fe;
289
290 fe->font = font;
291 fe->free_pos = 0x21;
292
293 for (i = 0; i < 256; i++) {
294 fe->vector[i] = NULL;
295 fe->indices[i] = -1;
296 fe->to_unicode[i] = 0xFFFF;
297 }
298
299 return fe;
300 }
301
302 static font_data *make_std_font(font_list *fontlist, char const *name)
303 {
304 const int *widths;
305 int nglyphs;
306 font_data *f;
307 font_encoding *fe;
308 int i;
309
310 widths = ps_std_font_widths(name);
311 if (!widths)
312 return NULL;
313
314 for (nglyphs = 0; ps_std_glyphs[nglyphs] != NULL; nglyphs++);
315
316 f = mknew(font_data);
317
318 f->list = fontlist;
319 f->name = name;
320 f->nglyphs = nglyphs;
321 f->glyphs = ps_std_glyphs;
322 f->widths = widths;
323 f->subfont_map = mknewa(subfont_map_entry, nglyphs);
324
325 /*
326 * Our first subfont will contain all of US-ASCII. This isn't
327 * really necessary - we could just create custom subfonts
328 * precisely as the whim of render_string dictated - but
329 * instinct suggests that it might be nice to have the text in
330 * the output files look _marginally_ recognisable.
331 */
332 fe = new_font_encoding(f);
333 fe->free_pos = 0xA1; /* only the top half is free */
334 f->latest_subfont = fe;
335
336 for (i = 0; i < (int)lenof(f->bmp); i++)
337 f->bmp[i] = 0xFFFF;
338
339 for (i = 0; i < nglyphs; i++) {
340 wchar_t ucs;
341 ucs = ps_glyph_to_unicode(f->glyphs[i]);
342 assert(ucs != 0xFFFF);
343 f->bmp[ucs] = i;
344 if (ucs >= 0x20 && ucs <= 0x7E) {
345 fe->vector[ucs] = f->glyphs[i];
346 fe->indices[ucs] = i;
347 fe->to_unicode[ucs] = ucs;
348 f->subfont_map[i].subfont = fe;
349 f->subfont_map[i].position = ucs;
350 } else {
351 /*
352 * This character is not yet assigned to a subfont.
353 */
354 f->subfont_map[i].subfont = NULL;
355 f->subfont_map[i].position = 0;
356 }
357 }
358
359 return f;
360 }
361
362 static int string_width(font_data *font, wchar_t const *string, int *errs)
363 {
364 int width = 0;
365
366 if (errs)
367 *errs = 0;
368
369 for (; *string; string++) {
370 int index;
371
372 index = font->bmp[(unsigned short)*string];
373 if (index == 0xFFFF) {
374 if (errs)
375 *errs = 1;
376 } else {
377 width += font->widths[index];
378 }
379 }
380
381 return width;
382 }
383
384 static int paper_width_internal(void *vctx, word *word, int *nspaces);
385
386 struct paper_width_ctx {
387 int minspacewidth;
388 para_data *pdata;
389 };
390
391 static int paper_width_list(void *vctx, word *text, word *end, int *nspaces) {
392 int w = 0;
393 while (text && text != end) {
394 w += paper_width_internal(vctx, text, nspaces);
395 text = text->next;
396 }
397 return w;
398 }
399
400 static int paper_width_internal(void *vctx, word *word, int *nspaces)
401 {
402 struct paper_width_ctx *ctx = (struct paper_width_ctx *)vctx;
403 int style, type, findex, width, errs;
404 wchar_t *str;
405
406 switch (word->type) {
407 case word_HyperLink:
408 case word_HyperEnd:
409 case word_UpperXref:
410 case word_LowerXref:
411 case word_XrefEnd:
412 case word_IndexRef:
413 return 0;
414 }
415
416 style = towordstyle(word->type);
417 type = removeattr(word->type);
418
419 findex = (style == word_Normal ? FONT_NORMAL :
420 style == word_Emph ? FONT_EMPH :
421 FONT_CODE);
422
423 if (type == word_Normal) {
424 str = word->text;
425 } else if (type == word_WhiteSpace) {
426 if (findex != FONT_CODE) {
427 if (nspaces)
428 (*nspaces)++;
429 return ctx->minspacewidth;
430 } else
431 str = L" ";
432 } else /* if (type == word_Quote) */ {
433 if (word->aux == quote_Open)
434 str = L"\x2018"; /* FIXME: configurability! */
435 else
436 str = L"\x2019"; /* FIXME: configurability! */
437 }
438
439 width = string_width(ctx->pdata->fonts[findex], str, &errs);
440
441 if (errs && word->alt)
442 return paper_width_list(vctx, word->alt, NULL, nspaces);
443 else
444 return ctx->pdata->sizes[findex] * width;
445 }
446
447 static int paper_width(void *vctx, word *word)
448 {
449 return paper_width_internal(vctx, word, NULL);
450 }
451
452 static void wrap_paragraph(para_data *pdata, word *words,
453 int w, int i1, int i2)
454 {
455 wrappedline *wrapping, *p;
456 int spacewidth;
457 struct paper_width_ctx ctx;
458 int line_height;
459
460 /*
461 * We're going to need to store the line height in every line
462 * structure we generate.
463 */
464 {
465 int i;
466 line_height = 0;
467 for (i = 0; i < NFONTS; i++)
468 if (line_height < pdata->sizes[i])
469 line_height = pdata->sizes[i];
470 line_height *= 4096;
471 }
472
473 spacewidth = (pdata->sizes[FONT_NORMAL] *
474 string_width(pdata->fonts[FONT_NORMAL], L" ", NULL));
475 if (spacewidth == 0) {
476 /*
477 * A font without a space?! Disturbing. I hope this never
478 * comes up, but I'll make a random guess anyway and set my
479 * space width to half the point size.
480 */
481 spacewidth = pdata->sizes[FONT_NORMAL] * 4096 / 2;
482 }
483
484 /*
485 * I'm going to set the _minimum_ space width to 3/5 of the
486 * standard one, and use the standard one as the optimum.
487 */
488 ctx.minspacewidth = spacewidth * 3 / 5;
489 ctx.pdata = pdata;
490
491 wrapping = wrap_para(words, w - i1, w - i2, paper_width, &ctx, spacewidth);
492
493 /*
494 * Having done the wrapping, we now concoct a set of line_data
495 * structures.
496 */
497 pdata->first = pdata->last = NULL;
498
499 for (p = wrapping; p; p = p->next) {
500 line_data *ldata;
501 word *wd;
502 int len, wid, spaces;
503
504 ldata = mknew(line_data);
505
506 ldata->pdata = pdata;
507 ldata->first = p->begin;
508 ldata->end = p->end;
509 ldata->line_height = line_height;
510
511 ldata->xpos = (p == wrapping ? i1 : i2);
512
513 if (pdata->last) {
514 pdata->last->next = ldata;
515 ldata->prev = pdata->last;
516 } else {
517 pdata->first = ldata;
518 ldata->prev = NULL;
519 }
520 ldata->next = NULL;
521 pdata->last = ldata;
522
523 spaces = 0;
524 len = paper_width_list(&ctx, ldata->first, ldata->end, &spaces);
525 wid = (p == wrapping ? w - i1 : w - i2);
526 wd = ldata->first;
527
528 ldata->hshortfall = wid - len;
529 ldata->nspaces = spaces;
530 /*
531 * This tells us how much the space width needs to
532 * change from _min_spacewidth. But we want to store
533 * its difference from the _natural_ space width, to
534 * make the text rendering easier.
535 */
536 ldata->hshortfall += ctx.minspacewidth * spaces;
537 ldata->hshortfall -= spacewidth * spaces;
538 /*
539 * Special case: on the last line of a paragraph, we
540 * never stretch spaces.
541 */
542 if (ldata->hshortfall > 0 && !p->next)
543 ldata->hshortfall = 0;
544
545 ldata->aux_text = NULL;
546 ldata->aux_left_indent = 0;
547 }
548
549 }
550
551 static page_data *page_breaks(line_data *first, line_data *last,
552 int page_height)
553 {
554 line_data *l, *m;
555 page_data *ph, *pt;
556
557 /*
558 * Page breaking is done by a close analogue of the optimal
559 * paragraph wrapping algorithm used by wrap_para(). We work
560 * backwards from the end of the document line by line; for
561 * each line, we contemplate every possible number of lines we
562 * could put on a page starting with that line, determine a
563 * cost function for each one, add it to the pre-computed cost
564 * function for optimally page-breaking everything after that
565 * page, and pick the best option.
566 *
567 * Since my line_data structures are only used for this
568 * purpose, I might as well just store the algorithm data
569 * directly in them.
570 */
571
572 for (l = last; l; l = l->prev) {
573 int minheight, text = 0, space = 0;
574 int cost;
575
576 l->bestcost = -1;
577 for (m = l; m; m = m->next) {
578 if (m != l && m->page_break)
579 break; /* we've gone as far as we can */
580
581 if (m != l)
582 space += m->prev->space_after;
583 if (m != l || m->page_break)
584 space += m->space_before;
585 text += m->line_height;
586 minheight = text + space;
587
588 if (m != l && minheight > page_height)
589 break;
590
591 /*
592 * Compute the cost of this arrangement, as the square
593 * of the amount of wasted space on the page.
594 * Exception: if this is the last page before a
595 * mandatory break or the document end, we don't
596 * penalise a large blank area.
597 */
598 if (m->next && !m->next->page_break)
599 {
600 int x = page_height - minheight;
601 int xf;
602
603 xf = x & 0xFF;
604 x >>= 8;
605
606 cost = x*x;
607 cost += (x * xf) >> 8;
608 } else
609 cost = 0;
610
611 /*
612 * FIXME: here I should introduce penalties for
613 * breaking in mid-paragraph, particularly very close
614 * to one end of a paragraph and particularly in code
615 * paragraphs.
616 */
617
618 if (m->next && !m->next->page_break)
619 cost += m->next->bestcost;
620
621 if (l->bestcost == -1 || l->bestcost > cost) {
622 /*
623 * This is the best option yet for this starting
624 * point.
625 */
626 l->bestcost = cost;
627 if (m->next && !m->next->page_break)
628 l->vshortfall = page_height - minheight;
629 else
630 l->vshortfall = 0;
631 l->text = text;
632 l->space = space;
633 l->page_last = m;
634 }
635 }
636 }
637
638 /*
639 * Now go through the line list forwards and assemble the
640 * actual pages.
641 */
642 ph = pt = NULL;
643
644 l = first;
645 while (l) {
646 page_data *page;
647 int text, space;
648
649 page = mknew(page_data);
650 page->next = NULL;
651 page->prev = pt;
652 if (pt)
653 pt->next = page;
654 else
655 ph = page;
656 pt = page;
657
658 page->first_line = l;
659 page->last_line = l->page_last;
660
661 page->first_text = page->last_text = NULL;
662
663 /*
664 * Now assign a y-coordinate to each line on the page.
665 */
666 text = space = 0;
667 for (l = page->first_line; l; l = l->next) {
668 if (l != page->first_line)
669 space += l->prev->space_after;
670 if (l != page->first_line || l->page_break)
671 space += l->space_before;
672 text += l->line_height;
673
674 l->page = page;
675 l->ypos = text + space +
676 space * (float)page->first_line->vshortfall /
677 page->first_line->space;
678
679 if (l == page->last_line)
680 break;
681 }
682
683 l = page->last_line->next;
684 }
685
686 return ph;
687 }
688
689 static void add_string_to_page(page_data *page, int x, int y,
690 font_encoding *fe, int size, char *text)
691 {
692 text_fragment *frag;
693
694 frag = mknew(text_fragment);
695 frag->next = NULL;
696
697 if (page->last_text)
698 page->last_text->next = frag;
699 else
700 page->first_text = frag;
701 page->last_text = frag;
702
703 frag->x = x;
704 frag->y = y;
705 frag->fe = fe;
706 frag->fontsize = size;
707 frag->text = dupstr(text);
708 }
709
710 /*
711 * Returns the updated x coordinate.
712 */
713 static int render_string(page_data *page, font_data *font, int fontsize,
714 int x, int y, wchar_t *str)
715 {
716 char *text;
717 int textpos, textwid, glyph;
718 font_encoding *subfont = NULL, *sf;
719
720 text = mknewa(char, 1 + ustrlen(str));
721 textpos = textwid = 0;
722
723 while (*str) {
724 glyph = font->bmp[*str];
725
726 if (glyph == 0xFFFF)
727 continue; /* nothing more we can do here */
728
729 /*
730 * Find which subfont this character is going in.
731 */
732 sf = font->subfont_map[glyph].subfont;
733
734 if (!sf) {
735 int c;
736
737 /*
738 * This character is not yet in a subfont. Assign one.
739 */
740 if (font->latest_subfont->free_pos >= 0x100)
741 font->latest_subfont = new_font_encoding(font);
742
743 c = font->latest_subfont->free_pos++;
744 if (font->latest_subfont->free_pos == 0x7F)
745 font->latest_subfont->free_pos = 0xA1;
746
747 font->subfont_map[glyph].subfont = font->latest_subfont;
748 font->subfont_map[glyph].position = c;
749 font->latest_subfont->vector[c] = font->glyphs[glyph];
750 font->latest_subfont->indices[c] = glyph;
751 font->latest_subfont->to_unicode[c] = *str;
752
753 sf = font->latest_subfont;
754 }
755
756 if (!subfont || sf != subfont) {
757 if (subfont) {
758 text[textpos] = '\0';
759 add_string_to_page(page, x, y, subfont, fontsize, text);
760 x += textwid;
761 } else {
762 assert(textpos == 0);
763 }
764 textpos = 0;
765 subfont = sf;
766 }
767
768 text[textpos++] = font->subfont_map[glyph].position;
769 textwid += font->widths[glyph] * fontsize;
770
771 str++;
772 }
773
774 if (textpos > 0) {
775 text[textpos] = '\0';
776 add_string_to_page(page, x, y, subfont, fontsize, text);
777 x += textwid;
778 }
779
780 return x;
781 }
782
783 /*
784 * Returns the updated x coordinate.
785 */
786 static int render_text(page_data *page, para_data *pdata, int x, int y,
787 word *text, word *text_end,
788 int shortfall, int nspaces, int *nspace)
789 {
790 while (text && text != text_end) {
791 int style, type, findex, errs;
792 wchar_t *str;
793
794 switch (text->type) {
795 case word_HyperLink:
796 case word_HyperEnd:
797 case word_UpperXref:
798 case word_LowerXref:
799 case word_XrefEnd:
800 case word_IndexRef:
801 goto nextword;
802 /*
803 * FIXME: we should do something with all of these!
804 * Hyperlinks and xrefs have meaning in PDF, and this
805 * is probably the right place to nail down the index
806 * references too.
807 */
808 }
809
810 style = towordstyle(text->type);
811 type = removeattr(text->type);
812
813 findex = (style == word_Normal ? FONT_NORMAL :
814 style == word_Emph ? FONT_EMPH :
815 FONT_CODE);
816
817 if (type == word_Normal) {
818 str = text->text;
819 } else if (type == word_WhiteSpace) {
820 x += pdata->sizes[findex] *
821 string_width(pdata->fonts[findex], L" ", NULL);
822 if (nspaces && findex != FONT_CODE) {
823 x += (*nspace+1) * shortfall / nspaces;
824 x -= *nspace * shortfall / nspaces;
825 (*nspace)++;
826 }
827 goto nextword;
828 } else /* if (type == word_Quote) */ {
829 if (text->aux == quote_Open)
830 str = L"\x2018"; /* FIXME: configurability! */
831 else
832 str = L"\x2019"; /* FIXME: configurability! */
833 }
834
835 (void) string_width(pdata->fonts[findex], str, &errs);
836
837 if (errs && text->alt)
838 x = render_text(page, pdata, x, y, text->alt, NULL,
839 shortfall, nspaces, nspace);
840 else
841 x = render_string(page, pdata->fonts[findex],
842 pdata->sizes[findex], x, y, str);
843
844 nextword:
845 text = text->next;
846 }
847
848 return x;
849 }
850
851 static void render_line(line_data *ldata, int left_x, int top_y)
852 {
853 int nspace;
854 if (ldata->aux_text) {
855 nspace = 0;
856 render_text(ldata->page, ldata->pdata, left_x + ldata->aux_left_indent,
857 top_y - ldata->ypos, ldata->aux_text, NULL, 0, 0, &nspace);
858 }
859 nspace = 0;
860 render_text(ldata->page, ldata->pdata, left_x + ldata->xpos,
861 top_y - ldata->ypos, ldata->first, ldata->end,
862 ldata->hshortfall, ldata->nspaces, &nspace);
863 }