Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/gfx/cairo/cairo/src/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 89 kB image not shown  

Quelle  cairo-pdf-interchange.c   Sprache: C

 
/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
/* cairo - a vector graphics library with display and print output
 *
 * Copyright © 2016 Adrian Johnson
 *
 * 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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 Adrian Johnson.
 *
 * Contributor(s):
 * Adrian Johnson <ajohnson@redneon.com>
 */



/* PDF Document Interchange features:
 *  - metadata
 *  - document outline
 *  - tagged pdf
 *  - hyperlinks
 *  - page labels
 */


#define _DEFAULT_SOURCE /* for localtime_r(), gmtime_r(), snprintf(), strdup() */
#include "cairoint.h"

#include "cairo-pdf.h"
#include "cairo-pdf-surface-private.h"

#include "cairo-array-private.h"
#include "cairo-error-private.h"
#include "cairo-output-stream-private.h"
#include "cairo-recording-surface-inline.h"
#include "cairo-recording-surface-private.h"
#include "cairo-surface-snapshot-inline.h"

#include <time.h>

#ifndef HAVE_LOCALTIME_R
#define localtime_r(T, BUF) (*(BUF) = *localtime (T))
#endif
#ifndef HAVE_GMTIME_R
#define gmtime_r(T, BUF) (*(BUF) = *gmtime (T))
#endif

/* #define DEBUG_PDF_INTERCHANGE 1 */

#if DEBUG_PDF_INTERCHANGE
static void
print_tree (cairo_pdf_surface_t *surface, cairo_pdf_struct_tree_node_t *node);

static void
print_command (cairo_pdf_command_t *command, int indent);

static void
print_command_list(cairo_pdf_command_list_t *command_list);
#endif

static void
_cairo_pdf_command_init_key (cairo_pdf_command_entry_t *key)
{
    key->base.hash = _cairo_hash_uintptr (_CAIRO_HASH_INIT_VALUE, (uintptr_t)key->recording_id);
    key->base.hash = _cairo_hash_uintptr (key->base.hash, (uintptr_t)key->command_id);
}

static cairo_bool_t
_cairo_pdf_command_equal (const void *key_a, const void *key_b)
{
    const cairo_pdf_command_entry_t *a = key_a;
    const cairo_pdf_command_entry_t *b = key_b;

    return a->recording_id == b->recording_id && a->command_id == b->command_id;
}

static void
_cairo_pdf_command_pluck (void *entry, void *closure)
{
    cairo_pdf_command_entry_t *dest = entry;
    cairo_hash_table_t *table = closure;

    _cairo_hash_table_remove (table, &dest->base);
    free (dest);
}

static cairo_pdf_struct_tree_node_t *
lookup_node_for_command (cairo_pdf_surface_t    *surface,
    unsigned int            recording_id,
    unsigned int            command_id)
{
    cairo_pdf_command_entry_t entry_key;
    cairo_pdf_command_entry_t *entry;
    cairo_pdf_interchange_t *ic = &surface->interchange;

    entry_key.recording_id = recording_id;
    entry_key.command_id = command_id;
    _cairo_pdf_command_init_key (&entry_key);
    entry = _cairo_hash_table_lookup (ic->command_to_node_map, &entry_key.base);
    assert (entry != NULL);
    return entry->node;
}

static cairo_int_status_t
command_list_add (cairo_pdf_surface_t    *surface,
    unsigned int            command_id,
    cairo_pdf_operation_t   flags)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_command_t command;
    cairo_int_status_t status;

    unsigned num_elements = _cairo_array_num_elements (&ic->current_commands->commands);
    if (command_id > num_elements) {
 void *elements;
 unsigned additional_elements = command_id - num_elements;
 status = _cairo_array_allocate (&ic->current_commands->commands, additional_elements, &elements);
 if (unlikely (status))
     return status;
 memset (elements, 0, additional_elements * sizeof(cairo_pdf_command_t));
    }

    command.group = NULL;
    command.node = NULL;
    command.command_id = command_id;
    command.mcid_index = 0;
    command.flags = flags;
    return _cairo_array_append (&ic->current_commands->commands, &command);
}

static cairo_int_status_t
command_list_push_group (cairo_pdf_surface_t    *surface,
    unsigned int            command_id,
    cairo_surface_t        *recording_surface,
    unsigned int            region_id)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_command_t *command;
    cairo_pdf_command_list_t *group;
    cairo_pdf_recording_surface_commands_t recording_commands;
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;

    group = _cairo_malloc (sizeof(cairo_pdf_command_list_t));
    _cairo_array_init (&group->commands, sizeof(cairo_pdf_command_t));
    group->parent = ic->current_commands;

    command_list_add (surface, command_id, PDF_GROUP);
    command = _cairo_array_index (&ic->current_commands->commands, command_id);
    command->group = group;
    ic->current_commands = group;

    recording_commands.recording_surface = recording_surface;
    recording_commands.command_list = group;
    recording_commands.region_id = region_id;
    status = _cairo_array_append (&ic->recording_surface_commands, &recording_commands);

    return status;
}

static void
command_list_pop_group (cairo_pdf_surface_t    *surface)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;

    ic->current_commands = ic->current_commands->parent;
}

static cairo_bool_t
command_list_is_group (cairo_pdf_surface_t    *surface,
         unsigned int            command_id)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_command_t *command;
    unsigned num_elements = _cairo_array_num_elements (&ic->current_commands->commands);

    if (command_id >= num_elements)
 return FALSE;

    command = _cairo_array_index (&ic->current_commands->commands, command_id);
    return command->flags == PDF_GROUP;
}


/* Is there any content between current command and next
 * begin/end/group? */

static cairo_bool_t
command_list_has_content (cairo_pdf_surface_t    *surface,
     unsigned int            command_id,
     unsigned int           *content_command_id)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_command_t *command;
    unsigned i;
    unsigned num_elements = _cairo_array_num_elements (&ic->current_commands->commands);

    for (i = command_id + 1; i < num_elements; i++) {
 command = _cairo_array_index (&ic->current_commands->commands, i);
 switch (command->flags) {
     case PDF_CONTENT:
  if (content_command_id)
      *content_command_id = i;
  return TRUE;
  break;
     case PDF_BEGIN:
     case PDF_END:
     case PDF_GROUP:
  return FALSE;
     case PDF_NONE:
  break;
 }
    }
    return FALSE;
}

static void
command_list_set_mcid (cairo_pdf_surface_t          *surface,
         unsigned int                  command_id,
         cairo_pdf_struct_tree_node_t *node,
         int                           mcid_index)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_command_t *command;

    command = _cairo_array_index (&ic->current_commands->commands, command_id);
    command->node = node;
    command->mcid_index = mcid_index;
}

static void
command_list_set_current_recording_commands (cairo_pdf_surface_t    *surface,
          cairo_surface_t        *recording_surface,
          unsigned int            region_id)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    unsigned i;
    cairo_pdf_recording_surface_commands_t *commands;
    unsigned num_elements = _cairo_array_num_elements (&ic->recording_surface_commands);

    for (i = 0; i < num_elements; i++) {
 commands = _cairo_array_index (&ic->recording_surface_commands, i);
 if (commands->region_id == region_id) {
     ic->current_commands = commands->command_list;
     return;
 }
    }
    ASSERT_NOT_REACHED; /* recording_surface not found */
}

static void
update_mcid_order (cairo_pdf_surface_t       *surface,
     cairo_pdf_command_list_t  *command_list)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_command_t *command;
    cairo_pdf_page_mcid_t *mcid_elem;
    unsigned i;
    unsigned num_elements = _cairo_array_num_elements (&command_list->commands);

    for (i = 0; i < num_elements; i++) {
 command = _cairo_array_index (&command_list->commands, i);
 if (command->node) {
     mcid_elem = _cairo_array_index (&command->node->mcid, command->mcid_index);
     mcid_elem->order = ic->mcid_order++;
 }

 if (command->group)
     update_mcid_order (surface, command->group);
    }
}

static void
_cairo_pdf_content_tag_init_key (cairo_pdf_content_tag_t *key)
{
    key->base.hash = _cairo_hash_string (key->node->attributes.content.id);
}

static cairo_bool_t
_cairo_pdf_content_tag_equal (const void *key_a, const void *key_b)
{
    const cairo_pdf_content_tag_t *a = key_a;
    const cairo_pdf_content_tag_t *b = key_b;

    return strcmp (a->node->attributes.content.id, b->node->attributes.content.id) == 0;
}

