1
/*
2
 * Copyright © 2016 Adrian Johnson
3
 *
4
 * Permission is hereby granted, free of charge, to any person
5
 * obtaining a copy of this software and associated documentation
6
 * files (the "Software"), to deal in the Software without
7
 * restriction, including without limitation the rights to use, copy,
8
 * modify, merge, publish, distribute, sublicense, and/or sell copies
9
 * of the Software, and to permit persons to whom the Software is
10
 * furnished to do so, subject to the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 *
24
 * Author: Adrian Johnson <ajohnson@redneon.com>
25
 */
26

            
27
#include "cairo-test.h"
28

            
29
#include <stdio.h>
30
#include <string.h>
31
#include <stdlib.h>
32
#include <errno.h>
33
#include <fcntl.h>
34
#include <sys/stat.h>
35

            
36
#if HAVE_UNISTD_H
37
#include <unistd.h>
38
#endif
39

            
40
#ifdef HAVE_MMAP
41
#include <sys/mman.h>
42
#endif
43

            
44
#ifdef _WIN32
45
#include <fcntl.h>
46
#include <sys/types.h>
47
#include <sys/stat.h>
48
#include <io.h>
49
#endif
50

            
51
#include <cairo.h>
52

            
53
#if CAIRO_HAS_PDF_SURFACE
54

            
55
#include <cairo-pdf.h>
56

            
57
/* This test checks PDF with
58
 * - tagged text
59
 * - hyperlinks
60
 * - document outline
61
 * - metadata
62
 * - thumbnails
63
 * - page labels
64
 */
65

            
66
#define BASENAME "pdf-tagged-text.out"
67

            
68
#define PAGE_WIDTH 595
69
#define PAGE_HEIGHT 842
70

            
71
#define HEADING1_SIZE 16
72
#define HEADING2_SIZE 14
73
#define HEADING3_SIZE 12
74
#define TEXT_SIZE 12
75
#define HEADING_HEIGHT 50
76
#define MARGIN 50
77

            
78
struct section {
79
    int level;
80
    const char *heading;
81
    int num_paragraphs;
82
};
83

            
84
static const struct section contents[] = {
85
    { 0, "Chapter 1",     1 },
86
    { 1, "Section 1.1",   4 },
87
    { 2, "Section 1.1.1", 3 },
88
    { 1, "Section 1.2",   2 },
89
    { 2, "Section 1.2.1", 4 },
90
    { 2, "Section 1.2.2", 4 },
91
    { 1, "Section 1.3",   2 },
92
    { 0, "Chapter 2",     1 },
93
    { 1, "Section 2.1",   4 },
94
    { 2, "Section 2.1.1", 3 },
95
    { 1, "Section 2.2",   2 },
96
    { 2, "Section 2.2.1", 4 },
97
    { 2, "Section 2.2.2", 4 },
98
    { 1, "Section 2.3",   2 },
99
    { 0, "Chapter 3",     1 },
100
    { 1, "Section 3.1",   4 },
101
    { 2, "Section 3.1.1", 3 },
102
    { 1, "Section 3.2",   2 },
103
    { 2, "Section 3.2.1", 4 },
104
    { 2, "Section 3.2.2", 4 },
105
    { 1, "Section 3.3",   2 },
106
    { 0, NULL }
107
};
108

            
109
static const char *ipsum_lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing"
110
    " elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
111
    " Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi"
112
    " ut aliquip ex ea commodo consequat. Duis aute irure dolor in"
113
    " reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla"
114
    " pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa"
115
    " qui officia deserunt mollit anim id est laborum.";
