/*---------------------------------------------------------------------------*
 |              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_page.c,v 1.116.2.25 2010/01/14 13:56:48 kurt Exp $
 *
 * PDFlib page related routines
 *
 */

#define P_PAGE_C

#include "p_intern.h"
#include "p_color.h"
#include "p_font.h"
#include "p_image.h"
#include "p_layer.h"
#include "p_page.h"
#include "p_tagged.h"



#define PDF_N_PAGE_BOXES        5

static const pdc_keyconn pdf_labelstyle_pdfkeylist[] =
{
    {"none", label_none},
    {"D",    label_123},
    {"R",    label_IVX},
    {"r",    label_ivx},
    {"A",    label_ABC},
    {"a",    label_abc},
    {NULL, 0}
};

typedef enum
{
    tabo_none,
    tabo_row,
    tabo_column,
    tabo_structure
}
pdf_taborder;

static const pdc_keyconn pdf_taborder_keylist[] =
{
    {"none",      tabo_none},
    {"row",       tabo_row},
    {"column",    tabo_column},
    {"structure", tabo_structure},
    {NULL, 0}
};

static const pdc_keyconn pdf_taborder_pdfkeylist[] =
{
    {"R",     tabo_row},
    {"C",     tabo_column},
    {"S",     tabo_structure},
    {NULL, 0}
};

typedef struct
{
    pdf_labelstyle      style;          /* page label style             */
    char *              prefix;         /* page label prefix            */
    int                 start;          /* page label numbering start   */
                                        /* 0 means "no label"           */
} pg_label;

typedef struct
{
    char *		name;		/* group name			*/
    int			n_pages;	/* # of pages in this group	*/
    int			capacity;	/* # of pages reserved		*/
    int			start;		/* 1-based physical page number	*/
    pg_label		label;
} pg_group;

/* per page resource list
*/
struct pdf_reslist_s
{
    int *		list;		/* resource numbers		*/
    int			capacity;
    int			length;
};

/* current, or suspended page.
*/
typedef struct
{
    pdf_ppt		ppt;

    /* list of content stream IDs.
    */
    pdc_id *		contents_ids;
    int			contents_ids_capacity;
    int			next_content;

    pdc_vtr *           annots;         /* annotation chain             */

    /* local values of global parameters.
    */
    pdc_scalar		ydir;		/* p->ydirection		*/

    /* resource lists.
    */
    pdf_reslist		rl_colorspaces;
    pdf_reslist		rl_extgstates;
    pdf_reslist		rl_fonts;
    pdf_reslist		rl_layers;
    pdf_reslist		rl_patterns;
    pdf_reslist		rl_shadings;
    pdf_reslist		rl_xobjects;
} pdf_page;


/* PDF page object.
*/
typedef struct
{
    pg_label		label;
    pdc_id		id;		/* object id for this page */
    pdf_page *		pg;		/* NULL if this page is not suspended */

    /* object ids (PDC_BAD_ID if not present).
    */
    pdc_id		annots_id;	/* id of page's /Annots entry */
    pdc_id		contents_id;	/* id of page's /Contents entry */
    pdc_id		res_id;		/* id of page's /Resources entry */
    pdc_id              thumb_id;       /* id of page's /Thumb entry */

    int			rotate;		/* page's /Rotate entry */
    int                 transition;     /* page transition type, or -1 */
    int                 taborder;       /* page taborder type */
    double              duration;       /* page display duration, or -1 */
    pdc_scalar          userunit;       /* page user unit */
    char *		action;		/* "action" option string */

    pdf_transgroup	tgroup;         /* transparency group definition */
    pdc_bool            autotgroup;     /* create transp. group automatically */


    pdc_id *		act_idlist;			/* action object ids */
    pdc_rectangle *	boxes[PDF_N_PAGE_BOXES];	/* MediaBox etc. */
} page_obj;


struct pdf_pages_s
{
    pdf_page   *curr_pg;
    pdc_bool	have_labels;
    pdc_bool	have_groups;
    pdc_bool	in_csect;		/* currently in contents section */
    int		last_suspended;		/* 1-based page number or -1 */
    pdf_ppt	default_ppt;		/* pseudo-ppt (for document scope) */

    /* as long as we support the old global parameters in addition to
    ** the new options, we have to save their values on entry to
    ** begin/resume_page(), and restore them during end/suspend_page().
    */
    pdc_scalar	old_ydir;		/* p->ydirection		*/

    /* deprecated parameters.
    */
    int         transition;             /* page transition type */
    double      duration;               /* page display duration */

    /* page descriptors in physical page order.
    */
    page_obj *	pages;                  /* page ids and suspended page descr */
    int         pages_capacity;
    int         current_page;           /* current page number (1-based) */
    int         last_page;              /* last page number allocated yet */
    int         max_page;           /* highest page number pre-allocated yet */

    /* page groups.
    */
    pg_group *	groups;
    int		groups_capacity;
    int		n_groups;

    pdc_id     *pnodes;                 /* page tree node ids */
    int         pnodes_capacity;        /* current # of entries in pnodes */
    int         current_pnode;          /* current node number (0-based) */
    int         current_pnode_kids;     /* current # of kids in current node */
};


static const pdc_rectangle pdf_null_rect =
{
    0, 0, 0, 0
};


/*********************** initialization & cleanup ***********************/

static void
pdf_init_ppt(PDF *p)
{
    pdf_ppt *ppt = p->curr_ppt;

    ppt->cstate = (pdf_cstate *) 0;
    ppt->tstate = (pdf_tstate *) 0;

    ppt->mboxes = (pdc_vtr *) 0;

} /* pdf_init_ppt */

static void
pdf_init_ppt_states(PDF *p)
{
    pdf_ppt *ppt = p->curr_ppt;

    ppt->sl = 0;

    pdf_init_tstate(p);
    pdf_init_gstate(p);
    pdf_init_cstate(p);
} /* pdf_init_ppt_states */

static void
pdf_reset_ppt(pdf_ppt *ppt)
{
    if (ppt->mboxes)
    {
        pdc_vtr_delete(ppt->mboxes);
        ppt->mboxes = (pdc_vtr *) 0;
    }
}

static void
pdf_delete_page(PDF *p, pdf_page *pg)
{
    if (pg != 0)
    {
	pdf_cleanup_page_cstate(p, &pg->ppt);
	pdf_cleanup_page_tstate(p, &pg->ppt);
        pdf_reset_ppt(&pg->ppt);

	if (pg->contents_ids)
	    pdc_free(p->pdc, pg->contents_ids);

        if (pg->annots)
        {
            pdc_vtr_delete(pg->annots);
            pg->annots = (pdc_vtr *) 0;
        }

	if (pg->rl_colorspaces.list)
	    pdc_free(p->pdc, pg->rl_colorspaces.list);
	if (pg->rl_extgstates.list)
	    pdc_free(p->pdc, pg->rl_extgstates.list);
	if (pg->rl_fonts.list)
	    pdc_free(p->pdc, pg->rl_fonts.list);
	if (pg->rl_layers.list)
	    pdc_free(p->pdc, pg->rl_layers.list);
	if (pg->rl_patterns.list)
	    pdc_free(p->pdc, pg->rl_patterns.list);
	if (pg->rl_shadings.list)
	    pdc_free(p->pdc, pg->rl_shadings.list);
	if (pg->rl_xobjects.list)
	    pdc_free(p->pdc, pg->rl_xobjects.list);

	pdc_free(p->pdc, pg);
    }
} /* pdf_delete_page */


static void
pdf_init_page_obj(page_obj *po)
{
    int i;

    po->id = PDC_BAD_ID;
    po->pg = (pdf_page *) 0;
    po->label.start = 0;
    po->label.prefix = (char *) 0;

    po->tgroup.colorspace = color_none;
    po->tgroup.isolated = pdc_false;
    po->tgroup.knockout = pdc_false;
    po->autotgroup = pdc_false;

    po->annots_id = PDC_BAD_ID;
    po->contents_id = PDC_BAD_ID;
    po->res_id = PDC_BAD_ID;
    po->thumb_id = PDC_BAD_ID;
    po->transition = -1;
    po->duration = -1;
    po->taborder = (int) tabo_none;
    po->userunit = 1.0;
    po->action = (char *) 0;

    po->rotate = 0;

    po->act_idlist = (pdc_id *) 0;
    for (i = 0; i < PDF_N_PAGE_BOXES; ++i)
	po->boxes[i] = (pdc_rectangle *) 0;
} /* pdf_init_page_obj */


static void
pdf_grow_pages(PDF *p)
{
    static const char fn[] = "pdf_grow_pages";

    pdf_pages *dp = p->doc_pages;
    int i;

    dp->pages = (page_obj *) pdc_realloc(p->pdc, dp->pages,
			    2 * sizeof (page_obj) * dp->pages_capacity, fn);

    for (i = dp->pages_capacity; i < dp->pages_capacity * 2; i++)
	pdf_init_page_obj(&dp->pages[i]);

    dp->pages_capacity *= 2;
} /* pdf_grow_pages */


