/* This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks. Copyright (C) 2008-2016 by Jin-Hwan Cho, Matthias Franz, and Shunsaku Hirata, the dvipdfmx project team. Copyright (C) 1998, 1999 by Mark A. Wicks This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ /* * TODO: Many things... * {begin,end}_{bead,article}, box stack, name tree (not limited to dests)... */ #ifdef HAVE_CONFIG_H #include #endif #include #include "system.h" #include "mem.h" #include "error.h" #include "mfileio.h" #include "numbers.h" #include "pdfobj.h" #include "pdfparse.h" #include "pdfnames.h" #include "pdfencrypt.h" #include "dvipdfmx.h" #include "pdfdev.h" #include "pdfdraw.h" #include "pdfcolor.h" #include "pdfresource.h" #include "pdffont.h" #include "pdfximage.h" #include "pdflimits.h" #if HAVE_LIBPNG #include "pngimage.h" #endif #include "jpegimage.h" #include "pdfdoc.h" #define PDFDOC_PAGES_ALLOC_SIZE 128u #define PDFDOC_ARTICLE_ALLOC_SIZE 16 #define PDFDOC_BEAD_ALLOC_SIZE 16 static int verbose = 0; static char manual_thumb_enabled = 0; static char *thumb_basename = NULL; void pdf_doc_enable_manual_thumbnails (void) { #if HAVE_LIBPNG manual_thumb_enabled = 1; #else WARN("Manual thumbnail is not supported without the libpng library."); #endif } static pdf_obj * read_thumbnail (const char *thumb_filename) { pdf_obj *image_ref; int xobj_id; FILE *fp; load_options options = {1, 0, NULL}; fp = MFOPEN(thumb_filename, FOPEN_RBIN_MODE); if (!fp) { WARN("Could not open thumbnail file \"%s\"", thumb_filename); return NULL; } if (!check_for_png(fp) && !check_for_jpeg(fp)) { WARN("Thumbnail \"%s\" not a png/jpeg file!", thumb_filename); MFCLOSE(fp); return NULL; } MFCLOSE(fp); xobj_id = pdf_ximage_findresource(thumb_filename, options); if (xobj_id < 0) { WARN("Could not read thumbnail file \"%s\".", thumb_filename); image_ref = NULL; } else { image_ref = pdf_ximage_get_reference(xobj_id); } return image_ref; } void pdf_doc_set_verbose (void) { verbose++; pdf_font_set_verbose(); pdf_color_set_verbose(); pdf_ximage_set_verbose(); } typedef struct pdf_form { char *ident; pdf_tmatrix matrix; pdf_rect cropbox; pdf_obj *resources; pdf_obj *contents; } pdf_form; struct form_list_node { int q_depth; pdf_form form; struct form_list_node *prev; }; #define USE_MY_MEDIABOX (1 << 0) typedef struct pdf_page { pdf_obj *page_obj; pdf_obj *page_ref; int flags; double ref_x, ref_y; pdf_rect cropbox; pdf_obj *resources; /* Contents */ pdf_obj *background; pdf_obj *contents; /* global bop, background, contents, global eop */ pdf_obj *content_refs[4]; pdf_obj *annots; pdf_obj *beads; } pdf_page; typedef struct pdf_olitem { pdf_obj *dict; int is_open; struct pdf_olitem *first; struct pdf_olitem *parent; struct pdf_olitem *next; } pdf_olitem; typedef struct pdf_bead { char *id; int page_no; pdf_rect rect; } pdf_bead; typedef struct pdf_article { char *id; pdf_obj *info; int num_beads; int max_beads; pdf_bead *beads; } pdf_article; struct name_dict { const char *category; struct ht_table *data; }; typedef struct pdf_doc { struct { pdf_obj *dict; pdf_obj *viewerpref; pdf_obj *pagelabels; pdf_obj *pages; pdf_obj *names; pdf_obj *threads; } root; pdf_obj *info; struct { pdf_rect mediabox; pdf_obj *bop, *eop; int num_entries; /* This is not actually total number of pages. */ int max_entries; pdf_page *entries; } pages; struct { pdf_olitem *first; pdf_olitem *current; int current_depth; } outlines; struct { int num_entries; int max_entries; pdf_article *entries; } articles; struct name_dict *names; int check_gotos; struct ht_table gotos; struct { int outline_open_depth; double annot_grow; } opt; struct form_list_node *pending_forms; } pdf_doc; static pdf_doc pdoc; static void pdf_doc_init_catalog (pdf_doc *p) { p->root.viewerpref = NULL; p->root.pagelabels = NULL; p->root.pages = NULL; p->root.names = NULL; p->root.threads = NULL; p->root.dict = pdf_new_dict(); pdf_set_root(p->root.dict); return; } static void pdf_doc_close_catalog (pdf_doc *p) { pdf_obj *tmp; if (p->root.viewerpref) { tmp = pdf_lookup_dict(p->root.dict, "ViewerPreferences"); if (!tmp) { pdf_add_dict(p->root.dict, pdf_new_name("ViewerPreferences"), pdf_ref_obj (p->root.viewerpref)); } else if (PDF_OBJ_DICTTYPE(tmp)) { pdf_merge_dict(p->root.viewerpref, tmp); pdf_add_dict(p->root.dict, pdf_new_name("ViewerPreferences"), pdf_ref_obj (p->root.viewerpref)); } else { /* Maybe reference */ /* What should I do? */ WARN("Could not modify ViewerPreferences."); } pdf_release_obj(p->root.viewerpref); p->root.viewerpref = NULL; } if (p->root.pagelabels) { tmp = pdf_lookup_dict(p->root.dict, "PageLabels"); if (!tmp) { tmp = pdf_new_dict(); pdf_add_dict(tmp, pdf_new_name("Nums"), pdf_link_obj(p->root.pagelabels)); pdf_add_dict(p->root.dict, pdf_new_name("PageLabels"), pdf_ref_obj(tmp)); pdf_release_obj(tmp); } else { /* Maybe reference */ /* What should I do? */ WARN("Could not modify PageLabels."); } pdf_release_obj(p->root.pagelabels); p->root.pagelabels = NULL; } pdf_add_dict(p->root.dict, pdf_new_name("Type"), pdf_new_name("Catalog")); pdf_release_obj(p->root.dict); p->root.dict = NULL; return; } /* * Pages are starting at 1. * The page count does not increase until the page is finished. */ #define LASTPAGE(p) (&(p->pages.entries[p->pages.num_entries])) #define FIRSTPAGE(p) (&(p->pages.entries[0])) #define PAGECOUNT(p) (p->pages.num_entries) #define MAXPAGES(p) (p->pages.max_entries) static void doc_resize_page_entries (pdf_doc *p, int size) { if (size > MAXPAGES(p)) { int i; p->pages.entries = RENEW(p->pages.entries, size, struct pdf_page); for (i = p->pages.max_entries; i < size; i++) { p->pages.entries[i].page_obj = NULL; p->pages.entries[i].page_ref = NULL; p->pages.entries[i].flags = 0; p->pages.entries[i].resources = NULL; p->pages.entries[i].background = NULL; p->pages.entries[i].contents = NULL; p->pages.entries[i].content_refs[0] = NULL; /* global bop */ p->pages.entries[i].content_refs[1] = NULL; /* background */ p->pages.entries[i].content_refs[2] = NULL; /* page body */ p->pages.entries[i].content_refs[3] = NULL; /* global eop */ p->pages.entries[i].annots = NULL; p->pages.entries[i].beads = NULL; } p->pages.max_entries = size; } return; } static pdf_page * doc_get_page_entry (pdf_doc *p, unsigned int page_no) { pdf_page *page; if (page_no > 65535ul) { ERROR("Page number %ul too large!", page_no); } else if (page_no == 0) { ERROR("Invalid Page number %ul.", page_no); } if (page_no > MAXPAGES(p)) { doc_resize_page_entries(p, page_no + PDFDOC_PAGES_ALLOC_SIZE); } page = &(p->pages.entries[page_no - 1]); return page; } static void pdf_doc_init_page_tree (pdf_doc *p, double media_width, double media_height); static void pdf_doc_close_page_tree (pdf_doc *p); static void pdf_doc_init_names (pdf_doc *p, int check_gotos); static void pdf_doc_close_names (pdf_doc *p); static void pdf_doc_add_goto (pdf_obj *annot_dict); static void pdf_doc_init_docinfo (pdf_doc *p); static void pdf_doc_close_docinfo (pdf_doc *p); static void pdf_doc_init_articles (pdf_doc *p); static void pdf_doc_close_articles (pdf_doc *p); static void pdf_doc_init_bookmarks (pdf_doc *p, int bm_open_depth); static void pdf_doc_close_bookmarks (pdf_doc *p); void pdf_doc_set_bop_content (const char *content, unsigned length) { pdf_doc *p = &pdoc; ASSERT(p); if (p->pages.bop) { pdf_release_obj(p->pages.bop); p->pages.bop = NULL; } if (length > 0) { p->pages.bop = pdf_new_stream(STREAM_COMPRESS); pdf_add_stream(p->pages.bop, content, length); } else { p->pages.bop = NULL; } return; } void pdf_doc_set_eop_content (const char *content, unsigned length) { pdf_doc *p = &pdoc; if (p->pages.eop) { pdf_release_obj(p->pages.eop); p->pages.eop = NULL; } if (length > 0) { p->pages.eop = pdf_new_stream(STREAM_COMPRESS); pdf_add_stream(p->pages.eop, content, length); } else { p->pages.eop = NULL; } return; } #ifndef HAVE_TM_GMTOFF #ifndef HAVE_TIMEZONE /* auxiliary function to compute timezone offset on systems that do not support the tm_gmtoff in struct tm, or have a timezone variable. Such as i386-solaris. */ static int32_t compute_timezone_offset() { time_t now; struct tm tm; struct tm local; time_t gmtoff; now = get_unique_time_if_given(); if (now == INVALID_EPOCH_VALUE) { now = time(NULL); localtime_r(&now, &local); gmtime_r(&now, &tm); return (mktime(&local) - mktime(&tm)); } else { return(0); } } #endif /* HAVE_TIMEZONE */ #endif /* HAVE_TM_GMTOFF */ /* * Docinfo */ static int asn_date (char *date_string) { int32_t tz_offset; time_t current_time; struct tm *bd_time; current_time = get_unique_time_if_given(); if (current_time == INVALID_EPOCH_VALUE) { time(¤t_time); bd_time = localtime(¤t_time); #ifdef HAVE_TM_GMTOFF tz_offset = bd_time->tm_gmtoff; #else # ifdef HAVE_TIMEZONE tz_offset = -timezone; # else tz_offset = compute_timezone_offset(); # endif /* HAVE_TIMEZONE */ #endif /* HAVE_TM_GMTOFF */ } else { bd_time = gmtime(¤t_time); tz_offset = 0; } sprintf(date_string, "D:%04d%02d%02d%02d%02d%02d%c%02d'%02d'", bd_time->tm_year + 1900, bd_time->tm_mon + 1, bd_time->tm_mday, bd_time->tm_hour, bd_time->tm_min, bd_time->tm_sec, (tz_offset > 0) ? '+' : '-', abs(tz_offset) / 3600, (abs(tz_offset) / 60) % 60); return strlen(date_string); } static void pdf_doc_init_docinfo (pdf_doc *p) { p->info = pdf_new_dict(); pdf_set_info(p->info); return; } static void pdf_doc_close_docinfo (pdf_doc *p) { pdf_obj *docinfo = p->info; /* * Excerpt from PDF Reference 4th ed., sec. 10.2.1. * * Any entry whose value is not known should be omitted from the dictionary, * rather than included with an empty string as its value. * * .... * * Note: Although viewer applications can store custom metadata in the document * information dictionary, it is inappropriate to store private content or * structural information there; such information should be stored in the * document catalog instead (see Section 3.6.1, Document Catalog ). */ const char *keys[] = { "Title", "Author", "Subject", "Keywords", "Creator", "Producer", "CreationDate", "ModDate", /* Date */ NULL }; pdf_obj *value; int i; for (i = 0; keys[i] != NULL; i++) { value = pdf_lookup_dict(docinfo, keys[i]); if (value) { if (!PDF_OBJ_STRINGTYPE(value)) { WARN("\"%s\" in DocInfo dictionary not string type.", keys[i]); pdf_remove_dict(docinfo, keys[i]); WARN("\"%s\" removed from DocInfo.", keys[i]); } else if (pdf_string_length(value) == 0) { /* The hyperref package often uses emtpy strings. */ pdf_remove_dict(docinfo, keys[i]); } } } if (!pdf_lookup_dict(docinfo, "Producer")) { char *banner; banner = NEW(strlen(my_name)+strlen(VERSION)+4, char); sprintf(banner, "%s (%s)", my_name, VERSION); pdf_add_dict(docinfo, pdf_new_name("Producer"), pdf_new_string(banner, strlen(banner))); RELEASE(banner); } if (!pdf_lookup_dict(docinfo, "CreationDate")) { char now[32]; asn_date(now); pdf_add_dict(docinfo, pdf_new_name ("CreationDate"), pdf_new_string(now, strlen(now))); } pdf_release_obj(docinfo); p->info = NULL; return; } static pdf_obj * pdf_doc_get_page_resources (pdf_doc *p, const char *category) { pdf_obj *resources; pdf_page *currentpage; pdf_obj *res_dict; if (!p || !category) { return NULL; } if (p->pending_forms) { if (p->pending_forms->form.resources) { res_dict = p->pending_forms->form.resources; } else { res_dict = p->pending_forms->form.resources = pdf_new_dict(); } } else { currentpage = LASTPAGE(p); if (currentpage->resources) { res_dict = currentpage->resources; } else { res_dict = currentpage->resources = pdf_new_dict(); } } resources = pdf_lookup_dict(res_dict, category); if (!resources) { resources = pdf_new_dict(); pdf_add_dict(res_dict, pdf_new_name(category), resources); } return resources; } void pdf_doc_add_page_resource (const char *category, const char *resource_name, pdf_obj *resource_ref) { pdf_doc *p = &pdoc; pdf_obj *resources; pdf_obj *duplicate; if (!PDF_OBJ_INDIRECTTYPE(resource_ref)) { WARN("Passed non indirect reference..."); resource_ref = pdf_ref_obj(resource_ref); /* leak */ } resources = pdf_doc_get_page_resources(p, category); duplicate = pdf_lookup_dict(resources, resource_name); if (duplicate && pdf_compare_reference(duplicate, resource_ref)) { WARN("Conflicting page resource found (page: %d, category: %s, name: %s).", pdf_doc_current_page_number(), category, resource_name); WARN("Ignoring..."); pdf_release_obj(resource_ref); } else { pdf_add_dict(resources, pdf_new_name(resource_name), resource_ref); } return; } static void doc_flush_page (pdf_doc *p, pdf_page *page, pdf_obj *parent_ref) { pdf_obj *contents_array; int count; pdf_add_dict(page->page_obj, pdf_new_name("Type"), pdf_new_name("Page")); pdf_add_dict(page->page_obj, pdf_new_name("Parent"), parent_ref); /* * Clipping area specified by CropBox is affected by MediaBox which * might be inherit from parent node. If MediaBox of the root node * does not have enough size to cover all page's imaging area, using * CropBox here gives incorrect result. */ if (page->flags & USE_MY_MEDIABOX) { pdf_obj *mediabox; mediabox = pdf_new_array(); pdf_add_array(mediabox, pdf_new_number(ROUND(page->cropbox.llx, 0.01))); pdf_add_array(mediabox, pdf_new_number(ROUND(page->cropbox.lly, 0.01))); pdf_add_array(mediabox, pdf_new_number(ROUND(page->cropbox.urx, 0.01))); pdf_add_array(mediabox, pdf_new_number(ROUND(page->cropbox.ury, 0.01))); pdf_add_dict(page->page_obj, pdf_new_name("MediaBox"), mediabox); } count = 0; contents_array = pdf_new_array(); if (page->content_refs[0]) { /* global bop */ pdf_add_array(contents_array, page->content_refs[0]); count++; } else if (p->pages.bop && pdf_stream_length(p->pages.bop) > 0) { pdf_add_array(contents_array, pdf_ref_obj(p->pages.bop)); count++; } if (page->content_refs[1]) { /* background */ pdf_add_array(contents_array, page->content_refs[1]); count++; } if (page->content_refs[2]) { /* page body */ pdf_add_array(contents_array, page->content_refs[2]); count++; } if (page->content_refs[3]) { /* global eop */ pdf_add_array(contents_array, page->content_refs[3]); count++; } else if (p->pages.eop && pdf_stream_length(p->pages.eop) > 0) { pdf_add_array(contents_array, pdf_ref_obj(p->pages.eop)); count++; } if (count == 0) { WARN("Page with empty content found!!!"); } page->content_refs[0] = NULL; page->content_refs[1] = NULL; page->content_refs[2] = NULL; page->content_refs[3] = NULL; pdf_add_dict(page->page_obj, pdf_new_name("Contents"), contents_array); if (page->annots) { pdf_add_dict(page->page_obj, pdf_new_name("Annots"), pdf_ref_obj(page->annots)); pdf_release_obj(page->annots); } if (page->beads) { pdf_add_dict(page->page_obj, pdf_new_name("B"), pdf_ref_obj(page->beads)); pdf_release_obj(page->beads); } pdf_release_obj(page->page_obj); pdf_release_obj(page->page_ref); page->page_obj = NULL; page->page_ref = NULL; page->annots = NULL; page->beads = NULL; return; } /* B-tree? */ #define PAGE_CLUSTER 4 static pdf_obj * build_page_tree (pdf_doc *p, pdf_page *firstpage, int num_pages, pdf_obj *parent_ref) { pdf_obj *self, *self_ref, *kids; int i; self = pdf_new_dict(); /* * This is a slight kludge which allow the subtree dictionary * generated by this routine to be merged with the real * page_tree dictionary, while keeping the indirect object * references right. */ self_ref = parent_ref ? pdf_ref_obj(self) : pdf_ref_obj(p->root.pages); pdf_add_dict(self, pdf_new_name("Type"), pdf_new_name("Pages")); pdf_add_dict(self, pdf_new_name("Count"), pdf_new_number((double) num_pages)); if (parent_ref != NULL) pdf_add_dict(self, pdf_new_name("Parent"), parent_ref); kids = pdf_new_array(); if (num_pages > 0 && num_pages <= PAGE_CLUSTER) { for (i = 0; i < num_pages; i++) { pdf_page *page; page = firstpage + i; if (!page->page_ref) page->page_ref = pdf_ref_obj(page->page_obj); pdf_add_array (kids, pdf_link_obj(page->page_ref)); doc_flush_page(p, page, pdf_link_obj(self_ref)); } } else if (num_pages > 0) { for (i = 0; i < PAGE_CLUSTER; i++) { int start, end; start = (i*num_pages)/PAGE_CLUSTER; end = ((i+1)*num_pages)/PAGE_CLUSTER; if (end - start > 1) { pdf_obj *subtree; subtree = build_page_tree(p, firstpage + start, end - start, pdf_link_obj(self_ref)); pdf_add_array(kids, pdf_ref_obj(subtree)); pdf_release_obj(subtree); } else { pdf_page *page; page = firstpage + start; if (!page->page_ref) page->page_ref = pdf_ref_obj(page->page_obj); pdf_add_array (kids, pdf_link_obj(page->page_ref)); doc_flush_page(p, page, pdf_link_obj(self_ref)); } } } pdf_add_dict(self, pdf_new_name("Kids"), kids); pdf_release_obj(self_ref); return self; } static void pdf_doc_init_page_tree (pdf_doc *p, double media_width, double media_height) { /* * Create empty page tree. * The docroot.pages is kept open until the document is closed. * This allows the user to write to pages if he so choses. */ p->root.pages = pdf_new_dict(); p->pages.num_entries = 0; p->pages.max_entries = 0; p->pages.entries = NULL; p->pages.bop = NULL; p->pages.eop = NULL; p->pages.mediabox.llx = 0.0; p->pages.mediabox.lly = 0.0; p->pages.mediabox.urx = media_width; p->pages.mediabox.ury = media_height; return; } static void pdf_doc_close_page_tree (pdf_doc *p) { pdf_obj *page_tree_root; pdf_obj *mediabox; int page_no; /* * Do consistency check on forward references to pages. */ for (page_no = PAGECOUNT(p) + 1; page_no <= MAXPAGES(p); page_no++) { pdf_page *page; page = doc_get_page_entry(p, page_no); if (page->page_obj) { WARN("Nonexistent page #%ld refered.", page_no); pdf_release_obj(page->page_ref); page->page_ref = NULL; } if (page->page_obj) { WARN("Entry for a nonexistent page #%ld created.", page_no); pdf_release_obj(page->page_obj); page->page_obj = NULL; } if (page->annots) { WARN("Annotation attached to a nonexistent page #%ld.", page_no); pdf_release_obj(page->annots); page->annots = NULL; } if (page->beads) { WARN("Article beads attached to a nonexistent page #%ld.", page_no); pdf_release_obj(page->beads); page->beads = NULL; } if (page->resources) { pdf_release_obj(page->resources); page->resources = NULL; } } /* * Connect page tree to root node. */ page_tree_root = build_page_tree(p, FIRSTPAGE(p), PAGECOUNT(p), NULL); pdf_merge_dict (p->root.pages, page_tree_root); pdf_release_obj(page_tree_root); /* They must be after build_page_tree() */ if (p->pages.bop) { pdf_add_stream (p->pages.bop, "\n", 1); pdf_release_obj(p->pages.bop); p->pages.bop = NULL; } if (p->pages.eop) { pdf_add_stream (p->pages.eop, "\n", 1); pdf_release_obj(p->pages.eop); p->pages.eop = NULL; } /* Create media box at root node and let the other pages inherit it. */ mediabox = pdf_new_array(); pdf_add_array(mediabox, pdf_new_number(ROUND(p->pages.mediabox.llx, 0.01))); pdf_add_array(mediabox, pdf_new_number(ROUND(p->pages.mediabox.lly, 0.01))); pdf_add_array(mediabox, pdf_new_number(ROUND(p->pages.mediabox.urx, 0.01))); pdf_add_array(mediabox, pdf_new_number(ROUND(p->pages.mediabox.ury, 0.01))); pdf_add_dict(p->root.pages, pdf_new_name("MediaBox"), mediabox); pdf_add_dict(p->root.dict, pdf_new_name("Pages"), pdf_ref_obj (p->root.pages)); pdf_release_obj(p->root.pages); p->root.pages = NULL; RELEASE(p->pages.entries); p->pages.entries = NULL; p->pages.num_entries = 0; p->pages.max_entries = 0; return; } int pdf_doc_get_page_count (pdf_file *pf) { int count = 0; pdf_obj *page_tree = NULL; pdf_obj *catalog; catalog = pdf_file_get_catalog(pf); page_tree = pdf_deref_obj(pdf_lookup_dict(catalog, "Pages")); if (!PDF_OBJ_DICTTYPE(page_tree)) { return 0; } { pdf_obj *tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "Count")); if (!PDF_OBJ_NUMBERTYPE(tmp)) { if (tmp) pdf_release_obj(tmp); return 0; } count = pdf_number_value(tmp); pdf_release_obj(tmp); } return count; } /* * From PDFReference15_v6.pdf (p.119 and p.834) * * MediaBox rectangle (Required; inheritable) * * The media box defines the boundaries of the physical medium on which the * page is to be printed. It may include any extended area surrounding the * finished page for bleed, printing marks, or other such purposes. It may * also include areas close to the edges of the medium that cannot be marked * because of physical limitations of the output device. Content falling * outside this boundary can safely be discarded without affecting the * meaning of the PDF file. * * CropBox rectangle (Optional; inheritable) * * The crop box defines the region to which the contents of the page are to be * clipped (cropped) when displayed or printed. Unlike the other boxes, the * crop box has no defined meaning in terms of physical page geometry or * intended use; it merely imposes clipping on the page contents. However, * in the absence of additional information (such as imposition instructions * specified in a JDF or PJTF job ticket), the crop box will determine how * the page's contents are to be positioned on the output medium. The default * value is the page's media box. * * BleedBox rectangle (Optional; PDF 1.3) * * The bleed box (PDF 1.3) defines the region to which the contents of the * page should be clipped when output in a production environment. This may * include any extra "bleed area" needed to accommodate the physical * limitations of cutting, folding, and trimming equipment. The actual printed * page may include printing marks that fall outside the bleed box. * The default value is the page's crop box. * * TrimBox rectangle (Optional; PDF 1.3) * * The trim box (PDF 1.3) defines the intended dimensions of the finished page * after trimming. It may be smaller than the media box, to allow for * production-related content such as printing instructions, cut marks, or * color bars. The default value is the page's crop box. * * ArtBox rectangle (Optional; PDF 1.3) * * The art box (PDF 1.3) defines the extent of the page's meaningful content * (including potential white space) as intended by the page's creator. * The default value is the page's crop box. * * Rotate integer (Optional; inheritable) * * The number of degrees by which the page should be rotated clockwise when * displayed or printed. The value must be a multiple of 90. Default value: 0. */ /* count_p removed: Please use different interface if you want to get total page * number. pdf_doc_get_page() is obviously not an interface to do such. */ pdf_obj * pdf_doc_get_page (pdf_file *pf, int page_no, int options, /* load options */ pdf_rect *bbox, pdf_obj **resources_p /* returned values */ ) { pdf_obj *page_tree = NULL; pdf_obj *resources = NULL, *box = NULL, *rotate = NULL, *medbox = NULL; pdf_obj *catalog; catalog = pdf_file_get_catalog(pf); page_tree = pdf_deref_obj(pdf_lookup_dict(catalog, "Pages")); if (!PDF_OBJ_DICTTYPE(page_tree)) goto error; { int count; pdf_obj *tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "Count")); if (!PDF_OBJ_NUMBERTYPE(tmp)) { if (tmp) pdf_release_obj(tmp); goto error; } count = pdf_number_value(tmp); pdf_release_obj(tmp); if (page_no <= 0 || page_no > count) { WARN("Page %ld does not exist.", page_no); goto error_silent; } } /* * Seek correct page. Get MediaBox, CropBox and Resources. * (Note that these entries can be inherited.) */ { pdf_obj *art_box = NULL, *trim_box = NULL, *bleed_box = NULL; pdf_obj *media_box = NULL, *crop_box = NULL, *kids, *tmp; int depth = PDF_OBJ_MAX_DEPTH; int page_idx = page_no-1, kids_length = 1, i = 0; while (--depth && i != kids_length) { if ((tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "MediaBox")))) { if (media_box) pdf_release_obj(media_box); media_box = tmp; } if ((tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "CropBox")))) { if (crop_box) pdf_release_obj(crop_box); crop_box = tmp; } if ((tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "ArtBox")))) { if (art_box) pdf_release_obj(art_box); art_box = tmp; } if ((tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "TrimBox")))) { if (trim_box) pdf_release_obj(trim_box); trim_box = tmp; } if ((tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "BleedBox")))) { if (bleed_box) pdf_release_obj(bleed_box); bleed_box = tmp; } if ((tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "Rotate")))) { if (rotate) pdf_release_obj(rotate); rotate = tmp; } if ((tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "Resources")))) { if (resources) pdf_release_obj(resources); resources = tmp; } kids = pdf_deref_obj(pdf_lookup_dict(page_tree, "Kids")); if (!kids) break; else if (!PDF_OBJ_ARRAYTYPE(kids)) { pdf_release_obj(kids); goto error; } kids_length = pdf_array_length(kids); for (i = 0; i < kids_length; i++) { int count; pdf_release_obj(page_tree); page_tree = pdf_deref_obj(pdf_get_array(kids, i)); if (!PDF_OBJ_DICTTYPE(page_tree)) goto error; tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "Count")); if (PDF_OBJ_NUMBERTYPE(tmp)) { /* Pages object */ count = pdf_number_value(tmp); pdf_release_obj(tmp); } else if (!tmp) /* Page object */ count = 1; else { pdf_release_obj(tmp); goto error; } if (page_idx < count) break; page_idx -= count; } pdf_release_obj(kids); } if (!depth || kids_length == i) { if (media_box) pdf_release_obj(media_box); if (crop_box) pdf_release_obj(crop_box); goto error; } /* Nasty BBox selection... */ if (options == 0) { if (crop_box) box = crop_box; else { if (is_xdv) { /* New scheme only for XDV files */ if (!(box = media_box) && !(box = bleed_box) && !(box = trim_box) && art_box) { box = art_box; } } else { /* Backward compatibility */ if (!(box = art_box) && !(box = trim_box) && !(box = bleed_box) && media_box) { box = media_box; } } } } else if (options == 1) { if (crop_box) box = crop_box; else if (!(box = media_box) && !(box = bleed_box) && !(box = trim_box) && art_box) { box = art_box; } } else if (options == 2) { if (media_box) box = media_box; else if (!(box = crop_box) && !(box = bleed_box) && !(box = trim_box) && art_box) { box = art_box; } } else if (options == 3) { if (art_box) box = art_box; else if (!(box = crop_box) && !(box = media_box) && !(box = bleed_box) && trim_box) { box = trim_box; } } else if (options == 4) { if (trim_box) box = trim_box; else if (!(box = crop_box) && !(box = media_box) && !(box = bleed_box) && art_box) { box = art_box; } } else if (options == 5) { if (bleed_box) box = bleed_box; else if (!(box = crop_box) && !(box = media_box) && !(box = trim_box) && art_box) { box = art_box; } } medbox = media_box; } if (!PDF_OBJ_ARRAYTYPE(box) || pdf_array_length(box) != 4 || !PDF_OBJ_DICTTYPE(resources)) goto error; if (PDF_OBJ_NUMBERTYPE(rotate)) { if (pdf_number_value(rotate)) WARN("<< /Rotate %d >> found. (Not supported yet)", (int) pdf_number_value(rotate)); pdf_release_obj(rotate); rotate = NULL; } else if (rotate) goto error; { int i; for (i = 4; i--; ) { double x; pdf_obj *tmp = pdf_deref_obj(pdf_get_array(box, i)); if (!PDF_OBJ_NUMBERTYPE(tmp)) { pdf_release_obj(tmp); goto error; } x = pdf_number_value(tmp); switch (i) { case 0: bbox->llx = x; break; case 1: bbox->lly = x; break; case 2: bbox->urx = x; break; case 3: bbox->ury = x; break; } pdf_release_obj(tmp); } /* New scheme only for XDV files */ if (medbox && (is_xdv || options)) { for (i = 4; i--; ) { double x; pdf_obj *tmp = pdf_deref_obj(pdf_get_array(medbox, i)); if (!PDF_OBJ_NUMBERTYPE(tmp)) { pdf_release_obj(tmp); goto error; } x = pdf_number_value(tmp); switch (i) { case 0: if (bbox->llx < x) bbox->llx = x; break; case 1: if (bbox->lly < x) bbox->lly = x; break; case 2: if (bbox->urx > x) bbox->urx = x; break; case 3: if (bbox->ury > x) bbox->ury = x; break; } pdf_release_obj(tmp); } } } pdf_release_obj(box); if (resources_p) *resources_p = resources; else if (resources) pdf_release_obj(resources); return page_tree; error: WARN("Cannot parse document. Broken PDF file?"); error_silent: if (box) pdf_release_obj(box); if (rotate) pdf_release_obj(rotate); if (resources) pdf_release_obj(resources); if (page_tree) pdf_release_obj(page_tree); return NULL; } #ifndef BOOKMARKS_OPEN_DEFAULT #define BOOKMARKS_OPEN_DEFAULT 0 #endif static int clean_bookmarks (pdf_olitem *item); static int flush_bookmarks (pdf_olitem *item, pdf_obj *parent_ref, pdf_obj *parent_dict); static void pdf_doc_init_bookmarks (pdf_doc *p, int bm_open_depth) { pdf_olitem *item; #define MAX_OUTLINE_DEPTH 256u p->opt.outline_open_depth = ((bm_open_depth >= 0) ? bm_open_depth : MAX_OUTLINE_DEPTH - bm_open_depth); p->outlines.current_depth = 1; item = NEW(1, pdf_olitem); item->dict = NULL; item->next = NULL; item->first = NULL; item->parent = NULL; item->is_open = 1; p->outlines.current = item; p->outlines.first = item; return; } static int clean_bookmarks (pdf_olitem *item) { pdf_olitem *next; while (item) { next = item->next; if (item->dict) pdf_release_obj(item->dict); if (item->first) clean_bookmarks(item->first); RELEASE(item); item = next; } return 0; } static int flush_bookmarks (pdf_olitem *node, pdf_obj *parent_ref, pdf_obj *parent_dict) { int retval; int count; pdf_olitem *item; pdf_obj *this_ref, *prev_ref, *next_ref; ASSERT(node->dict); this_ref = pdf_ref_obj(node->dict); pdf_add_dict(parent_dict, pdf_new_name("First"), pdf_link_obj(this_ref)); retval = 0; for (item = node, prev_ref = NULL; item && item->dict; item = item->next) { if (item->first && item->first->dict) { count = flush_bookmarks(item->first, this_ref, item->dict); if (item->is_open) { pdf_add_dict(item->dict, pdf_new_name("Count"), pdf_new_number(count)); retval += count; } else { pdf_add_dict(item->dict, pdf_new_name("Count"), pdf_new_number(-count)); } } pdf_add_dict(item->dict, pdf_new_name("Parent"), pdf_link_obj(parent_ref)); if (prev_ref) { pdf_add_dict(item->dict, pdf_new_name("Prev"), prev_ref); } if (item->next && item->next->dict) { next_ref = pdf_ref_obj(item->next->dict); pdf_add_dict(item->dict, pdf_new_name("Next"), pdf_link_obj(next_ref)); } else { next_ref = NULL; } pdf_release_obj(item->dict); item->dict = NULL; prev_ref = this_ref; this_ref = next_ref; retval++; } pdf_add_dict(parent_dict, pdf_new_name("Last"), pdf_link_obj(prev_ref)); pdf_release_obj(prev_ref); pdf_release_obj(node->dict); node->dict = NULL; return retval; } int pdf_doc_bookmarks_up (void) { pdf_doc *p = &pdoc; pdf_olitem *parent, *item; item = p->outlines.current; if (!item || !item->parent) { WARN("Can't go up above the bookmark root node!"); return -1; } parent = item->parent; item = parent->next; if (!parent->next) { parent->next = item = NEW(1, pdf_olitem); item->dict = NULL; item->first = NULL; item->next = NULL; item->is_open = 0; item->parent = parent->parent; } p->outlines.current = item; p->outlines.current_depth--; return 0; } int pdf_doc_bookmarks_down (void) { pdf_doc *p = &pdoc; pdf_olitem *item, *first; item = p->outlines.current; if (!item->dict) { pdf_obj *tcolor, *action; WARN("Empty bookmark node!"); WARN("You have tried to jump more than 1 level."); item->dict = pdf_new_dict(); #define TITLE_STRING "" pdf_add_dict(item->dict, pdf_new_name("Title"), pdf_new_string(TITLE_STRING, strlen(TITLE_STRING))); tcolor = pdf_new_array(); pdf_add_array(tcolor, pdf_new_number(1.0)); pdf_add_array(tcolor, pdf_new_number(0.0)); pdf_add_array(tcolor, pdf_new_number(0.0)); pdf_add_dict (item->dict, pdf_new_name("C"), pdf_link_obj(tcolor)); pdf_release_obj(tcolor); pdf_add_dict (item->dict, pdf_new_name("F"), pdf_new_number(1.0)); #define JS_CODE "app.alert(\"The author of this document made this bookmark item empty!\", 3, 0)" action = pdf_new_dict(); pdf_add_dict(action, pdf_new_name("S"), pdf_new_name("JavaScript")); pdf_add_dict(action, pdf_new_name("JS"), pdf_new_string(JS_CODE, strlen(JS_CODE))); pdf_add_dict(item->dict, pdf_new_name("A"), pdf_link_obj(action)); pdf_release_obj(action); } item->first = first = NEW(1, pdf_olitem); first->dict = NULL; first->is_open = 0; first->parent = item; first->next = NULL; first->first = NULL; p->outlines.current = first; p->outlines.current_depth++; return 0; } int pdf_doc_bookmarks_depth (void) { pdf_doc *p = &pdoc; return p->outlines.current_depth; } void pdf_doc_bookmarks_add (pdf_obj *dict, int is_open) { pdf_doc *p = &pdoc; pdf_olitem *item, *next; ASSERT(p && dict); item = p->outlines.current; if (!item) { item = NEW(1, pdf_olitem); item->parent = NULL; p->outlines.first = item; } else if (item->dict) { /* go to next item */ item = item->next; } #define BMOPEN(b,p) (((b) < 0) ? (((p)->outlines.current_depth > (p)->opt.outline_open_depth) ? 0 : 1) : (b)) #if 0 item->dict = pdf_link_obj(dict); #endif item->dict = dict; item->first = NULL; item->is_open = BMOPEN(is_open, p); item->next = next = NEW(1, pdf_olitem); next->dict = NULL; next->parent = item->parent; next->first = NULL; next->is_open = -1; next->next = NULL; p->outlines.current = item; pdf_doc_add_goto(dict); return; } static void pdf_doc_close_bookmarks (pdf_doc *p) { pdf_obj *catalog = p->root.dict; pdf_olitem *item; int count; pdf_obj *bm_root, *bm_root_ref; item = p->outlines.first; if (item->dict) { bm_root = pdf_new_dict(); bm_root_ref = pdf_ref_obj(bm_root); count = flush_bookmarks(item, bm_root_ref, bm_root); pdf_add_dict(bm_root, pdf_new_name("Count"), pdf_new_number(count)); pdf_add_dict(catalog, pdf_new_name("Outlines"), bm_root_ref); pdf_release_obj(bm_root); } clean_bookmarks(item); p->outlines.first = NULL; p->outlines.current = NULL; p->outlines.current_depth = 0; return; } static const char *name_dict_categories[] = { "Dests", "AP", "JavaScript", "Pages", "Templates", "IDS", "URLS", "EmbeddedFiles", "AlternatePresentations", "Renditions" }; #define NUM_NAME_CATEGORY (sizeof(name_dict_categories)/sizeof(name_dict_categories[0])) static void pdf_doc_init_names (pdf_doc *p, int check_gotos) { int i; p->root.names = NULL; p->names = NEW(NUM_NAME_CATEGORY + 1, struct name_dict); for (i = 0; i < NUM_NAME_CATEGORY; i++) { p->names[i].category = name_dict_categories[i]; p->names[i].data = strcmp(name_dict_categories[i], "Dests") ? NULL : pdf_new_name_tree(); /* * We need a non-null entry for PDF destinations in order to find * broken links even if no destination is defined in the DVI file. */ } p->names[NUM_NAME_CATEGORY].category = NULL; p->names[NUM_NAME_CATEGORY].data = NULL; p->check_gotos = check_gotos; ht_init_table(&p->gotos, (void (*) (void *)) pdf_release_obj); return; } int pdf_doc_add_names (const char *category, const void *key, int keylen, pdf_obj *value) { pdf_doc *p = &pdoc; int i; for (i = 0; p->names[i].category != NULL; i++) { if (!strcmp(p->names[i].category, category)) { break; } } if (p->names[i].category == NULL) { WARN("Unknown name dictionary category \"%s\".", category); return -1; } if (!p->names[i].data) { p->names[i].data = pdf_new_name_tree(); } return pdf_names_add_object(p->names[i].data, key, keylen, value); } static void pdf_doc_add_goto (pdf_obj *annot_dict) { pdf_obj *subtype = NULL, *A = NULL, *S = NULL, *D = NULL, *D_new, *dict; const char *dest, *key; int destlen = 0; if (!pdoc.check_gotos) return; /* * An annotation dictionary coming from an annotation special * must have a "Subtype". An annotation dictionary coming from * an outline special has none. */ subtype = pdf_deref_obj(pdf_lookup_dict(annot_dict, "Subtype")); if (subtype) { if (PDF_OBJ_UNDEFINED(subtype)) goto undefined; else if (!PDF_OBJ_NAMETYPE(subtype)) goto error; else if (strcmp(pdf_name_value(subtype), "Link")) goto cleanup; } dict = annot_dict; key = "Dest"; D = pdf_deref_obj(pdf_lookup_dict(annot_dict, key)); if (PDF_OBJ_UNDEFINED(D)) goto undefined; A = pdf_deref_obj(pdf_lookup_dict(annot_dict, "A")); if (A) { if (PDF_OBJ_UNDEFINED(A)) goto undefined; else if (D || !PDF_OBJ_DICTTYPE(A)) goto error; else { S = pdf_deref_obj(pdf_lookup_dict(A, "S")); if (PDF_OBJ_UNDEFINED(S)) goto undefined; else if (!PDF_OBJ_NAMETYPE(S)) goto error; else if (strcmp(pdf_name_value(S), "GoTo")) goto cleanup; dict = A; key = "D"; D = pdf_deref_obj(pdf_lookup_dict(A, key)); } } if (PDF_OBJ_STRINGTYPE(D)) { dest = (char *) pdf_string_value(D); destlen = pdf_string_length(D); } #if 0 /* Names as destinations are not supported by dvipdfmx */ else if (PDF_OBJ_NAMETYPE(D)) dest = pdf_name_value(D); #endif else if (PDF_OBJ_ARRAYTYPE(D)) goto cleanup; else if (PDF_OBJ_UNDEFINED(D)) goto undefined; else goto error; D_new = ht_lookup_table(&pdoc.gotos, dest, destlen); if (!D_new) { char buf[10]; /* We use hexadecimal notation for our numeric destinations. * Other bases (e.g., 10+26 or 10+2*26) would be more efficient. */ sprintf(buf, "%x", ht_table_size(&pdoc.gotos)); D_new = pdf_new_string(buf, strlen(buf)); ht_append_table(&pdoc.gotos, dest, destlen, D_new); } { pdf_obj *key_obj = pdf_new_name(key); if (!pdf_add_dict(dict, key_obj, pdf_link_obj(D_new))) pdf_release_obj(key_obj); } cleanup: if (subtype) pdf_release_obj(subtype); if (A) pdf_release_obj(A); if (S) pdf_release_obj(S); if (D) pdf_release_obj(D); return; error: WARN("Unknown PDF annotation format. Output file may be broken."); goto cleanup; undefined: WARN("Cannot optimize PDF annotations. Output file may be broken." " Please restart with option \"-C 0x10\"\n"); goto cleanup; } static void warn_undef_dests (struct ht_table *dests, struct ht_table *gotos) { struct ht_iter iter; if (ht_set_iter(gotos, &iter) < 0) return; do { int keylen; char *key = ht_iter_getkey(&iter, &keylen); if (!ht_lookup_table(dests, key, keylen)) { char *dest = NEW(keylen+1, char); memcpy(dest, key, keylen); dest[keylen] = 0; WARN("PDF destination \"%s\" not defined.", dest); RELEASE(dest); } } while (ht_iter_next(&iter) >= 0); ht_clear_iter(&iter); } static void pdf_doc_close_names (pdf_doc *p) { pdf_obj *tmp; int i; for (i = 0; p->names[i].category != NULL; i++) { if (p->names[i].data) { struct ht_table *data = p->names[i].data; pdf_obj *name_tree; int count; if (!pdoc.check_gotos || strcmp(p->names[i].category, "Dests")) name_tree = pdf_names_create_tree(data, &count, NULL); else { name_tree = pdf_names_create_tree(data, &count, &pdoc.gotos); if (verbose && count < data->count) MESG("\nRemoved %ld unused PDF destinations\n", data->count-count); if (count < pdoc.gotos.count) warn_undef_dests(data, &pdoc.gotos); } if (name_tree) { if (!p->root.names) p->root.names = pdf_new_dict(); pdf_add_dict(p->root.names, pdf_new_name(p->names[i].category), pdf_ref_obj(name_tree)); pdf_release_obj(name_tree); } pdf_delete_name_tree(&p->names[i].data); } } if (p->root.names) { tmp = pdf_lookup_dict(p->root.dict, "Names"); if (!tmp) { pdf_add_dict(p->root.dict, pdf_new_name("Names"), pdf_ref_obj (p->root.names)); } else if (PDF_OBJ_DICTTYPE(tmp)) { pdf_merge_dict(p->root.names, tmp); pdf_add_dict(p->root.dict, pdf_new_name("Names"), pdf_ref_obj (p->root.names)); } else { /* Maybe reference */ /* What should I do? */ WARN("Could not modify Names dictionary."); } pdf_release_obj(p->root.names); p->root.names = NULL; } RELEASE(p->names); p->names = NULL; ht_clear_table(&p->gotos); return; } static void pdf_doc_get_mediabox (unsigned page_no, pdf_rect *mediabox); void pdf_doc_add_annot (unsigned page_no, const pdf_rect *rect, pdf_obj *annot_dict, int new_annot) { pdf_doc *p = &pdoc; pdf_page *page; pdf_obj *rect_array; double annot_grow = p->opt.annot_grow; double xpos, ypos; pdf_rect annbox; page = doc_get_page_entry(p, page_no); if (!page->annots) page->annots = pdf_new_array(); { pdf_rect mediabox; pdf_doc_get_mediabox(page_no, &mediabox); pdf_dev_get_coord(&xpos, &ypos); annbox.llx = rect->llx - xpos; annbox.lly = rect->lly - ypos; annbox.urx = rect->urx - xpos; annbox.ury = rect->ury - ypos; if (annbox.llx < mediabox.llx || annbox.urx > mediabox.urx || annbox.lly < mediabox.lly || annbox.ury > mediabox.ury) { WARN("Annotation out of page boundary."); WARN("Current page's MediaBox: [%g %g %g %g]", mediabox.llx, mediabox.lly, mediabox.urx, mediabox.ury); WARN("Annotation: [%g %g %g %g]", annbox.llx, annbox.lly, annbox.urx, annbox.ury); WARN("Maybe incorrect paper size specified."); } if (annbox.llx > annbox.urx || annbox.lly > annbox.ury) { WARN("Rectangle with negative width/height: [%g %g %g %g]", annbox.llx, annbox.lly, annbox.urx, annbox.ury); } } rect_array = pdf_new_array(); pdf_add_array(rect_array, pdf_new_number(ROUND(annbox.llx - annot_grow, 0.001))); pdf_add_array(rect_array, pdf_new_number(ROUND(annbox.lly - annot_grow, 0.001))); pdf_add_array(rect_array, pdf_new_number(ROUND(annbox.urx + annot_grow, 0.001))); pdf_add_array(rect_array, pdf_new_number(ROUND(annbox.ury + annot_grow, 0.001))); pdf_add_dict (annot_dict, pdf_new_name("Rect"), rect_array); pdf_add_array(page->annots, pdf_ref_obj(annot_dict)); if (new_annot) pdf_doc_add_goto(annot_dict); return; } /* * PDF Article Thread */ static void pdf_doc_init_articles (pdf_doc *p) { p->root.threads = NULL; p->articles.num_entries = 0; p->articles.max_entries = 0; p->articles.entries = NULL; return; } void pdf_doc_begin_article (const char *article_id, pdf_obj *article_info) { pdf_doc *p = &pdoc; pdf_article *article; if (article_id == NULL || strlen(article_id) == 0) ERROR("Article thread without internal identifier."); if (p->articles.num_entries >= p->articles.max_entries) { p->articles.max_entries += PDFDOC_ARTICLE_ALLOC_SIZE; p->articles.entries = RENEW(p->articles.entries, p->articles.max_entries, struct pdf_article); } article = &(p->articles.entries[p->articles.num_entries]); article->id = NEW(strlen(article_id)+1, char); strcpy(article->id, article_id); article->info = article_info; article->num_beads = 0; article->max_beads = 0; article->beads = NULL; p->articles.num_entries++; return; } static pdf_bead * find_bead (pdf_article *article, const char *bead_id) { pdf_bead *bead; int i; bead = NULL; for (i = 0; i < article->num_beads; i++) { if (!strcmp(article->beads[i].id, bead_id)) { bead = &(article->beads[i]); break; } } return bead; } void pdf_doc_add_bead (const char *article_id, const char *bead_id, int page_no, const pdf_rect *rect) { pdf_doc *p = &pdoc; pdf_article *article; pdf_bead *bead; int i; if (!article_id) { ERROR("No article identifier specified."); } article = NULL; for (i = 0; i < p->articles.num_entries; i++) { if (!strcmp(p->articles.entries[i].id, article_id)) { article = &(p->articles.entries[i]); break; } } if (!article) { ERROR("Specified article thread that doesn't exist."); return; } bead = bead_id ? find_bead(article, bead_id) : NULL; if (!bead) { if (article->num_beads >= article->max_beads) { article->max_beads += PDFDOC_BEAD_ALLOC_SIZE; article->beads = RENEW(article->beads, article->max_beads, struct pdf_bead); for (i = article->num_beads; i < article->max_beads; i++) { article->beads[i].id = NULL; article->beads[i].page_no = -1; } } bead = &(article->beads[article->num_beads]); if (bead_id) { bead->id = NEW(strlen(bead_id)+1, char); strcpy(bead->id, bead_id); } else { bead->id = NULL; } article->num_beads++; } bead->rect.llx = rect->llx; bead->rect.lly = rect->lly; bead->rect.urx = rect->urx; bead->rect.ury = rect->ury; bead->page_no = page_no; return; } static pdf_obj * make_article (pdf_doc *p, pdf_article *article, const char **bead_ids, int num_beads, pdf_obj *article_info) { pdf_obj *art_dict; pdf_obj *first, *prev, *last; int i, n; if (!article) return NULL; art_dict = pdf_new_dict(); first = prev = last = NULL; /* * The bead_ids represents logical order of beads in an article thread. * If bead_ids is not given, we create an article thread in the order of * beads appeared. */ n = bead_ids ? num_beads : article->num_beads; for (i = 0; i < n; i++) { pdf_bead *bead; bead = bead_ids ? find_bead(article, bead_ids[i]) : &(article->beads[i]); if (!bead || bead->page_no < 0) { continue; } last = pdf_new_dict(); if (prev == NULL) { first = last; pdf_add_dict(first, pdf_new_name("T"), pdf_ref_obj(art_dict)); } else { pdf_add_dict(prev, pdf_new_name("N"), pdf_ref_obj(last)); pdf_add_dict(last, pdf_new_name("V"), pdf_ref_obj(prev)); /* We must link first to last. */ if (prev != first) pdf_release_obj(prev); } /* Realize bead now. */ { pdf_page *page; pdf_obj *rect; page = doc_get_page_entry(p, bead->page_no); if (!page->beads) { page->beads = pdf_new_array(); } pdf_add_dict(last, pdf_new_name("P"), pdf_link_obj(page->page_ref)); rect = pdf_new_array(); pdf_add_array(rect, pdf_new_number(ROUND(bead->rect.llx, 0.01))); pdf_add_array(rect, pdf_new_number(ROUND(bead->rect.lly, 0.01))); pdf_add_array(rect, pdf_new_number(ROUND(bead->rect.urx, 0.01))); pdf_add_array(rect, pdf_new_number(ROUND(bead->rect.ury, 0.01))); pdf_add_dict (last, pdf_new_name("R"), rect); pdf_add_array(page->beads, pdf_ref_obj(last)); } prev = last; } if (first && last) { pdf_add_dict(last, pdf_new_name("N"), pdf_ref_obj(first)); pdf_add_dict(first, pdf_new_name("V"), pdf_ref_obj(last)); if (first != last) { pdf_release_obj(last); } pdf_add_dict(art_dict, pdf_new_name("F"), pdf_ref_obj(first)); /* If article_info is supplied, we override article->info. */ if (article_info) { pdf_add_dict(art_dict, pdf_new_name("I"), article_info); } else if (article->info) { pdf_add_dict(art_dict, pdf_new_name("I"), pdf_ref_obj(article->info)); pdf_release_obj(article->info); article->info = NULL; /* We do not write as object reference. */ } pdf_release_obj(first); } else { pdf_release_obj(art_dict); art_dict = NULL; } return art_dict; } static void clean_article (pdf_article *article) { if (!article) return; if (article->beads) { int i; for (i = 0; i < article->num_beads; i++) { if (article->beads[i].id) RELEASE(article->beads[i].id); } RELEASE(article->beads); article->beads = NULL; } if (article->id) RELEASE(article->id); article->id = NULL; article->num_beads = 0; article->max_beads = 0; return; } static void pdf_doc_close_articles (pdf_doc *p) { int i; for (i = 0; i < p->articles.num_entries; i++) { pdf_article *article; article = &(p->articles.entries[i]); if (article->beads) { pdf_obj *art_dict; art_dict = make_article(p, article, NULL, 0, NULL); if (!p->root.threads) { p->root.threads = pdf_new_array(); } pdf_add_array(p->root.threads, pdf_ref_obj(art_dict)); pdf_release_obj(art_dict); } clean_article(article); } RELEASE(p->articles.entries); p->articles.entries = NULL; p->articles.num_entries = 0; p->articles.max_entries = 0; if (p->root.threads) { pdf_add_dict(p->root.dict, pdf_new_name("Threads"), pdf_ref_obj (p->root.threads)); pdf_release_obj(p->root.threads); p->root.threads = NULL; } return; } /* page_no = 0 for root page tree node. */ void pdf_doc_set_mediabox (unsigned page_no, const pdf_rect *mediabox) { pdf_doc *p = &pdoc; pdf_page *page; if (page_no == 0) { p->pages.mediabox.llx = mediabox->llx; p->pages.mediabox.lly = mediabox->lly; p->pages.mediabox.urx = mediabox->urx; p->pages.mediabox.ury = mediabox->ury; } else { page = doc_get_page_entry(p, page_no); page->cropbox.llx = mediabox->llx; page->cropbox.lly = mediabox->lly; page->cropbox.urx = mediabox->urx; page->cropbox.ury = mediabox->ury; page->flags |= USE_MY_MEDIABOX; } return; } static void pdf_doc_get_mediabox (unsigned page_no, pdf_rect *mediabox) { pdf_doc *p = &pdoc; pdf_page *page; if (page_no == 0) { mediabox->llx = p->pages.mediabox.llx; mediabox->lly = p->pages.mediabox.lly; mediabox->urx = p->pages.mediabox.urx; mediabox->ury = p->pages.mediabox.ury; } else { page = doc_get_page_entry(p, page_no); if (page->flags & USE_MY_MEDIABOX) { mediabox->llx = page->cropbox.llx; mediabox->lly = page->cropbox.lly; mediabox->urx = page->cropbox.urx; mediabox->ury = page->cropbox.ury; } else { mediabox->llx = p->pages.mediabox.llx; mediabox->lly = p->pages.mediabox.lly; mediabox->urx = p->pages.mediabox.urx; mediabox->ury = p->pages.mediabox.ury; } } return; } pdf_obj * pdf_doc_current_page_resources (void) { pdf_obj *resources; pdf_doc *p = &pdoc; pdf_page *currentpage; if (p->pending_forms) { if (p->pending_forms->form.resources) { resources = p->pending_forms->form.resources; } else { resources = p->pending_forms->form.resources = pdf_new_dict(); } } else { currentpage = LASTPAGE(p); if (currentpage->resources) { resources = currentpage->resources; } else { resources = currentpage->resources = pdf_new_dict(); } } return resources; } pdf_obj * pdf_doc_get_dictionary (const char *category) { pdf_doc *p = &pdoc; pdf_obj *dict = NULL; ASSERT(category); if (!strcmp(category, "Names")) { if (!p->root.names) p->root.names = pdf_new_dict(); dict = p->root.names; } else if (!strcmp(category, "Pages")) { if (!p->root.pages) p->root.pages = pdf_new_dict(); dict = p->root.pages; } else if (!strcmp(category, "Catalog")) { if (!p->root.dict) p->root.dict = pdf_new_dict(); dict = p->root.dict; } else if (!strcmp(category, "Info")) { if (!p->info) p->info = pdf_new_dict(); dict = p->info; } else if (!strcmp(category, "@THISPAGE")) { /* Sorry for this... */ pdf_page *currentpage; currentpage = LASTPAGE(p); dict = currentpage->page_obj; } if (!dict) { ERROR("Document dict. \"%s\" not exist. ", category); } return dict; } int pdf_doc_current_page_number (void) { pdf_doc *p = &pdoc; return (int) (PAGECOUNT(p) + 1); } pdf_obj * pdf_doc_ref_page (unsigned page_no) { pdf_doc *p = &pdoc; pdf_page *page; page = doc_get_page_entry(p, page_no); if (!page->page_obj) { page->page_obj = pdf_new_dict(); page->page_ref = pdf_ref_obj(page->page_obj); } return pdf_link_obj(page->page_ref); } pdf_obj * pdf_doc_get_reference (const char *category) { pdf_obj *ref = NULL; int page_no; ASSERT(category); page_no = pdf_doc_current_page_number(); if (!strcmp(category, "@THISPAGE")) { ref = pdf_doc_ref_page(page_no); } else if (!strcmp(category, "@PREVPAGE")) { if (page_no <= 1) { ERROR("Reference to previous page, but no pages have been completed yet."); } ref = pdf_doc_ref_page(page_no - 1); } else if (!strcmp(category, "@NEXTPAGE")) { ref = pdf_doc_ref_page(page_no + 1); } if (!ref) { ERROR("Reference to \"%s\" not exist. ", category); } return ref; } static void pdf_doc_new_page (pdf_doc *p) { pdf_page *currentpage; if (PAGECOUNT(p) >= MAXPAGES(p)) { doc_resize_page_entries(p, MAXPAGES(p) + PDFDOC_PAGES_ALLOC_SIZE); } /* * This is confusing. pdf_doc_finish_page() have increased page count! */ currentpage = LASTPAGE(p); /* Was this page already instantiated by a forward reference to it? */ if (!currentpage->page_ref) { currentpage->page_obj = pdf_new_dict(); currentpage->page_ref = pdf_ref_obj(currentpage->page_obj); } currentpage->background = NULL; currentpage->contents = pdf_new_stream(STREAM_COMPRESS); currentpage->resources = pdf_new_dict(); currentpage->annots = NULL; currentpage->beads = NULL; return; } /* This only closes contents and resources. */ static void pdf_doc_finish_page (pdf_doc *p) { pdf_page *currentpage; if (p->pending_forms) { ERROR("A pending form XObject at the end of page."); } currentpage = LASTPAGE(p); if (!currentpage->page_obj) currentpage->page_obj = pdf_new_dict(); /* * Make Contents array. */ /* * Global BOP content stream. * pdf_ref_obj() returns reference itself when the object is * indirect reference, not reference to the indirect reference. * We keep bop itself but not reference to it since it is * expected to be small. */ if (p->pages.bop && pdf_stream_length(p->pages.bop) > 0) { currentpage->content_refs[0] = pdf_ref_obj(p->pages.bop); } else { currentpage->content_refs[0] = NULL; } /* * Current page background content stream. */ if (currentpage->background) { if (pdf_stream_length(currentpage->background) > 0) { currentpage->content_refs[1] = pdf_ref_obj(currentpage->background); pdf_add_stream (currentpage->background, "\n", 1); } pdf_release_obj(currentpage->background); currentpage->background = NULL; } else { currentpage->content_refs[1] = NULL; } /* Content body of current page */ currentpage->content_refs[2] = pdf_ref_obj(currentpage->contents); pdf_add_stream (currentpage->contents, "\n", 1); pdf_release_obj(currentpage->contents); currentpage->contents = NULL; /* * Global EOP content stream. */ if (p->pages.eop && pdf_stream_length(p->pages.eop) > 0) { currentpage->content_refs[3] = pdf_ref_obj(p->pages.eop); } else { currentpage->content_refs[3] = NULL; } /* * Page resources. */ if (currentpage->resources) { pdf_obj *procset; /* * ProcSet is obsolete in PDF-1.4 but recommended for compatibility. */ procset = pdf_new_array (); pdf_add_array(procset, pdf_new_name("PDF")); pdf_add_array(procset, pdf_new_name("Text")); pdf_add_array(procset, pdf_new_name("ImageC")); pdf_add_array(procset, pdf_new_name("ImageB")); pdf_add_array(procset, pdf_new_name("ImageI")); pdf_add_dict(currentpage->resources, pdf_new_name("ProcSet"), procset); pdf_add_dict(currentpage->page_obj, pdf_new_name("Resources"), pdf_ref_obj(currentpage->resources)); pdf_release_obj(currentpage->resources); currentpage->resources = NULL; } if (manual_thumb_enabled) { char *thumb_filename; pdf_obj *thumb_ref; thumb_filename = NEW(strlen(thumb_basename)+7, char); sprintf(thumb_filename, "%s.%ld", thumb_basename, (p->pages.num_entries % 99999) + 1L); thumb_ref = read_thumbnail(thumb_filename); RELEASE(thumb_filename); if (thumb_ref) pdf_add_dict(currentpage->page_obj, pdf_new_name("Thumb"), thumb_ref); } p->pages.num_entries++; return; } static pdf_color bgcolor = { 1, NULL, { 1.0 } }; void pdf_doc_set_bgcolor (const pdf_color *color) { if (color) pdf_color_copycolor(&bgcolor, color); else { /* as clear... */ pdf_color_white(&bgcolor); } } static void doc_fill_page_background (pdf_doc *p) { pdf_page *currentpage; pdf_rect r; int cm; pdf_obj *saved_content; cm = pdf_dev_get_param(PDF_DEV_PARAM_COLORMODE); if (!cm || pdf_color_is_white(&bgcolor)) { return; } pdf_doc_get_mediabox(pdf_doc_current_page_number(), &r); currentpage = LASTPAGE(p); ASSERT(currentpage); if (!currentpage->background) currentpage->background = pdf_new_stream(STREAM_COMPRESS); saved_content = currentpage->contents; currentpage->contents = currentpage->background; pdf_dev_gsave(); pdf_dev_set_nonstrokingcolor(&bgcolor); pdf_dev_rectfill(r.llx, r.lly, r.urx - r.llx, r.ury - r.lly); pdf_dev_grestore(); currentpage->contents = saved_content; return; } void pdf_doc_begin_page (double scale, double x_origin, double y_origin) { pdf_doc *p = &pdoc; pdf_tmatrix M; M.a = scale; M.b = 0.0; M.c = 0.0 ; M.d = scale; M.e = x_origin; M.f = y_origin; /* pdf_doc_new_page() allocates page content stream. */ pdf_doc_new_page(p); pdf_dev_bop(&M); return; } void pdf_doc_end_page (void) { pdf_doc *p = &pdoc; pdf_dev_eop(); doc_fill_page_background(p); pdf_doc_finish_page(p); return; } void pdf_doc_add_page_content (const char *buffer, unsigned length) { pdf_doc *p = &pdoc; pdf_page *currentpage; if (p->pending_forms) { pdf_add_stream(p->pending_forms->form.contents, buffer, length); } else { currentpage = LASTPAGE(p); pdf_add_stream(currentpage->contents, buffer, length); } return; } static char *doccreator = NULL; /* Ugh */ void pdf_open_document (const char *filename, int enable_encrypt, int enable_objstm, double media_width, double media_height, double annot_grow_amount, int bookmark_open_depth, int check_gotos) { pdf_doc *p = &pdoc; pdf_out_init(filename, enable_encrypt, enable_objstm); pdf_doc_init_catalog(p); p->opt.annot_grow = annot_grow_amount; p->opt.outline_open_depth = bookmark_open_depth; pdf_init_resources(); pdf_init_colors(); pdf_init_fonts(); /* Thumbnail want this to be initialized... */ pdf_init_images(); pdf_doc_init_docinfo(p); if (doccreator) { pdf_add_dict(p->info, pdf_new_name("Creator"), pdf_new_string(doccreator, strlen(doccreator))); RELEASE(doccreator); doccreator = NULL; } pdf_doc_init_bookmarks(p, bookmark_open_depth); pdf_doc_init_articles (p); pdf_doc_init_names (p, check_gotos); pdf_doc_init_page_tree(p, media_width, media_height); pdf_doc_set_bgcolor(NULL); if (enable_encrypt) { pdf_obj *encrypt = pdf_encrypt_obj(); pdf_set_encrypt(encrypt); pdf_release_obj(encrypt); } pdf_set_id(pdf_enc_id_array()); /* Create a default name for thumbnail image files */ if (manual_thumb_enabled) { if (strlen(filename) > 4 && !strncmp(".pdf", filename + strlen(filename) - 4, 4)) { thumb_basename = NEW(strlen(filename)-4+1, char); strncpy(thumb_basename, filename, strlen(filename)-4); thumb_basename[strlen(filename)-4] = 0; } else { thumb_basename = NEW(strlen(filename)+1, char); strcpy(thumb_basename, filename); } } p->pending_forms = NULL; return; } void pdf_doc_set_creator (const char *creator) { if (!creator || creator[0] == '\0') return; doccreator = NEW(strlen(creator)+1, char); strcpy(doccreator, creator); /* Ugh */ } void pdf_close_document (void) { pdf_doc *p = &pdoc; /* * Following things were kept around so user can add dictionary items. */ pdf_doc_close_articles (p); pdf_doc_close_names (p); pdf_doc_close_bookmarks(p); pdf_doc_close_page_tree(p); pdf_doc_close_docinfo (p); pdf_doc_close_catalog (p); pdf_close_images(); pdf_close_fonts (); pdf_close_colors(); pdf_close_resources(); /* Should be at last. */ pdf_out_flush(); if (thumb_basename) RELEASE(thumb_basename); return; } /* * All this routine does is give the form a name and add a unity scaling matrix. * It fills in required fields. The caller must initialize the stream. */ static void pdf_doc_make_xform (pdf_obj *xform, pdf_rect *bbox, pdf_tmatrix *matrix, pdf_obj *resources, pdf_obj *attrib) { pdf_obj *xform_dict; pdf_obj *tmp; xform_dict = pdf_stream_dict(xform); pdf_add_dict(xform_dict, pdf_new_name("Type"), pdf_new_name("XObject")); pdf_add_dict(xform_dict, pdf_new_name("Subtype"), pdf_new_name("Form")); pdf_add_dict(xform_dict, pdf_new_name("FormType"), pdf_new_number(1.0)); if (!bbox) ERROR("No BoundingBox supplied."); tmp = pdf_new_array(); pdf_add_array(tmp, pdf_new_number(ROUND(bbox->llx, .001))); pdf_add_array(tmp, pdf_new_number(ROUND(bbox->lly, .001))); pdf_add_array(tmp, pdf_new_number(ROUND(bbox->urx, .001))); pdf_add_array(tmp, pdf_new_number(ROUND(bbox->ury, .001))); pdf_add_dict(xform_dict, pdf_new_name("BBox"), tmp); if (matrix) { tmp = pdf_new_array(); pdf_add_array(tmp, pdf_new_number(ROUND(matrix->a, .00001))); pdf_add_array(tmp, pdf_new_number(ROUND(matrix->b, .00001))); pdf_add_array(tmp, pdf_new_number(ROUND(matrix->c, .00001))); pdf_add_array(tmp, pdf_new_number(ROUND(matrix->d, .00001))); pdf_add_array(tmp, pdf_new_number(ROUND(matrix->e, .001 ))); pdf_add_array(tmp, pdf_new_number(ROUND(matrix->f, .001 ))); pdf_add_dict(xform_dict, pdf_new_name("Matrix"), tmp); } if (attrib) { pdf_merge_dict(xform_dict, attrib); } pdf_add_dict(xform_dict, pdf_new_name("Resources"), resources); return; } /* * begin_form_xobj creates an xobject with its "origin" at * xpos and ypos that is clipped to the specified bbox. Note * that the origin is not the lower left corner of the bbox. */ int pdf_doc_begin_grabbing (const char *ident, double ref_x, double ref_y, const pdf_rect *cropbox) { int xobj_id = -1; pdf_doc *p = &pdoc; pdf_form *form; struct form_list_node *fnode; xform_info info; pdf_dev_push_gstate(); fnode = NEW(1, struct form_list_node); fnode->prev = p->pending_forms; fnode->q_depth = pdf_dev_current_depth(); form = &fnode->form; /* * The reference point of an Xobject is at the lower left corner * of the bounding box. Since we would like to have an arbitrary * reference point, we use a transformation matrix, translating * the reference point to (0,0). */ form->matrix.a = 1.0; form->matrix.b = 0.0; form->matrix.c = 0.0; form->matrix.d = 1.0; form->matrix.e = -ref_x; form->matrix.f = -ref_y; form->cropbox.llx = ref_x + cropbox->llx; form->cropbox.lly = ref_y + cropbox->lly; form->cropbox.urx = ref_x + cropbox->urx; form->cropbox.ury = ref_y + cropbox->ury; form->contents = pdf_new_stream(STREAM_COMPRESS); form->resources = pdf_new_dict(); pdf_ximage_init_form_info(&info); info.matrix.a = 1.0; info.matrix.b = 0.0; info.matrix.c = 0.0; info.matrix.d = 1.0; info.matrix.e = -ref_x; info.matrix.f = -ref_y; info.bbox.llx = cropbox->llx; info.bbox.lly = cropbox->lly; info.bbox.urx = cropbox->urx; info.bbox.ury = cropbox->ury; /* Use reference since content itself isn't available yet. */ xobj_id = pdf_ximage_defineresource(ident, PDF_XOBJECT_TYPE_FORM, &info, pdf_ref_obj(form->contents)); p->pending_forms = fnode; /* * Make sure the object is self-contained by adding the * current font and color to the object stream. */ pdf_dev_reset_fonts(1); pdf_dev_reset_color(1); /* force color operators to be added to stream */ return xobj_id; } void pdf_doc_end_grabbing (pdf_obj *attrib) { pdf_form *form; pdf_obj *procset; pdf_doc *p = &pdoc; struct form_list_node *fnode; if (!p->pending_forms) { WARN("Tried to close a nonexistent form XOject."); return; } fnode = p->pending_forms; form = &fnode->form; pdf_dev_grestore_to(fnode->q_depth); /* * ProcSet is obsolete in PDF-1.4 but recommended for compatibility. */ procset = pdf_new_array(); pdf_add_array(procset, pdf_new_name("PDF")); pdf_add_array(procset, pdf_new_name("Text")); pdf_add_array(procset, pdf_new_name("ImageC")); pdf_add_array(procset, pdf_new_name("ImageB")); pdf_add_array(procset, pdf_new_name("ImageI")); pdf_add_dict (form->resources, pdf_new_name("ProcSet"), procset); pdf_doc_make_xform(form->contents, &form->cropbox, &form->matrix, pdf_ref_obj(form->resources), attrib); pdf_release_obj(form->resources); pdf_release_obj(form->contents); if (attrib) pdf_release_obj(attrib); p->pending_forms = fnode->prev; pdf_dev_pop_gstate(); pdf_dev_reset_fonts(1); pdf_dev_reset_color(0); RELEASE(fnode); return; } static struct { int dirty; int broken; pdf_obj *annot_dict; pdf_rect rect; } breaking_state = {0, 0, NULL, {0.0, 0.0, 0.0, 0.0}}; static void reset_box (void) { breaking_state.rect.llx = breaking_state.rect.lly = HUGE_VAL; breaking_state.rect.urx = breaking_state.rect.ury = -HUGE_VAL; breaking_state.dirty = 0; } void pdf_doc_begin_annot (pdf_obj *dict) { breaking_state.annot_dict = dict; breaking_state.broken = 0; reset_box(); } void pdf_doc_end_annot (void) { pdf_doc_break_annot(); breaking_state.annot_dict = NULL; } void pdf_doc_break_annot (void) { if (breaking_state.dirty) { pdf_obj *annot_dict; /* Copy dict */ annot_dict = pdf_new_dict(); pdf_merge_dict(annot_dict, breaking_state.annot_dict); pdf_doc_add_annot(pdf_doc_current_page_number(), &(breaking_state.rect), annot_dict, !breaking_state.broken); pdf_release_obj(annot_dict); breaking_state.broken = 1; } reset_box(); } void pdf_doc_expand_box (const pdf_rect *rect) { breaking_state.rect.llx = MIN(breaking_state.rect.llx, rect->llx); breaking_state.rect.lly = MIN(breaking_state.rect.lly, rect->lly); breaking_state.rect.urx = MAX(breaking_state.rect.urx, rect->urx); breaking_state.rect.ury = MAX(breaking_state.rect.ury, rect->ury); breaking_state.dirty = 1; } #if 0 /* This should be number tree */ void pdf_doc_set_pagelabel (int pg_start, const char *type, const void *prefix, int prfx_len, int start) { pdf_doc *p = &pdoc; pdf_obj *label_dict; if (!p->root.pagelabels) p->root.pagelabels = pdf_new_array(); label_dict = pdf_new_dict(); if (!type || type[0] == '\0') /* Set back to default. */ pdf_add_dict(label_dict, pdf_new_name("S"), pdf_new_name("D")); else { if (type) pdf_add_dict(label_dict, pdf_new_name("S"), pdf_new_name(type)); if (prefix && prfx_len > 0) pdf_add_dict(label_dict, pdf_new_name("P"), pdf_new_string(prefix, prfx_len)); if (start != 1) pdf_add_dict(label_dict, pdf_new_name("St"), pdf_new_number(start)); } pdf_add_array(p->root.pagelabels, pdf_new_number(pg_start)); pdf_add_array(p->root.pagelabels, label_dict); return; } #endif