1
/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
2
/* cairo - a vector graphics library with display and print output
3
 *
4
 * Copyright © 2016 Adrian Johnson
5
 *
6
 * This library is free software; you can redistribute it and/or
7
 * modify it either under the terms of the GNU Lesser General Public
8
 * License version 2.1 as published by the Free Software Foundation
9
 * (the "LGPL") or, at your option, under the terms of the Mozilla
10
 * Public License Version 1.1 (the "MPL"). If you do not alter this
11
 * notice, a recipient may use your version of this file under either
12
 * the MPL or the LGPL.
13
 *
14
 * You should have received a copy of the LGPL along with this library
15
 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
16
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
17
 * You should have received a copy of the MPL along with this library
18
 * in the file COPYING-MPL-1.1
19
 *
20
 * The contents of this file are subject to the Mozilla Public License
21
 * Version 1.1 (the "License"); you may not use this file except in
22
 * compliance with the License. You may obtain a copy of the License at
23
 * http://www.mozilla.org/MPL/
24
 *
25
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
26
 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
27
 * the specific language governing rights and limitations.
28
 *
29
 * The Original Code is the cairo graphics library.
30
 *
31
 * The Initial Developer of the Original Code is Adrian Johnson.
32
 *
33
 * Contributor(s):
34
 *	Adrian Johnson <ajohnson@redneon.com>
35
 */
36

            
37

            
38
/* PDF Document Interchange features:
39
 *  - metadata
40
 *  - document outline
41
 *  - tagged pdf
42
 *  - hyperlinks
43
 *  - page labels
44
 */
45

            
46
#define _DEFAULT_SOURCE /* for localtime_r(), gmtime_r(), snprintf(), strdup() */
47
#include "cairoint.h"
48

            
49
#include "cairo-pdf.h"
50
#include "cairo-pdf-surface-private.h"
51

            
52
#include "cairo-array-private.h"
53
#include "cairo-error-private.h"
54
#include "cairo-output-stream-private.h"
55
#include "cairo-recording-surface-inline.h"
56
#include "cairo-recording-surface-private.h"
57
#include "cairo-surface-snapshot-inline.h"
58

            
59
#include <time.h>
60

            
61
#ifndef HAVE_LOCALTIME_R
62
#define localtime_r(T, BUF) (*(BUF) = *localtime (T))
63
#endif
64
#ifndef HAVE_GMTIME_R
65
#define gmtime_r(T, BUF) (*(BUF) = *gmtime (T))
66
#endif
67

            
68
/* #define DEBUG_PDF_INTERCHANGE 1 */
69

            
70
#if DEBUG_PDF_INTERCHANGE
71
static void
72
print_tree (cairo_pdf_surface_t *surface, cairo_pdf_struct_tree_node_t *node);
73

            
74
static void
75
print_command (cairo_pdf_command_t *command, int indent);
76

            
77
static void
78
print_command_list(cairo_pdf_command_list_t *command_list);
79
#endif
80

            
81
static void
82
_cairo_pdf_command_init_key (cairo_pdf_command_entry_t *key)
83
{
84
    key->base.hash = _cairo_hash_uintptr (_CAIRO_HASH_INIT_VALUE, (uintptr_t)key->recording_id);
85
    key->base.hash = _cairo_hash_uintptr (key->base.hash, (uintptr_t)key->command_id);
86
}
87

            
88
static cairo_bool_t
89
_cairo_pdf_command_equal (const void *key_a, const void *key_b)
90
{
91
    const cairo_pdf_command_entry_t *a = key_a;
92
    const cairo_pdf_command_entry_t *b = key_b;
93

            
94
    return a->recording_id == b->recording_id && a->command_id == b->command_id;
95
}
96

            
97
static void
98
_cairo_pdf_command_pluck (void *entry, void *closure)
99
{
100
    cairo_pdf_command_entry_t *dest = entry;
101
    cairo_hash_table_t *table = closure;
102

            
103
    _cairo_hash_table_remove (table, &dest->base);
104
    free (dest);
105
}
106

            
107
static cairo_pdf_struct_tree_node_t *
108
lookup_node_for_command (cairo_pdf_surface_t    *surface,
109
			 unsigned int            recording_id,
110
			 unsigned int            command_id)
111
{
112
    cairo_pdf_command_entry_t entry_key;
113
    cairo_pdf_command_entry_t *entry;
114
    cairo_pdf_interchange_t *ic = &surface->interchange;
115

            
116
    entry_key.recording_id = recording_id;
117
    entry_key.command_id = command_id;
118
    _cairo_pdf_command_init_key (&entry_key);
119
    entry = _cairo_hash_table_lookup (ic->command_to_node_map, &entry_key.base);
120
    assert (entry != NULL);
121
    return entry->node;
122
}
123

            
124
static cairo_int_status_t
125
command_list_add (cairo_pdf_surface_t    *surface,
126
		  unsigned int            command_id,
127
		  cairo_pdf_operation_t   flags)
128
{
129
    cairo_pdf_interchange_t *ic = &surface->interchange;
130
    cairo_pdf_command_t command;
131
    cairo_int_status_t status;
132

            
133
    unsigned num_elements = _cairo_array_num_elements (&ic->current_commands->commands);
134
    if (command_id > num_elements) {
135
	void *elements;
136
	unsigned additional_elements = command_id - num_elements;
137
	status = _cairo_array_allocate (&ic->current_commands->commands, additional_elements, &elements);
138
	if (unlikely (status))
139
	    return status;
140
	memset (elements, 0, additional_elements * sizeof(cairo_pdf_command_t));
141
    }
142

            
143
    command.group = NULL;
144
    command.node = NULL;
145
    command.command_id = command_id;
146
    command.mcid_index = 0;
147
    command.flags = flags;
148
    return _cairo_array_append (&ic->current_commands->commands, &command);
149
}
150

            
151
static cairo_int_status_t
152
command_list_push_group (cairo_pdf_surface_t    *surface,
153
			 unsigned int            command_id,
154
			 cairo_surface_t        *recording_surface,
155
			 unsigned int            region_id)
156
{
157
    cairo_pdf_interchange_t *ic = &surface->interchange;
158
    cairo_pdf_command_t *command;
159
    cairo_pdf_command_list_t *group;
160
    cairo_pdf_recording_surface_commands_t recording_commands;
161
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
162

            
163
    group = _cairo_calloc (sizeof(cairo_pdf_command_list_t));
164
    _cairo_array_init (&group->commands, sizeof(cairo_pdf_command_t));
165
    group->parent = ic->current_commands;
166

            
167
    command_list_add (surface, command_id, PDF_GROUP);
168
    command = _cairo_array_index (&ic->current_commands->commands, command_id);
169
    command->group = group;
170
    ic->current_commands = group;
171

            
172
    recording_commands.recording_surface = recording_surface;
173
    recording_commands.command_list = group;
174
    recording_commands.region_id = region_id;
175
    status = _cairo_array_append (&ic->recording_surface_commands, &recording_commands);
176

            
177
    return status;
178
}
179

            
180
static void
181
command_list_pop_group (cairo_pdf_surface_t    *surface)
182
{
183
    cairo_pdf_interchange_t *ic = &surface->interchange;
184

            
185
    ic->current_commands = ic->current_commands->parent;
186
}
187

            
188
static cairo_bool_t
189
command_list_is_group (cairo_pdf_surface_t    *surface,
190
		       unsigned int            command_id)
191
{
192
    cairo_pdf_interchange_t *ic = &surface->interchange;
193
    cairo_pdf_command_t *command;
194
    unsigned num_elements = _cairo_array_num_elements (&ic->current_commands->commands);
195

            
196
    if (command_id >= num_elements)
197
	return FALSE;
198

            
199
    command = _cairo_array_index (&ic->current_commands->commands, command_id);
200
    return command->flags == PDF_GROUP;
201
}
202

            
203

            
204
/* Is there any content between current command and next
205
 * begin/end/group? */
206
static cairo_bool_t
207
command_list_has_content (cairo_pdf_surface_t    *surface,
208
			  unsigned int            command_id,
209
			  unsigned int           *content_command_id)
210
{
211
    cairo_pdf_interchange_t *ic = &surface->interchange;
212
    cairo_pdf_command_t *command;
213
    unsigned i;
214
    unsigned num_elements = _cairo_array_num_elements (&ic->current_commands->commands);
215

            
216
    for (i = command_id + 1; i < num_elements; i++) {
217
	command = _cairo_array_index (&ic->current_commands->commands, i);
218
	switch (command->flags) {
219
	    case PDF_CONTENT:
220
		if (content_command_id)
221
		    *content_command_id = i;
222
		return TRUE;
223
		break;
224
	    case PDF_BEGIN:
225
	    case PDF_END:
226
	    case PDF_GROUP:
227
		return FALSE;
228
	    case PDF_NONE:
229
		break;
230
	}
231
    }
232
    return FALSE;
233
}
234

            
235
static void
236
command_list_set_mcid (cairo_pdf_surface_t          *surface,
237
		       unsigned int                  command_id,
238
		       cairo_pdf_struct_tree_node_t *node,
239
		       int                           mcid_index)
240
{
241
    cairo_pdf_interchange_t *ic = &surface->interchange;
242
    cairo_pdf_command_t *command;
243

            
244
    command = _cairo_array_index (&ic->current_commands->commands, command_id);
245
    command->node = node;
246
    command->mcid_index = mcid_index;
247
}
248

            
249
static void
250
command_list_set_current_recording_commands (cairo_pdf_surface_t    *surface,
251
					     cairo_surface_t        *recording_surface,
252
					     unsigned int            region_id)
253
{
254
    cairo_pdf_interchange_t *ic = &surface->interchange;
255
    unsigned i;
256
    cairo_pdf_recording_surface_commands_t *commands;
257
    unsigned num_elements = _cairo_array_num_elements (&ic->recording_surface_commands);
258

            
259
    for (i = 0; i < num_elements; i++) {
260
	commands = _cairo_array_index (&ic->recording_surface_commands, i);
261
	if (commands->region_id == region_id) {
262
	    ic->current_commands = commands->command_list;
263
	    return;
264
	}
265
    }
266
    ASSERT_NOT_REACHED; /* recording_surface not found */
267
}
268

            
269
static void
270
update_mcid_order (cairo_pdf_surface_t       *surface,
271
		   cairo_pdf_command_list_t  *command_list)
272
{
273
    cairo_pdf_interchange_t *ic = &surface->interchange;
274
    cairo_pdf_command_t *command;
275
    cairo_pdf_page_mcid_t *mcid_elem;
276
    unsigned i;
277
    unsigned num_elements = _cairo_array_num_elements (&command_list->commands);
278

            
279
    for (i = 0; i < num_elements; i++) {
280
	command = _cairo_array_index (&command_list->commands, i);
281
	if (command->node) {
282
	    mcid_elem = _cairo_array_index (&command->node->mcid, command->mcid_index);
283
	    mcid_elem->order = ic->mcid_order++;
284
	}
285

            
286
	if (command->group)
287
	    update_mcid_order (surface, command->group);
288
    }
289
}
290

            
291
static void
292
_cairo_pdf_content_tag_init_key (cairo_pdf_content_tag_t *key)
293
{
294
    key->base.hash = _cairo_hash_string (key->node->attributes.content.id);
295
}
296

            
297
static cairo_bool_t
298
_cairo_pdf_content_tag_equal (const void *key_a, const void *key_b)
299
{
300
    const cairo_pdf_content_tag_t *a = key_a;
301
    const cairo_pdf_content_tag_t *b = key_b;
302

            
303
    return strcmp (a->node->attributes.content.id, b->node->attributes.content.id) == 0;
304
}
305

            
306
static void
307
_cairo_pdf_content_tag_pluck (void *entry, void *closure)
308
{
309
    cairo_pdf_content_tag_t *content_tag = entry;
310
    cairo_hash_table_t *table = closure;
311

            
312
    _cairo_hash_table_remove (table, &content_tag->base);
313
    free (content_tag);
314
}
315

            
316
static cairo_status_t
317
lookup_content_node_for_ref_node (cairo_pdf_surface_t           *surface,
318
				  cairo_pdf_struct_tree_node_t  *ref_node,
319
				  cairo_pdf_struct_tree_node_t **node)
320
{
321
    cairo_pdf_content_tag_t entry_key;
322
    cairo_pdf_content_tag_t *entry;
323
    cairo_pdf_interchange_t *ic = &surface->interchange;
324

            
325
    entry_key.node = ref_node;
326
    _cairo_pdf_content_tag_init_key (&entry_key);
327
    entry = _cairo_hash_table_lookup (ic->content_tag_map, &entry_key.base);
328
    if (!entry) {
329
	return _cairo_tag_error ("CONTENT_REF ref='%s' not found",
330
				 ref_node->attributes.content_ref.ref);
331
    }
332

            
333
    *node = entry->node;
334
    return CAIRO_STATUS_SUCCESS;
335
}
336

            
337
static void
338
write_rect_to_pdf_quad_points (cairo_output_stream_t   *stream,
339
			       const cairo_rectangle_t *rect,
340
			       double                   surface_height)