116

            
117
static const char *roman_numerals[] = {
118
    "i", "ii", "iii", "iv", "v"
119
};
120

            
121
#define MAX_PARAGRAPH_LINES 20
122

            
123
static const char *utf8_destination = "l\xc3\xa4nk";
124

            
125
static int paragraph_num_lines;
126
static char *paragraph_text[MAX_PARAGRAPH_LINES];
127
static double paragraph_height;
128
static double line_height;
129
static double y_pos;
130
static int outline_parents[10];
131
static int page_num;
132

            
133
static void
134
layout_paragraph (cairo_t *cr)
135
{
136
    char *text, *begin, *end, *prev_end;
137
    cairo_text_extents_t text_extents;
138
    cairo_font_extents_t font_extents;
139

            
140
    cairo_select_font_face (cr, "Serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
141
    cairo_set_font_size(cr, TEXT_SIZE);
142
    cairo_font_extents (cr, &font_extents);
143
    line_height = font_extents.height;
144
    paragraph_height = 0;
145
    paragraph_num_lines = 0;
146
    text = strdup (ipsum_lorem);
147
    begin = text;
148
    end = text;
149
    prev_end = end;
150
    while (*begin) {
151
	end = strchr(end, ' ');
152
	if (!end) {
153
	    paragraph_text[paragraph_num_lines++] = strdup (begin);
154
	    break;
155
	}
156
	*end = 0;
157
	cairo_text_extents (cr, begin, &text_extents);
158
	*end = ' ';
159
	if (text_extents.width + 2*MARGIN > PAGE_WIDTH) {
160
	    int len = prev_end - begin;
161
	    char *s = xmalloc (len);
162
	    memcpy (s, begin, len);
163
	    s[len-1] = 0;
164
	    paragraph_text[paragraph_num_lines++] = s;
165
	    begin = prev_end + 1;
166
	}
167
	prev_end = end;
168
	end++;
169
    }
170
    paragraph_height = line_height * (paragraph_num_lines + 1);
171
    free (text);
172
}
173

            
174
static void
175
draw_paragraph (cairo_t *cr)
176
{
177
    int i;
178

            
179
    cairo_select_font_face (cr, "Serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
180
    cairo_set_font_size(cr, TEXT_SIZE);
181
    cairo_tag_begin (cr, "P", NULL);
182
    for (i = 0; i < paragraph_num_lines; i++) {
183
	cairo_move_to (cr, MARGIN, y_pos);
184
	cairo_show_text (cr, paragraph_text[i]);
185
	y_pos += line_height;
186
    }
187
    cairo_tag_end (cr, "P");
188
    y_pos += line_height;
189
}
190

            
191
static void
192
draw_page_num (cairo_surface_t *surface, cairo_t *cr, const char *prefix, int num)
193
{
194
    char buf[100];
195

            
196
    buf[0] = 0;
197
    if (prefix)
198
	strcat (buf, prefix);
199

            
200
    if (num)
201
	sprintf (buf + strlen(buf), "%d", num);
202

            
203
    cairo_save (cr);
204
    cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
205
    cairo_set_font_size(cr, 12);
206
    cairo_set_source_rgb (cr, 0, 0, 0);
207
    cairo_move_to (cr, PAGE_WIDTH/2, PAGE_HEIGHT - MARGIN);
208
    cairo_show_text (cr, buf);
209
    cairo_restore (cr);
210
    cairo_pdf_surface_set_page_label (surface, buf);
211
}
212

            
213
static void
214
draw_contents (cairo_surface_t *surface, cairo_t *cr, const struct section *section)
215
{
216
    char *attrib;
217

            
218
    xasprintf (&attrib, "dest='%s'", section->heading);
219
    cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
220
    switch (section->level) {
221
	case 0:
222
	    cairo_set_font_size(cr, HEADING1_SIZE);
223
	    break;
224
	case 1:
225
	    cairo_set_font_size(cr, HEADING2_SIZE);
226
	    break;
227
	case 2:
228
	    cairo_set_font_size(cr, HEADING3_SIZE);
229
	    break;
230
    }
231

            
232
    if (y_pos + HEADING_HEIGHT + MARGIN > PAGE_HEIGHT) {
233
	cairo_show_page (cr);
234
	draw_page_num (surface, cr, roman_numerals[page_num++], 0);
235
	y_pos = MARGIN;
236
    }
237
    cairo_move_to (cr, MARGIN, y_pos);
238
    cairo_save (cr);
239
    cairo_set_source_rgb (cr, 0, 0, 1);
240
    cairo_tag_begin (cr, "TOCI", NULL);
241
    cairo_tag_begin (cr, "Reference", NULL);
242
    cairo_tag_begin (cr, CAIRO_TAG_LINK, attrib);
243
    cairo_show_text (cr, section->heading);
244
    cairo_tag_end (cr, CAIRO_TAG_LINK);
245
    cairo_tag_end (cr, "Reference");
246
    cairo_tag_end (cr, "TOCI");
247
    cairo_restore (cr);
248
    y_pos += HEADING_HEIGHT;
249
    free (attrib);
250
}
251

            
252
static void
253
draw_section (cairo_surface_t *surface, cairo_t *cr, const struct section *section)
254
{
255
    int flags, i;
256
    char *name_attrib;
257
    char *dest_attrib;
258

            
259
    cairo_tag_begin (cr, "Sect", NULL);
260
    xasprintf(&name_attrib, "name='%s'", section->heading);
261
    xasprintf(&dest_attrib, "dest='%s'", section->heading);
262
    cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
263
    if (section->level == 0) {
264
	cairo_show_page (cr);
265
	draw_page_num (surface, cr, NULL, page_num++);
266
	cairo_set_font_size(cr, HEADING1_SIZE);
267
	cairo_move_to (cr, MARGIN, MARGIN);
268
	cairo_tag_begin (cr, "H1", NULL);
269
	cairo_tag_begin (cr, CAIRO_TAG_DEST, name_attrib);
270
	cairo_show_text (cr, section->heading);
271
	cairo_tag_end (cr, CAIRO_TAG_DEST);
272
	cairo_tag_end (cr, "H1");
273
	y_pos = MARGIN + HEADING_HEIGHT;
274
	flags = CAIRO_PDF_OUTLINE_FLAG_BOLD | CAIRO_PDF_OUTLINE_FLAG_OPEN;
275
	outline_parents[0] = cairo_pdf_surface_add_outline (surface,
276
							    CAIRO_PDF_OUTLINE_ROOT,
277
							    section->heading,
278
							    dest_attrib,
279
							    flags);
280
    } else {
281
	if (section->level == 1) {
282
	    cairo_set_font_size(cr, HEADING2_SIZE);
283
	    flags = 0;
284
	} else {
285
	    cairo_set_font_size(cr, HEADING3_SIZE);
286
	    flags = CAIRO_PDF_OUTLINE_FLAG_ITALIC;
287
	}
288

            
289
	if (y_pos + HEADING_HEIGHT + paragraph_height + MARGIN > PAGE_HEIGHT) {
290
	    cairo_show_page (cr);
291
	    draw_page_num (surface, cr, NULL, page_num++);
292
	    y_pos = MARGIN;
293
	}
294
	cairo_move_to (cr, MARGIN, y_pos);
295
	if (section->level == 1)
296
	    cairo_tag_begin (cr, "H2", NULL);
297
	else
298
	    cairo_tag_begin (cr, "H3", NULL);
299
	cairo_tag_begin (cr, CAIRO_TAG_DEST, name_attrib);
300
	cairo_show_text (cr, section->heading);
301
	cairo_tag_end (cr, CAIRO_TAG_DEST);
302
	if (section->level == 1)
303
	    cairo_tag_end (cr, "H2");
304
	else
305
	    cairo_tag_end (cr, "H3");
306
	y_pos += HEADING_HEIGHT;
307
	outline_parents[section->level] = cairo_pdf_surface_add_outline (surface,
308
									 outline_parents[section->level - 1],
309
									 section->heading,
310
									 dest_attrib,
311
									 flags);
312
    }
313

            
314
    for (i = 0; i < section->num_paragraphs; i++) {
315
	if (y_pos + paragraph_height + MARGIN > PAGE_HEIGHT) {
316
	    cairo_show_page (cr);
317
	    draw_page_num (surface, cr, NULL, page_num++);
318
		y_pos = MARGIN;
319
	}
320
	draw_paragraph (cr);
321
    }
322
    cairo_tag_end (cr, "Sect");
323
    free (name_attrib);
324
    free (dest_attrib);
325
}
326

            
327
static void
328
draw_cover (cairo_surface_t *surface, cairo_t *cr)
329
{
330
    cairo_text_extents_t text_extents;
331
    char *attrib;
332
    cairo_rectangle_t url_box;
333
    const char *cairo_url = "https://www.cairographics.org/";
334
    const double url_box_margin = 20.0;
335

            
336
    cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='cover'  internal");
337
    cairo_tag_end (cr, CAIRO_TAG_DEST);
338

            
339
    cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
340
    cairo_set_font_size(cr, 16);
341
    cairo_move_to (cr, PAGE_WIDTH/3, 0.15*PAGE_HEIGHT);
342
    cairo_tag_begin (cr, "Span", NULL);
343
    cairo_show_text (cr, "PDF Features Test");
344
    cairo_tag_end (cr, "Span");
345

            
346
    /* Test URL link using "rect" attribute. The entire rectangle surrounding the URL should be a clickable link.  */
347
    cairo_move_to (cr, PAGE_WIDTH/3, 0.2*PAGE_HEIGHT);
348
    cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
349
    cairo_set_font_size(cr, TEXT_SIZE);
350
    cairo_set_source_rgb (cr, 0, 0, 1);
351
    cairo_show_text (cr, cairo_url);
352
    cairo_text_extents (cr, cairo_url, &text_extents);
353
    url_box.x = PAGE_WIDTH/3 - url_box_margin;
354
    url_box.y = 0.2*PAGE_HEIGHT - url_box_margin;
355
    url_box.width = text_extents.width + 2*url_box_margin;
356
    url_box.height = -text_extents.height + 2*url_box_margin;
357
    cairo_rectangle(cr, url_box.x, url_box.y, url_box.width, url_box.height);
358
    cairo_stroke(cr);
359
    xasprintf(&attrib, "rect=[%f %f %f %f] uri=\'%s\'",
360
             url_box.x, url_box.y, url_box.width, url_box.height, cairo_url);
361
    cairo_tag_begin (cr, CAIRO_TAG_LINK, attrib);
362
    cairo_tag_end (cr, CAIRO_TAG_LINK);
363
    free (attrib);
364

            
365
    /* Create link to not yet emmited page number */
366
    cairo_tag_begin (cr, CAIRO_TAG_LINK, "page=5");
367
    cairo_move_to (cr, PAGE_WIDTH/3, 0.25*PAGE_HEIGHT);
368
    cairo_show_text (cr, "link to page 5");
369
    cairo_tag_end (cr, CAIRO_TAG_LINK);
370

            
371
    /* Create link to not yet emmited destination */
372
    cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='Section 3.3'");
373
    cairo_move_to (cr, PAGE_WIDTH/3, 0.3*PAGE_HEIGHT);
374
    cairo_show_text (cr, "link to page section 3.3");
375
    cairo_tag_end (cr, CAIRO_TAG_LINK);
376

            
377
    /* Create link to external file */
378
    cairo_move_to (cr, PAGE_WIDTH/3, 0.35*PAGE_HEIGHT);
379
    cairo_tag_begin (cr, CAIRO_TAG_LINK, "file='foo.pdf' page=1");
380
    cairo_show_text (cr, "link file 'foo.pdf'");
381
    cairo_tag_end (cr, CAIRO_TAG_LINK);
382

            
383
    /* Create link to missing dest */
384
    cairo_move_to (cr, PAGE_WIDTH/3, 0.4*PAGE_HEIGHT);
385
    cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='I don\\'t exist'");
386
    cairo_show_text (cr, "link to missing dest");
387
    cairo_tag_end (cr, CAIRO_TAG_LINK);
388

            
389
    /* Create link to missing dest with URI fallback*/
390
    cairo_move_to (cr, PAGE_WIDTH/3, 0.45*PAGE_HEIGHT);
391
    xasprintf(&attrib, "dest='I also don\\'t exist' uri='%s'", cairo_url);
392
    cairo_tag_begin (cr, CAIRO_TAG_LINK, attrib);
393
    cairo_show_text (cr, "link to missing dest with uri fallback");
394
    cairo_tag_end (cr, CAIRO_TAG_LINK);
395
    free (attrib);
396

            
397
    /* Create link to utf8 dest */
398
    cairo_move_to (cr, PAGE_WIDTH/3, 0.5*PAGE_HEIGHT);
399
    xasprintf(&attrib, "dest='%s'", utf8_destination);
400
    cairo_tag_begin (cr, CAIRO_TAG_LINK, attrib);
401
    cairo_show_text (cr, "link to utf8 dest");
402
    cairo_tag_end (cr, CAIRO_TAG_LINK);
403
    free (attrib);
404

            
405
    draw_page_num (surface, cr, "cover", 0);
406
}
407

            
408
static void
409
create_document (cairo_surface_t *surface, cairo_t *cr)
410
{
411
    char *attrib;
412

            
413
    layout_paragraph (cr);
414

            
415
    cairo_pdf_surface_set_thumbnail_size (surface, PAGE_WIDTH/10, PAGE_HEIGHT/10);
416

            
417
    cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_TITLE, "PDF Features Test");
418
    cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_AUTHOR, "cairo test suite");
419
    cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_SUBJECT, "cairo test");
