1
/* vim: set sw=4 sts=4: -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */
2
/* cairo - a vector graphics library with display and print output
3
 *
4
 * Copyright © 2004 Red Hat, Inc
5
 * Copyright © 2005-2007 Emmanuel Pacaud <emmanuel.pacaud@free.fr>
6
 * Copyright © 2006 Red Hat, Inc
7
 * Copyright © 2020-2021 Anton Danilkin <afdw@yandex.ru>
8
 *
9
 * This library is free software; you can redistribute it and/or
10
 * modify it either under the terms of the GNU Lesser General Public
11
 * License version 2.1 as published by the Free Software Foundation
12
 * (the "LGPL") or, at your option, under the terms of the Mozilla
13
 * Public License Version 1.1 (the "MPL"). If you do not alter this
14
 * notice, a recipient may use your version of this file under either
15
 * the MPL or the LGPL.
16
 *
17
 * You should have received a copy of the LGPL along with this library
18
 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
20
 * You should have received a copy of the MPL along with this library
21
 * in the file COPYING-MPL-1.1
22
 *
23
 * The contents of this file are subject to the Mozilla Public License
24
 * Version 1.1 (the "License"); you may not use this file except in
25
 * compliance with the License. You may obtain a copy of the License at
26
 * http://www.mozilla.org/MPL/
27
 *
28
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
29
 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
30
 * the specific language governing rights and limitations.
31
 *
32
 * The Original Code is the cairo graphics library.
33
 *
34
 * The Initial Developer of the Original Code is University of Southern
35
 * California.
36
 *
37
 * Contributor(s):
38
 *	Kristian Høgsberg <krh@redhat.com>
39
 *	Emmanuel Pacaud <emmanuel.pacaud@free.fr>
40
 *	Carl Worth <cworth@cworth.org>
41
 *	Anton Danilkin <afdw@yandex.ru>
42
 */
43

            
44
#include "cairoint.h"
45

            
46
#include "cairo-svg.h"
47

            
48
#include "cairo-array-private.h"
49
#include "cairo-default-context-private.h"
50
#include "cairo-error-private.h"
51
#include "cairo-image-info-private.h"
52
#include "cairo-image-surface-private.h"
53
#include "cairo-recording-surface-inline.h"
54
#include "cairo-output-stream-private.h"
55
#include "cairo-paginated-private.h"
56
#include "cairo-scaled-font-subsets-private.h"
57
#include "cairo-surface-clipper-private.h"
58
#include "cairo-surface-snapshot-inline.h"
59
#include "cairo-svg-surface-private.h"
60

            
61
/**
62
 * SECTION:cairo-svg
63
 * @Title: SVG Surfaces
64
 * @Short_Description: Rendering SVG documents
65
 * @See_Also: #cairo_surface_t
66
 *
67
 * The SVG surface is used to render cairo graphics to
68
 * SVG files and is a multi-page vector surface backend.
69
 **/
70

            
71
typedef struct _cairo_svg_source_surface {
72
    cairo_hash_entry_t base;
73
    unsigned int id;
74
    unsigned char *unique_id;
75
    unsigned long unique_id_length;
76
    cairo_bool_t transitive_paint_used;
77
} cairo_svg_source_surface_t;
78

            
79
/*
80
 * _cairo_svg_paint_element and _cairo_svg_paint are used to implement paints in transformed recording patterns.
81
 */
82

            
83
typedef struct _cairo_svg_paint_element {
84
    unsigned int source_id;
85
    cairo_matrix_t matrix;
86
} cairo_svg_paint_element_t;
87

            
88
typedef struct _cairo_svg_paint {
89
    cairo_hash_entry_t base;
90
    unsigned int source_id;
91
    cairo_array_t paint_elements;
92
    cairo_box_double_t box;
93
} cairo_svg_paint_t;
94

            
95
static void
96
22
_cairo_svg_source_surface_init_key (cairo_svg_source_surface_t *source_surface)
97
{
98
22
    if (source_surface->unique_id && source_surface->unique_id_length > 0) {
99
	source_surface->base.hash = _cairo_hash_bytes (_CAIRO_HASH_INIT_VALUE,
100
						       source_surface->unique_id,
101
						       source_surface->unique_id_length);
102
    } else {
103
22
	source_surface->base.hash = source_surface->id;
104
    }
105
22
}
106

            
107
static cairo_bool_t
108
_cairo_svg_source_surface_equal (const void *key_a, const void *key_b)
109
{
110
    const cairo_svg_source_surface_t *a = key_a;
111
    const cairo_svg_source_surface_t *b = key_b;
112

            
113
    if (a->unique_id && b->unique_id && a->unique_id_length == b->unique_id_length) {
114
	return memcmp (a->unique_id, b->unique_id, a->unique_id_length) == 0;
115
    }
116

            
117
    return a->id == b->id;
118
}
119

            
120
static void
121
11
_cairo_svg_source_surface_pluck (void *entry, void *closure)
122
{
123
11
    cairo_svg_source_surface_t *source_surface = entry;
124
11
    cairo_hash_table_t *patterns = closure;
125

            
126
11
    _cairo_hash_table_remove (patterns, &source_surface->base);
127
11
    free (source_surface->unique_id);
128
11
    free (source_surface);
129
11
}
130

            
131
static void
132
284
_cairo_svg_paint_init_key (cairo_svg_paint_t *paint)
133
{
134
284
    paint->base.hash = paint->source_id;
135
284
}
136

            
137
static cairo_bool_t
138
274
_cairo_svg_paint_equal (const void *key_a, const void *key_b)
139
{
140
274
    const cairo_svg_paint_t *a = key_a;
141
274
    const cairo_svg_paint_t *b = key_b;
142

            
143
274
    return a->source_id == b->source_id;
144
}
145

            
146
static void
147
10
_cairo_svg_paint_pluck (void *entry, void *closure)
148
{
149
10
    cairo_svg_paint_t *paint = entry;
150
10
    cairo_hash_table_t *patterns = closure;
151

            
152
10
    _cairo_hash_table_remove (patterns, &paint->base);
153
10
    _cairo_array_fini (&paint->paint_elements);
154
10
    free (paint);
155
10
}
156

            
157
static void
158
10
_cairo_svg_paint_box_add_padding (cairo_box_double_t *box)
159
{
160
10
    double width = box->p2.x - box->p1.x;
161
10
    double height = box->p2.y - box->p1.y;
162

            
163
10
    box->p1.x -= width / 10.0;
164
10
    box->p1.y -= height / 10.0;
165
10
    box->p2.x += width / 10.0;
166
10
    box->p2.y += height / 10.0;
167
10
}
168

            
169
enum cairo_svg_stream_element_type {
170
    CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT,
171
    CAIRO_SVG_STREAM_ELEMENT_TYPE_PAINT_DEPENDENT,
172
};
173

            
174
enum cairo_svg_stream_paint_dependent_element_type {
175
    CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE,
176
    CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN,
177
    CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_TRANSLATION,
178
    CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION,
179
};
180

            
181
typedef struct _cairo_svg_stream_element {
182
    enum cairo_svg_stream_element_type type;
183
    union {
184
        struct {
185
	    cairo_output_stream_t *output_stream;
186
	} text;
187
        struct {
188
	    unsigned int source_id;
189
	    enum cairo_svg_stream_paint_dependent_element_type type;
190
        } paint_dependent;
191
    };
192
} cairo_svg_stream_element_t;
193

            
194
typedef struct _cairo_svg_stream {
195
    cairo_status_t status;
196
    cairo_array_t elements;
197
} cairo_svg_stream_t;
198

            
199
static cairo_svg_stream_t
200
127
_cairo_svg_stream_create ()
201
{
202
    cairo_svg_stream_t svg_stream;
203
127
    svg_stream.status = CAIRO_STATUS_SUCCESS;
204
127
    _cairo_array_init (&svg_stream.elements, sizeof (cairo_svg_stream_element_t));
205
127
    return svg_stream;
206
}
207

            
208
static void
209
1200
_cairo_svg_stream_write (cairo_svg_stream_t *svg_stream,
210
			 const void *data,
211
			 size_t length)
212
{
213
    cairo_status_t status;
214

            
215
1200
    cairo_svg_stream_element_t *last_element = NULL;
216
1200
    if (svg_stream->elements.num_elements > 0) {
217
1200
	last_element = _cairo_array_index (&svg_stream->elements,
218
1200
					   svg_stream->elements.num_elements - 1);
219
    }
220

            
221
1200
    if (last_element == NULL || last_element->type != CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) {
222
	cairo_svg_stream_element_t element;
223
	element.type = CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT;
224
	element.text.output_stream = _cairo_memory_stream_create();
225
	status = _cairo_array_append (&svg_stream->elements, &element);
226
	if (unlikely (status)) {
227
	    if (svg_stream->status == CAIRO_STATUS_SUCCESS) {
228
		svg_stream->status = status;
229
	    }
230
	    return;
231
	}
232
	last_element = _cairo_array_index (&svg_stream->elements,
233
					   svg_stream->elements.num_elements - 1);
234
    }
235

            
236
1200
    _cairo_output_stream_write (last_element->text.output_stream, data, length);
237
}
238

            
239
static void CAIRO_PRINTF_FORMAT (2, 0)
240
1891
_cairo_svg_stream_printf (cairo_svg_stream_t *svg_stream,
241
			  const char *fmt,
242
			  ...)
243
{
244
    cairo_status_t status;
245

            
246
1891
    cairo_svg_stream_element_t *last_element = NULL;
247
1891
    if (svg_stream->elements.num_elements > 0) {
248
1789
	last_element = _cairo_array_index (&svg_stream->elements,
249
1789
					   svg_stream->elements.num_elements - 1);
250
    }
251

            
252
1891
    if (last_element == NULL || last_element->type != CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) {
253
        cairo_svg_stream_element_t element;
254
364
	element.type = CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT;
255
364
	element.text.output_stream = _cairo_memory_stream_create();
256
364
	status = _cairo_array_append (&svg_stream->elements, &element);
257
364
	if (unlikely (status)) {
258
	    if (svg_stream->status == CAIRO_STATUS_SUCCESS) {
259
		svg_stream->status = status;
260
	    }
261
	    return;
262
	}
263
364
	last_element = _cairo_array_index (&svg_stream->elements,
264
364
					   svg_stream->elements.num_elements - 1);
265
    }
266

            
267
    va_list ap;
268
1891
    va_start (ap, fmt);
269
1891
    _cairo_output_stream_vprintf (last_element->text.output_stream, fmt, ap);
270
1891
    va_end (ap);
271
}
272

            
273
static void
274
262
_cairo_svg_stream_append_paint_dependent (cairo_svg_stream_t *svg_stream,
275
					  unsigned int source_id,
276
					  enum cairo_svg_stream_paint_dependent_element_type type)
277
{
278
    cairo_status_t status;
279

            
280
    cairo_svg_stream_element_t element;
281
262
    element.type = CAIRO_SVG_STREAM_ELEMENT_TYPE_PAINT_DEPENDENT;
282
262
    element.paint_dependent.source_id = source_id;
283
262
    element.paint_dependent.type = type;
284
262
    status = _cairo_array_append (&svg_stream->elements, &element);
285
262
    if (svg_stream->status == CAIRO_STATUS_SUCCESS) {
286
262
	svg_stream->status = status;
287
    }
288
262
}
289

            
290
static void
291
93
_cairo_svg_stream_copy (cairo_svg_stream_t *from,
292
			cairo_svg_stream_t *to)
293
{
294
    cairo_status_t status;
295

            
296
93
    if (unlikely (from->status)) {
297
	if (to->status == CAIRO_STATUS_SUCCESS) {
298
	    to->status = from->status;
299
	}
300
	return;
301
    }
302

            
303
339
    for (unsigned int i = 0; i < from->elements.num_elements; i++) {
304
246
	cairo_svg_stream_element_t *element = _cairo_array_index (&from->elements, i);
305
246
	cairo_svg_stream_element_t element_copy = *element;
306
246
	if (element->type == CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) {
307
168
	    element_copy.text.output_stream = _cairo_memory_stream_create ();
308
168
	    _cairo_memory_stream_copy (element->text.output_stream, element_copy.text.output_stream);
309
168
	    if (to->status == CAIRO_STATUS_SUCCESS) {
310
168
		to->status = element->text.output_stream->status;
311
	    }
312
	}
313
246
	status = _cairo_array_append (&to->elements, &element_copy);
314
246
	if (unlikely (status)) {
315
	    if (to->status == CAIRO_STATUS_SUCCESS) {
316
		to->status = status;
317
	    }
318
	    return;
319
	}
320
    }
321
}
322

            
323
static void
324
13
_cairo_svg_stream_copy_to_output_stream (cairo_svg_stream_t *from,
325
					 cairo_output_stream_t *to,
326
					 cairo_hash_table_t *paints)
327
{
328
13
    if (unlikely (from->status)) {
329
	if (to->status == CAIRO_STATUS_SUCCESS) {
330
	    to->status = from->status;
331
	}
332
	return;
333
    }
334

            
335
639
    for (unsigned int i = 0; i < from->elements.num_elements; i++) {
336
626
	cairo_svg_stream_element_t *element = _cairo_array_index (&from->elements, i);
337
626
	if (element->type == CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) {
338
364
	    _cairo_memory_stream_copy (element->text.output_stream, to);
339
	}
340
626
	if (element->type == CAIRO_SVG_STREAM_ELEMENT_TYPE_PAINT_DEPENDENT) {
341
	    cairo_svg_paint_t paint_key;
342
262
	    paint_key.source_id = element->paint_dependent.source_id;
343
262
	    _cairo_svg_paint_init_key (&paint_key);
344

            
345
262
	    cairo_svg_paint_t *found_paint_entry = _cairo_hash_table_lookup (paints,
346
									     &paint_key.base);
347
262
	    assert (found_paint_entry);
348

            
349
262
	    switch (element->paint_dependent.type) {
350
58
	    case CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE:
351
58
		_cairo_output_stream_printf (to,
352
					     " x=\"%f\" y=\"%f\" width=\"%f\" height=\"%f\"",
353
					     found_paint_entry->box.p1.x,
354
					     found_paint_entry->box.p1.y,
355
58
					     found_paint_entry->box.p2.x - found_paint_entry->box.p1.x,
356
58
					     found_paint_entry->box.p2.y - found_paint_entry->box.p1.y);
357
58
		break;
358
90
	    case CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN:
359
90
		_cairo_output_stream_printf (to,
360
					     " x=\"0\" y=\"0\" width=\"%f\" height=\"%f\"",
361
90
					     found_paint_entry->box.p2.x - found_paint_entry->box.p1.x,
362
90
					     found_paint_entry->box.p2.y - found_paint_entry->box.p1.y);
363
90
		break;
364
28
	    case CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_TRANSLATION:
365
28
		_cairo_output_stream_printf (to,
366
					     " transform=\"translate(%f, %f)\"",
367
					     found_paint_entry->box.p1.x,
368
					     found_paint_entry->box.p1.y);
369
28
		break;
370
86
	    case CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION:
371
86
		_cairo_output_stream_printf (to,
372
					     " transform=\"translate(%f, %f)\"",
373
86
					     -found_paint_entry->box.p1.x,
374
86
					     -found_paint_entry->box.p1.y);
375
86
		break;
376
	    }
377
	}
378
    }
379
}
380

            
381
static cairo_status_t
382
127
_cairo_svg_stream_destroy (cairo_svg_stream_t *svg_stream)
383
{
384
127
    cairo_status_t status = svg_stream->status;
385
999
    for (unsigned int i = 0; i < svg_stream->elements.num_elements; i++) {
386
872
	cairo_svg_stream_element_t *element = _cairo_array_index (&svg_stream->elements, i);
387
872
	if (element->type == CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) {
388
532
	    cairo_status_t element_status = _cairo_output_stream_destroy (element->text.output_stream);
389
532
	    if (status == CAIRO_STATUS_SUCCESS) {
390
532
		status = element_status;
391
	    }
392
	}
393
    }
394
127
    _cairo_array_fini (&svg_stream->elements);
395
127
    return status;
396
}
397

            
398
/**
399
 * CAIRO_HAS_SVG_SURFACE:
400
 *
401
 * Defined if the SVG surface backend is available.
402
 * This macro can be used to conditionally compile backend-specific code.
403
 *
404
 * Since: 1.2
405
 **/
406

            
407
static const unsigned int invalid_pattern_id = -1;
408

            
409
static const cairo_svg_version_t _cairo_svg_versions[] =
410
{
411
    CAIRO_SVG_VERSION_1_1,
412
    CAIRO_SVG_VERSION_1_2
413
};
414

            
415
#define CAIRO_SVG_VERSION_LAST ARRAY_LENGTH (_cairo_svg_versions)
416

            
417
static const char *_cairo_svg_supported_mime_types[] =
418
{
419
    CAIRO_MIME_TYPE_JPEG,
420
    CAIRO_MIME_TYPE_PNG,
421
    CAIRO_MIME_TYPE_UNIQUE_ID,
422
    CAIRO_MIME_TYPE_URI,
423
    NULL
424
};
425

            
426
static void
427
_cairo_svg_surface_emit_path (cairo_svg_stream_t *output,
428
			      const cairo_path_fixed_t *path,
429
			      const cairo_matrix_t *ctm_inverse);
430

            
431
static const char * _cairo_svg_version_strings[CAIRO_SVG_VERSION_LAST] =
432
{
433
    "SVG 1.1",
434
    "SVG 1.2"
435
};
436

            
437
static const char * _cairo_svg_unit_strings[] =
438
{
439
    "",
440
    "em",
441
    "ex",
442
    "px",
443
    "in",
444
    "cm",
445
    "mm",
446
    "pt",
447
    "pc",
448
    "%"
449
};
450

            
451
enum cairo_svg_filter {
452
    CAIRO_SVG_FILTER_REMOVE_COLOR,
453
    CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA,
454
    CAIRO_SVG_FILTER_COLOR_TO_ALPHA,
455
    CAIRO_SVG_FILTER_LAST_STATIC_FILTER,
456
    CAIRO_SVG_FILTER_OVER,
457
    CAIRO_SVG_FILTER_IN,
458
    CAIRO_SVG_FILTER_OUT,
459
    CAIRO_SVG_FILTER_ATOP,
460
    CAIRO_SVG_FILTER_XOR,
461
    CAIRO_SVG_FILTER_ADD,
462
    CAIRO_SVG_FILTER_MULTIPLY,
463
    CAIRO_SVG_FILTER_SCREEN,
464
    CAIRO_SVG_FILTER_OVERLAY,
465
    CAIRO_SVG_FILTER_DARKEN,
466
    CAIRO_SVG_FILTER_LIGHTEN,
467
    CAIRO_SVG_FILTER_COLOR_DODGE,
468
    CAIRO_SVG_FILTER_COLOR_BURN,
469
    CAIRO_SVG_FILTER_HARD_LIGHT,
470
    CAIRO_SVG_FILTER_SOFT_LIGHT,
471
    CAIRO_SVG_FILTER_DIFFERENCE,
472
    CAIRO_SVG_FILTER_EXCLUSION,
473
    CAIRO_SVG_FILTER_HUE,
474
    CAIRO_SVG_FILTER_SATURATION,
475
    CAIRO_SVG_FILTER_COLOR,
476
    CAIRO_SVG_FILTER_LUMINOSITY,
477
};
478

            
479
typedef struct _cairo_svg_page {
480
    cairo_svg_stream_t xml_node;
481
} cairo_svg_page_t;
482

            
483
typedef struct _cairo_svg_document {
484
    cairo_output_stream_t *output_stream;
485
    unsigned long refcount;
486
    cairo_surface_t *owner;
487
    cairo_bool_t finished;
488

            
489
    double width;
490
    double height;
491
    cairo_svg_unit_t unit;
492

            
493
    cairo_svg_stream_t xml_node_defs;
494
    cairo_svg_stream_t xml_node_glyphs;
495
    cairo_svg_stream_t xml_node_filters;
496

            
497
    unsigned int linear_pattern_id;
498
    unsigned int radial_pattern_id;
499
    unsigned int pattern_id;
500
    unsigned int clip_id;
501
    unsigned int mask_id;
502
    unsigned int compositing_group_id;
503
    unsigned int filter_id;
504

            
505
    cairo_bool_t filters_emitted[CAIRO_SVG_FILTER_LAST_STATIC_FILTER];
506

            
507
    cairo_svg_version_t svg_version;
508

            
509
    cairo_scaled_font_subsets_t *font_subsets;
510

            
511
    cairo_hash_table_t *paints;
512
} cairo_svg_document_t;
513

            
514
// Must be compatible with the struct _cairo_svg_surface_start.
515
typedef struct _cairo_svg_surface {
516
    cairo_surface_t base;
517

            
518
    cairo_bool_t force_fallbacks;
519

            
520
    unsigned int source_id;
521
    unsigned int depth;
522

            
523
    double width;
524
    double height;
525
    cairo_bool_t surface_bounded;
526

            
527
    cairo_svg_document_t *document;
528

            
529
    cairo_svg_stream_t xml_node;
530
    cairo_array_t page_set;
531

            
532
    cairo_hash_table_t *source_surfaces;
533

            
534
    cairo_surface_clipper_t clipper;
535
    cairo_svg_stream_t *current_clipper_stream;
536
    unsigned int clip_level;
537

            
538
    cairo_bool_t transitive_paint_used;
539

            
540
    cairo_paginated_mode_t paginated_mode;
541
} cairo_svg_surface_t;
542

            
543
static cairo_status_t
544
_cairo_svg_document_create (cairo_output_stream_t *stream,
545
			    double width,
546
			    double height,
547
			    cairo_svg_version_t version,
548
			    cairo_svg_document_t **document_out);
