diff options
author | Adrian Johnson <ajohnson@redneon.com> | 2023-02-19 21:10:58 +1030 |
---|---|---|
committer | Adrian Johnson <ajohnson@redneon.com> | 2023-04-18 18:27:12 +0930 |
commit | b53b48116e610d61cdf630c24a11b59a18345e16 (patch) | |
tree | 3bc8f3410e795ce81c1408b9d7f3a217033d29ba /test | |
parent | e7ed40a71dac04cb4c608b409b04577d01f08454 (diff) | |
download | cairo-b53b48116e610d61cdf630c24a11b59a18345e16.tar.gz |
Make cairo_tag_begin/end work correctly in groups
Fixes #508
Diffstat (limited to 'test')
-rw-r--r-- | test/cairo-test.c | 8 | ||||
-rw-r--r-- | test/cairo-test.h | 2 | ||||
-rwxr-xr-x | test/check-pdf-structure.sh | 21 | ||||
-rw-r--r-- | test/meson.build | 7 | ||||
-rw-r--r-- | test/pdf-structure.c | 568 | ||||
-rw-r--r-- | test/pdf-tagged-text.c | 62 | ||||
-rw-r--r-- | test/reference/pdf-structure-group-ref.ref.txt | 6 | ||||
-rw-r--r-- | test/reference/pdf-structure-group.ref.txt | 6 | ||||
-rw-r--r-- | test/reference/pdf-structure-multipage-group.ref.txt | 8 | ||||
-rw-r--r-- | test/reference/pdf-structure-multipage-group2.ref.txt | 10 | ||||
-rw-r--r-- | test/reference/pdf-structure-multipage-simple-ref.ref.txt | 12 | ||||
-rw-r--r-- | test/reference/pdf-structure-multipage-simple.ref.txt | 12 | ||||
-rw-r--r-- | test/reference/pdf-structure-repeated-group.ref.txt | 9 | ||||
-rw-r--r-- | test/reference/pdf-structure-simple-ref.ref.txt | 11 | ||||
-rw-r--r-- | test/reference/pdf-structure-simple.ref.txt | 11 |
15 files changed, 735 insertions, 18 deletions
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" |