summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorAdrian Johnson <ajohnson@redneon.com>2016-10-01 22:48:05 +0930
committerAdrian Johnson <ajohnson@redneon.com>2016-10-01 22:48:05 +0930
commit23fd706bd1b83a00cdd3d666631e41e2ceab8a70 (patch)
tree3e20ac7cca88f636f04631e2560530371333ff92 /test
parent2d6a0f5d16d61c8f4236760c71061a0c4c3a199c (diff)
add test for PDF document interchange features such as tagged text and links
Diffstat (limited to 'test')
-rw-r--r--test/Makefile.am1
-rw-r--r--test/Makefile.sources3
-rw-r--r--test/pdf-tagged-text.c397
3 files changed, 400 insertions, 1 deletions
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 <ajohnson@redneon.com>
+ */
+
+#include "cairo-test.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <cairo.h>
+#include <cairo-pdf.h>
+
+/* 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)