549

            
550
static cairo_status_t
551
_cairo_svg_document_destroy (cairo_svg_document_t *document);
552

            
553
static cairo_status_t
554
_cairo_svg_document_finish (cairo_svg_document_t *document);
555

            
556
static cairo_svg_document_t *
557
_cairo_svg_document_reference (cairo_svg_document_t *document);
558

            
559
static cairo_surface_t *
560
_cairo_svg_surface_create_for_document (cairo_svg_document_t *document,
561
					cairo_content_t content,
562
					double width,
563
					double height,
564
					cairo_bool_t bounded);
565

            
566
static cairo_surface_t *
567
_cairo_svg_surface_create_for_stream_internal (cairo_output_stream_t *stream,
568
					       double width,
569
					       double height,
570
					       cairo_svg_version_t version);
571

            
572
static cairo_status_t
573
_cairo_svg_surface_emit_composite_pattern (cairo_svg_stream_t *output,
574
					   cairo_svg_surface_t *surface,
575
					   cairo_surface_pattern_t *pattern,
576
					   unsigned int pattern_id,
577
					   const cairo_matrix_t *parent_matrix);
578

            
579
static cairo_status_t
580
_cairo_svg_surface_emit_paint (cairo_svg_stream_t *output,
581
			       cairo_svg_surface_t *surface,
582
			       const cairo_pattern_t *source,
583
			       cairo_bool_t at_origin);
584

            
585
static const cairo_surface_backend_t cairo_svg_surface_backend;
586
static const cairo_paginated_surface_backend_t cairo_svg_surface_paginated_backend;
587

            
588
/**
589
 * cairo_svg_surface_create_for_stream:
590
 * @write_func: a #cairo_write_func_t to accept the output data, may be %NULL
591
 *              to indicate a no-op @write_func. With a no-op @write_func,
592
 *              the surface may be queried or used as a source without
593
 *              generating any temporary files.
594
 * @closure: the closure argument for @write_func
595
 * @width_in_points: width of the surface, in points (1 point == 1/72.0 inch)
596
 * @height_in_points: height of the surface, in points (1 point == 1/72.0 inch)
597
 *
598
 * Creates a SVG surface of the specified size in points to be written
599
 * incrementally to the stream represented by @write_func and @closure.
600
 *
601
 * Return value: a pointer to the newly created surface. The caller
602
 * owns the surface and should call cairo_surface_destroy() when done
603
 * with it.
604
 *
605
 * This function always returns a valid pointer, but it will return a
606
 * pointer to a "nil" surface if an error such as out of memory
607
 * occurs. You can use cairo_surface_status() to check for this.
608
 *
609
 * Since: 1.2
610
 **/
611
cairo_surface_t *
612
1
cairo_svg_surface_create_for_stream (cairo_write_func_t		 write_func,
613
				     void			*closure,
614
				     double			 width,
615
				     double			 height)
616
{
617
    cairo_output_stream_t *stream;
618

            
619
1
    stream = _cairo_output_stream_create (write_func, NULL, closure);
620
1
    if (_cairo_output_stream_get_status (stream))
621
	return _cairo_surface_create_in_error (_cairo_output_stream_destroy (stream));
622

            
623
1
    return _cairo_svg_surface_create_for_stream_internal (stream, width, height, CAIRO_SVG_VERSION_1_1);
624
}
625

            
626
/**
627
 * cairo_svg_surface_create:
628
 * @filename: a filename for the SVG output (must be writable), %NULL may be
629
 *            used to specify no output. This will generate a SVG surface that
630
 *            may be queried and used as a source, without generating a
631
 *            temporary file.
632
 * @width_in_points: width of the surface, in points (1 point == 1/72.0 inch)
633
 * @height_in_points: height of the surface, in points (1 point == 1/72.0 inch)
634
 *
635
 * Creates a SVG surface of the specified size in points to be written
636
 * to @filename.
637
 *
638
 * The SVG surface backend recognizes the following MIME types for the
639
 * data attached to a surface (see cairo_surface_set_mime_data()) when
640
 * it is used as a source pattern for drawing on this surface:
641
 * %CAIRO_MIME_TYPE_JPEG, %CAIRO_MIME_TYPE_PNG,
642
 * %CAIRO_MIME_TYPE_URI. If any of them is specified, the SVG backend
643
 * emits a href with the content of MIME data instead of a surface
644
 * snapshot (PNG, Base64-encoded) in the corresponding image tag.
645
 *
646
 * The unofficial MIME type %CAIRO_MIME_TYPE_URI is examined
647
 * first. If present, the URI is emitted as is: assuring the
648
 * correctness of URI is left to the client code.
649
 *
650
 * If %CAIRO_MIME_TYPE_URI is not present, but %CAIRO_MIME_TYPE_JPEG
651
 * or %CAIRO_MIME_TYPE_PNG is specified, the corresponding data is
652
 * Base64-encoded and emitted.
653
 *
654
 * If %CAIRO_MIME_TYPE_UNIQUE_ID is present, all surfaces with the same
655
 * unique identifier will only be embedded once.
656
 *
657
 * Return value: a pointer to the newly created surface. The caller
658
 * owns the surface and should call cairo_surface_destroy() when done
659
 * with it.
660
 *
661
 * This function always returns a valid pointer, but it will return a
662
 * pointer to a "nil" surface if an error such as out of memory
663
 * occurs. You can use cairo_surface_status() to check for this.
664
 *
665
 * Since: 1.2
666
 **/
667
cairo_surface_t *
668
4
cairo_svg_surface_create (const char	*filename,
669
			  double	 width,
670
			  double	 height)
671
{
672
    cairo_output_stream_t *stream;
673

            
674
4
    stream = _cairo_output_stream_create_for_filename (filename);
675
4
    if (_cairo_output_stream_get_status (stream))
676
	return _cairo_surface_create_in_error (_cairo_output_stream_destroy (stream));
677

            
678
4
    return _cairo_svg_surface_create_for_stream_internal (stream, width, height, CAIRO_SVG_VERSION_1_1);
679
}
680

            
681
static cairo_bool_t
682
_cairo_surface_is_svg (cairo_surface_t *surface)
683
{
684
    return surface->backend == &cairo_svg_surface_backend;
685
}
686

            
687
/* If the abstract_surface is a paginated surface, and that paginated
688
 * surface's target is a svg_surface, then set svg_surface to that
689
 * target. Otherwise return FALSE.
690
 */
691
static cairo_bool_t
692
7
_extract_svg_surface (cairo_surface_t *surface,
693
		      cairo_svg_surface_t **svg_surface)
694
{
695
    cairo_surface_t *target;
696

            
697
7
    if (surface->status)
698
1
	return FALSE;
699
6
    if (surface->finished) {
700
3
	(void) _cairo_surface_set_error (surface, _cairo_error (CAIRO_STATUS_SURFACE_FINISHED));
701
3
	return FALSE;
702
    }
703

            
704
3
    if (!_cairo_surface_is_paginated (surface)) {
705
3
	(void) _cairo_surface_set_error (surface, _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH));
706
3
	return FALSE;
707
    }
708

            
709
    target = _cairo_paginated_surface_get_target (surface);
710
    if (target->status) {
711
	(void) _cairo_surface_set_error (surface, target->status);
712
	return FALSE;
713
    }
714
    if (target->finished) {
715
	(void) _cairo_surface_set_error (surface, _cairo_error (CAIRO_STATUS_SURFACE_FINISHED));
716
	return FALSE;
717
    }
718

            
719
    if (!_cairo_surface_is_svg (target)) {
720
	(void) _cairo_surface_set_error (surface, _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH));
721
	return FALSE;
722
    }
723

            
724
    *svg_surface = (cairo_svg_surface_t *) target;
725
    return TRUE;
726
}
727

            
728
/**
729
 * cairo_svg_surface_restrict_to_version:
730
 * @surface: a SVG #cairo_surface_t
731
 * @version: SVG version
732
 *
733
 * Restricts the generated SVG file to @version. See cairo_svg_get_versions()
734
 * for a list of available version values that can be used here.
735
 *
736
 * This function should only be called before any drawing operations
737
 * have been performed on the given surface. The simplest way to do
738
 * this is to call this function immediately after creating the
739
 * surface.
740
 *
741
 * Since: 1.2
742
 **/
743
void
744
7
cairo_svg_surface_restrict_to_version (cairo_surface_t		*abstract_surface,
745
				       cairo_svg_version_t	 version)
746
{
747
    cairo_svg_surface_t *surface;
748

            
749
7
    if (! _extract_svg_surface (abstract_surface, &surface))
750
7
	return;
751

            
752
    if (version < CAIRO_SVG_VERSION_LAST)
753
	surface->document->svg_version = version;
754
}
755

            
756
/**
757
 * cairo_svg_get_versions:
758
 * @versions: supported version list
759
 * @num_versions: list length
760
 *
761
 * Used to retrieve the list of supported versions. See
762
 * cairo_svg_surface_restrict_to_version().
763
 *
764
 * Since: 1.2
765
 **/
766
void
767
cairo_svg_get_versions (cairo_svg_version_t const	**versions,
768
                        int				 *num_versions)
769
{
770
    if (versions != NULL)
771
	*versions = _cairo_svg_versions;
772

            
773
    if (num_versions != NULL)
774
	*num_versions = CAIRO_SVG_VERSION_LAST;
775
}
776

            
777
/**
778
 * cairo_svg_version_to_string:
779
 * @version: a version id
780
 *
781
 * Get the string representation of the given @version id. This function
782
 * will return %NULL if @version isn't valid. See cairo_svg_get_versions()
783
 * for a way to get the list of valid version ids.
784
 *
785
 * Return value: the string associated to given version.
786
 *
787
 * Since: 1.2
788
 **/
789
const char *
790
cairo_svg_version_to_string (cairo_svg_version_t version)
791
{
792
    if (version >= CAIRO_SVG_VERSION_LAST)
793
	return NULL;
794

            
795
    return _cairo_svg_version_strings[version];
796
}
797

            
798
/**
799
 * cairo_svg_surface_set_document_unit:
800
 * @surface: a SVG #cairo_surface_t
801
 * @unit: SVG unit
802
 *
803
 * Use the specified unit for the width and height of the generated SVG file.
804
 * See #cairo_svg_unit_t for a list of available unit values that can be used
805
 * here.
806
 *
807
 * This function can be called at any time before generating the SVG file.
808
 *
809
 * However to minimize the risk of ambiguities it's recommended to call it
810
 * before any drawing operations have been performed on the given surface, to
811
 * make it clearer what the unit used in the drawing operations is.
812
 *
813
 * The simplest way to do this is to call this function immediately after
814
 * creating the SVG surface.
815
 *
816
 * Note if this function is never called, the default unit for SVG documents
817
 * generated by cairo will be user unit.
818
 *
819
 * Since: 1.16
820
 **/
821
void
822
cairo_svg_surface_set_document_unit (cairo_surface_t	*abstract_surface,
823
				     cairo_svg_unit_t	 unit)
824
{
825
    cairo_svg_surface_t *surface;
826

            
827
    if (! _extract_svg_surface (abstract_surface, &surface))
828
	return;
829

            
830
    if (unit <= CAIRO_SVG_UNIT_PERCENT)
831
	surface->document->unit = unit;
832
}
833

            
834
/**
835
 * cairo_svg_surface_get_document_unit:
836
 * @surface: a SVG #cairo_surface_t
837
 *
838
 * Get the unit of the SVG surface.
839
 *
840
 * If the surface passed as an argument is not a SVG surface, the function
841
 * sets the error status to CAIRO_STATUS_SURFACE_TYPE_MISMATCH and returns
842
 * CAIRO_SVG_UNIT_USER.
843
 *
844
 * Return value: the SVG unit of the SVG surface.
845
 *
846
 * Since: 1.16
847
 **/
848
cairo_svg_unit_t
849
cairo_svg_surface_get_document_unit (cairo_surface_t	*abstract_surface)
850
{
851
    cairo_svg_surface_t *surface;
852

            
853
    if (! _extract_svg_surface (abstract_surface, &surface)) {
854
	_cairo_error_throw (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
855
	return CAIRO_SVG_UNIT_USER;
856
    }
857

            
858
    return surface->document->unit;
859
}
860

            
861
static void
862
16
_cairo_svg_paint_compute (cairo_svg_document_t *document, cairo_svg_paint_t *paint) {
863
22
    for (unsigned int i = 0; i < paint->paint_elements.num_elements; i++) {
864
6
	cairo_svg_paint_element_t *paint_element = _cairo_array_index (&paint->paint_elements, i);
865

            
866
	cairo_svg_paint_t paint_key;
867
6
	paint_key.source_id = paint_element->source_id;
868
6
	_cairo_svg_paint_init_key (&paint_key);
869

            
870
6
	cairo_svg_paint_t *found_paint_entry = _cairo_hash_table_lookup (document->paints,
871
									 &paint_key.base);
872
6
	assert (found_paint_entry);
873

            
874
6
	_cairo_svg_paint_compute (document, found_paint_entry);
875

            
876
6
	cairo_box_double_t box = found_paint_entry->box;
877
6
	_cairo_matrix_transform_bounding_box (&paint_element->matrix,
878
					      &box.p1.x, &box.p1.y,
879
					      &box.p2.x, &box.p2.y,
880
					      NULL);
881
6
	_cairo_svg_paint_box_add_padding (&box);
882

            
883
6
	if (i == 0) {
884
6
	    paint->box = box;
885
	} else {
886
	    paint->box.p1.x = MIN (paint->box.p1.x, box.p1.x);
887
	    paint->box.p1.y = MIN (paint->box.p1.y, box.p1.y);
888
	    paint->box.p2.x = MAX (paint->box.p2.x, box.p2.x);
889
	    paint->box.p2.y = MAX (paint->box.p2.y, box.p2.y);
890
	}
891
    }
892
16
    _cairo_array_truncate (&paint->paint_elements, 0);
893
16
}
894

            
895
static void
896
10
_cairo_svg_paint_compute_func (void *entry, void *closure)
897
{
898
10
    cairo_svg_paint_t *paint = entry;
899
10
    cairo_svg_document_t *document = closure;
900

            
901
10
    _cairo_svg_paint_compute (document, paint);
902
10
}
903

            
904
static cairo_status_t
905
11
_cairo_svg_surface_add_source_surface (cairo_svg_surface_t *surface,
906
				       cairo_surface_t *source_surface,
907
				       cairo_bool_t *is_new,
908
				       cairo_svg_source_surface_t **result_source_surface)
909
{
910
    cairo_status_t status;
911

            
912
    cairo_svg_source_surface_t source_surface_key;
913
11
    source_surface_key.id = source_surface->unique_id;
914
11
    cairo_surface_get_mime_data (source_surface,
915
				 CAIRO_MIME_TYPE_UNIQUE_ID,
916
				 (const unsigned char **) &source_surface_key.unique_id,
917
				 &source_surface_key.unique_id_length);
918
11
    _cairo_svg_source_surface_init_key (&source_surface_key);
919

            
920
11
    cairo_svg_source_surface_t *found_source_surface_entry = _cairo_hash_table_lookup (surface->source_surfaces,
921
										       &source_surface_key.base);
922
11
    if (found_source_surface_entry) {
923
	*is_new = FALSE;
924
	*result_source_surface = found_source_surface_entry;
925
	return CAIRO_STATUS_SUCCESS;
926
    }
927

            
928
11
    unsigned char *unique_id = NULL;
929
11
    unsigned long unique_id_length = 0;
930
11
    if (source_surface_key.unique_id && source_surface_key.unique_id_length > 0) {
931
	unique_id = _cairo_malloc (source_surface_key.unique_id_length);
932
	if (unique_id == NULL) {
933
	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
934
	}
935

            
936
	unique_id_length = source_surface_key.unique_id_length;
937
	memcpy (unique_id, source_surface_key.unique_id, unique_id_length);
938
    } else {
939
11
	unique_id = NULL;
940
11
	unique_id_length = 0;
941
    }
942

            
943
11
    cairo_svg_source_surface_t *source_surface_entry = _cairo_calloc (sizeof (cairo_svg_source_surface_t));
944
11
    if (source_surface_entry == NULL) {
945
	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
946
	goto fail;
947
    }
948
11
    source_surface_entry->id = source_surface_key.id;
949
11
    source_surface_entry->unique_id_length = unique_id_length;
950
11
    source_surface_entry->unique_id = unique_id;
951
11
    _cairo_svg_source_surface_init_key (source_surface_entry);
952
11
    status = _cairo_hash_table_insert (surface->source_surfaces, &source_surface_entry->base);
953
11
    if (unlikely (status)) {
954
	goto fail;
955
    }
956

            
957
11
    *is_new = TRUE;
958
11
    *result_source_surface = source_surface_entry;
959
11
    return CAIRO_STATUS_SUCCESS;
960

            
961
    fail:
962
    free (unique_id);
963
    free (source_surface_entry);
964
    return status;
965
}
966

            
967
static cairo_bool_t
968
27
_cairo_svg_surface_cliprect_covers_surface (cairo_svg_surface_t *surface,
969
					    cairo_path_fixed_t *path)
970
{
971
    cairo_box_t box;
972

            
973
25
    return surface->surface_bounded &&
974
25
	   _cairo_path_fixed_is_box (path, &box) &&
975
13
	   box.p1.x <= 0 &&
976
1
	   box.p1.y <= 0 &&
977
53
	   _cairo_fixed_to_double (box.p2.x) >= surface->width &&
978
1
	   _cairo_fixed_to_double (box.p2.y) >= surface->height;
979
}
980

            
981
static cairo_status_t
982
54
_cairo_svg_surface_clipper_intersect_clip_path (cairo_surface_clipper_t *clipper,
983
						cairo_path_fixed_t *path,
984
						cairo_fill_rule_t fill_rule,
985
						double tolerance,
986
						cairo_antialias_t antialias)
987
{
988
54
    cairo_svg_surface_t *surface = cairo_container_of (clipper,
989
						       cairo_svg_surface_t,
990
						       clipper);
991
54
    cairo_svg_document_t *document = surface->document;
992

            
993
54
    if (path == NULL) {
994
27
	for (unsigned int i = 0; i < surface->clip_level; i++) {
995
	    _cairo_svg_stream_printf (surface->current_clipper_stream, "</g>\n");
996
	}
997
27
	surface->clip_level = 0;
998
27
	return CAIRO_STATUS_SUCCESS;
999
    }
    /* skip trivial whole-page clips */
27
    if (_cairo_svg_surface_cliprect_covers_surface (surface, path)) {
1
	return CAIRO_STATUS_SUCCESS;
    }
26
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<clipPath id=\"clip-%d\">\n",
			      document->clip_id);
26
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<path clip-rule=\"%s\"",
			      fill_rule == CAIRO_FILL_RULE_EVEN_ODD ? "evenodd" : "nonzero");
26
    _cairo_svg_surface_emit_path (&document->xml_node_defs, path, NULL);
26
    _cairo_svg_stream_printf (&document->xml_node_defs, "/>\n");
26
    _cairo_svg_stream_printf (&document->xml_node_defs, "</clipPath>\n");