void
pdf_init_pages(PDF *p, const char **groups, int n_groups)
{
    static const char fn[] = "pdf_init_pages";

    int		i, k;
    pdf_pages *	dp = (pdf_pages *) pdc_malloc(p->pdc, sizeof (pdf_pages), fn);

    p->doc_pages = dp;

    dp->have_labels = pdc_false;
    dp->have_groups = (n_groups != 0);
    dp->n_groups = 0;
    dp->last_suspended = 0;
    dp->in_csect = pdc_false;
    dp->transition = (int) trans_none;
    dp->duration = 0;

    dp->pages = (page_obj *) 0;
    dp->pnodes = (pdc_id *) 0;

    p->curr_ppt = &dp->default_ppt;
    pdf_init_ppt(p);

    dp->pages_capacity = PAGES_CHUNKSIZE;
    dp->pages = (page_obj *)
	    pdc_malloc(p->pdc, sizeof (page_obj) * dp->pages_capacity, fn);

    /* mark ids to allow for pre-allocation of page ids */
    for (i = 0; i < dp->pages_capacity; i++)
	pdf_init_page_obj(&dp->pages[i]);

    dp->current_page	= 0;
    dp->last_page	= 0;
    dp->max_page	= 0;
    dp->curr_pg		= (pdf_page *) 0;

    dp->pnodes_capacity = PNODES_CHUNKSIZE;
    dp->pnodes = (pdc_id *)
		pdc_malloc(p->pdc, sizeof (pdc_id) * dp->pnodes_capacity, fn);

    dp->current_pnode	= 0;
    dp->current_pnode_kids = 0;

    for (i = 0; i < n_groups - 1; ++i)
	for (k = i + 1; k < n_groups; ++k)
	    if (strcmp(groups[i], groups[k]) == 0)
	    {
		pdc_error(p->pdc, PDF_E_DOC_DUPLGROUP, groups[i], 0, 0, 0);
	    }

    dp->n_groups = n_groups;
    dp->groups = (pg_group *) (n_groups ?
	pdc_malloc(p->pdc, sizeof (pg_group) * n_groups, fn) : 0);

    for (i = 0; i < n_groups; ++i)
    {
	dp->groups[i].name = pdc_strdup(p->pdc, groups[i]);
	dp->groups[i].n_pages = 0;
	dp->groups[i].capacity = 0;
	dp->groups[i].start = 1;
	dp->groups[i].label.prefix = (char *) 0;
	dp->groups[i].label.start = 0;
    }

    /* clients may set char/word spacing and horizontal scaling outside pages
    ** for PDF_stringwidth() calculations, and they may set a color for use
    ** in PDF_makespotcolor(). that's what default_ppt is good for.
    */
    pdf_init_ppt_states(p);

} /* pdf_init_pages */


void pdf_check_suspended_pages(PDF *p)
{
    int		i;
    pdf_pages *	dp = p->doc_pages;

    for (i = 0; i <= dp->last_page; ++i)
    {
	if (dp->pages[i].pg != (pdf_page *) 0)
	{
	    pdc_error(p->pdc, PDF_E_PAGE_SUSPENDED,
		pdc_errprintf(p->pdc, "%d", i), 0, 0, 0);
	}
    }
} /* pdf_check_suspended_pages */


void
pdf_cleanup_pages(PDF *p)
{
    if (p->doc_pages != (pdf_pages *) 0)
    {
	int		i;
	pdf_pages *	dp = p->doc_pages;

	if (dp->groups)
	{
	    for (i = 0; i < dp->n_groups; ++i)
	    {
		if (dp->groups[i].name)
		    pdc_free(p->pdc, dp->groups[i].name);

		if (dp->groups[i].label.prefix)
		    pdc_free(p->pdc, dp->groups[i].label.prefix);
	    }

	    pdc_free(p->pdc, dp->groups);
	}

	if (dp->curr_pg)
	    pdf_delete_page(p, dp->curr_pg);

	if (dp->pages)
	{
	    for (i = 0; i <= dp->last_page; ++i)
	    {
		int k;
		page_obj *po = &dp->pages[i];

		if (po->label.prefix)
		    pdc_free(p->pdc, po->label.prefix);

		if (po->action)
		    pdc_free(p->pdc, po->action);


		if (po->pg != (pdf_page *) 0)
		    pdf_delete_page(p, po->pg);

		if (po->act_idlist != (pdc_id *) 0)
		    pdc_free(p->pdc, po->act_idlist);

		for (k = 0; k < PDF_N_PAGE_BOXES; ++k)
		{
		    if (po->boxes[k] != (pdc_rectangle *) 0)
			pdc_free(p->pdc, po->boxes[k]);
		}
	    }

	    pdc_free(p->pdc, dp->pages);
	}

	if (dp->pnodes)
	{
	    pdc_free(p->pdc, dp->pnodes);
	}

	if (p->curr_ppt != 0)
	{
	    pdf_cleanup_page_cstate(p, &dp->default_ppt);
	    pdf_cleanup_page_tstate(p, &dp->default_ppt);
	}

	pdc_free(p->pdc, p->doc_pages);
	p->doc_pages = (pdf_pages *) 0;
    }
} /* pdf_cleanup_pages */


/******************** page group & labels management ********************/

static pg_group *
find_group(pdf_pages *dp, const char *name)
{
    int i;

    for (i = 0; i < dp->n_groups; ++i)
	if (strcmp(dp->groups[i].name, name) == 0)
	    return &dp->groups[i];

    return (pg_group *) 0;
} /* find_group */


static void
grow_group(PDF *p, pg_group *group, int pageno, int n)
{
    pdf_pages *	dp = p->doc_pages;
    int		i;

    while (dp->max_page + n >= dp->pages_capacity)
	pdf_grow_pages(p);

    if (dp->max_page >= pageno)
    {
	memmove(&dp->pages[pageno + n], &dp->pages[pageno],
		    (dp->max_page - pageno + 1) * sizeof (page_obj));

	for (i = pageno; i < pageno + n; ++i)
	    pdf_init_page_obj(&dp->pages[i]);
    }

    dp->max_page += n;

    if (dp->last_page >= pageno)
	dp->last_page += n;

    if (dp->current_page >= pageno)
	dp->current_page += n;

    group->capacity += n;

    for (i = group - dp->groups + 1; i < dp->n_groups; ++i)
	dp->groups[i].start += n;
}


/* translate the group-relative pageno to an absolute page number.
** as a side effect, the group gets enlarged as needed if pageno
** exceeds the current number of pages in the group.
*/
int
pdf_xlat_pageno(PDF *p, int pageno, const char *groupname)
{
    pdf_pages *	dp = p->doc_pages;
    pg_group *	group = (pg_group *) 0;

    if (groupname && *groupname)
    {
	if ((group = find_group(dp, groupname)) == (pg_group *) 0)
	    pdc_error(p->pdc, PDF_E_DOC_UNKNOWNGROUP, groupname, 0, 0, 0);
    }

    if (group)
    {
	if (pageno < 1)
	    pdc_error(p->pdc, PDF_E_PAGE_NOTEXIST2,
		pdc_errprintf(p->pdc, "%d", pageno), group->name, 0, 0);

	if (pageno > group->capacity)
	    grow_group(p, group, group->start + group->capacity,
					pageno - group->capacity);

	pageno = group->start + pageno - 1;
    }
    else
    {
	if (dp->have_groups && pageno != 0)
	    pdc_error(p->pdc, PDF_E_PAGE_NEEDGROUP, 0, 0, 0, 0);
    }

    return pageno;
}


/* TODO (york): get rid of this function.
*/
void
pdf_init_pages2(PDF *p)
{
    pdf_pages *	dp = p->doc_pages;

    dp->pnodes[0] = pdc_alloc_id(p->out);
} /* pdf_init_pages2 */


static const pdc_defopt pdf_pagelabel_options[] =
{
    {"pagenumber", pdc_integerlist, PDC_OPT_NONE, 1, 1,
      1.0, PDC_INT_MAX, NULL},

    {"group", pdc_stringlist, PDC_OPT_NONE, 1, 1,
      1.0, PDF_MAX_NAMESTRING, NULL},

    {"style", pdc_keywordlist, PDC_OPT_CASESENS, 1, 1,
      0.0, 0.0, pdf_labelstyle_pdfkeylist},

    {"hypertextencoding", pdc_stringlist,  PDC_OPT_NONE, 1, 1,
      0.0, PDF_MAX_NAMESTRING, NULL},

    {"prefix", pdc_stringlist, PDC_OPT_NONE, 1, 1,
      0.0, PDF_MAX_NAMESTRING, NULL},

    {"start", pdc_integerlist, PDC_OPT_NONE, 1, 1,
      1.0, PDC_INT_MAX, NULL},

    PDC_OPT_TERMINATE
};

