2 * Paper printing pre-backend for Halibut.
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.
15 * - set up contents section now we know what sections begin on
20 * - header/footer? Page numbers at least would be handy. Fully
21 * configurable footer can wait, though.
23 * That should bring us to the same level of functionality that
24 * original-Halibut had, and the same in PDF plus the obvious
25 * interactive navigation features. After that, in future work:
27 * - linearised PDF, perhaps?
29 * - I'm uncertain of whether I need to include a ToUnicode CMap
30 * in each of my font definitions in PDF. Currently things (by
31 * which I mean cut and paste out of acroread) seem to be
32 * working fairly happily without it, but I don't know.
45 static font_data
*make_std_font(font_list
*fontlist
, char const *name
);
46 static void wrap_paragraph(para_data
*pdata
, word
*words
,
47 int w
, int i1
, int i2
);
48 static page_data
*page_breaks(line_data
*first
, line_data
*last
,
50 static void render_line(line_data
*ldata
, int left_x
, int top_y
,
51 xref_dest
*dest
, keywordlist
*keywords
);
52 static int paper_width_simple(para_data
*pdata
, word
*text
);
53 static void code_paragraph(para_data
*pdata
,
54 font_data
*fn
, font_data
*fi
, font_data
*fb
,
55 int font_size
, int indent
, word
*words
);
56 static void rule_paragraph(para_data
*pdata
, int indent
, int height
);
57 static void add_rect_to_page(page_data
*page
, int x
, int y
, int w
, int h
);
59 void *paper_pre_backend(paragraph
*sourceform
, keywordlist
*keywords
,
63 int indent
, extra_indent
, firstline_indent
, aux_indent
;
65 line_data
*ldata
, *firstline
, *lastline
;
66 font_data
*tr
, *ti
, *hr
, *hi
, *cr
, *co
, *cb
;
72 * FIXME: All these things ought to become configurable.
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 int chapter_top_space
= 72 * 4096;
86 int sect_num_left_space
= 12 * 4096;
87 int chapter_underline_depth
= 14 * 4096;
88 int chapter_underline_thickness
= 3 * 4096;
89 int rule_thickness
= 1 * 4096;
91 int base_width
= paper_width
- left_margin
- right_margin
;
92 int page_height
= paper_height
- top_margin
- bottom_margin
;
94 IGNORE(idx
); /* FIXME */
97 * First, set up some font structures.
99 fontlist
= mknew(font_list
);
100 fontlist
->head
= fontlist
->tail
= NULL
;
101 tr
= make_std_font(fontlist
, "Times-Roman");
102 ti
= make_std_font(fontlist
, "Times-Italic");
103 hr
= make_std_font(fontlist
, "Helvetica-Bold");
104 hi
= make_std_font(fontlist
, "Helvetica-BoldOblique");
105 cr
= make_std_font(fontlist
, "Courier");
106 co
= make_std_font(fontlist
, "Courier-Oblique");
107 cb
= make_std_font(fontlist
, "Courier-Bold");
110 * Go through and break up each paragraph into lines.
113 firstline
= lastline
= NULL
;
114 for (p
= sourceform
; p
; p
= p
->next
) {
115 p
->private_data
= NULL
;
119 * These paragraph types are either invisible or don't
120 * define text in the normal sense. Either way, they
121 * don't require wrapping.
126 case para_NotParaType
:
133 * These paragraph types don't require wrapping, but
134 * they do affect the line width to which we wrap the
135 * rest of the paragraphs, so we need to pay attention.
138 indent
+= indent_list
; break;
140 indent
-= indent_list
; assert(indent
>= 0); break;
142 indent
+= indent_quote
; break;
144 indent
-= indent_quote
; assert(indent
>= 0); break;
147 * This paragraph type is special. Process it
151 pdata
= mknew(para_data
);
152 code_paragraph(pdata
, cr
, co
, cb
, 12, indent
, p
->words
);
153 p
->private_data
= pdata
;
154 if (pdata
->first
!= pdata
->last
) {
155 pdata
->first
->penalty_after
+= 100000;
156 pdata
->last
->penalty_before
+= 100000;
161 * This paragraph is also special.
164 pdata
= mknew(para_data
);
165 rule_paragraph(pdata
, indent
, rule_thickness
);
166 p
->private_data
= pdata
;
170 * All of these paragraph types require wrapping in the
171 * ordinary way. So we must supply a set of fonts, a
172 * line width and auxiliary information (e.g. bullet
173 * text) for each one.
177 case para_UnnumberedChapter
:
181 case para_BiblioCited
:
183 case para_NumberedList
:
184 case para_DescribedThing
:
185 case para_Description
:
188 pdata
= mknew(para_data
);
191 * Choose fonts for this paragraph.
193 * FIXME: All of this ought to be completely
198 pdata
->fonts
[FONT_NORMAL
] = hr
;
199 pdata
->sizes
[FONT_NORMAL
] = 24;
200 pdata
->fonts
[FONT_EMPH
] = hi
;
201 pdata
->sizes
[FONT_EMPH
] = 24;
202 pdata
->fonts
[FONT_CODE
] = cb
;
203 pdata
->sizes
[FONT_CODE
] = 24;
208 case para_UnnumberedChapter
:
209 pdata
->fonts
[FONT_NORMAL
] = hr
;
210 pdata
->sizes
[FONT_NORMAL
] = 20;
211 pdata
->fonts
[FONT_EMPH
] = hi
;
212 pdata
->sizes
[FONT_EMPH
] = 20;
213 pdata
->fonts
[FONT_CODE
] = cb
;
214 pdata
->sizes
[FONT_CODE
] = 20;
219 pdata
->fonts
[FONT_NORMAL
] = hr
;
220 pdata
->fonts
[FONT_EMPH
] = hi
;
221 pdata
->fonts
[FONT_CODE
] = cb
;
222 pdata
->sizes
[FONT_NORMAL
] =
223 pdata
->sizes
[FONT_EMPH
] =
224 pdata
->sizes
[FONT_CODE
] =
225 (p
->aux
== 0 ?
16 : p
->aux
== 1 ?
14 : 13);
229 case para_BiblioCited
:
231 case para_NumberedList
:
232 case para_DescribedThing
:
233 case para_Description
:
235 pdata
->fonts
[FONT_NORMAL
] = tr
;
236 pdata
->sizes
[FONT_NORMAL
] = 12;
237 pdata
->fonts
[FONT_EMPH
] = ti
;
238 pdata
->sizes
[FONT_EMPH
] = 12;
239 pdata
->fonts
[FONT_CODE
] = cr
;
240 pdata
->sizes
[FONT_CODE
] = 12;
245 * Also select an indentation level depending on the
246 * paragraph type (list paragraphs other than
247 * para_DescribedThing need extra indent).
249 * (FIXME: Perhaps at some point we might even arrange
250 * for the user to be able to request indented first
251 * lines in paragraphs.)
253 if (p
->type
== para_Bullet
||
254 p
->type
== para_NumberedList
||
255 p
->type
== para_Description
) {
256 extra_indent
= firstline_indent
= indent_list
;
258 extra_indent
= firstline_indent
= 0;
262 * Find the auxiliary text for this paragraph.
273 * For some heading styles (FIXME: be able to
274 * configure which), the auxiliary text contains
275 * the chapter number and is arranged to be
276 * right-aligned a few points left of the primary
277 * margin. For other styles, the auxiliary text is
278 * the full chapter _name_ and takes up space
279 * within the (wrapped) chapter title, meaning that
280 * we must move the first line indent over to make
283 if (p
->type
== para_Heading
|| p
->type
== para_Subsect
) {
287 len
= paper_width_simple(pdata
, p
->kwtext2
);
288 aux_indent
= -len
- sect_num_left_space
;
294 aux2
->type
= word_Normal
;
295 aux2
->text
= ustrdup(L
": ");
296 aux2
->breaks
= FALSE
;
300 firstline_indent
+= paper_width_simple(pdata
, aux
);
301 firstline_indent
+= paper_width_simple(pdata
, aux2
);
307 * Auxiliary text consisting of a bullet. (FIXME:
308 * configurable bullet.)
313 aux
->type
= word_Normal
;
314 aux
->text
= ustrdup(L
"\x2022");
317 aux_indent
= indent
+ indent_list_bullet
;
320 case para_NumberedList
:
322 * Auxiliary text consisting of the number followed
323 * by a (FIXME: configurable) full stop.
329 aux2
->type
= word_Normal
;
330 aux2
->text
= ustrdup(L
".");
331 aux2
->breaks
= FALSE
;
333 aux_indent
= indent
+ indent_list_bullet
;
336 case para_BiblioCited
:
338 * Auxiliary text consisting of the bibliography
339 * reference text, and a trailing space.
345 aux2
->type
= word_Normal
;
346 aux2
->text
= ustrdup(L
" ");
347 aux2
->breaks
= FALSE
;
350 firstline_indent
+= paper_width_simple(pdata
, aux
);
351 firstline_indent
+= paper_width_simple(pdata
, aux2
);
355 wrap_paragraph(pdata
, p
->words
, base_width
,
356 indent
+ firstline_indent
,
357 indent
+ extra_indent
);
359 p
->private_data
= pdata
;
361 pdata
->first
->aux_text
= aux
;
362 pdata
->first
->aux_text_2
= aux2
;
363 pdata
->first
->aux_left_indent
= aux_indent
;
366 * Line breaking penalties.
373 case para_UnnumberedChapter
:
375 * Fixed and large penalty for breaking straight
376 * after a heading; corresponding bonus for
377 * breaking straight before.
379 pdata
->first
->penalty_before
= -500000;
380 pdata
->last
->penalty_after
= 500000;
381 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
)
382 ldata
->penalty_after
= 500000;
385 case para_DescribedThing
:
387 * This is treated a bit like a small heading:
388 * there's a penalty for breaking after it (i.e.
389 * between it and its description), and a bonus for
390 * breaking before it (actually _between_ list
393 pdata
->first
->penalty_before
= -200000;
394 pdata
->last
->penalty_after
= 200000;
399 * Most paragraph types: widow/orphan control by
400 * discouraging breaking one line from the end of
403 if (pdata
->first
!= pdata
->last
) {
404 pdata
->first
->penalty_after
= 100000;
405 pdata
->last
->penalty_before
= 100000;
413 if (p
->private_data
) {
414 pdata
= (para_data
*)p
->private_data
;
417 * Set the line spacing for each line in this paragraph.
419 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
) {
420 if (ldata
== pdata
->first
)
421 ldata
->space_before
= base_para_spacing
/ 2;
423 ldata
->space_before
= base_leading
/ 2;
424 if (ldata
== pdata
->last
)
425 ldata
->space_after
= base_para_spacing
/ 2;
427 ldata
->space_after
= base_leading
/ 2;
428 ldata
->page_break
= FALSE
;
432 * Some kinds of section heading do require a page
435 if (p
->type
== para_Title
||
436 p
->type
== para_Chapter
||
437 p
->type
== para_Appendix
||
438 p
->type
== para_UnnumberedChapter
) {
439 pdata
->first
->page_break
= TRUE
;
440 pdata
->first
->space_before
= chapter_top_space
;
441 pdata
->last
->space_after
+=
442 chapter_underline_depth
+ chapter_underline_thickness
;
446 * Link all line structures together into a big list.
450 lastline
->next
= pdata
->first
;
451 pdata
->first
->prev
= lastline
;
453 firstline
= pdata
->first
;
454 pdata
->first
->prev
= NULL
;
456 lastline
= pdata
->last
;
462 * Now we have an enormous linked list of every line of text in
463 * the document. Break it up into pages.
465 pages
= page_breaks(firstline
, lastline
, page_height
);
468 * Now we're ready to actually lay out the pages. We do this by
469 * looping over _paragraphs_, since we may need to track cross-
470 * references between lines and even across pages.
472 for (p
= sourceform
; p
; p
= p
->next
) {
473 pdata
= (para_data
*)p
->private_data
;
478 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
) {
479 render_line(ldata
, left_margin
, paper_height
- top_margin
,
481 if (ldata
== pdata
->last
)
486 * Some section headings (FIXME: should be configurable
487 * which) want to be underlined.
489 if (p
->type
== para_Chapter
|| p
->type
== para_Appendix
||
490 p
->type
== para_UnnumberedChapter
|| p
->type
== para_Title
) {
491 add_rect_to_page(pdata
->last
->page
,
493 (paper_height
- top_margin
-
494 pdata
->last
->ypos
- chapter_underline_depth
),
496 chapter_underline_thickness
);
500 * Rule paragraphs need to contain an actual rule!
502 if (p
->type
== para_Rule
) {
503 add_rect_to_page(pdata
->first
->page
,
504 left_margin
+ pdata
->first
->xpos
,
505 (paper_height
- top_margin
-
507 pdata
->last
->line_height
),
508 base_width
- pdata
->first
->xpos
,
509 pdata
->last
->line_height
);
515 * Start putting together the overall document structure we're
518 doc
= mknew(document
);
519 doc
->fonts
= fontlist
;
521 doc
->paper_width
= paper_width
;
522 doc
->paper_height
= paper_height
;
525 * Collect the section heading paragraphs into a document
526 * outline. This is slightly fiddly because the Title paragraph
527 * isn't required to be at the start, although all the others
533 doc
->outline_elements
= mknewa(outline_element
, osize
);
534 doc
->n_outline_elements
= 0;
536 /* First find the title. */
537 for (p
= sourceform
; p
; p
= p
->next
) {
540 doc
->outline_elements
[0].level
= 0;
541 doc
->outline_elements
[0].para
= p
;
542 doc
->n_outline_elements
++;
547 /* Then collect the rest. */
548 for (p
= sourceform
; p
; p
= p
->next
) {
551 case para_UnnumberedChapter
:
556 if (doc
->n_outline_elements
>= osize
) {
558 doc
->outline_elements
=
559 resize(doc
->outline_elements
, osize
);
562 if (p
->type
== para_Heading
) {
563 doc
->outline_elements
[doc
->n_outline_elements
].level
= 2;
564 } else if (p
->type
== para_Subsect
) {
565 doc
->outline_elements
[doc
->n_outline_elements
].level
=
568 doc
->outline_elements
[doc
->n_outline_elements
].level
= 1;
570 doc
->outline_elements
[doc
->n_outline_elements
].para
= p
;
571 doc
->n_outline_elements
++;
580 static font_encoding
*new_font_encoding(font_data
*font
)
585 fe
= mknew(font_encoding
);
588 if (font
->list
->tail
)
589 font
->list
->tail
->next
= fe
;
591 font
->list
->head
= fe
;
592 font
->list
->tail
= fe
;
597 for (i
= 0; i
< 256; i
++) {
598 fe
->vector
[i
] = NULL
;
600 fe
->to_unicode
[i
] = 0xFFFF;
606 static font_data
*make_std_font(font_list
*fontlist
, char const *name
)
614 widths
= ps_std_font_widths(name
);
618 for (nglyphs
= 0; ps_std_glyphs
[nglyphs
] != NULL
; nglyphs
++);
620 f
= mknew(font_data
);
624 f
->nglyphs
= nglyphs
;
625 f
->glyphs
= ps_std_glyphs
;
627 f
->subfont_map
= mknewa(subfont_map_entry
, nglyphs
);
630 * Our first subfont will contain all of US-ASCII. This isn't
631 * really necessary - we could just create custom subfonts
632 * precisely as the whim of render_string dictated - but
633 * instinct suggests that it might be nice to have the text in
634 * the output files look _marginally_ recognisable.
636 fe
= new_font_encoding(f
);
637 fe
->free_pos
= 0xA1; /* only the top half is free */
638 f
->latest_subfont
= fe
;
640 for (i
= 0; i
< (int)lenof(f
->bmp
); i
++)
643 for (i
= 0; i
< nglyphs
; i
++) {
645 ucs
= ps_glyph_to_unicode(f
->glyphs
[i
]);
646 assert(ucs
!= 0xFFFF);
648 if (ucs
>= 0x20 && ucs
<= 0x7E) {
649 fe
->vector
[ucs
] = f
->glyphs
[i
];
650 fe
->indices
[ucs
] = i
;
651 fe
->to_unicode
[ucs
] = ucs
;
652 f
->subfont_map
[i
].subfont
= fe
;
653 f
->subfont_map
[i
].position
= ucs
;
656 * This character is not yet assigned to a subfont.
658 f
->subfont_map
[i
].subfont
= NULL
;
659 f
->subfont_map
[i
].position
= 0;
666 static int string_width(font_data
*font
, wchar_t const *string
, int *errs
)
673 for (; *string
; string
++) {
676 index
= font
->bmp
[(unsigned short)*string
];
677 if (index
== 0xFFFF) {
681 width
+= font
->widths
[index
];
688 static int paper_width_internal(void *vctx
, word
*word
, int *nspaces
);
690 struct paper_width_ctx
{
695 static int paper_width_list(void *vctx
, word
*text
, word
*end
, int *nspaces
) {
697 while (text
&& text
!= end
) {
698 w
+= paper_width_internal(vctx
, text
, nspaces
);
704 static int paper_width_internal(void *vctx
, word
*word
, int *nspaces
)
706 struct paper_width_ctx
*ctx
= (struct paper_width_ctx
*)vctx
;
707 int style
, type
, findex
, width
, errs
;
710 switch (word
->type
) {
720 style
= towordstyle(word
->type
);
721 type
= removeattr(word
->type
);
723 findex
= (style
== word_Normal ? FONT_NORMAL
:
724 style
== word_Emph ? FONT_EMPH
:
727 if (type
== word_Normal
) {
729 } else if (type
== word_WhiteSpace
) {
730 if (findex
!= FONT_CODE
) {
733 return ctx
->minspacewidth
;
736 } else /* if (type == word_Quote) */ {
737 if (word
->aux
== quote_Open
)
738 str
= L
"\x2018"; /* FIXME: configurability! */
740 str
= L
"\x2019"; /* FIXME: configurability! */
743 width
= string_width(ctx
->pdata
->fonts
[findex
], str
, &errs
);
745 if (errs
&& word
->alt
)
746 return paper_width_list(vctx
, word
->alt
, NULL
, nspaces
);
748 return ctx
->pdata
->sizes
[findex
] * width
;
751 static int paper_width(void *vctx
, word
*word
)
753 return paper_width_internal(vctx
, word
, NULL
);
756 static int paper_width_simple(para_data
*pdata
, word
*text
)
758 struct paper_width_ctx ctx
;
762 (pdata
->sizes
[FONT_NORMAL
] *
763 string_width(pdata
->fonts
[FONT_NORMAL
], L
" ", NULL
));
765 return paper_width_list(&ctx
, text
, NULL
, NULL
);
768 static void wrap_paragraph(para_data
*pdata
, word
*words
,
769 int w
, int i1
, int i2
)
771 wrappedline
*wrapping
, *p
;
773 struct paper_width_ctx ctx
;
777 * We're going to need to store the line height in every line
778 * structure we generate.
783 for (i
= 0; i
< NFONTS
; i
++)
784 if (line_height
< pdata
->sizes
[i
])
785 line_height
= pdata
->sizes
[i
];
789 spacewidth
= (pdata
->sizes
[FONT_NORMAL
] *
790 string_width(pdata
->fonts
[FONT_NORMAL
], L
" ", NULL
));
791 if (spacewidth
== 0) {
793 * A font without a space?! Disturbing. I hope this never
794 * comes up, but I'll make a random guess anyway and set my
795 * space width to half the point size.
797 spacewidth
= pdata
->sizes
[FONT_NORMAL
] * 4096 / 2;
801 * I'm going to set the _minimum_ space width to 3/5 of the
802 * standard one, and use the standard one as the optimum.
804 ctx
.minspacewidth
= spacewidth
* 3 / 5;
807 wrapping
= wrap_para(words
, w
- i1
, w
- i2
, paper_width
, &ctx
, spacewidth
);
810 * Having done the wrapping, we now concoct a set of line_data
813 pdata
->first
= pdata
->last
= NULL
;
815 for (p
= wrapping
; p
; p
= p
->next
) {
818 int len
, wid
, spaces
;
820 ldata
= mknew(line_data
);
822 ldata
->pdata
= pdata
;
823 ldata
->first
= p
->begin
;
825 ldata
->line_height
= line_height
;
827 ldata
->xpos
= (p
== wrapping ? i1
: i2
);
830 pdata
->last
->next
= ldata
;
831 ldata
->prev
= pdata
->last
;
833 pdata
->first
= ldata
;
840 len
= paper_width_list(&ctx
, ldata
->first
, ldata
->end
, &spaces
);
841 wid
= (p
== wrapping ? w
- i1
: w
- i2
);
844 ldata
->hshortfall
= wid
- len
;
845 ldata
->nspaces
= spaces
;
847 * This tells us how much the space width needs to
848 * change from _min_spacewidth. But we want to store
849 * its difference from the _natural_ space width, to
850 * make the text rendering easier.
852 ldata
->hshortfall
+= ctx
.minspacewidth
* spaces
;
853 ldata
->hshortfall
-= spacewidth
* spaces
;
855 * Special case: on the last line of a paragraph, we
856 * never stretch spaces.
858 if (ldata
->hshortfall
> 0 && !p
->next
)
859 ldata
->hshortfall
= 0;
861 ldata
->aux_text
= NULL
;
862 ldata
->aux_text_2
= NULL
;
863 ldata
->aux_left_indent
= 0;
864 ldata
->penalty_before
= ldata
->penalty_after
= 0;
869 static page_data
*page_breaks(line_data
*first
, line_data
*last
,
876 * Page breaking is done by a close analogue of the optimal
877 * paragraph wrapping algorithm used by wrap_para(). We work
878 * backwards from the end of the document line by line; for
879 * each line, we contemplate every possible number of lines we
880 * could put on a page starting with that line, determine a
881 * cost function for each one, add it to the pre-computed cost
882 * function for optimally page-breaking everything after that
883 * page, and pick the best option.
885 * Since my line_data structures are only used for this
886 * purpose, I might as well just store the algorithm data
890 for (l
= last
; l
; l
= l
->prev
) {
891 int minheight
, text
= 0, space
= 0;
895 for (m
= l
; m
; m
= m
->next
) {
896 if (m
!= l
&& m
->page_break
)
897 break; /* we've gone as far as we can */
900 space
+= m
->prev
->space_after
;
901 if (m
!= l
|| m
->page_break
)
902 space
+= m
->space_before
;
903 text
+= m
->line_height
;
904 minheight
= text
+ space
;
906 if (m
!= l
&& minheight
> page_height
)
910 * Compute the cost of this arrangement, as the square
911 * of the amount of wasted space on the page.
912 * Exception: if this is the last page before a
913 * mandatory break or the document end, we don't
914 * penalise a large blank area.
916 if (m
->next
&& !m
->next
->page_break
)
918 int x
= page_height
- minheight
;
925 cost
+= (x
* xf
) >> 8;
929 if (m
->next
&& !m
->next
->page_break
) {
930 cost
+= m
->penalty_after
;
931 cost
+= m
->next
->penalty_before
;
934 if (m
->next
&& !m
->next
->page_break
)
935 cost
+= m
->next
->bestcost
;
936 if (l
->bestcost
== -1 || l
->bestcost
> cost
) {
938 * This is the best option yet for this starting
942 if (m
->next
&& !m
->next
->page_break
)
943 l
->vshortfall
= page_height
- minheight
;
954 * Now go through the line list forwards and assemble the
964 page
= mknew(page_data
);
973 page
->first_line
= l
;
974 page
->last_line
= l
->page_last
;
976 page
->first_text
= page
->last_text
= NULL
;
977 page
->first_xref
= page
->last_xref
= NULL
;
978 page
->first_rect
= page
->last_rect
= NULL
;
981 * Now assign a y-coordinate to each line on the page.
984 for (l
= page
->first_line
; l
; l
= l
->next
) {
985 if (l
!= page
->first_line
)
986 space
+= l
->prev
->space_after
;
987 if (l
!= page
->first_line
|| l
->page_break
)
988 space
+= l
->space_before
;
989 text
+= l
->line_height
;
992 l
->ypos
= text
+ space
+
993 space
* (float)page
->first_line
->vshortfall
/
994 page
->first_line
->space
;
996 if (l
== page
->last_line
)
1000 l
= page
->last_line
->next
;
1006 static void add_rect_to_page(page_data
*page
, int x
, int y
, int w
, int h
)
1008 rect
*r
= mknew(rect
);
1011 if (page
->last_rect
)
1012 page
->last_rect
->next
= r
;
1014 page
->first_rect
= r
;
1015 page
->last_rect
= r
;
1023 static void add_string_to_page(page_data
*page
, int x
, int y
,
1024 font_encoding
*fe
, int size
, char *text
)
1026 text_fragment
*frag
;
1028 frag
= mknew(text_fragment
);
1031 if (page
->last_text
)
1032 page
->last_text
->next
= frag
;
1034 page
->first_text
= frag
;
1035 page
->last_text
= frag
;
1040 frag
->fontsize
= size
;
1041 frag
->text
= dupstr(text
);
1045 * Returns the updated x coordinate.
1047 static int render_string(page_data
*page
, font_data
*font
, int fontsize
,
1048 int x
, int y
, wchar_t *str
)
1051 int textpos
, textwid
, glyph
;
1052 font_encoding
*subfont
= NULL
, *sf
;
1054 text
= mknewa(char, 1 + ustrlen(str
));
1055 textpos
= textwid
= 0;
1058 glyph
= font
->bmp
[*str
];
1060 if (glyph
== 0xFFFF)
1061 continue; /* nothing more we can do here */
1064 * Find which subfont this character is going in.
1066 sf
= font
->subfont_map
[glyph
].subfont
;
1072 * This character is not yet in a subfont. Assign one.
1074 if (font
->latest_subfont
->free_pos
>= 0x100)
1075 font
->latest_subfont
= new_font_encoding(font
);
1077 c
= font
->latest_subfont
->free_pos
++;
1078 if (font
->latest_subfont
->free_pos
== 0x7F)
1079 font
->latest_subfont
->free_pos
= 0xA1;
1081 font
->subfont_map
[glyph
].subfont
= font
->latest_subfont
;
1082 font
->subfont_map
[glyph
].position
= c
;
1083 font
->latest_subfont
->vector
[c
] = font
->glyphs
[glyph
];
1084 font
->latest_subfont
->indices
[c
] = glyph
;
1085 font
->latest_subfont
->to_unicode
[c
] = *str
;
1087 sf
= font
->latest_subfont
;
1090 if (!subfont
|| sf
!= subfont
) {
1092 text
[textpos
] = '\0';
1093 add_string_to_page(page
, x
, y
, subfont
, fontsize
, text
);
1096 assert(textpos
== 0);
1102 text
[textpos
++] = font
->subfont_map
[glyph
].position
;
1103 textwid
+= font
->widths
[glyph
] * fontsize
;
1109 text
[textpos
] = '\0';
1110 add_string_to_page(page
, x
, y
, subfont
, fontsize
, text
);
1118 * Returns the updated x coordinate.
1120 static int render_text(page_data
*page
, para_data
*pdata
, line_data
*ldata
,
1121 int x
, int y
, word
*text
, word
*text_end
, xref
**xr
,
1122 int shortfall
, int nspaces
, int *nspace
,
1123 keywordlist
*keywords
)
1125 while (text
&& text
!= text_end
) {
1126 int style
, type
, findex
, errs
;
1130 switch (text
->type
) {
1132 * Start a cross-reference.
1134 case word_HyperLink
:
1135 case word_UpperXref
:
1136 case word_LowerXref
:
1138 if (text
->type
== word_HyperLink
) {
1140 dest
.url
= utoa_dup(text
->text
);
1143 keyword
*kwl
= kw_lookup(keywords
, text
->text
);
1147 assert(kwl
->para
->private_data
);
1148 pdata
= (para_data
*) kwl
->para
->private_data
;
1150 dest
.page
= pdata
->first
->page
;
1154 * Shouldn't happen, but *shrug*
1161 if (dest
.type
!= NONE
) {
1163 (*xr
)->dest
= dest
; /* structure copy */
1164 if (page
->last_xref
)
1165 page
->last_xref
->next
= *xr
;
1167 page
->first_xref
= *xr
;
1168 page
->last_xref
= *xr
;
1172 * FIXME: Ideally we should have, and use, some
1173 * vertical font metric information here so that
1174 * our cross-ref rectangle can take account of
1175 * descenders and the font's cap height. This will
1176 * do for the moment, but it isn't ideal.
1178 (*xr
)->lx
= (*xr
)->rx
= x
;
1180 (*xr
)->ty
= y
+ ldata
->line_height
;
1185 * Finish extending a cross-reference box.
1195 * FIXME: we should do something with this.
1199 style
= towordstyle(text
->type
);
1200 type
= removeattr(text
->type
);
1202 findex
= (style
== word_Normal ? FONT_NORMAL
:
1203 style
== word_Emph ? FONT_EMPH
:
1206 if (type
== word_Normal
) {
1208 } else if (type
== word_WhiteSpace
) {
1209 x
+= pdata
->sizes
[findex
] *
1210 string_width(pdata
->fonts
[findex
], L
" ", NULL
);
1211 if (nspaces
&& findex
!= FONT_CODE
) {
1212 x
+= (*nspace
+1) * shortfall
/ nspaces
;
1213 x
-= *nspace
* shortfall
/ nspaces
;
1217 } else /* if (type == word_Quote) */ {
1218 if (text
->aux
== quote_Open
)
1219 str
= L
"\x2018"; /* FIXME: configurability! */
1221 str
= L
"\x2019"; /* FIXME: configurability! */
1224 (void) string_width(pdata
->fonts
[findex
], str
, &errs
);
1226 if (errs
&& text
->alt
)
1227 x
= render_text(page
, pdata
, ldata
, x
, y
, text
->alt
, NULL
,
1228 xr
, shortfall
, nspaces
, nspace
, keywords
);
1230 x
= render_string(page
, pdata
->fonts
[findex
],
1231 pdata
->sizes
[findex
], x
, y
, str
);
1243 static void render_line(line_data
*ldata
, int left_x
, int top_y
,
1244 xref_dest
*dest
, keywordlist
*keywords
)
1249 if (ldata
->aux_text
) {
1253 x
= render_text(ldata
->page
, ldata
->pdata
, ldata
,
1254 left_x
+ ldata
->aux_left_indent
,
1255 top_y
- ldata
->ypos
,
1256 ldata
->aux_text
, NULL
, &xr
, 0, 0, &nspace
, keywords
);
1257 if (ldata
->aux_text_2
)
1258 render_text(ldata
->page
, ldata
->pdata
, ldata
,
1259 x
, top_y
- ldata
->ypos
,
1260 ldata
->aux_text_2
, NULL
, &xr
, 0, 0, &nspace
, keywords
);
1266 * There might be a cross-reference carried over from a
1269 if (dest
->type
!= NONE
) {
1272 xr
->dest
= *dest
; /* structure copy */
1273 if (ldata
->page
->last_xref
)
1274 ldata
->page
->last_xref
->next
= xr
;
1276 ldata
->page
->first_xref
= xr
;
1277 ldata
->page
->last_xref
= xr
;
1278 xr
->lx
= xr
->rx
= left_x
+ ldata
->xpos
;
1279 xr
->by
= top_y
- ldata
->ypos
;
1280 xr
->ty
= top_y
- ldata
->ypos
+ ldata
->line_height
;
1284 render_text(ldata
->page
, ldata
->pdata
, ldata
, left_x
+ ldata
->xpos
,
1285 top_y
- ldata
->ypos
, ldata
->first
, ldata
->end
, &xr
,
1286 ldata
->hshortfall
, ldata
->nspaces
, &nspace
, keywords
);
1290 * There's a cross-reference continued on to the next line.
1298 static void code_paragraph(para_data
*pdata
,
1299 font_data
*fn
, font_data
*fi
, font_data
*fb
,
1300 int font_size
, int indent
, word
*words
)
1303 * For code paragraphs, I'm going to hack grievously and
1304 * pretend the three normal fonts are the three code paragraph
1307 pdata
->fonts
[FONT_NORMAL
] = fb
;
1308 pdata
->fonts
[FONT_EMPH
] = fi
;
1309 pdata
->fonts
[FONT_CODE
] = fn
;
1310 pdata
->sizes
[FONT_NORMAL
] =
1311 pdata
->sizes
[FONT_EMPH
] =
1312 pdata
->sizes
[FONT_CODE
] = font_size
;
1314 pdata
->first
= pdata
->last
= NULL
;
1316 for (; words
; words
= words
->next
) {
1317 wchar_t *t
, *e
, *start
;
1318 word
*lhead
= NULL
, *ltail
= NULL
, *w
;
1320 int prev
= -1, curr
;
1323 if (words
->next
&& words
->next
->type
== word_Emph
) {
1324 e
= words
->next
->text
;
1325 words
= words
->next
;
1335 else if (*e
== L
'i')
1337 else if (*e
== L
'b')
1354 * We've isolated a maximal subsequence of the line
1355 * which has the same emphasis. Form it into a word
1361 w
->type
= (prev
== 0 ? word_WeakCode
:
1362 prev
== 1 ? word_Emph
: word_Normal
);
1363 w
->text
= mknewa(wchar_t, t
-start
+1);
1364 memcpy(w
->text
, start
, (t
-start
) * sizeof(wchar_t));
1365 w
->text
[t
-start
] = '\0';
1378 ldata
= mknew(line_data
);
1380 ldata
->pdata
= pdata
;
1381 ldata
->first
= lhead
;
1383 ldata
->line_height
= font_size
* 4096;
1385 ldata
->xpos
= indent
;
1388 pdata
->last
->next
= ldata
;
1389 ldata
->prev
= pdata
->last
;
1391 pdata
->first
= ldata
;
1395 pdata
->last
= ldata
;
1397 ldata
->hshortfall
= 0;
1399 ldata
->aux_text
= NULL
;
1400 ldata
->aux_text_2
= NULL
;
1401 ldata
->aux_left_indent
= 0;
1402 /* General opprobrium for breaking in a code paragraph. */
1403 ldata
->penalty_before
= ldata
->penalty_after
= 50000;
1407 static void rule_paragraph(para_data
*pdata
, int indent
, int height
)
1411 ldata
= mknew(line_data
);
1413 ldata
->pdata
= pdata
;
1414 ldata
->first
= NULL
;
1416 ldata
->line_height
= height
;
1418 ldata
->xpos
= indent
;
1423 ldata
->hshortfall
= 0;
1425 ldata
->aux_text
= NULL
;
1426 ldata
->aux_text_2
= NULL
;
1427 ldata
->aux_left_indent
= 0;
1430 * Better to break after a rule than before it
1432 ldata
->penalty_after
+= 100000;
1433 ldata
->penalty_before
+= -100000;
1435 pdata
->first
= pdata
->last
= ldata
;