420
    cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_KEYWORDS,
421
				    "tags, links, outline, page labels, metadata, thumbnails");
422
    cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_CREATOR, "pdf-features");
423
    cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_CREATE_DATE, "2016-01-01T12:34:56+10:30");
424
    cairo_pdf_surface_set_metadata (surface, CAIRO_PDF_METADATA_MOD_DATE, "2016-06-21T05:43:21Z");
425

            
426
    cairo_pdf_surface_set_custom_metadata (surface, "DocumentNumber", "12345");
427
    /* Include some non ASCII characters */
428
    cairo_pdf_surface_set_custom_metadata (surface, "Document Name", "\xc2\xab""cairo test\xc2\xbb");
429
    /* Test unsetting custom metadata. "DocumentNumber" should not be emitted. */
430
    cairo_pdf_surface_set_custom_metadata (surface, "DocumentNumber", "");
431

            
432
    cairo_tag_begin (cr, "Document", NULL);
433

            
434
    draw_cover (surface, cr);
435
    cairo_pdf_surface_add_outline (surface,
436
				   CAIRO_PDF_OUTLINE_ROOT,
437
				   "Cover", "page=1",
438
                                   CAIRO_PDF_OUTLINE_FLAG_BOLD);
439

            
440
    /* Create a simple link annotation. */