void
pdf_set_pagelabel(PDF *p, const char *optlist, int pageno)
{
    pdf_pages *	dp = p->doc_pages;
    pg_label *	lp;
    pdc_resopt *resopts = NULL;
    char **	strlist;
    int		inum;

    int		page = 0;
    char *	groupname = NULL;
    pdf_labelstyle style = label_none;
    pdc_encoding htenc;
    int         htcp;
    char *	prefix = NULL;
    int		start = 1;

    resopts = pdc_parse_optionlist(p->pdc, optlist, pdf_pagelabel_options,
                                   NULL, pdc_true);

    switch (pageno)
    {
	case PDF_FC_BEGIN_DOCUMENT:
	    if (pdc_get_optvalues("group", resopts, NULL, &strlist))
		groupname = strlist[0];
	    else
		pdc_error(p->pdc, PDF_E_DOC_NEED_LABELOPT, "group", 0, 0, 0);

	    if (pdc_get_optvalues("pagenumber", resopts, &page, NULL))
		pdc_error(p->pdc, PDF_E_DOC_ILL_LABELOPT,
		    "pagenumber", 0, 0, 0);
	    break;

	case PDF_FC_END_DOCUMENT:
	    if (pdc_get_optvalues("group", resopts, NULL, &strlist))
		pdc_error(p->pdc, PDF_E_DOC_ILL_LABELOPT, "group", 0, 0, 0);

	    if (!pdc_get_optvalues("pagenumber", resopts, &page, NULL))
		pdc_error(p->pdc, PDF_E_DOC_NEED_LABELOPT,
		    "pagenumber", 0, 0, 0);

	    break;

	default:
	    if (pdc_get_optvalues("group", resopts, NULL, &strlist))
		pdc_error(p->pdc, PDF_E_DOC_ILL_LABELOPT, "group", 0, 0, 0);

	    if (pdc_get_optvalues("pagenumber", resopts, &page, NULL))
		pdc_error(p->pdc, PDF_E_DOC_ILL_LABELOPT,
		    "pagenumber", 0, 0, 0);

	    page = pageno;
	    break;
    }

    if (pdc_get_optvalues("style", resopts, &inum, NULL))
        style = (pdf_labelstyle) inum;

    htenc = pdf_get_hypertextencoding_opt(p, resopts, &htcp, pdc_true);

    pdf_get_opt_textlist(p, "prefix", resopts, htenc, htcp,
                         pdc_true, NULL, &prefix, NULL);
    pdc_get_optvalues("start", resopts, &start, NULL);

    dp->have_labels = pdc_true;

    if (groupname)
    {
	pg_group *group;

	if ((group = find_group(dp, groupname)) == (pg_group *) 0)
	    pdc_error(p->pdc, PDF_E_DOC_UNKNOWNGROUP, groupname, 0, 0, 0);

	lp = &group->label;
    }
    else
    {
	if (dp->last_page < page)
	    pdc_error(p->pdc, PDF_E_PAGE_NOTEXIST,
		pdc_errprintf(p->pdc, "%d", page), 0, 0, 0);

	lp = &dp->pages[page].label;
    }

    lp->style = style;
    lp->start = start;

    if (prefix)
    {
	if (lp->prefix)
	    pdc_free(p->pdc, lp->prefix);

	lp->prefix = pdc_strdup(p->pdc, prefix);
    }
} /* pdf_set_pagelabel */


static void
write_label(PDF *p, pg_label *label, int pageno)
{
    pdc_printf(p->out, "%d", pageno);
    pdc_begin_dict(p->out);

    if (label->style != label_none)
    {
	pdc_printf(p->out, "/S/%s",
	    pdc_get_keyword(label->style, pdf_labelstyle_pdfkeylist));
    }

    if (label->prefix)
    {
	pdc_printf(p->out, "/P");
	pdf_put_hypertext(p, label->prefix);
    }

    if (label->start != 1)
	pdc_printf(p->out, "/St %d", label->start);

    pdc_end_dict(p->out);
} /* write_label */


pdc_id
pdf_write_pagelabels(PDF *p)
{
    pdf_pages *	dp = p->doc_pages;
    pdc_id	result;
    int		i, k;

    if (!dp->have_labels || dp->last_page == 0)
	return PDC_BAD_ID;

    result = pdc_begin_obj(p->out, PDC_NEW_ID);
    pdc_begin_dict(p->out);

    pdc_printf(p->out, "/Nums");
    pdc_begin_array(p->out);

    /* generate default label if page 1 doesn't have one:
    */
    if (dp->pages[1].label.start == 0 &&
	(dp->n_groups == 0 || dp->groups[0].label.start == 0))
    {
	pdc_puts(p->out, "0");
	pdc_begin_dict(p->out);
	pdc_puts(p->out, "/S/D");	/* 1-based decimal w/o prefix */
	pdc_end_dict(p->out);
    }

    if (dp->n_groups == 0)
    {
	for (i = 1; i <= dp->last_page; ++i)
	    if (dp->pages[i].label.start != 0)
		write_label(p, &dp->pages[i].label, i - 1);
    }
    else
    {
	for (i = 0; i < dp->n_groups; ++i)
	{
	    pg_group *gp = &dp->groups[i];

	    if (gp->label.start != 0 && gp->n_pages != 0)
	    {
		/* if present, the page label beats the group label.
		*/
		if (dp->pages[gp->start].label.start == 0)
		    write_label(p, &gp->label, gp->start - 1);
	    }

	    for (k = gp->start; k < gp->start + gp->n_pages; ++k)
		if (dp->pages[k].label.start != 0)
		    write_label(p, &dp->pages[k].label, k - 1);
	}
    }

    pdc_end_array_c(p->out);
    pdc_end_dict(p->out);
    pdc_end_obj(p->out);

    return result;
} /* pdf_write_pagelabels */

static const pdc_defopt pdf_transgroup_options[] =
{
    {"CS", pdc_keywordlist, PDC_OPT_NONE, 1, 1,
      1.0, PDC_INT_MAX, pdf_tgroup_cs_pdfkeylist},

    {"I", pdc_booleanlist, PDC_OPT_NONE, 1, 1,
      0.0, 0.0, NULL},

    {"K", pdc_booleanlist, PDC_OPT_NONE, 1, 1,
      0.0, 0.0, NULL},

    PDC_OPT_TERMINATE
};

void
pdf_set_transgroup(PDF *p, const char *optlist, pdf_transgroup *tgroup)
{
    pdc_resopt *resopts = NULL;
    int         inum;

    resopts = pdc_parse_optionlist(p->pdc, optlist, pdf_transgroup_options,
                                   NULL, pdc_true);

    if (pdc_get_optvalues("CS", resopts, &inum, NULL))
        tgroup->colorspace = (pdf_colortype) inum;

    pdc_get_optvalues("I", resopts, &tgroup->isolated, NULL);
    pdc_get_optvalues("K", resopts, &tgroup->knockout, NULL);
}

void
pdf_write_transgroup(PDF *p, pdf_transgroup *tgroup)
{
    pdc_puts(p->out, "/Group");
    pdc_begin_dict(p->out);

    pdc_puts(p->out, "/S/Transparency/CS/");

    pdc_printf(p->out, "%s", pdc_get_keyword(tgroup->colorspace,
					     pdf_tgroup_cs_pdfkeylist));

    if (tgroup->isolated)
	pdc_puts(p->out, "/I true");

    if (tgroup->knockout)
	pdc_puts(p->out, "/K true");

    pdc_end_dict(p->out);
}


/************************** utility functions ***************************/

/* get the id of an existing or future page.
** pageno == 0 means current page. note that pdf_get_page_id(0) returns
** PDC_BAD_ID if no page has been opened yet, whereas pdf_current_page_id()
** returns a pre-allocated id for page 1 in this case.
*/
pdc_id
pdf_get_page_id(PDF *p, int pageno)
{
    pdf_pages *dp = p->doc_pages;

    if (pageno == 0)
    {
	return dp->pages[dp->current_page].id;
    }
    else
    {
	while (pageno >= dp->pages_capacity)
	    pdf_grow_pages(p);

	/* preallocate page object id for a later page
	*/
	if (dp->pages[pageno].id == PDC_BAD_ID)
	    dp->pages[pageno].id = pdc_alloc_id(p->out);

	return dp->pages[pageno].id;
    }
} /* pdf_get_page_id */

int
pdf_current_page(PDF *p)
{
    pdf_pages *dp = p->doc_pages;

    return dp ? dp->current_page : 0;
} /* pdf_current_page */

/* get the id of the current page. if there are no pages in the
** document yet, an id will be pre-allocated for page 1.
*/
int
pdf_current_page_id(PDF *p)
{
    pdf_pages *dp = p->doc_pages;

    if (dp->current_page != 0)
	return dp->pages[dp->current_page].id;
    else
	return pdf_get_page_id(p, 1);
} /* pdf_current_page_id */

int
pdf_last_page(PDF *p)
{
    return p->doc_pages->last_page;
} /* pdf_last_page */

int
pdf_search_page_fwd(PDF *p, int start_page, pdc_id id)
{
    pdf_pages *	dp = p->doc_pages;
    int		i;

    for (i = start_page; i <= dp->last_page; ++i)
    {
	if (dp->pages[i].id == id)
	    return i;
    }

    return -1;
} /* pdf_search_page_fwd */