static void
_cairo_pdf_content_tag_pluck (void *entry, void *closure)
{
    cairo_pdf_content_tag_t *content_tag = entry;
    cairo_hash_table_t *table = closure;

    _cairo_hash_table_remove (table, &content_tag->base);
    free (content_tag);
}

static cairo_status_t
lookup_content_node_for_ref_node (cairo_pdf_surface_t           *surface,
      cairo_pdf_struct_tree_node_t  *ref_node,
      cairo_pdf_struct_tree_node_t **node)
{
    cairo_pdf_content_tag_t entry_key;
    cairo_pdf_content_tag_t *entry;
    cairo_pdf_interchange_t *ic = &surface->interchange;

    entry_key.node = ref_node;
    _cairo_pdf_content_tag_init_key (&entry_key);
    entry = _cairo_hash_table_lookup (ic->content_tag_map, &entry_key.base);
    if (!entry) {
 return _cairo_tag_error ("CONTENT_REF ref='%s' not found",
     ref_node->attributes.content_ref.ref);
    }

    *node = entry->node;
    return CAIRO_STATUS_SUCCESS;
}

static void
write_rect_to_pdf_quad_points (cairo_output_stream_t   *stream,
          const cairo_rectangle_t *rect,
          double                   surface_height)
{
    _cairo_output_stream_printf (stream,
     "%f %f %f %f %f %f %f %f",
     rect->x,
     surface_height - rect->y,
     rect->x + rect->width,
     surface_height - rect->y,
     rect->x + rect->width,
     surface_height - (rect->y + rect->height),
     rect->x,
     surface_height - (rect->y + rect->height));
}

static void
write_rect_int_to_pdf_bbox (cairo_output_stream_t       *stream,
       const cairo_rectangle_int_t *rect,
       double                       surface_height)
{
    _cairo_output_stream_printf (stream,
     "%d %f %d %f",
     rect->x,
     surface_height - (rect->y + rect->height),
     rect->x + rect->width,
     surface_height - rect->y);
}

static cairo_int_status_t
add_tree_node (cairo_pdf_surface_t           *surface,
        cairo_pdf_struct_tree_node_t  *parent,
        const char                    *name,
        const char                    *attributes,
        cairo_pdf_struct_tree_node_t **new_node)
{
    cairo_pdf_struct_tree_node_t *node;
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;

    node = _cairo_malloc (sizeof(cairo_pdf_struct_tree_node_t));
    if (unlikely (node == NULL))
 return _cairo_error (CAIRO_STATUS_NO_MEMORY);

    node->name = strdup (name);
    node->res = _cairo_pdf_surface_new_object (surface);
    if (node->res.id == 0)
 return _cairo_error (CAIRO_STATUS_NO_MEMORY);

    node->parent = parent;
    cairo_list_init (&node->children);
    _cairo_array_init (&node->mcid, sizeof (cairo_pdf_page_mcid_t));
    node->annot = NULL;
    node->extents.valid = FALSE;

    cairo_list_add_tail (&node->link, &parent->children);

    if (strcmp (node->name, CAIRO_TAG_CONTENT) == 0) {
 node->type = PDF_NODE_CONTENT;
 status = _cairo_tag_parse_content_attributes (attributes, &node->attributes.content);
    } else if (strcmp (node->name, CAIRO_TAG_CONTENT_REF) == 0) {
 node->type = PDF_NODE_CONTENT_REF;
 status = _cairo_tag_parse_content_ref_attributes (attributes, &node->attributes.content_ref);
    } else if (strcmp (node->name, "Artifact") == 0) {
 node->type = PDF_NODE_ARTIFACT;
    } else {
 node->type = PDF_NODE_STRUCT;
    }

    *new_node = node;
    return status;
}

static void
free_node (cairo_pdf_struct_tree_node_t *node)
{
    cairo_pdf_struct_tree_node_t *child, *next;

    if (!node)
 return;

    cairo_list_foreach_entry_safe (child, next, cairo_pdf_struct_tree_node_t,
       &node->children,
       link)
    {
 cairo_list_del (&child->link);
 free_node (child);
    }
    free (node->name);
    _cairo_array_fini (&node->mcid);
    if (node->type == PDF_NODE_CONTENT)
 _cairo_tag_free_content_attributes (&node->attributes.content);

    if (node->type == PDF_NODE_CONTENT_REF)
 _cairo_tag_free_content_ref_attributes (&node->attributes.content_ref);

    free (node);
}

static cairo_status_t
add_mcid_to_node (cairo_pdf_surface_t          *surface,
    cairo_pdf_struct_tree_node_t *node,
    unsigned int                  command_id,
    int                          *mcid)
{
    cairo_pdf_page_mcid_t mcid_elem;
    cairo_int_status_t status;
    cairo_pdf_interchange_t *ic = &surface->interchange;

    status = _cairo_array_append (&ic->mcid_to_tree, &node);
    if (unlikely (status))
 return status;

    mcid_elem.order = -1;
    mcid_elem.page = _cairo_array_num_elements (&surface->pages);
    mcid_elem.xobject_res = ic->current_recording_surface_res;
    mcid_elem.mcid = _cairo_array_num_elements (&ic->mcid_to_tree) - 1;
    mcid_elem.child_node = NULL;
    command_list_set_mcid (surface, command_id, node, _cairo_array_num_elements (&node->mcid));
    *mcid = mcid_elem.mcid;
    return _cairo_array_append (&node->mcid, &mcid_elem);
}

static cairo_status_t
add_child_to_mcid_array (cairo_pdf_surface_t          *surface,
    cairo_pdf_struct_tree_node_t *node,
    unsigned int                  command_id,
    cairo_pdf_struct_tree_node_t *child)
{
    cairo_pdf_page_mcid_t mcid_elem;

    mcid_elem.order = -1;
    mcid_elem.page = 0;
    mcid_elem.xobject_res.id = 0;
    mcid_elem.mcid = 0;
    mcid_elem.child_node = child;
    command_list_set_mcid (surface, command_id, node, _cairo_array_num_elements (&node->mcid));
    return _cairo_array_append (&node->mcid, &mcid_elem);
}

static cairo_int_status_t
add_annotation (cairo_pdf_surface_t           *surface,
  cairo_pdf_struct_tree_node_t  *node,
  const char                    *name,
  const char                    *attributes)
{
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_annotation_t *annot;

    annot = _cairo_malloc (sizeof (cairo_pdf_annotation_t));
    if (unlikely (annot == NULL))
 return _cairo_error (CAIRO_STATUS_NO_MEMORY);

    status = _cairo_tag_parse_link_attributes (attributes, &annot->link_attrs);
    if (unlikely (status)) {
 free (annot);
 return status;
    }

    if (annot->link_attrs.link_page == 0)
 annot->link_attrs.link_page = _cairo_array_num_elements (&surface->pages);

    annot->node = node;

    annot->res = _cairo_pdf_surface_new_object (surface);
    if (annot->res.id == 0)
 return _cairo_error (CAIRO_STATUS_NO_MEMORY);

    node->annot = annot;
    status = _cairo_array_append (&ic->annots, &annot);

    return status;
}

static void
free_annotation (cairo_pdf_annotation_t *annot)
{
    _cairo_tag_free_link_attributes (&annot->link_attrs);
    free (annot);
}

static void
cairo_pdf_interchange_clear_annotations (cairo_pdf_surface_t *surface)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    int num_elems, i;

    num_elems = _cairo_array_num_elements (&ic->annots);
    for (i = 0; i < num_elems; i++) {
 cairo_pdf_annotation_t * annot;

 _cairo_array_copy_element (&ic->annots, i, &annot);
 free_annotation (annot);
    }
    _cairo_array_truncate (&ic->annots, 0);
}

static void
cairo_pdf_interchange_write_node_mcid (cairo_pdf_surface_t            *surface,
           cairo_pdf_page_mcid_t          *mcid_elem,
           int                             page)
{
    cairo_pdf_page_info_t *page_info;

    page_info = _cairo_array_index (&surface->pages, mcid_elem->page - 1);
    if (mcid_elem->page == page && mcid_elem->xobject_res.id == 0) {
 _cairo_output_stream_printf (surface->object_stream.stream, "%d ", mcid_elem->mcid);
    } else {
 _cairo_output_stream_printf (surface->object_stream.stream,
         "\n << /Type /MCR ");
 if (mcid_elem->page != page) {
     _cairo_output_stream_printf (surface->object_stream.stream,
      "/Pg %d 0 R ",
      page_info->page_res.id);
 }
 if (mcid_elem->xobject_res.id != 0) {
     _cairo_output_stream_printf (surface->object_stream.stream,
      "/Stm %d 0 R ",
      mcid_elem->xobject_res.id);
 }
 _cairo_output_stream_printf (surface->object_stream.stream,
         "/MCID %d >> ",
         mcid_elem->mcid);
    }
}

