Coverage Report

Created: 2025-11-16 09:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/work/workdir/UnpackedTarball/cairo/src/cairo-svg-surface.c
Line
Count
Source
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
_cairo_svg_source_surface_init_key (cairo_svg_source_surface_t *source_surface)
97
0
{
98
0
    if (source_surface->unique_id && source_surface->unique_id_length > 0) {
99
0
  source_surface->base.hash = _cairo_hash_bytes (_CAIRO_HASH_INIT_VALUE,
100
0
                   source_surface->unique_id,
101
0
                   source_surface->unique_id_length);
102
0
    } else {
103
0
  source_surface->base.hash = source_surface->id;
104
0
    }
105
0
}
106
107
static cairo_bool_t
108
_cairo_svg_source_surface_equal (const void *key_a, const void *key_b)
109
0
{
110
0
    const cairo_svg_source_surface_t *a = key_a;
111
0
    const cairo_svg_source_surface_t *b = key_b;
112
113
0
    if (a->unique_id && b->unique_id && a->unique_id_length == b->unique_id_length) {
114
0
  return memcmp (a->unique_id, b->unique_id, a->unique_id_length) == 0;
115
0
    }
116
117
0
    return a->id == b->id;
118
0
}
119
120
static void
121
_cairo_svg_source_surface_pluck (void *entry, void *closure)
122
0
{
123
0
    cairo_svg_source_surface_t *source_surface = entry;
124
0
    cairo_hash_table_t *patterns = closure;
125
126
0
    _cairo_hash_table_remove (patterns, &source_surface->base);
127
0
    free (source_surface->unique_id);
128
0
    free (source_surface);
129
0
}
130
131
static void
132
_cairo_svg_paint_init_key (cairo_svg_paint_t *paint)
133
0
{
134
0
    paint->base.hash = paint->source_id;
135
0
}
136
137
static cairo_bool_t
138
_cairo_svg_paint_equal (const void *key_a, const void *key_b)
139
0
{
140
0
    const cairo_svg_paint_t *a = key_a;
141
0
    const cairo_svg_paint_t *b = key_b;
142
143
0
    return a->source_id == b->source_id;
144
0
}
145
146
static void
147
_cairo_svg_paint_pluck (void *entry, void *closure)
148
0
{
149
0
    cairo_svg_paint_t *paint = entry;
150
0
    cairo_hash_table_t *patterns = closure;
151
152
0
    _cairo_hash_table_remove (patterns, &paint->base);
153
0
    _cairo_array_fini (&paint->paint_elements);
154
0
    free (paint);
155
0
}
156
157
static void
158
_cairo_svg_paint_box_add_padding (cairo_box_double_t *box)
159
0
{
160
0
    double width = box->p2.x - box->p1.x;
161
0
    double height = box->p2.y - box->p1.y;
162
163
0
    box->p1.x -= width / 10.0;
164
0
    box->p1.y -= height / 10.0;
165
0
    box->p2.x += width / 10.0;
166
0
    box->p2.y += height / 10.0;
167
0
}
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
_cairo_svg_stream_create ()
201
10
{
202
10
    cairo_svg_stream_t svg_stream;
203
10
    svg_stream.status = CAIRO_STATUS_SUCCESS;
204
10
    _cairo_array_init (&svg_stream.elements, sizeof (cairo_svg_stream_element_t));
205
10
    return svg_stream;
206
10
}
207
208
static void
209
_cairo_svg_stream_write (cairo_svg_stream_t *svg_stream,
210
       const void *data,
211
       size_t length)
212
0
{
213
0
    cairo_status_t status;
214
215
0
    cairo_svg_stream_element_t *last_element = NULL;
216
0
    if (svg_stream->elements.num_elements > 0) {
217
0
  last_element = _cairo_array_index (&svg_stream->elements,
218
0
             svg_stream->elements.num_elements - 1);
219
0
    }
220
221
0
    if (last_element == NULL || last_element->type != CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) {
222
0
  cairo_svg_stream_element_t element;
223
0
  element.type = CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT;
224
0
  element.text.output_stream = _cairo_memory_stream_create();
225
0
  status = _cairo_array_append (&svg_stream->elements, &element);
226
0
  if (unlikely (status)) {
227
0
      if (svg_stream->status == CAIRO_STATUS_SUCCESS) {
228
0
    svg_stream->status = status;
229
0
      }
230
0
      return;
231
0
  }
232
0
  last_element = _cairo_array_index (&svg_stream->elements,
233
0
             svg_stream->elements.num_elements - 1);
234
0
    }
235
236
0
    _cairo_output_stream_write (last_element->text.output_stream, data, length);
237
0
}
238
239
static void CAIRO_PRINTF_FORMAT (2, 0)
240
_cairo_svg_stream_printf (cairo_svg_stream_t *svg_stream,
241
        const char *fmt,
242
        ...)
243
0
{
244
0
    cairo_status_t status;
245
246
0
    cairo_svg_stream_element_t *last_element = NULL;
247
0
    if (svg_stream->elements.num_elements > 0) {
248
0
  last_element = _cairo_array_index (&svg_stream->elements,
249
0
             svg_stream->elements.num_elements - 1);
250
0
    }
251
252
0
    if (last_element == NULL || last_element->type != CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) {
253
0
        cairo_svg_stream_element_t element;
254
0
  element.type = CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT;
255
0
  element.text.output_stream = _cairo_memory_stream_create();
256
0
  status = _cairo_array_append (&svg_stream->elements, &element);
257
0
  if (unlikely (status)) {
258
0
      if (svg_stream->status == CAIRO_STATUS_SUCCESS) {
259
0
    svg_stream->status = status;
260
0
      }
261
0
      return;
262
0
  }
263
0
  last_element = _cairo_array_index (&svg_stream->elements,
264
0
             svg_stream->elements.num_elements - 1);
265
0
    }
266
267
0
    va_list ap;
268
0
    va_start (ap, fmt);
269
0
    _cairo_output_stream_vprintf (last_element->text.output_stream, fmt, ap);
270
0
    va_end (ap);
271
0
}
272
273
static void
274
_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
0
{
278
0
    cairo_status_t status;
279
280
0
    cairo_svg_stream_element_t element;
281
0
    element.type = CAIRO_SVG_STREAM_ELEMENT_TYPE_PAINT_DEPENDENT;
282
0
    element.paint_dependent.source_id = source_id;
283
0
    element.paint_dependent.type = type;
284
0
    status = _cairo_array_append (&svg_stream->elements, &element);
285
0
    if (svg_stream->status == CAIRO_STATUS_SUCCESS) {
286
0
  svg_stream->status = status;
287
0
    }
288
0
}
289
290
static void
291
_cairo_svg_stream_copy (cairo_svg_stream_t *from,
292
      cairo_svg_stream_t *to)
293
0
{
294
0
    cairo_status_t status;
295
296
0
    if (unlikely (from->status)) {
297
0
  if (to->status == CAIRO_STATUS_SUCCESS) {
298
0
      to->status = from->status;
299
0
  }
300
0
  return;
301
0
    }
302
303
0
    for (unsigned int i = 0; i < from->elements.num_elements; i++) {
304
0
  cairo_svg_stream_element_t *element = _cairo_array_index (&from->elements, i);
305
0
  cairo_svg_stream_element_t element_copy = *element;
306
0
  if (element->type == CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) {
307
0
      element_copy.text.output_stream = _cairo_memory_stream_create ();
308
0
      _cairo_memory_stream_copy (element->text.output_stream, element_copy.text.output_stream);
309
0
      if (to->status == CAIRO_STATUS_SUCCESS) {
310
0
    to->status = element->text.output_stream->status;
311
0
      }
312
0
  }
313
0
  status = _cairo_array_append (&to->elements, &element_copy);
314
0
  if (unlikely (status)) {
315
0
      if (to->status == CAIRO_STATUS_SUCCESS) {
316
0
    to->status = status;
317
0
      }
318
0
      return;
319
0
  }
320
0
    }
321
0
}
322
323
static void
324
_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
2
{
328
2
    if (unlikely (from->status)) {
329
0
  if (to->status == CAIRO_STATUS_SUCCESS) {
330
0
      to->status = from->status;
331
0
  }
332
0
  return;
333
0
    }
334
335
2
    for (unsigned int i = 0; i < from->elements.num_elements; i++) {
336
0
  cairo_svg_stream_element_t *element = _cairo_array_index (&from->elements, i);
337
0
  if (element->type == CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) {
338
0
      _cairo_memory_stream_copy (element->text.output_stream, to);
339
0
  }
340
0
  if (element->type == CAIRO_SVG_STREAM_ELEMENT_TYPE_PAINT_DEPENDENT) {
341
0
      cairo_svg_paint_t paint_key;
342
0
      paint_key.source_id = element->paint_dependent.source_id;
343
0
      _cairo_svg_paint_init_key (&paint_key);
344
345
0
      cairo_svg_paint_t *found_paint_entry = _cairo_hash_table_lookup (paints,
346
0
                       &paint_key.base);
347
0
      assert (found_paint_entry);
348
349
0
      switch (element->paint_dependent.type) {
350
0
      case CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE:
351
0
    _cairo_output_stream_printf (to,
352
0
               " x=\"%f\" y=\"%f\" width=\"%f\" height=\"%f\"",
353
0
               found_paint_entry->box.p1.x,
354
0
               found_paint_entry->box.p1.y,
355
0
               found_paint_entry->box.p2.x - found_paint_entry->box.p1.x,
356
0
               found_paint_entry->box.p2.y - found_paint_entry->box.p1.y);
357
0
    break;
358
0
      case CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN:
359
0
    _cairo_output_stream_printf (to,
360
0
               " x=\"0\" y=\"0\" width=\"%f\" height=\"%f\"",
361
0
               found_paint_entry->box.p2.x - found_paint_entry->box.p1.x,
362
0
               found_paint_entry->box.p2.y - found_paint_entry->box.p1.y);
363
0
    break;
364
0
      case CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_TRANSLATION:
365
0
    _cairo_output_stream_printf (to,
366
0
               " transform=\"translate(%f, %f)\"",
367
0
               found_paint_entry->box.p1.x,
368
0
               found_paint_entry->box.p1.y);
369
0
    break;
370
0
      case CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION:
371
0
    _cairo_output_stream_printf (to,
372
0
               " transform=\"translate(%f, %f)\"",
373
0
               -found_paint_entry->box.p1.x,
374
0
               -found_paint_entry->box.p1.y);
375
0
    break;
376
0
      }
377
0
  }
378
0
    }
379
2
}
380
381
static cairo_status_t
382
_cairo_svg_stream_destroy (cairo_svg_stream_t *svg_stream)
383
10
{
384
10
    cairo_status_t status = svg_stream->status;
385
10
    for (unsigned int i = 0; i < svg_stream->elements.num_elements; i++) {
386
0
  cairo_svg_stream_element_t *element = _cairo_array_index (&svg_stream->elements, i);
387
0
  if (element->type == CAIRO_SVG_STREAM_ELEMENT_TYPE_TEXT) {
388
0
      cairo_status_t element_status = _cairo_output_stream_destroy (element->text.output_stream);
389
0
      if (status == CAIRO_STATUS_SUCCESS) {
390
0
    status = element_status;
391
0
      }
392
0
  }
393
0
    }
394
10
    _cairo_array_fini (&svg_stream->elements);
395
10
    return status;
396
10
}
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
0
#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
cairo_svg_surface_create_for_stream (cairo_write_func_t    write_func,
613
             void     *closure,
614
             double      width,
615
             double      height)
616
0
{
617
0
    cairo_output_stream_t *stream;
618
619
0
    stream = _cairo_output_stream_create (write_func, NULL, closure);
620
0
    if (_cairo_output_stream_get_status (stream))
621
0
  return _cairo_surface_create_in_error (_cairo_output_stream_destroy (stream));
622
623
0
    return _cairo_svg_surface_create_for_stream_internal (stream, width, height, CAIRO_SVG_VERSION_1_1);
624
0
}
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
cairo_svg_surface_create (const char  *filename,
669
        double   width,
670
        double   height)
671
2
{
672
2
    cairo_output_stream_t *stream;
673
674
2
    stream = _cairo_output_stream_create_for_filename (filename);
675
2
    if (_cairo_output_stream_get_status (stream))
676
0
  return _cairo_surface_create_in_error (_cairo_output_stream_destroy (stream));
677
678
2
    return _cairo_svg_surface_create_for_stream_internal (stream, width, height, CAIRO_SVG_VERSION_1_1);
679
2
}
680
681
static cairo_bool_t
682
_cairo_surface_is_svg (cairo_surface_t *surface)
683
0
{
684
0
    return surface->backend == &cairo_svg_surface_backend;
685
0
}
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
_extract_svg_surface (cairo_surface_t *surface,
693
          cairo_svg_surface_t **svg_surface)
694
0
{
695
0
    cairo_surface_t *target;
696
697
0
    if (surface->status)
698
0
  return FALSE;
699
0
    if (surface->finished) {
700
0
  (void) _cairo_surface_set_error (surface, _cairo_error (CAIRO_STATUS_SURFACE_FINISHED));
701
0
  return FALSE;
702
0
    }
703
704
0
    if (!_cairo_surface_is_paginated (surface)) {
705
0
  (void) _cairo_surface_set_error (surface, _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH));
706
0
  return FALSE;
707
0
    }
708
709
0
    target = _cairo_paginated_surface_get_target (surface);
710
0
    if (target->status) {
711
0
  (void) _cairo_surface_set_error (surface, target->status);
712
0
  return FALSE;
713
0
    }
714
0
    if (target->finished) {
715
0
  (void) _cairo_surface_set_error (surface, _cairo_error (CAIRO_STATUS_SURFACE_FINISHED));
716
0
  return FALSE;
717
0
    }
718
719
0
    if (!_cairo_surface_is_svg (target)) {
720
0
  (void) _cairo_surface_set_error (surface, _cairo_error (CAIRO_STATUS_SURFACE_TYPE_MISMATCH));
721
0
  return FALSE;
722
0
    }
723
724
0
    *svg_surface = (cairo_svg_surface_t *) target;
725
0
    return TRUE;
726
0
}
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
cairo_svg_surface_restrict_to_version (cairo_surface_t    *abstract_surface,
745
               cairo_svg_version_t   version)
746
0
{
747
0
    cairo_svg_surface_t *surface;
748
749
0
    if (! _extract_svg_surface (abstract_surface, &surface))
750
0
  return;
751
752
0
    if (version < CAIRO_SVG_VERSION_LAST)
753
0
  surface->document->svg_version = version;
754
0
}
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
0
{
770
0
    if (versions != NULL)
771
0
  *versions = _cairo_svg_versions;
772
773
0
    if (num_versions != NULL)
774
0
  *num_versions = CAIRO_SVG_VERSION_LAST;
775
0
}
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
0
{
792
0
    if (version >= CAIRO_SVG_VERSION_LAST)
793
0
  return NULL;
794
795
0
    return _cairo_svg_version_strings[version];
796
0
}
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
0
{
825
0
    cairo_svg_surface_t *surface;
826
827
0
    if (! _extract_svg_surface (abstract_surface, &surface))
828
0
  return;
829
830
0
    if (unit <= CAIRO_SVG_UNIT_PERCENT)
831
0
  surface->document->unit = unit;
832
0
}
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
0
{
851
0
    cairo_svg_surface_t *surface;
852
853
0
    if (! _extract_svg_surface (abstract_surface, &surface)) {
854
0
  _cairo_error_throw (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
855
0
  return CAIRO_SVG_UNIT_USER;
856
0
    }
857
858
0
    return surface->document->unit;
859
0
}
860
861
static void
862
0
_cairo_svg_paint_compute (cairo_svg_document_t *document, cairo_svg_paint_t *paint) {
863
0
    for (unsigned int i = 0; i < paint->paint_elements.num_elements; i++) {
864
0
  cairo_svg_paint_element_t *paint_element = _cairo_array_index (&paint->paint_elements, i);
865
866
0
  cairo_svg_paint_t paint_key;
867
0
  paint_key.source_id = paint_element->source_id;
868
0
  _cairo_svg_paint_init_key (&paint_key);
869
870
0
  cairo_svg_paint_t *found_paint_entry = _cairo_hash_table_lookup (document->paints,
871
0
                   &paint_key.base);
872
0
  assert (found_paint_entry);
873
874
0
  _cairo_svg_paint_compute (document, found_paint_entry);
875
876
0
  cairo_box_double_t box = found_paint_entry->box;
877
0
  _cairo_matrix_transform_bounding_box (&paint_element->matrix,
878
0
                &box.p1.x, &box.p1.y,
879
0
                &box.p2.x, &box.p2.y,
880
0
                NULL);
881
0
  _cairo_svg_paint_box_add_padding (&box);
882
883
0
  if (i == 0) {
884
0
      paint->box = box;
885
0
  } else {
886
0
      paint->box.p1.x = MIN (paint->box.p1.x, box.p1.x);
887
0
      paint->box.p1.y = MIN (paint->box.p1.y, box.p1.y);
888
0
      paint->box.p2.x = MAX (paint->box.p2.x, box.p2.x);
889
0
      paint->box.p2.y = MAX (paint->box.p2.y, box.p2.y);
890
0
  }
891
0
    }
892
0
    _cairo_array_truncate (&paint->paint_elements, 0);
893
0
}
894
895
static void
896
_cairo_svg_paint_compute_func (void *entry, void *closure)
897
0
{
898
0
    cairo_svg_paint_t *paint = entry;
899
0
    cairo_svg_document_t *document = closure;
900
901
0
    _cairo_svg_paint_compute (document, paint);
902
0
}
903
904
static cairo_status_t
905
_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
0
{
910
0
    cairo_status_t status;
911
912
0
    cairo_svg_source_surface_t source_surface_key;
913
0
    source_surface_key.id = source_surface->unique_id;
914
0
    cairo_surface_get_mime_data (source_surface,
915
0
         CAIRO_MIME_TYPE_UNIQUE_ID,
916
0
         (const unsigned char **) &source_surface_key.unique_id,
917
0
         &source_surface_key.unique_id_length);
918
0
    _cairo_svg_source_surface_init_key (&source_surface_key);
919
920
0
    cairo_svg_source_surface_t *found_source_surface_entry = _cairo_hash_table_lookup (surface->source_surfaces,
921
0
                           &source_surface_key.base);
922
0
    if (found_source_surface_entry) {
923
0
  *is_new = FALSE;
924
0
  *result_source_surface = found_source_surface_entry;
925
0
  return CAIRO_STATUS_SUCCESS;
926
0
    }
927
928
0
    unsigned char *unique_id = NULL;
929
0
    unsigned long unique_id_length = 0;
930
0
    if (source_surface_key.unique_id && source_surface_key.unique_id_length > 0) {
931
0
  unique_id = _cairo_malloc (source_surface_key.unique_id_length);
932
0
  if (unique_id == NULL) {
933
0
      return _cairo_error (CAIRO_STATUS_NO_MEMORY);
934
0
  }
935
936
0
  unique_id_length = source_surface_key.unique_id_length;
937
0
  memcpy (unique_id, source_surface_key.unique_id, unique_id_length);
938
0
    } else {
939
0
  unique_id = NULL;
940
0
  unique_id_length = 0;
941
0
    }
942
943
0
    cairo_svg_source_surface_t *source_surface_entry = _cairo_calloc (sizeof (cairo_svg_source_surface_t));
944
0
    if (source_surface_entry == NULL) {
945
0
  status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
946
0
  goto fail;
947
0
    }
948
0
    source_surface_entry->id = source_surface_key.id;
949
0
    source_surface_entry->unique_id_length = unique_id_length;
950
0
    source_surface_entry->unique_id = unique_id;
951
0
    _cairo_svg_source_surface_init_key (source_surface_entry);
952
0
    status = _cairo_hash_table_insert (surface->source_surfaces, &source_surface_entry->base);
953
0
    if (unlikely (status)) {
954
0
  goto fail;
955
0
    }
956
957
0
    *is_new = TRUE;
958
0
    *result_source_surface = source_surface_entry;
959
0
    return CAIRO_STATUS_SUCCESS;
960
961
0
    fail:
962
0
    free (unique_id);
963
0
    free (source_surface_entry);
964
0
    return status;
965
0
}
966
967
static cairo_bool_t
968
_cairo_svg_surface_cliprect_covers_surface (cairo_svg_surface_t *surface,
969
              cairo_path_fixed_t *path)
970
0
{
971
0
    cairo_box_t box;
972
973
0
    return surface->surface_bounded &&
974
0
     _cairo_path_fixed_is_box (path, &box) &&
975
0
     box.p1.x <= 0 &&
976
0
     box.p1.y <= 0 &&
977
0
     _cairo_fixed_to_double (box.p2.x) >= surface->width &&
978
0
     _cairo_fixed_to_double (box.p2.y) >= surface->height;
979
0
}
980
981
static cairo_status_t
982
_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
0
{
988
0
    cairo_svg_surface_t *surface = cairo_container_of (clipper,
989
0
                   cairo_svg_surface_t,
990
0
                   clipper);
991
0
    cairo_svg_document_t *document = surface->document;
992
993
0
    if (path == NULL) {
994
0
  for (unsigned int i = 0; i < surface->clip_level; i++) {
995
0
      _cairo_svg_stream_printf (surface->current_clipper_stream, "</g>\n");
996
0
  }
997
0
  surface->clip_level = 0;
998
0
  return CAIRO_STATUS_SUCCESS;
999
0
    }
1000
1001
    /* skip trivial whole-page clips */
1002
0
    if (_cairo_svg_surface_cliprect_covers_surface (surface, path)) {
1003
0
  return CAIRO_STATUS_SUCCESS;
1004
0
    }
1005
1006
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
1007
0
            "<clipPath id=\"clip-%d\">\n",
1008
0
            document->clip_id);
1009
1010
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
1011
0
            "<path clip-rule=\"%s\"",
1012
0
            fill_rule == CAIRO_FILL_RULE_EVEN_ODD ? "evenodd" : "nonzero");
1013
0
    _cairo_svg_surface_emit_path (&document->xml_node_defs, path, NULL);
1014
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "/>\n");
1015
1016
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "</clipPath>\n");
1017
1018
0
    _cairo_svg_stream_printf (surface->current_clipper_stream,
1019
0
            "<g clip-path=\"url(#clip-%d)\">\n",
1020
0
            document->clip_id);
1021
1022
0
    document->clip_id++;
1023
0
    surface->clip_level++;
1024
1025
0
    return CAIRO_STATUS_SUCCESS;