341
{
342
    _cairo_output_stream_printf (stream,
343
				 "%f %f %f %f %f %f %f %f",
344
				 rect->x,
345
				 surface_height - rect->y,
346
				 rect->x + rect->width,
347
				 surface_height - rect->y,
348
				 rect->x + rect->width,
349
				 surface_height - (rect->y + rect->height),
350
				 rect->x,
351
				 surface_height - (rect->y + rect->height));
352
}
353

            
354
static void
355
write_rect_int_to_pdf_bbox (cairo_output_stream_t       *stream,
356
			    const cairo_rectangle_int_t *rect,
357
			    double                       surface_height)
358
{
359
    _cairo_output_stream_printf (stream,
360
				 "%d %f %d %f",
361
				 rect->x,
362
				 surface_height - (rect->y + rect->height),
363
				 rect->x + rect->width,
364
				 surface_height - rect->y);
365
}
366

            
367
static cairo_int_status_t
368
add_tree_node (cairo_pdf_surface_t           *surface,
369
	       cairo_pdf_struct_tree_node_t  *parent,
370
	       const char                    *name,
371
	       const char                    *attributes,
372
	       cairo_pdf_struct_tree_node_t **new_node)
373
{
374
    cairo_pdf_struct_tree_node_t *node;
375
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
376

            
377
    node = _cairo_calloc (sizeof(cairo_pdf_struct_tree_node_t));
378
    if (unlikely (node == NULL))
379
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
380

            
381
    node->name = strdup (name);
382
    node->res = _cairo_pdf_surface_new_object (surface);
383
    if (node->res.id == 0)
384
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
385

            
386
    node->parent = parent;
387
    cairo_list_init (&node->children);
388
    _cairo_array_init (&node->mcid, sizeof (cairo_pdf_page_mcid_t));
389
    node->annot = NULL;
390
    node->extents.valid = FALSE;
391

            
392
    cairo_list_add_tail (&node->link, &parent->children);
393

            
394
    if (strcmp (node->name, CAIRO_TAG_CONTENT) == 0) {
395
	node->type = PDF_NODE_CONTENT;
396
	status = _cairo_tag_parse_content_attributes (attributes, &node->attributes.content);
397
    } else if (strcmp (node->name, CAIRO_TAG_CONTENT_REF) == 0) {
398
	node->type = PDF_NODE_CONTENT_REF;
399
	status = _cairo_tag_parse_content_ref_attributes (attributes, &node->attributes.content_ref);
400
    } else if (strcmp (node->name, "Artifact") == 0) {
401
	node->type = PDF_NODE_ARTIFACT;
402
    } else {
403
	node->type = PDF_NODE_STRUCT;
404
    }
405

            
406
    *new_node = node;
407
    return status;
408
}
409

            
410
static void
411
5
free_node (cairo_pdf_struct_tree_node_t *node)
412
{
413
    cairo_pdf_struct_tree_node_t *child, *next;
414

            
415
5
    if (!node)
416
	return;
417

            
418
5
    cairo_list_foreach_entry_safe (child, next, cairo_pdf_struct_tree_node_t,
419
				   &node->children,
420
				   link)
421
    {
422
	cairo_list_del (&child->link);
423
	free_node (child);
424
    }
425
5
    free (node->name);
426
5
    _cairo_array_fini (&node->mcid);
427
5
    if (node->type == PDF_NODE_CONTENT)
428
	_cairo_tag_free_content_attributes (&node->attributes.content);
429

            
430
5
    if (node->type == PDF_NODE_CONTENT_REF)
431
	_cairo_tag_free_content_ref_attributes (&node->attributes.content_ref);
432

            
433
5
    free (node);
434
}
435

            
436
static cairo_status_t
437
add_mcid_to_node (cairo_pdf_surface_t          *surface,
438
		  cairo_pdf_struct_tree_node_t *node,
439
		  unsigned int                  command_id,
440
		  int                          *mcid)
441
{
442
    cairo_pdf_page_mcid_t mcid_elem;
443
    cairo_int_status_t status;
444
    cairo_pdf_interchange_t *ic = &surface->interchange;
445

            
446
    status = _cairo_array_append (&ic->mcid_to_tree, &node);
447
    if (unlikely (status))
448
	return status;
449

            
450
    mcid_elem.order = -1;
451
    mcid_elem.page = _cairo_array_num_elements (&surface->pages);
452
    mcid_elem.xobject_res = ic->current_recording_surface_res;
453
    mcid_elem.mcid = _cairo_array_num_elements (&ic->mcid_to_tree) - 1;
454
    mcid_elem.child_node = NULL;
455
    command_list_set_mcid (surface, command_id, node, _cairo_array_num_elements (&node->mcid));
456
    *mcid = mcid_elem.mcid;
457
    return _cairo_array_append (&node->mcid, &mcid_elem);
458
}
459

            
460
static cairo_status_t
461
add_child_to_mcid_array (cairo_pdf_surface_t          *surface,
462
			 cairo_pdf_struct_tree_node_t *node,
463
			 unsigned int                  command_id,
464
			 cairo_pdf_struct_tree_node_t *child)
465
{
466
    cairo_pdf_page_mcid_t mcid_elem;
467

            
468
    mcid_elem.order = -1;
469
    mcid_elem.page = 0;
470
    mcid_elem.xobject_res.id = 0;
471
    mcid_elem.mcid = 0;
472
    mcid_elem.child_node = child;
473
    command_list_set_mcid (surface, command_id, node, _cairo_array_num_elements (&node->mcid));
474
    return _cairo_array_append (&node->mcid, &mcid_elem);
475
}
476

            
477
static cairo_int_status_t
478
add_annotation (cairo_pdf_surface_t           *surface,
479
		cairo_pdf_struct_tree_node_t  *node,
480
		const char                    *name,
481
		const char                    *attributes)
482
{
483
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
484
    cairo_pdf_interchange_t *ic = &surface->interchange;
485
    cairo_pdf_annotation_t *annot;
486

            
487
    annot = _cairo_calloc (sizeof (cairo_pdf_annotation_t));
488
    if (unlikely (annot == NULL))
489
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
490

            
491
    status = _cairo_tag_parse_link_attributes (attributes, &annot->link_attrs);
492
    if (unlikely (status)) {
493
	free (annot);
494
	return status;
495
    }
496

            
497
    if (annot->link_attrs.link_page == 0)
498
	annot->link_attrs.link_page = _cairo_array_num_elements (&surface->pages);
499

            
500
    annot->node = node;
501

            
502
    annot->res = _cairo_pdf_surface_new_object (surface);
503
    if (annot->res.id == 0)
504
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
505

            
506
    node->annot = annot;
507
    status = _cairo_array_append (&ic->annots, &annot);
508

            
509
    return status;
510
}
511

            
512
static void
513
free_annotation (cairo_pdf_annotation_t *annot)
514
{
515
    _cairo_tag_free_link_attributes (&annot->link_attrs);
516
    free (annot);
517
}
518

            
519
static void
520
5
cairo_pdf_interchange_clear_annotations (cairo_pdf_surface_t *surface)
521
{
522
5
    cairo_pdf_interchange_t *ic = &surface->interchange;
523
    int num_elems, i;
524

            
525
5
    num_elems = _cairo_array_num_elements (&ic->annots);
526
5
    for (i = 0; i < num_elems; i++) {
527
	cairo_pdf_annotation_t * annot;
528

            
529
	_cairo_array_copy_element (&ic->annots, i, &annot);
530
	free_annotation (annot);
531
    }
532
5
    _cairo_array_truncate (&ic->annots, 0);
533
5
}
534

            
535
static void
536
cairo_pdf_interchange_write_node_mcid (cairo_pdf_surface_t            *surface,
537
				       cairo_pdf_page_mcid_t          *mcid_elem,
538
				       int                             page)
539
{
540
    cairo_pdf_page_info_t *page_info;
541

            
542
    page_info = _cairo_array_index (&surface->pages, mcid_elem->page - 1);
543
    if (mcid_elem->page == page && mcid_elem->xobject_res.id == 0) {
544
	_cairo_output_stream_printf (surface->object_stream.stream, "%d ", mcid_elem->mcid);
545
    } else {
546
	_cairo_output_stream_printf (surface->object_stream.stream,
547
				     "\n       << /Type /MCR ");
548
	if (mcid_elem->page != page) {
549
	    _cairo_output_stream_printf (surface->object_stream.stream,
550
					 "/Pg %d 0 R ",
551
					 page_info->page_res.id);
552
	}
553
	if (mcid_elem->xobject_res.id != 0) {
554
	    _cairo_output_stream_printf (surface->object_stream.stream,
555
					 "/Stm %d 0 R ",
556
					 mcid_elem->xobject_res.id);
557
	}
558
	_cairo_output_stream_printf (surface->object_stream.stream,
559
				     "/MCID %d >> ",
560
				     mcid_elem->mcid);
561
    }
562
}
563

            
564
static int
565
_mcid_order_compare (const void *a,
566
		     const void *b)
567
{
568
    const cairo_pdf_page_mcid_t *mcid_a = a;
569
    const cairo_pdf_page_mcid_t *mcid_b = b;
570

            
571
    if (mcid_a->order < mcid_b->order)
572
	return -1;
573
    else if (mcid_a->order > mcid_b->order)
574
	return 1;
575
    else
576
	return 0;
577
}
578

            
579
static cairo_int_status_t
580
cairo_pdf_interchange_write_node_object (cairo_pdf_surface_t            *surface,
581
					 cairo_pdf_struct_tree_node_t   *node,
582
					 int                             depth)
583
{
584
    cairo_pdf_page_mcid_t *mcid_elem, *child_mcid_elem;
585
    unsigned i, j, num_mcid;
586
    int first_page = 0;
587
    cairo_pdf_page_info_t *page_info;
588
    cairo_int_status_t status;
589
    cairo_bool_t has_children = FALSE;
590

            
591
    /* The Root node is written in cairo_pdf_interchange_write_struct_tree(). */
592
    if (!node->parent)
593
	return CAIRO_STATUS_SUCCESS;
594

            
595
    if (node->type == PDF_NODE_CONTENT ||
596
	node->type == PDF_NODE_CONTENT_REF ||
597
	node->type == PDF_NODE_ARTIFACT)
598
    {
599
	return CAIRO_STATUS_SUCCESS;
600
    }
601

            
602
    status = _cairo_pdf_surface_object_begin (surface, node->res);
603
    if (unlikely (status))
604
	return status;
605

            
606
    _cairo_output_stream_printf (surface->object_stream.stream,
607
				 "<< /Type /StructElem\n"
608
				 "   /S /%s\n"
609
				 "   /P %d 0 R\n",
610
				 node->name,
611
				 node->parent->res.id);
612

            
613
    /* Write /K entry (children of this StructElem) */
614
    num_mcid = _cairo_array_num_elements (&node->mcid);
615
    if (num_mcid > 0 ) {
616
	_cairo_array_sort (&node->mcid, _mcid_order_compare);
617
	/* Find the first MCID element and use the page number to set /Pg */
618
	for (i = 0; i < num_mcid; i++) {
619
	    mcid_elem = _cairo_array_index (&node->mcid, i);
620
	    assert (mcid_elem->order != -1);
621
	    if (mcid_elem->child_node) {
622
		if (mcid_elem->child_node->type == PDF_NODE_CONTENT_REF) {
623
		    cairo_pdf_struct_tree_node_t *content_node;
624
		    status = lookup_content_node_for_ref_node (surface, mcid_elem->child_node, &content_node);
625
		    if (status)
626
			return status;
627

            
628
		    /* CONTENT_REF will not have child nodes */
629
		    if (_cairo_array_num_elements (&content_node->mcid) > 0) {
630
			child_mcid_elem = _cairo_array_index (&content_node->mcid, 0);
631
			first_page = child_mcid_elem->page;
632
			page_info = _cairo_array_index (&surface->pages, first_page - 1);
633
			_cairo_output_stream_printf (surface->object_stream.stream,
634
						     "   /Pg %d 0 R\n",
635
						     page_info->page_res.id);
636
			has_children = TRUE;
637
			break;
638
		    }
639
		} else {
640
		    has_children = TRUE;
641
		}
642
	    } else {
643
		first_page = mcid_elem->page;
644
		page_info = _cairo_array_index (&surface->pages, first_page - 1);
645
		_cairo_output_stream_printf (surface->object_stream.stream,
646
					     "   /Pg %d 0 R\n",
647
					     page_info->page_res.id);
648
		has_children = TRUE;
649
		break;
650
	    }
651
	}
652

            
653
	if (has_children || node->annot) {
654
	    _cairo_output_stream_printf (surface->object_stream.stream, "   /K ");
655

            
656
	    if (num_mcid > 1 || node->annot)
657
		_cairo_output_stream_printf (surface->object_stream.stream, "[ ");
658

            
659
	    for (i = 0; i < num_mcid; i++) {
660
		if (node->annot) {
661
		    if (node->annot->link_attrs.link_page != first_page) {
662
			page_info = _cairo_array_index (&surface->pages, node->annot->link_attrs.link_page - 1);
663
			_cairo_output_stream_printf (surface->object_stream.stream,
664
						     "<< /Type /OBJR /Pg %d 0 R /Obj %d 0 R >> ",
665
						     page_info->page_res.id,
666
						     node->annot->res.id);
667
		    } else {
668
			_cairo_output_stream_printf (surface->object_stream.stream,
669
						     "<< /Type /OBJR /Obj %d 0 R >> ",
670
						     node->annot->res.id);
671
		    }
672
		}
673
		mcid_elem = _cairo_array_index (&node->mcid, i);
674
		if (mcid_elem->child_node) {
675
		    if (mcid_elem->child_node->type == PDF_NODE_CONTENT_REF) {
676
			cairo_pdf_struct_tree_node_t *content_node;
677
			status = lookup_content_node_for_ref_node (surface, mcid_elem->child_node, &content_node);
678
			if (status)
679
			    return status;
680

            
681
			assert (content_node->type == PDF_NODE_CONTENT);
682

            
683
			/* CONTENT_REF will not have child nodes */
684
			for (j = 0; j < _cairo_array_num_elements (&content_node->mcid); j++) {
685
			    child_mcid_elem = _cairo_array_index (&content_node->mcid, j);
686
			    cairo_pdf_interchange_write_node_mcid (surface, child_mcid_elem, first_page);
687
			}
688
		    } else if (mcid_elem->child_node->type != PDF_NODE_CONTENT) {
689
			_cairo_output_stream_printf (surface->object_stream.stream,
690
						     " %d 0 R ",
691
						     mcid_elem->child_node->res.id);
692
		    }
693
		} else {
694
		    cairo_pdf_interchange_write_node_mcid (surface, mcid_elem, first_page);
695
		}
696
	    }
697

            
698
	    if (num_mcid > 1 || node->annot)
699
		_cairo_output_stream_printf (surface->object_stream.stream, "]");
700
	}
701

            
702
	_cairo_output_stream_printf (surface->object_stream.stream, "\n");
703
    }
704

            
705
    _cairo_output_stream_printf (surface->object_stream.stream,
706
				 ">>\n");
707

            
708
    _cairo_pdf_surface_object_end (surface);
709

            
710
    return _cairo_output_stream_get_status (surface->object_stream.stream);
711
}
712

            
713
static void
714
init_named_dest_key (cairo_pdf_named_dest_t *dest)
715
{
716
    dest->base.hash = _cairo_hash_bytes (_CAIRO_HASH_INIT_VALUE,
717
					 dest->attrs.name,
718
					 strlen (dest->attrs.name));
719
}
720

            
721
static cairo_bool_t
722
_named_dest_equal (const void *key_a, const void *key_b)
723
{
724
    const cairo_pdf_named_dest_t *a = key_a;
725
    const cairo_pdf_named_dest_t *b = key_b;
726

            
727
    return strcmp (a->attrs.name, b->attrs.name) == 0;
728
}
729

            
730
static void
731
_named_dest_pluck (void *entry, void *closure)
732
{
733
    cairo_pdf_named_dest_t *dest = entry;
734
    cairo_hash_table_t *table = closure;
735

            
736
    _cairo_hash_table_remove (table, &dest->base);
737
    _cairo_tag_free_dest_attributes (&dest->attrs);
738
    free (dest);
739
}
740

            
741
static cairo_int_status_t
742
cairo_pdf_interchange_write_explicit_dest (cairo_pdf_surface_t *surface,
743
                                          int                  page,
744
                                          cairo_bool_t         has_pos,
745
                                          double               x,
746
                                          double               y)