441
    cairo_tag_begin (cr, CAIRO_TAG_LINK, "uri='http://example.org' rect=[10 10 20 20]");
442
    cairo_tag_end (cr, CAIRO_TAG_LINK);
443

            
444
    /* Try to create a link annotation while the clip is empty;
445
     * it will still be emitted.
446
     */
447
    cairo_save (cr);
448
    cairo_new_path (cr);
449
    cairo_rectangle (cr, 100, 100, 50, 0);
450
    cairo_clip (cr);
451
    cairo_tag_begin (cr, CAIRO_TAG_LINK, "uri='http://example.com' rect=[100 100 20 20]");
452
    cairo_tag_end (cr, CAIRO_TAG_LINK);
453
    cairo_restore (cr);
454

            
455
    /* An annotation whose rect has a negative coordinate. */
456
    cairo_tag_begin (cr, CAIRO_TAG_LINK, "uri='http://127.0.0.1/' rect=[10.0 -10.0 100.0 100.0]");
457
    cairo_tag_end (cr, CAIRO_TAG_LINK);
458

            
459

            
460
    /* Distilled from Mozilla bug https://bugzilla.mozilla.org/show_bug.cgi?id=1725743:
461
     * attempting to emit a Destination tag within a pushed group will lead to an
462
     * assertion in _cairo_pdf_interchange_end_structure_tag when processing a
463
     * following LINK tag that is outside the pushed group.
464
     */
