2016-10-01 22:48:05 +09:30
|
|
|
/*
|
|
|
|
|
* 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>
|
2021-01-04 12:45:38 +01:00
|
|
|
#include <errno.h>
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
|
|
|
|
|
#if HAVE_UNISTD_H
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_MMAP
|
|
|
|
|
#include <sys/mman.h>
|
|
|
|
|
#endif
|
2016-10-01 22:48:05 +09:30
|
|
|
|
|
|
|
|
#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;
|
2016-10-04 11:55:28 +10:30
|
|
|
prev_end = end;
|
2016-10-01 22:48:05 +09:30
|
|
|
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) {
|
2016-10-07 07:38:37 +10:30
|
|
|
int len = prev_end - begin;
|
|
|
|
|
char *s = malloc (len);
|
|
|
|
|
memcpy (s, begin, len);
|
2017-08-19 10:50:58 +09:30
|
|
|
s[len-1] = 0;
|
2016-10-07 07:38:37 +10:30
|
|
|
paragraph_text[paragraph_num_lines++] = s;
|
2016-10-01 22:48:05 +09:30
|
|
|
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);
|
2021-07-16 09:50:35 +09:30
|
|
|
cairo_set_source_rgb (cr, 0, 0, 0);
|
2016-10-01 22:48:05 +09:30
|
|
|
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];
|
2017-08-26 16:32:48 +09:30
|
|
|
char buf2[100];
|
2016-10-01 22:48:05 +09:30
|
|
|
|
|
|
|
|
cairo_tag_begin (cr, "Sect", NULL);
|
|
|
|
|
sprintf(buf, "name='%s'", section->heading);
|
2017-08-26 16:32:48 +09:30
|
|
|
sprintf(buf2, "dest='%s'", section->heading);
|
2016-10-01 22:48:05 +09:30
|
|
|
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;
|
2017-01-04 23:38:17 +09:00
|
|
|
flags = CAIRO_PDF_OUTLINE_FLAG_BOLD | CAIRO_PDF_OUTLINE_FLAG_OPEN;
|
2016-10-01 22:48:05 +09:30
|
|
|
outline_parents[0] = cairo_pdf_surface_add_outline (surface,
|
|
|
|
|
CAIRO_PDF_OUTLINE_ROOT,
|
|
|
|
|
section->heading,
|
2017-08-26 16:32:48 +09:30
|
|
|
buf2,
|
2016-10-01 22:48:05 +09:30
|
|
|
flags);
|
|
|
|
|
} else {
|
|
|
|
|
if (section->level == 1) {
|
|
|
|
|
cairo_set_font_size(cr, HEADING2_SIZE);
|
|
|
|
|
flags = 0;
|
|
|
|
|
} else {
|
|
|
|
|
cairo_set_font_size(cr, HEADING3_SIZE);
|
2017-01-04 23:38:17 +09:00
|
|
|
flags = CAIRO_PDF_OUTLINE_FLAG_ITALIC;
|
2016-10-01 22:48:05 +09:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
2017-08-26 16:32:48 +09:30
|
|
|
buf2,
|
2016-10-01 22:48:05 +09:30
|
|
|
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)
|
|
|
|
|
{
|
2021-07-16 09:50:35 +09:30
|
|
|
cairo_text_extents_t text_extents;
|
|
|
|
|
char buf[200];
|
|
|
|
|
cairo_rectangle_t url_box;
|
|
|
|
|
const char *cairo_url = "https://www.cairographics.org/";
|
|
|
|
|
const double url_box_margin = 20.0;
|
|
|
|
|
|
2021-07-24 21:26:22 +09:30
|
|
|
cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='cover' internal");
|
|
|
|
|
cairo_tag_end (cr, CAIRO_TAG_DEST);
|
|
|
|
|
|
2016-10-01 22:48:05 +09:30
|
|
|
cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
|
|
|
|
|
cairo_set_font_size(cr, 16);
|
2021-07-16 09:50:35 +09:30
|
|
|
cairo_move_to (cr, PAGE_WIDTH/3, PAGE_HEIGHT/3);
|
2016-10-01 22:48:05 +09:30
|
|
|
cairo_tag_begin (cr, "Span", NULL);
|
|
|
|
|
cairo_show_text (cr, "PDF Features Test");
|
|
|
|
|
cairo_tag_end (cr, "Span");
|
|
|
|
|
|
2021-07-16 09:50:35 +09:30
|
|
|
/* 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_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.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);
|
|
|
|
|
cairo_stroke(cr);
|
|
|
|
|
snprintf(buf, sizeof(buf), "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_end (cr, CAIRO_TAG_LINK);
|
|
|
|
|
|
2021-07-24 21:26:22 +09:30
|
|
|
/* 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);
|
|
|
|
|
|
2021-07-28 07:18:25 +09:30
|
|
|
/* 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_show_text (cr, "link to page section 3.3");
|
|
|
|
|
cairo_tag_end (cr, CAIRO_TAG_LINK);
|
|
|
|
|
|
2022-01-09 17:55:11 +10:30
|
|
|
/* Create link to external file */
|
|
|
|
|
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);
|
|
|
|
|
|
2016-10-01 22:48:05 +09:30
|
|
|
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");
|
|
|
|
|
|
2021-08-18 19:41:26 +09:30
|
|
|
cairo_pdf_surface_set_custom_metadata (surface, "DocumentNumber", "12345");
|
|
|
|
|
/* Include some non ASCII characters */
|
|
|
|
|
cairo_pdf_surface_set_custom_metadata (surface, "Document Name", "\xc2\xab""cairo test\xc2\xbb");
|
|
|
|
|
/* Test unsetting custom metadata. "DocumentNumber" should not be emitted. */
|
|
|
|
|
cairo_pdf_surface_set_custom_metadata (surface, "DocumentNumber", "");
|
|
|
|
|
|
2016-10-01 22:48:05 +09:30
|
|
|
cairo_tag_begin (cr, "Document", NULL);
|
|
|
|
|
|
|
|
|
|
draw_cover (surface, cr);
|
2017-08-26 16:32:48 +09:30
|
|
|
cairo_pdf_surface_add_outline (surface,
|
|
|
|
|
CAIRO_PDF_OUTLINE_ROOT,
|
|
|
|
|
"Cover", "page=1",
|
|
|
|
|
CAIRO_PDF_OUTLINE_FLAG_BOLD);
|
2021-07-19 12:28:14 +01:00
|
|
|
|
|
|
|
|
/* Create a simple link annotation. */
|
|
|
|
|
cairo_tag_begin (cr, CAIRO_TAG_LINK, "uri='http://example.org' rect=[10 10 20 20]");
|
|
|
|
|
cairo_tag_end (cr, CAIRO_TAG_LINK);
|
|
|
|
|
|
|
|
|
|
/* Try to create a link annotation while the clip is empty;
|
|
|
|
|
* it will still be emitted.
|
|
|
|
|
*/
|
|
|
|
|
cairo_save (cr);
|
|
|
|
|
cairo_new_path (cr);
|
|
|
|
|
cairo_rectangle (cr, 100, 100, 50, 0);
|
|
|
|
|
cairo_clip (cr);
|
|
|
|
|
cairo_tag_begin (cr, CAIRO_TAG_LINK, "uri='http://example.com' rect=[100 100 20 20]");
|
|
|
|
|
cairo_tag_end (cr, CAIRO_TAG_LINK);
|
|
|
|
|
cairo_restore (cr);
|
|
|
|
|
|
2021-07-20 17:45:21 +01:00
|
|
|
/* An annotation whose rect has a negative coordinate. */
|
|
|
|
|
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);
|
|
|
|
|
|
2016-10-01 22:48:05 +09:30
|
|
|
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,
|
2017-08-26 16:32:48 +09:30
|
|
|
"Contents", "dest='TOC'",
|
2017-01-04 23:38:17 +09:00
|
|
|
CAIRO_PDF_OUTLINE_FLAG_BOLD);
|
2016-10-01 22:48:05 +09:30
|
|
|
|
2017-08-29 21:40:52 +09:30
|
|
|
cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='TOC' internal");
|
2016-10-01 22:48:05 +09:30
|
|
|
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++;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-24 21:26:22 +09:30
|
|
|
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);
|
|
|
|
|
|
2016-10-01 22:48:05 +09:30
|
|
|
cairo_tag_end (cr, "Document");
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-01 17:34:14 -07:00
|
|
|
#ifdef HAVE_MMAP
|
2021-01-04 12:45:38 +01:00
|
|
|
static cairo_test_status_t
|
|
|
|
|
check_contains_string(cairo_test_context_t *ctx, const void *hay, size_t size, const char *needle)
|
|
|
|
|
{
|
|
|
|
|
if (memmem(hay, size, needle, strlen(needle)))
|
|
|
|
|
return CAIRO_TEST_SUCCESS;
|
|
|
|
|
|
|
|
|
|
cairo_test_log (ctx, "Failed to find expected string in generated PDF: %s\n", needle);
|
|
|
|
|
return CAIRO_TEST_FAILURE;
|
|
|
|
|
}
|
2021-08-01 17:34:14 -07:00
|
|
|
#endif
|
2021-01-04 12:45:38 +01:00
|
|
|
|
|
|
|
|
static cairo_test_status_t
|
|
|
|
|
check_created_pdf(cairo_test_context_t *ctx, const char* filename)
|
|
|
|
|
{
|
|
|
|
|
cairo_test_status_t result = CAIRO_TEST_SUCCESS;
|
|
|
|
|
int fd;
|
|
|
|
|
struct stat st;
|
2021-08-28 14:08:25 +09:30
|
|
|
#ifdef HAVE_MMAP
|
2021-01-04 12:45:38 +01:00
|
|
|
void *contents;
|
2021-08-28 14:08:25 +09:30
|
|
|
#endif
|
2021-01-04 12:45:38 +01:00
|
|
|
|
|
|
|
|
fd = open(filename, O_RDONLY, 0);
|
|
|
|
|
if (fd < 0) {
|
|
|
|
|
cairo_test_log (ctx, "Failed to open generated PDF file %s: %s\n", filename, strerror(errno));
|
|
|
|
|
return CAIRO_TEST_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fstat(fd, &st) == -1)
|
|
|
|
|
{
|
|
|
|
|
cairo_test_log (ctx, "Failed to stat generated PDF file %s: %s\n", filename, strerror(errno));
|
|
|
|
|
close(fd);
|
|
|
|
|
return CAIRO_TEST_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (st.st_size == 0)
|
|
|
|
|
{
|
|
|
|
|
cairo_test_log (ctx, "Generated PDF file %s is empty\n", filename);
|
|
|
|
|
close(fd);
|
|
|
|
|
return CAIRO_TEST_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_MMAP
|
|
|
|
|
contents = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
|
|
|
if (contents == NULL)
|
|
|
|
|
{
|
|
|
|
|
cairo_test_log (ctx, "Failed to mmap generated PDF file %s: %s\n", filename, strerror(errno));
|
|
|
|
|
close(fd);
|
|
|
|
|
return CAIRO_TEST_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* check metadata */
|
|
|
|
|
result |= check_contains_string(ctx, contents, st.st_size, "/Title (PDF Features Test)");
|
|
|
|
|
result |= check_contains_string(ctx, contents, st.st_size, "/Author (cairo test suite)");
|
|
|
|
|
result |= check_contains_string(ctx, contents, st.st_size, "/Creator (pdf-features)");
|
|
|
|
|
result |= check_contains_string(ctx, contents, st.st_size, "/CreationDate (20160101123456+10'30')");
|
|
|
|
|
result |= check_contains_string(ctx, contents, st.st_size, "/ModDate (20160621054321Z)");
|
|
|
|
|
|
2021-07-19 12:28:14 +01:00
|
|
|
/* check that both the example.org and example.com links were generated */
|
|
|
|
|
result |= check_contains_string(ctx, contents, st.st_size, "http://example.org");
|
|
|
|
|
result |= check_contains_string(ctx, contents, st.st_size, "http://example.com");
|
|
|
|
|
|
2021-01-04 12:45:38 +01:00
|
|
|
// TODO: add more checks
|
|
|
|
|
|
|
|
|
|
munmap(contents, st.st_size);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-01 22:48:05 +09:30
|
|
|
static cairo_test_status_t
|
2021-07-28 06:18:01 +09:30
|
|
|
create_pdf (cairo_test_context_t *ctx, cairo_bool_t check_output)
|
2016-10-01 22:48:05 +09:30
|
|
|
{
|
|
|
|
|
cairo_surface_t *surface;
|
|
|
|
|
cairo_t *cr;
|
|
|
|
|
cairo_status_t status, status2;
|
2021-01-04 12:45:38 +01:00
|
|
|
cairo_test_status_t result;
|
2021-07-28 06:18:01 +09:30
|
|
|
cairo_pdf_version_t version;
|
2016-10-01 22:48:05 +09:30
|
|
|
char *filename;
|
|
|
|
|
const char *path = cairo_test_mkdir (CAIRO_TEST_OUTPUT_DIR) ? CAIRO_TEST_OUTPUT_DIR : ".";
|
|
|
|
|
|
2021-07-28 06:18:01 +09:30
|
|
|
/* check_created_pdf() only works with version 1.4. In version 1.5
|
|
|
|
|
* the text that is searched for is compressed. */
|
|
|
|
|
version = check_output ? CAIRO_PDF_VERSION_1_4 : CAIRO_PDF_VERSION_1_5;
|
2016-10-01 22:48:05 +09:30
|
|
|
|
2021-07-28 06:18:01 +09:30
|
|
|
xasprintf (&filename, "%s/%s-%s.pdf",
|
|
|
|
|
path,
|
|
|
|
|
BASENAME,
|
|
|
|
|
check_output ? "1.4" : "1.5");
|
2016-10-01 22:48:05 +09:30
|
|
|
surface = cairo_pdf_surface_create (filename, PAGE_WIDTH, PAGE_HEIGHT);
|
|
|
|
|
|
2021-07-28 06:18:01 +09:30
|
|
|
cairo_pdf_surface_restrict_to_version (surface, version);
|
2021-07-26 18:33:34 +09:30
|
|
|
|
2016-10-01 22:48:05 +09:30
|
|
|
cr = cairo_create (surface);
|
|
|
|
|
create_document (surface, cr);
|
|
|
|
|
|
|
|
|
|
status = cairo_status (cr);
|
|
|
|
|
cairo_destroy (cr);
|
|
|
|
|
cairo_surface_finish (surface);
|
|
|
|
|
status2 = cairo_surface_status (surface);
|
2021-07-24 21:26:22 +09:30
|
|
|
if (status == CAIRO_STATUS_SUCCESS)
|
2016-10-01 22:48:05 +09:30
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-28 06:18:01 +09:30
|
|
|
result = CAIRO_TEST_SUCCESS;
|
|
|
|
|
if (check_output)
|
|
|
|
|
result = check_created_pdf(ctx, filename);
|
2021-01-04 12:45:38 +01:00
|
|
|
|
2016-10-01 22:48:05 +09:30
|
|
|
free (filename);
|
|
|
|
|
|
2021-01-04 12:45:38 +01:00
|
|
|
return result;
|
2016-10-01 22:48:05 +09:30
|
|
|
}
|
|
|
|
|
|
2021-07-28 06:18:01 +09:30
|
|
|
static cairo_test_status_t
|
|
|
|
|
preamble (cairo_test_context_t *ctx)
|
|
|
|
|
{
|
|
|
|
|
cairo_test_status_t result;
|
|
|
|
|
|
|
|
|
|
if (! cairo_test_is_target_enabled (ctx, "pdf"))
|
|
|
|
|
return CAIRO_TEST_UNTESTED;
|
|
|
|
|
|
|
|
|
|
/* Create version 1.5 PDF. This can only be manually checked */
|
|
|
|
|
create_pdf (ctx, FALSE);
|
|
|
|
|
|
|
|
|
|
/* Create version 1.4 PDF and checkout output */
|
|
|
|
|
result = create_pdf (ctx, TRUE);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-01 22:48:05 +09:30
|
|
|
CAIRO_TEST (pdf_tagged_text,
|
|
|
|
|
"Check tagged text, hyperlinks and PDF document features",
|
|
|
|
|
"pdf", /* keywords */
|
|
|
|
|
NULL, /* requirements */
|
|
|
|
|
0, 0,
|
|
|
|
|
preamble, NULL)
|