747
{
748
    cairo_pdf_page_info_t *page_info;
749

            
750
    page_info = _cairo_array_index (&surface->pages, page - 1);
751

            
752
    if (has_pos) {
753
       _cairo_output_stream_printf (surface->object_stream.stream,
754
                                    "[%d 0 R /XYZ %f %f 0]\n",
755
                                    page_info->page_res.id,
756
                                    x,
757
                                    page_info->height - y);
758
    } else {
759
       _cairo_output_stream_printf (surface->object_stream.stream,
760
                                    "[%d 0 R /XYZ null null 0]\n",
761
                                    page_info->page_res.id);
762
    }
763

            
764
    return CAIRO_STATUS_SUCCESS;
765
}
766

            
767
static cairo_int_status_t
768
cairo_pdf_interchange_write_dest (cairo_pdf_surface_t *surface,
769
				  cairo_link_attrs_t  *link_attrs)
770
{
771
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
772
    cairo_pdf_interchange_t *ic = &surface->interchange;
773

            
774
    /* If the dest is known, emit an explicit dest */
775
    if (link_attrs->link_type == TAG_LINK_DEST_AND_URI || link_attrs->link_type == TAG_LINK_DEST) {
776
	cairo_pdf_named_dest_t key;
777
	cairo_pdf_named_dest_t *named_dest;
778

            
779
	/* check if we already have this dest */
780
	key.attrs.name = link_attrs->dest;
781
	init_named_dest_key (&key);
782
	named_dest = _cairo_hash_table_lookup (ic->named_dests, &key.base);
783
	if (named_dest) {
784
	    double x = 0;
785
	    double y = 0;
786

            
787
	    if (named_dest->extents.valid) {
788
		x = named_dest->extents.extents.x;
789
		y = named_dest->extents.extents.y;
790
	    }
791

            
792
	    if (named_dest->attrs.x_valid)
793
		x = named_dest->attrs.x;
794

            
795
	    if (named_dest->attrs.y_valid)
796
		y = named_dest->attrs.y;
797

            
798
	    if (named_dest->attrs.internal) {
799
		_cairo_output_stream_printf (surface->object_stream.stream, "   /Dest ");
800
		status = cairo_pdf_interchange_write_explicit_dest (surface,
801
								    named_dest->page,
802
								    TRUE,
803
								    x, y);
804
	    } else {
805
		char *name = NULL;
806

            
807
		status = _cairo_utf8_to_pdf_string (named_dest->attrs.name, &name);
808
		if (unlikely (status))
809
		    return status;
810

            
811
		_cairo_output_stream_printf (surface->object_stream.stream, "   /Dest %s\n",
812
					     name);
813
		free (name);
814
	    }
815
	    return status;
816
	}
817
	/* name does not exist */
818
	if (link_attrs->link_type == TAG_LINK_DEST_AND_URI) {
819
	    /* Don't emit anything. The caller will fallback to emitting a URI destination. */
820
	    return CAIRO_INT_STATUS_NOTHING_TO_DO;
821
	}
822

            
823
	/* Mising destination. Emit a "do nothing" dest that points to the same page and position. */
824
	_cairo_tag_warning ("Link to dest=\"%s\" not found", link_attrs->dest);
825
	_cairo_output_stream_printf (surface->object_stream.stream, "   /Dest ");
826
	status = cairo_pdf_interchange_write_explicit_dest (surface,
827
							    link_attrs->link_page,
828
							    FALSE,
829
							    0, 0);
830
	return status;
831
    }
832

            
833
    /* link_attrs->link_type == TAG_LINK_PAGE */
834

            
835
    if (link_attrs->page < 1)
836
	return _cairo_tag_error ("Link attribute: \"page=%d\" page must be >= 1", link_attrs->page);
837

            
838
    if (link_attrs->page > (int)_cairo_array_num_elements (&surface->pages))
839
	return _cairo_tag_error ("Link attribute: \"page=%d\" page exceeds page count (%d)",
840
				 link_attrs->page, _cairo_array_num_elements (&surface->pages));
841

            
842
    _cairo_output_stream_printf (surface->object_stream.stream, "   /Dest ");
843
    return cairo_pdf_interchange_write_explicit_dest (surface,
844
						      link_attrs->page,
845
						      link_attrs->has_pos,
846
						      link_attrs->pos.x,
847
						      link_attrs->pos.y);
848
}
849

            
850
static cairo_int_status_t
851
_cairo_utf8_to_pdf_utf8_hexstring (const char *utf8, char **str_out)
852
{
853
    int i;
854
    int len;
855
    unsigned char *p;
856
    cairo_bool_t ascii;
857
    char *str;
858
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
859

            
860
    ascii = TRUE;
861
    p = (unsigned char *)utf8;
862
    len = 0;
863
    while (*p) {
864
	if (*p < 32 || *p > 126) {
865
	    ascii = FALSE;
866
	}
867
	if (*p == '(' || *p == ')' || *p == '\\')
868
	    len += 2;
869
	else
870
	    len++;
871
	p++;
872
    }
873

            
874
    if (ascii) {
875
	str = _cairo_malloc (len + 3);
876
	if (str == NULL)
877
	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
878

            
879
	str[0] = '(';
880
	p = (unsigned char *)utf8;
881
	i = 1;
882
	while (*p) {
883
	    if (*p == '(' || *p == ')' || *p == '\\')
884
		str[i++] = '\\';
885
	    str[i++] = *p;
886
	    p++;
887
	}
888
	str[i++] = ')';
889
	str[i++] = 0;
890
    } else {
891
	str = _cairo_malloc (len*2 + 3);
892
	if (str == NULL)
893
	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
894

            
895
	str[0] = '<';
896
	p = (unsigned char *)utf8;
897
	i = 1;
898
	while (*p) {
899
	    if (*p == '\\') {
900
		snprintf(str + i, 3, "%02x", '\\');
901
		i += 2;
902
	    }
903
	    snprintf(str + i, 3, "%02x", *p);
904
	    i += 2;
905
	    p++;
906
	}
907
	str[i++] = '>';
908
	str[i++] = 0;
909
    }
910
    *str_out = str;
911

            
912
    return status;
913
}
914

            
915
static cairo_int_status_t
916
cairo_pdf_interchange_write_link_action (cairo_pdf_surface_t   *surface,
917
					 cairo_link_attrs_t    *link_attrs)