static int
_mcid_order_compare (const void *a,
       const void *b)
{
    const cairo_pdf_page_mcid_t *mcid_a = a;
    const cairo_pdf_page_mcid_t *mcid_b = b;

    if (mcid_a->order < mcid_b->order)
 return -1;
    else if (mcid_a->order > mcid_b->order)
 return 1;
    else
 return 0;
}

static cairo_int_status_t
cairo_pdf_interchange_write_node_object (cairo_pdf_surface_t            *surface,
      cairo_pdf_struct_tree_node_t   *node,
      int                             depth)
{
    cairo_pdf_page_mcid_t *mcid_elem, *child_mcid_elem;
    unsigned i, j, num_mcid;
    int first_page = 0;
    cairo_pdf_page_info_t *page_info;
    cairo_int_status_t status;
    cairo_bool_t has_children = FALSE;

    /* The Root node is written in cairo_pdf_interchange_write_struct_tree(). */
    if (!node->parent)
 return CAIRO_STATUS_SUCCESS;

    if (node->type == PDF_NODE_CONTENT ||
 node->type == PDF_NODE_CONTENT_REF ||
 node->type == PDF_NODE_ARTIFACT)
    {
 return CAIRO_STATUS_SUCCESS;
    }

    status = _cairo_pdf_surface_object_begin (surface, node->res);
    if (unlikely (status))
 return status;

    _cairo_output_stream_printf (surface->object_stream.stream,
     "<< /Type /StructElem\n"
     " /S /%s\n"
     " /P %d 0 R\n",
     node->name,
     node->parent->res.id);

    /* Write /K entry (children of this StructElem) */
    num_mcid = _cairo_array_num_elements (&node->mcid);
    if (num_mcid > 0 ) {
 _cairo_array_sort (&node->mcid, _mcid_order_compare);
 /* Find the first MCID element and use the page number to set /Pg */
 for (i = 0; i < num_mcid; i++) {
     mcid_elem = _cairo_array_index (&node->mcid, i);
     assert (mcid_elem->order != -1);
     if (mcid_elem->child_node) {
  if (mcid_elem->child_node->type == PDF_NODE_CONTENT_REF) {
      cairo_pdf_struct_tree_node_t *content_node;
      status = lookup_content_node_for_ref_node (surface, mcid_elem->child_node, &content_node);
      if (status)
   return status;

      /* CONTENT_REF will not have child nodes */
      if (_cairo_array_num_elements (&content_node->mcid) > 0) {
   child_mcid_elem = _cairo_array_index (&content_node->mcid, 0);
   first_page = child_mcid_elem->page;
   page_info = _cairo_array_index (&surface->pages, first_page - 1);
   _cairo_output_stream_printf (surface->object_stream.stream,
           " /Pg %d 0 R\n",
           page_info->page_res.id);
   has_children = TRUE;
   break;
      }
  } else {
      has_children = TRUE;
  }
     } else {
  first_page = mcid_elem->page;
  page_info = _cairo_array_index (&surface->pages, first_page - 1);
  _cairo_output_stream_printf (surface->object_stream.stream,
          " /Pg %d 0 R\n",
          page_info->page_res.id);
  has_children = TRUE;
  break;
     }
 }

 if (has_children || node->annot) {
     _cairo_output_stream_printf (surface->object_stream.stream, " /K ");

     if (num_mcid > 1 || node->annot)
  _cairo_output_stream_printf (surface->object_stream.stream, "[ ");

     for (i = 0; i < num_mcid; i++) {
  if (node->annot) {
      if (node->annot->link_attrs.link_page != first_page) {
   page_info = _cairo_array_index (&surface->pages, node->annot->link_attrs.link_page - 1);
   _cairo_output_stream_printf (surface->object_stream.stream,
           "<< /Type /OBJR /Pg %d 0 R /Obj %d 0 R >> ",
           page_info->page_res.id,
           node->annot->res.id);
      } else {
   _cairo_output_stream_printf (surface->object_stream.stream,
           "<< /Type /OBJR /Obj %d 0 R >> ",
           node->annot->res.id);
      }
  }
  mcid_elem = _cairo_array_index (&node->mcid, i);
  if (mcid_elem->child_node) {
      if (mcid_elem->child_node->type == PDF_NODE_CONTENT_REF) {
   cairo_pdf_struct_tree_node_t *content_node;
   status = lookup_content_node_for_ref_node (surface, mcid_elem->child_node, &content_node);
   if (status)
       return status;

   assert (content_node->type == PDF_NODE_CONTENT);

   /* CONTENT_REF will not have child nodes */
   for (j = 0; j < _cairo_array_num_elements (&content_node->mcid); j++) {
       child_mcid_elem = _cairo_array_index (&content_node->mcid, j);
       cairo_pdf_interchange_write_node_mcid (surface, child_mcid_elem, first_page);
   }
      } else if (mcid_elem->child_node->type != PDF_NODE_CONTENT) {
   _cairo_output_stream_printf (surface->object_stream.stream,
           " %d 0 R ",
           mcid_elem->child_node->res.id);
      }
  } else {
      cairo_pdf_interchange_write_node_mcid (surface, mcid_elem, first_page);
  }
     }

     if (num_mcid > 1 || node->annot)
  _cairo_output_stream_printf (surface->object_stream.stream, "]");
 }

 _cairo_output_stream_printf (surface->object_stream.stream, "\n");
    }

    _cairo_output_stream_printf (surface->object_stream.stream,
     ">>\n");

    _cairo_pdf_surface_object_end (surface);

    return _cairo_output_stream_get_status (surface->object_stream.stream);
}

static void
init_named_dest_key (cairo_pdf_named_dest_t *dest)
{
    dest->base.hash = _cairo_hash_bytes (_CAIRO_HASH_INIT_VALUE,
      dest->attrs.name,
      strlen (dest->attrs.name));
}

static cairo_bool_t
_named_dest_equal (const void *key_a, const void *key_b)
{
    const cairo_pdf_named_dest_t *a = key_a;
    const cairo_pdf_named_dest_t *b = key_b;

    return strcmp (a->attrs.name, b->attrs.name) == 0;
}

static void
_named_dest_pluck (void *entry, void *closure)
{
    cairo_pdf_named_dest_t *dest = entry;
    cairo_hash_table_t *table = closure;

    _cairo_hash_table_remove (table, &dest->base);
    _cairo_tag_free_dest_attributes (&dest->attrs);
    free (dest);
}

static cairo_int_status_t
cairo_pdf_interchange_write_explicit_dest (cairo_pdf_surface_t *surface,
                                          int                  page,
                                          cairo_bool_t         has_pos,
                                          double               x,
                                          double               y)
{
    cairo_pdf_page_info_t *page_info;

    page_info = _cairo_array_index (&surface->pages, page - 1);

    if (has_pos) {
       _cairo_output_stream_printf (surface->object_stream.stream,
                                    "[%d 0 R /XYZ %f %f 0]\n",
                                    page_info->page_res.id,
                                    x,
                                    page_info->height - y);
    } else {
       _cairo_output_stream_printf (surface->object_stream.stream,
                                    "[%d 0 R /XYZ null null 0]\n",
                                    page_info->page_res.id);
    }

    return CAIRO_STATUS_SUCCESS;
}