26
    _cairo_svg_stream_printf (surface->current_clipper_stream,
			      "<g clip-path=\"url(#clip-%d)\">\n",
			      document->clip_id);
26
    document->clip_id++;
26
    surface->clip_level++;
26
    return CAIRO_STATUS_SUCCESS;
}
static void
87
_cairo_svg_surface_reset_clip (cairo_svg_surface_t *surface)
{
87
    _cairo_surface_clipper_reset (&surface->clipper);
87
    if (surface->current_clipper_stream != NULL) {
96
	for (unsigned int i = 0; i < surface->clip_level; i++) {
26
	    _cairo_svg_stream_printf (surface->current_clipper_stream, "</g>\n");
	}
    }
87
    surface->clip_level = 0;
87
}
static cairo_status_t
45
_cairo_svg_surface_set_clip (cairo_svg_surface_t *surface,
			     cairo_svg_stream_t *clipper_stream,
			     const cairo_clip_t *clip)
{
45
    if (surface->current_clipper_stream != clipper_stream) {
17
	_cairo_svg_surface_reset_clip (surface);
17
	surface->current_clipper_stream = clipper_stream;
    }
45
    return _cairo_surface_clipper_set_clip (&surface->clipper, clip);
}
static cairo_surface_t *
14
_cairo_svg_surface_create_for_document (cairo_svg_document_t *document,
					cairo_content_t content,
					double width,
					double height,
					cairo_bool_t bounded)
{
    cairo_svg_surface_t *surface;
    cairo_surface_t *paginated;
    cairo_status_t status;
14
    surface = _cairo_calloc (sizeof (cairo_svg_surface_t));
14
    if (unlikely (surface == NULL)) {
	return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
    }
14
    _cairo_surface_init (&surface->base,
			 &cairo_svg_surface_backend,
			 NULL, /* device */
			 content,
			 TRUE); /* is_vector */
14
    surface->source_id = surface->base.unique_id;
14
    surface->depth = 0;
14
    surface->width = width;
14
    surface->height = height;
14
    surface->surface_bounded = bounded;
14
    surface->document = _cairo_svg_document_reference (document);
14
    surface->xml_node = _cairo_svg_stream_create ();
14
    _cairo_array_init (&surface->page_set, sizeof (cairo_svg_page_t));
14
    surface->source_surfaces = _cairo_hash_table_create (_cairo_svg_source_surface_equal);
14
    if (unlikely (surface->source_surfaces == NULL)) {
	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
	goto CLEANUP;
    }
14
    _cairo_surface_clipper_init (&surface->clipper, _cairo_svg_surface_clipper_intersect_clip_path);
14
    surface->current_clipper_stream = NULL;
14
    surface->clip_level = 0;
14
    surface->transitive_paint_used = FALSE;
14
    surface->paginated_mode = CAIRO_PAGINATED_MODE_ANALYZE;
14
    surface->force_fallbacks = FALSE;
14
    paginated = _cairo_paginated_surface_create (&surface->base,
						 surface->base.content,
						 &cairo_svg_surface_paginated_backend);
14
    status = paginated->status;
14
    if (status == CAIRO_STATUS_SUCCESS) {
	/* paginated keeps the only reference to surface now, drop ours */
14
	cairo_surface_destroy (&surface->base);
14
	return paginated;
    }
    /* ignore status as we are on the error path */
    CLEANUP:
    (void) _cairo_svg_stream_destroy (&surface->xml_node);
    (void) _cairo_svg_document_destroy (document);
    free (surface);
    return _cairo_surface_create_in_error (status);
}
static cairo_surface_t *
5
_cairo_svg_surface_create_for_stream_internal (cairo_output_stream_t	*stream,
					       double			 width,
					       double			 height,
					       cairo_svg_version_t	 version)
{
    cairo_svg_document_t *document;
    cairo_surface_t *surface;
    cairo_status_t status;
5
    status = _cairo_svg_document_create (stream,
	                                 width, height, version,
					 &document);
5
    if (unlikely (status)) {
	surface =  _cairo_surface_create_in_error (status);
	/* consume the output stream on behalf of caller */
	status = _cairo_output_stream_destroy (stream);
	return surface;
    }
5
    surface = _cairo_svg_surface_create_for_document (document, CAIRO_CONTENT_COLOR_ALPHA,
						      width, height, TRUE);
5
    if (surface->status) {
	return surface;
    }
5
    document->owner = surface;
5
    status = _cairo_svg_document_destroy (document);
    /* the ref count should be 2 at this point */
5
    assert (status == CAIRO_STATUS_SUCCESS);
5
    return surface;
}
static cairo_svg_page_t *
14
_cairo_svg_surface_store_page (cairo_svg_surface_t *surface)
{
14
    _cairo_svg_surface_reset_clip (surface);
    cairo_svg_page_t page;
14
    page.xml_node = surface->xml_node;
14
    if (_cairo_array_append (&surface->page_set, &page)) {
	return NULL;
    }
14
    surface->xml_node = _cairo_svg_stream_create ();
14
    return _cairo_array_index (&surface->page_set,
14
			       surface->page_set.num_elements - 1);
}
static cairo_int_status_t
_cairo_svg_surface_copy_page (void *abstract_surface)
{
    cairo_svg_surface_t *surface = abstract_surface;
    cairo_svg_page_t *page = _cairo_svg_surface_store_page (surface);
    if (unlikely (page == NULL)) {
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    }
    _cairo_svg_stream_copy (&page->xml_node, &surface->xml_node);
    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
14
_cairo_svg_surface_show_page (void *abstract_surface)
{
14
    cairo_svg_surface_t *surface = abstract_surface;
14
    cairo_svg_page_t *page = _cairo_svg_surface_store_page (surface);
14
    if (unlikely (page == NULL)) {
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    }
14
    return CAIRO_STATUS_SUCCESS;
}
static void
11
_cairo_svg_surface_emit_transform (cairo_svg_stream_t *output,
				   char const *attribute_name,
				   const cairo_matrix_t *object_matrix,
				   const cairo_matrix_t *parent_matrix)
{
11
    cairo_matrix_t matrix = *object_matrix;
11
    if (parent_matrix != NULL) {
	cairo_matrix_multiply (&matrix, &matrix, parent_matrix);
    }
11
    if (!_cairo_matrix_is_identity (&matrix)) {
8
	_cairo_svg_stream_printf (output,
				  " %s=\"matrix(%f, %f, %f, %f, %f, %f)\"",
				  attribute_name,
				  matrix.xx, matrix.yx,
				  matrix.xy, matrix.yy,
				  matrix.x0, matrix.y0);
    }
11
}
typedef struct {
    cairo_svg_stream_t *output;
    const cairo_matrix_t *ctm_inverse;
} svg_path_info_t;
static cairo_status_t
132
_cairo_svg_path_move_to (void *closure,
			 const cairo_point_t *point)
{
132
    svg_path_info_t *info = closure;
132
    double x = _cairo_fixed_to_double (point->x);
132
    double y = _cairo_fixed_to_double (point->y);
132
    if (info->ctm_inverse)
	cairo_matrix_transform_point (info->ctm_inverse, &x, &y);
132
    _cairo_svg_stream_printf (info->output, "M %f %f ", x, y);
132
    return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
216
_cairo_svg_path_line_to (void *closure,
			 const cairo_point_t *point)
{
216
    svg_path_info_t *info = closure;
216
    double x = _cairo_fixed_to_double (point->x);
216
    double y = _cairo_fixed_to_double (point->y);
216
    if (info->ctm_inverse)
	cairo_matrix_transform_point (info->ctm_inverse, &x, &y);
216
    _cairo_svg_stream_printf (info->output, "L %f %f ", x, y);
216
    return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
_cairo_svg_path_curve_to (void          *closure,
			  const cairo_point_t *b,
			  const cairo_point_t *c,
			  const cairo_point_t *d)
{
    svg_path_info_t *info = closure;
    double bx = _cairo_fixed_to_double (b->x);
    double by = _cairo_fixed_to_double (b->y);
    double cx = _cairo_fixed_to_double (c->x);
    double cy = _cairo_fixed_to_double (c->y);
    double dx = _cairo_fixed_to_double (d->x);
    double dy = _cairo_fixed_to_double (d->y);
    if (info->ctm_inverse) {
	cairo_matrix_transform_point (info->ctm_inverse, &bx, &by);
	cairo_matrix_transform_point (info->ctm_inverse, &cx, &cy);
	cairo_matrix_transform_point (info->ctm_inverse, &dx, &dy);
    }
    _cairo_svg_stream_printf (info->output,
			      "C %f %f %f %f %f %f ",
			      bx, by, cx, cy, dx, dy);
    return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
72
_cairo_svg_path_close_path (void *closure)
{
72
    svg_path_info_t *info = closure;
72
    _cairo_svg_stream_printf (info->output, "Z ");
72
    return CAIRO_STATUS_SUCCESS;
}
static void
60
_cairo_svg_surface_emit_path (cairo_svg_stream_t *output,
			      const cairo_path_fixed_t *path,
			      const cairo_matrix_t *ctm_inverse)
{
    cairo_status_t status;
    svg_path_info_t info;
60
    _cairo_svg_stream_printf (output, " d=\"");
60
    info.output = output;
60
    info.ctm_inverse = ctm_inverse;
60
    status = _cairo_path_fixed_interpret (path,
					  _cairo_svg_path_move_to,
					  _cairo_svg_path_line_to,
					  _cairo_svg_path_curve_to,
					  _cairo_svg_path_close_path,
					  &info);
60
    assert (status == CAIRO_STATUS_SUCCESS);
60
    _cairo_svg_stream_printf (output, "\"");
60
}
static cairo_int_status_t
_cairo_svg_document_emit_outline_glyph_data (cairo_svg_document_t *document,
					     cairo_scaled_font_t *scaled_font,
					     unsigned long glyph_index)
{
    cairo_scaled_glyph_t *scaled_glyph;
    cairo_int_status_t status;
    status = _cairo_scaled_glyph_lookup (scaled_font,
					 glyph_index,
					 CAIRO_SCALED_GLYPH_INFO_METRICS | CAIRO_SCALED_GLYPH_INFO_PATH,
					 NULL, /* foreground color */
					 &scaled_glyph);
    if (unlikely (status)) {
	return status;
    }
    if (_cairo_path_fixed_size (scaled_glyph->path) != 0) {
	_cairo_svg_stream_printf (&document->xml_node_glyphs,
				  "<path");
	_cairo_svg_surface_emit_path (&document->xml_node_glyphs,
				      scaled_glyph->path,
				      NULL);
	_cairo_svg_stream_printf (&document->xml_node_glyphs,
				  "/>\n");
    }
    return status;
}
static cairo_int_status_t
_cairo_svg_document_emit_bitmap_glyph_data (cairo_svg_document_t *document,
					    cairo_scaled_font_t *scaled_font,
					    unsigned long glyph_index)
{
    cairo_status_t status;
    cairo_scaled_glyph_t *scaled_glyph;
    status = _cairo_scaled_glyph_lookup (scaled_font,
					 glyph_index,
					 CAIRO_SCALED_GLYPH_INFO_METRICS | CAIRO_SCALED_GLYPH_INFO_SURFACE,
					 NULL, /* foreground color */
					 &scaled_glyph);
    if (unlikely (status)) {
	return status;
    }
    cairo_bool_t use_recording_surface = (scaled_glyph->has_info & CAIRO_SCALED_GLYPH_INFO_RECORDING_SURFACE) != 0;
    cairo_matrix_t glyph_matrix = scaled_glyph->surface->base.device_transform_inverse;
    cairo_image_surface_t *glyph_image_surface = scaled_glyph->surface;
    // Attempt to recognize a common pattern for a bitmap font and extract the original glyph image from it
    cairo_surface_t *extracted_surface;
    cairo_image_surface_t *extracted_image = NULL;
    void *extracted_image_extra;
    if (use_recording_surface) {
	cairo_recording_surface_t *recording_surface = (cairo_recording_surface_t *) scaled_glyph->recording_surface;
	if (recording_surface->commands.num_elements == 1) {
	    cairo_command_t *command = *((cairo_command_t **) _cairo_array_index (&recording_surface->commands, 0));
	    if (command->header.type == CAIRO_COMMAND_MASK &&
		command->header.op == CAIRO_OPERATOR_OVER &&
		command->header.clip == NULL &&
		command->mask.source.base.type == CAIRO_PATTERN_TYPE_SOLID &&
		_cairo_color_equal (&command->mask.source.solid.color, _cairo_stock_color (CAIRO_STOCK_BLACK)) &&
		command->mask.mask.base.extend == CAIRO_EXTEND_NONE &&
		command->mask.mask.base.type == CAIRO_PATTERN_TYPE_SURFACE &&
		command->mask.mask.surface.surface->type == CAIRO_SURFACE_TYPE_IMAGE) {
		extracted_surface = command->mask.mask.surface.surface;
		if (_cairo_surface_acquire_source_image (extracted_surface,
							 &extracted_image,
							 &extracted_image_extra) == CAIRO_STATUS_SUCCESS) {
		    if (extracted_image->format == CAIRO_FORMAT_A1 || extracted_image->format == CAIRO_FORMAT_A8) {
			use_recording_surface = FALSE;
			glyph_image_surface = extracted_image;
			glyph_matrix = command->mask.mask.base.matrix;
			status = cairo_matrix_invert (&glyph_matrix);
			assert (status == CAIRO_STATUS_SUCCESS);
		    }
		}
	    }
	}
    }
    cairo_surface_t *paginated_surface = _cairo_svg_surface_create_for_document (document,
										 CAIRO_CONTENT_COLOR_ALPHA,
										 0,
										 0,
										 FALSE);
    cairo_svg_surface_t *svg_surface = (cairo_svg_surface_t *) _cairo_paginated_surface_get_target (paginated_surface);
    status = paginated_surface->status;
    if (unlikely (status)) {
	goto cleanup;
    }
    unsigned int source_id = svg_surface->base.unique_id;
    cairo_surface_set_fallback_resolution (paginated_surface,
					   document->owner->x_fallback_resolution,
					   document->owner->y_fallback_resolution);
    cairo_svg_stream_t temporary_stream = _cairo_svg_stream_create ();
    unsigned int mask_id = document->mask_id++;
    _cairo_svg_stream_printf (&temporary_stream,
			      "<mask id=\"mask-%d\">\n",
			      mask_id);
    cairo_pattern_t *pattern = cairo_pattern_create_for_surface (use_recording_surface ? scaled_glyph->recording_surface
										       : &glyph_image_surface->base);
    _cairo_svg_surface_emit_composite_pattern (&temporary_stream,
					       svg_surface,
					       (cairo_surface_pattern_t *) pattern,
					       invalid_pattern_id,
					       NULL);
    cairo_pattern_destroy (pattern);
    _cairo_svg_stream_printf (&temporary_stream, "</mask>\n");
    _cairo_svg_stream_copy (&temporary_stream, &document->xml_node_defs);
    status = _cairo_svg_stream_destroy (&temporary_stream);
    if (unlikely (status)) {
	goto cleanup;
    }
    svg_surface->transitive_paint_used = TRUE;
    _cairo_svg_stream_printf (&document->xml_node_glyphs, "<rect");
    _cairo_svg_stream_append_paint_dependent (&document->xml_node_glyphs,
					      source_id,
					      CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE);
    _cairo_svg_stream_printf (&document->xml_node_glyphs,
			      " mask=\"url(#mask-%d)\"",
			      mask_id);
    if (!use_recording_surface) {
	_cairo_svg_surface_emit_transform (&document->xml_node_glyphs,
					   "transform",
					   &glyph_matrix,
					   NULL);
    }
    _cairo_svg_stream_printf (&document->xml_node_glyphs, "/>\n");
    cairo_svg_paint_t *paint_entry = _cairo_calloc (sizeof (cairo_svg_paint_t));
    if (paint_entry == NULL) {
	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
	goto cleanup;
    }
    paint_entry->source_id = source_id;
    paint_entry->box.p1.x = 0;
    paint_entry->box.p1.y = 0;
    paint_entry->box.p2.x = glyph_image_surface->width;
    paint_entry->box.p2.y = glyph_image_surface->height;
    if (use_recording_surface) {
	_cairo_matrix_transform_bounding_box (&glyph_matrix,
					      &paint_entry->box.p1.x, &paint_entry->box.p1.y,
					      &paint_entry->box.p2.x, &paint_entry->box.p2.y,
					      NULL);
    }
    _cairo_svg_paint_box_add_padding (&paint_entry->box);
    _cairo_array_init (&paint_entry->paint_elements, sizeof (cairo_svg_paint_element_t));
    _cairo_svg_paint_init_key (paint_entry);
    status = _cairo_hash_table_insert (document->paints, &paint_entry->base);
    if (unlikely (status)) {
	goto cleanup;
    }
    cleanup:
    if (status == CAIRO_STATUS_SUCCESS) {
	status = cairo_surface_status (paginated_surface);
    }
    cairo_surface_destroy (paginated_surface);
    if (extracted_image != NULL) {
	_cairo_surface_release_source_image (extracted_surface, extracted_image, extracted_image_extra);
    }
    return status;
}
static cairo_int_status_t
_cairo_svg_document_emit_glyph (cairo_svg_document_t	*document,
				cairo_scaled_font_t	*scaled_font,
				unsigned long		 scaled_font_glyph_index,
				unsigned int		 font_id,
				unsigned int		 subset_glyph_index)
{
    cairo_int_status_t	     status;
    _cairo_svg_stream_printf (&document->xml_node_glyphs,
			      "<g id=\"glyph-%d-%d\">\n",
			      font_id,
			      subset_glyph_index);
    status = _cairo_svg_document_emit_outline_glyph_data (document,
							  scaled_font,
							  scaled_font_glyph_index);
    if (status == CAIRO_INT_STATUS_UNSUPPORTED)
	status = _cairo_svg_document_emit_bitmap_glyph_data (document,
							     scaled_font,
							     scaled_font_glyph_index);
    if (unlikely (status))
	return status;
    _cairo_svg_stream_printf (&document->xml_node_glyphs, "</g>\n");
    return CAIRO_INT_STATUS_SUCCESS;
}
static cairo_int_status_t
_cairo_svg_document_emit_font_subset (cairo_scaled_font_subset_t	*font_subset,
				      void				*closure)
{
    cairo_svg_document_t *document = closure;
    cairo_int_status_t status = CAIRO_INT_STATUS_SUCCESS;
    unsigned int i;
    _cairo_scaled_font_freeze_cache (font_subset->scaled_font);
    for (i = 0; i < font_subset->num_glyphs; i++) {
	status = _cairo_svg_document_emit_glyph (document,
					         font_subset->scaled_font,
					         font_subset->glyphs[i],
					         font_subset->font_id, i);
	if (unlikely (status))
	    break;
    }
    _cairo_scaled_font_thaw_cache (font_subset->scaled_font);
    return status;
}
static cairo_status_t
5
_cairo_svg_document_emit_font_subsets (cairo_svg_document_t *document)
{
    cairo_status_t status;
5
    status = _cairo_scaled_font_subsets_foreach_scaled (document->font_subsets,
                                                        _cairo_svg_document_emit_font_subset,
                                                        document);
5
    _cairo_scaled_font_subsets_destroy (document->font_subsets);
5
    document->font_subsets = NULL;
5
    return status;
}
static cairo_bool_t
45
_cairo_svg_surface_are_operation_and_pattern_supported (cairo_svg_surface_t *surface,
							cairo_operator_t op,
							const cairo_pattern_t *pattern)
{
45
    if (surface->force_fallbacks) {
	return FALSE;
    }
45
    if (op == CAIRO_OPERATOR_SATURATE) {
        return FALSE;
    }
    /* SVG 1.1 does not support these operators. We already have code for them for SVG 2
     * that can be enabled when SVG 2 becomes widespread.  */
45
    if (op == CAIRO_OPERATOR_OVERLAY ||
45
	op == CAIRO_OPERATOR_COLOR_DODGE ||
45
	op == CAIRO_OPERATOR_COLOR_BURN ||
45
	op == CAIRO_OPERATOR_HARD_LIGHT ||
45
	op == CAIRO_OPERATOR_SOFT_LIGHT ||
43
	op == CAIRO_OPERATOR_DIFFERENCE ||
43
	op == CAIRO_OPERATOR_EXCLUSION ||
43
	op == CAIRO_OPERATOR_HSL_HUE ||
43
	op == CAIRO_OPERATOR_HSL_SATURATION ||
43
	op == CAIRO_OPERATOR_HSL_COLOR ||
	op == CAIRO_OPERATOR_HSL_LUMINOSITY) {
2
	return FALSE;
    }
43
    if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) {
        /* Do not cause stack overflow because of too deep or infinite recording surfaces. */
9
	if (((cairo_surface_pattern_t *) pattern)->surface->type == CAIRO_SURFACE_TYPE_RECORDING &&
9
	    surface->depth > 1000) {
	    return FALSE;
	}
	/* SVG doesn't support extends reflect and pad for surface pattern. */
9
        if (pattern->extend != CAIRO_EXTEND_NONE && pattern->extend != CAIRO_EXTEND_REPEAT) {
	    return FALSE;
	}
    }
    /* SVG 1.1 does not support the focal point (fx, fy) that is outside of the circle defined by (cx, cy) and r. */
43
    if (pattern->type == CAIRO_PATTERN_TYPE_RADIAL) {
	cairo_radial_pattern_t *radial_pattern = (cairo_radial_pattern_t *) pattern;
	double max_radius;
	if (radial_pattern->cd1.radius > radial_pattern->cd2.radius) {
	    max_radius = radial_pattern->cd1.radius;
	} else {
	    max_radius = radial_pattern->cd2.radius;
	}
	cairo_point_double_t c1 = radial_pattern->cd1.center;
	cairo_point_double_t c2 = radial_pattern->cd2.center;
	if ((c1.x - c2.x) * (c1.x - c2.x) + (c1.y - c2.y) * (c1.y - c2.y) >= max_radius * max_radius) {
	    return FALSE;
	}
    }
43
    if (pattern->type == CAIRO_PATTERN_TYPE_MESH) {
	return FALSE;
    }
43
    if (pattern->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) {
	return FALSE;
    }
43
    return TRUE;
}
static cairo_status_t
14
_cairo_svg_surface_finish (void *abstract_surface)
{
    cairo_status_t status, final_status;
14
    cairo_svg_surface_t *surface = abstract_surface;
14
    if (_cairo_paginated_surface_get_target (surface->document->owner) == &surface->base) {
5
	final_status = _cairo_svg_document_finish (surface->document);
    } else {
9
	final_status = CAIRO_STATUS_SUCCESS;
    }
14
    status = _cairo_svg_stream_destroy (&surface->xml_node);
14
    if (final_status == CAIRO_STATUS_SUCCESS) {
14
	final_status = status;
    }
28
    for (unsigned int i = 0; i < surface->page_set.num_elements; i++) {
14
	cairo_svg_page_t *page = _cairo_array_index (&surface->page_set, i);
14
	status = _cairo_svg_stream_destroy (&page->xml_node);
14
	if (final_status == CAIRO_STATUS_SUCCESS) {
14
	    final_status = status;
	}
    }
14
    _cairo_array_fini (&surface->page_set);
14
    _cairo_surface_clipper_reset (&surface->clipper);
14
    _cairo_hash_table_foreach (surface->source_surfaces, _cairo_svg_source_surface_pluck, surface->source_surfaces);
14
    _cairo_hash_table_destroy (surface->source_surfaces);
14
    status = _cairo_svg_document_destroy (surface->document);
14
    if (final_status == CAIRO_STATUS_SUCCESS) {
14
	final_status = status;
    }
14
    return final_status;
}
static const char *
28
_cairo_svg_surface_emit_static_filter (cairo_svg_document_t *document, enum cairo_svg_filter filter)
{
28
    if (!document->filters_emitted[filter]) {
4
	document->filters_emitted[filter] = TRUE;
4
	if (filter == CAIRO_SVG_FILTER_REMOVE_COLOR) {
	    // (r, g, b, a) -> (1, 1, 1, a)
	    _cairo_svg_stream_printf (&document->xml_node_filters,
				      "<filter id=\"filter-remove-color\" "
				      "x=\"0%%\" y=\"0%%\" width=\"100%%\" height=\"100%%\">\n"
				      "<feColorMatrix color-interpolation-filters=\"sRGB\" "
	                              "values=\"0 0 0 0 1 "
				      /*    */ "0 0 0 0 1 "
				      /*    */ "0 0 0 0 1 "
				      /*    */ "0 0 0 1 0\" />\n"
				      "</filter>\n");
4
	} else if (filter == CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA) {
	    // (r, g, b, a) -> (1, 1, 1, 1 - a)
4
	    _cairo_svg_stream_printf (&document->xml_node_filters,
				      "<filter id=\"filter-remove-color-and-invert-alpha\" "
				      "x=\"0%%\" y=\"0%%\" width=\"100%%\" height=\"100%%\">\n"
				      "<feColorMatrix color-interpolation-filters=\"sRGB\" "
				      "values=\"0 0 0 0 1 "
				      /*    */ "0 0 0 0 1 "
				      /*    */ "0 0 0 0 1 "
				      /*    */ "0 0 0 -1 1\"/>\n"
				      "</filter>\n");
	} else if (filter ==  CAIRO_SVG_FILTER_COLOR_TO_ALPHA) {
	    // (r, g, b, a) -> (1, 1, 1, 0.2126 * r + 0.7152 * g + 0.0722 * b)
	    _cairo_svg_stream_printf (&document->xml_node_filters,
				      "<filter id=\"filter-color-to-alpha\" "
				      "x=\"0%%\" y=\"0%%\" width=\"100%%\" height=\"100%%\">\n"
				      "<feColorMatrix color-interpolation-filters=\"sRGB\" "
				      "values=\"0 0 0 0 1 "
				      /*    */ "0 0 0 0 1 "
				      /*    */ "0 0 0 0 1 "
				      /*    */ "0.2126 0.7152 0.0722 0 0\"/>\n"
				      "</filter>\n");
	}
    }
28
    if (filter == CAIRO_SVG_FILTER_REMOVE_COLOR) {
	return "remove-color";
28
    } else if (filter == CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA) {
28
	return "remove-color-and-invert-alpha";
    } else if (filter ==  CAIRO_SVG_FILTER_COLOR_TO_ALPHA) {
	return "color-to-alpha";
    } else {
	ASSERT_NOT_REACHED;
    }
    return FALSE; /* squelch warning */
}
#define _CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER(operation) \
    _cairo_svg_stream_printf (&surface->document->xml_node_filters, \
                              "<filter id=\"filter-%d\" " \
                              "x=\"0%%\" y=\"0%%\" width=\"100%%\" height=\"100%%\">\n" \
                              "<feImage xlink:href=\"#compositing-group-%d\" result=\"source\"", \
                              filter_id, \
                              source_compositing_group_id); \
    _cairo_svg_stream_append_paint_dependent (&surface->document->xml_node_filters, \
                                              surface->source_id, \
                                              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); \
    _cairo_svg_stream_printf (&surface->document->xml_node_filters, \
                              "/>\n" \
                              "<feImage xlink:href=\"#compositing-group-%d\" result=\"destination\"", \
                              destination_compositing_group_id); \
    _cairo_svg_stream_append_paint_dependent (&surface->document->xml_node_filters, \
                                              surface->source_id, \
                                              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); \
    _cairo_svg_stream_printf (&surface->document->xml_node_filters, \
                              "/>\n" \
                              "<feComposite in=\"source\" in2=\"destination\" " \
                              "operator=\"" operation "\" " \
                              "color-interpolation-filters=\"sRGB\"/>\n" \
                              "</filter>\n", \
                              filter_id, \
                              source_compositing_group_id, \
                              destination_compositing_group_id);
#define _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER(mode) \
    _cairo_svg_stream_printf (&surface->document->xml_node_filters, \
                              "<filter id=\"filter-%d\" " \
                              "x=\"0%%\" y=\"0%%\" width=\"100%%\" height=\"100%%\">\n" \
                              "<feImage xlink:href=\"#compositing-group-%d\" result=\"source\"", \
                              filter_id, \
                              source_compositing_group_id); \
    _cairo_svg_stream_append_paint_dependent (&surface->document->xml_node_filters, \
                                              surface->source_id, \
                                              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); \
    _cairo_svg_stream_printf (&surface->document->xml_node_filters, \
                              "/>\n" \
                              "<feImage xlink:href=\"#compositing-group-%d\" result=\"destination\"", \
                              destination_compositing_group_id); \
    _cairo_svg_stream_append_paint_dependent (&surface->document->xml_node_filters, \
                                              surface->source_id, \
                                              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); \
    _cairo_svg_stream_printf (&surface->document->xml_node_filters, \
                              "/>\n" \
                              "<feBlend in=\"source\" in2=\"destination\" " \
                              "mode=\"" mode "\" " \
                              "color-interpolation-filters=\"sRGB\"/>\n" \
                              "</filter>\n", \
                              filter_id, \
                              source_compositing_group_id, \
                              destination_compositing_group_id);
static unsigned int
30
_cairo_svg_surface_emit_parametric_filter (cairo_svg_surface_t *surface,
					   enum cairo_svg_filter filter,
					   unsigned int source_compositing_group_id,
					   unsigned int destination_compositing_group_id)
{
30
    unsigned int filter_id = surface->document->filter_id++;
30
    switch (filter) {
    case CAIRO_SVG_FILTER_OVER:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("over")
	break;
    case CAIRO_SVG_FILTER_IN:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("in")
	break;
    case CAIRO_SVG_FILTER_OUT:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("out")
	break;
    case CAIRO_SVG_FILTER_ATOP:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("atop")
	break;
2
    case CAIRO_SVG_FILTER_XOR:
2
	_CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("xor")
2
	break;
28
    case CAIRO_SVG_FILTER_ADD:
	// This can also be done with <feComposite operator="lighter"/>, but it is not in SVG 1.1
28
	_cairo_svg_stream_printf (&surface->document->xml_node_filters,
				  "<filter id=\"filter-%d\" "
				  "x=\"0%%\" y=\"0%%\" width=\"100%%\" height=\"100%%\">\n"
				  "<feImage xlink:href=\"#compositing-group-%d\" result=\"source\"",
				  filter_id,
				  source_compositing_group_id);
28
	_cairo_svg_stream_append_paint_dependent (&surface->document->xml_node_filters,
						  surface->source_id,
						  CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN);
28
	_cairo_svg_stream_printf (&surface->document->xml_node_filters,
				  "/>\n"
				  "<feImage xlink:href=\"#compositing-group-%d\" result=\"destination\"",
				  destination_compositing_group_id);
28
	_cairo_svg_stream_append_paint_dependent (&surface->document->xml_node_filters,
						  surface->source_id,
						  CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN);
28
	_cairo_svg_stream_printf (&surface->document->xml_node_filters,
				  "/>\n"
				  "<feComposite in=\"source\" in2=\"destination\" "
				  "operator=\"arithmetic\" k1=\"0\" k2=\"1\" k3=\"1\" k4=\"0\" "
				  "color-interpolation-filters=\"sRGB\"/>\n"
				  "</filter>\n");
28
	break;
    case CAIRO_SVG_FILTER_MULTIPLY:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("multiply")
	break;
    case CAIRO_SVG_FILTER_SCREEN:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("screen")
	break;
    case CAIRO_SVG_FILTER_OVERLAY:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("overlay")
	break;
    case CAIRO_SVG_FILTER_DARKEN:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("darken")
	break;
    case CAIRO_SVG_FILTER_LIGHTEN:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("lighten")
	break;
    case CAIRO_SVG_FILTER_COLOR_DODGE:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("color-dodge")
	break;
    case CAIRO_SVG_FILTER_COLOR_BURN:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("color-burn")
	break;
    case CAIRO_SVG_FILTER_HARD_LIGHT:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("hard-light")
	break;
    case CAIRO_SVG_FILTER_SOFT_LIGHT:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("soft-light")
	break;
    case CAIRO_SVG_FILTER_DIFFERENCE:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("difference")
	break;
    case CAIRO_SVG_FILTER_EXCLUSION:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("exclusion")
	break;
    case CAIRO_SVG_FILTER_HUE:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("hue")
	break;
    case CAIRO_SVG_FILTER_SATURATION:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("saturation")
	break;
    case CAIRO_SVG_FILTER_COLOR:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("color")
	break;
    case CAIRO_SVG_FILTER_LUMINOSITY:
	_CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("luminosity")
	break;
    case CAIRO_SVG_FILTER_REMOVE_COLOR:
    case CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA:
    case CAIRO_SVG_FILTER_COLOR_TO_ALPHA:
    case CAIRO_SVG_FILTER_LAST_STATIC_FILTER:
    default:
	ASSERT_NOT_REACHED;
    }
30
    return filter_id;
}
typedef struct {
    cairo_svg_stream_t *output;
    unsigned int in_mem;
    unsigned int trailing;
    unsigned char src[3];
} base64_write_closure_t;
static char const base64_table[64] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static cairo_status_t
24
base64_write_func (void *closure,
		   const unsigned char *data,
		   unsigned int length)
{
24
    base64_write_closure_t *info = (base64_write_closure_t *) closure;
    unsigned int i;
    unsigned char *src;
24
    src = info->src;
24
    if (info->in_mem + length < 3) {
	for (i = 0; i < length; i++) {
	    src[i + info->in_mem] = *data++;
	}
	info->in_mem += length;
	return CAIRO_STATUS_SUCCESS;
    }
    do {
	unsigned char dst[4];
4770
	for (i = info->in_mem; i < 3; i++) {
3570
	    src[i] = *data++;
3570
	    length--;
	}
1200
	info->in_mem = 0;
1200
	dst[0] = base64_table[src[0] >> 2];
1200
	dst[1] = base64_table[(src[0] & 0x03) << 4 | src[1] >> 4];
1200
	dst[2] = base64_table[(src[1] & 0x0f) << 2 | src[2] >> 6];
1200
	dst[3] = base64_table[src[2] & 0xfc >> 2];
	/* Special case for the last missing bits */
1200
	switch (info->trailing) {
	    case 2:
		dst[2] = '=';
		/* fall through */
	    case 1:
		dst[3] = '=';
1200
	    default:
1200
		break;
	}
1200
	_cairo_svg_stream_write (info->output, dst, 4);
1200
    } while (length >= 3);
54
    for (i = 0; i < length; i++) {
30
	src[i] = *data++;
    }
24
    info->in_mem = length;
24
    return info->output->status;
}
static cairo_int_status_t
2
_cairo_surface_base64_encode_jpeg (cairo_surface_t       *surface,
				   cairo_svg_stream_t *output)
{
    const unsigned char *mime_data;
    unsigned long mime_data_length;
    cairo_image_info_t image_info;
    base64_write_closure_t info;
    cairo_status_t status;
2
    cairo_surface_get_mime_data (surface, CAIRO_MIME_TYPE_JPEG,
				 &mime_data, &mime_data_length);
2
    if (mime_data == NULL)
2
	return CAIRO_INT_STATUS_UNSUPPORTED;
    status = _cairo_image_info_get_jpeg_info (&image_info, mime_data, mime_data_length);
    if (unlikely (status))
	return status;
    if (image_info.num_components == 4)
	return CAIRO_INT_STATUS_UNSUPPORTED;
    _cairo_svg_stream_printf (output, "data:image/jpeg;base64,");
    info.output = output;
    info.in_mem = 0;
    info.trailing = 0;
    status = base64_write_func (&info, mime_data, mime_data_length);
    if (unlikely (status))
	return status;
    if (info.in_mem > 0) {
	memset (info.src + info.in_mem, 0, 3 - info.in_mem);
	info.trailing = 3 - info.in_mem;
	info.in_mem = 3;
	status = base64_write_func (&info, NULL, 0);
    }
    return status;
}
static cairo_int_status_t
2
_cairo_surface_base64_encode_png (cairo_surface_t       *surface,
				  cairo_svg_stream_t *output)
{
    const unsigned char *mime_data;
    unsigned long mime_data_length;
    base64_write_closure_t info;
    cairo_status_t status;
2
    cairo_surface_get_mime_data (surface, CAIRO_MIME_TYPE_PNG,
				 &mime_data, &mime_data_length);
2
    if (unlikely (surface->status))
	return surface->status;
2
    if (mime_data == NULL)
2
	return CAIRO_INT_STATUS_UNSUPPORTED;
    _cairo_svg_stream_printf (output, "data:image/png;base64,");
    info.output = output;
    info.in_mem = 0;
    info.trailing = 0;
    status = base64_write_func (&info, mime_data, mime_data_length);
    if (unlikely (status))
	return status;
    if (info.in_mem > 0) {
	memset (info.src + info.in_mem, 0, 3 - info.in_mem);
	info.trailing = 3 - info.in_mem;
	info.in_mem = 3;
	status = base64_write_func (&info, NULL, 0);
    }
    return status;
}
static cairo_int_status_t
2
_cairo_surface_base64_encode (cairo_surface_t       *surface,
			      cairo_svg_stream_t *output)
{
    cairo_int_status_t status;
    base64_write_closure_t info;
2
    status = _cairo_surface_base64_encode_jpeg (surface, output);
2
    if (status != CAIRO_INT_STATUS_UNSUPPORTED)
	return status;
2
    status = _cairo_surface_base64_encode_png (surface, output);
2
    if (status != CAIRO_INT_STATUS_UNSUPPORTED)
	return status;
2
    info.output = output;
2
    info.in_mem = 0;
2
    info.trailing = 0;
2
    _cairo_svg_stream_printf (info.output, "data:image/png;base64,");
2
    status = cairo_surface_write_to_png_stream (surface, base64_write_func,
						(void *) &info);
2
    if (unlikely (status))
	return status;
2
    if (info.in_mem > 0) {
	memset (info.src + info.in_mem, 0, 3 - info.in_mem);
	info.trailing = 3 - info.in_mem;
	info.in_mem = 3;
	status = base64_write_func (&info, NULL, 0);
    }
2
    return status;
}
/**
 * _cairo_svg_surface_emit_attr_value:
 *
 * Write the value to output the stream as a sequence of characters,
 * while escaping those which have special meaning in the XML
 * attribute's value context: &amp; and &quot;.
 **/
static void
_cairo_svg_surface_emit_attr_value (cairo_svg_stream_t *stream,
				    const unsigned char *value,
				    unsigned int length)
{
    const unsigned char *p;
    const unsigned char *q;
    unsigned int i;
    /* we'll accumulate non-special chars in [q, p) range */
    p = value;
    q = p;
    for (i = 0; i < length; i++, p++) {
	if (*p == '&' || *p == '"') {
	    /* flush what's left before special char */
	    if (p != q) {
		_cairo_svg_stream_write (stream, q, p - q);
		q = p + 1;
	    }
	    if (*p == '&')
		_cairo_svg_stream_printf (stream, "&amp;");
	    else // p == '"'
		_cairo_svg_stream_printf (stream, "&quot;");
	}
    }
    /* flush the trailing chars if any */
    if (p != q)
	_cairo_svg_stream_write (stream, q, p - q);
}
static cairo_status_t
2
_cairo_svg_surface_emit_surface (cairo_svg_document_t *document,
				 cairo_surface_t *surface,
				 unsigned int source_id)
{
    cairo_rectangle_int_t extents;
    cairo_bool_t is_bounded;
    cairo_status_t status;
    const unsigned char *uri;
    unsigned long uri_len;
2
    is_bounded = _cairo_surface_get_extents (surface, &extents);
2
    assert (is_bounded);
2
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<image id=\"source-%d\" x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\"",
			      source_id,
			      extents.x, extents.y,
			      extents.width, extents.height);
2
    if (extents.width != 0 && extents.height != 0) {
2
	_cairo_svg_stream_printf (&document->xml_node_defs, " xlink:href=\"");
2
	cairo_surface_get_mime_data (surface, CAIRO_MIME_TYPE_URI,
				     &uri, &uri_len);
2
	if (uri != NULL) {
	    _cairo_svg_surface_emit_attr_value (&document->xml_node_defs,
						uri, uri_len);
	} else {
2
	    status = _cairo_surface_base64_encode (surface,
						   &document->xml_node_defs);
2
	    if (unlikely (status))
		return status;
	}
2
    _cairo_svg_stream_printf (&document->xml_node_defs, "\"");
    }
2
    _cairo_svg_stream_printf (&document->xml_node_defs, "/>\n");
2
    return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
2
_cairo_svg_surface_emit_composite_surface_pattern (cairo_svg_stream_t *output,
						   cairo_svg_surface_t *surface,
						   cairo_surface_pattern_t *pattern,
						   unsigned int pattern_id,
						   const cairo_matrix_t *parent_matrix)
{
    cairo_status_t status;
2
    cairo_matrix_t p2u = pattern->base.matrix;
2
    status = cairo_matrix_invert (&p2u);
    /* cairo_pattern_set_matrix ensures the matrix is invertible */
2
    assert (status == CAIRO_STATUS_SUCCESS);
    cairo_bool_t is_new;
    cairo_svg_source_surface_t *source_surface;
2
    status = _cairo_svg_surface_add_source_surface (surface,
						    pattern->surface,
						    &is_new,
						    &source_surface);
2
    if (unlikely (status)) {
	return status;
    }
2
    unsigned int source_id = source_surface->id;
2
    if (is_new) {
2
	status = _cairo_svg_surface_emit_surface (surface->document,
						  pattern->surface,
						  source_id);
2
	if (unlikely (status)) {
	    return status;
	}
    }
2
    if (pattern_id != invalid_pattern_id) {
	cairo_rectangle_int_t extents;
	cairo_bool_t is_bounded;
	is_bounded = _cairo_surface_get_extents (pattern->surface, &extents);
	assert (is_bounded);
	_cairo_svg_stream_printf (output,
				  "<pattern id=\"pattern-%d\" "
				  "patternUnits=\"userSpaceOnUse\" "
				  "x=\"%d\" y=\"%d\" "
				  "width=\"%d\" height=\"%d\" "
				  "viewBox=\"%d %d %d %d\"",
				  pattern_id,
				  extents.x, extents.y,
				  extents.width, extents.height,
				  extents.x, extents.y,
				  extents.width, extents.height);
	_cairo_svg_surface_emit_transform (output,
					   "patternTransform",
					   &p2u,
					   parent_matrix);
	_cairo_svg_stream_printf (output, ">\n");
    }
2
    _cairo_svg_stream_printf (output,
			      "<use xlink:href=\"#source-%d\"",
			      source_id);
2
    if (pattern->surface->content == CAIRO_CONTENT_ALPHA) {
	cairo_bool_t can_skip_filter = FALSE;
	if (pattern->surface->backend &&
	    pattern->surface->backend->type == CAIRO_SURFACE_TYPE_IMAGE &&
	    (((cairo_image_surface_t *) pattern->surface)->format == CAIRO_FORMAT_A1 ||
	     ((cairo_image_surface_t *) pattern->surface)->format == CAIRO_FORMAT_A8)) {
	    can_skip_filter = TRUE;
	}
	if (!can_skip_filter) {
	    _cairo_svg_stream_printf (output,
				      " filter=\"url(#filter-%s)\"",
				      _cairo_svg_surface_emit_static_filter (surface->document,
									     CAIRO_SVG_FILTER_COLOR_TO_ALPHA));
	}
    }
2
    if (pattern_id == invalid_pattern_id) {
2
	_cairo_svg_surface_emit_transform (output,
					   "transform",
					   &p2u,
					   parent_matrix);
    }
2
    _cairo_svg_stream_printf (output, "/>\n");
2
    if (pattern_id != invalid_pattern_id) {
	_cairo_svg_stream_printf (output, "</pattern>\n");
    }
2
    return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
9
_cairo_svg_surface_emit_recording_surface (cairo_svg_surface_t *surface,
					   cairo_recording_surface_t *source,
					   unsigned int source_id,
					   cairo_bool_t *transitive_paint_used)
{
    cairo_status_t status;
9
    cairo_svg_document_t *document = surface->document;
9
    cairo_surface_t *paginated_surface = _cairo_svg_surface_create_for_document (document,
										 source->base.content,
										 0,
										 0,
										 FALSE);
9
    cairo_svg_surface_t *svg_surface = (cairo_svg_surface_t *) _cairo_paginated_surface_get_target (paginated_surface);
9
    if (unlikely (paginated_surface->status)) {
	return paginated_surface->status;
    }
9
    svg_surface->source_id = source_id;
9
    svg_surface->depth = surface->depth + 1;
    cairo_rectangle_int_t extents;
9
    cairo_bool_t bounded = _cairo_surface_get_extents (&source->base, &extents);
9
    cairo_surface_set_fallback_resolution (paginated_surface,
9
					   document->owner->x_fallback_resolution,
9
					   document->owner->y_fallback_resolution);
9
    if (source->base.content == CAIRO_CONTENT_COLOR) {
	_cairo_svg_surface_emit_paint (&svg_surface->xml_node, svg_surface, &_cairo_pattern_black.base, FALSE);
    }
9
    status = _cairo_recording_surface_replay (&source->base, paginated_surface);
9
    if (unlikely (status)) {
	cairo_surface_destroy (paginated_surface);
	return status;
    }
9
    cairo_surface_show_page (paginated_surface);
9
    status = cairo_surface_status (paginated_surface);
9
    if (unlikely (status)) {
	cairo_surface_destroy (paginated_surface);
	return status;
    }
    unsigned int clip_id;
9
    if (bounded) {
	clip_id = document->clip_id++;
	_cairo_svg_stream_printf (&document->xml_node_defs,
				  "<clipPath id=\"clip-%d\">\n"
				  "<rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\"/>\n"
				  "</clipPath>\n",
				  clip_id,
				  extents.x,
				  extents.y,
				  extents.width,
				  extents.height);
    }
9
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<g id=\"source-%d\"",
			      source_id);
9
    if (bounded) {
	_cairo_svg_stream_printf (&document->xml_node_defs,
				  " clip-path=\"url(#clip-%d)\"",
				  clip_id);
    }
9
    if (source->base.content == CAIRO_CONTENT_ALPHA) {
	_cairo_svg_stream_printf (&document->xml_node_defs,
				  " filter=\"url(#filter-%s)\"",
				  _cairo_svg_surface_emit_static_filter (document, CAIRO_SVG_FILTER_REMOVE_COLOR));
    }
9
    _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
9
    if (svg_surface->xml_node.elements.num_elements > 0) {
	cairo_svg_page_t *page = _cairo_svg_surface_store_page (svg_surface);
	if (unlikely (page == NULL)) {
	    cairo_surface_destroy (paginated_surface);
	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
	}
    }
9
    if (svg_surface->page_set.num_elements > 0) {
9
	cairo_svg_page_t *page = _cairo_array_index (&svg_surface->page_set, svg_surface->page_set.num_elements - 1);
9
	_cairo_svg_stream_copy (&page->xml_node, &document->xml_node_defs);
    }
9
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
9
    *transitive_paint_used = svg_surface->transitive_paint_used;
9
    status = cairo_surface_status (paginated_surface);
9
    cairo_surface_destroy (paginated_surface);
9
    return status;
}
static cairo_recording_surface_t *
9
_cairo_svg_surface_to_recording_surface (const cairo_surface_pattern_t *pattern)
{
9
    cairo_surface_t *surface = pattern->surface;
9
    if (_cairo_surface_is_paginated (surface))
	surface = _cairo_paginated_surface_get_recording (surface);
9
    if (_cairo_surface_is_snapshot (surface))
9
	surface = _cairo_surface_snapshot_get_target (surface);
9
    return (cairo_recording_surface_t *) surface;
}
static cairo_bool_t
11
_cairo_svg_surface_svg_pattern_should_be_used (const cairo_pattern_t *pattern)
{
    cairo_rectangle_int_t extents;
22
    return pattern->type == CAIRO_PATTERN_TYPE_SURFACE &&
11
	   pattern->extend == CAIRO_EXTEND_REPEAT &&
	   _cairo_surface_get_extents (((cairo_surface_pattern_t *) pattern)->surface, &extents);
}
static cairo_bool_t
133
_cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (const cairo_pattern_t *pattern)
{
133
    return pattern->type == CAIRO_PATTERN_TYPE_SURFACE && !_cairo_svg_surface_svg_pattern_should_be_used (pattern);
}
static cairo_status_t
9
_cairo_svg_surface_emit_composite_recording_pattern (cairo_svg_stream_t *output,
						     cairo_svg_surface_t *surface,
						     cairo_surface_pattern_t *pattern,
						     unsigned int pattern_id,
						     const cairo_matrix_t *parent_matrix)
{
    cairo_status_t status;
9
    cairo_svg_document_t *document = surface->document;
9
    cairo_matrix_t p2u = pattern->base.matrix;
9
    status = cairo_matrix_invert (&p2u);
    /* cairo_pattern_set_matrix ensures the matrix is invertible */
9
    assert (status == CAIRO_STATUS_SUCCESS);
    cairo_bool_t is_new;
    cairo_svg_source_surface_t *source_surface;
9
    status = _cairo_svg_surface_add_source_surface (surface,
						    pattern->surface,
						    &is_new,
						    &source_surface);
9
    if (unlikely (status)) {
	return status;
    }
9
    unsigned int source_id = source_surface->id;
9
    cairo_recording_surface_t *recording_surface = _cairo_svg_surface_to_recording_surface (pattern);
9
    if (is_new) {
9
	status = _cairo_svg_surface_emit_recording_surface (surface,
							    recording_surface,
							    source_id,
9
							    &source_surface->transitive_paint_used);
9
	if (unlikely (status)) {
	    return status;
	}
9
	if (source_surface->transitive_paint_used) {
6
	    cairo_svg_paint_t *paint_entry = _cairo_calloc (sizeof (cairo_svg_paint_t));
6
	    if (paint_entry == NULL) {
		return _cairo_error (CAIRO_STATUS_NO_MEMORY);
	    }
6
	    paint_entry->source_id = source_id;
6
	    _cairo_array_init (&paint_entry->paint_elements, sizeof (cairo_svg_paint_element_t));
6
	    _cairo_svg_paint_init_key (paint_entry);
6
	    status = _cairo_hash_table_insert (document->paints, &paint_entry->base);
6
	    if (unlikely (status)) {
		return status;
	    }
	}
    }
9
    if (source_surface->transitive_paint_used) {
	cairo_svg_paint_t paint_key;
6
	paint_key.source_id = source_id;
6
	_cairo_svg_paint_init_key (&paint_key);
6
	cairo_svg_paint_t *found_paint_entry = _cairo_hash_table_lookup (document->paints,
									 &paint_key.base);
6
	assert (found_paint_entry);
	cairo_svg_paint_element_t paint_element;
6
	paint_element.source_id = surface->source_id;
6
	paint_element.matrix = pattern->base.matrix;
6
	if (parent_matrix != NULL) {
	    cairo_matrix_t parent_matrix_inverse = *parent_matrix;
	    status = cairo_matrix_invert (&parent_matrix_inverse);
	    /* cairo_pattern_set_matrix ensures the matrix is invertible */
	    assert (status == CAIRO_STATUS_SUCCESS);
	    cairo_matrix_multiply (&paint_element.matrix, &parent_matrix_inverse, &paint_element.matrix);
	}
6
	status = _cairo_array_append (&found_paint_entry->paint_elements, &paint_element);
6
	if (unlikely (status)) {
	    return status;
	}
6
	surface->transitive_paint_used = TRUE;
    }
9
    if (pattern_id != invalid_pattern_id) {
	assert (!recording_surface->unbounded);
	_cairo_svg_stream_printf (output,
				  "<pattern id=\"pattern-%d\" "
				  "patternUnits=\"userSpaceOnUse\" "
				  "x=\"%f\" y=\"%f\" width=\"%f\" height=\"%f\" "
				  "viewBox=\"%f %f %f %f\"",
				  pattern_id,
				  recording_surface->extents_pixels.x,
				  recording_surface->extents_pixels.y,
				  recording_surface->extents_pixels.width,
				  recording_surface->extents_pixels.height,
				  recording_surface->extents_pixels.x,
				  recording_surface->extents_pixels.y,
				  recording_surface->extents_pixels.width,
				  recording_surface->extents_pixels.height);
	_cairo_svg_surface_emit_transform (output, "patternTransform", &p2u, parent_matrix);
	_cairo_svg_stream_printf (output, ">\n");
    }
9
    _cairo_svg_stream_printf (output,
			      "<use xlink:href=\"#source-%d\"",
			      source_id);
9
    if (pattern_id == invalid_pattern_id) {
9
	_cairo_svg_surface_emit_transform (output, "transform", &p2u, parent_matrix);
    }
9
    _cairo_svg_stream_printf (output, "/>\n");
9
    if (pattern_id != invalid_pattern_id) {
	_cairo_svg_stream_printf (output, "</pattern>\n");
    }
9
    return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
11
_cairo_svg_surface_emit_composite_pattern (cairo_svg_stream_t *output,
					   cairo_svg_surface_t *surface,
					   cairo_surface_pattern_t *pattern,
					   unsigned int pattern_id,
					   const cairo_matrix_t *parent_matrix)
{
11
    if (pattern_id != invalid_pattern_id) {
	assert (_cairo_svg_surface_svg_pattern_should_be_used (&pattern->base));
    }
11
    if (pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING) {
9
	return _cairo_svg_surface_emit_composite_recording_pattern (output,
								    surface,
								    pattern,
								    pattern_id,
								    parent_matrix);
    } else {
2
	return _cairo_svg_surface_emit_composite_surface_pattern (output,
								  surface,
								  pattern,
								  pattern_id,
								  parent_matrix);
    }
}
static cairo_status_t
122
_cairo_svg_surface_emit_solid_pattern (cairo_svg_surface_t *surface,
				       cairo_solid_pattern_t *pattern,
				       cairo_svg_stream_t *output,
				       cairo_bool_t is_stroke)
{
122
    _cairo_svg_stream_printf (output,
			      is_stroke ? " stroke=\"rgb(%f%%, %f%%, %f%%)\" stroke-opacity=\"%f\""
					: " fill=\"rgb(%f%%, %f%%, %f%%)\" fill-opacity=\"%f\"",
122
			      pattern->color.red * 100.0,
122
			      pattern->color.green * 100.0,
122
			      pattern->color.blue * 100.0,
			      pattern->color.alpha);
122
    return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
_cairo_svg_surface_emit_surface_pattern (cairo_svg_surface_t *surface,
					 cairo_surface_pattern_t *pattern,
					 cairo_svg_stream_t *output,
					 cairo_bool_t is_stroke,
					 const cairo_matrix_t *parent_matrix)
{
    cairo_svg_document_t *document = surface->document;
    cairo_status_t status;
    unsigned int pattern_id = document->pattern_id++;
    status = _cairo_svg_surface_emit_composite_pattern (&document->xml_node_defs,
							surface,
							pattern,
							pattern_id,
							parent_matrix);
    if (unlikely (status))
	return status;
    _cairo_svg_stream_printf (output,
			      is_stroke ? " stroke=\"url(#pattern-%d)\""
					: " fill=\"url(#pattern-%d)\"",
			      pattern_id);
    return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
_cairo_svg_surface_emit_pattern_stops (cairo_svg_stream_t *output,
				       const cairo_gradient_pattern_t *pattern,
				       double start_offset,
				       cairo_bool_t reverse_stops,
				       cairo_bool_t emulate_reflect)
{
    cairo_gradient_stop_t *stops;
    unsigned int n_stops;
    if (pattern->n_stops < 1) {
	return CAIRO_STATUS_SUCCESS;
    }
    if (pattern->n_stops == 1) {
	_cairo_svg_stream_printf (output,
				  "<stop offset=\"%f\" "
				  "stop-color=\"rgb(%f%%, %f%%, %f%%)\" "
				  "stop-opacity=\"%f\"/>\n",
				  pattern->stops[0].offset,
				  pattern->stops[0].color.red * 100.0,
				  pattern->stops[0].color.green * 100.0,
				  pattern->stops[0].color.blue * 100.0,
				  pattern->stops[0].color.alpha);
	return CAIRO_STATUS_SUCCESS;
    }
    if (emulate_reflect || reverse_stops) {
	n_stops = emulate_reflect ? pattern->n_stops * 2 - 2 : pattern->n_stops;
	stops = _cairo_malloc_ab (n_stops, sizeof (cairo_gradient_stop_t));
	if (unlikely (stops == NULL))
	    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
	for (unsigned int i = 0; i < pattern->n_stops; i++) {
	    if (reverse_stops) {
		stops[i] = pattern->stops[pattern->n_stops - i - 1];
		stops[i].offset = 1.0 - stops[i].offset;
	    } else {
		stops[i] = pattern->stops[i];
	    }
	    if (emulate_reflect) {
		stops[i].offset *= 0.5;
		if (i > 0 && i < pattern->n_stops - 1) {
		    if (reverse_stops) {
			stops[i + pattern->n_stops - 1] = pattern->stops[i];
			stops[i + pattern->n_stops - 1].offset = 0.5 + 0.5 * stops[i + pattern->n_stops - 1].offset;
		    } else {
			stops[i + pattern->n_stops - 1] = pattern->stops[pattern->n_stops - i - 1];
			stops[i + pattern->n_stops - 1].offset = 1.0 - 0.5 * stops[i + pattern->n_stops - 1].offset;
		    }
		}
	    }
	}
    } else {
	n_stops = pattern->n_stops;
	stops = pattern->stops;
    }
    if (start_offset >= 0.0) {
	for (unsigned int i = 0; i < n_stops; i++) {
	    _cairo_svg_stream_printf (output,
				      "<stop offset=\"%f\" "
				      "stop-color=\"rgb(%f%%, %f%%, %f%%)\" "
				      "stop-opacity=\"%f\"/>\n",
				      start_offset + (1.0 - start_offset) * stops[i].offset,
				      stops[i].color.red * 100.0,
				      stops[i].color.green * 100.0,
				      stops[i].color.blue * 100.0,
				      stops[i].color.alpha);
	}
    } else {
	cairo_bool_t found = FALSE;
	unsigned int offset_index;
	cairo_color_stop_t offset_color_start, offset_color_stop;
	for (unsigned int i = 0; i <= n_stops; i++) {
	    double x1 = i == n_stops ? stops[0].offset + 1 : stops[i].offset;
	    cairo_color_stop_t *color1 = i == n_stops ? &stops[0].color : &stops[i].color;
	    if (x1 >= -start_offset) {
		if (i > 0) {
		    double x0 = stops[i - 1].offset;
		    cairo_color_stop_t *color0 = &stops[i - 1].color;
		    if (x0 != x1) {
			offset_color_start.red = color0->red + (color1->red - color0->red)
							       * (-start_offset - x0) / (x1 - x0);
			offset_color_start.green = color0->green + (color1->green - color0->green)
								   * (-start_offset - x0) / (x1 - x0);
			offset_color_start.blue = color0->blue + (color1->blue - color0->blue)
								 * (-start_offset - x0) / (x1 - x0);
			offset_color_start.alpha = color0->alpha + (color1->alpha - color0->alpha)
								   * (-start_offset - x0) / (x1 - x0);
			offset_color_stop = offset_color_start;
		    } else {
			offset_color_stop = stops[i - 1].color;
			offset_color_start = stops[i].color;
		    }
		} else {
		    offset_color_stop = offset_color_start = stops[i].color;
		}
		offset_index = i;
		found = TRUE;
		break;
	    }
	}
	if (!found) {
	    offset_index = n_stops - 1;
	    offset_color_stop = offset_color_start = stops[offset_index].color;
	}
	_cairo_svg_stream_printf (output,
				  "<stop offset=\"0\" "
				  "stop-color=\"rgb(%f%%, %f%%, %f%%)\" "
				  "stop-opacity=\"%f\"/>\n",
				  offset_color_start.red * 100.0,
				  offset_color_start.green * 100.0,
				  offset_color_start.blue * 100.0,
				  offset_color_start.alpha);
	for (unsigned int i = offset_index; i < n_stops; i++) {
	    _cairo_svg_stream_printf (output,
				      "<stop offset=\"%f\" "
				      "stop-color=\"rgb(%f%%, %f%%, %f%%)\" "
				      "stop-opacity=\"%f\"/>\n",
				      stops[i].offset + start_offset,
				      stops[i].color.red * 100.0,
				      stops[i].color.green * 100.0,
				      stops[i].color.blue * 100.0,
				      stops[i].color.alpha);
	}
	for (unsigned int i = 0; i < offset_index; i++) {
	    _cairo_svg_stream_printf (output,
				      "<stop offset=\"%f\" "
				      "stop-color=\"rgb(%f%%, %f%%, %f%%)\" "
				      "stop-opacity=\"%f\"/>\n",
				      1.0 + stops[i].offset + start_offset,
				      stops[i].color.red * 100.0,
				      stops[i].color.green * 100.0,
				      stops[i].color.blue * 100.0,
				      stops[i].color.alpha);
	}
	_cairo_svg_stream_printf (output,
				  "<stop offset=\"1\" "
				  "stop-color=\"rgb(%f%%, %f%%, %f%%)\" "
				  "stop-opacity=\"%f\"/>\n",
				  offset_color_stop.red * 100.0,
				  offset_color_stop.green * 100.0,
				  offset_color_stop.blue * 100.0,
				  offset_color_stop.alpha);
    }
    if (reverse_stops || emulate_reflect) {
	free (stops);
    }
    return CAIRO_STATUS_SUCCESS;
}
static void
_cairo_svg_surface_emit_pattern_extend (cairo_svg_stream_t *output,
					cairo_pattern_t *pattern)
{
    switch (pattern->extend) {
    case CAIRO_EXTEND_REPEAT:
	_cairo_svg_stream_printf (output, " spreadMethod=\"repeat\"");
	break;
    case CAIRO_EXTEND_REFLECT:
	_cairo_svg_stream_printf (output, " spreadMethod=\"reflect\"");
	break;
    case CAIRO_EXTEND_NONE:
    case CAIRO_EXTEND_PAD:
	break;
    }
}
static cairo_status_t
_cairo_svg_surface_emit_linear_pattern (cairo_svg_surface_t *surface,
					cairo_linear_pattern_t *pattern,
					cairo_svg_stream_t *output,
					cairo_bool_t is_stroke,
					const cairo_matrix_t *parent_matrix)
{
    cairo_status_t status;
    cairo_svg_document_t *document = surface->document;
    cairo_matrix_t p2u = pattern->base.base.matrix;
    status = cairo_matrix_invert (&p2u);
    /* cairo_pattern_set_matrix ensures the matrix is invertible */
    assert (status == CAIRO_STATUS_SUCCESS);
    unsigned int linear_pattern_id = document->linear_pattern_id++;
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<linearGradient id=\"linear-pattern-%d\" "
			      "gradientUnits=\"userSpaceOnUse\" "
			      "x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\"",
			      linear_pattern_id,
			      pattern->pd1.x, pattern->pd1.y,
			      pattern->pd2.x, pattern->pd2.y);
    _cairo_svg_surface_emit_pattern_extend (&document->xml_node_defs, &pattern->base.base);
    _cairo_svg_surface_emit_transform (&document->xml_node_defs, "gradientTransform", &p2u, parent_matrix);
    _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
    status = _cairo_svg_surface_emit_pattern_stops (&document->xml_node_defs,
						    &pattern->base,
						    0.0,
						    FALSE,
						    FALSE);
    if (unlikely (status))
	return status;
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "</linearGradient>\n");
    _cairo_svg_stream_printf (output,
			      is_stroke ? " stroke=\"url(#linear-pattern-%d)\""
					: " fill=\"url(#linear-pattern-%d)\"",
			      linear_pattern_id);
    return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
_cairo_svg_surface_emit_radial_pattern (cairo_svg_surface_t *surface,
					cairo_radial_pattern_t *pattern,
					cairo_svg_stream_t *output,
					cairo_bool_t is_stroke,
					const cairo_matrix_t *parent_matrix)
{
    cairo_status_t status;
    cairo_svg_document_t *document = surface->document;
    cairo_extend_t extend = pattern->base.base.extend;
    cairo_bool_t reverse_stops;
    cairo_circle_double_t *c0, *c1;
    if (pattern->cd1.radius < pattern->cd2.radius) {
	c0 = &pattern->cd1;
	c1 = &pattern->cd2;
	reverse_stops = FALSE;
    } else {
	c0 = &pattern->cd2;
	c1 = &pattern->cd1;
	reverse_stops = TRUE;
    }
    double x0 = c0->center.x;
    double y0 = c0->center.y;
    double r0 = c0->radius;
    double x1 = c1->center.x;
    double y1 = c1->center.y;
    double r1 = c1->radius;
    cairo_matrix_t p2u = pattern->base.base.matrix;
    status = cairo_matrix_invert (&p2u);
    /* cairo_pattern_set_matrix ensures the matrix is invertible */
    assert (status == CAIRO_STATUS_SUCCESS);
    unsigned int radial_pattern_id = document->radial_pattern_id++;
    double start_offset;
    cairo_bool_t emulate_reflect = FALSE;
    double fx = (r1 * x0 - r0 * x1) / (r1 - r0);
    double fy = (r1 * y0 - r0 * y1) / (r1 - r0);
    /* SVG doesn't support the inner circle and use instead a gradient focal.
     * That means we need to emulate the cairo behaviour by processing the
     * cairo gradient stops.
     * The CAIRO_EXTEND_NONE and CAIRO_EXTEND_PAD modes are quite easy to handle,
     * it's just a matter of stop position translation and calculation of
     * the corresponding SVG radial gradient focal.
     * The CAIRO_EXTEND_REFLECT and CAIRO_EXTEND_REPEAT modes require to compute a new
     * radial gradient, with an new outer circle, equal to r1 - r0 in the CAIRO_EXTEND_REPEAT
     * case, and 2 * r1 - r0 in the CAIRO_EXTEND_REFLECT case, and a new gradient stop
     * list that maps to the original cairo stop list.
     */
    if ((extend == CAIRO_EXTEND_REFLECT || extend == CAIRO_EXTEND_REPEAT) && r0 > 0.0) {
	double r_org = r1;
	if (extend == CAIRO_EXTEND_REFLECT) {
	    r1 = 2.0 * r1 - r0;
	    emulate_reflect = TRUE;
	}
	start_offset = fmod (r1, r1 - r0) / (r1 - r0) - 1.0;
	double r = r1 - r0;
	/* New position of outer circle. */
	double x = r * (x1 - fx) / r_org + fx;
	double y = r * (y1 - fy) / r_org + fy;
	x1 = x;
	y1 = y;
	r1 = r;
	r0 = 0.0;
    } else {
	start_offset = r0 / r1;
    }
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<radialGradient id=\"radial-pattern-%d\" "
			      "gradientUnits=\"userSpaceOnUse\" "
			      "cx=\"%f\" cy=\"%f\" "
			      "fx=\"%f\" fy=\"%f\" r=\"%f\"",
			      radial_pattern_id,
			      x1, y1,
			      fx, fy, r1);
    if (emulate_reflect) {
	_cairo_svg_stream_printf (&document->xml_node_defs, " spreadMethod=\"repeat\"");
    } else {
	_cairo_svg_surface_emit_pattern_extend (&document->xml_node_defs, &pattern->base.base);
    }
    _cairo_svg_surface_emit_transform (&document->xml_node_defs, "gradientTransform", &p2u, parent_matrix);
    _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
    /* To support cairo's EXTEND_NONE, (for which SVG has no similar
     * notion), we add transparent color stops on either end of the
     * user-provided stops. */
    if (extend == CAIRO_EXTEND_NONE) {
	_cairo_svg_stream_printf (&document->xml_node_defs,
				  "<stop offset=\"0\" "
				  "stop-color=\"rgb(0%%, 0%%, 0%%)\" "
				  "stop-opacity=\"0\"/>\n");
	if (r0 != 0.0) {
	    _cairo_svg_stream_printf (&document->xml_node_defs,
				      "<stop offset=\"%f\" "
				      "stop-color=\"rgb(0%%, 0%%, 0%%)\" "
				      "stop-opacity=\"0\"/>\n",
				      r0 / r1);
	}
    }
    status = _cairo_svg_surface_emit_pattern_stops (&document->xml_node_defs,
						    &pattern->base,
						    start_offset,
						    reverse_stops,
						    emulate_reflect);
    if (unlikely (status)) {
	return status;
    }
    if (pattern->base.base.extend == CAIRO_EXTEND_NONE) {
	_cairo_svg_stream_printf (&document->xml_node_defs,
				  "<stop offset=\"1\" "
				  "stop-color=\"rgb(0%%, 0%%, 0%%)\" "
				  "stop-opacity=\"0\"/>\n");
    }
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "</radialGradient>\n");
    _cairo_svg_stream_printf (output,
			      is_stroke ? " stroke=\"url(#radial-pattern-%d)\""
					: " fill=\"url(#radial-pattern-%d)\"",
			      radial_pattern_id);
    return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
122
_cairo_svg_surface_emit_pattern (cairo_svg_surface_t *surface,
				 const cairo_pattern_t *pattern,
				 cairo_svg_stream_t *output,
				 cairo_bool_t is_stroke,
				 const cairo_matrix_t *parent_matrix)
{
122
    switch (pattern->type) {
122
    case CAIRO_PATTERN_TYPE_SOLID:
122
	return _cairo_svg_surface_emit_solid_pattern (surface, (cairo_solid_pattern_t *) pattern,
						      output, is_stroke);
    case CAIRO_PATTERN_TYPE_SURFACE:
	return _cairo_svg_surface_emit_surface_pattern (surface, (cairo_surface_pattern_t *) pattern,
							output, is_stroke, parent_matrix);
    case CAIRO_PATTERN_TYPE_LINEAR:
	return _cairo_svg_surface_emit_linear_pattern (surface, (cairo_linear_pattern_t *) pattern,
						       output, is_stroke, parent_matrix);
    case CAIRO_PATTERN_TYPE_RADIAL:
	return _cairo_svg_surface_emit_radial_pattern (surface, (cairo_radial_pattern_t *) pattern,
						       output, is_stroke, parent_matrix);
    case CAIRO_PATTERN_TYPE_MESH:
    case CAIRO_PATTERN_TYPE_RASTER_SOURCE:
	ASSERT_NOT_REACHED;
    }
    return _cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH);
}
static cairo_status_t
34
_cairo_svg_surface_emit_fill_style (cairo_svg_stream_t *output,
				    cairo_svg_surface_t *surface,
				    const cairo_pattern_t *source,
				    cairo_fill_rule_t fill_rule,
				    const cairo_matrix_t *parent_matrix)
{
34
    _cairo_svg_stream_printf (output,
			      " fill-rule=\"%s\"",
			      fill_rule == CAIRO_FILL_RULE_EVEN_ODD ? "evenodd" : "nonzero");
34
    return _cairo_svg_surface_emit_pattern (surface, source, output, FALSE, parent_matrix);
}
static cairo_status_t
_cairo_svg_surface_emit_stroke_style (cairo_svg_stream_t *output,
				      cairo_svg_surface_t *surface,
				      const cairo_pattern_t *source,
				      const cairo_stroke_style_t *stroke_style,
				      const cairo_matrix_t *parent_matrix)
{
    cairo_status_t status;
    const char *line_cap, *line_join;
    unsigned int i;
    switch (stroke_style->line_cap) {
    case CAIRO_LINE_CAP_BUTT:
	line_cap = "butt";
	break;
    case CAIRO_LINE_CAP_ROUND:
	line_cap = "round";
	break;
    case CAIRO_LINE_CAP_SQUARE:
	line_cap = "square";
	break;
    default:
	ASSERT_NOT_REACHED;
    }
    switch (stroke_style->line_join) {
    case CAIRO_LINE_JOIN_MITER:
	line_join = "miter";
	break;
    case CAIRO_LINE_JOIN_ROUND:
	line_join = "round";
	break;
    case CAIRO_LINE_JOIN_BEVEL:
	line_join = "bevel";
	break;
    default:
	ASSERT_NOT_REACHED;
    }
    if (stroke_style->is_hairline) {
		_cairo_svg_stream_printf (output,
					" stroke-width=\"1px\""
					" stroke-linecap=\"%s\""
					" stroke-linejoin=\"%s\""
					" style=\"vector-effect: non-scaling-stroke\"",
					line_cap,
					line_join);
	} else {
		_cairo_svg_stream_printf (output,
					" stroke-width=\"%f\""
					" stroke-linecap=\"%s\""
					" stroke-linejoin=\"%s\"",
					stroke_style->line_width,
					line_cap,
					line_join);
	}
    status = _cairo_svg_surface_emit_pattern (surface, source, output, TRUE, parent_matrix);
    if (unlikely (status)) {
	return status;
    }
    if (stroke_style->num_dashes > 0) {
	_cairo_svg_stream_printf (output, " stroke-dasharray=\"");
	for (i = 0; i < stroke_style->num_dashes; i++) {
	    _cairo_svg_stream_printf (output,
				      "%f",
				      stroke_style->dash[i]);
	    _cairo_svg_stream_printf (output, i + 1 < stroke_style->num_dashes ? " " : "\"");
	}
	if (stroke_style->dash_offset != 0.0) {
	    _cairo_svg_stream_printf (output,
				      " stroke-dashoffset=\"%f\"",
				      stroke_style->dash_offset);
	}
    }
    _cairo_svg_stream_printf (output,
			      " stroke-miterlimit=\"%f\"",
			      stroke_style->miter_limit);
    return CAIRO_STATUS_SUCCESS;
}
static cairo_bool_t
153
_cairo_svg_surface_get_extents (void		        *abstract_surface,
				cairo_rectangle_int_t   *rectangle)
{
153
    cairo_svg_surface_t *surface = abstract_surface;
153
    rectangle->x = 0;
153
    rectangle->y = 0;
    /* XXX: The conversion to integers here is pretty bogus, (not to
     * mention the arbitrary limitation of width to a short(!). We
     * may need to come up with a better interface for get_size.
     */
153
    rectangle->width  = ceil (surface->width);
153
    rectangle->height = ceil (surface->height);
153
    return surface->surface_bounded;
}
static cairo_status_t
99
_cairo_svg_surface_emit_paint (cairo_svg_stream_t *output,
			       cairo_svg_surface_t *surface,
			       const cairo_pattern_t *source,
			       cairo_bool_t at_origin)
{
    cairo_status_t status;
99
    if (_cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (source)) {
11
	return _cairo_svg_surface_emit_composite_pattern (output,
							  surface,
							  (cairo_surface_pattern_t *) source,
							  invalid_pattern_id,
							  NULL);
    }
88
    surface->transitive_paint_used = TRUE;
88
    _cairo_svg_stream_printf (output, "<rect");
88
    if (at_origin) {
30
	_cairo_svg_stream_append_paint_dependent (output,
						  surface->source_id,
						  CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN);
    } else {
58
	_cairo_svg_stream_append_paint_dependent (output,
						  surface->source_id,
						  CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE);
    }
88
    status = _cairo_svg_surface_emit_pattern (surface, source, output, FALSE, NULL);
88
    if (unlikely (status)) {
	return status;
    }
88
    _cairo_svg_stream_printf (output, "/>\n");
88
    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
28
_cairo_svg_surface_do_operator (cairo_svg_stream_t *output,
				cairo_svg_surface_t *surface,
				cairo_operator_t op,
				const cairo_clip_t *clip,
				cairo_svg_stream_t *mask_stream,
				cairo_svg_stream_t *source_stream,
				cairo_svg_stream_t *destination_stream)
{
    cairo_status_t status;
28
    cairo_svg_document_t *document = surface->document;
    // For operators that do not always produce opaque output, we first need to emit a black paint
    // if the content does not have alpha
28
    if (surface->base.content == CAIRO_CONTENT_COLOR && (op == CAIRO_OPERATOR_CLEAR ||
							 op == CAIRO_OPERATOR_SOURCE ||
							 op == CAIRO_OPERATOR_IN ||
							 op == CAIRO_OPERATOR_OUT ||
							 op == CAIRO_OPERATOR_DEST_IN ||
							 op == CAIRO_OPERATOR_DEST_OUT ||
							 op == CAIRO_OPERATOR_DEST_ATOP ||
							 op == CAIRO_OPERATOR_XOR)) {
	_cairo_svg_surface_emit_paint (output, surface, &_cairo_pattern_black.base, FALSE);
    }
28
    if (op == CAIRO_OPERATOR_CLEAR) {
	/*
	 * The result is the same as one of the SOURCE operation application with the same arguments,
	 * but with an empty source.
	 */
	status = _cairo_svg_stream_destroy (source_stream);
	if (unlikely (status)) {
	    (void) _cairo_svg_stream_destroy (destination_stream);
	    (void) _cairo_svg_stream_destroy (mask_stream);
	    return status;
	}
	cairo_svg_stream_t empty_stream = _cairo_svg_stream_create ();
	return _cairo_svg_surface_do_operator (output,
					       surface,
					       CAIRO_OPERATOR_SOURCE,
					       clip,
					       mask_stream,
					       &empty_stream,
					       destination_stream);
    }
28
    if (op == CAIRO_OPERATOR_SOURCE) {
	/*
	 * Below we use the "Bounded" equation with SOURCE as the operation from the "Clipping and masking" section
	 * of https://cairographics.org/operators/:
	 * result = source LEPR_(clip IN mask) destination
	 *
	 * It is equivalent to:
	 * result = (source IN (clip IN mask)) ADD (destination IN (NOT (clip IN mask)))
	 *
	 * 1. We put the clip masked with the mask into the SVG group `lerp_compositing_group`.
	 * 2. `positive_lerp_mask` is an SVG mask with `lerp_compositing_group`.
	 * 3. `negative_lerp_mask` is an SVG mask with inverted `lerp_compositing_group`.
	 * 5. We put the source masked with `positive_lerp_mask` into the SVG group `lerped_source_compositing_group`.
	 * 6. We put the destination masked with `negative_lerp_mask` into
	 *    the SVG group `lerped_destination_compositing_group`.
	 * 7. The result is addition of `lerped_source_compositing_group` and `lerped_destination_compositing_group`.
	 */
26
	unsigned int lerp_compositing_group_id = document->compositing_group_id++;
26
	_cairo_svg_stream_printf (&document->xml_node_defs,
				  "<g id=\"compositing-group-%d\"",
				  lerp_compositing_group_id);
26
	_cairo_svg_stream_append_paint_dependent (&document->xml_node_defs,
						  surface->source_id,
						  CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION);
26
	_cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
26
	_cairo_svg_surface_emit_paint (&document->xml_node_defs, surface, &_cairo_pattern_clear.base, FALSE);
26
	status = _cairo_svg_surface_set_clip (surface, &document->xml_node_defs, clip);
26
	if (unlikely (status)) {
	    (void) _cairo_svg_stream_destroy (destination_stream);
	    (void) _cairo_svg_stream_destroy (source_stream);
	    (void) _cairo_svg_stream_destroy (mask_stream);
	    return status;
	}
26
	_cairo_svg_stream_copy (mask_stream, &document->xml_node_defs);
26
	status = _cairo_svg_stream_destroy (mask_stream);
26
	if (unlikely (status)) {
	    (void) _cairo_svg_stream_destroy (destination_stream);
	    (void) _cairo_svg_stream_destroy (source_stream);
	    return status;
	}
26
	_cairo_svg_surface_reset_clip (surface);
26
	_cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
26
	unsigned int positive_lerp_mask_id = document->mask_id++;
26
	_cairo_svg_stream_printf (&document->xml_node_defs,
				  "<mask id=\"mask-%d\">\n",
				  positive_lerp_mask_id);
26
	_cairo_svg_stream_printf (&document->xml_node_defs,
				  "<use xlink:href=\"#compositing-group-%d\"/>\n",
				  lerp_compositing_group_id);
26
	_cairo_svg_stream_printf (&document->xml_node_defs, "</mask>\n");
26
	unsigned int negative_lerp_mask_id = document->mask_id++;
26
	_cairo_svg_stream_printf (&document->xml_node_defs,
				  "<mask id=\"mask-%d\">\n",
				  negative_lerp_mask_id);
26
	_cairo_svg_stream_printf (&document->xml_node_defs,
				  "<use xlink:href=\"#compositing-group-%d\" filter=\"url(#filter-%s)\"/>\n",
				  lerp_compositing_group_id,
				  _cairo_svg_surface_emit_static_filter (document,
									 CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA));
26
	_cairo_svg_stream_printf (&document->xml_node_defs, "</mask>\n");
26
	unsigned int lerped_source_compositing_group_id = document->compositing_group_id++;
26
	_cairo_svg_stream_printf (&document->xml_node_defs,
				  "<g id=\"compositing-group-%d\" mask=\"url(#mask-%d)\">\n",
				  lerped_source_compositing_group_id,
				  positive_lerp_mask_id);
26
	_cairo_svg_stream_printf (&document->xml_node_defs, "<g");
26
	_cairo_svg_stream_append_paint_dependent (&document->xml_node_defs,
						  surface->source_id,
						  CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION);
26
	_cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
26
	_cairo_svg_stream_copy (source_stream, &document->xml_node_defs);
26
	status = _cairo_svg_stream_destroy (source_stream);
26
	if (unlikely (status)) {
	    (void) _cairo_svg_stream_destroy (destination_stream);
	    return status;
	}
26
	_cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
26
	_cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
26
	unsigned int lerped_destination_compositing_group_id = document->compositing_group_id++;
26
	_cairo_svg_stream_printf (&document->xml_node_defs,
				  "<g id=\"compositing-group-%d\" mask=\"url(#mask-%d)\">\n",
				  lerped_destination_compositing_group_id,
				  negative_lerp_mask_id);
26
	_cairo_svg_stream_printf (&document->xml_node_defs, "<g");
26
	_cairo_svg_stream_append_paint_dependent (&document->xml_node_defs,
						  surface->source_id,
						  CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION);
26
	_cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
26
	_cairo_svg_stream_copy (destination_stream, &document->xml_node_defs);
26
	status = _cairo_svg_stream_destroy (destination_stream);
26
	if (unlikely (status)) {
	    return status;
	}
26
	_cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
26
	_cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
26
	_cairo_svg_stream_printf (&surface->xml_node,
				  "<g filter=\"url(#filter-%d)\"",
				  _cairo_svg_surface_emit_parametric_filter (surface,
									     CAIRO_SVG_FILTER_ADD,
									     lerped_source_compositing_group_id,
									     lerped_destination_compositing_group_id));
26
	_cairo_svg_stream_append_paint_dependent (&surface->xml_node,
						  surface->source_id,
						  CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_TRANSLATION);
26
	_cairo_svg_stream_printf (&surface->xml_node, ">\n");
26
	status = _cairo_svg_surface_emit_paint (&surface->xml_node, surface, &_cairo_pattern_black.base, TRUE);
26
	if (unlikely (status)) {
	    return status;
	}
26
	_cairo_svg_stream_printf (&surface->xml_node, "</g>\n");
26
	return CAIRO_STATUS_SUCCESS;
    }
2
    if (op == CAIRO_OPERATOR_DEST) {
	/*
	 * The result is the destination.
	 */
	_cairo_svg_stream_copy (destination_stream, &surface->xml_node);
	status = _cairo_svg_stream_destroy (destination_stream);
	if (unlikely (status)) {
	    (void) _cairo_svg_stream_destroy (source_stream);
	    (void) _cairo_svg_stream_destroy (mask_stream);
	    return status;
	}
	status = _cairo_svg_stream_destroy (source_stream);
	if (unlikely (status)) {
	    (void) _cairo_svg_stream_destroy (source_stream);
	    return status;
	}
	status = _cairo_svg_stream_destroy (source_stream);
	if (unlikely (status)) {
	    return status;
	}
	return CAIRO_STATUS_SUCCESS;
    }
    /*
     * Below we use the "XRender" equation from the "Clipping and masking" section
     * of https://cairographics.org/operators/:
     * result = ((source IN mask) OP destination) LERP_clip destination
     *
     * It is equivalent to:
     * result = (((source IN mask) OP destination) IN clip) ADD (destination IN (NOT clip))
     *
     * 1. We put the clip into the SVG group `lerp_compositing_group`.
     * 2. `positive_lerp_mask` is an SVG mask with `lerp_compositing_group`.
     * 3. `negative_lerp_mask` is an SVG mask with inverted `lerp_compositing_group`.
     * 4. We put the mask into the SVG mask `mask_mask`.
     * 5. We put the source masked with `mask_mask` into the SVG group `masked_source_compositing_group`.
     * 6. We put the destination into the SVG group `destination_compositing_group`.
     * 7. `lerped_operation_compositing_group` is an SVG group of operation applied to
     *    (`masked_source_compositing_group`, `destination_compositing_group`)
     *    masked with `positive_lerp_mask`.
     * 8. `lerped_destination_compositing_group` is an SVG group of `destination_compositing_group`
     *    masked with `negative_lerp_mask`.
     * 9. The result is addition of `lerped_operation_compositing_group` and `lerped_destination_compositing_group`.
     */
2
    unsigned int lerp_compositing_group_id = document->compositing_group_id++;
2
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<g id=\"compositing-group-%d\"",
			      lerp_compositing_group_id);
2
    _cairo_svg_stream_append_paint_dependent (&document->xml_node_defs,
					      surface->source_id,
					      CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION);
2
    _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
2
    _cairo_svg_surface_emit_paint (&document->xml_node_defs, surface, &_cairo_pattern_clear.base, FALSE);
2
    status = _cairo_svg_surface_set_clip (surface, &document->xml_node_defs, clip);
2
    if (unlikely (status)) {
	(void) _cairo_svg_stream_destroy (destination_stream);
	(void) _cairo_svg_stream_destroy (source_stream);
	(void) _cairo_svg_stream_destroy (mask_stream);
	return status;
    }
2
    status = _cairo_svg_surface_emit_paint (&document->xml_node_defs, surface, &_cairo_pattern_white.base, FALSE);
2
    if (unlikely (status)) {
	(void) _cairo_svg_stream_destroy (destination_stream);
	(void) _cairo_svg_stream_destroy (source_stream);
	(void) _cairo_svg_stream_destroy (mask_stream);
	return status;
    }
2
    _cairo_svg_surface_reset_clip (surface);
2
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
2
    unsigned int positive_lerp_mask_id = document->mask_id++;
2
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<mask id=\"mask-%d\">\n",
			      positive_lerp_mask_id);
2
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<use xlink:href=\"#compositing-group-%d\"/>\n",
			      lerp_compositing_group_id);
2
    _cairo_svg_stream_printf (&document->xml_node_defs, "</mask>\n");
2
    unsigned int negative_lerp_mask_id = document->mask_id++;
2
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<mask id=\"mask-%d\">\n",
			      negative_lerp_mask_id);
2
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<use xlink:href=\"#compositing-group-%d\" filter=\"url(#filter-%s)\"/>\n",
			      lerp_compositing_group_id,
			      _cairo_svg_surface_emit_static_filter (document,
								     CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA));