918
{
919
    cairo_int_status_t status;
920
    char *dest = NULL;
921

            
922
    if (link_attrs->link_type == TAG_LINK_DEST_AND_URI ||
923
	link_attrs->link_type == TAG_LINK_DEST         ||
924
	link_attrs->link_type == TAG_LINK_PAGE)
925
    {
926
	status = cairo_pdf_interchange_write_dest (surface, link_attrs);
927
	if (status != CAIRO_INT_STATUS_NOTHING_TO_DO)
928
	    return status;
929

            
930
	/* CAIRO_INT_STATUS_NOTHING_TO_DO means that the link type is TAG_LINK_DEST_AND_URI
931
	 * and the DEST is missing. Fall through to writing a URI link below.
932
	 */
933
    }
934

            
935
    if (link_attrs->link_type == TAG_LINK_URI || link_attrs->link_type == TAG_LINK_DEST_AND_URI) {
936
	status = _cairo_utf8_to_pdf_string (link_attrs->uri, &dest);
937
	if (unlikely (status))
938
	    return status;
939

            
940
	if (dest[0] != '(') {
941
	    free (dest);
942
	    return _cairo_tag_error ("Link attribute: \"url=%s\" URI may only contain ASCII characters",
943
				     link_attrs->uri);
944
	}
945

            
946
	_cairo_output_stream_printf (surface->object_stream.stream,
947
				     "   /A <<\n"
948
				     "      /Type /Action\n"
949
				     "      /S /URI\n"
950
				     "      /URI %s\n"
951
				     "   >>\n",
952
				     dest);
953
	free (dest);
954
    } else if (link_attrs->link_type == TAG_LINK_FILE) {
955
	/* According to "Developing with PDF", Leonard Rosenthol, 2013,
956
	 * The F key is encoded in the "standard encoding for the
957
	 * platform on which the document is being viewed. For most
958
	 * modern operating systems, that's UTF-8"
959
	 *
960
	 * As we don't know the target platform, we assume UTF-8. The
961
	 * F key may contain multi-byte encodings using the hex
962
	 * encoding.
963
	 *
964
	 * For PDF 1.7 we also include the UF key which uses the
965
	 * standard PDF UTF-16BE strings.
966
	 */
967
	status = _cairo_utf8_to_pdf_utf8_hexstring (link_attrs->file, &dest);
968
	if (unlikely (status))
969
	    return status;
970

            
971
	_cairo_output_stream_printf (surface->object_stream.stream,
972
				     "   /A <<\n"
973
				     "      /Type /Action\n"
974
				     "      /S /GoToR\n"
975
				     "      /F %s\n",
976
				     dest);
977
	free (dest);
978

            
979
	if (surface->pdf_version >= CAIRO_PDF_VERSION_1_7)
980
	{
981
	    status = _cairo_utf8_to_pdf_string (link_attrs->file, &dest);
982
	    if (unlikely (status))
983
		return status;
984

            
985
	    _cairo_output_stream_printf (surface->object_stream.stream,
986
				     "      /UF %s\n",
987
				     dest);
988
	    free (dest);
989
	}
990

            
991
	if (link_attrs->dest) {
992
	    status = _cairo_utf8_to_pdf_string (link_attrs->dest, &dest);
993
	    if (unlikely (status))
994
		return status;
995

            
996
	    _cairo_output_stream_printf (surface->object_stream.stream,
997
					 "      /D %s\n",
998
					 dest);
999
	    free (dest);
	} else {
	    if (link_attrs->has_pos) {
		_cairo_output_stream_printf (surface->object_stream.stream,
					     "      /D [%d /XYZ %f %f 0]\n",
					     link_attrs->page,
					     link_attrs->pos.x,
					     link_attrs->pos.y);
	    } else {
		_cairo_output_stream_printf (surface->object_stream.stream,
					     "      /D [%d /XYZ null null 0]\n",
					     link_attrs->page);
	    }
	}
	_cairo_output_stream_printf (surface->object_stream.stream,
				     "   >>\n");
    }
    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
cairo_pdf_interchange_write_annot (cairo_pdf_surface_t    *surface,
				   cairo_pdf_annotation_t *annot,
				   cairo_bool_t            struct_parents)
{
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_struct_tree_node_t *node = annot->node;
    int sp;
    int i, num_rects;
    double height;
    num_rects = _cairo_array_num_elements (&annot->link_attrs.rects);
    if (strcmp (node->name, CAIRO_TAG_LINK) == 0 &&
	annot->link_attrs.link_type != TAG_LINK_EMPTY &&
	(node->extents.valid || num_rects > 0))
    {
	status = _cairo_array_append (&ic->parent_tree, &node->res);
	if (unlikely (status))
	    return status;
	sp = _cairo_array_num_elements (&ic->parent_tree) - 1;
	status = _cairo_pdf_surface_object_begin (surface, annot->res);
	if (unlikely (status))
	    return status;
	_cairo_output_stream_printf (surface->object_stream.stream,
				     "<< /Type /Annot\n"
				     "   /Subtype /Link\n");
	if (struct_parents) {
	    _cairo_output_stream_printf (surface->object_stream.stream,
					 "   /StructParent %d\n",
					 sp);
	}
	height = surface->height;
	if (num_rects > 0) {
	    cairo_rectangle_int_t bbox_rect;
	    _cairo_output_stream_printf (surface->object_stream.stream,
					 "   /QuadPoints [ ");
	    for (i = 0; i < num_rects; i++) {
		cairo_rectangle_t rectf;
		cairo_rectangle_int_t recti;
		_cairo_array_copy_element (&annot->link_attrs.rects, i, &rectf);
		_cairo_rectangle_int_from_double (&recti, &rectf);
		if (i == 0)
		    bbox_rect = recti;
		else
		    _cairo_rectangle_union (&bbox_rect, &recti);
		write_rect_to_pdf_quad_points (surface->object_stream.stream, &rectf, height);
		_cairo_output_stream_printf (surface->object_stream.stream, " ");
	    }
	    _cairo_output_stream_printf (surface->object_stream.stream,
					 "]\n"
					 "   /Rect [ ");
	    write_rect_int_to_pdf_bbox (surface->object_stream.stream, &bbox_rect, height);
	    _cairo_output_stream_printf (surface->object_stream.stream, " ]\n");
	} else {
	    _cairo_output_stream_printf (surface->object_stream.stream,
					 "   /Rect [ ");
	    write_rect_int_to_pdf_bbox (surface->object_stream.stream, &node->extents.extents, height);
	    _cairo_output_stream_printf (surface->object_stream.stream, " ]\n");
	}
	status = cairo_pdf_interchange_write_link_action (surface, &annot->link_attrs);
	if (unlikely (status))
	    return status;
	_cairo_output_stream_printf (surface->object_stream.stream,
				     "   /BS << /W 0 >>\n"
				     ">>\n");
	_cairo_pdf_surface_object_end (surface);
	status = _cairo_output_stream_get_status (surface->object_stream.stream);
    }
    return status;
}
static cairo_int_status_t
5
cairo_pdf_interchange_walk_struct_tree (cairo_pdf_surface_t          *surface,
					cairo_pdf_struct_tree_node_t *node,
					int                           depth,
					cairo_int_status_t (*func) (cairo_pdf_surface_t          *surface,
								    cairo_pdf_struct_tree_node_t *node,
					                            int                           depth))
{
    cairo_int_status_t status;
    cairo_pdf_struct_tree_node_t *child;
5
    status = func (surface, node, depth);
5
    if (unlikely (status))
	return status;
5
    depth++;
5
    cairo_list_foreach_entry (child, cairo_pdf_struct_tree_node_t,
			      &node->children, link)
    {
	status = cairo_pdf_interchange_walk_struct_tree (surface, child, depth, func);
	if (unlikely (status))
	    return status;
    }
5
    depth--;
5
    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
cairo_pdf_interchange_write_struct_tree (cairo_pdf_surface_t *surface)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_struct_tree_node_t *child;
    cairo_int_status_t status;
    if (cairo_list_is_empty (&ic->struct_root->children))
	return CAIRO_STATUS_SUCCESS;
    status = cairo_pdf_interchange_walk_struct_tree (surface,
						     ic->struct_root,
						     0,
						     cairo_pdf_interchange_write_node_object);
    if (unlikely (status))
	return status;
    status = _cairo_pdf_surface_object_begin (surface, surface->struct_tree_root);
    if (unlikely (status))
	return status;
    _cairo_output_stream_printf (surface->object_stream.stream,
				 "<< /Type /StructTreeRoot\n"
				 "   /ParentTree %d 0 R\n",
				 ic->parent_tree_res.id);
    if (cairo_list_is_singular (&ic->struct_root->children)) {
	child = cairo_list_first_entry (&ic->struct_root->children, cairo_pdf_struct_tree_node_t, link);
	_cairo_output_stream_printf (surface->object_stream.stream, "   /K [ %d 0 R ]\n", child->res.id);
    } else {
	_cairo_output_stream_printf (surface->object_stream.stream, "   /K [ ");
	cairo_list_foreach_entry (child, cairo_pdf_struct_tree_node_t,
				  &ic->struct_root->children, link)
	{
	    if (child->type == PDF_NODE_CONTENT || child->type == PDF_NODE_ARTIFACT)
		continue;
	    _cairo_output_stream_printf (surface->object_stream.stream, "%d 0 R ", child->res.id);
	}
	_cairo_output_stream_printf (surface->object_stream.stream, "]\n");
    }
    _cairo_output_stream_printf (surface->object_stream.stream,
				 ">>\n");
    _cairo_pdf_surface_object_end (surface);
    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
5
cairo_pdf_interchange_write_annots (cairo_pdf_surface_t *surface,
				    cairo_bool_t         struct_parents)
{
5
    cairo_pdf_interchange_t *ic = &surface->interchange;
    int num_elems, i, page_num;
    cairo_pdf_page_info_t *page_info;
    cairo_pdf_annotation_t *annot;
5
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
5
    num_elems = _cairo_array_num_elements (&ic->annots);
5
    for (i = 0; i < num_elems; i++) {
	_cairo_array_copy_element (&ic->annots, i, &annot);
	page_num = annot->link_attrs.link_page;
	if (page_num > (int)_cairo_array_num_elements (&surface->pages)) {
	    return _cairo_tag_error ("Link attribute: \"link_page=%d\" page exceeds page count (%d)",
				     page_num,
				     _cairo_array_num_elements (&surface->pages));
	}
	page_info = _cairo_array_index (&surface->pages, page_num - 1);
	status = _cairo_array_append (&page_info->annots, &annot->res);
	if (status)
	    return status;
	status = cairo_pdf_interchange_write_annot (surface, annot, struct_parents);
	if (unlikely (status))
	    return status;
    }
5
    return status;
}
static cairo_int_status_t
5
cairo_pdf_interchange_write_content_parent_elems (cairo_pdf_surface_t *surface)
{
    int num_elems, i;
    cairo_pdf_struct_tree_node_t *node;
5
    cairo_pdf_interchange_t *ic = &surface->interchange;
5
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
5
    num_elems = _cairo_array_num_elements (&ic->mcid_to_tree);
5
    status = _cairo_pdf_surface_object_begin (surface, ic->content_parent_res);
5
    if (unlikely (status))
	return status;
5
    _cairo_output_stream_printf (surface->object_stream.stream,
				     "[\n");
5
    for (i = 0; i < num_elems; i++) {
	_cairo_array_copy_element (&ic->mcid_to_tree, i, &node);
	_cairo_output_stream_printf (surface->object_stream.stream, "  %d 0 R\n", node->res.id);
    }
5
    _cairo_output_stream_printf (surface->object_stream.stream,
				 "]\n");
5
    _cairo_pdf_surface_object_end (surface);
5
    return status;
}
static cairo_int_status_t
5
cairo_pdf_interchange_apply_extents_from_content_ref (cairo_pdf_surface_t            *surface,
						      cairo_pdf_struct_tree_node_t   *node,
						      int                             depth)
{
    cairo_int_status_t status;
5
    if (node->type != PDF_NODE_CONTENT_REF)
5
	return CAIRO_STATUS_SUCCESS;
    cairo_pdf_struct_tree_node_t *content_node;
    status = lookup_content_node_for_ref_node (surface, node, &content_node);
    if (status)
	return status;
    /* Merge extents with all parent nodes */
    node = node->parent;
    while (node) {
	if (node->extents.valid) {
	    _cairo_rectangle_union (&node->extents.extents, &content_node->extents.extents);
	} else {
	    node->extents = content_node->extents;
	}
	node = node->parent;
    }
    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
5
cairo_pdf_interchange_update_extents (cairo_pdf_surface_t *surface)
{
5
    cairo_pdf_interchange_t *ic = &surface->interchange;
5
    return cairo_pdf_interchange_walk_struct_tree (surface,
						   ic->struct_root,
						   0,
						   cairo_pdf_interchange_apply_extents_from_content_ref);
}
static cairo_int_status_t
cairo_pdf_interchange_write_parent_tree (cairo_pdf_surface_t *surface)
{
    int num_elems, i;
    cairo_pdf_resource_t *res;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status;
    num_elems = _cairo_array_num_elements (&ic->parent_tree);
    if (num_elems > 0) {
	ic->parent_tree_res = _cairo_pdf_surface_new_object (surface);
	if (ic->parent_tree_res.id == 0)
	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
	status = _cairo_pdf_surface_object_begin (surface, ic->parent_tree_res);
	if (unlikely (status))
	    return status;
	_cairo_output_stream_printf (surface->object_stream.stream,
				     "<< /Nums [\n");
	for (i = 0; i < num_elems; i++) {
	    res = _cairo_array_index (&ic->parent_tree, i);
	    if (res->id) {
		_cairo_output_stream_printf (surface->object_stream.stream,
					     "   %d %d 0 R\n",
					     i,
					     res->id);
	    }
	}
	_cairo_output_stream_printf (surface->object_stream.stream,
				     "  ]\n"
				     ">>\n");
	_cairo_pdf_surface_object_end (surface);
    }
    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
5
cairo_pdf_interchange_write_outline (cairo_pdf_surface_t *surface)
{
    int num_elems, i;
    cairo_pdf_outline_entry_t *outline;
5
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status;
5
    char *name = NULL;
5
    num_elems = _cairo_array_num_elements (&ic->outline);
5
    if (num_elems < 2)
5
	return CAIRO_INT_STATUS_SUCCESS;
    _cairo_array_copy_element (&ic->outline, 0, &outline);
    outline->res = _cairo_pdf_surface_new_object (surface);
    if (outline->res.id == 0)
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    surface->outlines_dict_res = outline->res;
    status = _cairo_pdf_surface_object_begin (surface, outline->res);
    if (unlikely (status))
	return status;
    _cairo_output_stream_printf (surface->object_stream.stream,
				 "<< /Type /Outlines\n"
				 "   /First %d 0 R\n"
				 "   /Last %d 0 R\n"
				 "   /Count %d\n"
				 ">>\n",
				 outline->first_child->res.id,
				 outline->last_child->res.id,
				 outline->count);
    _cairo_pdf_surface_object_end (surface);
    for (i = 1; i < num_elems; i++) {
	_cairo_array_copy_element (&ic->outline, i, &outline);
	_cairo_pdf_surface_update_object (surface, outline->res);
	status = _cairo_utf8_to_pdf_string (outline->name, &name);
	if (unlikely (status))
	    return status;
	status = _cairo_pdf_surface_object_begin (surface, outline->res);
	if (unlikely (status))
	    return status;
	_cairo_output_stream_printf (surface->object_stream.stream,
				     "<< /Title %s\n"
				     "   /Parent %d 0 R\n",
				     name,
				     outline->parent->res.id);
	free (name);
	if (outline->prev) {
	    _cairo_output_stream_printf (surface->object_stream.stream,
					 "   /Prev %d 0 R\n",
					 outline->prev->res.id);
	}
	if (outline->next) {
	    _cairo_output_stream_printf (surface->object_stream.stream,
					 "   /Next %d 0 R\n",
					 outline->next->res.id);
	}
	if (outline->first_child) {
	    _cairo_output_stream_printf (surface->object_stream.stream,
					 "   /First %d 0 R\n"
					 "   /Last %d 0 R\n"
					 "   /Count %d\n",
					 outline->first_child->res.id,
					 outline->last_child->res.id,
					 outline->count);
	}
	if (outline->flags) {
	    int flags = 0;
	    if (outline->flags & CAIRO_PDF_OUTLINE_FLAG_ITALIC)
		flags |= 1;
	    if (outline->flags & CAIRO_PDF_OUTLINE_FLAG_BOLD)
		flags |= 2;
	    _cairo_output_stream_printf (surface->object_stream.stream,
					 "   /F %d\n",
					 flags);
	}
	status = cairo_pdf_interchange_write_link_action (surface, &outline->link_attrs);
	if (unlikely (status))
	    return status;
	_cairo_output_stream_printf (surface->object_stream.stream,
				     ">>\n");
	_cairo_pdf_surface_object_end (surface);
    }
    return status;
}
/*
 * Split a page label into a text prefix and numeric suffix. Leading '0's are
 * included in the prefix. eg
 *  "3"     => NULL,    3
 *  "cover" => "cover", 0
 *  "A-2"   => "A-",    2
 *  "A-002" => "A-00",  2
 */
static char *
split_label (const char* label, int *num)
{
    int len, i;
    *num = 0;
    len = strlen (label);
    if (len == 0)
	return NULL;
    i = len;
    while (i > 0 && _cairo_isdigit (label[i-1]))
	   i--;
    while (i < len && label[i] == '0')
	i++;
    if (i < len)
	sscanf (label + i, "%d", num);
    if (i > 0) {
	char *s;
	s = _cairo_malloc (i + 1);
	if (!s)
	    return NULL;
	memcpy (s, label, i);
	s[i] = 0;
	return s;
    }
    return NULL;
}
/* strcmp that handles NULL arguments */
static cairo_bool_t
strcmp_null (const char *s1, const char *s2)
{
    if (s1 && s2)
	return strcmp (s1, s2) == 0;
    if (!s1 && !s2)
	return TRUE;
    return FALSE;
}
static cairo_int_status_t
5
cairo_pdf_interchange_write_page_labels (cairo_pdf_surface_t *surface)
{
    int num_elems, i;
    char *label;
    char *prefix;
    char *prev_prefix;
    int num, prev_num;
    cairo_int_status_t status;
    cairo_bool_t has_labels;
    /* Check if any labels defined */
5
    num_elems = _cairo_array_num_elements (&surface->page_labels);
5
    has_labels = FALSE;
10
    for (i = 0; i < num_elems; i++) {
5
	_cairo_array_copy_element (&surface->page_labels, i, &label);
5
	if (label) {
	    has_labels = TRUE;
	    break;
	}
    }
5
    if (!has_labels)
5
	return CAIRO_STATUS_SUCCESS;
    surface->page_labels_res = _cairo_pdf_surface_new_object (surface);
    if (surface->page_labels_res.id == 0)
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    status = _cairo_pdf_surface_object_begin (surface, surface->page_labels_res);
    if (unlikely (status))
	return status;
    _cairo_output_stream_printf (surface->object_stream.stream,
				 "<< /Nums [\n");
    prefix = NULL;
    prev_prefix = NULL;
    num = 0;
    prev_num = 0;
    for (i = 0; i < num_elems; i++) {
	_cairo_array_copy_element (&surface->page_labels, i, &label);
	if (label) {
	    prefix = split_label (label, &num);
	} else {
	    prefix = NULL;
	    num = i + 1;
	}
	if (!strcmp_null (prefix, prev_prefix) || num != prev_num + 1) {
	    _cairo_output_stream_printf (surface->object_stream.stream,  "   %d << ", i);
	    if (num)
		_cairo_output_stream_printf (surface->object_stream.stream,  "/S /D /St %d ", num);
	    if (prefix) {
		char *s;
		status = _cairo_utf8_to_pdf_string (prefix, &s);
		if (unlikely (status))
		    return status;
		_cairo_output_stream_printf (surface->object_stream.stream,  "/P %s ", s);
		free (s);
	    }
	    _cairo_output_stream_printf (surface->object_stream.stream,  ">>\n");
	}
	free (prev_prefix);
	prev_prefix = prefix;
	prefix = NULL;
	prev_num = num;
    }
    free (prefix);
    free (prev_prefix);
    _cairo_output_stream_printf (surface->object_stream.stream,
				 "  ]\n"
				 ">>\n");
    _cairo_pdf_surface_object_end (surface);
    return CAIRO_STATUS_SUCCESS;
}
static void
_collect_external_dest (void *entry, void *closure)
{
    cairo_pdf_named_dest_t *dest = entry;
    cairo_pdf_surface_t *surface = closure;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    if (!dest->attrs.internal)
	ic->sorted_dests[ic->num_dests++] = dest;
}
static int
_dest_compare (const void *a, const void *b)
{
    const cairo_pdf_named_dest_t * const *dest_a = a;
    const cairo_pdf_named_dest_t * const *dest_b = b;
    return strcmp ((*dest_a)->attrs.name, (*dest_b)->attrs.name);
}
static cairo_int_status_t
5
_cairo_pdf_interchange_write_document_dests (cairo_pdf_surface_t *surface)
{
    int i;
5
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status;
    cairo_pdf_page_info_t *page_info;
5
    if (ic->num_dests == 0) {
5
	ic->dests_res.id = 0;
5
        return CAIRO_STATUS_SUCCESS;
    }
    ic->sorted_dests = _cairo_calloc_ab (ic->num_dests, sizeof (cairo_pdf_named_dest_t *));
    if (unlikely (ic->sorted_dests == NULL))
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    ic->num_dests = 0;
    _cairo_hash_table_foreach (ic->named_dests, _collect_external_dest, surface);
    qsort (ic->sorted_dests, ic->num_dests, sizeof (cairo_pdf_named_dest_t *), _dest_compare);
    ic->dests_res = _cairo_pdf_surface_new_object (surface);
    if (ic->dests_res.id == 0)
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    status = _cairo_pdf_surface_object_begin (surface, ic->dests_res);
    if (unlikely (status))
	return status;
    _cairo_output_stream_printf (surface->object_stream.stream,
				 "<< /Names [\n");
    for (i = 0; i < ic->num_dests; i++) {
	cairo_pdf_named_dest_t *dest = ic->sorted_dests[i];
	double x = 0;
	double y = 0;
	char *name = NULL;
	if (dest->attrs.internal)
	    continue;
	if (dest->extents.valid) {
	    x = dest->extents.extents.x;
	    y = dest->extents.extents.y;
	}
	if (dest->attrs.x_valid)
	    x = dest->attrs.x;
	if (dest->attrs.y_valid)
	    y = dest->attrs.y;
	status = _cairo_utf8_to_pdf_string (dest->attrs.name, &name);
	if (unlikely (status))
	    return status;
	page_info = _cairo_array_index (&surface->pages, dest->page - 1);
	_cairo_output_stream_printf (surface->object_stream.stream,
				     "   %s [%d 0 R /XYZ %f %f 0]\n",
				     name,
				     page_info->page_res.id,
				     x,
				     page_info->height - y);
	free (name);
    }
    _cairo_output_stream_printf (surface->object_stream.stream,
				 "  ]\n"
				 ">>\n");
    _cairo_pdf_surface_object_end (surface);
    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
5
cairo_pdf_interchange_write_names_dict (cairo_pdf_surface_t *surface)
{
5
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status;
5
    status = _cairo_pdf_interchange_write_document_dests (surface);
5
    if (unlikely (status))
	return status;
5
    surface->names_dict_res.id = 0;
5
    if (ic->dests_res.id != 0) {
	surface->names_dict_res = _cairo_pdf_surface_new_object (surface);
	if (surface->names_dict_res.id == 0)
	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
	status = _cairo_pdf_surface_object_begin (surface, surface->names_dict_res);
	if (unlikely (status))
	    return status;
	_cairo_output_stream_printf (surface->object_stream.stream,
				     "<< /Dests %d 0 R >>\n",
				     ic->dests_res.id);
	_cairo_pdf_surface_object_end (surface);
    }
5
    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
5
cairo_pdf_interchange_write_docinfo (cairo_pdf_surface_t *surface)
{
5
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status;
    unsigned int i, num_elems;
    struct metadata *data;
    unsigned char *p;
5
    surface->docinfo_res = _cairo_pdf_surface_new_object (surface);
5
    if (surface->docinfo_res.id == 0)
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
5
    status = _cairo_pdf_surface_object_begin (surface, surface->docinfo_res);
5
    if (unlikely (status))
	return status;
5
    _cairo_output_stream_printf (surface->object_stream.stream,
				 "<< /Producer (cairo %s (https://cairographics.org))\n",
				 cairo_version_string ());
5
    if (ic->docinfo.title)
	_cairo_output_stream_printf (surface->object_stream.stream, "   /Title %s\n", ic->docinfo.title);
5
    if (ic->docinfo.author)
	_cairo_output_stream_printf (surface->object_stream.stream, "   /Author %s\n", ic->docinfo.author);
5
    if (ic->docinfo.subject)
	_cairo_output_stream_printf (surface->object_stream.stream, "   /Subject %s\n", ic->docinfo.subject);
5
    if (ic->docinfo.keywords)
	_cairo_output_stream_printf (surface->object_stream.stream, "   /Keywords %s\n", ic->docinfo.keywords);
5
    if (ic->docinfo.creator)
	_cairo_output_stream_printf (surface->object_stream.stream, "   /Creator %s\n", ic->docinfo.creator);
5
    if (ic->docinfo.create_date)
5
	_cairo_output_stream_printf (surface->object_stream.stream, "   /CreationDate %s\n", ic->docinfo.create_date);
5
    if (ic->docinfo.mod_date)
	_cairo_output_stream_printf (surface->object_stream.stream, "   /ModDate %s\n", ic->docinfo.mod_date);
5
    num_elems = _cairo_array_num_elements (&ic->custom_metadata);
5
    for (i = 0; i < num_elems; i++) {
	data = _cairo_array_index (&ic->custom_metadata, i);
	if (data->value) {
	    _cairo_output_stream_printf (surface->object_stream.stream, "   /");
	    /* The name can be any utf8 string. Use hex codes as
	     * specified in section 7.3.5 of PDF reference
	     */
	    p = (unsigned char *)data->name;
	    while (*p) {
		if (*p < 0x21 || *p > 0x7e || *p == '#' || *p == '/')
		    _cairo_output_stream_printf (surface->object_stream.stream, "#%02x", *p);
		else
		    _cairo_output_stream_printf (surface->object_stream.stream, "%c", *p);
		p++;
	    }
	    _cairo_output_stream_printf (surface->object_stream.stream, " %s\n", data->value);
	}
    }
5
    _cairo_output_stream_printf (surface->object_stream.stream,
				 ">>\n");
5
    _cairo_pdf_surface_object_end (surface);
5
    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
_cairo_pdf_interchange_begin_structure_tag (cairo_pdf_surface_t    *surface,
					    cairo_tag_type_t        tag_type,
					    const char             *name,
					    const char             *attributes)
{
    int mcid;
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_command_entry_t *command_entry;
    cairo_pdf_struct_tree_node_t *parent_node;
    unsigned int content_command_id;
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
	ic->content_emitted = FALSE;
	status = add_tree_node (surface, ic->current_analyze_node, name, attributes, &ic->current_analyze_node);
	if (unlikely (status))
	    return status;
	status = command_list_add (surface, ic->command_id, PDF_BEGIN);
	if (unlikely (status))
	    return status;
	/* Add to command_id to node map. */
	command_entry = _cairo_calloc (sizeof(cairo_pdf_command_entry_t));
	command_entry->recording_id = ic->recording_id;
	command_entry->command_id = ic->command_id;
	command_entry->node = ic->current_analyze_node;
	_cairo_pdf_command_init_key (command_entry);
	status = _cairo_hash_table_insert (ic->command_to_node_map, &command_entry->base);
	if (unlikely(status))
	    return status;
	if (tag_type & TAG_TYPE_LINK) {
	    status = add_annotation (surface, ic->current_analyze_node, name, attributes);
	    if (unlikely (status))
		return status;
	}
	if (ic->current_analyze_node->type == PDF_NODE_CONTENT) {
	    cairo_pdf_content_tag_t *content = _cairo_calloc (sizeof(cairo_pdf_content_tag_t));
	    content->node = ic->current_analyze_node;
	    _cairo_pdf_content_tag_init_key (content);
	    status = _cairo_hash_table_insert (ic->content_tag_map, &content->base);
	    if (unlikely (status))
		return status;
	}
	ic->content_emitted = FALSE;
    } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
	if (ic->marked_content_open) {
	    status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
	    ic->marked_content_open = FALSE;
	    if (unlikely (status))
		return status;
	}
	ic->current_render_node = lookup_node_for_command (surface, ic->recording_id, ic->command_id);
	if (ic->current_render_node->type == PDF_NODE_ARTIFACT) {
	    if (command_list_has_content (surface, ic->command_id, NULL)) {
		status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators, name, -1);
		ic->marked_content_open = TRUE;
	    }
	} else if (ic->current_render_node->type == PDF_NODE_CONTENT_REF) {
	    parent_node = ic->current_render_node->parent;
	    add_child_to_mcid_array (surface, parent_node, ic->command_id, ic->current_render_node);
	} else {
	    parent_node = ic->current_render_node->parent;
	    add_child_to_mcid_array (surface, parent_node, ic->command_id, ic->current_render_node);
	    if (command_list_has_content (surface, ic->command_id, &content_command_id)) {
		add_mcid_to_node (surface, ic->current_render_node, content_command_id, &mcid);
		const char *tag_name = name;
		if (ic->current_render_node->type == PDF_NODE_CONTENT)
		    tag_name = ic->current_render_node->attributes.content.tag_name;
		status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators, tag_name, mcid);
		ic->marked_content_open = TRUE;
	    }
	}
    }
    return status;
}
static cairo_int_status_t
_cairo_pdf_interchange_begin_dest_tag (cairo_pdf_surface_t    *surface,
				       cairo_tag_type_t        tag_type,
				       const char             *name,
				       const char             *attributes)
{
    cairo_pdf_named_dest_t *dest;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
	dest = _cairo_calloc (sizeof (cairo_pdf_named_dest_t));
	if (unlikely (dest == NULL))
	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
	status = _cairo_tag_parse_dest_attributes (attributes, &dest->attrs);
	if (unlikely (status))
	{
	    free (dest);
	    return status;
	}
	dest->page = _cairo_array_num_elements (&surface->pages);
	init_named_dest_key (dest);
	status = _cairo_hash_table_insert (ic->named_dests, &dest->base);
	if (unlikely (status)) {
	    free (dest->attrs.name);
	    free (dest);
	    return status;
	}
	_cairo_tag_stack_set_top_data (&ic->analysis_tag_stack, dest);
	ic->num_dests++;
    }
    return status;
}
cairo_int_status_t
_cairo_pdf_interchange_tag_begin (cairo_pdf_surface_t    *surface,
				  const char             *name,
				  const char             *attributes)
{
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    cairo_tag_type_t tag_type;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    if (ic->ignore_current_surface)
        return CAIRO_STATUS_SUCCESS;
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
	status = _cairo_tag_stack_push (&ic->analysis_tag_stack, name, attributes);
    } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
	status = _cairo_tag_stack_push (&ic->render_tag_stack, name, attributes);
    }
    if (unlikely (status))
	return status;
    tag_type = _cairo_tag_get_type (name);
    if (tag_type & (TAG_TYPE_STRUCTURE|TAG_TYPE_CONTENT|TAG_TYPE_CONTENT_REF|TAG_TYPE_ARTIFACT)) {
	status = _cairo_pdf_interchange_begin_structure_tag (surface, tag_type, name, attributes);
	if (unlikely (status))
	    return status;
    }
    if (tag_type & TAG_TYPE_DEST) {
	status = _cairo_pdf_interchange_begin_dest_tag (surface, tag_type, name, attributes);
	if (unlikely (status))
	    return status;
    }
    return status;
}
static cairo_int_status_t
_cairo_pdf_interchange_end_structure_tag (cairo_pdf_surface_t    *surface,
					  cairo_tag_type_t        tag_type,
					  cairo_tag_stack_elem_t *elem)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    int mcid;
    unsigned int content_command_id;
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
	assert (ic->current_analyze_node->parent != NULL);
	status = command_list_add (surface, ic->command_id, PDF_END);
	if (unlikely (status))
	    return status;
	ic->content_emitted = FALSE;
	ic->current_analyze_node = ic->current_analyze_node->parent;
    } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
	if (ic->marked_content_open) {
	    status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
	    ic->marked_content_open = FALSE;
	    if (unlikely (status))
		return status;
	}
	ic->current_render_node = ic->current_render_node->parent;
	if (ic->current_render_node->parent &&
	    command_list_has_content (surface, ic->command_id, &content_command_id))
	{
	    add_mcid_to_node (surface, ic->current_render_node, content_command_id, &mcid);
	    status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators,
						     ic->current_render_node->name, mcid);
	    ic->marked_content_open = TRUE;
	}
    }
    return status;
}
cairo_int_status_t
_cairo_pdf_interchange_tag_end (cairo_pdf_surface_t *surface,
				const char          *name)
{
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_tag_type_t tag_type;
    cairo_tag_stack_elem_t *elem;
    if (ic->ignore_current_surface)
        return CAIRO_STATUS_SUCCESS;
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
	status = _cairo_tag_stack_pop (&ic->analysis_tag_stack, name, &elem);
    } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
	status = _cairo_tag_stack_pop (&ic->render_tag_stack, name, &elem);
    } else {
	ASSERT_NOT_REACHED;
    }
    if (unlikely (status))
	return status;
    tag_type = _cairo_tag_get_type (name);
    if (tag_type & (TAG_TYPE_STRUCTURE|TAG_TYPE_CONTENT|TAG_TYPE_CONTENT_REF|TAG_TYPE_ARTIFACT)) {
	status = _cairo_pdf_interchange_end_structure_tag (surface, tag_type, elem);
	if (unlikely (status))
	    goto cleanup;
    }
  cleanup:
    _cairo_tag_stack_free_elem (elem);
    return status;
}
cairo_int_status_t
67
_cairo_pdf_interchange_command_id (cairo_pdf_surface_t  *surface,
				   unsigned int          recording_id,
				   unsigned int          command_id)
{
67
    cairo_pdf_interchange_t *ic = &surface->interchange;
    int mcid;
67
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
67
    ic->recording_id = recording_id;
67
    ic->command_id = command_id;
67
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER && ic->current_render_node) {
	/* TODO If the group does not have tags we don't need to close the current tag. */
	if (command_list_is_group (surface, command_id)) {
	    /* A "Do /xnnn" can not be inside a tag (since the
	     * XObject may also contain tags). Close the tag.
	     */
	    if (ic->marked_content_open) {
		status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
		ic->marked_content_open = FALSE;
	    }
	    /* If there is any more content after this and we are
	     * inside a tag (current node is not the root node),
	     * ensure that the next command will open the tag.
	     */
	    if (command_list_has_content (surface, command_id, NULL) && ic->current_render_node->parent) {
		ic->render_next_command_has_content = TRUE;
	    }
	} else if (ic->render_next_command_has_content) {
	    /* After a "Do /xnnn" operation, if there is more content, open the tag. */
	    add_mcid_to_node (surface, ic->current_render_node, ic->command_id, &mcid);
	    status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators,
						     ic->current_render_node->name, mcid);
	    ic->marked_content_open = TRUE;
	    ic->render_next_command_has_content = FALSE;
	}
    }