static cairo_int_status_t
cairo_pdf_interchange_write_dest (cairo_pdf_surface_t *surface,
      cairo_link_attrs_t  *link_attrs)
{
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    cairo_pdf_interchange_t *ic = &surface->interchange;

    /* If the dest is known, emit an explicit dest */
    if (link_attrs->link_type == TAG_LINK_DEST_AND_URI || link_attrs->link_type == TAG_LINK_DEST) {
 cairo_pdf_named_dest_t key;
 cairo_pdf_named_dest_t *named_dest;

 /* check if we already have this dest */
 key.attrs.name = link_attrs->dest;
 init_named_dest_key (&key);
 named_dest = _cairo_hash_table_lookup (ic->named_dests, &key.base);
 if (named_dest) {
     double x = 0;
     double y = 0;

     if (named_dest->extents.valid) {
  x = named_dest->extents.extents.x;
  y = named_dest->extents.extents.y;
     }

     if (named_dest->attrs.x_valid)
  x = named_dest->attrs.x;

     if (named_dest->attrs.y_valid)
  y = named_dest->attrs.y;

     if (named_dest->attrs.internal) {
  _cairo_output_stream_printf (surface->object_stream.stream, " /Dest ");
  status = cairo_pdf_interchange_write_explicit_dest (surface,
            named_dest->page,
            TRUE,
            x, y);
     } else {
  char *name = NULL;

  status = _cairo_utf8_to_pdf_string (named_dest->attrs.name, &name);
  if (unlikely (status))
      return status;

  _cairo_output_stream_printf (surface->object_stream.stream, " /Dest %s\n",
          name);
  free (name);
     }
     return status;
 }
 /* name does not exist */
 if (link_attrs->link_type == TAG_LINK_DEST_AND_URI) {
     /* Don't emit anything. The caller will fallback to emitting a URI destination. */
     return CAIRO_INT_STATUS_NOTHING_TO_DO;
 }

 /* Mising destination. Emit a "do nothing" dest that points to the same page and position. */
 _cairo_tag_warning ("Link to dest=\"%s\" not found", link_attrs->dest);
 _cairo_output_stream_printf (surface->object_stream.stream, " /Dest ");
 status = cairo_pdf_interchange_write_explicit_dest (surface,
           link_attrs->link_page,
           FALSE,
           0, 0);
 return status;
    }

    /* link_attrs->link_type == TAG_LINK_PAGE */

    if (link_attrs->page < 1)
 return _cairo_tag_error ("Link attribute: \"page=%d\" page must be >= 1", link_attrs->page);

    if (link_attrs->page > (int)_cairo_array_num_elements (&surface->pages))
 return _cairo_tag_error ("Link attribute: \"page=%d\" page exceeds page count (%d)",
     link_attrs->page, _cairo_array_num_elements (&surface->pages));

    _cairo_output_stream_printf (surface->object_stream.stream, " /Dest ");
    return cairo_pdf_interchange_write_explicit_dest (surface,
            link_attrs->page,
            link_attrs->has_pos,
            link_attrs->pos.x,
            link_attrs->pos.y);
}

static cairo_int_status_t
_cairo_utf8_to_pdf_utf8_hexstring (const char *utf8, char **str_out)
{
    int i;
    int len;
    unsigned char *p;
    cairo_bool_t ascii;
    char *str;
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;

    ascii = TRUE;
    p = (unsigned char *)utf8;
    len = 0;
    while (*p) {
 if (*p < 32 || *p > 126) {
     ascii = FALSE;
 }
 if (*p == '(' || *p == ')' || *p == '\\')
     len += 2;
 else
     len++;
 p++;
    }

    if (ascii) {
 str = _cairo_malloc (len + 3);
 if (str == NULL)
     return _cairo_error (CAIRO_STATUS_NO_MEMORY);

 str[0] = '(';
 p = (unsigned char *)utf8;
 i = 1;
 while (*p) {
     if (*p == '(' || *p == ')' || *p == '\\')
  str[i++] = '\\';
     str[i++] = *p;
     p++;
 }
 str[i++] = ')';
 str[i++] = 0;
    } else {
 str = _cairo_malloc (len*2 + 3);
 if (str == NULL)
     return _cairo_error (CAIRO_STATUS_NO_MEMORY);

 str[0] = '<';
 p = (unsigned char *)utf8;
 i = 1;
 while (*p) {
     if (*p == '\\') {
  snprintf(str + i, 3, "%02x"'\\');
  i += 2;
     }
     snprintf(str + i, 3, "%02x", *p);
     i += 2;
     p++;
 }
 str[i++] = '>';
 str[i++] = 0;
    }
    *str_out = str;

    return status;
}

static cairo_int_status_t
cairo_pdf_interchange_write_link_action (cairo_pdf_surface_t   *surface,
      cairo_link_attrs_t    *link_attrs)
{
    cairo_int_status_t status;
    char *dest = NULL;

    if (link_attrs->link_type == TAG_LINK_DEST_AND_URI ||
 link_attrs->link_type == TAG_LINK_DEST         ||
 link_attrs->link_type == TAG_LINK_PAGE)
    {
 status = cairo_pdf_interchange_write_dest (surface, link_attrs);
 if (status != CAIRO_INT_STATUS_NOTHING_TO_DO)
     return status;

 /* CAIRO_INT_STATUS_NOTHING_TO_DO means that the link type is TAG_LINK_DEST_AND_URI
 * and the DEST is missing. Fall through to writing a URI link below.
 */

    }

    if (link_attrs->link_type == TAG_LINK_URI || link_attrs->link_type == TAG_LINK_DEST_AND_URI) {
 status = _cairo_utf8_to_pdf_string (link_attrs->uri, &dest);
 if (unlikely (status))
     return status;

 if (dest[0] != '(') {
     free (dest);
     return _cairo_tag_error ("Link attribute: \"url=%s\" URI may only contain ASCII characters",
         link_attrs->uri);
 }

 _cairo_output_stream_printf (surface->object_stream.stream,
         " /A <<\n"
         " /Type /Action\n"
         " /S /URI\n"
         " /URI %s\n"
         " >>\n",
         dest);
 free (dest);
    } else if (link_attrs->link_type == TAG_LINK_FILE) {
 /* According to "Developing with PDF", Leonard Rosenthol, 2013,
 * The F key is encoded in the "standard encoding for the
 * platform on which the document is being viewed. For most
 * modern operating systems, that's UTF-8"
 *
 * As we don't know the target platform, we assume UTF-8. The
 * F key may contain multi-byte encodings using the hex
 * encoding.
 *
 * For PDF 1.7 we also include the UF key which uses the
 * standard PDF UTF-16BE strings.
 */

 status = _cairo_utf8_to_pdf_utf8_hexstring (link_attrs->file, &dest);
 if (unlikely (status))
     return status;

 _cairo_output_stream_printf (surface->object_stream.stream,
         " /A <<\n"
         " /Type /Action\n"
         " /S /GoToR\n"
         " /F %s\n",
         dest);
 free (dest);

 if (surface->pdf_version >= CAIRO_PDF_VERSION_1_7)
 {
     status = _cairo_utf8_to_pdf_string (link_attrs->file, &dest);
     if (unlikely (status))
  return status;

     _cairo_output_stream_printf (surface->object_stream.stream,
         " /UF %s\n",
         dest);
     free (dest);
 }

 if (link_attrs->dest) {
     status = _cairo_utf8_to_pdf_string (link_attrs->dest, &dest);
     if (unlikely (status))
  return status;

     _cairo_output_stream_printf (surface->object_stream.stream,
      " /D %s\n",
      dest);
     free (dest);
 } else {
     if (link_attrs->has_pos) {
  _cairo_output_stream_printf (surface->object_stream.stream,
          " /D [%d /XYZ %f %f 0]\n",
          link_attrs->page,
          link_attrs->pos.x,
          link_attrs->pos.y);
     } else {
  _cairo_output_stream_printf (surface->object_stream.stream,
          " /D [%d /XYZ null null 0]\n",
          link_attrs->page);
     }
 }
 _cairo_output_stream_printf (surface->object_stream.stream,
         " >>\n");
    }

    return CAIRO_STATUS_SUCCESS;
}