1026
0
}
1027
1028
static void
1029
_cairo_svg_surface_reset_clip (cairo_svg_surface_t *surface)
1030
2
{
1031
2
    _cairo_surface_clipper_reset (&surface->clipper);
1032
2
    if (surface->current_clipper_stream != NULL) {
1033
0
  for (unsigned int i = 0; i < surface->clip_level; i++) {
1034
0
      _cairo_svg_stream_printf (surface->current_clipper_stream, "</g>\n");
1035
0
  }
1036
0
    }
1037
2
    surface->clip_level = 0;
1038
2
}
1039
1040
static cairo_status_t
1041
_cairo_svg_surface_set_clip (cairo_svg_surface_t *surface,
1042
           cairo_svg_stream_t *clipper_stream,
1043
           const cairo_clip_t *clip)
1044
0
{
1045
0
    if (surface->current_clipper_stream != clipper_stream) {
1046
0
  _cairo_svg_surface_reset_clip (surface);
1047
0
  surface->current_clipper_stream = clipper_stream;
1048
0
    }
1049
0
    return _cairo_surface_clipper_set_clip (&surface->clipper, clip);
1050
0
}
1051
1052
static cairo_surface_t *
1053
_cairo_svg_surface_create_for_document (cairo_svg_document_t *document,
1054
          cairo_content_t content,
1055
          double width,
1056
          double height,
1057
          cairo_bool_t bounded)
1058
2
{
1059
2
    cairo_svg_surface_t *surface;
1060
2
    cairo_surface_t *paginated;
1061
2
    cairo_status_t status;
1062
1063
2
    surface = _cairo_calloc (sizeof (cairo_svg_surface_t));
1064
2
    if (unlikely (surface == NULL)) {
1065
0
  return _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));
1066
0
    }
1067
1068
2
    _cairo_surface_init (&surface->base,
1069
2
       &cairo_svg_surface_backend,
1070
2
       NULL, /* device */
1071
2
       content,
1072
2
       TRUE); /* is_vector */
1073
1074
2
    surface->source_id = surface->base.unique_id;
1075
2
    surface->depth = 0;
1076
1077
2
    surface->width = width;
1078
2
    surface->height = height;
1079
2
    surface->surface_bounded = bounded;
1080
1081
2
    surface->document = _cairo_svg_document_reference (document);
1082
1083
2
    surface->xml_node = _cairo_svg_stream_create ();
1084
2
    _cairo_array_init (&surface->page_set, sizeof (cairo_svg_page_t));
1085
1086
2
    surface->source_surfaces = _cairo_hash_table_create (_cairo_svg_source_surface_equal);
1087
2
    if (unlikely (surface->source_surfaces == NULL)) {
1088
0
  status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
1089
0
  goto CLEANUP;
1090
0
    }
1091
1092
2
    _cairo_surface_clipper_init (&surface->clipper, _cairo_svg_surface_clipper_intersect_clip_path);
1093
2
    surface->current_clipper_stream = NULL;
1094
2
    surface->clip_level = 0;
1095
2
    surface->transitive_paint_used = FALSE;
1096
1097
2
    surface->paginated_mode = CAIRO_PAGINATED_MODE_ANALYZE;
1098
1099
2
    surface->force_fallbacks = FALSE;
1100
1101
1102
2
    paginated = _cairo_paginated_surface_create (&surface->base,
1103
2
             surface->base.content,
1104
2
             &cairo_svg_surface_paginated_backend);
1105
2
    status = paginated->status;
1106
2
    if (status == CAIRO_STATUS_SUCCESS) {
1107
  /* paginated keeps the only reference to surface now, drop ours */
1108
2
  cairo_surface_destroy (&surface->base);
1109
2
  return paginated;
1110
2
    }
1111
1112
    /* ignore status as we are on the error path */
1113
0
    CLEANUP:
1114
0
    (void) _cairo_svg_stream_destroy (&surface->xml_node);
1115
0
    (void) _cairo_svg_document_destroy (document);
1116
1117
0
    free (surface);
1118
1119
0
    return _cairo_surface_create_in_error (status);
1120
2
}
1121
1122
static cairo_surface_t *
1123
_cairo_svg_surface_create_for_stream_internal (cairo_output_stream_t  *stream,
1124
                 double      width,
1125
                 double      height,
1126
                 cairo_svg_version_t   version)
1127
2
{
1128
2
    cairo_svg_document_t *document;
1129
2
    cairo_surface_t *surface;
1130
2
    cairo_status_t status;
1131
1132
2
    status = _cairo_svg_document_create (stream,
1133
2
                                   width, height, version,
1134
2
           &document);
1135
2
    if (unlikely (status)) {
1136
0
  surface =  _cairo_surface_create_in_error (status);
1137
  /* consume the output stream on behalf of caller */
1138
0
  status = _cairo_output_stream_destroy (stream);
1139
0
  return surface;
1140
0
    }
1141
1142
2
    surface = _cairo_svg_surface_create_for_document (document, CAIRO_CONTENT_COLOR_ALPHA,
1143
2
                  width, height, TRUE);
1144
2
    if (surface->status) {
1145
0
  return surface;
1146
0
    }
1147
1148
2
    document->owner = surface;
1149
2
    status = _cairo_svg_document_destroy (document);
1150
    /* the ref count should be 2 at this point */
1151
2
    assert (status == CAIRO_STATUS_SUCCESS);
1152
1153
2
    return surface;
1154
2
}
1155
1156
static cairo_svg_page_t *
1157
_cairo_svg_surface_store_page (cairo_svg_surface_t *surface)
1158
2
{
1159
2
    _cairo_svg_surface_reset_clip (surface);
1160
2
    cairo_svg_page_t page;
1161
2
    page.xml_node = surface->xml_node;
1162
2
    if (_cairo_array_append (&surface->page_set, &page)) {
1163
0
  return NULL;
1164
0
    }
1165
2
    surface->xml_node = _cairo_svg_stream_create ();
1166
2
    return _cairo_array_index (&surface->page_set,
1167
2
             surface->page_set.num_elements - 1);
1168
2
}
1169
1170
static cairo_int_status_t
1171
_cairo_svg_surface_copy_page (void *abstract_surface)
1172
0
{
1173
0
    cairo_svg_surface_t *surface = abstract_surface;
1174
1175
0
    cairo_svg_page_t *page = _cairo_svg_surface_store_page (surface);
1176
0
    if (unlikely (page == NULL)) {
1177
0
  return _cairo_error (CAIRO_STATUS_NO_MEMORY);
1178
0
    }
1179
1180
0
    _cairo_svg_stream_copy (&page->xml_node, &surface->xml_node);
1181
1182
0
    return CAIRO_STATUS_SUCCESS;
1183
0
}
1184
1185
static cairo_int_status_t
1186
_cairo_svg_surface_show_page (void *abstract_surface)
1187
2
{
1188
2
    cairo_svg_surface_t *surface = abstract_surface;
1189
1190
2
    cairo_svg_page_t *page = _cairo_svg_surface_store_page (surface);
1191
2
    if (unlikely (page == NULL)) {
1192
0
  return _cairo_error (CAIRO_STATUS_NO_MEMORY);
1193
0
    }
1194
1195
2
    return CAIRO_STATUS_SUCCESS;
1196
2
}
1197
1198
static void
1199
_cairo_svg_surface_emit_transform (cairo_svg_stream_t *output,
1200
           char const *attribute_name,
1201
           const cairo_matrix_t *object_matrix,
1202
           const cairo_matrix_t *parent_matrix)
1203
0
{
1204
0
    cairo_matrix_t matrix = *object_matrix;
1205
1206
0
    if (parent_matrix != NULL) {
1207
0
  cairo_matrix_multiply (&matrix, &matrix, parent_matrix);
1208
0
    }
1209
1210
0
    if (!_cairo_matrix_is_identity (&matrix)) {
1211
0
  _cairo_svg_stream_printf (output,
1212
0
          " %s=\"matrix(%f, %f, %f, %f, %f, %f)\"",
1213
0
          attribute_name,
1214
0
          matrix.xx, matrix.yx,
1215
0
          matrix.xy, matrix.yy,
1216
0
          matrix.x0, matrix.y0);
1217
0
    }
1218
0
}
1219
1220
typedef struct {
1221
    cairo_svg_stream_t *output;
1222
    const cairo_matrix_t *ctm_inverse;
1223
} svg_path_info_t;
1224
1225
static cairo_status_t
1226
_cairo_svg_path_move_to (void *closure,
1227
       const cairo_point_t *point)
1228
0
{
1229
0
    svg_path_info_t *info = closure;
1230
0
    double x = _cairo_fixed_to_double (point->x);
1231
0
    double y = _cairo_fixed_to_double (point->y);
1232
1233
0
    if (info->ctm_inverse)
1234
0
  cairo_matrix_transform_point (info->ctm_inverse, &x, &y);
1235
1236
0
    _cairo_svg_stream_printf (info->output, "M %f %f ", x, y);
1237
1238
0
    return CAIRO_STATUS_SUCCESS;
1239
0
}
1240
1241
static cairo_status_t
1242
_cairo_svg_path_line_to (void *closure,
1243
       const cairo_point_t *point)
1244
0
{
1245
0
    svg_path_info_t *info = closure;
1246
0
    double x = _cairo_fixed_to_double (point->x);
1247
0
    double y = _cairo_fixed_to_double (point->y);
1248
1249
0
    if (info->ctm_inverse)
1250
0
  cairo_matrix_transform_point (info->ctm_inverse, &x, &y);
1251
1252
0
    _cairo_svg_stream_printf (info->output, "L %f %f ", x, y);
1253
1254
0
    return CAIRO_STATUS_SUCCESS;
1255
0
}
1256
1257
static cairo_status_t
1258
_cairo_svg_path_curve_to (void          *closure,
1259
        const cairo_point_t *b,
1260
        const cairo_point_t *c,
1261
        const cairo_point_t *d)
1262
0
{
1263
0
    svg_path_info_t *info = closure;
1264
0
    double bx = _cairo_fixed_to_double (b->x);
1265
0
    double by = _cairo_fixed_to_double (b->y);
1266
0
    double cx = _cairo_fixed_to_double (c->x);
1267
0
    double cy = _cairo_fixed_to_double (c->y);
1268
0
    double dx = _cairo_fixed_to_double (d->x);
1269
0
    double dy = _cairo_fixed_to_double (d->y);
1270
1271
0
    if (info->ctm_inverse) {
1272
0
  cairo_matrix_transform_point (info->ctm_inverse, &bx, &by);
1273
0
  cairo_matrix_transform_point (info->ctm_inverse, &cx, &cy);
1274
0
  cairo_matrix_transform_point (info->ctm_inverse, &dx, &dy);
1275
0
    }
1276
1277
0
    _cairo_svg_stream_printf (info->output,
1278
0
            "C %f %f %f %f %f %f ",
1279
0
            bx, by, cx, cy, dx, dy);
1280
1281
0
    return CAIRO_STATUS_SUCCESS;
1282
0
}
1283
1284
static cairo_status_t
1285
_cairo_svg_path_close_path (void *closure)
1286
0
{
1287
0
    svg_path_info_t *info = closure;
1288
1289
0
    _cairo_svg_stream_printf (info->output, "Z ");
1290
1291
0
    return CAIRO_STATUS_SUCCESS;
1292
0
}
1293
1294
static void
1295
_cairo_svg_surface_emit_path (cairo_svg_stream_t *output,
1296
            const cairo_path_fixed_t *path,
1297
            const cairo_matrix_t *ctm_inverse)
1298
0
{
1299
0
    cairo_status_t status;
1300
0
    svg_path_info_t info;
1301
1302
0
    _cairo_svg_stream_printf (output, " d=\"");
1303
1304
0
    info.output = output;
1305
0
    info.ctm_inverse = ctm_inverse;
1306
0
    status = _cairo_path_fixed_interpret (path,
1307
0
            _cairo_svg_path_move_to,
1308
0
            _cairo_svg_path_line_to,
1309
0
            _cairo_svg_path_curve_to,
1310
0
            _cairo_svg_path_close_path,
1311
0
            &info);
1312
0
    assert (status == CAIRO_STATUS_SUCCESS);
1313
1314
0
    _cairo_svg_stream_printf (output, "\"");
1315
0
}
1316
1317
static cairo_int_status_t
1318
_cairo_svg_document_emit_outline_glyph_data (cairo_svg_document_t *document,
1319
               cairo_scaled_font_t *scaled_font,
1320
               unsigned long glyph_index)
1321
0
{
1322
0
    cairo_scaled_glyph_t *scaled_glyph;
1323
0
    cairo_int_status_t status;
1324
1325
0
    status = _cairo_scaled_glyph_lookup (scaled_font,
1326
0
           glyph_index,
1327
0
           CAIRO_SCALED_GLYPH_INFO_METRICS | CAIRO_SCALED_GLYPH_INFO_PATH,
1328
0
           NULL, /* foreground color */
1329
0
           &scaled_glyph);
1330
0
    if (unlikely (status)) {
1331
0
  return status;
1332
0
    }
1333
1334
0
    if (_cairo_path_fixed_size (scaled_glyph->path) != 0) {
1335
0
  _cairo_svg_stream_printf (&document->xml_node_glyphs,
1336
0
          "<path");
1337
1338
0
  _cairo_svg_surface_emit_path (&document->xml_node_glyphs,
1339
0
              scaled_glyph->path,
1340
0
              NULL);
1341
1342
0
  _cairo_svg_stream_printf (&document->xml_node_glyphs,
1343
0
          "/>\n");
1344
0
    }
1345
1346
0
    return status;
1347
0
}
1348
1349
static cairo_int_status_t
1350
_cairo_svg_document_emit_bitmap_glyph_data (cairo_svg_document_t *document,
1351
              cairo_scaled_font_t *scaled_font,
1352
              unsigned long glyph_index)
1353
0
{
1354
0
    cairo_status_t status;
1355
1356
0
    cairo_scaled_glyph_t *scaled_glyph;
1357
0
    status = _cairo_scaled_glyph_lookup (scaled_font,
1358
0
           glyph_index,
1359
0
           CAIRO_SCALED_GLYPH_INFO_METRICS | CAIRO_SCALED_GLYPH_INFO_SURFACE,
1360
0
           NULL, /* foreground color */
1361
0
           &scaled_glyph);
1362
0
    if (unlikely (status)) {
1363
0
  return status;
1364
0
    }
1365
1366
0
    cairo_bool_t use_recording_surface = (scaled_glyph->has_info & CAIRO_SCALED_GLYPH_INFO_RECORDING_SURFACE) != 0;
1367
0
    cairo_matrix_t glyph_matrix = scaled_glyph->surface->base.device_transform_inverse;
1368
0
    cairo_image_surface_t *glyph_image_surface = scaled_glyph->surface;
1369
1370
    // Attempt to recognize a common pattern for a bitmap font and extract the original glyph image from it
1371
0
    cairo_surface_t *extracted_surface;
1372
0
    cairo_image_surface_t *extracted_image = NULL;
1373
0
    void *extracted_image_extra;
1374
0
    if (use_recording_surface) {
1375
0
  cairo_recording_surface_t *recording_surface = (cairo_recording_surface_t *) scaled_glyph->recording_surface;
1376
0
  if (recording_surface->commands.num_elements == 1) {
1377
0
      cairo_command_t *command = *((cairo_command_t **) _cairo_array_index (&recording_surface->commands, 0));
1378
0
      if (command->header.type == CAIRO_COMMAND_MASK &&
1379
0
    command->header.op == CAIRO_OPERATOR_OVER &&
1380
0
    command->header.clip == NULL &&
1381
0
    command->mask.source.base.type == CAIRO_PATTERN_TYPE_SOLID &&
1382
0
    _cairo_color_equal (&command->mask.source.solid.color, _cairo_stock_color (CAIRO_STOCK_BLACK)) &&
1383
0
    command->mask.mask.base.extend == CAIRO_EXTEND_NONE &&
1384
0
    command->mask.mask.base.type == CAIRO_PATTERN_TYPE_SURFACE &&
1385
0
    command->mask.mask.surface.surface->type == CAIRO_SURFACE_TYPE_IMAGE) {
1386
0
    extracted_surface = command->mask.mask.surface.surface;
1387
0
    if (_cairo_surface_acquire_source_image (extracted_surface,
1388
0
               &extracted_image,
1389
0
               &extracted_image_extra) == CAIRO_STATUS_SUCCESS) {
1390
0
        if (extracted_image->format == CAIRO_FORMAT_A1 || extracted_image->format == CAIRO_FORMAT_A8) {
1391
0
      use_recording_surface = FALSE;
1392
0
      glyph_image_surface = extracted_image;
1393
0
      glyph_matrix = command->mask.mask.base.matrix;
1394
0
      status = cairo_matrix_invert (&glyph_matrix);
1395
0
      assert (status == CAIRO_STATUS_SUCCESS);
1396
0
        }
1397
0
    }
1398
0
      }
1399
0
  }
1400
0
    }
1401
1402
0
    cairo_surface_t *paginated_surface = _cairo_svg_surface_create_for_document (document,
1403
0
                     CAIRO_CONTENT_COLOR_ALPHA,
1404
0
                     0,
1405
0
                     0,
1406
0
                     FALSE);
1407
0
    cairo_svg_surface_t *svg_surface = (cairo_svg_surface_t *) _cairo_paginated_surface_get_target (paginated_surface);
1408
0
    status = paginated_surface->status;
1409
0
    if (unlikely (status)) {
1410
0
  goto cleanup;
1411
0
    }
1412
1413
0
    unsigned int source_id = svg_surface->base.unique_id;
1414
1415
0
    cairo_surface_set_fallback_resolution (paginated_surface,
1416
0
             document->owner->x_fallback_resolution,
1417
0
             document->owner->y_fallback_resolution);
1418
1419
0
    cairo_svg_stream_t temporary_stream = _cairo_svg_stream_create ();
1420
1421
0
    unsigned int mask_id = document->mask_id++;
1422
1423
0
    _cairo_svg_stream_printf (&temporary_stream,
1424
0
            "<mask id=\"mask-%d\">\n",
1425
0
            mask_id);
1426
1427
0
    cairo_pattern_t *pattern = cairo_pattern_create_for_surface (use_recording_surface ? scaled_glyph->recording_surface
1428
0
                           : &glyph_image_surface->base);
1429
0
    _cairo_svg_surface_emit_composite_pattern (&temporary_stream,
1430
0
                 svg_surface,
1431
0
                 (cairo_surface_pattern_t *) pattern,
1432
0
                 invalid_pattern_id,
1433
0
                 NULL);
1434
0
    cairo_pattern_destroy (pattern);
1435
1436
0
    _cairo_svg_stream_printf (&temporary_stream, "</mask>\n");
1437
1438
0
    _cairo_svg_stream_copy (&temporary_stream, &document->xml_node_defs);
1439
1440
0
    status = _cairo_svg_stream_destroy (&temporary_stream);
1441
0
    if (unlikely (status)) {
1442
0
  goto cleanup;
1443
0
    }
1444
1445
0
    svg_surface->transitive_paint_used = TRUE;
1446
1447
0
    _cairo_svg_stream_printf (&document->xml_node_glyphs, "<rect");
1448
0
    _cairo_svg_stream_append_paint_dependent (&document->xml_node_glyphs,
1449
0
                source_id,
1450
0
                CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE);
1451
0
    _cairo_svg_stream_printf (&document->xml_node_glyphs,
1452
0
            " mask=\"url(#mask-%d)\"",
1453
0
            mask_id);
1454
0
    if (!use_recording_surface) {
1455
0
  _cairo_svg_surface_emit_transform (&document->xml_node_glyphs,
1456
0
             "transform",
1457
0
             &glyph_matrix,
1458
0
             NULL);
1459
0
    }
1460
0
    _cairo_svg_stream_printf (&document->xml_node_glyphs, "/>\n");
1461
1462
0
    cairo_svg_paint_t *paint_entry = _cairo_calloc (sizeof (cairo_svg_paint_t));
1463
0
    if (paint_entry == NULL) {
1464
0
  status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
1465
0
  goto cleanup;
1466
0
    }
1467
0
    paint_entry->source_id = source_id;
1468
0
    paint_entry->box.p1.x = 0;
1469
0
    paint_entry->box.p1.y = 0;
1470
0
    paint_entry->box.p2.x = glyph_image_surface->width;
1471
0
    paint_entry->box.p2.y = glyph_image_surface->height;
1472
0
    if (use_recording_surface) {
1473
0
  _cairo_matrix_transform_bounding_box (&glyph_matrix,
1474
0
                &paint_entry->box.p1.x, &paint_entry->box.p1.y,
1475
0
                &paint_entry->box.p2.x, &paint_entry->box.p2.y,
1476
0
                NULL);
1477
0
    }
1478
0
    _cairo_svg_paint_box_add_padding (&paint_entry->box);
1479
0
    _cairo_array_init (&paint_entry->paint_elements, sizeof (cairo_svg_paint_element_t));
1480
0
    _cairo_svg_paint_init_key (paint_entry);
1481
0
    status = _cairo_hash_table_insert (document->paints, &paint_entry->base);
1482
0
    if (unlikely (status)) {
1483
0
  goto cleanup;
1484
0
    }
1485
1486
0
    cleanup:
1487
0
    if (status == CAIRO_STATUS_SUCCESS) {
1488
0
  status = cairo_surface_status (paginated_surface);
1489
0
    }
1490
0
    cairo_surface_destroy (paginated_surface);
1491
1492
0
    if (extracted_image != NULL) {
1493
0
  _cairo_surface_release_source_image (extracted_surface, extracted_image, extracted_image_extra);
1494
0
    }
1495
1496
0
    return status;
1497
0
}
1498
1499
static cairo_int_status_t
1500
_cairo_svg_document_emit_glyph (cairo_svg_document_t  *document,
1501
        cairo_scaled_font_t *scaled_font,
1502
        unsigned long    scaled_font_glyph_index,
1503
        unsigned int     font_id,
1504
        unsigned int     subset_glyph_index)
1505
0
{
1506
0
    cairo_int_status_t       status;
1507
1508
0
    _cairo_svg_stream_printf (&document->xml_node_glyphs,
1509
0
            "<g id=\"glyph-%d-%d\">\n",
1510
0
            font_id,
1511
0
            subset_glyph_index);
1512
1513
0
    status = _cairo_svg_document_emit_outline_glyph_data (document,
1514
0
                scaled_font,
1515
0
                scaled_font_glyph_index);
1516
0
    if (status == CAIRO_INT_STATUS_UNSUPPORTED)
1517
0
  status = _cairo_svg_document_emit_bitmap_glyph_data (document,
1518
0
                   scaled_font,
1519
0
                   scaled_font_glyph_index);
1520
0
    if (unlikely (status))
1521
0
  return status;
1522
1523
0
    _cairo_svg_stream_printf (&document->xml_node_glyphs, "</g>\n");
1524
1525
0
    return CAIRO_INT_STATUS_SUCCESS;
1526
0
}
1527
1528
static cairo_int_status_t
1529
_cairo_svg_document_emit_font_subset (cairo_scaled_font_subset_t  *font_subset,
1530
              void        *closure)