67
    return status;
}
/* Check if this use of recording surface is or will need to be part of the the struct tree */
cairo_bool_t
22
_cairo_pdf_interchange_struct_tree_requires_recording_surface (
    cairo_pdf_surface_t           *surface,
    const cairo_surface_pattern_t *recording_surface_pattern,
    cairo_analysis_source_t        source_type)
{
22
    cairo_surface_t *recording_surface = recording_surface_pattern->surface;
22
    cairo_surface_t *free_me = NULL;
22
    cairo_bool_t requires_recording = FALSE;
22
    if (recording_surface_pattern->base.extend != CAIRO_EXTEND_NONE)
	return FALSE;
22
    if (_cairo_surface_is_snapshot (recording_surface))
15
	free_me = recording_surface = _cairo_surface_snapshot_get_target (recording_surface);
37
    if (_cairo_surface_is_recording (recording_surface) &&
15
	_cairo_recording_surface_has_tags (recording_surface))
    {
	/* Check if tags are to be ignored in this source */
	switch (source_type) {
	    case CAIRO_ANALYSIS_SOURCE_PAINT:
	    case CAIRO_ANALYSIS_SOURCE_FILL:
		requires_recording = TRUE;
		break;
	    case CAIRO_ANALYSIS_SOURCE_MASK: /* TODO: allow SOURCE_MASK with solid MASK_MASK */
	    case CAIRO_ANALYSIS_MASK_MASK:
	    case CAIRO_ANALYSIS_SOURCE_STROKE:
	    case CAIRO_ANALYSIS_SOURCE_SHOW_GLYPHS:
	    case CAIRO_ANALYSIS_SOURCE_NONE:
		break;
	}
    }
22
    cairo_surface_destroy (free_me);
22
    return requires_recording;
}
/* Called at the start of a recording group during analyze. This will
 * be called during the analysis of the drawing operation. */