static cairo_int_status_t
cairo_pdf_interchange_write_annot (cairo_pdf_surface_t    *surface,
       cairo_pdf_annotation_t *annot,
       cairo_bool_t            struct_parents)
{
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_struct_tree_node_t *node = annot->node;
    int sp;
    int i, num_rects;
    double height;

    num_rects = _cairo_array_num_elements (&annot->link_attrs.rects);
    if (strcmp (node->name, CAIRO_TAG_LINK) == 0 &&
 annot->link_attrs.link_type != TAG_LINK_EMPTY &&
 (node->extents.valid || num_rects > 0))
    {
 status = _cairo_array_append (&ic->parent_tree, &node->res);
 if (unlikely (status))
     return status;

 sp = _cairo_array_num_elements (&ic->parent_tree) - 1;

 status = _cairo_pdf_surface_object_begin (surface, annot->res);
 if (unlikely (status))
     return status;

 _cairo_output_stream_printf (surface->object_stream.stream,
         "<< /Type /Annot\n"
         " /Subtype /Link\n");

 if (struct_parents) {
     _cairo_output_stream_printf (surface->object_stream.stream,
      " /StructParent %d\n",
      sp);
 }

 height = surface->height;
 if (num_rects > 0) {
     cairo_rectangle_int_t bbox_rect;

     _cairo_output_stream_printf (surface->object_stream.stream,
      " /QuadPoints [ ");
     for (i = 0; i < num_rects; i++) {
  cairo_rectangle_t rectf;
  cairo_rectangle_int_t recti;

  _cairo_array_copy_element (&annot->link_attrs.rects, i, &rectf);
  _cairo_rectangle_int_from_double (&recti, &rectf);
  if (i == 0)
      bbox_rect = recti;
  else
      _cairo_rectangle_union (&bbox_rect, &recti);

  write_rect_to_pdf_quad_points (surface->object_stream.stream, &rectf, height);
  _cairo_output_stream_printf (surface->object_stream.stream, " ");
     }
     _cairo_output_stream_printf (surface->object_stream.stream,
      "]\n"
      " /Rect [ ");
     write_rect_int_to_pdf_bbox (surface->object_stream.stream, &bbox_rect, height);
     _cairo_output_stream_printf (surface->object_stream.stream, " ]\n");
 } else {
     _cairo_output_stream_printf (surface->object_stream.stream,
      " /Rect [ ");
     write_rect_int_to_pdf_bbox (surface->object_stream.stream, &node->extents.extents, height);
     _cairo_output_stream_printf (surface->object_stream.stream, " ]\n");
 }

 status = cairo_pdf_interchange_write_link_action (surface, &annot->link_attrs);
 if (unlikely (status))
     return status;

 _cairo_output_stream_printf (surface->object_stream.stream,
         " /BS << /W 0 >>\n"
         ">>\n");

 _cairo_pdf_surface_object_end (surface);
 status = _cairo_output_stream_get_status (surface->object_stream.stream);
    }

    return status;
}

static cairo_int_status_t
cairo_pdf_interchange_walk_struct_tree (cairo_pdf_surface_t          *surface,
     cairo_pdf_struct_tree_node_t *node,
     int                           depth,
     cairo_int_status_t (*func) (cairo_pdf_surface_t          *surface,
            cairo_pdf_struct_tree_node_t *node,
                                 int                           depth))
{
    cairo_int_status_t status;
    cairo_pdf_struct_tree_node_t *child;

    status = func (surface, node, depth);
    if (unlikely (status))
 return status;

    depth++;
    cairo_list_foreach_entry (child, cairo_pdf_struct_tree_node_t,
         &node->children, link)
    {
 status = cairo_pdf_interchange_walk_struct_tree (surface, child, depth, func);
 if (unlikely (status))
     return status;
    }
    depth--;

    return CAIRO_STATUS_SUCCESS;
}

static cairo_int_status_t
cairo_pdf_interchange_write_struct_tree (cairo_pdf_surface_t *surface)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_struct_tree_node_t *child;
    cairo_int_status_t status;

    if (cairo_list_is_empty (&ic->struct_root->children))
 return CAIRO_STATUS_SUCCESS;

    status = cairo_pdf_interchange_walk_struct_tree (surface,
           ic->struct_root,
           0,
           cairo_pdf_interchange_write_node_object);
    if (unlikely (status))
 return status;

    status = _cairo_pdf_surface_object_begin (surface, surface->struct_tree_root);
    if (unlikely (status))
 return status;

    _cairo_output_stream_printf (surface->object_stream.stream,
     "<< /Type /StructTreeRoot\n"
     " /ParentTree %d 0 R\n",
     ic->parent_tree_res.id);

    if (cairo_list_is_singular (&ic->struct_root->children)) {
 child = cairo_list_first_entry (&ic->struct_root->children, cairo_pdf_struct_tree_node_t, link);
 _cairo_output_stream_printf (surface->object_stream.stream, " /K [ %d 0 R ]\n", child->res.id);
    } else {
 _cairo_output_stream_printf (surface->object_stream.stream, " /K [ ");

 cairo_list_foreach_entry (child, cairo_pdf_struct_tree_node_t,
      &ic->struct_root->children, link)
 {
     if (child->type == PDF_NODE_CONTENT || child->type == PDF_NODE_ARTIFACT)
  continue;

     _cairo_output_stream_printf (surface->object_stream.stream, "%d 0 R ", child->res.id);
 }
 _cairo_output_stream_printf (surface->object_stream.stream, "]\n");
    }

    _cairo_output_stream_printf (surface->object_stream.stream,
     ">>\n");
    _cairo_pdf_surface_object_end (surface);

    return CAIRO_STATUS_SUCCESS;
}

static cairo_int_status_t
cairo_pdf_interchange_write_annots (cairo_pdf_surface_t *surface,
        cairo_bool_t         struct_parents)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    int num_elems, i, page_num;
    cairo_pdf_page_info_t *page_info;
    cairo_pdf_annotation_t *annot;
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;

    num_elems = _cairo_array_num_elements (&ic->annots);
    for (i = 0; i < num_elems; i++) {
 _cairo_array_copy_element (&ic->annots, i, &annot);
 page_num = annot->link_attrs.link_page;
 if (page_num > (int)_cairo_array_num_elements (&surface->pages)) {
     return _cairo_tag_error ("Link attribute: \"link_page=%d\" page exceeds page count (%d)",
         page_num,
         _cairo_array_num_elements (&surface->pages));
 }

 page_info = _cairo_array_index (&surface->pages, page_num - 1);
 status = _cairo_array_append (&page_info->annots, &annot->res);
 if (status)
     return status;

 status = cairo_pdf_interchange_write_annot (surface, annot, struct_parents);
 if (unlikely (status))
     return status;
    }

    return status;
}

static cairo_int_status_t
cairo_pdf_interchange_write_content_parent_elems (cairo_pdf_surface_t *surface)
{
    int num_elems, i;
    cairo_pdf_struct_tree_node_t *node;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;

    num_elems = _cairo_array_num_elements (&ic->mcid_to_tree);
    status = _cairo_pdf_surface_object_begin (surface, ic->content_parent_res);
    if (unlikely (status))
 return status;

    _cairo_output_stream_printf (surface->object_stream.stream,
         "[\n");
    for (i = 0; i < num_elems; i++) {
 _cairo_array_copy_element (&ic->mcid_to_tree, i, &node);
 _cairo_output_stream_printf (surface->object_stream.stream, " %d 0 R\n", node->res.id);
    }
    _cairo_output_stream_printf (surface->object_stream.stream,
     "]\n");
    _cairo_pdf_surface_object_end (surface);

    return status;
}

static cairo_int_status_t
cairo_pdf_interchange_apply_extents_from_content_ref (cairo_pdf_surface_t            *surface,
            cairo_pdf_struct_tree_node_t   *node,
            int                             depth)
{
    cairo_int_status_t status;

    if (node->type != PDF_NODE_CONTENT_REF)
 return CAIRO_STATUS_SUCCESS;

    cairo_pdf_struct_tree_node_t *content_node;
    status = lookup_content_node_for_ref_node (surface, node, &content_node);
    if (status)
 return status;

    /* Merge extents with all parent nodes */
    node = node->parent;
    while (node) {
 if (node->extents.valid) {
     _cairo_rectangle_union (&node->extents.extents, &content_node->extents.extents);
 } else {
     node->extents = content_node->extents;
 }
 node = node->parent;
    }

    return CAIRO_STATUS_SUCCESS;
}

static cairo_int_status_t
cairo_pdf_interchange_update_extents (cairo_pdf_surface_t *surface)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;

    return cairo_pdf_interchange_walk_struct_tree (surface,
         ic->struct_root,
         0,
         cairo_pdf_interchange_apply_extents_from_content_ref);
}

static cairo_int_status_t
cairo_pdf_interchange_write_parent_tree (cairo_pdf_surface_t *surface)
{
    int num_elems, i;
    cairo_pdf_resource_t *res;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status;

    num_elems = _cairo_array_num_elements (&ic->parent_tree);
    if (num_elems > 0) {
 ic->parent_tree_res = _cairo_pdf_surface_new_object (surface);
 if (ic->parent_tree_res.id == 0)
     return _cairo_error (CAIRO_STATUS_NO_MEMORY);

 status = _cairo_pdf_surface_object_begin (surface, ic->parent_tree_res);
 if (unlikely (status))
     return status;

 _cairo_output_stream_printf (surface->object_stream.stream,
         "<< /Nums [\n");
 for (i = 0; i < num_elems; i++) {
     res = _cairo_array_index (&ic->parent_tree, i);
     if (res->id) {
  _cairo_output_stream_printf (surface->object_stream.stream,
          " %d %d 0 R\n",
          i,
          res->id);
     }
 }
 _cairo_output_stream_printf (surface->object_stream.stream,
         " ]\n"
         ">>\n");
 _cairo_pdf_surface_object_end (surface);
    }

    return CAIRO_STATUS_SUCCESS;
}

