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