2
    _cairo_svg_stream_printf (&document->xml_node_defs, "</mask>\n");
2
    unsigned int mask_mask_id = document->mask_id++;
2
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<mask id=\"mask-%d\">\n",
			      mask_mask_id);
2
    _cairo_svg_stream_printf (&document->xml_node_defs, "<g");
2
    _cairo_svg_stream_append_paint_dependent (&document->xml_node_defs,
					      surface->source_id,
					      CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION);
2
    _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
2
    _cairo_svg_stream_copy (mask_stream, &document->xml_node_defs);
2
    status = _cairo_svg_stream_destroy (mask_stream);
2
    if (unlikely (status)) {
	(void) _cairo_svg_stream_destroy (source_stream);
	(void) _cairo_svg_stream_destroy (destination_stream);
	return status;
    }
2
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
2
    _cairo_svg_stream_printf (&document->xml_node_defs, "</mask>\n");
2
    unsigned int masked_source_compositing_group_id = document->compositing_group_id++;
2
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<g id=\"compositing-group-%d\" mask=\"url(#mask-%d)\">\n",
			      masked_source_compositing_group_id,
			      mask_mask_id);
2
    _cairo_svg_stream_printf (&document->xml_node_defs, "<g");
2
    _cairo_svg_stream_append_paint_dependent (&document->xml_node_defs,
					      surface->source_id,
					      CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION);