cairo_int_status_t
9
_cairo_pdf_interchange_recording_source_surface_begin (
    cairo_pdf_surface_t           *surface,
    const cairo_surface_pattern_t *recording_surface_pattern,
    unsigned int                   region_id,
    cairo_analysis_source_t        source_type)
{
9
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_recording_surface_stack_entry_t element;
    cairo_int_status_t status;
    /* A new recording surface is being replayed */
9
    ic->ignore_current_surface = TRUE;
9
    if (_cairo_pdf_interchange_struct_tree_requires_recording_surface (surface,
								       recording_surface_pattern,
								       source_type))
    {
	ic->ignore_current_surface = FALSE;
    }
9
    element.ignore_surface = ic->ignore_current_surface;
9
    element.current_node = ic->current_analyze_node;
9
    ic->content_emitted = FALSE;
    /* Push to stack so that the current source identifiers can be
     * restored after this recording surface has ended. */
9
    status = _cairo_array_append (&ic->recording_surface_stack, &element);
9
    if (status)
	return status;
9
    if (ic->ignore_current_surface)
9
	return CAIRO_STATUS_SUCCESS;
    status = command_list_push_group (surface, ic->command_id, recording_surface_pattern->surface, region_id);
    if (unlikely (status))
	return status;
    return CAIRO_INT_STATUS_ANALYZE_RECORDING_SURFACE_PATTERN;
}
/* Called at the end of a recording group during analyze. */
cairo_int_status_t
9
_cairo_pdf_interchange_recording_source_surface_end (
    cairo_pdf_surface_t           *surface,
    const cairo_surface_pattern_t *recording_surface_pattern,
    unsigned int                   region_id,
    cairo_analysis_source_t        source_type)
{
9
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_recording_surface_stack_entry_t element;
    cairo_recording_surface_stack_entry_t *element_ptr;
9
    if (!ic->ignore_current_surface)
	command_list_pop_group (surface);
9
    if (_cairo_array_pop_element (&ic->recording_surface_stack, &element)) {
9
	element_ptr = _cairo_array_last_element (&ic->recording_surface_stack);
9
	if (element_ptr) {
3
	    ic->ignore_current_surface = element_ptr->ignore_surface;
3
	    assert (ic->current_analyze_node == element_ptr->current_node);
	} else {
	    /* Back at the page content. */
6
	    ic->ignore_current_surface = FALSE;
	}
9
	ic->content_emitted = FALSE;
9
	return CAIRO_STATUS_SUCCESS;
    }
    ASSERT_NOT_REACHED; /* _recording_source_surface_begin/end mismatch */
    return CAIRO_STATUS_SUCCESS;
}
/* Called at the start of a recording group during render. This will
 * be called after the end of page content. */
cairo_int_status_t
4
_cairo_pdf_interchange_emit_recording_surface_begin (cairo_pdf_surface_t     *surface,
						     cairo_surface_t         *recording_surface,
						     int                      region_id,
						     cairo_pdf_resource_t     surface_resource,
						     int                     *struct_parents)
{
4
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status;
    /* When
     * _cairo_pdf_interchange_struct_tree_requires_recording_surface()
     * is false, the region_id of the recording surface is set to 0.
     */
4
    if (region_id == 0) {
4
	ic->ignore_current_surface = TRUE;
4
	return CAIRO_STATUS_SUCCESS;
    }
    command_list_set_current_recording_commands (surface, recording_surface, region_id);
    ic->ignore_current_surface = FALSE;
    _cairo_array_truncate (&ic->mcid_to_tree, 0);
    ic->current_recording_surface_res = surface_resource;
    ic->content_parent_res = _cairo_pdf_surface_new_object (surface);
    if (ic->content_parent_res.id == 0)
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    status = _cairo_array_append (&ic->parent_tree, &ic->content_parent_res);
    if (unlikely (status))
	return status;
    *struct_parents = _cairo_array_num_elements (&ic->parent_tree) - 1;
    ic->render_next_command_has_content = FALSE;
    return CAIRO_STATUS_SUCCESS;
}
/* Called at the end of a recording group during render. */
cairo_int_status_t
_cairo_pdf_interchange_emit_recording_surface_end (cairo_pdf_surface_t     *surface,
						   cairo_surface_t         *recording_surface)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    if (ic->ignore_current_surface)
	return CAIRO_STATUS_SUCCESS;
    ic->current_recording_surface_res.id = 0;
    return cairo_pdf_interchange_write_content_parent_elems (surface);
}
static void _add_operation_extents_to_dest_tag (cairo_tag_stack_elem_t *elem,
						void                   *closure)
{
    const cairo_rectangle_int_t *extents = (const cairo_rectangle_int_t *) closure;
    cairo_pdf_named_dest_t *dest;
    if (_cairo_tag_get_type (elem->name)  & TAG_TYPE_DEST) {
	if (elem->data) {
	    dest = (cairo_pdf_named_dest_t *) elem->data;
	    if (dest->extents.valid) {
		_cairo_rectangle_union (&dest->extents.extents, extents);
	    } else {
		dest->extents.extents = *extents;
		dest->extents.valid = TRUE;
	    }
	}
    }
}
cairo_int_status_t
74
_cairo_pdf_interchange_add_operation_extents (cairo_pdf_surface_t         *surface,
					      const cairo_rectangle_int_t *extents)
{
74
    cairo_pdf_interchange_t *ic = &surface->interchange;
    /* Add extents to current node and all DEST tags on the stack */
74
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
45
	if (ic->current_analyze_node) {
45
	    if (ic->current_analyze_node->extents.valid) {
41
		_cairo_rectangle_union (&ic->current_analyze_node->extents.extents, extents);
	    } else {
4
		ic->current_analyze_node->extents.extents = *extents;
4
		ic->current_analyze_node->extents.valid = TRUE;
	    }
	}
45
	_cairo_tag_stack_foreach (&ic->analysis_tag_stack,
				  _add_operation_extents_to_dest_tag,
				  (void*)extents);
    }
