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 * - compression of output files. For the actual text display,
18 * both output formats currently average about 50-60 characters
19 * per 5-6 character word of text, and almost all of it's the
21 * * In PS, we can define custom text operators to make things
23 * * In PDF, there already are!
25 * - I'm uncertain of whether I need to include a ToUnicode CMap
26 * in each of my font definitions in PDF. Currently things (by
27 * which I mean cut and paste out of acroread) seem to be
28 * working fairly happily without it, but I don't know.
30 * - rather than the ugly aux_text mechanism for rendering chapter
31 * titles, we could actually build the correct word list and
34 * - get vertical font metrics and use them to position the PDF
35 * xref boxes more pleasantly
38 * * all the measurements in `conf' should be configurable
39 * + notably paper size/shape
40 * * page header and footer should be configurable; we should
41 * be able to shift the page number elsewhere, and add other
42 * things such as the current chapter/section title and fixed
44 * * remove the fixed mapping from heading levels to heading
45 * styles; offer a menu of styles from which the user can
46 * choose at every heading level
47 * * first-line indent in paragraphs
48 * * fixed text: `Contents', `Index', bullet, quotes, the
49 * colon-space and full stop in chapter title constructions
50 * * configurable location of contents?
51 * * certainly configurably _remove_ the contents, and possibly
53 * * double-sided document switch?
54 * + means you have two header/footer formats which
56 * + and means that mandatory page breaks before chapter
57 * titles should include a blank page if necessary to
58 * start the next section to a right-hand page
62 * - ability to import other Type 1 fonts
63 * * we need to parse the font to extract its metrics
64 * * then we pass the font bodily to both PS and PDF so it can
65 * be included in the output file
67 * - character substitution for better typography?
68 * * fi, fl, ffi, ffl ligatures
69 * * use real ellipsis rather than ...
70 * * a hyphen in a word by itself might prefer to be an en-dash
71 * * (Americans might even want a convenient way to use an
73 * * DON'T DO ANY OF THE ABOVE WITHIN \c OR \cw!
74 * * substituting `minus' for `hyphen' in the standard encoding
75 * is probably preferable in Courier, though certainly not in
77 * * if I do do this lot, I'm rather inclined to at least try
78 * to think up a configurable way to do it so that Americans
79 * can do em-dash tricks without my intervention and other
80 * people can do other odd things too.
89 typedef struct paper_conf_Tag paper_conf
;
90 typedef struct paper_idx_Tag paper_idx
;
92 struct paper_conf_Tag
{
99 int indent_list_bullet
;
103 int base_para_spacing
;
104 int chapter_top_space
;
105 int sect_num_left_space
;
106 int chapter_underline_depth
;
107 int chapter_underline_thickness
;
110 int contents_indent_step
;
112 int leader_separation
;
116 int pagenum_fontsize
;
118 /* These are derived from the above */
122 /* Fonts used in the configuration */
123 font_data
*tr
, *ti
, *hr
, *hi
, *cr
, *co
, *cb
;
126 struct paper_idx_Tag
{
128 * Word list giving the page numbers on which this index entry
129 * appears. Also the last word in the list, for ease of
135 * The last page added to the list (so we can ensure we don't
142 word_PageXref
= word_NotWordType
+ 1
145 static font_data
*make_std_font(font_list
*fontlist
, char const *name
);
146 static void wrap_paragraph(para_data
*pdata
, word
*words
,
147 int w
, int i1
, int i2
);
148 static page_data
*page_breaks(line_data
*first
, line_data
*last
,
149 int page_height
, int ncols
, int headspace
);
150 static int render_string(page_data
*page
, font_data
*font
, int fontsize
,
151 int x
, int y
, wchar_t *str
);
152 static int render_line(line_data
*ldata
, int left_x
, int top_y
,
153 xref_dest
*dest
, keywordlist
*keywords
, indexdata
*idx
);
154 static void render_para(para_data
*pdata
, paper_conf
*conf
,
155 keywordlist
*keywords
, indexdata
*idx
,
156 paragraph
*index_placeholder
, page_data
*index_page
);
157 static int string_width(font_data
*font
, wchar_t const *string
, int *errs
);
158 static int paper_width_simple(para_data
*pdata
, word
*text
);
159 static para_data
*code_paragraph(int indent
, word
*words
, paper_conf
*conf
);
160 static para_data
*rule_paragraph(int indent
, paper_conf
*conf
);
161 static void add_rect_to_page(page_data
*page
, int x
, int y
, int w
, int h
);
162 static para_data
*make_para_data(int ptype
, int paux
, int indent
, int rmargin
,
163 word
*pkwtext
, word
*pkwtext2
, word
*pwords
,
165 static void standard_line_spacing(para_data
*pdata
, paper_conf
*conf
);
166 static wchar_t *prepare_outline_title(word
*first
, wchar_t *separator
,
168 static word
*fake_word(wchar_t *text
);
169 static word
*fake_space_word(void);
170 static word
*fake_page_ref(page_data
*page
);
171 static word
*fake_end_ref(void);
172 static word
*prepare_contents_title(word
*first
, wchar_t *separator
,
174 static void fold_into_page(page_data
*dest
, page_data
*src
, int right_shift
);
176 void *paper_pre_backend(paragraph
*sourceform
, keywordlist
*keywords
,
180 int indent
, used_contents
;
181 para_data
*pdata
, *firstpara
= NULL
, *lastpara
= NULL
;
182 para_data
*firstcont
, *lastcont
;
183 line_data
*firstline
, *lastline
, *firstcontline
, *lastcontline
;
189 paragraph index_placeholder_para
;
190 page_data
*first_index_page
;
193 * FIXME: All these things ought to become configurable.
195 conf
= mknew(paper_conf
);
196 conf
->paper_width
= 595 * 4096;
197 conf
->paper_height
= 841 * 4096;
198 conf
->left_margin
= 72 * 4096;
199 conf
->top_margin
= 72 * 4096;
200 conf
->right_margin
= 72 * 4096;
201 conf
->bottom_margin
= 108 * 4096;
202 conf
->indent_list_bullet
= 6 * 4096;
203 conf
->indent_list
= 24 * 4096;
204 conf
->indent_quote
= 18 * 4096;
205 conf
->base_leading
= 4096;
206 conf
->base_para_spacing
= 10 * 4096;
207 conf
->chapter_top_space
= 72 * 4096;
208 conf
->sect_num_left_space
= 12 * 4096;
209 conf
->chapter_underline_depth
= 14 * 4096;
210 conf
->chapter_underline_thickness
= 3 * 4096;
211 conf
->rule_thickness
= 1 * 4096;
212 conf
->base_font_size
= 12;
213 conf
->contents_indent_step
= 24 * 4096;
214 conf
->contents_margin
= 84 * 4096;
215 conf
->leader_separation
= 12 * 4096;
216 conf
->index_gutter
= 36 * 4096;
217 conf
->index_cols
= 2;
218 conf
->index_minsep
= 18 * 4096;
219 conf
->pagenum_fontsize
= 12;
220 conf
->footer_distance
= 32 * 4096;
223 conf
->paper_width
- conf
->left_margin
- conf
->right_margin
;
225 conf
->paper_height
- conf
->top_margin
- conf
->bottom_margin
;
226 conf
->index_colwidth
=
227 (conf
->base_width
- (conf
->index_cols
-1) * conf
->index_gutter
)
231 * First, set up some font structures.
233 fontlist
= mknew(font_list
);
234 fontlist
->head
= fontlist
->tail
= NULL
;
235 conf
->tr
= make_std_font(fontlist
, "Times-Roman");
236 conf
->ti
= make_std_font(fontlist
, "Times-Italic");
237 conf
->hr
= make_std_font(fontlist
, "Helvetica-Bold");
238 conf
->hi
= make_std_font(fontlist
, "Helvetica-BoldOblique");
239 conf
->cr
= make_std_font(fontlist
, "Courier");
240 conf
->co
= make_std_font(fontlist
, "Courier-Oblique");
241 conf
->cb
= make_std_font(fontlist
, "Courier-Bold");
244 * Set up a data structure to collect page numbers for each
253 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
254 paper_idx
*pi
= mknew(paper_idx
);
258 pi
->words
= pi
->lastword
= NULL
;
261 entry
->backend_data
= pi
;
266 * Format the contents entry for each heading.
269 word
*contents_title
;
270 contents_title
= fake_word(L
"Contents");
272 firstcont
= make_para_data(para_UnnumberedChapter
, 0, 0, 0,
273 NULL
, NULL
, contents_title
, conf
);
274 lastcont
= firstcont
;
275 lastcont
->next
= NULL
;
276 firstcontline
= firstcont
->first
;
277 lastcontline
= lastcont
->last
;
278 for (p
= sourceform
; p
; p
= p
->next
) {
285 case para_UnnumberedChapter
:
291 words
= prepare_contents_title(p
->kwtext
, L
": ", p
->words
);
294 case para_UnnumberedChapter
:
295 words
= prepare_contents_title(NULL
, NULL
, p
->words
);
300 words
= prepare_contents_title(p
->kwtext2
, L
" ", p
->words
);
301 indent
= (p
->aux
+ 1) * conf
->contents_indent_step
;
304 pdata
= make_para_data(para_Normal
, p
->aux
, indent
,
305 conf
->contents_margin
,
306 NULL
, NULL
, words
, conf
);
308 pdata
->contents_entry
= p
;
309 lastcont
->next
= pdata
;
313 * Link all contents line structures together into
318 lastcontline
->next
= pdata
->first
;
319 pdata
->first
->prev
= lastcontline
;
321 firstcontline
= pdata
->first
;
322 pdata
->first
->prev
= NULL
;
324 lastcontline
= pdata
->last
;
325 lastcontline
->next
= NULL
;
333 * And one extra one, for the index.
336 pdata
= make_para_data(para_Normal
, 0, 0,
337 conf
->contents_margin
,
338 NULL
, NULL
, fake_word(L
"Index"), conf
);
340 pdata
->contents_entry
= &index_placeholder_para
;
341 lastcont
->next
= pdata
;
346 lastcontline
->next
= pdata
->first
;
347 pdata
->first
->prev
= lastcontline
;
349 firstcontline
= pdata
->first
;
350 pdata
->first
->prev
= NULL
;
352 lastcontline
= pdata
->last
;
353 lastcontline
->next
= NULL
;
359 * Do the main paragraph formatting.
362 used_contents
= FALSE
;
363 firstline
= lastline
= NULL
;
364 for (p
= sourceform
; p
; p
= p
->next
) {
365 p
->private_data
= NULL
;
369 * These paragraph types are either invisible or don't
370 * define text in the normal sense. Either way, they
371 * don't require wrapping.
376 case para_NotParaType
:
383 * These paragraph types don't require wrapping, but
384 * they do affect the line width to which we wrap the
385 * rest of the paragraphs, so we need to pay attention.
388 indent
+= conf
->indent_list
; break;
390 indent
-= conf
->indent_list
; assert(indent
>= 0); break;
392 indent
+= conf
->indent_quote
; break;
394 indent
-= conf
->indent_quote
; assert(indent
>= 0); break;
397 * This paragraph type is special. Process it
401 pdata
= code_paragraph(indent
, p
->words
, conf
);
402 p
->private_data
= pdata
;
403 if (pdata
->first
!= pdata
->last
) {
404 pdata
->first
->penalty_after
+= 100000;
405 pdata
->last
->penalty_before
+= 100000;
410 * This paragraph is also special.
413 pdata
= rule_paragraph(indent
, conf
);
414 p
->private_data
= pdata
;
418 * All of these paragraph types require wrapping in the
419 * ordinary way. So we must supply a set of fonts, a
420 * line width and auxiliary information (e.g. bullet
421 * text) for each one.
425 case para_UnnumberedChapter
:
429 case para_BiblioCited
:
431 case para_NumberedList
:
432 case para_DescribedThing
:
433 case para_Description
:
436 pdata
= make_para_data(p
->type
, p
->aux
, indent
, 0,
437 p
->kwtext
, p
->kwtext2
, p
->words
, conf
);
439 p
->private_data
= pdata
;
444 if (p
->private_data
) {
445 pdata
= (para_data
*)p
->private_data
;
448 * If this is the first non-title heading, we link the
449 * contents section in before it.
451 if (!used_contents
&& pdata
->outline_level
> 0) {
452 used_contents
= TRUE
;
454 lastpara
->next
= firstcont
;
456 firstpara
= firstcont
;
458 assert(lastpara
->next
== NULL
);
461 lastline
->next
= firstcontline
;
462 firstcontline
->prev
= lastline
;
464 firstline
= firstcontline
;
465 firstcontline
->prev
= NULL
;
467 assert(lastcontline
!= NULL
);
468 lastline
= lastcontline
;
469 lastline
->next
= NULL
;
473 * Link all line structures together into a big list.
477 lastline
->next
= pdata
->first
;
478 pdata
->first
->prev
= lastline
;
480 firstline
= pdata
->first
;
481 pdata
->first
->prev
= NULL
;
483 lastline
= pdata
->last
;
484 lastline
->next
= NULL
;
488 * Link all paragraph structures together similarly.
492 lastpara
->next
= pdata
;
500 * Now we have an enormous linked list of every line of text in
501 * the document. Break it up into pages.
503 pages
= page_breaks(firstline
, lastline
, conf
->page_height
, 0, 0);
514 for (page
= pages
; page
; page
= page
->next
) {
515 sprintf(buf
, "%d", ++pagenum
);
516 page
->number
= ufroma_dup(buf
);
520 first_index_page
= mknew(page_data
);
521 first_index_page
->next
= first_index_page
->prev
= NULL
;
522 first_index_page
->first_line
= NULL
;
523 first_index_page
->last_line
= NULL
;
524 first_index_page
->first_text
= first_index_page
->last_text
= NULL
;
525 first_index_page
->first_xref
= first_index_page
->last_xref
= NULL
;
526 first_index_page
->first_rect
= first_index_page
->last_rect
= NULL
;
528 /* And don't forget the as-yet-uncreated index. */
529 sprintf(buf
, "%d", ++pagenum
);
530 first_index_page
->number
= ufroma_dup(buf
);
535 * Now we're ready to actually lay out the pages. We do this by
536 * looping over _paragraphs_, since we may need to track cross-
537 * references between lines and even across pages.
539 for (pdata
= firstpara
; pdata
; pdata
= pdata
->next
)
540 render_para(pdata
, conf
, keywords
, idx
,
541 &index_placeholder_para
, first_index_page
);
544 * Now we've laid out the main body pages, we should have
545 * acquired a full set of page numbers for the index.
551 para_data
*firstidx
, *lastidx
;
552 line_data
*firstidxline
, *lastidxline
, *ldata
;
553 page_data
*ipages
, *ipages2
, *page
;
556 * Create a set of paragraphs for the index.
558 index_title
= fake_word(L
"Index");
560 firstidx
= make_para_data(para_UnnumberedChapter
, 0, 0, 0,
561 NULL
, NULL
, index_title
, conf
);
563 lastidx
->next
= NULL
;
564 firstidxline
= firstidx
->first
;
565 lastidxline
= lastidx
->last
;
566 for (i
= 0; (entry
= index234(idx
->entries
, i
)) != NULL
; i
++) {
567 paper_idx
*pi
= (paper_idx
*)entry
->backend_data
;
568 para_data
*text
, *pages
;
573 text
= make_para_data(para_Normal
, 0, 0,
574 conf
->base_width
- conf
->index_colwidth
,
575 NULL
, NULL
, entry
->text
, conf
);
577 pages
= make_para_data(para_Normal
, 0, 0,
578 conf
->base_width
- conf
->index_colwidth
,
579 NULL
, NULL
, pi
->words
, conf
);
581 text
->justification
= LEFT
;
582 pages
->justification
= RIGHT
;
583 text
->last
->space_after
= pages
->first
->space_before
=
584 conf
->base_leading
/ 2;
586 pages
->last
->space_after
= text
->first
->space_before
=
590 assert(pages
->first
);
595 * If feasible, fold the two halves of the index entry
598 if (text
->last
->real_shortfall
+ pages
->first
->real_shortfall
>
599 conf
->index_colwidth
+ conf
->index_minsep
) {
600 text
->last
->space_after
= -1;
601 pages
->first
->space_before
= -pages
->first
->line_height
+1;
604 lastidx
->next
= text
;
610 * Link all index line structures together into
613 text
->last
->next
= pages
->first
;
614 pages
->first
->prev
= text
->last
;
616 lastidxline
->next
= text
->first
;
617 text
->first
->prev
= lastidxline
;
619 lastidxline
= pages
->last
;
622 * Breaking an index entry anywhere is so bad that I
623 * think I'm going to forbid it totally.
625 for (ldata
= text
->first
; ldata
&& ldata
->next
;
626 ldata
= ldata
->next
) {
627 ldata
->next
->space_before
+= ldata
->space_after
+ 1;
628 ldata
->space_after
= -1;
633 * Now break the index into pages.
635 ipages
= page_breaks(firstidxline
, firstidxline
, conf
->page_height
,
637 ipages2
= page_breaks(firstidxline
->next
, lastidxline
,
640 firstidxline
->space_before
+
641 firstidxline
->line_height
+
642 firstidxline
->space_after
);
645 * This will have put each _column_ of the index on a
646 * separate page, which isn't what we want. Fold the pages
653 for (i
= 1; i
< conf
->index_cols
; i
++)
657 fold_into_page(page
, page
->next
,
658 i
* (conf
->index_colwidth
+
659 conf
->index_gutter
));
661 page
->next
= page
->next
->next
;
663 page
->next
->prev
= page
;
669 /* Also fold the heading on to the same page as the index items. */
670 fold_into_page(ipages
, ipages2
, 0);
671 ipages
->next
= ipages2
->next
;
673 ipages
->next
->prev
= ipages
;
675 fold_into_page(first_index_page
, ipages
, 0);
676 first_index_page
->next
= ipages
->next
;
677 if (first_index_page
->next
)
678 first_index_page
->next
->prev
= first_index_page
;
680 ipages
= first_index_page
;
683 * Number the index pages, except the already-numbered
686 for (page
= ipages
->next
; page
; page
= page
->next
) {
688 sprintf(buf
, "%d", ++pagenum
);
689 page
->number
= ufroma_dup(buf
);
693 * Render the index pages.
695 for (pdata
= firstidx
; pdata
; pdata
= pdata
->next
)
696 render_para(pdata
, conf
, keywords
, idx
,
697 &index_placeholder_para
, first_index_page
);
700 * Link the index page list on to the end of the main page
706 for (page
= pages
; page
->next
; page
= page
->next
);
711 * Same with the paragraph list, which will cause the index
712 * to be mentioned in the document outline.
715 firstpara
= firstidx
;
717 lastpara
->next
= firstidx
;
722 * Draw the headers and footers.
724 * FIXME: this should be fully configurable, but for the moment
725 * I'm just going to put in page numbers in the centre of a
726 * footer and leave it at that.
731 for (page
= pages
; page
; page
= page
->next
) {
734 width
= conf
->pagenum_fontsize
*
735 string_width(conf
->tr
, page
->number
, NULL
);
737 render_string(page
, conf
->tr
, conf
->pagenum_fontsize
,
738 conf
->left_margin
+ (conf
->base_width
- width
)/2,
739 conf
->bottom_margin
- conf
->footer_distance
,
745 * Start putting together the overall document structure we're
748 doc
= mknew(document
);
749 doc
->fonts
= fontlist
;
751 doc
->paper_width
= conf
->paper_width
;
752 doc
->paper_height
= conf
->paper_height
;
755 * Collect the section heading paragraphs into a document
756 * outline. This is slightly fiddly because the Title paragraph
757 * isn't required to be at the start, although all the others
763 doc
->outline_elements
= mknewa(outline_element
, osize
);
764 doc
->n_outline_elements
= 0;
766 /* First find the title. */
767 for (pdata
= firstpara
; pdata
; pdata
= pdata
->next
) {
768 if (pdata
->outline_level
== 0) {
769 doc
->outline_elements
[0].level
= 0;
770 doc
->outline_elements
[0].pdata
= pdata
;
771 doc
->n_outline_elements
++;
776 /* Then collect the rest. */
777 for (pdata
= firstpara
; pdata
; pdata
= pdata
->next
) {
778 if (pdata
->outline_level
> 0) {
779 if (doc
->n_outline_elements
>= osize
) {
781 doc
->outline_elements
=
782 resize(doc
->outline_elements
, osize
);
785 doc
->outline_elements
[doc
->n_outline_elements
].level
=
786 pdata
->outline_level
;
787 doc
->outline_elements
[doc
->n_outline_elements
].pdata
= pdata
;
788 doc
->n_outline_elements
++;
798 static para_data
*make_para_data(int ptype
, int paux
, int indent
, int rmargin
,
799 word
*pkwtext
, word
*pkwtext2
, word
*pwords
,
804 int extra_indent
, firstline_indent
, aux_indent
;
807 pdata
= mknew(para_data
);
808 pdata
->outline_level
= -1;
809 pdata
->outline_title
= NULL
;
810 pdata
->rect_type
= RECT_NONE
;
811 pdata
->contents_entry
= NULL
;
812 pdata
->justification
= JUST
;
815 * Choose fonts for this paragraph.
817 * FIXME: All of this ought to be completely
822 pdata
->fonts
[FONT_NORMAL
] = conf
->hr
;
823 pdata
->sizes
[FONT_NORMAL
] = 24;
824 pdata
->fonts
[FONT_EMPH
] = conf
->hi
;
825 pdata
->sizes
[FONT_EMPH
] = 24;
826 pdata
->fonts
[FONT_CODE
] = conf
->cb
;
827 pdata
->sizes
[FONT_CODE
] = 24;
828 pdata
->outline_level
= 0;
833 case para_UnnumberedChapter
:
834 pdata
->fonts
[FONT_NORMAL
] = conf
->hr
;
835 pdata
->sizes
[FONT_NORMAL
] = 20;
836 pdata
->fonts
[FONT_EMPH
] = conf
->hi
;
837 pdata
->sizes
[FONT_EMPH
] = 20;
838 pdata
->fonts
[FONT_CODE
] = conf
->cb
;
839 pdata
->sizes
[FONT_CODE
] = 20;
840 pdata
->outline_level
= 1;
845 pdata
->fonts
[FONT_NORMAL
] = conf
->hr
;
846 pdata
->fonts
[FONT_EMPH
] = conf
->hi
;
847 pdata
->fonts
[FONT_CODE
] = conf
->cb
;
848 pdata
->sizes
[FONT_NORMAL
] =
849 pdata
->sizes
[FONT_EMPH
] =
850 pdata
->sizes
[FONT_CODE
] =
851 (paux
== 0 ?
16 : paux
== 1 ?
14 : 13);
852 pdata
->outline_level
= 2 + paux
;
856 case para_BiblioCited
:
858 case para_NumberedList
:
859 case para_DescribedThing
:
860 case para_Description
:
862 pdata
->fonts
[FONT_NORMAL
] = conf
->tr
;
863 pdata
->sizes
[FONT_NORMAL
] = 12;
864 pdata
->fonts
[FONT_EMPH
] = conf
->ti
;
865 pdata
->sizes
[FONT_EMPH
] = 12;
866 pdata
->fonts
[FONT_CODE
] = conf
->cr
;
867 pdata
->sizes
[FONT_CODE
] = 12;
872 * Also select an indentation level depending on the
873 * paragraph type (list paragraphs other than
874 * para_DescribedThing need extra indent).
876 * (FIXME: Perhaps at some point we might even arrange
877 * for the user to be able to request indented first
878 * lines in paragraphs.)
880 if (ptype
== para_Bullet
||
881 ptype
== para_NumberedList
||
882 ptype
== para_Description
) {
883 extra_indent
= firstline_indent
= conf
->indent_list
;
885 extra_indent
= firstline_indent
= 0;
889 * Find the auxiliary text for this paragraph.
900 * For some heading styles (FIXME: be able to
901 * configure which), the auxiliary text contains
902 * the chapter number and is arranged to be
903 * right-aligned a few points left of the primary
904 * margin. For other styles, the auxiliary text is
905 * the full chapter _name_ and takes up space
906 * within the (wrapped) chapter title, meaning that
907 * we must move the first line indent over to make
910 if (ptype
== para_Heading
|| ptype
== para_Subsect
) {
914 len
= paper_width_simple(pdata
, pkwtext2
);
915 aux_indent
= -len
- conf
->sect_num_left_space
;
917 pdata
->outline_title
=
918 prepare_outline_title(pkwtext2
, L
" ", pwords
);
921 aux2
= fake_word(L
": ");
924 firstline_indent
+= paper_width_simple(pdata
, aux
);
925 firstline_indent
+= paper_width_simple(pdata
, aux2
);
927 pdata
->outline_title
=
928 prepare_outline_title(pkwtext
, L
": ", pwords
);
934 * Auxiliary text consisting of a bullet. (FIXME:
935 * configurable bullet.)
937 aux
= fake_word(L
"\x2022");
938 aux_indent
= indent
+ conf
->indent_list_bullet
;
941 case para_NumberedList
:
943 * Auxiliary text consisting of the number followed
944 * by a (FIXME: configurable) full stop.
947 aux2
= fake_word(L
".");
948 aux_indent
= indent
+ conf
->indent_list_bullet
;
951 case para_BiblioCited
:
953 * Auxiliary text consisting of the bibliography
954 * reference text, and a trailing space.
957 aux2
= fake_word(L
" ");
959 firstline_indent
+= paper_width_simple(pdata
, aux
);
960 firstline_indent
+= paper_width_simple(pdata
, aux2
);
964 if (pdata
->outline_level
>= 0 && !pdata
->outline_title
) {
965 pdata
->outline_title
=
966 prepare_outline_title(NULL
, NULL
, pwords
);
969 wrap_paragraph(pdata
, pwords
, conf
->base_width
- rmargin
,
970 indent
+ firstline_indent
,
971 indent
+ extra_indent
);
973 pdata
->first
->aux_text
= aux
;
974 pdata
->first
->aux_text_2
= aux2
;
975 pdata
->first
->aux_left_indent
= aux_indent
;
978 * Line breaking penalties.
985 case para_UnnumberedChapter
:
987 * Fixed and large penalty for breaking straight
988 * after a heading; corresponding bonus for
989 * breaking straight before.
991 pdata
->first
->penalty_before
= -500000;
992 pdata
->last
->penalty_after
= 500000;
993 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
)
994 ldata
->penalty_after
= 500000;
997 case para_DescribedThing
:
999 * This is treated a bit like a small heading:
1000 * there's a penalty for breaking after it (i.e.
1001 * between it and its description), and a bonus for
1002 * breaking before it (actually _between_ list
1005 pdata
->first
->penalty_before
= -200000;
1006 pdata
->last
->penalty_after
= 200000;
1011 * Most paragraph types: widow/orphan control by
1012 * discouraging breaking one line from the end of
1015 if (pdata
->first
!= pdata
->last
) {
1016 pdata
->first
->penalty_after
= 100000;
1017 pdata
->last
->penalty_before
= 100000;
1022 standard_line_spacing(pdata
, conf
);
1025 * Some kinds of section heading require a page break before
1026 * them and an underline after.
1028 if (ptype
== para_Title
||
1029 ptype
== para_Chapter
||
1030 ptype
== para_Appendix
||
1031 ptype
== para_UnnumberedChapter
) {
1032 pdata
->first
->page_break
= TRUE
;
1033 pdata
->first
->space_before
= conf
->chapter_top_space
;
1034 pdata
->last
->space_after
+=
1035 (conf
->chapter_underline_depth
+
1036 conf
->chapter_underline_thickness
);
1037 pdata
->rect_type
= RECT_CHAPTER_UNDERLINE
;
1043 static void standard_line_spacing(para_data
*pdata
, paper_conf
*conf
)
1048 * Set the line spacing for each line in this paragraph.
1050 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
) {
1051 if (ldata
== pdata
->first
)
1052 ldata
->space_before
= conf
->base_para_spacing
/ 2;
1054 ldata
->space_before
= conf
->base_leading
/ 2;
1055 if (ldata
== pdata
->last
)
1056 ldata
->space_after
= conf
->base_para_spacing
/ 2;
1058 ldata
->space_after
= conf
->base_leading
/ 2;
1059 ldata
->page_break
= FALSE
;
1063 static font_encoding
*new_font_encoding(font_data
*font
)
1068 fe
= mknew(font_encoding
);
1071 if (font
->list
->tail
)
1072 font
->list
->tail
->next
= fe
;
1074 font
->list
->head
= fe
;
1075 font
->list
->tail
= fe
;
1078 fe
->free_pos
= 0x21;
1080 for (i
= 0; i
< 256; i
++) {
1081 fe
->vector
[i
] = NULL
;
1082 fe
->indices
[i
] = -1;
1083 fe
->to_unicode
[i
] = 0xFFFF;
1089 static font_data
*make_std_font(font_list
*fontlist
, char const *name
)
1097 widths
= ps_std_font_widths(name
);
1101 for (nglyphs
= 0; ps_std_glyphs
[nglyphs
] != NULL
; nglyphs
++);
1103 f
= mknew(font_data
);
1107 f
->nglyphs
= nglyphs
;
1108 f
->glyphs
= ps_std_glyphs
;
1110 f
->subfont_map
= mknewa(subfont_map_entry
, nglyphs
);
1113 * Our first subfont will contain all of US-ASCII. This isn't
1114 * really necessary - we could just create custom subfonts
1115 * precisely as the whim of render_string dictated - but
1116 * instinct suggests that it might be nice to have the text in
1117 * the output files look _marginally_ recognisable.
1119 fe
= new_font_encoding(f
);
1120 fe
->free_pos
= 0xA1; /* only the top half is free */
1121 f
->latest_subfont
= fe
;
1123 for (i
= 0; i
< (int)lenof(f
->bmp
); i
++)
1126 for (i
= 0; i
< nglyphs
; i
++) {
1128 ucs
= ps_glyph_to_unicode(f
->glyphs
[i
]);
1129 assert(ucs
!= 0xFFFF);
1131 if (ucs
>= 0x20 && ucs
<= 0x7E) {
1132 fe
->vector
[ucs
] = f
->glyphs
[i
];
1133 fe
->indices
[ucs
] = i
;
1134 fe
->to_unicode
[ucs
] = ucs
;
1135 f
->subfont_map
[i
].subfont
= fe
;
1136 f
->subfont_map
[i
].position
= ucs
;
1139 * This character is not yet assigned to a subfont.
1141 f
->subfont_map
[i
].subfont
= NULL
;
1142 f
->subfont_map
[i
].position
= 0;
1149 static int string_width(font_data
*font
, wchar_t const *string
, int *errs
)
1156 for (; *string
; string
++) {
1159 index
= font
->bmp
[(unsigned short)*string
];
1160 if (index
== 0xFFFF) {
1164 width
+= font
->widths
[index
];
1171 static int paper_width_internal(void *vctx
, word
*word
, int *nspaces
);
1173 struct paper_width_ctx
{
1178 static int paper_width_list(void *vctx
, word
*text
, word
*end
, int *nspaces
) {
1180 while (text
&& text
!= end
) {
1181 w
+= paper_width_internal(vctx
, text
, nspaces
);
1187 static int paper_width_internal(void *vctx
, word
*word
, int *nspaces
)
1189 struct paper_width_ctx
*ctx
= (struct paper_width_ctx
*)vctx
;
1190 int style
, type
, findex
, width
, errs
;
1193 switch (word
->type
) {
1194 case word_HyperLink
:
1196 case word_UpperXref
:
1197 case word_LowerXref
:
1204 style
= towordstyle(word
->type
);
1205 type
= removeattr(word
->type
);
1207 findex
= (style
== word_Normal ? FONT_NORMAL
:
1208 style
== word_Emph ? FONT_EMPH
:
1211 if (type
== word_Normal
) {
1213 } else if (type
== word_WhiteSpace
) {
1214 if (findex
!= FONT_CODE
) {
1217 return ctx
->minspacewidth
;
1220 } else /* if (type == word_Quote) */ {
1221 if (word
->aux
== quote_Open
)
1222 str
= L
"\x2018"; /* FIXME: configurability! */
1224 str
= L
"\x2019"; /* FIXME: configurability! */
1227 width
= string_width(ctx
->pdata
->fonts
[findex
], str
, &errs
);
1229 if (errs
&& word
->alt
)
1230 return paper_width_list(vctx
, word
->alt
, NULL
, nspaces
);
1232 return ctx
->pdata
->sizes
[findex
] * width
;
1235 static int paper_width(void *vctx
, word
*word
)
1237 return paper_width_internal(vctx
, word
, NULL
);
1240 static int paper_width_simple(para_data
*pdata
, word
*text
)
1242 struct paper_width_ctx ctx
;
1246 (pdata
->sizes
[FONT_NORMAL
] *
1247 string_width(pdata
->fonts
[FONT_NORMAL
], L
" ", NULL
));
1249 return paper_width_list(&ctx
, text
, NULL
, NULL
);
1252 static void wrap_paragraph(para_data
*pdata
, word
*words
,
1253 int w
, int i1
, int i2
)
1255 wrappedline
*wrapping
, *p
;
1257 struct paper_width_ctx ctx
;
1261 * We're going to need to store the line height in every line
1262 * structure we generate.
1267 for (i
= 0; i
< NFONTS
; i
++)
1268 if (line_height
< pdata
->sizes
[i
])
1269 line_height
= pdata
->sizes
[i
];
1270 line_height
*= 4096;
1273 spacewidth
= (pdata
->sizes
[FONT_NORMAL
] *
1274 string_width(pdata
->fonts
[FONT_NORMAL
], L
" ", NULL
));
1275 if (spacewidth
== 0) {
1277 * A font without a space?! Disturbing. I hope this never
1278 * comes up, but I'll make a random guess anyway and set my
1279 * space width to half the point size.
1281 spacewidth
= pdata
->sizes
[FONT_NORMAL
] * 4096 / 2;
1285 * I'm going to set the _minimum_ space width to 3/5 of the
1286 * standard one, and use the standard one as the optimum.
1288 ctx
.minspacewidth
= spacewidth
* 3 / 5;
1291 wrapping
= wrap_para(words
, w
- i1
, w
- i2
, paper_width
, &ctx
, spacewidth
);
1294 * Having done the wrapping, we now concoct a set of line_data
1297 pdata
->first
= pdata
->last
= NULL
;
1299 for (p
= wrapping
; p
; p
= p
->next
) {
1302 int len
, wid
, spaces
;
1304 ldata
= mknew(line_data
);
1306 ldata
->pdata
= pdata
;
1307 ldata
->first
= p
->begin
;
1308 ldata
->end
= p
->end
;
1309 ldata
->line_height
= line_height
;
1311 ldata
->xpos
= (p
== wrapping ? i1
: i2
);
1314 pdata
->last
->next
= ldata
;
1315 ldata
->prev
= pdata
->last
;
1317 pdata
->first
= ldata
;
1321 pdata
->last
= ldata
;
1324 len
= paper_width_list(&ctx
, ldata
->first
, ldata
->end
, &spaces
);
1325 wid
= (p
== wrapping ? w
- i1
: w
- i2
);
1328 ldata
->hshortfall
= wid
- len
;
1329 ldata
->nspaces
= spaces
;
1331 * This tells us how much the space width needs to
1332 * change from _min_spacewidth. But we want to store
1333 * its difference from the _natural_ space width, to
1334 * make the text rendering easier.
1336 ldata
->hshortfall
+= ctx
.minspacewidth
* spaces
;
1337 ldata
->hshortfall
-= spacewidth
* spaces
;
1338 ldata
->real_shortfall
= ldata
->hshortfall
;
1340 * Special case: on the last line of a paragraph, we
1341 * never stretch spaces.
1343 if (ldata
->hshortfall
> 0 && !p
->next
)
1344 ldata
->hshortfall
= 0;
1346 ldata
->aux_text
= NULL
;
1347 ldata
->aux_text_2
= NULL
;
1348 ldata
->aux_left_indent
= 0;
1349 ldata
->penalty_before
= ldata
->penalty_after
= 0;
1354 static page_data
*page_breaks(line_data
*first
, line_data
*last
,
1355 int page_height
, int ncols
, int headspace
)
1359 int n
, n1
, this_height
;
1362 * Page breaking is done by a close analogue of the optimal
1363 * paragraph wrapping algorithm used by wrap_para(). We work
1364 * backwards from the end of the document line by line; for
1365 * each line, we contemplate every possible number of lines we
1366 * could put on a page starting with that line, determine a
1367 * cost function for each one, add it to the pre-computed cost
1368 * function for optimally page-breaking everything after that
1369 * page, and pick the best option.
1371 * This is made slightly more complex by the fact that we have
1372 * a multi-column index with a heading at the top of the
1373 * _first_ page, meaning that the first _ncols_ pages must have
1374 * a different length. Hence, we must do the wrapping ncols+1
1375 * times over, hypothetically trying to put every subsequence
1376 * on every possible page.
1378 * Since my line_data structures are only used for this
1379 * purpose, I might as well just store the algorithm data
1383 for (l
= last
; l
; l
= l
->prev
) {
1384 l
->bestcost
= mknewa(int, ncols
+1);
1385 l
->vshortfall
= mknewa(int, ncols
+1);
1386 l
->text
= mknewa(int, ncols
+1);
1387 l
->space
= mknewa(int, ncols
+1);
1388 l
->page_last
= mknewa(line_data
*, ncols
+1);
1390 for (n
= 0; n
<= ncols
; n
++) {
1391 int minheight
, text
= 0, space
= 0;
1394 n1
= (n
< ncols ? n
+1 : ncols
);
1396 this_height
= page_height
- headspace
;
1398 this_height
= page_height
;
1400 l
->bestcost
[n
] = -1;
1401 for (m
= l
; m
; m
= m
->next
) {
1402 if (m
!= l
&& m
->page_break
)
1403 break; /* we've gone as far as we can */
1406 if (m
->prev
->space_after
> 0)
1407 space
+= m
->prev
->space_after
;
1409 text
+= m
->prev
->space_after
;
1411 if (m
!= l
|| m
->page_break
) {
1412 if (m
->space_before
> 0)
1413 space
+= m
->space_before
;
1415 text
+= m
->space_before
;
1417 text
+= m
->line_height
;
1418 minheight
= text
+ space
;
1420 if (m
!= l
&& minheight
> this_height
)
1424 * If the space after this paragraph is _negative_
1425 * (which means the next line is folded on to this
1426 * one, which happens in the index), we absolutely
1427 * cannot break here.
1429 if (m
->space_after
>= 0) {
1432 * Compute the cost of this arrangement, as the
1433 * square of the amount of wasted space on the
1434 * page. Exception: if this is the last page
1435 * before a mandatory break or the document
1436 * end, we don't penalise a large blank area.
1438 if (m
!= last
&& m
->next
&& !m
->next
->page_break
)
1440 int x
= this_height
- minheight
;
1447 cost
+= (x
* xf
) >> 8;
1451 if (m
!= last
&& m
->next
&& !m
->next
->page_break
) {
1452 cost
+= m
->penalty_after
;
1453 cost
+= m
->next
->penalty_before
;
1456 if (m
!= last
&& m
->next
&& !m
->next
->page_break
)
1457 cost
+= m
->next
->bestcost
[n1
];
1458 if (l
->bestcost
[n
] == -1 || l
->bestcost
[n
] > cost
) {
1460 * This is the best option yet for this
1463 l
->bestcost
[n
] = cost
;
1464 if (m
!= last
&& m
->next
&& !m
->next
->page_break
)
1465 l
->vshortfall
[n
] = this_height
- minheight
;
1467 l
->vshortfall
[n
] = 0;
1469 l
->space
[n
] = space
;
1470 l
->page_last
[n
] = m
;
1481 * Now go through the line list forwards and assemble the
1490 int text
, space
, head
;
1492 page
= mknew(page_data
);
1501 page
->first_line
= l
;
1502 page
->last_line
= l
->page_last
[n
];
1504 page
->first_text
= page
->last_text
= NULL
;
1505 page
->first_xref
= page
->last_xref
= NULL
;
1506 page
->first_rect
= page
->last_rect
= NULL
;
1509 * Now assign a y-coordinate to each line on the page.
1512 head
= (n
< ncols ? headspace
: 0);
1513 for (l
= page
->first_line
; l
; l
= l
->next
) {
1514 if (l
!= page
->first_line
) {
1515 if (l
->prev
->space_after
> 0)
1516 space
+= l
->prev
->space_after
;
1518 text
+= l
->prev
->space_after
;
1520 if (l
!= page
->first_line
|| l
->page_break
) {
1521 if (l
->space_before
> 0)
1522 space
+= l
->space_before
;
1524 text
+= l
->space_before
;
1526 text
+= l
->line_height
;
1529 l
->ypos
= text
+ space
+ head
+
1530 space
* (float)page
->first_line
->vshortfall
[n
] /
1531 page
->first_line
->space
[n
];
1533 if (l
== page
->last_line
)
1537 l
= page
->last_line
;
1542 n
= (n
< ncols ? n
+1 : ncols
);
1548 static void add_rect_to_page(page_data
*page
, int x
, int y
, int w
, int h
)
1550 rect
*r
= mknew(rect
);
1553 if (page
->last_rect
)
1554 page
->last_rect
->next
= r
;
1556 page
->first_rect
= r
;
1557 page
->last_rect
= r
;
1565 static void add_string_to_page(page_data
*page
, int x
, int y
,
1566 font_encoding
*fe
, int size
, char *text
)
1568 text_fragment
*frag
;
1570 frag
= mknew(text_fragment
);
1573 if (page
->last_text
)
1574 page
->last_text
->next
= frag
;
1576 page
->first_text
= frag
;
1577 page
->last_text
= frag
;
1582 frag
->fontsize
= size
;
1583 frag
->text
= dupstr(text
);
1587 * Returns the updated x coordinate.
1589 static int render_string(page_data
*page
, font_data
*font
, int fontsize
,
1590 int x
, int y
, wchar_t *str
)
1593 int textpos
, textwid
, glyph
;
1594 font_encoding
*subfont
= NULL
, *sf
;
1596 text
= mknewa(char, 1 + ustrlen(str
));
1597 textpos
= textwid
= 0;
1600 glyph
= font
->bmp
[*str
];
1602 if (glyph
== 0xFFFF)
1603 continue; /* nothing more we can do here */
1606 * Find which subfont this character is going in.
1608 sf
= font
->subfont_map
[glyph
].subfont
;
1614 * This character is not yet in a subfont. Assign one.
1616 if (font
->latest_subfont
->free_pos
>= 0x100)
1617 font
->latest_subfont
= new_font_encoding(font
);
1619 c
= font
->latest_subfont
->free_pos
++;
1620 if (font
->latest_subfont
->free_pos
== 0x7F)
1621 font
->latest_subfont
->free_pos
= 0xA1;
1623 font
->subfont_map
[glyph
].subfont
= font
->latest_subfont
;
1624 font
->subfont_map
[glyph
].position
= c
;
1625 font
->latest_subfont
->vector
[c
] = font
->glyphs
[glyph
];
1626 font
->latest_subfont
->indices
[c
] = glyph
;
1627 font
->latest_subfont
->to_unicode
[c
] = *str
;
1629 sf
= font
->latest_subfont
;
1632 if (!subfont
|| sf
!= subfont
) {
1634 text
[textpos
] = '\0';
1635 add_string_to_page(page
, x
, y
, subfont
, fontsize
, text
);
1638 assert(textpos
== 0);
1644 text
[textpos
++] = font
->subfont_map
[glyph
].position
;
1645 textwid
+= font
->widths
[glyph
] * fontsize
;
1651 text
[textpos
] = '\0';
1652 add_string_to_page(page
, x
, y
, subfont
, fontsize
, text
);
1660 * Returns the updated x coordinate.
1662 static int render_text(page_data
*page
, para_data
*pdata
, line_data
*ldata
,
1663 int x
, int y
, word
*text
, word
*text_end
, xref
**xr
,
1664 int shortfall
, int nspaces
, int *nspace
,
1665 keywordlist
*keywords
, indexdata
*idx
)
1667 while (text
&& text
!= text_end
) {
1668 int style
, type
, findex
, errs
;
1672 switch (text
->type
) {
1674 * Start a cross-reference.
1676 case word_HyperLink
:
1677 case word_UpperXref
:
1678 case word_LowerXref
:
1681 if (text
->type
== word_HyperLink
) {
1683 dest
.url
= utoa_dup(text
->text
);
1685 } else if (text
->type
== word_PageXref
) {
1688 dest
.page
= (page_data
*)text
->private_data
;
1690 keyword
*kwl
= kw_lookup(keywords
, text
->text
);
1694 assert(kwl
->para
->private_data
);
1695 pdata
= (para_data
*) kwl
->para
->private_data
;
1697 dest
.page
= pdata
->first
->page
;
1701 * Shouldn't happen, but *shrug*
1708 if (dest
.type
!= NONE
) {
1710 (*xr
)->dest
= dest
; /* structure copy */
1711 if (page
->last_xref
)
1712 page
->last_xref
->next
= *xr
;
1714 page
->first_xref
= *xr
;
1715 page
->last_xref
= *xr
;
1719 * FIXME: Ideally we should have, and use, some
1720 * vertical font metric information here so that
1721 * our cross-ref rectangle can take account of
1722 * descenders and the font's cap height. This will
1723 * do for the moment, but it isn't ideal.
1725 (*xr
)->lx
= (*xr
)->rx
= x
;
1727 (*xr
)->ty
= y
+ ldata
->line_height
;
1732 * Finish extending a cross-reference box.
1740 * Add the current page number to the list of pages
1741 * referenced by an index entry.
1745 * We don't create index references in contents entries.
1747 if (!pdata
->contents_entry
) {
1751 tag
= index_findtag(idx
, text
->text
);
1755 for (i
= 0; i
< tag
->nrefs
; i
++) {
1756 indexentry
*entry
= tag
->refs
[i
];
1757 paper_idx
*pi
= (paper_idx
*)entry
->backend_data
;
1760 * If the same index term is indexed twice
1761 * within the same section, we only want to
1762 * mention it once in the index.
1764 if (pi
->lastpage
!= page
) {
1768 pi
->lastword
= pi
->lastword
->next
=
1770 pi
->lastword
= pi
->lastword
->next
=
1772 wp
= &pi
->lastword
->next
;
1776 pi
->lastword
= *wp
=
1777 fake_page_ref(page
);
1778 pi
->lastword
= pi
->lastword
->next
=
1779 fake_word(page
->number
);
1780 pi
->lastword
= pi
->lastword
->next
=
1784 pi
->lastpage
= page
;
1790 style
= towordstyle(text
->type
);
1791 type
= removeattr(text
->type
);
1793 findex
= (style
== word_Normal ? FONT_NORMAL
:
1794 style
== word_Emph ? FONT_EMPH
:
1797 if (type
== word_Normal
) {
1799 } else if (type
== word_WhiteSpace
) {
1800 x
+= pdata
->sizes
[findex
] *
1801 string_width(pdata
->fonts
[findex
], L
" ", NULL
);
1802 if (nspaces
&& findex
!= FONT_CODE
) {
1803 x
+= (*nspace
+1) * shortfall
/ nspaces
;
1804 x
-= *nspace
* shortfall
/ nspaces
;
1808 } else /* if (type == word_Quote) */ {
1809 if (text
->aux
== quote_Open
)
1810 str
= L
"\x2018"; /* FIXME: configurability! */
1812 str
= L
"\x2019"; /* FIXME: configurability! */
1815 (void) string_width(pdata
->fonts
[findex
], str
, &errs
);
1817 if (errs
&& text
->alt
)
1818 x
= render_text(page
, pdata
, ldata
, x
, y
, text
->alt
, NULL
,
1819 xr
, shortfall
, nspaces
, nspace
, keywords
, idx
);
1821 x
= render_string(page
, pdata
->fonts
[findex
],
1822 pdata
->sizes
[findex
], x
, y
, str
);
1835 * Returns the last x position used on the line.
1837 static int render_line(line_data
*ldata
, int left_x
, int top_y
,
1838 xref_dest
*dest
, keywordlist
*keywords
, indexdata
*idx
)
1844 if (ldata
->aux_text
) {
1848 x
= render_text(ldata
->page
, ldata
->pdata
, ldata
,
1849 left_x
+ ldata
->aux_left_indent
,
1850 top_y
- ldata
->ypos
,
1851 ldata
->aux_text
, NULL
, &xr
, 0, 0, &nspace
,
1853 if (ldata
->aux_text_2
)
1854 render_text(ldata
->page
, ldata
->pdata
, ldata
,
1855 x
, top_y
- ldata
->ypos
,
1856 ldata
->aux_text_2
, NULL
, &xr
, 0, 0, &nspace
,
1863 * There might be a cross-reference carried over from a
1866 if (dest
->type
!= NONE
) {
1869 xr
->dest
= *dest
; /* structure copy */
1870 if (ldata
->page
->last_xref
)
1871 ldata
->page
->last_xref
->next
= xr
;
1873 ldata
->page
->first_xref
= xr
;
1874 ldata
->page
->last_xref
= xr
;
1875 xr
->lx
= xr
->rx
= left_x
+ ldata
->xpos
;
1876 xr
->by
= top_y
- ldata
->ypos
;
1877 xr
->ty
= top_y
- ldata
->ypos
+ ldata
->line_height
;
1882 int extra_indent
, shortfall
, spaces
;
1883 int just
= ldata
->pdata
->justification
;
1886 * All forms of justification become JUST when we have
1887 * to squeeze the paragraph.
1889 if (ldata
->hshortfall
< 0)
1894 shortfall
= ldata
->hshortfall
;
1895 spaces
= ldata
->nspaces
;
1899 shortfall
= spaces
= extra_indent
= 0;
1902 shortfall
= spaces
= 0;
1903 extra_indent
= ldata
->real_shortfall
;
1907 ret
= render_text(ldata
->page
, ldata
->pdata
, ldata
,
1908 left_x
+ ldata
->xpos
+ extra_indent
,
1909 top_y
- ldata
->ypos
, ldata
->first
, ldata
->end
,
1910 &xr
, shortfall
, spaces
, &nspace
,
1916 * There's a cross-reference continued on to the next line.
1926 static void render_para(para_data
*pdata
, paper_conf
*conf
,
1927 keywordlist
*keywords
, indexdata
*idx
,
1928 paragraph
*index_placeholder
, page_data
*index_page
)
1932 page_data
*cxref_page
;
1941 for (ldata
= pdata
->first
; ldata
; ldata
= ldata
->next
) {
1943 * If this is a contents entry, we expect to have a single
1944 * enormous cross-reference rectangle covering the whole
1945 * thing. (Unless, of course, it spans multiple pages.)
1947 if (pdata
->contents_entry
&& ldata
->page
!= cxref_page
) {
1948 cxref_page
= ldata
->page
;
1949 cxref
= mknew(xref
);
1951 cxref
->dest
.type
= PAGE
;
1952 if (pdata
->contents_entry
== index_placeholder
) {
1953 cxref
->dest
.page
= index_page
;
1955 assert(pdata
->contents_entry
->private_data
);
1956 target
= (para_data
*)pdata
->contents_entry
->private_data
;
1957 cxref
->dest
.page
= target
->first
->page
;
1959 cxref
->dest
.url
= NULL
;
1960 if (ldata
->page
->last_xref
)
1961 ldata
->page
->last_xref
->next
= cxref
;
1963 ldata
->page
->first_xref
= cxref
;
1964 ldata
->page
->last_xref
= cxref
;
1965 cxref
->lx
= conf
->left_margin
;
1966 cxref
->rx
= conf
->paper_width
- conf
->right_margin
;
1967 cxref
->ty
= conf
->paper_height
- conf
->top_margin
1968 - ldata
->ypos
+ ldata
->line_height
;
1970 if (pdata
->contents_entry
) {
1971 assert(cxref
!= NULL
);
1972 cxref
->by
= conf
->paper_height
- conf
->top_margin
1976 last_x
= render_line(ldata
, conf
->left_margin
,
1977 conf
->paper_height
- conf
->top_margin
,
1978 &dest
, keywords
, idx
);
1979 if (ldata
== pdata
->last
)
1984 * If this is a contents entry, add leaders and a page
1987 if (pdata
->contents_entry
) {
1993 if (pdata
->contents_entry
== index_placeholder
) {
1994 num
= index_page
->number
;
1996 assert(pdata
->contents_entry
->private_data
);
1997 target
= (para_data
*)pdata
->contents_entry
->private_data
;
1998 num
= target
->first
->page
->number
;
2002 wid
= paper_width_simple(pdata
, w
);
2005 render_string(pdata
->last
->page
,
2006 pdata
->fonts
[FONT_NORMAL
],
2007 pdata
->sizes
[FONT_NORMAL
],
2008 conf
->paper_width
- conf
->right_margin
- wid
,
2009 (conf
->paper_height
- conf
->top_margin
-
2010 pdata
->last
->ypos
), num
);
2012 for (x
= 0; x
< conf
->base_width
; x
+= conf
->leader_separation
)
2013 if (x
- conf
->leader_separation
> last_x
- conf
->left_margin
&&
2014 x
+ conf
->leader_separation
< conf
->base_width
- wid
)
2015 render_string(pdata
->last
->page
,
2016 pdata
->fonts
[FONT_NORMAL
],
2017 pdata
->sizes
[FONT_NORMAL
],
2018 conf
->left_margin
+ x
,
2019 (conf
->paper_height
- conf
->top_margin
-
2020 pdata
->last
->ypos
), L
".");
2024 * Render any rectangle (chapter title underline or rule)
2025 * that goes with this paragraph.
2027 switch (pdata
->rect_type
) {
2028 case RECT_CHAPTER_UNDERLINE
:
2029 add_rect_to_page(pdata
->last
->page
,
2031 (conf
->paper_height
- conf
->top_margin
-
2033 conf
->chapter_underline_depth
),
2035 conf
->chapter_underline_thickness
);
2038 add_rect_to_page(pdata
->first
->page
,
2039 conf
->left_margin
+ pdata
->first
->xpos
,
2040 (conf
->paper_height
- conf
->top_margin
-
2042 pdata
->last
->line_height
),
2043 conf
->base_width
- pdata
->first
->xpos
,
2044 pdata
->last
->line_height
);
2046 default: /* placate gcc */
2051 static para_data
*code_paragraph(int indent
, word
*words
, paper_conf
*conf
)
2053 para_data
*pdata
= mknew(para_data
);
2056 * For code paragraphs, I'm going to hack grievously and
2057 * pretend the three normal fonts are the three code paragraph
2060 pdata
->fonts
[FONT_NORMAL
] = conf
->cb
;
2061 pdata
->fonts
[FONT_EMPH
] = conf
->co
;
2062 pdata
->fonts
[FONT_CODE
] = conf
->cr
;
2063 pdata
->sizes
[FONT_NORMAL
] =
2064 pdata
->sizes
[FONT_EMPH
] =
2065 pdata
->sizes
[FONT_CODE
] = 12;
2067 pdata
->first
= pdata
->last
= NULL
;
2068 pdata
->outline_level
= -1;
2069 pdata
->rect_type
= RECT_NONE
;
2070 pdata
->contents_entry
= NULL
;
2071 pdata
->justification
= LEFT
;
2073 for (; words
; words
= words
->next
) {
2074 wchar_t *t
, *e
, *start
;
2075 word
*lhead
= NULL
, *ltail
= NULL
, *w
;
2077 int prev
= -1, curr
;
2080 if (words
->next
&& words
->next
->type
== word_Emph
) {
2081 e
= words
->next
->text
;
2082 words
= words
->next
;
2092 else if (*e
== L
'i')
2094 else if (*e
== L
'b')
2111 * We've isolated a maximal subsequence of the line
2112 * which has the same emphasis. Form it into a word
2118 w
->type
= (prev
== 0 ? word_WeakCode
:
2119 prev
== 1 ? word_Emph
: word_Normal
);
2120 w
->text
= mknewa(wchar_t, t
-start
+1);
2121 memcpy(w
->text
, start
, (t
-start
) * sizeof(wchar_t));
2122 w
->text
[t
-start
] = '\0';
2135 ldata
= mknew(line_data
);
2137 ldata
->pdata
= pdata
;
2138 ldata
->first
= lhead
;
2140 ldata
->line_height
= conf
->base_font_size
* 4096;
2142 ldata
->xpos
= indent
;
2145 pdata
->last
->next
= ldata
;
2146 ldata
->prev
= pdata
->last
;
2148 pdata
->first
= ldata
;
2152 pdata
->last
= ldata
;
2154 ldata
->hshortfall
= 0;
2156 ldata
->aux_text
= NULL
;
2157 ldata
->aux_text_2
= NULL
;
2158 ldata
->aux_left_indent
= 0;
2159 /* General opprobrium for breaking in a code paragraph. */
2160 ldata
->penalty_before
= ldata
->penalty_after
= 50000;
2163 standard_line_spacing(pdata
, conf
);
2168 static para_data
*rule_paragraph(int indent
, paper_conf
*conf
)
2170 para_data
*pdata
= mknew(para_data
);
2173 ldata
= mknew(line_data
);
2175 ldata
->pdata
= pdata
;
2176 ldata
->first
= NULL
;
2178 ldata
->line_height
= conf
->rule_thickness
;
2180 ldata
->xpos
= indent
;
2185 ldata
->hshortfall
= 0;
2187 ldata
->aux_text
= NULL
;
2188 ldata
->aux_text_2
= NULL
;
2189 ldata
->aux_left_indent
= 0;
2192 * Better to break after a rule than before it
2194 ldata
->penalty_after
+= 100000;
2195 ldata
->penalty_before
+= -100000;
2197 pdata
->first
= pdata
->last
= ldata
;
2198 pdata
->outline_level
= -1;
2199 pdata
->rect_type
= RECT_RULE
;
2200 pdata
->contents_entry
= NULL
;
2201 pdata
->justification
= LEFT
;
2203 standard_line_spacing(pdata
, conf
);
2209 * Plain-text-like formatting for outline titles.
2211 static void paper_rdaddw(rdstring
*rs
, word
*text
) {
2212 for (; text
; text
= text
->next
) switch (text
->type
) {
2213 case word_HyperLink
:
2215 case word_UpperXref
:
2216 case word_LowerXref
:
2225 case word_WhiteSpace
:
2226 case word_EmphSpace
:
2227 case word_CodeSpace
:
2228 case word_WkCodeSpace
:
2230 case word_EmphQuote
:
2231 case word_CodeQuote
:
2232 case word_WkCodeQuote
:
2233 assert(text
->type
!= word_CodeQuote
&&
2234 text
->type
!= word_WkCodeQuote
);
2235 if (towordstyle(text
->type
) == word_Emph
&&
2236 (attraux(text
->aux
) == attr_First
||
2237 attraux(text
->aux
) == attr_Only
))
2238 rdadd(rs
, L
'_'); /* FIXME: configurability */
2239 else if (towordstyle(text
->type
) == word_Code
&&
2240 (attraux(text
->aux
) == attr_First
||
2241 attraux(text
->aux
) == attr_Only
))
2242 rdadd(rs
, L
'\''); /* FIXME: configurability */
2243 if (removeattr(text
->type
) == word_Normal
) {
2244 rdadds(rs
, text
->text
);
2245 } else if (removeattr(text
->type
) == word_WhiteSpace
) {
2247 } else if (removeattr(text
->type
) == word_Quote
) {
2248 rdadd(rs
, L
'\''); /* fixme: configurability */
2250 if (towordstyle(text
->type
) == word_Emph
&&
2251 (attraux(text
->aux
) == attr_Last
||
2252 attraux(text
->aux
) == attr_Only
))
2253 rdadd(rs
, L
'_'); /* FIXME: configurability */
2254 else if (towordstyle(text
->type
) == word_Code
&&
2255 (attraux(text
->aux
) == attr_Last
||
2256 attraux(text
->aux
) == attr_Only
))
2257 rdadd(rs
, L
'\''); /* FIXME: configurability */
2262 static wchar_t *prepare_outline_title(word
*first
, wchar_t *separator
,
2265 rdstring rs
= {0, 0, NULL
};
2268 paper_rdaddw(&rs
, first
);
2270 rdadds(&rs
, separator
);
2272 paper_rdaddw(&rs
, second
);
2277 static word
*fake_word(wchar_t *text
)
2279 word
*ret
= mknew(word
);
2282 ret
->type
= word_Normal
;
2283 ret
->text
= ustrdup(text
);
2284 ret
->breaks
= FALSE
;
2289 static word
*fake_space_word(void)
2291 word
*ret
= mknew(word
);
2294 ret
->type
= word_WhiteSpace
;
2301 static word
*fake_page_ref(page_data
*page
)
2303 word
*ret
= mknew(word
);
2306 ret
->type
= word_PageXref
;
2308 ret
->breaks
= FALSE
;
2310 ret
->private_data
= page
;
2314 static word
*fake_end_ref(void)
2316 word
*ret
= mknew(word
);
2319 ret
->type
= word_XrefEnd
;
2321 ret
->breaks
= FALSE
;
2326 static word
*prepare_contents_title(word
*first
, wchar_t *separator
,
2335 w
= dup_word_list(first
);
2343 w
= fake_word(separator
);
2349 *wptr
= dup_word_list(second
);
2355 static void fold_into_page(page_data
*dest
, page_data
*src
, int right_shift
)
2359 if (!src
->first_line
)
2362 if (dest
->last_line
) {
2363 dest
->last_line
->next
= src
->first_line
;
2364 src
->first_line
->prev
= dest
->last_line
;
2366 dest
->last_line
= src
->last_line
;
2368 for (ldata
= src
->first_line
; ldata
; ldata
= ldata
->next
) {
2370 ldata
->xpos
+= right_shift
;
2372 if (ldata
== src
->last_line
)