diff --git a/src/cairo-pdf-interchange.c b/src/cairo-pdf-interchange.c index 9d7aa42fd..6bda9e8b8 100644 --- a/src/cairo-pdf-interchange.c +++ b/src/cairo-pdf-interchange.c @@ -768,13 +768,11 @@ static cairo_int_status_t cairo_pdf_interchange_write_dest (cairo_pdf_surface_t *surface, cairo_link_attrs_t *link_attrs) { - cairo_int_status_t status; + cairo_int_status_t status = CAIRO_STATUS_SUCCESS; cairo_pdf_interchange_t *ic = &surface->interchange; - cairo_pdf_forward_link_t *link; - cairo_pdf_resource_t link_res; /* If the dest is known, emit an explicit dest */ - if (link_attrs->dest) { + if (link_attrs->link_type == TAG_LINK_DEST_AND_URI || link_attrs->link_type == TAG_LINK_DEST) { cairo_pdf_named_dest_t key; cairo_pdf_named_dest_t *named_dest; @@ -797,53 +795,56 @@ cairo_pdf_interchange_write_dest (cairo_pdf_surface_t *surface, if (named_dest->attrs.y_valid) y = named_dest->attrs.y; - _cairo_output_stream_printf (surface->object_stream.stream, " /Dest "); - status = cairo_pdf_interchange_write_explicit_dest (surface, - named_dest->page, - TRUE, - x, y); + if (named_dest->attrs.internal) { + _cairo_output_stream_printf (surface->object_stream.stream, " /Dest "); + status = cairo_pdf_interchange_write_explicit_dest (surface, + named_dest->page, + TRUE, + x, y); + } else { + char *name = NULL; + + status = _cairo_utf8_to_pdf_string (named_dest->attrs.name, &name); + if (unlikely (status)) + return status; + + _cairo_output_stream_printf (surface->object_stream.stream, " /Dest %s\n", + name); + free (name); + } return status; } - } - - /* If the page is known, emit an explicit dest */ - if (!link_attrs->dest) { - if (link_attrs->page < 1) - return _cairo_tag_error ("Link attribute: \"page=%d\" page must be >= 1", link_attrs->page); - - if (link_attrs->page <= (int)_cairo_array_num_elements (&surface->pages)) { - _cairo_output_stream_printf (surface->object_stream.stream, " /Dest "); - return cairo_pdf_interchange_write_explicit_dest (surface, - link_attrs->page, - link_attrs->has_pos, - link_attrs->pos.x, - link_attrs->pos.y); + /* name does not exist */ + if (link_attrs->link_type == TAG_LINK_DEST_AND_URI) { + /* Don't emit anything. The caller will fallback to emitting a URI destination. */ + return CAIRO_INT_STATUS_NOTHING_TO_DO; } + + /* Mising destination. Emit a "do nothing" dest that points to the same page and position. */ + _cairo_tag_warning ("Link to dest=\"%s\" not found", link_attrs->dest); + _cairo_output_stream_printf (surface->object_stream.stream, " /Dest "); + status = cairo_pdf_interchange_write_explicit_dest (surface, + link_attrs->link_page, + FALSE, + 0, 0); + return status; } - /* Link refers to a future or unknown page. Use an indirect object - * and write the link at the end of the document */ + /* link_attrs->link_type == TAG_LINK_PAGE */ - link = _cairo_malloc (sizeof (cairo_pdf_forward_link_t)); - if (unlikely (link == NULL)) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); + if (link_attrs->page < 1) + return _cairo_tag_error ("Link attribute: \"page=%d\" page must be >= 1", link_attrs->page); - link_res = _cairo_pdf_surface_new_object (surface); - if (link_res.id == 0) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); + if (link_attrs->page > (int)_cairo_array_num_elements (&surface->pages)) + return _cairo_tag_error ("Link attribute: \"page=%d\" page exceeds page count (%d)", + link_attrs->page, _cairo_array_num_elements (&surface->pages)); - _cairo_output_stream_printf (surface->object_stream.stream, - " /Dest %d 0 R\n", - link_res.id); - - link->res = link_res; - link->dest = link_attrs->dest ? strdup (link_attrs->dest) : NULL; - link->page = link_attrs->page; - link->has_pos = link_attrs->has_pos; - link->pos = link_attrs->pos; - status = _cairo_array_append (&surface->forward_links, link); - - return status; + _cairo_output_stream_printf (surface->object_stream.stream, " /Dest "); + return cairo_pdf_interchange_write_explicit_dest (surface, + link_attrs->page, + link_attrs->has_pos, + link_attrs->pos.x, + link_attrs->pos.y); } static cairo_int_status_t @@ -918,12 +919,20 @@ cairo_pdf_interchange_write_link_action (cairo_pdf_surface_t *surface, cairo_int_status_t status; char *dest = NULL; - if (link_attrs->link_type == TAG_LINK_DEST) { + if (link_attrs->link_type == TAG_LINK_DEST_AND_URI || + link_attrs->link_type == TAG_LINK_DEST || + link_attrs->link_type == TAG_LINK_PAGE) + { status = cairo_pdf_interchange_write_dest (surface, link_attrs); - if (unlikely (status)) + if (status != CAIRO_INT_STATUS_NOTHING_TO_DO) return status; - } else if (link_attrs->link_type == TAG_LINK_URI) { + /* CAIRO_INT_STATUS_NOTHING_TO_DO means that the link type is TAG_LINK_DEST_AND_URI + * and the DEST is missing. Fall through to writing a URI link below. + */ + } + + if (link_attrs->link_type == TAG_LINK_URI || link_attrs->link_type == TAG_LINK_DEST_AND_URI) { status = _cairo_utf8_to_pdf_string (link_attrs->uri, &dest); if (unlikely (status)) return status; @@ -1460,67 +1469,6 @@ strcmp_null (const char *s1, const char *s2) return FALSE; } -static cairo_int_status_t -cairo_pdf_interchange_write_forward_links (cairo_pdf_surface_t *surface) -{ - int num_elems, i; - cairo_pdf_forward_link_t *link; - cairo_int_status_t status; - cairo_pdf_named_dest_t key; - cairo_pdf_named_dest_t *named_dest; - cairo_pdf_interchange_t *ic = &surface->interchange; - - num_elems = _cairo_array_num_elements (&surface->forward_links); - for (i = 0; i < num_elems; i++) { - link = _cairo_array_index (&surface->forward_links, i); - if (link->page > (int)_cairo_array_num_elements (&surface->pages)) - return _cairo_tag_error ("Link attribute: \"page=%d\" page exceeds page count (%d)", - link->page, _cairo_array_num_elements (&surface->pages)); - - - status = _cairo_pdf_surface_object_begin (surface, link->res); - if (unlikely (status)) - return status; - - if (link->dest) { - key.attrs.name = link->dest; - init_named_dest_key (&key); - named_dest = _cairo_hash_table_lookup (ic->named_dests, &key.base); - if (named_dest) { - double x = 0; - double y = 0; - - if (named_dest->extents.valid) { - x = named_dest->extents.extents.x; - y = named_dest->extents.extents.y; - } - - if (named_dest->attrs.x_valid) - x = named_dest->attrs.x; - - if (named_dest->attrs.y_valid) - y = named_dest->attrs.y; - - status = cairo_pdf_interchange_write_explicit_dest (surface, - named_dest->page, - TRUE, - x, y); - } else { - return _cairo_tag_error ("Link to dest=\"%s\" not found", link->dest); - } - } else { - cairo_pdf_interchange_write_explicit_dest (surface, - link->page, - link->has_pos, - link->pos.x, - link->pos.y); - } - _cairo_pdf_surface_object_end (surface); - } - - return CAIRO_STATUS_SUCCESS; -} - static cairo_int_status_t cairo_pdf_interchange_write_page_labels (cairo_pdf_surface_t *surface) { @@ -1658,6 +1606,7 @@ _cairo_pdf_interchange_write_document_dests (cairo_pdf_surface_t *surface) cairo_pdf_named_dest_t *dest = ic->sorted_dests[i]; double x = 0; double y = 0; + char *name = NULL; if (dest->attrs.internal) continue; @@ -1673,13 +1622,19 @@ _cairo_pdf_interchange_write_document_dests (cairo_pdf_surface_t *surface) if (dest->attrs.y_valid) y = dest->attrs.y; + status = _cairo_utf8_to_pdf_string (dest->attrs.name, &name); + if (unlikely (status)) + return status; + page_info = _cairo_array_index (&surface->pages, dest->page - 1); _cairo_output_stream_printf (surface->object_stream.stream, - " (%s) [%d 0 R /XYZ %f %f 0]\n", - dest->attrs.name, + " %s [%d 0 R /XYZ %f %f 0]\n", + name, page_info->page_res.id, x, page_info->height - y); + free (name); + } _cairo_output_stream_printf (surface->object_stream.stream, " ]\n" @@ -2419,10 +2374,6 @@ _cairo_pdf_interchange_write_document_objects (cairo_pdf_surface_t *surface) if (unlikely (status)) return status; - status = cairo_pdf_interchange_write_forward_links (surface); - if (unlikely (status)) - return status; - status = cairo_pdf_interchange_write_names_dict (surface); if (unlikely (status)) return status; diff --git a/src/cairo-pdf-surface-private.h b/src/cairo-pdf-surface-private.h index 13ccc5001..bf1d39456 100644 --- a/src/cairo-pdf-surface-private.h +++ b/src/cairo-pdf-surface-private.h @@ -266,14 +266,6 @@ typedef struct _cairo_pdf_outline_entry { int count; } cairo_pdf_outline_entry_t; -typedef struct _cairo_pdf_forward_link { - cairo_pdf_resource_t res; - char *dest; - int page; - cairo_bool_t has_pos; - cairo_point_double_t pos; -} cairo_pdf_forward_link_t; - struct docinfo { char *title; char *author; @@ -468,7 +460,6 @@ struct _cairo_pdf_surface { cairo_pdf_interchange_t interchange; int page_parent_tree; /* -1 if not used */ cairo_array_t page_annots; - cairo_array_t forward_links; cairo_bool_t tagged; char *current_page_label; cairo_array_t page_labels; diff --git a/src/cairo-pdf-surface.c b/src/cairo-pdf-surface.c index 713880703..d4c6e39d9 100644 --- a/src/cairo-pdf-surface.c +++ b/src/cairo-pdf-surface.c @@ -555,7 +555,6 @@ _cairo_pdf_surface_create_for_stream_internal (cairo_output_stream_t *output, surface->page_parent_tree = -1; _cairo_array_init (&surface->page_annots, sizeof (cairo_pdf_resource_t)); - _cairo_array_init (&surface->forward_links, sizeof (cairo_pdf_forward_link_t)); surface->tagged = FALSE; surface->current_page_label = NULL; _cairo_array_init (&surface->page_labels, sizeof (char *)); @@ -2824,7 +2823,6 @@ _cairo_pdf_surface_finish (void *abstract_surface) _cairo_array_fini (&surface->fonts); _cairo_array_fini (&surface->knockout_group); _cairo_array_fini (&surface->page_annots); - _cairo_array_fini (&surface->forward_links); _cairo_hash_table_foreach (surface->color_glyphs, _cairo_pdf_color_glyph_pluck, diff --git a/src/cairo-tag-attributes-private.h b/src/cairo-tag-attributes-private.h index 1b770aef9..5853f9a0d 100644 --- a/src/cairo-tag-attributes-private.h +++ b/src/cairo-tag-attributes-private.h @@ -47,6 +47,8 @@ typedef enum { TAG_LINK_DEST, TAG_LINK_URI, TAG_LINK_FILE, + TAG_LINK_PAGE, + TAG_LINK_DEST_AND_URI, } cairo_tag_link_type_t; typedef struct _cairo_content_attrs { diff --git a/src/cairo-tag-attributes.c b/src/cairo-tag-attributes.c index 4fde1b5b8..7873bb8da 100644 --- a/src/cairo-tag-attributes.c +++ b/src/cairo-tag-attributes.c @@ -655,25 +655,30 @@ _cairo_tag_parse_link_attributes (const char *attributes, cairo_link_attrs_t *li } } - if (link_attrs->uri) { - link_attrs->link_type = TAG_LINK_URI; - if (link_attrs->dest || link_attrs->page || link_attrs->has_pos || link_attrs->file) - invalid_combination = TRUE; - - } else if (link_attrs->file) { + if (link_attrs->file) { link_attrs->link_type = TAG_LINK_FILE; if (link_attrs->uri) invalid_combination = TRUE; else if (link_attrs->dest && (link_attrs->page || link_attrs->has_pos)) invalid_combination = TRUE; + } else if (link_attrs->uri && link_attrs->dest) { + link_attrs->link_type = TAG_LINK_DEST_AND_URI; + if (link_attrs->page || link_attrs->has_pos || link_attrs->file) + invalid_combination = TRUE; + + } else if (link_attrs->uri) { + link_attrs->link_type = TAG_LINK_URI; + if (link_attrs->dest || link_attrs->page || link_attrs->has_pos || link_attrs->file) + invalid_combination = TRUE; + } else if (link_attrs->dest) { link_attrs->link_type = TAG_LINK_DEST; if (link_attrs->uri || link_attrs->page || link_attrs->has_pos) invalid_combination = TRUE; } else if (link_attrs->page) { - link_attrs->link_type = TAG_LINK_DEST; + link_attrs->link_type = TAG_LINK_PAGE; if (link_attrs->uri || link_attrs->dest) invalid_combination = TRUE; diff --git a/src/cairo-tag-stack-private.h b/src/cairo-tag-stack-private.h index 49145bf1d..b3878ddb2 100644 --- a/src/cairo-tag-stack-private.h +++ b/src/cairo-tag-stack-private.h @@ -116,4 +116,7 @@ _cairo_tag_get_type (const char *name); cairo_private cairo_status_t _cairo_tag_error (const char *fmt, ...) CAIRO_PRINTF_FORMAT (1, 2); +cairo_private void +_cairo_tag_warning (const char *fmt, ...) CAIRO_PRINTF_FORMAT (1, 2); + #endif /* CAIRO_TAG_STACK_PRIVATE_H */ diff --git a/src/cairo-tag-stack.c b/src/cairo-tag-stack.c index f182b508b..ffe1e483a 100644 --- a/src/cairo-tag-stack.c +++ b/src/cairo-tag-stack.c @@ -331,3 +331,17 @@ _cairo_tag_error (const char *fmt, ...) } return _cairo_error (CAIRO_STATUS_TAG_ERROR); } + +void +_cairo_tag_warning (const char *fmt, ...) +{ + va_list ap; + + if (getenv ("CAIRO_DEBUG_TAG") != NULL) { + printf ("TAG WARNING: "); + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + printf ("\n"); + } +} diff --git a/test/pdf-tagged-text.c b/test/pdf-tagged-text.c index 3883d418e..094b1c8b0 100644 --- a/test/pdf-tagged-text.c +++ b/test/pdf-tagged-text.c @@ -113,6 +113,8 @@ static const char *roman_numerals[] = { #define MAX_PARAGRAPH_LINES 20 +static const char *utf8_destination = "l\xc3\xa4nk"; + static int paragraph_num_lines; static char *paragraph_text[MAX_PARAGRAPH_LINES]; static double paragraph_height; @@ -329,20 +331,20 @@ draw_cover (cairo_surface_t *surface, cairo_t *cr) cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); cairo_set_font_size(cr, 16); - cairo_move_to (cr, PAGE_WIDTH/3, PAGE_HEIGHT/3); + cairo_move_to (cr, PAGE_WIDTH/3, 0.15*PAGE_HEIGHT); cairo_tag_begin (cr, "Span", NULL); cairo_show_text (cr, "PDF Features Test"); cairo_tag_end (cr, "Span"); /* Test URL link using "rect" attribute. The entire rectangle surrounding the URL should be a clickable link. */ - cairo_move_to (cr, PAGE_WIDTH/3, 2*PAGE_HEIGHT/3); + cairo_move_to (cr, PAGE_WIDTH/3, 0.2*PAGE_HEIGHT); cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); cairo_set_font_size(cr, TEXT_SIZE); cairo_set_source_rgb (cr, 0, 0, 1); cairo_show_text (cr, cairo_url); cairo_text_extents (cr, cairo_url, &text_extents); url_box.x = PAGE_WIDTH/3 - url_box_margin; - url_box.y = 2*PAGE_HEIGHT/3 - url_box_margin; + url_box.y = 0.2*PAGE_HEIGHT - url_box_margin; url_box.width = text_extents.width + 2*url_box_margin; 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); @@ -355,28 +357,52 @@ draw_cover (cairo_surface_t *surface, cairo_t *cr) /* Create link to not yet emmited page number */ cairo_tag_begin (cr, CAIRO_TAG_LINK, "page=5"); - cairo_move_to (cr, PAGE_WIDTH/3, 4*PAGE_HEIGHT/5); + cairo_move_to (cr, PAGE_WIDTH/3, 0.25*PAGE_HEIGHT); cairo_show_text (cr, "link to page 5"); cairo_tag_end (cr, CAIRO_TAG_LINK); /* Create link to not yet emmited destination */ cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='Section 3.3'"); - cairo_move_to (cr, PAGE_WIDTH/3, 4.2*PAGE_HEIGHT/5); + cairo_move_to (cr, PAGE_WIDTH/3, 0.3*PAGE_HEIGHT); cairo_show_text (cr, "link to page section 3.3"); cairo_tag_end (cr, CAIRO_TAG_LINK); /* Create link to external file */ + cairo_move_to (cr, PAGE_WIDTH/3, 0.35*PAGE_HEIGHT); cairo_tag_begin (cr, CAIRO_TAG_LINK, "file='foo.pdf' page=1"); - cairo_move_to (cr, PAGE_WIDTH/3, 4.4*PAGE_HEIGHT/5); cairo_show_text (cr, "link file 'foo.pdf'"); cairo_tag_end (cr, CAIRO_TAG_LINK); + /* Create link to missing dest */ + cairo_move_to (cr, PAGE_WIDTH/3, 0.4*PAGE_HEIGHT); + cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='I don\\'t exist'"); + cairo_show_text (cr, "link to missing dest"); + cairo_tag_end (cr, CAIRO_TAG_LINK); + + /* Create link to missing dest with URI fallback*/ + cairo_move_to (cr, PAGE_WIDTH/3, 0.45*PAGE_HEIGHT); + xasprintf(&attrib, "dest='I also don\\'t exist' uri='%s'", cairo_url); + cairo_tag_begin (cr, CAIRO_TAG_LINK, attrib); + cairo_show_text (cr, "link to missing dest with uri fallback"); + cairo_tag_end (cr, CAIRO_TAG_LINK); + free (attrib); + + /* Create link to utf8 dest */ + cairo_move_to (cr, PAGE_WIDTH/3, 0.5*PAGE_HEIGHT); + xasprintf(&attrib, "dest='%s'", utf8_destination); + cairo_tag_begin (cr, CAIRO_TAG_LINK, attrib); + cairo_show_text (cr, "link to utf8 dest"); + cairo_tag_end (cr, CAIRO_TAG_LINK); + free (attrib); + draw_page_num (surface, cr, "cover", 0); } static void create_document (cairo_surface_t *surface, cairo_t *cr) { + char *attrib; + layout_paragraph (cr); cairo_pdf_surface_set_thumbnail_size (surface, PAGE_WIDTH/10, PAGE_HEIGHT/10); @@ -491,6 +517,14 @@ create_document (cairo_surface_t *surface, cairo_t *cr) cairo_show_text (cr, "link to page 3"); cairo_tag_end (cr, CAIRO_TAG_LINK); + /* Create utf8 dest */ + cairo_move_to (cr, PAGE_WIDTH/3, 4*PAGE_HEIGHT/5); + xasprintf(&attrib, "name='%s'", utf8_destination); + cairo_tag_begin (cr, CAIRO_TAG_DEST, attrib); + cairo_show_text (cr, utf8_destination); + cairo_tag_end (cr, CAIRO_TAG_DEST); + free (attrib); + cairo_tag_end (cr, "Document"); }