int
pdf_search_page_bwd(PDF *p, int start_page, pdc_id id)
{
    pdf_pages *	dp = p->doc_pages;
    int		i;

    if (start_page == -1)
	start_page = dp->last_page;

    for (i = start_page; i > 0; --i)
    {
	if (dp->pages[i].id == id)
	    return i;
    }

    return -1;
} /* pdf_search_page_bwd */


pdc_scalar
pdf_get_pageheight(PDF *p)
{
    pdf_pages *	dp = p->doc_pages;
    page_obj *	po = &dp->pages[dp->current_page];

    return po->boxes[pdf_mediabox]->ury - po->boxes[pdf_mediabox]->lly;
} /* pdf_get_pageheight */

void
pdf_set_pagebox(
    PDF *	p,
    pdf_pagebox	box,
    pdc_scalar	llx,
    pdc_scalar	lly,
    pdc_scalar	urx,
    pdc_scalar	ury)
{
    static const char fn[] = "pdf_set_pagebox";

    pdf_pages *	dp = p->doc_pages;
    page_obj *	po = &dp->pages[dp->current_page];

    if (po->boxes[box] == (pdc_rectangle *) 0)
    {
	po->boxes[box] = (pdc_rectangle *)
	    pdc_malloc(p->pdc, sizeof (pdc_rectangle), fn);
    }

    pdc_rect_init(po->boxes[box], llx, lly, urx, ury);
} /* pdf_set_pagebox */

void
pdf_set_pagebox_llx(PDF *p, pdf_pagebox box, pdc_scalar llx)
{
    static const char fn[] = "pdf_set_pagebox_llx";

    pdf_pages *	dp = p->doc_pages;
    page_obj *	po = &dp->pages[dp->current_page];

    if (po->boxes[box] == (pdc_rectangle *) 0)
    {
	po->boxes[box] = (pdc_rectangle *)
	    pdc_malloc(p->pdc, sizeof (pdc_rectangle), fn);

	pdc_rect_init(po->boxes[box], 0, 0, 0, 0);
    }

    po->boxes[box]->llx = llx;
} /* pdf_set_pagebox_llx */

void
pdf_set_pagebox_lly(PDF *p, pdf_pagebox box, pdc_scalar lly)
{
    static const char fn[] = "pdf_set_pagebox_lly";

    pdf_pages *	dp = p->doc_pages;
    page_obj *	po = &dp->pages[dp->current_page];

    if (po->boxes[box] == (pdc_rectangle *) 0)
    {
	po->boxes[box] = (pdc_rectangle *)
	    pdc_malloc(p->pdc, sizeof (pdc_rectangle), fn);

	pdc_rect_init(po->boxes[box], 0, 0, 0, 0);
    }

    po->boxes[box]->lly = lly;
} /* pdf_set_pagebox_lly */

void
pdf_set_pagebox_urx(PDF *p, pdf_pagebox box, pdc_scalar urx)
{
    static const char fn[] = "pdf_set_pagebox_urx";

    pdf_pages *	dp = p->doc_pages;
    page_obj *	po = &dp->pages[dp->current_page];

    if (po->boxes[box] == (pdc_rectangle *) 0)
    {
	po->boxes[box] = (pdc_rectangle *)
	    pdc_malloc(p->pdc, sizeof (pdc_rectangle), fn);

	pdc_rect_init(po->boxes[box], 0, 0, 0, 0);
    }

    po->boxes[box]->urx = urx;
} /* pdf_set_pagebox_urx */

void
pdf_set_pagebox_ury(PDF *p, pdf_pagebox box, pdc_scalar ury)
{
    static const char fn[] = "pdf_set_pagebox_ury";

    pdf_pages *	dp = p->doc_pages;
    page_obj *	po = &dp->pages[dp->current_page];

    if (po->boxes[box] == (pdc_rectangle *) 0)
    {
	po->boxes[box] = (pdc_rectangle *)
	    pdc_malloc(p->pdc, sizeof (pdc_rectangle), fn);

	pdc_rect_init(po->boxes[box], 0, 0, 0, 0);
    }

    po->boxes[box]->ury = ury;
} /* pdf_set_pagebox_ury */

const pdc_rectangle *
pdf_get_pagebox(PDF *p, pdf_pagebox box)
{
    pdf_pages *	dp = p->doc_pages;
    page_obj *	po = &dp->pages[dp->current_page];

    if (po->boxes[box])
	return po->boxes[box];
    else
	return &pdf_null_rect;
} /* pdf_get_pagebox */

pdc_vtr *
pdf_get_annots_list(PDF *p)
{
    pdf_pages * dp = p->doc_pages;

    return dp->curr_pg->annots;
} /* pdf_get_annots_list */

void
pdf_set_annots_list(PDF *p, pdc_vtr *annots)
{
    pdf_pages * dp = p->doc_pages;

    if (dp->curr_pg)
	dp->curr_pg->annots = annots;
} /* pdf_set_annots_list */


pdc_id
pdf_get_thumb_id(PDF *p)
{
    pdf_pages *	dp = p->doc_pages;
    page_obj *	po = &dp->pages[dp->current_page];

    return po->thumb_id;
} /* pdf_get_thumb_id */

void
pdf_set_thumb_id(PDF *p, pdc_id id)
{
    pdf_pages * dp = p->doc_pages;
    page_obj *  po = &dp->pages[dp->current_page];

    po->thumb_id = id;
} /* pdf_set_thumb_id */

void
pdf_set_autotgroup(PDF *p, pdc_bool autotgroup)
{
    pdf_pages * dp = p->doc_pages;
    page_obj *  po = &dp->pages[dp->current_page];

    po->autotgroup = autotgroup;
} /* pdf_set_thumb_id */


/************************* contents sections ***************************/

void
pdf_begin_contents_section(PDF *p)
{
    pdf_page *pg = p->doc_pages->curr_pg;

    if (PDF_GET_STATE(p) != pdf_state_page || p->doc_pages->in_csect)
	return;

    p->doc_pages->in_csect = pdc_true;

    if (pg->next_content >= pg->contents_ids_capacity) {
	pg->contents_ids_capacity *= 2;
	pg->contents_ids = (pdc_id *) pdc_realloc(p->pdc, pg->contents_ids,
			    sizeof(pdc_id) * pg->contents_ids_capacity,
			    "pdf_begin_contents_section");
    }

    pg->contents_ids[pg->next_content] = pdc_begin_obj(p->out, PDC_NEW_ID);
    pdc_begin_dict(p->out);
    p->length_id = pdc_alloc_id(p->out);
    pdc_objref(p->out, "/Length", p->length_id);

    if (pdc_get_compresslevel(p->out))
	    pdc_puts(p->out, "/Filter/FlateDecode\n");

    pdc_end_dict(p->out);

    pdc_begin_pdfstream(p->out);

    pg->next_content++;
} /* pdf_begin_contents_section */


void
pdf_end_contents_section(PDF *p)
{
    if (!p->doc_pages->in_csect)
	return;

    p->doc_pages->in_csect = pdc_false;

    pdf_end_text(p);
    pdc_end_pdfstream(p->out);
    pdc_end_obj(p->out);

    pdc_put_pdfstreamlength(p->out, p->length_id);
} /* pdf_end_contents_section */


/************************* page tree generation *************************/

static void
pdf_write_pnode(PDF *p,
	pdc_id node_id,
	pdc_id parent_id,
	page_obj *kids,
	int n_kids,
	int n_pages)
{
    pdc_begin_obj(p->out, node_id);
    pdc_begin_dict(p->out);
    pdc_puts(p->out, "/Type/Pages\n");
    pdc_printf(p->out, "/Count %d\n", n_pages);

    if (parent_id != PDC_BAD_ID)
	pdc_objref(p->out, "/Parent", parent_id);

    pdc_puts(p->out, "/Kids");
    pdc_begin_array(p->out);

    do
    {
	pdc_objref_c(p->out, kids->id);
	++kids;
    } while (--n_kids > 0);

    pdc_end_array_c(p->out);
    pdc_end_dict(p->out);
    pdc_end_obj(p->out);
} /* pdf_write_pnode */

#define N_KIDS	10

static pdc_id
pdf_get_pnode_id(PDF *p)
{
    static const char fn[] = "pdf_get_pnode_id";

    pdf_pages *dp = p->doc_pages;

    if (dp->current_pnode_kids == N_KIDS)
    {
	if (++dp->current_pnode == dp->pnodes_capacity)
	{
	    dp->pnodes_capacity *= 2;
	    dp->pnodes = (pdc_id *) pdc_realloc(p->pdc, dp->pnodes,
			sizeof (pdc_id) * dp->pnodes_capacity, fn);
	}

	dp->pnodes[dp->current_pnode] = pdc_alloc_id(p->out);
	dp->current_pnode_kids = 1;
    }
    else
	++dp->current_pnode_kids;

    return dp->pnodes[dp->current_pnode];
} /* pdf_get_pnode_id */