1531
0
{
1532
0
    cairo_svg_document_t *document = closure;
1533
0
    cairo_int_status_t status = CAIRO_INT_STATUS_SUCCESS;
1534
0
    unsigned int i;
1535
1536
0
    _cairo_scaled_font_freeze_cache (font_subset->scaled_font);
1537
0
    for (i = 0; i < font_subset->num_glyphs; i++) {
1538
0
  status = _cairo_svg_document_emit_glyph (document,
1539
0
                   font_subset->scaled_font,
1540
0
                   font_subset->glyphs[i],
1541
0
                   font_subset->font_id, i);
1542
0
  if (unlikely (status))
1543
0
      break;
1544
0
    }
1545
0
    _cairo_scaled_font_thaw_cache (font_subset->scaled_font);
1546
1547
0
    return status;
1548
0
}
1549
1550
static cairo_status_t
1551
_cairo_svg_document_emit_font_subsets (cairo_svg_document_t *document)
1552
2
{
1553
2
    cairo_status_t status;
1554
1555
2
    status = _cairo_scaled_font_subsets_foreach_scaled (document->font_subsets,
1556
2
                                                        _cairo_svg_document_emit_font_subset,
1557
2
                                                        document);
1558
2
    _cairo_scaled_font_subsets_destroy (document->font_subsets);
1559
2
    document->font_subsets = NULL;
1560
1561
2
    return status;
1562
2
}
1563
1564
static cairo_bool_t
1565
_cairo_svg_surface_are_operation_and_pattern_supported (cairo_svg_surface_t *surface,
1566
              cairo_operator_t op,
1567
              const cairo_pattern_t *pattern)
1568
0
{
1569
0
    if (surface->force_fallbacks) {
1570
0
  return FALSE;
1571
0
    }
1572
1573
0
    if (op == CAIRO_OPERATOR_SATURATE) {
1574
0
        return FALSE;
1575
0
    }
1576
1577
    /* SVG 1.1 does not support these operators. We already have code for them for SVG 2
1578
     * that can be enabled when SVG 2 becomes widespread.  */
1579
0
    if (op == CAIRO_OPERATOR_OVERLAY ||
1580
0
  op == CAIRO_OPERATOR_COLOR_DODGE ||
1581
0
  op == CAIRO_OPERATOR_COLOR_BURN ||
1582
0
  op == CAIRO_OPERATOR_HARD_LIGHT ||
1583
0
  op == CAIRO_OPERATOR_SOFT_LIGHT ||
1584
0
  op == CAIRO_OPERATOR_DIFFERENCE ||
1585
0
  op == CAIRO_OPERATOR_EXCLUSION ||
1586
0
  op == CAIRO_OPERATOR_HSL_HUE ||
1587
0
  op == CAIRO_OPERATOR_HSL_SATURATION ||
1588
0
  op == CAIRO_OPERATOR_HSL_COLOR ||
1589
0
  op == CAIRO_OPERATOR_HSL_LUMINOSITY) {
1590
0
  return FALSE;
1591
0
    }
1592
1593
0
    if (pattern->type == CAIRO_PATTERN_TYPE_SURFACE) {
1594
        /* Do not cause stack overflow because of too deep or infinite recording surfaces. */
1595
0
  if (((cairo_surface_pattern_t *) pattern)->surface->type == CAIRO_SURFACE_TYPE_RECORDING &&
1596
0
      surface->depth > 1000) {
1597
0
      return FALSE;
1598
0
  }
1599
  /* SVG doesn't support extends reflect and pad for surface pattern. */
1600
0
        if (pattern->extend != CAIRO_EXTEND_NONE && pattern->extend != CAIRO_EXTEND_REPEAT) {
1601
0
      return FALSE;
1602
0
  }
1603
0
    }
1604
1605
    /* SVG 1.1 does not support the focal point (fx, fy) that is outside of the circle defined by (cx, cy) and r. */
1606
0
    if (pattern->type == CAIRO_PATTERN_TYPE_RADIAL) {
1607
0
  cairo_radial_pattern_t *radial_pattern = (cairo_radial_pattern_t *) pattern;
1608
0
  double max_radius;
1609
0
  if (radial_pattern->cd1.radius > radial_pattern->cd2.radius) {
1610
0
      max_radius = radial_pattern->cd1.radius;
1611
0
  } else {
1612
0
      max_radius = radial_pattern->cd2.radius;
1613
0
  }
1614
0
  cairo_point_double_t c1 = radial_pattern->cd1.center;
1615
0
  cairo_point_double_t c2 = radial_pattern->cd2.center;
1616
0
  if ((c1.x - c2.x) * (c1.x - c2.x) + (c1.y - c2.y) * (c1.y - c2.y) >= max_radius * max_radius) {
1617
0
      return FALSE;
1618
0
  }
1619
0
    }
1620
1621
0
    if (pattern->type == CAIRO_PATTERN_TYPE_MESH) {
1622
0
  return FALSE;
1623
0
    }
1624
1625
0
    if (pattern->type == CAIRO_PATTERN_TYPE_RASTER_SOURCE) {
1626
0
  return FALSE;
1627
0
    }
1628
1629
0
    return TRUE;
1630
0
}
1631
1632
static cairo_status_t
1633
_cairo_svg_surface_finish (void *abstract_surface)
1634
2
{
1635
2
    cairo_status_t status, final_status;
1636
2
    cairo_svg_surface_t *surface = abstract_surface;
1637
1638
2
    if (_cairo_paginated_surface_get_target (surface->document->owner) == &surface->base) {
1639
2
  final_status = _cairo_svg_document_finish (surface->document);
1640
2
    } else {
1641
0
  final_status = CAIRO_STATUS_SUCCESS;
1642
0
    }
1643
1644
2
    status = _cairo_svg_stream_destroy (&surface->xml_node);
1645
2
    if (final_status == CAIRO_STATUS_SUCCESS) {
1646
2
  final_status = status;
1647
2
    }
1648
1649
4
    for (unsigned int i = 0; i < surface->page_set.num_elements; i++) {
1650
2
  cairo_svg_page_t *page = _cairo_array_index (&surface->page_set, i);
1651
2
  status = _cairo_svg_stream_destroy (&page->xml_node);
1652
2
  if (final_status == CAIRO_STATUS_SUCCESS) {
1653
2
      final_status = status;
1654
2
  }
1655
2
    }
1656
2
    _cairo_array_fini (&surface->page_set);
1657
1658
2
    _cairo_surface_clipper_reset (&surface->clipper);
1659
1660
2
    _cairo_hash_table_foreach (surface->source_surfaces, _cairo_svg_source_surface_pluck, surface->source_surfaces);
1661
2
    _cairo_hash_table_destroy (surface->source_surfaces);
1662
1663
2
    status = _cairo_svg_document_destroy (surface->document);
1664
2
    if (final_status == CAIRO_STATUS_SUCCESS) {
1665
2
  final_status = status;
1666
2
    }
1667
1668
2
    return final_status;
1669
2
}
1670
1671
static const char *
1672
_cairo_svg_surface_emit_static_filter (cairo_svg_document_t *document, enum cairo_svg_filter filter)
1673
0
{
1674
0
    if (!document->filters_emitted[filter]) {
1675
0
  document->filters_emitted[filter] = TRUE;
1676
0
  if (filter == CAIRO_SVG_FILTER_REMOVE_COLOR) {
1677
      // (r, g, b, a) -> (1, 1, 1, a)
1678
0
      _cairo_svg_stream_printf (&document->xml_node_filters,
1679
0
              "<filter id=\"filter-remove-color\" "
1680
0
              "x=\"0%%\" y=\"0%%\" width=\"100%%\" height=\"100%%\">\n"
1681
0
              "<feColorMatrix color-interpolation-filters=\"sRGB\" "
1682
0
                                "values=\"0 0 0 0 1 "
1683
0
              /*    */ "0 0 0 0 1 "
1684
0
              /*    */ "0 0 0 0 1 "
1685
0
              /*    */ "0 0 0 1 0\" />\n"
1686
0
              "</filter>\n");
1687
0
  } else if (filter == CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA) {
1688
      // (r, g, b, a) -> (1, 1, 1, 1 - a)
1689
0
      _cairo_svg_stream_printf (&document->xml_node_filters,
1690
0
              "<filter id=\"filter-remove-color-and-invert-alpha\" "
1691
0
              "x=\"0%%\" y=\"0%%\" width=\"100%%\" height=\"100%%\">\n"
1692
0
              "<feColorMatrix color-interpolation-filters=\"sRGB\" "
1693
0
              "values=\"0 0 0 0 1 "
1694
0
              /*    */ "0 0 0 0 1 "
1695
0
              /*    */ "0 0 0 0 1 "
1696
0
              /*    */ "0 0 0 -1 1\"/>\n"
1697
0
              "</filter>\n");
1698
0
  } else if (filter ==  CAIRO_SVG_FILTER_COLOR_TO_ALPHA) {
1699
      // (r, g, b, a) -> (1, 1, 1, 0.2126 * r + 0.7152 * g + 0.0722 * b)
1700
0
      _cairo_svg_stream_printf (&document->xml_node_filters,
1701
0
              "<filter id=\"filter-color-to-alpha\" "
1702
0
              "x=\"0%%\" y=\"0%%\" width=\"100%%\" height=\"100%%\">\n"
1703
0
              "<feColorMatrix color-interpolation-filters=\"sRGB\" "
1704
0
              "values=\"0 0 0 0 1 "
1705
0
              /*    */ "0 0 0 0 1 "
1706
0
              /*    */ "0 0 0 0 1 "
1707
0
              /*    */ "0.2126 0.7152 0.0722 0 0\"/>\n"
1708
0
              "</filter>\n");
1709
0
  }
1710
0
    }
1711
1712
0
    if (filter == CAIRO_SVG_FILTER_REMOVE_COLOR) {
1713
0
  return "remove-color";
1714
0
    } else if (filter == CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA) {
1715
0
  return "remove-color-and-invert-alpha";
1716
0
    } else if (filter ==  CAIRO_SVG_FILTER_COLOR_TO_ALPHA) {
1717
0
  return "color-to-alpha";
1718
0
    } else {
1719
0
  ASSERT_NOT_REACHED;
1720
0
    }
1721
0
    return FALSE; /* squelch warning */
1722
0
}
1723
1724
#define _CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER(operation) \
1725
0
    _cairo_svg_stream_printf (&surface->document->xml_node_filters, \
1726
0
                              "<filter id=\"filter-%d\" " \
1727
0
                              "x=\"0%%\" y=\"0%%\" width=\"100%%\" height=\"100%%\">\n" \
1728
0
                              "<feImage xlink:href=\"#compositing-group-%d\" result=\"source\"", \
1729
0
                              filter_id, \
1730
0
                              source_compositing_group_id); \
1731
0
    _cairo_svg_stream_append_paint_dependent (&surface->document->xml_node_filters, \
1732
0
                                              surface->source_id, \
1733
0
                                              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); \
1734
0
    _cairo_svg_stream_printf (&surface->document->xml_node_filters, \
1735
0
                              "/>\n" \
1736
0
                              "<feImage xlink:href=\"#compositing-group-%d\" result=\"destination\"", \
1737
0
                              destination_compositing_group_id); \
1738
0
    _cairo_svg_stream_append_paint_dependent (&surface->document->xml_node_filters, \
1739
0
                                              surface->source_id, \
1740
0
                                              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); \
1741
0
    _cairo_svg_stream_printf (&surface->document->xml_node_filters, \
1742
0
                              "/>\n" \
1743
0
                              "<feComposite in=\"source\" in2=\"destination\" " \
1744
0
                              "operator=\"" operation "\" " \
1745
0
                              "color-interpolation-filters=\"sRGB\"/>\n" \
1746
0
                              "</filter>\n", \
1747
0
                              filter_id, \
1748
0
                              source_compositing_group_id, \
1749
0
                              destination_compositing_group_id);
1750
1751
#define _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER(mode) \
1752
0
    _cairo_svg_stream_printf (&surface->document->xml_node_filters, \
1753
0
                              "<filter id=\"filter-%d\" " \
1754
0
                              "x=\"0%%\" y=\"0%%\" width=\"100%%\" height=\"100%%\">\n" \
1755
0
                              "<feImage xlink:href=\"#compositing-group-%d\" result=\"source\"", \
1756
0
                              filter_id, \
1757
0
                              source_compositing_group_id); \
1758
0
    _cairo_svg_stream_append_paint_dependent (&surface->document->xml_node_filters, \
1759
0
                                              surface->source_id, \
1760
0
                                              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); \
1761
0
    _cairo_svg_stream_printf (&surface->document->xml_node_filters, \
1762
0
                              "/>\n" \
1763
0
                              "<feImage xlink:href=\"#compositing-group-%d\" result=\"destination\"", \
1764
0
                              destination_compositing_group_id); \
1765
0
    _cairo_svg_stream_append_paint_dependent (&surface->document->xml_node_filters, \
1766
0
                                              surface->source_id, \
1767
0
                                              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN); \
1768
0
    _cairo_svg_stream_printf (&surface->document->xml_node_filters, \
1769
0
                              "/>\n" \
1770
0
                              "<feBlend in=\"source\" in2=\"destination\" " \
1771
0
                              "mode=\"" mode "\" " \
1772
0
                              "color-interpolation-filters=\"sRGB\"/>\n" \
1773
0
                              "</filter>\n", \
1774
0
                              filter_id, \
1775
0
                              source_compositing_group_id, \
1776
0
                              destination_compositing_group_id);
1777
1778
static unsigned int
1779
_cairo_svg_surface_emit_parametric_filter (cairo_svg_surface_t *surface,
1780
             enum cairo_svg_filter filter,
1781
             unsigned int source_compositing_group_id,
1782
             unsigned int destination_compositing_group_id)
1783
0
{
1784
0
    unsigned int filter_id = surface->document->filter_id++;
1785
0
    switch (filter) {
1786
0
    case CAIRO_SVG_FILTER_OVER:
1787
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("over")
1788
0
  break;
1789
0
    case CAIRO_SVG_FILTER_IN:
1790
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("in")
1791
0
  break;
1792
0
    case CAIRO_SVG_FILTER_OUT:
1793
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("out")
1794
0
  break;
1795
0
    case CAIRO_SVG_FILTER_ATOP:
1796
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("atop")
1797
0
  break;
1798
0
    case CAIRO_SVG_FILTER_XOR:
1799
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_COMPOSITE_FILTER ("xor")
1800
0
  break;
1801
0
    case CAIRO_SVG_FILTER_ADD:
1802
  // This can also be done with <feComposite operator="lighter"/>, but it is not in SVG 1.1
1803
0
  _cairo_svg_stream_printf (&surface->document->xml_node_filters,
1804
0
          "<filter id=\"filter-%d\" "
1805
0
          "x=\"0%%\" y=\"0%%\" width=\"100%%\" height=\"100%%\">\n"
1806
0
          "<feImage xlink:href=\"#compositing-group-%d\" result=\"source\"",
1807
0
          filter_id,
1808
0
          source_compositing_group_id);
1809
0
  _cairo_svg_stream_append_paint_dependent (&surface->document->xml_node_filters,
1810
0
              surface->source_id,
1811
0
              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN);
1812
0
  _cairo_svg_stream_printf (&surface->document->xml_node_filters,
1813
0
          "/>\n"
1814
0
          "<feImage xlink:href=\"#compositing-group-%d\" result=\"destination\"",
1815
0
          destination_compositing_group_id);
1816
0
  _cairo_svg_stream_append_paint_dependent (&surface->document->xml_node_filters,
1817
0
              surface->source_id,
1818
0
              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN);
1819
0
  _cairo_svg_stream_printf (&surface->document->xml_node_filters,
1820
0
          "/>\n"
1821
0
          "<feComposite in=\"source\" in2=\"destination\" "
1822
0
          "operator=\"arithmetic\" k1=\"0\" k2=\"1\" k3=\"1\" k4=\"0\" "
1823
0
          "color-interpolation-filters=\"sRGB\"/>\n"
1824
0
          "</filter>\n");
1825
0
  break;
1826
0
    case CAIRO_SVG_FILTER_MULTIPLY:
1827
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("multiply")
1828
0
  break;
1829
0
    case CAIRO_SVG_FILTER_SCREEN:
1830
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("screen")
1831
0
  break;
1832
0
    case CAIRO_SVG_FILTER_OVERLAY:
1833
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("overlay")
1834
0
  break;
1835
0
    case CAIRO_SVG_FILTER_DARKEN:
1836
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("darken")
1837
0
  break;
1838
0
    case CAIRO_SVG_FILTER_LIGHTEN:
1839
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("lighten")
1840
0
  break;
1841
0
    case CAIRO_SVG_FILTER_COLOR_DODGE:
1842
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("color-dodge")
1843
0
  break;
1844
0
    case CAIRO_SVG_FILTER_COLOR_BURN:
1845
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("color-burn")
1846
0
  break;
1847
0
    case CAIRO_SVG_FILTER_HARD_LIGHT:
1848
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("hard-light")
1849
0
  break;
1850
0
    case CAIRO_SVG_FILTER_SOFT_LIGHT:
1851
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("soft-light")
1852
0
  break;
1853
0
    case CAIRO_SVG_FILTER_DIFFERENCE:
1854
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("difference")
1855
0
  break;
1856
0
    case CAIRO_SVG_FILTER_EXCLUSION:
1857
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("exclusion")
1858
0
  break;
1859
0
    case CAIRO_SVG_FILTER_HUE:
1860
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("hue")
1861
0
  break;
1862
0
    case CAIRO_SVG_FILTER_SATURATION:
1863
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("saturation")
1864
0
  break;
1865
0
    case CAIRO_SVG_FILTER_COLOR:
1866
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("color")
1867
0
  break;
1868
0
    case CAIRO_SVG_FILTER_LUMINOSITY:
1869
0
  _CAIRO_SVG_SURFACE_OUTPUT_FE_BLEND_FILTER ("luminosity")
1870
0
  break;
1871
0
    case CAIRO_SVG_FILTER_REMOVE_COLOR:
1872
0
    case CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA:
1873
0
    case CAIRO_SVG_FILTER_COLOR_TO_ALPHA:
1874
0
    case CAIRO_SVG_FILTER_LAST_STATIC_FILTER:
1875
0
    default:
1876
0
  ASSERT_NOT_REACHED;
1877
0
    }
1878
0
    return filter_id;
1879
0
}
1880
1881
typedef struct {
1882
    cairo_svg_stream_t *output;
1883
    unsigned int in_mem;
1884
    unsigned int trailing;
1885
    unsigned char src[3];
1886
} base64_write_closure_t;
1887
1888
static char const base64_table[64] =
1889
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1890
1891
static cairo_status_t
1892
base64_write_func (void *closure,
1893
       const unsigned char *data,
1894
       unsigned int length)
1895
0
{
1896
0
    base64_write_closure_t *info = (base64_write_closure_t *) closure;
1897
0
    unsigned int i;
1898
0
    unsigned char *src;
1899
1900
0
    src = info->src;
1901
1902
0
    if (info->in_mem + length < 3) {
1903
0
  for (i = 0; i < length; i++) {
1904
0
      src[i + info->in_mem] = *data++;
1905
0
  }
1906
0
  info->in_mem += length;
1907
0
  return CAIRO_STATUS_SUCCESS;
1908
0
    }
1909
1910
0
    do {
1911
0
  unsigned char dst[4];
1912
1913
0
  for (i = info->in_mem; i < 3; i++) {
1914
0
      src[i] = *data++;
1915
0
      length--;
1916
0
  }
1917
0
  info->in_mem = 0;
1918
1919
0
  dst[0] = base64_table[src[0] >> 2];
1920
0
  dst[1] = base64_table[(src[0] & 0x03) << 4 | src[1] >> 4];
1921
0
  dst[2] = base64_table[(src[1] & 0x0f) << 2 | src[2] >> 6];
1922
0
  dst[3] = base64_table[src[2] & 0xfc >> 2];
1923
  /* Special case for the last missing bits */
1924
0
  switch (info->trailing) {
1925
0
      case 2:
1926
0
    dst[2] = '=';
1927
    /* fall through */
1928
0
      case 1:
1929
0
    dst[3] = '=';
1930
0
      default:
1931
0
    break;
1932
0
  }
1933
0
  _cairo_svg_stream_write (info->output, dst, 4);
1934
0
    } while (length >= 3);
1935
1936
0
    for (i = 0; i < length; i++) {
1937
0
  src[i] = *data++;
1938
0
    }
1939
0
    info->in_mem = length;
1940
1941
0
    return info->output->status;
1942
0
}
1943
1944
static cairo_int_status_t
1945
_cairo_surface_base64_encode_jpeg (cairo_surface_t       *surface,
1946
           cairo_svg_stream_t *output)
1947
0
{
1948
0
    const unsigned char *mime_data;
1949
0
    unsigned long mime_data_length;
1950
0
    cairo_image_info_t image_info;
1951
0
    base64_write_closure_t info;
1952
0
    cairo_status_t status;
1953
1954
0
    cairo_surface_get_mime_data (surface, CAIRO_MIME_TYPE_JPEG,
1955
0
         &mime_data, &mime_data_length);
1956
0
    if (mime_data == NULL)
1957
0
  return CAIRO_INT_STATUS_UNSUPPORTED;
1958
1959
0
    status = _cairo_image_info_get_jpeg_info (&image_info, mime_data, mime_data_length);
1960
0
    if (unlikely (status))
1961
0
  return status;
1962
1963
0
    if (image_info.num_components == 4)
1964
0
  return CAIRO_INT_STATUS_UNSUPPORTED;
1965
1966
0
    _cairo_svg_stream_printf (output, "data:image/jpeg;base64,");
1967
1968
0
    info.output = output;
1969
0
    info.in_mem = 0;
1970
0
    info.trailing = 0;
1971
1972
0
    status = base64_write_func (&info, mime_data, mime_data_length);
1973
0
    if (unlikely (status))
1974
0
  return status;
1975
1976
0
    if (info.in_mem > 0) {
1977
0
  memset (info.src + info.in_mem, 0, 3 - info.in_mem);
1978
0
  info.trailing = 3 - info.in_mem;
1979
0
  info.in_mem = 3;
1980
0
  status = base64_write_func (&info, NULL, 0);
1981
0
    }
1982
1983
0
    return status;
1984
0
}
1985
1986
static cairo_int_status_t
1987
_cairo_surface_base64_encode_png (cairo_surface_t       *surface,
1988
          cairo_svg_stream_t *output)
