summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrian Johnson <ajohnson@redneon.com>2023-02-19 21:10:58 +1030
committerAdrian Johnson <ajohnson@redneon.com>2023-04-18 18:27:12 +0930
commitb53b48116e610d61cdf630c24a11b59a18345e16 (patch)
tree3bc8f3410e795ce81c1408b9d7f3a217033d29ba
parente7ed40a71dac04cb4c608b409b04577d01f08454 (diff)
downloadcairo-b53b48116e610d61cdf630c24a11b59a18345e16.tar.gz
Make cairo_tag_begin/end work correctly in groups
Fixes #508
-rw-r--r--src/cairo-analysis-surface.c112
-rw-r--r--src/cairo-array-private.h7
-rw-r--r--src/cairo-array.c36
-rw-r--r--src/cairo-image-source.c2
-rw-r--r--src/cairo-paginated-surface.c20
-rw-r--r--src/cairo-pdf-interchange.c1427
-rw-r--r--src/cairo-pdf-operators.c14
-rw-r--r--src/cairo-pdf-surface-private.h199
-rw-r--r--src/cairo-pdf-surface.c433
-rw-r--r--src/cairo-quartz-surface.c3
-rw-r--r--src/cairo-recording-surface-private.h24
-rw-r--r--src/cairo-recording-surface.c76
-rw-r--r--src/cairo-spans-compositor.c2
-rw-r--r--src/cairo-surface-backend-private.h25
-rw-r--r--src/cairo-tag-attributes-private.h30
-rw-r--r--src/cairo-tag-attributes.c145
-rw-r--r--src/cairo-tag-stack-private.h9
-rw-r--r--src/cairo-tag-stack.c37
-rw-r--r--src/cairo-traps-compositor.c2
-rw-r--r--src/cairo-types-private.h11
-rw-r--r--src/cairo-xcb-surface-render.c3
-rw-r--r--src/cairo-xlib-source.c3
-rw-r--r--src/cairo.h2
-rw-r--r--src/cairoint.h1
-rw-r--r--test/cairo-test.c8
-rw-r--r--test/cairo-test.h2
-rwxr-xr-xtest/check-pdf-structure.sh21
-rw-r--r--test/meson.build7
-rw-r--r--test/pdf-structure.c568
-rw-r--r--test/pdf-tagged-text.c62
-rw-r--r--test/reference/pdf-structure-group-ref.ref.txt6
-rw-r--r--test/reference/pdf-structure-group.ref.txt6
-rw-r--r--test/reference/pdf-structure-multipage-group.ref.txt8
-rw-r--r--test/reference/pdf-structure-multipage-group2.ref.txt10
-rw-r--r--test/reference/pdf-structure-multipage-simple-ref.ref.txt12
-rw-r--r--test/reference/pdf-structure-multipage-simple.ref.txt12
-rw-r--r--test/reference/pdf-structure-repeated-group.ref.txt9
-rw-r--r--test/reference/pdf-structure-simple-ref.ref.txt11
-rw-r--r--test/reference/pdf-structure-simple.ref.txt11
39 files changed, 2891 insertions, 485 deletions
diff --git a/src/cairo-analysis-surface.c b/src/cairo-analysis-surface.c
index 0e7ba8a38..6889be38f 100644
--- a/src/cairo-analysis-surface.c
+++ b/src/cairo-analysis-surface.c
@@ -263,7 +263,8 @@ static cairo_int_status_t
_analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
const cairo_pattern_t *pattern,
cairo_rectangle_int_t *extents,
- unsigned int *regions_id)
+ unsigned int *regions_id,
+ cairo_analysis_source_t source_type)
{
const cairo_surface_pattern_t *surface_pattern;
cairo_analysis_surface_t *tmp;
@@ -273,6 +274,7 @@ _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
cairo_int_status_t analysis_status = CAIRO_INT_STATUS_SUCCESS;
cairo_bool_t surface_is_unbounded;
cairo_bool_t unused;
+ cairo_bool_t replay_all;
assert (pattern->type == CAIRO_PATTERN_TYPE_SURFACE);
surface_pattern = (const cairo_surface_pattern_t *) pattern;
@@ -288,7 +290,7 @@ _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
tmp = (cairo_analysis_surface_t *)
_cairo_analysis_surface_create (surface->target, surface->create_region_ids);
if (unlikely (tmp->base.status)) {
- status =tmp->base.status;
+ status = tmp->base.status;
goto cleanup1;
}
proxy = attach_proxy (source, &tmp->base);
@@ -298,7 +300,6 @@ _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
assert (status == CAIRO_INT_STATUS_SUCCESS);
_cairo_analysis_surface_set_ctm (&tmp->base, &p2d);
-
source = _cairo_surface_get_source (source, NULL);
surface_is_unbounded = (pattern->extend == CAIRO_EXTEND_REPEAT
|| pattern->extend == CAIRO_EXTEND_REFLECT);
@@ -307,22 +308,54 @@ _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
status = _cairo_recording_surface_region_array_attach (source, regions_id);
if (unlikely (status))
goto cleanup2;
+ }
+
+ replay_all = FALSE;
+ if (surface->target->backend->analyze_recording_surface) {
+ status = surface->target->backend->analyze_recording_surface (
+ surface->target,
+ surface_pattern,
+ surface->create_region_ids ? *regions_id : 0,
+ source_type,
+ TRUE);
+ if (status == CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN) {
+ /* Ensure all commands are replayed even if previously
+ * replayed and assigned to a region.*/
+ replay_all = TRUE;
+ status = CAIRO_INT_STATUS_SUCCESS;
+ }
+ if (unlikely (status))
+ goto cleanup3;
+ }
+ if (surface->create_region_ids) {
status = _cairo_recording_surface_replay_and_create_regions (source,
*regions_id,
&pattern->matrix,
&tmp->base,
- surface_is_unbounded);
+ surface_is_unbounded,
+ replay_all);
if (unlikely (status))
- goto cleanup2;
+ goto cleanup3;
} else {
- status = _cairo_recording_surface_replay_with_clip (source,
- &pattern->matrix,
- &tmp->base,
- NULL, /* target clip */
- surface_is_unbounded);
+ status = _cairo_recording_surface_replay_with_transform (source,
+ &pattern->matrix,
+ &tmp->base,
+ surface_is_unbounded,
+ replay_all);
if (unlikely (status))
- goto cleanup2;
+ goto cleanup3;
+ }
+
+ if (surface->target->backend->analyze_recording_surface) {
+ status = surface->target->backend->analyze_recording_surface (
+ surface->target,
+ surface_pattern,
+ surface->create_region_ids ? *regions_id : 0,
+ source_type,
+ FALSE);
+ if (unlikely (status))
+ goto cleanup3;
}
/* black background or mime data fills entire extents */
@@ -339,7 +372,7 @@ _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
if (status == CAIRO_INT_STATUS_IMAGE_FALLBACK)
status = CAIRO_INT_STATUS_SUCCESS;
if (unlikely (status))
- goto cleanup2;
+ goto cleanup3;
}
}
@@ -363,6 +396,10 @@ _analyze_recording_surface_pattern (cairo_analysis_surface_t *surface,
_cairo_box_round_to_rectangle (&tmp->page_bbox, extents);
}
+ cleanup3:
+ if (surface->create_region_ids && unlikely (status)) {
+ _cairo_recording_surface_region_array_remove (source, *regions_id);
+ }
cleanup2:
detach_proxy (proxy);
cleanup1:
@@ -454,7 +491,8 @@ _cairo_analysis_surface_paint (void *abstract_surface,
backend_status = _analyze_recording_surface_pattern (surface,
source,
&rec_extents,
- &surface->source_region_id);
+ &surface->source_region_id,
+ CAIRO_ANALYSIS_SOURCE_PAINT);
_cairo_rectangle_intersect (&extents, &rec_extents);
}
@@ -500,7 +538,8 @@ _cairo_analysis_surface_mask (void *abstract_surface,
_analyze_recording_surface_pattern (surface,
source,
&rec_extents,
- &surface->source_region_id);
+ &surface->source_region_id,
+ CAIRO_ANALYSIS_SOURCE_MASK);
if (_cairo_int_status_is_error (backend_source_status))
return backend_source_status;
@@ -516,7 +555,8 @@ _cairo_analysis_surface_mask (void *abstract_surface,
_analyze_recording_surface_pattern (surface,
mask,
&rec_extents,
- &surface->mask_region_id);
+ &surface->mask_region_id,
+ CAIRO_ANALYSIS_MASK_MASK);
if (_cairo_int_status_is_error (backend_mask_status))
return backend_mask_status;
@@ -578,7 +618,8 @@ _cairo_analysis_surface_stroke (void *abstract_surface,
backend_status = _analyze_recording_surface_pattern (surface,
source,
&rec_extents,
- &surface->source_region_id);
+ &surface->source_region_id,
+ CAIRO_ANALYSIS_SOURCE_STROKE);
_cairo_rectangle_intersect (&extents, &rec_extents);
}
@@ -633,7 +674,8 @@ _cairo_analysis_surface_fill (void *abstract_surface,
backend_status = _analyze_recording_surface_pattern (surface,
source,
&rec_extents,
- &surface->source_region_id);
+ &surface->source_region_id,
+ CAIRO_ANALYSIS_SOURCE_FILL);
_cairo_rectangle_intersect (&extents, &rec_extents);
}
@@ -703,7 +745,8 @@ _cairo_analysis_surface_show_glyphs (void *abstract_surface,
backend_status = _analyze_recording_surface_pattern (surface,
source,
&rec_extents,
- &surface->source_region_id);
+ &surface->source_region_id,
+ CAIRO_ANALYSIS_SOURCE_SHOW_GLYPHS);
_cairo_rectangle_intersect (&extents, &rec_extents);
}
@@ -787,7 +830,8 @@ _cairo_analysis_surface_show_text_glyphs (void *abstract_surface,
_analyze_recording_surface_pattern (surface,
source,
&rec_extents,
- &surface->source_region_id);
+ &surface->source_region_id,
+ CAIRO_ANALYSIS_SOURCE_SHOW_GLYPHS);
_cairo_rectangle_intersect (&extents, &rec_extents);
}
@@ -839,6 +883,25 @@ _cairo_analysis_surface_supports_color_glyph (void *abstract_sur
return TRUE;
}
+static cairo_int_status_t
+_cairo_analysis_surface_command_id (void *abstract_surface,
+ unsigned int recording_id,
+ unsigned int command_id)
+{
+ cairo_analysis_surface_t *surface = abstract_surface;
+ cairo_int_status_t backend_status;
+
+ backend_status = CAIRO_INT_STATUS_SUCCESS;
+ if (surface->target->backend->command_id != NULL) {
+ backend_status =
+ surface->target->backend->command_id (surface->target,
+ recording_id,
+ command_id);
+ }
+
+ return backend_status;
+}
+
static const cairo_surface_backend_t cairo_analysis_surface_backend = {
CAIRO_INTERNAL_SURFACE_TYPE_ANALYSIS,
@@ -874,7 +937,9 @@ static const cairo_surface_backend_t cairo_analysis_surface_backend = {
_cairo_analysis_surface_show_text_glyphs,
NULL, /* get_supported_mime_types */
_cairo_analysis_surface_tag,
- _cairo_analysis_surface_supports_color_glyph
+ _cairo_analysis_surface_supports_color_glyph,
+ NULL, /* analyze_recording_surface */
+ _cairo_analysis_surface_command_id,
};
cairo_surface_t *
@@ -1135,7 +1200,12 @@ static const cairo_surface_backend_t cairo_null_surface_backend = {
NULL, /* fill_stroke */
_show_glyphs_return_success, /* show_glyphs */
NULL, /* has_show_text_glyphs */
- NULL /* show_text_glyphs */
+ NULL, /* show_text_glyphs */
+ NULL, /* get_supported_mime_types */
+ NULL, /* tag */
+ NULL, /* supports_color_glyph */
+ NULL, /* analyze_recording_surface */
+ NULL, /* command_id*/
};
cairo_surface_t *
diff --git a/src/cairo-array-private.h b/src/cairo-array-private.h
index 8e779d41a..ff5b51f50 100644
--- a/src/cairo-array-private.h
+++ b/src/cairo-array-private.h
@@ -1,3 +1,4 @@
+/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
/* cairo - a vector graphics library with display and print output
*
* Copyright © 2002 University of Southern California
@@ -88,6 +89,12 @@ _cairo_array_size (const cairo_array_t *array);
cairo_private void
_cairo_array_sort (const cairo_array_t *array, int (*compar)(const void *, const void *));
+cairo_private cairo_bool_t
+_cairo_array_pop_element (cairo_array_t *array, void *dst);
+
+cairo_private void *
+_cairo_array_last_element (cairo_array_t *array);
+
CAIRO_END_DECLS
#endif /* CAIRO_ARRAY_PRIVATE_H */
diff --git a/src/cairo-array.c b/src/cairo-array.c
index 1c91b7c73..064b63f29 100644
--- a/src/cairo-array.c
+++ b/src/cairo-array.c
@@ -542,3 +542,39 @@ _cairo_array_sort (const cairo_array_t *array, int (*compar)(const void *, const
{
qsort (array->elements, array->num_elements, array->element_size, compar);
}
+
+/**
+ * _cairo_array_pop_element:
+ * @array: a #cairo_array_t
+ * Returns: A TRUE if element successfully popped, FALSE if the array is empty.
+ *
+ * Copy the last element out of the array from index @index into the
+ * location pointed to by @dst and remove the element from the array.
+ **/
+cairo_bool_t
+_cairo_array_pop_element (cairo_array_t *array, void *dst)
+{
+ if (array->num_elements > 0) {
+ _cairo_array_copy_element (array, array->num_elements - 1, dst);
+ array->num_elements--;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * _cairo_array_top_element:
+ * @array: a #cairo_array_t
+ * Returns: A pointer to the last of object or NULL if array is empty.
+ *
+ * Get the pointer to the last element of of the array.
+ **/
+void *
+_cairo_array_last_element (cairo_array_t *array)
+{
+ if (array->num_elements > 0)
+ return _cairo_array_index (array, array->num_elements - 1);
+
+ return NULL;
+}
diff --git a/src/cairo-image-source.c b/src/cairo-image-source.c
index aafdaeded..b8c1c88f5 100644
--- a/src/cairo-image-source.c
+++ b/src/cairo-image-source.c
@@ -1225,7 +1225,7 @@ _pixman_image_for_recording (cairo_image_surface_t *dst,
/* Handle recursion by returning future reads from the current image */
proxy = attach_proxy (source, clone);
- status = _cairo_recording_surface_replay_with_clip (source, m, clone, NULL, FALSE);
+ status = _cairo_recording_surface_replay_with_clip (source, m, clone, NULL);
if (clone->foreground_used)
dst->base.foreground_used = clone->foreground_used;
detach_proxy (source, proxy);
diff --git a/src/cairo-paginated-surface.c b/src/cairo-paginated-surface.c
index e079a9a28..ac24745e3 100644
--- a/src/cairo-paginated-surface.c
+++ b/src/cairo-paginated-surface.c
@@ -403,6 +403,7 @@ _paint_page (cairo_paginated_surface_t *surface)
cairo_int_status_t status;
cairo_bool_t has_supported, has_page_fallback, has_finegrained_fallback;
unsigned int regions_id = 0;
+ cairo_bool_t replay_all;
if (unlikely (surface->target->status))
return surface->target->status;
@@ -416,13 +417,20 @@ _paint_page (cairo_paginated_surface_t *surface)
if (unlikely (status))
goto FAIL;
+ replay_all = FALSE;
+ if (surface->target->backend->analyze_recording_surface &&
+ _cairo_recording_surface_has_tags (surface->recording_surface))
+ {
+ replay_all = TRUE;
+ }
+
status = _cairo_recording_surface_region_array_attach (surface->recording_surface, &regions_id);
if (status)
goto FAIL;
status = _cairo_recording_surface_replay_and_create_regions (surface->recording_surface,
regions_id,
- NULL, analysis, FALSE);
+ NULL, analysis, FALSE, replay_all);
if (status)
goto FAIL;
@@ -467,12 +475,12 @@ _paint_page (cairo_paginated_surface_t *surface)
has_finegrained_fallback = FALSE;
}
- if (has_supported) {
- status = surface->backend->set_paginated_mode (surface->target,
- CAIRO_PAGINATED_MODE_RENDER);
- if (unlikely (status))
- goto FAIL;
+ status = surface->backend->set_paginated_mode (surface->target,
+ CAIRO_PAGINATED_MODE_RENDER);
+ if (unlikely (status))
+ goto FAIL;
+ if (has_supported) {
status = _cairo_recording_surface_replay_region (surface->recording_surface,
regions_id,
NULL,
diff --git a/src/cairo-pdf-interchange.c b/src/cairo-pdf-interchange.c
index cb80151d1..2f97b081c 100644
--- a/src/cairo-pdf-interchange.c
+++ b/src/cairo-pdf-interchange.c
@@ -52,6 +52,8 @@
#include "cairo-array-private.h"
#include "cairo-error-private.h"
#include "cairo-output-stream-private.h"
+#include "cairo-recording-surface-private.h"
+#include "cairo-surface-snapshot-inline.h"
#include <time.h>
@@ -62,6 +64,275 @@
#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,
@@ -96,9 +367,11 @@ 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))
@@ -111,21 +384,26 @@ add_tree_node (cairo_pdf_surface_t *surface,
node->parent = parent;
cairo_list_init (&node->children);
- _cairo_array_init (&node->mcid, sizeof(struct page_mcid));
- node->annot_res.id = 0;
+ _cairo_array_init (&node->mcid, sizeof (cairo_pdf_page_mcid_t));
+ node->annot = NULL;
node->extents.valid = FALSE;
- cairo_list_init (&node->extents.link);
cairo_list_add_tail (&node->link, &parent->children);
- *new_node = node;
- return CAIRO_STATUS_SUCCESS;
-}
+ 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;
+ }
-static cairo_bool_t
-is_leaf_node (cairo_pdf_struct_tree_node_t *node)
-{
- return node->parent && cairo_list_is_empty (&node->children) ;
+ *new_node = node;
+ return status;
}
static void
@@ -137,23 +415,30 @@ free_node (cairo_pdf_struct_tree_node_t *node)
return;
cairo_list_foreach_entry_safe (child, next, cairo_pdf_struct_tree_node_t,
- &node->children, link)
+ &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,
- int page,
+ unsigned int command_id,
int *mcid)
{
- struct page_mcid mcid_elem;
+ cairo_pdf_page_mcid_t mcid_elem;
cairo_int_status_t status;
cairo_pdf_interchange_t *ic = &surface->interchange;
@@ -161,12 +446,33 @@ add_mcid_to_node (cairo_pdf_surface_t *surface,
if (unlikely (status))
return status;
- mcid_elem.page = page;
+ 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,
@@ -177,7 +483,7 @@ add_annotation (cairo_pdf_surface_t *surface,
cairo_pdf_interchange_t *ic = &surface->interchange;
cairo_pdf_annotation_t *annot;
- annot = malloc (sizeof(cairo_pdf_annotation_t));
+ annot = _cairo_malloc (sizeof (cairo_pdf_annotation_t));
if (unlikely (annot == NULL))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
@@ -187,8 +493,16 @@ add_annotation (cairo_pdf_surface_t *surface,
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;
@@ -197,10 +511,7 @@ add_annotation (cairo_pdf_surface_t *surface,
static void
free_annotation (cairo_pdf_annotation_t *annot)
{
- _cairo_array_fini (&annot->link_attrs.rects);
- free (annot->link_attrs.dest);
- free (annot->link_attrs.uri);
- free (annot->link_attrs.file);
+ _cairo_tag_free_link_attributes (&annot->link_attrs);
free (annot);
}
@@ -220,15 +531,72 @@ cairo_pdf_interchange_clear_annotations (cairo_pdf_surface_t *surface)
_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)
+ cairo_pdf_struct_tree_node_t *node,
+ int depth)
{
- struct page_mcid *mcid_elem;
- int i, num_mcid, first_page;
- cairo_pdf_resource_t *page_res;
- cairo_pdf_struct_tree_node_t *child;
+ 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))
@@ -241,57 +609,98 @@ cairo_pdf_interchange_write_node_object (cairo_pdf_surface_t *surface
node->name,
node->parent->res.id);
- if (! cairo_list_is_empty (&node->children)) {
- if (cairo_list_is_singular (&node->children) && node->annot_res.id == 0) {
- child = cairo_list_first_entry (&node->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 [ ");
- if (node->annot_res.id != 0) {
+ /* 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,
- "<< /Type /OBJR /Obj %d 0 R >> ",
- node->annot_res.id);
- }
- cairo_list_foreach_entry (child, cairo_pdf_struct_tree_node_t,
- &node->children, link)
- {
- _cairo_output_stream_printf (surface->object_stream.stream, "%d 0 R ", child->res.id);
+ " /Pg %d 0 R\n",
+ page_info->page_res.id);
+ has_children = TRUE;
+ break;
}
- _cairo_output_stream_printf (surface->object_stream.stream, "]\n");
}
- } else {
- num_mcid = _cairo_array_num_elements (&node->mcid);
- if (num_mcid > 0 ) {
- mcid_elem = _cairo_array_index (&node->mcid, 0);
- first_page = mcid_elem->page;
- page_res = _cairo_array_index (&surface->pages, first_page - 1);
- _cairo_output_stream_printf (surface->object_stream.stream, " /Pg %d 0 R\n", page_res->id);
-
- if (num_mcid == 1 && node->annot_res.id == 0) {
- _cairo_output_stream_printf (surface->object_stream.stream, " /K %d\n", mcid_elem->mcid);
- } else {
- _cairo_output_stream_printf (surface->object_stream.stream, " /K [ ");
- if (node->annot_res.id != 0) {
- _cairo_output_stream_printf (surface->object_stream.stream,
- "<< /Type /OBJR /Obj %d 0 R >> ",
- node->annot_res.id);
- }
- for (i = 0; i < num_mcid; i++) {
- mcid_elem = _cairo_array_index (&node->mcid, i);
- page_res = _cairo_array_index (&surface->pages, mcid_elem->page - 1);
- if (mcid_elem->page == first_page) {
- _cairo_output_stream_printf (surface->object_stream.stream, "%d ", mcid_elem->mcid);
+
+ 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,
- "\n << /Type /MCR /Pg %d 0 R /MCID %d >> ",
- page_res->id,
- mcid_elem->mcid);
+ "<< /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);
}
- _cairo_output_stream_printf (surface->object_stream.stream, "]\n");
}
+
+ 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");
@@ -324,7 +733,7 @@ _named_dest_pluck (void *entry, void *closure)
cairo_hash_table_t *table = closure;
_cairo_hash_table_remove (table, &dest->base);
- free (dest->attrs.name);
+ _cairo_tag_free_dest_attributes (&dest->attrs);
free (dest);
}
@@ -335,21 +744,20 @@ cairo_pdf_interchange_write_explicit_dest (cairo_pdf_surface_t *surface,
double x,
double y)
{
- cairo_pdf_resource_t res;
- double height;
+ cairo_pdf_page_info_t *page_info;
+
+ page_info = _cairo_array_index (&surface->pages, page - 1);
- _cairo_array_copy_element (&surface->page_heights, page - 1, &height);
- _cairo_array_copy_element (&surface->pages, page - 1, &res);
if (has_pos) {
_cairo_output_stream_printf (surface->object_stream.stream,
"[%d 0 R /XYZ %f %f 0]\n",
- res.id,
+ page_info->page_res.id,
x,
- height - y);
+ page_info->height - y);
} else {
_cairo_output_stream_printf (surface->object_stream.stream,
"[%d 0 R /XYZ null null 0]\n",
- res.id);
+ page_info->page_res.id);
}
return CAIRO_STATUS_SUCCESS;
@@ -601,7 +1009,8 @@ cairo_pdf_interchange_write_link_action (cairo_pdf_surface_t *surface,
static cairo_int_status_t
cairo_pdf_interchange_write_annot (cairo_pdf_surface_t *surface,
- cairo_pdf_annotation_t *annot)
+ 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;
@@ -621,23 +1030,19 @@ cairo_pdf_interchange_write_annot (cairo_pdf_surface_t *surface,
sp = _cairo_array_num_elements (&ic->parent_tree) - 1;
- node->annot_res = _cairo_pdf_surface_new_object (surface);
- if (node->annot_res.id == 0)
- return _cairo_error (CAIRO_STATUS_NO_MEMORY);
-
- status = _cairo_array_append (&surface->page_annots, &node->annot_res);
- if (unlikely (status))
- return status;
-
- status = _cairo_pdf_surface_object_begin (surface, node->annot_res);
+ 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"
- " /StructParent %d\n",
- sp);
+ " /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) {
@@ -676,7 +1081,7 @@ cairo_pdf_interchange_write_annot (cairo_pdf_surface_t *surface,
return status;
_cairo_output_stream_printf (surface->object_stream.stream,
- " /BS << /W 0 >>"
+ " /BS << /W 0 >>\n"
">>\n");
_cairo_pdf_surface_object_end (surface);
@@ -689,25 +1094,27 @@ cairo_pdf_interchange_write_annot (cairo_pdf_surface_t *surface,
static cairo_int_status_t
cairo_pdf_interchange_walk_struct_tree (cairo_pdf_surface_t *surface,
cairo_pdf_struct_tree_node_t *node,
- cairo_int_status_t (*func) (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;
- if (node->parent) {
- status = func (surface, node);
- if (unlikely (status))
- return status;
- }
+ 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, func);
+ status = cairo_pdf_interchange_walk_struct_tree (surface, child, depth, func);
if (unlikely (status))
return status;
}
+ depth--;
return CAIRO_STATUS_SUCCESS;
}
@@ -722,15 +1129,12 @@ cairo_pdf_interchange_write_struct_tree (cairo_pdf_surface_t *surface)
if (cairo_list_is_empty (&ic->struct_root->children))
return CAIRO_STATUS_SUCCESS;
- surface->struct_tree_root = _cairo_pdf_surface_new_object (surface);
- if (surface->struct_tree_root.id == 0)
- return _cairo_error (CAIRO_STATUS_NO_MEMORY);
-
- ic->struct_root->res = surface->struct_tree_root;
-
- cairo_pdf_interchange_walk_struct_tree (surface, ic->struct_root, cairo_pdf_interchange_write_node_object);
-
- child = cairo_list_first_entry (&ic->struct_root->children, cairo_pdf_struct_tree_node_t, link);
+ 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))
@@ -750,6 +1154,9 @@ cairo_pdf_interchange_write_struct_tree (cairo_pdf_surface_t *surface)
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");
@@ -763,18 +1170,31 @@ cairo_pdf_interchange_write_struct_tree (cairo_pdf_surface_t *surface)
}
static cairo_int_status_t
-cairo_pdf_interchange_write_page_annots (cairo_pdf_surface_t *surface)
+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;
+ 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_pdf_annotation_t * annot;
-
_cairo_array_copy_element (&ic->annots, i, &annot);
- status = cairo_pdf_interchange_write_annot (surface, 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;
}
@@ -783,43 +1203,72 @@ cairo_pdf_interchange_write_page_annots (cairo_pdf_surface_t *surface)
}
static cairo_int_status_t
-cairo_pdf_interchange_write_page_parent_elems (cairo_pdf_surface_t *surface)
+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_resource_t res;
cairo_pdf_interchange_t *ic = &surface->interchange;
cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
- surface->page_parent_tree = -1;
num_elems = _cairo_array_num_elements (&ic->mcid_to_tree);
- if (num_elems > 0) {
- res = _cairo_pdf_surface_new_object (surface);
- if (res.id == 0)
- return _cairo_error (CAIRO_STATUS_NO_MEMORY);
-
- status = _cairo_pdf_surface_object_begin (surface, res);
- if (unlikely (status))
- return status;
+ status = _cairo_pdf_surface_object_begin (surface, ic->content_parent_res);
+ if (unlikely (status))
+ return status;
- _cairo_output_stream_printf (surface->object_stream.stream,
+ _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);
-
- status = _cairo_array_append (&ic->parent_tree, &res);
- surface->page_parent_tree = _cairo_array_num_elements (&ic->parent_tree) - 1;
+ 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;
@@ -1178,6 +1627,7 @@ _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;
@@ -1205,10 +1655,8 @@ _cairo_pdf_interchange_write_document_dests (cairo_pdf_surface_t *surface)
"<< /Names [\n");
for (i = 0; i < ic->num_dests; i++) {
cairo_pdf_named_dest_t *dest = ic->sorted_dests[i];
- cairo_pdf_resource_t page_res;
double x = 0;
double y = 0;
- double height;
if (dest->attrs.internal)
continue;
@@ -1224,14 +1672,13 @@ _cairo_pdf_interchange_write_document_dests (cairo_pdf_surface_t *surface)
if (dest->attrs.y_valid)
y = dest->attrs.y;
- _cairo_array_copy_element (&surface->pages, dest->page - 1, &page_res);
- _cairo_array_copy_element (&surface->page_heights, dest->page - 1, &height);
+ 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",
dest->attrs.name,
- page_res.id,
+ page_info->page_res.id,
x,
- height - y);
+ page_info->height - y);
}
_cairo_output_stream_printf (surface->object_stream.stream,
" ]\n"
@@ -1345,32 +1792,79 @@ _cairo_pdf_interchange_begin_structure_tag (cairo_pdf_surface_t *surface,
const char *name,
const char *attributes)
{
- int page_num, mcid;
+ 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) {
- status = add_tree_node (surface, ic->current_node, name, &ic->current_node);
+ 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;
- _cairo_tag_stack_set_top_data (&ic->analysis_tag_stack, ic->current_node);
+ /* 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_node, name, attributes);
+ status = add_annotation (surface, ic->current_analyze_node, name, attributes);
if (unlikely (status))
return status;
+ }
- cairo_list_add_tail (&ic->current_node->extents.link, &ic->extents_list);
+ 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) {
- ic->current_node = _cairo_tag_stack_top_elem (&ic->render_tag_stack)->data;
- assert (ic->current_node != NULL);
- if (is_leaf_node (ic->current_node)) {
- page_num = _cairo_array_num_elements (&surface->pages);
- add_mcid_to_node (surface, ic->current_node, page_num, &mcid);
- status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators, name, mcid);
+ 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;
+ }
}
}
@@ -1402,15 +1896,13 @@ _cairo_pdf_interchange_begin_dest_tag (cairo_pdf_surface_t *surface,
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))
- {
+ if (unlikely (status)) {
free (dest->attrs.name);
free (dest);
return status;
}
_cairo_tag_stack_set_top_data (&ic->analysis_tag_stack, dest);
- cairo_list_add_tail (&dest->extents.link, &ic->extents_list);
ic->num_dests++;
}
@@ -1425,22 +1917,21 @@ _cairo_pdf_interchange_tag_begin (cairo_pdf_surface_t *surface,
cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
cairo_tag_type_t tag_type;
cairo_pdf_interchange_t *ic = &surface->interchange;
- void *ptr;
+
+ 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);
- _cairo_array_copy_element (&ic->push_data, ic->push_data_index++, &ptr);
- _cairo_tag_stack_set_top_data (&ic->render_tag_stack, ptr);
}
-
if (unlikely (status))
return status;
tag_type = _cairo_tag_get_type (name);
- if (tag_type & TAG_TYPE_STRUCTURE) {
+ 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;
@@ -1452,11 +1943,6 @@ _cairo_pdf_interchange_tag_begin (cairo_pdf_surface_t *surface,
return status;
}
- if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
- ptr = _cairo_tag_stack_top_elem (&ic->analysis_tag_stack)->data;
- status = _cairo_array_append (&ic->push_data, &ptr);
- }
-
return status;
}
@@ -1465,59 +1951,39 @@ _cairo_pdf_interchange_end_structure_tag (cairo_pdf_surface_t *surface,
cairo_tag_type_t tag_type,
cairo_tag_stack_elem_t *elem)
{
- const cairo_pdf_struct_tree_node_t *node;
- struct tag_extents *tag, *next;
cairo_pdf_interchange_t *ic = &surface->interchange;
cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
+ int mcid;
+ unsigned int content_command_id;
- assert (elem->data != NULL);
- node = elem->data;
if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
- if (tag_type & TAG_TYPE_LINK) {
- cairo_list_foreach_entry_safe (tag, next, struct tag_extents,
- &ic->extents_list, link) {
- if (tag == &node->extents) {
- cairo_list_del (&tag->link);
- break;
- }
- }
- }
+ 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 (is_leaf_node (ic->current_node)) {
+ 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_node = ic->current_node->parent;
- assert (ic->current_node != NULL);
-
- return status;
-}
-
-static cairo_int_status_t
-_cairo_pdf_interchange_end_dest_tag (cairo_pdf_surface_t *surface,
- cairo_tag_type_t tag_type,
- cairo_tag_stack_elem_t *elem)
-{
- struct tag_extents *tag, *next;
- cairo_pdf_named_dest_t *dest;
- cairo_pdf_interchange_t *ic = &surface->interchange;
-
- if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
- assert (elem->data != NULL);
- dest = (cairo_pdf_named_dest_t *) elem->data;
- cairo_list_foreach_entry_safe (tag, next, struct tag_extents,
- &ic->extents_list, link) {
- if (tag == &dest->extents) {
- cairo_list_del (&tag->link);
- break;
- }
+ 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 CAIRO_STATUS_SUCCESS;
+ return status;
}
cairo_int_status_t
@@ -1529,27 +1995,25 @@ _cairo_pdf_interchange_tag_end (cairo_pdf_surface_t *surface,
cairo_tag_type_t tag_type;
cairo_tag_stack_elem_t *elem;
- if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE)
+ 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);
+ } 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) {
+ 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;
}
- if (tag_type & TAG_TYPE_DEST) {
- status = _cairo_pdf_interchange_end_dest_tag (surface, tag_type, elem);
- if (unlikely (status))
- goto cleanup;
- }
-
cleanup:
_cairo_tag_stack_free_elem (elem);
@@ -1557,52 +2021,320 @@ _cairo_pdf_interchange_tag_end (cairo_pdf_surface_t *surface,
}
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)) {
+ if (ic->marked_content_open) {
+ status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
+ ic->marked_content_open = FALSE;
+ }
+ if (command_list_has_content (surface, command_id, NULL)) {
+ ic->render_next_command_has_content = TRUE;
+ }
+ } else if (ic->render_next_command_has_content) {
+ 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_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;
+ cairo_recording_surface_stack_entry_t element;
+ cairo_recording_surface_stack_entry_t *element_ptr;
+
+ if (!ic->ignore_current_surface)
+ command_list_pop_group (surface);
+
+ if (_cairo_array_pop_element (&ic->recording_surface_stack, &element)) {
+ element_ptr = _cairo_array_last_element (&ic->recording_surface_stack);
+ if (element_ptr) {
+ ic->ignore_current_surface = element_ptr->ignore_surface;
+ assert (ic->current_analyze_node == element_ptr->current_node);
+ } else {
+ /* Back at the page content. */
+ ic->ignore_current_surface = FALSE;
+ }
+ ic->content_emitted = FALSE;
+ return CAIRO_STATUS_SUCCESS;
+ }
+ ASSERT_NOT_REACHED; /* _recording_source_surface_begin/end mismatch */
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+/* Called at the start of a recording group during render. This will
+ * be called after the end of page content. */
+cairo_int_status_t
+_cairo_pdf_interchange_emit_recording_surface_begin (cairo_pdf_surface_t *surface,
+ cairo_surface_t *recording_surface,
+ int region_id,
+ cairo_pdf_resource_t surface_resource,
+ int *struct_parents)
+{
+ cairo_pdf_interchange_t *ic = &surface->interchange;
+ cairo_int_status_t status;
+
+ /* When
+ * _cairo_pdf_interchange_struct_tree_requires_recording_surface()
+ * is false, the region_id of the recording surface is set to 0.
+ */
+ if (region_id == 0) {
+ ic->ignore_current_surface = TRUE;
+ return CAIRO_STATUS_SUCCESS;
+ }
+
+ command_list_set_current_recording_commands (surface, recording_surface, region_id);
+
+ ic->ignore_current_surface = FALSE;
+ _cairo_array_truncate (&ic->mcid_to_tree, 0);
+ ic->current_recording_surface_res = surface_resource;
+
+ ic->content_parent_res = _cairo_pdf_surface_new_object (surface);
+ if (ic->content_parent_res.id == 0)
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+ status = _cairo_array_append (&ic->parent_tree, &ic->content_parent_res);
+ if (unlikely (status))
+ return status;
+
+ *struct_parents = _cairo_array_num_elements (&ic->parent_tree) - 1;
+
+ ic->render_next_command_has_content = FALSE;
+
+ return CAIRO_STATUS_SUCCESS;
+}
+
+/* Called at the end of a recording group during render. */
+cairo_int_status_t
+_cairo_pdf_interchange_emit_recording_surface_end (cairo_pdf_surface_t *surface,
+ cairo_surface_t *recording_surface)
+{
+ cairo_pdf_interchange_t *ic = &surface->interchange;
+
+ if (ic->ignore_current_surface)
+ return CAIRO_STATUS_SUCCESS;
+
+ ic->current_recording_surface_res.id = 0;
+ return cairo_pdf_interchange_write_content_parent_elems (surface);
+}
+
+static void _add_operation_extents_to_dest_tag (cairo_tag_stack_elem_t *elem,
+ void *closure)
+{
+ const cairo_rectangle_int_t *extents = (const cairo_rectangle_int_t *) closure;
+ cairo_pdf_named_dest_t *dest;
+
+ if (_cairo_tag_get_type (elem->name) & TAG_TYPE_DEST) {
+ if (elem->data) {
+ dest = (cairo_pdf_named_dest_t *) elem->data;
+ if (dest->extents.valid) {
+ _cairo_rectangle_union (&dest->extents.extents, extents);
+ } else {
+ dest->extents.extents = *extents;
+ dest->extents.valid = TRUE;
+ }
+ }
+ }
+}
+
+cairo_int_status_t
_cairo_pdf_interchange_add_operation_extents (cairo_pdf_surface_t *surface,
const cairo_rectangle_int_t *extents)
{
cairo_pdf_interchange_t *ic = &surface->interchange;
- struct tag_extents *tag;
+ /* Add extents to current node and all DEST tags on the stack */
if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
- cairo_list_foreach_entry (tag, struct tag_extents, &ic->extents_list, link) {
- if (tag->valid) {
- _cairo_rectangle_union (&tag->extents, extents);
+ if (ic->current_analyze_node) {
+ if (ic->current_analyze_node->extents.valid) {
+ _cairo_rectangle_union (&ic->current_analyze_node->extents.extents, extents);
} else {
- tag->extents = *extents;
- tag->valid = TRUE;
+ ic->current_analyze_node->extents.extents = *extents;
+ ic->current_analyze_node->extents.valid = TRUE;
}
}
+
+ _cairo_tag_stack_foreach (&ic->analysis_tag_stack,
+ _add_operation_extents_to_dest_tag,
+ (void*)extents);
}
return CAIRO_STATUS_SUCCESS;
}
cairo_int_status_t
+_cairo_pdf_interchange_add_content (cairo_pdf_surface_t *surface)
+{
+ cairo_pdf_interchange_t *ic = &surface->interchange;
+ cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
+
+ if (ic->ignore_current_surface)
+ return CAIRO_STATUS_SUCCESS;
+
+ if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
+ status = command_list_add (surface, ic->command_id, PDF_CONTENT);
+ if (unlikely (status))
+ return status;
+ }
+
+ return status;
+}
+
+/* Called at the start of 1emiting the page content during analyze or render */
+cairo_int_status_t
_cairo_pdf_interchange_begin_page_content (cairo_pdf_surface_t *surface)
{
cairo_pdf_interchange_t *ic = &surface->interchange;
cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
- int page_num, mcid;
+ int mcid;
+ unsigned int content_command_id;
+ cairo_pdf_command_list_t *page_commands;
if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
- _cairo_array_truncate (&ic->mcid_to_tree, 0);
- _cairo_array_truncate (&ic->push_data, 0);
- ic->begin_page_node = ic->current_node;
+ status = _cairo_array_allocate (&ic->page_commands, 1, (void**)&page_commands);
+ if (unlikely (status))
+ return status;
+
+ _cairo_array_init (&page_commands->commands, sizeof(cairo_pdf_command_t));
+ page_commands->parent = NULL;
+ ic->current_commands = page_commands;
+ ic->ignore_current_surface = FALSE;
} else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
- ic->push_data_index = 0;
- ic->current_node = ic->begin_page_node;
- if (ic->end_page_node && is_leaf_node (ic->end_page_node)) {
- page_num = _cairo_array_num_elements (&surface->pages);
- add_mcid_to_node (surface, ic->end_page_node, page_num, &mcid);
- status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators,
- ic->end_page_node->name,
- mcid);
+ ic->current_commands = _cairo_array_last_element (&ic->page_commands);
+ /* Each page has its own parent tree to map MCID to nodes. */
+ _cairo_array_truncate (&ic->mcid_to_tree, 0);
+ ic->ignore_current_surface = FALSE;
+ ic->content_parent_res = _cairo_pdf_surface_new_object (surface);
+ if (ic->content_parent_res.id == 0)
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+ status = _cairo_array_append (&ic->parent_tree, &ic->content_parent_res);
+ if (unlikely (status))
+ return status;
+
+ surface->page_parent_tree = _cairo_array_num_elements (&ic->parent_tree) - 1;
+
+ if (ic->next_page_render_node && ic->next_page_render_node->parent &&
+ command_list_has_content (surface, -1, &content_command_id))
+ {
+ add_mcid_to_node (surface, ic->next_page_render_node, content_command_id, &mcid);
+ const char *tag_name = ic->next_page_render_node->name;
+ if (ic->next_page_render_node->type == PDF_NODE_CONTENT)
+ tag_name = ic->next_page_render_node->attributes.content.tag_name;
+
+ status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators, tag_name, mcid);
+ ic->marked_content_open = TRUE;
}
+ ic->render_next_command_has_content = FALSE;
}
return status;
}
+/* Called at the end of emiting the page content during analyze or render */
cairo_int_status_t
_cairo_pdf_interchange_end_page_content (cairo_pdf_surface_t *surface)
{
@@ -1610,9 +2342,12 @@ _cairo_pdf_interchange_end_page_content (cairo_pdf_surface_t *surface)
cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
- ic->end_page_node = ic->current_node;
- if (is_leaf_node (ic->current_node))
+ /* If a content tag is open across pages, the old page needs an EMC emitted. */
+ if (ic->marked_content_open) {
status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
+ ic->marked_content_open = FALSE;
+ }
+ ic->next_page_render_node = ic->current_render_node;
}
return status;
@@ -1621,15 +2356,7 @@ _cairo_pdf_interchange_end_page_content (cairo_pdf_surface_t *surface)
cairo_int_status_t
_cairo_pdf_interchange_write_page_objects (cairo_pdf_surface_t *surface)
{
- cairo_int_status_t status;
-
- status = cairo_pdf_interchange_write_page_annots (surface);
- if (unlikely (status))
- return status;
-
- cairo_pdf_interchange_clear_annotations (surface);
-
- return cairo_pdf_interchange_write_page_parent_elems (surface);
+ return cairo_pdf_interchange_write_content_parent_elems (surface);
}
cairo_int_status_t
@@ -1638,15 +2365,41 @@ _cairo_pdf_interchange_write_document_objects (cairo_pdf_surface_t *surface)
cairo_pdf_interchange_t *ic = &surface->interchange;
cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
cairo_tag_stack_structure_type_t tag_type;
+ cairo_bool_t write_struct_tree = FALSE;
+
+ status = cairo_pdf_interchange_update_extents (surface);
+ if (unlikely (status))
+ return status;
tag_type = _cairo_tag_stack_get_structure_type (&ic->analysis_tag_stack);
if (tag_type == TAG_TREE_TYPE_TAGGED || tag_type == TAG_TREE_TYPE_STRUCTURE ||
- tag_type == TAG_TREE_TYPE_LINK_ONLY) {
+ tag_type == TAG_TREE_TYPE_LINK_ONLY)
+ {
+ write_struct_tree = TRUE;
+ }
+
+ status = cairo_pdf_interchange_write_annots (surface, write_struct_tree);
+ if (unlikely (status))
+ return status;
+
+ if (write_struct_tree) {
+ surface->struct_tree_root = _cairo_pdf_surface_new_object (surface);
+ if (surface->struct_tree_root.id == 0)
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+ ic->struct_root->res = surface->struct_tree_root;
status = cairo_pdf_interchange_write_parent_tree (surface);
if (unlikely (status))
return status;
+ unsigned num_pages = _cairo_array_num_elements (&ic->page_commands);
+ for (unsigned i = 0; i < num_pages; i++) {
+ cairo_pdf_command_list_t *command_list;
+ command_list = _cairo_array_index (&ic->page_commands, i);
+ update_mcid_order (surface, command_list);
+ }
+
status = cairo_pdf_interchange_write_struct_tree (surface);
if (unlikely (status))
return status;
@@ -1724,16 +2477,27 @@ _cairo_pdf_interchange_init (cairo_pdf_surface_t *surface)
_cairo_tag_stack_init (&ic->analysis_tag_stack);
_cairo_tag_stack_init (&ic->render_tag_stack);
- _cairo_array_init (&ic->push_data, sizeof(void *));
ic->struct_root = calloc (1, sizeof(cairo_pdf_struct_tree_node_t));
if (unlikely (ic->struct_root == NULL))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ ic->struct_root->res.id = 0;
cairo_list_init (&ic->struct_root->children);
- _cairo_array_init (&ic->struct_root->mcid, sizeof(struct page_mcid));
- ic->current_node = ic->struct_root;
- ic->begin_page_node = NULL;
- ic->end_page_node = NULL;
+ _cairo_array_init (&ic->struct_root->mcid, sizeof(cairo_pdf_page_mcid_t));
+
+ ic->current_analyze_node = ic->struct_root;
+ ic->current_render_node = NULL;
+ ic->next_page_render_node = ic->struct_root;
+ _cairo_array_init (&ic->recording_surface_stack, sizeof(cairo_recording_surface_stack_entry_t));
+ ic->current_recording_surface_res.id = 0;
+ ic->command_to_node_map = _cairo_hash_table_create (_cairo_pdf_command_equal);
+ if (unlikely (ic->command_to_node_map == NULL))
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
+ ic->content_tag_map = _cairo_hash_table_create (_cairo_pdf_content_tag_equal);
+ if (unlikely (ic->content_tag_map == NULL))
+ return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+
_cairo_array_init (&ic->parent_tree, sizeof(cairo_pdf_resource_t));
_cairo_array_init (&ic->mcid_to_tree, sizeof(cairo_pdf_struct_tree_node_t *));
_cairo_array_init (&ic->annots, sizeof(cairo_pdf_annotation_t *));
@@ -1743,9 +2507,18 @@ _cairo_pdf_interchange_init (cairo_pdf_surface_t *surface)
if (unlikely (ic->named_dests == NULL))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
+ _cairo_array_init (&ic->page_commands, sizeof(cairo_pdf_command_list_t));
+ ic->current_commands = NULL;
+ _cairo_array_init (&ic->recording_surface_commands, sizeof(cairo_pdf_recording_surface_commands_t));
+
ic->num_dests = 0;
ic->sorted_dests = NULL;
ic->dests_res.id = 0;
+ ic->ignore_current_surface = FALSE;
+ ic->content_emitted = FALSE;
+ ic->marked_content_open = FALSE;
+ ic->render_next_command_has_content = FALSE;
+ ic->mcid_order = 0;
_cairo_array_init (&ic->outline, sizeof(cairo_pdf_outline_entry_t *));
outline_root = calloc (1, sizeof(cairo_pdf_outline_entry_t));
@@ -1772,9 +2545,7 @@ _cairo_pdf_interchange_free_outlines (cairo_pdf_surface_t *surface)
_cairo_array_copy_element (&ic->outline, i, &outline);
free (outline->name);
- free (outline->link_attrs.dest);
- free (outline->link_attrs.uri);
- free (outline->link_attrs.file);
+ _cairo_tag_free_link_attributes (&outline->link_attrs);
free (outline);
}
_cairo_array_fini (&ic->outline);
@@ -1789,14 +2560,46 @@ _cairo_pdf_interchange_fini (cairo_pdf_surface_t *surface)
_cairo_tag_stack_fini (&ic->analysis_tag_stack);
_cairo_tag_stack_fini (&ic->render_tag_stack);
- _cairo_array_fini (&ic->push_data);
- free_node (ic->struct_root);
_cairo_array_fini (&ic->mcid_to_tree);
cairo_pdf_interchange_clear_annotations (surface);
_cairo_array_fini (&ic->annots);
+
+ _cairo_array_fini (&ic->recording_surface_stack);
_cairo_array_fini (&ic->parent_tree);
+
+ _cairo_hash_table_foreach (ic->command_to_node_map,
+ _cairo_pdf_command_pluck,
+ ic->command_to_node_map);
+ _cairo_hash_table_destroy (ic->command_to_node_map);
+
_cairo_hash_table_foreach (ic->named_dests, _named_dest_pluck, ic->named_dests);
_cairo_hash_table_destroy (ic->named_dests);
+
+ _cairo_hash_table_foreach (ic->content_tag_map, _cairo_pdf_content_tag_pluck, ic->content_tag_map);
+ _cairo_hash_table_destroy(ic->content_tag_map);
+
+ free_node (ic->struct_root);
+
+ num_elems = _cairo_array_num_elements (&ic->recording_surface_commands);
+ for (i = 0; i < num_elems; i++) {
+ cairo_pdf_recording_surface_commands_t *recording_command;
+ cairo_pdf_command_list_t *command_list;
+
+ recording_command = _cairo_array_index (&ic->recording_surface_commands, i);
+ command_list = recording_command->command_list;
+ _cairo_array_fini (&command_list->commands);
+ free (command_list);
+ }
+ _cairo_array_fini (&ic->recording_surface_commands);
+
+ num_elems = _cairo_array_num_elements (&ic->page_commands);
+ for (i = 0; i < num_elems; i++) {
+ cairo_pdf_command_list_t *command_list;
+ command_list = _cairo_array_index (&ic->page_commands, i);
+ _cairo_array_fini (&command_list->commands);
+ }
+ _cairo_array_fini (&ic->page_commands);
+
free (ic->sorted_dests);
_cairo_pdf_interchange_free_outlines (surface);
free (ic->docinfo.title);
@@ -2074,3 +2877,123 @@ _cairo_pdf_interchange_set_custom_metadata (cairo_pdf_surface_t *surface,
return status;
}
+
+#if DEBUG_PDF_INTERCHANGE
+static cairo_int_status_t
+print_node (cairo_pdf_surface_t *surface,
+ cairo_pdf_struct_tree_node_t *node,
+ int depth)
+{
+ if (node == NULL) {
+ printf("%*sNode: ptr: NULL\n", depth*2, "");
+ } else if (node == surface->interchange.struct_root) {
+ printf("%*sNode: ptr: %p root\n", depth*2, "", node);
+ } else {
+ printf("%*sNode: ptr: %p name: '%s'\n", depth*2, "", node, node->name);
+ }
+ depth++;
+ printf("%*sType: ", depth*2, "");
+ switch (node->type) {
+ case PDF_NODE_STRUCT:
+ printf("STRUCT\n");
+ break;
+ case PDF_NODE_CONTENT:
+ printf("CONTENT\n");
+ printf("%*sContent.id: %s\n", depth*2, "", node->attributes.content.id);
+ printf("%*sContent.tag_name: %s\n", depth*2, "", node->attributes.content.tag_name);
+ break;
+ case PDF_NODE_CONTENT_REF:
+ printf("CONTENT_REF\n");
+ printf("%*sContent_Ref.ref: %s\n", depth*2, "", node->attributes.content_ref.ref);
+ break;
+ case PDF_NODE_ARTIFACT:
+ printf("ARTIFACT\n");
+ break;
+ }
+ printf("%*sres: %d\n", depth*2, "", node->res.id);
+ printf("%*sparent: %p\n", depth*2, "", node->parent);
+ printf("%*sannot:", depth*2, "");
+ if (node->annot)
+ printf(" node: %p res: %d", node->annot->node, node->annot->res.id);
+ printf("\n");
+ printf("%*sextents: ", depth*2, "");
+ if (node->extents.valid) {
+ printf("x: %d y: %d w: %d h: %d\n",
+ node->extents.extents.x,
+ node->extents.extents.y,
+ node->extents.extents.width,
+ node->extents.extents.height);
+ } else {
+ printf("not valid\n");
+ }
+
+ printf("%*smcid: ", depth*2, "");
+ int num_mcid = _cairo_array_num_elements (&node->mcid);
+ for (int i = 0; i < num_mcid; i++) {
+ cairo_pdf_page_mcid_t *mcid_elem = _cairo_array_index (&node->mcid, i);
+ if (mcid_elem->child_node) {
+ printf("(order: %d, %p) ", mcid_elem->order, mcid_elem->child_node);
+ } else {
+ printf("(order: %d, pg: %d, xobject_res: %d, mcid: %d) ",
+ mcid_elem->order, mcid_elem->page, mcid_elem->xobject_res.id, mcid_elem->mcid);
+ }
+ }
+ printf("\n");
+ return CAIRO_STATUS_SUCCESS;
+}
+
+static void
+print_tree (cairo_pdf_surface_t *surface,
+ cairo_pdf_struct_tree_node_t *node)
+{
+ printf("Structure Tree:\n");
+ cairo_pdf_interchange_walk_struct_tree (surface, node, 0, print_node);
+}
+
+static void
+print_command (cairo_pdf_command_t *command, int indent)
+{
+ printf("%*s%d ", indent*2, "", command->command_id);
+ switch (command->flags) {
+ case PDF_CONTENT:
+ printf("CONTENT");
+ break;
+ case PDF_BEGIN:
+ printf("BEGIN");
+ break;
+ case PDF_END:
+ printf("END");
+ break;
+ case PDF_GROUP:
+ printf("GROUP: %p", command->group);
+ break;
+ case PDF_NONE:
+ printf("NONE");
+ break;
+ }
+ printf(" node: %p index: %d\n", command->node, command->mcid_index);
+}
+
+static void
+print_commands (cairo_pdf_command_list_t *command_list, int indent)
+{
+ cairo_pdf_command_t *command;
+ 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);
+ print_command (command, indent);
+ if (command->flags == PDF_GROUP)
+ print_commands (command->group, indent + 1);
+ }
+}
+
+static void
+print_command_list(cairo_pdf_command_list_t *command_list)
+{
+ printf("Command List: %p\n", command_list);
+ print_commands (command_list, 0);
+ printf("end\n");
+}
+#endif
diff --git a/src/cairo-pdf-operators.c b/src/cairo-pdf-operators.c
index 328e893d7..176f45b48 100644
--- a/src/cairo-pdf-operators.c
+++ b/src/cairo-pdf-operators.c
@@ -1568,10 +1568,16 @@ _cairo_pdf_operators_tag_begin (cairo_pdf_operators_t *pdf_operators,
return status;
}
- _cairo_output_stream_printf (pdf_operators->stream,
- "/%s << /MCID %d >> BDC\n",
- tag_name,
- mcid);
+ if (mcid >= 0) {
+ _cairo_output_stream_printf (pdf_operators->stream,
+ "/%s << /MCID %d >> BDC\n",
+ tag_name,
+ mcid);
+ } else {
+ _cairo_output_stream_printf (pdf_operators->stream,
+ "/%s BMC\n",
+ tag_name);
+ }
return _cairo_output_stream_get_status (pdf_operators->stream);
}
diff --git a/src/cairo-pdf-surface-private.h b/src/cairo-pdf-surface-private.h
index c97affe6b..13ccc5001 100644
--- a/src/cairo-pdf-surface-private.h
+++ b/src/cairo-pdf-surface-private.h
@@ -74,6 +74,10 @@ typedef struct _cairo_pdf_source_surface_entry {
unsigned char *unique_id;
unsigned long unique_id_length;
cairo_operator_t operator;
+
+ /* If not 0, this is the recording surface region id of the source surface. */
+ int region_id;
+
cairo_bool_t interpolate;
cairo_bool_t stencil_mask;
cairo_bool_t smask;
@@ -110,6 +114,9 @@ typedef struct _cairo_pdf_pattern {
cairo_operator_t operator;
cairo_bool_t is_shading;
+ /* Index into nodes array in cairo_pdf_surface_node_entry_t or -1 if not used */
+ int region_id;
+
/* PDF pattern space is the pattern matrix concatenated with the
* initial space of the parent object. If the parent object is the
* page, the initial space does not include the Y-axis flipping
@@ -161,38 +168,87 @@ typedef struct _cairo_pdf_jbig2_global {
cairo_bool_t emitted;
} cairo_pdf_jbig2_global_t;
-/* cairo-pdf-interchange.c types */
+typedef struct _cairo_pdf_page_info {
+ double width;
+ double height;
+ cairo_pdf_resource_t page_res;
+ cairo_pdf_resource_t content;
+ cairo_pdf_resource_t resources;
+ cairo_pdf_resource_t thumbnail;
+ cairo_array_t annots; /* <cairo_pdf_resource_t> */
+ int struct_parents;
+} cairo_pdf_page_info_t;
-struct page_mcid {
- int page;
- int mcid;
-};
-struct tag_extents {
+/* cairo-pdf-interchange.c types */
+
+typedef struct _pdf_tag_extents {
cairo_rectangle_int_t extents;
cairo_bool_t valid;
- cairo_list_t link;
-};
+} cairo_pdf_tag_extents_t;
+
+typedef struct _pdf_page_mcid {
+ int order;
+ int page;
+ cairo_pdf_resource_t xobject_res; /* 0 if not in an XObject */
+ int mcid;
+ struct _cairo_pdf_struct_tree_node *child_node;
+} cairo_pdf_page_mcid_t;
+
+/* The non PDF_NODE_STRUCT types are excluded from the struct tree embedded in
+ * the PDF. */
+typedef enum _cairo_pdf_tree_node_type {
+ PDF_NODE_STRUCT,
+ PDF_NODE_CONTENT,
+ PDF_NODE_CONTENT_REF,
+ PDF_NODE_ARTIFACT,
+} cairo_pdf_tree_node_type_t;
typedef struct _cairo_pdf_struct_tree_node {
+ cairo_hash_entry_t hash;
+ cairo_pdf_tree_node_type_t type;
char *name;
cairo_pdf_resource_t res;
struct _cairo_pdf_struct_tree_node *parent;
cairo_list_t children;
- cairo_array_t mcid; /* array of struct page_mcid */
- cairo_pdf_resource_t annot_res; /* 0 if no annot */
- struct tag_extents extents;
- cairo_list_t link;
+ cairo_array_t mcid; /* array of cairo_pdf_page_mcid_t */
+ struct _cairo_pdf_annotation *annot;
+ cairo_pdf_tag_extents_t extents;
+
+ union {
+ cairo_content_attrs_t content; /* type == PDF_NODE_CONTENT */
+ cairo_content_ref_attrs_t content_ref; /* type == PDF_NODE_CONTENT_REF */
+ } attributes;
+
+ cairo_list_t link; /* linked list of parent's children */
} cairo_pdf_struct_tree_node_t;
+typedef struct _cairo_pdf_command_entry {
+ cairo_hash_entry_t base;
+ unsigned int recording_id;
+ unsigned int command_id;
+ cairo_pdf_struct_tree_node_t *node;
+} cairo_pdf_command_entry_t;
+
+typedef struct _cairo_recording_surface_stack_entry {
+ cairo_bool_t ignore_surface;
+ cairo_pdf_struct_tree_node_t *current_node;
+} cairo_recording_surface_stack_entry_t;
+
+typedef struct _cairo_pdf_content_tag {
+ cairo_hash_entry_t base;
+ cairo_pdf_struct_tree_node_t *node;
+} cairo_pdf_content_tag_t;
+
typedef struct _cairo_pdf_annotation {
cairo_pdf_struct_tree_node_t *node; /* node containing the annotation */
cairo_link_attrs_t link_attrs;
+ cairo_pdf_resource_t res;
} cairo_pdf_annotation_t;
typedef struct _cairo_pdf_named_dest {
cairo_hash_entry_t base;
- struct tag_extents extents;
+ cairo_pdf_tag_extents_t extents;
cairo_dest_attrs_t attrs;
int page;
} cairo_pdf_named_dest_t;
@@ -233,19 +289,65 @@ struct metadata {
char *value;
};
+typedef enum _cairo_pdf_operation_flags_t {
+ PDF_NONE = 0,
+ PDF_CONTENT,
+ PDF_BEGIN,
+ PDF_END,
+ PDF_GROUP,
+} cairo_pdf_operation_flags_t;
+
+typedef struct _pdf_command_list {
+ cairo_array_t commands;
+ struct _pdf_command_list *parent;
+} cairo_pdf_command_list_t;
+
+typedef struct _pdf_operation {
+ cairo_pdf_command_list_t *group;
+ cairo_pdf_struct_tree_node_t *node;
+ unsigned int command_id;
+ int mcid_index;
+ cairo_pdf_operation_flags_t flags;
+} cairo_pdf_command_t;
+
+typedef struct _pdf_recording_surface_commands {
+ cairo_surface_t *recording_surface;
+ cairo_pdf_command_list_t *command_list;
+ unsigned int region_id;
+} cairo_pdf_recording_surface_commands_t;
+
typedef struct _cairo_pdf_interchange {
cairo_tag_stack_t analysis_tag_stack;
cairo_tag_stack_t render_tag_stack;
- cairo_array_t push_data; /* records analysis_tag_stack data field for each push */
- int push_data_index;
cairo_pdf_struct_tree_node_t *struct_root;
- cairo_pdf_struct_tree_node_t *current_node;
- cairo_pdf_struct_tree_node_t *begin_page_node;
- cairo_pdf_struct_tree_node_t *end_page_node;
- cairo_array_t parent_tree; /* parent tree resources */
- cairo_array_t mcid_to_tree; /* mcid to tree node mapping for current page */
+
+ /* Current position in the tree during the analysis stage and across
+ * pages as each page adds to the tree */
+ cairo_pdf_struct_tree_node_t *current_analyze_node;
+
+ /* Currently open tag content containing content. NULL if no content tag open.
+ * A content containg tag may be open across pages */
+ cairo_pdf_struct_tree_node_t *current_render_node;
+ cairo_pdf_struct_tree_node_t *next_page_render_node;
+
+ cairo_array_t recording_surface_stack; /* cairo_recording_surface_stack_entry_t */
+ cairo_pdf_resource_t current_recording_surface_res;
+ cairo_hash_table_t *command_to_node_map; /* <cairo_pdf_surface_node_entry_t> */
+ cairo_bool_t ignore_current_surface;
+ cairo_hash_table_t *content_tag_map; /* <char*,cairo_pdf_content_tag_t> */
+
+ cairo_array_t parent_tree; /* <cairo_pdf_resource_t> */
cairo_array_t annots; /* array of pointers to cairo_pdf_annotation_t */
+ cairo_pdf_resource_t content_parent_res;
cairo_pdf_resource_t parent_tree_res;
+
+ /* mcid to tree node for current page or group */
+ cairo_array_t mcid_to_tree; /* <cairo_pdf_struct_tree_node_t *> */
+
+ cairo_array_t page_commands; /* <cairo_pdf_command_list_t> */
+ cairo_pdf_command_list_t *current_commands; /* <cairo_pdf_command_list_t> */
+ cairo_array_t recording_surface_commands; /* <cairo_pdf_recording_surface_commands_t> */
+
cairo_list_t extents_list;
cairo_hash_table_t *named_dests;
int num_dests;
@@ -255,6 +357,12 @@ typedef struct _cairo_pdf_interchange {
cairo_array_t outline; /* array of pointers to cairo_pdf_outline_entry_t; */
struct docinfo docinfo;
cairo_array_t custom_metadata; /* array of struct metadata */
+ cairo_bool_t content_emitted;
+ cairo_bool_t marked_content_open;
+ unsigned int recording_id;
+ unsigned int command_id;
+ cairo_bool_t render_next_command_has_content;
+ int mcid_order;
} cairo_pdf_interchange_t;
@@ -283,18 +391,18 @@ struct _cairo_pdf_surface {
cairo_matrix_t cairo_to_pdf;
cairo_bool_t in_xobject;
- cairo_array_t objects;
- cairo_array_t pages;
+ cairo_array_t objects; /* cairo_pdf_resource_t - list of every resource in the PDF */
+ cairo_array_t pages; /* <cairo_pdf_page_info_t> */
cairo_array_t rgb_linear_functions;
cairo_array_t alpha_linear_functions;
cairo_array_t page_patterns; /* cairo_pdf_pattern_t */
cairo_array_t page_surfaces; /* cairo_pdf_source_surface_t */
cairo_array_t doc_surfaces; /* cairo_pdf_source_surface_t */
- cairo_hash_table_t *all_surfaces;
+ cairo_hash_table_t *all_surfaces; /* cairo_pdf_source_surface_entry_t* */
+ int duplicate_surface_number;
cairo_array_t smask_groups;
cairo_array_t knockout_group;
cairo_array_t jbig2_global;
- cairo_array_t page_heights;
cairo_hash_table_t *color_glyphs;
cairo_scaled_font_subsets_t *font_subsets;
@@ -374,6 +482,8 @@ struct _cairo_pdf_surface {
cairo_image_surface_t *thumbnail_image;
cairo_surface_t *paginated_surface;
+
+ cairo_bool_t debug;
};
cairo_private cairo_pdf_resource_t
@@ -410,15 +520,56 @@ _cairo_pdf_surface_object_begin (cairo_pdf_surface_t *surface,
cairo_private void
_cairo_pdf_surface_object_end (cairo_pdf_surface_t *surface);
+cairo_private cairo_bool_t
+_cairo_pdf_interchange_struct_tree_requires_recording_surface (
+ cairo_pdf_surface_t *surface,
+ const cairo_surface_pattern_t *recording_surface,
+ cairo_analysis_source_t source_type);
+
+cairo_private 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_private 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_private cairo_int_status_t
+_cairo_pdf_interchange_emit_recording_surface_begin (
+ cairo_pdf_surface_t *surface,
+ cairo_surface_t *recording_surface,
+ int region_id,
+ cairo_pdf_resource_t surface_resource,
+ int *struct_parents);
+
+cairo_private cairo_int_status_t
+_cairo_pdf_interchange_emit_recording_surface_end (
+ cairo_pdf_surface_t *surface,
+ cairo_surface_t *recording_surface);
+
cairo_private cairo_int_status_t
_cairo_pdf_interchange_tag_end (cairo_pdf_surface_t *surface,
const char *name);
cairo_private cairo_int_status_t
+_cairo_pdf_interchange_command_id (cairo_pdf_surface_t *surface,
+ unsigned int recording_id,
+ unsigned int command_id);
+
+cairo_private cairo_int_status_t
_cairo_pdf_interchange_add_operation_extents (cairo_pdf_surface_t *surface,
const cairo_rectangle_int_t *extents);
cairo_private cairo_int_status_t
+_cairo_pdf_interchange_add_content (cairo_pdf_surface_t *surface);
+
+cairo_private cairo_int_status_t
_cairo_pdf_interchange_write_page_objects (cairo_pdf_surface_t *surface);
cairo_private cairo_int_status_t
diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c
index 2b1bf72e4..ca83e61ed 100644
--- a/src/cairo-pdf-surface.c
+++ b/src/cairo-pdf-surface.c
@@ -471,7 +471,7 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t *output,
surface->surface_bounded = TRUE;
_cairo_array_init (&surface->objects, sizeof (cairo_pdf_object_t));
- _cairo_array_init (&surface->pages, sizeof (cairo_pdf_resource_t));
+ _cairo_array_init (&surface->pages, sizeof (cairo_pdf_page_info_t));
_cairo_array_init (&surface->rgb_linear_functions, sizeof (cairo_pdf_rgb_linear_function_t));
_cairo_array_init (&surface->alpha_linear_functions, sizeof (cairo_pdf_alpha_linear_function_t));
_cairo_array_init (&surface->fonts, sizeof (cairo_pdf_font_t));
@@ -482,7 +482,6 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t *output,
_cairo_array_init (&surface->page_surfaces, sizeof (cairo_pdf_source_surface_t));
_cairo_array_init (&surface->doc_surfaces, sizeof (cairo_pdf_source_surface_t));
_cairo_array_init (&surface->jbig2_global, sizeof (cairo_pdf_jbig2_global_t));
- _cairo_array_init (&surface->page_heights, sizeof (double));
surface->all_surfaces = _cairo_hash_table_create (_cairo_pdf_source_surface_equal);
if (unlikely (surface->all_surfaces == NULL)) {
status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
@@ -495,6 +494,8 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t *output,
goto BAIL1;
}
+ surface->duplicate_surface_number = 0;
+
_cairo_pdf_group_resources_init (&surface->resources);
surface->font_subsets = _cairo_scaled_font_subsets_create_composite ();
@@ -565,8 +566,11 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t *output,
surface->thumbnail_height = 0;
surface->thumbnail_image = NULL;
- if (getenv ("CAIRO_DEBUG_PDF") != NULL)
+ surface->debug = FALSE;
+ if (getenv ("CAIRO_DEBUG_PDF") != NULL) {
+ surface->debug = TRUE;
surface->compress_streams = FALSE;
+ }
surface->paginated_surface = _cairo_paginated_surface_create (
&surface->base,
@@ -1457,6 +1461,9 @@ _cairo_pdf_source_surface_equal (const void *key_a, const void *key_b)
if (a->interpolate != b->interpolate)
return FALSE;
+ if (a->region_id != b->region_id)
+ return FALSE;
+
if (a->unique_id && b->unique_id && a->unique_id_length == b->unique_id_length)
return (memcmp (a->unique_id, b->unique_id, a->unique_id_length) == 0);
@@ -1472,6 +1479,9 @@ _cairo_pdf_source_surface_init_key (cairo_pdf_source_surface_entry_t *key)
} else {
key->base.hash = key->id;
}
+ key->base.hash = _cairo_hash_bytes (key->base.hash,
+ &key->region_id,
+ sizeof(key->region_id));
}
static cairo_bool_t
@@ -1601,6 +1611,11 @@ _get_source_surface_extents (cairo_surface_t *source,
* @surface: [in] the pdf surface
* @source_surface: [in] A #cairo_surface_t to use as the source surface
* @source_pattern: [in] A #cairo_pattern_t of type SURFACE or RASTER_SOURCE to use as the source
+ * @region_id: [in] Surfaces containing tags set this to the recording
+ * surface region id. When tags are used in a XObject, PDF requires a
+ * separate object for each use (section 14.7.4.2) @region_id is used
+ * as a key to ensure a separate object is emitted for each use. Set
+ * to 0 for surfaces without tags.
* @op: [in] the operator used to composite this source
* @filter: [in] filter type of the source pattern
* @stencil_mask: [in] if true, the surface will be written to the PDF as an /ImageMask
@@ -1628,6 +1643,7 @@ static cairo_int_status_t
_cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t *surface,
cairo_surface_t *source_surface,
const cairo_pattern_t *source_pattern,
+ int region_id,
cairo_operator_t op,
cairo_filter_t filter,
cairo_bool_t stencil_mask,
@@ -1653,6 +1669,7 @@ _cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t *surface,
cairo_rectangle_int_t op_extents;
double x, y;
cairo_bool_t subsurface;
+ cairo_bool_t emit_image;
switch (filter) {
default:
@@ -1700,10 +1717,21 @@ _cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t *surface,
*source_extents = op_extents;
surface_key.id = source_surface->unique_id;
- surface_key.interpolate = interpolate;
+
+ /* Recording surfaces do not use interpolate. Ensure it is always
+ * false for recording surfaces. This is because pdf-interchange
+ * needs to lookup recording surfaces in the hash table using
+ * interpolate = FALSE in the key since it does not know the
+ * interpolate value passed to this function.
+ */
+ emit_image = source_surface->type != CAIRO_SURFACE_TYPE_RECORDING;
+ surface_key.interpolate = emit_image ? interpolate : FALSE;
+
cairo_surface_get_mime_data (source_surface, CAIRO_MIME_TYPE_UNIQUE_ID,
(const unsigned char **) &surface_key.unique_id,
&surface_key.unique_id_length);
+
+ surface_key.region_id = region_id;
_cairo_pdf_source_surface_init_key (&surface_key);
surface_entry = _cairo_hash_table_lookup (surface->all_surfaces, &surface_key.base);
if (surface_entry) {
@@ -1746,9 +1774,12 @@ _cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t *surface,
if (pdf_source)
*pdf_source = surface_entry;
+
surface_entry->id = surface_key.id;
+ surface_entry->region_id = region_id;
surface_entry->operator = op;
- surface_entry->interpolate = interpolate;
+ surface_entry->interpolate = emit_image ? interpolate : FALSE;
+ surface_entry->emit_image = emit_image;
surface_entry->stencil_mask = stencil_mask;
surface_entry->smask = smask;
surface_entry->need_transp_group = need_transp_group;
@@ -1810,11 +1841,6 @@ _cairo_pdf_surface_add_source_surface (cairo_pdf_surface_t *surface,
goto fail3;
}
- /* Test if surface will be emitted as image or recording */
- status = _cairo_pdf_surface_emit_surface (surface, &src_surface, TRUE, &surface_entry->emit_image);
- if (unlikely (status))
- goto fail3;
-
if (surface_entry->bounded) {
status = _cairo_array_append (&surface->page_surfaces, &src_surface);
if (unlikely (status))
@@ -1858,6 +1884,7 @@ static cairo_int_status_t
_cairo_pdf_surface_add_pdf_pattern_or_shading (cairo_pdf_surface_t *surface,
const cairo_pattern_t *pattern,
cairo_operator_t op,
+ cairo_analysis_source_t source_type,
const cairo_rectangle_int_t *extents,
cairo_bool_t is_shading,
cairo_pdf_resource_t *pattern_res,
@@ -1865,6 +1892,7 @@ _cairo_pdf_surface_add_pdf_pattern_or_shading (cairo_pdf_surface_t *surface,
{
cairo_pdf_pattern_t pdf_pattern;
cairo_int_status_t status;
+ int region_id = 0;
pdf_pattern.is_shading = is_shading;
pdf_pattern.operator = op;
@@ -1923,6 +1951,17 @@ _cairo_pdf_surface_add_pdf_pattern_or_shading (cairo_pdf_surface_t *surface,
* Y-axis. */
pdf_pattern.inverted_y_axis = pdf_pattern.gstate_res.id ? TRUE : surface->in_xobject;
+ if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) {
+ cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) pattern;
+ if (_cairo_pdf_interchange_struct_tree_requires_recording_surface (surface,
+ surface_pattern,
+ source_type))
+ {
+ region_id = surface_pattern->region_array_id;
+ }
+ }
+ pdf_pattern.region_id = region_id;
+
status = _cairo_array_append (&surface->page_patterns, &pdf_pattern);
if (unlikely (status)) {
cairo_pattern_destroy (pdf_pattern.pattern);
@@ -1954,6 +1993,7 @@ _cairo_pdf_surface_add_pdf_shading (cairo_pdf_surface_t *surface,
return _cairo_pdf_surface_add_pdf_pattern_or_shading (surface,
pattern,
op,
+ CAIRO_ANALYSIS_SOURCE_NONE,
extents,
TRUE,
shading_res,
@@ -1964,6 +2004,7 @@ static cairo_int_status_t
_cairo_pdf_surface_add_pdf_pattern (cairo_pdf_surface_t *surface,
const cairo_pattern_t *pattern,
cairo_operator_t op,
+ cairo_analysis_source_t source_type,
const cairo_rectangle_int_t *extents,
cairo_pdf_resource_t *pattern_res,
cairo_pdf_resource_t *gstate_res)
@@ -1971,6 +2012,7 @@ _cairo_pdf_surface_add_pdf_pattern (cairo_pdf_surface_t *surface,
return _cairo_pdf_surface_add_pdf_pattern_or_shading (surface,
pattern,
op,
+ source_type,
extents,
FALSE,
pattern_res,
@@ -2242,6 +2284,7 @@ _cairo_pdf_surface_close_group (cairo_pdf_surface_t *surface,
surface->group_stream.mem_stream = NULL;
surface->group_stream.stream = NULL;
+ surface->reset_gs_required = FALSE;
return status;
}
@@ -2249,7 +2292,7 @@ _cairo_pdf_surface_close_group (cairo_pdf_surface_t *surface,
static cairo_int_status_t
_cairo_pdf_surface_open_object_stream (cairo_pdf_surface_t *surface)
{
- if (surface->pdf_version < CAIRO_PDF_VERSION_1_5) {
+ if (surface->debug || surface->pdf_version < CAIRO_PDF_VERSION_1_5) {
/* Object streams not supported. All objects will be written
* directly to the file. */
assert (surface->pdf_stream.active == FALSE);
@@ -2305,7 +2348,9 @@ _cairo_pdf_surface_object_end (cairo_pdf_surface_t *surface)
}
}
-static int _cairo_xref_stream_object_compare (const void *a, const void *b)
+static int
+_cairo_xref_stream_object_compare (const void *a,
+ const void *b)
{
const cairo_xref_stream_object_t *a_obj = a;
const cairo_xref_stream_object_t *b_obj = b;
@@ -2427,9 +2472,11 @@ _cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t *surface,
const cairo_box_double_t *bbox,
cairo_pdf_resource_t *resource,
cairo_bool_t is_form,
- cairo_bool_t is_group)
+ cairo_bool_t is_group,
+ int struct_parents)
{
cairo_int_status_t status;
+ char buf[1000];
assert (surface->pdf_stream.active == FALSE);
assert (surface->group_stream.active == FALSE);
@@ -2442,40 +2489,46 @@ _cairo_pdf_surface_open_content_stream (cairo_pdf_surface_t *surface,
assert (bbox != NULL);
if (is_group) {
- status =
- _cairo_pdf_surface_open_stream (surface,
- resource,
- surface->compress_streams,
- " /Type /XObject\n"
- " /Subtype /Form\n"
- " /BBox [ %f %f %f %f ]\n"
- " /Group <<\n"
- " /Type /Group\n"
- " /S /Transparency\n"
- " /I true\n"
- " /CS /DeviceRGB\n"
- " >>\n"
- " /Resources %d 0 R\n",
- bbox->p1.x,
- bbox->p1.y,
- bbox->p2.x,
- bbox->p2.y,
- surface->content_resources.id);
+ snprintf(buf,
+ sizeof(buf),
+ " /Type /XObject\n"
+ " /Subtype /Form\n"
+ " /BBox [ %f %f %f %f ]\n"
+ " /Group <<\n"
+ " /Type /Group\n"
+ " /S /Transparency\n"
+ " /I true\n"
+ " /CS /DeviceRGB\n"
+ " >>\n"
+ " /Resources %d 0 R\n",
+ bbox->p1.x,
+ bbox->p1.y,
+ bbox->p2.x,
+ bbox->p2.y,
+ surface->content_resources.id);
} else {
- status =
- _cairo_pdf_surface_open_stream (surface,
- resource,
- surface->compress_streams,
- " /Type /XObject\n"
- " /Subtype /Form\n"
- " /BBox [ %f %f %f %f ]\n"
- " /Resources %d 0 R\n",
- bbox->p1.x,
- bbox->p1.y,
- bbox->p2.x,
- bbox->p2.y,
- surface->content_resources.id);
+ snprintf(buf,
+ sizeof(buf),
+ " /Type /XObject\n"
+ " /Subtype /Form\n"
+ " /BBox [ %f %f %f %f ]\n"
+ " /Resources %d 0 R\n",
+ bbox->p1.x,
+ bbox->p1.y,
+ bbox->p2.x,
+ bbox->p2.y,
+ surface->content_resources.id);
}
+ if (struct_parents >= 0) {
+ snprintf(buf + strlen(buf),
+ sizeof(buf) - strlen(buf),
+ " /StructParents %d\n", struct_parents);
+ }
+ status =
+ _cairo_pdf_surface_open_stream (surface,
+ resource,
+ surface->compress_streams,
+ buf);
} else {
status =
_cairo_pdf_surface_open_stream (surface,
@@ -2550,6 +2603,73 @@ _cairo_pdf_color_glyph_pluck (void *entry, void *closure)
free (glyph_entry);
}
+static cairo_int_status_t
+_cairo_pdf_surface_write_page_dicts (cairo_pdf_surface_t *surface)
+{
+ cairo_int_status_t status;
+ cairo_pdf_page_info_t *page_info;
+ int num_annots;
+ cairo_pdf_resource_t res;
+
+ for (unsigned i = 0; i < _cairo_array_num_elements (&surface->pages); i++) {
+ page_info = _cairo_array_index (&surface->pages, i);
+
+ status = _cairo_pdf_surface_object_begin (surface, page_info->page_res);
+ if (unlikely (status))
+ return status;
+
+ _cairo_output_stream_printf (surface->object_stream.stream,
+ "<< /Type /Page %% %d\n"
+ " /Parent %d 0 R\n"
+ " /MediaBox [ 0 0 %f %f ]\n"
+ " /Contents %d 0 R\n"
+ " /Group <<\n"
+ " /Type /Group\n"
+ " /S /Transparency\n"
+ " /I true\n"
+ " /CS /DeviceRGB\n"
+ " >>\n"
+ " /Resources %d 0 R\n",
+ i + 1,
+ surface->pages_resource.id,
+ page_info->width,
+ page_info->height,
+ page_info->content.id,
+ page_info->resources.id);
+
+ if (page_info->struct_parents >= 0) {
+ _cairo_output_stream_printf (surface->object_stream.stream,
+ " /StructParents %d\n",
+ page_info->struct_parents);
+ }
+
+ num_annots = _cairo_array_num_elements (&page_info->annots);
+ if (num_annots > 0) {
+ _cairo_output_stream_printf (surface->object_stream.stream,
+ " /Annots [ ");
+ for (int j = 0; j < num_annots; j++) {
+ _cairo_array_copy_element (&page_info->annots, j, &res);
+ _cairo_output_stream_printf (surface->object_stream.stream,
+ "%d 0 R ",
+ res.id);
+ }
+ _cairo_output_stream_printf (surface->object_stream.stream, "]\n");
+ }
+
+ if (page_info->thumbnail.id) {
+ _cairo_output_stream_printf (surface->object_stream.stream,
+ " /Thumb %d 0 R\n",
+ page_info->thumbnail.id);
+ }
+
+ _cairo_output_stream_printf (surface->object_stream.stream,
+ ">>\n");
+ _cairo_pdf_surface_object_end (surface);
+ }
+
+ return status;
+}
+
static cairo_status_t
_cairo_pdf_surface_finish (void *abstract_surface)
{
@@ -2594,6 +2714,10 @@ _cairo_pdf_surface_finish (void *abstract_surface)
if (unlikely (status))
return status;
+ status = _cairo_pdf_surface_write_page_dicts (surface);
+ if (unlikely (status))
+ return status;
+
catalog = _cairo_pdf_surface_new_object (surface);
if (catalog.id == 0)
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
@@ -2606,7 +2730,7 @@ _cairo_pdf_surface_finish (void *abstract_surface)
if (unlikely (status))
return status;
- if (surface->pdf_version >= CAIRO_PDF_VERSION_1_5)
+ if (!surface->debug && surface->pdf_version >= CAIRO_PDF_VERSION_1_5)
{
xref_res = _cairo_pdf_surface_new_object (surface);
status = _cairo_pdf_surface_write_xref_stream (surface,
@@ -2669,6 +2793,11 @@ _cairo_pdf_surface_finish (void *abstract_surface)
_cairo_pdf_group_resources_fini (&surface->resources);
_cairo_array_fini (&surface->objects);
+ size = _cairo_array_num_elements (&surface->pages);
+ for (i = 0; i < size; i++) {
+ cairo_pdf_page_info_t *page_info = _cairo_array_index (&surface->pages, i);
+ _cairo_array_fini (&page_info->annots);
+ }
_cairo_array_fini (&surface->pages);
_cairo_array_fini (&surface->rgb_linear_functions);
_cairo_array_fini (&surface->alpha_linear_functions);
@@ -2705,7 +2834,6 @@ _cairo_pdf_surface_finish (void *abstract_surface)
return _cairo_error (CAIRO_STATUS_JBIG2_GLOBAL_MISSING);
}
_cairo_array_fini (&surface->jbig2_global);
- _cairo_array_fini (&surface->page_heights);
size = _cairo_array_num_elements (&surface->page_labels);
for (i = 0; i < size; i++) {
@@ -2725,7 +2853,7 @@ static cairo_int_status_t
_cairo_pdf_surface_start_page (void *abstract_surface)
{
cairo_pdf_surface_t *surface = abstract_surface;
- cairo_pdf_resource_t page;
+ cairo_pdf_page_info_t page_info;
cairo_int_status_t status;
/* Document header */
@@ -2758,11 +2886,19 @@ _cairo_pdf_surface_start_page (void *abstract_surface)
_cairo_pdf_group_resources_clear (&surface->resources);
surface->in_xobject = FALSE;
- page = _cairo_pdf_surface_new_object (surface);
- if (page.id == 0)
+ page_info.page_res = _cairo_pdf_surface_new_object (surface);
+ if (page_info.page_res.id == 0)
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
- status = _cairo_array_append (&surface->pages, &page);
+ page_info.width = surface->width;
+ page_info.height = surface->height;
+ page_info.content.id = 0;
+ page_info.resources.id = 0;
+ page_info.thumbnail.id = 0;
+ page_info.struct_parents = -1;
+ _cairo_array_init (&page_info.annots, sizeof (cairo_pdf_resource_t));
+
+ status = _cairo_array_append (&surface->pages, &page_info);
if (unlikely (status))
return status;
@@ -2777,13 +2913,17 @@ _cairo_pdf_surface_has_fallback_images (void *abstract_surface,
cairo_pdf_surface_t *surface = abstract_surface;
cairo_box_double_t bbox;
+ status = _cairo_pdf_interchange_end_page_content (surface);
+ if (unlikely (status))
+ return status;
+
surface->has_fallback_images = has_fallbacks;
surface->in_xobject = has_fallbacks;
bbox.p1.x = 0;
bbox.p1.y = 0;
bbox.p2.x = surface->width;
bbox.p2.y = surface->height;
- status = _cairo_pdf_surface_open_content_stream (surface, &bbox, NULL, has_fallbacks, has_fallbacks);
+ status = _cairo_pdf_surface_open_content_stream (surface, &bbox, NULL, has_fallbacks, has_fallbacks, -1);
if (unlikely (status))
return status;
@@ -2883,6 +3023,7 @@ _cairo_pdf_surface_add_padded_image_surface (cairo_pdf_surface_t *surfa
status = _cairo_pdf_surface_add_source_surface (surface,
pad_image,
NULL,
+ -1 , /* node_surface_index */
CAIRO_OPERATOR_OVER, /* not used for images */
source->filter,
FALSE, /* stencil mask */
@@ -3722,6 +3863,7 @@ _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t *surface,
cairo_bool_t is_subsurface;
cairo_bool_t transparency_group;
cairo_recording_surface_t *recording;
+ int struct_parents = -1;
assert (pdf_source->type == CAIRO_PATTERN_TYPE_SURFACE);
@@ -3780,11 +3922,20 @@ _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t *surface,
_cairo_recording_surface_has_only_bilevel_alpha (recording) &&
_cairo_recording_surface_has_only_op_over (recording));
+ status = _cairo_pdf_interchange_emit_recording_surface_begin (surface,
+ pdf_source->surface,
+ pdf_source->hash_entry->region_id,
+ pdf_source->hash_entry->surface_res,
+ &struct_parents);
+ if (unlikely (status))
+ goto err;
+
status = _cairo_pdf_surface_open_content_stream (surface,
&bbox,
&pdf_source->hash_entry->surface_res,
TRUE,
- transparency_group);
+ transparency_group,
+ struct_parents);
if (unlikely (status))
goto err;
@@ -3823,6 +3974,10 @@ _cairo_pdf_surface_emit_recording_surface (cairo_pdf_surface_t *surface,
surface->paginated_mode = old_paginated_mode;
surface->surface_extents = old_surface_extents;
surface->surface_bounded = old_surface_bounded;
+ surface->reset_gs_required = FALSE;
+
+ if (pdf_source->hash_entry->region_id > 0)
+ status = _cairo_pdf_interchange_emit_recording_surface_end (surface, pdf_source->surface);
err:
cairo_surface_destroy (free_me);
@@ -3964,6 +4119,7 @@ _cairo_pdf_surface_emit_surface_pattern (cairo_pdf_surface_t *surface,
status = _cairo_pdf_surface_add_source_surface (surface,
NULL,
pattern,
+ pdf_pattern->region_id,
pdf_pattern->operator,
pattern->filter,
FALSE, /* stencil mask */
@@ -5108,6 +5264,7 @@ static cairo_int_status_t
_cairo_pdf_surface_paint_surface_pattern (cairo_pdf_surface_t *surface,
cairo_operator_t op,
const cairo_pattern_t *source,
+ cairo_analysis_source_t source_type,
const cairo_rectangle_int_t *extents,
double alpha,
cairo_pdf_resource_t *smask_res,
@@ -5119,6 +5276,7 @@ _cairo_pdf_surface_paint_surface_pattern (cairo_pdf_surface_t *surface,
double x_offset;
double y_offset;
cairo_pdf_source_surface_entry_t *pdf_source;
+ int region_id = 0;
if (source->extend == CAIRO_EXTEND_PAD &&
!(source->type == CAIRO_PATTERN_TYPE_SURFACE &&
@@ -5132,9 +5290,19 @@ _cairo_pdf_surface_paint_surface_pattern (cairo_pdf_surface_t *surface,
&y_offset,
NULL);
} else {
+ if (source->type == CAIRO_PATTERN_TYPE_SURFACE) {
+ cairo_surface_pattern_t *surface_pattern = (cairo_surface_pattern_t *) source;
+ if (_cairo_pdf_interchange_struct_tree_requires_recording_surface (surface,
+ surface_pattern,
+ source_type))
+ {
+ region_id = surface_pattern->region_array_id;
+ }
+ }
status = _cairo_pdf_surface_add_source_surface (surface,
NULL,
source,
+ region_id,
op,
source->filter,
stencil_mask,
@@ -5269,6 +5437,7 @@ static cairo_int_status_t
_cairo_pdf_surface_paint_pattern (cairo_pdf_surface_t *surface,
cairo_operator_t op,
const cairo_pattern_t *source,
+ cairo_analysis_source_t source_type,
const cairo_rectangle_int_t *extents,
double alpha,
cairo_bool_t mask)
@@ -5279,6 +5448,7 @@ _cairo_pdf_surface_paint_pattern (cairo_pdf_surface_t *surface,
return _cairo_pdf_surface_paint_surface_pattern (surface,
op,
source,
+ source_type,
extents,
alpha,
NULL,
@@ -5474,10 +5644,6 @@ _cairo_pdf_surface_show_page (void *abstract_surface)
cairo_pdf_surface_t *surface = abstract_surface;
cairo_int_status_t status;
- status = _cairo_array_append (&surface->page_heights, &surface->height);
- if (unlikely (status))
- return status;
-
status = _cairo_array_append (&surface->page_labels, &surface->current_page_label);
if (unlikely (status))
return status;
@@ -5530,7 +5696,7 @@ _cairo_pdf_surface_get_font_options (void *abstract_surface,
static cairo_int_status_t
_cairo_pdf_surface_write_pages (cairo_pdf_surface_t *surface)
{
- cairo_pdf_resource_t page;
+ cairo_pdf_page_info_t *page_info;
int num_pages, i;
cairo_int_status_t status;
@@ -5544,8 +5710,8 @@ _cairo_pdf_surface_write_pages (cairo_pdf_surface_t *surface)
num_pages = _cairo_array_num_elements (&surface->pages);
for (i = 0; i < num_pages; i++) {
- _cairo_array_copy_element (&surface->pages, i, &page);
- _cairo_output_stream_printf (surface->object_stream.stream, "%d 0 R ", page.id);
+ page_info = _cairo_array_index (&surface->pages, i);
+ _cairo_output_stream_printf (surface->object_stream.stream, "%d 0 R ", page_info->page_res.id);
}
_cairo_output_stream_printf (surface->object_stream.stream, "]\n");
@@ -6605,7 +6771,7 @@ cairo_pdf_surface_emit_color_glyph (cairo_pdf_surface_t *surface,
goto cleanup;
status = _cairo_recording_surface_replay_and_create_regions (glyph_surface, regions_id,
- NULL, analysis, TRUE);
+ NULL, analysis, TRUE, FALSE);
if (status)
goto cleanup;
@@ -6667,6 +6833,7 @@ cairo_pdf_surface_emit_color_glyph (cairo_pdf_surface_t *surface,
_cairo_pdf_surface_paint_surface_pattern (surface,
CAIRO_OPERATOR_OVER,
&surface_pattern.base,
+ CAIRO_ANALYSIS_SOURCE_NONE,
&extents,
1.0, /* alpha */
NULL, /* smask_res */
@@ -6738,6 +6905,7 @@ cairo_pdf_surface_emit_color_glyph_image (cairo_pdf_surface_t *surface,
_cairo_pdf_surface_paint_surface_pattern (surface,
CAIRO_OPERATOR_OVER,
image_pattern,
+ CAIRO_ANALYSIS_SOURCE_NONE,
&extents,
1.0, /* alpha */
NULL, /* smask_res */
@@ -7267,6 +7435,7 @@ _cairo_pdf_surface_write_mask_group (cairo_pdf_surface_t *surface,
status = _cairo_pdf_surface_paint_pattern (surface,
CAIRO_OPERATOR_OVER,
group->mask,
+ CAIRO_ANALYSIS_MASK_MASK,
&group->extents,
1.0, /* alpha */
FALSE); /* mask */
@@ -7279,6 +7448,7 @@ _cairo_pdf_surface_write_mask_group (cairo_pdf_surface_t *surface,
gstate_res.id = 0;
status = _cairo_pdf_surface_add_pdf_pattern (surface, group->mask,
CAIRO_OPERATOR_OVER,
+ CAIRO_ANALYSIS_MASK_MASK,
NULL,
&pattern_res, &gstate_res);
if (unlikely (status))
@@ -7344,6 +7514,7 @@ _cairo_pdf_surface_write_mask_group (cairo_pdf_surface_t *surface,
status = _cairo_pdf_surface_paint_pattern (surface,
CAIRO_OPERATOR_OVER,
group->source,
+ CAIRO_ANALYSIS_MASK_MASK,
&group->extents,
1.0, /* alpha */
FALSE); /* mask */
@@ -7356,6 +7527,7 @@ _cairo_pdf_surface_write_mask_group (cairo_pdf_surface_t *surface,
gstate_res.id = 0;
status = _cairo_pdf_surface_add_pdf_pattern (surface, group->source,
CAIRO_OPERATOR_OVER,
+ CAIRO_ANALYSIS_MASK_MASK,
NULL,
&pattern_res, &gstate_res);
if (unlikely (status))
@@ -7593,10 +7765,12 @@ _cairo_pdf_surface_write_patterns_and_smask_groups (cairo_pdf_surface_t *surface
static cairo_int_status_t
_cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
{
- cairo_pdf_resource_t knockout, res, thumbnail_res;
- cairo_pdf_resource_t *page;
+ cairo_pdf_resource_t knockout, res;
cairo_int_status_t status;
- unsigned int i, len, page_num, num_annots;
+ unsigned int i, len;
+ cairo_pdf_page_info_t *page_info;
+
+ page_info = _cairo_array_last_element (&surface->pages);
status = _cairo_pdf_surface_open_object_stream (surface);
if (unlikely (status))
@@ -7642,7 +7816,7 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
return status;
_cairo_pdf_group_resources_clear (&surface->resources);
- status = _cairo_pdf_surface_open_content_stream (surface, NULL, NULL, FALSE, FALSE);
+ status = _cairo_pdf_surface_open_content_stream (surface, NULL, NULL, FALSE, FALSE, -1);
if (unlikely (status))
return status;
@@ -7658,70 +7832,18 @@ _cairo_pdf_surface_write_page (cairo_pdf_surface_t *surface)
return status;
}
- thumbnail_res.id = 0;
if (surface->thumbnail_image) {
cairo_pdf_source_surface_entry_t entry;
memset (&entry, 0, sizeof (entry));
- thumbnail_res = _cairo_pdf_surface_new_object (surface);
- entry.surface_res = thumbnail_res;
+ page_info->thumbnail = _cairo_pdf_surface_new_object (surface);
+ entry.surface_res = page_info->thumbnail;
_cairo_pdf_surface_emit_image (surface, surface->thumbnail_image, &entry);
}
- page_num = _cairo_array_num_elements (&surface->pages);
- page = _cairo_array_index (&surface->pages, page_num - 1);
-
- status = _cairo_pdf_surface_object_begin (surface, *page);
- if (unlikely (status))
- return status;
-
- _cairo_output_stream_printf (surface->object_stream.stream,
- "<< /Type /Page %% %d\n"
- " /Parent %d 0 R\n"
- " /MediaBox [ 0 0 %f %f ]\n"
- " /Contents %d 0 R\n"
- " /Group <<\n"
- " /Type /Group\n"
- " /S /Transparency\n"
- " /I true\n"
- " /CS /DeviceRGB\n"
- " >>\n"
- " /Resources %d 0 R\n",
- page_num,
- surface->pages_resource.id,
- surface->width,
- surface->height,
- surface->content.id,
- surface->content_resources.id);
-
- if (surface->page_parent_tree >= 0) {
- _cairo_output_stream_printf (surface->object_stream.stream,
- " /StructParents %d\n",
- surface->page_parent_tree);
- }
-
- num_annots = _cairo_array_num_elements (&surface->page_annots);
- if (num_annots > 0) {
- _cairo_output_stream_printf (surface->object_stream.stream,
- " /Annots [ ");
- for (i = 0; i < num_annots; i++) {
- _cairo_array_copy_element (&surface->page_annots, i, &res);
- _cairo_output_stream_printf (surface->object_stream.stream,
- "%d 0 R ",
- res.id);
- }
- _cairo_output_stream_printf (surface->object_stream.stream, "]\n");
- }
-
- if (thumbnail_res.id) {
- _cairo_output_stream_printf (surface->object_stream.stream,
- " /Thumb %d 0 R\n",
- thumbnail_res.id);
- }
-
- _cairo_output_stream_printf (surface->object_stream.stream,
- ">>\n");
- _cairo_pdf_surface_object_end (surface);
+ page_info->content = surface->content;
+ page_info->resources = surface->content_resources;
+ page_info->struct_parents = surface->page_parent_tree;
status = _cairo_pdf_surface_write_patterns_and_smask_groups (surface, FALSE);
if (unlikely (status))
@@ -7968,7 +8090,7 @@ _cairo_pdf_surface_start_fallback (cairo_pdf_surface_t *surface)
bbox.p1.y = 0;
bbox.p2.x = surface->width;
bbox.p2.y = surface->height;
- status = _cairo_pdf_surface_open_content_stream (surface, &bbox, NULL, TRUE, TRUE);
+ status = _cairo_pdf_surface_open_content_stream (surface, &bbox, NULL, TRUE, TRUE, -1);
if (unlikely (status))
return status;
@@ -8115,6 +8237,7 @@ _cairo_pdf_surface_emit_combined_smask (cairo_pdf_surface_t *surface,
status = _cairo_pdf_surface_add_source_surface (surface,
NULL,
mask,
+ -1, /* node_surface_index */
op,
source->filter,
FALSE, /* stencil mask */
@@ -8135,7 +8258,9 @@ _cairo_pdf_surface_emit_combined_smask (cairo_pdf_surface_t *surface,
return status;
_cairo_output_stream_printf (surface->output, "q\n");
- status = _cairo_pdf_surface_paint_surface_pattern (surface, op, source, extents,
+ status = _cairo_pdf_surface_paint_surface_pattern (surface, op, source,
+ CAIRO_ANALYSIS_SOURCE_NONE,
+ extents,
1.0, /* alpha */
need_smask ? &pdf_source->surface_res : NULL,
FALSE);
@@ -8200,7 +8325,9 @@ _cairo_pdf_surface_emit_stencil_mask (cairo_pdf_surface_t *surface,
return status;
_cairo_output_stream_printf (surface->output, "q\n");
- status = _cairo_pdf_surface_paint_surface_pattern (surface, op, mask, extents, 1.0, NULL, TRUE);
+ status = _cairo_pdf_surface_paint_surface_pattern (surface, op, mask,
+ CAIRO_ANALYSIS_SOURCE_NONE,
+ extents, 1.0, NULL, TRUE);
if (unlikely (status))
return status;
@@ -8282,6 +8409,7 @@ _cairo_pdf_surface_paint (void *abstract_surface,
status = _cairo_pdf_surface_paint_pattern (surface,
op,
source,
+ CAIRO_ANALYSIS_SOURCE_PAINT,
&extents.bounded,
1.0, /* alpha */
FALSE); /* mask */
@@ -8296,6 +8424,7 @@ _cairo_pdf_surface_paint (void *abstract_surface,
pattern_res.id = 0;
gstate_res.id = 0;
status = _cairo_pdf_surface_add_pdf_pattern (surface, source, op,
+ CAIRO_ANALYSIS_SOURCE_PAINT,
&extents.bounded,
&pattern_res, &gstate_res);
if (unlikely (status))
@@ -8467,6 +8596,7 @@ _cairo_pdf_surface_mask (void *abstract_surface,
status = _cairo_pdf_surface_paint_pattern (surface,
op,
source,
+ CAIRO_ANALYSIS_SOURCE_MASK,
&extents.bounded,
alpha,
FALSE); /* mask */
@@ -8596,6 +8726,7 @@ _cairo_pdf_surface_stroke (void *abstract_surface,
pattern_res.id = 0;
gstate_res.id = 0;
status = _cairo_pdf_surface_add_pdf_pattern (surface, source, op,
+ CAIRO_ANALYSIS_SOURCE_STROKE,
&extents.bounded,
&pattern_res, &gstate_res);
if (unlikely (status))
@@ -8754,6 +8885,7 @@ _cairo_pdf_surface_fill (void *abstract_surface,
status = _cairo_pdf_surface_paint_pattern (surface,
op,
source,
+ CAIRO_ANALYSIS_SOURCE_FILL,
&extents.bounded,
1.0, /* alpha */
FALSE); /* mask */
@@ -8768,6 +8900,7 @@ _cairo_pdf_surface_fill (void *abstract_surface,
pattern_res.id = 0;
gstate_res.id = 0;
status = _cairo_pdf_surface_add_pdf_pattern (surface, source, op,
+ CAIRO_ANALYSIS_SOURCE_FILL,
&extents.bounded,
&pattern_res, &gstate_res);
if (unlikely (status))
@@ -8947,6 +9080,7 @@ _cairo_pdf_surface_fill_stroke (void *abstract_surface,
gstate_res.id = 0;
status = _cairo_pdf_surface_add_pdf_pattern (surface, fill_source,
fill_op,
+ CAIRO_ANALYSIS_SOURCE_FILL,
&extents.bounded,
&fill_pattern_res,
&gstate_res);
@@ -8960,6 +9094,7 @@ _cairo_pdf_surface_fill_stroke (void *abstract_surface,
status = _cairo_pdf_surface_add_pdf_pattern (surface,
stroke_source,
stroke_op,
+ CAIRO_ANALYSIS_SOURCE_STROKE,
&extents.bounded,
&stroke_pattern_res,
&gstate_res);
@@ -9038,6 +9173,10 @@ _cairo_pdf_surface_show_text_glyphs (void *abstract_surface,
if (unlikely (status))
return status;
+ status = _cairo_pdf_interchange_add_content (surface);
+ if (unlikely (status))
+ return status;
+
status = _cairo_pdf_interchange_add_operation_extents (surface, &extents.bounded);
if (unlikely (status))
return status;
@@ -9063,6 +9202,7 @@ _cairo_pdf_surface_show_text_glyphs (void *abstract_surface,
pattern_res.id = 0;
gstate_res.id = 0;
status = _cairo_pdf_surface_add_pdf_pattern (surface, source, op,
+ CAIRO_ANALYSIS_SOURCE_SHOW_GLYPHS,
&extents.bounded,
&pattern_res, &gstate_res);
if (unlikely (status))
@@ -9191,9 +9331,10 @@ _cairo_pdf_surface_tag (void *abstract_surface,
cairo_bool_t begin,
const char *tag_name,
const char *attributes)
+
{
cairo_pdf_surface_t *surface = abstract_surface;
- cairo_int_status_t status = 0;
+ cairo_int_status_t status = CAIRO_INT_STATUS_SUCCESS;
if (begin)
status = _cairo_pdf_interchange_tag_begin (surface, tag_name, attributes);
@@ -9203,6 +9344,16 @@ _cairo_pdf_surface_tag (void *abstract_surface,
return status;
}
+static cairo_int_status_t
+_cairo_pdf_surface_command_id (void *abstract_surface,
+ unsigned int recording_id,
+ unsigned int command_id)
+{
+ cairo_pdf_surface_t *surface = abstract_surface;
+
+ return _cairo_pdf_interchange_command_id (surface, recording_id, command_id);
+}
+
/* The Type 3 font subset support will the embed the
* CAIRO_SCALED_GLYPH_INFO_COLOR_SURFACE image if vector operations
* are not supported. The only case we don't currently handle is if a
@@ -9266,6 +9417,33 @@ _cairo_pdf_surface_supports_color_glyph (void *abstract_surface
}
static cairo_int_status_t
+_cairo_pdf_surface_analyze_recording_surface(void *abstract_surface,
+ const cairo_surface_pattern_t *recording_surface_pattern,
+ unsigned int region_id,
+ cairo_analysis_source_t source_type,
+ cairo_bool_t begin)
+{
+ cairo_pdf_surface_t *surface = abstract_surface;
+ cairo_int_status_t status = CAIRO_INT_STATUS_SUCCESS;
+
+ if (begin) {
+ status = _cairo_pdf_interchange_recording_source_surface_begin (
+ surface,
+ recording_surface_pattern,
+ region_id,
+ source_type);
+ } else {
+ status = _cairo_pdf_interchange_recording_source_surface_end (
+ surface,
+ recording_surface_pattern,
+ region_id,
+ source_type);
+ }
+
+ return status;
+}
+
+static cairo_int_status_t
_cairo_pdf_surface_set_paginated_mode (void *abstract_surface,
cairo_paginated_mode_t paginated_mode)
{
@@ -9273,6 +9451,7 @@ _cairo_pdf_surface_set_paginated_mode (void *abstract_surface,
cairo_int_status_t status;
surface->paginated_mode = paginated_mode;
+
status = _cairo_pdf_interchange_begin_page_content (surface);
if (unlikely (status))
return status;
@@ -9324,6 +9503,8 @@ static const cairo_surface_backend_t cairo_pdf_surface_backend = {
_cairo_pdf_surface_get_supported_mime_types,
_cairo_pdf_surface_tag,
_cairo_pdf_surface_supports_color_glyph,
+ _cairo_pdf_surface_analyze_recording_surface,
+ _cairo_pdf_surface_command_id,
};
static const cairo_paginated_surface_backend_t
diff --git a/src/cairo-quartz-surface.c b/src/cairo-quartz-surface.c
index d8a8065d0..6da1b6ffa 100644
--- a/src/cairo-quartz-surface.c
+++ b/src/cairo-quartz-surface.c
@@ -722,8 +722,7 @@ _cairo_surface_to_cgimage (cairo_surface_t *source,
status = _cairo_recording_surface_replay_with_clip (source,
matrix,
&surface->base,
- NULL,
- FALSE);
+ NULL);
if (unlikely (status)) {
cairo_surface_destroy (&surface->base);
return status;
diff --git a/src/cairo-recording-surface-private.h b/src/cairo-recording-surface-private.h
index 7d4de1ed9..acace4ec8 100644
--- a/src/cairo-recording-surface-private.h
+++ b/src/cairo-recording-surface-private.h
@@ -155,6 +155,7 @@ typedef struct _cairo_recording_surface {
cairo_bool_t optimize_clears;
cairo_bool_t has_bilevel_alpha;
cairo_bool_t has_only_op_over;
+ cairo_bool_t has_tags;
struct bbtree {
cairo_box_t extents;
@@ -204,18 +205,26 @@ _cairo_recording_surface_replay_with_foreground_color (cairo_surface_t *surf
cairo_bool_t *foreground_used);
cairo_private cairo_status_t
-_cairo_recording_surface_replay_with_clip (cairo_surface_t *surface,
- const cairo_matrix_t *surface_transform,
- cairo_surface_t *target,
- const cairo_clip_t *target_clip,
- cairo_bool_t surface_is_unbounded);
+_cairo_recording_surface_replay_with_transform (cairo_surface_t *surface,
+ const cairo_matrix_t *surface_transform,
+ cairo_surface_t *target,
+ cairo_bool_t surface_is_unbounded,
+ cairo_bool_t replay_all);
+
+cairo_private cairo_status_t
+_cairo_recording_surface_replay_with_clip (cairo_surface_t *surface,
+ const cairo_matrix_t *surface_transform,
+ cairo_surface_t *target,
+ const cairo_clip_t *target_clip);
cairo_private cairo_status_t
_cairo_recording_surface_replay_and_create_regions (cairo_surface_t *surface,
unsigned int regions_id,
const cairo_matrix_t *surface_transform,
cairo_surface_t *target,
- cairo_bool_t surface_is_unbounded);
+ cairo_bool_t surface_is_unbounded,
+ cairo_bool_t replay_all);
+
cairo_private cairo_status_t
_cairo_recording_surface_replay_region (cairo_surface_t *surface,
unsigned int regions_id,
@@ -239,6 +248,9 @@ _cairo_recording_surface_has_only_bilevel_alpha (cairo_recording_surface_t *surf
cairo_private cairo_bool_t
_cairo_recording_surface_has_only_op_over (cairo_recording_surface_t *surface);
+cairo_private cairo_bool_t
+_cairo_recording_surface_has_tags (cairo_surface_t *surface);
+
cairo_private cairo_status_t
_cairo_recording_surface_region_array_attach (cairo_surface_t *surface,
unsigned int *id);
diff --git a/src/cairo-recording-surface.c b/src/cairo-recording-surface.c
index 2912f5ede..5735c7ca3 100644
--- a/src/cairo-recording-surface.c
+++ b/src/cairo-recording-surface.c
@@ -103,6 +103,7 @@ typedef struct _cairo_recording_surface_replay_params {
unsigned int regions_id;
const cairo_color_t *foreground_color;
cairo_bool_t foreground_used;
+ cairo_bool_t replay_all;
} cairo_recording_surface_replay_params_t;
static const cairo_surface_backend_t cairo_recording_surface_backend;
@@ -436,6 +437,7 @@ cairo_recording_surface_create (cairo_content_t content,
surface->optimize_clears = TRUE;
surface->has_bilevel_alpha = FALSE;
surface->has_only_op_over = FALSE;
+ surface->has_tags = FALSE;
CAIRO_MUTEX_INIT (surface->mutex);
@@ -1190,6 +1192,8 @@ _cairo_recording_surface_tag (void *abstract_surface,
TRACE ((stderr, "%s: surface=%d\n", __FUNCTION__, surface->base.unique_id));
+ surface->has_tags = TRUE;
+
command = calloc (1, sizeof (cairo_command_tag_t));
if (unlikely (command == NULL)) {
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
@@ -1635,6 +1639,7 @@ _cairo_recording_surface_snapshot (void *abstract_other)
surface->unbounded = other->unbounded;
surface->has_bilevel_alpha = other->has_bilevel_alpha;
surface->has_only_op_over = other->has_only_op_over;
+ surface->has_tags = other->has_tags;
surface->base.is_clear = other->base.is_clear;
@@ -1644,8 +1649,6 @@ _cairo_recording_surface_snapshot (void *abstract_other)
surface->indices = NULL;
surface->num_indices = 0;
surface->optimize_clears = TRUE;
- surface->has_bilevel_alpha = other->has_bilevel_alpha;
- surface->has_only_op_over = other->has_only_op_over;
CAIRO_MUTEX_INIT (surface->mutex);
@@ -1715,6 +1718,8 @@ static const cairo_surface_backend_t cairo_recording_surface_backend = {
NULL, /* get_supported_mime_types */
_cairo_recording_surface_tag,
_cairo_recording_surface_supports_color_glyph,
+ NULL, /* analyze_recording_surface */
+ NULL, /* command_id */
};
static unsigned int
@@ -1918,7 +1923,7 @@ _cairo_recording_surface_get_visible_commands (cairo_recording_surface_t *surfac
cairo_box_t box;
if (surface->commands.num_elements == 0)
- return 0;
+ return 0;
_cairo_box_from_rectangle (&box, extents);
@@ -2078,7 +2083,7 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t *surface,
if (regions_array)
region_elements = _cairo_array_index (&regions_array->regions, 0);
- if (extents.width < r->width || extents.height < r->height) {
+ if (!params->replay_all && (extents.width < r->width || extents.height < r->height)) {
num_elements =
_cairo_recording_surface_get_visible_commands (surface, &extents);
use_indices = num_elements != surface->commands.num_elements;
@@ -2106,6 +2111,13 @@ _cairo_recording_surface_replay_internal (cairo_recording_surface_t *surface,
continue;
}
+
+ if (params->target->backend->command_id) {
+ status = params->target->backend->command_id (params->target, params->regions_id, i);
+ if (unlikely (status))
+ return status;
+ }
+
switch (command->header.type) {
case CAIRO_COMMAND_PAINT:
if (region_element)
@@ -2457,6 +2469,7 @@ _cairo_recording_surface_replay (cairo_surface_t *surface,
params.region = CAIRO_RECORDING_REGION_ALL;
params.regions_id = 0;
params.foreground_color = NULL;
+ params.replay_all = FALSE;
return _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
}
@@ -2480,6 +2493,7 @@ _cairo_recording_surface_replay_with_foreground_color (cairo_surface_t *surf
params.regions_id = 0;
params.foreground_color = foreground_color;
params.foreground_used = FALSE;
+ params.replay_all = FALSE;
status = _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
*foreground_used = params.foreground_used;
@@ -2488,11 +2502,33 @@ _cairo_recording_surface_replay_with_foreground_color (cairo_surface_t *surf
}
cairo_status_t
+_cairo_recording_surface_replay_with_transform (cairo_surface_t *surface,
+ const cairo_matrix_t *surface_transform,
+ cairo_surface_t *target,
+ cairo_bool_t surface_is_unbounded,
+ cairo_bool_t replay_all)
+{
+ cairo_recording_surface_replay_params_t params;
+
+ params.surface_extents = NULL;
+ params.surface_transform = surface_transform;
+ params.target = target;
+ params.target_clip = NULL;
+ params.surface_is_unbounded = surface_is_unbounded;
+ params.type = CAIRO_RECORDING_REPLAY;
+ params.region = CAIRO_RECORDING_REGION_ALL;
+ params.regions_id = 0;
+ params.foreground_color = NULL;
+ params.replay_all = replay_all;
+
+ return _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
+}
+
+cairo_status_t
_cairo_recording_surface_replay_with_clip (cairo_surface_t *surface,
const cairo_matrix_t *surface_transform,
cairo_surface_t *target,
- const cairo_clip_t *target_clip,
- cairo_bool_t surface_is_unbounded)
+ const cairo_clip_t *target_clip)
{
cairo_recording_surface_replay_params_t params;
@@ -2500,11 +2536,12 @@ _cairo_recording_surface_replay_with_clip (cairo_surface_t *surface,
params.surface_transform = surface_transform;
params.target = target;
params.target_clip = target_clip;
- params.surface_is_unbounded = surface_is_unbounded;
+ params.surface_is_unbounded = FALSE;
params.type = CAIRO_RECORDING_REPLAY;
params.region = CAIRO_RECORDING_REGION_ALL;
params.regions_id = 0;
params.foreground_color = NULL;
+ params.replay_all = FALSE;
return _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
}
@@ -2520,7 +2557,8 @@ _cairo_recording_surface_replay_and_create_regions (cairo_surface_t *surface,
unsigned int regions_id,
const cairo_matrix_t *surface_transform,
cairo_surface_t *target,
- cairo_bool_t surface_is_unbounded)
+ cairo_bool_t surface_is_unbounded,
+ cairo_bool_t replay_all)
{
cairo_recording_surface_replay_params_t params;
@@ -2533,6 +2571,7 @@ _cairo_recording_surface_replay_and_create_regions (cairo_surface_t *surface,
params.region = CAIRO_RECORDING_REGION_ALL;
params.regions_id = regions_id;
params.foreground_color = NULL;
+ params.replay_all = replay_all;
return _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
}
@@ -2555,6 +2594,7 @@ _cairo_recording_surface_replay_region (cairo_surface_t *surface,
params.region = region;
params.regions_id = regions_id;
params.foreground_color = NULL;
+ params.replay_all = FALSE;
return _cairo_recording_surface_replay_internal ((cairo_recording_surface_t *) surface, &params);
}
@@ -2702,6 +2742,21 @@ _cairo_recording_surface_has_only_op_over (cairo_recording_surface_t *surface)
return surface->has_only_op_over;
}
+cairo_bool_t
+_cairo_recording_surface_has_tags (cairo_surface_t *surface)
+{
+ cairo_recording_surface_t *record;
+
+ if (surface->status || ! _cairo_surface_is_recording (surface)) {
+ _cairo_error_throw (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
+ return FALSE;
+ }
+
+ record = (cairo_recording_surface_t *)surface;
+
+ return record->has_tags;
+}
+
static void
print_indent (FILE *file, int indent)
{
@@ -2875,7 +2930,10 @@ _cairo_debug_print_recording_surface (FILE *file,
case CAIRO_COMMAND_TAG:
print_indent (file, indent);
- fprintf(file, "%d TAG\n", i);
+ fprintf(file, "%d %s %s '%s'\n",
+ i,
+ command->tag.begin ? "BEGIN TAG" : "END TAG",
+ command->tag.tag_name, command->tag.attributes);
break;
default:
diff --git a/src/cairo-spans-compositor.c b/src/cairo-spans-compositor.c
index 49c5999d2..50c92b25c 100644
--- a/src/cairo-spans-compositor.c
+++ b/src/cairo-spans-compositor.c
@@ -612,7 +612,7 @@ composite_aligned_boxes (const cairo_spans_compositor_t *compositor,
recording_clip = _cairo_clip_from_boxes (boxes);
status = _cairo_recording_surface_replay_with_clip (unwrap_source (source),
- m, dst, recording_clip, FALSE);
+ m, dst, recording_clip);
_cairo_clip_destroy (recording_clip);
return status;
diff --git a/src/cairo-surface-backend-private.h b/src/cairo-surface-backend-private.h
index 15032de20..856c596c8 100644
--- a/src/cairo-surface-backend-private.h
+++ b/src/cairo-surface-backend-private.h
@@ -1,3 +1,4 @@
+/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
/* cairo - a vector graphics library with display and print output
*
* Copyright © 2002 University of Southern California
@@ -40,6 +41,7 @@
#include "cairo-compiler-private.h"
#include "cairo-error-private.h"
+#include "cairo-pattern-private.h"
CAIRO_BEGIN_DECLS
@@ -211,6 +213,29 @@ struct _cairo_surface_backend {
(*supports_color_glyph) (void *surface,
cairo_scaled_font_t *scaled_font,
unsigned long glyph_index);
+
+ /* Paginated surfaces only. During the analysis stage, if any
+ * recording surfaces used as a source are to be replayed, this
+ * function will be called at the begining and end of the replay.
+ *
+ * @recording_surface - the recording surface used as a source
+ * @source_type - a type indicating the combination of drawing
+ * operation and source type
+ * @begin - TRUE when called before the replay, FALSE when called
+ * after the replay has finished.
+ */
+ cairo_warn cairo_int_status_t
+ (*analyze_recording_surface)(void *surface,
+ const cairo_surface_pattern_t *recording_surface_pattern,
+ unsigned int region_id,
+ cairo_analysis_source_t source_type,
+ cairo_bool_t begin);
+
+ cairo_warn cairo_int_status_t
+ (*command_id) (void *surface,
+ unsigned int recording_id,
+ unsigned int command_id);
+
};
cairo_private cairo_status_t
diff --git a/src/cairo-tag-attributes-private.h b/src/cairo-tag-attributes-private.h
index 3f5fa5b64..1b770aef9 100644
--- a/src/cairo-tag-attributes-private.h
+++ b/src/cairo-tag-attributes-private.h
@@ -49,6 +49,15 @@ typedef enum {
TAG_LINK_FILE,
} cairo_tag_link_type_t;
+typedef struct _cairo_content_attrs {
+ char *id;
+ char *tag_name;
+} cairo_content_attrs_t;
+
+typedef struct _cairo_content_ref_attrs {
+ char *ref;
+} cairo_content_ref_attrs_t;
+
typedef struct _cairo_link_attrs {
cairo_tag_link_type_t link_type;
cairo_array_t rects;
@@ -58,6 +67,9 @@ typedef struct _cairo_link_attrs {
int page;
cairo_bool_t has_pos;
cairo_point_double_t pos;
+ char *id;
+ char *ref;
+ int link_page;
} cairo_link_attrs_t;
typedef struct _cairo_dest_attrs {
@@ -91,9 +103,27 @@ cairo_private cairo_int_status_t
_cairo_tag_parse_dest_attributes (const char *attributes, cairo_dest_attrs_t *dest_attrs);
cairo_private cairo_int_status_t
+_cairo_tag_parse_content_attributes (const char *attributes, cairo_content_attrs_t *content_attrs);
+
+cairo_private cairo_int_status_t
+_cairo_tag_parse_content_ref_attributes (const char *attributes, cairo_content_ref_attrs_t *content_ref_attrs);
+
+cairo_private cairo_int_status_t
_cairo_tag_parse_ccitt_params (const char *attributes, cairo_ccitt_params_t *dest_attrs);
cairo_private cairo_int_status_t
_cairo_tag_parse_eps_params (const char *attributes, cairo_eps_params_t *dest_attrs);
+cairo_private void
+_cairo_tag_free_link_attributes (cairo_link_attrs_t *link_attrs);
+
+cairo_private void
+_cairo_tag_free_dest_attributes (cairo_dest_attrs_t *link_attrs);
+
+cairo_private void
+_cairo_tag_free_content_attributes (cairo_content_attrs_t *link_attrs);
+
+cairo_private void
+_cairo_tag_free_content_ref_attributes (cairo_content_ref_attrs_t *link_attrs);
+
#endif /* CAIRO_TAG_ATTRIBUTES_PRIVATE_H */
diff --git a/src/cairo-tag-attributes.c b/src/cairo-tag-attributes.c
index 85467ad73..4fde1b5b8 100644
--- a/src/cairo-tag-attributes.c
+++ b/src/cairo-tag-attributes.c
@@ -60,6 +60,25 @@ typedef struct _attribute_spec {
} attribute_spec_t;
/*
+ * id [optional] content id
+ * content [optional] One or more content ids
+ */
+static const attribute_spec_t _content_attrib_spec[] = {
+ { "tag_name", ATTRIBUTE_STRING },
+ { "id", ATTRIBUTE_STRING },
+ { NULL }
+};
+
+/*
+ * id [optional] content id
+ * content [optional] One or more content ids
+ */
+static const attribute_spec_t _content_ref_attrib_spec[] = {
+ { "ref", ATTRIBUTE_STRING },
+ { NULL }
+};
+
+/*
* name [required] Unique name of this destination (UTF-8)
* x [optional] x coordinate of destination on page. Default is x coord of
* extents of operations enclosed by the dest begin/end tags.
@@ -68,7 +87,7 @@ typedef struct _attribute_spec {
* internal [optional] If true, the name may be optimized out of the PDF where
* possible. Default false.
*/
-static attribute_spec_t _dest_attrib_spec[] = {
+static const attribute_spec_t _dest_attrib_spec[] = {
{ "name", ATTRIBUTE_STRING },
{ "x", ATTRIBUTE_FLOAT },
{ "y", ATTRIBUTE_FLOAT },
@@ -103,7 +122,7 @@ static attribute_spec_t _dest_attrib_spec[] = {
* page - Page number in the PDF file to link to
* pos - [optional] Position of destination on page. Default is 0,0.
*/
-static attribute_spec_t _link_attrib_spec[] =
+static const attribute_spec_t _link_attrib_spec[] =
{
{ "rect", ATTRIBUTE_FLOAT, -1 },
{ "dest", ATTRIBUTE_STRING },
@@ -111,6 +130,9 @@ static attribute_spec_t _link_attrib_spec[] =
{ "file", ATTRIBUTE_STRING },
{ "page", ATTRIBUTE_INT },
{ "pos", ATTRIBUTE_FLOAT, 2 },
+ { "id", ATTRIBUTE_STRING },
+ { "ref", ATTRIBUTE_STRING },
+ { "link_page", ATTRIBUTE_INT },
{ NULL }
};
@@ -131,7 +153,7 @@ static attribute_spec_t _link_attrib_spec[] =
* DamagedRowsBeforeError - Number of damages rows tolerated before an error
* occurs. Default: 0.
*/
-static attribute_spec_t _ccitt_params_spec[] =
+static const attribute_spec_t _ccitt_params_spec[] =
{
{ "Columns", ATTRIBUTE_INT },
{ "Rows", ATTRIBUTE_INT },
@@ -152,7 +174,7 @@ static attribute_spec_t _ccitt_params_spec[] =
* ury - upper right y xoordinate
* all coordinates are in PostScript coordinates.
*/
-static attribute_spec_t _eps_params_spec[] =
+static const attribute_spec_t _eps_params_spec[] =
{
{ "bbox", ATTRIBUTE_FLOAT, 4 },
{ NULL }
@@ -362,7 +384,7 @@ parse_name (const char *attributes, const char *p, const char **end, char **s)
attributes, p);
p2 = p;
- while (_cairo_isalpha (*p2) || _cairo_isdigit (*p2))
+ while (_cairo_isalpha (*p2) || _cairo_isdigit (*p2) || *p2 == '_')
p2++;
len = p2 - p;
@@ -376,9 +398,9 @@ parse_name (const char *attributes, const char *p, const char **end, char **s)
}
static cairo_int_status_t
-parse_attributes (const char *attributes, attribute_spec_t *attrib_def, cairo_list_t *list)
+parse_attributes (const char *attributes, const attribute_spec_t *attrib_def, cairo_list_t *list)
{
- attribute_spec_t *def;
+ const attribute_spec_t *def;
attribute_t *attrib;
char *name = NULL;
cairo_int_status_t status;
@@ -490,13 +512,79 @@ free_attributes_list (cairo_list_t *list)
}
cairo_int_status_t
+_cairo_tag_parse_content_attributes (const char *attributes, cairo_content_attrs_t *content_attrs)
+{
+ cairo_list_t list;
+ cairo_int_status_t status;
+ attribute_t *attr;
+
+ cairo_list_init (&list);
+ status = parse_attributes (attributes, _content_attrib_spec, &list);
+ if (unlikely (status))
+ goto cleanup;
+
+ memset (content_attrs, 0, sizeof (cairo_content_attrs_t));
+ cairo_list_foreach_entry (attr, attribute_t, &list, link)
+ {
+ if (strcmp (attr->name, "tag_name") == 0) {
+ content_attrs->tag_name = strdup (attr->scalar.s);
+ } else if (strcmp (attr->name, "id") == 0) {
+ content_attrs->id = strdup (attr->scalar.s);
+ }
+ }
+
+ if (! content_attrs->tag_name) {
+ status = _cairo_tag_error ("CONTENT attributes: \"%s\" missing tag_name attribute",
+ attributes);
+ } else if (! content_attrs->tag_name) {
+ status = _cairo_tag_error ("CONTENT attributes: \"%s\" missing id attribute",
+ attributes);
+ }
+
+ cleanup:
+ free_attributes_list (&list);
+
+ return status;
+}
+
+cairo_int_status_t
+_cairo_tag_parse_content_ref_attributes (const char *attributes, cairo_content_ref_attrs_t *content_ref_attrs)
+{
+ cairo_list_t list;
+ cairo_int_status_t status;
+ attribute_t *attr;
+
+ cairo_list_init (&list);
+ status = parse_attributes (attributes, _content_ref_attrib_spec, &list);
+ if (unlikely (status))
+ goto cleanup;
+
+ memset (content_ref_attrs, 0, sizeof (cairo_content_ref_attrs_t));
+ cairo_list_foreach_entry (attr, attribute_t, &list, link)
+ {
+ if (strcmp (attr->name, "ref") == 0) {
+ content_ref_attrs->ref = strdup (attr->scalar.s);
+ }
+ }
+
+ if (! content_ref_attrs->ref) {
+ status = _cairo_tag_error ("CONTENT_REF attributes: \"%s\" missing ref attribute",
+ attributes);
+ }
+
+ cleanup:
+ free_attributes_list (&list);
+
+ return status;
+}
+
+cairo_int_status_t
_cairo_tag_parse_link_attributes (const char *attributes, cairo_link_attrs_t *link_attrs)
{
cairo_list_t list;
cairo_int_status_t status;
attribute_t *attr;
attrib_val_t val;
- cairo_bool_t has_rect = FALSE;
cairo_bool_t invalid_combination = FALSE;
cairo_list_init (&list);
@@ -554,7 +642,16 @@ _cairo_tag_parse_link_attributes (const char *attributes, cairo_link_attrs_t *li
if (unlikely (status))
goto cleanup;
}
- has_rect = TRUE;
+ } else if (strcmp (attr->name, "id") == 0) {
+ link_attrs->id = strdup (attr->scalar.s);
+ } else if (strcmp (attr->name, "ref") == 0) {
+ link_attrs->ref = strdup (attr->scalar.s);
+ } else if (strcmp (attr->name, "link_page") == 0) {
+ link_attrs->link_page = attr->scalar.i;
+ if (link_attrs->link_page < 1) {
+ status = _cairo_tag_error ("Link attributes: \"%s\" page must be >= 1", attributes);
+ goto cleanup;
+ }
}
}
@@ -723,3 +820,33 @@ _cairo_tag_parse_eps_params (const char *attributes, cairo_eps_params_t *eps_par
return status;
}
+
+void
+_cairo_tag_free_link_attributes (cairo_link_attrs_t *link_attrs)
+{
+ _cairo_array_fini (&link_attrs->rects);
+ free (link_attrs->dest);
+ free (link_attrs->uri);
+ free (link_attrs->file);
+ free (link_attrs->id);
+ free (link_attrs->ref);
+}
+
+void
+_cairo_tag_free_dest_attributes (cairo_dest_attrs_t *dest_attrs)
+{
+ free (dest_attrs->name);
+}
+
+void
+_cairo_tag_free_content_attributes (cairo_content_attrs_t *content_attrs)
+{
+ free (content_attrs->id);
+ free (content_attrs->tag_name);
+}
+
+void
+_cairo_tag_free_content_ref_attributes (cairo_content_ref_attrs_t *content_ref_attrs)
+{
+ free (content_ref_attrs->ref);
+}
diff --git a/src/cairo-tag-stack-private.h b/src/cairo-tag-stack-private.h
index fbb2c0e25..49145bf1d 100644
--- a/src/cairo-tag-stack-private.h
+++ b/src/cairo-tag-stack-private.h
@@ -46,6 +46,9 @@ typedef enum {
TAG_TYPE_STRUCTURE = 1,
TAG_TYPE_LINK = 2,
TAG_TYPE_DEST = 4,
+ TAG_TYPE_CONTENT = 8,
+ TAG_TYPE_CONTENT_REF = 16,
+ TAG_TYPE_ARTIFACT = 16,
} cairo_tag_type_t;
/* The type of the structure tree. */
@@ -99,6 +102,12 @@ cairo_private cairo_tag_stack_elem_t *
_cairo_tag_stack_top_elem (cairo_tag_stack_t *stack);
cairo_private void
+_cairo_tag_stack_foreach (cairo_tag_stack_t *stack,
+ void (*func)(cairo_tag_stack_elem_t *elem,
+ void *closure),
+ void *closure);
+
+cairo_private void
_cairo_tag_stack_free_elem (cairo_tag_stack_elem_t *elem);
cairo_private cairo_tag_type_t
diff --git a/src/cairo-tag-stack.c b/src/cairo-tag-stack.c
index 7341aa41a..65444c907 100644
--- a/src/cairo-tag-stack.c
+++ b/src/cairo-tag-stack.c
@@ -99,6 +99,9 @@ static const char * _cairo_tag_stack_struct_pdf_list[] =
"Formula",
"Form",
+ /* Section 14.8.2.2.2 - Artifacts */
+ "Artifact",
+
NULL
};
@@ -106,6 +109,8 @@ static const char * _cairo_tag_stack_struct_pdf_list[] =
static const char * _cairo_tag_stack_cairo_tag_list[] =
{
CAIRO_TAG_DEST,
+ CAIRO_TAG_CONTENT,
+ CAIRO_TAG_CONTENT_REF,
NULL
};
@@ -166,6 +171,17 @@ _cairo_tag_stack_push (cairo_tag_stack_t *stack,
return _cairo_tag_error ("Invalid tag: %s", name);
}
+ cairo_tag_stack_elem_t *top = _cairo_tag_stack_top_elem (stack);
+ if (top &&
+ (strcmp (top->name, CAIRO_TAG_CONTENT) == 0 ||
+ strcmp (top->name, CAIRO_TAG_CONTENT_REF) == 0 ||
+ strcmp (top->name, "Artifact") == 0))
+ {
+ return _cairo_tag_error ("%s tag can not contain nested tags",
+ (strcmp (top->name, CAIRO_TAG_CONTENT) == 0) ? "CAIRO_TAG_CONTENT" :
+ ((strcmp (top->name, CAIRO_TAG_CONTENT_REF) == 0) ? "CAIRO_TAG_CONTENT_REF" : top->name));
+ }
+
if (stack->type == TAG_TREE_TYPE_NO_TAGS) {
if (name_in_list (name, _cairo_tag_stack_tagged_pdf_top_level_element_list))
stack->type = TAG_TREE_TYPE_TAGGED;
@@ -257,6 +273,19 @@ _cairo_tag_stack_top_elem (cairo_tag_stack_t *stack)
}
void
+_cairo_tag_stack_foreach (cairo_tag_stack_t *stack,
+ void (*func)(cairo_tag_stack_elem_t *elem,
+ void *closure),
+ void *closure)
+{
+ cairo_tag_stack_elem_t *elem;
+
+ cairo_list_foreach_entry (elem, cairo_tag_stack_elem_t, &stack->list, link) {
+ func (elem, closure);
+ }
+}
+
+void
_cairo_tag_stack_free_elem (cairo_tag_stack_elem_t *elem)
{
free (elem->name);
@@ -274,9 +303,15 @@ _cairo_tag_get_type (const char *name)
if (strcmp(name, "Link") == 0)
return (TAG_TYPE_LINK | TAG_TYPE_STRUCTURE);
- if (strcmp(name, "cairo.dest") == 0)
+ if (strcmp(name, CAIRO_TAG_DEST) == 0)
return TAG_TYPE_DEST;
+ if (strcmp(name, CAIRO_TAG_CONTENT) == 0)
+ return TAG_TYPE_CONTENT;
+
+ if (strcmp(name, CAIRO_TAG_CONTENT_REF) == 0)
+ return TAG_TYPE_CONTENT_REF;
+
return TAG_TYPE_STRUCTURE;
}
diff --git a/src/cairo-traps-compositor.c b/src/cairo-traps-compositor.c
index d1402d2a3..3414fc268 100644
--- a/src/cairo-traps-compositor.c
+++ b/src/cairo-traps-compositor.c
@@ -1240,7 +1240,7 @@ composite_aligned_boxes (const cairo_traps_compositor_t *compositor,
recording_clip = _cairo_clip_from_boxes (boxes);
status = _cairo_recording_surface_replay_with_clip (recording_pattern_get_surface (source),
- m, dst, recording_clip, FALSE);
+ m, dst, recording_clip);
_cairo_clip_destroy (recording_clip);
return status;
diff --git a/src/cairo-types-private.h b/src/cairo-types-private.h
index 736a1bcb3..74a366c23 100644
--- a/src/cairo-types-private.h
+++ b/src/cairo-types-private.h
@@ -437,6 +437,17 @@ typedef struct _cairo_unscaled_font {
cairo_reference_count_t ref_count;
const cairo_unscaled_font_backend_t *backend;
} cairo_unscaled_font_t;
+
+typedef enum _cairo_analysis_source {
+ CAIRO_ANALYSIS_SOURCE_PAINT,
+ CAIRO_ANALYSIS_SOURCE_MASK,
+ CAIRO_ANALYSIS_MASK_MASK,
+ CAIRO_ANALYSIS_SOURCE_FILL,
+ CAIRO_ANALYSIS_SOURCE_STROKE,
+ CAIRO_ANALYSIS_SOURCE_SHOW_GLYPHS,
+ CAIRO_ANALYSIS_SOURCE_NONE /* Used when analysis_source is not applicable. */
+} cairo_analysis_source_t;
+
CAIRO_END_DECLS
#endif /* CAIRO_TYPES_PRIVATE_H */
diff --git a/src/cairo-xcb-surface-render.c b/src/cairo-xcb-surface-render.c
index a4441dc46..ab3d6881f 100644
--- a/src/cairo-xcb-surface-render.c
+++ b/src/cairo-xcb-surface-render.c
@@ -1112,8 +1112,7 @@ record_to_picture (cairo_surface_t *target,
status = _cairo_recording_surface_replay_with_clip (source,
&matrix, tmp,
- NULL,
- FALSE);
+ NULL);
if (unlikely (status)) {
cairo_surface_destroy (tmp);
return (cairo_xcb_picture_t *) _cairo_surface_create_in_error (status);
diff --git a/src/cairo-xlib-source.c b/src/cairo-xlib-source.c
index 63e155e00..4c3b99d9e 100644
--- a/src/cairo-xlib-source.c
+++ b/src/cairo-xlib-source.c
@@ -920,8 +920,7 @@ record_source (cairo_xlib_surface_t *dst,
recording = recording_pattern_get_surface (&pattern->base),
status = _cairo_recording_surface_replay_with_clip (recording,
&matrix, &src->base,
- NULL,
- FALSE);
+ NULL);
cairo_surface_destroy (recording);
if (unlikely (status)) {
cairo_surface_destroy (&src->base);
diff --git a/src/cairo.h b/src/cairo.h
index eef4c442b..a2c955281 100644
--- a/src/cairo.h
+++ b/src/cairo.h
@@ -1039,6 +1039,8 @@ cairo_rectangle_list_destroy (cairo_rectangle_list_t *rectangle_list);
#define CAIRO_TAG_DEST "cairo.dest"
#define CAIRO_TAG_LINK "Link"
+#define CAIRO_TAG_CONTENT "cairo.content"
+#define CAIRO_TAG_CONTENT_REF "cairo.content_ref"
cairo_public void
cairo_tag_begin (cairo_t *cr, const char *tag_name, const char *attributes);
diff --git a/src/cairoint.h b/src/cairoint.h
index e5c281842..ddbbe7baa 100644
--- a/src/cairoint.h
+++ b/src/cairoint.h
@@ -1,3 +1,4 @@
+/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
/* cairo - a vector graphics library with display and print output
*
* Copyright © 2002 University of Southern California
diff --git a/test/cairo-test.c b/test/cairo-test.c
index 5a2cf1a74..49dfadb15 100644
--- a/test/cairo-test.c
+++ b/test/cairo-test.c
@@ -185,7 +185,7 @@ _cairo_test_init (cairo_test_context_t *ctx,
ctx->own_targets = FALSE;
ctx->srcdir = parent->srcdir;
- ctx->refdir = parent->refdir;
+ ctx->refdir = xstrdup (parent->refdir);
} else {
int tmp_num_targets;
cairo_bool_t tmp_limited_targets;
@@ -204,7 +204,10 @@ _cairo_test_init (cairo_test_context_t *ctx,
ctx->srcdir = "srcdir";
#endif
}
- ctx->refdir = getenv ("CAIRO_REF_DIR");
+
+ ctx->refdir = xstrdup (getenv ("CAIRO_REF_DIR"));
+ if (ctx->refdir == NULL)
+ xasprintf (&ctx->refdir, "%s/reference", ctx->srcdir);
}
#ifdef HAVE_UNISTD_H
@@ -246,6 +249,7 @@ cairo_test_fini (cairo_test_context_t *ctx)
fclose (ctx->log_file);
ctx->log_file = NULL;
+ free (ctx->refdir);
free (ctx->ref_name);
cairo_surface_destroy (ctx->ref_image);
cairo_surface_destroy (ctx->ref_image_flattened);
diff --git a/test/cairo-test.h b/test/cairo-test.h
index adff2583c..b70654654 100644
--- a/test/cairo-test.h
+++ b/test/cairo-test.h
@@ -239,7 +239,7 @@ struct _cairo_test_context {
FILE *log_file;
const char *output;
const char *srcdir; /* directory containing sources and input data */
- const char *refdir; /* directory containing reference images */
+ char *refdir; /* directory containing reference images */
char *ref_name; /* cache of the current reference image */
cairo_surface_t *ref_image;
diff --git a/test/check-pdf-structure.sh b/test/check-pdf-structure.sh
new file mode 100755
index 000000000..31ae5ac2c
--- /dev/null
+++ b/test/check-pdf-structure.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+if test $# -ne 4 ; then
+ echo "Usage: $0 <pdf-file> <pdfinfo-output> <pdfinfo-ref> <diff-output>"
+ exit 3
+fi
+
+# Check for pdfinfo version >= 21.10.00
+if pdfinfo -v 2>& 1 | awk '/pdfinfo version/ { split($3,v,/[.]/); if (v[1] > 21 || (v[1] == 21 && v[2] >= 10) ) { print "yes" } } ' | grep -q 'yes'; then
+ pdfinfo -struct-text "$1" > "$2"
+ if test -f "$3" ; then
+ diff -u "$3" "$2" > "$4"
+ # diff exit codes: 0 = match, 1 = different, 2 = error
+ exit $?
+ else
+ exit 3 # missing ref file
+ fi
+fi
+
+ # pdfinfo missing or wrong version
+exit 4
diff --git a/test/meson.build b/test/meson.build
index 548e25b4d..1d76d5daa 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -452,6 +452,10 @@ test_pdf_sources = [
'pdf-tagged-text.c',
]
+test_pdf_structure_sources = [
+ 'pdf-structure.c',
+]
+
test_ps_sources = [
'ps-eps.c',
'ps-features.c',
@@ -555,6 +559,9 @@ endif
if feature_conf.get('CAIRO_HAS_PDF_SURFACE', 0) == 1
test_sources += test_pdf_sources
+ if host_machine.system() != 'windows'
+ test_sources += test_pdf_structure_sources
+ endif
has_multipage_surfaces = true
add_fallback_resolution = true
build_any2ppm = true
diff --git a/test/pdf-structure.c b/test/pdf-structure.c
new file mode 100644
index 000000000..ee4efe511
--- /dev/null
+++ b/test/pdf-structure.c
@@ -0,0 +1,568 @@
+/*
+ * Copyright © 2023 Adrian Johnson
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Author: Adrian Johnson <ajohnson@redneon.com>
+ */
+
+#include "cairo-test.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h> /* __unix__ */
+#endif
+
+#include <cairo.h>
+#include <cairo-pdf.h>
+
+/* Test PDF logical structure
+ */
+
+#define BASENAME "pdf-structure"
+
+#define PAGE_WIDTH 595
+#define PAGE_HEIGHT 842
+
+#define PDF_VERSION CAIRO_PDF_VERSION_1_4
+
+struct pdf_structure_test {
+ const char *name;
+ void (*func)(cairo_t *cr);
+};
+
+static void
+text(cairo_t *cr, const char *text)
+{
+ double x, y;
+
+ cairo_show_text (cr, text);
+ cairo_get_current_point (cr, &x, &y);
+ cairo_move_to (cr, 20, y + 15);
+}
+
+static void
+test_simple (cairo_t *cr)
+{
+ cairo_tag_begin (cr, "Document", NULL);
+
+ cairo_tag_begin (cr, "H", "");
+ text (cr, "Heading");
+ cairo_tag_end (cr, "H");
+
+ cairo_tag_begin (cr, "Sect", NULL);
+
+ cairo_tag_begin (cr, "P", "");
+ text (cr, "Para1");
+ text (cr, "Para2");
+ cairo_tag_end (cr, "P");
+
+ cairo_tag_begin (cr, "P", "");
+ text (cr, "Para3");
+
+ cairo_tag_begin (cr, "Note", "");
+ text (cr, "Note");
+ cairo_tag_end (cr, "Note");
+
+ text (cr, "Para4");
+ cairo_tag_end (cr, "P");
+
+ cairo_tag_end (cr, "Sect");
+
+ cairo_tag_end (cr, "Document");
+}
+
+static void
+test_simple_ref (cairo_t *cr)
+{
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='H' id='heading'");
+ text (cr, "Heading");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='P' id='para1'");
+ text (cr, "Para1");
+ text (cr, "Para2");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='P' id='para2'");
+ text (cr, "Para3");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='Note' id='note'");
+ text (cr, "Note");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='P' id='para3'");
+ text (cr, "Para4");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+ cairo_tag_begin (cr, "Document", NULL);
+
+ cairo_tag_begin (cr, "H", "");
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='heading'");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+ cairo_tag_end (cr, "H");
+
+ cairo_tag_begin (cr, "Sect", NULL);
+
+ cairo_tag_begin (cr, "P", "");
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='para1'");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+ cairo_tag_end (cr, "P");
+
+ cairo_tag_begin (cr, "P", "");
+
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='para2'");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+
+ cairo_tag_begin (cr, "Note", "");
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='note'");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+ cairo_tag_end (cr, "Note");
+
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='para3'");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+
+ cairo_tag_end (cr, "P");
+
+ cairo_tag_end (cr, "Sect");
+
+ cairo_tag_end (cr, "Document");
+}
+
+static void
+test_group (cairo_t *cr)
+{
+ cairo_tag_begin (cr, "Document", NULL);
+
+ cairo_tag_begin (cr, "H", "");
+ text (cr, "Heading");
+ cairo_tag_end (cr, "H");
+
+ cairo_tag_begin (cr, "Sect", NULL);
+
+ cairo_push_group (cr);
+
+ cairo_tag_begin (cr, "P", "");
+ text (cr, "Para1");
+ text (cr, "Para2");
+ cairo_tag_end (cr, "P");
+
+ cairo_pop_group_to_source (cr);
+ cairo_paint (cr);
+
+ cairo_tag_end (cr, "Sect");
+
+ cairo_tag_end (cr, "Document");
+}
+
+static void
+test_group_ref (cairo_t *cr)
+{
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='H' id='heading'");
+ text (cr, "Heading");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+ cairo_push_group (cr);
+
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='P' id='para'");
+ text (cr, "Para1");
+ text (cr, "Para2");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+ cairo_pop_group_to_source (cr);
+ cairo_paint (cr);
+
+ cairo_tag_begin (cr, "Document", NULL);
+
+ cairo_tag_begin (cr, "H", "");
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='heading'");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+ cairo_tag_end (cr, "H");
+
+ cairo_tag_begin (cr, "Sect", NULL);
+
+ cairo_tag_begin (cr, "P", "");
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='para'");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+ cairo_tag_end (cr, "P");
+
+ cairo_tag_end (cr, "Sect");
+
+ cairo_tag_end (cr, "Document");
+
+}
+
+static void
+test_repeated_group (cairo_t *cr)
+{
+ cairo_pattern_t *pat;
+
+ cairo_tag_begin (cr, "Document", NULL);
+
+ cairo_tag_begin (cr, "H", "");
+ text (cr, "Heading");
+ cairo_tag_end (cr, "H");
+
+ cairo_tag_begin (cr, "Sect", NULL);
+
+ cairo_push_group (cr);
+
+ cairo_tag_begin (cr, "P", "");
+ text (cr, "Para1");
+ text (cr, "Para2");
+ cairo_tag_end (cr, "P");
+
+ pat = cairo_pop_group (cr);
+
+ cairo_set_source (cr, pat);
+ cairo_paint (cr);
+
+ cairo_translate (cr, 0, 100);
+ cairo_set_source (cr, pat);
+ cairo_rectangle (cr, 0, 0, 100, 100);
+ cairo_fill (cr);
+
+ cairo_translate (cr, 0, 100);
+ cairo_set_source_rgb (cr, 1, 0, 0);
+ cairo_mask (cr, pat);
+
+ cairo_translate (cr, 0, 100);
+ cairo_set_source_rgb (cr, 0, 1, 0);
+ cairo_move_to (cr, 20, 0);
+ cairo_line_to (cr, 100, 0);
+ cairo_stroke (cr);
+
+ cairo_translate (cr, 0, 100);
+ cairo_set_source_rgb (cr, 0, 0, 1);
+ cairo_move_to (cr, 20, 0);
+ cairo_show_text (cr, "Text");
+
+ cairo_tag_end (cr, "Sect");
+
+ cairo_tag_end (cr, "Document");
+}
+
+static void
+test_multipage_simple (cairo_t *cr)
+{
+ cairo_tag_begin (cr, "Document", NULL);
+
+ cairo_tag_begin (cr, "H", "");
+
+ cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='para1-dest'");
+ text (cr, "Heading1");
+ cairo_tag_end (cr, CAIRO_TAG_LINK);
+
+ cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='para2-dest'");
+ text (cr, "Heading2");
+ cairo_tag_end (cr, CAIRO_TAG_LINK);
+
+ cairo_tag_end (cr, "H");
+
+ cairo_tag_begin (cr, "Sect", NULL);
+
+ cairo_show_page (cr);
+
+ cairo_tag_begin (cr, "P", "");
+
+ cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='para1-dest' internal");
+ text (cr, "Para1");
+ cairo_tag_end (cr, CAIRO_TAG_DEST);
+
+ cairo_show_page (cr);
+
+ cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='para2-dest' internal");
+ text (cr, "Para2");
+ cairo_tag_end (cr, CAIRO_TAG_DEST);
+
+ cairo_tag_end (cr, "P");
+
+ cairo_tag_end (cr, "Sect");
+
+ cairo_tag_end (cr, "Document");
+}
+
+static void
+test_multipage_simple_ref (cairo_t *cr)
+{
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='H' id='heading1'");
+ text (cr, "Heading1");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='H' id='heading2'");
+ text (cr, "Heading2");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+
+ cairo_show_page (cr);
+
+ cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='para1-dest' internal");
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='P' id='para1'");
+ text (cr, "Para1");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+ cairo_tag_end (cr, CAIRO_TAG_DEST);
+
+ cairo_show_page (cr);
+
+ cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='para2-dest' internal");
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT, "tag_name='P' id='para2'");
+ text (cr, "Para2");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT);
+ cairo_tag_end (cr, CAIRO_TAG_DEST);
+
+ cairo_tag_begin (cr, "Document", NULL);
+
+ cairo_tag_begin (cr, "H", "");
+
+ cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='para1-dest' link_page=1");
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='heading1'");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+ cairo_tag_end (cr, CAIRO_TAG_LINK);
+
+ cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='para2-dest' link_page=1");
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='heading2'");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+ cairo_tag_end (cr, CAIRO_TAG_LINK);
+
+ cairo_tag_end (cr, "H");
+
+ cairo_tag_begin (cr, "Sect", NULL);
+
+ cairo_tag_begin (cr, "P", "");
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='para1'");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+ cairo_tag_begin (cr, CAIRO_TAG_CONTENT_REF, "ref='para2'");
+ cairo_tag_end (cr, CAIRO_TAG_CONTENT_REF);
+ cairo_tag_end (cr, "P");
+
+ cairo_tag_end (cr, "Sect");
+
+ cairo_tag_end (cr, "Document");
+}
+
+static void
+test_multipage_group (cairo_t *cr)
+{
+ cairo_tag_begin (cr, "Document", NULL);
+
+ cairo_tag_begin (cr, "H", "");
+ text (cr, "Heading");
+ cairo_tag_end (cr, "H");
+
+ cairo_tag_begin (cr, "Sect", NULL);
+
+ cairo_push_group (cr);
+
+ cairo_tag_begin (cr, "P", "");
+ text (cr, "Para1");
+ text (cr, "Para2");
+ cairo_tag_end (cr, "P");
+
+ cairo_pop_group_to_source (cr);
+ cairo_paint (cr);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+
+ cairo_show_page (cr);
+
+ cairo_tag_begin (cr, "P", "");
+ text (cr, "Para3");
+ cairo_tag_end (cr, "P");
+
+ cairo_tag_end (cr, "Sect");
+
+ cairo_tag_end (cr, "Document");
+}
+
+/* Same as test_multipage_group but but repeat the group on the second page. */
+static void
+test_multipage_group2 (cairo_t *cr)
+{
+ cairo_tag_begin (cr, "Document", NULL);
+
+ cairo_tag_begin (cr, "H", "");
+ text (cr, "Heading");
+ cairo_tag_end (cr, "H");
+
+ cairo_tag_begin (cr, "Sect", NULL);
+
+ cairo_push_group (cr);
+
+ cairo_tag_begin (cr, "P", "");
+ text (cr, "Para1");
+ text (cr, "Para2");
+ cairo_tag_end (cr, "P");
+
+ cairo_pop_group_to_source (cr);
+ cairo_paint (cr);
+
+ cairo_show_page (cr);
+
+ cairo_paint (cr);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+
+ cairo_tag_begin (cr, "P", "");
+ text (cr, "Para3");
+ cairo_tag_end (cr, "P");
+
+ cairo_tag_end (cr, "Sect");
+
+ cairo_tag_end (cr, "Document");
+}
+
+static const struct pdf_structure_test pdf_structure_tests[] = {
+ { "simple", test_simple },
+ { "simple-ref", test_simple_ref },
+ { "group", test_group },
+ { "group-ref", test_group_ref },
+ { "repeated-group", test_repeated_group },
+ { "multipage-simple", test_multipage_simple },
+ { "multipage-simple-ref", test_multipage_simple_ref },
+ { "multipage-group", test_multipage_group },
+ { "multipage-group2", test_multipage_group2 },
+};
+
+static cairo_test_status_t
+create_pdf (cairo_test_context_t *ctx, const struct pdf_structure_test *test, const char *output)
+{
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ cairo_status_t status, status2;
+
+ surface = cairo_pdf_surface_create (output, PAGE_WIDTH, PAGE_HEIGHT);
+
+ cairo_pdf_surface_restrict_to_version (surface, PDF_VERSION);
+
+ cr = cairo_create (surface);
+
+ cairo_select_font_face (cr, CAIRO_TEST_FONT_FAMILY " Serif",
+ CAIRO_FONT_SLANT_NORMAL,
+ CAIRO_FONT_WEIGHT_NORMAL);
+ cairo_set_font_size (cr, 10);
+ cairo_move_to (cr, 20, 20);
+
+ test->func(cr);
+
+ status = cairo_status (cr);
+ cairo_destroy (cr);
+ cairo_surface_finish (surface);
+ status2 = cairo_surface_status (surface);
+ if (status == CAIRO_STATUS_SUCCESS)
+ status = status2;
+
+ cairo_surface_destroy (surface);
+ if (status) {
+ cairo_test_log (ctx, "Failed to create pdf surface for file %s: %s\n",
+ output, cairo_status_to_string (status));
+ return CAIRO_TEST_FAILURE;
+ }
+
+ return CAIRO_TEST_SUCCESS;
+}
+
+static cairo_test_status_t
+check_pdf (cairo_test_context_t *ctx, const struct pdf_structure_test *test, const char *output)
+{
+ char *command;
+ int ret;
+ cairo_test_status_t result = CAIRO_TEST_FAILURE;
+
+ /* check-pdf-structure.sh <pdf-file> <pdfinfo-output> <pdfinfo-ref> <diff-output> */
+ xasprintf (&command,
+ "%s/check-pdf-structure.sh %s %s/%s-%s.out.txt %s/%s-%s.ref.txt %s/%s-%s.diff.txt ",
+ ctx->srcdir,
+ output,
+ ctx->output, BASENAME, test->name,
+ ctx->refdir, BASENAME, test->name,
+ ctx->output, BASENAME, test->name);
+
+ ret = system (command);
+ cairo_test_log (ctx, "%s exit code %d\n", command,
+ WIFEXITED (ret) ? WEXITSTATUS (ret) : -1);
+
+ if (WIFEXITED (ret)) {
+ if (WEXITSTATUS (ret) == 0)
+ result = CAIRO_TEST_SUCCESS;
+ else if (WEXITSTATUS (ret) == 4)
+ result = CAIRO_TEST_UNTESTED; /* pdfinfo not found, wrong version, missing ref */
+ }
+
+ free (command);
+ return result;
+}
+
+static void
+merge_test_status (cairo_test_status_t *current, cairo_test_status_t new)
+{
+ if (new == CAIRO_TEST_FAILURE || *current == CAIRO_TEST_FAILURE)
+ *current = CAIRO_TEST_FAILURE;
+ else if (new == CAIRO_TEST_UNTESTED)
+ *current = CAIRO_TEST_UNTESTED;
+ else
+ *current = new;
+}
+
+static cairo_test_status_t
+preamble (cairo_test_context_t *ctx)
+{
+ int i;
+ char *filename;
+ cairo_test_status_t result, all_results;
+ cairo_bool_t can_check = FALSE;
+
+/* Need a POSIX shell to run the check. */
+#ifdef __unix__
+ can_check = TRUE;
+#endif
+
+ all_results = CAIRO_TEST_SUCCESS;
+ if (! cairo_test_is_target_enabled (ctx, "pdf"))
+ return CAIRO_TEST_UNTESTED;
+
+ for (i = 0; i < ARRAY_LENGTH(pdf_structure_tests); i++) {
+ xasprintf (&filename, "%s/%s-%s.out.pdf",
+ ctx->output,
+ BASENAME,
+ pdf_structure_tests[i].name);
+
+ result = create_pdf (ctx, &pdf_structure_tests[i], filename);
+ merge_test_status (&all_results, result);
+
+ if (can_check && result == CAIRO_TEST_SUCCESS) {
+ result = check_pdf (ctx, &pdf_structure_tests[i], filename);
+ merge_test_status (&all_results, result);
+ } else {
+ merge_test_status (&all_results, CAIRO_TEST_UNTESTED);
+ }
+ }
+
+ free (filename);
+ return all_results;
+}
+
+CAIRO_TEST (pdf_structure,
+ "Check PDF Structure",
+ "pdf", /* keywords */
+ NULL, /* requirements */
+ 0, 0,
+ preamble, NULL)
diff --git a/test/pdf-tagged-text.c b/test/pdf-tagged-text.c
index 1a2f62dac..3883d418e 100644
--- a/test/pdf-tagged-text.c
+++ b/test/pdf-tagged-text.c
@@ -149,7 +149,7 @@ layout_paragraph (cairo_t *cr)
*end = ' ';
if (text_extents.width + 2*MARGIN > PAGE_WIDTH) {
int len = prev_end - begin;
- char *s = malloc (len);
+ char *s = xmalloc (len);
memcpy (s, begin, len);
s[len-1] = 0;
paragraph_text[paragraph_num_lines++] = s;
@@ -204,9 +204,9 @@ draw_page_num (cairo_surface_t *surface, cairo_t *cr, const char *prefix, int nu
static void
draw_contents (cairo_surface_t *surface, cairo_t *cr, const struct section *section)
{
- char buf[100];
+ char *attrib;
- sprintf(buf, "dest='%s'", section->heading);
+ xasprintf (&attrib, "dest='%s'", section->heading);
cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
switch (section->level) {
case 0:
@@ -230,25 +230,26 @@ draw_contents (cairo_surface_t *surface, cairo_t *cr, const struct section *sect
cairo_set_source_rgb (cr, 0, 0, 1);
cairo_tag_begin (cr, "TOCI", NULL);
cairo_tag_begin (cr, "Reference", NULL);
- cairo_tag_begin (cr, CAIRO_TAG_LINK, buf);
+ cairo_tag_begin (cr, CAIRO_TAG_LINK, attrib);
cairo_show_text (cr, section->heading);
cairo_tag_end (cr, CAIRO_TAG_LINK);
cairo_tag_end (cr, "Reference");
cairo_tag_end (cr, "TOCI");
cairo_restore (cr);
y_pos += HEADING_HEIGHT;
+ free (attrib);
}
static void
draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *section)
{
int flags, i;
- char buf[100];
- char buf2[100];
+ char *name_attrib;
+ char *dest_attrib;
cairo_tag_begin (cr, "Sect", NULL);
- sprintf(buf, "name='%s'", section->heading);
- sprintf(buf2, "dest='%s'", section->heading);
+ xasprintf(&name_attrib, "name='%s'", section->heading);
+ xasprintf(&dest_attrib, "dest='%s'", section->heading);
cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
if (section->level == 0) {
cairo_show_page (cr);
@@ -256,7 +257,7 @@ draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *secti
cairo_set_font_size(cr, HEADING1_SIZE);
cairo_move_to (cr, MARGIN, MARGIN);
cairo_tag_begin (cr, "H1", NULL);
- cairo_tag_begin (cr, CAIRO_TAG_DEST, buf);
+ cairo_tag_begin (cr, CAIRO_TAG_DEST, name_attrib);
cairo_show_text (cr, section->heading);
cairo_tag_end (cr, CAIRO_TAG_DEST);
cairo_tag_end (cr, "H1");
@@ -265,7 +266,7 @@ draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *secti
outline_parents[0] = cairo_pdf_surface_add_outline (surface,
CAIRO_PDF_OUTLINE_ROOT,
section->heading,
- buf2,
+ dest_attrib,
flags);
} else {
if (section->level == 1) {
@@ -286,7 +287,7 @@ draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *secti
cairo_tag_begin (cr, "H2", NULL);
else
cairo_tag_begin (cr, "H3", NULL);
- cairo_tag_begin (cr, CAIRO_TAG_DEST, buf);
+ cairo_tag_begin (cr, CAIRO_TAG_DEST, name_attrib);
cairo_show_text (cr, section->heading);
cairo_tag_end (cr, CAIRO_TAG_DEST);
if (section->level == 1)
@@ -297,7 +298,7 @@ draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *secti
outline_parents[section->level] = cairo_pdf_surface_add_outline (surface,
outline_parents[section->level - 1],
section->heading,
- buf2,
+ dest_attrib,
flags);
}
@@ -310,13 +311,15 @@ draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *secti
draw_paragraph (cr);
}
cairo_tag_end (cr, "Sect");
+ free (name_attrib);
+ free (dest_attrib);
}
static void
draw_cover (cairo_surface_t *surface, cairo_t *cr)
{
cairo_text_extents_t text_extents;
- char buf[200];
+ char *attrib;
cairo_rectangle_t url_box;
const char *cairo_url = "https://www.cairographics.org/";
const double url_box_margin = 20.0;
@@ -344,10 +347,11 @@ draw_cover (cairo_surface_t *surface, cairo_t *cr)
url_box.height = -text_extents.height + 2*url_box_margin;
cairo_rectangle(cr, url_box.x, url_box.y, url_box.width, url_box.height);
cairo_stroke(cr);
- snprintf(buf, sizeof(buf), "rect=[%f %f %f %f] uri=\'%s\'",
+ xasprintf(&attrib, "rect=[%f %f %f %f] uri=\'%s\'",
url_box.x, url_box.y, url_box.width, url_box.height, cairo_url);
- cairo_tag_begin (cr, CAIRO_TAG_LINK, buf);
+ cairo_tag_begin (cr, CAIRO_TAG_LINK, attrib);
cairo_tag_end (cr, CAIRO_TAG_LINK);
+ free (attrib);
/* Create link to not yet emmited page number */
cairo_tag_begin (cr, CAIRO_TAG_LINK, "page=5");
@@ -419,6 +423,32 @@ create_document (cairo_surface_t *surface, cairo_t *cr)
cairo_tag_begin (cr, CAIRO_TAG_LINK, "uri='http://127.0.0.1/' rect=[10.0 -10.0 100.0 100.0]");
cairo_tag_end (cr, CAIRO_TAG_LINK);
+
+ /* Distilled from Mozilla bug https://bugzilla.mozilla.org/show_bug.cgi?id=1725743:
+ * attempting to emit a Destination tag within a pushed group will lead to an
+ * assertion in _cairo_pdf_interchange_end_structure_tag when processing a
+ * following LINK tag that is outside the pushed group.
+ */
+
+ /* PushLayer */
+ cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA);
+
+ /* Destination */
+ cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='a' x=42 y=42");
+ cairo_tag_end (cr, CAIRO_TAG_DEST);
+
+ /* PopLayer */
+ cairo_pop_group_to_source (cr);
+ cairo_paint_with_alpha (cr, 1);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+
+ /* Link */
+ cairo_tag_begin (cr, CAIRO_TAG_LINK, "rect=[100 200 300 400] uri='http://127.0.0.1/'");
+ cairo_tag_end (cr, CAIRO_TAG_LINK);
+
+ /* End of extra Mozilla testcase. */
+
+
cairo_show_page (cr);
page_num = 0;
@@ -449,6 +479,8 @@ create_document (cairo_surface_t *surface, cairo_t *cr)
cairo_show_page (cr);
+ cairo_set_source_rgb (cr, 0, 0, 1);
+
cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='cover'");
cairo_move_to (cr, PAGE_WIDTH/3, 2*PAGE_HEIGHT/5);
cairo_show_text (cr, "link to cover");
diff --git a/test/reference/pdf-structure-group-ref.ref.txt b/test/reference/pdf-structure-group-ref.ref.txt
new file mode 100644
index 000000000..8b1d97fef
--- /dev/null
+++ b/test/reference/pdf-structure-group-ref.ref.txt
@@ -0,0 +1,6 @@
+Document
+ H (block)
+ "Heading"
+ Sect
+ P (block)
+ "Para1Para2"
diff --git a/test/reference/pdf-structure-group.ref.txt b/test/reference/pdf-structure-group.ref.txt
new file mode 100644
index 000000000..8b1d97fef
--- /dev/null
+++ b/test/reference/pdf-structure-group.ref.txt
@@ -0,0 +1,6 @@
+Document
+ H (block)
+ "Heading"
+ Sect
+ P (block)
+ "Para1Para2"
diff --git a/test/reference/pdf-structure-multipage-group.ref.txt b/test/reference/pdf-structure-multipage-group.ref.txt
new file mode 100644
index 000000000..61b470ccc
--- /dev/null
+++ b/test/reference/pdf-structure-multipage-group.ref.txt
@@ -0,0 +1,8 @@
+Document
+ H (block)
+ "Heading"
+ Sect
+ P (block)
+ "Para1Para2"
+ P (block)
+ "Para3"
diff --git a/test/reference/pdf-structure-multipage-group2.ref.txt b/test/reference/pdf-structure-multipage-group2.ref.txt
new file mode 100644
index 000000000..0fbed52f1
--- /dev/null
+++ b/test/reference/pdf-structure-multipage-group2.ref.txt
@@ -0,0 +1,10 @@
+Document
+ H (block)
+ "Heading"
+ Sect
+ P (block)
+ "Para1Para2"
+ P (block)
+ "Para1Para2"
+ P (block)
+ "Para3"
diff --git a/test/reference/pdf-structure-multipage-simple-ref.ref.txt b/test/reference/pdf-structure-multipage-simple-ref.ref.txt
new file mode 100644
index 000000000..5a22505c9
--- /dev/null
+++ b/test/reference/pdf-structure-multipage-simple-ref.ref.txt
@@ -0,0 +1,12 @@
+Document
+ H (block)
+ Link (inline)
+ Object 21 0
+ "Heading1"
+ Link (inline)
+ Object 24 0
+ "Heading2"
+ Sect
+ P (block)
+ "Para1"
+ "Para2"
diff --git a/test/reference/pdf-structure-multipage-simple.ref.txt b/test/reference/pdf-structure-multipage-simple.ref.txt
new file mode 100644
index 000000000..4d535fbf8
--- /dev/null
+++ b/test/reference/pdf-structure-multipage-simple.ref.txt
@@ -0,0 +1,12 @@
+Document
+ H (block)
+ Link (inline)
+ Object 6 0
+ "Heading1"
+ Link (inline)
+ Object 8 0
+ "Heading2"
+ Sect
+ P (block)
+ "Para1"
+ "Para2"
diff --git a/test/reference/pdf-structure-repeated-group.ref.txt b/test/reference/pdf-structure-repeated-group.ref.txt
new file mode 100644
index 000000000..5292f0127
--- /dev/null
+++ b/test/reference/pdf-structure-repeated-group.ref.txt
@@ -0,0 +1,9 @@
+Document
+ H (block)
+ "Heading"
+ Sect
+ P (block)
+ "Para1Para2"
+ P (block)
+ "Para1Para2"
+ "Para1Para2Text"
diff --git a/test/reference/pdf-structure-simple-ref.ref.txt b/test/reference/pdf-structure-simple-ref.ref.txt
new file mode 100644
index 000000000..0b0541c0a
--- /dev/null
+++ b/test/reference/pdf-structure-simple-ref.ref.txt
@@ -0,0 +1,11 @@
+Document
+ H (block)
+ "Heading"
+ Sect
+ P (block)
+ "Para1Para2"
+ P (block)
+ "Para3"
+ Note (inline)
+ "Note"
+ "Para4"
diff --git a/test/reference/pdf-structure-simple.ref.txt b/test/reference/pdf-structure-simple.ref.txt
new file mode 100644
index 000000000..0b0541c0a
--- /dev/null
+++ b/test/reference/pdf-structure-simple.ref.txt
@@ -0,0 +1,11 @@
+Document
+ H (block)
+ "Heading"
+ Sect
+ P (block)
+ "Para1Para2"
+ P (block)
+ "Para3"
+ Note (inline)
+ "Note"
+ "Para4"