Merge branch 'dest-and-uri' into 'master'

Allow links to specify 'dest' and 'uri'

See merge request cairo/cairo!547
This commit is contained in:
Adrian Johnson 2024-05-15 22:12:24 +00:00
commit c75997a4ea
8 changed files with 135 additions and 137 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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,

View file

@ -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 {

View file

@ -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;

View file

@ -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 */

View file

@ -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");
}
}

View file

@ -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");
}