1989
0
{
1990
0
    const unsigned char *mime_data;
1991
0
    unsigned long mime_data_length;
1992
0
    base64_write_closure_t info;
1993
0
    cairo_status_t status;
1994
1995
0
    cairo_surface_get_mime_data (surface, CAIRO_MIME_TYPE_PNG,
1996
0
         &mime_data, &mime_data_length);
1997
0
    if (unlikely (surface->status))
1998
0
  return surface->status;
1999
0
    if (mime_data == NULL)
2000
0
  return CAIRO_INT_STATUS_UNSUPPORTED;
2001
2002
0
    _cairo_svg_stream_printf (output, "data:image/png;base64,");
2003
2004
0
    info.output = output;
2005
0
    info.in_mem = 0;
2006
0
    info.trailing = 0;
2007
2008
0
    status = base64_write_func (&info, mime_data, mime_data_length);
2009
0
    if (unlikely (status))
2010
0
  return status;
2011
2012
0
    if (info.in_mem > 0) {
2013
0
  memset (info.src + info.in_mem, 0, 3 - info.in_mem);
2014
0
  info.trailing = 3 - info.in_mem;
2015
0
  info.in_mem = 3;
2016
0
  status = base64_write_func (&info, NULL, 0);
2017
0
    }
2018
2019
0
    return status;
2020
0
}
2021
2022
static cairo_int_status_t
2023
_cairo_surface_base64_encode (cairo_surface_t       *surface,
2024
            cairo_svg_stream_t *output)
2025
0
{
2026
0
    cairo_int_status_t status;
2027
0
    base64_write_closure_t info;
2028
2029
0
    status = _cairo_surface_base64_encode_jpeg (surface, output);
2030
0
    if (status != CAIRO_INT_STATUS_UNSUPPORTED)
2031
0
  return status;
2032
2033
0
    status = _cairo_surface_base64_encode_png (surface, output);
2034
0
    if (status != CAIRO_INT_STATUS_UNSUPPORTED)
2035
0
  return status;
2036
2037
0
    info.output = output;
2038
0
    info.in_mem = 0;
2039
0
    info.trailing = 0;
2040
2041
0
    _cairo_svg_stream_printf (info.output, "data:image/png;base64,");
2042
2043
0
    status = cairo_surface_write_to_png_stream (surface, base64_write_func,
2044
0
            (void *) &info);
2045
2046
0
    if (unlikely (status))
2047
0
  return status;
2048
2049
0
    if (info.in_mem > 0) {
2050
0
  memset (info.src + info.in_mem, 0, 3 - info.in_mem);
2051
0
  info.trailing = 3 - info.in_mem;
2052
0
  info.in_mem = 3;
2053
0
  status = base64_write_func (&info, NULL, 0);
2054
0
    }
2055
2056
0
    return status;
2057
0
}
2058
2059
/**
2060
 * _cairo_svg_surface_emit_attr_value:
2061
 *
2062
 * Write the value to output the stream as a sequence of characters,
2063
 * while escaping those which have special meaning in the XML
2064
 * attribute's value context: &amp; and &quot;.
2065
 **/
2066
static void
2067
_cairo_svg_surface_emit_attr_value (cairo_svg_stream_t *stream,
2068
            const unsigned char *value,
2069
            unsigned int length)
2070
0
{
2071
0
    const unsigned char *p;
2072
0
    const unsigned char *q;
2073
0
    unsigned int i;
2074
2075
    /* we'll accumulate non-special chars in [q, p) range */
2076
0
    p = value;
2077
0
    q = p;
2078
0
    for (i = 0; i < length; i++, p++) {
2079
0
  if (*p == '&' || *p == '"') {
2080
      /* flush what's left before special char */
2081
0
      if (p != q) {
2082
0
    _cairo_svg_stream_write (stream, q, p - q);
2083
0
    q = p + 1;
2084
0
      }
2085
2086
0
      if (*p == '&')
2087
0
    _cairo_svg_stream_printf (stream, "&amp;");
2088
0
      else // p == '"'
2089
0
    _cairo_svg_stream_printf (stream, "&quot;");
2090
0
  }
2091
0
    }
2092
2093
    /* flush the trailing chars if any */
2094
0
    if (p != q)
2095
0
  _cairo_svg_stream_write (stream, q, p - q);
2096
0
}
2097
2098
static cairo_status_t
2099
_cairo_svg_surface_emit_surface (cairo_svg_document_t *document,
2100
         cairo_surface_t *surface,
2101
         unsigned int source_id)
2102
0
{
2103
0
    cairo_rectangle_int_t extents;
2104
0
    cairo_bool_t is_bounded;
2105
0
    cairo_status_t status;
2106
0
    const unsigned char *uri;
2107
0
    unsigned long uri_len;
2108
2109
0
    is_bounded = _cairo_surface_get_extents (surface, &extents);
2110
0
    assert (is_bounded);
2111
2112
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
2113
0
            "<image id=\"source-%d\" x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\"",
2114
0
            source_id,
2115
0
            extents.x, extents.y,
2116
0
            extents.width, extents.height);
2117
2118
0
    if (extents.width != 0 && extents.height != 0) {
2119
0
  _cairo_svg_stream_printf (&document->xml_node_defs, " xlink:href=\"");
2120
0
  cairo_surface_get_mime_data (surface, CAIRO_MIME_TYPE_URI,
2121
0
             &uri, &uri_len);
2122
0
  if (uri != NULL) {
2123
0
      _cairo_svg_surface_emit_attr_value (&document->xml_node_defs,
2124
0
            uri, uri_len);
2125
0
  } else {
2126
0
      status = _cairo_surface_base64_encode (surface,
2127
0
               &document->xml_node_defs);
2128
0
      if (unlikely (status))
2129
0
    return status;
2130
0
  }
2131
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "\"");
2132
0
    }
2133
2134
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "/>\n");
2135
2136
0
    return CAIRO_STATUS_SUCCESS;
2137
0
}
2138
2139
static cairo_status_t
2140
_cairo_svg_surface_emit_composite_surface_pattern (cairo_svg_stream_t *output,
2141
               cairo_svg_surface_t *surface,
2142
               cairo_surface_pattern_t *pattern,
2143
               unsigned int pattern_id,
2144
               const cairo_matrix_t *parent_matrix)
2145
0
{
2146
0
    cairo_status_t status;
2147
2148
0
    cairo_matrix_t p2u = pattern->base.matrix;
2149
0
    status = cairo_matrix_invert (&p2u);
2150
    /* cairo_pattern_set_matrix ensures the matrix is invertible */
2151
0
    assert (status == CAIRO_STATUS_SUCCESS);
2152
2153
0
    cairo_bool_t is_new;
2154
0
    cairo_svg_source_surface_t *source_surface;
2155
0
    status = _cairo_svg_surface_add_source_surface (surface,
2156
0
                pattern->surface,
2157
0
                &is_new,
2158
0
                &source_surface);
2159
0
    if (unlikely (status)) {
2160
0
  return status;
2161
0
    }
2162
0
    unsigned int source_id = source_surface->id;
2163
2164
0
    if (is_new) {
2165
0
  status = _cairo_svg_surface_emit_surface (surface->document,
2166
0
              pattern->surface,
2167
0
              source_id);
2168
0
  if (unlikely (status)) {
2169
0
      return status;
2170
0
  }
2171
0
    }
2172
2173
0
    if (pattern_id != invalid_pattern_id) {
2174
0
  cairo_rectangle_int_t extents;
2175
0
  cairo_bool_t is_bounded;
2176
2177
0
  is_bounded = _cairo_surface_get_extents (pattern->surface, &extents);
2178
0
  assert (is_bounded);
2179
2180
0
  _cairo_svg_stream_printf (output,
2181
0
          "<pattern id=\"pattern-%d\" "
2182
0
          "patternUnits=\"userSpaceOnUse\" "
2183
0
          "x=\"%d\" y=\"%d\" "
2184
0
          "width=\"%d\" height=\"%d\" "
2185
0
          "viewBox=\"%d %d %d %d\"",
2186
0
          pattern_id,
2187
0
          extents.x, extents.y,
2188
0
          extents.width, extents.height,
2189
0
          extents.x, extents.y,
2190
0
          extents.width, extents.height);
2191
0
  _cairo_svg_surface_emit_transform (output,
2192
0
             "patternTransform",
2193
0
             &p2u,
2194
0
             parent_matrix);
2195
0
  _cairo_svg_stream_printf (output, ">\n");
2196
0
    }
2197
2198
0
    _cairo_svg_stream_printf (output,
2199
0
            "<use xlink:href=\"#source-%d\"",
2200
0
            source_id);
2201
0
    if (pattern->surface->content == CAIRO_CONTENT_ALPHA) {
2202
0
  cairo_bool_t can_skip_filter = FALSE;
2203
0
  if (pattern->surface->backend &&
2204
0
      pattern->surface->backend->type == CAIRO_SURFACE_TYPE_IMAGE &&
2205
0
      (((cairo_image_surface_t *) pattern->surface)->format == CAIRO_FORMAT_A1 ||
2206
0
       ((cairo_image_surface_t *) pattern->surface)->format == CAIRO_FORMAT_A8)) {
2207
0
      can_skip_filter = TRUE;
2208
0
  }
2209
0
  if (!can_skip_filter) {
2210
0
      _cairo_svg_stream_printf (output,
2211
0
              " filter=\"url(#filter-%s)\"",
2212
0
              _cairo_svg_surface_emit_static_filter (surface->document,
2213
0
                       CAIRO_SVG_FILTER_COLOR_TO_ALPHA));
2214
0
  }
2215
0
    }
2216
0
    if (pattern_id == invalid_pattern_id) {
2217
0
  _cairo_svg_surface_emit_transform (output,
2218
0
             "transform",
2219
0
             &p2u,
2220
0
             parent_matrix);
2221
0
    }
2222
0
    _cairo_svg_stream_printf (output, "/>\n");
2223
2224
0
    if (pattern_id != invalid_pattern_id) {
2225
0
  _cairo_svg_stream_printf (output, "</pattern>\n");
2226
0
    }
2227
2228
0
    return CAIRO_STATUS_SUCCESS;
2229
0
}
2230
2231
static cairo_status_t
2232
_cairo_svg_surface_emit_recording_surface (cairo_svg_surface_t *surface,
2233
             cairo_recording_surface_t *source,
2234
             unsigned int source_id,
2235
             cairo_bool_t *transitive_paint_used)
2236
0
{
2237
0
    cairo_status_t status;
2238
0
    cairo_svg_document_t *document = surface->document;
2239
2240
0
    cairo_surface_t *paginated_surface = _cairo_svg_surface_create_for_document (document,
2241
0
                     source->base.content,
2242
0
                     0,
2243
0
                     0,
2244
0
                     FALSE);
2245
0
    cairo_svg_surface_t *svg_surface = (cairo_svg_surface_t *) _cairo_paginated_surface_get_target (paginated_surface);
2246
0
    if (unlikely (paginated_surface->status)) {
2247
0
  return paginated_surface->status;
2248
0
    }
2249
2250
0
    svg_surface->source_id = source_id;
2251
0
    svg_surface->depth = surface->depth + 1;
2252
2253
0
    cairo_rectangle_int_t extents;
2254
0
    cairo_bool_t bounded = _cairo_surface_get_extents (&source->base, &extents);
2255
2256
0
    cairo_surface_set_fallback_resolution (paginated_surface,
2257
0
             document->owner->x_fallback_resolution,
2258
0
             document->owner->y_fallback_resolution);
2259
2260
0
    if (source->base.content == CAIRO_CONTENT_COLOR) {
2261
0
  _cairo_svg_surface_emit_paint (&svg_surface->xml_node, svg_surface, &_cairo_pattern_black.base, FALSE);
2262
0
    }
2263
0
    status = _cairo_recording_surface_replay (&source->base, paginated_surface);
2264
0
    if (unlikely (status)) {
2265
0
  cairo_surface_destroy (paginated_surface);
2266
0
  return status;
2267
0
    }
2268
2269
0
    cairo_surface_show_page (paginated_surface);
2270
0
    status = cairo_surface_status (paginated_surface);
2271
0
    if (unlikely (status)) {
2272
0
  cairo_surface_destroy (paginated_surface);
2273
0
  return status;
2274
0
    }
2275
2276
0
    unsigned int clip_id;
2277
0
    if (bounded) {
2278
0
  clip_id = document->clip_id++;
2279
2280
0
  _cairo_svg_stream_printf (&document->xml_node_defs,
2281
0
          "<clipPath id=\"clip-%d\">\n"
2282
0
          "<rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\"/>\n"
2283
0
          "</clipPath>\n",
2284
0
          clip_id,
2285
0
          extents.x,
2286
0
          extents.y,
2287
0
          extents.width,
2288
0
          extents.height);
2289
0
    }
2290
2291
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
2292
0
            "<g id=\"source-%d\"",
2293
0
            source_id);
2294
2295
0
    if (bounded) {
2296
0
  _cairo_svg_stream_printf (&document->xml_node_defs,
2297
0
          " clip-path=\"url(#clip-%d)\"",
2298
0
          clip_id);
2299
0
    }
2300
2301
0
    if (source->base.content == CAIRO_CONTENT_ALPHA) {
2302
0
  _cairo_svg_stream_printf (&document->xml_node_defs,
2303
0
          " filter=\"url(#filter-%s)\"",
2304
0
          _cairo_svg_surface_emit_static_filter (document, CAIRO_SVG_FILTER_REMOVE_COLOR));
2305
0
    }
2306
2307
0
    _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
2308
2309
0
    if (svg_surface->xml_node.elements.num_elements > 0) {
2310
0
  cairo_svg_page_t *page = _cairo_svg_surface_store_page (svg_surface);
2311
0
  if (unlikely (page == NULL)) {
2312
0
      cairo_surface_destroy (paginated_surface);
2313
0
      return _cairo_error (CAIRO_STATUS_NO_MEMORY);
2314
0
  }
2315
0
    }
2316
2317
0
    if (svg_surface->page_set.num_elements > 0) {
2318
0
  cairo_svg_page_t *page = _cairo_array_index (&svg_surface->page_set, svg_surface->page_set.num_elements - 1);
2319
0
  _cairo_svg_stream_copy (&page->xml_node, &document->xml_node_defs);
2320
0
    }
2321
2322
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
2323
2324
0
    *transitive_paint_used = svg_surface->transitive_paint_used;
2325
2326
0
    status = cairo_surface_status (paginated_surface);
2327
0
    cairo_surface_destroy (paginated_surface);
2328
2329
0
    return status;
2330
0
}
2331
2332
static cairo_recording_surface_t *
2333
_cairo_svg_surface_to_recording_surface (const cairo_surface_pattern_t *pattern)
2334
0
{
2335
0
    cairo_surface_t *surface = pattern->surface;
2336
0
    if (_cairo_surface_is_paginated (surface))
2337
0
  surface = _cairo_paginated_surface_get_recording (surface);
2338
0
    if (_cairo_surface_is_snapshot (surface))
2339
0
  surface = _cairo_surface_snapshot_get_target (surface);
2340
0
    return (cairo_recording_surface_t *) surface;
2341
0
}
2342
2343
static cairo_bool_t
2344
_cairo_svg_surface_svg_pattern_should_be_used (const cairo_pattern_t *pattern)
2345
0
{
2346
0
    cairo_rectangle_int_t extents;
2347
0
    return pattern->type == CAIRO_PATTERN_TYPE_SURFACE &&
2348
0
     pattern->extend == CAIRO_EXTEND_REPEAT &&
2349
0
     _cairo_surface_get_extents (((cairo_surface_pattern_t *) pattern)->surface, &extents);
2350
0
}
2351
2352
static cairo_bool_t
2353
_cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (const cairo_pattern_t *pattern)
2354
0
{
2355
0
    return pattern->type == CAIRO_PATTERN_TYPE_SURFACE && !_cairo_svg_surface_svg_pattern_should_be_used (pattern);
2356
0
}
2357
2358
static cairo_status_t
2359
_cairo_svg_surface_emit_composite_recording_pattern (cairo_svg_stream_t *output,
2360
                 cairo_svg_surface_t *surface,
2361
                 cairo_surface_pattern_t *pattern,
2362
                 unsigned int pattern_id,
2363
                 const cairo_matrix_t *parent_matrix)
2364
0
{
2365
0
    cairo_status_t status;
2366
0
    cairo_svg_document_t *document = surface->document;
2367
2368
0
    cairo_matrix_t p2u = pattern->base.matrix;
2369
0
    status = cairo_matrix_invert (&p2u);
2370
    /* cairo_pattern_set_matrix ensures the matrix is invertible */
2371
0
    assert (status == CAIRO_STATUS_SUCCESS);
2372
2373
0
    cairo_bool_t is_new;
2374
0
    cairo_svg_source_surface_t *source_surface;
2375
0
    status = _cairo_svg_surface_add_source_surface (surface,
2376
0
                pattern->surface,
2377
0
                &is_new,
2378
0
                &source_surface);
2379
0
    if (unlikely (status)) {
2380
0
  return status;
2381
0
    }
2382
0
    unsigned int source_id = source_surface->id;
2383
2384
0
    cairo_recording_surface_t *recording_surface = _cairo_svg_surface_to_recording_surface (pattern);
2385
0
    if (is_new) {
2386
0
  status = _cairo_svg_surface_emit_recording_surface (surface,
2387
0
                  recording_surface,
2388
0
                  source_id,
2389
0
                  &source_surface->transitive_paint_used);
2390
0
  if (unlikely (status)) {
2391
0
      return status;
2392
0
  }
2393
2394
0
  if (source_surface->transitive_paint_used) {
2395
0
      cairo_svg_paint_t *paint_entry = _cairo_calloc (sizeof (cairo_svg_paint_t));
2396
0
      if (paint_entry == NULL) {
2397
0
    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
2398
0
      }
2399
0
      paint_entry->source_id = source_id;
2400
0
      _cairo_array_init (&paint_entry->paint_elements, sizeof (cairo_svg_paint_element_t));
2401
0
      _cairo_svg_paint_init_key (paint_entry);
2402
0
      status = _cairo_hash_table_insert (document->paints, &paint_entry->base);
2403
0
      if (unlikely (status)) {
2404
0
    return status;
2405
0
      }
2406
0
  }
2407
0
    }
2408
2409
0
    if (source_surface->transitive_paint_used) {
2410
0
  cairo_svg_paint_t paint_key;
2411
0
  paint_key.source_id = source_id;
2412
0
  _cairo_svg_paint_init_key (&paint_key);
2413
2414
0
  cairo_svg_paint_t *found_paint_entry = _cairo_hash_table_lookup (document->paints,
2415
0
                   &paint_key.base);
2416
0
  assert (found_paint_entry);
2417
2418
0
  cairo_svg_paint_element_t paint_element;
2419
0
  paint_element.source_id = surface->source_id;
2420
0
  paint_element.matrix = pattern->base.matrix;
2421
0
  if (parent_matrix != NULL) {
2422
0
      cairo_matrix_t parent_matrix_inverse = *parent_matrix;
2423
0
      status = cairo_matrix_invert (&parent_matrix_inverse);
2424
      /* cairo_pattern_set_matrix ensures the matrix is invertible */
2425
0
      assert (status == CAIRO_STATUS_SUCCESS);
2426
0
      cairo_matrix_multiply (&paint_element.matrix, &parent_matrix_inverse, &paint_element.matrix);
2427
0
  }
2428
0
  status = _cairo_array_append (&found_paint_entry->paint_elements, &paint_element);
2429
0
  if (unlikely (status)) {
2430
0
      return status;
2431
0
  }
2432
2433
0
  surface->transitive_paint_used = TRUE;
2434
0
    }
2435
2436
0
    if (pattern_id != invalid_pattern_id) {
2437
0
  assert (!recording_surface->unbounded);
2438
0
  _cairo_svg_stream_printf (output,
2439
0
          "<pattern id=\"pattern-%d\" "
2440
0
          "patternUnits=\"userSpaceOnUse\" "
2441
0
          "x=\"%f\" y=\"%f\" width=\"%f\" height=\"%f\" "
2442
0
          "viewBox=\"%f %f %f %f\"",
2443
0
          pattern_id,
2444
0
          recording_surface->extents_pixels.x,
2445
0
          recording_surface->extents_pixels.y,
2446
0
          recording_surface->extents_pixels.width,
2447
0
          recording_surface->extents_pixels.height,
2448
0
          recording_surface->extents_pixels.x,
2449
0
          recording_surface->extents_pixels.y,
2450
0
          recording_surface->extents_pixels.width,
2451
0
          recording_surface->extents_pixels.height);
2452
0
  _cairo_svg_surface_emit_transform (output, "patternTransform", &p2u, parent_matrix);
2453
0
  _cairo_svg_stream_printf (output, ">\n");
2454
0
    }
2455
2456
0
    _cairo_svg_stream_printf (output,
2457
0
            "<use xlink:href=\"#source-%d\"",
2458
0
            source_id);
2459
2460
0
    if (pattern_id == invalid_pattern_id) {
2461
0
  _cairo_svg_surface_emit_transform (output, "transform", &p2u, parent_matrix);
2462
0
    }
2463
2464
0
    _cairo_svg_stream_printf (output, "/>\n");
2465
2466
0
    if (pattern_id != invalid_pattern_id) {
2467
0
  _cairo_svg_stream_printf (output, "</pattern>\n");
2468
0
    }
2469
2470
0
    return CAIRO_STATUS_SUCCESS;
2471
0
}
2472
2473
static cairo_status_t
2474
_cairo_svg_surface_emit_composite_pattern (cairo_svg_stream_t *output,
2475
             cairo_svg_surface_t *surface,
2476
             cairo_surface_pattern_t *pattern,
2477
             unsigned int pattern_id,
2478
             const cairo_matrix_t *parent_matrix)
2479
0
{
2480
0
    if (pattern_id != invalid_pattern_id) {
2481
0
  assert (_cairo_svg_surface_svg_pattern_should_be_used (&pattern->base));
2482
0
    }
2483
2484
0
    if (pattern->surface->type == CAIRO_SURFACE_TYPE_RECORDING) {
2485
0
  return _cairo_svg_surface_emit_composite_recording_pattern (output,
2486
0
                    surface,
2487
0
                    pattern,
2488
0
                    pattern_id,
2489
0
                    parent_matrix);
2490
0
    } else {
2491
0
  return _cairo_svg_surface_emit_composite_surface_pattern (output,
2492
0
                  surface,
2493
0
                  pattern,
2494
0
                  pattern_id,
2495
0
                  parent_matrix);
2496
0
    }
2497
0
}
2498
2499
static cairo_status_t
2500
_cairo_svg_surface_emit_solid_pattern (cairo_svg_surface_t *surface,
2501
               cairo_solid_pattern_t *pattern,
2502
               cairo_svg_stream_t *output,
2503
               cairo_bool_t is_stroke)
