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 * * all the measurements in `conf' should be configurable
36 * + notably paper size/shape
37 * * page header and footer should be configurable; we should
38 * be able to shift the page number elsewhere, and add other
39 * things such as the current chapter/section title and fixed
41 * * remove the fixed mapping from heading levels to heading
42 * styles; offer a menu of styles from which the user can
43 * choose at every heading level
44 * * first-line indent in paragraphs
45 * * fixed text: `Contents', `Index', bullet, quotes, the
46 * colon-space and full stop in chapter title constructions
47 * * configurable location of contents?
48 * * certainly configurably _remove_ the contents, and possibly
50 * * double-sided document switch?
51 * + means you have two header/footer formats which
53 * + and means that mandatory page breaks before chapter
54 * titles should include a blank page if necessary to
55 * start the next section to a right-hand page
59 * - ability to import other Type 1 fonts
60 * * we need to parse the font to extract its metrics
61 * * then we pass the font bodily to both PS and PDF so it can
62 * be included in the output file
64 * - character substitution for better typography?
65 * * fi, fl, ffi, ffl ligatures
66 * * use real ellipsis rather than ...
67 * * a hyphen in a word by itself might prefer to be an en-dash
68 * * (Americans might even want a convenient way to use an
70 * * DON'T DO ANY OF THE ABOVE WITHIN \c OR \cw!
71 * * substituting `minus' for `hyphen' in the standard encoding
72 * is probably preferable in Courier, though certainly not in
74 * * if I do do this lot, I'm rather inclined to at least try
75 * to think up a configurable way to do it so that Americans
76 * can do em-dash tricks without my intervention and other
77 * people can do other odd things too.
86 typedef struct paper_conf_Tag paper_conf
;
87 typedef struct paper_idx_Tag paper_idx
;
89 struct paper_conf_Tag
{
96 int indent_list_bullet
;
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 /* These are derived from the above */
119 /* Fonts used in the configuration */
120 font_data
*tr
, *ti
, *hr
, *hi
, *cr
, *co
, *cb
;
123 struct paper_idx_Tag
{
125 * Word list giving the page numbers on which this index entry
126 * appears. Also the last word in the list, for ease of
132 * The last page added to the list (so we can ensure we don't
139 word_PageXref
= word_NotWordType
+ 1
142 static font_data
*make_std_font(font_list
*fontlist
, char const *name
);
143 static void wrap_paragraph(para_data
*pdata
, word
*words
,
144 int w
, int i1
, int i2
);
145 static page_data
*page_breaks(line_data
*first
, line_data
*last
,
146 int page_height
, int ncols
, int headspace
);
147 static int render_string(page_data
*page
, font_data
*font
, int fontsize
,
148 int x
, int y
, wchar_t *str
);
149 static int render_line(line_data
*ldata
, int left_x
, int top_y
,
150 xref_dest
*dest
, keywordlist
*keywords
, indexdata
*idx
);
151 static void render_para(para_data
*pdata
, paper_conf
*conf
,
152 keywordlist
*keywords
, indexdata
*idx
,
153 paragraph
*index_placeholder
, page_data
*index_page
);
154 static int string_width(font_data
*font
, wchar_t const *string
, int *errs
);
155 static int paper_width_simple(para_data
*pdata
, word
*text
);
156 static para_data
*code_paragraph(int indent
, word
*words
, paper_conf
*conf
);
157 static para_data
*rule_paragraph(int indent
, paper_conf
*conf
);
158 static void add_rect_to_page(page_data
*page
, int x
, int y
, int w
, int h
);
159 static para_data
*make_para_data(int ptype
, int paux
, int indent
, int rmargin
,
160 word
*pkwtext
, word
*pkwtext2
, word
*pwords
,
162 static void standard_line_spacing(para_data
*pdata
, paper_conf
*conf
);
163 static wchar_t *prepare_outline_title(word
*first
, wchar_t *separator
,
165 static word
*fake_word(wchar_t *text
);
166 static word
*fake_space_word(void);
167 static word
*fake_page_ref(page_data
*page
);
168 static word
*fake_end_ref(void);
169 static word
*prepare_contents_title(word
*first
, wchar_t *separator
,
171 static void fold_into_page(page_data
*dest
, page_data
*src
, int right_shift
);
173 void *paper_pre_backend(paragraph
*sourceform
, keywordlist
*keywords
,
177 int indent
, used_contents
;
178 para_data
*pdata
, *firstpara
= NULL
, *lastpara
= NULL
;
179 para_data
*firstcont
, *lastcont
;
180 line_data
*firstline
, *lastline
, *firstcontline
, *lastcontline
;
186 paragraph index_placeholder_para
;
187 page_data
*first_index_page
;
190 * FIXME: All these things ought to become configurable.
192 conf
= mknew(paper_conf
);
193 conf
->paper_width
= 595 * 4096;
194 conf
->paper_height
= 841 * 4096;
195 conf
->left_margin
= 72 * 4096;
196 conf
->top_margin
= 72 * 4096;
197 conf
->right_margin
= 72 * 4096;
198 conf
->bottom_margin
= 108 * 4096;
199 conf
->indent_list_bullet
= 6 * 4096;
200 conf
->indent_list
= 24 * 4096;
201 conf
->indent_quote
= 18 * 4096;
202 conf
->base_leading
= 4096;
203 conf
->base_para_spacing
= 10 * 4096;
204 conf
->chapter_top_space
= 72 * 4096;
205 conf
->sect_num_left_space
= 12 * 4096;
206 conf
->chapter_underline_depth
= 14 * 4096;
207 conf
->chapter_underline_thickness
= 3 * 4096;
208 conf
->rule_thickness
= 1 * 4096;
209 conf
->base_font_size
= 12;
210 conf
->contents_indent_step
= 24 * 4096;
211 conf
->contents_margin
= 84 * 4096;
212 conf
->leader_separation
= 12 * 4096;
213 conf
->index_gutter
= 36 * 4096;
214 conf
->index_cols
= 2;
215 conf
->index_minsep
= 18 * 4096;
216 conf
->pagenum_fontsize
= 12;
217 conf
->footer_distance
= 32 * 4096;
220 conf
->paper_width
- conf
->left_margin
- conf
->right_margin
;
222 conf
->paper_height
- conf
->top_margin
- conf
->bottom_margin
;
223 conf
->index_colwidth
=
224 (conf
->base_width
- (conf
->index_cols
-1) * conf
->index_gutter
)
228 * First, set up some font structures.
230 fontlist
= mknew(font_list
);
231 fontlist
->head
= fontlist
->tail
= NULL
;
232 conf
->tr
= make_std_font(fontlist
, "Times-Roman");
233 conf
->ti
= make_std_font(fontlist
, "Times-Italic");
234 conf
->hr
= make_std_font(fontlist
, "Helvetica-Bold");
235 conf
->hi
= make_std_font(fontlist
, "Helvetica-BoldOblique");
236 conf
->cr
= make_std_font(fontlist
, "Courier");
237 conf
->co
= make_std_font(fontlist
, "Courier-Oblique");
238 conf
->cb
= make_std_font(fontlist
, "Courier-Bold");
241 * Set up a data structure to collect page numbers for each
250 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
251 paper_idx
*pi
= mknew(paper_idx
);
255 pi
->words
= pi
->lastword
= NULL
;
258 entry
->backend_data
= pi
;
263 * Format the contents entry for each heading.
266 word
*contents_title
;
267 contents_title
= fake_word(L
"Contents");
269 firstcont
= make_para_data(para_UnnumberedChapter
, 0, 0, 0,
270 NULL
, NULL
, contents_title
, conf
);
271 lastcont
= firstcont
;
272 lastcont
->next
= NULL
;
273 firstcontline
= firstcont
->first
;
274 lastcontline
= lastcont
->last
;
275 for (p
= sourceform
; p
; p
= p
->next
) {
282 case para_UnnumberedChapter
:
288 words
= prepare_contents_title(p
->kwtext
, L
": ", p
->words
);
291 case para_UnnumberedChapter
:
292 words
= prepare_contents_title(NULL
, NULL
, p
->words
);
297 words
= prepare_contents_title(p
->kwtext2
, L
" ", p
->words
);
298 indent
= (p
->aux
+ 1) * conf
->contents_indent_step
;
301 pdata
= make_para_data(para_Normal
, p
->aux
, indent
,
302 conf
->contents_margin
,
303 NULL
, NULL
, words
, conf
);
305 pdata
->contents_entry
= p
;
306 lastcont
->next
= pdata
;
310 * Link all contents line structures together into
315 lastcontline
->next
= pdata
->first
;
316 pdata
->first
->prev
= lastcontline
;
318 firstcontline
= pdata
->first
;
319 pdata
->first
->prev
= NULL
;
321 lastcontline
= pdata
->last
;
322 lastcontline
->next
= NULL
;
330 * And one extra one, for the index.
333 pdata
= make_para_data(para_Normal
, 0, 0,
334 conf
->contents_margin
,
335 NULL
, NULL
, fake_word(L
"Index"), conf
);
337 pdata
->contents_entry
= &index_placeholder_para
;
338 lastcont
->next
= pdata
;
343 lastcontline
->next
= pdata
->first
;
344 pdata
->first
->prev
= lastcontline
;
346 firstcontline
= pdata
->first
;
347 pdata
->first
->prev
= NULL
;
349 lastcontline
= pdata
->last
;
350 lastcontline
->next
= NULL
;
356 * Do the main paragraph formatting.
359 used_contents
= FALSE
;
360 firstline
= lastline
= NULL
;
361 for (p
= sourceform
; p
; p
= p
->next
) {
362 p
->private_data
= NULL
;
366 * These paragraph types are either invisible or don't
367 * define text in the normal sense. Either way, they
368 * don't require wrapping.
373 case para_NotParaType
:
380 * These paragraph types don't require wrapping, but
381 * they do affect the line width to which we wrap the
382 * rest of the paragraphs, so we need to pay attention.
385 indent
+= conf
->indent_list
; break;
387 indent
-= conf
->indent_list
; assert(indent
>= 0); break;
389 indent
+= conf
->indent_quote
; break;
391 indent
-= conf
->indent_quote
; assert(indent
>= 0); break;
394 * This paragraph type is special. Process it
398 pdata
= code_paragraph(indent
, p
->words
, conf
);
399 p
->private_data
= pdata
;
400 if (pdata
->first
!= pdata
->last
) {
401 pdata
->first
->penalty_after
+= 100000;
402 pdata
->last
->penalty_before
+= 100000;
407 * This paragraph is also special.
410 pdata
= rule_paragraph(indent
, conf
);
411 p
->private_data
= pdata
;
415 * All of these paragraph types require wrapping in the
416 * ordinary way. So we must supply a set of fonts, a
417 * line width and auxiliary information (e.g. bullet
418 * text) for each one.
422 case para_UnnumberedChapter
:
426 case para_BiblioCited
:
428 case para_NumberedList
:
429 case para_DescribedThing
:
430 case para_Description
:
433 pdata
= make_para_data(p
->type
, p
->aux
, indent
, 0,
434 p
->kwtext
, p
->kwtext2
, p
->words
, conf
);
436 p
->private_data
= pdata
;
441 if (p
->private_data
) {
442 pdata
= (para_data
*)p
->private_data
;
445 * If this is the first non-title heading, we link the
446 * contents section in before it.
448 if (!used_contents
&& pdata
->outline_level
> 0) {
449 used_contents
= TRUE
;
451 lastpara
->next
= firstcont
;
453 firstpara
= firstcont
;
455 assert(lastpara
->next
== NULL
);
458 lastline
->next
= firstcontline
;
459 firstcontline
->prev
= lastline
;
461 firstline
= firstcontline
;
462 firstcontline
->prev
= NULL
;
464 assert(lastcontline
!= NULL
);
465 lastline
= lastcontline
;
466 lastline
->next
= NULL
;
470 * Link all line structures together into a big list.
474 lastline
->next
= pdata
->first
;
475 pdata
->first
->prev
= lastline
;
477 firstline
= pdata
->first
;
478 pdata
->first
->prev
= NULL
;
480 lastline
= pdata
->last
;
481 lastline
->next
= NULL
;
485 * Link all paragraph structures together similarly.
489 lastpara
->next
= pdata
;
497 * Now we have an enormous linked list of every line of text in
498 * the document. Break it up into pages.
500 pages
= page_breaks(firstline
, lastline
, conf
->page_height
, 0, 0);
511 for (page
= pages
; page
; page
= page
->next
) {
512 sprintf(buf
, "%d", ++pagenum
);
513 page
->number
= ufroma_dup(buf
, CS_ASCII
);
517 first_index_page
= mknew(page_data
);
518 first_index_page
->next
= first_index_page
->prev
= NULL
;
519 first_index_page
->first_line
= NULL
;
520 first_index_page
->last_line
= NULL
;
521 first_index_page
->first_text
= first_index_page
->last_text
= NULL
;
522 first_index_page
->first_xref
= first_index_page
->last_xref
= NULL
;
523 first_index_page
->first_rect
= first_index_page
->last_rect
= NULL
;
525 /* And don't forget the as-yet-uncreated index. */
526 sprintf(buf
, "%d", ++pagenum
);
527 first_index_page
->number
= ufroma_dup(buf
, CS_ASCII
);
532 * Now we're ready to actually lay out the pages. We do this by
533 * looping over _paragraphs_, since we may need to track cross-
534 * references between lines and even across pages.
536 for (pdata
= firstpara
; pdata
; pdata
= pdata
->next
)
537 render_para(pdata
, conf
, keywords
, idx
,
538 &index_placeholder_para
, first_index_page
);
541 * Now we've laid out the main body pages, we should have
542 * acquired a full set of page numbers for the index.
548 para_data
*firstidx
, *lastidx
;
549 line_data
*firstidxline
, *lastidxline
, *ldata
;
550 page_data
*ipages
, *ipages2
, *page
;
553 * Create a set of paragraphs for the index.
555 index_title
= fake_word(L
"Index");
557 firstidx
= make_para_data(para_UnnumberedChapter
, 0, 0, 0,
558 NULL
, NULL
, index_title
, conf
);
560 lastidx
->next
= NULL
;
561 firstidxline
= firstidx
->first
;
562 lastidxline
= lastidx
->last
;
563 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
564 paper_idx
*pi
= (paper_idx
*)entry
->backend_data
;
565 para_data
*text
, *pages
;
570 text
= make_para_data(para_Normal
, 0, 0,
571 conf
->base_width
- conf
->index_colwidth
,
572 NULL
, NULL
, entry
->text
, conf
);
574 pages
= make_para_data(para_Normal
, 0, 0,
575 conf
->base_width
- conf
->index_colwidth
,
576 NULL
, NULL
, pi
->words
, conf
);
578 text
->justification
= LEFT
;
579 pages
->justification
= RIGHT
;
580 text
->last
->space_after
= pages
->first
->space_before
=
581 conf
->base_leading
/ 2;
583 pages
->last
->space_after
= text
->first
->space_before
=
587 assert(pages
->first
);
592 * If feasible, fold the two halves of the index entry
595 if (text
->last
->real_shortfall
+ pages
->first
->real_shortfall
>
596 conf
->index_colwidth
+ conf
->index_minsep
) {
597 text
->last
->space_after
= -1;
598 pages
->first
->space_before
= -pages
->first
->line_height
+1;
601 lastidx
->next
= text
;
607 * Link all index line structures together into
610 text
->last
->next
= pages
->first
;
611 pages
->first
->prev
= text
->last
;
613 lastidxline
->next
= text
->first
;
614 text
->first
->prev
= lastidxline
;
616 lastidxline
= pages
->last
;
619 * Breaking an index entry anywhere is so bad that I
620 * think I'm going to forbid it totally.
622 for (ldata
= text
->first
; ldata
&& ldata
->next
;
623 ldata
= ldata
->next
) {
624 ldata
->next
->space_before
+= ldata
->space_after
+ 1;
625 ldata
->space_after
= -1;
630 * Now break the index into pages.
632 ipages
= page_breaks(firstidxline
, firstidxline
, conf
->page_height
,
634 ipages2
= page_breaks(firstidxline
->next
, lastidxline
,
637 firstidxline
->space_before
+
638 firstidxline
->line_height
+
639 firstidxline
->space_after
);
642 * This will have put each _column_ of the index on a
643 * separate page, which isn't what we want. Fold the pages
650 for (i
= 1; i
< conf
->index_cols
; i
++)
654 fold_into_page(page
, page
->next
,
655 i
* (conf
->index_colwidth
+
656 conf
->index_gutter
));
658 page
->next
= page
->next
->next
;
660 page
->next
->prev
= page
;
666 /* Also fold the heading on to the same page as the index items. */
667 fold_into_page(ipages
, ipages2
, 0);
668 ipages
->next
= ipages2
->next
;
670 ipages
->next
->prev
= ipages
;
672 fold_into_page(first_index_page
, ipages
, 0);
673 first_index_page
->next
= ipages
->next
;
674 if (first_index_page
->next
)
675 first_index_page
->next
->prev
= first_index_page
;
677 ipages
= first_index_page
;
680 * Number the index pages, except the already-numbered
683 for (page
= ipages
->next
; page
; page
= page
->next
) {
685 sprintf(buf
, "%d", ++pagenum
);
686 page
->number
= ufroma_dup(buf
, CS_ASCII
);
690 * Render the index pages.
692 for (pdata
= firstidx
; pdata
; pdata
= pdata
->next
)
693 render_para(pdata
, conf
, keywords
, idx
,
694 &index_placeholder_para
, first_index_page
);
697 * Link the index page list on to the end of the main page
703 for (page
= pages
; page
->next
; page
= page
->next
);
708 * Same with the paragraph list, which will cause the index
709 * to be mentioned in the document outline.
712 firstpara
= firstidx
;
714 lastpara
->next
= firstidx
;
719 * Draw the headers and footers.
721 * FIXME: this should be fully configurable, but for the moment
722 * I'm just going to put in page numbers in the centre of a
723 * footer and leave it at that.
728 for (page
= pages
; page
; page
= page
->next
) {
731 width
= conf
->pagenum_fontsize
*
732 string_width(conf
->tr
, page
->number
, NULL
);
734 render_string(page
, conf
->tr
, conf
->pagenum_fontsize
,
735 conf
->left_margin
+ (conf
->base_width
- width
)/2,
736 conf
->bottom_margin
- conf
->footer_distance
,
742 * Start putting together the overall document structure we're
745 doc
= mknew(document
);
746 doc
->fonts
= fontlist
;
748 doc
->paper_width
= conf
->paper_width
;
749 doc
->paper_height
= conf
->paper_height
;
752 * Collect the section heading paragraphs into a document
753 * outline. This is slightly fiddly because the Title paragraph
754 * isn't required to be at the start, although all the others
760 doc
->outline_elements
= mknewa(outline_element
, osize
);
761 doc
->n_outline_elements
= 0;
763 /* First find the title. */
764 for (pdata
= firstpara
; pdata
; pdata
= pdata
->next
) {
765 if (pdata
->outline_level
== 0) {
766 doc
->outline_elements
[0].level
= 0;
767 doc
->outline_elements
[0].pdata
= pdata
;
768 doc
->n_outline_elements
++;
773 /* Then collect the rest. */
774 for (pdata
= firstpara
; pdata
; pdata
= pdata
->next
) {
775 if (pdata
->outline_level
> 0) {
776 if (doc
->n_outline_elements
>= osize
) {
778 doc
->outline_elements
=
779 resize(doc
->outline_elements
, osize
);
782 doc
->outline_elements
[doc
->n_outline_elements
].level
=
783 pdata
->outline_level
;
784 doc
->outline_elements
[doc
->n_outline_elements
].pdata
= pdata
;
785 doc
->n_outline_elements
++;
795 static para_data
*make_para_data(int ptype
, int paux
, int indent
, int rmargin
,
796 word
*pkwtext
, word
*pkwtext2
, word
*pwords
,
801 int extra_indent
, firstline_indent
, aux_indent
;
804 pdata
= mknew(para_data
);
805 pdata
->outline_level
= -1;
806 pdata
->outline_title
= NULL
;
807 pdata
->rect_type
= RECT_NONE
;
808 pdata
->contents_entry
= NULL
;
809 pdata
->justification
= JUST
;
812 * Choose fonts for this paragraph.
814 * FIXME: All of this ought to be completely
819 pdata
->fonts
[FONT_NORMAL
] = conf
->hr
;
820 pdata
->sizes
[FONT_NORMAL
] = 24;
821 pdata
->fonts
[FONT_EMPH
] = conf
->hi
;
822 pdata
->sizes
[FONT_EMPH
] = 24;
823 pdata
->fonts
[FONT_CODE
] = conf
->cb
;
824 pdata
->sizes
[FONT_CODE
] = 24;
825 pdata
->outline_level
= 0;
830 case para_UnnumberedChapter
:
831 pdata
->fonts
[FONT_NORMAL
] = conf
->hr
;
832 pdata
->sizes
[FONT_NORMAL
] = 20;
833 pdata
->fonts
[FONT_EMPH
] = conf
->hi
;
834 pdata
->sizes
[FONT_EMPH
] = 20;
835 pdata
->fonts
[FONT_CODE
] = conf
->cb
;
836 pdata
->sizes
[FONT_CODE
] = 20;
837 pdata
->outline_level
= 1;
842 pdata
->fonts
[FONT_NORMAL
] = conf
->hr
;
843 pdata
->fonts
[FONT_EMPH
] = conf
->hi
;
844 pdata
->fonts
[FONT_CODE
] = conf
->cb
;
845 pdata
->sizes
[FONT_NORMAL
] =
846 pdata
->sizes
[FONT_EMPH
] =
847 pdata
->sizes
[FONT_CODE
] =
848 (paux
== 0 ?
16 : paux
== 1 ?
14 : 13);
849 pdata
->outline_level
= 2 + paux
;
853 case para_BiblioCited
:
855 case para_NumberedList
:
856 case para_DescribedThing
:
857 case para_Description
:
859 pdata
->fonts
[FONT_NORMAL
] = conf
->tr
;
860 pdata
->sizes
[FONT_NORMAL
] = 12;
861 pdata
->fonts
[FONT_EMPH
] = conf
->ti
;
862 pdata
->sizes
[FONT_EMPH
] = 12;
863 pdata
->fonts
[FONT_CODE
] = conf
->cr
;
864 pdata
->sizes
[FONT_CODE
] = 12;
869 * Also select an indentation level depending on the
870 * paragraph type (list paragraphs other than
871 * para_DescribedThing need extra indent).
873 * (FIXME: Perhaps at some point we might even arrange
874 * for the user to be able to request indented first
875 * lines in paragraphs.)
877 if (ptype
== para_Bullet
||
878 ptype
== para_NumberedList
||
879 ptype
== para_Description
) {
880 extra_indent
= firstline_indent
= conf
->indent_list
;
882 extra_indent
= firstline_indent
= 0;
886 * Find the auxiliary text for this paragraph.
897 * For some heading styles (FIXME: be able to
898 * configure which), the auxiliary text contains
899 * the chapter number and is arranged to be
900 * right-aligned a few points left of the primary
901 * margin. For other styles, the auxiliary text is
902 * the full chapter _name_ and takes up space
903 * within the (wrapped) chapter title, meaning that
904 * we must move the first line indent over to make
907 if (ptype
== para_Heading
|| ptype
== para_Subsect
) {
911 len
= paper_width_simple(pdata
, pkwtext2
);
912 aux_indent
= -len
- conf
->sect_num_left_space
;
914 pdata
->outline_title
=
915 prepare_outline_title(pkwtext2
, L
" ", pwords
);
918 aux2
= fake_word(L
": ");
921 firstline_indent
+= paper_width_simple(pdata
, aux
);
922 firstline_indent
+= paper_width_simple(pdata
, aux2
);
924 pdata
->outline_title
=
925 prepare_outline_title(pkwtext
, L
": ", pwords
);
931 * Auxiliary text consisting of a bullet. (FIXME:
932 * configurable bullet.)
934 aux
= fake_word(L
"\x2022");
935 aux_indent
= indent
+ conf
->indent_list_bullet
;
938 case para_NumberedList
:
940 * Auxiliary text consisting of the number followed
941 * by a (FIXME: configurable) full stop.
944 aux2
= fake_word(L
".");
945 aux_indent
= indent
+ conf
->indent_list_bullet
;
948 case para_BiblioCited
:
950 * Auxiliary text consisting of the bibliography
951 * reference text, and a trailing space.
954 aux2
= fake_word(L
" ");
956 firstline_indent
+= paper_width_simple(pdata
, aux
);
957 firstline_indent
+= paper_width_simple(pdata
, aux2
);
961 if (pdata
->outline_level
>= 0 && !pdata
->outline_title
) {
962 pdata
->outline_title
=
963 prepare_outline_title(NULL
, NULL
, pwords
);
966 wrap_paragraph(pdata
, pwords
, conf
->base_width
- rmargin
,
967 indent
+ firstline_indent
,
968 indent
+ extra_indent
);
970 pdata
->first
->aux_text
= aux
;
971 pdata
->first
->aux_text_2
= aux2
;
972 pdata
->first
->aux_left_indent
= aux_indent
;
975 * Line breaking penalties.
982 case para_UnnumberedChapter
:
984 * Fixed and large penalty for breaking straight
985 * after a heading; corresponding bonus for
986 * breaking straight before.
988 pdata
->first
->penalty_before
= -500000;
989 pdata
->last
->penalty_after
= 500000;
990 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
)
991 ldata
->penalty_after
= 500000;
994 case para_DescribedThing
:
996 * This is treated a bit like a small heading:
997 * there's a penalty for breaking after it (i.e.
998 * between it and its description), and a bonus for
999 * breaking before it (actually _between_ list
1002 pdata
->first
->penalty_before
= -200000;
1003 pdata
->last
->penalty_after
= 200000;
1008 * Most paragraph types: widow/orphan control by
1009 * discouraging breaking one line from the end of
1012 if (pdata
->first
!= pdata
->last
) {
1013 pdata
->first
->penalty_after
= 100000;
1014 pdata
->last
->penalty_before
= 100000;
1019 standard_line_spacing(pdata
, conf
);
1022 * Some kinds of section heading require a page break before
1023 * them and an underline after.
1025 if (ptype
== para_Title
||
1026 ptype
== para_Chapter
||
1027 ptype
== para_Appendix
||
1028 ptype
== para_UnnumberedChapter
) {
1029 pdata
->first
->page_break
= TRUE
;
1030 pdata
->first
->space_before
= conf
->chapter_top_space
;
1031 pdata
->last
->space_after
+=
1032 (conf
->chapter_underline_depth
+
1033 conf
->chapter_underline_thickness
);
1034 pdata
->rect_type
= RECT_CHAPTER_UNDERLINE
;
1040 static void standard_line_spacing(para_data
*pdata
, paper_conf
*conf
)
1045 * Set the line spacing for each line in this paragraph.
1047 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
) {
1048 if (ldata
== pdata
->first
)
1049 ldata
->space_before
= conf
->base_para_spacing
/ 2;
1051 ldata
->space_before
= conf
->base_leading
/ 2;
1052 if (ldata
== pdata
->last
)
1053 ldata
->space_after
= conf
->base_para_spacing
/ 2;
1055 ldata
->space_after
= conf
->base_leading
/ 2;
1056 ldata
->page_break
= FALSE
;
1060 static font_encoding
*new_font_encoding(font_data
*font
)
1065 fe
= mknew(font_encoding
);
1068 if (font
->list
->tail
)
1069 font
->list
->tail
->next
= fe
;
1071 font
->list
->head
= fe
;
1072 font
->list
->tail
= fe
;
1075 fe
->free_pos
= 0x21;
1077 for (i
= 0; i
< 256; i
++) {
1078 fe
->vector
[i
] = NULL
;
1079 fe
->indices
[i
] = -1;
1080 fe
->to_unicode
[i
] = 0xFFFF;
1086 static font_data
*make_std_font(font_list
*fontlist
, char const *name
)
1094 widths
= ps_std_font_widths(name
);
1098 for (nglyphs
= 0; ps_std_glyphs
[nglyphs
] != NULL
; nglyphs
++);
1100 f
= mknew(font_data
);
1104 f
->nglyphs
= nglyphs
;
1105 f
->glyphs
= ps_std_glyphs
;
1107 f
->subfont_map
= mknewa(subfont_map_entry
, nglyphs
);
1110 * Our first subfont will contain all of US-ASCII. This isn't
1111 * really necessary - we could just create custom subfonts
1112 * precisely as the whim of render_string dictated - but
1113 * instinct suggests that it might be nice to have the text in
1114 * the output files look _marginally_ recognisable.
1116 fe
= new_font_encoding(f
);
1117 fe
->free_pos
= 0xA1; /* only the top half is free */
1118 f
->latest_subfont
= fe
;
1120 for (i
= 0; i
< (int)lenof(f
->bmp
); i
++)
1123 for (i
= 0; i
< nglyphs
; i
++) {
1125 ucs
= ps_glyph_to_unicode(f
->glyphs
[i
]);
1126 assert(ucs
!= 0xFFFF);
1128 if (ucs
>= 0x20 && ucs
<= 0x7E) {
1129 fe
->vector
[ucs
] = f
->glyphs
[i
];
1130 fe
->indices
[ucs
] = i
;
1131 fe
->to_unicode
[ucs
] = ucs
;
1132 f
->subfont_map
[i
].subfont
= fe
;
1133 f
->subfont_map
[i
].position
= ucs
;
1136 * This character is not yet assigned to a subfont.
1138 f
->subfont_map
[i
].subfont
= NULL
;
1139 f
->subfont_map
[i
].position
= 0;
1146 static int string_width(font_data
*font
, wchar_t const *string
, int *errs
)
1153 for (; *string
; string
++) {
1156 index
= font
->bmp
[(unsigned short)*string
];
1157 if (index
== 0xFFFF) {
1161 width
+= font
->widths
[index
];
1168 static int paper_width_internal(void *vctx
, word
*word
, int *nspaces
);
1170 struct paper_width_ctx
{
1175 static int paper_width_list(void *vctx
, word
*text
, word
*end
, int *nspaces
) {
1177 while (text
&& text
!= end
) {
1178 w
+= paper_width_internal(vctx
, text
, nspaces
);
1184 static int paper_width_internal(void *vctx
, word
*word
, int *nspaces
)
1186 struct paper_width_ctx
*ctx
= (struct paper_width_ctx
*)vctx
;
1187 int style
, type
, findex
, width
, errs
;
1190 switch (word
->type
) {
1191 case word_HyperLink
:
1193 case word_UpperXref
:
1194 case word_LowerXref
:
1201 style
= towordstyle(word
->type
);
1202 type
= removeattr(word
->type
);
1204 findex
= (style
== word_Normal ? FONT_NORMAL
:
1205 style
== word_Emph ? FONT_EMPH
:
1208 if (type
== word_Normal
) {
1210 } else if (type
== word_WhiteSpace
) {
1211 if (findex
!= FONT_CODE
) {
1214 return ctx
->minspacewidth
;
1217 } else /* if (type == word_Quote) */ {
1218 if (word
->aux
== quote_Open
)
1219 str
= L
"\x2018"; /* FIXME: configurability! */
1221 str
= L
"\x2019"; /* FIXME: configurability! */
1224 width
= string_width(ctx
->pdata
->fonts
[findex
], str
, &errs
);
1226 if (errs
&& word
->alt
)
1227 return paper_width_list(vctx
, word
->alt
, NULL
, nspaces
);
1229 return ctx
->pdata
->sizes
[findex
] * width
;
1232 static int paper_width(void *vctx
, word
*word
)
1234 return paper_width_internal(vctx
, word
, NULL
);
1237 static int paper_width_simple(para_data
*pdata
, word
*text
)
1239 struct paper_width_ctx ctx
;
1243 (pdata
->sizes
[FONT_NORMAL
] *
1244 string_width(pdata
->fonts
[FONT_NORMAL
], L
" ", NULL
));
1246 return paper_width_list(&ctx
, text
, NULL
, NULL
);
1249 static void wrap_paragraph(para_data
*pdata
, word
*words
,
1250 int w
, int i1
, int i2
)
1252 wrappedline
*wrapping
, *p
;
1254 struct paper_width_ctx ctx
;
1258 * We're going to need to store the line height in every line
1259 * structure we generate.
1264 for (i
= 0; i
< NFONTS
; i
++)
1265 if (line_height
< pdata
->sizes
[i
])
1266 line_height
= pdata
->sizes
[i
];
1267 line_height
*= 4096;
1270 spacewidth
= (pdata
->sizes
[FONT_NORMAL
] *
1271 string_width(pdata
->fonts
[FONT_NORMAL
], L
" ", NULL
));
1272 if (spacewidth
== 0) {
1274 * A font without a space?! Disturbing. I hope this never
1275 * comes up, but I'll make a random guess anyway and set my
1276 * space width to half the point size.
1278 spacewidth
= pdata
->sizes
[FONT_NORMAL
] * 4096 / 2;
1282 * I'm going to set the _minimum_ space width to 3/5 of the
1283 * standard one, and use the standard one as the optimum.
1285 ctx
.minspacewidth
= spacewidth
* 3 / 5;
1288 wrapping
= wrap_para(words
, w
- i1
, w
- i2
, paper_width
, &ctx
, spacewidth
);
1291 * Having done the wrapping, we now concoct a set of line_data
1294 pdata
->first
= pdata
->last
= NULL
;
1296 for (p
= wrapping
; p
; p
= p
->next
) {
1299 int len
, wid
, spaces
;
1301 ldata
= mknew(line_data
);
1303 ldata
->pdata
= pdata
;
1304 ldata
->first
= p
->begin
;
1305 ldata
->end
= p
->end
;
1306 ldata
->line_height
= line_height
;
1308 ldata
->xpos
= (p
== wrapping ? i1
: i2
);
1311 pdata
->last
->next
= ldata
;
1312 ldata
->prev
= pdata
->last
;
1314 pdata
->first
= ldata
;
1318 pdata
->last
= ldata
;
1321 len
= paper_width_list(&ctx
, ldata
->first
, ldata
->end
, &spaces
);
1322 wid
= (p
== wrapping ? w
- i1
: w
- i2
);
1325 ldata
->hshortfall
= wid
- len
;
1326 ldata
->nspaces
= spaces
;
1328 * This tells us how much the space width needs to
1329 * change from _min_spacewidth. But we want to store
1330 * its difference from the _natural_ space width, to
1331 * make the text rendering easier.
1333 ldata
->hshortfall
+= ctx
.minspacewidth
* spaces
;
1334 ldata
->hshortfall
-= spacewidth
* spaces
;
1335 ldata
->real_shortfall
= ldata
->hshortfall
;
1337 * Special case: on the last line of a paragraph, we
1338 * never stretch spaces.
1340 if (ldata
->hshortfall
> 0 && !p
->next
)
1341 ldata
->hshortfall
= 0;
1343 ldata
->aux_text
= NULL
;
1344 ldata
->aux_text_2
= NULL
;
1345 ldata
->aux_left_indent
= 0;
1346 ldata
->penalty_before
= ldata
->penalty_after
= 0;
1351 static page_data
*page_breaks(line_data
*first
, line_data
*last
,
1352 int page_height
, int ncols
, int headspace
)
1356 int n
, n1
, this_height
;
1359 * Page breaking is done by a close analogue of the optimal
1360 * paragraph wrapping algorithm used by wrap_para(). We work
1361 * backwards from the end of the document line by line; for
1362 * each line, we contemplate every possible number of lines we
1363 * could put on a page starting with that line, determine a
1364 * cost function for each one, add it to the pre-computed cost
1365 * function for optimally page-breaking everything after that
1366 * page, and pick the best option.
1368 * This is made slightly more complex by the fact that we have
1369 * a multi-column index with a heading at the top of the
1370 * _first_ page, meaning that the first _ncols_ pages must have
1371 * a different length. Hence, we must do the wrapping ncols+1
1372 * times over, hypothetically trying to put every subsequence
1373 * on every possible page.
1375 * Since my line_data structures are only used for this
1376 * purpose, I might as well just store the algorithm data
1380 for (l
= last
; l
; l
= l
->prev
) {
1381 l
->bestcost
= mknewa(int, ncols
+1);
1382 l
->vshortfall
= mknewa(int, ncols
+1);
1383 l
->text
= mknewa(int, ncols
+1);
1384 l
->space
= mknewa(int, ncols
+1);
1385 l
->page_last
= mknewa(line_data
*, ncols
+1);
1387 for (n
= 0; n
<= ncols
; n
++) {
1388 int minheight
, text
= 0, space
= 0;
1391 n1
= (n
< ncols ? n
+1 : ncols
);
1393 this_height
= page_height
- headspace
;
1395 this_height
= page_height
;
1397 l
->bestcost
[n
] = -1;
1398 for (m
= l
; m
; m
= m
->next
) {
1399 if (m
!= l
&& m
->page_break
)
1400 break; /* we've gone as far as we can */
1403 if (m
->prev
->space_after
> 0)
1404 space
+= m
->prev
->space_after
;
1406 text
+= m
->prev
->space_after
;
1408 if (m
!= l
|| m
->page_break
) {
1409 if (m
->space_before
> 0)
1410 space
+= m
->space_before
;
1412 text
+= m
->space_before
;
1414 text
+= m
->line_height
;
1415 minheight
= text
+ space
;
1417 if (m
!= l
&& minheight
> this_height
)
1421 * If the space after this paragraph is _negative_
1422 * (which means the next line is folded on to this
1423 * one, which happens in the index), we absolutely
1424 * cannot break here.
1426 if (m
->space_after
>= 0) {
1429 * Compute the cost of this arrangement, as the
1430 * square of the amount of wasted space on the
1431 * page. Exception: if this is the last page
1432 * before a mandatory break or the document
1433 * end, we don't penalise a large blank area.
1435 if (m
!= last
&& m
->next
&& !m
->next
->page_break
)
1437 int x
= this_height
- minheight
;
1444 cost
+= (x
* xf
) >> 8;
1448 if (m
!= last
&& m
->next
&& !m
->next
->page_break
) {
1449 cost
+= m
->penalty_after
;
1450 cost
+= m
->next
->penalty_before
;
1453 if (m
!= last
&& m
->next
&& !m
->next
->page_break
)
1454 cost
+= m
->next
->bestcost
[n1
];
1455 if (l
->bestcost
[n
] == -1 || l
->bestcost
[n
] > cost
) {
1457 * This is the best option yet for this
1460 l
->bestcost
[n
] = cost
;
1461 if (m
!= last
&& m
->next
&& !m
->next
->page_break
)
1462 l
->vshortfall
[n
] = this_height
- minheight
;
1464 l
->vshortfall
[n
] = 0;
1466 l
->space
[n
] = space
;
1467 l
->page_last
[n
] = m
;
1478 * Now go through the line list forwards and assemble the
1487 int text
, space
, head
;
1489 page
= mknew(page_data
);
1498 page
->first_line
= l
;
1499 page
->last_line
= l
->page_last
[n
];
1501 page
->first_text
= page
->last_text
= NULL
;
1502 page
->first_xref
= page
->last_xref
= NULL
;
1503 page
->first_rect
= page
->last_rect
= NULL
;
1506 * Now assign a y-coordinate to each line on the page.
1509 head
= (n
< ncols ? headspace
: 0);
1510 for (l
= page
->first_line
; l
; l
= l
->next
) {
1511 if (l
!= page
->first_line
) {
1512 if (l
->prev
->space_after
> 0)
1513 space
+= l
->prev
->space_after
;
1515 text
+= l
->prev
->space_after
;
1517 if (l
!= page
->first_line
|| l
->page_break
) {
1518 if (l
->space_before
> 0)
1519 space
+= l
->space_before
;
1521 text
+= l
->space_before
;
1523 text
+= l
->line_height
;
1526 l
->ypos
= text
+ space
+ head
+
1527 space
* (float)page
->first_line
->vshortfall
[n
] /
1528 page
->first_line
->space
[n
];
1530 if (l
== page
->last_line
)
1534 l
= page
->last_line
;
1539 n
= (n
< ncols ? n
+1 : ncols
);
1545 static void add_rect_to_page(page_data
*page
, int x
, int y
, int w
, int h
)
1547 rect
*r
= mknew(rect
);
1550 if (page
->last_rect
)
1551 page
->last_rect
->next
= r
;
1553 page
->first_rect
= r
;
1554 page
->last_rect
= r
;
1562 static void add_string_to_page(page_data
*page
, int x
, int y
,
1563 font_encoding
*fe
, int size
, char *text
,
1566 text_fragment
*frag
;
1568 frag
= mknew(text_fragment
);
1571 if (page
->last_text
)
1572 page
->last_text
->next
= frag
;
1574 page
->first_text
= frag
;
1575 page
->last_text
= frag
;
1580 frag
->fontsize
= size
;
1581 frag
->text
= dupstr(text
);
1582 frag
->width
= width
;
1586 * Returns the updated x coordinate.
1588 static int render_string(page_data
*page
, font_data
*font
, int fontsize
,
1589 int x
, int y
, wchar_t *str
)
1592 int textpos
, textwid
, glyph
;
1593 font_encoding
*subfont
= NULL
, *sf
;
1595 text
= mknewa(char, 1 + ustrlen(str
));
1596 textpos
= textwid
= 0;
1599 glyph
= font
->bmp
[*str
];
1601 if (glyph
== 0xFFFF) {
1603 continue; /* nothing more we can do here */
1607 * Find which subfont this character is going in.
1609 sf
= font
->subfont_map
[glyph
].subfont
;
1615 * This character is not yet in a subfont. Assign one.
1617 if (font
->latest_subfont
->free_pos
>= 0x100)
1618 font
->latest_subfont
= new_font_encoding(font
);
1620 c
= font
->latest_subfont
->free_pos
++;
1621 if (font
->latest_subfont
->free_pos
== 0x7F)
1622 font
->latest_subfont
->free_pos
= 0xA1;
1624 font
->subfont_map
[glyph
].subfont
= font
->latest_subfont
;
1625 font
->subfont_map
[glyph
].position
= c
;
1626 font
->latest_subfont
->vector
[c
] = font
->glyphs
[glyph
];
1627 font
->latest_subfont
->indices
[c
] = glyph
;
1628 font
->latest_subfont
->to_unicode
[c
] = *str
;
1630 sf
= font
->latest_subfont
;
1633 if (!subfont
|| sf
!= subfont
) {
1635 text
[textpos
] = '\0';
1636 add_string_to_page(page
, x
, y
, subfont
, fontsize
, text
,
1640 assert(textpos
== 0);
1646 text
[textpos
++] = font
->subfont_map
[glyph
].position
;
1647 textwid
+= font
->widths
[glyph
] * fontsize
;
1653 text
[textpos
] = '\0';
1654 add_string_to_page(page
, x
, y
, subfont
, fontsize
, text
, textwid
);
1662 * Returns the updated x coordinate.
1664 static int render_text(page_data
*page
, para_data
*pdata
, line_data
*ldata
,
1665 int x
, int y
, word
*text
, word
*text_end
, xref
**xr
,
1666 int shortfall
, int nspaces
, int *nspace
,
1667 keywordlist
*keywords
, indexdata
*idx
)
1669 while (text
&& text
!= text_end
) {
1670 int style
, type
, findex
, errs
;
1674 switch (text
->type
) {
1676 * Start a cross-reference.
1678 case word_HyperLink
:
1679 case word_UpperXref
:
1680 case word_LowerXref
:
1683 if (text
->type
== word_HyperLink
) {
1685 dest
.url
= utoa_dup(text
->text
, CS_ASCII
);
1687 } else if (text
->type
== word_PageXref
) {
1690 dest
.page
= (page_data
*)text
->private_data
;
1692 keyword
*kwl
= kw_lookup(keywords
, text
->text
);
1696 assert(kwl
->para
->private_data
);
1697 pdata
= (para_data
*) kwl
->para
->private_data
;
1699 dest
.page
= pdata
->first
->page
;
1703 * Shouldn't happen, but *shrug*
1710 if (dest
.type
!= NONE
) {
1712 (*xr
)->dest
= dest
; /* structure copy */
1713 if (page
->last_xref
)
1714 page
->last_xref
->next
= *xr
;
1716 page
->first_xref
= *xr
;
1717 page
->last_xref
= *xr
;
1721 * FIXME: Ideally we should have, and use, some
1722 * vertical font metric information here so that
1723 * our cross-ref rectangle can take account of
1724 * descenders and the font's cap height. This will
1725 * do for the moment, but it isn't ideal.
1727 (*xr
)->lx
= (*xr
)->rx
= x
;
1729 (*xr
)->ty
= y
+ ldata
->line_height
;
1734 * Finish extending a cross-reference box.
1742 * Add the current page number to the list of pages
1743 * referenced by an index entry.
1747 * We don't create index references in contents entries.
1749 if (!pdata
->contents_entry
) {
1753 tag
= index_findtag(idx
, text
->text
);
1757 for (i
= 0; i
< tag
->nrefs
; i
++) {
1758 indexentry
*entry
= tag
->refs
[i
];
1759 paper_idx
*pi
= (paper_idx
*)entry
->backend_data
;
1762 * If the same index term is indexed twice
1763 * within the same section, we only want to
1764 * mention it once in the index.
1766 if (pi
->lastpage
!= page
) {
1770 pi
->lastword
= pi
->lastword
->next
=
1772 pi
->lastword
= pi
->lastword
->next
=
1774 wp
= &pi
->lastword
->next
;
1778 pi
->lastword
= *wp
=
1779 fake_page_ref(page
);
1780 pi
->lastword
= pi
->lastword
->next
=
1781 fake_word(page
->number
);
1782 pi
->lastword
= pi
->lastword
->next
=
1786 pi
->lastpage
= page
;
1792 style
= towordstyle(text
->type
);
1793 type
= removeattr(text
->type
);
1795 findex
= (style
== word_Normal ? FONT_NORMAL
:
1796 style
== word_Emph ? FONT_EMPH
:
1799 if (type
== word_Normal
) {
1801 } else if (type
== word_WhiteSpace
) {
1802 x
+= pdata
->sizes
[findex
] *
1803 string_width(pdata
->fonts
[findex
], L
" ", NULL
);
1804 if (nspaces
&& findex
!= FONT_CODE
) {
1805 x
+= (*nspace
+1) * shortfall
/ nspaces
;
1806 x
-= *nspace
* shortfall
/ nspaces
;
1810 } else /* if (type == word_Quote) */ {
1811 if (text
->aux
== quote_Open
)
1812 str
= L
"\x2018"; /* FIXME: configurability! */
1814 str
= L
"\x2019"; /* FIXME: configurability! */
1817 (void) string_width(pdata
->fonts
[findex
], str
, &errs
);
1819 if (errs
&& text
->alt
)
1820 x
= render_text(page
, pdata
, ldata
, x
, y
, text
->alt
, NULL
,
1821 xr
, shortfall
, nspaces
, nspace
, keywords
, idx
);
1823 x
= render_string(page
, pdata
->fonts
[findex
],
1824 pdata
->sizes
[findex
], x
, y
, str
);
1837 * Returns the last x position used on the line.
1839 static int render_line(line_data
*ldata
, int left_x
, int top_y
,
1840 xref_dest
*dest
, keywordlist
*keywords
, indexdata
*idx
)
1846 if (ldata
->aux_text
) {
1850 x
= render_text(ldata
->page
, ldata
->pdata
, ldata
,
1851 left_x
+ ldata
->aux_left_indent
,
1852 top_y
- ldata
->ypos
,
1853 ldata
->aux_text
, NULL
, &xr
, 0, 0, &nspace
,
1855 if (ldata
->aux_text_2
)
1856 render_text(ldata
->page
, ldata
->pdata
, ldata
,
1857 x
, top_y
- ldata
->ypos
,
1858 ldata
->aux_text_2
, NULL
, &xr
, 0, 0, &nspace
,
1865 * There might be a cross-reference carried over from a
1868 if (dest
->type
!= NONE
) {
1871 xr
->dest
= *dest
; /* structure copy */
1872 if (ldata
->page
->last_xref
)
1873 ldata
->page
->last_xref
->next
= xr
;
1875 ldata
->page
->first_xref
= xr
;
1876 ldata
->page
->last_xref
= xr
;
1877 xr
->lx
= xr
->rx
= left_x
+ ldata
->xpos
;
1878 xr
->by
= top_y
- ldata
->ypos
;
1879 xr
->ty
= top_y
- ldata
->ypos
+ ldata
->line_height
;
1884 int extra_indent
, shortfall
, spaces
;
1885 int just
= ldata
->pdata
->justification
;
1888 * All forms of justification become JUST when we have
1889 * to squeeze the paragraph.
1891 if (ldata
->hshortfall
< 0)
1896 shortfall
= ldata
->hshortfall
;
1897 spaces
= ldata
->nspaces
;
1901 shortfall
= spaces
= extra_indent
= 0;
1904 shortfall
= spaces
= 0;
1905 extra_indent
= ldata
->real_shortfall
;
1909 ret
= render_text(ldata
->page
, ldata
->pdata
, ldata
,
1910 left_x
+ ldata
->xpos
+ extra_indent
,
1911 top_y
- ldata
->ypos
, ldata
->first
, ldata
->end
,
1912 &xr
, shortfall
, spaces
, &nspace
,
1918 * There's a cross-reference continued on to the next line.
1928 static void render_para(para_data
*pdata
, paper_conf
*conf
,
1929 keywordlist
*keywords
, indexdata
*idx
,
1930 paragraph
*index_placeholder
, page_data
*index_page
)
1934 page_data
*cxref_page
;
1943 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
) {
1945 * If this is a contents entry, we expect to have a single
1946 * enormous cross-reference rectangle covering the whole
1947 * thing. (Unless, of course, it spans multiple pages.)
1949 if (pdata
->contents_entry
&& ldata
->page
!= cxref_page
) {
1950 cxref_page
= ldata
->page
;
1951 cxref
= mknew(xref
);
1953 cxref
->dest
.type
= PAGE
;
1954 if (pdata
->contents_entry
== index_placeholder
) {
1955 cxref
->dest
.page
= index_page
;
1957 assert(pdata
->contents_entry
->private_data
);
1958 target
= (para_data
*)pdata
->contents_entry
->private_data
;
1959 cxref
->dest
.page
= target
->first
->page
;
1961 cxref
->dest
.url
= NULL
;
1962 if (ldata
->page
->last_xref
)
1963 ldata
->page
->last_xref
->next
= cxref
;
1965 ldata
->page
->first_xref
= cxref
;
1966 ldata
->page
->last_xref
= cxref
;
1967 cxref
->lx
= conf
->left_margin
;
1968 cxref
->rx
= conf
->paper_width
- conf
->right_margin
;
1969 cxref
->ty
= conf
->paper_height
- conf
->top_margin
1970 - ldata
->ypos
+ ldata
->line_height
;
1972 if (pdata
->contents_entry
) {
1973 assert(cxref
!= NULL
);
1974 cxref
->by
= conf
->paper_height
- conf
->top_margin
1978 last_x
= render_line(ldata
, conf
->left_margin
,
1979 conf
->paper_height
- conf
->top_margin
,
1980 &dest
, keywords
, idx
);
1981 if (ldata
== pdata
->last
)
1986 * If this is a contents entry, add leaders and a page
1989 if (pdata
->contents_entry
) {
1995 if (pdata
->contents_entry
== index_placeholder
) {
1996 num
= index_page
->number
;
1998 assert(pdata
->contents_entry
->private_data
);
1999 target
= (para_data
*)pdata
->contents_entry
->private_data
;
2000 num
= target
->first
->page
->number
;
2004 wid
= paper_width_simple(pdata
, w
);
2007 render_string(pdata
->last
->page
,
2008 pdata
->fonts
[FONT_NORMAL
],
2009 pdata
->sizes
[FONT_NORMAL
],
2010 conf
->paper_width
- conf
->right_margin
- wid
,
2011 (conf
->paper_height
- conf
->top_margin
-
2012 pdata
->last
->ypos
), num
);
2014 for (x
= 0; x
< conf
->base_width
; x
+= conf
->leader_separation
)
2015 if (x
- conf
->leader_separation
> last_x
- conf
->left_margin
&&
2016 x
+ conf
->leader_separation
< conf
->base_width
- wid
)
2017 render_string(pdata
->last
->page
,
2018 pdata
->fonts
[FONT_NORMAL
],
2019 pdata
->sizes
[FONT_NORMAL
],
2020 conf
->left_margin
+ x
,
2021 (conf
->paper_height
- conf
->top_margin
-
2022 pdata
->last
->ypos
), L
".");
2026 * Render any rectangle (chapter title underline or rule)
2027 * that goes with this paragraph.
2029 switch (pdata
->rect_type
) {
2030 case RECT_CHAPTER_UNDERLINE
:
2031 add_rect_to_page(pdata
->last
->page
,
2033 (conf
->paper_height
- conf
->top_margin
-
2035 conf
->chapter_underline_depth
),
2037 conf
->chapter_underline_thickness
);
2040 add_rect_to_page(pdata
->first
->page
,
2041 conf
->left_margin
+ pdata
->first
->xpos
,
2042 (conf
->paper_height
- conf
->top_margin
-
2044 pdata
->last
->line_height
),
2045 conf
->base_width
- pdata
->first
->xpos
,
2046 pdata
->last
->line_height
);
2048 default: /* placate gcc */
2053 static para_data
*code_paragraph(int indent
, word
*words
, paper_conf
*conf
)
2055 para_data
*pdata
= mknew(para_data
);
2058 * For code paragraphs, I'm going to hack grievously and
2059 * pretend the three normal fonts are the three code paragraph
2062 pdata
->fonts
[FONT_NORMAL
] = conf
->cb
;
2063 pdata
->fonts
[FONT_EMPH
] = conf
->co
;
2064 pdata
->fonts
[FONT_CODE
] = conf
->cr
;
2065 pdata
->sizes
[FONT_NORMAL
] =
2066 pdata
->sizes
[FONT_EMPH
] =
2067 pdata
->sizes
[FONT_CODE
] = 12;
2069 pdata
->first
= pdata
->last
= NULL
;
2070 pdata
->outline_level
= -1;
2071 pdata
->rect_type
= RECT_NONE
;
2072 pdata
->contents_entry
= NULL
;
2073 pdata
->justification
= LEFT
;
2075 for (; words
; words
= words
->next
) {
2076 wchar_t *t
, *e
, *start
;
2077 word
*lhead
= NULL
, *ltail
= NULL
, *w
;
2079 int prev
= -1, curr
;
2082 if (words
->next
&& words
->next
->type
== word_Emph
) {
2083 e
= words
->next
->text
;
2084 words
= words
->next
;
2094 else if (*e
== L
'i')
2096 else if (*e
== L
'b')
2113 * We've isolated a maximal subsequence of the line
2114 * which has the same emphasis. Form it into a word
2120 w
->type
= (prev
== 0 ? word_WeakCode
:
2121 prev
== 1 ? word_Emph
: word_Normal
);
2122 w
->text
= mknewa(wchar_t, t
-start
+1);
2123 memcpy(w
->text
, start
, (t
-start
) * sizeof(wchar_t));
2124 w
->text
[t
-start
] = '\0';
2137 ldata
= mknew(line_data
);
2139 ldata
->pdata
= pdata
;
2140 ldata
->first
= lhead
;
2142 ldata
->line_height
= conf
->base_font_size
* 4096;
2144 ldata
->xpos
= indent
;
2147 pdata
->last
->next
= ldata
;
2148 ldata
->prev
= pdata
->last
;
2150 pdata
->first
= ldata
;
2154 pdata
->last
= ldata
;
2156 ldata
->hshortfall
= 0;
2158 ldata
->aux_text
= NULL
;
2159 ldata
->aux_text_2
= NULL
;
2160 ldata
->aux_left_indent
= 0;
2161 /* General opprobrium for breaking in a code paragraph. */
2162 ldata
->penalty_before
= ldata
->penalty_after
= 50000;
2165 standard_line_spacing(pdata
, conf
);
2170 static para_data
*rule_paragraph(int indent
, paper_conf
*conf
)
2172 para_data
*pdata
= mknew(para_data
);
2175 ldata
= mknew(line_data
);
2177 ldata
->pdata
= pdata
;
2178 ldata
->first
= NULL
;
2180 ldata
->line_height
= conf
->rule_thickness
;
2182 ldata
->xpos
= indent
;
2187 ldata
->hshortfall
= 0;
2189 ldata
->aux_text
= NULL
;
2190 ldata
->aux_text_2
= NULL
;
2191 ldata
->aux_left_indent
= 0;
2194 * Better to break after a rule than before it
2196 ldata
->penalty_after
+= 100000;
2197 ldata
->penalty_before
+= -100000;
2199 pdata
->first
= pdata
->last
= ldata
;
2200 pdata
->outline_level
= -1;
2201 pdata
->rect_type
= RECT_RULE
;
2202 pdata
->contents_entry
= NULL
;
2203 pdata
->justification
= LEFT
;
2205 standard_line_spacing(pdata
, conf
);
2211 * Plain-text-like formatting for outline titles.
2213 static void paper_rdaddw(rdstring
*rs
, word
*text
) {
2214 for (; text
; text
= text
->next
) switch (text
->type
) {
2215 case word_HyperLink
:
2217 case word_UpperXref
:
2218 case word_LowerXref
:
2227 case word_WhiteSpace
:
2228 case word_EmphSpace
:
2229 case word_CodeSpace
:
2230 case word_WkCodeSpace
:
2232 case word_EmphQuote
:
2233 case word_CodeQuote
:
2234 case word_WkCodeQuote
:
2235 assert(text
->type
!= word_CodeQuote
&&
2236 text
->type
!= word_WkCodeQuote
);
2237 if (towordstyle(text
->type
) == word_Emph
&&
2238 (attraux(text
->aux
) == attr_First
||
2239 attraux(text
->aux
) == attr_Only
))
2240 rdadd(rs
, L
'_'); /* FIXME: configurability */
2241 else if (towordstyle(text
->type
) == word_Code
&&
2242 (attraux(text
->aux
) == attr_First
||
2243 attraux(text
->aux
) == attr_Only
))
2244 rdadd(rs
, L
'\''); /* FIXME: configurability */
2245 if (removeattr(text
->type
) == word_Normal
) {
2246 rdadds(rs
, text
->text
);
2247 } else if (removeattr(text
->type
) == word_WhiteSpace
) {
2249 } else if (removeattr(text
->type
) == word_Quote
) {
2250 rdadd(rs
, L
'\''); /* fixme: configurability */
2252 if (towordstyle(text
->type
) == word_Emph
&&
2253 (attraux(text
->aux
) == attr_Last
||
2254 attraux(text
->aux
) == attr_Only
))
2255 rdadd(rs
, L
'_'); /* FIXME: configurability */
2256 else if (towordstyle(text
->type
) == word_Code
&&
2257 (attraux(text
->aux
) == attr_Last
||
2258 attraux(text
->aux
) == attr_Only
))
2259 rdadd(rs
, L
'\''); /* FIXME: configurability */
2264 static wchar_t *prepare_outline_title(word
*first
, wchar_t *separator
,
2267 rdstring rs
= {0, 0, NULL
};
2270 paper_rdaddw(&rs
, first
);
2272 rdadds(&rs
, separator
);
2274 paper_rdaddw(&rs
, second
);
2279 static word
*fake_word(wchar_t *text
)
2281 word
*ret
= mknew(word
);
2284 ret
->type
= word_Normal
;
2285 ret
->text
= ustrdup(text
);
2286 ret
->breaks
= FALSE
;
2291 static word
*fake_space_word(void)
2293 word
*ret
= mknew(word
);
2296 ret
->type
= word_WhiteSpace
;
2303 static word
*fake_page_ref(page_data
*page
)
2305 word
*ret
= mknew(word
);
2308 ret
->type
= word_PageXref
;
2310 ret
->breaks
= FALSE
;
2312 ret
->private_data
= page
;
2316 static word
*fake_end_ref(void)
2318 word
*ret
= mknew(word
);
2321 ret
->type
= word_XrefEnd
;
2323 ret
->breaks
= FALSE
;
2328 static word
*prepare_contents_title(word
*first
, wchar_t *separator
,
2337 w
= dup_word_list(first
);
2345 w
= fake_word(separator
);
2351 *wptr
= dup_word_list(second
);
2357 static void fold_into_page(page_data
*dest
, page_data
*src
, int right_shift
)
2361 if (!src
->first_line
)
2364 if (dest
->last_line
) {
2365 dest
->last_line
->next
= src
->first_line
;
2366 src
->first_line
->prev
= dest
->last_line
;
2368 dest
->last_line
= src
->last_line
;
2370 for (ldata
= src
->first_line
; ldata
; ldata
= ldata
->next
) {
2372 ldata
->xpos
+= right_shift
;
2374 if (ldata
== src
->last_line
)