465

            
466
    /* PushLayer */
467
    cairo_push_group_with_content (cr, CAIRO_CONTENT_COLOR_ALPHA);
468

            
469
    /* Destination */
470
    cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='a' x=42 y=42");
471
    cairo_tag_end (cr, CAIRO_TAG_DEST);
472

            
473
    /* PopLayer */
474
    cairo_pop_group_to_source (cr);
475
    cairo_paint_with_alpha (cr, 1);
476
    cairo_set_source_rgb (cr, 0, 0, 0);
477

            
478
    /* Link */
479
    cairo_tag_begin (cr, CAIRO_TAG_LINK, "rect=[100 200 300 400] uri='http://127.0.0.1/'");
480
    cairo_tag_end (cr, CAIRO_TAG_LINK);
481

            
482
    /* End of extra Mozilla testcase. */
483

            
484

            
485
    cairo_show_page (cr);
486

            
487
    page_num = 0;
488
    draw_page_num (surface, cr, roman_numerals[page_num++], 0);
489
    y_pos = MARGIN;
490

            
491
    cairo_pdf_surface_add_outline (surface,
492
				   CAIRO_PDF_OUTLINE_ROOT,
493
				   "Contents", "dest='TOC'",
494
                                   CAIRO_PDF_OUTLINE_FLAG_BOLD);