2504
0
{
2505
0
    _cairo_svg_stream_printf (output,
2506
0
            is_stroke ? " stroke=\"rgb(%f%%, %f%%, %f%%)\" stroke-opacity=\"%f\""
2507
0
          : " fill=\"rgb(%f%%, %f%%, %f%%)\" fill-opacity=\"%f\"",
2508
0
            pattern->color.red * 100.0,
2509
0
            pattern->color.green * 100.0,
2510
0
            pattern->color.blue * 100.0,
2511
0
            pattern->color.alpha);
2512
2513
0
    return CAIRO_STATUS_SUCCESS;
2514
0
}
2515
2516
static cairo_status_t
2517
_cairo_svg_surface_emit_surface_pattern (cairo_svg_surface_t *surface,
2518
           cairo_surface_pattern_t *pattern,
2519
           cairo_svg_stream_t *output,
2520
           cairo_bool_t is_stroke,
2521
           const cairo_matrix_t *parent_matrix)
2522
0
{
2523
0
    cairo_svg_document_t *document = surface->document;
2524
0
    cairo_status_t status;
2525
2526
0
    unsigned int pattern_id = document->pattern_id++;
2527
2528
0
    status = _cairo_svg_surface_emit_composite_pattern (&document->xml_node_defs,
2529
0
              surface,
2530
0
              pattern,
2531
0
              pattern_id,
2532
0
              parent_matrix);
2533
0
    if (unlikely (status))
2534
0
  return status;
2535
2536
0
    _cairo_svg_stream_printf (output,
2537
0
            is_stroke ? " stroke=\"url(#pattern-%d)\""
2538
0
          : " fill=\"url(#pattern-%d)\"",
2539
0
            pattern_id);
2540
2541
0
    return CAIRO_STATUS_SUCCESS;
2542
0
}
2543
2544
static cairo_status_t
2545
_cairo_svg_surface_emit_pattern_stops (cairo_svg_stream_t *output,
2546
               const cairo_gradient_pattern_t *pattern,
2547
               double start_offset,
2548
               cairo_bool_t reverse_stops,
2549
               cairo_bool_t emulate_reflect)
2550
0
{
2551
0
    cairo_gradient_stop_t *stops;
2552
0
    unsigned int n_stops;
2553
2554
0
    if (pattern->n_stops < 1) {
2555
0
  return CAIRO_STATUS_SUCCESS;
2556
0
    }
2557
2558
0
    if (pattern->n_stops == 1) {
2559
0
  _cairo_svg_stream_printf (output,
2560
0
          "<stop offset=\"%f\" "
2561
0
          "stop-color=\"rgb(%f%%, %f%%, %f%%)\" "
2562
0
          "stop-opacity=\"%f\"/>\n",
2563
0
          pattern->stops[0].offset,
2564
0
          pattern->stops[0].color.red * 100.0,
2565
0
          pattern->stops[0].color.green * 100.0,
2566
0
          pattern->stops[0].color.blue * 100.0,
2567
0
          pattern->stops[0].color.alpha);
2568
0
  return CAIRO_STATUS_SUCCESS;
2569
0
    }
2570
2571
0
    if (emulate_reflect || reverse_stops) {
2572
0
  n_stops = emulate_reflect ? pattern->n_stops * 2 - 2 : pattern->n_stops;
2573
0
  stops = _cairo_malloc_ab (n_stops, sizeof (cairo_gradient_stop_t));
2574
0
  if (unlikely (stops == NULL))
2575
0
      return _cairo_error (CAIRO_STATUS_NO_MEMORY);
2576
2577
0
  for (unsigned int i = 0; i < pattern->n_stops; i++) {
2578
0
      if (reverse_stops) {
2579
0
    stops[i] = pattern->stops[pattern->n_stops - i - 1];
2580
0
    stops[i].offset = 1.0 - stops[i].offset;
2581
0
      } else {
2582
0
    stops[i] = pattern->stops[i];
2583
0
      }
2584
0
      if (emulate_reflect) {
2585
0
    stops[i].offset *= 0.5;
2586
0
    if (i > 0 && i < pattern->n_stops - 1) {
2587
0
        if (reverse_stops) {
2588
0
      stops[i + pattern->n_stops - 1] = pattern->stops[i];
2589
0
      stops[i + pattern->n_stops - 1].offset = 0.5 + 0.5 * stops[i + pattern->n_stops - 1].offset;
2590
0
        } else {
2591
0
      stops[i + pattern->n_stops - 1] = pattern->stops[pattern->n_stops - i - 1];
2592
0
      stops[i + pattern->n_stops - 1].offset = 1.0 - 0.5 * stops[i + pattern->n_stops - 1].offset;
2593
0
        }
2594
0
    }
2595
0
      }
2596
0
  }
2597
0
    } else {
2598
0
  n_stops = pattern->n_stops;
2599
0
  stops = pattern->stops;
2600
0
    }
2601
2602
0
    if (start_offset >= 0.0) {
2603
0
  for (unsigned int i = 0; i < n_stops; i++) {
2604
0
      _cairo_svg_stream_printf (output,
2605
0
              "<stop offset=\"%f\" "
2606
0
              "stop-color=\"rgb(%f%%, %f%%, %f%%)\" "
2607
0
              "stop-opacity=\"%f\"/>\n",
2608
0
              start_offset + (1.0 - start_offset) * stops[i].offset,
2609
0
              stops[i].color.red * 100.0,
2610
0
              stops[i].color.green * 100.0,
2611
0
              stops[i].color.blue * 100.0,
2612
0
              stops[i].color.alpha);
2613
0
  }
2614
0
    } else {
2615
0
  cairo_bool_t found = FALSE;
2616
0
  unsigned int offset_index;
2617
0
  cairo_color_stop_t offset_color_start, offset_color_stop;
2618
2619
0
  for (unsigned int i = 0; i <= n_stops; i++) {
2620
0
      double x1 = i == n_stops ? stops[0].offset + 1 : stops[i].offset;
2621
0
      cairo_color_stop_t *color1 = i == n_stops ? &stops[0].color : &stops[i].color;
2622
0
      if (x1 >= -start_offset) {
2623
0
    if (i > 0) {
2624
0
        double x0 = stops[i - 1].offset;
2625
0
        cairo_color_stop_t *color0 = &stops[i - 1].color;
2626
0
        if (x0 != x1) {
2627
0
      offset_color_start.red = color0->red + (color1->red - color0->red)
2628
0
                     * (-start_offset - x0) / (x1 - x0);
2629
0
      offset_color_start.green = color0->green + (color1->green - color0->green)
2630
0
                   * (-start_offset - x0) / (x1 - x0);
2631
0
      offset_color_start.blue = color0->blue + (color1->blue - color0->blue)
2632
0
                 * (-start_offset - x0) / (x1 - x0);
2633
0
      offset_color_start.alpha = color0->alpha + (color1->alpha - color0->alpha)
2634
0
                   * (-start_offset - x0) / (x1 - x0);
2635
0
      offset_color_stop = offset_color_start;
2636
0
        } else {
2637
0
      offset_color_stop = stops[i - 1].color;
2638
0
      offset_color_start = stops[i].color;
2639
0
        }
2640
0
    } else {
2641
0
        offset_color_stop = offset_color_start = stops[i].color;
2642
0
    }
2643
0
    offset_index = i;
2644
0
    found = TRUE;
2645
0
    break;
2646
0
      }
2647
0
  }
2648
2649
0
  if (!found) {
2650
0
      offset_index = n_stops - 1;
2651
0
      offset_color_stop = offset_color_start = stops[offset_index].color;
2652
0
  }
2653
2654
0
  _cairo_svg_stream_printf (output,
2655
0
          "<stop offset=\"0\" "
2656
0
          "stop-color=\"rgb(%f%%, %f%%, %f%%)\" "
2657
0
          "stop-opacity=\"%f\"/>\n",
2658
0
          offset_color_start.red * 100.0,
2659
0
          offset_color_start.green * 100.0,
2660
0
          offset_color_start.blue * 100.0,
2661
0
          offset_color_start.alpha);
2662
0
  for (unsigned int i = offset_index; i < n_stops; i++) {
2663
0
      _cairo_svg_stream_printf (output,
2664
0
              "<stop offset=\"%f\" "
2665
0
              "stop-color=\"rgb(%f%%, %f%%, %f%%)\" "
2666
0
              "stop-opacity=\"%f\"/>\n",
2667
0
              stops[i].offset + start_offset,
2668
0
              stops[i].color.red * 100.0,
2669
0
              stops[i].color.green * 100.0,
2670
0
              stops[i].color.blue * 100.0,
2671
0
              stops[i].color.alpha);
2672
0
  }
2673
0
  for (unsigned int i = 0; i < offset_index; i++) {
2674
0
      _cairo_svg_stream_printf (output,
2675
0
              "<stop offset=\"%f\" "
2676
0
              "stop-color=\"rgb(%f%%, %f%%, %f%%)\" "
2677
0
              "stop-opacity=\"%f\"/>\n",
2678
0
              1.0 + stops[i].offset + start_offset,
2679
0
              stops[i].color.red * 100.0,
2680
0
              stops[i].color.green * 100.0,
2681
0
              stops[i].color.blue * 100.0,
2682
0
              stops[i].color.alpha);
2683
0
  }
2684
2685
0
  _cairo_svg_stream_printf (output,
2686
0
          "<stop offset=\"1\" "
2687
0
          "stop-color=\"rgb(%f%%, %f%%, %f%%)\" "
2688
0
          "stop-opacity=\"%f\"/>\n",
2689
0
          offset_color_stop.red * 100.0,
2690
0
          offset_color_stop.green * 100.0,
2691
0
          offset_color_stop.blue * 100.0,
2692
0
          offset_color_stop.alpha);
2693
2694
0
    }
2695
2696
0
    if (reverse_stops || emulate_reflect) {
2697
0
  free (stops);
2698
0
    }
2699
2700
0
    return CAIRO_STATUS_SUCCESS;
2701
0
}
2702
2703
static void
2704
_cairo_svg_surface_emit_pattern_extend (cairo_svg_stream_t *output,
2705
          cairo_pattern_t *pattern)
2706
0
{
2707
0
    switch (pattern->extend) {
2708
0
    case CAIRO_EXTEND_REPEAT:
2709
0
  _cairo_svg_stream_printf (output, " spreadMethod=\"repeat\"");
2710
0
  break;
2711
0
    case CAIRO_EXTEND_REFLECT:
2712
0
  _cairo_svg_stream_printf (output, " spreadMethod=\"reflect\"");
2713
0
  break;
2714
0
    case CAIRO_EXTEND_NONE:
2715
0
    case CAIRO_EXTEND_PAD:
2716
0
  break;
2717
0
    }
2718
0
}
2719
2720
static cairo_status_t
2721
_cairo_svg_surface_emit_linear_pattern (cairo_svg_surface_t *surface,
2722
          cairo_linear_pattern_t *pattern,
2723
          cairo_svg_stream_t *output,
2724
          cairo_bool_t is_stroke,
2725
          const cairo_matrix_t *parent_matrix)
2726
0
{
2727
0
    cairo_status_t status;
2728
0
    cairo_svg_document_t *document = surface->document;
2729
2730
0
    cairo_matrix_t p2u = pattern->base.base.matrix;
2731
0
    status = cairo_matrix_invert (&p2u);
2732
    /* cairo_pattern_set_matrix ensures the matrix is invertible */
2733
0
    assert (status == CAIRO_STATUS_SUCCESS);
2734
2735
0
    unsigned int linear_pattern_id = document->linear_pattern_id++;
2736
2737
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
2738
0
            "<linearGradient id=\"linear-pattern-%d\" "
2739
0
            "gradientUnits=\"userSpaceOnUse\" "
2740
0
            "x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\"",
2741
0
            linear_pattern_id,
2742
0
            pattern->pd1.x, pattern->pd1.y,
2743
0
            pattern->pd2.x, pattern->pd2.y);
2744
2745
0
    _cairo_svg_surface_emit_pattern_extend (&document->xml_node_defs, &pattern->base.base);
2746
0
    _cairo_svg_surface_emit_transform (&document->xml_node_defs, "gradientTransform", &p2u, parent_matrix);
2747
0
    _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
2748
2749
0
    status = _cairo_svg_surface_emit_pattern_stops (&document->xml_node_defs,
2750
0
                &pattern->base,
2751
0
                0.0,
2752
0
                FALSE,
2753
0
                FALSE);
2754
0
    if (unlikely (status))
2755
0
  return status;
2756
2757
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
2758
0
            "</linearGradient>\n");
2759
2760
0
    _cairo_svg_stream_printf (output,
2761
0
            is_stroke ? " stroke=\"url(#linear-pattern-%d)\""
2762
0
          : " fill=\"url(#linear-pattern-%d)\"",
2763
0
            linear_pattern_id);
2764
2765
0
    return CAIRO_STATUS_SUCCESS;
2766
0
}
2767
2768
static cairo_status_t
2769
_cairo_svg_surface_emit_radial_pattern (cairo_svg_surface_t *surface,
2770
          cairo_radial_pattern_t *pattern,
2771
          cairo_svg_stream_t *output,
2772
          cairo_bool_t is_stroke,
2773
          const cairo_matrix_t *parent_matrix)
2774
0
{
2775
0
    cairo_status_t status;
2776
0
    cairo_svg_document_t *document = surface->document;
2777
2778
0
    cairo_extend_t extend = pattern->base.base.extend;
2779
2780
0
    cairo_bool_t reverse_stops;
2781
0
    cairo_circle_double_t *c0, *c1;
2782
0
    if (pattern->cd1.radius < pattern->cd2.radius) {
2783
0
  c0 = &pattern->cd1;
2784
0
  c1 = &pattern->cd2;
2785
0
  reverse_stops = FALSE;
2786
0
    } else {
2787
0
  c0 = &pattern->cd2;
2788
0
  c1 = &pattern->cd1;
2789
0
  reverse_stops = TRUE;
2790
0
    }
2791
2792
0
    double x0 = c0->center.x;
2793
0
    double y0 = c0->center.y;
2794
0
    double r0 = c0->radius;
2795
0
    double x1 = c1->center.x;
2796
0
    double y1 = c1->center.y;
2797
0
    double r1 = c1->radius;
2798
2799
0
    cairo_matrix_t p2u = pattern->base.base.matrix;
2800
0
    status = cairo_matrix_invert (&p2u);
2801
    /* cairo_pattern_set_matrix ensures the matrix is invertible */
2802
0
    assert (status == CAIRO_STATUS_SUCCESS);
2803
2804
0
    unsigned int radial_pattern_id = document->radial_pattern_id++;
2805
2806
0
    double start_offset;
2807
0
    cairo_bool_t emulate_reflect = FALSE;
2808
2809
0
    double fx = (r1 * x0 - r0 * x1) / (r1 - r0);
2810
0
    double fy = (r1 * y0 - r0 * y1) / (r1 - r0);
2811
2812
    /* SVG doesn't support the inner circle and use instead a gradient focal.
2813
     * That means we need to emulate the cairo behaviour by processing the
2814
     * cairo gradient stops.
2815
     * The CAIRO_EXTEND_NONE and CAIRO_EXTEND_PAD modes are quite easy to handle,
2816
     * it's just a matter of stop position translation and calculation of
2817
     * the corresponding SVG radial gradient focal.
2818
     * The CAIRO_EXTEND_REFLECT and CAIRO_EXTEND_REPEAT modes require to compute a new
2819
     * radial gradient, with an new outer circle, equal to r1 - r0 in the CAIRO_EXTEND_REPEAT
2820
     * case, and 2 * r1 - r0 in the CAIRO_EXTEND_REFLECT case, and a new gradient stop
2821
     * list that maps to the original cairo stop list.
2822
     */
2823
0
    if ((extend == CAIRO_EXTEND_REFLECT || extend == CAIRO_EXTEND_REPEAT) && r0 > 0.0) {
2824
0
  double r_org = r1;
2825
2826
0
  if (extend == CAIRO_EXTEND_REFLECT) {
2827
0
      r1 = 2.0 * r1 - r0;
2828
0
      emulate_reflect = TRUE;
2829
0
  }
2830
2831
0
  start_offset = fmod (r1, r1 - r0) / (r1 - r0) - 1.0;
2832
0
  double r = r1 - r0;
2833
2834
  /* New position of outer circle. */
2835
0
  double x = r * (x1 - fx) / r_org + fx;
2836
0
  double y = r * (y1 - fy) / r_org + fy;
2837
2838
0
  x1 = x;
2839
0
  y1 = y;
2840
0
  r1 = r;
2841
0
  r0 = 0.0;
2842
0
    } else {
2843
0
  start_offset = r0 / r1;
2844
0
    }
2845
2846
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
2847
0
            "<radialGradient id=\"radial-pattern-%d\" "
2848
0
            "gradientUnits=\"userSpaceOnUse\" "
2849
0
            "cx=\"%f\" cy=\"%f\" "
2850
0
            "fx=\"%f\" fy=\"%f\" r=\"%f\"",
2851
0
            radial_pattern_id,
2852
0
            x1, y1,
2853
0
            fx, fy, r1);
2854
2855
0
    if (emulate_reflect) {
2856
0
  _cairo_svg_stream_printf (&document->xml_node_defs, " spreadMethod=\"repeat\"");
2857
0
    } else {
2858
0
  _cairo_svg_surface_emit_pattern_extend (&document->xml_node_defs, &pattern->base.base);
2859
0
    }
2860
0
    _cairo_svg_surface_emit_transform (&document->xml_node_defs, "gradientTransform", &p2u, parent_matrix);
2861
0
    _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
2862
2863
    /* To support cairo's EXTEND_NONE, (for which SVG has no similar
2864
     * notion), we add transparent color stops on either end of the
2865
     * user-provided stops. */
2866
0
    if (extend == CAIRO_EXTEND_NONE) {
2867
0
  _cairo_svg_stream_printf (&document->xml_node_defs,
2868
0
          "<stop offset=\"0\" "
2869
0
          "stop-color=\"rgb(0%%, 0%%, 0%%)\" "
2870
0
          "stop-opacity=\"0\"/>\n");
2871
0
  if (r0 != 0.0) {
2872
0
      _cairo_svg_stream_printf (&document->xml_node_defs,
2873
0
              "<stop offset=\"%f\" "
2874
0
              "stop-color=\"rgb(0%%, 0%%, 0%%)\" "
2875
0
              "stop-opacity=\"0\"/>\n",
2876
0
              r0 / r1);
2877
0
  }
2878
0
    }
2879
0
    status = _cairo_svg_surface_emit_pattern_stops (&document->xml_node_defs,
2880
0
                &pattern->base,
2881
0
                start_offset,
2882
0
                reverse_stops,
2883
0
                emulate_reflect);
2884
0
    if (unlikely (status)) {
2885
0
  return status;
2886
0
    }
2887
2888
0
    if (pattern->base.base.extend == CAIRO_EXTEND_NONE) {
2889
0
  _cairo_svg_stream_printf (&document->xml_node_defs,
2890
0
          "<stop offset=\"1\" "
2891
0
          "stop-color=\"rgb(0%%, 0%%, 0%%)\" "
2892
0
          "stop-opacity=\"0\"/>\n");
2893
0
    }
2894
2895
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
2896
0
            "</radialGradient>\n");
2897
2898
0
    _cairo_svg_stream_printf (output,
2899
0
            is_stroke ? " stroke=\"url(#radial-pattern-%d)\""
2900
0
          : " fill=\"url(#radial-pattern-%d)\"",
2901
0
            radial_pattern_id);
2902
2903
0
    return CAIRO_STATUS_SUCCESS;
2904
0
}
2905
2906
static cairo_status_t
2907
_cairo_svg_surface_emit_pattern (cairo_svg_surface_t *surface,
2908
         const cairo_pattern_t *pattern,
2909
         cairo_svg_stream_t *output,
2910
         cairo_bool_t is_stroke,
2911
         const cairo_matrix_t *parent_matrix)
2912
0
{
2913
0
    switch (pattern->type) {
2914
0
    case CAIRO_PATTERN_TYPE_SOLID:
2915
0
  return _cairo_svg_surface_emit_solid_pattern (surface, (cairo_solid_pattern_t *) pattern,
2916
0
                  output, is_stroke);
2917
2918
0
    case CAIRO_PATTERN_TYPE_SURFACE:
2919
0
  return _cairo_svg_surface_emit_surface_pattern (surface, (cairo_surface_pattern_t *) pattern,
2920
0
              output, is_stroke, parent_matrix);
2921
2922
0
    case CAIRO_PATTERN_TYPE_LINEAR:
2923
0
  return _cairo_svg_surface_emit_linear_pattern (surface, (cairo_linear_pattern_t *) pattern,
2924
0
                   output, is_stroke, parent_matrix);
2925
2926
0
    case CAIRO_PATTERN_TYPE_RADIAL:
2927
0
  return _cairo_svg_surface_emit_radial_pattern (surface, (cairo_radial_pattern_t *) pattern,
2928
0
                   output, is_stroke, parent_matrix);
2929
2930
0
    case CAIRO_PATTERN_TYPE_MESH:
2931
0
    case CAIRO_PATTERN_TYPE_RASTER_SOURCE:
2932
0
  ASSERT_NOT_REACHED;
2933
0
    }
2934
0
    return _cairo_error (CAIRO_STATUS_PATTERN_TYPE_MISMATCH);
2935
0
}
2936
2937
static cairo_status_t
2938
_cairo_svg_surface_emit_fill_style (cairo_svg_stream_t *output,
2939
            cairo_svg_surface_t *surface,
2940
            const cairo_pattern_t *source,
2941
            cairo_fill_rule_t fill_rule,
2942
            const cairo_matrix_t *parent_matrix)
2943
0
{
2944
0
    _cairo_svg_stream_printf (output,
2945
0
            " fill-rule=\"%s\"",
2946
0
            fill_rule == CAIRO_FILL_RULE_EVEN_ODD ? "evenodd" : "nonzero");
2947
0
    return _cairo_svg_surface_emit_pattern (surface, source, output, FALSE, parent_matrix);
2948
0
}
2949
2950
static cairo_status_t
2951
_cairo_svg_surface_emit_stroke_style (cairo_svg_stream_t *output,
2952
              cairo_svg_surface_t *surface,
2953
              const cairo_pattern_t *source,
2954
              const cairo_stroke_style_t *stroke_style,
2955
              const cairo_matrix_t *parent_matrix)
