/*---------------------------------------------------------------------------*
 |              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.191.2.31 2009/10/19 15:39:40 kurt 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;

    len = pdc_check_text_length(p->pdc, &name, len, PDF_MAXSTRINGSIZE);
    if (!len)
        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;

    len = pdc_check_text_length(p->pdc, &text, len, PDF_MAXSTRINGSIZE);
    if (!len)
        pdc_error(p->pdc, PDC_E_ILLARG_EMPTY, "text", 0, 0, 0);

    /* 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)
{
    static const char *fn = "pdf__add_bookmark";
    pdf_outline self;
    pdf_dest *dest = (pdf_dest *) p->bookmark_dest;
    char *hypertext = NULL;
    int acthdl;
    int retval = 0;

    len = pdc_check_text_length(p->pdc, &text, len, PDF_MAXSTRINGSIZE);
    if (!len)
        pdc_error(p->pdc, PDC_E_ILLARG_EMPTY, "text", 0, 0, 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;

        actoptlist = (char *)
            pdc_malloc(p->pdc, strlen(dest->filename) + 80, fn);
        pdc_sprintf(p->pdc, pdc_false, actoptlist, "filename {%s} ",
                    dest->filename);
        acthdl = pdf__create_action(p, "Launch", actoptlist);
        if (acthdl != -1)
        {
            if (p->pdc->hastobepos) acthdl++;
            pdc_sprintf(p->pdc, pdc_false, actoptlist, "activate %d", acthdl);
            self.action = pdc_strdup(p->pdc, actoptlist);
        }

        pdc_free(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;
    const char **kp;
    static const char *forbidden_keys[] =
    {
        "Producer",
        "CreationDate",
        "ModDate",
        "GTS_PDFXVersion",
        "GTS_PDFXConformance",
        "ISO_PDFEVersion"
    };

    if (key == NULL || !*key)
        pdc_error(p->pdc, PDC_E_ILLARG_EMPTY, "key", 0, 0, 0);

    len = pdc_check_text_length(p->pdc, &value, len, PDF_MAXSTRINGSIZE);

    for (kp = forbidden_keys;
        kp != sizeof(forbidden_keys) / sizeof(char *) + forbidden_keys;
        kp += 1)
    {
        if (!strcmp(*kp, key))
        {
            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 == NULL)
        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)
{
    pdc_bool logg3 = pdc_logg_is_enabled(p->pdc, 3, trc_xmp);
    char time_str[PDC_TIME_SBUF_SIZE];
    char producer[PDC_GEN_BUFSIZE];
    pdf_info    *info;
    pdc_id      info_id;



    const char *product = "PDFlib Lite";
    const char *security = "";

    (void) logg3;


    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)
        pdc_sprintf(p->pdc, pdc_false, producer, "%s %s%s (%s/%s)", product,
            PDFLIB_VERSIONSTRING, security, p->pdc->binding, PDF_PLATFORM);
    else
        pdc_sprintf(p->pdc, pdc_false, producer, "%s %s%s (%s)", product,
            PDFLIB_VERSIONSTRING, security, 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;
}



