[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