74
    return CAIRO_STATUS_SUCCESS;
}
cairo_int_status_t
_cairo_pdf_interchange_add_content (cairo_pdf_surface_t *surface)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    if (ic->ignore_current_surface)
        return CAIRO_STATUS_SUCCESS;
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
	status = command_list_add (surface, ic->command_id, PDF_CONTENT);
	if (unlikely (status))
	    return status;
    }
    return status;
}
/* Called at the start of 1emiting the page content during analyze or render */
cairo_int_status_t
21
_cairo_pdf_interchange_begin_page_content (cairo_pdf_surface_t *surface)
{
21
    cairo_pdf_interchange_t *ic = &surface->interchange;
21
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    int mcid;
    unsigned int content_command_id;
    cairo_pdf_command_list_t *page_commands;
21
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
5
	status = _cairo_array_allocate (&ic->page_commands, 1, (void**)&page_commands);
5
	if (unlikely (status))
	    return status;
5
	_cairo_array_init (&page_commands->commands, sizeof(cairo_pdf_command_t));
5
	page_commands->parent = NULL;
5
	ic->current_commands = page_commands;
5
	ic->ignore_current_surface = FALSE;
16
    } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
5
	ic->current_commands = _cairo_array_last_element (&ic->page_commands);
	/* Each page has its own parent tree to map MCID to nodes. */
5
	_cairo_array_truncate (&ic->mcid_to_tree, 0);
5
	ic->ignore_current_surface = FALSE;
5
	ic->content_parent_res = _cairo_pdf_surface_new_object (surface);
5
	if (ic->content_parent_res.id == 0)
	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
5
	status = _cairo_array_append (&ic->parent_tree, &ic->content_parent_res);
5
	if (unlikely (status))
	    return status;
5
	surface->page_parent_tree = _cairo_array_num_elements (&ic->parent_tree) - 1;
5
	if (ic->next_page_render_node && ic->next_page_render_node->parent &&
	    command_list_has_content (surface, -1, &content_command_id))
	{
	    add_mcid_to_node (surface, ic->next_page_render_node, content_command_id, &mcid);
	    const char *tag_name = ic->next_page_render_node->name;
	    if (ic->next_page_render_node->type == PDF_NODE_CONTENT)
		tag_name = ic->next_page_render_node->attributes.content.tag_name;
	    status = _cairo_pdf_operators_tag_begin (&surface->pdf_operators, tag_name, mcid);
	    ic->marked_content_open = TRUE;
	}
5
	ic->render_next_command_has_content = FALSE;
    }
21
    return status;
}
/* Called at the end of emiting the page content during analyze or render */
cairo_int_status_t
10
_cairo_pdf_interchange_end_page_content (cairo_pdf_surface_t *surface)
{
10
    cairo_pdf_interchange_t *ic = &surface->interchange;
10
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
10
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_RENDER) {
	/* If a content tag is open across pages, the old page needs an EMC emitted. */
1
	if (ic->marked_content_open) {
	    status = _cairo_pdf_operators_tag_end (&surface->pdf_operators);
	    ic->marked_content_open = FALSE;
	}
1
	ic->next_page_render_node = ic->current_render_node;
    }
10
    return status;
}
cairo_int_status_t
5
_cairo_pdf_interchange_write_page_objects (cairo_pdf_surface_t *surface)
{
5
    return cairo_pdf_interchange_write_content_parent_elems (surface);
}
cairo_int_status_t
5
_cairo_pdf_interchange_write_document_objects (cairo_pdf_surface_t *surface)
{
5
    cairo_pdf_interchange_t *ic = &surface->interchange;
5
    cairo_int_status_t status = CAIRO_STATUS_SUCCESS;
    cairo_tag_stack_structure_type_t tag_type;
5
    cairo_bool_t write_struct_tree = FALSE;
5
    status = cairo_pdf_interchange_update_extents (surface);
5
    if (unlikely (status))
	return status;
5
    tag_type = _cairo_tag_stack_get_structure_type (&ic->analysis_tag_stack);
5
    if (tag_type == TAG_TREE_TYPE_TAGGED || tag_type == TAG_TREE_TYPE_STRUCTURE ||
	tag_type == TAG_TREE_TYPE_LINK_ONLY)
    {
	write_struct_tree = TRUE;
    }
5
    status = cairo_pdf_interchange_write_annots (surface, write_struct_tree);
5
    if (unlikely (status))
	return status;
5
    if (write_struct_tree) {
	surface->struct_tree_root = _cairo_pdf_surface_new_object (surface);
	if (surface->struct_tree_root.id == 0)
	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
	ic->struct_root->res = surface->struct_tree_root;
	status = cairo_pdf_interchange_write_parent_tree (surface);
	if (unlikely (status))
	    return status;
	unsigned num_pages = _cairo_array_num_elements (&ic->page_commands);
	for (unsigned i = 0; i < num_pages; i++) {
	    cairo_pdf_command_list_t *command_list;
	    command_list = _cairo_array_index (&ic->page_commands, i);
	    update_mcid_order (surface, command_list);
	}
	status = cairo_pdf_interchange_write_struct_tree (surface);
	if (unlikely (status))
	    return status;
	if (tag_type == TAG_TREE_TYPE_TAGGED)
	    surface->tagged = TRUE;
    }
5
    status = cairo_pdf_interchange_write_outline (surface);
5
    if (unlikely (status))
	return status;
5
    status = cairo_pdf_interchange_write_page_labels (surface);
5
    if (unlikely (status))
	return status;
5
    status = cairo_pdf_interchange_write_names_dict (surface);
5
    if (unlikely (status))
	return status;
5
    status = cairo_pdf_interchange_write_docinfo (surface);
5
    return status;
}
static void
5
_cairo_pdf_interchange_set_create_date (cairo_pdf_surface_t *surface)
{
    time_t utc, local, offset;
    struct tm tm_local, tm_utc;
    char buf[50];
    int buf_size;
    char *p;
5
    cairo_pdf_interchange_t *ic = &surface->interchange;
5
    utc = time (NULL);
5
    localtime_r (&utc, &tm_local);
5
    strftime (buf, sizeof(buf), "(D:%Y%m%d%H%M%S", &tm_local);
    /* strftime "%z" is non standard and does not work on windows (it prints zone name, not offset).
     * Calculate time zone offset by comparing local and utc time_t values for the same time.
     */
5
    gmtime_r (&utc, &tm_utc);
5
    tm_utc.tm_isdst = tm_local.tm_isdst;
5
    local = mktime (&tm_utc);
5
    offset = difftime (utc, local);
5
    if (offset == 0) {
5
	strcat (buf, "Z");
    } else {
	if (offset > 0) {
	    strcat (buf, "+");
	} else {
	    strcat (buf, "-");
	    offset = -offset;
	}
	p = buf + strlen (buf);
	buf_size = sizeof (buf) - strlen (buf);
	snprintf (p, buf_size, "%02d'%02d", (int)(offset/3600), (int)(offset%3600)/60);
    }
5
    strcat (buf, ")");
5
    ic->docinfo.create_date = strdup (buf);
5
}
cairo_int_status_t
5
_cairo_pdf_interchange_init (cairo_pdf_surface_t *surface)
{
5
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_outline_entry_t *outline_root;
    cairo_int_status_t status;
5
    _cairo_tag_stack_init (&ic->analysis_tag_stack);
5
    _cairo_tag_stack_init (&ic->render_tag_stack);
5
    ic->struct_root = _cairo_calloc (sizeof(cairo_pdf_struct_tree_node_t));
5
    if (unlikely (ic->struct_root == NULL))
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
5
    ic->struct_root->res.id = 0;
5
    cairo_list_init (&ic->struct_root->children);
5
    _cairo_array_init (&ic->struct_root->mcid, sizeof(cairo_pdf_page_mcid_t));
5
    ic->current_analyze_node = ic->struct_root;
5
    ic->current_render_node = NULL;
5
    ic->next_page_render_node = ic->struct_root;
5
    _cairo_array_init (&ic->recording_surface_stack, sizeof(cairo_recording_surface_stack_entry_t));
5
    ic->current_recording_surface_res.id = 0;
5
    ic->command_to_node_map = _cairo_hash_table_create (_cairo_pdf_command_equal);
5
    if (unlikely (ic->command_to_node_map == NULL))
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
5
    ic->content_tag_map = _cairo_hash_table_create (_cairo_pdf_content_tag_equal);
5
    if (unlikely (ic->content_tag_map == NULL))
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
5
    _cairo_array_init (&ic->parent_tree, sizeof(cairo_pdf_resource_t));
5
    _cairo_array_init (&ic->mcid_to_tree, sizeof(cairo_pdf_struct_tree_node_t *));
5
    _cairo_array_init (&ic->annots, sizeof(cairo_pdf_annotation_t *));
5
    ic->parent_tree_res.id = 0;
5
    cairo_list_init (&ic->extents_list);
5
    ic->named_dests = _cairo_hash_table_create (_named_dest_equal);
5
    if (unlikely (ic->named_dests == NULL))
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
5
    _cairo_array_init (&ic->page_commands, sizeof(cairo_pdf_command_list_t));
5
    ic->current_commands = NULL;
5
    _cairo_array_init (&ic->recording_surface_commands, sizeof(cairo_pdf_recording_surface_commands_t));
5
    ic->num_dests = 0;
5
    ic->sorted_dests = NULL;
5
    ic->dests_res.id = 0;
5
    ic->ignore_current_surface = FALSE;
5
    ic->content_emitted = FALSE;
5
    ic->marked_content_open = FALSE;
5
    ic->render_next_command_has_content = FALSE;
5
    ic->mcid_order = 0;
5
    _cairo_array_init (&ic->outline, sizeof(cairo_pdf_outline_entry_t *));
5
    outline_root = _cairo_calloc (sizeof(cairo_pdf_outline_entry_t));
5
    if (unlikely (outline_root == NULL))
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
5
    memset (&ic->docinfo, 0, sizeof (ic->docinfo));
5
    _cairo_array_init (&ic->custom_metadata, sizeof(struct metadata));
5
    _cairo_pdf_interchange_set_create_date (surface);
5
    status = _cairo_array_append (&ic->outline, &outline_root);
5
    return status;
}
static void
5
_cairo_pdf_interchange_free_outlines (cairo_pdf_surface_t *surface)
{
5
    cairo_pdf_interchange_t *ic = &surface->interchange;
    int num_elems, i;
5
    num_elems = _cairo_array_num_elements (&ic->outline);
10
    for (i = 0; i < num_elems; i++) {
	cairo_pdf_outline_entry_t *outline;
5
	_cairo_array_copy_element (&ic->outline, i, &outline);
5
	free (outline->name);
5
	_cairo_tag_free_link_attributes (&outline->link_attrs);
5
	free (outline);
    }
5
    _cairo_array_fini (&ic->outline);
5
}
void
5
_cairo_pdf_interchange_fini (cairo_pdf_surface_t *surface)
{
5
    cairo_pdf_interchange_t *ic = &surface->interchange;
    unsigned int i, num_elems;
    struct metadata *data;
5
    _cairo_tag_stack_fini (&ic->analysis_tag_stack);
5
    _cairo_tag_stack_fini (&ic->render_tag_stack);
5
    _cairo_array_fini (&ic->mcid_to_tree);
5
    cairo_pdf_interchange_clear_annotations (surface);
5
    _cairo_array_fini (&ic->annots);
5
    _cairo_array_fini (&ic->recording_surface_stack);
5
    _cairo_array_fini (&ic->parent_tree);
5
    _cairo_hash_table_foreach (ic->command_to_node_map,
			       _cairo_pdf_command_pluck,
5
			       ic->command_to_node_map);
5
    _cairo_hash_table_destroy (ic->command_to_node_map);
5
    _cairo_hash_table_foreach (ic->named_dests, _named_dest_pluck, ic->named_dests);
5
    _cairo_hash_table_destroy (ic->named_dests);
5
    _cairo_hash_table_foreach (ic->content_tag_map, _cairo_pdf_content_tag_pluck, ic->content_tag_map);
5
    _cairo_hash_table_destroy(ic->content_tag_map);
5
    free_node (ic->struct_root);
5
    num_elems = _cairo_array_num_elements (&ic->recording_surface_commands);
5
    for (i = 0; i < num_elems; i++) {
	cairo_pdf_recording_surface_commands_t *recording_command;
	cairo_pdf_command_list_t *command_list;
	recording_command = _cairo_array_index (&ic->recording_surface_commands, i);
	command_list = recording_command->command_list;
	_cairo_array_fini (&command_list->commands);
	free (command_list);
    }
5
    _cairo_array_fini (&ic->recording_surface_commands);
5
    num_elems = _cairo_array_num_elements (&ic->page_commands);
10
    for (i = 0; i < num_elems; i++) {
	cairo_pdf_command_list_t *command_list;
5
	command_list = _cairo_array_index (&ic->page_commands, i);
5
	_cairo_array_fini (&command_list->commands);
    }
5
    _cairo_array_fini (&ic->page_commands);
5
    free (ic->sorted_dests);
5
    _cairo_pdf_interchange_free_outlines (surface);
5
    free (ic->docinfo.title);
5
    free (ic->docinfo.author);
5
    free (ic->docinfo.subject);
5
    free (ic->docinfo.keywords);
5
    free (ic->docinfo.creator);
5
    free (ic->docinfo.create_date);
5
    free (ic->docinfo.mod_date);
5
    num_elems = _cairo_array_num_elements (&ic->custom_metadata);
5
    for (i = 0; i < num_elems; i++) {
	data = _cairo_array_index (&ic->custom_metadata, i);
	free (data->name);
	free (data->value);
    }
5
    _cairo_array_fini (&ic->custom_metadata);
5
}
cairo_int_status_t
_cairo_pdf_interchange_add_outline (cairo_pdf_surface_t        *surface,
				    int                         parent_id,
				    const char                 *name,
				    const char                 *link_attribs,
				    cairo_pdf_outline_flags_t   flags,
				    int                        *id)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_pdf_outline_entry_t *outline;
    cairo_pdf_outline_entry_t *parent;
    cairo_int_status_t status;
    if (parent_id < 0 || parent_id >= (int)_cairo_array_num_elements (&ic->outline))
	return CAIRO_STATUS_SUCCESS;
    outline = _cairo_calloc (sizeof(cairo_pdf_outline_entry_t));
    if (unlikely (outline == NULL))
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    status = _cairo_tag_parse_link_attributes (link_attribs, &outline->link_attrs);
    if (unlikely (status)) {
	free (outline);
	return status;
    }
    outline->res = _cairo_pdf_surface_new_object (surface);
    if (outline->res.id == 0)
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    outline->name = strdup (name);
    outline->flags = flags;
    outline->count = 0;
    _cairo_array_copy_element (&ic->outline, parent_id, &parent);
    outline->parent = parent;
    outline->first_child = NULL;
    outline->last_child = NULL;
    outline->next = NULL;
    if (parent->last_child) {
	parent->last_child->next = outline;
	outline->prev = parent->last_child;
	parent->last_child = outline;
    } else {
	parent->first_child = outline;
	parent->last_child = outline;
	outline->prev = NULL;
    }
    *id = _cairo_array_num_elements (&ic->outline);
    status = _cairo_array_append (&ic->outline, &outline);
    if (unlikely (status))
	return status;
    /* Update Count */
    outline = outline->parent;
    while (outline) {
	if (outline->flags & CAIRO_PDF_OUTLINE_FLAG_OPEN) {
	    outline->count++;
	} else {
	    outline->count--;
	    break;
	}
	outline = outline->parent;
    }
    return CAIRO_STATUS_SUCCESS;
}
/*
 * Date must be in the following format:
 *
 *     YYYY-MM-DDThh:mm:ss[Z+-]hh:mm
 *
 * Only the year is required. If a field is included all preceding
 * fields must be included.
 */