static cairo_int_status_t
cairo_pdf_interchange_write_outline (cairo_pdf_surface_t *surface)
{
    int num_elems, i;
    cairo_pdf_outline_entry_t *outline;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status;
    char *name = NULL;

    num_elems = _cairo_array_num_elements (&ic->outline);
    if (num_elems < 2)
 return CAIRO_INT_STATUS_SUCCESS;

    _cairo_array_copy_element (&ic->outline, 0, &outline);
    outline->res = _cairo_pdf_surface_new_object (surface);
    if (outline->res.id == 0)
 return _cairo_error (CAIRO_STATUS_NO_MEMORY);

    surface->outlines_dict_res = outline->res;
    status = _cairo_pdf_surface_object_begin (surface, outline->res);
    if (unlikely (status))
 return status;

    _cairo_output_stream_printf (surface->object_stream.stream,
     "<< /Type /Outlines\n"
     " /First %d 0 R\n"
     " /Last %d 0 R\n"
     " /Count %d\n"
     ">>\n",
     outline->first_child->res.id,
     outline->last_child->res.id,
     outline->count);
    _cairo_pdf_surface_object_end (surface);

    for (i = 1; i < num_elems; i++) {
 _cairo_array_copy_element (&ic->outline, i, &outline);
 _cairo_pdf_surface_update_object (surface, outline->res);

 status = _cairo_utf8_to_pdf_string (outline->name, &name);
 if (unlikely (status))
     return status;

 status = _cairo_pdf_surface_object_begin (surface, outline->res);
 if (unlikely (status))
     return status;

 _cairo_output_stream_printf (surface->object_stream.stream,
         "<< /Title %s\n"
         " /Parent %d 0 R\n",
         name,
         outline->parent->res.id);
 free (name);

 if (outline->prev) {
     _cairo_output_stream_printf (surface->object_stream.stream,
      " /Prev %d 0 R\n",
      outline->prev->res.id);
 }

 if (outline->next) {
     _cairo_output_stream_printf (surface->object_stream.stream,
      " /Next %d 0 R\n",
      outline->next->res.id);
 }

 if (outline->first_child) {
     _cairo_output_stream_printf (surface->object_stream.stream,
      " /First %d 0 R\n"
      " /Last %d 0 R\n"
      " /Count %d\n",
      outline->first_child->res.id,
      outline->last_child->res.id,
      outline->count);
 }

 if (outline->flags) {
     int flags = 0;
     if (outline->flags & CAIRO_PDF_OUTLINE_FLAG_ITALIC)
  flags |= 1;
     if (outline->flags & CAIRO_PDF_OUTLINE_FLAG_BOLD)
  flags |= 2;
     _cairo_output_stream_printf (surface->object_stream.stream,
      " /F %d\n",
      flags);
 }

 status = cairo_pdf_interchange_write_link_action (surface, &outline->link_attrs);
 if (unlikely (status))
     return status;

 _cairo_output_stream_printf (surface->object_stream.stream,
         ">>\n");
 _cairo_pdf_surface_object_end (surface);
    }

    return status;
}

/*
 * Split a page label into a text prefix and numeric suffix. Leading '0's are
 * included in the prefix. eg
 *  "3"     => NULL,    3
 *  "cover" => "cover", 0
 *  "A-2"   => "A-",    2
 *  "A-002" => "A-00",  2
 */

static char *
split_label (const char* label, int *num)
{
    int len, i;

    *num = 0;
    len = strlen (label);
    if (len == 0)
 return NULL;

    i = len;
    while (i > 0 && _cairo_isdigit (label[i-1]))
    i--;

    while (i < len && label[i] == '0')
 i++;

    if (i < len)
 sscanf (label + i, "%d", num);

    if (i > 0) {
 char *s;
 s = _cairo_malloc (i + 1);
 if (!s)
     return NULL;

 memcpy (s, label, i);
 s[i] = 0;
 return s;
    }

    return NULL;
}

/* strcmp that handles NULL arguments */
static cairo_bool_t
strcmp_null (const char *s1, const char *s2)
{
    if (s1 && s2)
 return strcmp (s1, s2) == 0;

    if (!s1 && !s2)
 return TRUE;

    return FALSE;
}

static cairo_int_status_t
cairo_pdf_interchange_write_page_labels (cairo_pdf_surface_t *surface)
{
    int num_elems, i;
    char *label;
    char *prefix;
    char *prev_prefix;
    int num, prev_num;
    cairo_int_status_t status;
    cairo_bool_t has_labels;

    /* Check if any labels defined */
    num_elems = _cairo_array_num_elements (&surface->page_labels);
    has_labels = FALSE;
    for (i = 0; i < num_elems; i++) {
 _cairo_array_copy_element (&surface->page_labels, i, &label);
 if (label) {
     has_labels = TRUE;
     break;
 }
    }

    if (!has_labels)
 return CAIRO_STATUS_SUCCESS;

    surface->page_labels_res = _cairo_pdf_surface_new_object (surface);
    if (surface->page_labels_res.id == 0)
 return _cairo_error (CAIRO_STATUS_NO_MEMORY);

    status = _cairo_pdf_surface_object_begin (surface, surface->page_labels_res);
    if (unlikely (status))
 return status;

    _cairo_output_stream_printf (surface->object_stream.stream,
     "<< /Nums [\n");
    prefix = NULL;
    prev_prefix = NULL;
    num = 0;
    prev_num = 0;
    for (i = 0; i < num_elems; i++) {
 _cairo_array_copy_element (&surface->page_labels, i, &label);
 if (label) {
     prefix = split_label (label, &num);
 } else {
     prefix = NULL;
     num = i + 1;
 }

 if (!strcmp_null (prefix, prev_prefix) || num != prev_num + 1) {
     _cairo_output_stream_printf (surface->object_stream.stream,  " %d << ", i);

     if (num)
  _cairo_output_stream_printf (surface->object_stream.stream,  "/S /D /St %d ", num);

     if (prefix) {
  char *s;
  status = _cairo_utf8_to_pdf_string (prefix, &s);
  if (unlikely (status))
      return status;

  _cairo_output_stream_printf (surface->object_stream.stream,  "/P %s ", s);
  free (s);
     }

     _cairo_output_stream_printf (surface->object_stream.stream,  ">>\n");
 }
 free (prev_prefix);
 prev_prefix = prefix;
 prefix = NULL;
 prev_num = num;
    }
    free (prefix);
    free (prev_prefix);
    _cairo_output_stream_printf (surface->object_stream.stream,
     " ]\n"
     ">>\n");
    _cairo_pdf_surface_object_end (surface);

    return CAIRO_STATUS_SUCCESS;
}

static void
_collect_external_dest (void *entry, void *closure)
{
    cairo_pdf_named_dest_t *dest = entry;
    cairo_pdf_surface_t *surface = closure;
    cairo_pdf_interchange_t *ic = &surface->interchange;

    if (!dest->attrs.internal)
 ic->sorted_dests[ic->num_dests++] = dest;
}

static int
_dest_compare (const void *a, const void *b)
{
    const cairo_pdf_named_dest_t * const *dest_a = a;
    const cairo_pdf_named_dest_t * const *dest_b = b;

    return strcmp ((*dest_a)->attrs.name, (*dest_b)->attrs.name);
}

static cairo_int_status_t
_cairo_pdf_interchange_write_document_dests (cairo_pdf_surface_t *surface)
{
    int i;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status;
    cairo_pdf_page_info_t *page_info;

    if (ic->num_dests == 0) {
 ic->dests_res.id = 0;
        return CAIRO_STATUS_SUCCESS;
    }

    ic->sorted_dests = calloc (ic->num_dests, sizeof (cairo_pdf_named_dest_t *));
    if (unlikely (ic->sorted_dests == NULL))
 return _cairo_error (CAIRO_STATUS_NO_MEMORY);

    ic->num_dests = 0;
    _cairo_hash_table_foreach (ic->named_dests, _collect_external_dest, surface);

    qsort (ic->sorted_dests, ic->num_dests, sizeof (cairo_pdf_named_dest_t *), _dest_compare);

    ic->dests_res = _cairo_pdf_surface_new_object (surface);
    if (ic->dests_res.id == 0)
 return _cairo_error (CAIRO_STATUS_NO_MEMORY);

    status = _cairo_pdf_surface_object_begin (surface, ic->dests_res);
    if (unlikely (status))
 return status;

    _cairo_output_stream_printf (surface->object_stream.stream,
     "<< /Names [\n");
    for (i = 0; i < ic->num_dests; i++) {
 cairo_pdf_named_dest_t *dest = ic->sorted_dests[i];
 double x = 0;
 double y = 0;
 char *name = NULL;

 if (dest->attrs.internal)
     continue;

 if (dest->extents.valid) {
     x = dest->extents.extents.x;
     y = dest->extents.extents.y;
 }

 if (dest->attrs.x_valid)
     x = dest->attrs.x;

 if (dest->attrs.y_valid)
     y = dest->attrs.y;

 status = _cairo_utf8_to_pdf_string (dest->attrs.name, &name);
 if (unlikely (status))
     return status;

 page_info = _cairo_array_index (&surface->pages, dest->page - 1);
 _cairo_output_stream_printf (surface->object_stream.stream,
         " %s [%d 0 R /XYZ %f %f 0]\n",
         name,
         page_info->page_res.id,
         x,
         page_info->height - y);
 free (name);

    }
    _cairo_output_stream_printf (surface->object_stream.stream,
     " ]\n"
     ">>\n");
    _cairo_pdf_surface_object_end (surface);

    return CAIRO_STATUS_SUCCESS;
}