2956
0
{
2957
0
    cairo_status_t status;
2958
0
    const char *line_cap, *line_join;
2959
0
    unsigned int i;
2960
2961
0
    switch (stroke_style->line_cap) {
2962
0
    case CAIRO_LINE_CAP_BUTT:
2963
0
  line_cap = "butt";
2964
0
  break;
2965
0
    case CAIRO_LINE_CAP_ROUND:
2966
0
  line_cap = "round";
2967
0
  break;
2968
0
    case CAIRO_LINE_CAP_SQUARE:
2969
0
  line_cap = "square";
2970
0
  break;
2971
0
    default:
2972
0
  ASSERT_NOT_REACHED;
2973
0
    }
2974
2975
0
    switch (stroke_style->line_join) {
2976
0
    case CAIRO_LINE_JOIN_MITER:
2977
0
  line_join = "miter";
2978
0
  break;
2979
0
    case CAIRO_LINE_JOIN_ROUND:
2980
0
  line_join = "round";
2981
0
  break;
2982
0
    case CAIRO_LINE_JOIN_BEVEL:
2983
0
  line_join = "bevel";
2984
0
  break;
2985
0
    default:
2986
0
  ASSERT_NOT_REACHED;
2987
0
    }
2988
2989
0
    if (stroke_style->is_hairline) {
2990
0
    _cairo_svg_stream_printf (output,
2991
0
          " stroke-width=\"1px\""
2992
0
          " stroke-linecap=\"%s\""
2993
0
          " stroke-linejoin=\"%s\""
2994
0
          " style=\"vector-effect: non-scaling-stroke\"",
2995
0
          line_cap,
2996
0
          line_join);
2997
0
  } else {
2998
0
    _cairo_svg_stream_printf (output,
2999
0
          " stroke-width=\"%f\""
3000
0
          " stroke-linecap=\"%s\""
3001
0
          " stroke-linejoin=\"%s\"",
3002
0
          stroke_style->line_width,
3003
0
          line_cap,
3004
0
          line_join);
3005
0
  }
3006
3007
0
    status = _cairo_svg_surface_emit_pattern (surface, source, output, TRUE, parent_matrix);
3008
0
    if (unlikely (status)) {
3009
0
  return status;
3010
0
    }
3011
3012
0
    if (stroke_style->num_dashes > 0) {
3013
0
  _cairo_svg_stream_printf (output, " stroke-dasharray=\"");
3014
0
  for (i = 0; i < stroke_style->num_dashes; i++) {
3015
0
      _cairo_svg_stream_printf (output,
3016
0
              "%f",
3017
0
              stroke_style->dash[i]);
3018
0
      _cairo_svg_stream_printf (output, i + 1 < stroke_style->num_dashes ? " " : "\"");
3019
0
  }
3020
0
  if (stroke_style->dash_offset != 0.0) {
3021
0
      _cairo_svg_stream_printf (output,
3022
0
              " stroke-dashoffset=\"%f\"",
3023
0
              stroke_style->dash_offset);
3024
0
  }
3025
0
    }
3026
3027
0
    _cairo_svg_stream_printf (output,
3028
0
            " stroke-miterlimit=\"%f\"",
3029
0
            stroke_style->miter_limit);
3030
3031
0
    return CAIRO_STATUS_SUCCESS;
3032
0
}
3033
3034
static cairo_bool_t
3035
_cairo_svg_surface_get_extents (void            *abstract_surface,
3036
        cairo_rectangle_int_t   *rectangle)
3037
4
{
3038
4
    cairo_svg_surface_t *surface = abstract_surface;
3039
3040
4
    rectangle->x = 0;
3041
4
    rectangle->y = 0;
3042
3043
    /* XXX: The conversion to integers here is pretty bogus, (not to
3044
     * mention the arbitrary limitation of width to a short(!). We
3045
     * may need to come up with a better interface for get_size.
3046
     */
3047
4
    rectangle->width  = ceil (surface->width);
3048
4
    rectangle->height = ceil (surface->height);
3049
3050
4
    return surface->surface_bounded;
3051
4
}
3052
3053
static cairo_status_t
3054
_cairo_svg_surface_emit_paint (cairo_svg_stream_t *output,
3055
             cairo_svg_surface_t *surface,
3056
             const cairo_pattern_t *source,
3057
             cairo_bool_t at_origin)
3058
0
{
3059
0
    cairo_status_t status;
3060
3061
0
    if (_cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (source)) {
3062
0
  return _cairo_svg_surface_emit_composite_pattern (output,
3063
0
                surface,
3064
0
                (cairo_surface_pattern_t *) source,
3065
0
                invalid_pattern_id,
3066
0
                NULL);
3067
0
    }
3068
3069
0
    surface->transitive_paint_used = TRUE;
3070
3071
0
    _cairo_svg_stream_printf (output, "<rect");
3072
0
    if (at_origin) {
3073
0
  _cairo_svg_stream_append_paint_dependent (output,
3074
0
              surface->source_id,
3075
0
              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE_AT_ORIGIN);
3076
0
    } else {
3077
0
  _cairo_svg_stream_append_paint_dependent (output,
3078
0
              surface->source_id,
3079
0
              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_RECTANGLE);
3080
0
    }
3081
0
    status = _cairo_svg_surface_emit_pattern (surface, source, output, FALSE, NULL);
3082
0
    if (unlikely (status)) {
3083
0
  return status;
3084
0
    }
3085
0
    _cairo_svg_stream_printf (output, "/>\n");
3086
3087
0
    return CAIRO_STATUS_SUCCESS;
3088
0
}
3089
3090
static cairo_int_status_t
3091
_cairo_svg_surface_do_operator (cairo_svg_stream_t *output,
3092
        cairo_svg_surface_t *surface,
3093
        cairo_operator_t op,
3094
        const cairo_clip_t *clip,
3095
        cairo_svg_stream_t *mask_stream,
3096
        cairo_svg_stream_t *source_stream,
3097
        cairo_svg_stream_t *destination_stream)
3098
0
{
3099
0
    cairo_status_t status;
3100
0
    cairo_svg_document_t *document = surface->document;
3101
3102
    // For operators that do not always produce opaque output, we first need to emit a black paint
3103
    // if the content does not have alpha
3104
0
    if (surface->base.content == CAIRO_CONTENT_COLOR && (op == CAIRO_OPERATOR_CLEAR ||
3105
0
               op == CAIRO_OPERATOR_SOURCE ||
3106
0
               op == CAIRO_OPERATOR_IN ||
3107
0
               op == CAIRO_OPERATOR_OUT ||
3108
0
               op == CAIRO_OPERATOR_DEST_IN ||
3109
0
               op == CAIRO_OPERATOR_DEST_OUT ||
3110
0
               op == CAIRO_OPERATOR_DEST_ATOP ||
3111
0
               op == CAIRO_OPERATOR_XOR)) {
3112
0
  _cairo_svg_surface_emit_paint (output, surface, &_cairo_pattern_black.base, FALSE);
3113
0
    }
3114
3115
0
    if (op == CAIRO_OPERATOR_CLEAR) {
3116
  /*
3117
   * The result is the same as one of the SOURCE operation application with the same arguments,
3118
   * but with an empty source.
3119
   */
3120
3121
0
  status = _cairo_svg_stream_destroy (source_stream);
3122
0
  if (unlikely (status)) {
3123
0
      (void) _cairo_svg_stream_destroy (destination_stream);
3124
0
      (void) _cairo_svg_stream_destroy (mask_stream);
3125
0
      return status;
3126
0
  }
3127
0
  cairo_svg_stream_t empty_stream = _cairo_svg_stream_create ();
3128
0
  return _cairo_svg_surface_do_operator (output,
3129
0
                 surface,
3130
0
                 CAIRO_OPERATOR_SOURCE,
3131
0
                 clip,
3132
0
                 mask_stream,
3133
0
                 &empty_stream,
3134
0
                 destination_stream);
3135
0
    }
3136
3137
0
    if (op == CAIRO_OPERATOR_SOURCE) {
3138
  /*
3139
   * Below we use the "Bounded" equation with SOURCE as the operation from the "Clipping and masking" section
3140
   * of https://cairographics.org/operators/:
3141
   * result = source LEPR_(clip IN mask) destination
3142
   *
3143
   * It is equivalent to:
3144
   * result = (source IN (clip IN mask)) ADD (destination IN (NOT (clip IN mask)))
3145
   *
3146
   * 1. We put the clip masked with the mask into the SVG group `lerp_compositing_group`.
3147
   * 2. `positive_lerp_mask` is an SVG mask with `lerp_compositing_group`.
3148
   * 3. `negative_lerp_mask` is an SVG mask with inverted `lerp_compositing_group`.
3149
   * 5. We put the source masked with `positive_lerp_mask` into the SVG group `lerped_source_compositing_group`.
3150
   * 6. We put the destination masked with `negative_lerp_mask` into
3151
   *    the SVG group `lerped_destination_compositing_group`.
3152
   * 7. The result is addition of `lerped_source_compositing_group` and `lerped_destination_compositing_group`.
3153
   */
3154
3155
0
  unsigned int lerp_compositing_group_id = document->compositing_group_id++;
3156
0
  _cairo_svg_stream_printf (&document->xml_node_defs,
3157
0
          "<g id=\"compositing-group-%d\"",
3158
0
          lerp_compositing_group_id);
3159
0
  _cairo_svg_stream_append_paint_dependent (&document->xml_node_defs,
3160
0
              surface->source_id,
3161
0
              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION);
3162
0
  _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
3163
0
  _cairo_svg_surface_emit_paint (&document->xml_node_defs, surface, &_cairo_pattern_clear.base, FALSE);
3164
0
  status = _cairo_svg_surface_set_clip (surface, &document->xml_node_defs, clip);
3165
0
  if (unlikely (status)) {
3166
0
      (void) _cairo_svg_stream_destroy (destination_stream);
3167
0
      (void) _cairo_svg_stream_destroy (source_stream);
3168
0
      (void) _cairo_svg_stream_destroy (mask_stream);
3169
0
      return status;
3170
0
  }
3171
0
  _cairo_svg_stream_copy (mask_stream, &document->xml_node_defs);
3172
0
  status = _cairo_svg_stream_destroy (mask_stream);
3173
0
  if (unlikely (status)) {
3174
0
      (void) _cairo_svg_stream_destroy (destination_stream);
3175
0
      (void) _cairo_svg_stream_destroy (source_stream);
3176
0
      return status;
3177
0
  }
3178
0
  _cairo_svg_surface_reset_clip (surface);
3179
0
  _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
3180
3181
0
  unsigned int positive_lerp_mask_id = document->mask_id++;
3182
0
  _cairo_svg_stream_printf (&document->xml_node_defs,
3183
0
          "<mask id=\"mask-%d\">\n",
3184
0
          positive_lerp_mask_id);
3185
0
  _cairo_svg_stream_printf (&document->xml_node_defs,
3186
0
          "<use xlink:href=\"#compositing-group-%d\"/>\n",
3187
0
          lerp_compositing_group_id);
3188
0
  _cairo_svg_stream_printf (&document->xml_node_defs, "</mask>\n");
3189
3190
0
  unsigned int negative_lerp_mask_id = document->mask_id++;
3191
0
  _cairo_svg_stream_printf (&document->xml_node_defs,
3192
0
          "<mask id=\"mask-%d\">\n",
3193
0
          negative_lerp_mask_id);
3194
0
  _cairo_svg_stream_printf (&document->xml_node_defs,
3195
0
          "<use xlink:href=\"#compositing-group-%d\" filter=\"url(#filter-%s)\"/>\n",
3196
0
          lerp_compositing_group_id,
3197
0
          _cairo_svg_surface_emit_static_filter (document,
3198
0
                   CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA));
3199
0
  _cairo_svg_stream_printf (&document->xml_node_defs, "</mask>\n");
3200
3201
0
  unsigned int lerped_source_compositing_group_id = document->compositing_group_id++;
3202
0
  _cairo_svg_stream_printf (&document->xml_node_defs,
3203
0
          "<g id=\"compositing-group-%d\" mask=\"url(#mask-%d)\">\n",
3204
0
          lerped_source_compositing_group_id,
3205
0
          positive_lerp_mask_id);
3206
0
  _cairo_svg_stream_printf (&document->xml_node_defs, "<g");
3207
0
  _cairo_svg_stream_append_paint_dependent (&document->xml_node_defs,
3208
0
              surface->source_id,
3209
0
              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION);
3210
0
  _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
3211
0
  _cairo_svg_stream_copy (source_stream, &document->xml_node_defs);
3212
0
  status = _cairo_svg_stream_destroy (source_stream);
3213
0
  if (unlikely (status)) {
3214
0
      (void) _cairo_svg_stream_destroy (destination_stream);
3215
0
      return status;
3216
0
  }
3217
0
  _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
3218
0
  _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
3219
3220
0
  unsigned int lerped_destination_compositing_group_id = document->compositing_group_id++;
3221
0
  _cairo_svg_stream_printf (&document->xml_node_defs,
3222
0
          "<g id=\"compositing-group-%d\" mask=\"url(#mask-%d)\">\n",
3223
0
          lerped_destination_compositing_group_id,
3224
0
          negative_lerp_mask_id);
3225
0
  _cairo_svg_stream_printf (&document->xml_node_defs, "<g");
3226
0
  _cairo_svg_stream_append_paint_dependent (&document->xml_node_defs,
3227
0
              surface->source_id,
3228
0
              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION);
3229
0
  _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
3230
0
  _cairo_svg_stream_copy (destination_stream, &document->xml_node_defs);
3231
0
  status = _cairo_svg_stream_destroy (destination_stream);
3232
0
  if (unlikely (status)) {
3233
0
      return status;
3234
0
  }
3235
0
  _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
3236
0
  _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
3237
3238
0
  _cairo_svg_stream_printf (&surface->xml_node,
3239
0
          "<g filter=\"url(#filter-%d)\"",
3240
0
          _cairo_svg_surface_emit_parametric_filter (surface,
3241
0
                       CAIRO_SVG_FILTER_ADD,
3242
0
                       lerped_source_compositing_group_id,
3243
0
                       lerped_destination_compositing_group_id));
3244
0
  _cairo_svg_stream_append_paint_dependent (&surface->xml_node,
3245
0
              surface->source_id,
3246
0
              CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_TRANSLATION);
3247
0
  _cairo_svg_stream_printf (&surface->xml_node, ">\n");
3248
0
  status = _cairo_svg_surface_emit_paint (&surface->xml_node, surface, &_cairo_pattern_black.base, TRUE);
3249
0
  if (unlikely (status)) {
3250
0
      return status;
3251
0
  }
3252
0
  _cairo_svg_stream_printf (&surface->xml_node, "</g>\n");
3253
3254
0
  return CAIRO_STATUS_SUCCESS;
3255
0
    }
3256
3257
0
    if (op == CAIRO_OPERATOR_DEST) {
3258
  /*
3259
   * The result is the destination.
3260
   */
3261
3262
0
  _cairo_svg_stream_copy (destination_stream, &surface->xml_node);
3263
0
  status = _cairo_svg_stream_destroy (destination_stream);
3264
0
  if (unlikely (status)) {
3265
0
      (void) _cairo_svg_stream_destroy (source_stream);
3266
0
      (void) _cairo_svg_stream_destroy (mask_stream);
3267
0
      return status;
3268
0
  }
3269
0
  status = _cairo_svg_stream_destroy (source_stream);
3270
0
  if (unlikely (status)) {
3271
0
      (void) _cairo_svg_stream_destroy (source_stream);
3272
0
      return status;
3273
0
  }
3274
0
  status = _cairo_svg_stream_destroy (source_stream);
3275
0
  if (unlikely (status)) {
3276
0
      return status;
3277
0
  }
3278
0
  return CAIRO_STATUS_SUCCESS;
3279
0
    }
3280
3281
    /*
3282
     * Below we use the "XRender" equation from the "Clipping and masking" section
3283
     * of https://cairographics.org/operators/:
3284
     * result = ((source IN mask) OP destination) LERP_clip destination
3285
     *
3286
     * It is equivalent to:
3287
     * result = (((source IN mask) OP destination) IN clip) ADD (destination IN (NOT clip))
3288
     *
3289
     * 1. We put the clip into the SVG group `lerp_compositing_group`.
3290
     * 2. `positive_lerp_mask` is an SVG mask with `lerp_compositing_group`.
3291
     * 3. `negative_lerp_mask` is an SVG mask with inverted `lerp_compositing_group`.
3292
     * 4. We put the mask into the SVG mask `mask_mask`.
3293
     * 5. We put the source masked with `mask_mask` into the SVG group `masked_source_compositing_group`.
3294
     * 6. We put the destination into the SVG group `destination_compositing_group`.
3295
     * 7. `lerped_operation_compositing_group` is an SVG group of operation applied to
3296
     *    (`masked_source_compositing_group`, `destination_compositing_group`)
3297
     *    masked with `positive_lerp_mask`.
3298
     * 8. `lerped_destination_compositing_group` is an SVG group of `destination_compositing_group`
3299
     *    masked with `negative_lerp_mask`.
3300
     * 9. The result is addition of `lerped_operation_compositing_group` and `lerped_destination_compositing_group`.
3301
     */
3302
3303
0
    unsigned int lerp_compositing_group_id = document->compositing_group_id++;
3304
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
3305
0
            "<g id=\"compositing-group-%d\"",
3306
0
            lerp_compositing_group_id);
3307
0
    _cairo_svg_stream_append_paint_dependent (&document->xml_node_defs,
3308
0
                surface->source_id,
3309
0
                CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION);
3310
0
    _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
3311
0
    _cairo_svg_surface_emit_paint (&document->xml_node_defs, surface, &_cairo_pattern_clear.base, FALSE);
3312
0
    status = _cairo_svg_surface_set_clip (surface, &document->xml_node_defs, clip);
3313
0
    if (unlikely (status)) {
3314
0
  (void) _cairo_svg_stream_destroy (destination_stream);
3315
0
  (void) _cairo_svg_stream_destroy (source_stream);
3316
0
  (void) _cairo_svg_stream_destroy (mask_stream);
3317
0
  return status;
3318
0
    }
3319
0
    status = _cairo_svg_surface_emit_paint (&document->xml_node_defs, surface, &_cairo_pattern_white.base, FALSE);
3320
0
    if (unlikely (status)) {
3321
0
  (void) _cairo_svg_stream_destroy (destination_stream);
3322
0
  (void) _cairo_svg_stream_destroy (source_stream);
3323
0
  (void) _cairo_svg_stream_destroy (mask_stream);
3324
0
  return status;
3325
0
    }
3326
0
    _cairo_svg_surface_reset_clip (surface);
3327
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
3328
3329
0
    unsigned int positive_lerp_mask_id = document->mask_id++;
3330
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
3331
0
            "<mask id=\"mask-%d\">\n",
3332
0
            positive_lerp_mask_id);
3333
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
3334
0
            "<use xlink:href=\"#compositing-group-%d\"/>\n",
3335
0
            lerp_compositing_group_id);
3336
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "</mask>\n");
3337
3338
0
    unsigned int negative_lerp_mask_id = document->mask_id++;
3339
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
3340
0
            "<mask id=\"mask-%d\">\n",
3341
0
            negative_lerp_mask_id);
3342
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
3343
0
            "<use xlink:href=\"#compositing-group-%d\" filter=\"url(#filter-%s)\"/>\n",
3344
0
            lerp_compositing_group_id,
3345
0
            _cairo_svg_surface_emit_static_filter (document,
3346
0
                     CAIRO_SVG_FILTER_REMOVE_COLOR_AND_INVERT_ALPHA));
3347
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "</mask>\n");
3348
3349
0
    unsigned int mask_mask_id = document->mask_id++;
3350
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
3351
0
            "<mask id=\"mask-%d\">\n",
3352
0
            mask_mask_id);
3353
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "<g");
3354
0
    _cairo_svg_stream_append_paint_dependent (&document->xml_node_defs,
3355
0
                surface->source_id,
3356
0
                CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION);
3357
0
    _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
3358
0
    _cairo_svg_stream_copy (mask_stream, &document->xml_node_defs);
3359
0
    status = _cairo_svg_stream_destroy (mask_stream);
3360
0
    if (unlikely (status)) {
3361
0
  (void) _cairo_svg_stream_destroy (source_stream);
3362
0
  (void) _cairo_svg_stream_destroy (destination_stream);
3363
0
  return status;
3364
0
    }
3365
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
3366
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "</mask>\n");
3367
3368
0
    unsigned int masked_source_compositing_group_id = document->compositing_group_id++;
3369
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
3370
0
            "<g id=\"compositing-group-%d\" mask=\"url(#mask-%d)\">\n",
3371
0
            masked_source_compositing_group_id,
3372
0
            mask_mask_id);
3373
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "<g");
3374
0
    _cairo_svg_stream_append_paint_dependent (&document->xml_node_defs,
3375
0
                surface->source_id,
3376
0
                CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION);
3377
0
    _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
3378
0
    _cairo_svg_stream_copy (source_stream, &document->xml_node_defs);
3379
0
    status = _cairo_svg_stream_destroy (source_stream);
3380
0
    if (unlikely (status)) {
3381
0
  (void) _cairo_svg_stream_destroy (destination_stream);
3382
0
  return status;
3383
0
    }
3384
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
3385
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
3386
3387
0
    unsigned int destination_compositing_group_id = document->compositing_group_id++;
3388
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
3389
0
            "<g id=\"compositing-group-%d\"",
3390
0
            destination_compositing_group_id);
3391
0
    _cairo_svg_stream_append_paint_dependent (&document->xml_node_defs,
3392
0
                surface->source_id,
3393
0
                CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_INVERSE_TRANSLATION);
3394
0
    _cairo_svg_stream_printf (&document->xml_node_defs, ">\n");
3395
0
    _cairo_svg_stream_copy (destination_stream, &document->xml_node_defs);
3396
0
    status = _cairo_svg_stream_destroy (destination_stream);
3397
0
    if (unlikely (status)) {
3398
0
  return status;
3399
0
    }
3400
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
3401
3402
0
    unsigned int lerped_operation_compositing_group_id = document->compositing_group_id++;
3403
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
3404
0
            "<g id=\"compositing-group-%d\"",
3405
0
            lerped_operation_compositing_group_id);
3406
0
    unsigned int filter_id;
3407
0
    switch (op) {
3408
0
    case CAIRO_OPERATOR_CLEAR:
3409
0
    case CAIRO_OPERATOR_SOURCE:
3410
0
    case CAIRO_OPERATOR_OVER:
3411
0
  ASSERT_NOT_REACHED;
3412
0
    case CAIRO_OPERATOR_IN:
3413
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3414
0
                     CAIRO_SVG_FILTER_IN,
3415
0
                     masked_source_compositing_group_id,
3416
0
                     destination_compositing_group_id);
3417
0
  break;
3418
0
    case CAIRO_OPERATOR_OUT:
3419
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3420
0
                     CAIRO_SVG_FILTER_OUT,
3421
0
                     masked_source_compositing_group_id,
3422
0
                     destination_compositing_group_id);
