[RFC][PATCH] Add API for metadata annotation to PDF surfaces
Peter TB Brett <[email protected]>
| Newsgroups | gmane.comp.lib.cairo |
|---|---|
| Message-ID | <[email protected]> |
This patch adds an API for PDF surfaces for adding PDF metadata. The public API encompasses: - metadata strings - internal links and URI links - destination dictionary entries - document outline entries Signed-off-by: Peter TB Brett <[email protected]> Signed-off-by: Ian Macphail <[email protected]> --- src/Makefile.sources | 6 src/cairo-pdf-ext-object.h | 200 +++++++++++ src/cairo-pdf-ext-private.h | 133 ++++++++ src/cairo-pdf-ext.c | 689 +++++++++++++++++++++++++++++++++++++++ src/cairo-pdf-surface-private.h | 9 + src/cairo-pdf-surface.c | 502 ++++++++++++++++++++++++++++ src/cairo-pdf.h | 33 ++ 7 files changed, 1555 insertions(+), 17 deletions(-) create mode 100644 src/cairo-pdf-ext-object.h create mode 100644 src/cairo-pdf-ext-private.h create mode 100644 src/cairo-pdf-ext.c diff --git a/src/Makefile.sources b/src/Makefile.sources index fac24d7..bd350a1 100644 --- a/src/Makefile.sources +++ b/src/Makefile.sources @@ -278,9 +278,9 @@ cairo_ps_sources = cairo-ps-surface.c _cairo_deflate_stream_sources = cairo-deflate-stream.c cairo_sources += $(_cairo_deflate_stream_sources) -cairo_pdf_headers = cairo-pdf.h -cairo_pdf_private = cairo-pdf-surface-private.h -cairo_pdf_sources = cairo-pdf-surface.c +cairo_pdf_headers = cairo-pdf.h cairo-pdf-ext-object.h +cairo_pdf_private = cairo-pdf-surface-private.h cairo-pdf-ext-private.h +cairo_pdf_sources = cairo-pdf-surface.c cairo-pdf-ext.c cairo_svg_headers = cairo-svg.h cairo_svg_private = cairo-svg-surface-private.h diff --git a/src/cairo-pdf-ext-object.h b/src/cairo-pdf-ext-object.h new file mode 100644 index 0000000..2dc321e --- /dev/null +++ b/src/cairo-pdf-ext-object.h @@ -0,0 +1,200 @@ +/* cairo - a vector graphics library with display and print output + * + * Copyright © 2016 LiveCode Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + * The Original Code is the cairo graphics library. + * + * The Initial Developer of the Original Code is University of Southern + * California. + * + * Contributor(s): + * Ian Macphail <[email protected]> + */ + +#ifndef CAIRO_PDF_EXT_OBJECT_H +#define CAIRO_PDF_EXT_OBJECT_H + +typedef enum _cairo_pdf_value_type +{ + // basic types + CAIRO_PDF_VALUE_TYPE_BOOLEAN, + CAIRO_PDF_VALUE_TYPE_INTEGER, + CAIRO_PDF_VALUE_TYPE_REAL, + CAIRO_PDF_VALUE_TYPE_STRING, + CAIRO_PDF_VALUE_TYPE_NAME, + CAIRO_PDF_VALUE_TYPE_ARRAY, + CAIRO_PDF_VALUE_TYPE_DICTIONARY, + CAIRO_PDF_VALUE_TYPE_STREAM, + CAIRO_PDF_VALUE_TYPE_NULL, + + // composite types + CAIRO_PDF_VALUE_TYPE_ACTION, + CAIRO_PDF_VALUE_TYPE_ANNOTATION, + CAIRO_PDF_VALUE_TYPE_DATE, + CAIRO_PDF_VALUE_TYPE_DEST, + CAIRO_PDF_VALUE_TYPE_REFERENCE, + CAIRO_PDF_VALUE_TYPE_OUTLINE_ENTRY, +} cairo_pdf_value_type_t; + +// currently, only link & uri action annotations are supported +typedef enum _cairo_pdf_action_type +{ + CAIRO_PDF_ACTION_TYPE_URI, +} cairo_pdf_action_type_t; + +typedef enum _cairo_pdf_annotation_type +{ + CAIRO_PDF_ANNOTATION_TYPE_LINK, +} cairo_pdf_annotation_type_t; + +typedef enum _cairo_pdf_dest_type +{ + CAIRO_PDF_DEST_TYPE_XYZ, + CAIRO_PDF_DEST_TYPE_FIT, + CAIRO_PDF_DEST_TYPE_FIT_H, + CAIRO_PDF_DEST_TYPE_FIT_V, + CAIRO_PDF_DEST_TYPE_FIT_R, + CAIRO_PDF_DEST_TYPE_FIT_B, + CAIRO_PDF_DEST_TYPE_FIT_BH, + CAIRO_PDF_DEST_TYPE_FIT_BV, +} cairo_pdf_dest_type_t; + +struct _cairo_pdf_value; + +typedef struct _cairo_pdf_array +{ + unsigned int size; + struct _cairo_pdf_value *elements; +} cairo_pdf_array_t; + +typedef struct _cairo_pdf_dictionary +{ + unsigned int size; + const char **keys; + struct _cairo_pdf_value *elements; +} cairo_pdf_dictionary_t; + +typedef struct _cairo_pdf_reference +{ + int id; + int generation; +} cairo_pdf_reference_t; + +typedef struct _cairo_pdf_action +{ + cairo_pdf_action_type_t type; + union + { + struct + { + const char *uri; + int is_map; + } uri; + }; +} cairo_pdf_action_t; + +typedef struct _cairo_pdf_annotation +{ + cairo_pdf_annotation_type_t type; + cairo_rectangle_t rect; + union + { + struct + { + const char *dest; + cairo_pdf_reference_t action; + const char *uri; + } link; + }; +} cairo_pdf_annotation_t; + +typedef struct _cairo_pdf_datetime +{ + int year, month, day; + int hour, minute, second; + int utc_minute_offset; +} cairo_pdf_datetime_t; + +typedef struct _cairo_pdf_dest +{ + cairo_pdf_dest_type_t type; + int page; + double top, left, bottom, right; + double zoom; +} cairo_pdf_dest_t; + +typedef struct _cairo_pdf_string +{ + char *_buffer; + + const char *data; + int length; +} cairo_pdf_string_t; + +typedef struct _cairo_pdf_outline_entry +{ + cairo_pdf_string_t title; + cairo_pdf_dest_t destination; + int depth; + int count; + int closed; + + int parent; + + int next, prev; + int first, last; +} cairo_pdf_outline_entry_t; + +typedef int cairo_pdf_boolean_t; +typedef int cairo_pdf_integer_t; +typedef double cairo_pdf_real_t; +typedef const char * cairo_pdf_name_t; + +typedef struct _cairo_pdf_value +{ + cairo_pdf_value_type_t type; + int id; + union + { + cairo_pdf_boolean_t boolean; + cairo_pdf_integer_t integer; + cairo_pdf_real_t real; + cairo_pdf_name_t name; + cairo_pdf_string_t string; + cairo_pdf_array_t array; + cairo_pdf_dictionary_t dictionary; + + cairo_pdf_action_t action; + cairo_pdf_annotation_t annotation; + cairo_pdf_datetime_t date; + cairo_pdf_dest_t dest; + cairo_pdf_reference_t reference; + cairo_pdf_outline_entry_t outline_entry; + }; +} cairo_pdf_value_t; + +#endif /* CAIRO_PDF_EXT_OBJECT_H */ + diff --git a/src/cairo-pdf-ext-private.h b/src/cairo-pdf-ext-private.h new file mode 100644 index 0000000..4bc7b33 --- /dev/null +++ b/src/cairo-pdf-ext-private.h @@ -0,0 +1,133 @@ +/* cairo - a vector graphics library with display and print output + * + * Copyright © 2016 LiveCode Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + * The Original Code is the cairo graphics library. + * + * The Initial Developer of the Original Code is University of Southern + * California. + * + * Contributor(s): + * Ian Macphail <[email protected]> + */ + +#ifndef CAIRO_PDF_EXT_PRIVATE_H +#define CAIRO_PDF_EXT_PRIVATE_H + +#include "cairo-compiler-private.h" +#include "cairo-types-private.h" + +#include "cairo-pdf-ext-object.h" + +cairo_private void +_cairo_pdf_value_init (cairo_pdf_value_t *object, + cairo_pdf_value_type_t type); + +cairo_private void +_cairo_pdf_value_array_init (cairo_pdf_array_t *object); + +cairo_private void +_cairo_pdf_value_array_finish (cairo_pdf_array_t *object); + +cairo_private void +_cairo_pdf_value_array_clear (cairo_pdf_array_t *object); + +cairo_private cairo_status_t +_cairo_pdf_value_array_append (cairo_pdf_array_t *array, + const cairo_pdf_value_t *value); + +cairo_private void +_cairo_pdf_value_dictionary_init (cairo_pdf_dictionary_t *object); + +cairo_private void +_cairo_pdf_value_dictionary_finish (cairo_pdf_dictionary_t *object); + +cairo_private cairo_status_t +_cairo_pdf_value_dictionary_set (cairo_pdf_dictionary_t *object, + const char *key, + const cairo_pdf_value_t *value); + +cairo_private void +_cairo_pdf_value_dest_set (cairo_pdf_dest_t *dest, + cairo_pdf_dest_type_t type, + int page, + double left, + double top, + double right, + double bottom, + double zoom); + +cairo_private void +_cairo_pdf_value_dest_set_xyz (cairo_pdf_dest_t *dest, + int page, + double left, + double top, + double zoom); + +cairo_private void +_cairo_pdf_value_string_init (cairo_pdf_string_t *string); + +cairo_private cairo_status_t +_cairo_pdf_value_string_copy_text (cairo_pdf_string_t *dest, + const char *text); + +cairo_private void +_cairo_pdf_value_string_finish (cairo_pdf_string_t *string); + +cairo_private void +_cairo_pdf_value_name_init (cairo_pdf_name_t *name); + +cairo_private void +_cairo_pdf_value_outline_entry_init (cairo_pdf_outline_entry_t *entry); + +cairo_private void +_cairo_pdf_value_outline_entry_finish (cairo_pdf_outline_entry_t *entry); + +cairo_private void +_cairo_pdf_output_stream_write_object (cairo_output_stream_t *stream, + const cairo_pdf_value_t *object); + +cairo_private void +_cairo_pdf_output_stream_write_string (cairo_output_stream_t *stream, + const cairo_pdf_string_t *string); + +cairo_private void +_cairo_pdf_output_stream_write_name (cairo_output_stream_t *stream, + const char *name); + +cairo_private void +_cairo_pdf_output_stream_write_dictionary (cairo_output_stream_t *stream, + const cairo_pdf_dictionary_t *dict); + +cairo_private void +_cairo_pdf_output_stream_write_date (cairo_output_stream_t *stream, + const cairo_pdf_datetime_t *date); + +cairo_private void +_cairo_pdf_output_stream_write_dest (cairo_output_stream_t *stream, + const cairo_pdf_dest_t *dest); + +#endif /* CAIRO_PDF_EXT_PRIVATE_H */ diff --git a/src/cairo-pdf-ext.c b/src/cairo-pdf-ext.c new file mode 100644 index 0000000..108420b --- /dev/null +++ b/src/cairo-pdf-ext.c @@ -0,0 +1,689 @@ +/* cairo - a vector graphics library with display and print output + * + * Copyright © 2016 LiveCode Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + * The Original Code is the cairo graphics library. + * + * The Initial Developer of the Original Code is University of Southern + * California. + * + * Contributor(s): + * Ian Macphail <[email protected]> + */ + +#include "cairoint.h" + +#include "cairo-pdf-ext-object.h" +#include "cairo-pdf-ext-private.h" +#include "cairo-output-stream-private.h" + +#include <string.h> +#include <stdio.h> + + +struct _escape_char_map { + char special_char; + char escape_char; +}; + +static struct _escape_char_map ESCAPE_CHARS[] = { + {'\n', 'n'}, + {'\r', 'r'}, + {'\t', 't'}, + {'\b', 'b'}, + {'\f', 'f'}, + {'(', '('}, + {')', ')'}, + {'\\', '\\'}, +}; + +static cairo_bool_t +_cairo_pdf_escape_in_string (char text_char, + char *escape_char) +{ + size_t i; + for (i = 0; i < (sizeof (ESCAPE_CHARS) / sizeof (ESCAPE_CHARS[0])); i++) { + if (text_char == ESCAPE_CHARS[i].special_char) { + *escape_char = ESCAPE_CHARS[i].escape_char; + return TRUE; + } + } + + return FALSE; +} + +static cairo_bool_t +_cairo_pdf_allowed_in_name (char text_char) +{ + return text_char != '#' && + (text_char >= '!' && text_char <= '~'); +} + + +//////////////////////////////////////////////////////////////////////////////// +// +// streaming +// + +static void +_cairo_pdf_output_stream_write_text (cairo_output_stream_t *stream, + const char *text, + int length) +{ + char escape_seq; + int i; + + _cairo_output_stream_printf (stream, "("); + if (text != NULL) { + for (i = 0; i < length; i++) { + if (_cairo_pdf_escape_in_string (text[i], &escape_seq)) { + _cairo_output_stream_printf (stream, "\\"); + _cairo_output_stream_write (stream, &escape_seq, 1); + } else { + _cairo_output_stream_write (stream, &text[i], 1); + } + } + } + _cairo_output_stream_printf (stream, ")"); +} + +cairo_private void +_cairo_pdf_output_stream_write_string (cairo_output_stream_t *stream, + const cairo_pdf_string_t *string) +{ + _cairo_pdf_output_stream_write_text (stream, + string->data, + string->length); +} + +cairo_private void +_cairo_pdf_output_stream_write_name (cairo_output_stream_t *stream, + const char *name) +{ + const char *in_ptr = name; + + _cairo_output_stream_printf (stream, "/"); + if (in_ptr != NULL) { + for (in_ptr = name; *in_ptr != '0'; ++in_ptr) { + if (!_cairo_pdf_allowed_in_name (*in_ptr)) { + _cairo_output_stream_printf (stream, "#%02hhx", *in_ptr); + } else { + _cairo_output_stream_write (stream, in_ptr, 1); + } + } + } +} + +static void +_cairo_pdf_output_stream_write_array (cairo_output_stream_t *stream, + const cairo_pdf_array_t *array) +{ + unsigned int i; + _cairo_output_stream_printf (stream, "["); + for (i = 0; i < array->size; i++) { + if (i > 0) + _cairo_output_stream_printf (stream, " "); + _cairo_pdf_output_stream_write_object (stream, &array->elements[i]); + } + _cairo_output_stream_printf (stream, "]"); +} + +cairo_private void +_cairo_pdf_output_stream_write_dictionary (cairo_output_stream_t *stream, + const cairo_pdf_dictionary_t *dict) +{ + unsigned int i; + _cairo_output_stream_printf (stream, "<< "); + for (i = 0; i < dict->size; i++) { + _cairo_pdf_output_stream_write_name (stream, dict->keys[i]); + _cairo_output_stream_printf (stream, " "); + _cairo_pdf_output_stream_write_object (stream, &dict->elements[i]); + _cairo_output_stream_printf (stream, "\n"); + } + _cairo_output_stream_printf (stream, ">>\n"); +} + +static void +_cairo_pdf_output_stream_write_reference (cairo_output_stream_t *stream, + const cairo_pdf_reference_t *ref) +{ + _cairo_output_stream_printf (stream, "%d %d R", ref->id, ref->generation); +} + +static void +_cairo_pdf_output_stream_write_action (cairo_output_stream_t *stream, + const cairo_pdf_action_t *action) +{ + switch (action->type) { + case CAIRO_PDF_ACTION_TYPE_URI: + _cairo_output_stream_printf (stream, + "<< /Type /Action\n" + " /S /URI\n" + " /URI "); + _cairo_pdf_output_stream_write_text (stream, + action->uri.uri, + strlen(action->uri.uri)); + if (action->uri.is_map) { + _cairo_output_stream_printf (stream, + "\n" + " /IsMap true"); + } + _cairo_output_stream_printf (stream, + "\n" + ">>\n"); + break; + default: + /* Only URI actions are currently supported */ + break; + } +} + +static void +_cairo_pdf_output_stream_write_annotation (cairo_output_stream_t *stream, + const cairo_pdf_annotation_t *annotation) +{ + switch (annotation->type) { + case CAIRO_PDF_ANNOTATION_TYPE_LINK: + if (annotation->link.dest != NULL) { + _cairo_output_stream_printf (stream, + "<< /Type /Annot\n" + " /Subtype /Link\n" + " /Rect [%f %f %f %f]\n" + " /Border [0 0 0]\n" + " /Dest ", + annotation->rect.x, + annotation->rect.y, + annotation->rect.x + annotation->rect.width, + annotation->rect.y + annotation->rect.height); + _cairo_pdf_output_stream_write_name (stream, annotation->link.dest); + _cairo_output_stream_printf (stream, + "\n" + ">>\n"); + } + else if (annotation->link.uri != NULL) { + _cairo_output_stream_printf (stream, + "<< /Type /Annot\n" + " /Subtype /Link\n" + " /Rect [%f %f %f %f]\n" + " /Border [0 0 0]\n" + " /A %d %d R\n" + ">>\n", + annotation->rect.x, + annotation->rect.y, + annotation->rect.x + annotation->rect.width, + annotation->rect.y + annotation->rect.height, + annotation->link.action.id, + annotation->link.action.generation); + } + } +} + +void +_cairo_pdf_output_stream_write_date (cairo_output_stream_t *stream, + const cairo_pdf_datetime_t *date) +{ + _cairo_output_stream_printf (stream, + "(%04d%02d%02d%02d%02d%02d", + date->year, + date->month, + date->day, + date->hour, + date->minute, + date->second); + + if (date->utc_minute_offset == 0) { + _cairo_output_stream_printf (stream, "Z"); + } else { + int32_t utc_offset; + if (date->utc_minute_offset > 0) { + utc_offset = date->utc_minute_offset; + _cairo_output_stream_printf (stream, "+"); + } else { + utc_offset = -date->utc_minute_offset; + _cairo_output_stream_printf (stream, "-"); + } + + _cairo_output_stream_printf (stream, "%02d", utc_offset / 60); + if (utc_offset % 60 != 0) + _cairo_output_stream_printf (stream, "%02d", utc_offset % 60); + } + _cairo_output_stream_printf (stream, ")"); +} + +static const char *DEST_TYPE_STRINGS[] = { + "XYZ", + "Fit", + "FitH", + "FitV", + "FitR", + "FitB", + "FitBH", + "FitBV", +}; + +void +_cairo_pdf_output_stream_write_dest (cairo_output_stream_t *stream, + const cairo_pdf_dest_t *dest) +{ + _cairo_output_stream_printf (stream, + "[%d /%s", + dest->page, + DEST_TYPE_STRINGS[dest->type]); + + switch (dest->type) { + case CAIRO_PDF_DEST_TYPE_XYZ: + /* args: left top zoom */ + _cairo_output_stream_printf (stream, + " %f %f %f", + dest->left, + dest->top, + dest->zoom); + break; + case CAIRO_PDF_DEST_TYPE_FIT_H: + case CAIRO_PDF_DEST_TYPE_FIT_BH: + /* args: top */ + _cairo_output_stream_printf (stream, " %f", dest->top); + break; + case CAIRO_PDF_DEST_TYPE_FIT_V: + case CAIRO_PDF_DEST_TYPE_FIT_BV: + /* args: left */ + _cairo_output_stream_printf (stream, " %f", dest->left); + break; + case CAIRO_PDF_DEST_TYPE_FIT_R: + /* args: left bottom right top */ + _cairo_output_stream_printf (stream, + " %f %f %f %f", + dest->left, + dest->bottom, + dest->right, + dest->top); + break; + case CAIRO_PDF_DEST_TYPE_FIT: + case CAIRO_PDF_DEST_TYPE_FIT_B: + /* no args */ + break; + } + + _cairo_output_stream_printf (stream, "]"); +} + +void +_cairo_pdf_output_stream_write_object (cairo_output_stream_t *stream, + const cairo_pdf_value_t *object) +{ + if (object != NULL) { + switch (object->type) { + case CAIRO_PDF_VALUE_TYPE_STRING: + _cairo_pdf_output_stream_write_string (stream, &object->string); + break; + case CAIRO_PDF_VALUE_TYPE_NAME: + _cairo_pdf_output_stream_write_name (stream, object->name); + break; + case CAIRO_PDF_VALUE_TYPE_ARRAY: + _cairo_pdf_output_stream_write_array (stream, &object->array); + break; + case CAIRO_PDF_VALUE_TYPE_DICTIONARY: + _cairo_pdf_output_stream_write_dictionary (stream, &object->dictionary); + break; + case CAIRO_PDF_VALUE_TYPE_REFERENCE: + _cairo_pdf_output_stream_write_reference (stream, &object->reference); + break; + case CAIRO_PDF_VALUE_TYPE_ACTION: + _cairo_pdf_output_stream_write_action (stream, &object->action); + break; + case CAIRO_PDF_VALUE_TYPE_ANNOTATION: + _cairo_pdf_output_stream_write_annotation (stream, &object->annotation); + break; + case CAIRO_PDF_VALUE_TYPE_DATE: + _cairo_pdf_output_stream_write_date (stream, &object->date); + break; + case CAIRO_PDF_VALUE_TYPE_DEST: + _cairo_pdf_output_stream_write_dest (stream, &object->dest); + break; + case CAIRO_PDF_VALUE_TYPE_BOOLEAN: + case CAIRO_PDF_VALUE_TYPE_INTEGER: + case CAIRO_PDF_VALUE_TYPE_REAL: + case CAIRO_PDF_VALUE_TYPE_STREAM: + case CAIRO_PDF_VALUE_TYPE_NULL: + case CAIRO_PDF_VALUE_TYPE_OUTLINE_ENTRY: + /* TODO handle output for these types */ + assert(0); + break; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +// object initialization +// + +void +_cairo_pdf_value_string_init (cairo_pdf_string_t *string) +{ + string->_buffer = NULL; + string->data = NULL; + string->length = 0; +} + +void +_cairo_pdf_value_name_init (cairo_pdf_name_t *name) +{ + *name = NULL; +} + +void +_cairo_pdf_value_array_init (cairo_pdf_array_t *array) +{ + array->elements = NULL; + array->size = 0; +} + +void +_cairo_pdf_value_dictionary_init (cairo_pdf_dictionary_t *dict) +{ + dict->keys = NULL; + dict->elements = NULL; + dict->size = 0; +} + +void +_cairo_pdf_value_outline_entry_init (cairo_pdf_outline_entry_t *entry) +{ + entry->depth = 1; + entry->closed = FALSE; + _cairo_pdf_value_string_init (&entry->title); + _cairo_pdf_value_dest_set_xyz (&entry->destination, 0, 0, 0, 0); + entry->parent = -1; + entry->prev = -1; + entry->next = -1; + entry->first = -1; + entry->last = -1; +} + +void +_cairo_pdf_value_init (cairo_pdf_value_t *object, + cairo_pdf_value_type_t object_type) +{ + object->type = object_type; + switch (object_type) { + case CAIRO_PDF_VALUE_TYPE_ARRAY: + _cairo_pdf_value_array_init (&object->array); + break; + case CAIRO_PDF_VALUE_TYPE_DICTIONARY: + _cairo_pdf_value_dictionary_init (&object->dictionary); + break; + case CAIRO_PDF_VALUE_TYPE_STRING: + _cairo_pdf_value_string_init (&object->string); + break; + case CAIRO_PDF_VALUE_TYPE_NAME: + _cairo_pdf_value_name_init (&object->name); + break; + case CAIRO_PDF_VALUE_TYPE_OUTLINE_ENTRY: + _cairo_pdf_value_outline_entry_init (&object->outline_entry); + break; + case CAIRO_PDF_VALUE_TYPE_BOOLEAN: + case CAIRO_PDF_VALUE_TYPE_INTEGER: + case CAIRO_PDF_VALUE_TYPE_REAL: + case CAIRO_PDF_VALUE_TYPE_STREAM: + case CAIRO_PDF_VALUE_TYPE_NULL: + case CAIRO_PDF_VALUE_TYPE_ACTION: + case CAIRO_PDF_VALUE_TYPE_ANNOTATION: + case CAIRO_PDF_VALUE_TYPE_DATE: + case CAIRO_PDF_VALUE_TYPE_DEST: + case CAIRO_PDF_VALUE_TYPE_REFERENCE: + /* TODO do these need initialisation? */ + assert (FALSE); + break; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +// object finalization +// + +void +_cairo_pdf_value_array_finish (cairo_pdf_array_t *array) +{ + free (array->elements); +} + +void +_cairo_pdf_value_dictionary_finish (cairo_pdf_dictionary_t *dict) +{ + free (dict->keys); + free (dict->elements); +} + +void +_cairo_pdf_value_outline_entry_finish (cairo_pdf_outline_entry_t *entry) +{ + _cairo_pdf_value_string_finish (&entry->title); +} + +void +_cairo_pdf_value_string_finish (cairo_pdf_string_t *string) +{ + if (string->_buffer != NULL) + free (string->_buffer); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// object set functions +// + +cairo_private void +_cairo_pdf_value_dest_set (cairo_pdf_dest_t *dest, + cairo_pdf_dest_type_t dest_type, + int page, + double left, + double top, + double right, + double bottom, + double zoom) +{ + dest->type = dest_type; + dest->page = page; + dest->left = left; + dest->top = top; + dest->right = right; + dest->bottom = bottom; + dest->zoom = zoom; +} + +cairo_private void +_cairo_pdf_value_dest_set_xyz (cairo_pdf_dest_t *dest, + int page, + double left, + double top, + double zoom) +{ + _cairo_pdf_value_dest_set (dest, + CAIRO_PDF_DEST_TYPE_XYZ, + page, + left, top, + 0, 0, + zoom); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// string functions +// + +cairo_status_t +_cairo_pdf_value_string_copy_text (cairo_pdf_string_t *dest, + const char *text) +{ + int is_unicode = FALSE; + const unsigned char *str_ptr = (const unsigned char*) text; + uint16_t *utf16_data = NULL; + int item_count, status; + int i; + + while (*str_ptr != '0' && !is_unicode) { + if (*str_ptr >= 128) + is_unicode = TRUE; + str_ptr++; + } + + if (is_unicode) { + status = _cairo_utf8_to_utf16 (text, -1, &utf16_data, &item_count); + if (status != CAIRO_STATUS_SUCCESS) + return status; + + dest->_buffer = _cairo_malloc (2 + item_count * 2); + if (dest->_buffer == NULL) { + free (utf16_data); + return CAIRO_STATUS_NO_MEMORY; + } + + dest->length = 2 + item_count * 2; + + /* _cario_utf8_to_utf16 outputs as LE with no bytemark, PDF + * requires BE with bytemark */ + dest->_buffer[0] = 0xFE; + dest->_buffer[1] = 0xFF; + for (i = 0; i < item_count; i++) { + dest->_buffer[2 + i*2] = utf16_data[i] >> 8; + dest->_buffer[2 + i*2 + 1] = utf16_data[i] & 0xFF; + } + dest->data = dest->_buffer; + + } else { + dest->length = strlen (text); + dest->_buffer = (char *) malloc (dest->length); + if (dest->_buffer == NULL) + return CAIRO_STATUS_NO_MEMORY; + memcpy (dest->_buffer, text, dest->length); + dest->data = dest->_buffer; + } + + return CAIRO_STATUS_SUCCESS; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// array functions +// + +static cairo_bool_t +_cairo_pdf_value_array_reserve (cairo_pdf_array_t *dict, + int32_t count) +{ + dict->elements = (cairo_pdf_value_t *) realloc (dict->elements, sizeof(cairo_pdf_value_t) * (dict->size + count)); + + return (dict->elements != NULL); +} + +cairo_status_t +_cairo_pdf_value_array_append (cairo_pdf_array_t *array, + const cairo_pdf_value_t *value) +{ + if (!_cairo_pdf_value_array_reserve (array, 1)) + return CAIRO_STATUS_NO_MEMORY; + array->elements[array->size] = *value; + array->size += 1; + return CAIRO_STATUS_SUCCESS; +} + +void +_cairo_pdf_value_array_clear (cairo_pdf_array_t *array) +{ + free (array->elements); + array->elements = NULL; + array->size = 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// dictionary functions +// + +static cairo_bool_t +_cairo_pdf_value_dictionary_index_of (cairo_pdf_dictionary_t *dict, + const char *key, + int32_t *index); +static cairo_bool_t +_cairo_pdf_value_dictionary_reserve (cairo_pdf_dictionary_t *dict, + int32_t count); + +static cairo_bool_t +_cairo_pdf_value_dictionary_index_of (cairo_pdf_dictionary_t *dict, + const char *key, + int32_t *index) +{ + uint32_t i; + + for (i = 0; i < dict->size; i++) { + if (strcmp (key, dict->keys[i]) == 0) { + *index = i; + return TRUE; + } + } + return FALSE; +} + +cairo_bool_t +_cairo_pdf_value_dictionary_reserve (cairo_pdf_dictionary_t *dict, + int32_t count) +{ + dict->keys = (const char **) realloc (dict->keys, sizeof (char *) * (dict->size + count)); + if (dict->keys == NULL) + return FALSE; + + dict->elements = (cairo_pdf_value_t *) realloc (dict->elements, sizeof (cairo_pdf_value_t) * (dict->size + count)); + if (dict->elements == NULL) { + free (dict->keys); + return FALSE; + } + + return TRUE; +} + +cairo_status_t +_cairo_pdf_value_dictionary_set (cairo_pdf_dictionary_t *dict, + const char *key, + const cairo_pdf_value_t *value) +{ + int32_t index; + + if (_cairo_pdf_value_dictionary_index_of (dict, key, &index)) { + dict->elements[index] = *value; + return CAIRO_STATUS_SUCCESS; + + } else { + if (!_cairo_pdf_value_dictionary_reserve (dict, 1)) + return CAIRO_STATUS_NO_MEMORY; + dict->keys[dict->size] = key; + dict->elements[dict->size] = *value; + dict->size += 1; + return CAIRO_STATUS_SUCCESS; + } +} + diff --git a/src/cairo-pdf-surface-private.h b/src/cairo-pdf-surface-private.h index 0229dbc..52ff6c6 100644 --- a/src/cairo-pdf-surface-private.h +++ b/src/cairo-pdf-surface-private.h @@ -37,6 +37,7 @@ * Kristian Høgsberg <[email protected]> * Carl Worth <[email protected]> * Adrian Johnson <[email protected]> + * Ian Macphail <[email protected]> */ #ifndef CAIRO_PDF_SURFACE_PRIVATE_H @@ -159,6 +160,14 @@ typedef struct _cairo_pdf_surface cairo_pdf_surface_t; struct _cairo_pdf_surface { cairo_surface_t base; + cairo_pdf_value_t metadata; + cairo_pdf_value_t dests; + cairo_pdf_value_t annotations; + cairo_pdf_value_t annotation_ids; + + cairo_array_t actions; + cairo_array_t outline_entries; + /* Prefer the name "output" here to avoid confusion over the * structure within a PDF document known as a "stream". */ cairo_output_stream_t *output; diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c index 944e9d6..52207b6 100644 --- a/src/cairo-pdf-surface.c +++ b/src/cairo-pdf-surface.c @@ -4,6 +4,7 @@ * Copyright © 2004 Red Hat, Inc * Copyright © 2006 Red Hat, Inc * Copyright © 2007, 2008 Adrian Johnson + * Copyright © 2016 LiveCode Ltd * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -37,6 +38,7 @@ * Kristian Høgsberg <[email protected]> * Carl Worth <[email protected]> * Adrian Johnson <[email protected]> + * Ian Macphail <[email protected]> */ #define _BSD_SOURCE /* for snprintf() */ @@ -47,6 +49,8 @@ #include "cairo-pdf-operators-private.h" #include "cairo-pdf-shading-private.h" +#include "cairo-pdf-ext-private.h" + #include "cairo-array-private.h" #include "cairo-analysis-surface-private.h" #include "cairo-composite-rectangles-private.h" @@ -153,6 +157,13 @@ static cairo_bool_t _cairo_pdf_surface_get_extents (void *abstract_surface, cairo_rectangle_int_t *rectangle); +static cairo_pdf_resource_t +_cairo_pdf_surface_write_dests (cairo_pdf_surface_t *surface); +static cairo_pdf_resource_t +_cairo_pdf_surface_write_outlines (cairo_pdf_surface_t *surface); +static cairo_status_t +_cairo_pdf_surface_write_page_annotations (cairo_pdf_surface_t *surface, cairo_pdf_array_t *p_annotations, cairo_pdf_array_t *p_ids); + /** * CAIRO_HAS_PDF_SURFACE: * @@ -371,6 +382,14 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t *output, CAIRO_CONTENT_COLOR_ALPHA, TRUE); /* is_vector */ + _cairo_pdf_value_init (&surface->metadata, CAIRO_PDF_VALUE_TYPE_DICTIONARY); + _cairo_pdf_value_init (&surface->dests, CAIRO_PDF_VALUE_TYPE_DICTIONARY); + _cairo_pdf_value_init (&surface->annotations, CAIRO_PDF_VALUE_TYPE_ARRAY); + _cairo_pdf_value_init (&surface->annotation_ids, CAIRO_PDF_VALUE_TYPE_ARRAY); + + _cairo_array_init (&surface->actions, sizeof (cairo_pdf_object_t)); + _cairo_array_init (&surface->outline_entries, sizeof (cairo_pdf_outline_entry_t)); + surface->output = output; surface->width = width; surface->height = height; @@ -716,7 +735,7 @@ cairo_pdf_surface_set_size (cairo_surface_t *surface, static void _cairo_pdf_surface_clear (cairo_pdf_surface_t *surface) { - int i, size; + unsigned int i, size; cairo_pdf_pattern_t *pattern; cairo_pdf_source_surface_t *src_surface; cairo_pdf_smask_group_t *group; @@ -742,6 +761,8 @@ _cairo_pdf_surface_clear (cairo_pdf_surface_t *surface) } _cairo_array_truncate (&surface->smask_groups, 0); _cairo_array_truncate (&surface->knockout_group, 0); + + _cairo_pdf_value_array_clear (&surface->annotations.array); } static void @@ -2064,6 +2085,7 @@ _cairo_pdf_surface_finish (void *abstract_surface) cairo_status_t status, status2; int size, i; cairo_pdf_jbig2_global_t *global; + cairo_pdf_outline_entry_t *entry; status = surface->base.status; if (status == CAIRO_STATUS_SUCCESS) @@ -2132,6 +2154,20 @@ _cairo_pdf_surface_finish (void *abstract_surface) _cairo_pdf_surface_clear (surface); _cairo_pdf_group_resources_fini (&surface->resources); + _cairo_pdf_value_dictionary_finish (&surface->metadata.dictionary); + _cairo_pdf_value_dictionary_finish (&surface->dests.dictionary); + _cairo_pdf_value_array_finish (&surface->annotations.array); + _cairo_pdf_value_array_finish (&surface->annotation_ids.array); + + _cairo_array_fini (&surface->actions); + + size = _cairo_array_num_elements (&surface->outline_entries); + for (i = 0; i < size; i++) { + entry = (cairo_pdf_outline_entry_t *) _cairo_array_index (&surface->outline_entries, i); + _cairo_pdf_value_outline_entry_finish (entry); + } + _cairo_array_fini (&surface->outline_entries); + _cairo_array_fini (&surface->objects); _cairo_array_fini (&surface->pages); _cairo_array_fini (&surface->rgb_linear_functions); @@ -4726,15 +4762,9 @@ _cairo_pdf_surface_write_info (cairo_pdf_surface_t *surface) if (info.id == 0) return info; - _cairo_output_stream_printf (surface->output, - "%d 0 obj\n" - "<< /Creator (cairo %s (http://cairographics.org))\n" - " /Producer (cairo %s (http://cairographics.org))\n" - ">>\n" - "endobj\n", - info.id, - cairo_version_string (), - cairo_version_string ()); + _cairo_output_stream_printf (surface->output, "%d 0 obj\n", info.id); + _cairo_pdf_output_stream_write_object (surface->output, &surface->metadata); + _cairo_output_stream_printf (surface->output, "endobj\n"); return info; } @@ -6057,7 +6087,10 @@ BAIL: static cairo_pdf_resource_t _cairo_pdf_surface_write_catalog (cairo_pdf_surface_t *surface) { - cairo_pdf_resource_t catalog; + cairo_pdf_resource_t catalog, dests, outlines; + + dests = _cairo_pdf_surface_write_dests (surface); + outlines = _cairo_pdf_surface_write_outlines (surface); catalog = _cairo_pdf_surface_new_object (surface); if (catalog.id == 0) @@ -6067,10 +6100,14 @@ _cairo_pdf_surface_write_catalog (cairo_pdf_surface_t *surface) "%d 0 obj\n" "<< /Type /Catalog\n" " /Pages %d 0 R\n" + " /Dests %d 0 R\n" + " /Outlines %d 0 R\n" ">>\n" "endobj\n", catalog.id, - surface->pages_resource.id); + surface->pages_resource.id, + dests.id, + outlines.id); return catalog; } @@ -6492,6 +6529,10 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface) return status; } + status = _cairo_pdf_surface_write_page_annotations (surface, &surface->annotations.array, &surface->annotation_ids.array); + if (unlikely (status)) + return status; + page = _cairo_pdf_surface_new_object (surface); if (page.id == 0) return _cairo_error (CAIRO_STATUS_NO_MEMORY); @@ -6509,8 +6550,7 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface) " /CS /DeviceRGB\n" " >>\n" " /Resources %d 0 R\n" - ">>\n" - "endobj\n", + " /Annots ", page.id, surface->pages_resource.id, surface->width, @@ -6518,6 +6558,11 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface) surface->content.id, surface->content_resources.id); + _cairo_pdf_output_stream_write_object (surface->output, &surface->annotation_ids); + _cairo_output_stream_printf (surface->output, "\n" + ">>\n" + "endobj\n"); + status = _cairo_array_append (&surface->pages, &page); if (unlikely (status)) return status; @@ -7932,6 +7977,435 @@ _cairo_pdf_surface_set_paginated_mode (void *abstract_surface, } } +void +cairo_pdf_surface_set_metadata (cairo_surface_t *surface, + const char *key, + cairo_pdf_value_t *value) +{ + cairo_pdf_surface_t *pdf_surface = NULL; /* hide compiler warning */ + cairo_status_t status; + + if (!_extract_pdf_surface (surface, &pdf_surface)) { + return; + } + + status = _cairo_pdf_value_dictionary_set (&pdf_surface->metadata.dictionary, + key, value); + if (unlikely (status)) { + status = _cairo_surface_set_error (surface, status); + return; + } +} + +void +cairo_pdf_surface_add_destination (cairo_surface_t *surface, + const char *name, + double x, + double y) +{ + cairo_pdf_surface_t *pdf_surface = NULL; /* hide compiler warning */ + cairo_status_t status; + cairo_pdf_value_t t_dest; + + if (!_extract_pdf_surface (surface, &pdf_surface)) { + return; + } + + t_dest.type = CAIRO_PDF_VALUE_TYPE_DEST; + _cairo_pdf_value_dest_set_xyz (&t_dest.dest, + _cairo_array_num_elements(&pdf_surface->pages), + x, + pdf_surface->height - y, + 0); + + status = _cairo_pdf_value_dictionary_set (&pdf_surface->dests.dictionary, + name, &t_dest); + if (unlikely (status)) { + status = _cairo_surface_set_error (surface, status); + return; + } +} + +void +cairo_pdf_surface_add_goto_link (cairo_surface_t *surface, + cairo_rectangle_t area, + const char *name) +{ + cairo_pdf_surface_t *pdf_surface = NULL; /* hide compiler warning */ + cairo_status_t status; + cairo_pdf_value_t annotation; + + if (!_extract_pdf_surface (surface, &pdf_surface)) { + return; + } + + annotation.type = CAIRO_PDF_VALUE_TYPE_ANNOTATION; + annotation.annotation.type = CAIRO_PDF_ANNOTATION_TYPE_LINK; + annotation.annotation.link.dest = name; + annotation.annotation.rect.x = area.x; + annotation.annotation.rect.y = pdf_surface->height - (area.y + area.height); + annotation.annotation.rect.width = area.width; + annotation.annotation.rect.height = area.height; + + status = _cairo_pdf_value_array_append (&pdf_surface->annotations.array, + &annotation); + if (unlikely (status)) { + status = _cairo_surface_set_error (surface, status); + return; + } +} + +static cairo_pdf_value_t * +_cairo_pdf_surface_find_uri_action (cairo_pdf_surface_t *surface, + const char *uri) +{ + cairo_pdf_value_t *action; + unsigned int i, size; + + size = _cairo_array_num_elements (&surface->actions); + for (i = 0; i < size; i++) { + action = (cairo_pdf_value_t*) _cairo_array_index (&surface->actions, i); + + if (action->type == CAIRO_PDF_VALUE_TYPE_ACTION && + action->action.type == CAIRO_PDF_ACTION_TYPE_URI && + strcmp (action->action.uri.uri, uri) == 0) { + return action; + } + } + return NULL; +} + +void +cairo_pdf_surface_add_uri_link (cairo_surface_t *surface, + cairo_rectangle_t area, + const char *uri) +{ + cairo_status_t status; + cairo_pdf_value_t annotation; + cairo_pdf_surface_t *pdf_surface; + + if (!_extract_pdf_surface (surface, &pdf_surface)) { + return; + } + + annotation.type = CAIRO_PDF_VALUE_TYPE_ANNOTATION; + annotation.annotation.type = CAIRO_PDF_ANNOTATION_TYPE_LINK; + annotation.annotation.link.dest = NULL; + annotation.annotation.link.uri = uri; + annotation.annotation.rect.x = area.x; + annotation.annotation.rect.y = pdf_surface->height - (area.y + area.height); + annotation.annotation.rect.width = area.width; + annotation.annotation.rect.height = area.height; + + status = _cairo_pdf_value_array_append (&pdf_surface->annotations.array, + &annotation); + if (unlikely (status)) { + status = _cairo_surface_set_error (surface, status); + return; + } +} + +void +cairo_pdf_surface_add_outline_entry (cairo_surface_t *surface, + const char *title, + int dest_x, + int dest_y, + int depth, + int closed) +{ + cairo_pdf_surface_t *pdf_surface = NULL; + cairo_status_t status; + cairo_pdf_outline_entry_t *outline_entry = NULL; + int entry_count = 0; + cairo_pdf_outline_entry_t *previous_entry; + int previous_index; + + if (!_extract_pdf_surface (surface, &pdf_surface)) { + return; + } + + entry_count = _cairo_array_num_elements (&pdf_surface->outline_entries); + + if (depth < 1 || (depth > 1 && entry_count == 0)) { + // invalid depth value + _cairo_surface_set_error (surface, CAIRO_STATUS_INVALID_INDEX); + return; + } + + status = _cairo_array_allocate (&pdf_surface->outline_entries, 1, + (void **) &outline_entry); + if (unlikely (status)) { + status = _cairo_surface_set_error (surface, status); + return; + } + + _cairo_pdf_value_outline_entry_init (outline_entry); + outline_entry->depth = depth; + outline_entry->closed = closed; + _cairo_pdf_value_dest_set_xyz (&outline_entry->destination, _cairo_array_num_elements(&pdf_surface->pages), dest_x, pdf_surface->height - dest_y, 0); + + if (title != NULL) { + status = _cairo_pdf_value_string_copy_text (&outline_entry->title, title); + if (unlikely (status)) { + status = _cairo_surface_set_error (surface, CAIRO_STATUS_NO_MEMORY); + return; + } + } + + if (entry_count > 0) { + previous_index = entry_count - 1; + previous_entry = _cairo_array_index (&pdf_surface->outline_entries, previous_index); + while (previous_entry->depth > depth) { + previous_index = previous_entry->parent; + previous_entry = _cairo_array_index (&pdf_surface->outline_entries, previous_index); + } + + if (previous_entry->depth == depth) { + previous_entry->next = entry_count; + outline_entry->prev = previous_index; + outline_entry->parent = previous_entry->parent; + if (outline_entry->parent != -1) { + cairo_pdf_outline_entry_t *parent; + parent = (cairo_pdf_outline_entry_t*) _cairo_array_index(&pdf_surface->outline_entries, outline_entry->parent); + parent->last = entry_count; + } + } else if (previous_entry->depth == depth - 1) { + previous_entry->first = previous_entry->last = entry_count; + outline_entry->parent = previous_index; + } else { + // depth must be between 1 and 1 more than the depth of the previous element + status = _cairo_surface_set_error (surface, CAIRO_STATUS_INVALID_INDEX); + return; + } + } +} + +static cairo_pdf_resource_t +_cairo_pdf_surface_write_dests (cairo_pdf_surface_t *surface) +{ + cairo_pdf_resource_t dest; + + dest = _cairo_pdf_surface_new_object (surface); + if (dest.id == 0) + return dest; + + _cairo_output_stream_printf (surface->output, "%d 0 obj\n", dest.id); + _cairo_pdf_output_stream_write_object (surface->output, &surface->dests); + _cairo_output_stream_printf (surface->output, "endobj\n"); + + return dest; +} + +static void +_cairo_pdf_surface_write_outline_entries (cairo_pdf_surface_t *surface, + int base_id) +{ + int index, entry_count; + + entry_count = _cairo_array_num_elements (&surface->outline_entries); + + for (index = 0; index < entry_count; index++) { + cairo_pdf_resource_t object; + cairo_pdf_outline_entry_t *entry; + + entry = (cairo_pdf_outline_entry_t*) _cairo_array_index (&surface->outline_entries, index); + + object = _cairo_pdf_surface_new_object (surface); + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Title ", + index + base_id); + _cairo_pdf_output_stream_write_string (surface->output, &entry->title); + _cairo_output_stream_printf (surface->output, + "\n /Parent %d 0 R\n", + entry->parent + base_id); + if (entry->prev != -1) { + _cairo_output_stream_printf (surface->output, + " /Prev %d 0 R\n", + entry->prev + base_id); + } + if (entry->next != -1) { + _cairo_output_stream_printf (surface->output, + " /Next %d 0 R\n", + entry->next + base_id); + } + + if (entry->first != -1) { + _cairo_output_stream_printf (surface->output, + " /First %d 0 R\n" + " /Last %d 0 R\n" + " /Count %d\n", + entry->first + base_id, + entry->last + base_id, + entry->count); + } + + _cairo_output_stream_printf (surface->output, + " /Dest "); + _cairo_pdf_output_stream_write_dest (surface->output, &entry->destination); + _cairo_output_stream_printf (surface->output, + "\n>>\n" + "endobj\n"); + } +} + +static int +_cairo_pdf_surface_update_outline_entry_count (cairo_array_t *array, + int index) +{ + cairo_pdf_outline_entry_t *entry; + int count_total, current_index; + int child_count; + + count_total = 0; + current_index = index; + + while (current_index != -1) { + entry = (cairo_pdf_outline_entry_t*) _cairo_array_index (array, current_index); + + if (entry->first != -1) { + child_count = _cairo_pdf_surface_update_outline_entry_count (array, entry->first); + if (entry->closed) + entry->count = -child_count; + else + count_total += (entry->count = child_count); + } + count_total++; + current_index = entry->next; + } + + return count_total; +} + +static int +_cairo_pdf_surface_update_outline_counts (cairo_array_t *outlines) +{ + int outline_count; + + outline_count = _cairo_array_num_elements (outlines); + if (outline_count == 0) + return 0; + + return _cairo_pdf_surface_update_outline_entry_count (outlines, 0); +} + +static cairo_pdf_resource_t +_cairo_pdf_surface_write_outlines (cairo_pdf_surface_t *surface) +{ + cairo_pdf_resource_t outlines; + int count, base_index; + int first, last; + cairo_pdf_outline_entry_t *entry; + + count = _cairo_pdf_surface_update_outline_counts (&surface->outline_entries); + + outlines = _cairo_pdf_surface_new_object (surface); + if (outlines.id == 0) + return outlines; + + base_index = outlines.id + 1; + + if (count > 0) { + first = 0; last = first; + entry = (cairo_pdf_outline_entry_t*) _cairo_array_index(&surface->outline_entries, last); + while (entry->next != -1) { + last = entry->next; + entry = (cairo_pdf_outline_entry_t*) _cairo_array_index(&surface->outline_entries, last); + } + + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Outlines\n" + " /First %d 0 R\n" + " /Last %d 0 R\n" + " /Count %d\n" + ">>\n" + "endobj\n", + outlines.id, + base_index + first, + base_index + last, + count); + + _cairo_pdf_surface_write_outline_entries (surface, base_index); + } else { + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n" + "<< /Type /Outlines >>\n" + "endobj\n", + outlines.id); + } + return outlines; +} + +static cairo_status_t +_cairo_pdf_surface_write_page_annotations (cairo_pdf_surface_t *surface, + cairo_pdf_array_t *annotations, + cairo_pdf_array_t *ids) +{ + unsigned int i; + cairo_pdf_resource_t dest; + cairo_pdf_value_t ref; + cairo_status_t status; + cairo_pdf_value_t action; + cairo_pdf_value_t *action_ptr; + cairo_pdf_resource_t action_res; + + status = CAIRO_STATUS_SUCCESS; + + _cairo_pdf_value_array_clear (ids); + + ref.type = CAIRO_PDF_VALUE_TYPE_REFERENCE; + ref.reference.id = 0; + ref.reference.generation = 0; + + for (i = 0; i < annotations->size; i++) { + if (annotations->elements[i].annotation.type == CAIRO_PDF_ANNOTATION_TYPE_LINK && + annotations->elements[i].annotation.link.dest == NULL && + annotations->elements[i].annotation.link.uri != NULL) { + action_ptr = _cairo_pdf_surface_find_uri_action (surface, annotations->elements[i].annotation.link.uri); + if (action_ptr == NULL) { + action_res = _cairo_pdf_surface_new_object (surface); + if (action_res.id == 0) + return CAIRO_STATUS_NO_MEMORY; + action.type = CAIRO_PDF_VALUE_TYPE_ACTION; + action.action.type = CAIRO_PDF_ACTION_TYPE_URI; + action.action.uri.uri = annotations->elements[i].annotation.link.uri; + action.action.uri.is_map = FALSE; + + action.id = action_res.id; + + action_ptr = &action; + + status = _cairo_array_append (&surface->actions, &action); + if (status != CAIRO_STATUS_SUCCESS) + return status; + + _cairo_output_stream_printf (surface->output, "%d 0 obj\n", action.id); + _cairo_pdf_output_stream_write_object (surface->output, &action); + _cairo_output_stream_printf (surface->output, "endobj\n"); + } + annotations->elements[i].annotation.link.action.id = action_ptr->id; + annotations->elements[i].annotation.link.action.generation = 0; + } + dest = _cairo_pdf_surface_new_object (surface); + if (dest.id == 0) + return CAIRO_STATUS_NO_MEMORY; + + ref.reference.id = dest.id; + + status = _cairo_pdf_value_array_append (ids, &ref); + if (status != CAIRO_STATUS_SUCCESS) + return status; + + _cairo_output_stream_printf (surface->output, "%d 0 obj\n", dest.id); + _cairo_pdf_output_stream_write_object (surface->output, &annotations->elements[i]); + _cairo_output_stream_printf (surface->output, "endobj\n"); + } + + return status; +} + static const cairo_surface_backend_t cairo_pdf_surface_backend = { CAIRO_SURFACE_TYPE_PDF, _cairo_pdf_surface_finish, diff --git a/src/cairo-pdf.h b/src/cairo-pdf.h index 1bc8524..14f8e5a 100644 --- a/src/cairo-pdf.h +++ b/src/cairo-pdf.h @@ -1,6 +1,7 @@ /* cairo - a vector graphics library with display and print output * * Copyright © 2002 University of Southern California + * Copyright © 2016 LiveCode Ltd * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -32,6 +33,7 @@ * * Contributor(s): * Carl D. Worth <[email protected]> + * Ian Macphail <[email protected]> */ #ifndef CAIRO_PDF_H @@ -85,6 +87,37 @@ cairo_pdf_surface_set_size (cairo_surface_t *surface, double width_in_points, double height_in_points); +#include "cairo-pdf-ext-object.h" + +cairo_public void +cairo_pdf_surface_set_metadata (cairo_surface_t *surface, + const char *key, + cairo_pdf_value_t *value); + +cairo_public void +cairo_pdf_surface_add_destination (cairo_surface_t *surface, + const char *name, + double x, + double y); + +cairo_public void +cairo_pdf_surface_add_goto_link (cairo_surface_t *surface, + cairo_rectangle_t area, + const char *name); + +cairo_public void +cairo_pdf_surface_add_uri_link (cairo_surface_t *surface, + cairo_rectangle_t area, + const char *uri); + +cairo_public void +cairo_pdf_surface_add_outline_entry (cairo_surface_t *surface, + const char *title, + int dest_x, + int dest_y, + int depth, + int closed); + CAIRO_END_DECLS #else /* CAIRO_HAS_PDF_SURFACE */ -- cairo mailing list [email protected] https://lists.cairographics.org/mailman/listinfo/cairo