static pdc_id
write_pages_tree(PDF *p,
    pdc_id parent_id,
    pdc_id *pnodes,
    page_obj *pages,
    int n_pages)
{
    if (n_pages <= N_KIDS)
    {
	/* this is a near-to-leaf node. use the pre-allocated id
	** from dp->pnodes.
	*/
	pdf_write_pnode(p, *pnodes, parent_id, pages, n_pages, n_pages);
	return *pnodes;
    }
    else
    {
	pdc_id node_id = pdc_alloc_id(p->out);
	page_obj kids[N_KIDS];
	int n_kids, rest;
        int tpow = N_KIDS;
	int i;

        /* tpow < n_pages <= tpow*N_KIDS
	*/
        while (tpow * N_KIDS < n_pages)
            tpow *= N_KIDS;

        n_kids = n_pages / tpow;
        rest = n_pages % tpow;

        for (i = 0; i < n_kids; ++i, pnodes += tpow / N_KIDS, pages += tpow)
	{
            kids[i].id = write_pages_tree(p, node_id, pnodes, pages, tpow);
	}

	if (rest)
	{
	    kids[i].id = write_pages_tree(p, node_id, pnodes, pages, rest);
	    ++n_kids;
	}

	pdf_write_pnode(p, node_id, parent_id, kids, n_kids, n_pages);
	return node_id;
    }
} /* write_pages_tree */

static void
pdf_write_box(PDF *p, pdc_rectangle *box, const char *name)
{
    if (!box || pdc_rect_isnull(box))
	return;

    if (box->urx <= box->llx || box->ury <= box->lly)
    {
	pdc_error(p->pdc, PDF_E_PAGE_BADBOX, name,
	    pdc_errprintf(p->pdc, "%f %f %f %f",
	    box->llx, box->lly, box->urx, box->ury), 0, 0);
    }

    pdc_printf(p->out, "/%s[%f %f %f %f]\n",
	name, box->llx, box->lly, box->urx, box->ury);
} /* pdf_write_box */

pdc_id
pdf_write_pages_tree(PDF *p)
{
    int		i;
    pdf_pages *	dp = p->doc_pages;

    for (i = dp->last_page + 1; i < dp->pages_capacity; ++i)
    {
	if (dp->pages[i].id != PDC_BAD_ID)
	{
	    pdc_error(p->pdc, PDF_E_PAGE_ILLREF,
		pdc_errprintf(p->pdc, "%d", i), 0, 0, 0);
	}
    }

    for (i = 1; i <= dp->last_page; ++i)
    {
	page_obj *po = &dp->pages[i];

	pdc_begin_obj(p->out, po->id);
	pdc_begin_dict(p->out);
	pdc_puts(p->out, "/Type/Page\n");
	pdc_objref(p->out, "/Parent", pdf_get_pnode_id(p));

	if (po->annots_id != PDC_BAD_ID)
	    pdc_objref(p->out, "/Annots", po->annots_id);

	if (po->contents_id != PDC_BAD_ID)
	    pdc_objref(p->out, "/Contents", po->contents_id);

	if (po->res_id != PDC_BAD_ID)
	    pdc_objref(p->out, "/Resources", po->res_id);

	if (po->thumb_id != PDC_BAD_ID)
	    pdc_objref(p->out, "/Thumb", po->thumb_id);

        if (po->duration > 0)
            pdc_printf(p->out, "/Dur %f\n", po->duration);

        if (po->taborder != (int) tabo_none)
            pdc_printf(p->out, "/Tabs/%s\n",
                pdc_get_keyword(po->taborder, pdf_taborder_pdfkeylist));

        if (po->userunit != 1.0)
            pdc_printf(p->out, "/UserUnit %f\n", po->userunit);

	if (po->rotate > 0)
	    pdc_printf(p->out, "/Rotate %d\n", po->rotate);

	if (po->action)
	    pdf_write_action_entries(p, event_page, po->act_idlist);




	if (po->transition != trans_none)
	{
	    pdc_puts(p->out, "/Trans");
	    pdc_begin_dict(p->out);
	    pdc_printf(p->out, "/S/%s",
		pdc_get_keyword(po->transition, pdf_transition_pdfkeylist));

	    pdc_end_dict(p->out);
	}

        if (po->tgroup.colorspace == color_none && po->autotgroup)
	    po->tgroup.colorspace = color_rgb;

        if (po->tgroup.colorspace != color_none)
            pdf_write_transgroup(p, &po->tgroup);

	pdf_write_box(p, po->boxes[pdf_artbox],		"ArtBox");
	pdf_write_box(p, po->boxes[pdf_bleedbox],	"BleedBox");
	pdf_write_box(p, po->boxes[pdf_cropbox],	"CropBox");
	pdf_write_box(p, po->boxes[pdf_mediabox],	"MediaBox");
	pdf_write_box(p, po->boxes[pdf_trimbox],	"TrimBox");

	pdc_end_dict(p->out);
	pdc_end_obj(p->out);
    }

    return write_pages_tree(p, PDC_BAD_ID, dp->pnodes, dp->pages + 1,
							    dp->last_page);
} /* pdf_write_pages_tree */


/**************************** resource lists ****************************/

static void
pdf_init_reslist(pdf_reslist *rl)
{
    rl->length = 0;
    rl->capacity = 0;
    rl->list = (int *) 0;
} /* pdf_init_reslist */

void
pdf_add_reslist(PDF *p, pdf_reslist *rl, int num)
{
    static const char fn[] = "pdf_add_reslist";

    if (rl->length == rl->capacity)
    {
        if (rl->capacity == 0)
        {
            rl->capacity = RESLIST_CHUNKSIZE;
            rl->list = (int *)
                pdc_malloc(p->pdc, rl->capacity * sizeof (pdf_reslist), fn);
        }
        else
        {
            rl->capacity *= 2;
            rl->list = (int *) pdc_realloc(p->pdc,
                rl->list, rl->capacity * sizeof (pdf_reslist), fn);
        }
    }

    rl->list[rl->length++] = num;
} /* pdf_add_reslist */


/****************************** begin_page ******************************/

/* begin_page_ext() only:
*/
#define PDF_ICC_FLAG  PDC_OPT_UNSUPP
#define PDF_SPOT_FLAG  PDC_OPT_UNSUPP

#define PDF_METADATA_FLAG  PDC_OPT_UNSUPP

static const pdc_defopt pdf_sepinfo_options[] =
{
    {"pages", pdc_integerlist, PDC_OPT_NONE, 1, 1,
      1.0, PDC_INT_MAX, NULL},

    {"spotname", pdc_stringlist, PDC_OPT_NONE, 1, 1,
      1.0, PDF_MAX_NAMESTRING, NULL},

    {"spotcolor", pdc_colorhandle, PDC_OPT_NONE, 1, 1,
      1.0, PDC_INT_MAX, NULL},

    PDC_OPT_TERMINATE
};

#define PDF_PAGE_OPTIONS1 \
\
    {"topdown", pdc_booleanlist, PDC_OPT_NONE, 1, 1, \
      0.0, 0.0, NULL}, \
\
    {"defaultgray", pdc_iccprofilehandle,  PDF_ICC_FLAG, 1, 1, \
      0.0, PDC_INT_MAX, NULL}, \
\
    {"defaultrgb", pdc_iccprofilehandle,  PDF_ICC_FLAG, 1, 1, \
      0.0, PDC_INT_MAX, NULL}, \
\
    {"defaultcmyk", pdc_iccprofilehandle,  PDF_ICC_FLAG, 1, 1, \
      0.0, PDC_INT_MAX, NULL}, \
\
    {"separationinfo", pdc_stringlist, PDF_SPOT_FLAG, 1, 1, \
      0.0, PDC_USHRT_MAX, NULL}, \

/* begin_page_ext() and resume_page():
*/
#define PDF_PAGE_OPTIONS2 \
\
    {"group", pdc_stringlist,  PDC_OPT_NONE, 1, 1, \
      1.0, PDF_MAX_NAMESTRING, NULL}, \
\
    {"pagenumber", pdc_integerlist,  PDC_OPT_NONE, 1, 1, \
      1.0, PDC_INT_MAX, NULL}, \

/* begin_page_ext() and end_page_ext():
*/
static const pdc_keyconn pdf_pagedim_keylist[] =
{
    { "a0.width",	(int) a0_width		},
    { "a0.height",	(int) a0_height		},
    { "a1.width",	(int) a1_width		},
    { "a1.height",	(int) a1_height		},
    { "a2.width",	(int) a2_width		},
    { "a2.height",	(int) a2_height		},
    { "a3.width",	(int) a3_width		},
    { "a3.height",	(int) a3_height		},
    { "a4.width",	(int) a4_width		},
    { "a4.height",	(int) a4_height		},
    { "a5.width",	(int) a5_width		},
    { "a5.height",	(int) a5_height		},
    { "a6.width",	(int) a6_width		},
    { "a6.height",	(int) a6_height		},
    { "b5.width",	(int) b5_width		},
    { "b5.height",	(int) b5_height		},
    { "letter.width",	(int) letter_width	},
    { "letter.height",	(int) letter_height	},
    { "legal.width",	(int) legal_width 	},
    { "legal.height",	(int) legal_height 	},
    { "ledger.width",	(int) ledger_width	},
    { "ledger.height",	(int) ledger_height	},
    { "11x17.width",	(int) p11x17_width	},
    { "11x17.height",	(int) p11x17_height	},
    { NULL, 0 }
};

