[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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.