static cairo_int_status_t
cairo_pdf_interchange_write_names_dict (cairo_pdf_surface_t *surface)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status;

    status = _cairo_pdf_interchange_write_document_dests (surface);
    if (unlikely (status))
 return status;

    surface->names_dict_res.id = 0;
    if (ic->dests_res.id != 0) {
 surface->names_dict_res = _cairo_pdf_surface_new_object (surface);
 if (surface->names_dict_res.id == 0)
     return _cairo_error (CAIRO_STATUS_NO_MEMORY);

 status = _cairo_pdf_surface_object_begin (surface, surface->names_dict_res);
 if (unlikely (status))
     return status;

 _cairo_output_stream_printf (surface->object_stream.stream,
         "<< /Dests %d 0 R >>\n",
         ic->dests_res.id);
 _cairo_pdf_surface_object_end (surface);
    }

    return CAIRO_STATUS_SUCCESS;
}

static cairo_int_status_t
cairo_pdf_interchange_write_docinfo (cairo_pdf_surface_t *surface)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status;
    unsigned int i, num_elems;
    struct metadata *data;
    unsigned char *p;

    surface->docinfo_res = _cairo_pdf_surface_new_object (surface);
    if (surface->docinfo_res.id == 0)
 return _cairo_error (CAIRO_STATUS_NO_MEMORY);

    status = _cairo_pdf_surface_object_begin (surface, surface->docinfo_res);
    if (unlikely (status))
 return status;

    _cairo_output_stream_printf (surface->object_stream.stream,
     "<< /Producer (cairo %s (https://cairographics.org))\n",
     cairo_version_string ());

    if (ic->docinfo.title)
 _cairo_output_stream_printf (surface->object_stream.stream, " /Title %s\n", ic->docinfo.title);

    if (ic->docinfo.author)
 _cairo_output_stream_printf (surface->object_stream.stream, " /Author %s\n", ic->docinfo.author);

    if (ic->docinfo.subject)
 _cairo_output_stream_printf (surface->object_stream.stream, " /Subject %s\n", ic->docinfo.subject);

    if (ic->docinfo.keywords)
 _cairo_output_stream_printf (surface->object_stream.stream, " /Keywords %s\n", ic->docinfo.keywords);

    if (ic->docinfo.creator)
 _cairo_output_stream_printf (surface->object_stream.stream, " /Creator %s\n", ic->docinfo.creator);

    if (ic->docinfo.create_date)
 _cairo_output_stream_printf (surface->object_stream.stream, " /CreationDate %s\n", ic->docinfo.create_date);

    if (ic->docinfo.mod_date)
 _cairo_output_stream_printf (surface->object_stream.stream, " /ModDate %s\n", ic->docinfo.mod_date);

    num_elems = _cairo_array_num_elements (&ic->custom_metadata);
    for (i = 0; i < num_elems; i++) {
 data = _cairo_array_index (&ic->custom_metadata, i);
 if (data->value) {
     _cairo_output_stream_printf (surface->object_stream.stream, " /");
     /* The name can be any utf8 string. Use hex codes as
     * specified in section 7.3.5 of PDF reference
     */

     p = (unsigned char *)data->name;
     while (*p) {
  if (*p < 0x21 || *p > 0x7e || *p == '#' || *p == '/')
      _cairo_output_stream_printf (surface->object_stream.stream, "#%02x", *p);
  else
      _cairo_output_stream_printf (surface->object_stream.stream, "%c", *p);
  p++;
     }
     _cairo_output_stream_printf (surface->object_stream.stream, " %s\n", data->value);
 }
    }

    _cairo_output_stream_printf (surface->object_stream.stream,
     ">>\n");
    _cairo_pdf_surface_object_end (surface);

    return CAIRO_STATUS_SUCCESS;
}

static cairo_int_status_t
_cairo_pdf_interchange_begin_structure_tag (cairo_pdf_surface_t    *surface,
         cairo_tag_type_t        tag_type,
         const char             *name,
         const char             *attributes)
{
    int mcid;
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_command_entry_t *command_entry;
    cairo_pdf_struct_tree_node_t *parent_node;
    unsigned int content_command_id;

    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
 ic->content_emitted = FALSE;
 status = add_tree_node (surface, ic->current_analyze_node, name, attributes, &ic->current_analyze_node);
 if (unlikely (status))
     return status;

 status = command_list_add (surface, ic->command_id, PDF_BEGIN);
 if (unlikely (status))
     return status;

 /* Add to command_id to node map. */
 command_entry = _cairo_malloc (sizeof(cairo_pdf_command_entry_t));
 command_entry->recording_id = ic->recording_id;
 command_entry->command_id = ic->command_id;
 command_entry->node = ic->current_analyze_node;
 _cairo_pdf_command_init_key (command_entry);
 status = _cairo_hash_table_insert (ic->command_to_node_map, &command_entry->base);
 if (unlikely(status))
     return status;

 if (tag_type & TAG_TYPE_LINK) {
     status = add_annotation (surface, ic->current_analyze_node, name, attributes);
     if (unlikely (status))
  return status;
 }

 if (ic->current_analyze_node->type == PDF_NODE_CONTENT) {
     cairo_pdf_content_tag_t *content = _cairo_malloc (sizeof(cairo_pdf_content_tag_t));
     content->node = ic->current_analyze_node;
     _cairo_pdf_content_tag_init_key (content);
     status = _cairo_hash_table_insert (ic->content_tag_map, &content->base);
     if (unlikely (status))
  return status;
 }

 ic->content_emitted = FALSE;

    } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
 if (ic->marked_content_open) {
     status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
     ic->marked_content_open = FALSE;
     if (unlikely (status))
  return status;
 }

 ic->current_render_node = lookup_node_for_command (surface, ic->recording_id, ic->command_id);
 if (ic->current_render_node->type == PDF_NODE_ARTIFACT) {
     if (command_list_has_content (surface, ic->command_id, NULL)) {
  status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators, name, -1);
  ic->marked_content_open = TRUE;
     }
 } else if (ic->current_render_node->type == PDF_NODE_CONTENT_REF) {
     parent_node = ic->current_render_node->parent;
     add_child_to_mcid_array (surface, parent_node, ic->command_id, ic->current_render_node);
 } else {
     parent_node = ic->current_render_node->parent;
     add_child_to_mcid_array (surface, parent_node, ic->command_id, ic->current_render_node);
     if (command_list_has_content (surface, ic->command_id, &content_command_id)) {
  add_mcid_to_node (surface, ic->current_render_node, content_command_id, &mcid);
  const char *tag_name = name;
  if (ic->current_render_node->type == PDF_NODE_CONTENT)
      tag_name = ic->current_render_node->attributes.content.tag_name;

  status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators, tag_name, mcid);
  ic->marked_content_open = TRUE;
     }
 }
    }

    return status;
}

static cairo_int_status_t
_cairo_pdf_interchange_begin_dest_tag (cairo_pdf_surface_t    *surface,
           cairo_tag_type_t        tag_type,
           const char             *name,
           const char             *attributes)
{
    cairo_pdf_named_dest_t *dest;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;

    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
 dest = calloc (1, sizeof (cairo_pdf_named_dest_t));
 if (unlikely (dest == NULL))
     return _cairo_error (CAIRO_STATUS_NO_MEMORY);

 status = _cairo_tag_parse_dest_attributes (attributes, &dest->attrs);
 if (unlikely (status))
 {
     free (dest);
     return status;
 }

 dest->page = _cairo_array_num_elements (&surface->pages);
 init_named_dest_key (dest);
 status = _cairo_hash_table_insert (ic->named_dests, &dest->base);
 if (unlikely (status)) {
     free (dest->attrs.name);
     free (dest);
     return status;
 }

 _cairo_tag_stack_set_top_data (&ic->analysis_tag_stack, dest);
 ic->num_dests++;
    }

    return status;
}

