Back to index

texmacs  1.0.7.15
pdfdoc.c
Go to the documentation of this file.
00001 /*  $Header: /home/cvsroot/dvipdfmx/src/pdfdoc.c,v 1.75 2010/03/28 06:42:59 chofchof Exp $
00002  
00003     This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks.
00004 
00005     Copyright (C) 2008 by Jin-Hwan Cho, Matthias Franz, and Shunsaku Hirata,
00006     the dvipdfmx project team <dvipdfmx@project.ktug.or.kr>
00007     
00008     Copyright (C) 1998, 1999 by Mark A. Wicks <mwicks@kettering.edu>
00009 
00010     This program is free software; you can redistribute it and/or modify
00011     it under the terms of the GNU General Public License as published by
00012     the Free Software Foundation; either version 2 of the License, or
00013     (at your option) any later version.
00014     
00015     This program is distributed in the hope that it will be useful,
00016     but WITHOUT ANY WARRANTY; without even the implied warranty of
00017     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018     GNU General Public License for more details.
00019     
00020     You should have received a copy of the GNU General Public License
00021     along with this program; if not, write to the Free Software
00022     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
00023 */
00024 
00025 /*
00026  * TODO: Many things...
00027  *  {begin,end}_{bead,article}, box stack, name tree (not limited to dests)...
00028  */
00029 #if HAVE_CONFIG_H
00030 #include "config.h"
00031 #endif
00032 
00033 #include <time.h>
00034 #include <string.h>
00035 
00036 #include "system.h"
00037 #include "mem.h"
00038 #include "error.h"
00039 #include "mfileio.h"
00040 
00041 #include "numbers.h"
00042 
00043 #include "pdfobj.h"
00044 #include "pdfparse.h"
00045 #include "pdfnames.h"
00046 
00047 #include "pdfencrypt.h"
00048 
00049 #include "pdfdev.h"
00050 #include "pdfdraw.h"
00051 #include "pdfcolor.h"
00052 
00053 #include "pdfresource.h"
00054 #include "pdffont.h"
00055 #include "pdfximage.h"
00056 
00057 #include "pdflimits.h"
00058 
00059 #if HAVE_LIBPNG
00060 #include "pngimage.h"
00061 #endif
00062 #include "jpegimage.h"
00063 
00064 #include "pdfdoc.h"
00065 
00066 #define PDFDOC_PAGES_ALLOC_SIZE   128u
00067 #define PDFDOC_ARTICLE_ALLOC_SIZE 16
00068 #define PDFDOC_BEAD_ALLOC_SIZE    16
00069 
00070 static int verbose = 0;
00071 
00072 static char  manual_thumb_enabled  = 0;
00073 static char *thumb_basename = NULL;
00074 
00075 void
00076 pdf_doc_enable_manual_thumbnails (void)
00077 {
00078 #if HAVE_LIBPNG
00079   manual_thumb_enabled = 1;
00080 #else
00081   WARN("Manual thumbnail is not supported without the libpng library.");
00082 #endif
00083 }
00084 
00085 static pdf_obj *
00086 read_thumbnail (const char *thumb_filename) 
00087 {
00088   pdf_obj *image_ref;
00089   int      xobj_id;
00090   FILE    *fp;
00091 
00092   fp = MFOPEN(thumb_filename, FOPEN_RBIN_MODE);
00093   if (!fp) {
00094     WARN("Could not open thumbnail file \"%s\"", thumb_filename);
00095     return NULL;
00096   }
00097 #if HAVE_LIBPNG
00098   if (!check_for_png(fp) && !check_for_jpeg(fp)) {
00099     WARN("Thumbnail \"%s\" not a png/jpeg file!", thumb_filename);
00100     MFCLOSE(fp);
00101     return NULL;
00102   }
00103 #else
00104   if ( !check_for_jpeg(fp)) {
00105     WARN("Thumbnail \"%s\" not a jpeg file!", thumb_filename);
00106     MFCLOSE(fp);
00107     return NULL;
00108   }
00109 #endif
00110   MFCLOSE(fp);
00111 
00112   xobj_id = pdf_ximage_findresource(thumb_filename, 0, NULL);
00113   if (xobj_id < 0) {
00114     WARN("Could not read thumbnail file \"%s\".", thumb_filename);
00115     image_ref = NULL;
00116   } else {
00117     image_ref = pdf_ximage_get_reference(xobj_id);
00118   }
00119 
00120   return image_ref;
00121 }
00122 
00123 void
00124 pdf_doc_set_verbose (void)
00125 {
00126   verbose++;
00127   pdf_font_set_verbose();
00128   pdf_color_set_verbose();
00129   pdf_ximage_set_verbose();
00130 }
00131 
00132 typedef struct pdf_form
00133 {
00134   char       *ident;
00135 
00136   pdf_tmatrix matrix;
00137   pdf_rect    cropbox;
00138 
00139   pdf_obj    *resources;
00140   pdf_obj    *contents;
00141 } pdf_form;
00142 
00143 struct form_list_node
00144 {
00145   int      q_depth;
00146   pdf_form form;
00147 
00148   struct form_list_node *prev;
00149 };
00150 
00151 #define USE_MY_MEDIABOX (1 << 0)
00152 typedef struct pdf_page
00153 {
00154   pdf_obj  *page_obj;
00155   pdf_obj  *page_ref;
00156 
00157   int       flags;
00158 
00159   double    ref_x, ref_y;
00160   pdf_rect  cropbox;
00161 
00162   pdf_obj  *resources;
00163 
00164   /* Contents */
00165   pdf_obj  *background;
00166   pdf_obj  *contents;
00167 
00168   /* global bop, background, contents, global eop */
00169   pdf_obj  *content_refs[4];
00170 
00171   pdf_obj  *annots;
00172   pdf_obj  *beads;
00173 } pdf_page;
00174 
00175 typedef struct pdf_olitem
00176 {
00177   pdf_obj *dict;
00178 
00179   int      is_open;
00180 
00181   struct pdf_olitem *first;
00182   struct pdf_olitem *parent;
00183 
00184   struct pdf_olitem *next;
00185 } pdf_olitem;
00186 
00187 typedef struct pdf_bead
00188 {
00189   char    *id;
00190   long     page_no;
00191   pdf_rect rect;
00192 } pdf_bead;
00193 
00194 typedef struct pdf_article
00195 {
00196   char     *id;
00197   pdf_obj  *info;
00198   long      num_beads;
00199   long      max_beads;
00200   pdf_bead *beads;
00201 } pdf_article;
00202 
00203 struct name_dict
00204 {
00205   char  *category;
00206   struct ht_table *data;
00207 };
00208 
00209 
00210 typedef struct pdf_doc
00211 {
00212   struct {
00213     pdf_obj *dict;
00214 
00215     pdf_obj *viewerpref;
00216     pdf_obj *pagelabels;
00217     pdf_obj *pages;
00218     pdf_obj *names;
00219     pdf_obj *threads;
00220   } root;
00221 
00222   pdf_obj *info;
00223 
00224   struct {
00225     pdf_rect mediabox;
00226     pdf_obj *bop, *eop;
00227 
00228     long      num_entries; /* This is not actually total number of pages. */
00229     long      max_entries;
00230     pdf_page *entries;
00231   } pages;
00232 
00233   struct {
00234     pdf_olitem *first;
00235     pdf_olitem *current;
00236     int         current_depth;
00237   } outlines;
00238 
00239   struct {
00240     long         num_entries;
00241     long         max_entries;
00242     pdf_article *entries;
00243   } articles;
00244 
00245   struct name_dict *names;
00246 
00247   int check_gotos;
00248   struct ht_table gotos;
00249 
00250   struct {
00251     int    outline_open_depth;
00252     double annot_grow;
00253   } opt;
00254 
00255   struct form_list_node *pending_forms;
00256 
00257 } pdf_doc;
00258 static pdf_doc pdoc;
00259 
00260 static void
00261 pdf_doc_init_catalog (pdf_doc *p)
00262 {
00263   p->root.viewerpref = NULL;
00264   p->root.pagelabels = NULL;
00265   p->root.pages      = NULL;
00266   p->root.names      = NULL;
00267   p->root.threads    = NULL;
00268   
00269   p->root.dict = pdf_new_dict();
00270   pdf_set_root(p->root.dict);
00271 
00272   return;
00273 }
00274 
00275 static void
00276 pdf_doc_close_catalog (pdf_doc *p)
00277 {
00278   pdf_obj *tmp;
00279 
00280   if (p->root.viewerpref) {
00281     tmp = pdf_lookup_dict(p->root.dict, "ViewerPreferences");
00282     if (!tmp) {
00283       pdf_add_dict(p->root.dict,
00284                    pdf_new_name("ViewerPreferences"),
00285                    pdf_ref_obj (p->root.viewerpref));
00286     } else if (PDF_OBJ_DICTTYPE(tmp)) {
00287       pdf_merge_dict(p->root.viewerpref, tmp);
00288       pdf_add_dict(p->root.dict,
00289                    pdf_new_name("ViewerPreferences"),
00290                    pdf_ref_obj (p->root.viewerpref));
00291     } else { /* Maybe reference */
00292       /* What should I do? */
00293       WARN("Could not modify ViewerPreferences.");
00294     }
00295     pdf_release_obj(p->root.viewerpref);
00296     p->root.viewerpref = NULL;
00297   }
00298 
00299   if (p->root.pagelabels) {
00300     tmp = pdf_lookup_dict(p->root.dict, "PageLabels");
00301     if (!tmp) {
00302       tmp = pdf_new_dict();
00303       pdf_add_dict(tmp, pdf_new_name("Nums"),  pdf_link_obj(p->root.pagelabels));
00304       pdf_add_dict(p->root.dict,
00305                    pdf_new_name("PageLabels"), pdf_ref_obj(tmp));
00306       pdf_release_obj(tmp);
00307     } else { /* Maybe reference */
00308       /* What should I do? */
00309       WARN("Could not modify PageLabels.");
00310     }
00311     pdf_release_obj(p->root.pagelabels);
00312     p->root.pagelabels = NULL;
00313   }
00314 
00315   pdf_add_dict(p->root.dict,
00316                pdf_new_name("Type"), pdf_new_name("Catalog"));
00317   pdf_release_obj(p->root.dict);
00318   p->root.dict = NULL;
00319 
00320   return;
00321 }
00322 
00323 /*
00324  * Pages are starting at 1.
00325  * The page count does not increase until the page is finished.
00326  */
00327 #define LASTPAGE(p)  (&(p->pages.entries[p->pages.num_entries]))
00328 #define FIRSTPAGE(p) (&(p->pages.entries[0]))
00329 #define PAGECOUNT(p) (p->pages.num_entries)
00330 #define MAXPAGES(p)  (p->pages.max_entries)
00331 
00332 static void
00333 doc_resize_page_entries (pdf_doc *p, long size)
00334 {
00335   if (size > MAXPAGES(p)) {
00336     long i;
00337 
00338     p->pages.entries = RENEW(p->pages.entries, size, struct pdf_page);
00339     for (i = p->pages.max_entries; i < size; i++) {
00340       p->pages.entries[i].page_obj   = NULL;
00341       p->pages.entries[i].page_ref   = NULL;
00342       p->pages.entries[i].flags      = 0;
00343       p->pages.entries[i].resources  = NULL;
00344       p->pages.entries[i].background = NULL;
00345       p->pages.entries[i].contents   = NULL;
00346       p->pages.entries[i].content_refs[0] = NULL; /* global bop */
00347       p->pages.entries[i].content_refs[1] = NULL; /* background */
00348       p->pages.entries[i].content_refs[2] = NULL; /* page body  */
00349       p->pages.entries[i].content_refs[3] = NULL; /* global eop */
00350       p->pages.entries[i].annots    = NULL;
00351       p->pages.entries[i].beads     = NULL;
00352     }
00353     p->pages.max_entries = size;
00354   }
00355 
00356   return;
00357 }
00358 
00359 static pdf_page *
00360 doc_get_page_entry (pdf_doc *p, unsigned long page_no)
00361 {
00362   pdf_page *page;
00363 
00364   if (page_no > 65535ul) {
00365     ERROR("Page number %ul too large!", page_no);
00366   } else if (page_no == 0) {
00367     ERROR("Invalid Page number %ul.", page_no);
00368   }
00369 
00370   if (page_no > MAXPAGES(p)) {
00371     doc_resize_page_entries(p, page_no + PDFDOC_PAGES_ALLOC_SIZE);
00372   }
00373 
00374   page = &(p->pages.entries[page_no - 1]);
00375 
00376   return page;
00377 }
00378 
00379 static void pdf_doc_init_page_tree  (pdf_doc *p, double media_width, double media_height);
00380 static void pdf_doc_close_page_tree (pdf_doc *p);
00381 
00382 static void pdf_doc_init_names  (pdf_doc *p, int check_gotos);
00383 static void pdf_doc_close_names (pdf_doc *p);
00384 
00385 static void pdf_doc_add_goto (pdf_obj *annot_dict);
00386 
00387 static void pdf_doc_init_docinfo  (pdf_doc *p);
00388 static void pdf_doc_close_docinfo (pdf_doc *p);
00389 
00390 static void pdf_doc_init_articles    (pdf_doc *p);
00391 static void pdf_doc_close_articles   (pdf_doc *p);
00392 static void pdf_doc_init_bookmarks   (pdf_doc *p, int bm_open_depth);
00393 static void pdf_doc_close_bookmarks  (pdf_doc *p);
00394 
00395 void
00396 pdf_doc_set_bop_content (const char *content, unsigned length)
00397 {
00398   pdf_doc *p = &pdoc;
00399 
00400   ASSERT(p);
00401 
00402   if (p->pages.bop) {
00403     pdf_release_obj(p->pages.bop);
00404     p->pages.bop = NULL;
00405   }
00406 
00407   if (length > 0) {
00408     p->pages.bop = pdf_new_stream(STREAM_COMPRESS);
00409     pdf_add_stream(p->pages.bop, content, length);
00410   } else {
00411     p->pages.bop = NULL;
00412   }
00413 
00414   return;
00415 }
00416 
00417 void
00418 pdf_doc_set_eop_content (const char *content, unsigned length)
00419 {
00420   pdf_doc *p = &pdoc;
00421 
00422   if (p->pages.eop) {
00423     pdf_release_obj(p->pages.eop);
00424     p->pages.eop = NULL;
00425   }
00426 
00427   if (length > 0) {
00428     p->pages.eop = pdf_new_stream(STREAM_COMPRESS);
00429     pdf_add_stream(p->pages.eop, content, length);
00430   } else {
00431     p->pages.eop = NULL;
00432   }
00433 
00434   return;
00435 }
00436 
00437 #ifndef HAVE_TM_GMTOFF
00438 #ifndef HAVE_TIMEZONE
00439 
00440 /* auxiliary function to compute timezone offset on
00441    systems that do not support the tm_gmtoff in struct tm,
00442    or have a timezone variable.  Such as i386-solaris.  */
00443 
00444 static long
00445 compute_timezone_offset()
00446 {
00447   const time_t now = time(NULL);
00448   struct tm tm;
00449   struct tm local;
00450   time_t gmtoff;
00451 
00452   localtime_r(&now, &local);
00453   gmtime_r(&now, &tm);
00454   return (mktime(&local) - mktime(&tm));
00455 }
00456 
00457 #endif /* HAVE_TIMEZONE */
00458 #endif /* HAVE_TM_GMTOFF */
00459 
00460 /*
00461  * Docinfo
00462  */
00463 static long
00464 asn_date (char *date_string)
00465 {
00466   long        tz_offset;
00467   time_t      current_time;
00468   struct tm  *bd_time;
00469 
00470   time(&current_time);
00471   bd_time = localtime(&current_time);
00472 
00473 #ifdef HAVE_TM_GMTOFF
00474   tz_offset = bd_time->tm_gmtoff;
00475 #else
00476 #  ifdef HAVE_TIMEZONE
00477   tz_offset = -timezone;
00478 #  else
00479   tz_offset = compute_timezone_offset();
00480 #  endif /* HAVE_TIMEZONE */
00481 #endif /* HAVE_TM_GMTOFF */
00482 
00483   sprintf(date_string, "D:%04d%02d%02d%02d%02d%02d%c%02ld'%02ld'",
00484          bd_time->tm_year + 1900, bd_time->tm_mon + 1, bd_time->tm_mday,
00485          bd_time->tm_hour, bd_time->tm_min, bd_time->tm_sec,
00486          (tz_offset > 0) ? '+' : '-', labs(tz_offset) / 3600,
00487                                       (labs(tz_offset) / 60) % 60);
00488 
00489   return strlen(date_string);
00490 }
00491 
00492 static void
00493 pdf_doc_init_docinfo (pdf_doc *p)
00494 {
00495   p->info = pdf_new_dict();
00496   pdf_set_info(p->info);
00497 
00498   return;
00499 }
00500 
00501 static void
00502 pdf_doc_close_docinfo (pdf_doc *p)
00503 {
00504   pdf_obj *docinfo = p->info;
00505 
00506   /*
00507    * Excerpt from PDF Reference 4th ed., sec. 10.2.1.
00508    *
00509    * Any entry whose value is not known should be omitted from the dictionary,
00510    * rather than included with an empty string as its value.
00511    *
00512    * ....
00513    *
00514    * Note: Although viewer applications can store custom metadata in the document
00515    * information dictionary, it is inappropriate to store private content or
00516    * structural information there; such information should be stored in the
00517    * document catalog instead (see Section 3.6.1,  Document Catalog ).
00518    */
00519   const char *keys[] = {
00520     "Title", "Author", "Subject", "Keywords", "Creator", "Producer",
00521     "CreationDate", "ModDate", /* Date */
00522     NULL
00523   };
00524   pdf_obj *value;
00525   char    *banner;
00526   int      i;
00527 
00528   for (i = 0; keys[i] != NULL; i++) {
00529     value = pdf_lookup_dict(docinfo, keys[i]);
00530     if (value) {
00531       if (!PDF_OBJ_STRINGTYPE(value)) {
00532         WARN("\"%s\" in DocInfo dictionary not string type.", keys[i]);
00533         pdf_remove_dict(docinfo, keys[i]);
00534         WARN("\"%s\" removed from DocInfo.", keys[i]);
00535       } else if (pdf_string_length(value) == 0) {
00536         /* The hyperref package often uses emtpy strings. */
00537         pdf_remove_dict(docinfo, keys[i]);
00538       }
00539     }
00540   }
00541 
00542   banner = NEW(strlen(PACKAGE)+strlen(VERSION)+4, char);
00543   sprintf(banner, "%s (%s)", PACKAGE, VERSION);
00544   pdf_add_dict(docinfo,
00545                pdf_new_name("Producer"),
00546                pdf_new_string(banner, strlen(banner)));
00547   RELEASE(banner);
00548   
00549   if (!pdf_lookup_dict(docinfo, "CreationDate")) {
00550     char now[32];
00551 
00552     asn_date(now);
00553     pdf_add_dict(docinfo, 
00554                  pdf_new_name ("CreationDate"),
00555                  pdf_new_string(now, strlen(now)));
00556   }
00557 
00558   pdf_release_obj(docinfo);
00559   p->info = NULL;
00560 
00561   return;
00562 }
00563 
00564 static pdf_obj *
00565 pdf_doc_get_page_resources (pdf_doc *p, const char *category)
00566 {
00567   pdf_obj  *resources;
00568   pdf_page *currentpage;
00569   pdf_obj  *res_dict;
00570 
00571   if (!p || !category) {
00572     return NULL;
00573   }
00574 
00575   if (p->pending_forms) {
00576     if (p->pending_forms->form.resources) {
00577       res_dict = p->pending_forms->form.resources;
00578     } else {
00579       res_dict = p->pending_forms->form.resources = pdf_new_dict();
00580     }
00581   } else {
00582     currentpage = LASTPAGE(p);
00583     if (currentpage->resources) {
00584       res_dict = currentpage->resources;
00585     } else {
00586       res_dict = currentpage->resources = pdf_new_dict();
00587     }
00588   }
00589   resources = pdf_lookup_dict(res_dict, category);
00590   if (!resources) {
00591     resources = pdf_new_dict();
00592     pdf_add_dict(res_dict, pdf_new_name(category), resources);
00593   }
00594 
00595   return resources;
00596 }
00597 
00598 void
00599 pdf_doc_add_page_resource (const char *category,
00600                            const char *resource_name, pdf_obj *resource_ref)
00601 {
00602   pdf_doc *p = &pdoc;
00603   pdf_obj *resources;
00604   pdf_obj *duplicate;
00605 
00606   if (!PDF_OBJ_INDIRECTTYPE(resource_ref)) {
00607     WARN("Passed non indirect reference...");
00608     resource_ref = pdf_ref_obj(resource_ref); /* leak */
00609   }
00610   resources = pdf_doc_get_page_resources(p, category);
00611   duplicate = pdf_lookup_dict(resources, resource_name);
00612   if (duplicate && pdf_compare_reference(duplicate, resource_ref)) {
00613     WARN("Conflicting page resource found (page: %ld, category: %s, name: %s).",
00614          pdf_doc_current_page_number(), category, resource_name);
00615     WARN("Ignoring...");
00616     pdf_release_obj(resource_ref);
00617   } else {
00618     pdf_add_dict(resources, pdf_new_name(resource_name), resource_ref);
00619   }
00620 
00621   return;
00622 }
00623 
00624 static void
00625 doc_flush_page (pdf_doc *p, pdf_page *page, pdf_obj *parent_ref)
00626 {
00627   pdf_obj *contents_array;
00628   int      count;
00629 
00630   pdf_add_dict(page->page_obj,
00631                pdf_new_name("Type"), pdf_new_name("Page"));
00632   pdf_add_dict(page->page_obj,
00633                pdf_new_name("Parent"), parent_ref);
00634 
00635   /*
00636    * Clipping area specified by CropBox is affected by MediaBox which
00637    * might be inherit from parent node. If MediaBox of the root node
00638    * does not have enough size to cover all page's imaging area, using
00639    * CropBox here gives incorrect result.
00640    */
00641   if (page->flags & USE_MY_MEDIABOX) {
00642     pdf_obj *mediabox;
00643 
00644     mediabox = pdf_new_array();
00645     pdf_add_array(mediabox,
00646                   pdf_new_number(ROUND(page->cropbox.llx, 0.01)));
00647     pdf_add_array(mediabox,
00648                   pdf_new_number(ROUND(page->cropbox.lly, 0.01)));
00649     pdf_add_array(mediabox,
00650                   pdf_new_number(ROUND(page->cropbox.urx, 0.01)));
00651     pdf_add_array(mediabox,
00652                   pdf_new_number(ROUND(page->cropbox.ury, 0.01)));
00653     pdf_add_dict(page->page_obj, pdf_new_name("MediaBox"),  mediabox);
00654   }
00655 
00656   count = 0;
00657   contents_array = pdf_new_array();
00658   if (page->content_refs[0]) { /* global bop */
00659     pdf_add_array(contents_array, page->content_refs[0]);
00660     count++;
00661   } else if (p->pages.bop &&
00662              pdf_stream_length(p->pages.bop) > 0) {
00663     pdf_add_array(contents_array, pdf_ref_obj(p->pages.bop));
00664     count++;
00665   }
00666   if (page->content_refs[1]) { /* background */
00667     pdf_add_array(contents_array, page->content_refs[1]);
00668     count++;
00669   }
00670   if (page->content_refs[2]) { /* page body */
00671     pdf_add_array(contents_array, page->content_refs[2]);
00672     count++;
00673   }
00674   if (page->content_refs[3]) { /* global eop */
00675     pdf_add_array(contents_array, page->content_refs[3]);
00676     count++;
00677   } else if (p->pages.eop &&
00678              pdf_stream_length(p->pages.eop) > 0) {
00679     pdf_add_array(contents_array, pdf_ref_obj(p->pages.eop));
00680     count++;
00681   }
00682 
00683   if (count == 0) {
00684     WARN("Page with empty content found!!!");
00685   }
00686   page->content_refs[0] = NULL;
00687   page->content_refs[1] = NULL;
00688   page->content_refs[2] = NULL;
00689   page->content_refs[3] = NULL;
00690 
00691   pdf_add_dict(page->page_obj,
00692                pdf_new_name("Contents"), contents_array);
00693 
00694 
00695   if (page->annots) {
00696     pdf_add_dict(page->page_obj,
00697                  pdf_new_name("Annots"), pdf_ref_obj(page->annots));
00698     pdf_release_obj(page->annots);
00699   }
00700   if (page->beads) {
00701     pdf_add_dict(page->page_obj,
00702                  pdf_new_name("B"), pdf_ref_obj(page->beads));
00703     pdf_release_obj(page->beads);
00704   }
00705   pdf_release_obj(page->page_obj);
00706   pdf_release_obj(page->page_ref);
00707 
00708   page->page_obj = NULL;
00709   page->page_ref = NULL;
00710   page->annots   = NULL;
00711   page->beads    = NULL;
00712 
00713   return;
00714 }
00715 
00716 /* B-tree? */
00717 #define PAGE_CLUSTER 4
00718 static pdf_obj *
00719 build_page_tree (pdf_doc  *p,
00720                  pdf_page *firstpage, long num_pages,
00721                  pdf_obj  *parent_ref)
00722 {
00723   pdf_obj *self, *self_ref, *kids;
00724   long     i;
00725 
00726   self = pdf_new_dict();
00727   /*
00728    * This is a slight kludge which allow the subtree dictionary
00729    * generated by this routine to be merged with the real
00730    * page_tree dictionary, while keeping the indirect object
00731    * references right.
00732    */
00733   self_ref = parent_ref ? pdf_ref_obj(self) : pdf_ref_obj(p->root.pages);
00734 
00735   pdf_add_dict(self, pdf_new_name("Type"),  pdf_new_name("Pages"));
00736   pdf_add_dict(self, pdf_new_name("Count"), pdf_new_number((double) num_pages));
00737 
00738   if (parent_ref != NULL)
00739     pdf_add_dict(self, pdf_new_name("Parent"), parent_ref);
00740 
00741   kids = pdf_new_array();
00742   if (num_pages > 0 && num_pages <= PAGE_CLUSTER) {
00743     for (i = 0; i < num_pages; i++) {
00744       pdf_page *page;
00745 
00746       page = firstpage + i;
00747       if (!page->page_ref)
00748         page->page_ref = pdf_ref_obj(page->page_obj);
00749       pdf_add_array (kids, pdf_link_obj(page->page_ref));
00750       doc_flush_page(p, page, pdf_link_obj(self_ref));
00751     }
00752   } else if (num_pages > 0) {
00753     for (i = 0; i < PAGE_CLUSTER; i++) {
00754       long start, end;
00755 
00756       start = (i*num_pages)/PAGE_CLUSTER;
00757       end   = ((i+1)*num_pages)/PAGE_CLUSTER;
00758       if (end - start > 1) {
00759         pdf_obj *subtree;
00760 
00761         subtree = build_page_tree(p, firstpage + start, end - start,
00762                                   pdf_link_obj(self_ref));
00763         pdf_add_array(kids, pdf_ref_obj(subtree));
00764         pdf_release_obj(subtree);
00765       } else {
00766         pdf_page *page;
00767 
00768         page = firstpage + start;
00769         if (!page->page_ref)
00770           page->page_ref = pdf_ref_obj(page->page_obj);
00771         pdf_add_array (kids, pdf_link_obj(page->page_ref));
00772         doc_flush_page(p, page, pdf_link_obj(self_ref));
00773       }
00774     }
00775   }
00776   pdf_add_dict(self, pdf_new_name("Kids"), kids);
00777   pdf_release_obj(self_ref);
00778 
00779   return self;
00780 }
00781 
00782 static void
00783 pdf_doc_init_page_tree (pdf_doc *p, double media_width, double media_height)
00784 {
00785   /*
00786    * Create empty page tree.
00787    * The docroot.pages is kept open until the document is closed.
00788    * This allows the user to write to pages if he so choses.
00789    */
00790   p->root.pages = pdf_new_dict();
00791 
00792   p->pages.num_entries = 0;
00793   p->pages.max_entries = 0;
00794   p->pages.entries     = NULL;
00795 
00796   p->pages.bop = NULL;
00797   p->pages.eop = NULL;
00798 
00799   p->pages.mediabox.llx = 0.0;
00800   p->pages.mediabox.lly = 0.0;
00801   p->pages.mediabox.urx = media_width;
00802   p->pages.mediabox.ury = media_height;
00803 
00804   return;
00805 }
00806 
00807 static void
00808 pdf_doc_close_page_tree (pdf_doc *p)
00809 {
00810   pdf_obj *page_tree_root;
00811   pdf_obj *mediabox;
00812   long     page_no;
00813 
00814   /*
00815    * Do consistency check on forward references to pages.
00816    */
00817   for (page_no = PAGECOUNT(p) + 1; page_no <= MAXPAGES(p); page_no++) {
00818     pdf_page  *page;
00819 
00820     page = doc_get_page_entry(p, page_no);
00821     if (page->page_obj) {
00822       WARN("Nonexistent page #%ld refered.", page_no);
00823       pdf_release_obj(page->page_ref);
00824       page->page_ref = NULL;
00825     }
00826     if (page->page_obj) {
00827       WARN("Entry for a nonexistent page #%ld created.", page_no);
00828       pdf_release_obj(page->page_obj);
00829       page->page_obj = NULL;
00830     }
00831     if (page->annots) {
00832       WARN("Annotation attached to a nonexistent page #%ld.", page_no);
00833       pdf_release_obj(page->annots);
00834       page->annots = NULL;
00835     }
00836     if (page->beads) {
00837       WARN("Article beads attached to a nonexistent page #%ld.", page_no);
00838       pdf_release_obj(page->beads);
00839       page->beads = NULL;
00840     }
00841     if (page->resources) {
00842       pdf_release_obj(page->resources);
00843       page->resources = NULL;
00844     }
00845   }
00846 
00847   /*
00848    * Connect page tree to root node.
00849    */
00850   page_tree_root = build_page_tree(p, FIRSTPAGE(p), PAGECOUNT(p), NULL);
00851   pdf_merge_dict (p->root.pages, page_tree_root);
00852   pdf_release_obj(page_tree_root);
00853 
00854   /* They must be after build_page_tree() */
00855   if (p->pages.bop) {
00856     pdf_add_stream (p->pages.bop, "\n", 1);
00857     pdf_release_obj(p->pages.bop);
00858     p->pages.bop = NULL;
00859   }
00860   if (p->pages.eop) {
00861     pdf_add_stream (p->pages.eop, "\n", 1);
00862     pdf_release_obj(p->pages.eop);
00863     p->pages.eop = NULL;
00864   }
00865 
00866   /* Create media box at root node and let the other pages inherit it. */
00867   mediabox = pdf_new_array();
00868   pdf_add_array(mediabox, pdf_new_number(ROUND(p->pages.mediabox.llx, 0.01)));
00869   pdf_add_array(mediabox, pdf_new_number(ROUND(p->pages.mediabox.lly, 0.01)));
00870   pdf_add_array(mediabox, pdf_new_number(ROUND(p->pages.mediabox.urx, 0.01)));
00871   pdf_add_array(mediabox, pdf_new_number(ROUND(p->pages.mediabox.ury, 0.01)));
00872   pdf_add_dict(p->root.pages, pdf_new_name("MediaBox"), mediabox);
00873 
00874   pdf_add_dict(p->root.dict,
00875                pdf_new_name("Pages"),
00876                pdf_ref_obj (p->root.pages));
00877   pdf_release_obj(p->root.pages);
00878   p->root.pages  = NULL;
00879 
00880   RELEASE(p->pages.entries);
00881   p->pages.entries     = NULL;
00882   p->pages.num_entries = 0;
00883   p->pages.max_entries = 0;
00884 
00885   return;
00886 }
00887 
00888 /*
00889  * From PDFReference15_v6.pdf (p.119 and p.834)
00890  *
00891  * MediaBox rectangle (Required; inheritable)
00892  *
00893  * The media box defines the boundaries of the physical medium on which the
00894  * page is to be printed. It may include any extended area surrounding the
00895  * finished page for bleed, printing marks, or other such purposes. It may
00896  * also include areas close to the edges of the medium that cannot be marked
00897  * because of physical limitations of the output device. Content falling
00898  * outside this boundary can safely be discarded without affecting the
00899  * meaning of the PDF file.
00900  *
00901  * CropBox rectangle (Optional; inheritable)
00902  *
00903  * The crop box defines the region to which the contents of the page are to be
00904  * clipped (cropped) when displayed or printed. Unlike the other boxes, the
00905  * crop box has no defined meaning in terms of physical page geometry or
00906  * intended use; it merely imposes clipping on the page contents. However,
00907  * in the absence of additional information (such as imposition instructions
00908  * specified in a JDF or PJTF job ticket), the crop box will determine how
00909  * the page's contents are to be positioned on the output medium. The default
00910  * value is the page's media box. 
00911  *
00912  * BleedBox rectangle (Optional; PDF 1.3)
00913  *
00914  * The bleed box (PDF 1.3) defines the region to which the contents of the
00915  * page should be clipped when output in a production environment. This may
00916  * include any extra "bleed area" needed to accommodate the physical
00917  * limitations of cutting, folding, and trimming equipment. The actual printed
00918  * page may include printing marks that fall outside the bleed box.
00919  * The default value is the page's crop box. 
00920  *
00921  * TrimBox rectangle (Optional; PDF 1.3)
00922  *
00923  * The trim box (PDF 1.3) defines the intended dimensions of the finished page
00924  * after trimming. It may be smaller than the media box, to allow for
00925  * production-related content such as printing instructions, cut marks, or
00926  * color bars. The default value is the page's crop box. 
00927  *
00928  * ArtBox rectangle (Optional; PDF 1.3)
00929  *
00930  * The art box (PDF 1.3) defines the extent of the page's meaningful content
00931  * (including potential white space) as intended by the page's creator.
00932  * The default value is the page's crop box.
00933  *
00934  * Rotate integer (Optional; inheritable)
00935  *
00936  * The number of degrees by which the page should be rotated clockwise when
00937  * displayed or printed. The value must be a multiple of 90. Default value: 0.
00938  */
00939 
00940 pdf_obj *
00941 pdf_doc_get_page (pdf_file *pf, long page_no, long *count_p,
00942                 pdf_rect *bbox, pdf_obj **resources_p) {
00943   pdf_obj *page_tree = NULL;
00944   pdf_obj *resources = NULL, *box = NULL, *rotate = NULL;
00945   pdf_obj *catalog;
00946 
00947   catalog = pdf_file_get_catalog(pf);
00948 
00949   page_tree = pdf_deref_obj(pdf_lookup_dict(catalog, "Pages"));
00950 
00951   if (!PDF_OBJ_DICTTYPE(page_tree))
00952     goto error;
00953 
00954   {
00955     long count;
00956     pdf_obj *tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "Count"));
00957     if (!PDF_OBJ_NUMBERTYPE(tmp)) {
00958       if (tmp)
00959        pdf_release_obj(tmp);
00960       goto error;
00961     }
00962     count = pdf_number_value(tmp);
00963     pdf_release_obj(tmp);
00964     if (count_p)
00965       *count_p = count;
00966     if (page_no <= 0 || page_no > count) {
00967        WARN("Page %ld does not exist.", page_no);
00968        goto error_silent;
00969       }
00970   }
00971 
00972   /*
00973    * Seek correct page. Get MediaBox, CropBox and Resources.
00974    * (Note that these entries can be inherited.)
00975    */
00976   {
00977     pdf_obj *media_box = NULL, *crop_box = NULL, *kids, *tmp;
00978     int depth = PDF_OBJ_MAX_DEPTH;
00979     long page_idx = page_no-1, kids_length = 1, i = 0;
00980 
00981     while (--depth && i != kids_length) {
00982       if ((tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "MediaBox")))) {
00983        if (media_box)
00984          pdf_release_obj(media_box);
00985        media_box = tmp;
00986       }
00987 
00988       if ((tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "CropBox")))) {
00989        if (crop_box)
00990          pdf_release_obj(crop_box);
00991        crop_box = tmp;
00992       }
00993 
00994       if ((tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "Rotate")))) {
00995        if (rotate)
00996          pdf_release_obj(rotate);
00997        rotate = tmp;
00998       }
00999 
01000       if ((tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "Resources")))) {
01001        if (resources)
01002          pdf_release_obj(resources);
01003        resources = tmp;
01004       }
01005 
01006       kids = pdf_deref_obj(pdf_lookup_dict(page_tree, "Kids"));
01007       if (!kids)
01008        break;
01009       else if (!PDF_OBJ_ARRAYTYPE(kids)) {
01010        pdf_release_obj(kids);
01011        goto error;
01012       }
01013       kids_length = pdf_array_length(kids);
01014 
01015       for (i = 0; i < kids_length; i++) {
01016        long count;
01017 
01018        pdf_release_obj(page_tree);
01019        page_tree = pdf_deref_obj(pdf_get_array(kids, i));
01020        if (!PDF_OBJ_DICTTYPE(page_tree))
01021          goto error;
01022 
01023        tmp = pdf_deref_obj(pdf_lookup_dict(page_tree, "Count"));
01024        if (PDF_OBJ_NUMBERTYPE(tmp)) {
01025          /* Pages object */
01026          count = pdf_number_value(tmp);
01027          pdf_release_obj(tmp);
01028        } else if (!tmp)
01029          /* Page object */
01030          count = 1;
01031        else {
01032          pdf_release_obj(tmp);
01033          goto error;
01034        }
01035 
01036        if (page_idx < count)
01037          break;
01038 
01039        page_idx -= count;
01040       }
01041       
01042       pdf_release_obj(kids);
01043     }
01044 
01045     if (!depth || kids_length == i) {
01046       if (media_box)
01047        pdf_release_obj(media_box);
01048      if (crop_box)
01049        pdf_release_obj(crop_box);
01050       goto error;
01051     }
01052 
01053     if (crop_box)
01054       box = crop_box;
01055     else
01056       if (!(box = pdf_deref_obj(pdf_lookup_dict(page_tree, "ArtBox"))) &&
01057          !(box = pdf_deref_obj(pdf_lookup_dict(page_tree, "TrimBox"))) &&
01058          !(box = pdf_deref_obj(pdf_lookup_dict(page_tree, "BleedBox"))) &&
01059          media_box) {
01060          box = media_box;
01061          media_box = NULL;
01062       }
01063     if (media_box)
01064       pdf_release_obj(media_box);
01065   }
01066 
01067   if (!PDF_OBJ_ARRAYTYPE(box) || pdf_array_length(box) != 4 ||
01068       !PDF_OBJ_DICTTYPE(resources))
01069     goto error;
01070 
01071   if (PDF_OBJ_NUMBERTYPE(rotate)) {
01072     if (pdf_number_value(rotate))
01073       WARN("<< /Rotate %d >> found. (Not supported yet)", 
01074           (int) pdf_number_value(rotate));
01075     pdf_release_obj(rotate);
01076     rotate = NULL;
01077   } else if (rotate)
01078     goto error;
01079 
01080   {
01081     int i;
01082 
01083     for (i = 4; i--; ) {
01084       double x;
01085       pdf_obj *tmp = pdf_deref_obj(pdf_get_array(box, i));
01086       if (!PDF_OBJ_NUMBERTYPE(tmp)) {
01087        pdf_release_obj(tmp);
01088        goto error;
01089       }
01090       x = pdf_number_value(tmp);
01091       switch (i) {
01092       case 0: bbox->llx = x; break;
01093       case 1: bbox->lly = x; break;
01094       case 2: bbox->urx = x; break;
01095       case 3: bbox->ury = x; break;
01096       }
01097       pdf_release_obj(tmp);
01098     }
01099   }
01100 
01101   pdf_release_obj(box);
01102 
01103   if (resources_p)
01104     *resources_p = resources;
01105   else if (resources)
01106     pdf_release_obj(resources);
01107 
01108   return page_tree;
01109 
01110  error:
01111   WARN("Cannot parse document. Broken PDF file?");
01112  error_silent:
01113   if (box)
01114     pdf_release_obj(box);
01115   if (rotate)
01116     pdf_release_obj(rotate);
01117   if (resources)
01118     pdf_release_obj(resources);
01119   if (page_tree)
01120     pdf_release_obj(page_tree);
01121 
01122   return NULL;
01123 }
01124 
01125 #ifndef BOOKMARKS_OPEN_DEFAULT
01126 #define BOOKMARKS_OPEN_DEFAULT 0
01127 #endif
01128 
01129 static int clean_bookmarks (pdf_olitem *item);
01130 static int flush_bookmarks (pdf_olitem *item,
01131                             pdf_obj *parent_ref,
01132                             pdf_obj *parent_dict);
01133 
01134 static void
01135 pdf_doc_init_bookmarks (pdf_doc *p, int bm_open_depth)
01136 {
01137   pdf_olitem *item;
01138 
01139 #define MAX_OUTLINE_DEPTH 256u
01140   p->opt.outline_open_depth =
01141     ((bm_open_depth >= 0) ?
01142      bm_open_depth : MAX_OUTLINE_DEPTH - bm_open_depth);
01143 
01144   p->outlines.current_depth = 1;
01145 
01146   item = NEW(1, pdf_olitem);
01147   item->dict    = NULL;
01148   item->next    = NULL;
01149   item->first   = NULL;
01150   item->parent  = NULL;
01151   item->is_open = 1;
01152 
01153   p->outlines.current = item;
01154   p->outlines.first   = item;
01155 
01156   return;
01157 }
01158 
01159 static int
01160 clean_bookmarks (pdf_olitem *item)
01161 {
01162   pdf_olitem *next;
01163 
01164   while (item) {
01165     next = item->next;
01166     if (item->dict)
01167       pdf_release_obj(item->dict);
01168     if (item->first)
01169       clean_bookmarks(item->first);
01170     RELEASE(item);
01171     
01172     item = next;
01173   }
01174 
01175   return 0;
01176 }
01177 
01178 static int
01179 flush_bookmarks (pdf_olitem *node,
01180                  pdf_obj *parent_ref, pdf_obj *parent_dict)
01181 {
01182   int         retval;
01183   int         count;
01184   pdf_olitem *item;
01185   pdf_obj    *this_ref, *prev_ref, *next_ref;
01186 
01187   ASSERT(node->dict);
01188 
01189   this_ref = pdf_ref_obj(node->dict);
01190   pdf_add_dict(parent_dict,
01191                pdf_new_name("First"), pdf_link_obj(this_ref));
01192 
01193   retval = 0;
01194   for (item = node, prev_ref = NULL;
01195        item && item->dict; item = item->next) {
01196     if (item->first && item->first->dict) {
01197       count = flush_bookmarks(item->first, this_ref, item->dict);
01198       if (item->is_open) {
01199         pdf_add_dict(item->dict,
01200                      pdf_new_name("Count"),
01201                      pdf_new_number(count));
01202         retval += count;
01203       } else {
01204         pdf_add_dict(item->dict,
01205                      pdf_new_name("Count"),
01206                      pdf_new_number(-count));
01207       }
01208     }
01209     pdf_add_dict(item->dict,
01210                  pdf_new_name("Parent"),
01211                  pdf_link_obj(parent_ref));
01212     if (prev_ref) {
01213       pdf_add_dict(item->dict,
01214                    pdf_new_name("Prev"),
01215                    prev_ref);
01216     }
01217     if (item->next && item->next->dict) {
01218       next_ref = pdf_ref_obj(item->next->dict);
01219       pdf_add_dict(item->dict,
01220                    pdf_new_name("Next"),
01221                    pdf_link_obj(next_ref));
01222     } else {
01223       next_ref = NULL;
01224     }
01225 
01226     pdf_release_obj(item->dict);
01227     item->dict = NULL;
01228 
01229     prev_ref = this_ref;
01230     this_ref = next_ref;
01231     retval++;    
01232   }
01233 
01234   pdf_add_dict(parent_dict,
01235                pdf_new_name("Last"),
01236                pdf_link_obj(prev_ref));
01237 
01238   pdf_release_obj(prev_ref);
01239   pdf_release_obj(node->dict);
01240   node->dict = NULL;
01241 
01242   return retval;
01243 }
01244   
01245 int
01246 pdf_doc_bookmarks_up (void)
01247 {
01248   pdf_doc    *p = &pdoc;
01249   pdf_olitem *parent, *item;
01250 
01251   item = p->outlines.current;
01252   if (!item || !item->parent) {
01253     WARN("Can't go up above the bookmark root node!");
01254     return -1;
01255   }
01256   parent = item->parent;
01257   item   = parent->next;
01258   if (!parent->next) {
01259     parent->next  = item = NEW(1, pdf_olitem);
01260     item->dict    = NULL;
01261     item->first   = NULL;
01262     item->next    = NULL;
01263     item->is_open = 0;
01264     item->parent  = parent->parent;
01265   }
01266   p->outlines.current = item;
01267   p->outlines.current_depth--;
01268 
01269   return 0;
01270 }
01271 
01272 int
01273 pdf_doc_bookmarks_down (void)
01274 {
01275   pdf_doc    *p = &pdoc;
01276   pdf_olitem *item, *first;
01277 
01278   item = p->outlines.current;
01279   if (!item->dict) {
01280     pdf_obj *tcolor, *action;
01281 
01282     WARN("Empty bookmark node!");
01283     WARN("You have tried to jump more than 1 level.");
01284 
01285     item->dict = pdf_new_dict();
01286 
01287 #define TITLE_STRING "<No Title>"
01288     pdf_add_dict(item->dict,
01289                  pdf_new_name("Title"),
01290                  pdf_new_string(TITLE_STRING, strlen(TITLE_STRING)));
01291 
01292     tcolor = pdf_new_array();
01293     pdf_add_array(tcolor, pdf_new_number(1.0));
01294     pdf_add_array(tcolor, pdf_new_number(0.0));
01295     pdf_add_array(tcolor, pdf_new_number(0.0));
01296     pdf_add_dict (item->dict,
01297                   pdf_new_name("C"), pdf_link_obj(tcolor));
01298     pdf_release_obj(tcolor);
01299 
01300     pdf_add_dict (item->dict,
01301                   pdf_new_name("F"), pdf_new_number(1.0));
01302 
01303 #define JS_CODE "app.alert(\"The author of this document made this bookmark item empty!\", 3, 0)"
01304     action = pdf_new_dict();
01305     pdf_add_dict(action,
01306                  pdf_new_name("S"), pdf_new_name("JavaScript"));
01307     pdf_add_dict(action, 
01308                  pdf_new_name("JS"), pdf_new_string(JS_CODE, strlen(JS_CODE)));
01309     pdf_add_dict(item->dict,
01310                  pdf_new_name("A"), pdf_link_obj(action));
01311     pdf_release_obj(action);
01312   }
01313 
01314   item->first    = first = NEW(1, pdf_olitem);
01315   first->dict    = NULL;
01316   first->is_open = 0;
01317   first->parent  = item;
01318   first->next    = NULL;
01319   first->first   = NULL;
01320 
01321   p->outlines.current = first;
01322   p->outlines.current_depth++;
01323 
01324   return 0;
01325 }
01326 
01327 int
01328 pdf_doc_bookmarks_depth (void)
01329 {
01330   pdf_doc *p = &pdoc;
01331 
01332   return p->outlines.current_depth;
01333 }
01334 
01335 void
01336 pdf_doc_bookmarks_add (pdf_obj *dict, int is_open)
01337 {
01338   pdf_doc    *p = &pdoc;
01339   pdf_olitem *item, *next;
01340 
01341   ASSERT(p && dict);
01342 
01343   item = p->outlines.current;
01344 
01345   if (!item) {
01346     item = NEW(1, pdf_olitem);
01347     item->parent = NULL;
01348     p->outlines.first = item;
01349   } else if (item->dict) { /* go to next item */
01350     item = item->next;
01351   }
01352 
01353 #define BMOPEN(b,p) (((b) < 0) ? (((p)->outlines.current_depth > (p)->opt.outline_open_depth) ? 0 : 1) : (b))
01354 
01355 #if 0
01356   item->dict    = pdf_link_obj(dict);
01357 #endif
01358   item->dict    = dict; 
01359   item->first   = NULL;
01360   item->is_open = BMOPEN(is_open, p);
01361 
01362   item->next    = next = NEW(1, pdf_olitem);
01363   next->dict    = NULL;
01364   next->parent  = item->parent;
01365   next->first   = NULL;
01366   next->is_open = -1;
01367   next->next    = NULL;
01368 
01369   p->outlines.current = item;
01370 
01371   pdf_doc_add_goto(dict);
01372 
01373   return;
01374 }
01375 
01376 static void
01377 pdf_doc_close_bookmarks (pdf_doc *p)
01378 {
01379   pdf_obj     *catalog = p->root.dict;
01380   pdf_olitem  *item;
01381   int          count;
01382   pdf_obj     *bm_root, *bm_root_ref;
01383   
01384   item = p->outlines.first;
01385   if (item->dict) {
01386     bm_root     = pdf_new_dict();
01387     bm_root_ref = pdf_ref_obj(bm_root);
01388     count       = flush_bookmarks(item, bm_root_ref, bm_root);
01389     pdf_add_dict(bm_root,
01390                  pdf_new_name("Count"),
01391                  pdf_new_number(count));
01392     pdf_add_dict(catalog,
01393                  pdf_new_name("Outlines"),
01394                  bm_root_ref);
01395     pdf_release_obj(bm_root);
01396   }
01397   clean_bookmarks(item);
01398 
01399   p->outlines.first   = NULL;
01400   p->outlines.current = NULL;
01401   p->outlines.current_depth = 0;
01402 
01403   return;
01404 }
01405 
01406 
01407 static const char *name_dict_categories[] = {
01408   "Dests", "AP", "JavaScript", "Pages",
01409   "Templates", "IDS", "URLS", "EmbeddedFiles",
01410   "AlternatePresentations", "Renditions"
01411 };
01412 #define NUM_NAME_CATEGORY (sizeof(name_dict_categories)/sizeof(name_dict_categories[0]))
01413 
01414 static void
01415 pdf_doc_init_names (pdf_doc *p, int check_gotos)
01416 {
01417   int    i;
01418 
01419   p->root.names   = NULL;
01420   
01421   p->names = NEW(NUM_NAME_CATEGORY + 1, struct name_dict);
01422   for (i = 0; i < NUM_NAME_CATEGORY; i++) {
01423     p->names[i].category = (char *) name_dict_categories[i];
01424     p->names[i].data     = strcmp(name_dict_categories[i], "Dests") ?
01425                              NULL : pdf_new_name_tree();
01426     /*
01427      * We need a non-null entry for PDF destinations in order to find
01428      * broken links even if no destination is defined in the DVI file.
01429      */
01430   }
01431   p->names[NUM_NAME_CATEGORY].category = NULL;
01432   p->names[NUM_NAME_CATEGORY].data     = NULL;
01433 
01434   p->check_gotos   = check_gotos;
01435   ht_init_table(&p->gotos, (void (*) (void *)) pdf_release_obj);
01436 
01437   return;
01438 }
01439 
01440 int
01441 pdf_doc_add_names (const char *category,
01442                    const void *key, int keylen, pdf_obj *value)
01443 {
01444   pdf_doc *p = &pdoc;
01445   int      i;
01446 
01447   for (i = 0; p->names[i].category != NULL; i++) {
01448     if (!strcmp(p->names[i].category, category)) {
01449       break;
01450     }
01451   }
01452   if (p->names[i].category == NULL) {
01453     WARN("Unknown name dictionary category \"%s\".", category);
01454     return -1;
01455   }
01456   if (!p->names[i].data) {
01457     p->names[i].data = pdf_new_name_tree();
01458   }
01459 
01460   return pdf_names_add_object(p->names[i].data, key, keylen, value);
01461 }
01462 
01463 static void
01464 pdf_doc_add_goto (pdf_obj *annot_dict)
01465 {
01466   pdf_obj *subtype = NULL, *A = NULL, *S = NULL, *D = NULL, *D_new, *dict;
01467   char *dest, *key;
01468 
01469   if (!pdoc.check_gotos)
01470     return;
01471 
01472   /*
01473    * An annotation dictionary coming from an annotation special
01474    * must have a "Subtype". An annotation dictionary coming from
01475    * an outline special has none.
01476    */
01477   subtype = pdf_deref_obj(pdf_lookup_dict(annot_dict, "Subtype"));
01478   if (subtype) {
01479     if (PDF_OBJ_UNDEFINED(subtype))
01480       goto undefined;
01481     else if (!PDF_OBJ_NAMETYPE(subtype))
01482       goto error;
01483     else if (strcmp(pdf_name_value(subtype), "Link"))
01484       goto cleanup;
01485   }
01486 
01487   dict = annot_dict;
01488   key = "Dest";
01489   D = pdf_deref_obj(pdf_lookup_dict(annot_dict, key));
01490   if (PDF_OBJ_UNDEFINED(D))
01491     goto undefined;
01492 
01493   A = pdf_deref_obj(pdf_lookup_dict(annot_dict, "A"));
01494   if (A) {
01495     if (PDF_OBJ_UNDEFINED(A))
01496       goto undefined;
01497     else if (D || !PDF_OBJ_DICTTYPE(A))
01498       goto error;
01499     else {
01500       S = pdf_deref_obj(pdf_lookup_dict(A, "S"));
01501       if (PDF_OBJ_UNDEFINED(S))
01502        goto undefined;
01503       else if (!PDF_OBJ_NAMETYPE(S))
01504        goto error;
01505       else if (strcmp(pdf_name_value(S), "GoTo"))
01506        goto cleanup;
01507 
01508       dict = A;
01509       key = "D";
01510       D = pdf_deref_obj(pdf_lookup_dict(A, key));
01511     }
01512   }
01513 
01514   if (PDF_OBJ_STRINGTYPE(D))
01515     dest = (char *) pdf_string_value(D);
01516 #if 0
01517   /* Names as destinations are not supported by dvipdfmx */
01518   else if (PDF_OBJ_NAMETYPE(D))
01519     dest = pdf_name_value(D);
01520 #endif
01521   else if (PDF_OBJ_ARRAYTYPE(D))
01522     goto cleanup;
01523   else if (PDF_OBJ_UNDEFINED(D))
01524     goto undefined;
01525   else
01526     goto error;
01527 
01528   D_new = ht_lookup_table(&pdoc.gotos, dest, strlen(dest));
01529   if (!D_new) {
01530     char buf[10];
01531 
01532     /* We use hexadecimal notation for our numeric destinations.
01533      * Other bases (e.g., 10+26 or 10+2*26) would be more efficient.
01534      */
01535     sprintf(buf, "%lx", ht_table_size(&pdoc.gotos));
01536     D_new = pdf_new_string(buf, strlen(buf));
01537     ht_append_table(&pdoc.gotos, dest, strlen(dest), D_new);
01538   }
01539 
01540   {
01541     pdf_obj *key_obj = pdf_new_name(key);
01542     if (!pdf_add_dict(dict, key_obj, pdf_link_obj(D_new)))
01543       pdf_release_obj(key_obj);
01544   }
01545 
01546  cleanup:
01547   if (subtype)
01548     pdf_release_obj(subtype);
01549   if (A)
01550     pdf_release_obj(A);
01551   if (S)
01552     pdf_release_obj(S);
01553   if (D)
01554     pdf_release_obj(D);
01555 
01556   return;
01557 
01558  error:
01559   WARN("Unknown PDF annotation format. Output file may be broken.");
01560   goto cleanup;
01561 
01562  undefined:
01563   WARN("Cannot optimize PDF annotations. Output file may be broken."
01564        " Please restart with option \"-C 0x10\"\n");
01565   goto cleanup;
01566 }
01567 
01568 static void
01569 warn_undef_dests (struct ht_table *dests, struct ht_table *gotos)
01570 {
01571   struct ht_iter iter;
01572 
01573   if (ht_set_iter(gotos, &iter) < 0)
01574     return;
01575 
01576   do {
01577     int keylen;
01578     char *key = ht_iter_getkey(&iter, &keylen);
01579     if (!ht_lookup_table(dests, key, keylen)) {
01580       char *dest = NEW(keylen+1, char);
01581       memcpy(dest, key, keylen);
01582       dest[keylen] = 0;
01583       WARN("PDF destination \"%s\" not defined.", dest);
01584       RELEASE(dest);
01585     }
01586   } while (ht_iter_next(&iter) >= 0);
01587 
01588   ht_clear_iter(&iter);
01589 }
01590 
01591 static void
01592 pdf_doc_close_names (pdf_doc *p)
01593 {
01594   pdf_obj  *tmp;
01595   int       i;
01596 
01597   for (i = 0; p->names[i].category != NULL; i++) {
01598     if (p->names[i].data) {
01599       struct ht_table *data = p->names[i].data;
01600       pdf_obj  *name_tree;
01601       long count;
01602 
01603       if (!pdoc.check_gotos || strcmp(p->names[i].category, "Dests"))
01604        name_tree = pdf_names_create_tree(data, &count, NULL);
01605       else {
01606        name_tree = pdf_names_create_tree(data, &count, &pdoc.gotos);
01607 
01608        if (verbose && count < data->count)
01609          MESG("\nRemoved %ld unused PDF destinations\n", data->count-count);
01610 
01611        if (count < pdoc.gotos.count)
01612          warn_undef_dests(data, &pdoc.gotos);
01613       }
01614 
01615       if (name_tree) {
01616        if (!p->root.names)
01617          p->root.names = pdf_new_dict();
01618        pdf_add_dict(p->root.names,
01619                    pdf_new_name(p->names[i].category),
01620                    pdf_ref_obj(name_tree));
01621        pdf_release_obj(name_tree);
01622       }
01623       pdf_delete_name_tree(&p->names[i].data);
01624     }
01625   }
01626 
01627   if (p->root.names) {
01628     tmp = pdf_lookup_dict(p->root.dict, "Names");
01629     if (!tmp) {
01630       pdf_add_dict(p->root.dict,
01631                    pdf_new_name("Names"),
01632                    pdf_ref_obj (p->root.names));
01633     } else if (PDF_OBJ_DICTTYPE(tmp)) {
01634       pdf_merge_dict(p->root.names, tmp);
01635       pdf_add_dict(p->root.dict,
01636                    pdf_new_name("Names"),
01637                    pdf_ref_obj (p->root.names));
01638     } else { /* Maybe reference */
01639       /* What should I do? */
01640       WARN("Could not modify Names dictionary.");
01641     }
01642     pdf_release_obj(p->root.names);
01643     p->root.names = NULL;
01644   }
01645 
01646   RELEASE(p->names);
01647   p->names = NULL;
01648 
01649   ht_clear_table(&p->gotos);
01650 
01651   return;
01652 }
01653 
01654 
01655 void
01656 pdf_doc_add_annot (unsigned page_no, const pdf_rect *rect,
01657                  pdf_obj *annot_dict, int new_annot)
01658 {
01659   pdf_doc  *p = &pdoc;
01660   pdf_page *page;
01661   pdf_obj  *rect_array;
01662   double    annot_grow = p->opt.annot_grow;
01663   double    xpos, ypos;
01664   pdf_rect  mediabox, annbox;
01665 
01666   page = doc_get_page_entry(p, page_no);
01667   if (!page->annots)
01668     page->annots = pdf_new_array();
01669 
01670   pdf_doc_get_mediabox(page_no, &mediabox);
01671   pdf_dev_get_coord(&xpos, &ypos);
01672   annbox.llx = rect->llx - xpos; annbox.lly = rect->lly - ypos;
01673   annbox.urx = rect->urx - xpos; annbox.ury = rect->ury - ypos;
01674 
01675   if (annbox.llx < mediabox.llx || annbox.urx > mediabox.urx ||
01676       annbox.lly < mediabox.lly || annbox.ury > mediabox.ury) {
01677     WARN("Annotation out of page boundary.");
01678     WARN("Current page's MediaBox: [%g %g %g %g]",
01679          mediabox.llx, mediabox.lly, mediabox.urx, mediabox.ury);
01680     WARN("Annotation: [%g %g %g %g]",
01681          annbox.llx, annbox.lly, annbox.urx, annbox.ury);
01682     WARN("Maybe incorrect paper size specified.");
01683   }
01684   if (annbox.llx > annbox.urx || annbox.lly > annbox.ury) {
01685     WARN("Rectangle with negative width/height: [%g %g %g %g]",
01686          annbox.llx, annbox.lly, annbox.urx, annbox.ury);
01687   }
01688 
01689   rect_array = pdf_new_array();
01690   pdf_add_array(rect_array, pdf_new_number(ROUND(annbox.llx - annot_grow, 0.001)));
01691   pdf_add_array(rect_array, pdf_new_number(ROUND(annbox.lly - annot_grow, 0.001)));
01692   pdf_add_array(rect_array, pdf_new_number(ROUND(annbox.urx + annot_grow, 0.001)));
01693   pdf_add_array(rect_array, pdf_new_number(ROUND(annbox.ury + annot_grow, 0.001)));
01694   pdf_add_dict (annot_dict, pdf_new_name("Rect"), rect_array);
01695 
01696   pdf_add_array(page->annots, pdf_ref_obj(annot_dict));
01697 
01698   if (new_annot)
01699     pdf_doc_add_goto(annot_dict);
01700 
01701   return;
01702 }
01703 
01704 
01705 /*
01706  * PDF Article Thread
01707  */
01708 static void
01709 pdf_doc_init_articles (pdf_doc *p)
01710 {
01711   p->root.threads = NULL;
01712 
01713   p->articles.num_entries = 0;
01714   p->articles.max_entries = 0;
01715   p->articles.entries     = NULL;
01716 
01717   return;
01718 }
01719 
01720 void
01721 pdf_doc_begin_article (const char *article_id, pdf_obj *article_info)
01722 {
01723   pdf_doc     *p = &pdoc;
01724   pdf_article *article;
01725 
01726   if (article_id == NULL || strlen(article_id) == 0)
01727     ERROR("Article thread without internal identifier.");
01728 
01729   if (p->articles.num_entries >= p->articles.max_entries) {
01730     p->articles.max_entries += PDFDOC_ARTICLE_ALLOC_SIZE;
01731     p->articles.entries = RENEW(p->articles.entries,
01732                                 p->articles.max_entries, struct pdf_article);
01733   }
01734   article = &(p->articles.entries[p->articles.num_entries]);
01735 
01736   article->id = NEW(strlen(article_id)+1, char);
01737   strcpy(article->id, article_id);
01738   article->info = article_info;
01739   article->num_beads = 0;
01740   article->max_beads = 0;
01741   article->beads     = NULL;
01742 
01743   p->articles.num_entries++;
01744 
01745   return;
01746 }
01747 
01748 #if 0
01749 void
01750 pdf_doc_end_article (const char *article_id)
01751 {
01752   return; /* no-op */
01753 }
01754 #endif
01755 
01756 static pdf_bead *
01757 find_bead (pdf_article *article, const char *bead_id)
01758 {
01759   pdf_bead *bead;
01760   long      i;
01761 
01762   bead = NULL;
01763   for (i = 0; i < article->num_beads; i++) {
01764     if (!strcmp(article->beads[i].id, bead_id)) {
01765       bead = &(article->beads[i]);
01766       break;
01767     }
01768   }
01769 
01770   return bead;
01771 }
01772 
01773 void
01774 pdf_doc_add_bead (const char *article_id,
01775                   const char *bead_id, long page_no, const pdf_rect *rect)
01776 {
01777   pdf_doc     *p = &pdoc;
01778   pdf_article *article;
01779   pdf_bead    *bead;
01780   long         i;
01781 
01782   if (!article_id) {
01783     ERROR("No article identifier specified.");
01784   }
01785 
01786   article = NULL;
01787   for (i = 0; i < p->articles.num_entries; i++) {
01788     if (!strcmp(p->articles.entries[i].id, article_id)) {
01789       article = &(p->articles.entries[i]);
01790       break;
01791     }
01792   }
01793   if (!article) {
01794     ERROR("Specified article thread that doesn't exist.");
01795     return;
01796   }
01797 
01798   bead = bead_id ? find_bead(article, bead_id) : NULL;
01799   if (!bead) {
01800     if (article->num_beads >= article->max_beads) {
01801       article->max_beads += PDFDOC_BEAD_ALLOC_SIZE;
01802       article->beads = RENEW(article->beads,
01803                              article->max_beads, struct pdf_bead);
01804       for (i = article->num_beads; i < article->max_beads; i++) {
01805         article->beads[i].id = NULL;
01806         article->beads[i].page_no = -1;
01807       }
01808     }
01809     bead = &(article->beads[article->num_beads]);
01810     if (bead_id) {
01811       bead->id = NEW(strlen(bead_id)+1, char);
01812       strcpy(bead->id, bead_id);
01813     } else {
01814       bead->id = NULL;
01815     }
01816     article->num_beads++;
01817   }
01818   bead->rect.llx = rect->llx;
01819   bead->rect.lly = rect->lly;
01820   bead->rect.urx = rect->urx;
01821   bead->rect.ury = rect->ury;
01822   bead->page_no  = page_no;
01823 
01824   return;
01825 }
01826 
01827 static pdf_obj *
01828 make_article (pdf_doc *p,
01829               pdf_article *article,
01830               const char **bead_ids, int num_beads,
01831               pdf_obj *article_info)
01832 {
01833   pdf_obj *art_dict;
01834   pdf_obj *first, *prev, *last;
01835   long     i, n;
01836 
01837   if (!article)
01838     return NULL;
01839 
01840   art_dict = pdf_new_dict();
01841   first = prev = last = NULL;
01842   /*
01843    * The bead_ids represents logical order of beads in an article thread.
01844    * If bead_ids is not given, we create an article thread in the order of
01845    * beads appeared.
01846    */
01847   n = bead_ids ? num_beads : article->num_beads;
01848   for (i = 0; i < n; i++) {
01849     pdf_bead *bead;
01850 
01851     bead = bead_ids ? find_bead(article, bead_ids[i]) : &(article->beads[i]);
01852     if (!bead || bead->page_no < 0) {
01853       continue;
01854     }
01855     last = pdf_new_dict();
01856     if (prev == NULL) {
01857       first = last;
01858       pdf_add_dict(first,
01859                    pdf_new_name("T"), pdf_ref_obj(art_dict));
01860     } else {
01861       pdf_add_dict(prev,
01862                    pdf_new_name("N"), pdf_ref_obj(last));
01863       pdf_add_dict(last,
01864                    pdf_new_name("V"), pdf_ref_obj(prev));
01865       /* We must link first to last. */
01866       if (prev != first)
01867         pdf_release_obj(prev);
01868     }
01869 
01870     /* Realize bead now. */
01871     {
01872       pdf_page *page;
01873       pdf_obj  *rect;
01874 
01875       page = doc_get_page_entry(p, bead->page_no);
01876       if (!page->beads) {
01877         page->beads = pdf_new_array();
01878       }
01879       pdf_add_dict(last, pdf_new_name("P"), pdf_link_obj(page->page_ref));
01880       rect = pdf_new_array();
01881       pdf_add_array(rect, pdf_new_number(ROUND(bead->rect.llx, 0.01)));
01882       pdf_add_array(rect, pdf_new_number(ROUND(bead->rect.lly, 0.01)));
01883       pdf_add_array(rect, pdf_new_number(ROUND(bead->rect.urx, 0.01)));
01884       pdf_add_array(rect, pdf_new_number(ROUND(bead->rect.ury, 0.01)));
01885       pdf_add_dict (last, pdf_new_name("R"), rect);
01886       pdf_add_array(page->beads, pdf_ref_obj(last));
01887     }
01888 
01889     prev = last;
01890   }
01891 
01892   if (first && last) {
01893     pdf_add_dict(last,
01894                  pdf_new_name("N"), pdf_ref_obj(first));
01895     pdf_add_dict(first,
01896                  pdf_new_name("V"), pdf_ref_obj(last));
01897     if (first != last) {
01898       pdf_release_obj(last);
01899     }
01900     pdf_add_dict(art_dict,
01901                  pdf_new_name("F"), pdf_ref_obj(first));
01902     /* If article_info is supplied, we override article->info. */
01903     if (article_info) {
01904       pdf_add_dict(art_dict,
01905                    pdf_new_name("I"), article_info);
01906     } else if (article->info) {
01907       pdf_add_dict(art_dict,
01908                    pdf_new_name("I"), pdf_ref_obj(article->info));
01909       pdf_release_obj(article->info);
01910       article->info = NULL; /* We do not write as object reference. */
01911     }
01912     pdf_release_obj(first);
01913   } else {
01914     pdf_release_obj(art_dict);
01915     art_dict = NULL;
01916   }
01917 
01918   return art_dict;
01919 }
01920 
01921 static void
01922 clean_article (pdf_article *article)
01923 {
01924   if (!article)
01925     return;
01926     
01927   if (article->beads) {
01928     long  i;
01929 
01930     for (i = 0; i < article->num_beads; i++) {
01931       if (article->beads[i].id)
01932         RELEASE(article->beads[i].id);
01933     }
01934     RELEASE(article->beads);
01935     article->beads = NULL;
01936   }
01937     
01938   if (article->id)
01939     RELEASE(article->id);
01940   article->id = NULL;
01941   article->num_beads = 0;
01942   article->max_beads = 0;
01943 
01944   return;
01945 }
01946 
01947 static void
01948 pdf_doc_close_articles (pdf_doc *p)
01949 {
01950   int  i;
01951 
01952   for (i = 0; i < p->articles.num_entries; i++) {
01953     pdf_article *article;
01954 
01955     article = &(p->articles.entries[i]);
01956     if (article->beads) {
01957       pdf_obj *art_dict;
01958 
01959       art_dict = make_article(p, article, NULL, 0, NULL);
01960       if (!p->root.threads) {
01961         p->root.threads = pdf_new_array();
01962       }
01963       pdf_add_array(p->root.threads, pdf_ref_obj(art_dict));
01964       pdf_release_obj(art_dict);
01965     }
01966     clean_article(article);
01967   }
01968   RELEASE(p->articles.entries);
01969   p->articles.entries = NULL;
01970   p->articles.num_entries = 0;
01971   p->articles.max_entries = 0;
01972 
01973   if (p->root.threads) {
01974     pdf_add_dict(p->root.dict,
01975                  pdf_new_name("Threads"),
01976                  pdf_ref_obj (p->root.threads));
01977     pdf_release_obj(p->root.threads);
01978     p->root.threads = NULL;
01979   }
01980 
01981   return;
01982 }
01983 
01984 /* page_no = 0 for root page tree node. */
01985 void
01986 pdf_doc_set_mediabox (unsigned page_no, const pdf_rect *mediabox)
01987 {
01988   pdf_doc  *p = &pdoc;
01989   pdf_page *page;
01990 
01991   if (page_no == 0) {
01992     p->pages.mediabox.llx = mediabox->llx;
01993     p->pages.mediabox.lly = mediabox->lly;
01994     p->pages.mediabox.urx = mediabox->urx;
01995     p->pages.mediabox.ury = mediabox->ury;
01996   } else {
01997     page = doc_get_page_entry(p, page_no);
01998     page->cropbox.llx = mediabox->llx;
01999     page->cropbox.lly = mediabox->lly;
02000     page->cropbox.urx = mediabox->urx;
02001     page->cropbox.ury = mediabox->ury;
02002     page->flags |= USE_MY_MEDIABOX;
02003   }
02004 
02005   return;
02006 }
02007 
02008 void
02009 pdf_doc_get_mediabox (unsigned page_no, pdf_rect *mediabox)
02010 {
02011   pdf_doc  *p = &pdoc;
02012   pdf_page *page;
02013 
02014   if (page_no == 0) {
02015     mediabox->llx = p->pages.mediabox.llx;
02016     mediabox->lly = p->pages.mediabox.lly;
02017     mediabox->urx = p->pages.mediabox.urx;
02018     mediabox->ury = p->pages.mediabox.ury;
02019   } else {
02020     page = doc_get_page_entry(p, page_no);
02021     if (page->flags & USE_MY_MEDIABOX) {
02022       mediabox->llx = page->cropbox.llx;
02023       mediabox->lly = page->cropbox.lly;
02024       mediabox->urx = page->cropbox.urx;
02025       mediabox->ury = page->cropbox.ury;
02026     } else {
02027       mediabox->llx = p->pages.mediabox.llx;
02028       mediabox->lly = p->pages.mediabox.lly;
02029       mediabox->urx = p->pages.mediabox.urx;
02030       mediabox->ury = p->pages.mediabox.ury;
02031     }
02032   }
02033 
02034   return;
02035 }
02036 
02037 pdf_obj *
02038 pdf_doc_current_page_resources (void)
02039 {
02040   pdf_obj  *resources;
02041   pdf_doc  *p = &pdoc;
02042   pdf_page *currentpage;
02043 
02044   if (p->pending_forms) {
02045     if (p->pending_forms->form.resources) {
02046       resources = p->pending_forms->form.resources;
02047     } else {
02048       resources = p->pending_forms->form.resources = pdf_new_dict();
02049     }
02050   } else {
02051     currentpage = LASTPAGE(p);
02052     if (currentpage->resources) {
02053       resources = currentpage->resources;
02054     } else {
02055       resources = currentpage->resources = pdf_new_dict();
02056     }
02057   }
02058 
02059   return resources;
02060 }
02061 
02062 pdf_obj *
02063 pdf_doc_get_dictionary (const char *category)
02064 {
02065   pdf_doc *p    = &pdoc;
02066   pdf_obj *dict = NULL;
02067 
02068   ASSERT(category);
02069 
02070   if (!strcmp(category, "Names")) {
02071     if (!p->root.names)
02072       p->root.names = pdf_new_dict();
02073     dict = p->root.names;
02074   } else if (!strcmp(category, "Pages")) {
02075     if (!p->root.pages)
02076       p->root.pages = pdf_new_dict();
02077     dict = p->root.pages;
02078   } else if (!strcmp(category, "Catalog")) {
02079     if (!p->root.dict)
02080       p->root.dict = pdf_new_dict();
02081     dict = p->root.dict;
02082   } else if (!strcmp(category, "Info")) {
02083     if (!p->info)
02084       p->info = pdf_new_dict();
02085     dict = p->info;
02086   } else if (!strcmp(category, "@THISPAGE")) {
02087     /* Sorry for this... */
02088     pdf_page *currentpage;
02089 
02090     currentpage = LASTPAGE(p);
02091     dict =  currentpage->page_obj;
02092   }
02093 
02094   if (!dict) {
02095     ERROR("Document dict. \"%s\" not exist. ", category);
02096   }
02097 
02098   return dict;
02099 }
02100 
02101 long
02102 pdf_doc_current_page_number (void)
02103 {
02104   pdf_doc *p = &pdoc;
02105 
02106   return (long) (PAGECOUNT(p) + 1);
02107 }
02108 
02109 pdf_obj *
02110 pdf_doc_ref_page (unsigned long page_no)
02111 {
02112   pdf_doc  *p = &pdoc;
02113   pdf_page *page;
02114 
02115   page = doc_get_page_entry(p, page_no);
02116   if (!page->page_obj) {
02117     page->page_obj = pdf_new_dict();
02118     page->page_ref = pdf_ref_obj(page->page_obj);
02119   }
02120 
02121   return pdf_link_obj(page->page_ref);
02122 }
02123 
02124 pdf_obj *
02125 pdf_doc_get_reference (const char *category)
02126 {
02127   pdf_obj *ref = NULL;
02128   long     page_no;
02129 
02130   ASSERT(category);
02131 
02132   page_no = pdf_doc_current_page_number();
02133   if (!strcmp(category, "@THISPAGE")) {
02134     ref = pdf_doc_ref_page(page_no);
02135   } else if (!strcmp(category, "@PREVPAGE")) {
02136     if (page_no <= 1) {
02137       ERROR("Reference to previous page, but no pages have been completed yet.");
02138     }
02139     ref = pdf_doc_ref_page(page_no - 1);
02140   } else if (!strcmp(category, "@NEXTPAGE")) {
02141     ref = pdf_doc_ref_page(page_no + 1);
02142   }
02143 
02144   if (!ref) {
02145     ERROR("Reference to \"%s\" not exist. ", category);
02146   }
02147 
02148   return ref;
02149 }
02150 
02151 static void
02152 pdf_doc_new_page (pdf_doc *p)
02153 {
02154   pdf_page *currentpage;
02155 
02156   if (PAGECOUNT(p) >= MAXPAGES(p)) {
02157     doc_resize_page_entries(p, MAXPAGES(p) + PDFDOC_PAGES_ALLOC_SIZE);
02158   }
02159 
02160   /*
02161    * This is confusing. pdf_doc_finish_page() have increased page count!
02162    */
02163   currentpage = LASTPAGE(p);
02164   /* Was this page already instantiated by a forward reference to it? */
02165   if (!currentpage->page_ref) {
02166     currentpage->page_obj = pdf_new_dict();
02167     currentpage->page_ref = pdf_ref_obj(currentpage->page_obj);
02168   }
02169 
02170   currentpage->background = NULL;
02171   currentpage->contents   = pdf_new_stream(STREAM_COMPRESS);
02172   currentpage->resources  = pdf_new_dict();
02173 
02174   currentpage->annots = NULL;
02175   currentpage->beads  = NULL;
02176 
02177   return;
02178 }
02179 
02180 /* This only closes contents and resources. */
02181 static void
02182 pdf_doc_finish_page (pdf_doc *p)
02183 {
02184   pdf_page *currentpage;
02185 
02186   if (p->pending_forms) {
02187     ERROR("A pending form XObject at the end of page.");
02188   }
02189 
02190   currentpage = LASTPAGE(p);
02191   if (!currentpage->page_obj)
02192     currentpage->page_obj = pdf_new_dict();
02193 
02194   /*
02195    * Make Contents array.
02196    */
02197 
02198   /*
02199    * Global BOP content stream.
02200    * pdf_ref_obj() returns reference itself when the object is
02201    * indirect reference, not reference to the indirect reference.
02202    * We keep bop itself but not reference to it since it is
02203    * expected to be small.
02204    */
02205   if (p->pages.bop &&
02206       pdf_stream_length(p->pages.bop) > 0) {
02207     currentpage->content_refs[0] = pdf_ref_obj(p->pages.bop);
02208   } else {
02209     currentpage->content_refs[0] = NULL;
02210   }
02211   /*
02212    * Current page background content stream.
02213    */
02214   if (currentpage->background) {
02215     if (pdf_stream_length(currentpage->background) > 0) {
02216       currentpage->content_refs[1] = pdf_ref_obj(currentpage->background);
02217       pdf_add_stream (currentpage->background, "\n", 1);
02218     }
02219     pdf_release_obj(currentpage->background);
02220     currentpage->background = NULL;
02221   } else {
02222     currentpage->content_refs[1] = NULL;
02223   }
02224 
02225   /* Content body of current page */
02226   currentpage->content_refs[2] = pdf_ref_obj(currentpage->contents);
02227   pdf_add_stream (currentpage->contents, "\n", 1);
02228   pdf_release_obj(currentpage->contents);
02229   currentpage->contents = NULL;
02230 
02231   /*
02232    * Global EOP content stream.
02233    */
02234   if (p->pages.eop &&
02235       pdf_stream_length(p->pages.eop) > 0) {
02236     currentpage->content_refs[3] = pdf_ref_obj(p->pages.eop);
02237   } else {
02238     currentpage->content_refs[3] = NULL;
02239   }
02240 
02241   /*
02242    * Page resources.
02243    */
02244   if (currentpage->resources) {
02245     pdf_obj *procset;
02246     /*
02247      * ProcSet is obsolete in PDF-1.4 but recommended for compatibility.
02248      */
02249 
02250     procset = pdf_new_array ();
02251     pdf_add_array(procset, pdf_new_name("PDF"));
02252     pdf_add_array(procset, pdf_new_name("Text"));
02253     pdf_add_array(procset, pdf_new_name("ImageC"));
02254     pdf_add_array(procset, pdf_new_name("ImageB"));
02255     pdf_add_array(procset, pdf_new_name("ImageI"));
02256     pdf_add_dict(currentpage->resources, pdf_new_name("ProcSet"), procset);
02257 
02258     pdf_add_dict(currentpage->page_obj,
02259                  pdf_new_name("Resources"),
02260                  pdf_ref_obj(currentpage->resources));
02261     pdf_release_obj(currentpage->resources);
02262     currentpage->resources = NULL;
02263   }
02264 
02265   if (manual_thumb_enabled) {
02266     char    *thumb_filename;
02267     pdf_obj *thumb_ref;
02268 
02269     thumb_filename = NEW(strlen(thumb_basename)+7, char);
02270     sprintf(thumb_filename, "%s.%ld",
02271             thumb_basename, (p->pages.num_entries % 99999) + 1L);
02272     thumb_ref = read_thumbnail(thumb_filename);
02273     RELEASE(thumb_filename);
02274     if (thumb_ref)
02275       pdf_add_dict(currentpage->page_obj, pdf_new_name("Thumb"), thumb_ref);
02276   }
02277 
02278   p->pages.num_entries++;
02279 
02280   return;
02281 }
02282 
02283 
02284 static pdf_color bgcolor;
02285 
02286 void
02287 pdf_doc_set_bgcolor (const pdf_color *color)
02288 {
02289   if (color)
02290     pdf_color_copycolor(&bgcolor, color);
02291   else { /* as clear... */
02292     pdf_color_white(&bgcolor);
02293   }
02294 }
02295 
02296 static void
02297 doc_fill_page_background (pdf_doc *p)
02298 {
02299   pdf_page  *currentpage;
02300   pdf_rect   r;
02301   int        cm;
02302   pdf_obj   *saved_content;
02303 
02304   cm = pdf_dev_get_param(PDF_DEV_PARAM_COLORMODE);
02305   if (!cm || pdf_color_is_white(&bgcolor)) {
02306     return;
02307   }
02308 
02309   pdf_doc_get_mediabox(pdf_doc_current_page_number(), &r);
02310 
02311   currentpage = LASTPAGE(p);
02312   ASSERT(currentpage);
02313 
02314   if (!currentpage->background)
02315     currentpage->background = pdf_new_stream(STREAM_COMPRESS);
02316 
02317   saved_content = currentpage->contents;
02318   currentpage->contents = currentpage->background;
02319 
02320   pdf_dev_gsave();
02321   pdf_dev_set_nonstrokingcolor(&bgcolor);
02322   pdf_dev_rectfill(r.llx, r.lly, r.urx - r.llx, r.ury - r.lly);
02323   pdf_dev_grestore();
02324 
02325   currentpage->contents = saved_content;
02326 
02327   return;
02328 }
02329 
02330 void
02331 pdf_doc_begin_page (double scale, double x_origin, double y_origin)
02332 {
02333   pdf_doc     *p = &pdoc;
02334   pdf_tmatrix  M;
02335 
02336   M.a = scale; M.b = 0.0;
02337   M.c = 0.0  ; M.d = scale;
02338   M.e = x_origin;
02339   M.f = y_origin;
02340 
02341   /* pdf_doc_new_page() allocates page content stream. */
02342   pdf_doc_new_page(p);
02343   pdf_dev_bop(&M);
02344 
02345   return;
02346 }
02347 
02348 void
02349 pdf_doc_end_page (void)
02350 {
02351   pdf_doc *p = &pdoc;
02352 
02353   pdf_dev_eop();
02354   doc_fill_page_background(p);
02355 
02356   pdf_doc_finish_page(p);
02357 
02358   return;
02359 }
02360 
02361 void
02362 pdf_doc_add_page_content (const char *buffer, unsigned length)
02363 {
02364   pdf_doc  *p = &pdoc;
02365   pdf_page *currentpage;
02366 
02367   if (p->pending_forms) {
02368     pdf_add_stream(p->pending_forms->form.contents, buffer, length);
02369   } else {
02370     currentpage = LASTPAGE(p);
02371     pdf_add_stream(currentpage->contents, buffer, length);
02372   }
02373 
02374   return;
02375 }
02376 
02377 static char *doccreator = NULL; /* Ugh */
02378 
02379 void
02380 pdf_open_document (const char *filename,
02381                  int do_encryption,
02382                    double media_width, double media_height,
02383                    double annot_grow_amount, int bookmark_open_depth,
02384                  int check_gotos)
02385 {
02386   pdf_doc *p = &pdoc;
02387 
02388   pdf_out_init(filename, do_encryption);
02389 
02390   pdf_doc_init_catalog(p);
02391 
02392   p->opt.annot_grow = annot_grow_amount;
02393   p->opt.outline_open_depth = bookmark_open_depth;
02394 
02395   pdf_init_resources();
02396   pdf_init_colors();
02397   pdf_init_fonts();
02398   /* Thumbnail want this to be initialized... */
02399   pdf_init_images();
02400 
02401   pdf_doc_init_docinfo(p);
02402   if (doccreator) {
02403     pdf_add_dict(p->info,
02404                  pdf_new_name("Creator"),
02405                  pdf_new_string(doccreator, strlen(doccreator)));
02406     RELEASE(doccreator); doccreator = NULL;
02407   }
02408 
02409   pdf_doc_init_bookmarks(p, bookmark_open_depth);
02410   pdf_doc_init_articles (p);
02411   pdf_doc_init_names    (p, check_gotos);
02412   pdf_doc_init_page_tree(p, media_width, media_height);
02413 
02414   pdf_doc_set_bgcolor(NULL);
02415 
02416   if (do_encryption) {
02417     pdf_obj *encrypt = pdf_encrypt_obj();
02418     pdf_set_encrypt(encrypt);
02419     pdf_release_obj(encrypt);
02420   }
02421   pdf_set_id(pdf_enc_id_array());
02422 
02423   /* Create a default name for thumbnail image files */
02424   if (manual_thumb_enabled) {
02425     if (strlen(filename) > 4 &&
02426         !strncmp(".pdf", filename + strlen(filename) - 4, 4)) {
02427       thumb_basename = NEW(strlen(filename)-4+1, char);
02428       strncpy(thumb_basename, filename, strlen(filename)-4);
02429       thumb_basename[strlen(filename)-4] = 0;
02430     } else {
02431       thumb_basename = NEW(strlen(filename)+1, char);
02432       strcpy(thumb_basename, filename);
02433     }
02434   }
02435 
02436   p->pending_forms = NULL;
02437    
02438   return;
02439 }
02440 
02441 void
02442 pdf_doc_set_creator (const char *creator)
02443 {
02444   if (!creator ||
02445       creator[0] == '\0')
02446     return;
02447 
02448   doccreator = NEW(strlen(creator)+1, char);
02449   strcpy(doccreator, creator); /* Ugh */
02450 }
02451 
02452 
02453 void
02454 pdf_close_document (void)
02455 {
02456   pdf_doc *p = &pdoc;
02457 
02458   /*
02459    * Following things were kept around so user can add dictionary items.
02460    */
02461   pdf_doc_close_articles (p);
02462   pdf_doc_close_names    (p);
02463   pdf_doc_close_bookmarks(p);
02464   pdf_doc_close_page_tree(p);
02465   pdf_doc_close_docinfo  (p);
02466 
02467   pdf_doc_close_catalog  (p);
02468 
02469   pdf_close_images();
02470   pdf_close_fonts ();
02471   pdf_close_colors();
02472 
02473   pdf_close_resources(); /* Should be at last. */
02474 
02475   pdf_out_flush();
02476 
02477   if (thumb_basename)
02478     RELEASE(thumb_basename);
02479 
02480   return;
02481 }
02482 
02483 /*
02484  * All this routine does is give the form a name and add a unity scaling matrix.
02485  * It fills in required fields.  The caller must initialize the stream.
02486  */
02487 static void
02488 pdf_doc_make_xform (pdf_obj     *xform,
02489                     pdf_rect    *bbox,
02490                     pdf_tmatrix *matrix,
02491                     pdf_obj     *resources,
02492                     pdf_obj     *attrib)
02493 {
02494   pdf_obj *xform_dict;
02495   pdf_obj *tmp;
02496 
02497   xform_dict = pdf_stream_dict(xform);
02498   pdf_add_dict(xform_dict,
02499                pdf_new_name("Type"),     pdf_new_name("XObject"));
02500   pdf_add_dict(xform_dict,
02501                pdf_new_name("Subtype"),  pdf_new_name("Form"));
02502   pdf_add_dict(xform_dict,
02503                pdf_new_name("FormType"), pdf_new_number(1.0));
02504 
02505   if (!bbox)
02506     ERROR("No BoundingBox supplied.");
02507 
02508   tmp = pdf_new_array();
02509   pdf_add_array(tmp, pdf_new_number(ROUND(bbox->llx, .001)));
02510   pdf_add_array(tmp, pdf_new_number(ROUND(bbox->lly, .001)));
02511   pdf_add_array(tmp, pdf_new_number(ROUND(bbox->urx, .001)));
02512   pdf_add_array(tmp, pdf_new_number(ROUND(bbox->ury, .001)));
02513   pdf_add_dict(xform_dict, pdf_new_name("BBox"), tmp);
02514 
02515   if (matrix) {
02516     tmp = pdf_new_array();
02517     pdf_add_array(tmp, pdf_new_number(ROUND(matrix->a, .00001)));
02518     pdf_add_array(tmp, pdf_new_number(ROUND(matrix->b, .00001)));
02519     pdf_add_array(tmp, pdf_new_number(ROUND(matrix->c, .00001)));
02520     pdf_add_array(tmp, pdf_new_number(ROUND(matrix->d, .00001)));
02521     pdf_add_array(tmp, pdf_new_number(ROUND(matrix->e, .001  )));
02522     pdf_add_array(tmp, pdf_new_number(ROUND(matrix->f, .001  )));
02523     pdf_add_dict(xform_dict, pdf_new_name("Matrix"), tmp);
02524   }
02525 
02526   if (attrib) {
02527     pdf_merge_dict(xform_dict, attrib);
02528   }
02529 
02530   pdf_add_dict(xform_dict, pdf_new_name("Resources"), resources);
02531 
02532   return;
02533 }
02534 
02535 /*
02536  * begin_form_xobj creates an xobject with its "origin" at
02537  * xpos and ypos that is clipped to the specified bbox. Note
02538  * that the origin is not the lower left corner of the bbox.
02539  */
02540 int
02541 pdf_doc_begin_grabbing (const char *ident,
02542                         double ref_x, double ref_y, const pdf_rect *cropbox)
02543 {
02544   int         xobj_id = -1;
02545   pdf_doc    *p = &pdoc;
02546   pdf_form   *form;
02547   struct form_list_node *fnode;
02548   xform_info  info;
02549 
02550   pdf_dev_push_gstate();
02551 
02552   fnode = NEW(1, struct form_list_node);
02553 
02554   fnode->prev    = p->pending_forms;
02555   fnode->q_depth = pdf_dev_current_depth();
02556   form           = &fnode->form;
02557 
02558   /*
02559   * The reference point of an Xobject is at the lower left corner
02560   * of the bounding box.  Since we would like to have an arbitrary
02561   * reference point, we use a transformation matrix, translating
02562   * the reference point to (0,0).
02563   */
02564 
02565   form->matrix.a = 1.0; form->matrix.b = 0.0;
02566   form->matrix.c = 0.0; form->matrix.d = 1.0;
02567   form->matrix.e = -ref_x;
02568   form->matrix.f = -ref_y;
02569 
02570   form->cropbox.llx = ref_x + cropbox->llx;
02571   form->cropbox.lly = ref_y + cropbox->lly;
02572   form->cropbox.urx = ref_x + cropbox->urx;
02573   form->cropbox.ury = ref_y + cropbox->ury;
02574 
02575   form->contents  = pdf_new_stream(STREAM_COMPRESS);
02576   form->resources = pdf_new_dict();
02577 
02578   pdf_ximage_init_form_info(&info);
02579 
02580   info.matrix.a = 1.0; info.matrix.b = 0.0;
02581   info.matrix.c = 0.0; info.matrix.d = 1.0;
02582   info.matrix.e = -ref_x;
02583   info.matrix.f = -ref_y;
02584 
02585   info.bbox.llx = cropbox->llx;
02586   info.bbox.lly = cropbox->lly;
02587   info.bbox.urx = cropbox->urx;
02588   info.bbox.ury = cropbox->ury;
02589 
02590   /* Use reference since content itself isn't available yet. */
02591   xobj_id = pdf_ximage_defineresource(ident,
02592                                       PDF_XOBJECT_TYPE_FORM,
02593                                       &info, pdf_ref_obj(form->contents));
02594 
02595   p->pending_forms = fnode;
02596 
02597   /*
02598    * Make sure the object is self-contained by adding the
02599    * current font and color to the object stream.
02600    */
02601   pdf_dev_reset_fonts();
02602   pdf_dev_reset_color(1);  /* force color operators to be added to stream */
02603 
02604   return xobj_id;
02605 }
02606 
02607 void
02608 pdf_doc_end_grabbing (pdf_obj *attrib)
02609 {
02610   pdf_form *form;
02611   pdf_obj  *procset;
02612   pdf_doc  *p = &pdoc;
02613   struct form_list_node *fnode;
02614 
02615   if (!p->pending_forms) {
02616     WARN("Tried to close a nonexistent form XOject.");
02617     return;
02618   }
02619   
02620   fnode = p->pending_forms;
02621   form  = &fnode->form;
02622 
02623   pdf_dev_grestore_to(fnode->q_depth);
02624 
02625   /*
02626    * ProcSet is obsolete in PDF-1.4 but recommended for compatibility.
02627    */
02628   procset = pdf_new_array();
02629   pdf_add_array(procset, pdf_new_name("PDF"));
02630   pdf_add_array(procset, pdf_new_name("Text"));
02631   pdf_add_array(procset, pdf_new_name("ImageC"));
02632   pdf_add_array(procset, pdf_new_name("ImageB"));
02633   pdf_add_array(procset, pdf_new_name("ImageI"));
02634   pdf_add_dict (form->resources, pdf_new_name("ProcSet"), procset);
02635 
02636   pdf_doc_make_xform(form->contents,
02637                      &form->cropbox, &form->matrix,
02638                      pdf_ref_obj(form->resources), attrib);
02639   pdf_release_obj(form->resources);
02640   pdf_release_obj(form->contents);
02641   if (attrib) pdf_release_obj(attrib);
02642 
02643   p->pending_forms = fnode->prev;
02644   
02645   pdf_dev_pop_gstate();
02646 
02647   pdf_dev_reset_fonts();
02648   pdf_dev_reset_color(0);
02649 
02650   RELEASE(fnode);
02651 
02652   return;
02653 }
02654 
02655 static struct
02656 {
02657   int      dirty;
02658   int      broken;
02659   pdf_obj *annot_dict;
02660   pdf_rect rect;
02661 } breaking_state = {0, 0, NULL, {0.0, 0.0, 0.0, 0.0}};
02662 
02663 static void
02664 reset_box (void)
02665 {
02666   breaking_state.rect.llx = breaking_state.rect.lly =  HUGE_VAL;
02667   breaking_state.rect.urx = breaking_state.rect.ury = -HUGE_VAL;
02668   breaking_state.dirty    = 0;
02669 }
02670 
02671 void
02672 pdf_doc_begin_annot (pdf_obj *dict)
02673 {
02674   breaking_state.annot_dict = dict;
02675   breaking_state.broken = 0;
02676   reset_box();
02677 }
02678 
02679 void
02680 pdf_doc_end_annot (void)
02681 {
02682   pdf_doc_break_annot();
02683   breaking_state.annot_dict = NULL;
02684 }
02685 
02686 void
02687 pdf_doc_break_annot (void)
02688 {
02689   if (breaking_state.dirty) {
02690     pdf_obj  *annot_dict;
02691 
02692     /* Copy dict */
02693     annot_dict = pdf_new_dict();
02694     pdf_merge_dict(annot_dict, breaking_state.annot_dict);
02695     pdf_doc_add_annot(pdf_doc_current_page_number(), &(breaking_state.rect),
02696                     annot_dict, !breaking_state.broken);
02697     pdf_release_obj(annot_dict);
02698 
02699     breaking_state.broken = 1;
02700   }
02701   reset_box();
02702 }
02703 
02704 void
02705 pdf_doc_expand_box (const pdf_rect *rect)
02706 {
02707   breaking_state.rect.llx = MIN(breaking_state.rect.llx, rect->llx);
02708   breaking_state.rect.lly = MIN(breaking_state.rect.lly, rect->lly);
02709   breaking_state.rect.urx = MAX(breaking_state.rect.urx, rect->urx);
02710   breaking_state.rect.ury = MAX(breaking_state.rect.ury, rect->ury);
02711   breaking_state.dirty    = 1;
02712 }
02713 
02714 #if 0
02715 /* This should be number tree */
02716 void
02717 pdf_doc_set_pagelabel (long  pg_start,
02718                        const char *type,
02719                        const void *prefix, int prfx_len, long start)
02720 {
02721   pdf_doc *p = &pdoc;
02722   pdf_obj *label_dict;
02723 
02724   if (!p->root.pagelabels)
02725     p->root.pagelabels = pdf_new_array();
02726 
02727   label_dict = pdf_new_dict();
02728   if (!type || type[0] == '\0') /* Set back to default. */
02729     pdf_add_dict(label_dict, pdf_new_name("S"),  pdf_new_name("D"));
02730   else {
02731     if (type)
02732       pdf_add_dict(label_dict, pdf_new_name("S"), pdf_new_name(type));
02733     if (prefix && prfx_len > 0)
02734       pdf_add_dict(label_dict,
02735                    pdf_new_name("P"),
02736                    pdf_new_string(prefix, prfx_len));
02737     if (start != 1)
02738       pdf_add_dict(label_dict,
02739                    pdf_new_name("St"), pdf_new_number(start));
02740   }
02741 
02742   pdf_add_array(p->root.pagelabels, pdf_new_number(pg_start));
02743   pdf_add_array(p->root.pagelabels, label_dict);
02744 
02745   return;
02746 }
02747 #endif