67176bf6b74822a7e68331b937ecf49b489868e0
2 * PDF backend for Halibut
10 #define TREE_BRANCH 8 /* max branching factor in page tree */
12 paragraph
*pdf_config_filename(char *filename
)
14 return cmdline_cfg_simple("pdf-filename", filename
, NULL
);
17 typedef struct object_Tag object
;
18 typedef struct objlist_Tag objlist
;
24 rdstringc main
, stream
;
34 static object
*new_object(objlist
*list
);
35 static void objtext(object
*o
, char const *text
);
36 static void objstream(object
*o
, char const *text
);
37 static void pdf_string(void (*add
)(object
*, char const *),
38 object
*, char const *);
39 static void pdf_string_len(void (*add
)(object
*, char const *),
40 object
*, char const *, int);
41 static void objref(object
*o
, object
*dest
);
42 static char *pdf_outline_convert(wchar_t *s
, int *len
);
44 static int is_std_font(char const *name
);
46 static void make_pages_node(object
*node
, object
*parent
, page_data
*first
,
47 page_data
*last
, object
*resources
,
49 static int make_outline(object
*parent
, outline_element
*start
, int n
,
51 static int pdf_versionid(FILE *fp
, word
*words
);
53 void pdf_backend(paragraph
*sourceform
, keywordlist
*keywords
,
54 indexdata
*idx
, void *vdoc
) {
55 document
*doc
= (document
*)vdoc
;
64 object
*o
, *info
, *cat
, *outlines
, *pages
, *resources
, *mediabox
;
70 filename
= dupstr("output.pdf");
71 for (p
= sourceform
; p
; p
= p
->next
) {
72 if (p
->type
== para_Config
) {
73 if (!ustricmp(p
->keyword
, L
"pdf-filename")) {
75 filename
= dupstr(adv(p
->origkeyword
));
80 olist
.head
= olist
.tail
= NULL
;
86 info
= new_object(&olist
);
87 objtext(info
, "<<\n");
88 if (doc
->n_outline_elements
> 0) {
93 pdf_outline_convert(doc
->outline_elements
->pdata
->outline_title
,
95 objtext(info
, "/Title ");
96 pdf_string_len(objtext
, info
, title
, titlelen
);
100 objtext(info
, "/Producer ");
101 sprintf(buf
, "Halibut, %s", version
);
102 pdf_string(objtext
, info
, buf
);
103 objtext(info
, "\n>>\n");
106 cat
= new_object(&olist
);
107 if (doc
->n_outline_elements
> 0)
108 outlines
= new_object(&olist
);
111 pages
= new_object(&olist
);
112 resources
= new_object(&olist
);
115 * The catalogue just contains references to the outlines and
116 * pages objects, and the pagelabels dictionary.
118 objtext(cat
, "<<\n/Type /Catalog");
120 objtext(cat
, "\n/Outlines ");
121 objref(cat
, outlines
);
123 objtext(cat
, "\n/Pages ");
125 /* Halibut just numbers pages 1, 2, 3, ... */
126 objtext(cat
, "\n/PageLabels<</Nums[0<</S/D>>]>>");
128 objtext(cat
, "\n/PageMode /UseOutlines");
129 objtext(cat
, "\n>>\n");
132 * Set up the resources dictionary, which mostly means
133 * providing all the font objects and names to call them by.
136 objtext(resources
, "<<\n/ProcSet [/PDF/Text]\n/Font <<\n");
137 for (fe
= doc
->fonts
->head
; fe
; fe
= fe
->next
) {
142 sprintf(fname
, "f%d", font_index
++);
143 fe
->name
= dupstr(fname
);
145 font
= new_object(&olist
);
147 objtext(resources
, "/");
148 objtext(resources
, fe
->name
);
149 objtext(resources
, " ");
150 objref(resources
, font
);
151 objtext(resources
, "\n");
153 objtext(font
, "<<\n/Type /Font\n/Subtype /Type1\n/Name /");
154 objtext(font
, fe
->name
);
155 objtext(font
, "\n/BaseFont /");
156 objtext(font
, fe
->font
->info
->name
);
157 objtext(font
, "\n/Encoding <<\n/Type /Encoding\n/Differences [");
159 for (i
= 0; i
< 256; i
++) {
164 sprintf(buf
, "\n%d", i
);
167 objtext(font
, i
% 8 ?
"/" : "\n/");
168 objtext(font
, fe
->vector
[i
]);
172 objtext(font
, "\n]\n>>\n");
174 #define FF_FIXEDPITCH 0x00000001
175 #define FF_SERIF 0x00000002
176 #define FF_SYMBOLIC 0x00000004
177 #define FF_SCRIPT 0x00000008
178 #define FF_NONSYMBOLIC 0x00000020
179 #define FF_ITALIC 0x00000040
180 #define FF_ALLCAP 0x00010000
181 #define FF_SMALLCAP 0x00020000
182 #define FF_FORCEBOLD 0x00040000
184 if (!is_std_font(fe
->font
->info
->name
)){
185 object
*widths
= new_object(&olist
);
186 object
*fontdesc
= new_object(&olist
);
187 int firstchar
= -1, lastchar
= -1;
189 font_info
const *fi
= fe
->font
->info
;
191 for (i
= 0; i
< 256; i
++)
192 if (fe
->indices
[i
] >= 0) {
193 if (firstchar
< 0) firstchar
= i
;
196 sprintf(buf
, "/FirstChar %d\n/LastChar %d\n/Widths ",
197 firstchar
, lastchar
);
199 objref(font
, widths
);
201 objtext(widths
, "[\n");
202 for (i
= firstchar
; i
<= lastchar
; i
++) {
204 if (fe
->indices
[i
] < 0)
207 width
= fi
->widths
[fe
->indices
[i
]];
208 sprintf(buf
, "%g\n", 1000.0 * width
/ FUNITS_PER_PT
);
209 objtext(widths
, buf
);
211 objtext(widths
, "]\n");
212 objtext(font
, "/FontDescriptor ");
213 objref(font
, fontdesc
);
214 objtext(fontdesc
, "<<\n/Type /FontDescriptor\n/Name /");
215 objtext(fontdesc
, fi
->name
);
217 if (fi
->italicangle
) flags
|= FF_ITALIC
;
218 flags
|= FF_NONSYMBOLIC
;
219 sprintf(buf
, "\n/Flags %d\n", flags
);
220 objtext(fontdesc
, buf
);
221 sprintf(buf
, "/FontBBox [%g %g %g %g]\n", fi
->fontbbox
[0],
222 fi
->fontbbox
[1], fi
->fontbbox
[2], fi
->fontbbox
[3]);
223 objtext(fontdesc
, buf
);
224 sprintf(buf
, "/ItalicAngle %g\n", fi
->italicangle
);
225 objtext(fontdesc
, buf
);
226 sprintf(buf
, "/Ascent %g\n", fi
->ascent
);
227 objtext(fontdesc
, buf
);
228 sprintf(buf
, "/Descent %g\n", fi
->descent
);
229 objtext(fontdesc
, buf
);
230 sprintf(buf
, "/CapHeight %g\n", fi
->capheight
);
231 objtext(fontdesc
, buf
);
232 sprintf(buf
, "/XHeight %g\n", fi
->xheight
);
233 objtext(fontdesc
, buf
);
234 sprintf(buf
, "/StemH %g\n", fi
->stemh
);
235 objtext(fontdesc
, buf
);
236 sprintf(buf
, "/StemV %g\n", fi
->stemv
);
237 objtext(fontdesc
, buf
);
239 object
*fontfile
= new_object(&olist
);
244 len
= fread(buf
, 1, sizeof(buf
)-1, fi
->fp
);
246 objstream(fontfile
, buf
);
247 } while (len
== sizeof(buf
)-1);
248 objtext(fontdesc
, "/FontFile ");
249 objref(fontdesc
, fontfile
);
251 objtext(fontdesc
, "\n>>\n");
254 objtext(font
, "\n>>\n");
256 objtext(resources
, ">>\n>>\n");
260 mediabox
= new_object(&olist
);
261 sprintf(buf
, "[0 0 %g %g]\n",
262 doc
->paper_width
/ FUNITS_PER_PT
,
263 doc
->paper_height
/ FUNITS_PER_PT
);
264 objtext(mediabox
, buf
);
268 * Define the page objects for each page, and get each one
269 * ready to have a `Parent' specification added to it.
271 for (page
= doc
->pages
; page
; page
= page
->next
) {
274 opage
= new_object(&olist
);
276 objtext(opage
, "<<\n/Type /Page\n");
280 * Recursively build the page tree.
282 make_pages_node(pages
, NULL
, doc
->pages
, NULL
, resources
, mediabox
);
285 * Create and render the individual pages.
288 for (page
= doc
->pages
; page
; page
= page
->next
) {
289 object
*opage
, *cstr
;
291 text_fragment
*frag
, *frag_end
;
295 opage
= (object
*)page
->spare
;
297 * At this point the page dictionary is already
298 * half-written, with /Type and /Parent already present. We
299 * continue from there.
303 * The PDF spec says /Resources is required, but also says
304 * that it's inheritable and may be omitted if it's present
305 * in a Pages node. In our case it is: it's present in the
306 * topmost /Pages node because we carefully put it there.
307 * So we don't need a /Resources entry here. The same applies
312 * Now we're ready to define a content stream containing
313 * the actual text on the page.
315 cstr
= new_object(&olist
);
316 objtext(opage
, "/Contents ");
318 objtext(opage
, "\n");
321 * Render any rectangles on the page.
323 for (r
= page
->first_rect
; r
; r
= r
->next
) {
325 sprintf(buf
, "%g %g %g %g re f\n",
326 r
->x
/ FUNITS_PER_PT
, r
->y
/ FUNITS_PER_PT
,
327 r
->w
/ FUNITS_PER_PT
, r
->h
/ FUNITS_PER_PT
);
328 objstream(cstr
, buf
);
331 objstream(cstr
, "BT\n");
334 * PDF tracks two separate current positions: the position
335 * given in the `line matrix' and the position given in the
336 * `text matrix'. We must therefore track both as well.
337 * They start off at -1 (unset).
342 frag
= page
->first_text
;
345 * For compactness, I'm going to group text fragments
346 * into subsequences that use the same font+size. So
347 * first find the end of this subsequence.
349 for (frag_end
= frag
;
351 frag_end
->fe
== frag
->fe
&&
352 frag_end
->fontsize
== frag
->fontsize
);
353 frag_end
= frag_end
->next
);
356 * Now select the text fragment, and prepare to display
359 objstream(cstr
, "/");
360 objstream(cstr
, frag
->fe
->name
);
361 sprintf(buf
, " %d Tf ", frag
->fontsize
);
362 objstream(cstr
, buf
);
364 while (frag
&& frag
!= frag_end
) {
366 * Place the text position for the first piece of
370 sprintf(buf
, "1 0 0 1 %g %g Tm ",
371 frag
->x
/FUNITS_PER_PT
, frag
->y
/FUNITS_PER_PT
);
373 sprintf(buf
, "%g %g Td ",
374 (frag
->x
- lx
)/FUNITS_PER_PT
,
375 (frag
->y
- ly
)/FUNITS_PER_PT
);
377 objstream(cstr
, buf
);
382 * See if we're going to use Tj (show a single
383 * string) or TJ (show an array of strings with
384 * x-spacings between them). We determine this by
385 * seeing if there's more than one text fragment in
386 * sequence with the same y-coordinate.
388 if (frag
->next
&& frag
->next
!= frag_end
&&
389 frag
->next
->y
== y
) {
393 objstream(cstr
, "[");
394 while (frag
&& frag
!= frag_end
&& frag
->y
== y
) {
397 (x
- frag
->x
) * 1000.0 /
398 (FUNITS_PER_PT
* frag
->fontsize
));
399 objstream(cstr
, buf
);
401 pdf_string(objstream
, cstr
, frag
->text
);
402 x
= frag
->x
+ frag
->width
;
405 objstream(cstr
, "]TJ\n");
411 pdf_string(objstream
, cstr
, frag
->text
);
412 objstream(cstr
, "Tj\n");
417 objstream(cstr
, "ET");
420 * Also, we want an annotation dictionary containing the
421 * cross-references from this page.
423 if (page
->first_xref
) {
425 objtext(opage
, "/Annots [\n");
427 for (xr
= page
->first_xref
; xr
; xr
= xr
->next
) {
430 objtext(opage
, "<</Subtype/Link\n/Rect[");
431 sprintf(buf
, "%g %g %g %g",
432 xr
->lx
/ FUNITS_PER_PT
, xr
->by
/ FUNITS_PER_PT
,
433 xr
->rx
/ FUNITS_PER_PT
, xr
->ty
/ FUNITS_PER_PT
);
435 objtext(opage
, "]/Border[0 0 0]\n");
437 if (xr
->dest
.type
== PAGE
) {
438 objtext(opage
, "/Dest[");
439 objref(opage
, (object
*)xr
->dest
.page
->spare
);
440 objtext(opage
, "/XYZ null null null]");
442 objtext(opage
, "/A<</S/URI/URI");
443 pdf_string(objtext
, opage
, xr
->dest
.url
);
444 objtext(opage
, ">>");
447 objtext(opage
, ">>\n");
450 objtext(opage
, "]\n");
453 objtext(opage
, ">>\n");
457 * Set up the outlines dictionary.
463 objtext(outlines
, "<<\n/Type /Outlines\n");
464 topcount
= make_outline(outlines
, doc
->outline_elements
,
465 doc
->n_outline_elements
, TRUE
);
466 sprintf(buf
, "/Count %d\n>>\n", topcount
);
467 objtext(outlines
, buf
);
471 * Assemble the final linear form of every object.
473 for (o
= olist
.head
; o
; o
= o
->next
) {
474 rdstringc rs
= {0, 0, NULL
};
476 deflate_compress_ctx
*zcontext
;
480 sprintf(text
, "%d 0 obj\n", o
->number
);
483 if (!o
->main
.text
&& o
->stream
.text
) {
484 zcontext
= deflate_compress_new(DEFLATE_TYPE_ZLIB
);
485 deflate_compress_data(zcontext
, o
->stream
.text
, o
->stream
.pos
,
486 DEFLATE_END_OF_DATA
, &zbuf
, &zlen
);
487 deflate_compress_free(zcontext
);
488 sprintf(text
, "<<\n/Filter/FlateDecode\n/Length %d\n>>\n", zlen
);
489 rdaddsc(&o
->main
, text
);
492 assert(o
->main
.text
);
493 rdaddsc(&rs
, o
->main
.text
);
496 if (rs
.text
[rs
.pos
-1] != '\n')
499 if (o
->stream
.text
) {
500 rdaddsc(&rs
, "stream\n");
501 rdaddsn(&rs
, zbuf
, zlen
);
502 rdaddsc(&rs
, "\nendstream\n");
503 sfree(o
->stream
.text
);
507 rdaddsc(&rs
, "endobj\n");
514 * Write out the PDF file.
517 fp
= fopen(filename
, "wb");
519 error(err_cantopenw
, filename
);
524 * Header. I'm going to put the version IDs in the header as
525 * well, simply in PDF comments. The PDF Reference also suggests
526 * that binary PDF files contain four top-bit-set characters in
529 fileoff
= fprintf(fp
, "%%PDF-1.3\n%% L\xc3\xba\xc3\xb0""a\n");
530 for (p
= sourceform
; p
; p
= p
->next
)
531 if (p
->type
== para_VersionID
)
532 fileoff
+= pdf_versionid(fp
, p
->words
);
537 for (o
= olist
.head
; o
; o
= o
->next
) {
538 o
->fileoff
= fileoff
;
539 fwrite(o
->final
, 1, o
->size
, fp
);
544 * Cross-reference table
546 fprintf(fp
, "xref\n");
547 assert(olist
.head
->number
== 1);
548 fprintf(fp
, "0 %d\n", olist
.tail
->number
+ 1);
549 fprintf(fp
, "0000000000 65535 f \n");
550 for (o
= olist
.head
; o
; o
= o
->next
) {
552 sprintf(entry
, "%010d 00000 n \n", o
->fileoff
);
553 assert(strlen(entry
) == 20);
560 fprintf(fp
, "trailer\n<<\n/Size %d\n/Root %d 0 R\n/Info %d 0 R\n>>\n",
561 olist
.tail
->number
+ 1, cat
->number
, info
->number
);
562 fprintf(fp
, "startxref\n%d\n%%%%EOF\n", fileoff
);
569 static object
*new_object(objlist
*list
)
571 object
*obj
= snew(object
);
575 obj
->main
.text
= NULL
;
576 obj
->main
.pos
= obj
->main
.size
= 0;
577 obj
->stream
.text
= NULL
;
578 obj
->stream
.pos
= obj
->stream
.size
= 0;
580 obj
->number
= list
->number
++;
584 list
->tail
->next
= obj
;
595 static void objtext(object
*o
, char const *text
)
597 rdaddsc(&o
->main
, text
);
600 static void objstream(object
*o
, char const *text
)
602 rdaddsc(&o
->stream
, text
);
605 static void objref(object
*o
, object
*dest
)
608 sprintf(buf
, "%d 0 R", dest
->number
);
609 rdaddsc(&o
->main
, buf
);
612 static char const * const stdfonts
[] = {
613 "Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic",
614 "Helvetica", "Helvetica-Bold", "Helvetica-Oblique","Helvetica-BoldOblique",
615 "Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique",
616 "Symbol", "ZapfDingbats"
619 static int is_std_font(char const *name
) {
621 for (i
= 0; i
< lenof(stdfonts
); i
++)
622 if (strcmp(name
, stdfonts
[i
]) == 0)
627 static void make_pages_node(object
*node
, object
*parent
, page_data
*first
,
628 page_data
*last
, object
*resources
,
635 objtext(node
, "<<\n/Type /Pages\n");
637 objtext(node
, "/Parent ");
638 objref(node
, parent
);
643 * Count the pages in this stretch, to see if there are few
644 * enough to reference directly.
647 for (page
= first
; page
; page
= page
->next
) {
653 sprintf(buf
, "/Count %d\n/Kids [\n", count
);
656 if (count
> TREE_BRANCH
) {
658 page_data
*thisfirst
, *thislast
;
662 for (i
= 0; i
< TREE_BRANCH
; i
++) {
663 int number
= (i
+1) * count
/ TREE_BRANCH
- i
* count
/ TREE_BRANCH
;
670 if (thisfirst
== thislast
) {
671 objref(node
, (object
*)thisfirst
->spare
);
672 objtext((object
*)thisfirst
->spare
, "/Parent ");
673 objref((object
*)thisfirst
->spare
, node
);
674 objtext((object
*)thisfirst
->spare
, "\n");
676 object
*newnode
= new_object(node
->list
);
677 make_pages_node(newnode
, node
, thisfirst
, thislast
,
679 objref(node
, newnode
);
684 assert(thislast
== last
|| page
== NULL
);
687 for (page
= first
; page
; page
= page
->next
) {
688 objref(node
, (object
*)page
->spare
);
690 objtext((object
*)page
->spare
, "/Parent ");
691 objref((object
*)page
->spare
, node
);
692 objtext((object
*)page
->spare
, "\n");
698 objtext(node
, "]\n");
701 objtext(node
, "/Resources ");
702 objref(node
, resources
);
706 objtext(node
, "/MediaBox ");
707 objref(node
, mediabox
);
711 objtext(node
, ">>\n");
715 * In text on the page, PDF uses the PostScript font model, which
716 * means that glyphs are identified by PS strings and hence font
717 * encoding can be managed independently of the supplied encoding
718 * of the font. However, in the document outline, the PDF spec
719 * encodes in either PDFDocEncoding (a custom superset of
720 * ISO-8859-1) or UTF-16BE.
722 static char *pdf_outline_convert(wchar_t *s
, int *len
) {
725 ret
= utoa_careful_dup(s
, CS_PDF
);
728 * Very silly special case: if the returned string begins with
729 * FE FF, then the PDF reader will mistake it for a UTF-16BE
730 * string. So in this case we give up on PDFDocEncoding and
731 * encode it in UTF-16 straight away.
733 if (ret
&& ret
[0] == '\xFE' && ret
[1] == '\xFF') {
739 ret
= utoa_dup_len(s
, CS_UTF16BE
, len
);
747 static int make_outline(object
*parent
, outline_element
*items
, int n
,
750 int level
, totalcount
= 0;
751 outline_element
*itemp
;
752 object
*curr
, *prev
= NULL
, *first
= NULL
, *last
= NULL
;
756 level
= items
->level
;
763 * Here we expect to be sitting on an item at the given
764 * level. So we start by constructing an outline entry for
767 assert(items
->level
== level
);
769 title
= pdf_outline_convert(items
->pdata
->outline_title
, &titlelen
);
772 curr
= new_object(parent
->list
);
773 if (!first
) first
= curr
;
775 objtext(curr
, "<<\n/Title ");
776 pdf_string_len(objtext
, curr
, title
, titlelen
);
778 objtext(curr
, "\n/Parent ");
779 objref(curr
, parent
);
780 objtext(curr
, "\n/Dest [");
781 objref(curr
, (object
*)items
->pdata
->first
->page
->spare
);
782 objtext(curr
, " /XYZ null null null]\n");
784 objtext(curr
, "/Prev ");
788 objtext(prev
, "/Next ");
790 objtext(prev
, "\n>>\n");
795 for (itemp
= items
; itemp
< items
+n
&& itemp
->level
> level
;
800 int count
= make_outline(curr
, items
, itemp
- items
, FALSE
);
805 sprintf(buf
, "/Count %d\n", count
);
812 objtext(prev
, ">>\n");
814 assert(first
&& last
);
815 objtext(parent
, "/First ");
816 objref(parent
, first
);
817 objtext(parent
, "\n/Last ");
818 objref(parent
, last
);
819 objtext(parent
, "\n");
824 static int pdf_versionid(FILE *fp
, word
*words
)
828 ret
= fprintf(fp
, "%% ");
830 for (; words
; words
= words
->next
) {
834 switch (words
->type
) {
844 type
= removeattr(words
->type
);
848 text
= utoa_dup(words
->text
, CS_ASCII
);
850 case word_WhiteSpace
:
863 ret
+= fprintf(fp
, "\n");
868 static void pdf_string_len(void (*add
)(object
*, char const *),
869 object
*o
, char const *str
, int len
)
874 for (p
= str
; len
> 0; p
++, len
--) {
876 if (*p
< ' ' || *p
> '~') {
877 sprintf(c
, "\\%03o", 0xFF & (int)*p
);
880 if (*p
== '\\' || *p
== '(' || *p
== ')')
890 static void pdf_string(void (*add
)(object
*, char const *),
891 object
*o
, char const *str
)
893 pdf_string_len(add
, o
, str
, strlen(str
));