495

            
496
    cairo_tag_begin (cr, CAIRO_TAG_DEST, "name='TOC' internal");
497
    cairo_tag_begin (cr, "TOC", NULL);
498
    const struct section *sect = contents;
499
    while (sect->heading) {
500
	draw_contents (surface, cr, sect);
501
	sect++;
502
    }
503
    cairo_tag_end (cr, "TOC");
504
    cairo_tag_end (cr, CAIRO_TAG_DEST);
505

            
506
    page_num = 1;
507
    sect = contents;
508
    while (sect->heading) {
509
	draw_section (surface, cr, sect);
510
	sect++;
511
    }
512

            
513
    cairo_show_page (cr);
514

            
515
    cairo_set_source_rgb (cr, 0, 0, 1);
516

            
517
    cairo_tag_begin (cr, CAIRO_TAG_LINK, "dest='cover'");
518
    cairo_move_to (cr, PAGE_WIDTH/3, 2*PAGE_HEIGHT/5);
519
    cairo_show_text (cr, "link to cover");
520
    cairo_tag_end (cr, CAIRO_TAG_LINK);
521

            
522
    cairo_tag_begin (cr, CAIRO_TAG_LINK, "page=3");
523
    cairo_move_to (cr, PAGE_WIDTH/3, 3*PAGE_HEIGHT/5);
524
    cairo_show_text (cr, "link to page 3");
525
    cairo_tag_end (cr, CAIRO_TAG_LINK);
526

            
527
    /* Create utf8 dest */
528
    cairo_move_to (cr, PAGE_WIDTH/3, 4*PAGE_HEIGHT/5);
529
    xasprintf(&attrib, "name='%s'", utf8_destination);
530
    cairo_tag_begin (cr, CAIRO_TAG_DEST, attrib);
531
    cairo_show_text (cr, utf8_destination);
532
    cairo_tag_end (cr, CAIRO_TAG_DEST);
533
    free (attrib);
534

            
535
    cairo_tag_end (cr, "Document");