typedef enum
{
    pdf_unit_mm = -1000,
    pdf_unit_cm = -100,
    pdf_unit_m  = -1
}
pdf_page_unit;

static const pdc_keyconn pdf_userunit_keylist[] =
{
    { "mm", pdf_unit_mm },
    { "cm", pdf_unit_cm },
    { "m",  pdf_unit_m  },

    { NULL, 0 }
};


#define PDF_PAGE_OPTIONS3 \
\
    {"action", pdc_stringlist,  PDC_OPT_NONE, 1, 1, \
      1.0, PDF_MAX_NAMESTRING, NULL}, \
\
    {"artbox", pdc_scalarlist, PDC_OPT_NONE, 4, 4,\
      PDC_FLOAT_MIN, PDC_FLOAT_MAX, NULL},\
\
    {"bleedbox", pdc_scalarlist, PDC_OPT_NONE, 4, 4,\
      PDC_FLOAT_MIN, PDC_FLOAT_MAX, NULL},\
\
    {"cropbox", pdc_scalarlist, PDC_OPT_NONE, 4, 4,\
      PDC_FLOAT_MIN, PDC_FLOAT_MAX, NULL},\
\
    {"duration", pdc_scalarlist, PDC_OPT_NONE, 1, 1,\
      0.0, PDC_FLOAT_MAX, NULL},\
\
    {"height", pdc_scalarlist, PDC_OPT_NONE, 1, 1,\
      0.0, PDC_FLOAT_MAX, pdf_pagedim_keylist},\
\
    {"label", pdc_stringlist,  PDC_OPT_NONE, 1, 1, \
      0.0, PDC_USHRT_MAX, NULL}, \
\
    {"mediabox", pdc_scalarlist, PDC_OPT_NONE, 4, 4,\
      PDC_FLOAT_MIN, PDC_FLOAT_MAX, NULL},\
\
    {"metadata", pdc_stringlist, PDF_METADATA_FLAG, 1, 1, \
      0.0, PDC_INT_MAX, NULL}, \
\
    {"rotate", pdc_integerlist,  PDC_OPT_NONE, 1, 1, \
      0.0, 270, NULL}, \
\
    {"transition", pdc_keywordlist, PDC_OPT_NONE, 1, 1, \
      0.0, 0.0, pdf_transition_keylist}, \
\
    {"transparencygroup", pdc_stringlist,  PDC_OPT_PDC_1_4, 1, 1, \
      1.0, PDF_MAX_NAMESTRING, NULL}, \
\
    {"taborder", pdc_keywordlist, PDC_OPT_PDC_1_5, 1, 1, \
      0.0, 0.0, pdf_taborder_keylist}, \
\
    {"trimbox", pdc_scalarlist, PDC_OPT_NONE, 4, 4,\
      PDC_FLOAT_MIN, PDC_FLOAT_MAX, NULL},\
\
    {"userunit", pdc_scalarlist, PDC_OPT_PDC_1_6, 1, 1,\
      1.0, 75000, pdf_userunit_keylist},\
\
    {"width", pdc_scalarlist, PDC_OPT_NONE, 1, 1,\
      0.0, PDC_FLOAT_MAX, pdf_pagedim_keylist},\

/* common helper function for pdf__begin_page_ext() and pdf__resume_page().
** returns the target group (if any) and the target page number. the
** page number is relative to the group (if available). page number -1
** means "no page number".
*/
static pg_group *
get_page_options2(PDF *p, pdc_resopt *resopts, int *pageno)
{
    pdf_pages *	dp = p->doc_pages;
    pg_group *	group = (pg_group *) 0;
    char **	strlist;

    *pageno = -1;

    if (pdc_get_optvalues("pagenumber", resopts, pageno, NULL))
    {
	if (*pageno <= 0)
	    pdc_error(p->pdc, PDF_E_PAGE_ILLNUMBER,
		pdc_errprintf(p->pdc, "%d", *pageno), 0, 0, 0);
    }

    if (pdc_get_optvalues("group", resopts, NULL, &strlist))
    {
	if ((group = find_group(dp, strlist[0])) == (pg_group *) 0)
	    pdc_error(p->pdc, PDF_E_DOC_UNKNOWNGROUP, strlist[0], 0, 0, 0);
    }

    if (group)
    {
	if (*pageno > group->n_pages)
	    pdc_error(p->pdc, PDF_E_PAGE_NOTEXIST2,
		pdc_errprintf(p->pdc, "%d", *pageno), group->name, 0, 0);
    }
    else
    {
	if (dp->have_groups)
	    pdc_error(p->pdc, PDF_E_PAGE_NEEDGROUP, 0, 0, 0, 0);

	if (*pageno > dp->last_page)
	    pdc_error(p->pdc, PDF_E_PAGE_NOTEXIST,
		pdc_errprintf(p->pdc, "%d", *pageno), 0, 0, 0);
    }

    return group;
} /* get_page_options2 */

static pdc_rectangle *
pdf_new_box(PDF *p, const pdc_rectangle *box)
{
    static const char fn[] = "pdf_new_box";

    pdc_rectangle *result = (pdc_rectangle *)
	pdc_malloc(p->pdc, sizeof (pdc_rectangle), fn);

    if (box)
	*result = *box;
    else
	pdc_rect_init(result, 0, 0, 0, 0);
    return result;
} /* pdf_new_box */

/* common helper function for pdf__begin_page_ext() and pdf__end_page_ext().
*/
static void
get_page_options3(PDF *p, pdc_resopt *resopts, pdc_bool end_page)
{
    pdf_pages *	dp = p->doc_pages;
    int		pageno = dp->current_page;
    page_obj *	po = &dp->pages[pageno];
    pdc_scalar	width;
    pdc_scalar	height;
    pdc_bool	has_width;
    pdc_bool	has_height;
    pdc_bool	has_mediabox;
    pdc_rectangle box;
    char        **slist;

    if (pdc_get_optvalues("action", resopts, NULL, NULL))
    {
        po->action = (char *) pdc_save_lastopt(resopts, PDC_OPT_SAVE1ELEM);
	pdf_parse_and_write_actionlist(p, event_page, NULL,
                                       (char *) po->action);
    }

    if (pdc_get_optvalues("artbox", resopts, &box, NULL))
	po->boxes[pdf_artbox] = pdf_new_box(p, &box);
    if (pdc_get_optvalues("bleedbox", resopts, &box, NULL))
	po->boxes[pdf_bleedbox] = pdf_new_box(p, &box);
    if (pdc_get_optvalues("cropbox", resopts, &box, NULL))
	po->boxes[pdf_cropbox] = pdf_new_box(p, &box);
    if (pdc_get_optvalues("trimbox", resopts, &box, NULL))
	po->boxes[pdf_trimbox] = pdf_new_box(p, &box);

    pdc_get_optvalues("taborder", resopts, &po->taborder, NULL);
    pdc_get_optvalues("duration", resopts, &po->duration, NULL);
    pdc_get_optvalues("userunit", resopts, &po->userunit, NULL);
    if (po->userunit < 0.0)
        po->userunit = 72.0 / (PDC_INCH2METER * -po->userunit);

    if (pdc_get_optvalues("label", resopts, NULL, NULL))
    {
        char *pagelabel = pdf_get_opt_utf8name(p, "label", resopts);
        pdf_set_pagelabel(p, pagelabel, pageno);
        pdc_free(p->pdc, pagelabel);
    }

    if (pdc_get_optvalues("transparencygroup", resopts, NULL, &slist))
    {
        pdf_set_transgroup(p, slist[0], &po->tgroup);
    }

    /* the "width" and "height" options must be processed BEFORE the
    ** "mediabox" option, since the latter dominates over the formers.
    */
    has_width = pdc_get_optvalues("width", resopts, &width, NULL);
    has_height = pdc_get_optvalues("height", resopts, &height, NULL);

    if (has_width)
	po->boxes[pdf_mediabox]->urx = po->boxes[pdf_mediabox]->llx + width;

    if (has_height)
	po->boxes[pdf_mediabox]->ury = po->boxes[pdf_mediabox]->lly + height;

    has_mediabox =
	pdc_get_optvalues("mediabox", resopts, po->boxes[pdf_mediabox], NULL);

    width = po->boxes[pdf_mediabox]->urx - po->boxes[pdf_mediabox]->llx;
    height = po->boxes[pdf_mediabox]->ury - po->boxes[pdf_mediabox]->lly;

    if (p->ydirection == -1)
    {
	if (end_page)
	{
	    if (has_mediabox || has_width || has_height)
		pdc_error(p->pdc, PDF_E_PAGE_ILLCHGSIZE, 0, 0, 0, 0);
	}
	else
	{
	    if (width == 0 || height == 0)
		pdc_error(p->pdc, PDF_E_PAGE_TOPDOWN_NODIMS, 0, 0, 0, 0);

	    if ((height < PDF_ACRO_MINPAGE || width < PDF_ACRO_MINPAGE ||
		height > PDF_ACRO_MAXPAGE || width > PDF_ACRO_MAXPAGE))
		    pdc_warning(p->pdc, PDF_E_PAGE_SIZE_ACRO, 0, 0, 0, 0);
	}
    }


    pdc_get_optvalues("rotate", resopts, &po->rotate, NULL);
    switch (po->rotate)
    {
	case 0: case 90: case 180: case 270:
	    break;

	default:
	    pdc_error(p->pdc, PDF_E_PAGE_ILLROTATE,
		pdc_errprintf(p->pdc, "%d", po->rotate), 0, 0, 0);
    }

    pdc_get_optvalues("transition", resopts, &po->transition, NULL);
    if (po->transition >= (int) TRANS_1_5 && p->compatibility < PDC_1_5)
	pdc_error(p->pdc, PDF_E_PAGE_TRANS_COMPAT,
	    pdc_get_keyword(po->transition, pdf_transition_keylist), 0, 0, 0);
} /* get_page_options3 */


