diff --git a/src/cairo-pdf-interchange.c b/src/cairo-pdf-interchange.c index ad4072e63..8158161fb 100644 --- a/src/cairo-pdf-interchange.c +++ b/src/cairo-pdf-interchange.c @@ -335,20 +335,17 @@ cairo_pdf_interchange_write_explicit_dest (cairo_pdf_surface_t *surface, cairo_pdf_resource_t res; double height; - if (page < 1 || page > (int)_cairo_array_num_elements (&surface->pages)) - return CAIRO_INT_STATUS_TAG_ERROR; - _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->output, - " /Dest [%d 0 R /XYZ %f %f 0]\n", + "[%d 0 R /XYZ %f %f 0]\n", res.id, x, height - y); } else { _cairo_output_stream_printf (surface->output, - " /Dest [%d 0 R /XYZ null null 0]\n", + "[%d 0 R /XYZ null null 0]\n", res.id); } @@ -362,6 +359,8 @@ cairo_pdf_interchange_write_dest (cairo_pdf_surface_t *surface, cairo_int_status_t status; cairo_pdf_interchange_t *ic = &surface->interchange; char *dest = NULL; + cairo_pdf_forward_link_t *link; + cairo_pdf_resource_t link_res; if (link_attrs->dest) { cairo_pdf_named_dest_t key; @@ -388,10 +387,11 @@ 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->output, " /Dest "); status = cairo_pdf_interchange_write_explicit_dest (surface, - named_dest->page, - TRUE, - x, y); + named_dest->page, + TRUE, + x, y); return status; } } @@ -406,14 +406,41 @@ cairo_pdf_interchange_write_dest (cairo_pdf_surface_t *surface, dest); free (dest); } else { - status = cairo_pdf_interchange_write_explicit_dest (surface, - link_attrs->page, - link_attrs->has_pos, - link_attrs->pos.x, - link_attrs->pos.y); + if (link_attrs->page < 1) + return CAIRO_INT_STATUS_TAG_ERROR; + + if (link_attrs->page <= (int)_cairo_array_num_elements (&surface->pages)) { + _cairo_output_stream_printf (surface->output, " /Dest "); + status = cairo_pdf_interchange_write_explicit_dest (surface, + link_attrs->page, + link_attrs->has_pos, + link_attrs->pos.x, + link_attrs->pos.y); + } else { + /* Link refers to a future page. Use an indirect object and + * write the link at the end of the document */ + + link = _cairo_malloc (sizeof (cairo_pdf_forward_link_t)); + if (unlikely (link == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + link_res = _cairo_pdf_surface_new_object (surface); + if (link_res.id == 0) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + _cairo_output_stream_printf (surface->output, + " /Dest %d 0 R\n", + link_res.id); + + link->res = link_res; + 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 CAIRO_STATUS_SUCCESS; + return status; } static cairo_int_status_t @@ -855,6 +882,36 @@ 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; + + 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_INT_STATUS_TAG_ERROR; + + _cairo_pdf_surface_update_object (surface, link->res); + _cairo_output_stream_printf (surface->output, + "%d 0 obj\n", + link->res.id); + + cairo_pdf_interchange_write_explicit_dest (surface, + link->page, + link->has_pos, + link->pos.x, + link->pos.y); + + _cairo_output_stream_printf (surface->output, + "endobj\n"); + } + + return CAIRO_STATUS_SUCCESS; +} + static cairo_int_status_t cairo_pdf_interchange_write_page_labels (cairo_pdf_surface_t *surface) { @@ -1364,10 +1421,10 @@ _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)) + if (unlikely (status)) return status; - cairo_pdf_interchange_clear_annotations (surface); + cairo_pdf_interchange_clear_annotations (surface); return cairo_pdf_interchange_write_page_parent_elems (surface); } @@ -1403,6 +1460,10 @@ _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 49776b90f..3fb8ffaf7 100644 --- a/src/cairo-pdf-surface-private.h +++ b/src/cairo-pdf-surface-private.h @@ -208,6 +208,13 @@ typedef struct _cairo_pdf_outline_entry { int count; } cairo_pdf_outline_entry_t; +typedef struct _cairo_pdf_forward_link { + cairo_pdf_resource_t res; + int page; + cairo_bool_t has_pos; + cairo_point_double_t pos; +} cairo_pdf_forward_link_t; + struct docinfo { char *title; char *author; @@ -327,6 +334,7 @@ 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 f2463a8c3..a9caed8ff 100644 --- a/src/cairo-pdf-surface.c +++ b/src/cairo-pdf-surface.c @@ -493,6 +493,7 @@ _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 *)); @@ -2304,6 +2305,7 @@ _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); if (surface->font_subsets) { _cairo_scaled_font_subsets_destroy (surface->font_subsets); diff --git a/test/pdf-tagged-text.c b/test/pdf-tagged-text.c index 69dc6b928..95b779306 100644 --- a/test/pdf-tagged-text.c +++ b/test/pdf-tagged-text.c @@ -318,6 +318,9 @@ draw_cover (cairo_surface_t *surface, cairo_t *cr) const char *cairo_url = "https://www.cairographics.org/"; const double url_box_margin = 20.0; + cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='cover' internal"); + cairo_tag_end (cr, CAIRO_TAG_DEST); + 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); @@ -343,6 +346,12 @@ draw_cover (cairo_surface_t *surface, cairo_t *cr) cairo_tag_begin (cr, CAIRO_TAG_LINK, buf); cairo_tag_end (cr, CAIRO_TAG_LINK); + /* 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_show_text (cr, "link to page 5"); + cairo_tag_end (cr, CAIRO_TAG_LINK); + draw_page_num (surface, cr, "cover", 0); } @@ -417,6 +426,18 @@ create_document (cairo_surface_t *surface, cairo_t *cr) sect++; } + cairo_show_page (cr); + + 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"); + cairo_tag_end (cr, CAIRO_TAG_LINK); + + cairo_tag_begin (cr, CAIRO_TAG_LINK, "page=3"); + cairo_move_to (cr, PAGE_WIDTH/3, 3*PAGE_HEIGHT/5); + cairo_show_text (cr, "link to page 3"); + cairo_tag_end (cr, CAIRO_TAG_LINK); + cairo_tag_end (cr, "Document"); } @@ -511,7 +532,7 @@ preamble (cairo_test_context_t *ctx) cairo_destroy (cr); cairo_surface_finish (surface); status2 = cairo_surface_status (surface); - if (status != CAIRO_STATUS_SUCCESS) + if (status == CAIRO_STATUS_SUCCESS) status = status2; cairo_surface_destroy (surface);