2
    _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
2
    _cairo_svg_stream_copy (source_stream, &document->xml_node_defs);
2
    status = _cairo_svg_stream_destroy (source_stream);
2
    if (unlikely (status)) {
	(void) _cairo_svg_stream_destroy (destination_stream);
	return status;
    }
2
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
2
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
2
    unsigned int destination_compositing_group_id = document->compositing_group_id++;
2
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<g id=\"compositing-group-%d\"",
			      destination_compositing_group_id);
2
    _cairo_svg_stream_append_paint_dependent (&document->xml_node_defs,
					      surface->source_id,
					      CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION);
2
    _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
2
    _cairo_svg_stream_copy (destination_stream, &document->xml_node_defs);
2
    status = _cairo_svg_stream_destroy (destination_stream);
2
    if (unlikely (status)) {
	return status;
    }
2
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
2
    unsigned int lerped_operation_compositing_group_id = document->compositing_group_id++;
2
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<g id=\"compositing-group-%d\"",
			      lerped_operation_compositing_group_id);
    unsigned int filter_id;
2
    switch (op) {
    case CAIRO_OPERATOR_CLEAR:
    case CAIRO_OPERATOR_SOURCE:
    case CAIRO_OPERATOR_OVER:
	ASSERT_NOT_REACHED;
    case CAIRO_OPERATOR_IN:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_IN,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_OUT:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_OUT,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_ATOP:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_ATOP,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_DEST:
	ASSERT_NOT_REACHED;
    case CAIRO_OPERATOR_DEST_OVER:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_OVER,
							       destination_compositing_group_id,
							       masked_source_compositing_group_id);
	break;
    case CAIRO_OPERATOR_DEST_IN:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_IN,
							       destination_compositing_group_id,
							       masked_source_compositing_group_id);
	break;
    case CAIRO_OPERATOR_DEST_OUT:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_OUT,
							       destination_compositing_group_id,
							       masked_source_compositing_group_id);
	break;
    case CAIRO_OPERATOR_DEST_ATOP:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_ATOP,
							       destination_compositing_group_id,
							       masked_source_compositing_group_id);
	break;