#if	0
#endif

static const pdc_defopt pdf_begin_page_ext_options[] =
{
    PDF_PAGE_OPTIONS1
    PDF_PAGE_OPTIONS2
    PDF_PAGE_OPTIONS3

    PDC_OPT_TERMINATE
};


void
pdf__begin_page_ext(
    PDF *	p,
    pdc_scalar	width,
    pdc_scalar	height,
    const char *optlist)
{
    static const char fn[] = "pdf__begin_page_ext";

    pdf_pages *	dp = p->doc_pages;
    pdf_page *	pg;
    page_obj *	po;


    pdc_resopt *resopts = NULL;
    pg_group *	group = (pg_group *) 0;
    int		pageno = -1;

    pdc_check_number_limits(p->pdc, "width", width, 0.0, PDC_FLOAT_MAX);
    pdc_check_number_limits(p->pdc, "height", height, 0.0, PDC_FLOAT_MAX);

    if (optlist && *optlist)
    {
	pdc_clientdata cdata;

        pdf_set_clientdata(p, &cdata);
	resopts = pdc_parse_optionlist(p->pdc,
	    optlist, pdf_begin_page_ext_options, &cdata, pdc_true);

	group = get_page_options2(p, resopts, &pageno);
    }

    if (group)
    {
	if (pageno == -1)
	    pageno = group->start + group->n_pages;
	else
	    pageno = group->start + pageno - 1;

	if (++group->n_pages > group->capacity)
	{
	    grow_group(p, group, pageno, 1);
	}
	else if (pageno < group->start + group->n_pages - 1)
	{
	    memmove(&dp->pages[pageno + 1], &dp->pages[pageno],
		(group->start + group->n_pages - pageno) * sizeof (page_obj));

	    pdf_init_page_obj(&dp->pages[pageno]);
	}

	if (dp->last_page < group->start + group->n_pages - 1)
	    dp->last_page = group->start + group->n_pages - 1;
    }
    else
    {
	if (dp->last_page + 1 >= dp->pages_capacity)
	    pdf_grow_pages(p);

	++dp->last_page;

	if (dp->last_page > dp->max_page)
	    ++dp->max_page;

	if (pageno == -1)
	    pageno = dp->last_page;

	if (pageno != dp->last_page)
	{
	    memmove(&dp->pages[pageno + 1], &dp->pages[pageno],
			(dp->max_page - pageno) * sizeof (page_obj));

	    pdf_init_page_obj(&dp->pages[pageno]);
	}
    }

    po = &dp->pages[pageno];
    dp->current_page = pageno;

    /* no id has been preallocated */
    if (po->id == PDC_BAD_ID)
	po->id = pdc_alloc_id(p->out);

    pg = dp->curr_pg = (pdf_page *) pdc_malloc(p->pdc, sizeof (pdf_page), fn);
    p->curr_ppt = &pg->ppt;
    pdf_init_ppt(p);

    pg->contents_ids = (pdc_id *) 0;
    pg->annots = (pdc_vtr *) 0;

    /* save and take over global parameters.
    */
    pg->ydir =		dp->old_ydir =		p->ydirection;

    pg->rl_colorspaces.list	= (int *) 0;
    pg->rl_extgstates.list	= (int *) 0;
    pg->rl_fonts.list		= (int *) 0;
    pg->rl_layers.list		= (int *) 0;
    pg->rl_patterns.list	= (int *) 0;
    pg->rl_shadings.list	= (int *) 0;
    pg->rl_xobjects.list	= (int *) 0;

    pg->contents_ids_capacity = CONTENTS_CHUNKSIZE;
    pg->contents_ids = (pdc_id *) pdc_malloc(p->pdc,
	    sizeof(pdc_id) * pg->contents_ids_capacity, fn);

    /* might be overwritten by options */
    po->boxes[pdf_mediabox] = pdf_new_box(p, 0);
    pdc_rect_init(po->boxes[pdf_mediabox], 0, 0, width, height);

    if (resopts)
    {
	pdc_bool topdown = pdc_false;

        if (pdc_get_optvalues("topdown", resopts, &topdown, NULL))
	    p->ydirection = pg->ydir = topdown ? -1 : 1;


	get_page_options3(p, resopts, pdc_false);
    }

    /* initialize the current ppt descriptor. p->ydirection
    ** must be set before pdf_init_ppt()!
    */
    pdf_init_ppt_states(p);

    pg->next_content = 0;

    pdf_init_reslist(&pg->rl_colorspaces);
    pdf_init_reslist(&pg->rl_extgstates);
    pdf_init_reslist(&pg->rl_fonts);
    pdf_init_reslist(&pg->rl_layers);
    pdf_init_reslist(&pg->rl_patterns);
    pdf_init_reslist(&pg->rl_shadings);
    pdf_init_reslist(&pg->rl_xobjects);

    PDF_SET_STATE(p, pdf_state_page);

    pdf_begin_contents_section(p);

    /* top-down y coordinates */
    pdf_set_topdownsystem(p, pdf_get_pageheight(p));

    /* set color differing from PDF default */
    pdf_set_default_color(p, pdc_false);
#if	0
#endif


    if (!p->pdc->smokerun)
        pdc_logg_cond(p->pdc, 1, trc_api, "[Begin page #%d]\n",
                      dp->current_page);

} /* pdf__begin_page_ext */


void
pdf__begin_page(
    PDF *	p,
    pdc_scalar	width,
    pdc_scalar	height)
{
    if (p->doc_pages->have_groups)
	pdc_error(p->pdc, PDF_E_PAGE_NEEDGROUP2, 0, 0, 0, 0);

    pdf__begin_page_ext(p, width, height, 0);
} /* pdf__begin_page */


/*************************** suspend & resume ***************************/

void
pdf_pg_suspend(PDF *p)
{
    pdf_pages *dp = p->doc_pages;

    if (PDF_GET_STATE(p) != pdf_state_page)
    {
	dp->last_suspended = -1;
    }
    else
    {
	pdf_page *pg = dp->curr_pg;


	pdf_end_contents_section(p);

	/* restore global parms.
	*/
	p->ydirection =		dp->old_ydir;

	pdf_get_page_colorspaces(p, &pg->rl_colorspaces);
	pdf_get_page_extgstates(p, &pg->rl_extgstates);
	pdf_get_page_fonts(p, &pg->rl_fonts);
	pdf_get_page_patterns(p, &pg->rl_patterns);
	pdf_get_page_shadings(p, &pg->rl_shadings);
	pdf_get_page_xobjects(p, &pg->rl_xobjects);

	dp->pages[dp->current_page].pg = pg;
	dp->curr_pg = (pdf_page *) 0;
	dp->last_suspended = dp->current_page;

	/* restore the default ppt for out-of-page usage.
	*/
	p->curr_ppt = &dp->default_ppt;
    }

    pdf_init_ppt_states(p);
} /* pdf_pg_suspend */


static const pdc_defopt pdf_suspend_page_options[] =
{
    PDC_OPT_TERMINATE
};

void
pdf__suspend_page(PDF *p, const char *optlist)
{
    if (optlist && *optlist)
    {
	pdc_resopt *resopts = pdc_parse_optionlist(p->pdc,
	    optlist, pdf_suspend_page_options, NULL, pdc_true);

	(void) resopts;
    }

    pdf_pg_suspend(p);
    PDF_SET_STATE(p, pdf_state_document);

    if (!p->pdc->smokerun)
        pdc_logg_cond(p->pdc, 1, trc_api, "[Suspend page #%d]\n",
                      p->doc_pages->current_page);
} /* pdf__suspend_page */