536
}
537

            
538
#ifdef HAVE_MMAP
539
static cairo_test_status_t
540
check_contains_string(cairo_test_context_t *ctx, const void *hay, size_t size, const char *needle)
541
{
542
    if (memmem(hay, size, needle, strlen(needle)))
543
        return CAIRO_TEST_SUCCESS;
544

            
545
    cairo_test_log (ctx, "Failed to find expected string in generated PDF: %s\n", needle);
546
    return CAIRO_TEST_FAILURE;
547
}
548
#endif
549

            
550
static cairo_test_status_t
551
check_created_pdf(cairo_test_context_t *ctx, const char* filename)
552
{
553
    cairo_test_status_t result = CAIRO_TEST_SUCCESS;
554
    int fd;
555
    struct stat st;
556
#ifdef HAVE_MMAP
557
    void *contents;
558
#endif
559

            
560
    fd = open(filename, O_RDONLY, 0);
561
    if (fd < 0) {
562
        cairo_test_log (ctx, "Failed to open generated PDF file %s: %s\n", filename, strerror(errno));
563
        return CAIRO_TEST_FAILURE;
564
    }
565

            
566
    if (fstat(fd, &st) == -1)
567
    {
568
        cairo_test_log (ctx, "Failed to stat generated PDF file %s: %s\n", filename, strerror(errno));
569
        close(fd);
570
        return CAIRO_TEST_FAILURE;
571
    }
572

            
573
    if (st.st_size == 0)
574
    {
575
        cairo_test_log (ctx, "Generated PDF file %s is empty\n", filename);
576
        close(fd);
577
        return CAIRO_TEST_FAILURE;
578
    }
579

            
580
#ifdef HAVE_MMAP
581
    contents = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
582
    if (contents == NULL)
583
    {
584
        cairo_test_log (ctx, "Failed to mmap generated PDF file %s: %s\n", filename, strerror(errno));
585
        close(fd);
586
        return CAIRO_TEST_FAILURE;
587
    }
588

            
589
    /* check metadata */
590
    result |= check_contains_string(ctx, contents, st.st_size, "/Title (PDF Features Test)");
591
    result |= check_contains_string(ctx, contents, st.st_size, "/Author (cairo test suite)");
592
    result |= check_contains_string(ctx, contents, st.st_size, "/Creator (pdf-features)");
593
    result |= check_contains_string(ctx, contents, st.st_size, "/CreationDate (20160101123456+10'30')");
594
    result |= check_contains_string(ctx, contents, st.st_size, "/ModDate (20160621054321Z)");
595

            
596
    /* check that both the example.org and example.com links were generated */
597
    result |= check_contains_string(ctx, contents, st.st_size, "http://example.org");
598
    result |= check_contains_string(ctx, contents, st.st_size, "http://example.com");
599

            
600
    // TODO: add more checks
601

            
602
    munmap(contents, st.st_size);
603
#endif
604

            
605
    close(fd);
606

            
607
    return result;
608
}
609

            
610
static cairo_test_status_t
611
create_pdf (cairo_test_context_t *ctx, cairo_bool_t check_output)
612
{
613
    cairo_surface_t *surface;
614
    cairo_t *cr;
615
    cairo_status_t status, status2;
616
    cairo_test_status_t result;
617
    cairo_pdf_version_t version;
618
    char *filename;
619
    const char *path = cairo_test_mkdir (CAIRO_TEST_OUTPUT_DIR) ? CAIRO_TEST_OUTPUT_DIR : ".";
620

            
621
    /* check_created_pdf() only works with version 1.4. In version 1.5
622
     * the text that is searched for is compressed. */
623
    version = check_output ? CAIRO_PDF_VERSION_1_4 : CAIRO_PDF_VERSION_1_5;
624

            
625
    xasprintf (&filename, "%s/%s-%s.pdf",
626
               path,
627
               BASENAME,
628
               check_output ? "1.4" : "1.5");
629
    surface = cairo_pdf_surface_create (filename, PAGE_WIDTH, PAGE_HEIGHT);
630

            
631
    cairo_pdf_surface_restrict_to_version (surface, version);
632

            
633
    cr = cairo_create (surface);
634
    create_document (surface, cr);
635

            
636
    status = cairo_status (cr);
637
    cairo_destroy (cr);
638
    cairo_surface_finish (surface);
639
    status2 = cairo_surface_status (surface);
640
    if (status == CAIRO_STATUS_SUCCESS)
641
	status = status2;
642

            
643
    cairo_surface_destroy (surface);
644
    if (status) {
645
	cairo_test_log (ctx, "Failed to create pdf surface for file %s: %s\n",
646
			filename, cairo_status_to_string (status));
647
	return CAIRO_TEST_FAILURE;
648
    }
649

            
650
    result = CAIRO_TEST_SUCCESS;
651
    if (check_output)
652
        result = check_created_pdf(ctx, filename);
653

            
654
    free (filename);
655

            
656
    return result;
657
}
658

            
659
static cairo_test_status_t
660
1
preamble (cairo_test_context_t *ctx)
661
{
662
    cairo_test_status_t result;
663

            
664
1
    if (! cairo_test_is_target_enabled (ctx, "pdf"))
665
1
	return CAIRO_TEST_UNTESTED;
666

            
667
    /* Create version 1.5 PDF. This can only be manually checked */
668
    create_pdf (ctx, FALSE);
669

            
670
    /* Create version 1.4 PDF and checkout output */
671
    result = create_pdf (ctx, TRUE);
672

            
673

            
674
    return result;
675
}
676

            
677
1
CAIRO_TEST (pdf_tagged_text,
678
	    "Check tagged text, hyperlinks and PDF document features",
679
	    "pdf", /* keywords */
680
	    NULL, /* requirements */
681
	    0, 0,
682
	    preamble, NULL)
683

            
684
#endif /* CAIRO_HAS_PDF_SURFACE */