2
    case CAIRO_OPERATOR_XOR:
2
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_XOR,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
2
	break;
    case CAIRO_OPERATOR_ADD:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_ADD,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_SATURATE:
	ASSERT_NOT_REACHED;
    case CAIRO_OPERATOR_MULTIPLY:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_MULTIPLY,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_SCREEN:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_SCREEN,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_OVERLAY:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_OVERLAY,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_DARKEN:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_DARKEN,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_LIGHTEN:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_LIGHTEN,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_COLOR_DODGE:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_COLOR_DODGE,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_COLOR_BURN:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_COLOR_BURN,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_HARD_LIGHT:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_HARD_LIGHT,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_SOFT_LIGHT:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_SOFT_LIGHT,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_DIFFERENCE:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_DIFFERENCE,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_EXCLUSION:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_EXCLUSION,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_HSL_HUE:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_HUE,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_HSL_SATURATION:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_SATURATION,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_HSL_COLOR:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_COLOR,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    case CAIRO_OPERATOR_HSL_LUMINOSITY:
	filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
							       CAIRO_SVG_FILTER_LUMINOSITY,
							       masked_source_compositing_group_id,
							       destination_compositing_group_id);
	break;
    default:
	ASSERT_NOT_REACHED;
    }
2
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      " filter=\"url(#filter-%d)\" mask=\"url(#mask-%d)\">\n",
			      filter_id,
			      positive_lerp_mask_id);
