summaryrefslogtreecommitdiff
path: root/src/pdflib/pdflib/p_hyper.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pdflib/pdflib/p_hyper.c')
-rw-r--r--src/pdflib/pdflib/p_hyper.c1449
1 files changed, 1449 insertions, 0 deletions
diff --git a/src/pdflib/pdflib/p_hyper.c b/src/pdflib/pdflib/p_hyper.c
new file mode 100644
index 0000000..644e311
--- /dev/null
+++ b/src/pdflib/pdflib/p_hyper.c
@@ -0,0 +1,1449 @@
+/*---------------------------------------------------------------------------*
+ | PDFlib - A library for generating PDF on the fly |
+ +---------------------------------------------------------------------------+
+ | Copyright (c) 1997-2006 Thomas Merz and PDFlib GmbH. All rights reserved. |
+ +---------------------------------------------------------------------------+
+ | |
+ | This software is subject to the PDFlib license. It is NOT in the |
+ | public domain. Extended versions and commercial licenses are |
+ | available, please check http://www.pdflib.com. |
+ | |
+ *---------------------------------------------------------------------------*/
+
+/* $Id: p_hyper.c,v 1.1 2008/10/17 06:11:49 scuri Exp $
+ *
+ * PDFlib routines for hypertext stuff:
+ * named destination, bookmarks, document info
+ *
+ */
+
+#define P_HYPER_C
+
+#include "p_intern.h"
+#include "p_color.h"
+
+
+
+
+/* -------------------------- named destinations -------------------------- */
+
+typedef enum
+{
+ fixed,
+ fitwindow,
+ fitwidth,
+ fitheight,
+ fitrect,
+ fitvisible,
+ fitvisiblewidth,
+ fitvisibleheight,
+ nameddest,
+ filedest
+}
+pdf_desttype;
+
+static const pdc_keyconn pdf_type_keylist[] =
+{
+ {"fixed", fixed},
+ {"fitwindow", fitwindow},
+ {"fitwidth", fitwidth},
+ {"fitheight", fitheight},
+ {"fitrect", fitrect},
+ {"fitvisible", fitvisible},
+ {"fitvisiblewidth", fitvisiblewidth},
+ {"fitvisibleheight",fitvisibleheight},
+ {"nameddest", nameddest},
+ {"file", filedest},
+ {NULL, 0}
+};
+
+/* Destination structure */
+struct pdf_dest_s
+{
+ pdf_desttype type;
+ char *filename; /* name of a file to be launched - deprecated */
+ int remote_page; /* remote target page number */
+ int pgnum;
+ pdc_id page; /* local target page object id */
+ char *name; /* destination name, only for type=nameddest */
+ int len; /* length of the name string */
+ pdc_scalar zoom; /* magnification */
+ pdc_scalar left;
+ pdc_scalar right;
+ pdc_scalar bottom;
+ pdc_scalar top;
+ pdc_scalar color[3]; /* rgb color of bookmark text - deprecated */
+ fnt_fontstyle fontstyle; /* font style of bookmark text - deprecated */
+};
+
+static const pdc_defopt pdf_destination_options[] =
+{
+ {"hypertextencoding", pdc_stringlist, PDC_OPT_NONE, 1, 1,
+ 0.0, PDF_MAX_NAMESTRING, NULL},
+
+ {"hypertextformat", pdc_keywordlist, PDC_OPT_NONE, 1, 1,
+ 0.0, 0.0, pdf_textformat_keylist},
+
+ {"fitbbox", pdc_booleanlist, PDC_OPT_NONE,
+ 1, 1, 0.0, 0.0, NULL},
+
+ {"fitheight", pdc_booleanlist, PDC_OPT_NONE,
+ 1, 1, 0.0, 0.0, NULL},
+
+ {"fitpage", pdc_booleanlist, PDC_OPT_NONE,
+ 1, 1, 0.0, 0.0, NULL},
+
+ {"fitwidth", pdc_booleanlist, PDC_OPT_NONE,
+ 1, 1, 0.0, 0.0, NULL},
+
+ {"retain", pdc_booleanlist, PDC_OPT_NONE,
+ 1, 1, 0.0, 0.0, NULL},
+
+ {"type", pdc_keywordlist, PDC_OPT_NONE,
+ 1, 1, 0.0, 0.0, pdf_type_keylist},
+
+ {"name", pdc_stringlist, PDC_OPT_NONE,
+ 1, 1, 1.0, PDC_INT_MAX, NULL},
+
+ {"page", pdc_integerlist, PDC_OPT_NONE,
+ 1, 1, 0, PDC_INT_MAX, NULL},
+
+ {"group", pdc_stringlist, PDC_OPT_NONE, 1, 1,
+ 1.0, PDF_MAX_NAMESTRING, NULL},
+
+ /* Acrobat 5 supports a maximum zoom of 1600%, but we allow some more */
+ {"zoom", pdc_scalarlist, PDC_OPT_PERCENT,
+ 1, 1, 0.0, 10000, NULL},
+
+ {"left", pdc_scalarlist, PDC_OPT_NONE,
+ 1, 1, 0.0, PDF_ACRO_MAXPAGE, NULL},
+
+ {"right", pdc_scalarlist, PDC_OPT_NONE,
+ 1, 1, 0.0, PDF_ACRO_MAXPAGE, NULL},
+
+ {"bottom", pdc_scalarlist, PDC_OPT_REQUIRIF1,
+ 1, 1, 0.0, PDF_ACRO_MAXPAGE, NULL},
+
+ {"top", pdc_scalarlist, PDC_OPT_NONE,
+ 1, 1, 0.0, PDF_ACRO_MAXPAGE, NULL},
+
+ {"color", pdc_scalarlist, PDC_OPT_NONE,
+ 1, 3, 0.0, 1.0, NULL},
+
+ {"fontstyle", pdc_keywordlist, PDC_OPT_NONE,
+ 1, 1, 0.0, 0.0, pdf_fontstyle_pdfkeylist},
+
+ {"filename", pdc_stringlist, PDC_OPT_NONE,
+ 1, 1, 0.0, PDC_FILENAMELEN, NULL},
+
+ PDC_OPT_TERMINATE
+};
+
+pdf_dest *
+pdf_init_destination(PDF *p)
+{
+ static const char fn[] = "pdf_init_destination";
+ pdf_dest *dest = (pdf_dest *) pdc_malloc(p->pdc, sizeof(pdf_dest), fn);
+
+ dest->type = fitwindow;
+ dest->remote_page = 0;
+ dest->pgnum = 0;
+ dest->page = PDC_BAD_ID;
+ dest->left = -1;
+ dest->right = -1;
+ dest->bottom = -1;
+ dest->top = -1;
+ dest->zoom = -1;
+ dest->name = NULL;
+ dest->color[0] = 0.0;
+ dest->color[1] = 0.0;
+ dest->color[2] = 0.0;
+ dest->fontstyle = fnt_Normal;
+ dest->filename = NULL;
+
+ return dest;
+}
+
+void
+pdf_cleanup_destination(PDF *p, pdf_dest *dest)
+{
+ if (dest)
+ {
+ if (dest->name)
+ {
+ pdc_free(p->pdc, dest->name);
+ dest->name = NULL;
+ }
+ if (dest->filename)
+ {
+ pdc_free(p->pdc, dest->filename);
+ dest->filename = NULL;
+ }
+
+ pdc_free(p->pdc, dest);
+ }
+}
+
+pdf_dest *
+pdf_parse_destination_optlist(
+ PDF *p,
+ const char *optlist,
+ int page,
+ pdf_destuse destuse)
+{
+ int minpage;
+ pdc_resopt *resopts;
+ pdc_encoding hypertextencoding;
+ int hypertextcodepage;
+ const char *keyword;
+ const char *type_name;
+ char **strlist = NULL;
+ int inum;
+ pdc_bool boolval;
+
+ /* Defaults */
+ pdf_dest *dest = pdf_init_destination(p);
+
+ /* parse option list */
+ resopts = pdc_parse_optionlist(p->pdc, optlist, pdf_destination_options,
+ NULL, pdc_true);
+
+ if (pdc_get_optvalues("fitbbox", resopts, &boolval, NULL) &&
+ boolval == pdc_true)
+ dest->type = fitvisible;
+
+ if (pdc_get_optvalues("fitheight", resopts, &boolval, NULL) &&
+ boolval == pdc_true)
+ dest->type = fitheight;
+
+ if (pdc_get_optvalues("fitpage", resopts, &boolval, NULL) &&
+ boolval == pdc_true)
+ dest->type = fitwindow;
+
+ if (pdc_get_optvalues("fitwidth", resopts, &boolval, NULL) &&
+ boolval == pdc_true)
+ dest->type = fitwidth;
+
+ if (pdc_get_optvalues("retain", resopts, &boolval, NULL) &&
+ boolval == pdc_true)
+ dest->type = fixed;
+
+ if (pdc_get_optvalues("type", resopts, &inum, NULL))
+ dest->type = (pdf_desttype) inum;
+ type_name = pdc_get_keyword(dest->type, pdf_type_keylist);
+
+ hypertextencoding =
+ pdf_get_hypertextencoding_opt(p, resopts, &hypertextcodepage, pdc_true);
+
+ keyword = "name";
+ if (pdf_get_opt_textlist(p, keyword, resopts, hypertextencoding,
+ hypertextcodepage, pdc_true, NULL, &dest->name, NULL))
+ {
+ if (dest->type != nameddest)
+ {
+ dest->name = NULL;
+ pdc_warning(p->pdc, PDF_E_HYP_OPTIGNORE_FORTYPE, keyword,
+ type_name, 0, 0);
+ }
+ else
+ pdc_save_lastopt(resopts, PDC_OPT_SAVE1ELEM);
+ }
+
+ keyword = "page";
+ if (pdc_get_optvalues(keyword, resopts, &page, NULL) &&
+ dest->type == filedest)
+ pdc_warning(p->pdc, PDF_E_HYP_OPTIGNORE_FORTYPE, keyword, type_name,
+ 0, 0);
+
+ keyword = "group";
+ if (pdc_get_optvalues(keyword, resopts, NULL, &strlist))
+ {
+ page = pdf_xlat_pageno(p, page, strlist[0]);
+ }
+
+ keyword = "zoom";
+ if (pdc_get_optvalues(keyword, resopts, &dest->zoom, NULL) &&
+ dest->type != fixed)
+ pdc_warning(p->pdc, PDF_E_HYP_OPTIGNORE_FORTYPE, keyword, type_name,
+ 0, 0);
+
+ keyword = "left";
+ if (pdc_get_optvalues(keyword, resopts, &dest->left, NULL) &&
+ (dest->type == fitwindow || dest->type == fitwidth ||
+ dest->type == fitvisible || dest->type == fitvisiblewidth ||
+ dest->type == nameddest || dest->type == filedest))
+ pdc_warning(p->pdc, PDF_E_HYP_OPTIGNORE_FORTYPE, keyword, type_name,
+ 0, 0);
+
+ keyword = "right";
+ if (pdc_get_optvalues(keyword, resopts, &dest->right, NULL) &&
+ dest->type != fitrect)
+ pdc_warning(p->pdc, PDF_E_HYP_OPTIGNORE_FORTYPE, keyword, type_name,
+ 0, 0);
+
+ keyword = "bottom";
+ if (pdc_get_optvalues(keyword, resopts, &dest->bottom, NULL) &&
+ dest->type != fitrect)
+ pdc_warning(p->pdc, PDF_E_HYP_OPTIGNORE_FORTYPE, keyword, type_name,
+ 0, 0);
+
+ keyword = "top";
+ if (pdc_get_optvalues(keyword, resopts, &dest->top, NULL) &&
+ (dest->type == fitwindow || dest->type == fitheight ||
+ dest->type == fitvisible || dest->type == fitvisibleheight ||
+ dest->type == nameddest || dest->type == filedest))
+ pdc_warning(p->pdc, PDF_E_HYP_OPTIGNORE_FORTYPE, keyword, type_name,
+ 0, 0);
+
+ keyword = "color";
+ if (pdc_get_optvalues(keyword, resopts, &dest->color, NULL) &&
+ destuse != pdf_bookmark)
+ pdc_warning(p->pdc, PDF_E_HYP_OPTIGNORE_FORELEM, keyword, 0, 0, 0);
+
+ keyword = "fontstyle";
+ if (pdc_get_optvalues(keyword, resopts, &inum, NULL))
+ {
+ dest->fontstyle = (fnt_fontstyle) inum;
+ if (destuse != pdf_bookmark)
+ pdc_warning(p->pdc, PDF_E_HYP_OPTIGNORE_FORELEM, keyword, 0, 0, 0);
+ }
+
+ keyword = "filename";
+ if (pdc_get_optvalues(keyword, resopts, NULL, NULL))
+ {
+ if (dest->type != filedest)
+ {
+ pdc_warning(p->pdc, PDF_E_HYP_OPTIGNORE_FORTYPE, keyword,
+ type_name, 0, 0);
+ }
+ else
+ dest->filename =
+ (char *) pdc_save_lastopt(resopts, PDC_OPT_SAVE1ELEM);
+ }
+
+ pdc_cleanup_optionlist(p->pdc, resopts);
+
+ switch (dest->type)
+ {
+ case fitwidth:
+ /* Trick: we don't know the height of a future page yet,
+ * so we use a "large" value for top which will do for
+ * most pages. If it doesn't work, not much harm is done.
+ */
+ if (dest->top == -1)
+ dest->top = 10000;
+ break;
+
+ case fitrect:
+ case fitheight:
+ case fitvisiblewidth:
+ case fitvisibleheight:
+ if (dest->left == -1)
+ dest->left = 0;
+ if (dest->bottom == -1)
+ dest->bottom = 0;
+ if (dest->right == -1)
+ dest->right = 1000;
+ if (dest->top == -1)
+ dest->top = 1000;
+ break;
+
+ case nameddest:
+ if (destuse == pdf_nameddest)
+ {
+ pdf_cleanup_destination(p, dest);
+ pdc_error(p->pdc, PDC_E_OPT_ILLKEYWORD, "type", type_name, 0, 0);
+ }
+ if (dest->name == NULL)
+ {
+ pdf_cleanup_destination(p, dest);
+ pdc_error(p->pdc, PDC_E_OPT_NOTFOUND, "name", 0, 0, 0);
+ }
+ break;
+
+ case filedest:
+ if (destuse != pdf_bookmark)
+ {
+ pdf_cleanup_destination(p, dest);
+ pdc_error(p->pdc, PDC_E_OPT_ILLKEYWORD, "type", type_name, 0, 0);
+ }
+ if (dest->filename == NULL)
+ {
+ pdf_cleanup_destination(p, dest);
+ pdc_error(p->pdc, PDC_E_OPT_NOTFOUND, "filename", 0, 0, 0);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /* check for minpage */
+ minpage = (destuse == pdf_bookmark) ? 0 : 1;
+ switch (destuse)
+ {
+ case pdf_nameddest:
+ case pdf_locallink:
+ if (page == 0)
+ {
+ page = pdf_current_page(p);
+ }
+
+ case pdf_openaction:
+ case pdf_bookmark:
+ case pdf_remotelink:
+ if (page < minpage)
+ {
+ const char *stemp = pdc_errprintf(p->pdc, "%d", page);
+ pdf_cleanup_destination(p, dest);
+ pdc_error(p->pdc, PDC_E_ILLARG_HANDLE, "page", stemp, 0, 0);
+ }
+ break;
+ }
+
+ dest->pgnum = page;
+
+ if (destuse != pdf_remotelink && destuse != pdf_openaction && page != 0)
+ {
+ dest->page = pdf_get_page_id(p, page);
+ }
+
+ /* remote page number */
+ if (destuse == pdf_remotelink)
+ dest->remote_page = page;
+
+ return dest;
+}
+
+#if defined(_MSC_VER) && defined(_MANAGED)
+#pragma unmanaged
+#endif
+pdf_dest *
+pdf_get_option_destname(PDF *p, pdc_resopt *resopts,
+ pdc_encoding hypertextencoding,
+ int hypertextcodepage)
+{
+ pdc_text_format hypertextformat = pdc_bytes;
+ pdf_dest *dest = NULL;
+ char **strlist;
+ int outlen;
+
+ if (pdc_get_optvalues("destname", resopts, NULL, &strlist))
+ {
+ dest = pdf_init_destination(p);
+ dest->type = nameddest;
+
+ if (pdc_is_lastopt_utf8(resopts))
+ hypertextformat = PDC_UTF8;
+ dest->name = pdf_convert_hypertext(p, strlist[0], 0, hypertextformat,
+ hypertextencoding, hypertextcodepage,
+ &outlen, PDC_UTF8_FLAG, pdc_true);
+ }
+ return dest;
+}
+#if defined(_MSC_VER) && defined(_MANAGED)
+#pragma managed
+#endif
+
+
+void
+pdf_write_destination(PDF *p, pdf_dest *dest)
+{
+ if (dest->type == nameddest)
+ {
+ pdf_put_hypertext(p, dest->name);
+ pdc_puts(p->out, "\n");
+ return;
+ }
+
+ pdc_begin_array(p->out);
+
+ if (dest->remote_page)
+ {
+ pdc_printf(p->out, "%d", dest->remote_page - 1); /* zero-based */
+ }
+ else
+ {
+ if (dest->page == PDC_BAD_ID)
+ dest->page = pdf_get_page_id(p, dest->pgnum);
+
+ pdc_objref_c(p->out, dest->page);
+ }
+
+ switch (dest->type) {
+
+ case fixed:
+ pdc_puts(p->out, "/XYZ ");
+
+ if (dest->left != -1)
+ pdc_printf(p->out, "%f ", dest->left);
+ else
+ pdc_puts(p->out, "null ");
+
+ if (dest->top != -1)
+ pdc_printf(p->out, "%f ", dest->top);
+ else
+ pdc_puts(p->out, "null ");
+
+ if (dest->zoom != -1)
+ pdc_printf(p->out, "%f", dest->zoom);
+ else
+ pdc_puts(p->out, "null");
+
+ break;
+
+ case fitwindow:
+ pdc_puts(p->out, "/Fit");
+ break;
+
+ case fitwidth:
+ pdc_printf(p->out, "/FitH %f", dest->top);
+ break;
+
+ case fitheight:
+ pdc_printf(p->out, "/FitV %f", dest->left);
+ break;
+
+ case fitrect:
+ pdc_printf(p->out, "/FitR %f %f %f %f",
+ dest->left, dest->bottom, dest->right, dest->top);
+ break;
+
+ case fitvisible:
+ pdc_puts(p->out, "/FitB");
+ break;
+
+ case fitvisiblewidth:
+ pdc_printf(p->out, "/FitBH %f", dest->top);
+ break;
+
+ case fitvisibleheight:
+ pdc_printf(p->out, "/FitBV %f", dest->left);
+ break;
+
+ default:
+ break;
+ }
+
+ pdc_end_array(p->out);
+}
+
+void
+pdf__add_nameddest(
+ PDF *p,
+ const char *name,
+ int len,
+ const char *optlist)
+{
+ pdc_resopt *resopts = NULL;
+ pdc_text_format hypertextformat = p->hypertextformat;
+ pdc_encoding hypertextencoding;
+ int hypertextcodepage;
+ pdc_id obj_id = PDC_BAD_ID;
+ char *name2 = NULL;
+ pdf_dest *dest;
+ int inum;
+
+ if (!name)
+ pdc_error(p->pdc, PDC_E_ILLARG_EMPTY, "name", 0, 0, 0);
+
+ resopts = pdc_parse_optionlist(p->pdc, optlist,
+ pdf_destination_options, NULL, pdc_true);
+
+ hypertextencoding =
+ pdf_get_hypertextencoding_opt(p, resopts, &hypertextcodepage, pdc_true);
+
+ if (pdc_get_optvalues("hypertextformat", resopts, &inum, NULL))
+ {
+ hypertextformat = (pdc_text_format) inum;
+ pdf_check_hypertextformat(p, hypertextformat);
+ }
+
+ pdc_cleanup_optionlist(p->pdc, resopts);
+
+ /* create hypertext string */
+ name2 = pdf_convert_hypertext(p, name, len, hypertextformat,
+ hypertextencoding, hypertextcodepage, &len,
+ pdc_true, pdc_true);
+ if (name2 == NULL || len == 0)
+ pdc_error(p->pdc, PDC_E_ILLARG_EMPTY, "name", 0, 0, 0);
+
+ /* parsing option list */
+ dest = pdf_parse_destination_optlist(p, optlist, 0, pdf_nameddest);
+
+ /* interrupt the content stream if we are on a page */
+ if (PDF_GET_STATE(p) == pdf_state_page)
+ pdf_end_contents_section(p);
+
+ obj_id = pdc_begin_obj(p->out, PDC_NEW_ID); /* Dest object */
+ pdc_begin_dict(p->out); /* Destination dict */
+
+ pdc_puts(p->out, "/D");
+ pdf_write_destination(p, dest);
+
+ pdc_end_dict(p->out); /* Destination dict */
+ pdc_end_obj(p->out); /* Dest object */
+
+ /* continue the contents stream */
+ if (PDF_GET_STATE(p) == pdf_state_page)
+ pdf_begin_contents_section(p);
+
+ pdf_cleanup_destination(p, dest);
+
+ /* insert name in tree */
+ pdf_insert_name(p, name2, names_dests, obj_id);
+}
+
+
+/* -------------------------- bookmarks -------------------------- */
+
+static const pdc_defopt pdf_create_bookmark_options[] =
+{
+ {"hypertextencoding", pdc_stringlist, PDC_OPT_NONE, 1, 1,
+ 0.0, PDF_MAX_NAMESTRING, NULL},
+
+ {"hypertextformat", pdc_keywordlist, PDC_OPT_NONE, 1, 1,
+ 0.0, 0.0, pdf_textformat_keylist},
+
+ {"textcolor", pdc_stringlist, PDC_OPT_NONE, 2, 5,
+ 0.0, PDF_MAX_NAMESTRING, NULL},
+
+ {"fontstyle", pdc_keywordlist, PDC_OPT_NONE, 1, 1,
+ 0.0, 0.0, pdf_fontstyle_pdfkeylist},
+
+ {"parent", pdc_bookmarkhandle, PDC_OPT_NONE, 1, 1,
+ 0.0, 0.0, NULL},
+
+ {"index", pdc_integerlist, PDC_OPT_NONE, 1, 1,
+ -1, PDC_INT_MAX, NULL},
+
+ {"open", pdc_booleanlist, PDC_OPT_NONE, 1, 1,
+ 0.0, 0.0, NULL},
+
+ {"destination", pdc_stringlist, PDC_OPT_NONE, 1, 1,
+ 0.0, PDC_INT_MAX, NULL},
+
+ {"destname", pdc_stringlist, PDC_OPT_IGNOREIF1, 1, 1,
+ 0.0, PDC_INT_MAX, NULL},
+
+ {"action", pdc_stringlist, PDC_OPT_NONE, 1, 1,
+ 0.0, PDC_INT_MAX, NULL},
+
+ PDC_OPT_TERMINATE
+};
+
+struct pdf_outline_s {
+ pdc_id obj_id; /* id of this outline object */
+ char *text; /* bookmark text */
+ int count; /* number of open sub-entries */
+ pdc_bool open; /* whether or not to display children */
+ pdc_scalar textcolor[3]; /* rgb color of bookmark text */
+ fnt_fontstyle fontstyle; /* font style of bookmark text */
+ char *action; /* action optlist */
+ pdf_dest *dest; /* outline destination */
+
+ /* these members control automatic ordering of bookmarks.
+ */
+ pdc_bool in_order; /* this book mark is "in order" */
+ pdc_id page_id; /* id of page where this bookmark */
+ /* was defined */
+
+ /* the members below are indices into the p->outlines[] array.
+ */
+ int prev; /* previous entry at this level */
+ int next; /* next entry at this level */
+ int parent; /* ancestor's index */
+ int first; /* first sub-entry */
+ int last; /* last sub-entry */
+};
+
+static void
+pdf_init_outline(PDF *p, pdf_outline *outline)
+{
+ (void) p;
+
+ outline->obj_id = PDC_BAD_ID;
+ outline->text = NULL;
+ outline->count = 0;
+ outline->open = pdc_false;
+ outline->textcolor[0] = 0.0;
+ outline->textcolor[1] = 0.0;
+ outline->textcolor[2] = 0.0;
+ outline->fontstyle = fnt_Normal;
+ outline->action = NULL;
+ outline->dest = NULL;
+ outline->in_order = pdc_false;
+ outline->page_id = PDC_BAD_ID;
+ outline->prev = 0;
+ outline->next = 0;
+ outline->parent = 0;
+ outline->first = 0;
+ outline->last = 0;
+}
+
+/* We can't work with pointers in the outline objects because
+ * the complete outline block may be reallocated. Therefore we use
+ * this simple mechanism for achieving indirection.
+ */
+#define COUNT(jndex) (p->outlines[jndex].count)
+#define OPEN(jndex) (p->outlines[jndex].open)
+#define IN_ORDER(jndex) (p->outlines[jndex].in_order)
+#define PAGE_ID(jndex) (p->outlines[jndex].page_id)
+#define LAST(jndex) (p->outlines[jndex].last)
+#define PARENT(jndex) (p->outlines[jndex].parent)
+#define FIRST(jndex) (p->outlines[jndex].first)
+#define OBJ_ID(jndex) (p->outlines[jndex].obj_id)
+#define PREV(jndex) (p->outlines[jndex].prev)
+#define NEXT(jndex) (p->outlines[jndex].next)
+
+static int
+search_forward(PDF *p, int start_page, int start_index)
+{
+ int idx;
+
+ for (idx = start_index; idx != 0; idx = NEXT(idx))
+ {
+ if (IN_ORDER(idx))
+ return pdf_search_page_fwd(p, start_page, PAGE_ID(idx));
+ }
+
+ return PDC_INT_MAX;
+}
+
+static int
+search_backward(PDF *p, int start_page, int start_index)
+{
+ int idx;
+
+ for (idx = start_index; idx != 0; idx = PREV(idx))
+ {
+ if (IN_ORDER(idx))
+ {
+ int pg = pdf_search_page_bwd(p, start_page, PAGE_ID(idx));
+
+ return (pg == -1) ? PDC_INT_MAX : pg;
+ }
+ }
+
+ return -1;
+}
+
+static int
+pdf_insert_bookmark(
+ PDF *p,
+ const char *hypertext,
+ pdf_outline *outline,
+ int jndex)
+{
+ static const char fn[] = "pdf_insert_bookmark";
+ pdf_outline *root, *self;
+ int parent;
+ int self_idx;
+ int pageno = pdf_current_page(p);
+
+ /* allocation */
+ if (p->outline_count == 0)
+ {
+ p->outline_capacity = OUTLINE_CHUNKSIZE;
+ p->outlines = (pdf_outline *) pdc_calloc(p->pdc,
+ sizeof(pdf_outline) * p->outline_capacity, fn);
+
+ /* populate the root outline object */
+ root = &p->outlines[0];
+ pdf_init_outline(p, root);
+ root->obj_id = pdc_alloc_id(p->out);
+ root->open = pdc_true;
+
+ /* set the open mode show bookmarks if we have at least one,
+ * and the client didn't already set his own open mode.
+ */
+ pdf_fix_openmode(p);
+ }
+ else if (p->outline_count + 1 >= p->outline_capacity)
+ {
+ p->outline_capacity *= 2;
+ p->outlines = (pdf_outline *) pdc_realloc(p->pdc, p->outlines,
+ sizeof(pdf_outline) * p->outline_capacity, fn);
+ }
+
+ /* copy */
+ self_idx = ++p->outline_count;
+ self = &p->outlines[self_idx];
+ memcpy(self, outline, sizeof(pdf_outline));
+
+ self->obj_id = pdc_alloc_id(p->out);
+ self->text = (char *) hypertext;
+ self->page_id = pdf_get_page_id(p, 0);
+ parent = self->parent;
+
+ /* default destination */
+ if (self->action == NULL && self->dest == NULL)
+ self->dest = pdf_init_destination(p);
+
+ /* no destination */
+ if (self->dest != NULL &&
+ self->dest->name != NULL && !strlen(self->dest->name))
+ {
+ pdf_cleanup_destination(p, self->dest);
+ self->dest = NULL;
+ }
+
+ /* current page */
+ if (self->dest)
+ {
+ /* this ugly code is for compatibility with the
+ ** obsolete "bookmarkdest" parameter.
+ */
+ if (self->dest->pgnum == 0)
+ self->dest->pgnum = pdf_current_page(p);
+
+ if (self->dest->pgnum == 0)
+ {
+ self->dest->pgnum = 1;
+ }
+ else if (self->dest->page == PDC_BAD_ID)
+ {
+ self->dest->page = pdf_get_page_id(p, self->dest->pgnum);
+ }
+ }
+
+ /* special case: empty list.
+ */
+ if (FIRST(parent) == 0)
+ {
+ if (jndex > 0)
+ pdc_error(p->pdc, PDC_E_OPT_ILLINTEGER, "index",
+ pdc_errprintf(p->pdc, "%d", jndex), 0, 0);
+
+ FIRST(parent) = LAST(parent) = self_idx;
+ self->in_order = pdc_true;
+ }
+ else switch (jndex)
+ {
+ case -2: /* insert "in order" */
+ {
+ /* the "natural" case: append to the end if appropriate.
+ */
+ if (pageno >= search_backward(p, -1, LAST(parent)))
+ {
+ self->prev = LAST(parent);
+ NEXT(LAST(parent)) = self_idx;
+ LAST(parent) = self_idx;
+ }
+ else
+ {
+ int idx;
+ int curr_pg = 1;
+ int next_pg;
+
+ for (idx = FIRST(parent); idx != 0; idx = NEXT(idx))
+ {
+ if (!IN_ORDER(idx))
+ continue;
+
+ next_pg = pdf_search_page_fwd(p, curr_pg, PAGE_ID(idx));
+
+ /* TODO: understand why this can happen.
+ */
+ if (next_pg < 1)
+ {
+ idx = 0;
+ break;
+ }
+
+ if (next_pg > pageno)
+ {
+ self->next = idx;
+ self->prev = PREV(idx);
+ PREV(idx) = self_idx;
+
+ if (self->prev == 0)
+ FIRST(parent) = self_idx;
+ else
+ NEXT(self->prev) = self_idx;
+
+ break;
+ }
+
+ curr_pg = next_pg;
+ }
+
+ /* if there are no "in order" bookmarks yet,
+ ** we simply append this one to the end.
+ */
+ if (idx == 0)
+ {
+ self->prev = LAST(parent);
+ NEXT(LAST(parent)) = self_idx;
+ LAST(parent) = self_idx;
+ }
+ }
+
+ self->in_order = pdc_true;
+ break;
+ }
+
+ case -1: /* append to the end */
+ {
+ self->prev = LAST(parent);
+ NEXT(LAST(parent)) = self_idx;
+ LAST(parent) = self_idx;
+
+ self->in_order =
+ (pageno >= search_backward(p, pageno, self->prev));
+ break;
+ }
+
+ case 0: /* insert at the beginning */
+ {
+ self->next = FIRST(parent);
+ PREV(FIRST(parent)) = self_idx;
+ FIRST(parent) = self_idx;
+
+ self->in_order =
+ (pageno <= search_forward(p, pageno, self->next));
+ break;
+ }
+
+ default: /* insert before [1..LAST] */
+ {
+ int i;
+ int target = FIRST(parent);
+
+ for (i = 0; i < jndex; ++i)
+ {
+ if (target == LAST(parent))
+ pdc_error(p->pdc, PDC_E_OPT_ILLINTEGER, "index",
+ pdc_errprintf(p->pdc, "%d", jndex), 0, 0);
+
+ target = NEXT(target);
+ }
+
+ self->next = target;
+ self->prev = PREV(target);
+ NEXT(self->prev) = PREV(self->next) = self_idx;
+
+ self->in_order =
+ ((pageno >= search_backward(p, pageno, self->prev)) &&
+ (pageno <= search_forward(p, pageno, self->next)));
+ break;
+ }
+ } /* else switch */
+
+ /* increase the number of open sub-entries for all relevant ancestors */
+ do {
+ COUNT(parent)++;
+ } while (OPEN(parent) && (parent = PARENT(parent)) != 0);
+
+ return (self_idx); /* caller may use this as handle */
+}
+
+int
+pdf__create_bookmark(PDF *p, const char *text, int len, const char *optlist)
+{
+ pdc_resopt *resopts = NULL;
+ pdc_clientdata data;
+ pdf_outline self;
+ pdf_dest *dest = NULL;
+ pdc_text_format hypertextformat;
+ pdc_encoding hypertextencoding;
+ pdf_coloropt textcolor;
+ char *hypertext = NULL;
+ const char *keyword = NULL;
+ char **strlist = NULL;
+ int hypertextcodepage;
+ int ns, inum, outlen, retval = 0;
+ int jndex = -2;
+
+ /* Initialize */
+ pdf_init_outline(p, &self);
+ hypertextformat = p->hypertextformat;
+ hypertextencoding = p->hypertextencoding;
+ hypertextcodepage = p->hypertextcodepage;
+
+ /* Parsing option list */
+ if (optlist && strlen(optlist))
+ {
+ pdf_set_clientdata(p, &data);
+ resopts = pdc_parse_optionlist(p->pdc, optlist,
+ pdf_create_bookmark_options, &data, pdc_true);
+
+ hypertextencoding =
+ pdf_get_hypertextencoding_opt(p, resopts, &hypertextcodepage,
+ pdc_true);
+
+ if (pdc_get_optvalues("hypertextformat", resopts, &inum, NULL))
+ {
+ hypertextformat = (pdc_text_format) inum;
+ pdf_check_hypertextformat(p, hypertextformat);
+ }
+
+ ns = pdc_get_optvalues("textcolor", resopts, NULL, &strlist);
+ if (ns)
+ {
+ pdf_parse_coloropt(p, "textcolor", strlist, ns, (int) color_rgb,
+ &textcolor);
+ self.textcolor[0] = textcolor.value[0];
+ self.textcolor[1] = textcolor.value[1];
+ self.textcolor[2] = textcolor.value[2];
+ }
+
+ if (pdc_get_optvalues("fontstyle", resopts, &inum, NULL))
+ self.fontstyle = (fnt_fontstyle) inum;
+
+ pdc_get_optvalues("parent", resopts, &self.parent, NULL);
+
+ pdc_get_optvalues("index", resopts, &jndex, NULL);
+
+ pdc_get_optvalues("open", resopts, &self.open, NULL);
+
+ if (pdc_get_optvalues("destination", resopts, NULL, &strlist))
+ {
+ self.dest = pdf_parse_destination_optlist(p, strlist[0], 0,
+ pdf_bookmark);
+ keyword = "destination";
+ }
+ else
+ {
+ dest = pdf_get_option_destname(p, resopts, hypertextencoding,
+ hypertextcodepage);
+ if (dest)
+ {
+ self.dest = dest;
+ keyword = "destname";
+ }
+ }
+
+ if (pdc_get_optvalues("action", resopts, NULL, &strlist))
+ {
+ if (self.dest)
+ {
+ pdf_cleanup_destination(p, self.dest);
+ self.dest = NULL;
+ pdc_warning(p->pdc, PDC_E_OPT_IGNORE, keyword, "action", 0, 0);
+ }
+
+ /* parsing of action list */
+ pdf_parse_and_write_actionlist(p, event_bookmark, NULL,
+ (const char *) strlist[0]);
+ self.action =
+ (char *) pdc_save_lastopt(resopts, PDC_OPT_SAVE1ELEM);
+ }
+
+ pdc_cleanup_optionlist(p->pdc, resopts);
+ }
+
+ /* create hypertext string */
+ hypertext = pdf_convert_hypertext(p, text, len, hypertextformat,
+ hypertextencoding, hypertextcodepage,
+ &outlen, PDC_UTF8_FLAG, pdc_true);
+ if (hypertext)
+ retval = pdf_insert_bookmark(p, hypertext, &self, jndex);
+
+ return retval;
+}
+
+static void
+pdf_write_outline_dict(PDF *p, int entry)
+{
+ pdf_outline *outline = &p->outlines[entry];
+ pdc_id act_idlist[PDF_MAX_EVENTS];
+
+ /* write action objects */
+ if (outline->action)
+ pdf_parse_and_write_actionlist(p, event_bookmark, act_idlist,
+ (const char *) outline->action);
+
+ pdc_begin_obj(p->out, OBJ_ID(entry)); /* outline object */
+ pdc_begin_dict(p->out);
+
+ pdc_objref(p->out, "/Parent", OBJ_ID(PARENT(entry)));
+
+ /* outline destination */
+ if (outline->dest)
+ {
+ pdc_puts(p->out, "/Dest");
+ pdf_write_destination(p, outline->dest);
+ }
+
+ /* write Action entries */
+ else if (outline->action)
+ pdf_write_action_entries(p, event_bookmark, act_idlist);
+
+ pdc_puts(p->out, "/Title"); /* outline text */
+ pdf_put_hypertext(p, outline->text);
+ pdc_puts(p->out, "\n");
+
+ if (PREV(entry))
+ pdc_objref(p->out, "/Prev", OBJ_ID(PREV(entry)));
+ if (NEXT(entry))
+ pdc_objref(p->out, "/Next", OBJ_ID(NEXT(entry)));
+
+ if (FIRST(entry)) {
+ pdc_objref(p->out, "/First", OBJ_ID(FIRST(entry)));
+ pdc_objref(p->out, "/Last", OBJ_ID(LAST(entry)));
+ }
+ if (COUNT(entry)) {
+ if (OPEN(entry))
+ pdc_printf(p->out, "/Count %d\n", COUNT(entry)); /* open */
+ else
+ pdc_printf(p->out, "/Count %d\n", -COUNT(entry));/* closed */
+ }
+
+ /* Color */
+ if (outline->textcolor[0] != 0.0 ||
+ outline->textcolor[1] != 0.0 ||
+ outline->textcolor[2] != 0.0)
+ pdc_printf(p->out, "/C[%f %f %f]\n", outline->textcolor[0],
+ outline->textcolor[1],
+ outline->textcolor[2]);
+
+ /* FontStyle */
+ if (outline->fontstyle != fnt_Normal)
+ {
+ int fontstyle = 0;
+ if (outline->fontstyle == fnt_Bold)
+ fontstyle = 2;
+ if (outline->fontstyle == fnt_Italic)
+ fontstyle = 1;
+ if (outline->fontstyle == fnt_BoldItalic)
+ fontstyle = 3;
+ pdc_printf(p->out, "/F %d\n", fontstyle);
+ }
+
+ pdc_end_dict(p->out);
+ pdc_end_obj(p->out); /* outline object */
+}
+
+void
+pdf_write_outlines(PDF *p)
+{
+ int i;
+
+ if (p->outline_count == 0) /* no outlines: return */
+ return;
+
+ pdc_begin_obj(p->out, p->outlines[0].obj_id); /* root outline object */
+ pdc_begin_dict(p->out);
+
+ if (p->outlines[0].count != 0)
+ pdc_printf(p->out, "/Count %d\n", COUNT(0));
+ pdc_objref(p->out, "/First", OBJ_ID(FIRST(0)));
+ pdc_objref(p->out, "/Last", OBJ_ID(LAST(0)));
+
+ pdc_end_dict(p->out);
+ pdc_end_obj(p->out); /* root outline object */
+
+#define PDF_FLUSH_AFTER_MANY_OUTLINES 1000 /* ca. 50-100 KB */
+ for (i = 1; i <= p->outline_count; i++) {
+ /* reduce memory usage for many outline entries */
+ if (i % PDF_FLUSH_AFTER_MANY_OUTLINES == 0)
+ pdc_flush_stream(p->out);
+
+ pdf_write_outline_dict(p, i);
+ }
+}
+
+void
+pdf_write_outline_root(PDF *p)
+{
+ if (p->outline_count != 0)
+ pdc_objref(p->out, "/Outlines", p->outlines[0].obj_id);
+}
+
+void
+pdf_init_outlines(PDF *p)
+{
+ p->outline_count = 0;
+}
+
+/* Free outline entries */
+void
+pdf_cleanup_outlines(PDF *p)
+{
+ int i;
+
+ if (!p->outlines || p->outline_count == 0)
+ return;
+
+ /* outlines[0] is the outline root object */
+ for (i = 0; i <= p->outline_count; i++)
+ {
+ if (p->outlines[i].text)
+ {
+ pdc_free(p->pdc, p->outlines[i].text);
+ p->outlines[i].text = NULL;
+ }
+ if (p->outlines[i].action)
+ {
+ pdc_free(p->pdc, p->outlines[i].action);
+ p->outlines[i].action = NULL;
+ }
+ pdf_cleanup_destination(p, p->outlines[i].dest);
+ p->outlines[i].dest = NULL;
+ }
+
+ pdc_free(p->pdc, (void*) p->outlines);
+
+ p->outlines = NULL;
+}
+
+
+/*****************************************************************************/
+/** deprecated historical bookmark function **/
+/*****************************************************************************/
+
+int
+pdf__add_bookmark(PDF *p, const char *text, int len, int parent, int open)
+{
+ pdf_outline self;
+ pdf_dest *dest = (pdf_dest *) p->bookmark_dest;
+ char *hypertext = NULL;
+ int acthdl;
+ int retval = 0;
+
+ pdf_init_outline(p, &self);
+
+ if (parent != 0)
+ pdf_check_handle(p, parent, pdc_bookmarkhandle);
+ self.parent = parent;
+ self.open = open;
+
+ /* creating a Launch action - defined via bookmarkdest */
+ if (dest->filename)
+ {
+ char actoptlist[2048];
+
+ sprintf(actoptlist, "filename {%s} ", dest->filename);
+ acthdl = pdf__create_action(p, "Launch", actoptlist);
+ if (acthdl != -1)
+ {
+ if (p->pdc->hastobepos) acthdl++;
+ sprintf(actoptlist, "activate %d", acthdl);
+ self.action = pdc_strdup(p->pdc, actoptlist);
+ }
+ }
+ else
+ {
+ self.dest = pdf_init_destination(p);
+ *self.dest = *dest;
+ if (dest->name)
+ self.dest->name = pdc_strdup(p->pdc, dest->name);
+ }
+
+ memcpy(self.textcolor, dest->color, 3 * sizeof(pdc_scalar));
+ self.fontstyle = dest->fontstyle;
+
+ hypertext = pdf_convert_hypertext_depr(p, text, len);
+ if (hypertext)
+ retval = pdf_insert_bookmark(p, hypertext, &self, -1);
+
+ return retval;
+}
+
+/* -------------------------- document info ------------------------------- */
+
+struct pdf_info_s
+{
+ char *key; /* ASCII string */
+ char *value; /* Unicode string */
+ pdf_info *next; /* next info entry */
+};
+
+void
+pdf_cleanup_info(PDF *p)
+{
+ pdf_info *info, *last;
+
+ if (p->userinfo)
+ {
+ for (info = p->userinfo; info != NULL; /* */)
+ {
+ last = info;
+ info = info->next;
+
+ pdc_free(p->pdc, last->key);
+ pdc_free(p->pdc, last->value);
+ pdc_free(p->pdc, last);
+ }
+
+ p->userinfo = NULL;
+ }
+}
+
+static pdf_info *
+pdf_have_infokey(PDF *p, const char *key)
+{
+ pdf_info *info;
+
+ for (info = p->userinfo; info != NULL; info = info->next)
+ {
+ if (strlen(info->key) == strlen(key) && !strcmp(info->key, key))
+ return info;
+ }
+
+ return NULL;
+}
+
+void
+pdf_feed_digest_info(PDF *p)
+{
+ pdf_info *info;
+
+ if (p->userinfo)
+ {
+ for (info = p->userinfo; info != NULL; info = info->next)
+ {
+ pdc_update_digest(p->out,
+ (unsigned char *) info->key, strlen(info->key));
+ }
+ }
+}
+
+#define PDF_TRAPPED_TRUE "True"
+#define PDF_TRAPPED_FALSE "False"
+#define PDF_TRAPPED_UNKNOWN "Unknown"
+
+/* Set Info dictionary entries */
+void
+pdf__set_info(PDF *p, const char *key, const char *value, int len)
+{
+ static const char fn[] = "pdf__set_info";
+ char *key_buf, *val_buf;
+ pdf_info *oldentry, *newentry;
+
+ if (key == NULL || !*key)
+ pdc_error(p->pdc, PDC_E_ILLARG_EMPTY, "key", 0, 0, 0);
+
+ if (!strcmp(key, "Producer") ||
+ !strcmp(key, "CreationDate") ||
+ !strcmp(key, "ModDate"))
+ pdc_error(p->pdc, PDC_E_ILLARG_STRING, "key", key, 0, 0);
+
+ /* converting key */
+ key_buf = pdf_convert_name(p, key, 0, 0);
+
+ /* convert text string */
+ val_buf = pdf_convert_hypertext_depr(p, value, len);
+ if (!val_buf)
+ pdc_error(p->pdc, PDC_E_ILLARG_EMPTY, "value", 0, 0, 0);
+
+ /* special handling required for "Trapped" */
+ if (!strcmp(key_buf, "Trapped"))
+ {
+ if (strcmp(val_buf, PDF_TRAPPED_TRUE) &&
+ strcmp(val_buf, PDF_TRAPPED_FALSE) &&
+ strcmp(val_buf, PDF_TRAPPED_UNKNOWN))
+ {
+ pdc_free(p->pdc, val_buf);
+ pdc_free(p->pdc, key_buf);
+ pdc_error(p->pdc, PDC_E_PAR_ILLPARAM, value, key, 0, 0);
+ }
+ }
+
+ oldentry = pdf_have_infokey(p, key_buf);
+ if (oldentry != NULL)
+ {
+ pdc_free(p->pdc, key_buf);
+ pdc_free(p->pdc, oldentry->value);
+ oldentry->value = val_buf;
+ }
+ else
+ {
+ newentry = (pdf_info *)
+ pdc_malloc(p->pdc, sizeof(pdf_info), fn);
+ newentry->key = key_buf;
+ newentry->value = val_buf;
+ newentry->next = p->userinfo;
+
+ /* ordering doesn't matter so we insert at the beginning */
+ p->userinfo = newentry;
+ }
+}
+
+
+pdc_id
+pdf_write_info(PDF *p, pdc_bool moddate)
+{
+ char time_str[PDC_TIME_SBUF_SIZE];
+ char producer[PDF_MAX_PARAMSTRING];
+ pdf_info *info;
+ pdc_id info_id;
+
+
+
+ const char *product = "PDFlib Lite";
+
+
+ if (!p->pdc->smokerun)
+ pdc_logg_cond(p->pdc, 1, trc_api,
+ "[Full product name: \"%s\"]\n", product);
+
+ info_id = pdc_begin_obj(p->out, PDC_NEW_ID); /* Info object */
+
+ pdc_begin_dict(p->out);
+
+ /*
+ * Although it would be syntactically correct, we must not remove
+ * the space characters after the dictionary keys since this
+ * would break the PDF properties feature in Windows Explorer.
+ */
+
+ if (p->userinfo)
+ {
+ for (info = p->userinfo; info != NULL; info = info->next)
+ {
+ pdf_put_pdfname(p, info->key);
+ pdc_puts(p->out, " ");
+
+ if (strcmp(info->key, "Trapped"))
+ pdf_put_hypertext(p, info->value);
+ else
+ pdf_put_pdfname(p, info->value);
+
+ pdc_puts(p->out, "\n");
+ }
+ }
+
+
+ pdc_get_timestr(time_str, pdc_false);
+
+ /* creation date and time */
+ pdc_puts(p->out, "/CreationDate ");
+ pdf_put_hypertext(p, time_str);
+ pdc_puts(p->out, "\n");
+
+ /* modification date and time */
+ if (moddate)
+ {
+ pdc_puts(p->out, "/ModDate ");
+ pdf_put_hypertext(p, time_str);
+ pdc_puts(p->out, "\n");
+ }
+
+ /*
+ * If you change the /Producer entry your license to use
+ * PDFlib will be void!
+ */
+
+ if (p->pdc->binding)
+ sprintf(producer, "%s %s (%s/%s)", product,
+ PDFLIB_VERSIONSTRING, p->pdc->binding, PDF_PLATFORM);
+ else
+ sprintf(producer, "%s %s (%s)", product,
+ PDFLIB_VERSIONSTRING, PDF_PLATFORM);
+
+ pdc_puts(p->out, "/Producer ");
+ pdf_put_hypertext(p, producer);
+ pdc_puts(p->out, "\n");
+
+ pdc_end_dict(p->out);
+ pdc_end_obj(p->out); /* Info object */
+
+
+
+
+ return info_id;
+}
+
+