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