3423
0
  break;
3424
0
    case CAIRO_OPERATOR_ATOP:
3425
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3426
0
                     CAIRO_SVG_FILTER_ATOP,
3427
0
                     masked_source_compositing_group_id,
3428
0
                     destination_compositing_group_id);
3429
0
  break;
3430
0
    case CAIRO_OPERATOR_DEST:
3431
0
  ASSERT_NOT_REACHED;
3432
0
    case CAIRO_OPERATOR_DEST_OVER:
3433
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3434
0
                     CAIRO_SVG_FILTER_OVER,
3435
0
                     destination_compositing_group_id,
3436
0
                     masked_source_compositing_group_id);
3437
0
  break;
3438
0
    case CAIRO_OPERATOR_DEST_IN:
3439
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3440
0
                     CAIRO_SVG_FILTER_IN,
3441
0
                     destination_compositing_group_id,
3442
0
                     masked_source_compositing_group_id);
3443
0
  break;
3444
0
    case CAIRO_OPERATOR_DEST_OUT:
3445
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3446
0
                     CAIRO_SVG_FILTER_OUT,
3447
0
                     destination_compositing_group_id,
3448
0
                     masked_source_compositing_group_id);
3449
0
  break;
3450
0
    case CAIRO_OPERATOR_DEST_ATOP:
3451
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3452
0
                     CAIRO_SVG_FILTER_ATOP,
3453
0
                     destination_compositing_group_id,
3454
0
                     masked_source_compositing_group_id);
3455
0
  break;
3456
0
    case CAIRO_OPERATOR_XOR:
3457
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3458
0
                     CAIRO_SVG_FILTER_XOR,
3459
0
                     masked_source_compositing_group_id,
3460
0
                     destination_compositing_group_id);
3461
0
  break;
3462
0
    case CAIRO_OPERATOR_ADD:
3463
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3464
0
                     CAIRO_SVG_FILTER_ADD,
3465
0
                     masked_source_compositing_group_id,
3466
0
                     destination_compositing_group_id);
3467
0
  break;
3468
0
    case CAIRO_OPERATOR_SATURATE:
3469
0
  ASSERT_NOT_REACHED;
3470
0
    case CAIRO_OPERATOR_MULTIPLY:
3471
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3472
0
                     CAIRO_SVG_FILTER_MULTIPLY,
3473
0
                     masked_source_compositing_group_id,
3474
0
                     destination_compositing_group_id);
3475
0
  break;
3476
0
    case CAIRO_OPERATOR_SCREEN:
3477
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3478
0
                     CAIRO_SVG_FILTER_SCREEN,
3479
0
                     masked_source_compositing_group_id,
3480
0
                     destination_compositing_group_id);
3481
0
  break;
3482
0
    case CAIRO_OPERATOR_OVERLAY:
3483
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3484
0
                     CAIRO_SVG_FILTER_OVERLAY,
3485
0
                     masked_source_compositing_group_id,
3486
0
                     destination_compositing_group_id);
3487
0
  break;
3488
0
    case CAIRO_OPERATOR_DARKEN:
3489
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3490
0
                     CAIRO_SVG_FILTER_DARKEN,
3491
0
                     masked_source_compositing_group_id,
3492
0
                     destination_compositing_group_id);
3493
0
  break;
3494
0
    case CAIRO_OPERATOR_LIGHTEN:
3495
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3496
0
                     CAIRO_SVG_FILTER_LIGHTEN,
3497
0
                     masked_source_compositing_group_id,
3498
0
                     destination_compositing_group_id);
3499
0
  break;
3500
0
    case CAIRO_OPERATOR_COLOR_DODGE:
3501
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3502
0
                     CAIRO_SVG_FILTER_COLOR_DODGE,
3503
0
                     masked_source_compositing_group_id,
3504
0
                     destination_compositing_group_id);
3505
0
  break;
3506
0
    case CAIRO_OPERATOR_COLOR_BURN:
3507
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3508
0
                     CAIRO_SVG_FILTER_COLOR_BURN,
3509
0
                     masked_source_compositing_group_id,
3510
0
                     destination_compositing_group_id);
3511
0
  break;
3512
0
    case CAIRO_OPERATOR_HARD_LIGHT:
3513
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3514
0
                     CAIRO_SVG_FILTER_HARD_LIGHT,
3515
0
                     masked_source_compositing_group_id,
3516
0
                     destination_compositing_group_id);
3517
0
  break;
3518
0
    case CAIRO_OPERATOR_SOFT_LIGHT:
3519
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3520
0
                     CAIRO_SVG_FILTER_SOFT_LIGHT,
3521
0
                     masked_source_compositing_group_id,
3522
0
                     destination_compositing_group_id);
3523
0
  break;
3524
0
    case CAIRO_OPERATOR_DIFFERENCE:
3525
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3526
0
                     CAIRO_SVG_FILTER_DIFFERENCE,
3527
0
                     masked_source_compositing_group_id,
3528
0
                     destination_compositing_group_id);
3529
0
  break;
3530
0
    case CAIRO_OPERATOR_EXCLUSION:
3531
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3532
0
                     CAIRO_SVG_FILTER_EXCLUSION,
3533
0
                     masked_source_compositing_group_id,
3534
0
                     destination_compositing_group_id);
3535
0
  break;
3536
0
    case CAIRO_OPERATOR_HSL_HUE:
3537
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3538
0
                     CAIRO_SVG_FILTER_HUE,
3539
0
                     masked_source_compositing_group_id,
3540
0
                     destination_compositing_group_id);
3541
0
  break;
3542
0
    case CAIRO_OPERATOR_HSL_SATURATION:
3543
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3544
0
                     CAIRO_SVG_FILTER_SATURATION,
3545
0
                     masked_source_compositing_group_id,
3546
0
                     destination_compositing_group_id);
3547
0
  break;
3548
0
    case CAIRO_OPERATOR_HSL_COLOR:
3549
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3550
0
                     CAIRO_SVG_FILTER_COLOR,
3551
0
                     masked_source_compositing_group_id,
3552
0
                     destination_compositing_group_id);
3553
0
  break;
3554
0
    case CAIRO_OPERATOR_HSL_LUMINOSITY:
3555
0
  filter_id = _cairo_svg_surface_emit_parametric_filter (surface,
3556
0
                     CAIRO_SVG_FILTER_LUMINOSITY,
3557
0
                     masked_source_compositing_group_id,
3558
0
                     destination_compositing_group_id);
3559
0
  break;
3560
0
    default:
3561
0
  ASSERT_NOT_REACHED;
3562
0
    }
3563
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
3564
0
            " filter=\"url(#filter-%d)\" mask=\"url(#mask-%d)\">\n",
3565
0
            filter_id,
3566
0
            positive_lerp_mask_id);
3567
0
    status = _cairo_svg_surface_emit_paint (&document->xml_node_defs, surface, &_cairo_pattern_black.base, TRUE);
3568
0
    if (unlikely (status)) {
3569
0
  return status;
3570
0
    }
3571
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
3572
3573
0
    unsigned int lerped_destination_compositing_group_id = document->compositing_group_id++;
3574
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
3575
0
            "<g id=\"compositing-group-%d\" mask=\"url(#mask-%d)\">\n",
3576
0
            lerped_destination_compositing_group_id,
3577
0
            negative_lerp_mask_id);
3578
0
    _cairo_svg_stream_printf (&document->xml_node_defs,
3579
0
            "<use xlink:href=\"#compositing-group-%d\"/>\n",
3580
0
            destination_compositing_group_id);
3581
0
    _cairo_svg_stream_printf (&document->xml_node_defs, "</g>\n");
3582
3583
0
    _cairo_svg_stream_printf (&surface->xml_node,
3584
0
            "<g filter=\"url(#filter-%d)\"",
3585
0
            _cairo_svg_surface_emit_parametric_filter (surface,
3586
0
                   CAIRO_SVG_FILTER_ADD,
3587
0
                   lerped_operation_compositing_group_id,
3588
0
                   lerped_destination_compositing_group_id));
3589
0
    _cairo_svg_stream_append_paint_dependent (&surface->xml_node,
3590
0
                surface->source_id,
3591
0
                CAIRO_SVG_STREAM_PAINT_DEPENDENT_ELEMENT_TYPE_TRANSLATION);
3592
0
    _cairo_svg_stream_printf (&surface->xml_node, ">\n");
3593
0
    status = _cairo_svg_surface_emit_paint (&surface->xml_node, surface, &_cairo_pattern_black.base, TRUE);
3594
0
    if (unlikely (status)) {
3595
0
  return status;
3596
0
    }
3597
0
    _cairo_svg_stream_printf (&surface->xml_node, "</g>\n");
3598
3599
0
    return CAIRO_STATUS_SUCCESS;
3600
0
}
3601
3602
#define _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL(OPERATOR_IMPL, SOURCE, ...) \
3603
0
    if (op == CAIRO_OPERATOR_OVER) { \
3604
0
        status = _cairo_svg_surface_set_clip (surface, &surface->xml_node, clip); \
3605
0
        if (unlikely (status)) { \
3606
0
            return status; \
3607
0
        } \
3608
0
        return OPERATOR_IMPL (&surface->xml_node, surface, SOURCE, ## __VA_ARGS__); \
3609
0
    } else { \
3610
0
        _cairo_svg_surface_reset_clip (surface); \
3611
0
        cairo_svg_stream_t mask_stream = _cairo_svg_stream_create (); \
3612
0
        status = OPERATOR_IMPL (&mask_stream, surface, &_cairo_pattern_white.base, ## __VA_ARGS__); \
3613
0
        if (unlikely (status)) { \
3614
0
            (void) _cairo_svg_stream_destroy (&mask_stream); \
3615
0
            return status; \
3616
0
        } \
3617
0
        cairo_svg_stream_t source_stream = _cairo_svg_stream_create (); \
3618
0
        status = _cairo_svg_surface_emit_paint (&source_stream, \
3619
0
                                                surface, \
3620
0
                                                SOURCE,                   \
3621
0
                                                FALSE); \
3622
0
        if (unlikely (status)) { \
3623
0
            (void) _cairo_svg_stream_destroy (&source_stream); \
3624
0
            (void) _cairo_svg_stream_destroy (&mask_stream); \
3625
0
            return status; \
3626
0
        } \
3627
0
        cairo_svg_stream_t destination_stream = surface->xml_node; \
3628
0
        surface->xml_node = _cairo_svg_stream_create (); \
3629
0
        return _cairo_svg_surface_do_operator (&surface->xml_node, \
3630
0
                                               surface, \
3631
0
                                               op, \
3632
0
                                               clip, \
3633
0
                                               &mask_stream, \
3634
0
                                               &source_stream, \
3635
0
                                               &destination_stream); \
3636
0
    }
3637
3638
static cairo_int_status_t
3639
_cairo_svg_surface_paint_impl (cairo_svg_stream_t *output,
3640
             cairo_svg_surface_t *surface,
3641
             const cairo_pattern_t *source)
3642
0
{
3643
0
    return _cairo_svg_surface_emit_paint (output, surface, source, FALSE);
3644
0
}
3645
3646
static cairo_int_status_t
3647
_cairo_svg_surface_paint (void *abstract_surface,
3648
        cairo_operator_t op,
3649
        const cairo_pattern_t *source,
3650
        const cairo_clip_t *clip)
3651
0
{
3652
0
    cairo_status_t status;
3653
0
    cairo_svg_surface_t *surface = abstract_surface;
3654
3655
    /* Emulation of clear and source operators, when no clipping region
3656
     * is defined. We just delete existing content of surface root node,
3657
     * and exit early if operator is clear.
3658
     */
3659
0
    if ((op == CAIRO_OPERATOR_CLEAR || op == CAIRO_OPERATOR_SOURCE) && clip == NULL) {
3660
0
  switch (surface->paginated_mode) {
3661
0
  case CAIRO_PAGINATED_MODE_ANALYZE:
3662
0
      return CAIRO_STATUS_SUCCESS;
3663
0
  case CAIRO_PAGINATED_MODE_RENDER:
3664
0
      status = _cairo_svg_stream_destroy (&surface->xml_node);
3665
0
      if (unlikely (status)) {
3666
0
    return status;
3667
0
      }
3668
3669
0
      surface->xml_node = _cairo_svg_stream_create ();
3670
3671
0
      if (op == CAIRO_OPERATOR_CLEAR) {
3672
0
    return CAIRO_STATUS_SUCCESS;
3673
0
      }
3674
0
      break;
3675
0
  case CAIRO_PAGINATED_MODE_FALLBACK:
3676
0
      ASSERT_NOT_REACHED;
3677
0
  }
3678
0
    } else if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
3679
0
  return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source)
3680
0
         ? CAIRO_STATUS_SUCCESS
3681
0
         : CAIRO_INT_STATUS_UNSUPPORTED;
3682
0
    }
3683
3684
0
    _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_paint_impl,
3685
0
             source)
3686
0
}
3687
3688
static cairo_int_status_t
3689
_cairo_svg_surface_mask_impl (cairo_svg_stream_t *output,
3690
            cairo_svg_surface_t *surface,
3691
            const cairo_pattern_t *source,
3692
            const cairo_pattern_t *mask)
3693
0
{
3694
0
    cairo_status_t status;
3695
0
    cairo_svg_document_t *document = surface->document;
3696
3697
    /* _cairo_svg_surface_emit_paint() will output a pattern definition to
3698
     * document->xml_node_defs so we need to write the mask element to
3699
     * a temporary stream and then copy that to xml_node_defs. */
3700
0
    cairo_svg_stream_t temporary_stream = _cairo_svg_stream_create ();
3701
3702
0
    unsigned int mask_id = document->mask_id++;
3703
3704
0
    _cairo_svg_stream_printf (&temporary_stream,
3705
0
         "<mask id=\"mask-%d\">\n",
3706
0
         mask_id);
3707
0
    _cairo_svg_stream_printf (&temporary_stream,
3708
0
         "<g filter=\"url(#filter-%s)\">\n",
3709
0
         _cairo_svg_surface_emit_static_filter (document, CAIRO_SVG_FILTER_REMOVE_COLOR));
3710
0
    status = _cairo_svg_surface_emit_paint (&temporary_stream, surface, mask, FALSE);
3711
0
    if (unlikely (status)) {
3712
0
  (void) _cairo_svg_stream_destroy (&temporary_stream);
3713
0
  return status;
3714
0
    }
3715
0
    _cairo_svg_stream_printf (&temporary_stream, "</g>\n");
3716
0
    _cairo_svg_stream_printf (&temporary_stream, "</mask>\n");
3717
3718
0
    _cairo_svg_stream_copy (&temporary_stream, &document->xml_node_defs);
3719
3720
0
    status = _cairo_svg_stream_destroy (&temporary_stream);
3721
0
    if (unlikely (status)) {
3722
0
  return status;
3723
0
    }
3724
3725
0
    _cairo_svg_stream_printf (output,
3726
0
         "<g mask=\"url(#mask-%d)\">\n",
3727
0
         mask_id);
3728
3729
0
    status = _cairo_svg_surface_emit_paint (output, surface, source, FALSE);
3730
0
    if (unlikely (status)) {
3731
0
  return status;
3732
0
    }
3733
3734
0
    _cairo_svg_stream_printf (output, "</g>\n");
3735
3736
0
    return CAIRO_STATUS_SUCCESS;
3737
0
}
3738
3739
static cairo_int_status_t
3740
_cairo_svg_surface_mask (void *abstract_surface,
3741
       cairo_operator_t op,
3742
       const cairo_pattern_t *source,
3743
       const cairo_pattern_t *mask,
3744
       const cairo_clip_t *clip)
3745
0
{
3746
0
    cairo_status_t status;
3747
0
    cairo_svg_surface_t *surface = abstract_surface;
3748
3749
0
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
3750
0
  return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source) &&
3751
0
         _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, mask)
3752
0
         ? CAIRO_STATUS_SUCCESS
3753
0
         : CAIRO_INT_STATUS_UNSUPPORTED;
3754
0
    }
3755
3756
0
    _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_mask_impl,
3757
0
             source,
3758
0
             mask)
3759
0
}
3760
3761
static cairo_int_status_t
3762
_cairo_svg_surface_stroke_impl (cairo_svg_stream_t *output,
3763
        cairo_svg_surface_t *surface,
3764
        const cairo_pattern_t *source,
3765
        const cairo_path_fixed_t *path,
3766
        const cairo_stroke_style_t *stroke_style,
3767
        const cairo_matrix_t *ctm,
3768
        const cairo_matrix_t *ctm_inverse,
3769
        double tolerance,
3770
        cairo_antialias_t antialias)
3771
0
{
3772
0
    cairo_status_t status;
3773
3774
0
    cairo_bool_t svg_clip_or_svg_mask_should_be_used = _cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (source);
3775
0
    unsigned int mask_id;
3776
0
    cairo_svg_stream_t *output_stream = output;
3777
0
    if (svg_clip_or_svg_mask_should_be_used) {
3778
0
  mask_id = surface->document->mask_id++;
3779
3780
0
  output_stream = &surface->document->xml_node_defs;
3781
3782
0
  _cairo_svg_stream_printf (output_stream,
3783
0
          "<mask id=\"mask-%d\">\n",
3784
0
          mask_id);
3785
0
    }
3786
3787
0
    _cairo_svg_stream_printf (output_stream, "<path fill=\"none\"");
3788
0
    status = _cairo_svg_surface_emit_stroke_style (output_stream,
3789
0
               surface,
3790
0
               svg_clip_or_svg_mask_should_be_used ? &_cairo_pattern_white.base
3791
0
                           : source,
3792
0
               stroke_style,
3793
0
               ctm_inverse);
3794
3795
0
    if (unlikely (status)) {
3796
0
  return status;
3797
0
    }
3798
3799
0
    _cairo_svg_surface_emit_path (output_stream, path, ctm_inverse);
3800
3801
0
    _cairo_svg_surface_emit_transform (output_stream, "transform", ctm, NULL);
3802
0
    _cairo_svg_stream_printf (output_stream, "/>\n");
3803
3804
0
    if (svg_clip_or_svg_mask_should_be_used) {
3805
0
  _cairo_svg_stream_printf (output_stream, "</mask>\n");
3806
3807
0
  _cairo_svg_stream_printf (output,
3808
0
          "<g mask=\"url(#mask-%d)\">\n",
3809
0
          mask_id);
3810
3811
0
  status = _cairo_svg_surface_emit_composite_pattern (output,
3812
0
                  surface,
3813
0
                  (cairo_surface_pattern_t *) source,
3814
0
                  invalid_pattern_id,
3815
0
                  NULL);
3816
0
  if (unlikely (status)) {
3817
0
      return status;
3818
0
  }
3819
3820
0
  _cairo_svg_stream_printf (output, "</g>\n");
3821
0
    }
3822
3823
0
    return CAIRO_STATUS_SUCCESS;
3824
0
}
3825
3826
static cairo_int_status_t
3827
_cairo_svg_surface_stroke (void *abstract_dst,
3828
         cairo_operator_t op,
3829
         const cairo_pattern_t *source,
3830
         const cairo_path_fixed_t *path,
3831
         const cairo_stroke_style_t *stroke_style,
3832
         const cairo_matrix_t *ctm,
3833
         const cairo_matrix_t *ctm_inverse,
3834
         double tolerance,
3835
         cairo_antialias_t antialias,
3836
         const cairo_clip_t *clip)
3837
0
{
3838
0
    cairo_svg_surface_t *surface = abstract_dst;
3839
0
    cairo_status_t status;
3840
3841
0
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
3842
0
  return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source)
3843
0
         ? CAIRO_STATUS_SUCCESS
3844
0
         : CAIRO_INT_STATUS_UNSUPPORTED;
3845
0
    }
3846
3847
0
    _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_stroke_impl,
3848
0
             source,
3849
0
             path,
3850
0
             stroke_style,
3851
0
             ctm,
3852
0
             ctm_inverse,
3853
0
             tolerance,
3854
0
             antialias)
3855
0
}
3856
3857
static cairo_int_status_t
3858
_cairo_svg_surface_fill_impl (cairo_svg_stream_t *output,
3859
            cairo_svg_surface_t *surface,
3860
            const cairo_pattern_t *source,
3861
            const cairo_path_fixed_t *path,
3862
            cairo_fill_rule_t fill_rule,
3863
            double tolerance,
3864
            cairo_antialias_t antialias)
3865
0
{
3866
0
    cairo_status_t status;
3867
3868
0
    if (_cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (source)) {
3869
0
  _cairo_svg_stream_printf (&surface->document->xml_node_defs,
3870
0
          "<clipPath id=\"clip-%d\">\n",
3871
0
          surface->document->clip_id);
3872
3873
0
  _cairo_svg_stream_printf (&surface->document->xml_node_defs,
3874
0
          "<path clip-rule=\"%s\"",
3875
0
          fill_rule == CAIRO_FILL_RULE_EVEN_ODD ? "evenodd" : "nonzero");
3876
0
  _cairo_svg_surface_emit_path (&surface->document->xml_node_defs, path, NULL);
3877
0
  _cairo_svg_stream_printf (&surface->document->xml_node_defs, "/>\n");
3878
3879
0
  _cairo_svg_stream_printf (&surface->document->xml_node_defs, "</clipPath>\n");
3880
3881
0
  _cairo_svg_stream_printf (output,
3882
0
          "<g clip-path=\"url(#clip-%d)\">\n",
3883
0
          surface->document->clip_id++);
3884
3885
0
  status = _cairo_svg_surface_emit_composite_pattern (output,
3886
0
                  surface,
3887
0
                  (cairo_surface_pattern_t *) source,
3888
0
                  invalid_pattern_id,
3889
0
                  NULL);
3890
0
  if (unlikely (status)) {
3891
0
      return status;
3892
0
  }
3893
3894
0
  _cairo_svg_stream_printf (output, "</g>");
3895
0
    } else {
3896
0
  _cairo_svg_stream_printf (output, "<path");
3897
0
  status = _cairo_svg_surface_emit_fill_style (output, surface, source, fill_rule, NULL);
3898
0
  if (unlikely (status)) {
3899
0
      return status;
3900
0
  }
3901
0
  _cairo_svg_surface_emit_path (output, path, NULL);
3902
0
  _cairo_svg_stream_printf (output, "/>\n");
3903
0
    }
3904
3905
0
    return CAIRO_STATUS_SUCCESS;
3906
0
}
3907
3908
static cairo_int_status_t
3909
_cairo_svg_surface_fill (void *abstract_surface,
3910
       cairo_operator_t op,
3911
       const cairo_pattern_t *source,
3912
       const cairo_path_fixed_t *path,
3913
       cairo_fill_rule_t fill_rule,
3914
       double tolerance,
3915
       cairo_antialias_t antialias,
3916
       const cairo_clip_t *clip)