cairo_int_status_t
_cairo_pdf_interchange_tag_begin (cairo_pdf_surface_t    *surface,
      const char             *name,
      const char             *attributes)
{
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    cairo_tag_type_t tag_type;
    cairo_pdf_interchange_t *ic = &surface->interchange;

    if (ic->ignore_current_surface)
        return CAIRO_STATUS_SUCCESS;

    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
 status = _cairo_tag_stack_push (&ic->analysis_tag_stack, name, attributes);

    } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
 status = _cairo_tag_stack_push (&ic->render_tag_stack, name, attributes);
    }
    if (unlikely (status))
 return status;

    tag_type = _cairo_tag_get_type (name);
    if (tag_type & (TAG_TYPE_STRUCTURE|TAG_TYPE_CONTENT|TAG_TYPE_CONTENT_REF|TAG_TYPE_ARTIFACT)) {
 status = _cairo_pdf_interchange_begin_structure_tag (surface, tag_type, name, attributes);
 if (unlikely (status))
     return status;
    }

    if (tag_type & TAG_TYPE_DEST) {
 status = _cairo_pdf_interchange_begin_dest_tag (surface, tag_type, name, attributes);
 if (unlikely (status))
     return status;
    }

    return status;
}

static cairo_int_status_t
_cairo_pdf_interchange_end_structure_tag (cairo_pdf_surface_t    *surface,
       cairo_tag_type_t        tag_type,
       cairo_tag_stack_elem_t *elem)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    int mcid;
    unsigned int content_command_id;

    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
 assert (ic->current_analyze_node->parent != NULL);
 status = command_list_add (surface, ic->command_id, PDF_END);
 if (unlikely (status))
     return status;

 ic->content_emitted = FALSE;
 ic->current_analyze_node = ic->current_analyze_node->parent;

    } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
 if (ic->marked_content_open) {
     status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
     ic->marked_content_open = FALSE;
     if (unlikely (status))
  return status;
 }
 ic->current_render_node = ic->current_render_node->parent;
 if (ic->current_render_node->parent &&
     command_list_has_content (surface, ic->command_id, &content_command_id))
 {
     add_mcid_to_node (surface, ic->current_render_node, content_command_id, &mcid);
     status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators,
           ic->current_render_node->name, mcid);
     ic->marked_content_open = TRUE;
 }
    }

    return status;
}

cairo_int_status_t
_cairo_pdf_interchange_tag_end (cairo_pdf_surface_t *surface,
    const char          *name)
{
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_tag_type_t tag_type;
    cairo_tag_stack_elem_t *elem;

    if (ic->ignore_current_surface)
        return CAIRO_STATUS_SUCCESS;

    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
 status = _cairo_tag_stack_pop (&ic->analysis_tag_stack, name, &elem);

    } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
 status = _cairo_tag_stack_pop (&ic->render_tag_stack, name, &elem);
    }
    if (unlikely (status))
 return status;

    tag_type = _cairo_tag_get_type (name);
    if (tag_type & (TAG_TYPE_STRUCTURE|TAG_TYPE_CONTENT|TAG_TYPE_CONTENT_REF|TAG_TYPE_ARTIFACT)) {
 status = _cairo_pdf_interchange_end_structure_tag (surface, tag_type, elem);
 if (unlikely (status))
     goto cleanup;
    }

  cleanup:
    _cairo_tag_stack_free_elem (elem);

    return status;
}

cairo_int_status_t
_cairo_pdf_interchange_command_id (cairo_pdf_surface_t  *surface,
       unsigned int          recording_id,
       unsigned int          command_id)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    int mcid;
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;

    ic->recording_id = recording_id;
    ic->command_id = command_id;

    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER && ic->current_render_node) {
 /* TODO If the group does not have tags we don't need to close the current tag. */
 if (command_list_is_group (surface, command_id)) {
     /* A "Do /xnnn" can not be inside a tag (since the
     * XObject may also contain tags). Close the tag.
     */

     if (ic->marked_content_open) {
  status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
  ic->marked_content_open = FALSE;
     }
     /* If there is any more content after this and we are
     * inside a tag (current node is not the root node),
     * ensure that the next command will open the tag.
     */

     if (command_list_has_content (surface, command_id, NULL) && ic->current_render_node->parent) {
  ic->render_next_command_has_content = TRUE;
     }
 } else if (ic->render_next_command_has_content) {
     /* After a "Do /xnnn" operation, if there is more content, open the tag. */
     add_mcid_to_node (surface, ic->current_render_node, ic->command_id, &mcid);
     status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators,
           ic->current_render_node->name, mcid);
     ic->marked_content_open = TRUE;
     ic->render_next_command_has_content = FALSE;
 }
    }

    return status;
}

/* Check if this use of recording surface is or will need to be part of the the struct tree */
cairo_bool_t
_cairo_pdf_interchange_struct_tree_requires_recording_surface (
    cairo_pdf_surface_t           *surface,
    const cairo_surface_pattern_t *recording_surface_pattern,
    cairo_analysis_source_t        source_type)
{
    cairo_surface_t *recording_surface = recording_surface_pattern->surface;
    cairo_surface_t *free_me = NULL;
    cairo_bool_t requires_recording = FALSE;

    if (recording_surface_pattern->base.extend != CAIRO_EXTEND_NONE)
 return FALSE;

    if (_cairo_surface_is_snapshot (recording_surface))
 free_me = recording_surface = _cairo_surface_snapshot_get_target (recording_surface);

    if (_cairo_surface_is_recording(recording_surface) &&
        _cairo_recording_surface_has_tags(recording_surface))
    {
 /* Check if tags are to be ignored in this source */
 switch (source_type) {
     case CAIRO_ANALYSIS_SOURCE_PAINT:
     case CAIRO_ANALYSIS_SOURCE_FILL:
  requires_recording = TRUE;
  break;
     case CAIRO_ANALYSIS_SOURCE_MASK: /* TODO: allow SOURCE_MASK with solid MASK_MASK */
     case CAIRO_ANALYSIS_MASK_MASK:
     case CAIRO_ANALYSIS_SOURCE_STROKE:
     case CAIRO_ANALYSIS_SOURCE_SHOW_GLYPHS:
     case CAIRO_ANALYSIS_SOURCE_NONE:
  break;
 }
    }

    cairo_surface_destroy (free_me);
    return requires_recording;
}

/* Called at the start of a recording group during analyze. This will
 * be called during the analysis of the drawing operation. */

cairo_int_status_t
_cairo_pdf_interchange_recording_source_surface_begin (
    cairo_pdf_surface_t           *surface,
    const cairo_surface_pattern_t *recording_surface_pattern,
    unsigned int                   region_id,
    cairo_analysis_source_t        source_type)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_recording_surface_stack_entry_t element;
    cairo_int_status_t status;

    /* A new recording surface is being replayed */
    ic->ignore_current_surface = TRUE;
    if (_cairo_pdf_interchange_struct_tree_requires_recording_surface (surface,
               recording_surface_pattern,
               source_type))
    {
 ic->ignore_current_surface = FALSE;
    }

    element.ignore_surface = ic->ignore_current_surface;
    element.current_node = ic->current_analyze_node;
    ic->content_emitted = FALSE;

    /* Push to stack so that the current source identifiers can be
     * restored after this recording surface has ended. */

    status = _cairo_array_append (&ic->recording_surface_stack, &element);
    if (status)
 return status;

    if (ic->ignore_current_surface)
 return CAIRO_STATUS_SUCCESS;

    status = command_list_push_group (surface, ic->command_id, recording_surface_pattern->surface, region_id);
    if (unlikely (status))
 return status;

    return CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN;
}

/* Called at the end of a recording group during analyze. */
cairo_int_status_t
_cairo_pdf_interchange_recording_source_surface_end (
    cairo_pdf_surface_t           *surface,
    const cairo_surface_pattern_t *recording_surface_pattern,
    unsigned int                   region_id,
    cairo_analysis_source_t        source_type)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=98 H=92 G=94

¤ Dauer der Verarbeitung: 0.23 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.