5ebf67e6e78e3cdc82b8e25eab0d8bf7b5e6ba0d
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
);
21 rdstringc main
, stream
;
31 static void pdf_string(void (*add
)(object
*, char const *),
32 object
*, char const *);
33 static void pdf_string_len(void (*add
)(object
*, char const *),
34 object
*, char const *, int);
35 static void objref(object
*o
, object
*dest
);
36 static void objdest(object
*o
, page_data
*p
);
38 static int is_std_font(char const *name
);
40 static void make_pages_node(object
*node
, object
*parent
, page_data
*first
,
41 page_data
*last
, object
*resources
,
43 static int make_outline(object
*parent
, outline_element
*start
, int n
,
45 static int pdf_versionid(FILE *fp
, word
*words
);
47 void pdf_backend(paragraph
*sourceform
, keywordlist
*keywords
,
48 indexdata
*idx
, void *vdoc
) {
49 document
*doc
= (document
*)vdoc
;
57 object
*o
, *info
, *cat
, *outlines
, *pages
, *resources
, *mediabox
;
63 filename
= dupstr("output.pdf");
64 for (p
= sourceform
; p
; p
= p
->next
) {
65 if (p
->type
== para_Config
) {
66 if (!ustricmp(p
->keyword
, L
"pdf-filename")) {
68 filename
= dupstr(adv(p
->origkeyword
));
73 olist
.head
= olist
.tail
= NULL
;
79 info
= new_object(&olist
);
80 objtext(info
, "<<\n");
81 if (doc
->n_outline_elements
> 0) {
86 pdf_outline_convert(doc
->outline_elements
->pdata
->outline_title
,
88 objtext(info
, "/Title ");
89 pdf_string_len(objtext
, info
, title
, titlelen
);
93 objtext(info
, "/Producer ");
94 sprintf(buf
, "Halibut, %s", version
);
95 pdf_string(objtext
, info
, buf
);
96 objtext(info
, "\n>>\n");
99 cat
= new_object(&olist
);
100 if (doc
->n_outline_elements
> 0)
101 outlines
= new_object(&olist
);
104 pages
= new_object(&olist
);
105 resources
= new_object(&olist
);
108 * The catalogue just contains references to the outlines and
109 * pages objects, and the pagelabels dictionary.
111 objtext(cat
, "<<\n/Type /Catalog");
113 objtext(cat
, "\n/Outlines ");
114 objref(cat
, outlines
);
116 objtext(cat
, "\n/Pages ");
118 /* Halibut just numbers pages 1, 2, 3, ... */
119 objtext(cat
, "\n/PageLabels<</Nums[0<</S/D>>]>>");
121 objtext(cat
, "\n/PageMode /UseOutlines");
122 objtext(cat
, "\n>>\n");
125 * Set up the resources dictionary, which mostly means
126 * providing all the font objects and names to call them by.
129 objtext(resources
, "<<\n/ProcSet [/PDF/Text]\n/Font <<\n");
130 for (fe
= doc
->fonts
->head
; fe
; fe
= fe
->next
) {
134 object
*font
, *fontdesc
;
136 font_info
const *fi
= fe
->font
->info
;
138 sprintf(fname
, "f%d", font_index
++);
139 fe
->name
= dupstr(fname
);
141 font
= new_object(&olist
);
143 objtext(resources
, "/");
144 objtext(resources
, fe
->name
);
145 objtext(resources
, " ");
146 objref(resources
, font
);
147 objtext(resources
, "\n");
151 * Construct those parts of the font descriptor that don't dependd
152 * on the file format.
154 if (!is_std_font(fe
->font
->info
->name
)) {
155 fontdesc
= new_object(&olist
);
157 #define FF_FIXEDPITCH 0x00000001
158 #define FF_SERIF 0x00000002
159 #define FF_SYMBOLIC 0x00000004
160 #define FF_SCRIPT 0x00000008
161 #define FF_NONSYMBOLIC 0x00000020
162 #define FF_ITALIC 0x00000040
163 #define FF_ALLCAP 0x00010000
164 #define FF_SMALLCAP 0x00020000
165 #define FF_FORCEBOLD 0x00040000
167 objtext(fontdesc
, "<<\n/Type /FontDescriptor\n/Name /");
168 objtext(fontdesc
, fi
->name
);
170 if (fi
->italicangle
) flags
|= FF_ITALIC
;
171 flags
|= FF_NONSYMBOLIC
;
172 sprintf(buf
, "\n/Flags %d\n", flags
);
173 objtext(fontdesc
, buf
);
174 sprintf(buf
, "/FontBBox [%g %g %g %g]\n", fi
->fontbbox
[0],
175 fi
->fontbbox
[1], fi
->fontbbox
[2], fi
->fontbbox
[3]);
176 objtext(fontdesc
, buf
);
177 sprintf(buf
, "/ItalicAngle %g\n", fi
->italicangle
);
178 objtext(fontdesc
, buf
);
179 sprintf(buf
, "/Ascent %g\n", fi
->ascent
);
180 objtext(fontdesc
, buf
);
181 sprintf(buf
, "/Descent %g\n", fi
->descent
);
182 objtext(fontdesc
, buf
);
183 sprintf(buf
, "/CapHeight %g\n", fi
->capheight
);
184 objtext(fontdesc
, buf
);
185 sprintf(buf
, "/XHeight %g\n", fi
->xheight
);
186 objtext(fontdesc
, buf
);
187 sprintf(buf
, "/StemH %g\n", fi
->stemh
);
188 objtext(fontdesc
, buf
);
189 sprintf(buf
, "/StemV %g\n", fi
->stemv
);
190 objtext(fontdesc
, buf
);
193 objtext(font
, "<<\n/Type /Font\n/BaseFont /");
194 objtext(font
, fe
->font
->info
->name
);
195 if (fe
->font
->info
->filetype
== TRUETYPE
) {
196 object
*cidfont
= new_object(&olist
);
197 object
*cmap
= new_object(&olist
);
198 unsigned short ranges
[256];
199 unsigned startidx
, nranges
, nchars
;
202 objtext(font
, "/Subtype/Type0\n/Encoding ");
203 objtext(cmap
, "<</Type/CMap\n/CMapName/");
204 objtext(cmap
, fe
->name
);
205 objtext(cmap
, "\n/CIDSystemInfo<</Registry(Adobe)"
206 "/Ordering(Identity)/Supplement 0>>\n");
207 objstream(cmap
, "%!PS-Adobe-3.0 Resource-CMap\n"
208 "%%DocumentNeededResources: procset CIDInit\n"
209 "%%IncludeResource: procset CIDInit\n"
210 "%%BeginResource: CMap ");
211 objstream(cmap
, fe
->name
);
212 objstream(cmap
, "\n%%Title (");
213 objstream(cmap
, fe
->name
);
214 objstream(cmap
, " Adobe Identity 0)\n%%Version: 1\n%%EndComments\n");
215 objstream(cmap
, "/CIDInit/ProcSet findresource begin\n");
216 objstream(cmap
, "12 dict begin begincmap\n");
217 objstream(cmap
, "/CIDSystemInfo 3 dict dup begin\n"
218 "/Registry(Adobe)def/Ordering(Identity)def"
219 "/Supplement 0 def end def\n");
220 objstream(cmap
, "/CMapName/");
221 objstream(cmap
, fe
->name
);
222 objstream(cmap
, " def/CMapType 0 def/WMode 0 def\n");
223 objstream(cmap
, "1 begincodespacerange<00><FF>"
224 "endcodespacerange\n");
225 start
= -1; nranges
= nchars
= 0;
226 for (i
= 0; i
< 256; i
++) {
230 if (fe
->vector
[i
] == NOGLYPH
)
232 idx
= sfnt_glyphtoindex(fe
->font
->info
->fontfile
,
234 if (start
>= 0 && idx
- startidx
== (unsigned)(i
- start
)) {
235 if (ranges
[start
] == 1) {
238 ranges
[start
] = i
- start
+ 1;
248 unsigned blk
= nranges
> 100 ?
100 : nranges
;
250 sprintf(buf
, "%u ", blk
);
251 objstream(cmap
, buf
);
252 objstream(cmap
, "begincidrange\n");
255 sprintf(buf
, "<%02X>", i
);
256 objstream(cmap
, buf
);
257 sprintf(buf
, "<%02X>", i
+ ranges
[i
] - 1);
258 objstream(cmap
, buf
);
259 sprintf(buf
, "%hu\n",
261 sfnt_glyphtoindex(fe
->font
->info
->fontfile
,
263 objstream(cmap
, buf
);
268 objstream(cmap
, "endcidrange\n");
272 unsigned blk
= nchars
> 100 ?
100 : nchars
;
274 sprintf(buf
, "%u ", blk
);
275 objstream(cmap
, buf
);
276 objstream(cmap
, "begincidchar\n");
278 if (ranges
[i
] == 1) {
279 sprintf(buf
, "<%02X>", i
);
280 objstream(cmap
, buf
);
281 sprintf(buf
, "%hu\n",
283 sfnt_glyphtoindex(fe
->font
->info
->fontfile
,
285 objstream(cmap
, buf
);
290 objstream(cmap
, "endcidchar\n");
292 objstream(cmap
, "endcmap CMapName currentdict /CMap "
293 "defineresource pop end end\n%%EndResource\n%%EOF\n");
296 objtext(font
, "\n/DescendantFonts[");
297 objref(font
, cidfont
);
298 objtext(font
, "]\n");
299 objtext(cidfont
, "<<\n/Type/Font\n/Subtype/CIDFontType2\n"
301 objtext(cidfont
, fe
->font
->info
->name
);
302 objtext(cidfont
, "\n/CIDSystemInfo<</Registry(Adobe)"
303 "/Ordering(Identity)/Supplement 0>>\n");
304 objtext(cidfont
, "/FontDescriptor ");
305 objref(cidfont
, fontdesc
);
306 objtext(cidfont
, "\n/W[0[");
307 for (i
= 0; i
< (int)sfnt_nglyphs(fe
->font
->info
->fontfile
); i
++) {
310 width
= find_width(fe
->font
,
311 sfnt_indextoglyph(fe
->font
->info
->fontfile
, i
));
312 sprintf(buf
, "%g ", 1000.0 * width
/ FUNITS_PER_PT
);
313 objtext(cidfont
, buf
);
315 objtext(cidfont
, "]]>>\n");
317 objtext(font
, "/Subtype /Type1\n");
318 objtext(font
, "\n/Encoding <<\n/Type /Encoding\n/Differences [");
320 for (i
= 0; i
< 256; i
++) {
322 if (fe
->vector
[i
] == NOGLYPH
)
325 sprintf(buf
, "\n%d", i
);
328 objtext(font
, i
% 8 ?
"/" : "\n/");
329 objtext(font
, glyph_extern(fe
->vector
[i
]));
333 objtext(font
, "\n]\n>>\n");
334 if (!is_std_font(fe
->font
->info
->name
)){
335 object
*widths
= new_object(&olist
);
336 int firstchar
= -1, lastchar
= -1;
337 for (i
= 0; i
< 256; i
++)
338 if (fe
->vector
[i
] != NOGLYPH
) {
339 if (firstchar
< 0) firstchar
= i
;
342 sprintf(buf
, "/FirstChar %d\n/LastChar %d\n/Widths ",
343 firstchar
, lastchar
);
345 objref(font
, widths
);
347 objtext(widths
, "[\n");
348 for (i
= firstchar
; i
<= lastchar
; i
++) {
350 if (fe
->vector
[i
] == NOGLYPH
)
353 width
= find_width(fe
->font
, fe
->vector
[i
]);
354 sprintf(buf
, "%g\n", 1000.0 * width
/ FUNITS_PER_PT
);
355 objtext(widths
, buf
);
357 objtext(widths
, "]\n");
358 objtext(font
, "/FontDescriptor ");
359 objref(font
, fontdesc
);
364 if (!is_std_font(fe
->font
->info
->name
)) {
365 if (fi
->fontfile
&& fi
->filetype
== TYPE1
) {
366 object
*fontfile
= new_object(&olist
);
370 pf_part1((font_info
*)fi
, &ffbuf
, &len
);
371 objstream_len(fontfile
, ffbuf
, len
);
373 sprintf(buf
, "<<\n/Length1 %lu\n", (unsigned long)len
);
374 objtext(fontfile
, buf
);
375 pf_part2((font_info
*)fi
, &ffbuf
, &len
);
376 objstream_len(fontfile
, ffbuf
, len
);
378 sprintf(buf
, "/Length2 %lu\n", (unsigned long)len
);
379 objtext(fontfile
, buf
);
380 objtext(fontfile
, "/Length3 0\n");
381 objtext(fontdesc
, "/FontFile ");
382 objref(fontdesc
, fontfile
);
383 } else if (fi
->fontfile
&& fi
->filetype
== TRUETYPE
) {
384 object
*fontfile
= new_object(&olist
);
388 sfnt_data((font_info
*)fi
, &ffbuf
, &len
);
389 objstream_len(fontfile
, ffbuf
, len
);
390 sprintf(buf
, "<<\n/Length1 %lu\n", (unsigned long)len
);
391 objtext(fontfile
, buf
);
392 objtext(fontdesc
, "/FontFile2 ");
393 objref(fontdesc
, fontfile
);
395 objtext(fontdesc
, "\n>>\n");
398 objtext(font
, "\n>>\n");
400 objtext(resources
, ">>\n>>\n");
404 mediabox
= new_object(&olist
);
405 sprintf(buf
, "[0 0 %g %g]\n",
406 doc
->paper_width
/ FUNITS_PER_PT
,
407 doc
->paper_height
/ FUNITS_PER_PT
);
408 objtext(mediabox
, buf
);
412 * Define the page objects for each page, and get each one
413 * ready to have a `Parent' specification added to it.
415 for (page
= doc
->pages
; page
; page
= page
->next
) {
418 opage
= new_object(&olist
);
420 objtext(opage
, "<<\n/Type /Page\n");
424 * Recursively build the page tree.
426 make_pages_node(pages
, NULL
, doc
->pages
, NULL
, resources
, mediabox
);
429 * Create and render the individual pages.
431 for (page
= doc
->pages
; page
; page
= page
->next
) {
432 object
*opage
, *cstr
;
434 text_fragment
*frag
, *frag_end
;
438 opage
= (object
*)page
->spare
;
440 * At this point the page dictionary is already
441 * half-written, with /Type and /Parent already present. We
442 * continue from there.
446 * The PDF spec says /Resources is required, but also says
447 * that it's inheritable and may be omitted if it's present
448 * in a Pages node. In our case it is: it's present in the
449 * topmost /Pages node because we carefully put it there.
450 * So we don't need a /Resources entry here. The same applies
455 * Now we're ready to define a content stream containing
456 * the actual text on the page.
458 cstr
= new_object(&olist
);
459 objtext(opage
, "/Contents ");
461 objtext(opage
, "\n");
464 * Render any rectangles on the page.
466 for (r
= page
->first_rect
; r
; r
= r
->next
) {
468 sprintf(buf
, "%g %g %g %g re f\n",
469 r
->x
/ FUNITS_PER_PT
, r
->y
/ FUNITS_PER_PT
,
470 r
->w
/ FUNITS_PER_PT
, r
->h
/ FUNITS_PER_PT
);
471 objstream(cstr
, buf
);
474 objstream(cstr
, "BT\n");
477 * PDF tracks two separate current positions: the position
478 * given in the `line matrix' and the position given in the
479 * `text matrix'. We must therefore track both as well.
480 * They start off at -1 (unset).
485 frag
= page
->first_text
;
488 * For compactness, I'm going to group text fragments
489 * into subsequences that use the same font+size. So
490 * first find the end of this subsequence.
492 for (frag_end
= frag
;
494 frag_end
->fe
== frag
->fe
&&
495 frag_end
->fontsize
== frag
->fontsize
);
496 frag_end
= frag_end
->next
);
499 * Now select the text fragment, and prepare to display
502 objstream(cstr
, "/");
503 objstream(cstr
, frag
->fe
->name
);
504 sprintf(buf
, " %d Tf ", frag
->fontsize
);
505 objstream(cstr
, buf
);
507 while (frag
&& frag
!= frag_end
) {
509 * Place the text position for the first piece of
513 sprintf(buf
, "1 0 0 1 %g %g Tm ",
514 frag
->x
/FUNITS_PER_PT
, frag
->y
/FUNITS_PER_PT
);
516 sprintf(buf
, "%g %g Td ",
517 (frag
->x
- lx
)/FUNITS_PER_PT
,
518 (frag
->y
- ly
)/FUNITS_PER_PT
);
520 objstream(cstr
, buf
);
525 * See if we're going to use Tj (show a single
526 * string) or TJ (show an array of strings with
527 * x-spacings between them). We determine this by
528 * seeing if there's more than one text fragment in
529 * sequence with the same y-coordinate.
531 if (frag
->next
&& frag
->next
!= frag_end
&&
532 frag
->next
->y
== y
) {
536 objstream(cstr
, "[");
537 while (frag
&& frag
!= frag_end
&& frag
->y
== y
) {
540 (x
- frag
->x
) * 1000.0 /
541 (FUNITS_PER_PT
* frag
->fontsize
));
542 objstream(cstr
, buf
);
544 pdf_string(objstream
, cstr
, frag
->text
);
545 x
= frag
->x
+ frag
->width
;
548 objstream(cstr
, "]TJ\n");
554 pdf_string(objstream
, cstr
, frag
->text
);
555 objstream(cstr
, "Tj\n");
560 objstream(cstr
, "ET");
563 * Also, we want an annotation dictionary containing the
564 * cross-references from this page.
566 if (page
->first_xref
) {
568 objtext(opage
, "/Annots [\n");
570 for (xr
= page
->first_xref
; xr
; xr
= xr
->next
) {
573 objtext(opage
, "<</Subtype/Link\n/Rect[");
574 sprintf(buf
, "%g %g %g %g",
575 xr
->lx
/ FUNITS_PER_PT
, xr
->by
/ FUNITS_PER_PT
,
576 xr
->rx
/ FUNITS_PER_PT
, xr
->ty
/ FUNITS_PER_PT
);
578 objtext(opage
, "]/Border[0 0 0]\n");
580 if (xr
->dest
.type
== PAGE
) {
581 objtext(opage
, "/Dest");
582 objdest(opage
, xr
->dest
.page
);
584 objtext(opage
, "/A<</S/URI/URI");
585 pdf_string(objtext
, opage
, xr
->dest
.url
);
586 objtext(opage
, ">>");
589 objtext(opage
, ">>\n");
592 objtext(opage
, "]\n");
595 objtext(opage
, ">>\n");
599 * Set up the outlines dictionary.
605 objtext(outlines
, "<<\n/Type /Outlines\n");
606 topcount
= make_outline(outlines
, doc
->outline_elements
,
607 doc
->n_outline_elements
, TRUE
);
608 sprintf(buf
, "/Count %d\n>>\n", topcount
);
609 objtext(outlines
, buf
);
613 * Assemble the final linear form of every object.
615 for (o
= olist
.head
; o
; o
= o
->next
) {
616 rdstringc rs
= {0, 0, NULL
};
618 deflate_compress_ctx
*zcontext
;
622 sprintf(text
, "%d 0 obj\n", o
->number
);
625 if (o
->stream
.text
) {
627 rdaddsc(&o
->main
, "<<\n");
628 #ifdef PDF_NOCOMPRESS
629 zlen
= o
->stream
.pos
;
630 zbuf
= snewn(zlen
, char);
631 memcpy(zbuf
, o
->stream
.text
, zlen
);
632 sprintf(text
, "/Length %d\n>>\n", zlen
);
634 zcontext
= deflate_compress_new(DEFLATE_TYPE_ZLIB
);
635 deflate_compress_data(zcontext
, o
->stream
.text
, o
->stream
.pos
,
636 DEFLATE_END_OF_DATA
, &zbuf
, &zlen
);
637 deflate_compress_free(zcontext
);
638 sprintf(text
, "/Filter/FlateDecode\n/Length %d\n>>\n", zlen
);
640 rdaddsc(&o
->main
, text
);
643 assert(o
->main
.text
);
644 rdaddsc(&rs
, o
->main
.text
);
647 if (rs
.text
[rs
.pos
-1] != '\n')
650 if (o
->stream
.text
) {
651 rdaddsc(&rs
, "stream\n");
652 rdaddsn(&rs
, zbuf
, zlen
);
653 rdaddsc(&rs
, "\nendstream\n");
654 sfree(o
->stream
.text
);
658 rdaddsc(&rs
, "endobj\n");
665 * Write out the PDF file.
668 if (!strcmp(filename
, "-"))
671 fp
= fopen(filename
, "wb");
673 error(err_cantopenw
, filename
);
678 * Header. I'm going to put the version IDs in the header as
679 * well, simply in PDF comments. The PDF Reference also suggests
680 * that binary PDF files contain four top-bit-set characters in
683 fileoff
= fprintf(fp
, "%%PDF-1.3\n%% L\xc3\xba\xc3\xb0""a\n");
684 for (p
= sourceform
; p
; p
= p
->next
)
685 if (p
->type
== para_VersionID
)
686 fileoff
+= pdf_versionid(fp
, p
->words
);
691 for (o
= olist
.head
; o
; o
= o
->next
) {
692 o
->fileoff
= fileoff
;
693 fwrite(o
->final
, 1, o
->size
, fp
);
698 * Cross-reference table
700 fprintf(fp
, "xref\n");
701 assert(olist
.head
->number
== 1);
702 fprintf(fp
, "0 %d\n", olist
.tail
->number
+ 1);
703 fprintf(fp
, "0000000000 65535 f \n");
704 for (o
= olist
.head
; o
; o
= o
->next
) {
706 sprintf(entry
, "%010d 00000 n \n", o
->fileoff
);
707 assert(strlen(entry
) == 20);
714 fprintf(fp
, "trailer\n<<\n/Size %d\n/Root %d 0 R\n/Info %d 0 R\n>>\n",
715 olist
.tail
->number
+ 1, cat
->number
, info
->number
);
716 fprintf(fp
, "startxref\n%d\n%%%%EOF\n", fileoff
);
724 object
*new_object(objlist
*list
)
726 object
*obj
= snew(object
);
730 obj
->main
.text
= NULL
;
731 obj
->main
.pos
= obj
->main
.size
= 0;
732 obj
->stream
.text
= NULL
;
733 obj
->stream
.pos
= obj
->stream
.size
= 0;
735 obj
->number
= list
->number
++;
739 list
->tail
->next
= obj
;
750 void objtext(object
*o
, char const *text
)
752 rdaddsc(&o
->main
, text
);
755 void objstream_len(object
*o
, char const *text
, size_t len
)
757 rdaddsn(&o
->stream
, text
, len
);
760 void objstream(object
*o
, char const *text
)
762 rdaddsc(&o
->stream
, text
);
765 static void objref(object
*o
, object
*dest
)
768 sprintf(buf
, "%d 0 R", dest
->number
);
769 rdaddsc(&o
->main
, buf
);
772 static void objdest(object
*o
, page_data
*p
) {
774 objref(o
, (object
*)p
->spare
);
775 objtext(o
, "/XYZ null null null]");
778 static char const * const stdfonts
[] = {
779 "Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic",
780 "Helvetica", "Helvetica-Bold", "Helvetica-Oblique","Helvetica-BoldOblique",
781 "Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique",
782 "Symbol", "ZapfDingbats"
785 static int is_std_font(char const *name
) {
787 for (i
= 0; i
< lenof(stdfonts
); i
++)
788 if (strcmp(name
, stdfonts
[i
]) == 0)
793 static void make_pages_node(object
*node
, object
*parent
, page_data
*first
,
794 page_data
*last
, object
*resources
,
801 objtext(node
, "<<\n/Type /Pages\n");
803 objtext(node
, "/Parent ");
804 objref(node
, parent
);
809 * Count the pages in this stretch, to see if there are few
810 * enough to reference directly.
813 for (page
= first
; page
; page
= page
->next
) {
819 sprintf(buf
, "/Count %d\n/Kids [\n", count
);
822 if (count
> TREE_BRANCH
) {
824 page_data
*thisfirst
, *thislast
;
828 for (i
= 0; i
< TREE_BRANCH
; i
++) {
829 int number
= (i
+1) * count
/ TREE_BRANCH
- i
* count
/ TREE_BRANCH
;
836 if (thisfirst
== thislast
) {
837 objref(node
, (object
*)thisfirst
->spare
);
838 objtext((object
*)thisfirst
->spare
, "/Parent ");
839 objref((object
*)thisfirst
->spare
, node
);
840 objtext((object
*)thisfirst
->spare
, "\n");
842 object
*newnode
= new_object(node
->list
);
843 make_pages_node(newnode
, node
, thisfirst
, thislast
,
845 objref(node
, newnode
);
850 assert(thislast
== last
|| page
== NULL
);
853 for (page
= first
; page
; page
= page
->next
) {
854 objref(node
, (object
*)page
->spare
);
856 objtext((object
*)page
->spare
, "/Parent ");
857 objref((object
*)page
->spare
, node
);
858 objtext((object
*)page
->spare
, "\n");
864 objtext(node
, "]\n");
867 objtext(node
, "/Resources ");
868 objref(node
, resources
);
872 objtext(node
, "/MediaBox ");
873 objref(node
, mediabox
);
877 objtext(node
, ">>\n");
881 * In text on the page, PDF uses the PostScript font model, which
882 * means that glyphs are identified by PS strings and hence font
883 * encoding can be managed independently of the supplied encoding
884 * of the font. However, in the document outline, the PDF spec
885 * encodes in either PDFDocEncoding (a custom superset of
886 * ISO-8859-1) or UTF-16BE.
888 char *pdf_outline_convert(wchar_t *s
, int *len
) {
891 ret
= utoa_careful_dup(s
, CS_PDF
);
894 * Very silly special case: if the returned string begins with
895 * FE FF, then the PDF reader will mistake it for a UTF-16BE
896 * string. So in this case we give up on PDFDocEncoding and
897 * encode it in UTF-16 straight away.
899 if (ret
&& ret
[0] == '\xFE' && ret
[1] == '\xFF') {
905 ret
= utoa_dup_len(s
, CS_UTF16BE
, len
);
913 static int make_outline(object
*parent
, outline_element
*items
, int n
,
916 int level
, totalcount
= 0;
917 outline_element
*itemp
;
918 object
*curr
, *prev
= NULL
, *first
= NULL
, *last
= NULL
;
922 level
= items
->level
;
929 * Here we expect to be sitting on an item at the given
930 * level. So we start by constructing an outline entry for
933 assert(items
->level
== level
);
935 title
= pdf_outline_convert(items
->pdata
->outline_title
, &titlelen
);
938 curr
= new_object(parent
->list
);
939 if (!first
) first
= curr
;
941 objtext(curr
, "<<\n/Title ");
942 pdf_string_len(objtext
, curr
, title
, titlelen
);
944 objtext(curr
, "\n/Parent ");
945 objref(curr
, parent
);
946 objtext(curr
, "\n/Dest");
947 objdest(curr
, items
->pdata
->first
->page
);
950 objtext(curr
, "/Prev ");
954 objtext(prev
, "/Next ");
956 objtext(prev
, "\n>>\n");
961 for (itemp
= items
; itemp
< items
+n
&& itemp
->level
> level
;
966 int count
= make_outline(curr
, items
, itemp
- items
, FALSE
);
971 sprintf(buf
, "/Count %d\n", count
);
978 objtext(prev
, ">>\n");
980 assert(first
&& last
);
981 objtext(parent
, "/First ");
982 objref(parent
, first
);
983 objtext(parent
, "\n/Last ");
984 objref(parent
, last
);
985 objtext(parent
, "\n");
990 static int pdf_versionid(FILE *fp
, word
*words
)
994 ret
= fprintf(fp
, "%% ");
996 for (; words
; words
= words
->next
) {
1000 switch (words
->type
) {
1001 case word_HyperLink
:
1003 case word_UpperXref
:
1004 case word_LowerXref
:
1010 type
= removeattr(words
->type
);
1014 text
= utoa_dup(words
->text
, CS_ASCII
);
1016 case word_WhiteSpace
:
1025 ret
+= strlen(text
);
1029 ret
+= fprintf(fp
, "\n");
1034 static void pdf_string_len(void (*add
)(object
*, char const *),
1035 object
*o
, char const *str
, int len
)
1040 for (p
= str
; len
> 0; p
++, len
--) {
1042 if (*p
< ' ' || *p
> '~') {
1043 sprintf(c
, "\\%03o", 0xFF & (int)*p
);
1046 if (*p
== '\\' || *p
== '(' || *p
== ')')
1056 static void pdf_string(void (*add
)(object
*, char const *),
1057 object
*o
, char const *str
)
1059 pdf_string_len(add
, o
, str
, strlen(str
));