diff options
author | Thomas Deutschmann <whissi@gentoo.org> | 2019-10-15 12:24:12 +0200 |
---|---|---|
committer | Thomas Deutschmann <whissi@gentoo.org> | 2020-08-13 11:26:55 +0200 |
commit | e088156d5b620e5e639580dacf85c6dc13823c74 (patch) | |
tree | 57f5c025e203279944da512166c20bc0521d8ccd /devices/vector/gdevpdfm.c | |
download | ghostscript-gpl-patches-e088156d5b620e5e639580dacf85c6dc13823c74.tar.gz ghostscript-gpl-patches-e088156d5b620e5e639580dacf85c6dc13823c74.tar.bz2 ghostscript-gpl-patches-e088156d5b620e5e639580dacf85c6dc13823c74.zip |
Import Ghostscript 9.50ghostscript-9.50
Signed-off-by: Thomas Deutschmann <whissi@gentoo.org>
Diffstat (limited to 'devices/vector/gdevpdfm.c')
-rw-r--r-- | devices/vector/gdevpdfm.c | 2900 |
1 files changed, 2900 insertions, 0 deletions
diff --git a/devices/vector/gdevpdfm.c b/devices/vector/gdevpdfm.c new file mode 100644 index 00000000..6e018aa4 --- /dev/null +++ b/devices/vector/gdevpdfm.c @@ -0,0 +1,2900 @@ +/* Copyright (C) 2001-2019 Artifex Software, Inc. + All Rights Reserved. + + This software is provided AS-IS with no warranty, either express or + implied. + + This software is distributed under license and may not be copied, + modified or distributed except as expressly authorized under the terms + of the license contained in the file LICENSE in this distribution. + + Refer to licensing information at http://www.artifex.com or contact + Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato, + CA 94945, U.S.A., +1(415)492-9861, for further information. +*/ + + +/* pdfmark processing for PDF-writing driver */ +#include "math_.h" +#include "memory_.h" +#include "gx.h" +#include "gserrors.h" +#include "gsutil.h" /* for bytes_compare */ +#include "gdevpdfx.h" +#include "gdevpdfo.h" +#include "szlibx.h" +#include "slzwx.h" + +/* GC descriptors */ +private_st_pdf_article(); + +/* + * The pdfmark pseudo-parameter indicates the occurrence of a pdfmark + * operator in the input file. Its "value" is the arguments of the operator, + * passed through essentially unchanged: + * (key, value)*, CTM, type + */ + +/* + * Define an entry in a table of pdfmark-processing procedures. + * (The actual table is at the end of this file, to avoid the need for + * forward declarations for the procedures.) + */ +#define PDFMARK_NAMEABLE 1 /* allows _objdef */ +#define PDFMARK_ODD_OK 2 /* OK if odd # of parameters */ +#define PDFMARK_KEEP_NAME 4 /* don't substitute reference for name */ + /* in 1st argument */ +#define PDFMARK_NO_REFS 8 /* don't substitute references for names */ + /* anywhere */ +#define PDFMARK_TRUECTM 16 /* pass the true CTM to the procedure, */ + /* not the one transformed to reflect the default user space */ +typedef struct pdfmark_name_s { + const char *mname; + pdfmark_proc((*proc)); + byte options; +} pdfmark_name; + +/* ---------------- Public utilities ---------------- */ + +/* Compare a C string and a gs_param_string. */ +bool +pdf_key_eq(const gs_param_string * pcs, const char *str) +{ + return (strlen(str) == pcs->size && pcs->data && + !strncmp(str, (const char *)pcs->data, pcs->size)); +} + +/* Scan an integer out of a parameter string. */ +int +pdfmark_scan_int(const gs_param_string * pstr, int *pvalue) +{ +#define MAX_INT_STR 20 + uint size = pstr->size; + char str[MAX_INT_STR + 1]; + + if (size > MAX_INT_STR) + return_error(gs_error_limitcheck); + memcpy(str, pstr->data, size); + str[size] = 0; + return (sscanf(str, "%d", pvalue) == 1 ? 0 : + gs_note_error(gs_error_rangecheck)); +#undef MAX_INT_STR +} + +/* ---------------- Private utilities ---------------- */ + +/* Find a key in a dictionary. */ +static bool +pdfmark_find_key(const char *key, const gs_param_string * pairs, uint count, + gs_param_string * pstr) +{ + uint i; + + for (i = 0; i < count; i += 2) + if (pdf_key_eq(&pairs[i], key)) { + *pstr = pairs[i + 1]; + return true; + } + pstr->data = 0; + pstr->size = 0; + return false; +} + +/* + * Get the page number for a page referenced by number or as /Next or /Prev. + * The result may be 0 if the page number is 0 or invalid. + */ +static int +pdfmark_page_number(gx_device_pdf * pdev, const gs_param_string * pnstr) +{ + int page = pdev->next_page + 1; + + if (pnstr->data == 0); + else if (pdf_key_eq(pnstr, "/Next")) + ++page; + else if (pdf_key_eq(pnstr, "/Prev")) + --page; + else if (pdfmark_scan_int(pnstr, &page) < 0) + page = 0; + return page; +} + +/* + * This routine checks the page number is inside the valid range FirstPage->LastPage + * and if it is, and FirstPage is not 0, it updates the page number by rebasing it to + * the new FirstPage. If all conditions are met we also update the 'max_referred_page' + * which we check during closedown, and emit a warning if a reference should somehow + * point outside the valid range of pages in the document. This can happen if we reference + * a destination page that we haven't created yet, and when we get to the end of the + * job we discover we never did create it. + */ +static int +update_max_page_reference(gx_device_pdf * pdev, int *page) +{ + if (*page < pdev->FirstPage || (pdev->LastPage != 0 && *page > pdev->LastPage)) { + emprintf1(pdev->memory, "Destination page %d lies outside the valid page range.\n", *page); + return -1; + } + else { + if (pdev->FirstPage != 0) + *page = (*page - pdev->FirstPage) + 1; + + if (pdev->max_referred_page < *page) + pdev->max_referred_page = *page; + } + return 0; +} + +/* Construct a destination string specified by /Page and/or /View. */ +/* Return 0 if none (but still fill in a default), 1 or 2 if present */ +/* (1 if only one of /Page or /View, 2 if both), <0 if error. */ +static int +pdfmark_make_dest(char dstr[MAX_DEST_STRING], gx_device_pdf * pdev, + const char *Page_key, const char *View_key, + const gs_param_string * pairs, uint count, uint RequirePage) +{ + gs_param_string page_string, view_string; + int present = + pdfmark_find_key(Page_key, pairs, count, &page_string) + + pdfmark_find_key(View_key, pairs, count, &view_string); + int page=0; + gs_param_string action; + int len, code = 0; + + if (present || RequirePage) + page = pdfmark_page_number(pdev, &page_string); + + if (view_string.size == 0) + param_string_from_string(view_string, "[/XYZ null null null]"); + if (page == 0) + strcpy(dstr, "[null "); + else if (pdfmark_find_key("/Action", pairs, count, &action) && + pdf_key_eq(&action, "/GoToR") + ) + gs_sprintf(dstr, "[%d ", page - 1); + else { + code = update_max_page_reference(pdev, &page); + if (code < 0) + return code; + gs_sprintf(dstr, "[%ld 0 R ", pdf_page_id(pdev, page)); + } + len = strlen(dstr); + if (len + view_string.size > MAX_DEST_STRING) + return_error(gs_error_limitcheck); + if (view_string.data[0] != '[' || + view_string.data[view_string.size - 1] != ']' + ) + return_error(gs_error_rangecheck); + memcpy(dstr + len, view_string.data + 1, view_string.size - 1); + dstr[len + view_string.size - 1] = 0; + return present; +} + +/* + * If a named destination is specified by a string, convert it to a name, + * update *dstr, and return 1; otherwise return 0. + */ +static int +pdfmark_coerce_dest(gs_param_string *dstr, char dest[MAX_DEST_STRING]) +{ + const byte *data = dstr->data; + uint size = dstr->size; + if (size == 0 || data[0] != '(') + return 0; + /****** HANDLE ESCAPES ******/ + memcpy(dest, data, size - 1); + dest[0] = '/'; + dest[size - 1] = 0; + dstr->data = (byte *)dest; + dstr->size = size - 1; + return 1; +} + +/* Put pairs in a dictionary. */ +static int +pdfmark_put_c_pair(cos_dict_t *pcd, const char *key, + const gs_param_string * pvalue) +{ + return cos_dict_put_c_key_string(pcd, key, pvalue->data, pvalue->size); +} +static int +pdfmark_put_pair(cos_dict_t *pcd, const gs_param_string * pair) +{ + return cos_dict_put_string(pcd, pair->data, pair->size, + pair[1].data, pair[1].size); +} + +/* Scan a Rect value. */ +static int +pdfmark_scan_rect(gs_rect * prect, const gs_param_string * str, + const gs_matrix * pctm) +{ + uint size = str->size; + double v[4]; +#define MAX_RECT_STRING 100 + char chars[MAX_RECT_STRING + 3]; + int end_check; + + if (str->size > MAX_RECT_STRING) + return_error(gs_error_limitcheck); + memcpy(chars, str->data, size); + strcpy(chars + size, " 0"); + if (sscanf(chars, "[%lg %lg %lg %lg]%d", + &v[0], &v[1], &v[2], &v[3], &end_check) != 5 + ) + return_error(gs_error_rangecheck); + gs_point_transform(v[0], v[1], pctm, &prect->p); + gs_point_transform(v[2], v[3], pctm, &prect->q); + return 0; +} + +/* Make a Rect value. */ +static void +pdfmark_make_rect(char str[MAX_RECT_STRING], const gs_rect * prect) +{ + /* + * We have to use a stream and pprintf, rather than sprintf, + * because printf formats can't express the PDF restrictions on + * the form of the output. + */ + stream s; + + s_init(&s, NULL); + swrite_string(&s, (byte *)str, MAX_RECT_STRING - 1); + pprintg4(&s, "[%g %g %g %g]", + prect->p.x, prect->p.y, prect->q.x, prect->q.y); + str[stell(&s)] = 0; +} + +#define MAX_BORDER_STRING 100 +/* Write a transformed Border value on a stream. */ +static int +pdfmark_write_border(stream *s, const gs_param_string *str, + const gs_matrix *pctm) +{ + int i; + + for (i = 0; i < str->size; i++) + stream_putc(s, str->data[i]); + return 0; +} + +/* Put an element in a stream's dictionary. */ +static int +cos_stream_put_c_strings(cos_stream_t *pcs, const char *key, const char *value) +{ + return cos_dict_put_c_strings(cos_stream_dict(pcs), key, value); +} + +static int +setup_pdfmark_stream_no_compression(gx_device_psdf *pdev0, + cos_stream_t *pco) +{ + /* This function is for pdfwrite only. */ + gx_device_pdf *pdev = (gx_device_pdf *)pdev0; + gs_memory_t *mem = pdev->pdf_memory; + + pco->input_strm = cos_write_stream_alloc(pco, pdev, + "setup_pdfmark_stream_compression"); + if (pco->input_strm == 0) + return_error(gs_error_VMerror); + if (!pdev->binary_ok) { + stream_state *ss = s_alloc_state(mem, s_A85E_template.stype, + "setup_pdfmark_stream_compression"); + if (ss == 0) + return_error(gs_error_VMerror); + if (s_add_filter(&pco->input_strm, &s_A85E_template, ss, mem) == 0) { + gs_free_object(mem, ss, "setup_image_compression"); + return_error(gs_error_VMerror); + } + } + return 0; +} + +/* Setup pdfmak stream compression. */ +static int +setup_pdfmark_stream_compression(gx_device_psdf *pdev0, + cos_stream_t *pco) +{ + /* This function is for pdfwrite only. */ + gx_device_pdf *pdev = (gx_device_pdf *)pdev0; + gs_memory_t *mem = pdev->pdf_memory; + static const pdf_filter_names_t fnames = { + PDF_FILTER_NAMES + }; + const stream_template *templat = + (pdev->params.UseFlateCompression && + pdev->version >= psdf_version_ll3 ? + &s_zlibE_template : &s_LZWE_template); + stream_state *st; + + pco->input_strm = cos_write_stream_alloc(pco, pdev, + "setup_pdfmark_stream_compression"); + if (pco->input_strm == 0) + return_error(gs_error_VMerror); + if (!pdev->binary_ok) { + stream_state *ss = s_alloc_state(mem, s_A85E_template.stype, + "setup_pdfmark_stream_compression"); + if (ss == 0) + return_error(gs_error_VMerror); + if (s_add_filter(&pco->input_strm, &s_A85E_template, ss, mem) == 0) { + gs_free_object(mem, ss, "setup_image_compression"); + return_error(gs_error_VMerror); + } + } + st = s_alloc_state(mem, templat->stype, + "setup_pdfmark_stream_compression"); + if (st == 0) + return_error(gs_error_VMerror); + if (templat->set_defaults) + (*templat->set_defaults) (st); + if (s_add_filter(&pco->input_strm, templat, st, mem) == 0) { + gs_free_object(mem, st, "setup_image_compression"); + return_error(gs_error_VMerror); + } + return pdf_put_filters(cos_stream_dict(pco), pdev, pco->input_strm, &fnames); +} + +static int +pdfmark_bind_named_object(gx_device_pdf *pdev, const gs_const_string *objname, + pdf_resource_t **pres) +{ + int code; + + if (objname != NULL && objname->size) { + const cos_value_t *v = cos_dict_find(pdev->local_named_objects, objname->data, objname->size); + + if (v != NULL) { + if (v->value_type == COS_VALUE_OBJECT) { + if (cos_type(v->contents.object) == &cos_generic_procs) { + /* The object was referred but not defined. + Use the old object id. + The old object stub to be dropped. */ + pdf_reserve_object_id(pdev, *pres, v->contents.object->id); + } else if (!v->contents.object->written) { + /* We can't know whether the old object was referred or not. + Write it out for a consistent result in any case. */ + code = cos_write_object(v->contents.object, pdev, resourceOther); + + if (code < 0) + return code; + v->contents.object->written = true; + } + } else + return_error(gs_error_rangecheck); /* Must not happen. */ + } + } + if ((*pres)->object->id == -1) { + if(objname != NULL && objname->size) + code = pdf_substitute_resource(pdev, pres, resourceXObject, NULL, false); + else + code = pdf_substitute_resource(pdev, pres, resourceXObject, NULL, true); + (*pres)->where_used |= pdev->used_mask; + if (code < 0) + return code; + } else { + /* Unfortunately we can't apply pdf_substitute_resource, + because the object may already be referred by its id. + Redundant objects may happen in this case. + For better results users should define objects before usage. + */ + } + if (objname != NULL && objname->size) { + cos_value_t value; + + code = cos_dict_put(pdev->local_named_objects, objname->data, + objname->size, cos_object_value(&value, (cos_object_t *)(*pres)->object)); + if (code < 0) + return code; + } + return 0; +} + +/* ---------------- Miscellaneous pdfmarks ---------------- */ + +/* + * Create the dictionary for an annotation or outline. For some + * unfathomable reason, PDF requires the following key substitutions + * relative to pdfmarks: + * In annotation and link dictionaries: + * /Action => /A, /Color => /C, /Title => /T + * In outline directionaries: + * /Action => /A, but *not* /Color or /Title + * In Action subdictionaries: + * /Dest => /D, /File => /F, /Subtype => /S + * and also the following substitutions: + * /Action /Launch /File xxx => + * /A << /S /Launch /F xxx >> + * /Action /GoToR /File xxx /Dest yyy => + * /A << /S /GoToR /F xxx /D yyy' >> + * /Action /Article /Dest yyy => + * /A << /S /Thread /D yyy' >> + * /Action /GoTo => drop the Action key + * Also, \n in Contents strings must be replaced with \r. + * Also, an outline dictionary with no action, Dest, Page, or View has an + * implied GoTo action with Dest = [{ThisPage} /XYZ null null null]. + * Note that for Thread actions, the Dest is not a real destination, + * and must not be processed as one. + * + * We always treat /A and /F as equivalent to /Action and /File + * respectively. The pdfmark and PDF documentation is so confused on the + * issue of when the long and short names should be used that we only give + * this a 50-50 chance of being right. + * + * Note that we must transform Rect and Border coordinates. + */ + +typedef struct ao_params_s { + gx_device_pdf *pdev; /* for pdfmark_make_dest */ + const char *subtype; /* default Subtype in top-level dictionary */ + long src_pg; /* set to SrcPg - 1 if any */ +} ao_params_t; +static int +pdfmark_put_ao_pairs(gx_device_pdf * pdev, cos_dict_t *pcd, + const gs_param_string * pairs, uint count, + const gs_matrix * pctm, ao_params_t * params, + bool for_outline) +{ + const gs_param_string *Action = 0; + const gs_param_string *File = 0; + const gs_param_string *URI = 0; + gs_param_string Dest; + gs_param_string Subtype; + uint i; + int code; + char dest[MAX_DEST_STRING]; + bool coerce_dest = false; + + Subtype.data = 0; + Subtype.size = 0; + Dest.data = 0; + Dest.size = 0; + if (params->subtype) + param_string_from_string(Subtype, params->subtype); + else + Subtype.data = 0; + for (i = 0; i < count; i += 2) { + const gs_param_string *pair = &pairs[i]; + long src_pg; + + if (pdf_key_eq(pair, "/SrcPg")){ + unsigned char *buf0 = (unsigned char *)gs_alloc_bytes(pdev->memory, (pair[1].size + 1) * sizeof(unsigned char), + "pdf_xmp_write_translated"); + memcpy(buf0, pair[1].data, pair[1].size); + buf0[pair[1].size] = 0x00; + if (sscanf((char *)buf0, "%ld", &src_pg) == 1) + params->src_pg = src_pg - 1; + gs_free_object(pdev->memory, buf0, "pdf_xmp_write_translated"); + } + else if (!for_outline && pdf_key_eq(pair, "/Color")) + pdfmark_put_c_pair(pcd, "/C", pair + 1); + else if (!for_outline && pdf_key_eq(pair, "/Title")) + pdfmark_put_c_pair(pcd, "/T", pair + 1); + else if (pdf_key_eq(pair, "/Action") || pdf_key_eq(pair, "/A")) + Action = pair; + /* Previously also catered for '/F', but at the top level (outside an + * Action dict which is handled below), a /F can only be the Flags for + * the annotation, not a File or JavaScript action. + */ + else if (pdf_key_eq(pair, "/File") /* || pdf_key_eq(pair, "/F")*/) + File = pair; + else if (pdf_key_eq(pair, "/Dest")) { + Dest = pair[1]; + coerce_dest = true; + } + else if (pdf_key_eq(pair, "/URI")) { + URI = pair; /* save it for placing into the Action dict */ + } + else if (pdf_key_eq(pair, "/Page") || pdf_key_eq(pair, "/View")) { + /* Make a destination even if this is for an outline. */ + if (Dest.data == 0) { + code = pdfmark_make_dest(dest, params->pdev, "/Page", "/View", + pairs, count, 0); + if (code >= 0) { + param_string_from_string(Dest, dest); + if (for_outline) + coerce_dest = false; + } else { + emprintf(pdev->memory, " **** Warning: Outline has invalid link that was discarded.\n"); + return gs_note_error(gs_error_rangecheck); + } + } + } else if (pdf_key_eq(pair, "/Subtype")) + Subtype = pair[1]; + /* + * We also have to replace all occurrences of \n in Contents + * strings with \r. Unfortunately, they probably have already + * been converted to \012.... + */ + else if (pdf_key_eq(pair, "/Contents")) { + byte *cstr; + uint csize = pair[1].size; + cos_value_t *pcv; + uint i, j; + + /* + * Copy the string into value storage, then update it in place. + */ + pdfmark_put_pair(pcd, pair); + /* Break const so we can update the (copied) string. */ + pcv = (cos_value_t *)cos_dict_find_c_key(pcd, "/Contents"); + if (pcv == NULL) + return_error(gs_error_ioerror); /* shouldn't be possible */ + cstr = pcv->contents.chars.data; + /* Loop invariant: j <= i < csize. */ + for (i = j = 0; i < csize;) + if (csize - i >= 2 && !memcmp(cstr + i, "\\n", 2) && + (i == 0 || cstr[i - 1] != '\\') + ) { + cstr[j] = '\\', cstr[j + 1] = 'r'; + i += 2, j += 2; + } else if (csize - i >= 4 && !memcmp(cstr + i, "\\012", 4) && + (i == 0 || cstr[i - 1] != '\\') + ) { + cstr[j] = '\\', cstr[j + 1] = 'r'; + i += 4, j += 2; + } else + cstr[j++] = cstr[i++]; + if (j != i) + pcv->contents.chars.data = + gs_resize_string(pdev->pdf_memory, cstr, csize, j, + "pdfmark_put_ao_pairs"); + } else if (pdf_key_eq(pair, "/Rect")) { + gs_rect rect; + char rstr[MAX_RECT_STRING]; + int code = pdfmark_scan_rect(&rect, pair + 1, pctm); + + if (code < 0) + return code; + pdfmark_make_rect(rstr, &rect); + cos_dict_put_c_key_string(pcd, "/Rect", (byte *)rstr, + strlen(rstr)); + } else if (pdf_key_eq(pair, "/Border")) { + stream s; + char bstr[MAX_BORDER_STRING + 1]; + int code; + + s_init(&s, NULL); + swrite_string(&s, (byte *)bstr, MAX_BORDER_STRING + 1); + code = pdfmark_write_border(&s, pair + 1, pctm); + if (code < 0) + return code; + if (stell(&s) > MAX_BORDER_STRING) + return_error(gs_error_limitcheck); + bstr[stell(&s)] = 0; + cos_dict_put_c_key_string(pcd, "/Border", (byte *)bstr, + strlen(bstr)); + } else if (for_outline && pdf_key_eq(pair, "/Count")) + DO_NOTHING; + else { + int i, j=0; + unsigned char *temp, *buf0 = (unsigned char *)gs_alloc_bytes(pdev->memory, pair[1].size * 2 * sizeof(unsigned char), + "pdf_xmp_write_translated"); + if (buf0 == NULL) + return_error(gs_error_VMerror); + for (i = 0; i < pair[1].size; i++) { + byte c = pair[1].data[i]; + + buf0[j++] = c; + /* Acrobat doesn't like the 'short' escapes. and wants them as octal.... + * I beliwvw this should be considered an Acrtobat bug, either escapes + * can be used, or not, we shouldn't have to force them to octal. + */ + if (c == '\\') { + switch(pair[1].data[i + 1]) { + case 'b': + buf0[j++] = '0'; + buf0[j++] = '1'; + buf0[j++] = '0'; + i++; + break; + case 'f': + buf0[j++] = '0'; + buf0[j++] = '1'; + buf0[j++] = '4'; + i++; + break; + case 'n': + buf0[j++] = '0'; + buf0[j++] = '1'; + buf0[j++] = '2'; + i++; + break; + case 'r': + buf0[j++] = '0'; + buf0[j++] = '1'; + buf0[j++] = '5'; + i++; + break; + case 't': + buf0[j++] = '0'; + buf0[j++] = '1'; + buf0[j++] = '1'; + i++; + break; + case '\\': + buf0[j++] = '1'; + buf0[j++] = '3'; + buf0[j++] = '4'; + i++; + break; + default: + break; + } + } + } + i = pair[1].size; + temp = (unsigned char *)pair[1].data; + ((gs_param_string *)pair)[1].data = buf0; + ((gs_param_string *)pair)[1].size = j; + + pdfmark_put_pair(pcd, pair); + + ((gs_param_string *)pair)[1].data = temp; + ((gs_param_string *)pair)[1].size = i; + gs_free_object(pdev->memory, buf0, "pdf_xmp_write_translated"); + } + } + if (!for_outline && pdf_key_eq(&Subtype, "/Link")) { + if (Action) { + /* Don't delete the Dest for GoTo or file-GoToR. */ + if (pdf_key_eq(Action + 1, "/GoTo") || + (File && pdf_key_eq(Action + 1, "/GoToR")) + ) + DO_NOTHING; + else + Dest.data = 0; + } + } + + /* Now handle the deferred keys. */ + if (Action) { + const byte *astr = Action[1].data; + const uint asize = Action[1].size; + + if ((File != 0 || Dest.data != 0 || URI != 0) && + (pdf_key_eq(Action + 1, "/Launch") || + (pdf_key_eq(Action + 1, "/GoToR") && File) || + pdf_key_eq(Action + 1, "/Article")) + ) { + cos_dict_t *adict; + cos_value_t avalue; + + if (pdf_key_eq(Action + 1, "/Launch") && pdev->PDFA != 0) { + switch (pdev->PDFACompatibilityPolicy) { + /* Default behaviour matches Adobe Acrobat, warn and continue, + * output file will not be PDF/A compliant + */ + case 0: + emprintf(pdev->memory, + "Launch annotations not permitted in PDF/A, reverting to normal PDF output\n"); + pdev->AbortPDFAX = true; + pdev->PDFA = 0; + break; + /* Since the annotation would break PDF/A compatibility, do not + * include it, but warn the user that it has been dropped. + */ + case 1: + emprintf(pdev->memory, + "Launch annotations not permitted in PDF/A, cannot drop annotation, aborting conversion\n"); + return_error (gs_error_typecheck); + break; + case 2: + emprintf(pdev->memory, + "Launch annotations not permitted in PDF/A, aborting conversion\n"); + return_error (gs_error_typecheck); + break; + default: + emprintf(pdev->memory, + "Launch annotations not permitted in PDF/A, unrecognised PDFACompatibilityLevel,\nreverting to normal PDF output\n"); + pdev->AbortPDFAX = true; + pdev->PDFA = 0; + break; + } + } + + adict = cos_dict_alloc(pdev, "action dict"); + if (adict == 0) + return_error(gs_error_VMerror); + if (!for_outline) { + /* We aren't sure whether this is really needed.... */ + code = cos_dict_put_c_strings(adict, "/Type", "/Action"); + if (code < 0) + return code; + } + if (pdf_key_eq(Action + 1, "/Article")) { + code = cos_dict_put_c_strings(adict, "/S", "/Thread"); + if (code < 0) + return code; + coerce_dest = false; /* Dest is not a real destination */ + } + else + pdfmark_put_c_pair(adict, "/S", Action + 1); + if (Dest.data) { + if (coerce_dest) + pdfmark_coerce_dest(&Dest, dest); + pdfmark_put_c_pair(adict, "/D", &Dest); + Dest.data = 0; /* so we don't write it again */ + } + if (File) { + pdfmark_put_c_pair(adict, "/F", File + 1); + File = 0; /* so we don't write it again */ + } + if (URI) { + /* Adobe Distiller puts a /URI key from pdfmark into the */ + /* Action dict with /S /URI as Subtype */ + pdfmark_put_pair(adict, URI); + code = cos_dict_put_c_strings(adict, "/S", "/URI"); + if (code < 0) + return code; + } + cos_dict_put(pcd, (const byte *)"/A", 2, + COS_OBJECT_VALUE(&avalue, adict)); + } else if (asize >= 4 && !memcmp(astr, "<<", 2)) { + /* Replace occurrences of /Dest, /File, and /Subtype. */ + const byte *scan = astr + 2; + const byte *end = astr + asize; + gs_param_string key, value; + cos_dict_t *adict = cos_dict_alloc(pdev, "action dict"); + cos_value_t avalue; + int code = 0; + + if (adict == 0) + return_error(gs_error_VMerror); + if (URI) { + /* Adobe Distiller puts a /URI key from pdfmark into the */ + /* Action dict with /S /URI as Subtype */ + pdfmark_put_pair(adict, URI); + if(cos_dict_put_c_strings(adict, "/S", "/URI") < 0) + return code; + } + while ((code = pdf_scan_token(&scan, end, &key.data)) > 0) { + key.size = scan - key.data; + if (key.data[0] != '/' || + (code = pdf_scan_token_composite(&scan, end, &value.data)) != 1) + break; + value.size = scan - value.data; + if (pdev->PDFA != 0 && (pdf_key_eq(&key, "/Subtype") || pdf_key_eq(&key, "/S"))) { + if (pdf_key_eq(&value, "/Launch")) { + switch (pdev->PDFACompatibilityPolicy) { + /* Default behaviour matches Adobe Acrobat, warn and continue, + * output file will not be PDF/A compliant + */ + case 0: + emprintf(pdev->memory, + "Launch annotations not permitted in PDF/A, reverting to normal PDF output\n"); + pdev->AbortPDFAX = true; + pdev->PDFA = 0; + break; + /* Since the annotation would break PDF/A compatibility, do not + * include it, but warn the user that it has been dropped. + */ + case 1: + emprintf(pdev->memory, + "Launch annotations not permitted in PDF/A, cannot drop annotation, aborting conversion\n"); + gs_free_object(pdev->memory->stable_memory, adict, "action dict"); + return_error (gs_error_typecheck); + break; + case 2: + emprintf(pdev->memory, + "Launch annotations not permitted in PDF/A, aborting conversion\n"); + gs_free_object(pdev->memory->stable_memory, adict, "action dict"); + return_error (gs_error_typecheck); + break; + default: + emprintf(pdev->memory, + "Launch annotations not permitted in PDF/A, unrecognised PDFACompatibilityLevel,\nreverting to normal PDF output\n"); + pdev->AbortPDFAX = true; + pdev->PDFA = 0; + break; + } + } + } + if (pdf_key_eq(&key, "/Dest") || pdf_key_eq(&key, "/D")) { + param_string_from_string(key, "/D"); + if (value.data[0] == '(' && pdev->CompatibilityLevel < 1.2) { + int i; + + for (i = 0;i < value.size; i++) { + if (value.data[i] == '\\') { + emprintf(pdev->memory, + "Link Destination contains characters which cannot be represented in a name.\nDestinations cannot be strings in versions prior to PDF 1.2. Annotation removed in the output.\n"); + return gs_error_typecheck; + } + } + /****** FIXME: DETECT ESCAPES ******/ + pdfmark_coerce_dest(&value, dest); + } + } else if (pdf_key_eq(&key, "/File")) + param_string_from_string(key, "/F"); + else if (pdf_key_eq(&key, "/Subtype")) + param_string_from_string(key, "/S"); + cos_dict_put_string(adict, key.data, key.size, + value.data, value.size); + } + if (code <= 0 || !pdf_key_eq(&key, ">>")) + return_error(gs_error_rangecheck); + cos_dict_put(pcd, (const byte *)"/A", 2, + COS_OBJECT_VALUE(&avalue, adict)); + } else if (pdf_key_eq(Action + 1, "/GoTo")) + pdfmark_put_pair(pcd, Action); + else if (Action[1].size < 30) { + /* Hack: we could substitute names in pdfmark_process, + now should recognize whether it was done. + Not a perfect method though. + Go with it for a while. */ + char buf[30]; + int d0, d1; + + memcpy(buf, Action[1].data, Action[1].size); + buf[Action[1].size] = 0; + if (sscanf(buf, "%d %d R", &d0, &d1) == 2) + pdfmark_put_pair(pcd, Action); + } + } + /* + * If we have /Dest or /File without the right kind of action, + * simply write it at the top level. This doesn't seem right, + * but I'm not sure what else to do. + */ + if (Dest.data) { + if (coerce_dest) + pdfmark_coerce_dest(&Dest, dest); + /* + * For PDF 1.2 or better we write a Names tree, but in this case the names + * are required to be (counter-intuitively) strings, NOT name objects.... + */ + if (pdev->CompatibilityLevel > 1.1) { + gs_param_string DestString; + int i = 0, j; + char *D; + + /* + * If the name has any 'unusual' characters, it is 'escaped' by starting with NULLs + * I suspect we no longer need that, but here we remove the escaping NULLs + */ + if (Dest.size > 3 && Dest.data[0] == 0x00 && Dest.data[1] == 0x00 && Dest.data[Dest.size - 1] == 0x00) { + i = 2; + if (Dest.size > 5 && Dest.data[2] == 0x00 && Dest.data[3] == 0x00) + i += 2; + } + + if (Dest.data[i] == '/') { + i++; + + DestString.data = gs_alloc_bytes(pdev->memory->stable_memory, (Dest.size * 2) + 1, "DEST pdfmark temp string"); + if (DestString.data == 0) + return_error(gs_error_VMerror); + DestString.size = (Dest.size * 2) + 1; + DestString.persistent = 0; + D = (char *)DestString.data; + D[0] = '('; + for (j=1;i<Dest.size;i++, j++) { + if (Dest.data[i] == '(' || Dest.data[i] == ')') { + D[j++] = '\\'; + } + D[j] = Dest.data[i]; + } + D[j++] = ')'; + DestString.size = j; + pdfmark_put_c_pair(pcd, "/Dest", &DestString); + gs_free_object(pdev->memory->stable_memory, D, "DEST pdfmark temp string"); + } else + pdfmark_put_c_pair(pcd, "/Dest", &Dest); + } else + pdfmark_put_c_pair(pcd, "/Dest", &Dest); + } else if (for_outline && !Action) { + /* Make an implicit destination. */ + char dstr[1 + (sizeof(long) * 8 / 3 + 1) + 25 + 1]; + long page_id = pdf_page_id(pdev, pdev->next_page + 1); + + gs_sprintf(dstr, "[%ld 0 R /XYZ null null null]", page_id); + cos_dict_put_c_key_string(pcd, "/Dest", (const unsigned char*) dstr, + strlen(dstr)); + } + if (File) + pdfmark_put_pair(pcd, File); + if (Subtype.data) + pdfmark_put_c_pair(pcd, "/Subtype", &Subtype); + return 0; +} + +/* Copy an annotation dictionary. */ +static int +pdfmark_annot(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string *objname, + const char *subtype) +{ + ao_params_t params; + cos_dict_t *pcd; + int page_index = pdev->next_page; + cos_array_t *annots; + cos_value_t value; + int code; + + /* Annotations are only permitted in PDF/A if they have the + * Print flag enabled, so we need to prescan for that here. + */ + if(pdev->PDFA != 0) { + int i, Flags = 0; + /* Check all the keys to see if we have a /F (Flags) key/value pair defined */ + for (i = 0; i < count; i += 2) { + const gs_param_string *pair = &pairs[i]; + + if (pdf_key_eq(pair, "/F")) { + char Buffer[32]; + + pair = &pairs[i+1]; + memcpy(Buffer, pair->data, pair->size); + Buffer[pair->size] = 0x00; + code = sscanf(Buffer, "%ld", (long *)&Flags); + if (code != 1) + emprintf(pdev->memory, + "Annotation has an invalid /Flags attribute\n"); + break; + } + } + /* Check the Print flag, PDF/A annotations *must* be set to print */ + if ((Flags & 4) == 0){ + switch (pdev->PDFACompatibilityPolicy) { + /* Default behaviour matches Adobe Acrobat, warn and continue, + * output file will not be PDF/A compliant + */ + case 0: + emprintf(pdev->memory, + "Annotation set to non-printing,\n not permitted in PDF/A, reverting to normal PDF output\n"); + pdev->AbortPDFAX = true; + pdev->PDFA = 0; + break; + /* Since the annotation would break PDF/A compatibility, do not + * include it, but warn the user that it has been dropped. + */ + case 1: + emprintf(pdev->memory, + "Annotation set to non-printing,\n not permitted in PDF/A, annotation will not be present in output file\n"); + return 0; + break; + case 2: + emprintf(pdev->memory, + "Annotation set to non-printing,\n not permitted in PDF/A, aborting conversion\n"); + return_error(gs_error_invalidfont); + break; + default: + emprintf(pdev->memory, + "Annotation set to non-printing,\n not permitted in PDF/A, unrecognised PDFACompatibilityLevel,\nreverting to normal PDF output\n"); + pdev->AbortPDFAX = true; + pdev->PDFA = 0; + break; + } + } + } + if (pdev->PDFX != 0) { + gs_param_string Subtype; + bool discard = true; + pdf_page_t *page = 0L; + + page = &pdev->pages[pdev->next_page]; + + if (subtype) { + param_string_from_string(Subtype, subtype); + if (pdf_key_eq(&Subtype, "/TrapNet") || + pdf_key_eq(&Subtype, "/PrinterMark")) + discard = false; + } + if (discard) { + int i; + for (i = 0; i < count; i += 2) { + const gs_param_string *pair = &pairs[i]; + if (pdf_key_eq(pair, "/Rect")) { + gs_rect rect; + const cos_value_t *v_trimbox, *v_bleedbox, *v_artbox, *v_cropbox; + const byte *p; + char buf[100]; + int size, code; + float temp[4]; /* the type is float for sscanf. */ + double pagebox[4] = {0, 0}; + + pagebox[2] = pdev->MediaSize[0]; + pagebox[3] = pdev->MediaSize[1]; + + v_cropbox = v_bleedbox = v_trimbox = v_artbox = 0x0L; + + code = pdfmark_scan_rect(&rect, pair + 1, pctm); + + if (code < 0) + return code; + + if (page && page->Page) { + v_trimbox = cos_dict_find_c_key(page->Page, "/TrimBox"); + v_bleedbox = cos_dict_find_c_key(page->Page, "/BleedBox"); + v_artbox = cos_dict_find_c_key(page->Page, "/ArtBox"); + v_cropbox = cos_dict_find_c_key(page->Page, "/CropBox"); + } + + if (v_cropbox != NULL && v_cropbox->value_type == COS_VALUE_SCALAR) { + p = v_cropbox->contents.chars.data; + size = min (v_cropbox->contents.chars.size, sizeof(buf) - 1); + memcpy(buf, p, size); + buf[size] = 0; + if (sscanf(buf, "[ %g %g %g %g ]", + &temp[0], &temp[1], &temp[2], &temp[3]) == 4) { + if (temp[0] > pagebox[0]) pagebox[0] = temp[0]; + if (temp[1] > pagebox[1]) pagebox[1] = temp[1]; + } + } + if (v_bleedbox != NULL && v_bleedbox->value_type == COS_VALUE_SCALAR) { + p = v_bleedbox->contents.chars.data; + size = min (v_bleedbox->contents.chars.size, sizeof(buf) - 1); + memcpy(buf, p, size); + buf[size] = 0; + if (sscanf(buf, "[ %g %g %g %g ]", + &temp[0], &temp[1], &temp[2], &temp[3]) == 4) { + if (temp[0] > pagebox[0]) pagebox[0] = temp[0]; + if (temp[1] > pagebox[1]) pagebox[1] = temp[1]; + } + } + if (v_trimbox != NULL && v_trimbox->value_type == COS_VALUE_SCALAR) { + p = v_trimbox->contents.chars.data; + size = min (v_trimbox->contents.chars.size, sizeof(buf) - 1); + memcpy(buf, p, size); + buf[size] = 0; + if (sscanf(buf, "[ %g %g %g %g ]", + &temp[0], &temp[1], &temp[2], &temp[3]) == 4) { + if (temp[0] > pagebox[0]) pagebox[0] = temp[0]; + if (temp[1] > pagebox[1]) pagebox[1] = temp[1]; + } + } + if (v_artbox != NULL && v_artbox->value_type == COS_VALUE_SCALAR) { + p = v_artbox->contents.chars.data; + size = min (v_artbox->contents.chars.size, sizeof(buf) - 1); + memcpy(buf, p, size); + buf[size] = 0; + if (sscanf(buf, "[ %g %g %g %g ]", + &temp[0], &temp[1], &temp[2], &temp[3]) == 4) { + if (temp[0] > pagebox[0]) pagebox[0] = temp[0]; + if (temp[1] > pagebox[1]) pagebox[1] = temp[1]; + } + } + if (v_cropbox == 0 && v_trimbox == 0 && v_artbox == 0 && v_bleedbox == 0) { + if (pdev->PDFXTrimBoxToMediaBoxOffset.size >= 4 && + pdev->PDFXTrimBoxToMediaBoxOffset.data[0] >= 0 && + pdev->PDFXTrimBoxToMediaBoxOffset.data[1] >= 0 && + pdev->PDFXTrimBoxToMediaBoxOffset.data[2] >= 0 && + pdev->PDFXTrimBoxToMediaBoxOffset.data[3] >= 0) { + pagebox[0] += pdev->PDFXTrimBoxToMediaBoxOffset.data[0]; + pagebox[1] += pdev->PDFXTrimBoxToMediaBoxOffset.data[3]; + pagebox[2] -= pdev->PDFXTrimBoxToMediaBoxOffset.data[1]; + pagebox[3] -= pdev->PDFXTrimBoxToMediaBoxOffset.data[2]; + } else if (pdev->PDFXBleedBoxToTrimBoxOffset.size >= 4 && + pdev->PDFXBleedBoxToTrimBoxOffset.data[0] >= 0 && + pdev->PDFXBleedBoxToTrimBoxOffset.data[1] >= 0 && + pdev->PDFXBleedBoxToTrimBoxOffset.data[2] >= 0 && + pdev->PDFXBleedBoxToTrimBoxOffset.data[3] >= 0) { + pagebox[0] -= pdev->PDFXBleedBoxToTrimBoxOffset.data[0]; + pagebox[1] -= pdev->PDFXBleedBoxToTrimBoxOffset.data[3]; + pagebox[2] += pdev->PDFXBleedBoxToTrimBoxOffset.data[1]; + pagebox[3] += pdev->PDFXBleedBoxToTrimBoxOffset.data[2]; + } + } + + if (rect.p.x > pagebox[2] || rect.q.x < pagebox[0] || + rect.p.y > pagebox[3] || rect.q.y < pagebox[1]) + break; + switch (pdev->PDFACompatibilityPolicy) { + /* Default behaviour matches Adobe Acrobat, warn and continue, + * output file will not be PDF/A compliant + */ + case 0: + emprintf(pdev->memory, + "Annotation (not TrapNet or PrinterMark) on page,\n not permitted in PDF/X, reverting to normal PDF output\n"); + pdev->AbortPDFAX = true; + pdev->PDFX = 0; + break; + /* Since the annotation would break PDF/A compatibility, do not + * include it, but warn the user that it has been dropped. + */ + case 1: + emprintf(pdev->memory, + "Annotation (not TrapNet or PrinterMark) on page,\n not permitted in PDF/X, annotation will not be present in output file\n"); + return 0; + break; + case 2: + emprintf(pdev->memory, + "Annotation (not TrapNet or PrinterMark) on page,\n not permitted in PDF/X, aborting conversion\n"); + return_error(gs_error_invalidfont); + break; + default: + emprintf(pdev->memory, + "Annotation s(not TrapNet or PrinterMark) on page,\n not permitted in PDF/A, unrecognised PDFACompatibilityLevel,\nreverting to normal PDF output\n"); + pdev->AbortPDFAX = true; + pdev->PDFX = 0; + break; + } + break; + } + } + if (i > count) { + switch (pdev->PDFACompatibilityPolicy) { + /* Default behaviour matches Adobe Acrobat, warn and continue, + * output file will not be PDF/A compliant + */ + case 0: + emprintf(pdev->memory, + "Annotation (not TrapNet or PrinterMark) potentially on page (no /Rect in dict),\n not permitted in PDF/X, reverting to normal PDF output\n"); + pdev->AbortPDFAX = true; + pdev->PDFX = 0; + break; + /* Since the annotation would break PDF/A compatibility, do not + * include it, but warn the user that it has been dropped. + */ + case 1: + emprintf(pdev->memory, + "Annotation (not TrapNet or PrinterMark) potentially on page (no /Rect in dict),\n not permitted in PDF/X, annotation will not be present in output file\n"); + return 0; + break; + case 2: + emprintf(pdev->memory, + "Annotation (not TrapNet or PrinterMark) potentially on page (no /Rect in dict),\n not permitted in PDF/X, aborting conversion\n"); + return_error(gs_error_invalidfont); + break; + default: + emprintf(pdev->memory, + "Annotation s(not TrapNet or PrinterMark) potentially on page (no /Rect in dict),\n not permitted in PDF/A, unrecognised PDFACompatibilityLevel,\nreverting to normal PDF output\n"); + pdev->AbortPDFAX = true; + pdev->PDFX = 0; + break; + } + } + } + } + params.pdev = pdev; + params.subtype = subtype; + params.src_pg = -1; + code = pdf_make_named_dict(pdev, objname, &pcd, true); + if (code < 0) + return code; + code = cos_dict_put_c_strings(pcd, "/Type", "/Annot"); + if (code < 0) + return code; + code = pdfmark_put_ao_pairs(pdev, pcd, pairs, count, pctm, ¶ms, false); + if (code < 0) + return code; + if (params.src_pg >= 0) + page_index = params.src_pg; + if (pdf_page_id(pdev, page_index + 1) <= 0) + return_error(gs_error_rangecheck); + annots = pdev->pages[page_index].Annots; + if (annots == 0) { + annots = cos_array_alloc(pdev, "pdfmark_annot"); + if (annots == 0) + return_error(gs_error_VMerror); + pdev->pages[page_index].Annots = annots; + } + if (!objname) { + /* Write the annotation now. */ + COS_WRITE_OBJECT(pcd, pdev, resourceAnnotation); + COS_RELEASE(pcd, "pdfmark_annot"); + } + return cos_array_add(annots, + cos_object_value(&value, COS_OBJECT(pcd))); +} + +/* ANN pdfmark */ +static int +pdfmark_ANN(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * objname) +{ + return pdfmark_annot(pdev, pairs, count, pctm, objname, "/Text"); +} + +/* LNK pdfmark (obsolescent) */ +static int +pdfmark_LNK(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * objname) +{ + return pdfmark_annot(pdev, pairs, count, pctm, objname, "/Link"); +} + +/* Write and release one node of the outline tree. */ +static int +pdfmark_write_outline(gx_device_pdf * pdev, pdf_outline_node_t * pnode, + long next_id) +{ + stream *s; + int code = 0; + + pdf_open_separate(pdev, pnode->id, resourceOutline); + if (pnode->action != NULL) + pnode->action->id = pnode->id; + else { + emprintf1(pdev->memory, + "pdfmark error: Outline node %ld has no action or destination.\n", + pnode->id); + code = gs_note_error(gs_error_undefined); + } + s = pdev->strm; + stream_puts(s, "<< "); + if (pnode->action != NULL) + cos_dict_elements_write(pnode->action, pdev); + if (pnode->count) + pprintd1(s, "/Count %d ", pnode->count); + pprintld1(s, "/Parent %ld 0 R\n", pnode->parent_id); + if (pnode->prev_id) + pprintld1(s, "/Prev %ld 0 R\n", pnode->prev_id); + if (next_id) + pprintld1(s, "/Next %ld 0 R\n", next_id); + if (pnode->first_id) + pprintld2(s, "/First %ld 0 R /Last %ld 0 R\n", + pnode->first_id, pnode->last_id); + stream_puts(s, ">>\n"); + pdf_end_separate(pdev, resourceOutline); + if (pnode->action != NULL) + COS_FREE(pnode->action, "pdfmark_write_outline"); + pnode->action = 0; + return code; +} + +/* Adjust the parent's count when writing an outline node. */ +static void +pdfmark_adjust_parent_count(pdf_outline_level_t * plevel) +{ + pdf_outline_level_t *parent = plevel - 1; + int count = plevel->last.count; + + if (count > 0) { + if (parent->last.count < 0) + parent->last.count -= count; + else + parent->last.count += count; + } +} + +/* + * Close the current level of the outline tree. Note that if we are at + * the end of the document, some of the levels may be incomplete if the + * Count values were incorrect. + */ +int +pdfmark_close_outline(gx_device_pdf * pdev) +{ + int depth = pdev->outline_depth; + pdf_outline_level_t *plevel = &pdev->outline_levels[depth]; + int code = 0; + + if (plevel->last.id) { /* check for incomplete tree */ + code = pdfmark_write_outline(pdev, &plevel->last, 0); + } + if (depth > 0) { + plevel[-1].last.last_id = plevel->last.id; + pdfmark_adjust_parent_count(plevel); + --plevel; + if (plevel->last.count < 0) + pdev->closed_outline_depth--; + pdev->outline_depth--; + } + return code; +} + +/* OUT pdfmark */ +static int +pdfmark_OUT(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * no_objname) +{ + int depth = pdev->outline_depth; + pdf_outline_level_t *plevel = &pdev->outline_levels[depth]; + int sub_count = 0; + uint i; + pdf_outline_node_t node; + ao_params_t ao; + int code; + + for (i = 0; i < count; i += 2) { + const gs_param_string *pair = &pairs[i]; + + if (pdf_key_eq(pair, "/Count")) + pdfmark_scan_int(pair + 1, &sub_count); + } + if (sub_count != 0 && depth == pdev->max_outline_depth - 1) { + pdf_outline_level_t *new_ptr; + + new_ptr = (pdf_outline_level_t *)gs_alloc_bytes(pdev->pdf_memory, (pdev->max_outline_depth + INITIAL_MAX_OUTLINE_DEPTH) * sizeof(pdf_outline_level_t) * sizeof(pdf_outline_level_t), "outline_levels array"); + if (!new_ptr) + return_error(gs_error_VMerror); + memcpy (new_ptr, pdev->outline_levels, pdev->max_outline_depth * sizeof(pdf_outline_level_t)); + gs_free_object(pdev->pdf_memory, pdev->outline_levels, "outline_levels array"); + pdev->outline_levels = new_ptr; + pdev->max_outline_depth += INITIAL_MAX_OUTLINE_DEPTH; + plevel = &pdev->outline_levels[depth]; + } + node.action = cos_dict_alloc(pdev, "pdfmark_OUT"); + if (node.action == 0) + return_error(gs_error_VMerror); + ao.pdev = pdev; + ao.subtype = 0; + ao.src_pg = -1; + code = pdfmark_put_ao_pairs(pdev, node.action, pairs, count, pctm, &ao, + true); + if (code < 0) + return code; + if (pdev->outlines_id == 0) + pdev->outlines_id = pdf_obj_ref(pdev); + node.id = pdf_obj_ref(pdev); + node.parent_id = + (depth == 0 ? pdev->outlines_id : plevel[-1].last.id); + node.prev_id = plevel->last.id; + node.first_id = node.last_id = 0; + node.count = sub_count; + /* Add this node to the outline at the current level. */ + if (plevel->first.id == 0) { /* First node at this level. */ + if (depth > 0) + plevel[-1].last.first_id = node.id; + node.prev_id = 0; + plevel->first = node; + plevel->first.action = 0; /* never used */ + } else { /* Write the previous node. */ + if (depth > 0) + pdfmark_adjust_parent_count(plevel); + pdfmark_write_outline(pdev, &plevel->last, node.id); + } + plevel->last = node; + plevel->left--; + if (!pdev->closed_outline_depth) + pdev->outlines_open++; + /* If this node has sub-nodes, descend one level. */ + if (sub_count != 0) { + pdev->outline_depth++; + ++plevel; + plevel->left = (sub_count > 0 ? sub_count : -sub_count); + plevel->first.id = 0; + plevel->last.count = plevel->last.id = 0; + plevel->first.action = plevel->last.action = 0; /* for GC */ + if (sub_count < 0) + pdev->closed_outline_depth++; + } else { + while ((depth = pdev->outline_depth) > 0 && + pdev->outline_levels[depth].left == 0 + ) + pdfmark_close_outline(pdev); + } + return 0; +} + +/* Write an article bead. */ +static int +pdfmark_write_bead(gx_device_pdf * pdev, const pdf_bead_t * pbead) +{ + stream *s; + char rstr[MAX_RECT_STRING]; + + pdf_open_separate(pdev, pbead->id, resourceArticle); + s = pdev->strm; + pprintld3(s, "<</T %ld 0 R/V %ld 0 R/N %ld 0 R", + pbead->article_id, pbead->prev_id, pbead->next_id); + if (pbead->page_id != 0) + pprintld1(s, "/P %ld 0 R", pbead->page_id); + pdfmark_make_rect(rstr, &pbead->rect); + pprints1(s, "/R%s>>\n", rstr); + return pdf_end_separate(pdev, resourceArticle); +} + +/* Finish writing an article, and release its data. */ +int +pdfmark_write_article(gx_device_pdf * pdev, const pdf_article_t * part) +{ + pdf_article_t art; + stream *s; + + art = *part; + if (art.last.id == 0) { + /* Only one bead in the article. */ + art.first.prev_id = art.first.next_id = art.first.id; + } else { + /* More than one bead in the article. */ + art.first.prev_id = art.last.id; + art.last.next_id = art.first.id; + pdfmark_write_bead(pdev, &art.last); + } + pdfmark_write_bead(pdev, &art.first); + pdf_open_separate(pdev, art.contents->id, resourceArticle); + s = pdev->strm; + pprintld1(s, "<</F %ld 0 R/I<<", art.first.id); + cos_dict_elements_write(art.contents, pdev); + stream_puts(s, ">> >>\n"); + return pdf_end_separate(pdev, resourceArticle); +} + +/* ARTICLE pdfmark */ +static int +pdfmark_ARTICLE(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * no_objname) +{ + gs_memory_t *mem = pdev->pdf_memory; + gs_param_string title; + gs_param_string rectstr; + gs_rect rect; + long bead_id; + pdf_article_t *part; + int code; + + if (!pdfmark_find_key("/Title", pairs, count, &title) || + !pdfmark_find_key("/Rect", pairs, count, &rectstr) + ) + return_error(gs_error_rangecheck); + if ((code = pdfmark_scan_rect(&rect, &rectstr, pctm)) < 0) + return code; + bead_id = pdf_obj_ref(pdev); + + /* Find the article with this title, or create one. */ + for (part = pdev->articles; part != 0; part = part->next) { + const cos_value_t *a_title = + cos_dict_find_c_key(part->contents, "/Title"); + + if (a_title != 0 && !COS_VALUE_IS_OBJECT(a_title) && + !bytes_compare(a_title->contents.chars.data, + a_title->contents.chars.size, + title.data, title.size)) + break; + } + if (part == 0) { /* Create the article. */ + cos_dict_t *contents = + cos_dict_alloc(pdev, "pdfmark_ARTICLE(contents)"); + + if (contents == 0) + return_error(gs_error_VMerror); + part = gs_alloc_struct(mem, pdf_article_t, &st_pdf_article, + "pdfmark_ARTICLE(article)"); + if (part == 0 || contents == 0) { + gs_free_object(mem, part, "pdfmark_ARTICLE(article)"); + if (contents) + COS_FREE(contents, "pdfmark_ARTICLE(contents)"); + return_error(gs_error_VMerror); + } + contents->id = pdf_obj_ref(pdev); + part->next = pdev->articles; + pdev->articles = part; + cos_dict_put_string(contents, (const byte *)"/Title", 6, + title.data, title.size); + part->first.id = part->last.id = 0; + part->contents = contents; + } + /* + * Add the bead to the article. This is similar to what we do for + * outline nodes, except that articles have only a page number and + * not View/Dest. + */ + if (part->last.id == 0) { + part->first.next_id = bead_id; + part->last.id = part->first.id; + } else { + part->last.next_id = bead_id; + pdfmark_write_bead(pdev, &part->last); + } + part->last.prev_id = part->last.id; + part->last.id = bead_id; + part->last.article_id = part->contents->id; + part->last.next_id = 0; + part->last.rect = rect; + { + gs_param_string page_string; + int page = 0; + uint i; + + pdfmark_find_key("/Page", pairs, count, &page_string); + page = pdfmark_page_number(pdev, &page_string); + code = update_max_page_reference(pdev, &page); + if (code < 0) + return code; + part->last.page_id = pdf_page_id(pdev, page); + for (i = 0; i < count; i += 2) { + if (pdf_key_eq(&pairs[i], "/Rect") || pdf_key_eq(&pairs[i], "/Page")) + continue; + pdfmark_put_pair(part->contents, &pairs[i]); + } + } + if (part->first.id == 0) { /* This is the first bead of the article. */ + part->first = part->last; + part->last.id = 0; + } + return 0; +} + +/* EMBED pdfmark */ +static int +pdfmark_EMBED(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * objname) +{ + gs_param_string key; + int i; + + if (pdev->CompatibilityLevel < 1.4) + return_error(gs_error_undefined); + if (pdev->PDFA > 0 && pdev->PDFA < 2) { + switch(pdev->PDFACompatibilityPolicy) { + default: + case 0: + emprintf(pdev->memory, + "The PDF/A-1 specifcation prohibits the embedding of files, reverting to normal PDF output.\n"); + pdev->AbortPDFAX = true; + pdev->PDFX = 0; + return 0; + case 1: + emprintf(pdev->memory, + "The PDF/A-1 specifcation prohibits the embedding of files, pdfamrk operatoin ignored.\n"); + break; + case 2: + return_error(gs_error_undefined); + } + } + if (pdev->PDFA > 0 && pdev->PDFA < 3) { + emprintf(pdev->memory, + "The PDF/A-2 specifcation only permits the embedding of PDF/A-1 or PDF/A-2 files.\n"); + emprintf(pdev->memory, + "The pdfwrite device has not validated this embedded file, output may not conform to PDF/A-2.\n"); + } + if (!pdfmark_find_key("/FS", pairs, count, &key)) + return_error(gs_error_rangecheck); + if (!pdfmark_find_key("/Name", pairs, count, &key)) + return_error(gs_error_rangecheck); + if (!pdev->EmbeddedFiles) { + pdev->EmbeddedFiles = cos_dict_alloc(pdev, "pdfmark_EMBED(EmbeddedFiles)"); + if (pdev->EmbeddedFiles == 0) + return_error(gs_error_VMerror); + pdev->EmbeddedFiles->id = pdf_obj_ref(pdev); + } + + for (i = 0; i < count; i += 2) { + if (pdf_key_eq(&pairs[i], "/FS")) { + return cos_dict_put_string(pdev->EmbeddedFiles, key.data, key.size, + pairs[i+1].data, pairs[i+1].size); + } + } + return 0; +} + +/* DEST pdfmark */ +static int +pdfmark_DEST(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * objname) +{ + int present; + char dest[MAX_DEST_STRING]; + gs_param_string key; + cos_value_t value; + cos_dict_t *ddict; + int i, code; + + if (!pdfmark_find_key("/Dest", pairs, count, &key) || + (present = + pdfmark_make_dest(dest, pdev, "/Page", "/View", pairs, count, 1)) < 0 + ) + return_error(gs_error_rangecheck); + cos_string_value(&value, (byte *)dest, strlen(dest)); + if (!pdev->Dests) { + pdev->Dests = cos_dict_alloc(pdev, "pdfmark_DEST(Dests)"); + if (pdev->Dests == 0) + return_error(gs_error_VMerror); + pdev->Dests->id = pdf_obj_ref(pdev); + } + + /* + * Create the destination as a dictionary with a D key. + */ + code = pdf_make_named_dict(pdev, objname, &ddict, false); + ddict->id = pdf_obj_ref(pdev); + + if (code < 0) + return code; + code = cos_dict_put_c_key_string(ddict, "/D", (byte *)dest, + strlen(dest)); + for (i = 0; code >= 0 && i < count; i += 2) + if (!pdf_key_eq(&pairs[i], "/Dest") && + !pdf_key_eq(&pairs[i], "/Page") && + !pdf_key_eq(&pairs[i], "/View") + ) + code = pdfmark_put_pair(ddict, &pairs[i]); + if (code < 0) + return code; + COS_WRITE_OBJECT(ddict, pdev, resourceOther); + COS_OBJECT_VALUE(&value, ddict); + COS_RELEASE(ddict, "pdfmark_DEST(Dests dict)"); + + return cos_dict_put(pdev->Dests, key.data, key.size, &value); +} + +/* Check that pass-through PostScript code is a string. */ +static bool +ps_source_ok(const gs_memory_t *mem, const gs_param_string * psource) +{ + if (psource->size >= 2 && psource->data[0] == '(' && + psource->data[psource->size - 1] == ')' + ) + return true; + else { + int i; + lprintf("bad PS passthrough: "); + for (i=0; i<psource->size; i++) + errprintf(mem, "%c", psource->data[i]); + errprintf(mem, "\n"); + return false; + } +} + +/* Write the contents of pass-through PostScript code. */ +/* Return the size written on the file. */ +static uint +pdfmark_write_ps(stream *s, const gs_param_string * psource) +{ + /****** REMOVE ESCAPES WITH PSSDecode, SEE gdevpdfr p. 2 ******/ + uint size = psource->size - 2; + + stream_write(s, psource->data + 1, size); + stream_putc(s, '\n'); + return size + 1; +} + +/* Start a XObject. */ +static int +start_XObject(gx_device_pdf * pdev, bool compress, cos_stream_t **ppcs) +{ pdf_resource_t *pres; + cos_stream_t *pcs; + int code; + + code = pdf_open_page(pdev, PDF_IN_STREAM); + if (code < 0) + return code; + code = pdf_enter_substream(pdev, resourceXObject, gs_no_id, &pres, false, + pdev->CompressStreams); + if (code < 0) + return code; + pdev->accumulating_a_global_object = true; + pcs = (cos_stream_t *)pres->object; + pdev->substream_Resources = cos_dict_alloc(pdev, "start_XObject"); + if (!pdev->substream_Resources) + return_error(gs_error_VMerror); + if (pdev->ForOPDFRead) { + code = cos_dict_put_c_key_bool((cos_dict_t *)pres->object, "/.Global", true); + if (code < 0) + return code; + } + pres->named = true; + pres->where_used = 0; /* initially not used */ + pcs->pres = pres; + *ppcs = pcs; + return 0; +} + +/* PS pdfmark */ +#define MAX_PS_INLINE 100 +static int +pdfmark_PS(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * objname) +{ + gs_param_string source; + gs_param_string level1; + + if (!pdfmark_find_key("/DataSource", pairs, count, &source) || + !ps_source_ok(pdev->memory, &source) || + (pdfmark_find_key("/Level1", pairs, count, &level1) && + !ps_source_ok(pdev->memory, &level1)) + ) + return_error(gs_error_rangecheck); + if (level1.data == 0 && source.size <= MAX_PS_INLINE && objname == 0) { + /* Insert the PostScript code in-line */ + int code = pdf_open_contents(pdev, PDF_IN_STREAM); + stream *s; + + if (code < 0) + return code; + s = pdev->strm; + stream_write(s, source.data, source.size); + stream_puts(s, " PS\n"); + } else { + /* Put the PostScript code in a resource. */ + cos_stream_t *pcs; + int code; + gs_id level1_id = gs_no_id; + pdf_resource_t *pres; + + if (level1.data != 0) { + pdf_resource_t *pres; + + code = pdf_enter_substream(pdev, + resourceXObject, + gs_no_id, &pres, true, + pdev->CompressStreams); + if (code < 0) + return code; + pcs = (cos_stream_t *)pres->object; + if (pdev->ForOPDFRead && objname != 0) { + code = cos_dict_put_c_key_bool((cos_dict_t *)pres->object, "/.Global", true); + if (code < 0) + return code; + } + pres->named = (objname != 0); + pres->where_used = 0; + pcs->pres = pres; + DISCARD(pdfmark_write_ps(pdev->strm, &level1)); + code = pdf_exit_substream(pdev); + if (code < 0) + return code; + code = cos_write_object(pres->object, pdev, resourceOther); + if (code < 0) + return code; + level1_id = pres->object->id; + } + code = start_XObject(pdev, pdev->params.CompressPages, &pcs); + if (code < 0) + return code; + pres = pdev->accumulating_substream_resource; + code = cos_stream_put_c_strings(pcs, "/Type", "/XObject"); + if (code < 0) + return code; + code = cos_stream_put_c_strings(pcs, "/Subtype", "/PS"); + if (code < 0) + return code; + if (level1_id != gs_no_id) { + char r[MAX_DEST_STRING]; + + gs_sprintf(r, "%ld 0 R", level1_id); + code = cos_dict_put_c_key_string(cos_stream_dict(pcs), "/Level1", + (byte *)r, strlen(r)); + if (code < 0) + return code; + } + DISCARD(pdfmark_write_ps(pdev->strm, &source)); + code = pdf_exit_substream(pdev); + if (code < 0) + return code; + { gs_const_string objname1, *pon = NULL; + + if (objname != NULL) { + objname1.data = objname->data; + objname1.size = objname->size; + pon = &objname1; + } + code = pdfmark_bind_named_object(pdev, pon, &pres); + if (code < 0) + return code; + } + code = pdf_open_contents(pdev, PDF_IN_STREAM); + if (code < 0) + return code; + pcs->pres->where_used |= pdev->used_mask; + pprintld1(pdev->strm, "/R%ld Do\n", pcs->id); + } + return 0; +} + +/* Common code for pdfmarks that do PUT into a specific object. */ +static int +pdfmark_put_pairs(cos_dict_t *pcd, gs_param_string * pairs, uint count) +{ + int code = 0, i; + + if (count & 1) + return_error(gs_error_rangecheck); + for (i = 0; code >= 0 && i < count; i += 2) + code = pdfmark_put_pair(pcd, pairs + i); + return code; +} + +/* PAGES pdfmark */ +static int +pdfmark_PAGES(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * no_objname) +{ + return pdfmark_put_pairs(pdev->Pages, pairs, count); +} + +/* PAGE pdfmark */ +static int +pdfmark_PAGE(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * no_objname) +{ + return pdfmark_put_pairs(pdf_current_page_dict(pdev), pairs, count); +} + +/* Add a page label for the current page. The last label on a page + * overrides all previous labels for this page. Unlabeled pages will get + * empty page labels. label == NULL flushes the last label */ +static int +pdfmark_add_pagelabel(gx_device_pdf * pdev, const gs_param_string *label) +{ + cos_value_t value; + cos_dict_t *dict = 0; + int code = 0; + + /* create label dict (and page label array if not present yet) */ + if (label != 0) { + if (!pdev->PageLabels) { + pdev->PageLabels = cos_array_alloc(pdev, + "pdfmark_add_pagelabel(PageLabels)"); + if (pdev->PageLabels == 0) + return_error(gs_error_VMerror); + pdev->PageLabels->id = pdf_obj_ref(pdev); + + /* empty label for unlabled pages before first labled page */ + pdev->PageLabels_current_page = 0; + pdev->PageLabels_current_label = cos_dict_alloc(pdev, + "pdfmark_add_pagelabel(first)"); + if (pdev->PageLabels_current_label == 0) + return_error(gs_error_VMerror); + } + + dict = cos_dict_alloc(pdev, "pdfmark_add_pagelabel(dict)"); + if (dict == 0) + return_error(gs_error_VMerror); + + code = cos_dict_put_c_key(dict, "/P", cos_string_value(&value, + label->data, label->size)); + if (code < 0) { + COS_FREE(dict, "pdfmark_add_pagelabel(dict)"); + return code; + } + } + + /* flush current label */ + if (label == 0 || pdev->next_page != pdev->PageLabels_current_page) { + /* handle current label */ + if (pdev->PageLabels_current_label) { + if (code >= 0) { + code = cos_array_add_int(pdev->PageLabels, + pdev->PageLabels_current_page); + if (code >= 0) + code = cos_array_add(pdev->PageLabels, + COS_OBJECT_VALUE(&value, + pdev->PageLabels_current_label)); + } + pdev->PageLabels_current_label = 0; + } + + /* handle unlabled pages between current labeled page and + * next labeled page */ + if (pdev->PageLabels) { + if (pdev->next_page - pdev->PageLabels_current_page > 1) { + cos_dict_t *tmp = cos_dict_alloc(pdev, + "pdfmark_add_pagelabel(tmp)"); + if (tmp == 0) + return_error(gs_error_VMerror); + + code = cos_array_add_int(pdev->PageLabels, + pdev->PageLabels_current_page + 1); + if (code >= 0) + code = cos_array_add(pdev->PageLabels, + COS_OBJECT_VALUE(&value, tmp)); + } + } + } + + /* new current label */ + if (pdev->PageLabels_current_label) + COS_FREE(pdev->PageLabels_current_label, + "pdfmark_add_pagelabel(current_label)"); + pdev->PageLabels_current_label = dict; + pdev->PageLabels_current_page = pdev->next_page; + + return code; +} + +/* Close the pagelabel numtree.*/ +int +pdfmark_end_pagelabels(gx_device_pdf * pdev) +{ + return pdfmark_add_pagelabel(pdev, 0); +} + +/* [ /Label string /PlateColor string pdfmark */ +/* FIXME: /PlateColor is ignored */ +static int +pdfmark_PAGELABEL(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * no_objname) +{ + gs_param_string key; + + if (pdev->CompatibilityLevel >= 1.3) { + if (pdfmark_find_key("/Label", pairs, count, &key)) { + return pdfmark_add_pagelabel(pdev, &key); + } + } + return 0; +} + +/* DOCINFO pdfmark */ +static int +pdfmark_DOCINFO(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * no_objname) +{ + /* + * We could use pdfmark_put_pairs(pdev->Info, pairs, count), except + * that we want to replace "Distiller" with our own name as the + * Producer. + */ + cos_dict_t *const pcd = pdev->Info; + int code = 0, i; + + if (count & 1) + return_error(gs_error_rangecheck); + for (i = 0; code >= 0 && i < count; i += 2) { + const gs_param_string *pair = pairs + i; + + if (pdev->CompatibilityLevel >= 2.0) { + if (!pdf_key_eq(pairs + i, "/ModDate") && !pdf_key_eq(pairs + i, "/CreationDate")) + continue; + } + + if (pdev->PDFA !=0) { + const gs_param_string *p = pairs + i + 1; + if (p->size > 9 && memcmp(p->data, "(\\376\\377", 9) == 0) { + /* Can't handle UTF16BE in PDF/A1, so abort this pair or abort PDF/A or just abort, + * depending on PDFACompatibilityPolicy + */ + switch (pdev->PDFACompatibilityPolicy) { + case 0: + emprintf(pdev->memory, + "UTF16BE text string detected in DOCINFO cannot be represented in XMP for PDF/A1, reverting to normal PDF output\n"); + pdev->AbortPDFAX = true; + pdev->PDFX = 0; + break; + case 1: + emprintf(pdev->memory, + "UTF16BE text string detected in DOCINFO cannot be represented in XMP for PDF/A1, discarding DOCINFO\n"); + continue; + break; + case 2: + emprintf(pdev->memory, + "UTF16BE text string detected in DOCINFO cannot be represented in XMP for PDF/A1, aborting conversion.\n"); + /* If we don't return a fatal error then zputdeviceparams simply ignores it (!) */ + return_error(gs_error_Fatal); + break; + default: + break; + } + } + } + if (pdf_key_eq(pairs + i, "/Producer")) { + string_match_params params; + params = string_match_params_default; + params.ignore_case = true; + + if (!string_match((const byte *)GS_PRODUCTFAMILY, strlen(GS_PRODUCTFAMILY), (const byte *)"GPL Ghostscript", 15, ¶ms)) + code = pdfmark_put_pair(pcd, pair); + } else + code = pdfmark_put_pair(pcd, pair); + if (code < 0) + break; + } + return code; +} + +/* DOCVIEW pdfmark */ +static int +pdfmark_DOCVIEW(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * no_objname) +{ + char dest[MAX_DEST_STRING]; + int code = 0; + + if (count & 1) + return_error(gs_error_rangecheck); + code = pdfmark_make_dest(dest, pdev, "/Page", "/View", pairs, count, 0); + if (code < 0) + return gs_note_error(gs_error_rangecheck); + + if (code > 0) { + int i; + + code = cos_dict_put_c_key_string(pdev->Catalog, "/OpenAction", + (byte *)dest, strlen(dest)); + for (i = 0; code >= 0 && i < count; i += 2) + if (!(pdf_key_eq(&pairs[i], "/Page") || + pdf_key_eq(&pairs[i], "/View")) + ) + code = pdfmark_put_pair(pdev->Catalog, pairs + i); + return code; + } else + return pdfmark_put_pairs(pdev->Catalog, pairs, count); +} + +/* ---------------- Named object pdfmarks ---------------- */ + +/* [ /BBox [llx lly urx ury] /_objdef {obj} /BP pdfmark */ +static int +pdfmark_BP(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * objname) +{ + gs_rect bbox; + cos_stream_t *pcs; + int code; + gs_matrix ictm; + byte bbox_str[6 + 6 * 15], matrix_str[6 + 6 * 15]; + char chars[MAX_RECT_STRING + 1]; + int bbox_str_len, matrix_str_len; + stream s; + + if (objname == 0 || count != 2 || !pdf_key_eq(&pairs[0], "/BBox")) + return_error(gs_error_rangecheck); + code = gs_matrix_invert(pctm, &ictm); + if (code < 0) + return code; + if (pairs[1].size > MAX_RECT_STRING) + return_error(gs_error_limitcheck); + memcpy(chars, pairs[1].data, pairs[1].size); + chars[pairs[1].size] = 0; + if (sscanf(chars, "[%lg %lg %lg %lg]", + &bbox.p.x, &bbox.p.y, &bbox.q.x, &bbox.q.y) != 4) + return_error(gs_error_rangecheck); + if ((pdev->used_mask << 1) == 0) + return_error(gs_error_limitcheck); + code = start_XObject(pdev, pdev->params.CompressPages, &pcs); + if (code < 0) + return code; + { byte *s = gs_alloc_string(pdev->memory, objname->size, "pdfmark_PS"); + + if (s == NULL) + return_error(gs_error_VMerror); + memcpy(s, objname->data, objname->size); + pdev->objname.data = s; + pdev->objname.size = objname->size; + } + pcs->is_graphics = true; + gs_bbox_transform(&bbox, pctm, &bbox); + s_init(&s, NULL); + swrite_string(&s, bbox_str, sizeof(bbox_str)); + pprintg4(&s, "[%g %g %g %g]", + bbox.p.x, bbox.p.y, bbox.q.x, bbox.q.y); + bbox_str_len = stell(&s); + swrite_string(&s, matrix_str, sizeof(bbox_str)); + pprintg6(&s, "[%g %g %g %g %g %g]", + ictm.xx, ictm.xy, ictm.yx, ictm.yy, ictm.tx, ictm.ty); + matrix_str_len = stell(&s); + if ((code = cos_stream_put_c_strings(pcs, "/Type", "/XObject")) < 0 || + (code = cos_stream_put_c_strings(pcs, "/Subtype", "/Form")) < 0 || + (code = cos_stream_put_c_strings(pcs, "/FormType", "1")) < 0 || + (code = cos_dict_put_c_key_string(cos_stream_dict(pcs), "/BBox", + bbox_str, bbox_str_len)) < 0 || + (code = cos_dict_put_c_key_string(cos_stream_dict(pcs), "/Matrix", + matrix_str, matrix_str_len)) < 0 || + (code = cos_dict_put_c_key_object(cos_stream_dict(pcs), "/Resources", + COS_OBJECT(pdev->substream_Resources))) < 0 + ) + return code; + /* Don't add to local_named_objects untill the object is created + to prevent pending references, which may appear + if /PUT pdfmark executes before pdf_substitute_resource in pdfmark_EP + drops this object. + */ + pdev->FormDepth++; + return 0; +} + +/* [ /EP pdfmark */ +static int +pdfmark_EP(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * no_objname) +{ + int code; + pdf_resource_t *pres = pdev->accumulating_substream_resource; + gs_const_string objname = pdev->objname; + + /* We are not currently accumulating a resource, this suggests an /EP + * without an opening /BP. This is (obviously) an error, Distiller throws + * an 'undefined' error, so we will too. + */ + if (pres == NULL) + return_error(gs_error_undefined); + + if (pdev->CompatibilityLevel <= 1.7) { + code = pdf_add_procsets(pdev->substream_Resources, pdev->procsets); + if (code < 0) + return code; + } + code = pdf_exit_substream(pdev); + if (code < 0) + return code; + code = pdfmark_bind_named_object(pdev, &objname, &pres); + if (code < 0) + return 0; + gs_free_const_string(pdev->memory, objname.data, objname.size, "pdfmark_EP"); + pdev->FormDepth--; + return 0; +} + +/* [ {obj} /SP pdfmark */ +static int +pdfmark_SP(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * no_objname) +{ + cos_object_t *pco; /* stream */ + int code; + + if (count != 1) + return_error(gs_error_rangecheck); + if ((code = pdf_get_named(pdev, &pairs[0], cos_type_stream, &pco)) < 0) + return code; + if (pco->is_open || !pco->is_graphics) + return_error(gs_error_rangecheck); + code = pdf_open_contents(pdev, PDF_IN_STREAM); + if (code < 0) + return code; + pdf_put_matrix(pdev, "q ", pctm, "cm"); + pprintld1(pdev->strm, "/R%ld Do Q\n", pco->id); + pco->pres->where_used |= pdev->used_mask; + + code = pdf_add_resource(pdev, pdev->substream_Resources, "/XObject", pco->pres); + if (code < 0) + return code; + + return 0; +} + +/* [ /_objdef {array} /type /array /OBJ pdfmark */ +/* [ /_objdef {dict} /type /dict /OBJ pdfmark */ +/* [ /_objdef {stream} /type /stream /OBJ pdfmark */ +static int +pdfmark_OBJ(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * objname) +{ + cos_type_t cotype; + cos_object_t *pco; + bool stream = false; + int code; + + if (objname == 0 || count != 2 || !pdf_key_eq(&pairs[0], "/type")) + return_error(gs_error_rangecheck); + if (pdf_key_eq(&pairs[1], "/array")) + cotype = cos_type_array; + else if (pdf_key_eq(&pairs[1], "/dict")) + cotype = cos_type_dict; + else if ((stream = pdf_key_eq(&pairs[1], "/stream"))) + cotype = cos_type_stream; + else + return_error(gs_error_rangecheck); + if ((code = pdf_make_named(pdev, objname, cotype, &pco, true)) < 0) { + /* + * For Distiller compatibility, allows multiple /OBJ pdfmarks with + * the same name and type, even though the pdfmark specification + * doesn't say anything about this being legal. + */ + if (code == gs_error_rangecheck && + pdf_refer_named(pdev, objname, &pco) >= 0 && + cos_type(pco) == cotype + ) + return 0; /* already exists, but OK */ + return code; + } + if (stream) { + if (pdev->CompressStreams) + return setup_pdfmark_stream_compression((gx_device_psdf *)pdev, + (cos_stream_t *)pco); + else + return setup_pdfmark_stream_no_compression((gx_device_psdf *)pdev, + (cos_stream_t *)pco); + } + return 0; +} + +/* [ {array} index value /PUT pdfmark */ +/* Dictionaries are converted to .PUTDICT */ +/* Streams are converted to .PUTSTREAM */ +static int +pdfmark_PUT(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * no_objname) +{ + cos_object_t *pco; + cos_value_t value; + int code, index; + + if (count != 3) + return_error(gs_error_rangecheck); + if ((code = pdf_get_named(pdev, &pairs[0], cos_type_array, &pco)) < 0) + return code; + if ((code = pdfmark_scan_int(&pairs[1], &index)) < 0) + return code; + if (index < 0) + return_error(gs_error_rangecheck); + if (pco->written) + return_error(gs_error_rangecheck); + return cos_array_put((cos_array_t *)pco, index, + cos_string_value(&value, pairs[2].data, pairs[2].size)); +} + +/* [ {dict} key value ... /.PUTDICT pdfmark */ +/* [ {stream} key value ... /.PUTDICT pdfmark */ +/* + * Adobe's pdfmark documentation doesn't allow PUTDICT with a stream, + * but it's reasonable and unambiguous, and Acrobat Distiller accepts it, + * so we do too. + */ +static int +pdfmark_PUTDICT(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * no_objname) +{ + cos_object_t *pco; + int code, i; + + if ((code = pdf_refer_named(pdev, &pairs[0], &pco)) < 0) + return code; + if (cos_type(pco) != cos_type_dict && cos_type(pco) != cos_type_stream) + return_error(gs_error_typecheck); + if (pco->written) + return_error(gs_error_rangecheck); + + /* If this is a stream, and we are doing PDF/A output, and the stream + * is a Metadata stream, then we must not write it compressed. Bizarrely PDF/A + * excludes this. + */ + if (cos_type(pco) == cos_type_stream && pdev->PDFA) { + for (i=0;i<count;i++) { + if (pairs[i].size == 9 && strncmp((const char *)pairs[i].data, "/Metadata", 9) == 0) { + cos_dict_t *pcd = (cos_dict_t *)pco; + + /* Close the compressed stream */ + gs_free_object(pdev->pdf_memory, pco->input_strm, "free old stream, replacing with new stream"); + /* And create a new uncompressed stream */ + code = setup_pdfmark_stream_no_compression((gx_device_psdf *)pdev, + (cos_stream_t *)pco); + if (code < 0) + return code; + + /* We also need to remove any compression filters from the stream dictionary + * The only possible error here is that the key isn't found, which is possible + * and we don't care, so ignore the return code. + */ + cos_dict_delete_c_key(pcd, "/Filter"); + cos_dict_delete_c_key(pcd, "/DecodeParams"); + } + } + } + + return pdfmark_put_pairs((cos_dict_t *)pco, pairs + 1, count - 1); +} + +/* [ {stream} string ... /.PUTSTREAM pdfmark */ +static int +pdfmark_PUTSTREAM(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * no_objname) +{ + cos_object_t *pco; + int code, i; + uint l; + + if (count < 2) + return_error(gs_error_rangecheck); + if ((code = pdf_get_named(pdev, &pairs[0], cos_type_stream, &pco)) < 0) + return code; + if (!pco->is_open) + return_error(gs_error_rangecheck); + for (i = 1; i < count; ++i) + if (sputs(pco->input_strm, pairs[i].data, pairs[i].size, &l) != 0) + return_error(gs_error_ioerror); + if (pco->written) + return_error(gs_error_rangecheck); + return code; +} + +/* [ {array} value /APPEND pdfmark */ +static int +pdfmark_APPEND(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * objname) +{ + cos_object_t *pco; + cos_value_t value; + int code; + + if (count != 2) + return_error(gs_error_rangecheck); + if ((code = pdf_get_named(pdev, &pairs[0], cos_type_array, &pco)) < 0) + return code; + return cos_array_add((cos_array_t *)pco, + cos_string_value(&value, pairs[1].data, pairs[1].size)); +} + +/* [ {array} index value ... /.PUTINTERVAL pdfmark */ +static int +pdfmark_PUTINTERVAL(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * no_objname) +{ + cos_object_t *pco; + cos_value_t value; + int code, index, i; + + if (count < 2) + return_error(gs_error_rangecheck); + if ((code = pdf_get_named(pdev, &pairs[0], cos_type_array, &pco)) < 0) + return code; + if ((code = pdfmark_scan_int(&pairs[1], &index)) < 0) + return code; + if (index < 0) + return_error(gs_error_rangecheck); + for (i = 2; code >= 0 && i < count; ++i) + code = cos_array_put((cos_array_t *)pco, index + i - 2, + cos_string_value(&value, pairs[i].data, pairs[i].size)); + return code; +} + +/* [ {stream} /CLOSE pdfmark */ +static int +pdfmark_CLOSE(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * no_objname) +{ + cos_object_t *pco; + int code; + + if (count != 1) + return_error(gs_error_rangecheck); + if ((code = pdf_get_named(pdev, &pairs[0], cos_type_stream, &pco)) < 0) + return code; + if (!pco->is_open) + return_error(gs_error_rangecheck); + /* Currently we don't do anything special when closing a stream. */ + pco->is_open = false; + return 0; +} + +/* [ /NamespacePush pdfmark */ +static int +pdfmark_NamespacePush(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + if (count != 0) + return_error(gs_error_rangecheck); + return pdf_push_namespace(pdev); +} + +/* [ /NamespacePop pdfmark */ +static int +pdfmark_NamespacePop(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + if (count != 0) + return_error(gs_error_rangecheck); + cos_dict_objects_write(pdev->local_named_objects, pdev); + return pdf_pop_namespace(pdev); +} + +/* [ /_objdef {image} /NI pdfmark */ +static int +pdfmark_NI(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + cos_object_t *pco; + int code; + + if (objname == 0 || count != 0) + return_error(gs_error_rangecheck); + code = pdf_make_named(pdev, objname, cos_type_dict, &pco, true); + if (code < 0) + return code; + return cos_array_add_object(pdev->NI_stack, pco); +} + +/* ---------------- Named content pdfmarks ---------------- */ + +/* [ tag /MP pdfmark */ +static int +pdfmark_MP(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +/* [ tag propdict /DP pdfmark */ +static int +pdfmark_DP(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +/* [ tag /BMC pdfmark */ +static int +pdfmark_BMC(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + int code; + char *tag; + + if (count != 1) return_error(gs_error_rangecheck); + + tag = (char *)gs_alloc_bytes(pdev->memory, (pairs[0].size + 1) * sizeof(unsigned char), + "pdfmark_BMC"); + memcpy(tag, pairs[0].data, pairs[0].size); + tag[pairs[0].size] = 0x00; + + code = pdf_open_contents(pdev, PDF_IN_STREAM); + if (code < 0) return code; + + pprints1(pdev->strm, "%s BMC\n", tag); + + gs_free_object(pdev->memory, tag, "pdfmark_BMC"); + return 0; +} + +/* [ tag propdict /BDC pdfmark */ +static int +pdfmark_BDC(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + int code; + cos_object_t *pco; + char *cstring; + pdf_resource_t *pres; + + if (count != 2) return_error(gs_error_rangecheck); + + /* check tag for /Name object syntax */ + if ((pairs[0].data)[0] != '/') return_error(gs_error_rangecheck); + + /* check propdict for {object name} syntax */ + if (pdf_objname_is_valid(pairs[1].data, pairs[1].size)) + { + code = pdf_refer_named(pdev, &pairs[1], &pco); + if(code < 0) return code; + } + else /* << inline prop dict >> */ + { + /* strip << and >> */ + if ((pairs[1].data)[0]=='<'&&(pairs[1].data)[1]=='<') + { + pairs[1].data=&(pairs[1].data[2]); + pairs[1].size-=2; + } + else + return_error(gs_error_rangecheck); + + if ((pairs[1].data)[pairs[1].size-1]=='>'&&(pairs[1].data)[pairs[1].size-2]=='>') + pairs[1].size-=2; + + /* convert inline propdict to C string with object names replaced by refs */ + code = pdf_replace_names(pdev, &pairs[1], &pairs[1]); + if (code<0) return code; + cstring = (char *)gs_alloc_bytes(pdev->memory, (pairs[1].size + 1) * sizeof(unsigned char), + "pdfmark_BDC"); + memcpy(cstring, pairs[1].data, pairs[1].size); + cstring[pairs[1].size] = 0x00; + + code = pdf_make_named_dict(pdev, NULL, (cos_dict_t**) &pco, true); + if (code<0) return code; + + /* copy inline propdict to new object */ + code = cos_dict_put_c_strings((cos_dict_t*) pco, cstring, ""); + if(code < 0) return code; + COS_WRITE_OBJECT(pco, pdev, resourceProperties); + COS_RELEASE(pco, "pdfmark_BDC"); + gs_free_object(pdev->memory, cstring, "pdfmark_BDC"); + } + + pres = pdf_find_resource_by_resource_id(pdev, resourceProperties, pco->id); + if (pres==0){ + if ((code = pdf_alloc_resource(pdev, resourceProperties, pco->id, &(pco->pres), pco->id))<0) + return code; + } + + cstring = (char *)gs_alloc_bytes(pdev->memory, (pairs[0].size + 1) * sizeof(unsigned char), + "pdfmark_BDC"); + memcpy(cstring, pairs[0].data, pairs[0].size); + cstring[pairs[0].size] = 0x00; + + /* make sure we write to the correct stream */ + code = pdf_open_contents(pdev, PDF_IN_STREAM); + if (code < 0) return code; + + pprints1(pdev->strm, "%s", cstring); /* write tag */ + pprintld1(pdev->strm, "/R%ld BDC\n", pco->id); + pco->pres->where_used |= pdev->used_mask; + if ((code = pdf_add_resource(pdev, pdev->substream_Resources, "/Properties", pco->pres))<0) + return code; + + gs_free_object(pdev->memory, cstring, "pdfmark_BDC"); + return 0; +} + +/* [ /EMC pdfmark */ +static int +pdfmark_EMC(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + int code; + + code = pdf_open_contents(pdev, PDF_IN_STREAM); + if (code < 0) return code; + stream_puts(pdev->strm, "EMC\n"); + + return 0; +} + +/* ---------------- Document structure pdfmarks ---------------- */ + +/* [ newsubtype1 stdsubtype1 ... /StRoleMap pdfmark */ +static int +pdfmark_StRoleMap(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +/* [ class1 {attrobj1} ... /StClassMap pdfmark */ +static int +pdfmark_StClassMap(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +/* + * [ [/_objdef {objname}] /Subtype name [/Title string] [/Alt string] + * [/ID string] [/Class name] [/At index] [/Bookmark dict] [action_pairs...] + * /StPNE pdfmark + */ +static int +pdfmark_StPNE(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +/* [ [/Title string] [/Open bool] [action_pairs...] /StBookmarkRoot pdfmark */ +static int +pdfmark_StBookmarkRoot(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +/* [ [/E {elt}] /StPush pdfmark */ +static int +pdfmark_StPush(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +/* [ /StPop pdfmark */ +static int +pdfmark_StPop(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +/* [ /StPopAll pdfmark */ +static int +pdfmark_StPopAll(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +/* [ [/T tagname] [/At index] /StBMC pdfmark */ +static int +pdfmark_StBMC(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +/* [ [/P propdict] [/T tagname] [/At index] /StBDC pdfmark */ +static int +pdfmark_StBDC(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +/* [ /Obj {obj} [/At index] /StOBJ pdfmark */ +static int +pdfmark_StOBJ(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +/* [ /Obj {obj} /StAttr pdfmark */ +static int +pdfmark_StAttr(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +/* [ /StoreName name /StStore pdfmark */ +static int +pdfmark_StStore(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +/* [ /StoreName name /StRetrieve pdfmark */ +static int +pdfmark_StRetrieve(gx_device_pdf *pdev, gs_param_string *pairs, uint count, + const gs_matrix *pctm, const gs_param_string *objname) +{ + return 0; /****** NOT IMPLEMENTED YET ******/ +} + +static int +pdfmark_Metadata(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * objname) +{ + int i; + gs_param_string key; + char data[10] = "/Metadata"; + + if (pdev->CompatibilityLevel < 1.4) { + dmprintf(pdev->pdf_memory, "Cannot add Metadata to PDF files with version earlier than 1.4.\n"); + return 0; + } + if (pdev->PDFA != 0) + dmprintf(pdev->pdf_memory, "Warning: PDF/A output requires specific metadata, this pdfmark has overridden that,\n output conformance cannot be guaranteed\n"); + if (pdev->PDFX != 0) + dmprintf(pdev->pdf_memory, "Warning: PDF/X output requires specific metadata, this pdfmark has overridden that,\n output conformance cannot be guaranteed\n"); + + if(pdev->ExtensionMetadata) { + dmprintf(pdev->pdf_memory, "Extension metadata exists when /Metadata pdfmark executed, discarding extension metadata.\n"); + gs_free_object(pdev->pdf_memory->stable_memory, pdev->ExtensionMetadata, "Extension metadata discarded on /Metadata pdfmark"); + } + + if (!pdev->Catalog) { + gs_param_string nstr; + + param_string_from_string(nstr, "{Catalog}"); + pdf_create_named_dict(pdev, &nstr, &pdev->Catalog, 0L); + } + + key.data = (const byte *)&data; + key.size = 9; + + for (i = 0; i < count; i += 2) { + if (pdf_key_eq(&pairs[i], "{Catalog}")) { + return cos_dict_put_string(pdev->Catalog, key.data, key.size, + pairs[i+1].data, pairs[i+1].size); + } + } + return 0; +} + +static int +pdfmark_Ext_Metadata(gx_device_pdf * pdev, gs_param_string * pairs, uint count, + const gs_matrix * pctm, const gs_param_string * objname) +{ + int i, j=0; + + if (pdev->CompatibilityLevel < 1.4) { + dmprintf(pdev->pdf_memory, "Cannot add Metadata to PDF files with version earlier than 1.4.\n"); + return 0; + } + + if (cos_dict_find_c_key(pdev->Catalog, "/Metadata")) { + dmprintf(pdev->pdf_memory, "Cannot add extension to Metadata specified with the /Metadata pdfmark\n"); + return 0; + } + + if(pdev->ExtensionMetadata) { + dmprintf(pdev->pdf_memory, "Extension metadata already defined, discarding old data.\n"); + gs_free_object(pdev->pdf_memory->stable_memory, pdev->ExtensionMetadata, "Extension metadata"); + } + pdev->ExtensionMetadata = (char *)gs_alloc_bytes(pdev->pdf_memory->stable_memory, pairs[1].size - 1, "Extension metadata"); + memset(pdev->ExtensionMetadata, 0x00, pairs[1].size - 1); + for (i=1;i<pairs[1].size - 1;i++) { + if (pairs[1].data[i] == '\\') { + switch(pairs[1].data[i+1]) { + case '(': + case ')': + case '\\': + pdev->ExtensionMetadata[j++] = pairs[1].data[i+1]; + i++; + break; + case 'r': + pdev->ExtensionMetadata[j++] = 0x0D; + i++; + break; + case 'n': + pdev->ExtensionMetadata[j++] = 0x0A; + i++; + break; + case 't': + pdev->ExtensionMetadata[j++] = 0x09; + i++; + break; + case 'b': + pdev->ExtensionMetadata[j++] = 0x08; + i++; + break; + case 'f': + pdev->ExtensionMetadata[j++] = 0x0C; + i++; + break; + default: + if((pairs[1].data[i+1]) >= 0x30 && (pairs[1].data[i+1]) <= 0x39) { + pdev->ExtensionMetadata[j++] = (pairs[1].data[i+1] - 0x30) * 64 + (pairs[1].data[i+2] - 0x30) * 8 + (pairs[1].data[i+3] - 0x30); + i += 3; + } else + pdev->ExtensionMetadata[j++] = pairs[1].data[i]; + break; + } + } else + pdev->ExtensionMetadata[j++] = pairs[1].data[i]; + } + return 0; +} +/* ---------------- Dispatch ---------------- */ + +/* + * Define the pdfmark types we know about. + */ +static const pdfmark_name mark_names[] = +{ + /* Miscellaneous. */ + {"ANN", pdfmark_ANN, PDFMARK_NAMEABLE}, + {"LNK", pdfmark_LNK, PDFMARK_NAMEABLE}, + {"OUT", pdfmark_OUT, 0}, + {"ARTICLE", pdfmark_ARTICLE, 0}, + {"DEST", pdfmark_DEST, PDFMARK_NAMEABLE}, + {"EMBED", pdfmark_EMBED, PDFMARK_NAMEABLE}, + {"PS", pdfmark_PS, PDFMARK_NAMEABLE}, + {"PAGES", pdfmark_PAGES, 0}, + {"PAGE", pdfmark_PAGE, 0}, + {"PAGELABEL", pdfmark_PAGELABEL, 0}, + {"DOCINFO", pdfmark_DOCINFO, 0}, + {"DOCVIEW", pdfmark_DOCVIEW, 0}, + /* Named objects. */ + {"BP", pdfmark_BP, PDFMARK_NAMEABLE | PDFMARK_TRUECTM}, + {"EP", pdfmark_EP, 0}, + {"SP", pdfmark_SP, PDFMARK_ODD_OK | PDFMARK_KEEP_NAME | PDFMARK_TRUECTM}, + {"OBJ", pdfmark_OBJ, PDFMARK_NAMEABLE}, + {"PUT", pdfmark_PUT, PDFMARK_ODD_OK | PDFMARK_KEEP_NAME}, + {".PUTDICT", pdfmark_PUTDICT, PDFMARK_ODD_OK | PDFMARK_KEEP_NAME}, + {".PUTINTERVAL", pdfmark_PUTINTERVAL, PDFMARK_ODD_OK | PDFMARK_KEEP_NAME}, + {".PUTSTREAM", pdfmark_PUTSTREAM, PDFMARK_ODD_OK | PDFMARK_KEEP_NAME | + PDFMARK_NO_REFS}, + {"APPEND", pdfmark_APPEND, PDFMARK_KEEP_NAME}, + {"CLOSE", pdfmark_CLOSE, PDFMARK_ODD_OK | PDFMARK_KEEP_NAME}, + {"NamespacePush", pdfmark_NamespacePush, 0}, + {"NamespacePop", pdfmark_NamespacePop, 0}, + {"NI", pdfmark_NI, PDFMARK_NAMEABLE}, + /* Marked content. */ + {"MP", pdfmark_MP, PDFMARK_ODD_OK}, + {"DP", pdfmark_DP, 0}, + {"BMC", pdfmark_BMC, PDFMARK_ODD_OK}, + {"BDC", pdfmark_BDC, PDFMARK_NO_REFS}, + {"EMC", pdfmark_EMC, 0}, + /* Document structure. */ + {"StRoleMap", pdfmark_StRoleMap, 0}, + {"StClassMap", pdfmark_StClassMap, 0}, + {"StPNE", pdfmark_StPNE, PDFMARK_NAMEABLE}, + {"StBookmarkRoot", pdfmark_StBookmarkRoot, 0}, + {"StPush", pdfmark_StPush, 0}, + {"StPop", pdfmark_StPop, 0}, + {"StPopAll", pdfmark_StPopAll, 0}, + {"StBMC", pdfmark_StBMC, 0}, + {"StBDC", pdfmark_StBDC, 0}, + /* EMC is listed under "Marked content" above. */ + {"StOBJ", pdfmark_StOBJ, 0}, + {"StAttr", pdfmark_StAttr, 0}, + {"StStore", pdfmark_StStore, 0}, + {"StRetrieve", pdfmark_StRetrieve, 0}, + /* Metadata and extension */ + {"Metadata", pdfmark_Metadata, 0}, + {"Ext_Metadata", pdfmark_Ext_Metadata, 0}, + /* End of list. */ + {0, 0} +}; + +/* Process a pdfmark. */ +int +pdfmark_process(gx_device_pdf * pdev, const gs_param_string_array * pma) +{ + const gs_param_string *data = pma->data; + uint size = pma->size; + const gs_param_string *pts = &data[size - 1]; + const gs_param_string *objname = 0; + gs_matrix ctm; + const pdfmark_name *pmn; + int code = 0; + + { int cnt, len = pts[-1].size; + char buf[200]; /* 6 doubles should fit (%g == -0.14285714285714285e-101 = 25 chars) */ + + if (len > sizeof(buf) - 1) + return_error(gs_error_rangecheck); + memcpy(buf, pts[-1].data, len); + buf[len] = 0; + cnt = sscanf(buf, "[%g %g %g %g %g %g]", + &ctm.xx, &ctm.xy, &ctm.yx, &ctm.yy, &ctm.tx, &ctm.ty); + if (cnt != 6) + return_error(gs_error_rangecheck); + } + size -= 2; /* remove CTM & pdfmark name */ + for (pmn = mark_names; pmn->mname != 0; ++pmn) + if (pdf_key_eq(pts, pmn->mname)) { + gs_memory_t *mem = pdev->pdf_memory; + int odd_ok = (pmn->options & PDFMARK_ODD_OK) != 0; + gs_param_string *pairs; + int j; + + /* + * Our coordinate system is scaled so that user space is always + * default user space. Adjust the CTM to match this, except if this + * particular pdfmark requires the "true" CTM. + */ + if (pmn->options & PDFMARK_TRUECTM) + DO_NOTHING; + else { + double xscale = 72.0 / pdev->HWResolution[0], + yscale = 72.0 / pdev->HWResolution[1]; + ctm.xx *= xscale, ctm.xy *= yscale; + ctm.yx *= xscale, ctm.yy *= yscale; + ctm.tx *= xscale, ctm.ty *= yscale; + } + if (size & !odd_ok) + return_error(gs_error_rangecheck); + if (pmn->options & PDFMARK_NAMEABLE) { + /* Look for an object name. */ + for (j = 0; j < size; j += 2) { + if (pdf_key_eq(&data[j], "/_objdef")) { + objname = &data[j + 1]; + if (!pdf_objname_is_valid(objname->data, + objname->size) + ) + return_error(gs_error_rangecheck); + /* Save the pairs without the name. */ + size -= 2; + pairs = (gs_param_string *) + gs_alloc_byte_array(mem, size, + sizeof(gs_param_string), + "pdfmark_process(pairs)"); + if (!pairs) + return_error(gs_error_VMerror); + memcpy(pairs, data, j * sizeof(*data)); + memcpy(pairs + j, data + j + 2, + (size - j) * sizeof(*data)); + goto copied; + } + } + } + /* Save all the pairs. */ + pairs = (gs_param_string *) + gs_alloc_byte_array(mem, size, sizeof(gs_param_string), + "pdfmark_process(pairs)"); + if (!pairs) + return_error(gs_error_VMerror); + memcpy(pairs, data, size * sizeof(*data)); + copied: /* Substitute object references for names. */ + if (!(pmn->options & PDFMARK_NO_REFS)) { + for (j = (pmn->options & PDFMARK_KEEP_NAME ? 1 : 1 - odd_ok); + j < size; j += 2 - odd_ok + ) { + code = pdf_replace_names(pdev, &pairs[j], &pairs[j]); + if (code < 0) { + gs_free_object(mem, pairs, "pdfmark_process(pairs)"); + return code; + } + } + } + code = (*pmn->proc) (pdev, pairs, size, &ctm, objname); + gs_free_object(mem, pairs, "pdfmark_process(pairs)"); + break; + } + return code; +} |