2
    status = _cairo_svg_surface_emit_paint (&document->xml_node_defs, surface, &_cairo_pattern_black.base, TRUE);
2
    if (unlikely (status)) {
	return status;
    }
2
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
2
    unsigned int lerped_destination_compositing_group_id = document->compositing_group_id++;
2
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<g id=\"compositing-group-%d\" mask=\"url(#mask-%d)\">\n",
			      lerped_destination_compositing_group_id,
			      negative_lerp_mask_id);
2
    _cairo_svg_stream_printf (&document->xml_node_defs,
			      "<use xlink:href=\"#compositing-group-%d\"/>\n",
			      destination_compositing_group_id);
2
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
2
    _cairo_svg_stream_printf (&surface->xml_node,
			      "<g filter=\"url(#filter-%d)\"",
			      _cairo_svg_surface_emit_parametric_filter (surface,
									 CAIRO_SVG_FILTER_ADD,
									 lerped_operation_compositing_group_id,
									 lerped_destination_compositing_group_id));
2
    _cairo_svg_stream_append_paint_dependent (&surface->xml_node,
					      surface->source_id,
					      CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_TRANSLATION);
2
    _cairo_svg_stream_printf (&surface->xml_node, ">\n");
2
    status = _cairo_svg_surface_emit_paint (&surface->xml_node, surface, &_cairo_pattern_black.base, TRUE);
2
    if (unlikely (status)) {
	return status;
    }
2
    _cairo_svg_stream_printf (&surface->xml_node, "</g>\n");
2
    return CAIRO_STATUS_SUCCESS;
}
#define _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL(OPERATOR_IMPL, SOURCE, ...) \
    if (op == CAIRO_OPERATOR_OVER) { \
        status = _cairo_svg_surface_set_clip (surface, &surface->xml_node, clip); \
        if (unlikely (status)) { \
            return status; \
        } \
        return OPERATOR_IMPL (&surface->xml_node, surface, SOURCE, ## __VA_ARGS__); \
    } else { \
        _cairo_svg_surface_reset_clip (surface); \
        cairo_svg_stream_t mask_stream = _cairo_svg_stream_create (); \
        status = OPERATOR_IMPL (&mask_stream, surface, &_cairo_pattern_white.base, ## __VA_ARGS__); \
        if (unlikely (status)) { \
            (void) _cairo_svg_stream_destroy (&mask_stream); \
            return status; \
        } \
        cairo_svg_stream_t source_stream = _cairo_svg_stream_create (); \
        status = _cairo_svg_surface_emit_paint (&source_stream, \
                                                surface, \
                                                SOURCE,                   \
                                                FALSE); \
        if (unlikely (status)) { \
            (void) _cairo_svg_stream_destroy (&source_stream); \
            (void) _cairo_svg_stream_destroy (&mask_stream); \
            return status; \
        } \
        cairo_svg_stream_t destination_stream = surface->xml_node; \
        surface->xml_node = _cairo_svg_stream_create (); \
        return _cairo_svg_surface_do_operator (&surface->xml_node, \
                                               surface, \
                                               op, \
                                               clip, \
                                               &mask_stream, \
                                               &source_stream, \
                                               &destination_stream); \
    }
static cairo_int_status_t
11
_cairo_svg_surface_paint_impl (cairo_svg_stream_t *output,
			       cairo_svg_surface_t *surface,
			       const cairo_pattern_t *source)
{
11
    return _cairo_svg_surface_emit_paint (output, surface, source, FALSE);
}
static cairo_int_status_t
20
_cairo_svg_surface_paint (void *abstract_surface,
			  cairo_operator_t op,
			  const cairo_pattern_t *source,
			  const cairo_clip_t *clip)
{
    cairo_status_t status;
20
    cairo_svg_surface_t *surface = abstract_surface;
    /* Emulation of clear and source operators, when no clipping region
     * is defined. We just delete existing content of surface root node,
     * and exit early if operator is clear.
     */
20
    if ((op == CAIRO_OPERATOR_CLEAR || op == CAIRO_OPERATOR_SOURCE) && clip == NULL) {
	switch (surface->paginated_mode) {
	case CAIRO_PAGINATED_MODE_ANALYZE:
	    return CAIRO_STATUS_SUCCESS;
	case CAIRO_PAGINATED_MODE_RENDER:
	    status = _cairo_svg_stream_destroy (&surface->xml_node);
	    if (unlikely (status)) {
		return status;
	    }
	    surface->xml_node = _cairo_svg_stream_create ();
	    if (op == CAIRO_OPERATOR_CLEAR) {
		return CAIRO_STATUS_SUCCESS;
	    }
	    break;
	case CAIRO_PAGINATED_MODE_FALLBACK:
	    ASSERT_NOT_REACHED;
	}
20
    } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
9
	return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source)
	       ? CAIRO_STATUS_SUCCESS
9
	       : CAIRO_INT_STATUS_UNSUPPORTED;
    }
11
    _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_paint_impl,
					   source)
}
static cairo_int_status_t
_cairo_svg_surface_mask_impl (cairo_svg_stream_t *output,
			      cairo_svg_surface_t *surface,
			      const cairo_pattern_t *source,
			      const cairo_pattern_t *mask)
{
    cairo_status_t status;
    cairo_svg_document_t *document = surface->document;
    /* _cairo_svg_surface_emit_paint() will output a pattern definition to
     * document->xml_node_defs so we need to write the mask element to
     * a temporary stream and then copy that to xml_node_defs. */
    cairo_svg_stream_t temporary_stream = _cairo_svg_stream_create ();
    unsigned int mask_id = document->mask_id++;
    _cairo_svg_stream_printf (&temporary_stream,
				 "<mask id=\"mask-%d\">\n",
				 mask_id);
    _cairo_svg_stream_printf (&temporary_stream,
				 "<g filter=\"url(#filter-%s)\">\n",
				 _cairo_svg_surface_emit_static_filter (document, CAIRO_SVG_FILTER_REMOVE_COLOR));
    status = _cairo_svg_surface_emit_paint (&temporary_stream, surface, mask, FALSE);
    if (unlikely (status)) {
	(void) _cairo_svg_stream_destroy (&temporary_stream);
	return status;
    }
    _cairo_svg_stream_printf (&temporary_stream, "</g>\n");
    _cairo_svg_stream_printf (&temporary_stream, "</mask>\n");
    _cairo_svg_stream_copy (&temporary_stream, &document->xml_node_defs);
    status = _cairo_svg_stream_destroy (&temporary_stream);
    if (unlikely (status)) {
	return status;
    }
    _cairo_svg_stream_printf (output,
				 "<g mask=\"url(#mask-%d)\">\n",
				 mask_id);
    status = _cairo_svg_surface_emit_paint (output, surface, source, FALSE);
    if (unlikely (status)) {
	return status;
    }
    _cairo_svg_stream_printf (output, "</g>\n");
    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
_cairo_svg_surface_mask (void *abstract_surface,
			 cairo_operator_t op,
			 const cairo_pattern_t *source,
			 const cairo_pattern_t *mask,
			 const cairo_clip_t *clip)
{
    cairo_status_t status;
    cairo_svg_surface_t *surface = abstract_surface;
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
	return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source) &&
	       _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, mask)
	       ? CAIRO_STATUS_SUCCESS
	       : CAIRO_INT_STATUS_UNSUPPORTED;
    }
    _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_mask_impl,
					   source,
					   mask)
}
static cairo_int_status_t
_cairo_svg_surface_stroke_impl (cairo_svg_stream_t *output,
				cairo_svg_surface_t *surface,
				const cairo_pattern_t *source,
				const cairo_path_fixed_t *path,
				const cairo_stroke_style_t *stroke_style,
				const cairo_matrix_t *ctm,
				const cairo_matrix_t *ctm_inverse,
				double tolerance,
				cairo_antialias_t antialias)
{
    cairo_status_t status;
    cairo_bool_t svg_clip_or_svg_mask_should_be_used = _cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (source);
    unsigned int mask_id;
    cairo_svg_stream_t *output_stream = output;
    if (svg_clip_or_svg_mask_should_be_used) {
	mask_id = surface->document->mask_id++;
	output_stream = &surface->document->xml_node_defs;
	_cairo_svg_stream_printf (output_stream,
				  "<mask id=\"mask-%d\">\n",
				  mask_id);
    }
    _cairo_svg_stream_printf (output_stream, "<path fill=\"none\"");
    status = _cairo_svg_surface_emit_stroke_style (output_stream,
						   surface,
						   svg_clip_or_svg_mask_should_be_used ? &_cairo_pattern_white.base
										       : source,
						   stroke_style,
						   ctm_inverse);
    if (unlikely (status)) {
	return status;
    }
    _cairo_svg_surface_emit_path (output_stream, path, ctm_inverse);
    _cairo_svg_surface_emit_transform (output_stream, "transform", ctm, NULL);
    _cairo_svg_stream_printf (output_stream, "/>\n");
    if (svg_clip_or_svg_mask_should_be_used) {
	_cairo_svg_stream_printf (output_stream, "</mask>\n");
	_cairo_svg_stream_printf (output,
				  "<g mask=\"url(#mask-%d)\">\n",
				  mask_id);
	status = _cairo_svg_surface_emit_composite_pattern (output,
							    surface,
							    (cairo_surface_pattern_t *) source,
							    invalid_pattern_id,
							    NULL);
	if (unlikely (status)) {
	    return status;
	}
	_cairo_svg_stream_printf (output, "</g>\n");
    }
    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
_cairo_svg_surface_stroke (void *abstract_dst,
			   cairo_operator_t op,
			   const cairo_pattern_t *source,
			   const cairo_path_fixed_t *path,
			   const cairo_stroke_style_t *stroke_style,
			   const cairo_matrix_t *ctm,
			   const cairo_matrix_t *ctm_inverse,
			   double tolerance,
			   cairo_antialias_t antialias,
			   const cairo_clip_t *clip)
{
    cairo_svg_surface_t *surface = abstract_dst;
    cairo_status_t status;
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
	return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source)
	       ? CAIRO_STATUS_SUCCESS
	       : CAIRO_INT_STATUS_UNSUPPORTED;
    }
    _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_stroke_impl,
					   source,
					   path,
					   stroke_style,
					   ctm,
					   ctm_inverse,
					   tolerance,
					   antialias)
}
static cairo_int_status_t
34
_cairo_svg_surface_fill_impl (cairo_svg_stream_t *output,
			      cairo_svg_surface_t *surface,
			      const cairo_pattern_t *source,
			      const cairo_path_fixed_t *path,
			      cairo_fill_rule_t fill_rule,
			      double tolerance,
			      cairo_antialias_t antialias)
{
    cairo_status_t status;
34
    if (_cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (source)) {
	_cairo_svg_stream_printf (&surface->document->xml_node_defs,
				  "<clipPath id=\"clip-%d\">\n",
				  surface->document->clip_id);
	_cairo_svg_stream_printf (&surface->document->xml_node_defs,
				  "<path clip-rule=\"%s\"",
				  fill_rule == CAIRO_FILL_RULE_EVEN_ODD ? "evenodd" : "nonzero");
	_cairo_svg_surface_emit_path (&surface->document->xml_node_defs, path, NULL);
	_cairo_svg_stream_printf (&surface->document->xml_node_defs, "/>\n");
	_cairo_svg_stream_printf (&surface->document->xml_node_defs, "</clipPath>\n");
	_cairo_svg_stream_printf (output,
				  "<g clip-path=\"url(#clip-%d)\">\n",
				  surface->document->clip_id++);
	status = _cairo_svg_surface_emit_composite_pattern (output,
							    surface,
							    (cairo_surface_pattern_t *) source,
							    invalid_pattern_id,
							    NULL);
	if (unlikely (status)) {
	    return status;
	}
	_cairo_svg_stream_printf (output, "</g>");
    } else {
34
	_cairo_svg_stream_printf (output, "<path");
34
	status = _cairo_svg_surface_emit_fill_style (output, surface, source, fill_rule, NULL);
34
	if (unlikely (status)) {
	    return status;
	}
34
	_cairo_svg_surface_emit_path (output, path, NULL);
34
	_cairo_svg_stream_printf (output, "/>\n");
    }
34
    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
70
_cairo_svg_surface_fill (void *abstract_surface,
			 cairo_operator_t op,
			 const cairo_pattern_t *source,
			 const cairo_path_fixed_t *path,
			 cairo_fill_rule_t fill_rule,
			 double tolerance,
			 cairo_antialias_t antialias,
			 const cairo_clip_t *clip)
{
70
    cairo_svg_surface_t *surface = abstract_surface;
    cairo_status_t status;
70
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
36
	return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source)
	       ? CAIRO_STATUS_SUCCESS
36
	       : CAIRO_INT_STATUS_UNSUPPORTED;
    }
