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.
13 * TODO in future work:
15 * - linearised PDF, perhaps?
17 * - we should use PDFDocEncoding or Unicode for outline strings,
18 * now that I actually know how to do them. Probably easiest if
19 * I do this _after_ bringing in libcharset, since I can simply
20 * supply PDFDocEncoding in there.
22 * - I'm uncertain of whether I need to include a ToUnicode CMap
23 * in each of my font definitions in PDF. Currently things (by
24 * which I mean cut and paste out of acroread) seem to be
25 * working fairly happily without it, but I don't know.
27 * - rather than the ugly aux_text mechanism for rendering chapter
28 * titles, we could actually build the correct word list and
31 * - get vertical font metrics and use them to position the PDF
32 * xref boxes more pleasantly
35 * * page header and footer should be configurable; we should
36 * be able to shift the page number elsewhere, and add other
37 * things such as the current chapter/section title and fixed
39 * * remove the fixed mapping from heading levels to heading
40 * styles; offer a menu of styles from which the user can
41 * choose at every heading level
42 * * first-line indent in paragraphs
43 * * fixed text: `Contents', `Index', the colon-space and full
44 * stop in chapter title constructions
45 * * configurable location of contents?
46 * * certainly configurably _remove_ the contents, and possibly
48 * * double-sided document switch?
49 * + means you have two header/footer formats which
51 * + and means that mandatory page breaks before chapter
52 * titles should include a blank page if necessary to
53 * start the next section to a right-hand page
57 * - ability to import other Type 1 fonts
58 * * we need to parse the font to extract its metrics
59 * * then we pass the font bodily to both PS and PDF so it can
60 * be included in the output file
62 * - character substitution for better typography?
63 * * fi, fl, ffi, ffl ligatures
64 * * use real ellipsis rather than ...
65 * * a hyphen in a word by itself might prefer to be an en-dash
66 * * (Americans might even want a convenient way to use an
68 * * DON'T DO ANY OF THE ABOVE WITHIN \c OR \cw!
69 * * substituting `minus' for `hyphen' in the standard encoding
70 * is probably preferable in Courier, though certainly not in
72 * * if I do do this lot, I'm rather inclined to at least try
73 * to think up a configurable way to do it so that Americans
74 * can do em-dash tricks without my intervention and other
75 * people can do other odd things too.
85 typedef struct paper_conf_Tag paper_conf
;
86 typedef struct paper_idx_Tag paper_idx
;
88 struct paper_conf_Tag
{
95 int indent_list_bullet
;
96 int indent_list_after
;
100 int base_para_spacing
;
101 int chapter_top_space
;
102 int sect_num_left_space
;
103 int chapter_underline_depth
;
104 int chapter_underline_thickness
;
107 int contents_indent_step
;
109 int leader_separation
;
113 int pagenum_fontsize
;
115 wchar_t *lquote
, *rquote
, *bullet
;
116 /* These are derived from the above */
120 /* Fonts used in the configuration */
121 font_data
*tr
, *ti
, *hr
, *hi
, *cr
, *co
, *cb
;
124 struct paper_idx_Tag
{
126 * Word list giving the page numbers on which this index entry
127 * appears. Also the last word in the list, for ease of
133 * The last page added to the list (so we can ensure we don't
140 word_PageXref
= word_NotWordType
+ 1
143 static font_data
*make_std_font(font_list
*fontlist
, char const *name
);
144 static void wrap_paragraph(para_data
*pdata
, word
*words
,
145 int w
, int i1
, int i2
, paper_conf
*conf
);
146 static page_data
*page_breaks(line_data
*first
, line_data
*last
,
147 int page_height
, int ncols
, int headspace
);
148 static int render_string(page_data
*page
, font_data
*font
, int fontsize
,
149 int x
, int y
, wchar_t *str
);
150 static int render_line(line_data
*ldata
, int left_x
, int top_y
,
151 xref_dest
*dest
, keywordlist
*keywords
, indexdata
*idx
,
153 static void render_para(para_data
*pdata
, paper_conf
*conf
,
154 keywordlist
*keywords
, indexdata
*idx
,
155 paragraph
*index_placeholder
, page_data
*index_page
);
156 static int string_width(font_data
*font
, wchar_t const *string
, int *errs
);
157 static int paper_width_simple(para_data
*pdata
, word
*text
, paper_conf
*conf
);
158 static para_data
*code_paragraph(int indent
, word
*words
, paper_conf
*conf
);
159 static para_data
*rule_paragraph(int indent
, paper_conf
*conf
);
160 static void add_rect_to_page(page_data
*page
, int x
, int y
, int w
, int h
);
161 static para_data
*make_para_data(int ptype
, int paux
, int indent
, int rmargin
,
162 word
*pkwtext
, word
*pkwtext2
, word
*pwords
,
164 static void standard_line_spacing(para_data
*pdata
, paper_conf
*conf
);
165 static wchar_t *prepare_outline_title(word
*first
, wchar_t *separator
,
167 static word
*fake_word(wchar_t *text
);
168 static word
*fake_space_word(void);
169 static word
*fake_page_ref(page_data
*page
);
170 static word
*fake_end_ref(void);
171 static word
*prepare_contents_title(word
*first
, wchar_t *separator
,
173 static void fold_into_page(page_data
*dest
, page_data
*src
, int right_shift
);
175 static int fonts_ok(wchar_t *string
, ...)
181 va_start(ap
, string
);
182 while ( (font
= va_arg(ap
, font_data
*)) != NULL
) {
184 (void) string_width(font
, string
, &errs
);
195 static paper_conf
paper_configure(paragraph
*source
, font_list
*fontlist
) {
202 ret
.paper_width
= 595 * 4096;
203 ret
.paper_height
= 841 * 4096;
204 ret
.left_margin
= 72 * 4096;
205 ret
.top_margin
= 72 * 4096;
206 ret
.right_margin
= 72 * 4096;
207 ret
.bottom_margin
= 108 * 4096;
208 ret
.indent_list_bullet
= 6 * 4096;
209 ret
.indent_list_after
= 18 * 4096;
210 ret
.indent_quote
= 18 * 4096;
211 ret
.base_leading
= 4096;
212 ret
.base_para_spacing
= 10 * 4096;
213 ret
.chapter_top_space
= 72 * 4096;
214 ret
.sect_num_left_space
= 12 * 4096;
215 ret
.chapter_underline_depth
= 14 * 4096;
216 ret
.chapter_underline_thickness
= 3 * 4096;
217 ret
.rule_thickness
= 1 * 4096;
218 ret
.base_font_size
= 12;
219 ret
.contents_indent_step
= 24 * 4096;
220 ret
.contents_margin
= 84 * 4096;
221 ret
.leader_separation
= 12 * 4096;
222 ret
.index_gutter
= 36 * 4096;
224 ret
.index_minsep
= 18 * 4096;
225 ret
.pagenum_fontsize
= 12;
226 ret
.footer_distance
= 32 * 4096;
227 ret
.lquote
= L
"\x2018\0\x2019\0'\0'\0\0";
228 ret
.rquote
= uadv(ret
.lquote
);
229 ret
.bullet
= L
"\x2022\0-\0\0";
232 * Two-pass configuration so that we can pick up global config
233 * (e.g. `quotes') before having it overridden by specific
234 * config (`paper-quotes'), irrespective of the order in which
237 for (p
= source
; p
; p
= p
->next
) {
238 if (p
->type
== para_Config
) {
239 if (!ustricmp(p
->keyword
, L
"quotes")) {
240 if (*uadv(p
->keyword
) && *uadv(uadv(p
->keyword
))) {
241 ret
.lquote
= uadv(p
->keyword
);
242 ret
.rquote
= uadv(ret
.lquote
);
248 for (p
= source
; p
; p
= p
->next
) {
249 p
->private_data
= NULL
;
250 if (p
->type
== para_Config
) {
251 if (!ustricmp(p
->keyword
, L
"paper-quotes")) {
252 if (*uadv(p
->keyword
) && *uadv(uadv(p
->keyword
))) {
253 ret
.lquote
= uadv(p
->keyword
);
254 ret
.rquote
= uadv(ret
.lquote
);
256 } else if (!ustricmp(p
->keyword
, L
"paper-bullet")) {
257 ret
.bullet
= uadv(p
->keyword
);
258 } else if (!ustricmp(p
->keyword
, L
"paper-page-width")) {
260 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
261 } else if (!ustricmp(p
->keyword
, L
"paper-page-height")) {
263 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
264 } else if (!ustricmp(p
->keyword
, L
"paper-left-margin")) {
266 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
267 } else if (!ustricmp(p
->keyword
, L
"paper-top-margin")) {
269 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
270 } else if (!ustricmp(p
->keyword
, L
"paper-right-margin")) {
272 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
273 } else if (!ustricmp(p
->keyword
, L
"paper-bottom-margin")) {
275 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
276 } else if (!ustricmp(p
->keyword
, L
"paper-list-indent")) {
277 ret
.indent_list_bullet
=
278 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
279 } else if (!ustricmp(p
->keyword
, L
"paper-listitem-indent")) {
281 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
282 } else if (!ustricmp(p
->keyword
, L
"paper-quote-indent")) {
284 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
285 } else if (!ustricmp(p
->keyword
, L
"paper-base-leading")) {
287 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
288 } else if (!ustricmp(p
->keyword
, L
"paper-base-para-spacing")) {
289 ret
.base_para_spacing
=
290 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
291 } else if (!ustricmp(p
->keyword
, L
"paper-chapter-top-space")) {
292 ret
.chapter_top_space
=
293 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
294 } else if (!ustricmp(p
->keyword
, L
"paper-sect-num-left-space")) {
295 ret
.sect_num_left_space
=
296 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
297 } else if (!ustricmp(p
->keyword
, L
"paper-chapter-underline-depth")) {
298 ret
.chapter_underline_depth
=
299 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
300 } else if (!ustricmp(p
->keyword
, L
"paper-chapter-underline-thickness")) {
301 ret
.chapter_underline_thickness
=
302 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
303 } else if (!ustricmp(p
->keyword
, L
"paper-rule-thickness")) {
305 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
306 } else if (!ustricmp(p
->keyword
, L
"paper-contents-indent-step")) {
307 ret
.contents_indent_step
=
308 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
309 } else if (!ustricmp(p
->keyword
, L
"paper-contents-margin")) {
310 ret
.contents_margin
=
311 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
312 } else if (!ustricmp(p
->keyword
, L
"paper-leader-separation")) {
313 ret
.leader_separation
=
314 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
315 } else if (!ustricmp(p
->keyword
, L
"paper-index-gutter")) {
317 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
318 } else if (!ustricmp(p
->keyword
, L
"paper-index-minsep")) {
320 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
321 } else if (!ustricmp(p
->keyword
, L
"paper-footer-distance")) {
322 ret
.footer_distance
=
323 (int) 0.5 + 4096.0 * utof(uadv(p
->keyword
));
324 } else if (!ustricmp(p
->keyword
, L
"paper-base-font-size")) {
326 utoi(uadv(p
->keyword
));
327 } else if (!ustricmp(p
->keyword
, L
"paper-index-columns")) {
329 utoi(uadv(p
->keyword
));
330 } else if (!ustricmp(p
->keyword
, L
"paper-pagenum-font-size")) {
331 ret
.pagenum_fontsize
=
332 utoi(uadv(p
->keyword
));
338 * Set up the derived fields in the conf structure.
342 ret
.paper_width
- ret
.left_margin
- ret
.right_margin
;
344 ret
.paper_height
- ret
.top_margin
- ret
.bottom_margin
;
345 ret
.indent_list
= ret
.indent_list_bullet
+ ret
.indent_list_after
;
347 (ret
.base_width
- (ret
.index_cols
-1) * ret
.index_gutter
)
351 * Set up the font structures.
353 ret
.tr
= make_std_font(fontlist
, "Times-Roman");
354 ret
.ti
= make_std_font(fontlist
, "Times-Italic");
355 ret
.hr
= make_std_font(fontlist
, "Helvetica-Bold");
356 ret
.hi
= make_std_font(fontlist
, "Helvetica-BoldOblique");
357 ret
.cr
= make_std_font(fontlist
, "Courier");
358 ret
.co
= make_std_font(fontlist
, "Courier-Oblique");
359 ret
.cb
= make_std_font(fontlist
, "Courier-Bold");
362 * Now process fallbacks on quote characters and bullets. We
363 * use string_width() to determine whether all of the relevant
364 * fonts contain the same character, and fall back whenever we
365 * find a character which not all of them support.
368 /* Quote characters need not be supported in the fixed code fonts,
369 * but must be in the title and body fonts. */
370 while (*uadv(ret
.rquote
) && *uadv(uadv(ret
.rquote
)) &&
371 (!fonts_ok(ret
.lquote
, ret
.tr
, ret
.ti
, ret
.hr
, ret
.hi
, NULL
) ||
372 !fonts_ok(ret
.rquote
, ret
.tr
, ret
.ti
, ret
.hr
, ret
.hi
, NULL
))) {
373 ret
.lquote
= uadv(ret
.rquote
);
374 ret
.rquote
= uadv(ret
.lquote
);
377 /* The bullet character only needs to be supported in the normal body
378 * font (not even in italics). */
379 while (*ret
.bullet
&& *uadv(ret
.bullet
) &&
380 !fonts_ok(ret
.bullet
, ret
.tr
, NULL
))
381 ret
.bullet
= uadv(ret
.bullet
);
386 void *paper_pre_backend(paragraph
*sourceform
, keywordlist
*keywords
,
390 int indent
, used_contents
;
391 para_data
*pdata
, *firstpara
= NULL
, *lastpara
= NULL
;
392 para_data
*firstcont
, *lastcont
;
393 line_data
*firstline
, *lastline
, *firstcontline
, *lastcontline
;
396 paper_conf
*conf
, ourconf
;
399 paragraph index_placeholder_para
;
400 page_data
*first_index_page
;
402 fontlist
= snew(font_list
);
403 fontlist
->head
= fontlist
->tail
= NULL
;
405 ourconf
= paper_configure(sourceform
, fontlist
);
409 * Set up a data structure to collect page numbers for each
418 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
419 paper_idx
*pi
= snew(paper_idx
);
423 pi
->words
= pi
->lastword
= NULL
;
426 entry
->backend_data
= pi
;
431 * Format the contents entry for each heading.
434 word
*contents_title
;
435 contents_title
= fake_word(L
"Contents");
437 firstcont
= make_para_data(para_UnnumberedChapter
, 0, 0, 0,
438 NULL
, NULL
, contents_title
, conf
);
439 lastcont
= firstcont
;
440 lastcont
->next
= NULL
;
441 firstcontline
= firstcont
->first
;
442 lastcontline
= lastcont
->last
;
443 for (p
= sourceform
; p
; p
= p
->next
) {
450 case para_UnnumberedChapter
:
456 words
= prepare_contents_title(p
->kwtext
, L
": ", p
->words
);
459 case para_UnnumberedChapter
:
460 words
= prepare_contents_title(NULL
, NULL
, p
->words
);
465 words
= prepare_contents_title(p
->kwtext2
, L
" ", p
->words
);
466 indent
= (p
->aux
+ 1) * conf
->contents_indent_step
;
469 pdata
= make_para_data(para_Normal
, p
->aux
, indent
,
470 conf
->contents_margin
,
471 NULL
, NULL
, words
, conf
);
473 pdata
->contents_entry
= p
;
474 lastcont
->next
= pdata
;
478 * Link all contents line structures together into
483 lastcontline
->next
= pdata
->first
;
484 pdata
->first
->prev
= lastcontline
;
486 firstcontline
= pdata
->first
;
487 pdata
->first
->prev
= NULL
;
489 lastcontline
= pdata
->last
;
490 lastcontline
->next
= NULL
;
498 * And one extra one, for the index.
501 pdata
= make_para_data(para_Normal
, 0, 0,
502 conf
->contents_margin
,
503 NULL
, NULL
, fake_word(L
"Index"), conf
);
505 pdata
->contents_entry
= &index_placeholder_para
;
506 lastcont
->next
= pdata
;
511 lastcontline
->next
= pdata
->first
;
512 pdata
->first
->prev
= lastcontline
;
514 firstcontline
= pdata
->first
;
515 pdata
->first
->prev
= NULL
;
517 lastcontline
= pdata
->last
;
518 lastcontline
->next
= NULL
;
524 * Do the main paragraph formatting.
527 used_contents
= FALSE
;
528 firstline
= lastline
= NULL
;
529 for (p
= sourceform
; p
; p
= p
->next
) {
530 p
->private_data
= NULL
;
534 * These paragraph types are either invisible or don't
535 * define text in the normal sense. Either way, they
536 * don't require wrapping.
541 case para_NotParaType
:
548 * These paragraph types don't require wrapping, but
549 * they do affect the line width to which we wrap the
550 * rest of the paragraphs, so we need to pay attention.
553 indent
+= conf
->indent_list
; break;
555 indent
-= conf
->indent_list
; assert(indent
>= 0); break;
557 indent
+= conf
->indent_quote
; break;
559 indent
-= conf
->indent_quote
; assert(indent
>= 0); break;
562 * This paragraph type is special. Process it
566 pdata
= code_paragraph(indent
, p
->words
, conf
);
567 p
->private_data
= pdata
;
568 if (pdata
->first
!= pdata
->last
) {
569 pdata
->first
->penalty_after
+= 100000;
570 pdata
->last
->penalty_before
+= 100000;
575 * This paragraph is also special.
578 pdata
= rule_paragraph(indent
, conf
);
579 p
->private_data
= pdata
;
583 * All of these paragraph types require wrapping in the
584 * ordinary way. So we must supply a set of fonts, a
585 * line width and auxiliary information (e.g. bullet
586 * text) for each one.
590 case para_UnnumberedChapter
:
594 case para_BiblioCited
:
596 case para_NumberedList
:
597 case para_DescribedThing
:
598 case para_Description
:
601 pdata
= make_para_data(p
->type
, p
->aux
, indent
, 0,
602 p
->kwtext
, p
->kwtext2
, p
->words
, conf
);
604 p
->private_data
= pdata
;
609 if (p
->private_data
) {
610 pdata
= (para_data
*)p
->private_data
;
613 * If this is the first non-title heading, we link the
614 * contents section in before it.
616 if (!used_contents
&& pdata
->outline_level
> 0) {
617 used_contents
= TRUE
;
619 lastpara
->next
= firstcont
;
621 firstpara
= firstcont
;
623 assert(lastpara
->next
== NULL
);
626 lastline
->next
= firstcontline
;
627 firstcontline
->prev
= lastline
;
629 firstline
= firstcontline
;
630 firstcontline
->prev
= NULL
;
632 assert(lastcontline
!= NULL
);
633 lastline
= lastcontline
;
634 lastline
->next
= NULL
;
638 * Link all line structures together into a big list.
642 lastline
->next
= pdata
->first
;
643 pdata
->first
->prev
= lastline
;
645 firstline
= pdata
->first
;
646 pdata
->first
->prev
= NULL
;
648 lastline
= pdata
->last
;
649 lastline
->next
= NULL
;
653 * Link all paragraph structures together similarly.
657 lastpara
->next
= pdata
;
665 * Now we have an enormous linked list of every line of text in
666 * the document. Break it up into pages.
668 pages
= page_breaks(firstline
, lastline
, conf
->page_height
, 0, 0);
679 for (page
= pages
; page
; page
= page
->next
) {
680 sprintf(buf
, "%d", ++pagenum
);
681 page
->number
= ufroma_dup(buf
, CS_ASCII
);
685 first_index_page
= snew(page_data
);
686 first_index_page
->next
= first_index_page
->prev
= NULL
;
687 first_index_page
->first_line
= NULL
;
688 first_index_page
->last_line
= NULL
;
689 first_index_page
->first_text
= first_index_page
->last_text
= NULL
;
690 first_index_page
->first_xref
= first_index_page
->last_xref
= NULL
;
691 first_index_page
->first_rect
= first_index_page
->last_rect
= NULL
;
693 /* And don't forget the as-yet-uncreated index. */
694 sprintf(buf
, "%d", ++pagenum
);
695 first_index_page
->number
= ufroma_dup(buf
, CS_ASCII
);
700 * Now we're ready to actually lay out the pages. We do this by
701 * looping over _paragraphs_, since we may need to track cross-
702 * references between lines and even across pages.
704 for (pdata
= firstpara
; pdata
; pdata
= pdata
->next
)
705 render_para(pdata
, conf
, keywords
, idx
,
706 &index_placeholder_para
, first_index_page
);
709 * Now we've laid out the main body pages, we should have
710 * acquired a full set of page numbers for the index.
716 para_data
*firstidx
, *lastidx
;
717 line_data
*firstidxline
, *lastidxline
, *ldata
;
718 page_data
*ipages
, *ipages2
, *page
;
721 * Create a set of paragraphs for the index.
723 index_title
= fake_word(L
"Index");
725 firstidx
= make_para_data(para_UnnumberedChapter
, 0, 0, 0,
726 NULL
, NULL
, index_title
, conf
);
728 lastidx
->next
= NULL
;
729 firstidxline
= firstidx
->first
;
730 lastidxline
= lastidx
->last
;
731 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
732 paper_idx
*pi
= (paper_idx
*)entry
->backend_data
;
733 para_data
*text
, *pages
;
738 text
= make_para_data(para_Normal
, 0, 0,
739 conf
->base_width
- conf
->index_colwidth
,
740 NULL
, NULL
, entry
->text
, conf
);
742 pages
= make_para_data(para_Normal
, 0, 0,
743 conf
->base_width
- conf
->index_colwidth
,
744 NULL
, NULL
, pi
->words
, conf
);
746 text
->justification
= LEFT
;
747 pages
->justification
= RIGHT
;
748 text
->last
->space_after
= pages
->first
->space_before
=
749 conf
->base_leading
/ 2;
751 pages
->last
->space_after
= text
->first
->space_before
=
755 assert(pages
->first
);
760 * If feasible, fold the two halves of the index entry
763 if (text
->last
->real_shortfall
+ pages
->first
->real_shortfall
>
764 conf
->index_colwidth
+ conf
->index_minsep
) {
765 text
->last
->space_after
= -1;
766 pages
->first
->space_before
= -pages
->first
->line_height
+1;
769 lastidx
->next
= text
;
775 * Link all index line structures together into
778 text
->last
->next
= pages
->first
;
779 pages
->first
->prev
= text
->last
;
781 lastidxline
->next
= text
->first
;
782 text
->first
->prev
= lastidxline
;
784 lastidxline
= pages
->last
;
787 * Breaking an index entry anywhere is so bad that I
788 * think I'm going to forbid it totally.
790 for (ldata
= text
->first
; ldata
&& ldata
->next
;
791 ldata
= ldata
->next
) {
792 ldata
->next
->space_before
+= ldata
->space_after
+ 1;
793 ldata
->space_after
= -1;
798 * Now break the index into pages.
800 ipages
= page_breaks(firstidxline
, firstidxline
, conf
->page_height
,
802 ipages2
= page_breaks(firstidxline
->next
, lastidxline
,
805 firstidxline
->space_before
+
806 firstidxline
->line_height
+
807 firstidxline
->space_after
);
810 * This will have put each _column_ of the index on a
811 * separate page, which isn't what we want. Fold the pages
818 for (i
= 1; i
< conf
->index_cols
; i
++)
822 fold_into_page(page
, page
->next
,
823 i
* (conf
->index_colwidth
+
824 conf
->index_gutter
));
826 page
->next
= page
->next
->next
;
828 page
->next
->prev
= page
;
834 /* Also fold the heading on to the same page as the index items. */
835 fold_into_page(ipages
, ipages2
, 0);
836 ipages
->next
= ipages2
->next
;
838 ipages
->next
->prev
= ipages
;
840 fold_into_page(first_index_page
, ipages
, 0);
841 first_index_page
->next
= ipages
->next
;
842 if (first_index_page
->next
)
843 first_index_page
->next
->prev
= first_index_page
;
845 ipages
= first_index_page
;
848 * Number the index pages, except the already-numbered
851 for (page
= ipages
->next
; page
; page
= page
->next
) {
853 sprintf(buf
, "%d", ++pagenum
);
854 page
->number
= ufroma_dup(buf
, CS_ASCII
);
858 * Render the index pages.
860 for (pdata
= firstidx
; pdata
; pdata
= pdata
->next
)
861 render_para(pdata
, conf
, keywords
, idx
,
862 &index_placeholder_para
, first_index_page
);
865 * Link the index page list on to the end of the main page
871 for (page
= pages
; page
->next
; page
= page
->next
);
876 * Same with the paragraph list, which will cause the index
877 * to be mentioned in the document outline.
880 firstpara
= firstidx
;
882 lastpara
->next
= firstidx
;
887 * Draw the headers and footers.
889 * FIXME: this should be fully configurable, but for the moment
890 * I'm just going to put in page numbers in the centre of a
891 * footer and leave it at that.
896 for (page
= pages
; page
; page
= page
->next
) {
899 width
= conf
->pagenum_fontsize
*
900 string_width(conf
->tr
, page
->number
, NULL
);
902 render_string(page
, conf
->tr
, conf
->pagenum_fontsize
,
903 conf
->left_margin
+ (conf
->base_width
- width
)/2,
904 conf
->bottom_margin
- conf
->footer_distance
,
910 * Start putting together the overall document structure we're
913 doc
= snew(document
);
914 doc
->fonts
= fontlist
;
916 doc
->paper_width
= conf
->paper_width
;
917 doc
->paper_height
= conf
->paper_height
;
920 * Collect the section heading paragraphs into a document
921 * outline. This is slightly fiddly because the Title paragraph
922 * isn't required to be at the start, although all the others
928 doc
->outline_elements
= snewn(osize
, outline_element
);
929 doc
->n_outline_elements
= 0;
931 /* First find the title. */
932 for (pdata
= firstpara
; pdata
; pdata
= pdata
->next
) {
933 if (pdata
->outline_level
== 0) {
934 doc
->outline_elements
[0].level
= 0;
935 doc
->outline_elements
[0].pdata
= pdata
;
936 doc
->n_outline_elements
++;
941 /* Then collect the rest. */
942 for (pdata
= firstpara
; pdata
; pdata
= pdata
->next
) {
943 if (pdata
->outline_level
> 0) {
944 if (doc
->n_outline_elements
>= osize
) {
946 doc
->outline_elements
=
947 sresize(doc
->outline_elements
, osize
, outline_element
);
950 doc
->outline_elements
[doc
->n_outline_elements
].level
=
951 pdata
->outline_level
;
952 doc
->outline_elements
[doc
->n_outline_elements
].pdata
= pdata
;
953 doc
->n_outline_elements
++;
961 static para_data
*make_para_data(int ptype
, int paux
, int indent
, int rmargin
,
962 word
*pkwtext
, word
*pkwtext2
, word
*pwords
,
967 int extra_indent
, firstline_indent
, aux_indent
;
970 pdata
= snew(para_data
);
971 pdata
->outline_level
= -1;
972 pdata
->outline_title
= NULL
;
973 pdata
->rect_type
= RECT_NONE
;
974 pdata
->contents_entry
= NULL
;
975 pdata
->justification
= JUST
;
978 * Choose fonts for this paragraph.
980 * FIXME: All of this ought to be completely
985 pdata
->fonts
[FONT_NORMAL
] = conf
->hr
;
986 pdata
->sizes
[FONT_NORMAL
] = 24;
987 pdata
->fonts
[FONT_EMPH
] = conf
->hi
;
988 pdata
->sizes
[FONT_EMPH
] = 24;
989 pdata
->fonts
[FONT_CODE
] = conf
->cb
;
990 pdata
->sizes
[FONT_CODE
] = 24;
991 pdata
->outline_level
= 0;
996 case para_UnnumberedChapter
:
997 pdata
->fonts
[FONT_NORMAL
] = conf
->hr
;
998 pdata
->sizes
[FONT_NORMAL
] = 20;
999 pdata
->fonts
[FONT_EMPH
] = conf
->hi
;
1000 pdata
->sizes
[FONT_EMPH
] = 20;
1001 pdata
->fonts
[FONT_CODE
] = conf
->cb
;
1002 pdata
->sizes
[FONT_CODE
] = 20;
1003 pdata
->outline_level
= 1;
1008 pdata
->fonts
[FONT_NORMAL
] = conf
->hr
;
1009 pdata
->fonts
[FONT_EMPH
] = conf
->hi
;
1010 pdata
->fonts
[FONT_CODE
] = conf
->cb
;
1011 pdata
->sizes
[FONT_NORMAL
] =
1012 pdata
->sizes
[FONT_EMPH
] =
1013 pdata
->sizes
[FONT_CODE
] =
1014 (paux
== 0 ?
16 : paux
== 1 ?
14 : 13);
1015 pdata
->outline_level
= 2 + paux
;
1019 case para_BiblioCited
:
1021 case para_NumberedList
:
1022 case para_DescribedThing
:
1023 case para_Description
:
1024 case para_Copyright
:
1025 pdata
->fonts
[FONT_NORMAL
] = conf
->tr
;
1026 pdata
->sizes
[FONT_NORMAL
] = 12;
1027 pdata
->fonts
[FONT_EMPH
] = conf
->ti
;
1028 pdata
->sizes
[FONT_EMPH
] = 12;
1029 pdata
->fonts
[FONT_CODE
] = conf
->cr
;
1030 pdata
->sizes
[FONT_CODE
] = 12;
1035 * Also select an indentation level depending on the
1036 * paragraph type (list paragraphs other than
1037 * para_DescribedThing need extra indent).
1039 * (FIXME: Perhaps at some point we might even arrange
1040 * for the user to be able to request indented first
1041 * lines in paragraphs.)
1043 if (ptype
== para_Bullet
||
1044 ptype
== para_NumberedList
||
1045 ptype
== para_Description
) {
1046 extra_indent
= firstline_indent
= conf
->indent_list
;
1048 extra_indent
= firstline_indent
= 0;
1052 * Find the auxiliary text for this paragraph.
1063 * For some heading styles (FIXME: be able to
1064 * configure which), the auxiliary text contains
1065 * the chapter number and is arranged to be
1066 * right-aligned a few points left of the primary
1067 * margin. For other styles, the auxiliary text is
1068 * the full chapter _name_ and takes up space
1069 * within the (wrapped) chapter title, meaning that
1070 * we must move the first line indent over to make
1073 if (ptype
== para_Heading
|| ptype
== para_Subsect
) {
1077 len
= paper_width_simple(pdata
, pkwtext2
, conf
);
1078 aux_indent
= -len
- conf
->sect_num_left_space
;
1080 pdata
->outline_title
=
1081 prepare_outline_title(pkwtext2
, L
" ", pwords
);
1084 aux2
= fake_word(L
": ");
1087 firstline_indent
+= paper_width_simple(pdata
, aux
, conf
);
1088 firstline_indent
+= paper_width_simple(pdata
, aux2
, conf
);
1090 pdata
->outline_title
=
1091 prepare_outline_title(pkwtext
, L
": ", pwords
);
1097 * Auxiliary text consisting of a bullet.
1099 aux
= fake_word(conf
->bullet
);
1100 aux_indent
= indent
+ conf
->indent_list_bullet
;
1103 case para_NumberedList
:
1105 * Auxiliary text consisting of the number followed
1106 * by a (FIXME: configurable) full stop.
1109 aux2
= fake_word(L
".");
1110 aux_indent
= indent
+ conf
->indent_list_bullet
;
1113 case para_BiblioCited
:
1115 * Auxiliary text consisting of the bibliography
1116 * reference text, and a trailing space.
1119 aux2
= fake_word(L
" ");
1120 aux_indent
= indent
;
1121 firstline_indent
+= paper_width_simple(pdata
, aux
, conf
);
1122 firstline_indent
+= paper_width_simple(pdata
, aux2
, conf
);
1126 if (pdata
->outline_level
>= 0 && !pdata
->outline_title
) {
1127 pdata
->outline_title
=
1128 prepare_outline_title(NULL
, NULL
, pwords
);
1131 wrap_paragraph(pdata
, pwords
, conf
->base_width
- rmargin
,
1132 indent
+ firstline_indent
,
1133 indent
+ extra_indent
, conf
);
1135 pdata
->first
->aux_text
= aux
;
1136 pdata
->first
->aux_text_2
= aux2
;
1137 pdata
->first
->aux_left_indent
= aux_indent
;
1140 * Line breaking penalties.
1147 case para_UnnumberedChapter
:
1149 * Fixed and large penalty for breaking straight
1150 * after a heading; corresponding bonus for
1151 * breaking straight before.
1153 pdata
->first
->penalty_before
= -500000;
1154 pdata
->last
->penalty_after
= 500000;
1155 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
)
1156 ldata
->penalty_after
= 500000;
1159 case para_DescribedThing
:
1161 * This is treated a bit like a small heading:
1162 * there's a penalty for breaking after it (i.e.
1163 * between it and its description), and a bonus for
1164 * breaking before it (actually _between_ list
1167 pdata
->first
->penalty_before
= -200000;
1168 pdata
->last
->penalty_after
= 200000;
1173 * Most paragraph types: widow/orphan control by
1174 * discouraging breaking one line from the end of
1177 if (pdata
->first
!= pdata
->last
) {
1178 pdata
->first
->penalty_after
= 100000;
1179 pdata
->last
->penalty_before
= 100000;
1184 standard_line_spacing(pdata
, conf
);
1187 * Some kinds of section heading require a page break before
1188 * them and an underline after.
1190 if (ptype
== para_Title
||
1191 ptype
== para_Chapter
||
1192 ptype
== para_Appendix
||
1193 ptype
== para_UnnumberedChapter
) {
1194 pdata
->first
->page_break
= TRUE
;
1195 pdata
->first
->space_before
= conf
->chapter_top_space
;
1196 pdata
->last
->space_after
+=
1197 (conf
->chapter_underline_depth
+
1198 conf
->chapter_underline_thickness
);
1199 pdata
->rect_type
= RECT_CHAPTER_UNDERLINE
;
1205 static void standard_line_spacing(para_data
*pdata
, paper_conf
*conf
)
1210 * Set the line spacing for each line in this paragraph.
1212 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
) {
1213 if (ldata
== pdata
->first
)
1214 ldata
->space_before
= conf
->base_para_spacing
/ 2;
1216 ldata
->space_before
= conf
->base_leading
/ 2;
1217 if (ldata
== pdata
->last
)
1218 ldata
->space_after
= conf
->base_para_spacing
/ 2;
1220 ldata
->space_after
= conf
->base_leading
/ 2;
1221 ldata
->page_break
= FALSE
;
1225 static font_encoding
*new_font_encoding(font_data
*font
)
1230 fe
= snew(font_encoding
);
1233 if (font
->list
->tail
)
1234 font
->list
->tail
->next
= fe
;
1236 font
->list
->head
= fe
;
1237 font
->list
->tail
= fe
;
1240 fe
->free_pos
= 0x21;
1242 for (i
= 0; i
< 256; i
++) {
1243 fe
->vector
[i
] = NULL
;
1244 fe
->indices
[i
] = -1;
1245 fe
->to_unicode
[i
] = 0xFFFF;
1251 static font_data
*make_std_font(font_list
*fontlist
, char const *name
)
1259 widths
= ps_std_font_widths(name
);
1263 for (nglyphs
= 0; ps_std_glyphs
[nglyphs
] != NULL
; nglyphs
++);
1265 f
= snew(font_data
);
1269 f
->nglyphs
= nglyphs
;
1270 f
->glyphs
= ps_std_glyphs
;
1272 f
->subfont_map
= snewn(nglyphs
, subfont_map_entry
);
1275 * Our first subfont will contain all of US-ASCII. This isn't
1276 * really necessary - we could just create custom subfonts
1277 * precisely as the whim of render_string dictated - but
1278 * instinct suggests that it might be nice to have the text in
1279 * the output files look _marginally_ recognisable.
1281 fe
= new_font_encoding(f
);
1282 fe
->free_pos
= 0xA1; /* only the top half is free */
1283 f
->latest_subfont
= fe
;
1285 for (i
= 0; i
< (int)lenof(f
->bmp
); i
++)
1288 for (i
= 0; i
< nglyphs
; i
++) {
1290 ucs
= ps_glyph_to_unicode(f
->glyphs
[i
]);
1291 assert(ucs
!= 0xFFFF);
1293 if (ucs
>= 0x20 && ucs
<= 0x7E) {
1294 fe
->vector
[ucs
] = f
->glyphs
[i
];
1295 fe
->indices
[ucs
] = i
;
1296 fe
->to_unicode
[ucs
] = ucs
;
1297 f
->subfont_map
[i
].subfont
= fe
;
1298 f
->subfont_map
[i
].position
= ucs
;
1301 * This character is not yet assigned to a subfont.
1303 f
->subfont_map
[i
].subfont
= NULL
;
1304 f
->subfont_map
[i
].position
= 0;
1311 static int string_width(font_data
*font
, wchar_t const *string
, int *errs
)
1318 for (; *string
; string
++) {
1321 index
= font
->bmp
[(unsigned short)*string
];
1322 if (index
== 0xFFFF) {
1326 width
+= font
->widths
[index
];
1333 static int paper_width_internal(void *vctx
, word
*word
, int *nspaces
);
1335 struct paper_width_ctx
{
1341 static int paper_width_list(void *vctx
, word
*text
, word
*end
, int *nspaces
) {
1343 while (text
&& text
!= end
) {
1344 w
+= paper_width_internal(vctx
, text
, nspaces
);
1350 static int paper_width_internal(void *vctx
, word
*word
, int *nspaces
)
1352 struct paper_width_ctx
*ctx
= (struct paper_width_ctx
*)vctx
;
1353 int style
, type
, findex
, width
, errs
;
1356 switch (word
->type
) {
1357 case word_HyperLink
:
1359 case word_UpperXref
:
1360 case word_LowerXref
:
1367 style
= towordstyle(word
->type
);
1368 type
= removeattr(word
->type
);
1370 findex
= (style
== word_Normal ? FONT_NORMAL
:
1371 style
== word_Emph ? FONT_EMPH
:
1374 if (type
== word_Normal
) {
1376 } else if (type
== word_WhiteSpace
) {
1377 if (findex
!= FONT_CODE
) {
1380 return ctx
->minspacewidth
;
1383 } else /* if (type == word_Quote) */ {
1384 if (word
->aux
== quote_Open
)
1385 str
= ctx
->conf
->lquote
;
1387 str
= ctx
->conf
->rquote
;
1390 width
= string_width(ctx
->pdata
->fonts
[findex
], str
, &errs
);
1392 if (errs
&& word
->alt
)
1393 return paper_width_list(vctx
, word
->alt
, NULL
, nspaces
);
1395 return ctx
->pdata
->sizes
[findex
] * width
;
1398 static int paper_width(void *vctx
, word
*word
)
1400 return paper_width_internal(vctx
, word
, NULL
);
1403 static int paper_width_simple(para_data
*pdata
, word
*text
, paper_conf
*conf
)
1405 struct paper_width_ctx ctx
;
1409 (pdata
->sizes
[FONT_NORMAL
] *
1410 string_width(pdata
->fonts
[FONT_NORMAL
], L
" ", NULL
));
1413 return paper_width_list(&ctx
, text
, NULL
, NULL
);
1416 static void wrap_paragraph(para_data
*pdata
, word
*words
,
1417 int w
, int i1
, int i2
, paper_conf
*conf
)
1419 wrappedline
*wrapping
, *p
;
1421 struct paper_width_ctx ctx
;
1425 * We're going to need to store the line height in every line
1426 * structure we generate.
1431 for (i
= 0; i
< NFONTS
; i
++)
1432 if (line_height
< pdata
->sizes
[i
])
1433 line_height
= pdata
->sizes
[i
];
1434 line_height
*= 4096;
1437 spacewidth
= (pdata
->sizes
[FONT_NORMAL
] *
1438 string_width(pdata
->fonts
[FONT_NORMAL
], L
" ", NULL
));
1439 if (spacewidth
== 0) {
1441 * A font without a space?! Disturbing. I hope this never
1442 * comes up, but I'll make a random guess anyway and set my
1443 * space width to half the point size.
1445 spacewidth
= pdata
->sizes
[FONT_NORMAL
] * 4096 / 2;
1449 * I'm going to set the _minimum_ space width to 3/5 of the
1450 * standard one, and use the standard one as the optimum.
1452 ctx
.minspacewidth
= spacewidth
* 3 / 5;
1456 wrapping
= wrap_para(words
, w
- i1
, w
- i2
, paper_width
, &ctx
, spacewidth
);
1459 * Having done the wrapping, we now concoct a set of line_data
1462 pdata
->first
= pdata
->last
= NULL
;
1464 for (p
= wrapping
; p
; p
= p
->next
) {
1467 int len
, wid
, spaces
;
1469 ldata
= snew(line_data
);
1471 ldata
->pdata
= pdata
;
1472 ldata
->first
= p
->begin
;
1473 ldata
->end
= p
->end
;
1474 ldata
->line_height
= line_height
;
1476 ldata
->xpos
= (p
== wrapping ? i1
: i2
);
1479 pdata
->last
->next
= ldata
;
1480 ldata
->prev
= pdata
->last
;
1482 pdata
->first
= ldata
;
1486 pdata
->last
= ldata
;
1489 len
= paper_width_list(&ctx
, ldata
->first
, ldata
->end
, &spaces
);
1490 wid
= (p
== wrapping ? w
- i1
: w
- i2
);
1493 ldata
->hshortfall
= wid
- len
;
1494 ldata
->nspaces
= spaces
;
1496 * This tells us how much the space width needs to
1497 * change from _min_spacewidth. But we want to store
1498 * its difference from the _natural_ space width, to
1499 * make the text rendering easier.
1501 ldata
->hshortfall
+= ctx
.minspacewidth
* spaces
;
1502 ldata
->hshortfall
-= spacewidth
* spaces
;
1503 ldata
->real_shortfall
= ldata
->hshortfall
;
1505 * Special case: on the last line of a paragraph, we
1506 * never stretch spaces.
1508 if (ldata
->hshortfall
> 0 && !p
->next
)
1509 ldata
->hshortfall
= 0;
1511 ldata
->aux_text
= NULL
;
1512 ldata
->aux_text_2
= NULL
;
1513 ldata
->aux_left_indent
= 0;
1514 ldata
->penalty_before
= ldata
->penalty_after
= 0;
1519 static page_data
*page_breaks(line_data
*first
, line_data
*last
,
1520 int page_height
, int ncols
, int headspace
)
1524 int n
, n1
, this_height
;
1527 * Page breaking is done by a close analogue of the optimal
1528 * paragraph wrapping algorithm used by wrap_para(). We work
1529 * backwards from the end of the document line by line; for
1530 * each line, we contemplate every possible number of lines we
1531 * could put on a page starting with that line, determine a
1532 * cost function for each one, add it to the pre-computed cost
1533 * function for optimally page-breaking everything after that
1534 * page, and pick the best option.
1536 * This is made slightly more complex by the fact that we have
1537 * a multi-column index with a heading at the top of the
1538 * _first_ page, meaning that the first _ncols_ pages must have
1539 * a different length. Hence, we must do the wrapping ncols+1
1540 * times over, hypothetically trying to put every subsequence
1541 * on every possible page.
1543 * Since my line_data structures are only used for this
1544 * purpose, I might as well just store the algorithm data
1548 for (l
= last
; l
; l
= l
->prev
) {
1549 l
->bestcost
= snewn(ncols
+1, int);
1550 l
->vshortfall
= snewn(ncols
+1, int);
1551 l
->text
= snewn(ncols
+1, int);
1552 l
->space
= snewn(ncols
+1, int);
1553 l
->page_last
= snewn(ncols
+1, line_data
*);
1555 for (n
= 0; n
<= ncols
; n
++) {
1556 int minheight
, text
= 0, space
= 0;
1559 n1
= (n
< ncols ? n
+1 : ncols
);
1561 this_height
= page_height
- headspace
;
1563 this_height
= page_height
;
1565 l
->bestcost
[n
] = -1;
1566 for (m
= l
; m
; m
= m
->next
) {
1567 if (m
!= l
&& m
->page_break
)
1568 break; /* we've gone as far as we can */
1571 if (m
->prev
->space_after
> 0)
1572 space
+= m
->prev
->space_after
;
1574 text
+= m
->prev
->space_after
;
1576 if (m
!= l
|| m
->page_break
) {
1577 if (m
->space_before
> 0)
1578 space
+= m
->space_before
;
1580 text
+= m
->space_before
;
1582 text
+= m
->line_height
;
1583 minheight
= text
+ space
;
1585 if (m
!= l
&& minheight
> this_height
)
1589 * If the space after this paragraph is _negative_
1590 * (which means the next line is folded on to this
1591 * one, which happens in the index), we absolutely
1592 * cannot break here.
1594 if (m
->space_after
>= 0) {
1597 * Compute the cost of this arrangement, as the
1598 * square of the amount of wasted space on the
1599 * page. Exception: if this is the last page
1600 * before a mandatory break or the document
1601 * end, we don't penalise a large blank area.
1603 if (m
!= last
&& m
->next
&& !m
->next
->page_break
)
1605 int x
= this_height
- minheight
;
1612 cost
+= (x
* xf
) >> 8;
1616 if (m
!= last
&& m
->next
&& !m
->next
->page_break
) {
1617 cost
+= m
->penalty_after
;
1618 cost
+= m
->next
->penalty_before
;
1621 if (m
!= last
&& m
->next
&& !m
->next
->page_break
)
1622 cost
+= m
->next
->bestcost
[n1
];
1623 if (l
->bestcost
[n
] == -1 || l
->bestcost
[n
] > cost
) {
1625 * This is the best option yet for this
1628 l
->bestcost
[n
] = cost
;
1629 if (m
!= last
&& m
->next
&& !m
->next
->page_break
)
1630 l
->vshortfall
[n
] = this_height
- minheight
;
1632 l
->vshortfall
[n
] = 0;
1634 l
->space
[n
] = space
;
1635 l
->page_last
[n
] = m
;
1646 * Now go through the line list forwards and assemble the
1655 int text
, space
, head
;
1657 page
= snew(page_data
);
1666 page
->first_line
= l
;
1667 page
->last_line
= l
->page_last
[n
];
1669 page
->first_text
= page
->last_text
= NULL
;
1670 page
->first_xref
= page
->last_xref
= NULL
;
1671 page
->first_rect
= page
->last_rect
= NULL
;
1674 * Now assign a y-coordinate to each line on the page.
1677 head
= (n
< ncols ? headspace
: 0);
1678 for (l
= page
->first_line
; l
; l
= l
->next
) {
1679 if (l
!= page
->first_line
) {
1680 if (l
->prev
->space_after
> 0)
1681 space
+= l
->prev
->space_after
;
1683 text
+= l
->prev
->space_after
;
1685 if (l
!= page
->first_line
|| l
->page_break
) {
1686 if (l
->space_before
> 0)
1687 space
+= l
->space_before
;
1689 text
+= l
->space_before
;
1691 text
+= l
->line_height
;
1694 l
->ypos
= text
+ space
+ head
+
1695 space
* (float)page
->first_line
->vshortfall
[n
] /
1696 page
->first_line
->space
[n
];
1698 if (l
== page
->last_line
)
1702 l
= page
->last_line
;
1707 n
= (n
< ncols ? n
+1 : ncols
);
1713 static void add_rect_to_page(page_data
*page
, int x
, int y
, int w
, int h
)
1715 rect
*r
= snew(rect
);
1718 if (page
->last_rect
)
1719 page
->last_rect
->next
= r
;
1721 page
->first_rect
= r
;
1722 page
->last_rect
= r
;
1730 static void add_string_to_page(page_data
*page
, int x
, int y
,
1731 font_encoding
*fe
, int size
, char *text
,
1734 text_fragment
*frag
;
1736 frag
= snew(text_fragment
);
1739 if (page
->last_text
)
1740 page
->last_text
->next
= frag
;
1742 page
->first_text
= frag
;
1743 page
->last_text
= frag
;
1748 frag
->fontsize
= size
;
1749 frag
->text
= dupstr(text
);
1750 frag
->width
= width
;
1754 * Returns the updated x coordinate.
1756 static int render_string(page_data
*page
, font_data
*font
, int fontsize
,
1757 int x
, int y
, wchar_t *str
)
1760 int textpos
, textwid
, glyph
;
1761 font_encoding
*subfont
= NULL
, *sf
;
1763 text
= snewn(1 + ustrlen(str
), char);
1764 textpos
= textwid
= 0;
1767 glyph
= font
->bmp
[*str
];
1769 if (glyph
== 0xFFFF) {
1771 continue; /* nothing more we can do here */
1775 * Find which subfont this character is going in.
1777 sf
= font
->subfont_map
[glyph
].subfont
;
1783 * This character is not yet in a subfont. Assign one.
1785 if (font
->latest_subfont
->free_pos
>= 0x100)
1786 font
->latest_subfont
= new_font_encoding(font
);
1788 c
= font
->latest_subfont
->free_pos
++;
1789 if (font
->latest_subfont
->free_pos
== 0x7F)
1790 font
->latest_subfont
->free_pos
= 0xA1;
1792 font
->subfont_map
[glyph
].subfont
= font
->latest_subfont
;
1793 font
->subfont_map
[glyph
].position
= c
;
1794 font
->latest_subfont
->vector
[c
] = font
->glyphs
[glyph
];
1795 font
->latest_subfont
->indices
[c
] = glyph
;
1796 font
->latest_subfont
->to_unicode
[c
] = *str
;
1798 sf
= font
->latest_subfont
;
1801 if (!subfont
|| sf
!= subfont
) {
1803 text
[textpos
] = '\0';
1804 add_string_to_page(page
, x
, y
, subfont
, fontsize
, text
,
1808 assert(textpos
== 0);
1814 text
[textpos
++] = font
->subfont_map
[glyph
].position
;
1815 textwid
+= font
->widths
[glyph
] * fontsize
;
1821 text
[textpos
] = '\0';
1822 add_string_to_page(page
, x
, y
, subfont
, fontsize
, text
, textwid
);
1830 * Returns the updated x coordinate.
1832 static int render_text(page_data
*page
, para_data
*pdata
, line_data
*ldata
,
1833 int x
, int y
, word
*text
, word
*text_end
, xref
**xr
,
1834 int shortfall
, int nspaces
, int *nspace
,
1835 keywordlist
*keywords
, indexdata
*idx
, paper_conf
*conf
)
1837 while (text
&& text
!= text_end
) {
1838 int style
, type
, findex
, errs
;
1842 switch (text
->type
) {
1844 * Start a cross-reference.
1846 case word_HyperLink
:
1847 case word_UpperXref
:
1848 case word_LowerXref
:
1851 if (text
->type
== word_HyperLink
) {
1853 dest
.url
= utoa_dup(text
->text
, CS_ASCII
);
1855 } else if (text
->type
== word_PageXref
) {
1858 dest
.page
= (page_data
*)text
->private_data
;
1860 keyword
*kwl
= kw_lookup(keywords
, text
->text
);
1864 assert(kwl
->para
->private_data
);
1865 pdata
= (para_data
*) kwl
->para
->private_data
;
1867 dest
.page
= pdata
->first
->page
;
1871 * Shouldn't happen, but *shrug*
1878 if (dest
.type
!= NONE
) {
1880 (*xr
)->dest
= dest
; /* structure copy */
1881 if (page
->last_xref
)
1882 page
->last_xref
->next
= *xr
;
1884 page
->first_xref
= *xr
;
1885 page
->last_xref
= *xr
;
1889 * FIXME: Ideally we should have, and use, some
1890 * vertical font metric information here so that
1891 * our cross-ref rectangle can take account of
1892 * descenders and the font's cap height. This will
1893 * do for the moment, but it isn't ideal.
1895 (*xr
)->lx
= (*xr
)->rx
= x
;
1897 (*xr
)->ty
= y
+ ldata
->line_height
;
1902 * Finish extending a cross-reference box.
1910 * Add the current page number to the list of pages
1911 * referenced by an index entry.
1915 * We don't create index references in contents entries.
1917 if (!pdata
->contents_entry
) {
1921 tag
= index_findtag(idx
, text
->text
);
1925 for (i
= 0; i
< tag
->nrefs
; i
++) {
1926 indexentry
*entry
= tag
->refs
[i
];
1927 paper_idx
*pi
= (paper_idx
*)entry
->backend_data
;
1930 * If the same index term is indexed twice
1931 * within the same section, we only want to
1932 * mention it once in the index.
1934 if (pi
->lastpage
!= page
) {
1938 pi
->lastword
= pi
->lastword
->next
=
1940 pi
->lastword
= pi
->lastword
->next
=
1942 wp
= &pi
->lastword
->next
;
1946 pi
->lastword
= *wp
=
1947 fake_page_ref(page
);
1948 pi
->lastword
= pi
->lastword
->next
=
1949 fake_word(page
->number
);
1950 pi
->lastword
= pi
->lastword
->next
=
1954 pi
->lastpage
= page
;
1960 style
= towordstyle(text
->type
);
1961 type
= removeattr(text
->type
);
1963 findex
= (style
== word_Normal ? FONT_NORMAL
:
1964 style
== word_Emph ? FONT_EMPH
:
1967 if (type
== word_Normal
) {
1969 } else if (type
== word_WhiteSpace
) {
1970 x
+= pdata
->sizes
[findex
] *
1971 string_width(pdata
->fonts
[findex
], L
" ", NULL
);
1972 if (nspaces
&& findex
!= FONT_CODE
) {
1973 x
+= (*nspace
+1) * shortfall
/ nspaces
;
1974 x
-= *nspace
* shortfall
/ nspaces
;
1978 } else /* if (type == word_Quote) */ {
1979 if (text
->aux
== quote_Open
)
1985 (void) string_width(pdata
->fonts
[findex
], str
, &errs
);
1987 if (errs
&& text
->alt
)
1988 x
= render_text(page
, pdata
, ldata
, x
, y
, text
->alt
, NULL
,
1989 xr
, shortfall
, nspaces
, nspace
, keywords
, idx
,
1992 x
= render_string(page
, pdata
->fonts
[findex
],
1993 pdata
->sizes
[findex
], x
, y
, str
);
2006 * Returns the last x position used on the line.
2008 static int render_line(line_data
*ldata
, int left_x
, int top_y
,
2009 xref_dest
*dest
, keywordlist
*keywords
, indexdata
*idx
,
2016 if (ldata
->aux_text
) {
2020 x
= render_text(ldata
->page
, ldata
->pdata
, ldata
,
2021 left_x
+ ldata
->aux_left_indent
,
2022 top_y
- ldata
->ypos
,
2023 ldata
->aux_text
, NULL
, &xr
, 0, 0, &nspace
,
2024 keywords
, idx
, conf
);
2025 if (ldata
->aux_text_2
)
2026 render_text(ldata
->page
, ldata
->pdata
, ldata
,
2027 x
, top_y
- ldata
->ypos
,
2028 ldata
->aux_text_2
, NULL
, &xr
, 0, 0, &nspace
,
2029 keywords
, idx
, conf
);
2035 * There might be a cross-reference carried over from a
2038 if (dest
->type
!= NONE
) {
2041 xr
->dest
= *dest
; /* structure copy */
2042 if (ldata
->page
->last_xref
)
2043 ldata
->page
->last_xref
->next
= xr
;
2045 ldata
->page
->first_xref
= xr
;
2046 ldata
->page
->last_xref
= xr
;
2047 xr
->lx
= xr
->rx
= left_x
+ ldata
->xpos
;
2048 xr
->by
= top_y
- ldata
->ypos
;
2049 xr
->ty
= top_y
- ldata
->ypos
+ ldata
->line_height
;
2054 int extra_indent
, shortfall
, spaces
;
2055 int just
= ldata
->pdata
->justification
;
2058 * All forms of justification become JUST when we have
2059 * to squeeze the paragraph.
2061 if (ldata
->hshortfall
< 0)
2066 shortfall
= ldata
->hshortfall
;
2067 spaces
= ldata
->nspaces
;
2071 shortfall
= spaces
= extra_indent
= 0;
2074 shortfall
= spaces
= 0;
2075 extra_indent
= ldata
->real_shortfall
;
2079 ret
= render_text(ldata
->page
, ldata
->pdata
, ldata
,
2080 left_x
+ ldata
->xpos
+ extra_indent
,
2081 top_y
- ldata
->ypos
, ldata
->first
, ldata
->end
,
2082 &xr
, shortfall
, spaces
, &nspace
,
2083 keywords
, idx
, conf
);
2088 * There's a cross-reference continued on to the next line.
2098 static void render_para(para_data
*pdata
, paper_conf
*conf
,
2099 keywordlist
*keywords
, indexdata
*idx
,
2100 paragraph
*index_placeholder
, page_data
*index_page
)
2104 page_data
*cxref_page
;
2113 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
) {
2115 * If this is a contents entry, we expect to have a single
2116 * enormous cross-reference rectangle covering the whole
2117 * thing. (Unless, of course, it spans multiple pages.)
2119 if (pdata
->contents_entry
&& ldata
->page
!= cxref_page
) {
2120 cxref_page
= ldata
->page
;
2123 cxref
->dest
.type
= PAGE
;
2124 if (pdata
->contents_entry
== index_placeholder
) {
2125 cxref
->dest
.page
= index_page
;
2127 assert(pdata
->contents_entry
->private_data
);
2128 target
= (para_data
*)pdata
->contents_entry
->private_data
;
2129 cxref
->dest
.page
= target
->first
->page
;
2131 cxref
->dest
.url
= NULL
;
2132 if (ldata
->page
->last_xref
)
2133 ldata
->page
->last_xref
->next
= cxref
;
2135 ldata
->page
->first_xref
= cxref
;
2136 ldata
->page
->last_xref
= cxref
;
2137 cxref
->lx
= conf
->left_margin
;
2138 cxref
->rx
= conf
->paper_width
- conf
->right_margin
;
2139 cxref
->ty
= conf
->paper_height
- conf
->top_margin
2140 - ldata
->ypos
+ ldata
->line_height
;
2142 if (pdata
->contents_entry
) {
2143 assert(cxref
!= NULL
);
2144 cxref
->by
= conf
->paper_height
- conf
->top_margin
2148 last_x
= render_line(ldata
, conf
->left_margin
,
2149 conf
->paper_height
- conf
->top_margin
,
2150 &dest
, keywords
, idx
, conf
);
2151 if (ldata
== pdata
->last
)
2156 * If this is a contents entry, add leaders and a page
2159 if (pdata
->contents_entry
) {
2165 if (pdata
->contents_entry
== index_placeholder
) {
2166 num
= index_page
->number
;
2168 assert(pdata
->contents_entry
->private_data
);
2169 target
= (para_data
*)pdata
->contents_entry
->private_data
;
2170 num
= target
->first
->page
->number
;
2174 wid
= paper_width_simple(pdata
, w
, conf
);
2177 for (x
= 0; x
< conf
->base_width
; x
+= conf
->leader_separation
)
2178 if (x
- conf
->leader_separation
> last_x
- conf
->left_margin
&&
2179 x
+ conf
->leader_separation
< conf
->base_width
- wid
)
2180 render_string(pdata
->last
->page
,
2181 pdata
->fonts
[FONT_NORMAL
],
2182 pdata
->sizes
[FONT_NORMAL
],
2183 conf
->left_margin
+ x
,
2184 (conf
->paper_height
- conf
->top_margin
-
2185 pdata
->last
->ypos
), L
".");
2187 render_string(pdata
->last
->page
,
2188 pdata
->fonts
[FONT_NORMAL
],
2189 pdata
->sizes
[FONT_NORMAL
],
2190 conf
->paper_width
- conf
->right_margin
- wid
,
2191 (conf
->paper_height
- conf
->top_margin
-
2192 pdata
->last
->ypos
), num
);
2196 * Render any rectangle (chapter title underline or rule)
2197 * that goes with this paragraph.
2199 switch (pdata
->rect_type
) {
2200 case RECT_CHAPTER_UNDERLINE
:
2201 add_rect_to_page(pdata
->last
->page
,
2203 (conf
->paper_height
- conf
->top_margin
-
2205 conf
->chapter_underline_depth
),
2207 conf
->chapter_underline_thickness
);
2210 add_rect_to_page(pdata
->first
->page
,
2211 conf
->left_margin
+ pdata
->first
->xpos
,
2212 (conf
->paper_height
- conf
->top_margin
-
2214 pdata
->last
->line_height
),
2215 conf
->base_width
- pdata
->first
->xpos
,
2216 pdata
->last
->line_height
);
2218 default: /* placate gcc */
2223 static para_data
*code_paragraph(int indent
, word
*words
, paper_conf
*conf
)
2225 para_data
*pdata
= snew(para_data
);
2228 * For code paragraphs, I'm going to hack grievously and
2229 * pretend the three normal fonts are the three code paragraph
2232 pdata
->fonts
[FONT_NORMAL
] = conf
->cb
;
2233 pdata
->fonts
[FONT_EMPH
] = conf
->co
;
2234 pdata
->fonts
[FONT_CODE
] = conf
->cr
;
2235 pdata
->sizes
[FONT_NORMAL
] =
2236 pdata
->sizes
[FONT_EMPH
] =
2237 pdata
->sizes
[FONT_CODE
] = 12;
2239 pdata
->first
= pdata
->last
= NULL
;
2240 pdata
->outline_level
= -1;
2241 pdata
->rect_type
= RECT_NONE
;
2242 pdata
->contents_entry
= NULL
;
2243 pdata
->justification
= LEFT
;
2245 for (; words
; words
= words
->next
) {
2246 wchar_t *t
, *e
, *start
;
2247 word
*lhead
= NULL
, *ltail
= NULL
, *w
;
2249 int prev
= -1, curr
;
2252 if (words
->next
&& words
->next
->type
== word_Emph
) {
2253 e
= words
->next
->text
;
2254 words
= words
->next
;
2264 else if (*e
== L
'i')
2266 else if (*e
== L
'b')
2283 * We've isolated a maximal subsequence of the line
2284 * which has the same emphasis. Form it into a word
2290 w
->type
= (prev
== 0 ? word_WeakCode
:
2291 prev
== 1 ? word_Emph
: word_Normal
);
2292 w
->text
= snewn(t
-start
+1, wchar_t);
2293 memcpy(w
->text
, start
, (t
-start
) * sizeof(wchar_t));
2294 w
->text
[t
-start
] = '\0';
2307 ldata
= snew(line_data
);
2309 ldata
->pdata
= pdata
;
2310 ldata
->first
= lhead
;
2312 ldata
->line_height
= conf
->base_font_size
* 4096;
2314 ldata
->xpos
= indent
;
2317 pdata
->last
->next
= ldata
;
2318 ldata
->prev
= pdata
->last
;
2320 pdata
->first
= ldata
;
2324 pdata
->last
= ldata
;
2326 ldata
->hshortfall
= 0;
2328 ldata
->aux_text
= NULL
;
2329 ldata
->aux_text_2
= NULL
;
2330 ldata
->aux_left_indent
= 0;
2331 /* General opprobrium for breaking in a code paragraph. */
2332 ldata
->penalty_before
= ldata
->penalty_after
= 50000;
2335 standard_line_spacing(pdata
, conf
);
2340 static para_data
*rule_paragraph(int indent
, paper_conf
*conf
)
2342 para_data
*pdata
= snew(para_data
);
2345 ldata
= snew(line_data
);
2347 ldata
->pdata
= pdata
;
2348 ldata
->first
= NULL
;
2350 ldata
->line_height
= conf
->rule_thickness
;
2352 ldata
->xpos
= indent
;
2357 ldata
->hshortfall
= 0;
2359 ldata
->aux_text
= NULL
;
2360 ldata
->aux_text_2
= NULL
;
2361 ldata
->aux_left_indent
= 0;
2364 * Better to break after a rule than before it
2366 ldata
->penalty_after
+= 100000;
2367 ldata
->penalty_before
+= -100000;
2369 pdata
->first
= pdata
->last
= ldata
;
2370 pdata
->outline_level
= -1;
2371 pdata
->rect_type
= RECT_RULE
;
2372 pdata
->contents_entry
= NULL
;
2373 pdata
->justification
= LEFT
;
2375 standard_line_spacing(pdata
, conf
);
2381 * Plain-text-like formatting for outline titles.
2383 static void paper_rdaddw(rdstring
*rs
, word
*text
) {
2384 for (; text
; text
= text
->next
) switch (text
->type
) {
2385 case word_HyperLink
:
2387 case word_UpperXref
:
2388 case word_LowerXref
:
2397 case word_WhiteSpace
:
2398 case word_EmphSpace
:
2399 case word_CodeSpace
:
2400 case word_WkCodeSpace
:
2402 case word_EmphQuote
:
2403 case word_CodeQuote
:
2404 case word_WkCodeQuote
:
2405 assert(text
->type
!= word_CodeQuote
&&
2406 text
->type
!= word_WkCodeQuote
);
2407 if (towordstyle(text
->type
) == word_Emph
&&
2408 (attraux(text
->aux
) == attr_First
||
2409 attraux(text
->aux
) == attr_Only
))
2410 rdadd(rs
, L
'_'); /* FIXME: configurability */
2411 else if (towordstyle(text
->type
) == word_Code
&&
2412 (attraux(text
->aux
) == attr_First
||
2413 attraux(text
->aux
) == attr_Only
))
2414 rdadd(rs
, L
'\''); /* FIXME: configurability */
2415 if (removeattr(text
->type
) == word_Normal
) {
2416 rdadds(rs
, text
->text
);
2417 } else if (removeattr(text
->type
) == word_WhiteSpace
) {
2419 } else if (removeattr(text
->type
) == word_Quote
) {
2420 rdadd(rs
, L
'\''); /* fixme: configurability */
2422 if (towordstyle(text
->type
) == word_Emph
&&
2423 (attraux(text
->aux
) == attr_Last
||
2424 attraux(text
->aux
) == attr_Only
))
2425 rdadd(rs
, L
'_'); /* FIXME: configurability */
2426 else if (towordstyle(text
->type
) == word_Code
&&
2427 (attraux(text
->aux
) == attr_Last
||
2428 attraux(text
->aux
) == attr_Only
))
2429 rdadd(rs
, L
'\''); /* FIXME: configurability */
2434 static wchar_t *prepare_outline_title(word
*first
, wchar_t *separator
,
2437 rdstring rs
= {0, 0, NULL
};
2440 paper_rdaddw(&rs
, first
);
2442 rdadds(&rs
, separator
);
2444 paper_rdaddw(&rs
, second
);
2449 static word
*fake_word(wchar_t *text
)
2451 word
*ret
= snew(word
);
2454 ret
->type
= word_Normal
;
2455 ret
->text
= ustrdup(text
);
2456 ret
->breaks
= FALSE
;
2461 static word
*fake_space_word(void)
2463 word
*ret
= snew(word
);
2466 ret
->type
= word_WhiteSpace
;
2473 static word
*fake_page_ref(page_data
*page
)
2475 word
*ret
= snew(word
);
2478 ret
->type
= word_PageXref
;
2480 ret
->breaks
= FALSE
;
2482 ret
->private_data
= page
;
2486 static word
*fake_end_ref(void)
2488 word
*ret
= snew(word
);
2491 ret
->type
= word_XrefEnd
;
2493 ret
->breaks
= FALSE
;
2498 static word
*prepare_contents_title(word
*first
, wchar_t *separator
,
2507 w
= dup_word_list(first
);
2515 w
= fake_word(separator
);
2521 *wptr
= dup_word_list(second
);
2527 static void fold_into_page(page_data
*dest
, page_data
*src
, int right_shift
)
2531 if (!src
->first_line
)
2534 if (dest
->last_line
) {
2535 dest
->last_line
->next
= src
->first_line
;
2536 src
->first_line
->prev
= dest
->last_line
;
2538 dest
->last_line
= src
->last_line
;
2540 for (ldata
= src
->first_line
; ldata
; ldata
= ldata
->next
) {
2542 ldata
->xpos
+= right_shift
;
2544 if (ldata
== src
->last_line
)