3917
0
{
3918
0
    cairo_svg_surface_t *surface = abstract_surface;
3919
0
    cairo_status_t status;
3920
3921
0
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
3922
0
  return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source)
3923
0
         ? CAIRO_STATUS_SUCCESS
3924
0
         : CAIRO_INT_STATUS_UNSUPPORTED;
3925
0
    }
3926
3927
0
    _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_fill_impl,
3928
0
             source,
3929
0
             path,
3930
0
             fill_rule,
3931
0
             tolerance,
3932
0
             antialias)
3933
0
}
3934
3935
static cairo_int_status_t
3936
_cairo_svg_surface_fill_stroke (void *abstract_surface,
3937
        cairo_operator_t fill_op,
3938
        const cairo_pattern_t *fill_source,
3939
        cairo_fill_rule_t fill_rule,
3940
        double fill_tolerance,
3941
        cairo_antialias_t fill_antialias,
3942
        const cairo_path_fixed_t *path,
3943
        cairo_operator_t stroke_op,
3944
        const cairo_pattern_t *stroke_source,
3945
        const cairo_stroke_style_t *stroke_style,
3946
        const cairo_matrix_t *stroke_ctm,
3947
        const cairo_matrix_t *stroke_ctm_inverse,
3948
        double stroke_tolerance,
3949
        cairo_antialias_t stroke_antialias,
3950
        const cairo_clip_t *clip)
3951
0
{
3952
0
    cairo_svg_surface_t *surface = abstract_surface;
3953
0
    cairo_status_t status;
3954
3955
0
    if (_cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (fill_source) ||
3956
0
  _cairo_svg_surface_svg_clip_or_svg_mask_should_be_used (stroke_source) ||
3957
0
  fill_op != CAIRO_OPERATOR_OVER ||
3958
0
  stroke_op != CAIRO_OPERATOR_OVER) {
3959
0
  return CAIRO_INT_STATUS_UNSUPPORTED;
3960
0
    }
3961
3962
0
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
3963
0
  return _cairo_svg_surface_are_operation_and_pattern_supported (surface, fill_op, fill_source)
3964
0
         && _cairo_svg_surface_are_operation_and_pattern_supported (surface, stroke_op, stroke_source)
3965
0
         ? CAIRO_STATUS_SUCCESS
3966
0
         : CAIRO_INT_STATUS_UNSUPPORTED;
3967
0
    }
3968
3969
0
    status = _cairo_svg_surface_set_clip (surface, &surface->xml_node, clip);
3970
0
    if (unlikely (status)) {
3971
0
  return status;
3972
0
    }
3973
3974
0
    _cairo_svg_stream_printf (&surface->xml_node, "<path");
3975
0
    status = _cairo_svg_surface_emit_fill_style (&surface->xml_node, surface,
3976
0
             fill_source, fill_rule, stroke_ctm_inverse);
3977
0
    if (unlikely (status)) {
3978
0
  return status;
3979
0
    }
3980
3981
0
    status = _cairo_svg_surface_emit_stroke_style (&surface->xml_node, surface,
3982
0
               stroke_source, stroke_style, stroke_ctm_inverse);
3983
0
    if (unlikely (status)) {
3984
0
  return status;
3985
0
    }
3986
3987
0
    _cairo_svg_surface_emit_path (&surface->xml_node, path, stroke_ctm_inverse);
3988
3989
0
    _cairo_svg_surface_emit_transform (&surface->xml_node, "transform", stroke_ctm, NULL);
3990
3991
0
    _cairo_svg_stream_printf (&surface->xml_node, "/>\n");
3992
3993
0
    return CAIRO_STATUS_SUCCESS;
3994
0
}
3995
3996
static cairo_int_status_t
3997
_cairo_svg_surface_show_glyphs_impl (cairo_svg_stream_t *output,
3998
             cairo_svg_surface_t *surface,
3999
             const cairo_pattern_t *source,
4000
             cairo_glyph_t *glyphs,
4001
             int num_glyphs,
4002
             cairo_scaled_font_t *scaled_font)
4003
0
{
4004
0
    cairo_status_t status;
4005
0
    cairo_svg_document_t *document = surface->document;
4006
4007
0
    if (num_glyphs <= 0) {
4008
0
  return CAIRO_STATUS_SUCCESS;
4009
0
    }
4010
4011
    /* FIXME it's probably possible to apply a source of a gradient to
4012
     * a group of symbols, but I don't know how yet. Gradients or patterns
4013
     * are translated by x and y properties of use element. */
4014
0
    if (source->type != CAIRO_PATTERN_TYPE_SOLID) {
4015
0
  goto fallback;
4016
0
    }
4017
4018
0
    _cairo_svg_stream_printf (output, "<g");
4019
4020
0
    status = _cairo_svg_surface_emit_pattern (surface, source, output, FALSE, NULL);
4021
0
    if (unlikely (status)) {
4022
0
  return status;
4023
0
    }
4024
4025
0
    _cairo_svg_stream_printf (output, ">\n");
4026
4027
0
    for (int i = 0; i < num_glyphs; i++) {
4028
0
  cairo_scaled_font_subsets_glyph_t subset_glyph;
4029
4030
0
  status = _cairo_scaled_font_subsets_map_glyph (document->font_subsets,
4031
0
                   scaled_font,
4032
0
                   glyphs[i].index,
4033
0
                   NULL,
4034
0
                   0,
4035
0
                   &subset_glyph);
4036
0
  if ((cairo_int_status_t) status == CAIRO_INT_STATUS_UNSUPPORTED) {
4037
0
      _cairo_svg_stream_printf (output, "</g>\n");
4038
4039
0
      glyphs += i;
4040
0
      num_glyphs -= i;
4041
0
      goto fallback;
4042
0
  }
4043
4044
0
  if (unlikely (status)) {
4045
0
      return status;
4046
0
  }
4047
4048
0
  _cairo_svg_stream_printf (output,
4049
0
          "<use xlink:href=\"#glyph-%d-%d\" x=\"%f\" y=\"%f\"/>\n",
4050
0
          subset_glyph.font_id,
4051
0
          subset_glyph.subset_glyph_index,
4052
0
          glyphs[i].x, glyphs[i].y);
4053
0
    }
4054
4055
0
    _cairo_svg_stream_printf (output, "</g>\n");
4056
4057
0
    return CAIRO_STATUS_SUCCESS;
4058
4059
0
    fallback:;
4060
0
    cairo_path_fixed_t path;
4061
4062
0
    _cairo_path_fixed_init (&path);
4063
4064
0
    status = _cairo_scaled_font_glyph_path (scaled_font,
4065
0
              (cairo_glyph_t *) glyphs,
4066
0
              num_glyphs, &path);
4067
0
    if (unlikely (status)) {
4068
0
  _cairo_path_fixed_fini (&path);
4069
0
  return status;
4070
0
    }
4071
4072
0
    status = _cairo_svg_surface_fill_impl (output,
4073
0
             surface,
4074
0
             source,
4075
0
             &path,
4076
0
             CAIRO_FILL_RULE_WINDING,
4077
0
             0.0,
4078
0
             CAIRO_ANTIALIAS_DEFAULT);
4079
4080
0
    _cairo_path_fixed_fini (&path);
4081
4082
0
    return status;
4083
0
}
4084
4085
static cairo_int_status_t
4086
_cairo_svg_surface_show_glyphs (void *abstract_surface,
4087
        cairo_operator_t op,
4088
        const cairo_pattern_t *source,
4089
        cairo_glyph_t *glyphs,
4090
        int num_glyphs,
4091
        cairo_scaled_font_t *scaled_font,
4092
        const cairo_clip_t *clip)
4093
0
{
4094
0
    cairo_svg_surface_t *surface = abstract_surface;
4095
0
    cairo_int_status_t status;
4096
4097
0
    if (surface->paginated_mode == CAIRO_PAGINATED_MODE_ANALYZE) {
4098
0
  return _cairo_svg_surface_are_operation_and_pattern_supported (surface, op, source)
4099
0
         ? CAIRO_STATUS_SUCCESS
4100
0
         : CAIRO_INT_STATUS_UNSUPPORTED;
4101
0
    }
4102
4103
0
    _CAIRO_SVG_SURFACE_CALL_OPERATOR_IMPL (_cairo_svg_surface_show_glyphs_impl,
4104
0
             source,
4105
0
             glyphs,
4106
0
             num_glyphs,
4107
0
             scaled_font)
4108
0
}
4109
4110
static void
4111
_cairo_svg_surface_get_font_options (void                  *abstract_surface,
4112
             cairo_font_options_t  *options)
4113
2
{
4114
2
    _cairo_font_options_init_default (options);
4115
4116
2
    cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_NONE);
4117
2
    cairo_font_options_set_hint_metrics (options, CAIRO_HINT_METRICS_OFF);
4118
2
    cairo_font_options_set_antialias (options, CAIRO_ANTIALIAS_GRAY);
4119
2
    _cairo_font_options_set_round_glyph_positions (options, CAIRO_ROUND_GLYPH_POS_OFF);
4120
2
}
4121
4122
4123
static const char **
4124
_cairo_svg_surface_get_supported_mime_types (void    *abstract_surface)
4125
0
{
4126
0
    return _cairo_svg_supported_mime_types;
4127
0
}
4128
4129
static const cairo_surface_backend_t cairo_svg_surface_backend = {
4130
  CAIRO_SURFACE_TYPE_SVG,
4131
  _cairo_svg_surface_finish,
4132
4133
  _cairo_default_context_create,
4134
4135
  NULL, /* create_similar: handled by wrapper */
4136
  NULL, /* create_similar_image */
4137
  NULL, /* map to image */
4138
  NULL, /* unmap image */
4139
4140
  _cairo_surface_default_source,
4141
  NULL, /* acquire_source_image */
4142
  NULL, /* release_source_image */
4143
  NULL, /* snapshot */
4144
4145
  _cairo_svg_surface_copy_page,
4146
  _cairo_svg_surface_show_page,
4147
4148
  _cairo_svg_surface_get_extents,
4149
  _cairo_svg_surface_get_font_options,
4150
4151
  NULL, /* flush */
4152
  NULL, /* mark dirty rectangle */
4153
4154
  _cairo_svg_surface_paint,
4155
  _cairo_svg_surface_mask,
4156
  _cairo_svg_surface_stroke,
4157
  _cairo_svg_surface_fill,
4158
  _cairo_svg_surface_fill_stroke,
4159
  _cairo_svg_surface_show_glyphs,
4160
  NULL, /* has_show_text_glyphs */
4161
  NULL, /* show_text_glyphs */
4162
  _cairo_svg_surface_get_supported_mime_types,
4163
};
4164
4165
static cairo_status_t
4166
_cairo_svg_document_create (cairo_output_stream_t *output_stream,
4167
          double width,
4168
          double height,
4169
          cairo_svg_version_t version,
4170
          cairo_svg_document_t **document_out)
4171
2
{
4172
2
    cairo_svg_document_t *document;
4173
4174
2
    if (output_stream->status) {
4175
0
  return output_stream->status;
4176
0
    }
4177
4178
2
    document = _cairo_calloc (sizeof (cairo_svg_document_t));
4179
2
    if (unlikely (document == NULL)) {
4180
0
  return _cairo_error (CAIRO_STATUS_NO_MEMORY);
4181
0
    }
4182
4183
2
    document->output_stream = output_stream;
4184
2
    document->refcount = 1;
4185
2
    document->owner = NULL;
4186
2
    document->finished = FALSE;
4187
4188
2
    document->width = width;
4189
2
    document->height = height;
4190
2
    document->unit = CAIRO_SVG_UNIT_USER;
4191
4192
2
    document->xml_node_defs = _cairo_svg_stream_create ();
4193
2
    document->xml_node_glyphs = _cairo_svg_stream_create ();
4194
2
    document->xml_node_filters = _cairo_svg_stream_create ();
4195
4196
2
    document->linear_pattern_id = 0;
4197
2
    document->radial_pattern_id = 0;
4198
2
    document->pattern_id = 0;
4199
2
    document->clip_id = 0;
4200
2
    document->mask_id = 0;
4201
2
    document->compositing_group_id = 0;
4202
2
    document->filter_id = 0;
4203
4204
8
    for (enum cairo_svg_filter filter = 0; filter < CAIRO_SVG_FILTER_LAST_STATIC_FILTER; filter++) {
4205
6
  document->filters_emitted[filter] = FALSE;
4206
6
    }
4207
4208
2
    document->svg_version = version;
4209
4210
    /* The use of defs for font glyphs imposes no per-subset limit. */
4211
2
    document->font_subsets = _cairo_scaled_font_subsets_create_scaled ();
4212
2
    if (unlikely (document->font_subsets == NULL)) {
4213
0
  (void) _cairo_svg_stream_destroy(&document->xml_node_defs);
4214
0
  (void) _cairo_svg_stream_destroy(&document->xml_node_glyphs);
4215
0
  (void) _cairo_svg_stream_destroy(&document->xml_node_filters);
4216
0
  free (document);
4217
0
  return _cairo_error (CAIRO_STATUS_NO_MEMORY);
4218
0
    }
4219
4220
2
    document->paints = _cairo_hash_table_create (_cairo_svg_paint_equal);
4221
2
    if (unlikely (document->paints == NULL)) {
4222
0
  (void) _cairo_svg_stream_destroy(&document->xml_node_defs);
4223
0
  (void) _cairo_svg_stream_destroy(&document->xml_node_glyphs);
4224
0
  (void) _cairo_svg_stream_destroy(&document->xml_node_filters);
4225
0
  _cairo_scaled_font_subsets_destroy (document->font_subsets);
4226
0
  free (document);
4227
0
  return _cairo_error (CAIRO_STATUS_NO_MEMORY);
4228
0
    }
4229
4230
2
    *document_out = document;
4231
2
    return CAIRO_STATUS_SUCCESS;
4232
2
}
4233
4234
static cairo_svg_document_t *
4235
_cairo_svg_document_reference (cairo_svg_document_t *document)
4236
2
{
4237
2
    document->refcount++;
4238
4239
2
    return document;
4240
2
}
4241
4242
static cairo_status_t
4243
_cairo_svg_document_destroy (cairo_svg_document_t *document)
4244
4
{
4245
4
    cairo_status_t status;
4246
4247
4
    document->refcount--;
4248
4
    if (document->refcount > 0)
4249
2
      return CAIRO_STATUS_SUCCESS;
4250
4251
2
    status = _cairo_svg_document_finish (document);
4252
4253
2
    free (document);
4254
4255
2
    return status;
4256
4
}
4257
4258
static cairo_status_t
4259
_cairo_svg_document_finish (cairo_svg_document_t *document)
4260
4
{
4261
4
    if (document->finished) {
4262
2
  return CAIRO_STATUS_SUCCESS;
4263
2
    }
4264
2
    document->finished = TRUE;
4265
4266
2
    cairo_status_t status, final_status = CAIRO_STATUS_SUCCESS;
4267
4268
2
    cairo_output_stream_t *output = document->output_stream;
4269
4270
    /*
4271
     * Should we add DOCTYPE?
4272
     *
4273
     * Google says no.
4274
     *
4275
     * http://tech.groups.yahoo.com/group/svg-developers/message/48562:
4276
     *   There's a bunch of issues, but just to pick a few:
4277
     *   - they'll give false positives.
4278
     *   - they'll give false negatives.
4279
     *   - they're namespace-unaware.
4280
     *   - they don't wildcard.
4281
     *   So when they say OK they really haven't checked anything, when
4282
     *   they say NOT OK they might be on crack, and like all
4283
     *   namespace-unaware things they're a dead branch of the XML tree.
4284
     *
4285
     * http://jwatt.org/svg/authoring/:
4286
     *   Unfortunately the SVG DTDs are a source of so many issues that the
4287
     *   SVG WG has decided not to write one for the upcoming SVG 1.2
4288
     *   standard. In fact SVG WG members are even telling people not to use
4289
     *   a DOCTYPE declaration in SVG 1.0 and 1.1 documents.
4290
     */
4291
4292
2
    _cairo_output_stream_printf (output,
4293
2
         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
4294
2
         "<svg xmlns=\"http://www.w3.org/2000/svg\" "
4295
2
         "xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
4296
2
         "width=\"%f%s\" height=\"%f%s\" "
4297
2
         "viewBox=\"0 0 %f %f\">\n",
4298
2
         document->width, _cairo_svg_unit_strings[document->unit],
4299
2
         document->height, _cairo_svg_unit_strings[document->unit],
4300
2
         document->width, document->height);
4301
4302
2
    status = _cairo_svg_document_emit_font_subsets (document);
4303
2
    if (final_status == CAIRO_STATUS_SUCCESS) {
4304
2
  final_status = status;
4305
2
    }
4306
4307
2
    cairo_svg_surface_t *surface = NULL;
4308
2
    if (document->owner != NULL) {
4309
2
  surface = (cairo_svg_surface_t *) _cairo_paginated_surface_get_target (document->owner);
4310
4311
2
  if (surface->xml_node.elements.num_elements > 0) {
4312
0
      cairo_svg_page_t *page = _cairo_svg_surface_store_page (surface);
4313
0
      if (final_status == CAIRO_STATUS_SUCCESS && page == NULL) {
4314
0
    final_status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
4315
0
      }
4316
0
  }
4317
4318
2
  if (surface->transitive_paint_used) {
4319
0
      cairo_svg_paint_t *paint_entry = _cairo_calloc (sizeof (cairo_svg_paint_t));
4320
0
      if (paint_entry == NULL) {
4321
0
    return _cairo_error (CAIRO_STATUS_NO_MEMORY);
4322
0
      }
4323
0
      paint_entry->source_id = surface->source_id;
4324
0
      paint_entry->box.p1.x = 0;
4325
0
      paint_entry->box.p1.y = 0;
4326
0
      paint_entry->box.p2.x = document->width;
4327
0
      paint_entry->box.p2.y = document->height;
4328
0
      _cairo_svg_paint_box_add_padding (&paint_entry->box);
4329
0
      _cairo_array_init (&paint_entry->paint_elements, sizeof (cairo_svg_paint_element_t));
4330
0
      _cairo_svg_paint_init_key (paint_entry);
4331
0
      status = _cairo_hash_table_insert (document->paints, &paint_entry->base);
4332
0
      if (unlikely (status)) {
4333
0
    return status;
4334
0
      }
4335
0
  }
4336
2
    }
4337
4338
2
    _cairo_hash_table_foreach (document->paints, _cairo_svg_paint_compute_func, document);
4339
4340
2
    if (document->xml_node_filters.elements.num_elements > 0 ||
4341
2
  document->xml_node_glyphs.elements.num_elements > 0 ||
4342
2
  document->xml_node_defs.elements.num_elements > 0) {
4343
0
  _cairo_output_stream_printf (output, "<defs>\n");
4344
0
  _cairo_svg_stream_copy_to_output_stream (&document->xml_node_filters, output, document->paints);
4345
0
  if (document->xml_node_glyphs.elements.num_elements > 0) {
4346
0
      _cairo_output_stream_printf (output, "<g>\n");
4347
0
      _cairo_svg_stream_copy_to_output_stream (&document->xml_node_glyphs, output, document->paints);
4348
0
      _cairo_output_stream_printf (output, "</g>\n");
4349
0
  }
4350
0
  _cairo_svg_stream_copy_to_output_stream (&document->xml_node_defs, output, document->paints);
4351
0
  _cairo_output_stream_printf (output, "</defs>\n");
4352
0
    }
4353
4354
2
    if (document->owner != NULL) {
4355
2
  if (surface->page_set.num_elements == 1) {
4356
2
      cairo_svg_page_t *page = _cairo_array_index (&surface->page_set, 0);
4357
2
      _cairo_svg_stream_copy_to_output_stream (&page->xml_node, output, document->paints);
4358
2
  } else if (surface->page_set.num_elements > 1) {
4359
0
      _cairo_output_stream_printf (output, "<pageSet>\n");
4360
0
      for (unsigned int i = 0; i < surface->page_set.num_elements; i++) {
4361
0
    cairo_svg_page_t *page = _cairo_array_index (&surface->page_set, i);
4362
0
    _cairo_output_stream_printf (output, "<page>\n");
4363
0
    _cairo_svg_stream_copy_to_output_stream (&page->xml_node, output, document->paints);
4364
0
    _cairo_output_stream_printf (output, "</page>\n");
4365
0
      }
4366
0
      _cairo_output_stream_printf (output, "</pageSet>\n");
4367
0
  }
4368
2
    }
4369
4370
2
    _cairo_output_stream_printf (output, "</svg>\n");
4371
4372
2
    status = _cairo_svg_stream_destroy (&document->xml_node_defs);
4373
2
    if (final_status == CAIRO_STATUS_SUCCESS) {
4374
2
  final_status = status;
4375
2
    }
4376
4377
2
    status = _cairo_svg_stream_destroy (&document->xml_node_glyphs);
4378
2
    if (final_status == CAIRO_STATUS_SUCCESS) {
4379
2
  final_status = status;
4380
2
    }
4381
4382
2
    status = _cairo_svg_stream_destroy (&document->xml_node_filters);
4383
2
    if (final_status == CAIRO_STATUS_SUCCESS) {
4384
2
  final_status = status;
4385
2
    }
4386
4387
2
    _cairo_hash_table_foreach (document->paints, _cairo_svg_paint_pluck, document->paints);
4388
2
    _cairo_hash_table_destroy (document->paints);
4389
4390
2
    status = _cairo_output_stream_destroy (output);
4391
2
    if (final_status == CAIRO_STATUS_SUCCESS) {
4392
2
  final_status = status;
4393
2
    }
4394
4395
2
    return final_status;
4396
2
}
4397
4398
static cairo_int_status_t
4399
_cairo_svg_surface_set_paginated_mode (void     *abstract_surface,
4400
               cairo_paginated_mode_t  paginated_mode)
4401
4
{
4402
4
    cairo_svg_surface_t *surface = abstract_surface;
4403
4404
4
    surface->paginated_mode = paginated_mode;
4405
4406
4
    return CAIRO_STATUS_SUCCESS;
4407
4
}
4408
4409
static cairo_bool_t
4410
_cairo_svg_surface_supports_fine_grained_fallbacks (void *abstract_surface)
4411
2
{
4412
2
    return TRUE;
4413
2
}
4414
4415
static const cairo_paginated_surface_backend_t cairo_svg_surface_paginated_backend = {
4416
    NULL /*_cairo_svg_surface_start_page*/,
4417
    _cairo_svg_surface_set_paginated_mode,
4418
    NULL, /* _cairo_svg_surface_set_bounding_box */
4419
    NULL, /* _cairo_svg_surface_set_fallback_images_required */
4420
    _cairo_svg_surface_supports_fine_grained_fallbacks,
4421
};