34
    _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_fill_impl,
					   source,
					   path,
					   fill_rule,
					   tolerance,
					   antialias)
}
static cairo_int_status_t
_cairo_svg_surface_fill_stroke (void *abstract_surface,
				cairo_operator_t fill_op,
				const cairo_pattern_t *fill_source,
				cairo_fill_rule_t fill_rule,
				double fill_tolerance,
				cairo_antialias_t fill_antialias,
				const cairo_path_fixed_t *path,
				cairo_operator_t stroke_op,
				const cairo_pattern_t *stroke_source,
				const cairo_stroke_style_t *stroke_style,
				const cairo_matrix_t *stroke_ctm,
				const cairo_matrix_t *stroke_ctm_inverse,
				double stroke_tolerance,
				cairo_antialias_t stroke_antialias,
				const cairo_clip_t *clip)
{
    cairo_svg_surface_t *surface = abstract_surface;
    cairo_status_t status;
    if (_cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (fill_source) ||
	_cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (stroke_source) ||
	fill_op != CAIRO_OPERATOR_OVER ||
	stroke_op != CAIRO_OPERATOR_OVER) {
	return CAIRO_INT_STATUS_UNSUPPORTED;
    }
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
	return _cairo_svg_surface_are_operation_and_pattern_supported (surface, fill_op, fill_source)
	       && _cairo_svg_surface_are_operation_and_pattern_supported (surface, stroke_op, stroke_source)
	       ? CAIRO_STATUS_SUCCESS
	       : CAIRO_INT_STATUS_UNSUPPORTED;
    }
    status = _cairo_svg_surface_set_clip (surface, &surface->xml_node, clip);
    if (unlikely (status)) {
	return status;
    }
    _cairo_svg_stream_printf (&surface->xml_node, "<path");
    status = _cairo_svg_surface_emit_fill_style (&surface->xml_node, surface,
						 fill_source, fill_rule, stroke_ctm_inverse);
    if (unlikely (status)) {
	return status;
    }
    status = _cairo_svg_surface_emit_stroke_style (&surface->xml_node, surface,
						   stroke_source, stroke_style, stroke_ctm_inverse);
    if (unlikely (status)) {
	return status;
    }
    _cairo_svg_surface_emit_path (&surface->xml_node, path, stroke_ctm_inverse);
    _cairo_svg_surface_emit_transform (&surface->xml_node, "transform", stroke_ctm, NULL);
    _cairo_svg_stream_printf (&surface->xml_node, "/>\n");
    return CAIRO_STATUS_SUCCESS;
}
static cairo_int_status_t
_cairo_svg_surface_show_glyphs_impl (cairo_svg_stream_t *output,
				     cairo_svg_surface_t *surface,
				     const cairo_pattern_t *source,
				     cairo_glyph_t *glyphs,
				     int num_glyphs,
				     cairo_scaled_font_t *scaled_font)
{
    cairo_status_t status;
    cairo_svg_document_t *document = surface->document;
    if (num_glyphs <= 0) {
	return CAIRO_STATUS_SUCCESS;
    }
    /* FIXME it's probably possible to apply a source of a gradient to
     * a group of symbols, but I don't know how yet. Gradients or patterns
     * are translated by x and y properties of use element. */
    if (source->type != CAIRO_PATTERN_TYPE_SOLID) {
	goto fallback;
    }
    _cairo_svg_stream_printf (output, "<g");
    status = _cairo_svg_surface_emit_pattern (surface, source, output, FALSE, NULL);
    if (unlikely (status)) {
	return status;
    }
    _cairo_svg_stream_printf (output, ">\n");
    for (int i = 0; i < num_glyphs; i++) {
	cairo_scaled_font_subsets_glyph_t subset_glyph;
	status = _cairo_scaled_font_subsets_map_glyph (document->font_subsets,
						       scaled_font,
						       glyphs[i].index,
						       NULL,
						       0,
						       &subset_glyph);
	if ((cairo_int_status_t) status == CAIRO_INT_STATUS_UNSUPPORTED) {
	    _cairo_svg_stream_printf (output, "</g>\n");
	    glyphs += i;
	    num_glyphs -= i;
	    goto fallback;
	}
	if (unlikely (status)) {
	    return status;
	}
	_cairo_svg_stream_printf (output,
				  "<use xlink:href=\"#glyph-%d-%d\" x=\"%f\" y=\"%f\"/>\n",
				  subset_glyph.font_id,
				  subset_glyph.subset_glyph_index,
				  glyphs[i].x, glyphs[i].y);
    }
    _cairo_svg_stream_printf (output, "</g>\n");
    return CAIRO_STATUS_SUCCESS;
    fallback:;
    cairo_path_fixed_t path;
    _cairo_path_fixed_init (&path);
    status = _cairo_scaled_font_glyph_path (scaled_font,
					    (cairo_glyph_t *) glyphs,
					    num_glyphs, &path);
    if (unlikely (status)) {
	_cairo_path_fixed_fini (&path);
	return status;
    }
    status = _cairo_svg_surface_fill_impl (output,
					   surface,
					   source,
					   &path,
					   CAIRO_FILL_RULE_WINDING,
					   0.0,
					   CAIRO_ANTIALIAS_DEFAULT);
    _cairo_path_fixed_fini (&path);
    return status;
}
static cairo_int_status_t
_cairo_svg_surface_show_glyphs (void *abstract_surface,
				cairo_operator_t op,
				const cairo_pattern_t *source,
				cairo_glyph_t *glyphs,
				int num_glyphs,
				cairo_scaled_font_t *scaled_font,
				const cairo_clip_t *clip)
{
    cairo_svg_surface_t *surface = abstract_surface;
    cairo_int_status_t status;
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
	return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source)
	       ? CAIRO_STATUS_SUCCESS
	       : CAIRO_INT_STATUS_UNSUPPORTED;
    }
    _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_show_glyphs_impl,
					   source,
					   glyphs,
					   num_glyphs,
					   scaled_font)
}
static void
5
_cairo_svg_surface_get_font_options (void                  *abstract_surface,
				     cairo_font_options_t  *options)
{
5
    _cairo_font_options_init_default (options);
5
    cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_NONE);
5
    cairo_font_options_set_hint_metrics (options, CAIRO_HINT_METRICS_OFF);
5
    cairo_font_options_set_antialias (options, CAIRO_ANTIALIAS_GRAY);
5
    _cairo_font_options_set_round_glyph_positions (options, CAIRO_ROUND_GLYPH_POS_OFF);
5
}
static const char **
_cairo_svg_surface_get_supported_mime_types (void	   *abstract_surface)
{
    return _cairo_svg_supported_mime_types;
}
static const cairo_surface_backend_t cairo_svg_surface_backend = {
	CAIRO_SURFACE_TYPE_SVG,
	_cairo_svg_surface_finish,
	_cairo_default_context_create,
	NULL, /* create_similar: handled by wrapper */
	NULL, /* create_similar_image */
	NULL, /* map to image */
	NULL, /* unmap image */
	_cairo_surface_default_source,
	NULL, /* acquire_source_image */
	NULL, /* release_source_image */
	NULL, /* snapshot */
	_cairo_svg_surface_copy_page,
	_cairo_svg_surface_show_page,
	_cairo_svg_surface_get_extents,
	_cairo_svg_surface_get_font_options,
	NULL, /* flush */
	NULL, /* mark dirty rectangle */
	_cairo_svg_surface_paint,
	_cairo_svg_surface_mask,
	_cairo_svg_surface_stroke,
	_cairo_svg_surface_fill,
	_cairo_svg_surface_fill_stroke,
	_cairo_svg_surface_show_glyphs,
	NULL, /* has_show_text_glyphs */
	NULL, /* show_text_glyphs */
	_cairo_svg_surface_get_supported_mime_types,
};
static cairo_status_t
5
_cairo_svg_document_create (cairo_output_stream_t *output_stream,
			    double width,
			    double height,
			    cairo_svg_version_t version,
			    cairo_svg_document_t **document_out)
{
    cairo_svg_document_t *document;
5
    if (output_stream->status) {
	return output_stream->status;
    }
5
    document = _cairo_calloc (sizeof (cairo_svg_document_t));
5
    if (unlikely (document == NULL)) {
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    }
5
    document->output_stream = output_stream;
5
    document->refcount = 1;
5
    document->owner = NULL;
5
    document->finished = FALSE;
5
    document->width = width;
5
    document->height = height;
5
    document->unit = CAIRO_SVG_UNIT_USER;
5
    document->xml_node_defs = _cairo_svg_stream_create ();
5
    document->xml_node_glyphs = _cairo_svg_stream_create ();
5
    document->xml_node_filters = _cairo_svg_stream_create ();
5
    document->linear_pattern_id = 0;
5
    document->radial_pattern_id = 0;
5
    document->pattern_id = 0;
5
    document->clip_id = 0;
5
    document->mask_id = 0;
5
    document->compositing_group_id = 0;
5
    document->filter_id = 0;
20
    for (enum cairo_svg_filter filter = 0; filter < CAIRO_SVG_FILTER_LAST_STATIC_FILTER; filter++) {
15
	document->filters_emitted[filter] = FALSE;
    }
5
    document->svg_version = version;
    /* The use of defs for font glyphs imposes no per-subset limit. */
5
    document->font_subsets = _cairo_scaled_font_subsets_create_scaled ();
5
    if (unlikely (document->font_subsets == NULL)) {
	(void) _cairo_svg_stream_destroy(&document->xml_node_defs);
	(void) _cairo_svg_stream_destroy(&document->xml_node_glyphs);
	(void) _cairo_svg_stream_destroy(&document->xml_node_filters);
	free (document);
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    }
5
    document->paints = _cairo_hash_table_create (_cairo_svg_paint_equal);
5
    if (unlikely (document->paints == NULL)) {
	(void) _cairo_svg_stream_destroy(&document->xml_node_defs);
	(void) _cairo_svg_stream_destroy(&document->xml_node_glyphs);
	(void) _cairo_svg_stream_destroy(&document->xml_node_filters);
	_cairo_scaled_font_subsets_destroy (document->font_subsets);
	free (document);
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    }
5
    *document_out = document;
5
    return CAIRO_STATUS_SUCCESS;
}
static cairo_svg_document_t *
14
_cairo_svg_document_reference (cairo_svg_document_t *document)
{
14
    document->refcount++;
14
    return document;
}
static cairo_status_t
19
_cairo_svg_document_destroy (cairo_svg_document_t *document)
{
    cairo_status_t status;
19
    document->refcount--;
19
    if (document->refcount > 0)
14
      return CAIRO_STATUS_SUCCESS;
5
    status = _cairo_svg_document_finish (document);
5
    free (document);
5
    return status;
}
static cairo_status_t
10
_cairo_svg_document_finish (cairo_svg_document_t *document)
{
10
    if (document->finished) {
5
	return CAIRO_STATUS_SUCCESS;
    }
5
    document->finished = TRUE;
5
    cairo_status_t status, final_status = CAIRO_STATUS_SUCCESS;
5
    cairo_output_stream_t *output = document->output_stream;
    /*
     * Should we add DOCTYPE?
     *
     * Google says no.
     *
     * http://tech.groups.yahoo.com/group/svg-developers/message/48562:
     *   There's a bunch of issues, but just to pick a few:
     *   - they'll give false positives.
     *   - they'll give false negatives.
     *   - they're namespace-unaware.
     *   - they don't wildcard.
     *   So when they say OK they really haven't checked anything, when
     *   they say NOT OK they might be on crack, and like all
     *   namespace-unaware things they're a dead branch of the XML tree.
     *
     * http://jwatt.org/svg/authoring/:
     *   Unfortunately the SVG DTDs are a source of so many issues that the
     *   SVG WG has decided not to write one for the upcoming SVG 1.2
     *   standard. In fact SVG WG members are even telling people not to use
     *   a DOCTYPE declaration in SVG 1.0 and 1.1 documents.
     */
5
    _cairo_output_stream_printf (output,
				 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
				 "<svg xmlns=\"http://www.w3.org/2000/svg\" "
				 "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
				 "width=\"%f%s\" height=\"%f%s\" "
				 "viewBox=\"0 0 %f %f\">\n",
5
				 document->width, _cairo_svg_unit_strings[document->unit],
5
				 document->height, _cairo_svg_unit_strings[document->unit],
				 document->width, document->height);
5
    status = _cairo_svg_document_emit_font_subsets (document);
5
    if (final_status == CAIRO_STATUS_SUCCESS) {
5
	final_status = status;
    }
5
    cairo_svg_surface_t *surface = NULL;
5
    if (document->owner != NULL) {
5
	surface = (cairo_svg_surface_t *) _cairo_paginated_surface_get_target (document->owner);
5
	if (surface->xml_node.elements.num_elements > 0) {
	    cairo_svg_page_t *page = _cairo_svg_surface_store_page (surface);
	    if (final_status == CAIRO_STATUS_SUCCESS && page == NULL) {
		final_status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
	    }
	}
5
	if (surface->transitive_paint_used) {
4
	    cairo_svg_paint_t *paint_entry = _cairo_calloc (sizeof (cairo_svg_paint_t));
4
	    if (paint_entry == NULL) {
		return _cairo_error (CAIRO_STATUS_NO_MEMORY);
	    }
4
	    paint_entry->source_id = surface->source_id;
4
	    paint_entry->box.p1.x = 0;
4
	    paint_entry->box.p1.y = 0;
4
	    paint_entry->box.p2.x = document->width;
4
	    paint_entry->box.p2.y = document->height;
4
	    _cairo_svg_paint_box_add_padding (&paint_entry->box);
4
	    _cairo_array_init (&paint_entry->paint_elements, sizeof (cairo_svg_paint_element_t));
4
	    _cairo_svg_paint_init_key (paint_entry);
4
	    status = _cairo_hash_table_insert (document->paints, &paint_entry->base);
4
	    if (unlikely (status)) {
		return status;
	    }
	}
    }
5
    _cairo_hash_table_foreach (document->paints, _cairo_svg_paint_compute_func, document);
5
    if (document->xml_node_filters.elements.num_elements > 0 ||
1
	document->xml_node_glyphs.elements.num_elements > 0 ||
1
	document->xml_node_defs.elements.num_elements > 0) {
4
	_cairo_output_stream_printf (output, "<defs>\n");
4
	_cairo_svg_stream_copy_to_output_stream (&document->xml_node_filters, output, document->paints);
4
	if (document->xml_node_glyphs.elements.num_elements > 0) {
	    _cairo_output_stream_printf (output, "<g>\n");
	    _cairo_svg_stream_copy_to_output_stream (&document->xml_node_glyphs, output, document->paints);
	    _cairo_output_stream_printf (output, "</g>\n");
	}
4
	_cairo_svg_stream_copy_to_output_stream (&document->xml_node_defs, output, document->paints);
4
	_cairo_output_stream_printf (output, "</defs>\n");
    }
5
    if (document->owner != NULL) {
5
	if (surface->page_set.num_elements == 1) {
5
	    cairo_svg_page_t *page = _cairo_array_index (&surface->page_set, 0);
5
	    _cairo_svg_stream_copy_to_output_stream (&page->xml_node, output, document->paints);
	} else if (surface->page_set.num_elements > 1) {
	    _cairo_output_stream_printf (output, "<pageSet>\n");
	    for (unsigned int i = 0; i < surface->page_set.num_elements; i++) {
		cairo_svg_page_t *page = _cairo_array_index (&surface->page_set, i);
		_cairo_output_stream_printf (output, "<page>\n");
		_cairo_svg_stream_copy_to_output_stream (&page->xml_node, output, document->paints);
		_cairo_output_stream_printf (output, "</page>\n");
	    }
	    _cairo_output_stream_printf (output, "</pageSet>\n");
	}
    }
5
    _cairo_output_stream_printf (output, "</svg>\n");
5
    status = _cairo_svg_stream_destroy (&document->xml_node_defs);
5
    if (final_status == CAIRO_STATUS_SUCCESS) {
5
	final_status = status;
    }
5
    status = _cairo_svg_stream_destroy (&document->xml_node_glyphs);
5
    if (final_status == CAIRO_STATUS_SUCCESS) {
5
	final_status = status;
    }
5
    status = _cairo_svg_stream_destroy (&document->xml_node_filters);
5
    if (final_status == CAIRO_STATUS_SUCCESS) {
5
	final_status = status;
    }
5
    _cairo_hash_table_foreach (document->paints, _cairo_svg_paint_pluck, document->paints);
5
    _cairo_hash_table_destroy (document->paints);
5
    status = _cairo_output_stream_destroy (output);
5
    if (final_status == CAIRO_STATUS_SUCCESS) {
5
	final_status = status;
    }
5
    return final_status;
}
static cairo_int_status_t
30
_cairo_svg_surface_set_paginated_mode (void			*abstract_surface,
				       cairo_paginated_mode_t	 paginated_mode)
{
30
    cairo_svg_surface_t *surface = abstract_surface;
30
    surface->paginated_mode = paginated_mode;
30
    return CAIRO_STATUS_SUCCESS;
}
static cairo_bool_t
14
_cairo_svg_surface_supports_fine_grained_fallbacks (void *abstract_surface)
{
14
    return TRUE;
}
static const cairo_paginated_surface_backend_t cairo_svg_surface_paginated_backend = {
    NULL /*_cairo_svg_surface_start_page*/,
    _cairo_svg_surface_set_paginated_mode,
    NULL, /* _cairo_svg_surface_set_bounding_box */
    NULL, /* _cairo_svg_surface_set_fallback_images_required */
    _cairo_svg_surface_supports_fine_grained_fallbacks,
};