From 23fd706bd1b83a00cdd3d666631e41e2ceab8a70 Mon Sep 17 00:00:00 2001 From: Adrian Johnson Date: Sat, 1 Oct 2016 22:48:05 +0930 Subject: add test for PDF document interchange features such as tagged text and links --- test/Makefile.am | 1 + test/Makefile.sources | 3 +- test/pdf-tagged-text.c | 397 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 test/pdf-tagged-text.c diff --git a/test/Makefile.am b/test/Makefile.am index b2fcd275d..f03c21b08 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -323,6 +323,7 @@ CLEANFILES += \ ps-surface-source.out.ps \ pdf-features.pdf \ pdf-mime-data.out* \ + pdf-tagged-text.out* \ ps-features.ps \ svg-clip.svg \ svg-surface.svg \ diff --git a/test/Makefile.sources b/test/Makefile.sources index 479b2caa1..5ead2316c 100644 --- a/test/Makefile.sources +++ b/test/Makefile.sources @@ -417,7 +417,8 @@ quartz_surface_test_sources = quartz-surface-source.c pdf_surface_test_sources = \ pdf-features.c \ pdf-mime-data.c \ - pdf-surface-source.c + pdf-surface-source.c \ + pdf-tagged-text.c ps_surface_test_sources = \ ps-eps.c \ diff --git a/test/pdf-tagged-text.c b/test/pdf-tagged-text.c new file mode 100644 index 000000000..91b917f63 --- /dev/null +++ b/test/pdf-tagged-text.c @@ -0,0 +1,397 @@ +/* + * Copyright © 2016 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 + */ + +#include "cairo-test.h" + +#include +#include +#include + +#include +#include + +/* This test checks PDF with + * - tagged text + * - hyperlinks + * - document outline + * - metadata + * - thumbnails + * - page labels + */ + +#define BASENAME "pdf-tagged-text.out" + +#define PAGE_WIDTH 595 +#define PAGE_HEIGHT 842 + +#define HEADING1_SIZE 16 +#define HEADING2_SIZE 14 +#define HEADING3_SIZE 12 +#define TEXT_SIZE 12 +#define HEADING_HEIGHT 50 +#define MARGIN 50 + +struct section { + int level; + const char *heading; + int num_paragraphs; +}; + +static const struct section contents[] = { + { 0, "Chapter 1", 1 }, + { 1, "Section 1.1", 4 }, + { 2, "Section 1.1.1", 3 }, + { 1, "Section 1.2", 2 }, + { 2, "Section 1.2.1", 4 }, + { 2, "Section 1.2.2", 4 }, + { 1, "Section 1.3", 2 }, + { 0, "Chapter 2", 1 }, + { 1, "Section 2.1", 4 }, + { 2, "Section 2.1.1", 3 }, + { 1, "Section 2.2", 2 }, + { 2, "Section 2.2.1", 4 }, + { 2, "Section 2.2.2", 4 }, + { 1, "Section 2.3", 2 }, + { 0, "Chapter 3", 1 }, + { 1, "Section 3.1", 4 }, + { 2, "Section 3.1.1", 3 }, + { 1, "Section 3.2", 2 }, + { 2, "Section 3.2.1", 4 }, + { 2, "Section 3.2.2", 4 }, + { 1, "Section 3.3", 2 }, + { 0, NULL } +}; + +static const char *ipsum_lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing" + " elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + " Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi" + " ut aliquip ex ea commodo consequat. Duis aute irure dolor in" + " reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla" + " pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa" + " qui officia deserunt mollit anim id est laborum."; + +static const char *roman_numerals[] = { + "i", "ii", "iii", "iv", "v" +}; + +#define MAX_PARAGRAPH_LINES 20 + +static int paragraph_num_lines; +static char *paragraph_text[MAX_PARAGRAPH_LINES]; +static double paragraph_height; +static double line_height; +static double y_pos; +static int outline_parents[10]; +static int page_num; + +static void +layout_paragraph (cairo_t *cr) +{ + char *text, *begin, *end, *prev_end; + cairo_text_extents_t text_extents; + cairo_font_extents_t font_extents; + + cairo_select_font_face (cr, "Serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, TEXT_SIZE); + cairo_font_extents (cr, &font_extents); + line_height = font_extents.height; + paragraph_height = 0; + paragraph_num_lines = 0; + text = strdup (ipsum_lorem); + begin = text; + end = text; + while (*begin) { + end = strchr(end, ' '); + if (!end) { + paragraph_text[paragraph_num_lines++] = strdup (begin); + break; + } + *end = 0; + cairo_text_extents (cr, begin, &text_extents); + *end = ' '; + if (text_extents.width + 2*MARGIN > PAGE_WIDTH) { + paragraph_text[paragraph_num_lines++] = strndup (begin, prev_end - begin); + begin = prev_end + 1; + } + prev_end = end; + end++; + } + paragraph_height = line_height * (paragraph_num_lines + 1); + free (text); +} + +static void +draw_paragraph (cairo_t *cr) +{ + int i; + + cairo_select_font_face (cr, "Serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, TEXT_SIZE); + cairo_tag_begin (cr, "P", NULL); + for (i = 0; i < paragraph_num_lines; i++) { + cairo_move_to (cr, MARGIN, y_pos); + cairo_show_text (cr, paragraph_text[i]); + y_pos += line_height; + } + cairo_tag_end (cr, "P"); + y_pos += line_height; +} + +static void +draw_page_num (cairo_surface_t *surface, cairo_t *cr, const char *prefix, int num) +{ + char buf[100]; + + buf[0] = 0; + if (prefix) + strcat (buf, prefix); + + if (num) + sprintf (buf + strlen(buf), "%d", num); + + cairo_save (cr); + cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cr, 12); + cairo_move_to (cr, PAGE_WIDTH/2, PAGE_HEIGHT - MARGIN); + cairo_show_text (cr, buf); + cairo_restore (cr); + cairo_pdf_surface_set_page_label (surface, buf); +} + +static void +draw_contents (cairo_surface_t *surface, cairo_t *cr, const struct section *section) +{ + char buf[100]; + + sprintf(buf, "dest='%s'", section->heading); + cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); + switch (section->level) { + case 0: + cairo_set_font_size(cr, HEADING1_SIZE); + break; + case 1: + cairo_set_font_size(cr, HEADING2_SIZE); + break; + case 2: + cairo_set_font_size(cr, HEADING3_SIZE); + break; + } + + if (y_pos + HEADING_HEIGHT + MARGIN > PAGE_HEIGHT) { + cairo_show_page (cr); + draw_page_num (surface, cr, roman_numerals[page_num++], 0); + y_pos = MARGIN; + } + cairo_move_to (cr, MARGIN, y_pos); + cairo_save (cr); + 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_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; +} + +static void +draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *section) +{ + int flags, i; + char buf[100]; + + cairo_tag_begin (cr, "Sect", NULL); + sprintf(buf, "name='%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); + draw_page_num (surface, cr, NULL, page_num++); + 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_show_text (cr, section->heading); + cairo_tag_end (cr, CAIRO_TAG_DEST); + cairo_tag_end (cr, "H1"); + y_pos = MARGIN + HEADING_HEIGHT; + flags = CAIRO_BOOKMARK_FLAG_BOLD | CAIRO_BOOKMARK_FLAG_OPEN; + outline_parents[0] = cairo_pdf_surface_add_outline (surface, + CAIRO_PDF_OUTLINE_ROOT, + section->heading, + section->heading, + flags); + } else { + if (section->level == 1) { + cairo_set_font_size(cr, HEADING2_SIZE); + flags = 0; + } else { + cairo_set_font_size(cr, HEADING3_SIZE); + flags = CAIRO_BOOKMARK_FLAG_ITALIC; + } + + if (y_pos + HEADING_HEIGHT + paragraph_height + MARGIN > PAGE_HEIGHT) { + cairo_show_page (cr); + draw_page_num (surface, cr, NULL, page_num++); + y_pos = MARGIN; + } + cairo_move_to (cr, MARGIN, y_pos); + if (section->level == 1) + cairo_tag_begin (cr, "H2", NULL); + else + cairo_tag_begin (cr, "H3", NULL); + cairo_tag_begin (cr, CAIRO_TAG_DEST, buf); + cairo_show_text (cr, section->heading); + cairo_tag_end (cr, CAIRO_TAG_DEST); + if (section->level == 1) + cairo_tag_end (cr, "H2"); + else + cairo_tag_end (cr, "H3"); + y_pos += HEADING_HEIGHT; + outline_parents[section->level] = cairo_pdf_surface_add_outline (surface, + outline_parents[section->level - 1], + section->heading, + section->heading, + flags); + } + + for (i = 0; i < section->num_paragraphs; i++) { + if (y_pos + paragraph_height + MARGIN > PAGE_HEIGHT) { + cairo_show_page (cr); + draw_page_num (surface, cr, NULL, page_num++); + y_pos = MARGIN; + } + draw_paragraph (cr); + } + cairo_tag_end (cr, "Sect"); +} + +static void +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/2); + cairo_tag_begin (cr, "Span", NULL); + cairo_show_text (cr, "PDF Features Test"); + cairo_tag_end (cr, "Span"); + + draw_page_num (surface, cr, "cover", 0); +} + +static void +create_document (cairo_surface_t *surface, cairo_t *cr) +{ + layout_paragraph (cr); + + cairo_pdf_surface_set_thumbnail_size (surface, PAGE_WIDTH/10, PAGE_HEIGHT/10); + + cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_TITLE, "PDF Features Test"); + cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_AUTHOR, "cairo test suite"); + cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_SUBJECT, "cairo test"); + cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_KEYWORDS, + "tags, links, outline, page labels, metadata, thumbnails"); + cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_CREATOR, "pdf-features"); + cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_CREATE_DATE, "2016-01-01T12:34:56+10:30"); + cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_MOD_DATE, "2016-06-21T05:43:21Z"); + + cairo_tag_begin (cr, "Document", NULL); + + draw_cover (surface, cr); + cairo_show_page (cr); + + page_num = 0; + draw_page_num (surface, cr, roman_numerals[page_num++], 0); + y_pos = MARGIN; + + cairo_pdf_surface_add_outline (surface, + CAIRO_PDF_OUTLINE_ROOT, + "Contents", "TOC", CAIRO_BOOKMARK_FLAG_BOLD); + + cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='TOC'"); + cairo_tag_begin (cr, "TOC", NULL); + const struct section *sect = contents; + while (sect->heading) { + draw_contents (surface, cr, sect); + sect++; + } + cairo_tag_end (cr, "TOC"); + cairo_tag_end (cr, CAIRO_TAG_DEST); + + page_num = 1; + sect = contents; + while (sect->heading) { + draw_section (surface, cr, sect); + sect++; + } + + cairo_tag_end (cr, "Document"); +} + +static cairo_test_status_t +preamble (cairo_test_context_t *ctx) +{ + cairo_surface_t *surface; + cairo_t *cr; + cairo_status_t status, status2; + char *filename; + const char *path = cairo_test_mkdir (CAIRO_TEST_OUTPUT_DIR) ? CAIRO_TEST_OUTPUT_DIR : "."; + + if (! cairo_test_is_target_enabled (ctx, "pdf")) + return CAIRO_TEST_UNTESTED; + + xasprintf (&filename, "%s/%s.pdf", path, BASENAME); + surface = cairo_pdf_surface_create (filename, PAGE_WIDTH, PAGE_HEIGHT); + + cr = cairo_create (surface); + create_document (surface, 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", + filename, cairo_status_to_string (status)); + return CAIRO_TEST_FAILURE; + } + + free (filename); + + return CAIRO_TEST_SUCCESS; +} + +CAIRO_TEST (pdf_tagged_text, + "Check tagged text, hyperlinks and PDF document features", + "pdf", /* keywords */ + NULL, /* requirements */ + 0, 0, + preamble, NULL) -- cgit v1.2.3