void
pdf_pg_resume(PDF *p, int pageno)
{
    pdf_pages *dp = p->doc_pages;

    pdf_reset_ppt(p->curr_ppt);

    if (pageno == -1)
    {
	pageno = dp->last_suspended;
	dp->last_suspended = -1;
    }

    if (pageno == -1)
    {
	PDF_SET_STATE(p, pdf_state_document);
    }
    else
    {
	pdf_page *pg;
	int i;

	/* prevent error cleanup from killing the same page twice.
	*/
	pg = dp->curr_pg	= dp->pages[pageno].pg;
	dp->pages[pageno].pg	= (pdf_page *) 0;

	dp->current_page	= pageno;
	p->curr_ppt		= &pg->ppt;

	PDF_SET_STATE(p, pdf_state_page);

	/* save global parameters and replace them
	** with the page specific ones.
	*/
	dp->old_ydir =		p->ydirection;

	p->ydirection =		pg->ydir;

	pdf_begin_contents_section(p);

	/* mark global resources as "used on current page".
	*/
	for (i = 0; i < pg->rl_colorspaces.length; ++i)
	    pdf_mark_page_colorspace(p, pg->rl_colorspaces.list[i]);

	for (i = 0; i < pg->rl_extgstates.length; ++i)
	    pdf_mark_page_extgstate(p, pg->rl_extgstates.list[i]);

	for (i = 0; i < pg->rl_fonts.length; ++i)
	    pdf_mark_page_font(p, pg->rl_fonts.list[i]);


	for (i = 0; i < pg->rl_patterns.length; ++i)
	    pdf_mark_page_pattern(p, pg->rl_patterns.list[i]);

	for (i = 0; i < pg->rl_shadings.length; ++i)
	    pdf_mark_page_shading(p, pg->rl_shadings.list[i]);

	for (i = 0; i < pg->rl_xobjects.length; ++i)
	    pdf_mark_page_xobject(p, pg->rl_xobjects.list[i]);
    }
} /* pdf_pg_resume */


static const pdc_defopt pdf_resume_page_options[] =
{
    PDF_PAGE_OPTIONS2

    PDC_OPT_TERMINATE
};

void
pdf__resume_page(PDF *p, const char *optlist)
{
    pdf_pages *	dp = p->doc_pages;
    pg_group *	group = (pg_group *) 0;
    int		pageno = -1;		/* logical page number */
    int		physno;			/* physical page number */

    if (optlist && *optlist)
    {
	pdc_resopt *resopts = pdc_parse_optionlist(p->pdc,
	    optlist, pdf_resume_page_options, NULL, pdc_true);

	group = get_page_options2(p, resopts, &pageno);
    }

    if (group)
    {
	if (pageno == -1)
	    pageno = group->n_pages;

	physno = group->start + pageno - 1;
    }
    else
    {
	if (pageno == -1)
	    pageno = dp->last_page;

	physno = pageno;
    }

    if (dp->pages[physno].pg == (pdf_page *) 0)
    {
	if (group)
	{
	    pdc_error(p->pdc, PDF_E_PAGE_NOSUSPEND2,
		pdc_errprintf(p->pdc, "%d", pageno), group->name, 0, 0);
	}
	else
	{
	    pdc_error(p->pdc, PDF_E_PAGE_NOSUSPEND,
		pdc_errprintf(p->pdc, "%d", pageno), 0, 0, 0);
	}
    }

    pdf_pg_resume(p, physno);

    if (!p->pdc->smokerun)
        pdc_logg_cond(p->pdc, 1, trc_api, "[Resume page #%d]\n", physno);

} /* pdf__resume_page */


/******************************* end_page *******************************/


static const pdc_defopt pdf_end_page_ext_options[] =
{
    PDF_PAGE_OPTIONS3

    PDC_OPT_TERMINATE
};

void
pdf__end_page_ext(PDF *p, const char *optlist)
{
    static const char fn[] = "pdf__end_page_ext";

    pdf_pages *	dp = p->doc_pages;
    page_obj *	po = &dp->pages[dp->current_page];

    pdc_scalar	width;
    pdc_scalar	height;
    pdf_page *	pg;
    pdf_ppt *	ppt = p->curr_ppt;
    int		i;


    if (optlist && *optlist)
    {
	pdc_resopt *resopts = pdc_parse_optionlist(p->pdc,
	    optlist, pdf_end_page_ext_options, NULL, pdc_true);

	get_page_options3(p, resopts, pdc_true);
    }

    width = po->boxes[pdf_mediabox]->urx - po->boxes[pdf_mediabox]->llx;
    height = po->boxes[pdf_mediabox]->ury - po->boxes[pdf_mediabox]->lly;

    if (width == 0 || height == 0)
	pdc_error(p->pdc, PDF_E_PAGE_NODIMS, 0, 0, 0, 0);

    if ((height < PDF_ACRO_MINPAGE || width < PDF_ACRO_MINPAGE ||
	height > PDF_ACRO_MAXPAGE || width > PDF_ACRO_MAXPAGE))
	    pdc_warning(p->pdc, PDF_E_PAGE_SIZE_ACRO, 0, 0, 0, 0);




    /* check whether PDF_save() and PDF_restore() calls are balanced */
    if (ppt->sl > 0)
	pdc_error(p->pdc, PDF_E_GSTATE_UNMATCHEDSAVE, 0, 0, 0, 0);

    /* TODO (york): avoid memory leak in error case. */
    pg = dp->curr_pg;
#if	0
#endif


    pdf_end_contents_section(p);


    /* if no "duration" or "transition" options have been specified
    ** for this page, fall back on the (deprecated) global parameters.
    */
    if (po->duration == -1)
	po->duration = dp->duration;

    if (po->transition == -1)
	po->transition = dp->transition;

    if (pg->next_content > 0)
    {
	if (pg->next_content == 1)
	{
	    po->contents_id = pg->contents_ids[0];
	}
	else
	{
	    po->contents_id = pdc_begin_obj(p->out, PDC_NEW_ID);
	    pdc_begin_array(p->out);

	    for (i = 0; i < pg->next_content; ++i)
	    {
		pdc_objref_c(p->out, pg->contents_ids[i]);
	    }

	    pdc_end_array(p->out);
	    pdc_end_obj(p->out);
	}
    }

    if (po->action)
    {
	po->act_idlist = (pdc_id *)
	    pdc_malloc(p->pdc, PDF_MAX_EVENTS * sizeof (pdc_id), fn);

	pdf_parse_and_write_actionlist(p, event_page, po->act_idlist,
							    po->action);
    }

    po->annots_id = pdf_write_annots_root(p, pg->annots, NULL);

    /* resources dictionary
    */
    po->res_id = pdc_begin_obj(p->out, PDC_NEW_ID);

    pdc_begin_dict(p->out);

    pdf_write_page_fonts(p);			/* Font resources */

    pdf_write_page_colorspaces(p);		/* ColorSpace resources */

    pdf_write_page_pattern(p);			/* Pattern resources */

    pdf_write_page_shadings(p);			/* Shading resources */

    pdf_write_xobjects(p);			/* XObject resources */

    pdf_write_page_extgstates(p);		/* ExtGState resources */


    pdc_end_dict(p->out);
    pdc_end_obj(p->out);

    if (pg->annots != (pdc_vtr *) 0)
        pdf_write_page_annots(p, pg->annots);	/* Annotation dicts */


    /* restore global parms.
    */
    p->ydirection =	dp->old_ydir;

    /* restore the default ppt for out-of-page usage.
    */
    p->curr_ppt = &dp->default_ppt;
    pdf_init_ppt_states(p);
    PDF_SET_STATE(p, pdf_state_document);
    pdf_delete_page(p, pg);
    dp->curr_pg = (pdf_page *) 0;

    if (p->flush & (pdc_flush_page | pdc_flush_content))
	pdc_flush_stream(p->out);

    if (!p->pdc->smokerun)
        pdc_logg_cond(p->pdc, 1, trc_api, "[End page #%d]\n",
                      dp->current_page);

} /* pdf__end_page_ext */


/*****************************************************************************/
/**                deprecated historical page functions                     **/
/*****************************************************************************/

/* set page display duration for current and future pages */

void
pdf_set_duration(PDF *p, double t)
{
    p->doc_pages->duration = t;
}

/* set transition mode for current and future pages */

void
pdf_set_transition(PDF *p, const char *transition)
{
    int i;

    if (transition == NULL || !*transition)
        transition = "none";

    i = pdc_get_keycode_ci(transition, pdf_transition_keylist);

    if (i == PDC_KEY_NOTFOUND)
        pdc_error(p->pdc, PDC_E_PAR_ILLPARAM, transition, "transition", 0, 0);

    if (i >= (int) TRANS_1_5 && p->compatibility < PDC_1_5)
	pdc_error(p->pdc, PDF_E_PAGE_TRANS_COMPAT,
	    pdc_get_keyword(i, pdf_transition_keylist), 0, 0, 0);

    p->doc_pages->transition = i;
}


