[cairo] [RFC][PATCH] Add API for metadata annotation to PDF surfaces
Peter TB Brett
peter at peter-b.co.uk
Fri Sep 30 13:30:33 UTC 2016
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 <peter.brett at livecode.com>
Signed-off-by: Ian Macphail <ian at livecode.com>
---
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 <ian at livecode.com>
+ */
+
+#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 <ian at livecode.com>
+ */
+
+#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 <ian at livecode.com>
+ */
+
+#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 <krh at redhat.com>
* Carl Worth <cworth at cworth.org>
* Adrian Johnson <ajohnson at redneon.com>
+ * Ian Macphail <ian at livecode.com>
*/
#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 <krh at redhat.com>
* Carl Worth <cworth at cworth.org>
* Adrian Johnson <ajohnson at redneon.com>
+ * Ian Macphail <ian at livecode.com>
*/
#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 <cworth at cworth.org>
+ * Ian Macphail <ian at livecode.com>
*/
#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 */
More information about the cairo
mailing list