static char *
iso8601_to_pdf_date_string (const char *iso)
{
    char buf[40];
    const char *p;
    int i;
    /* Check that utf8 contains only the characters "0123456789-T:Z+" */
    p = iso;
    while (*p) {
       if (!_cairo_isdigit (*p) && *p != '-' && *p != 'T' &&
           *p != ':' && *p != 'Z' && *p != '+')
           return NULL;
       p++;
    }
    p = iso;
    strcpy (buf, "(");
   /* YYYY (required) */
    if (strlen (p) < 4)
       return NULL;
    strncat (buf, p, 4);
    p += 4;
    /* -MM, -DD, Thh, :mm, :ss */
    for (i = 0; i < 5; i++) {
	if (strlen (p) < 3)
	    goto finish;
	strncat (buf, p + 1, 2);
	p += 3;
    }
    /* Z, +, - */
    if (strlen (p) < 1)
       goto finish;
    strncat (buf, p, 1);
    p += 1;
    /* hh */
    if (strlen (p) < 2)
	goto finish;
    strncat (buf, p, 2);
    strcat (buf, "'");
    p += 2;
    /* :mm */
    if (strlen (p) < 3)
	goto finish;
    strncat (buf, p + 1, 2);
    strcat (buf, "'");
  finish:
    strcat (buf, ")");
    return strdup (buf);
}
cairo_int_status_t
_cairo_pdf_interchange_set_metadata (cairo_pdf_surface_t  *surface,
				     cairo_pdf_metadata_t  metadata,
				     const char           *utf8)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    cairo_status_t status;
    char *s = NULL;
    if (utf8) {
	if (metadata == CAIRO_PDF_METADATA_CREATE_DATE ||
	    metadata == CAIRO_PDF_METADATA_MOD_DATE) {
	    s = iso8601_to_pdf_date_string (utf8);
	} else {
	    status = _cairo_utf8_to_pdf_string (utf8, &s);
	    if (unlikely (status))
		return status;
	}
    }
    switch (metadata) {
	case CAIRO_PDF_METADATA_TITLE:
	    free (ic->docinfo.title);
	    ic->docinfo.title = s;
	    break;
	case CAIRO_PDF_METADATA_AUTHOR:
	    free (ic->docinfo.author);
	    ic->docinfo.author = s;
	    break;
	case CAIRO_PDF_METADATA_SUBJECT:
	    free (ic->docinfo.subject);
	    ic->docinfo.subject = s;
	    break;
	case CAIRO_PDF_METADATA_KEYWORDS:
	    free (ic->docinfo.keywords);
	    ic->docinfo.keywords = s;
	    break;
	case CAIRO_PDF_METADATA_CREATOR:
	    free (ic->docinfo.creator);
	    ic->docinfo.creator = s;
	    break;
	case CAIRO_PDF_METADATA_CREATE_DATE:
	    free (ic->docinfo.create_date);
	    ic->docinfo.create_date = s;
	    break;
	case CAIRO_PDF_METADATA_MOD_DATE:
	    free (ic->docinfo.mod_date);
	    ic->docinfo.mod_date = s;
	    break;
    }
    return CAIRO_STATUS_SUCCESS;
}
static const char *reserved_metadata_names[] = {
    "",
    "Title",
    "Author",
    "Subject",
    "Keywords",
    "Creator",
    "Producer",
    "CreationDate",
    "ModDate",
    "Trapped",
};
cairo_int_status_t
_cairo_pdf_interchange_set_custom_metadata (cairo_pdf_surface_t  *surface,
					    const char           *name,
					    const char           *value)
{
    cairo_pdf_interchange_t *ic = &surface->interchange;
    struct metadata *data;
    struct metadata new_data;
    int i, num_elems;
    cairo_int_status_t status;
    char *s = NULL;
    if (name == NULL)
	return CAIRO_STATUS_NULL_POINTER;
    for (i = 0; i < ARRAY_LENGTH (reserved_metadata_names); i++) {
	if (strcmp(name, reserved_metadata_names[i]) == 0)
	    return CAIRO_STATUS_INVALID_STRING;
    }
    /* First check if we already have an entry for this name. If so,
     * update the value. A NULL value means the entry has been removed
     * and will not be emitted. */
    num_elems = _cairo_array_num_elements (&ic->custom_metadata);
    for (i = 0; i < num_elems; i++) {
	data = _cairo_array_index (&ic->custom_metadata, i);
	if (strcmp(name, data->name) == 0) {
	    free (data->value);
	    data->value = NULL;
	    if (value && strlen(value)) {
		status = _cairo_utf8_to_pdf_string (value, &s);
		if (unlikely (status))
		    return status;
		data->value = s;
	    }
	    return CAIRO_STATUS_SUCCESS;
	}
    }
    /* Add new entry */
    status = CAIRO_STATUS_SUCCESS;
    if (value && strlen(value)) {
	new_data.name = strdup (name);
	status = _cairo_utf8_to_pdf_string (value, &s);
	if (unlikely (status)) {
            free (new_data.name);
	    return status;
        }
	new_data.value = s;
	status = _cairo_array_append (&ic->custom_metadata, &new_data);
    }
    return status;
}
#if DEBUG_PDF_INTERCHANGE
static cairo_int_status_t
print_node (cairo_pdf_surface_t          *surface,
	    cairo_pdf_struct_tree_node_t *node,
	    int                           depth)
{
    if (node == NULL) {
	printf("%*sNode: ptr: NULL\n", depth*2, "");
    } else if (node == surface->interchange.struct_root) {
       printf("%*sNode: ptr: %p root\n", depth*2, "", node);
    } else {
       printf("%*sNode: ptr: %p name: '%s'\n", depth*2, "", node, node->name);
    }
    depth++;
    printf("%*sType: ", depth*2, "");
    switch (node->type) {
	case PDF_NODE_STRUCT:
	    printf("STRUCT\n");
	    break;
	case PDF_NODE_CONTENT:
	    printf("CONTENT\n");
	    printf("%*sContent.id: %s\n", depth*2, "", node->attributes.content.id);
	    printf("%*sContent.tag_name: %s\n", depth*2, "", node->attributes.content.tag_name);
	    break;
	case PDF_NODE_CONTENT_REF:
	    printf("CONTENT_REF\n");
	    printf("%*sContent_Ref.ref: %s\n", depth*2, "", node->attributes.content_ref.ref);
	    break;
	case PDF_NODE_ARTIFACT:
	    printf("ARTIFACT\n");
	    break;
    }
    printf("%*sres: %d\n", depth*2, "", node->res.id);
    printf("%*sparent: %p\n", depth*2, "", node->parent);
    printf("%*sannot:", depth*2, "");
    if (node->annot)
	printf(" node: %p res: %d", node->annot->node,  node->annot->res.id);
    printf("\n");
    printf("%*sextents: ", depth*2, "");
    if (node->extents.valid) {
	printf("x: %d  y: %d  w: %d  h: %d\n",
	       node->extents.extents.x,
	       node->extents.extents.y,
	       node->extents.extents.width,
	       node->extents.extents.height);
    } else {
	printf("not valid\n");
    }
    printf("%*smcid: ", depth*2, "");
    int num_mcid = _cairo_array_num_elements (&node->mcid);
    for (int i = 0; i < num_mcid; i++) {
	cairo_pdf_page_mcid_t *mcid_elem = _cairo_array_index (&node->mcid, i);
	if (mcid_elem->child_node) {
	    printf("(order: %d, %p) ", mcid_elem->order, mcid_elem->child_node);
	} else {
	    printf("(order: %d, pg: %d, xobject_res: %d, mcid: %d) ",
		   mcid_elem->order, mcid_elem->page, mcid_elem->xobject_res.id, mcid_elem->mcid);
	}
    }
    printf("\n");
    return CAIRO_STATUS_SUCCESS;
}
static void
print_tree (cairo_pdf_surface_t          *surface,
	    cairo_pdf_struct_tree_node_t *node)
{
    printf("Structure Tree:\n");
    cairo_pdf_interchange_walk_struct_tree (surface, node, 0, print_node);
}
static void
print_command (cairo_pdf_command_t *command, int indent)
{
    printf("%*s%d  ", indent*2, "", command->command_id);
    switch (command->flags) {
	case PDF_CONTENT:
	    printf("CONTENT");
	    break;
	case PDF_BEGIN:
	    printf("BEGIN");
	    break;
	case PDF_END:
	    printf("END");
		break;
	case PDF_GROUP:
	    printf("GROUP: %p", command->group);
	    break;
	case PDF_NONE:
	    printf("NONE");
	    break;
    }
    printf("  node: %p index: %d\n", command->node, command->mcid_index);
}
static void
print_commands (cairo_pdf_command_list_t *command_list, int indent)
{
    cairo_pdf_command_t *command;
    unsigned i;
    unsigned num_elements = _cairo_array_num_elements (&command_list->commands);
    for (i = 0; i < num_elements; i++) {
	command = _cairo_array_index (&command_list->commands, i);
	print_command (command, indent);
	if (command->flags == PDF_GROUP)
	    print_commands (command->group, indent + 1);
    }
}
static void
print_command_list(cairo_pdf_command_list_t *command_list)
{
    printf("Command List: %p\n", command_list);
    print_commands (command_list, 0);
    printf("end\n");
}
#endif