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.
17 * - header/footer? Page numbers at least would be handy. Fully
18 * configurable footer can wait, though.
20 * That should bring us to the same level of functionality that
21 * original-Halibut had, and the same in PDF plus the obvious
22 * interactive navigation features. After that, in future work:
24 * - linearised PDF, perhaps?
26 * - I'm uncertain of whether I need to include a ToUnicode CMap
27 * in each of my font definitions in PDF. Currently things (by
28 * which I mean cut and paste out of acroread) seem to be
29 * working fairly happily without it, but I don't know.
42 typedef struct paper_conf_Tag paper_conf
;
43 typedef struct paper_idx_Tag paper_idx
;
45 struct paper_conf_Tag
{
52 int indent_list_bullet
;
56 int base_para_spacing
;
57 int chapter_top_space
;
58 int sect_num_left_space
;
59 int chapter_underline_depth
;
60 int chapter_underline_thickness
;
63 int contents_indent_step
;
65 int leader_separation
;
69 /* These are derived from the above */
73 /* Fonts used in the configuration */
74 font_data
*tr
, *ti
, *hr
, *hi
, *cr
, *co
, *cb
;
77 struct paper_idx_Tag
{
79 * Word list giving the page numbers on which this index entry
80 * appears. Also the last word in the list, for ease of
86 * The last page added to the list (so we can ensure we don't
92 static font_data
*make_std_font(font_list
*fontlist
, char const *name
);
93 static void wrap_paragraph(para_data
*pdata
, word
*words
,
94 int w
, int i1
, int i2
);
95 static page_data
*page_breaks(line_data
*first
, line_data
*last
,
96 int page_height
, int ncols
, int headspace
);
97 static int render_string(page_data
*page
, font_data
*font
, int fontsize
,
98 int x
, int y
, wchar_t *str
);
99 static int render_line(line_data
*ldata
, int left_x
, int top_y
,
100 xref_dest
*dest
, keywordlist
*keywords
, indexdata
*idx
);
101 static void render_para(para_data
*pdata
, paper_conf
*conf
,
102 keywordlist
*keywords
, indexdata
*idx
,
103 paragraph
*index_placeholder
, page_data
*index_page
);
104 static int paper_width_simple(para_data
*pdata
, word
*text
);
105 static para_data
*code_paragraph(int indent
, word
*words
, paper_conf
*conf
);
106 static para_data
*rule_paragraph(int indent
, paper_conf
*conf
);
107 static void add_rect_to_page(page_data
*page
, int x
, int y
, int w
, int h
);
108 static para_data
*make_para_data(int ptype
, int paux
, int indent
, int rmargin
,
109 word
*pkwtext
, word
*pkwtext2
, word
*pwords
,
111 static void standard_line_spacing(para_data
*pdata
, paper_conf
*conf
);
112 static wchar_t *prepare_outline_title(word
*first
, wchar_t *separator
,
114 static word
*fake_word(wchar_t *text
);
115 static word
*fake_space_word(void);
116 static word
*prepare_contents_title(word
*first
, wchar_t *separator
,
118 static void fold_into_page(page_data
*dest
, page_data
*src
, int right_shift
);
120 void *paper_pre_backend(paragraph
*sourceform
, keywordlist
*keywords
,
124 int indent
, used_contents
;
125 para_data
*pdata
, *firstpara
= NULL
, *lastpara
= NULL
;
126 para_data
*firstcont
, *lastcont
;
127 line_data
*firstline
, *lastline
, *firstcontline
, *lastcontline
;
133 paragraph index_placeholder_para
;
134 page_data
*first_index_page
;
137 * FIXME: All these things ought to become configurable.
139 conf
= mknew(paper_conf
);
140 conf
->paper_width
= 595 * 4096;
141 conf
->paper_height
= 841 * 4096;
142 conf
->left_margin
= 72 * 4096;
143 conf
->top_margin
= 72 * 4096;
144 conf
->right_margin
= 72 * 4096;
145 conf
->bottom_margin
= 108 * 4096;
146 conf
->indent_list_bullet
= 6 * 4096;
147 conf
->indent_list
= 24 * 4096;
148 conf
->indent_quote
= 18 * 4096;
149 conf
->base_leading
= 4096;
150 conf
->base_para_spacing
= 10 * 4096;
151 conf
->chapter_top_space
= 72 * 4096;
152 conf
->sect_num_left_space
= 12 * 4096;
153 conf
->chapter_underline_depth
= 14 * 4096;
154 conf
->chapter_underline_thickness
= 3 * 4096;
155 conf
->rule_thickness
= 1 * 4096;
156 conf
->base_font_size
= 12;
157 conf
->contents_indent_step
= 24 * 4096;
158 conf
->contents_margin
= 84 * 4096;
159 conf
->leader_separation
= 12 * 4096;
160 conf
->index_gutter
= 36 * 4096;
161 conf
->index_cols
= 2;
162 conf
->index_minsep
= 18 * 4096;
165 conf
->paper_width
- conf
->left_margin
- conf
->right_margin
;
167 conf
->paper_height
- conf
->top_margin
- conf
->bottom_margin
;
168 conf
->index_colwidth
=
169 (conf
->base_width
- (conf
->index_cols
-1) * conf
->index_gutter
)
172 IGNORE(idx
); /* FIXME */
175 * First, set up some font structures.
177 fontlist
= mknew(font_list
);
178 fontlist
->head
= fontlist
->tail
= NULL
;
179 conf
->tr
= make_std_font(fontlist
, "Times-Roman");
180 conf
->ti
= make_std_font(fontlist
, "Times-Italic");
181 conf
->hr
= make_std_font(fontlist
, "Helvetica-Bold");
182 conf
->hi
= make_std_font(fontlist
, "Helvetica-BoldOblique");
183 conf
->cr
= make_std_font(fontlist
, "Courier");
184 conf
->co
= make_std_font(fontlist
, "Courier-Oblique");
185 conf
->cb
= make_std_font(fontlist
, "Courier-Bold");
188 * Set up a data structure to collect page numbers for each
197 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
198 paper_idx
*pi
= mknew(paper_idx
);
202 pi
->words
= pi
->lastword
= NULL
;
205 entry
->backend_data
= pi
;
210 * Format the contents entry for each heading.
213 word
*contents_title
;
214 contents_title
= fake_word(L
"Contents");
216 firstcont
= make_para_data(para_UnnumberedChapter
, 0, 0, 0,
217 NULL
, NULL
, contents_title
, conf
);
218 lastcont
= firstcont
;
219 lastcont
->next
= NULL
;
220 firstcontline
= firstcont
->first
;
221 lastcontline
= lastcont
->last
;
222 for (p
= sourceform
; p
; p
= p
->next
) {
229 case para_UnnumberedChapter
:
235 words
= prepare_contents_title(p
->kwtext
, L
": ", p
->words
);
238 case para_UnnumberedChapter
:
239 words
= prepare_contents_title(NULL
, NULL
, p
->words
);
244 words
= prepare_contents_title(p
->kwtext2
, L
" ", p
->words
);
245 indent
= (p
->aux
+ 1) * conf
->contents_indent_step
;
248 pdata
= make_para_data(para_Normal
, p
->aux
, indent
,
249 conf
->contents_margin
,
250 NULL
, NULL
, words
, conf
);
252 pdata
->contents_entry
= p
;
253 lastcont
->next
= pdata
;
257 * Link all contents line structures together into
262 lastcontline
->next
= pdata
->first
;
263 pdata
->first
->prev
= lastcontline
;
265 firstcontline
= pdata
->first
;
266 pdata
->first
->prev
= NULL
;
268 lastcontline
= pdata
->last
;
269 lastcontline
->next
= NULL
;
277 * And one extra one, for the index.
280 pdata
= make_para_data(para_Normal
, 0, 0,
281 conf
->contents_margin
,
282 NULL
, NULL
, fake_word(L
"Index"), conf
);
284 pdata
->contents_entry
= &index_placeholder_para
;
285 lastcont
->next
= pdata
;
290 lastcontline
->next
= pdata
->first
;
291 pdata
->first
->prev
= lastcontline
;
293 firstcontline
= pdata
->first
;
294 pdata
->first
->prev
= NULL
;
296 lastcontline
= pdata
->last
;
297 lastcontline
->next
= NULL
;
303 * Do the main paragraph formatting.
306 used_contents
= FALSE
;
307 firstline
= lastline
= NULL
;
308 for (p
= sourceform
; p
; p
= p
->next
) {
309 p
->private_data
= NULL
;
313 * These paragraph types are either invisible or don't
314 * define text in the normal sense. Either way, they
315 * don't require wrapping.
320 case para_NotParaType
:
327 * These paragraph types don't require wrapping, but
328 * they do affect the line width to which we wrap the
329 * rest of the paragraphs, so we need to pay attention.
332 indent
+= conf
->indent_list
; break;
334 indent
-= conf
->indent_list
; assert(indent
>= 0); break;
336 indent
+= conf
->indent_quote
; break;
338 indent
-= conf
->indent_quote
; assert(indent
>= 0); break;
341 * This paragraph type is special. Process it
345 pdata
= code_paragraph(indent
, p
->words
, conf
);
346 p
->private_data
= pdata
;
347 if (pdata
->first
!= pdata
->last
) {
348 pdata
->first
->penalty_after
+= 100000;
349 pdata
->last
->penalty_before
+= 100000;
354 * This paragraph is also special.
357 pdata
= rule_paragraph(indent
, conf
);
358 p
->private_data
= pdata
;
362 * All of these paragraph types require wrapping in the
363 * ordinary way. So we must supply a set of fonts, a
364 * line width and auxiliary information (e.g. bullet
365 * text) for each one.
369 case para_UnnumberedChapter
:
373 case para_BiblioCited
:
375 case para_NumberedList
:
376 case para_DescribedThing
:
377 case para_Description
:
380 pdata
= make_para_data(p
->type
, p
->aux
, indent
, 0,
381 p
->kwtext
, p
->kwtext2
, p
->words
, conf
);
383 p
->private_data
= pdata
;
388 if (p
->private_data
) {
389 pdata
= (para_data
*)p
->private_data
;
392 * If this is the first non-title heading, we link the
393 * contents section in before it.
395 if (!used_contents
&& pdata
->outline_level
> 0) {
396 used_contents
= TRUE
;
398 lastpara
->next
= firstcont
;
400 firstpara
= firstcont
;
402 assert(lastpara
->next
== NULL
);
405 lastline
->next
= firstcontline
;
406 firstcontline
->prev
= lastline
;
408 firstline
= firstcontline
;
409 firstcontline
->prev
= NULL
;
411 assert(lastcontline
!= NULL
);
412 lastline
= lastcontline
;
413 lastline
->next
= NULL
;
417 * Link all line structures together into a big list.
421 lastline
->next
= pdata
->first
;
422 pdata
->first
->prev
= lastline
;
424 firstline
= pdata
->first
;
425 pdata
->first
->prev
= NULL
;
427 lastline
= pdata
->last
;
428 lastline
->next
= NULL
;
432 * Link all paragraph structures together similarly.
436 lastpara
->next
= pdata
;
444 * Now we have an enormous linked list of every line of text in
445 * the document. Break it up into pages.
447 pages
= page_breaks(firstline
, lastline
, conf
->page_height
, 0, 0);
458 for (page
= pages
; page
; page
= page
->next
) {
459 sprintf(buf
, "%d", ++pagenum
);
460 page
->number
= ufroma_dup(buf
);
464 first_index_page
= mknew(page_data
);
465 first_index_page
->next
= first_index_page
->prev
= NULL
;
466 first_index_page
->first_line
= NULL
;
467 first_index_page
->last_line
= NULL
;
468 first_index_page
->first_text
= first_index_page
->last_text
= NULL
;
469 first_index_page
->first_xref
= first_index_page
->last_xref
= NULL
;
470 first_index_page
->first_rect
= first_index_page
->last_rect
= NULL
;
472 /* And don't forget the as-yet-uncreated index. */
473 sprintf(buf
, "%d", ++pagenum
);
474 first_index_page
->number
= ufroma_dup(buf
);
479 * Now we're ready to actually lay out the pages. We do this by
480 * looping over _paragraphs_, since we may need to track cross-
481 * references between lines and even across pages.
483 for (pdata
= firstpara
; pdata
; pdata
= pdata
->next
)
484 render_para(pdata
, conf
, keywords
, idx
,
485 &index_placeholder_para
, first_index_page
);
488 * Now we've laid out the main body pages, we should have
489 * acquired a full set of page numbers for the index.
495 para_data
*firstidx
, *lastidx
;
496 line_data
*firstidxline
, *lastidxline
, *ldata
;
497 page_data
*ipages
, *ipages2
, *page
;
500 * Create a set of paragraphs for the index.
502 index_title
= fake_word(L
"Index");
504 firstidx
= make_para_data(para_UnnumberedChapter
, 0, 0, 0,
505 NULL
, NULL
, index_title
, conf
);
507 lastidx
->next
= NULL
;
508 firstidxline
= firstidx
->first
;
509 lastidxline
= lastidx
->last
;
510 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
511 paper_idx
*pi
= (paper_idx
*)entry
->backend_data
;
512 para_data
*text
, *pages
;
514 text
= make_para_data(para_Normal
, 0, 0,
515 conf
->base_width
- conf
->index_colwidth
,
516 NULL
, NULL
, entry
->text
, conf
);
518 pages
= make_para_data(para_Normal
, 0, 0,
519 conf
->base_width
- conf
->index_colwidth
,
520 NULL
, NULL
, pi
->words
, conf
);
522 text
->justification
= LEFT
;
523 pages
->justification
= RIGHT
;
524 text
->last
->space_after
= pages
->first
->space_before
=
525 conf
->base_leading
/ 2;
527 pages
->last
->space_after
= text
->first
->space_before
=
531 assert(pages
->first
);
536 * If feasible, fold the two halves of the index entry
539 if (text
->last
->real_shortfall
+ pages
->first
->real_shortfall
>
540 conf
->index_colwidth
+ conf
->index_minsep
) {
541 text
->last
->space_after
= -1;
542 pages
->first
->space_before
= -pages
->first
->line_height
+1;
545 lastidx
->next
= text
;
551 * Link all index line structures together into
554 text
->last
->next
= pages
->first
;
555 pages
->first
->prev
= text
->last
;
557 lastidxline
->next
= text
->first
;
558 text
->first
->prev
= lastidxline
;
560 lastidxline
= pages
->last
;
563 * Breaking an index entry anywhere is so bad that I
564 * think I'm going to forbid it totally.
566 for (ldata
= text
->first
; ldata
&& ldata
->next
;
567 ldata
= ldata
->next
) {
568 ldata
->next
->space_before
+= ldata
->space_after
+ 1;
569 ldata
->space_after
= -1;
574 * Now break the index into pages.
576 ipages
= page_breaks(firstidxline
, firstidxline
, conf
->page_height
,
578 ipages2
= page_breaks(firstidxline
->next
, lastidxline
,
581 firstidxline
->space_before
+
582 firstidxline
->line_height
+
583 firstidxline
->space_after
);
586 * This will have put each _column_ of the index on a
587 * separate page, which isn't what we want. Fold the pages
594 for (i
= 1; i
< conf
->index_cols
; i
++)
598 fold_into_page(page
, page
->next
,
599 i
* (conf
->index_colwidth
+
600 conf
->index_gutter
));
602 page
->next
= page
->next
->next
;
604 page
->next
->prev
= page
;
610 /* Also fold the heading on to the same page as the index items. */
611 fold_into_page(ipages
, ipages2
, 0);
612 ipages
->next
= ipages2
->next
;
614 ipages
->next
->prev
= ipages
;
616 fold_into_page(first_index_page
, ipages
, 0);
617 first_index_page
->next
= ipages
->next
;
618 if (first_index_page
->next
)
619 first_index_page
->next
->prev
= first_index_page
;
621 ipages
= first_index_page
;
624 * Number the index pages, except the already-numbered
627 for (page
= ipages
->next
; page
; page
= page
->next
) {
629 sprintf(buf
, "%d", ++pagenum
);
630 page
->number
= ufroma_dup(buf
);
634 * Render the index pages.
636 for (pdata
= firstidx
; pdata
; pdata
= pdata
->next
)
637 render_para(pdata
, conf
, keywords
, idx
,
638 &index_placeholder_para
, first_index_page
);
641 * Link the index page list on to the end of the main page
647 for (page
= pages
; page
->next
; page
= page
->next
);
652 * Same with the paragraph list, which will cause the index
653 * to be mentioned in the document outline.
656 firstpara
= firstidx
;
658 lastpara
->next
= firstidx
;
663 * Start putting together the overall document structure we're
666 doc
= mknew(document
);
667 doc
->fonts
= fontlist
;
669 doc
->paper_width
= conf
->paper_width
;
670 doc
->paper_height
= conf
->paper_height
;
673 * Collect the section heading paragraphs into a document
674 * outline. This is slightly fiddly because the Title paragraph
675 * isn't required to be at the start, although all the others
681 doc
->outline_elements
= mknewa(outline_element
, osize
);
682 doc
->n_outline_elements
= 0;
684 /* First find the title. */
685 for (pdata
= firstpara
; pdata
; pdata
= pdata
->next
) {
686 if (pdata
->outline_level
== 0) {
687 doc
->outline_elements
[0].level
= 0;
688 doc
->outline_elements
[0].pdata
= pdata
;
689 doc
->n_outline_elements
++;
694 /* Then collect the rest. */
695 for (pdata
= firstpara
; pdata
; pdata
= pdata
->next
) {
696 if (pdata
->outline_level
> 0) {
697 if (doc
->n_outline_elements
>= osize
) {
699 doc
->outline_elements
=
700 resize(doc
->outline_elements
, osize
);
703 doc
->outline_elements
[doc
->n_outline_elements
].level
=
704 pdata
->outline_level
;
705 doc
->outline_elements
[doc
->n_outline_elements
].pdata
= pdata
;
706 doc
->n_outline_elements
++;
716 static para_data
*make_para_data(int ptype
, int paux
, int indent
, int rmargin
,
717 word
*pkwtext
, word
*pkwtext2
, word
*pwords
,
722 int extra_indent
, firstline_indent
, aux_indent
;
725 pdata
= mknew(para_data
);
726 pdata
->outline_level
= -1;
727 pdata
->outline_title
= NULL
;
728 pdata
->rect_type
= RECT_NONE
;
729 pdata
->contents_entry
= NULL
;
730 pdata
->justification
= JUST
;
733 * Choose fonts for this paragraph.
735 * FIXME: All of this ought to be completely
740 pdata
->fonts
[FONT_NORMAL
] = conf
->hr
;
741 pdata
->sizes
[FONT_NORMAL
] = 24;
742 pdata
->fonts
[FONT_EMPH
] = conf
->hi
;
743 pdata
->sizes
[FONT_EMPH
] = 24;
744 pdata
->fonts
[FONT_CODE
] = conf
->cb
;
745 pdata
->sizes
[FONT_CODE
] = 24;
746 pdata
->outline_level
= 0;
751 case para_UnnumberedChapter
:
752 pdata
->fonts
[FONT_NORMAL
] = conf
->hr
;
753 pdata
->sizes
[FONT_NORMAL
] = 20;
754 pdata
->fonts
[FONT_EMPH
] = conf
->hi
;
755 pdata
->sizes
[FONT_EMPH
] = 20;
756 pdata
->fonts
[FONT_CODE
] = conf
->cb
;
757 pdata
->sizes
[FONT_CODE
] = 20;
758 pdata
->outline_level
= 1;
763 pdata
->fonts
[FONT_NORMAL
] = conf
->hr
;
764 pdata
->fonts
[FONT_EMPH
] = conf
->hi
;
765 pdata
->fonts
[FONT_CODE
] = conf
->cb
;
766 pdata
->sizes
[FONT_NORMAL
] =
767 pdata
->sizes
[FONT_EMPH
] =
768 pdata
->sizes
[FONT_CODE
] =
769 (paux
== 0 ?
16 : paux
== 1 ?
14 : 13);
770 pdata
->outline_level
= 2 + paux
;
774 case para_BiblioCited
:
776 case para_NumberedList
:
777 case para_DescribedThing
:
778 case para_Description
:
780 pdata
->fonts
[FONT_NORMAL
] = conf
->tr
;
781 pdata
->sizes
[FONT_NORMAL
] = 12;
782 pdata
->fonts
[FONT_EMPH
] = conf
->ti
;
783 pdata
->sizes
[FONT_EMPH
] = 12;
784 pdata
->fonts
[FONT_CODE
] = conf
->cr
;
785 pdata
->sizes
[FONT_CODE
] = 12;
790 * Also select an indentation level depending on the
791 * paragraph type (list paragraphs other than
792 * para_DescribedThing need extra indent).
794 * (FIXME: Perhaps at some point we might even arrange
795 * for the user to be able to request indented first
796 * lines in paragraphs.)
798 if (ptype
== para_Bullet
||
799 ptype
== para_NumberedList
||
800 ptype
== para_Description
) {
801 extra_indent
= firstline_indent
= conf
->indent_list
;
803 extra_indent
= firstline_indent
= 0;
807 * Find the auxiliary text for this paragraph.
818 * For some heading styles (FIXME: be able to
819 * configure which), the auxiliary text contains
820 * the chapter number and is arranged to be
821 * right-aligned a few points left of the primary
822 * margin. For other styles, the auxiliary text is
823 * the full chapter _name_ and takes up space
824 * within the (wrapped) chapter title, meaning that
825 * we must move the first line indent over to make
828 if (ptype
== para_Heading
|| ptype
== para_Subsect
) {
832 len
= paper_width_simple(pdata
, pkwtext2
);
833 aux_indent
= -len
- conf
->sect_num_left_space
;
835 pdata
->outline_title
=
836 prepare_outline_title(pkwtext2
, L
" ", pwords
);
839 aux2
= fake_word(L
": ");
842 firstline_indent
+= paper_width_simple(pdata
, aux
);
843 firstline_indent
+= paper_width_simple(pdata
, aux2
);
845 pdata
->outline_title
=
846 prepare_outline_title(pkwtext
, L
": ", pwords
);
852 * Auxiliary text consisting of a bullet. (FIXME:
853 * configurable bullet.)
855 aux
= fake_word(L
"\x2022");
856 aux_indent
= indent
+ conf
->indent_list_bullet
;
859 case para_NumberedList
:
861 * Auxiliary text consisting of the number followed
862 * by a (FIXME: configurable) full stop.
865 aux2
= fake_word(L
".");
866 aux_indent
= indent
+ conf
->indent_list_bullet
;
869 case para_BiblioCited
:
871 * Auxiliary text consisting of the bibliography
872 * reference text, and a trailing space.
875 aux2
= fake_word(L
" ");
877 firstline_indent
+= paper_width_simple(pdata
, aux
);
878 firstline_indent
+= paper_width_simple(pdata
, aux2
);
882 if (pdata
->outline_level
>= 0 && !pdata
->outline_title
) {
883 pdata
->outline_title
=
884 prepare_outline_title(NULL
, NULL
, pwords
);
887 wrap_paragraph(pdata
, pwords
, conf
->base_width
- rmargin
,
888 indent
+ firstline_indent
,
889 indent
+ extra_indent
);
891 pdata
->first
->aux_text
= aux
;
892 pdata
->first
->aux_text_2
= aux2
;
893 pdata
->first
->aux_left_indent
= aux_indent
;
896 * Line breaking penalties.
903 case para_UnnumberedChapter
:
905 * Fixed and large penalty for breaking straight
906 * after a heading; corresponding bonus for
907 * breaking straight before.
909 pdata
->first
->penalty_before
= -500000;
910 pdata
->last
->penalty_after
= 500000;
911 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
)
912 ldata
->penalty_after
= 500000;
915 case para_DescribedThing
:
917 * This is treated a bit like a small heading:
918 * there's a penalty for breaking after it (i.e.
919 * between it and its description), and a bonus for
920 * breaking before it (actually _between_ list
923 pdata
->first
->penalty_before
= -200000;
924 pdata
->last
->penalty_after
= 200000;
929 * Most paragraph types: widow/orphan control by
930 * discouraging breaking one line from the end of
933 if (pdata
->first
!= pdata
->last
) {
934 pdata
->first
->penalty_after
= 100000;
935 pdata
->last
->penalty_before
= 100000;
940 standard_line_spacing(pdata
, conf
);
943 * Some kinds of section heading require a page break before
944 * them and an underline after.
946 if (ptype
== para_Title
||
947 ptype
== para_Chapter
||
948 ptype
== para_Appendix
||
949 ptype
== para_UnnumberedChapter
) {
950 pdata
->first
->page_break
= TRUE
;
951 pdata
->first
->space_before
= conf
->chapter_top_space
;
952 pdata
->last
->space_after
+=
953 (conf
->chapter_underline_depth
+
954 conf
->chapter_underline_thickness
);
955 pdata
->rect_type
= RECT_CHAPTER_UNDERLINE
;
961 static void standard_line_spacing(para_data
*pdata
, paper_conf
*conf
)
966 * Set the line spacing for each line in this paragraph.
968 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
) {
969 if (ldata
== pdata
->first
)
970 ldata
->space_before
= conf
->base_para_spacing
/ 2;
972 ldata
->space_before
= conf
->base_leading
/ 2;
973 if (ldata
== pdata
->last
)
974 ldata
->space_after
= conf
->base_para_spacing
/ 2;
976 ldata
->space_after
= conf
->base_leading
/ 2;
977 ldata
->page_break
= FALSE
;
981 static font_encoding
*new_font_encoding(font_data
*font
)
986 fe
= mknew(font_encoding
);
989 if (font
->list
->tail
)
990 font
->list
->tail
->next
= fe
;
992 font
->list
->head
= fe
;
993 font
->list
->tail
= fe
;
998 for (i
= 0; i
< 256; i
++) {
999 fe
->vector
[i
] = NULL
;
1000 fe
->indices
[i
] = -1;
1001 fe
->to_unicode
[i
] = 0xFFFF;
1007 static font_data
*make_std_font(font_list
*fontlist
, char const *name
)
1015 widths
= ps_std_font_widths(name
);
1019 for (nglyphs
= 0; ps_std_glyphs
[nglyphs
] != NULL
; nglyphs
++);
1021 f
= mknew(font_data
);
1025 f
->nglyphs
= nglyphs
;
1026 f
->glyphs
= ps_std_glyphs
;
1028 f
->subfont_map
= mknewa(subfont_map_entry
, nglyphs
);
1031 * Our first subfont will contain all of US-ASCII. This isn't
1032 * really necessary - we could just create custom subfonts
1033 * precisely as the whim of render_string dictated - but
1034 * instinct suggests that it might be nice to have the text in
1035 * the output files look _marginally_ recognisable.
1037 fe
= new_font_encoding(f
);
1038 fe
->free_pos
= 0xA1; /* only the top half is free */
1039 f
->latest_subfont
= fe
;
1041 for (i
= 0; i
< (int)lenof(f
->bmp
); i
++)
1044 for (i
= 0; i
< nglyphs
; i
++) {
1046 ucs
= ps_glyph_to_unicode(f
->glyphs
[i
]);
1047 assert(ucs
!= 0xFFFF);
1049 if (ucs
>= 0x20 && ucs
<= 0x7E) {
1050 fe
->vector
[ucs
] = f
->glyphs
[i
];
1051 fe
->indices
[ucs
] = i
;
1052 fe
->to_unicode
[ucs
] = ucs
;
1053 f
->subfont_map
[i
].subfont
= fe
;
1054 f
->subfont_map
[i
].position
= ucs
;
1057 * This character is not yet assigned to a subfont.
1059 f
->subfont_map
[i
].subfont
= NULL
;
1060 f
->subfont_map
[i
].position
= 0;
1067 static int string_width(font_data
*font
, wchar_t const *string
, int *errs
)
1074 for (; *string
; string
++) {
1077 index
= font
->bmp
[(unsigned short)*string
];
1078 if (index
== 0xFFFF) {
1082 width
+= font
->widths
[index
];
1089 static int paper_width_internal(void *vctx
, word
*word
, int *nspaces
);
1091 struct paper_width_ctx
{
1096 static int paper_width_list(void *vctx
, word
*text
, word
*end
, int *nspaces
) {
1098 while (text
&& text
!= end
) {
1099 w
+= paper_width_internal(vctx
, text
, nspaces
);
1105 static int paper_width_internal(void *vctx
, word
*word
, int *nspaces
)
1107 struct paper_width_ctx
*ctx
= (struct paper_width_ctx
*)vctx
;
1108 int style
, type
, findex
, width
, errs
;
1111 switch (word
->type
) {
1112 case word_HyperLink
:
1114 case word_UpperXref
:
1115 case word_LowerXref
:
1121 style
= towordstyle(word
->type
);
1122 type
= removeattr(word
->type
);
1124 findex
= (style
== word_Normal ? FONT_NORMAL
:
1125 style
== word_Emph ? FONT_EMPH
:
1128 if (type
== word_Normal
) {
1130 } else if (type
== word_WhiteSpace
) {
1131 if (findex
!= FONT_CODE
) {
1134 return ctx
->minspacewidth
;
1137 } else /* if (type == word_Quote) */ {
1138 if (word
->aux
== quote_Open
)
1139 str
= L
"\x2018"; /* FIXME: configurability! */
1141 str
= L
"\x2019"; /* FIXME: configurability! */
1144 width
= string_width(ctx
->pdata
->fonts
[findex
], str
, &errs
);
1146 if (errs
&& word
->alt
)
1147 return paper_width_list(vctx
, word
->alt
, NULL
, nspaces
);
1149 return ctx
->pdata
->sizes
[findex
] * width
;
1152 static int paper_width(void *vctx
, word
*word
)
1154 return paper_width_internal(vctx
, word
, NULL
);
1157 static int paper_width_simple(para_data
*pdata
, word
*text
)
1159 struct paper_width_ctx ctx
;
1163 (pdata
->sizes
[FONT_NORMAL
] *
1164 string_width(pdata
->fonts
[FONT_NORMAL
], L
" ", NULL
));
1166 return paper_width_list(&ctx
, text
, NULL
, NULL
);
1169 static void wrap_paragraph(para_data
*pdata
, word
*words
,
1170 int w
, int i1
, int i2
)
1172 wrappedline
*wrapping
, *p
;
1174 struct paper_width_ctx ctx
;
1178 * We're going to need to store the line height in every line
1179 * structure we generate.
1184 for (i
= 0; i
< NFONTS
; i
++)
1185 if (line_height
< pdata
->sizes
[i
])
1186 line_height
= pdata
->sizes
[i
];
1187 line_height
*= 4096;
1190 spacewidth
= (pdata
->sizes
[FONT_NORMAL
] *
1191 string_width(pdata
->fonts
[FONT_NORMAL
], L
" ", NULL
));
1192 if (spacewidth
== 0) {
1194 * A font without a space?! Disturbing. I hope this never
1195 * comes up, but I'll make a random guess anyway and set my
1196 * space width to half the point size.
1198 spacewidth
= pdata
->sizes
[FONT_NORMAL
] * 4096 / 2;
1202 * I'm going to set the _minimum_ space width to 3/5 of the
1203 * standard one, and use the standard one as the optimum.
1205 ctx
.minspacewidth
= spacewidth
* 3 / 5;
1208 wrapping
= wrap_para(words
, w
- i1
, w
- i2
, paper_width
, &ctx
, spacewidth
);
1211 * Having done the wrapping, we now concoct a set of line_data
1214 pdata
->first
= pdata
->last
= NULL
;
1216 for (p
= wrapping
; p
; p
= p
->next
) {
1219 int len
, wid
, spaces
;
1221 ldata
= mknew(line_data
);
1223 ldata
->pdata
= pdata
;
1224 ldata
->first
= p
->begin
;
1225 ldata
->end
= p
->end
;
1226 ldata
->line_height
= line_height
;
1228 ldata
->xpos
= (p
== wrapping ? i1
: i2
);
1231 pdata
->last
->next
= ldata
;
1232 ldata
->prev
= pdata
->last
;
1234 pdata
->first
= ldata
;
1238 pdata
->last
= ldata
;
1241 len
= paper_width_list(&ctx
, ldata
->first
, ldata
->end
, &spaces
);
1242 wid
= (p
== wrapping ? w
- i1
: w
- i2
);
1245 ldata
->hshortfall
= wid
- len
;
1246 ldata
->nspaces
= spaces
;
1248 * This tells us how much the space width needs to
1249 * change from _min_spacewidth. But we want to store
1250 * its difference from the _natural_ space width, to
1251 * make the text rendering easier.
1253 ldata
->hshortfall
+= ctx
.minspacewidth
* spaces
;
1254 ldata
->hshortfall
-= spacewidth
* spaces
;
1255 ldata
->real_shortfall
= ldata
->hshortfall
;
1257 * Special case: on the last line of a paragraph, we
1258 * never stretch spaces.
1260 if (ldata
->hshortfall
> 0 && !p
->next
)
1261 ldata
->hshortfall
= 0;
1263 ldata
->aux_text
= NULL
;
1264 ldata
->aux_text_2
= NULL
;
1265 ldata
->aux_left_indent
= 0;
1266 ldata
->penalty_before
= ldata
->penalty_after
= 0;
1271 static page_data
*page_breaks(line_data
*first
, line_data
*last
,
1272 int page_height
, int ncols
, int headspace
)
1276 int n
, n1
, this_height
;
1279 * Page breaking is done by a close analogue of the optimal
1280 * paragraph wrapping algorithm used by wrap_para(). We work
1281 * backwards from the end of the document line by line; for
1282 * each line, we contemplate every possible number of lines we
1283 * could put on a page starting with that line, determine a
1284 * cost function for each one, add it to the pre-computed cost
1285 * function for optimally page-breaking everything after that
1286 * page, and pick the best option.
1288 * This is made slightly more complex by the fact that we have
1289 * a multi-column index with a heading at the top of the
1290 * _first_ page, meaning that the first _ncols_ pages must have
1291 * a different length. Hence, we must do the wrapping ncols+1
1292 * times over, hypothetically trying to put every subsequence
1293 * on every possible page.
1295 * Since my line_data structures are only used for this
1296 * purpose, I might as well just store the algorithm data
1300 for (l
= last
; l
; l
= l
->prev
) {
1301 l
->bestcost
= mknewa(int, ncols
+1);
1302 l
->vshortfall
= mknewa(int, ncols
+1);
1303 l
->text
= mknewa(int, ncols
+1);
1304 l
->space
= mknewa(int, ncols
+1);
1305 l
->page_last
= mknewa(line_data
*, ncols
+1);
1307 for (n
= 0; n
<= ncols
; n
++) {
1308 int minheight
, text
= 0, space
= 0;
1311 n1
= (n
< ncols ? n
+1 : ncols
);
1313 this_height
= page_height
- headspace
;
1315 this_height
= page_height
;
1317 l
->bestcost
[n
] = -1;
1318 for (m
= l
; m
; m
= m
->next
) {
1319 if (m
!= l
&& m
->page_break
)
1320 break; /* we've gone as far as we can */
1323 if (m
->prev
->space_after
> 0)
1324 space
+= m
->prev
->space_after
;
1326 text
+= m
->prev
->space_after
;
1328 if (m
!= l
|| m
->page_break
) {
1329 if (m
->space_before
> 0)
1330 space
+= m
->space_before
;
1332 text
+= m
->space_before
;
1334 text
+= m
->line_height
;
1335 minheight
= text
+ space
;
1337 if (m
!= l
&& minheight
> this_height
)
1341 * If the space after this paragraph is _negative_
1342 * (which means the next line is folded on to this
1343 * one, which happens in the index), we absolutely
1344 * cannot break here.
1346 if (m
->space_after
>= 0) {
1349 * Compute the cost of this arrangement, as the
1350 * square of the amount of wasted space on the
1351 * page. Exception: if this is the last page
1352 * before a mandatory break or the document
1353 * end, we don't penalise a large blank area.
1355 if (m
!= last
&& m
->next
&& !m
->next
->page_break
)
1357 int x
= this_height
- minheight
;
1364 cost
+= (x
* xf
) >> 8;
1368 if (m
!= last
&& m
->next
&& !m
->next
->page_break
) {
1369 cost
+= m
->penalty_after
;
1370 cost
+= m
->next
->penalty_before
;
1373 if (m
!= last
&& m
->next
&& !m
->next
->page_break
)
1374 cost
+= m
->next
->bestcost
[n1
];
1375 if (l
->bestcost
[n
] == -1 || l
->bestcost
[n
] > cost
) {
1377 * This is the best option yet for this
1380 l
->bestcost
[n
] = cost
;
1381 if (m
!= last
&& m
->next
&& !m
->next
->page_break
)
1382 l
->vshortfall
[n
] = this_height
- minheight
;
1384 l
->vshortfall
[n
] = 0;
1386 l
->space
[n
] = space
;
1387 l
->page_last
[n
] = m
;
1398 * Now go through the line list forwards and assemble the
1407 int text
, space
, head
;
1409 page
= mknew(page_data
);
1418 page
->first_line
= l
;
1419 page
->last_line
= l
->page_last
[n
];
1421 page
->first_text
= page
->last_text
= NULL
;
1422 page
->first_xref
= page
->last_xref
= NULL
;
1423 page
->first_rect
= page
->last_rect
= NULL
;
1426 * Now assign a y-coordinate to each line on the page.
1429 head
= (n
< ncols ? headspace
: 0);
1430 for (l
= page
->first_line
; l
; l
= l
->next
) {
1431 if (l
!= page
->first_line
) {
1432 if (l
->prev
->space_after
> 0)
1433 space
+= l
->prev
->space_after
;
1435 text
+= l
->prev
->space_after
;
1437 if (l
!= page
->first_line
|| l
->page_break
) {
1438 if (l
->space_before
> 0)
1439 space
+= l
->space_before
;
1441 text
+= l
->space_before
;
1443 text
+= l
->line_height
;
1446 l
->ypos
= text
+ space
+ head
+
1447 space
* (float)page
->first_line
->vshortfall
[n
] /
1448 page
->first_line
->space
[n
];
1450 if (l
== page
->last_line
)
1454 l
= page
->last_line
;
1459 n
= (n
< ncols ? n
+1 : ncols
);
1465 static void add_rect_to_page(page_data
*page
, int x
, int y
, int w
, int h
)
1467 rect
*r
= mknew(rect
);
1470 if (page
->last_rect
)
1471 page
->last_rect
->next
= r
;
1473 page
->first_rect
= r
;
1474 page
->last_rect
= r
;
1482 static void add_string_to_page(page_data
*page
, int x
, int y
,
1483 font_encoding
*fe
, int size
, char *text
)
1485 text_fragment
*frag
;
1487 frag
= mknew(text_fragment
);
1490 if (page
->last_text
)
1491 page
->last_text
->next
= frag
;
1493 page
->first_text
= frag
;
1494 page
->last_text
= frag
;
1499 frag
->fontsize
= size
;
1500 frag
->text
= dupstr(text
);
1504 * Returns the updated x coordinate.
1506 static int render_string(page_data
*page
, font_data
*font
, int fontsize
,
1507 int x
, int y
, wchar_t *str
)
1510 int textpos
, textwid
, glyph
;
1511 font_encoding
*subfont
= NULL
, *sf
;
1513 text
= mknewa(char, 1 + ustrlen(str
));
1514 textpos
= textwid
= 0;
1517 glyph
= font
->bmp
[*str
];
1519 if (glyph
== 0xFFFF)
1520 continue; /* nothing more we can do here */
1523 * Find which subfont this character is going in.
1525 sf
= font
->subfont_map
[glyph
].subfont
;
1531 * This character is not yet in a subfont. Assign one.
1533 if (font
->latest_subfont
->free_pos
>= 0x100)
1534 font
->latest_subfont
= new_font_encoding(font
);
1536 c
= font
->latest_subfont
->free_pos
++;
1537 if (font
->latest_subfont
->free_pos
== 0x7F)
1538 font
->latest_subfont
->free_pos
= 0xA1;
1540 font
->subfont_map
[glyph
].subfont
= font
->latest_subfont
;
1541 font
->subfont_map
[glyph
].position
= c
;
1542 font
->latest_subfont
->vector
[c
] = font
->glyphs
[glyph
];
1543 font
->latest_subfont
->indices
[c
] = glyph
;
1544 font
->latest_subfont
->to_unicode
[c
] = *str
;
1546 sf
= font
->latest_subfont
;
1549 if (!subfont
|| sf
!= subfont
) {
1551 text
[textpos
] = '\0';
1552 add_string_to_page(page
, x
, y
, subfont
, fontsize
, text
);
1555 assert(textpos
== 0);
1561 text
[textpos
++] = font
->subfont_map
[glyph
].position
;
1562 textwid
+= font
->widths
[glyph
] * fontsize
;
1568 text
[textpos
] = '\0';
1569 add_string_to_page(page
, x
, y
, subfont
, fontsize
, text
);
1577 * Returns the updated x coordinate.
1579 static int render_text(page_data
*page
, para_data
*pdata
, line_data
*ldata
,
1580 int x
, int y
, word
*text
, word
*text_end
, xref
**xr
,
1581 int shortfall
, int nspaces
, int *nspace
,
1582 keywordlist
*keywords
, indexdata
*idx
)
1584 while (text
&& text
!= text_end
) {
1585 int style
, type
, findex
, errs
;
1589 switch (text
->type
) {
1591 * Start a cross-reference.
1593 case word_HyperLink
:
1594 case word_UpperXref
:
1595 case word_LowerXref
:
1597 if (text
->type
== word_HyperLink
) {
1599 dest
.url
= utoa_dup(text
->text
);
1602 keyword
*kwl
= kw_lookup(keywords
, text
->text
);
1606 assert(kwl
->para
->private_data
);
1607 pdata
= (para_data
*) kwl
->para
->private_data
;
1609 dest
.page
= pdata
->first
->page
;
1613 * Shouldn't happen, but *shrug*
1620 if (dest
.type
!= NONE
) {
1622 (*xr
)->dest
= dest
; /* structure copy */
1623 if (page
->last_xref
)
1624 page
->last_xref
->next
= *xr
;
1626 page
->first_xref
= *xr
;
1627 page
->last_xref
= *xr
;
1631 * FIXME: Ideally we should have, and use, some
1632 * vertical font metric information here so that
1633 * our cross-ref rectangle can take account of
1634 * descenders and the font's cap height. This will
1635 * do for the moment, but it isn't ideal.
1637 (*xr
)->lx
= (*xr
)->rx
= x
;
1639 (*xr
)->ty
= y
+ ldata
->line_height
;
1644 * Finish extending a cross-reference box.
1652 * Add the current page number to the list of pages
1653 * referenced by an index entry.
1660 tag
= index_findtag(idx
, text
->text
);
1664 for (i
= 0; i
< tag
->nrefs
; i
++) {
1665 indexentry
*entry
= tag
->refs
[i
];
1666 paper_idx
*pi
= (paper_idx
*)entry
->backend_data
;
1669 * If the same index term is indexed twice
1670 * within the same section, we only want to
1671 * mention it once in the index.
1673 if (pi
->lastpage
!= page
) {
1675 pi
->lastword
= pi
->lastword
->next
=
1677 pi
->lastword
= pi
->lastword
->next
=
1679 pi
->lastword
= pi
->lastword
->next
=
1680 fake_word(page
->number
);
1682 pi
->lastword
= pi
->words
=
1683 fake_word(page
->number
);
1687 pi
->lastpage
= page
;
1693 style
= towordstyle(text
->type
);
1694 type
= removeattr(text
->type
);
1696 findex
= (style
== word_Normal ? FONT_NORMAL
:
1697 style
== word_Emph ? FONT_EMPH
:
1700 if (type
== word_Normal
) {
1702 } else if (type
== word_WhiteSpace
) {
1703 x
+= pdata
->sizes
[findex
] *
1704 string_width(pdata
->fonts
[findex
], L
" ", NULL
);
1705 if (nspaces
&& findex
!= FONT_CODE
) {
1706 x
+= (*nspace
+1) * shortfall
/ nspaces
;
1707 x
-= *nspace
* shortfall
/ nspaces
;
1711 } else /* if (type == word_Quote) */ {
1712 if (text
->aux
== quote_Open
)
1713 str
= L
"\x2018"; /* FIXME: configurability! */
1715 str
= L
"\x2019"; /* FIXME: configurability! */
1718 (void) string_width(pdata
->fonts
[findex
], str
, &errs
);
1720 if (errs
&& text
->alt
)
1721 x
= render_text(page
, pdata
, ldata
, x
, y
, text
->alt
, NULL
,
1722 xr
, shortfall
, nspaces
, nspace
, keywords
, idx
);
1724 x
= render_string(page
, pdata
->fonts
[findex
],
1725 pdata
->sizes
[findex
], x
, y
, str
);
1738 * Returns the last x position used on the line.
1740 static int render_line(line_data
*ldata
, int left_x
, int top_y
,
1741 xref_dest
*dest
, keywordlist
*keywords
, indexdata
*idx
)
1747 if (ldata
->aux_text
) {
1751 x
= render_text(ldata
->page
, ldata
->pdata
, ldata
,
1752 left_x
+ ldata
->aux_left_indent
,
1753 top_y
- ldata
->ypos
,
1754 ldata
->aux_text
, NULL
, &xr
, 0, 0, &nspace
,
1756 if (ldata
->aux_text_2
)
1757 render_text(ldata
->page
, ldata
->pdata
, ldata
,
1758 x
, top_y
- ldata
->ypos
,
1759 ldata
->aux_text_2
, NULL
, &xr
, 0, 0, &nspace
,
1766 * There might be a cross-reference carried over from a
1769 if (dest
->type
!= NONE
) {
1772 xr
->dest
= *dest
; /* structure copy */
1773 if (ldata
->page
->last_xref
)
1774 ldata
->page
->last_xref
->next
= xr
;
1776 ldata
->page
->first_xref
= xr
;
1777 ldata
->page
->last_xref
= xr
;
1778 xr
->lx
= xr
->rx
= left_x
+ ldata
->xpos
;
1779 xr
->by
= top_y
- ldata
->ypos
;
1780 xr
->ty
= top_y
- ldata
->ypos
+ ldata
->line_height
;
1785 int extra_indent
, shortfall
, spaces
;
1786 int just
= ldata
->pdata
->justification
;
1789 * All forms of justification become JUST when we have
1790 * to squeeze the paragraph.
1792 if (ldata
->hshortfall
< 0)
1797 shortfall
= ldata
->hshortfall
;
1798 spaces
= ldata
->nspaces
;
1802 shortfall
= spaces
= extra_indent
= 0;
1805 shortfall
= spaces
= 0;
1806 extra_indent
= ldata
->real_shortfall
;
1810 ret
= render_text(ldata
->page
, ldata
->pdata
, ldata
,
1811 left_x
+ ldata
->xpos
+ extra_indent
,
1812 top_y
- ldata
->ypos
, ldata
->first
, ldata
->end
,
1813 &xr
, shortfall
, spaces
, &nspace
,
1819 * There's a cross-reference continued on to the next line.
1829 static void render_para(para_data
*pdata
, paper_conf
*conf
,
1830 keywordlist
*keywords
, indexdata
*idx
,
1831 paragraph
*index_placeholder
, page_data
*index_page
)
1835 page_data
*cxref_page
;
1844 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
) {
1846 * If this is a contents entry, we expect to have a single
1847 * enormous cross-reference rectangle covering the whole
1848 * thing. (Unless, of course, it spans multiple pages.)
1850 if (pdata
->contents_entry
&& ldata
->page
!= cxref_page
) {
1851 cxref_page
= ldata
->page
;
1852 cxref
= mknew(xref
);
1854 cxref
->dest
.type
= PAGE
;
1855 if (pdata
->contents_entry
== index_placeholder
) {
1856 cxref
->dest
.page
= index_page
;
1858 assert(pdata
->contents_entry
->private_data
);
1859 target
= (para_data
*)pdata
->contents_entry
->private_data
;
1860 cxref
->dest
.page
= target
->first
->page
;
1862 cxref
->dest
.url
= NULL
;
1863 if (ldata
->page
->last_xref
)
1864 ldata
->page
->last_xref
->next
= cxref
;
1866 ldata
->page
->first_xref
= cxref
;
1867 ldata
->page
->last_xref
= cxref
;
1868 cxref
->lx
= conf
->left_margin
;
1869 cxref
->rx
= conf
->paper_width
- conf
->right_margin
;
1870 cxref
->ty
= conf
->paper_height
- conf
->top_margin
1871 - ldata
->ypos
+ ldata
->line_height
;
1873 if (pdata
->contents_entry
) {
1874 assert(cxref
!= NULL
);
1875 cxref
->by
= conf
->paper_height
- conf
->top_margin
1879 last_x
= render_line(ldata
, conf
->left_margin
,
1880 conf
->paper_height
- conf
->top_margin
,
1881 &dest
, keywords
, idx
);
1882 if (ldata
== pdata
->last
)
1887 * If this is a contents entry, add leaders and a page
1890 if (pdata
->contents_entry
) {
1896 if (pdata
->contents_entry
== index_placeholder
) {
1897 num
= index_page
->number
;
1899 assert(pdata
->contents_entry
->private_data
);
1900 target
= (para_data
*)pdata
->contents_entry
->private_data
;
1901 num
= target
->first
->page
->number
;
1905 wid
= paper_width_simple(pdata
, w
);
1908 render_string(pdata
->last
->page
,
1909 pdata
->fonts
[FONT_NORMAL
],
1910 pdata
->sizes
[FONT_NORMAL
],
1911 conf
->paper_width
- conf
->right_margin
- wid
,
1912 (conf
->paper_height
- conf
->top_margin
-
1913 pdata
->last
->ypos
), num
);
1915 for (x
= 0; x
< conf
->base_width
; x
+= conf
->leader_separation
)
1916 if (x
- conf
->leader_separation
> last_x
- conf
->left_margin
&&
1917 x
+ conf
->leader_separation
< conf
->base_width
- wid
)
1918 render_string(pdata
->last
->page
,
1919 pdata
->fonts
[FONT_NORMAL
],
1920 pdata
->sizes
[FONT_NORMAL
],
1921 conf
->left_margin
+ x
,
1922 (conf
->paper_height
- conf
->top_margin
-
1923 pdata
->last
->ypos
), L
".");
1927 * Render any rectangle (chapter title underline or rule)
1928 * that goes with this paragraph.
1930 switch (pdata
->rect_type
) {
1931 case RECT_CHAPTER_UNDERLINE
:
1932 add_rect_to_page(pdata
->last
->page
,
1934 (conf
->paper_height
- conf
->top_margin
-
1936 conf
->chapter_underline_depth
),
1938 conf
->chapter_underline_thickness
);
1941 add_rect_to_page(pdata
->first
->page
,
1942 conf
->left_margin
+ pdata
->first
->xpos
,
1943 (conf
->paper_height
- conf
->top_margin
-
1945 pdata
->last
->line_height
),
1946 conf
->base_width
- pdata
->first
->xpos
,
1947 pdata
->last
->line_height
);
1949 default: /* placate gcc */
1954 static para_data
*code_paragraph(int indent
, word
*words
, paper_conf
*conf
)
1956 para_data
*pdata
= mknew(para_data
);
1959 * For code paragraphs, I'm going to hack grievously and
1960 * pretend the three normal fonts are the three code paragraph
1963 pdata
->fonts
[FONT_NORMAL
] = conf
->cb
;
1964 pdata
->fonts
[FONT_EMPH
] = conf
->co
;
1965 pdata
->fonts
[FONT_CODE
] = conf
->cr
;
1966 pdata
->sizes
[FONT_NORMAL
] =
1967 pdata
->sizes
[FONT_EMPH
] =
1968 pdata
->sizes
[FONT_CODE
] = 12;
1970 pdata
->first
= pdata
->last
= NULL
;
1971 pdata
->outline_level
= -1;
1972 pdata
->rect_type
= RECT_NONE
;
1973 pdata
->contents_entry
= NULL
;
1974 pdata
->justification
= LEFT
;
1976 for (; words
; words
= words
->next
) {
1977 wchar_t *t
, *e
, *start
;
1978 word
*lhead
= NULL
, *ltail
= NULL
, *w
;
1980 int prev
= -1, curr
;
1983 if (words
->next
&& words
->next
->type
== word_Emph
) {
1984 e
= words
->next
->text
;
1985 words
= words
->next
;
1995 else if (*e
== L
'i')
1997 else if (*e
== L
'b')
2014 * We've isolated a maximal subsequence of the line
2015 * which has the same emphasis. Form it into a word
2021 w
->type
= (prev
== 0 ? word_WeakCode
:
2022 prev
== 1 ? word_Emph
: word_Normal
);
2023 w
->text
= mknewa(wchar_t, t
-start
+1);
2024 memcpy(w
->text
, start
, (t
-start
) * sizeof(wchar_t));
2025 w
->text
[t
-start
] = '\0';
2038 ldata
= mknew(line_data
);
2040 ldata
->pdata
= pdata
;
2041 ldata
->first
= lhead
;
2043 ldata
->line_height
= conf
->base_font_size
* 4096;
2045 ldata
->xpos
= indent
;
2048 pdata
->last
->next
= ldata
;
2049 ldata
->prev
= pdata
->last
;
2051 pdata
->first
= ldata
;
2055 pdata
->last
= ldata
;
2057 ldata
->hshortfall
= 0;
2059 ldata
->aux_text
= NULL
;
2060 ldata
->aux_text_2
= NULL
;
2061 ldata
->aux_left_indent
= 0;
2062 /* General opprobrium for breaking in a code paragraph. */
2063 ldata
->penalty_before
= ldata
->penalty_after
= 50000;
2066 standard_line_spacing(pdata
, conf
);
2071 static para_data
*rule_paragraph(int indent
, paper_conf
*conf
)
2073 para_data
*pdata
= mknew(para_data
);
2076 ldata
= mknew(line_data
);
2078 ldata
->pdata
= pdata
;
2079 ldata
->first
= NULL
;
2081 ldata
->line_height
= conf
->rule_thickness
;
2083 ldata
->xpos
= indent
;
2088 ldata
->hshortfall
= 0;
2090 ldata
->aux_text
= NULL
;
2091 ldata
->aux_text_2
= NULL
;
2092 ldata
->aux_left_indent
= 0;
2095 * Better to break after a rule than before it
2097 ldata
->penalty_after
+= 100000;
2098 ldata
->penalty_before
+= -100000;
2100 pdata
->first
= pdata
->last
= ldata
;
2101 pdata
->outline_level
= -1;
2102 pdata
->rect_type
= RECT_RULE
;
2103 pdata
->contents_entry
= NULL
;
2104 pdata
->justification
= LEFT
;
2106 standard_line_spacing(pdata
, conf
);
2112 * Plain-text-like formatting for outline titles.
2114 static void paper_rdaddw(rdstring
*rs
, word
*text
) {
2115 for (; text
; text
= text
->next
) switch (text
->type
) {
2116 case word_HyperLink
:
2118 case word_UpperXref
:
2119 case word_LowerXref
:
2128 case word_WhiteSpace
:
2129 case word_EmphSpace
:
2130 case word_CodeSpace
:
2131 case word_WkCodeSpace
:
2133 case word_EmphQuote
:
2134 case word_CodeQuote
:
2135 case word_WkCodeQuote
:
2136 assert(text
->type
!= word_CodeQuote
&&
2137 text
->type
!= word_WkCodeQuote
);
2138 if (towordstyle(text
->type
) == word_Emph
&&
2139 (attraux(text
->aux
) == attr_First
||
2140 attraux(text
->aux
) == attr_Only
))
2141 rdadd(rs
, L
'_'); /* FIXME: configurability */
2142 else if (towordstyle(text
->type
) == word_Code
&&
2143 (attraux(text
->aux
) == attr_First
||
2144 attraux(text
->aux
) == attr_Only
))
2145 rdadd(rs
, L
'\''); /* FIXME: configurability */
2146 if (removeattr(text
->type
) == word_Normal
) {
2147 rdadds(rs
, text
->text
);
2148 } else if (removeattr(text
->type
) == word_WhiteSpace
) {
2150 } else if (removeattr(text
->type
) == word_Quote
) {
2151 rdadd(rs
, L
'\''); /* fixme: configurability */
2153 if (towordstyle(text
->type
) == word_Emph
&&
2154 (attraux(text
->aux
) == attr_Last
||
2155 attraux(text
->aux
) == attr_Only
))
2156 rdadd(rs
, L
'_'); /* FIXME: configurability */
2157 else if (towordstyle(text
->type
) == word_Code
&&
2158 (attraux(text
->aux
) == attr_Last
||
2159 attraux(text
->aux
) == attr_Only
))
2160 rdadd(rs
, L
'\''); /* FIXME: configurability */
2165 static wchar_t *prepare_outline_title(word
*first
, wchar_t *separator
,
2168 rdstring rs
= {0, 0, NULL
};
2171 paper_rdaddw(&rs
, first
);
2173 rdadds(&rs
, separator
);
2175 paper_rdaddw(&rs
, second
);
2180 static word
*fake_word(wchar_t *text
)
2182 word
*ret
= mknew(word
);
2185 ret
->type
= word_Normal
;
2186 ret
->text
= ustrdup(text
);
2187 ret
->breaks
= FALSE
;
2192 static word
*fake_space_word(void)
2194 word
*ret
= mknew(word
);
2197 ret
->type
= word_WhiteSpace
;
2204 static word
*prepare_contents_title(word
*first
, wchar_t *separator
,
2213 w
= dup_word_list(first
);
2221 w
= fake_word(separator
);
2227 *wptr
= dup_word_list(second
);
2233 static void fold_into_page(page_data
*dest
, page_data
*src
, int right_shift
)
2237 if (!src
->first_line
)
2240 if (dest
->last_line
) {
2241 dest
->last_line
->next
= src
->first_line
;
2242 src
->first_line
->prev
= dest
->last_line
;
2244 dest
->last_line
= src
->last_line
;
2246 for (ldata
= src
->first_line
; ldata
; ldata
= ldata
->next
) {
2248 ldata
->xpos
+= right_shift
;
2250 if (ldata
== src
->last_line
)