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

            
37
#include "cairoint.h"
38
#include "cairo-array-private.h"
39
#include "cairo-ft-private.h"
40
#include "cairo-pattern-private.h"
41
#include "cairo-scaled-font-subsets-private.h"
42

            
43
#include <stdarg.h>
44
#include <stdio.h>
45
#include <string.h>
46

            
47
#if HAVE_FT_SVG_DOCUMENT
48

            
49
#include <ft2build.h>
50
#include FT_COLOR_H
51

            
52
/* #define SVG_RENDER_PRINT_FUNCTIONS 1 */
53

            
54
#define WHITE_SPACE_CHARS " \n\r\t\v\f"
55

            
56
typedef struct {
57
    const char *name;
58
    int red;
59
    int green;
60
    int blue;
61
} color_name_t;
62

            
63
/* Must be sorted */
64
static color_name_t color_names[] = {
65
    { "aliceblue", 240, 248, 255 },
66
    { "antiquewhite", 250, 235, 215 },
67
    { "aqua",  0, 255, 255 },
68
    { "aquamarine", 127, 255, 212 },
69
    { "azure", 240, 255, 255 },
70
    { "beige", 245, 245, 220 },
71
    { "bisque", 255, 228, 196 },
72
    { "black",  0, 0, 0 },
73
    { "blanchedalmond", 255, 235, 205 },
74
    { "blue",  0, 0, 255 },
75
    { "blueviolet", 138, 43, 226 },
76
    { "brown", 165, 42, 42 },
77
    { "burlywood", 222, 184, 135 },
78
    { "cadetblue",  95, 158, 160 },
79
    { "chartreuse", 127, 255, 0 },
80
    { "chocolate", 210, 105, 30 },
81
    { "coral", 255, 127, 80 },
82
    { "cornflowerblue", 100, 149, 237 },
83
    { "cornsilk", 255, 248, 220 },
84
    { "crimson", 220, 20, 60 },
85
    { "cyan",  0, 255, 255 },
86
    { "darkblue",  0, 0, 139 },
87
    { "darkcyan",  0, 139, 139 },
88
    { "darkgoldenrod", 184, 134, 11 },
89
    { "darkgray", 169, 169, 169 },
90
    { "darkgreen",  0, 100, 0 },
91
    { "darkgrey", 169, 169, 169 },
92
    { "darkkhaki", 189, 183, 107 },
93
    { "darkmagenta", 139, 0, 139 },
94
    { "darkolivegreen",  85, 107, 47 },
95
    { "darkorange", 255, 140, 0 },
96
    { "darkorchid", 153, 50, 204 },
97
    { "darkred", 139, 0, 0 },
98
    { "darksalmon", 233, 150, 122 },
99
    { "darkseagreen", 143, 188, 143 },
100
    { "darkslateblue",  72, 61, 139 },
101
    { "darkslategray",  47, 79, 79 },
102
    { "darkslategrey",  47, 79, 79 },
103
    { "darkturquoise",  0, 206, 209 },
104
    { "darkviolet", 148, 0, 211 },
105
    { "deeppink", 255, 20, 147 },
106
    { "deepskyblue",  0, 191, 255 },
107
    { "dimgray", 105, 105, 105 },
108
    { "dimgrey", 105, 105, 105 },
109
    { "dodgerblue",  30, 144, 255 },
110
    { "firebrick", 178, 34, 34 },
111
    { "floralwhite", 255, 250, 240 },
112
    { "forestgreen",  34, 139, 34 },
113
    { "fuchsia", 255, 0, 255 },
114
    { "gainsboro", 220, 220, 220 },
115
    { "ghostwhite", 248, 248, 255 },
116
    { "gold", 255, 215, 0 },
117
    { "goldenrod", 218, 165, 32 },
118
    { "gray", 128, 128, 128 },
119
    { "green",  0, 128, 0 },
120
    { "greenyellow", 173, 255, 47 },
121
    { "grey", 128, 128, 128 },
122
    { "honeydew", 240, 255, 240 },
123
    { "hotpink", 255, 105, 180 },
124
    { "indianred", 205, 92, 92 },
125
    { "indigo",  75, 0, 130 },
126
    { "ivory", 255, 255, 240 },
127
    { "khaki", 240, 230, 140 },
128
    { "lavender", 230, 230, 250 },
129
    { "lavenderblush", 255, 240, 245 },
130
    { "lawngreen", 124, 252, 0 },
131
    { "lemonchiffon", 255, 250, 205 },
132
    { "lightblue", 173, 216, 230 },
133
    { "lightcoral", 240, 128, 128 },
134
    { "lightcyan", 224, 255, 255 },
135
    { "lightgoldenrodyellow", 250, 250, 210 },
136
    { "lightgray", 211, 211, 211 },
137
    { "lightgreen", 144, 238, 144 },
138
    { "lightgrey", 211, 211, 211 },
139
    { "lightpink", 255, 182, 193 },
140
    { "lightsalmon", 255, 160, 122 },
141
    { "lightseagreen",  32, 178, 170 },
142
    { "lightskyblue", 135, 206, 250 },
143
    { "lightslategray", 119, 136, 153 },
144
    { "lightslategrey", 119, 136, 153 },
145
    { "lightsteelblue", 176, 196, 222 },
146
    { "lightyellow", 255, 255, 224 },
147
    { "lime",  0, 255, 0 },
148
    { "limegreen",  50, 205, 50 },
149
    { "linen", 250, 240, 230 },
150
    { "magenta", 255, 0, 255 },
151
    { "maroon", 128, 0, 0 },
152
    { "mediumaquamarine", 102, 205, 170 },
153
    { "mediumblue",  0, 0, 205 },
154
    { "mediumorchid", 186, 85, 211 },
155
    { "mediumpurple", 147, 112, 219 },
156
    { "mediumseagreen",  60, 179, 113 },
157
    { "mediumslateblue", 123, 104, 238 },
158
    { "mediumspringgreen",  0, 250, 154 },
159
    { "mediumturquoise",  72, 209, 204 },
160
    { "mediumvioletred", 199, 21, 133 },
161
    { "midnightblue",  25, 25, 112 },
162
    { "mintcream", 245, 255, 250 },
163
    { "mistyrose", 255, 228, 225 },
164
    { "moccasin", 255, 228, 181 },
165
    { "navajowhite", 255, 222, 173 },
166
    { "navy",  0, 0, 128 },
167
    { "oldlace", 253, 245, 230 },
168
    { "olive", 128, 128, 0 },
169
    { "olivedrab", 107, 142, 35 },
170
    { "orange", 255, 165, 0 },
171
    { "orangered", 255, 69, 0 },
172
    { "orchid", 218, 112, 214 },
173
    { "palegoldenrod", 238, 232, 170 },
174
    { "palegreen", 152, 251, 152 },
175
    { "paleturquoise", 175, 238, 238 },
176
    { "palevioletred", 219, 112, 147 },
177
    { "papayawhip", 255, 239, 213 },
178
    { "peachpuff", 255, 218, 185 },
179
    { "peru", 205, 133, 63 },
180
    { "pink", 255, 192, 203 },
181
    { "plum", 221, 160, 221 },
182
    { "powderblue", 176, 224, 230 },
183
    { "purple", 128, 0, 128 },
184
    { "red", 255, 0, 0 },
185
    { "rosybrown", 188, 143, 143 },
186
    { "royalblue",  65, 105, 225 },
187
    { "saddlebrown", 139, 69, 19 },
188
    { "salmon", 250, 128, 114 },
189
    { "sandybrown", 244, 164, 96 },
190
    { "seagreen",  46, 139, 87 },
191
    { "seashell", 255, 245, 238 },
192
    { "sienna", 160, 82, 45 },
193
    { "silver", 192, 192, 192 },
194
    { "skyblue", 135, 206, 235 },
195
    { "slateblue", 106, 90, 205 },
196
    { "slategray", 112, 128, 144 },
197
    { "slategrey", 112, 128, 144 },
198
    { "snow", 255, 250, 250 },
199
    { "springgreen",  0, 255, 127 },
200
    { "steelblue",  70, 130, 180 },
201
    { "tan", 210, 180, 140 },
202
    { "teal",  0, 128, 128 },
203
    { "thistle", 216, 191, 216 },
204
    { "tomato", 255, 99, 71 },
205
    { "turquoise",  64, 224, 208 },
206
    { "violet", 238, 130, 238 },
207
    { "wheat", 245, 222, 179 },
208
    { "white", 255, 255, 255 },
209
    { "whitesmoke", 245, 245, 245 },
210
    { "yellow", 255, 255, 0 },
211
    { "yellowgreen", 154, 205, 50 }
212
};
213

            
214
typedef struct {
215
    char *name;
216
    char *value;
217
} svg_attribute_t;
218

            
219
typedef enum {
220
    CONTAINER_ELEMENT,
221
    EMPTY_ELEMENT,
222
    PROCESSING_INSTRUCTION,
223
    DOCTYPE,
224
    CDATA,
225
    COMMENT
226
} tag_type_t;
227

            
228
#define TOP_ELEMENT_TAG "_top"
229

            
230
typedef struct _cairo_svg_element {
231
    cairo_hash_entry_t base;
232
    tag_type_t type;
233
    char *tag;
234
    char *id;
235
    cairo_array_t attributes; /* svg_attribute_t */
236
    cairo_array_t children; /* cairo_svg_element_t* */
237
    cairo_array_t  content; /* char */
238
    cairo_pattern_t *pattern; /* defined if a paint server */
239
    struct _cairo_svg_element *next; /* next on element stack */
240
} cairo_svg_element_t;
241

            
242
typedef struct _cairo_svg_color {
243
    enum { RGB, FOREGROUND } type;
244
    double red;
245
    double green;
246
    double blue;
247
} cairo_svg_color_t;
248

            
249
typedef struct _cairo_svg_paint {
250
    enum { PAINT_COLOR, PAINT_SERVER, PAINT_NONE } type;
251
    cairo_svg_color_t color;
252
    cairo_svg_element_t *paint_server;
253
} cairo_svg_paint_t;
254

            
255
typedef enum {
256
    GS_RENDER,
257
    GS_NO_RENDER,
258
    GS_COMPUTE_BBOX,
259
    GS_CLIP
260
} gs_mode_t;
261

            
262
typedef struct _cairo_svg_graphics_state {
263
    cairo_svg_paint_t fill;
264
    cairo_svg_paint_t stroke;
265
    cairo_svg_color_t color;
266
    double fill_opacity;
267
    double stroke_opacity;
268
    double opacity;
269
    cairo_fill_rule_t fill_rule;
270
    cairo_fill_rule_t clip_rule;
271
    cairo_path_t *clip_path;
272
    char *dash_array;
273
    double dash_offset;
274
    gs_mode_t mode;
275
    struct {
276
        double x;
277
        double y;
278
        double width;
279
        double height;
280
    } bbox;
281
    struct _cairo_svg_graphics_state *next;
282
} cairo_svg_graphics_state_t;
283

            
284
typedef enum {
285
    BUILD_PATTERN_NONE,
286
    BUILD_PATTERN_LINEAR,
287
    BUILD_PATTERN_RADIAL
288
} build_pattern_t;
289

            
290
typedef struct _cairo_svg_glyph_render {
291
    cairo_svg_element_t *tree;
292
    cairo_hash_table_t *ids;
293
    cairo_svg_graphics_state_t *graphics_state;
294
    cairo_t *cr;
295
    double units_per_em;
296
    struct {
297
        cairo_svg_element_t *paint_server;
298
        cairo_pattern_t *pattern;
299
        build_pattern_t type;
300
    } build_pattern;
301
    int render_element_tree_depth;
302
    int num_palette_entries;
303
    FT_Color* palette;
304

            
305
    /* Viewport */
306
    double width;
307
    double height;
308
    cairo_bool_t view_port_set;
309

            
310
    cairo_pattern_t *foreground_marker;
311
    cairo_pattern_t *foreground_source;
312
    cairo_bool_t foreground_source_used;
313

            
314
    int debug; /* 0 = quiet, 1 = errors, 2 = warnings, 3 = info */
315
} cairo_svg_glyph_render_t;
316

            
317

            
318
#define SVG_RENDER_ERROR 1
319
#define SVG_RENDER_WARNING 2
320
#define SVG_RENDER_INFO 3
321

            
322
#define print_error(render, ...) cairo_svg_glyph_render_printf(render, SVG_RENDER_ERROR, ##__VA_ARGS__)
323
#define print_warning(render, ...) cairo_svg_glyph_render_printf(render, SVG_RENDER_WARNING, ##__VA_ARGS__)
324
#define print_info(render, ...) cairo_svg_glyph_render_printf(render, SVG_RENDER_INFO, ##__VA_ARGS__)
325

            
326
static void
327
cairo_svg_glyph_render_printf (cairo_svg_glyph_render_t *svg_render,
328
                               int level,
329
                               const char *fmt, ...) CAIRO_PRINTF_FORMAT (3, 4);
330

            
331
static void
332
1080
cairo_svg_glyph_render_printf (cairo_svg_glyph_render_t *svg_render,
333
                               int level,
334
                               const char *fmt, ...)
335
{
336
    va_list ap;
337

            
338
1080
    if (svg_render->debug >= level ) {
339
        switch (level) {
340
            case SVG_RENDER_ERROR:
341
                printf("ERROR: ");
342
                break;
343
            case SVG_RENDER_WARNING:
344
                printf("WARNING: ");
345
                break;
346
        }
347
	va_start (ap, fmt);
348
	vprintf (fmt, ap);
349
	va_end (ap);
350
	printf ("\n");
351
    }
352
1080
}
353

            
354
static cairo_bool_t
355
105471
string_equal (const char *s1, const char *s2)
356
{
357
105471
    if (s1 && s2)
358
93588
        return strcmp (s1, s2) == 0;
359

            
360
11883
    if (!s1 && !s2)
361
        return TRUE;
362

            
363
11883
    return FALSE;
364
}
365

            
366
static cairo_bool_t
367
1713
string_match (const char **p, const char *str)
368
{
369
1713
    if (*p && strncmp (*p, str, strlen (str)) == 0) {
370
810
        *p += strlen (str);
371
810
        return TRUE;
372
    }
373
903
    return FALSE;
374
}
375

            
376
static const char *
377
15891
skip_space (const char *p)
378
{
379
29721
    while (*p && _cairo_isspace (*p))
380
13830
        p++;
381

            
382
15891
    return p;
383
}
384

            
385
/* Skip over character c and and whitespace before or after. Returns
386
 * NULL if c not found. */
387
static const char *
388
435
skip_char (const char *p, char c)
389
{
390
435
    while (_cairo_isspace (*p))
391
        p++;
392

            
393
435
    if (*p != c)
394
        return NULL;
395

            
396
435
    p++;
397

            
398
669
    while (_cairo_isspace (*p))
399
234
        p++;
400

            
401
435
    return p;
402
}
403

            
404
static int
405
3708
_color_name_compare (const void *a, const void *b)
406
{
407
3708
    const color_name_t *a_color = a;
408
3708
    const color_name_t *b_color = b;
409

            
410
3708
    return strcmp (a_color->name, b_color->name);
411
}
412

            
413
static void
414
384
init_element_id_key (cairo_svg_element_t *element)
415
{
416
384
    element->base.hash = _cairo_hash_string (element->id);
417
384
}
418

            
419
static cairo_bool_t
420
177
_element_id_equal (const void *key_a, const void *key_b)
421
{
422
177
    const cairo_svg_element_t *a = key_a;
423
177
    const cairo_svg_element_t *b = key_b;
424

            
425
177
    return string_equal (a->id, b->id);
426
}
427

            
428
/* Find element with the "id" attribute matching id. id may have the
429
 * '#' prefix. It will be stripped before searching.
430
 */
431
static cairo_svg_element_t *
432
177
lookup_element (cairo_svg_glyph_render_t *svg_render, const char *id)
433
{
434
    cairo_svg_element_t key;
435

            
436
177
    if (!id || strlen (id) < 1)
437
        return NULL;
438

            
439
177
    key.id = (char *)(id[0] == '#' ? id + 1 : id);
440
177
    init_element_id_key (&key);
441
177
    return _cairo_hash_table_lookup (svg_render->ids, &key.base);
442
}
443

            
444
/* Find element with the "id" attribute matching url where url is of
445
 * the form "url(#id)".
446
 */
447
static cairo_svg_element_t *
448
105
lookup_url_element (cairo_svg_glyph_render_t *svg_render, const char *url)
449
{
450
105
    const char *p = url;
451
105
    cairo_svg_element_t *element = NULL;
452

            
453
105
    if (p && string_match (&p, "url")) {
454
105
        p = skip_char (p, '(');
455
105
        if (!p)
456
            return NULL;
457

            
458
105
        const char *end = strpbrk(p, WHITE_SPACE_CHARS ")");
459
105
        if (end) {
460
105
            char *id = _cairo_strndup (p, end - p);
461
105
            element = lookup_element (svg_render, id);
462
105
            free (id);
463
        }
464
    }
465
105
    return element;
466
}
467

            
468
static const char *
469
24237
get_attribute (const cairo_svg_element_t *element, const char *name)
470
{
471
    svg_attribute_t attr;
472
    int num_elems, i;
473

            
474
24237
    num_elems = _cairo_array_num_elements (&element->attributes);
475
95838
    for (i = 0; i < num_elems; i++) {
476
74967
	_cairo_array_copy_element (&element->attributes, i, &attr);
477
74967
        if (string_equal (attr.name, name))
478
3366
            return attr.value;
479
    }
480
20871
    return NULL;
481
}
482

            
483
static const char *
484
78
get_href_attribute (const cairo_svg_element_t *element)
485
{
486
    svg_attribute_t attr;
487
    int num_elems, i, len;
488

            
489
    /* SVG2 requires the href attribute to be "href". Older versions
490
     * used "xlink:href". I have seen at least one font that used an
491
     * alternative name space eg "ns1:href". To keep things simple we
492
     * search for an attribute named "href" or ending in ":href".
493
     */
494
78
    num_elems = _cairo_array_num_elements (&element->attributes);
495
105
    for (i = 0; i < num_elems; i++) {
496
105
	_cairo_array_copy_element (&element->attributes, i, &attr);
497
105
        if (string_equal (attr.name, "href"))
498
            return attr.value;
499

            
500
105
        len = strlen (attr.name);
501
105
        if (len > 4 && string_equal (attr.name + len - 5, ":href"))
502
78
            return attr.value;
503
    }
504
    return NULL;
505
}
506

            
507
/* Get a float attribute or float percentage. If attribute is a
508
 * percentage, the returned value is percentage * scale.  Does not
509
 * modify value if it returns FALSE. This allows value to be set to a
510
 * default before calling get_float_attribute(), then used without
511
 * checking the return value of this function.
512
 */
513
static cairo_bool_t
514
5400
get_float_or_percent_attribute (const cairo_svg_element_t *element,
515
                                const char *name,
516
                                double scale,
517
                                double *value)
518
{
519
    const char *p;
520
    char *end;
521
    double v;
522

            
523
5400
    p = get_attribute (element, name);
524
5400
    if (p) {
525
2025
        v = _cairo_strtod (p, &end);
526
2025
        if (end != p) {
527
2025
            *value = v;
528
2025
            if (*end == '%')
529
189
                *value *= scale / 100.0;
530
2025
            return TRUE;
531
        }
532
    }
533
3375
    return FALSE;
534
}
535

            
536
/* Does not modify value if it returns FALSE. This allows value to be
537
 * set to a default before calling get_float_attribute(), then used
538
 * without checking the return value of this function.
539
 */
540
static cairo_bool_t
541
3777
get_float_attribute (const cairo_svg_element_t *element, const char *name, double *value)
542
{
543
    const char *p;
544
    char *end;
545
    double v;
546

            
547
3777
    p = get_attribute (element, name);
548
3777
    if (p) {
549
186
        v = _cairo_strtod (p, &end);
550
186
        if (end != p) {
551
186
            *value = v;
552
186
            return TRUE;
553
        }
554
    }
555
3591
    return FALSE;
556
}
557

            
558
static cairo_fill_rule_t
559
2328
get_fill_rule_attribute (const cairo_svg_element_t *element, const char *name, cairo_fill_rule_t default_value)
560
{
561
    const char *p;
562

            
563
2328
    p = get_attribute (element, name);
564
2328
    if (string_equal (p, "nonzero"))
565
6
        return CAIRO_FILL_RULE_WINDING;
566
2322
    else if (string_equal (p, "evenodd"))
567
6
        return CAIRO_FILL_RULE_EVEN_ODD;
568
    else
569
2316
        return default_value;
570
}
571

            
572
static void
573
1647
free_elements (cairo_svg_glyph_render_t *svg_render,
574
              cairo_svg_element_t      *element)
575
{
576
    int num_elems;
577

            
578
1647
    num_elems = _cairo_array_num_elements (&element->children);
579
3024
    for (int i = 0; i < num_elems; i++) {
580
	cairo_svg_element_t *child;
581
1377
        _cairo_array_copy_element (&element->children, i, &child);
582
1377
	free_elements (svg_render, child);
583
    }
584
1647
    _cairo_array_fini (&element->children);
585

            
586
1647
    num_elems = _cairo_array_num_elements (&element->attributes);
587
5574
    for (int i = 0; i < num_elems; i++) {
588
3927
	svg_attribute_t *attr = _cairo_array_index (&element->attributes, i);
589
3927
	free (attr->name);
590
3927
	free (attr->value);
591
    }
592
1647
    _cairo_array_fini (&element->attributes);
593
1647
    _cairo_array_fini (&element->content);
594

            
595
1647
    free (element->tag);
596

            
597
1647
    if (element->id) {
598
207
        _cairo_hash_table_remove (svg_render->ids, &element->base);
599
207
        free (element->id);
600
    }
601

            
602
1647
    if (element->pattern)
603
        cairo_pattern_destroy (element->pattern);
604

            
605
1647
    free (element);
606
1647
}
607

            
608
#if SVG_RENDER_PRINT_FUNCTIONS
609

            
610
static void indent(int level)
611
{
612
    for (int i = 1; i < level; i++)
613
        printf("  ");
614
}
615

            
616
static void
617
print_element (cairo_svg_element_t *element, cairo_bool_t recurse, int level)
618
{
619
    char *content = strndup (_cairo_array_index_const (&element->content, 0),
620
                             _cairo_array_num_elements (&element->content));
621

            
622
    indent(level);
623
    if (element->type == COMMENT) {
624
        printf("<!--%s-->\n", content);
625
    } else if (element->type == CDATA) {
626
        printf("<![CDATA[%s]]>\n", content);
627
    } else if (element->type == DOCTYPE) {
628
        printf("<!DOCTYPE%s>\n", content);
629
    } else if (element->type == PROCESSING_INSTRUCTION) {
630
        printf("<?%s?>\n", content);
631
    } else {
632
        cairo_bool_t top_element = string_equal (element->tag, TOP_ELEMENT_TAG);
633

            
634
        if (!top_element) {
635
            printf("<%s", element->tag);
636
            int num_elems = _cairo_array_num_elements (&element->attributes);
637
            for (int i = 0; i < num_elems; i++) {
638
                svg_attribute_t *attr = _cairo_array_index (&element->attributes, i);
639
                printf(" %s=\"%s\"", attr->name, attr->value);
640
            }
641
            if (num_elems > 0)
642
                printf(" ");
643

            
644
            if (element->type == EMPTY_ELEMENT)
645
                printf("/>\n");
646
            else
647
                printf(">\n");
648
        }
649

            
650
        if (element->type == CONTAINER_ELEMENT) {
651
            if (recurse) {
652
                int num_elems = _cairo_array_num_elements (&element->children);
653
                for (int i = 0; i < num_elems; i++) {
654
                    cairo_svg_element_t *child;
655
                    _cairo_array_copy_element (&element->children, i, &child);
656
                    print_element (child, TRUE, level + 1);
657
                }
658
            }
659
            if (!top_element)
660
                printf("</%s>\n", element->tag);
661
        }
662
    }
663
    free (content);
664
}
665
#endif
666

            
667
static const char *
668
963
parse_list_of_floats (const char *p,
669
                      int num_required,
670
                      int num_optional,
671
                      cairo_bool_t *have_optional,
672
                      va_list ap)
673
{
674
    double d;
675
    double *dp;
676
    char *end;
677
963
    const char *q = NULL;
678
963
    int num_found = 0;
679

            
680
4758
    for (int i = 0; i < num_required + num_optional; i++) {
681
7146
        while (p && (*p == ',' || _cairo_isspace (*p)))
682
3336
            p++;
683

            
684
3810
        if (!p)
685
            break;
686

            
687
3810
        d = _cairo_strtod (p, &end);
688
3810
        if (end == p) {
689
15
            p = NULL;
690
15
            break;
691
        }
692
3795
        p = end;
693
3795
        dp = va_arg (ap, double *);
694
3795
        *dp = d;
695
3795
        num_found++;
696
3795
        if (num_found == num_required)
697
963
            q = p;
698
    }
699

            
700
963
    if (num_optional > 0) {
701
138
        if (num_found == num_required + num_optional) {
702
123
            *have_optional = TRUE;
703
        } else {
704
15
            *have_optional = FALSE;
705
            /* restore pointer to end of required floats */
706
15
            p = q;
707
        }
708
    }
709

            
710
963
    return p;
711
}
712

            
713
static const char *
714
150
get_floats (const char *p,
715
            int num_required,
716
            int num_optional,
717
            cairo_bool_t *have_optional,
718
            ...)
719
{
720
    va_list ap;
721

            
722
150
    va_start (ap, have_optional);
723
150
    p = parse_list_of_floats (p, num_required, num_optional, have_optional, ap);
724
150
    va_end (ap);
725
150
    return p;
726
}
727

            
728
static const char *
729
813
get_path_params (const char *p, int num_params, ...)
730
{
731
    va_list ap;
732

            
733
813
    va_start (ap, num_params);
734
813
    p = parse_list_of_floats (p, num_params, 0, NULL, ap);
735
813
    va_end (ap);
736
813
    return p;
737
}
738

            
739
static cairo_bool_t
740
720
get_color (cairo_svg_glyph_render_t *svg_render,
741
           const char               *s,
742
           cairo_svg_color_t        *color)
743
{
744
    int len, matched;
745
720
    unsigned r = 0, g = 0, b = 0;
746

            
747
720
    if (!s)
748
        return FALSE;
749

            
750
720
    len = strlen(s);
751

            
752
720
    if (string_equal (s, "inherit")) {
753
	return FALSE;
754
1398
    } else if (string_equal (s, "currentColor") ||
755
1356
	       string_equal (s, "context-fill") ||
756
678
	       string_equal (s, "context-stroke"))
757
    {
758
42
	*color = svg_render->graphics_state->color;
759
42
        return TRUE;
760
678
    } else if (len > 0 && s[0] == '#') {
761
27
        if (len == 4) {
762
6
            matched = sscanf (s + 1, "%1x%1x%1x", &r, &g, &b);
763
6
            if (matched == 3) {
764
                /* Each digit is repeated to convert to 6 digits. eg 0x123 -> 0x112233 */
765
6
                color->type = RGB;
766
6
                color->red = 0x11*r/255.0;
767
6
                color->green = 0x11*g/255.0;
768
6
                color->blue = 0x11*b/255.0;
769
6
                return TRUE;
770
            }
771
21
        } else if (len == 7) {
772
21
            matched = sscanf (s + 1, "%2x%2x%2x", &r, &g, &b);
773
21
            if (matched == 3) {
774
21
                color->type = RGB;
775
21
                color->red = r/255.0;
776
21
                color->green = g/255.0;
777
21
                color->blue = b/255.0;
778
21
                return TRUE;
779
            }
780
        }
781
651
    } else if (strncmp (s, "rgb", 3) == 0) {
782
6
        matched = sscanf (s, "rgb ( %u , %u , %u )", &r, &g, &b);
783
6
        if (matched == 3) {
784
6
            color->type = RGB;
785
6
            color->red = r/255.0;
786
6
            color->green = g/255.0;
787
6
            color->blue = b/255.0;
788
6
            return TRUE;
789
        }
790
645
    } else if (strncmp (s, "var", 3) == 0) {
791
        /* CPAL palettes colors. eg "var(--color0, yellow)" */
792
30
        s += 3;
793
30
        s = skip_char (s, '(');
794
30
        if (!string_match (&s, "--color"))
795
            return FALSE;
796

            
797
        char *end;
798
30
        int entry = strtol (s, &end, 10);
799
30
        if (end == s)
800
            return FALSE;
801

            
802
30
        if (svg_render->palette && entry >= 0 && entry < svg_render->num_palette_entries) {
803
30
            FT_Color *palette_color = &svg_render->palette[entry];
804
30
            color->type = RGB;
805
30
            color->red = palette_color->red / 255.0;
806
30
            color->green = palette_color->green/ 255.0;
807
30
            color->blue = palette_color->blue / 255.0;
808
30
            return TRUE;
809
        } else {
810
            /* Fallback color */
811
            s = skip_char (end, ',');
812
            if (!s)
813
            return FALSE;
814

            
815
            end = strpbrk(s, WHITE_SPACE_CHARS ")");
816
            if (!end || end == s)
817
		return FALSE;
818

            
819
            char *fallback = _cairo_strndup (s, end - s);
820
            cairo_bool_t success = get_color (svg_render, fallback, color);
821
            free (fallback);
822
            return success;
823
        }
824
    } else {
825
        const color_name_t *color_name;
826
        color_name_t color_name_key;
827

            
828
615
        color_name_key.name = (char *) s;
829
615
        color_name = bsearch (&color_name_key,
830
                              color_names,
831
                              ARRAY_LENGTH (color_names),
832
                              sizeof (color_name_t),
833
                             _color_name_compare);
834
615
        if (color_name) {
835
615
            color->type = RGB;
836
615
            color->red = color_name->red/255.0;
837
615
            color->green = color_name->green/255.0;
838
615
            color->blue = color_name->blue/255.0;
839
615
            return TRUE;
840
        }
841
    }
842
    return FALSE;
843
}
844

            
845
static void
846
924
get_paint (cairo_svg_glyph_render_t *svg_render,
847
           const char *p,
848
           cairo_svg_paint_t *paint)
849
{
850
    cairo_svg_element_t *element;
851

            
852
924
    if (string_match (&p, "none")) {
853
387
        paint->type = PAINT_NONE;
854
387
        paint->paint_server = NULL;
855
537
    } else if (p && strncmp (p, "url", 3) == 0) {
856
57
        element = lookup_url_element (svg_render, p);
857
57
        if (element) {
858
57
            paint->type = PAINT_SERVER;
859
57
            paint->paint_server = element;
860
        }
861
    } else {
862
480
        if (get_color (svg_render, p, &paint->color)) {
863
480
            paint->type = PAINT_COLOR;
864
480
            paint->paint_server = NULL;
865
        }
866
    }
867
924
}
868

            
869
#ifdef SVG_RENDER_PRINT_FUNCTIONS
870

            
871
static void
872
print_color (cairo_svg_color_t *color)
873
{
874
    switch (color->type) {
875
        case FOREGROUND_COLOR:
876
            printf("foreground");
877
            break;
878
        case RGB:
879
            printf("#%02x%02x%02x",
880
                   (int)(color->red*255),
881
                   (int)(color->red*255),
882
                   (int)(color->red*255));
883
            break;
884
    }
885
}
886

            
887
static void
888
print_paint (cairo_svg_paint_t *paint)
889
{
890
    printf("Paint: ");
891
    switch (paint->type) {
892
        case PAINT_COLOR:
893
            printf("color: ");
894
            print_color (&paint->color);
895
            break;
896
        case PAINT_SERVER:
897
            printf("server: %s", paint->paint_server->tag);
898
            break;
899
        case PAINT_NONE:
900
            printf("none");
901
            break;
902
    }
903
    printf("\n");
904
}
905

            
906
#endif
907

            
908
static void
909
parse_error (cairo_svg_glyph_render_t *svg_render,
910
             const char *string,
911
             const char *location,
912
             const char *fmt,
913
             ...) CAIRO_PRINTF_FORMAT (4, 5);
914

            
915
static void
916
parse_error (cairo_svg_glyph_render_t *svg_render,
917
             const char *string,
918
             const char *location,
919
             const char *fmt,
920
             ...)
921
{
922
    va_list ap;
923
    const int context = 40;
924
    const char *start;
925
    const char *end;
926

            
927
    if (svg_render->debug >= SVG_RENDER_ERROR) {
928
        printf("ERROR: ");
929
	va_start (ap, fmt);
930
	vprintf (fmt, ap);
931
	va_end (ap);
932
        putchar ('\n');
933
        start = location - context;
934
        if (start < string)
935
            start = string;
936

            
937
        end = location + strlen (location);
938
        if (end - location > context)
939
            end = location + context;
940

            
941
        for (const char *p = start; p < end; p++) {
942
            if (_cairo_isspace (*p))
943
                putchar (' ');
944
            else
945
                putchar (*p);
946
        }
947
        putchar ('\n');
948

            
949
        for (int i = 0; i < location - start; i++)
950
            putchar(' ');
951
        putchar ('^');
952
        putchar ('\n');
953
	printf (" at position %td\n", location - string);
954
    }
955
}
956

            
957
static cairo_bool_t
958
3927
append_attribute (cairo_svg_element_t *element, svg_attribute_t *attribute)
959
{
960
    const char *p;
961
    const char *end;
962
    svg_attribute_t attr;
963

            
964
3927
    memset (&attr, 0, sizeof (attr));
965
3927
    if (string_equal (attribute->name, "style")) {
966
        /* split style into individual attributes */
967
        p = attribute->value;
968
        while (*p) {
969
            end = strchr (p, ':');
970
            if (!end || end == p)
971
                break;
972
            attr.name = _cairo_strndup (p, end - p);
973
            p = end + 1;
974
            p = skip_space(p);
975
            end = strchr (p, ';');
976
            if (!end)
977
                end = strchr (p, 0);
978
            if (end == p)
979
                goto split_style_fail;
980

            
981
            attr.value = _cairo_strndup (p, end - p);
982
            if (*end)
983
                p = end + 1;
984

            
985
            if (_cairo_array_append (&element->attributes, &attr))
986
                goto split_style_fail;
987

            
988
            memset (&attr, 0, sizeof (attr));
989
            p = skip_space (p);
990
        }
991
    }
992

            
993
3927
    if (_cairo_array_append (&element->attributes, attribute))
994
        return FALSE;
995

            
996
3927
    return TRUE;
997

            
998
  split_style_fail:
999
    free (attr.name);
    free (attr.value);
    return FALSE;
}
static cairo_bool_t
1377
add_child_element (cairo_svg_glyph_render_t *svg_render,
                   cairo_svg_element_t *parent,
                   cairo_svg_element_t *child)
{
    cairo_status_t status;
    const char* id;
1377
    id = get_attribute (child, "id");
1377
    if (id) {
207
        child->id = strdup (id);
207
        init_element_id_key (child);
207
	status = _cairo_hash_table_insert (svg_render->ids, &child->base);
207
	if (unlikely (status))
            return FALSE;
    }
1377
    status = _cairo_array_append (&parent->children, &child);
1377
    return status == CAIRO_STATUS_SUCCESS;
}
static cairo_svg_element_t *
1647
create_element (tag_type_t type, char *tag)
{
    cairo_svg_element_t *elem;
    cairo_status_t status;
1647
    elem = _cairo_calloc (sizeof (cairo_svg_element_t));
1647
    if (unlikely (elem == NULL)) {
	status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
        return NULL;
    }
1647
    elem->type = type;
1647
    elem->tag = tag;
1647
    elem->id = NULL;
1647
    _cairo_array_init (&elem->attributes, sizeof(svg_attribute_t));
1647
    _cairo_array_init (&elem->children, sizeof(cairo_svg_element_t *));
1647
    _cairo_array_init (&elem->content, sizeof(char));
1647
    elem->pattern = NULL;
1647
    elem->next = NULL;
1647
    return elem;
}
static const char *
1242
parse_attributes (cairo_svg_glyph_render_t *svg_render,
                  const char               *attributes,
                  cairo_svg_element_t      *element)
{
    svg_attribute_t attr;
    char quote_char;
    const char *p;
    const char *end;
1242
    p = attributes;
1242
    memset (&attr, 0, sizeof (svg_attribute_t));
1242
    p = skip_space (p);
5169
    while (*p && *p != '/' && *p != '>' && *p != '?') {
3927
        end = strpbrk(p, WHITE_SPACE_CHARS "=");
3927
        if (!end) {
            parse_error (svg_render, attributes, p, "Could not find '='");
            goto fail;
        }
3927
        if (end == p) {
            parse_error (svg_render, attributes, p, "Missing attribute name");
            goto fail;
        }
3927
        attr.name = _cairo_strndup (p, end - p);
3927
        p = end;
3927
        p = skip_space (p);
3927
        if (*p != '=') {
            parse_error (svg_render, attributes, p, "Expected '='");
            goto fail;
        }
3927
        p++;
3927
        p = skip_space (p);
3927
        if (*p == '\"' || *p == '\'') {
3927
            quote_char = *p;
        } else {
            parse_error (svg_render, attributes, p, "Could not find '\"' or '''");
            goto fail;
        }
3927
        p++;
3927
        end = strchr (p, quote_char);
3927
        if (!end) {
            parse_error (svg_render, attributes, p, "Could not find '%c'", quote_char);
            goto fail;
        }
3927
        attr.value = _cairo_strndup (p, end - p);
3927
        p = end + 1;
3927
        if (!append_attribute (element, &attr))
            goto fail;
3927
        memset (&attr, 0, sizeof (svg_attribute_t));
3927
        p = skip_space (p);
    }
1242
    return p;
  fail:
    free (attr.name);
    free (attr.value);
    return NULL;
}
static cairo_bool_t
270
parse_svg (cairo_svg_glyph_render_t *svg_render,
           const char               *svg_document)
{
270
    const char *p = svg_document;
    const char *end;
    int nesting; /* when > 0 we parse content */
    cairo_svg_element_t *open_elem; /* Stack of open elements */
270
    cairo_svg_element_t *new_elem = NULL;
    char *name;
    cairo_status_t status;
    /* Create top level element to use as a container for all top
     * level elements in the document and push it on the stack. */
270
    open_elem = create_element (CONTAINER_ELEMENT, strdup(TOP_ELEMENT_TAG));
    /* We don't want to add content to the top level container. There
     * should only be whitesapce between tags. */
270
    nesting = 0;
2190
    while (*p) {
1920
        if (nesting > 0) {
            /* In an open element. Anything before the next '<' is content */
1644
            end = strchr (p, '<');
1644
            if (!end) {
                parse_error (svg_render, svg_document, p, "Could not find '<'");
                goto fail;
            }
1644
            status = _cairo_array_append_multiple (&open_elem->content, p, end - p);
1644
            p = end;
        } else {
276
            p = skip_space (p);
276
            if (*p == 0)
                break; /* end of document */
        }
        /* We should now be at the start of a tag */
1920
        if (*p != '<') {
            parse_error (svg_render, svg_document, p, "Could not find '<'");
            goto fail;
        }
1920
        p++;
1920
        if (*p == '!') {
132
            p++;
132
            if (string_match (&p, "[CDATA[")) {
                new_elem = create_element (CDATA, NULL);
                end = strstr (p, "]]>");
                if (!end) {
                    parse_error (svg_render, svg_document, p, "Could not find ']]>'");
                    goto fail;
                }
                status = _cairo_array_append_multiple (&new_elem->content, p, end - p);
                p = end + 3;
132
            } else if (string_match (&p, "--")) {
129
                new_elem = create_element (COMMENT, NULL);
129
                end = strstr (p, "-->");
129
                if (!end) {
                    parse_error (svg_render, svg_document, p, "Could not find '-->'");
                    goto fail;
                }
129
                status = _cairo_array_append_multiple (&new_elem->content, p, end - p);
129
                p = end + 3;
3
            } else if (string_match (&p, "DOCTYPE")) {
3
                new_elem = create_element (DOCTYPE, NULL);
3
                end = strchr (p, '>');
3
                if (!end) {
                    parse_error (svg_render, svg_document, p, "Could not find '>'");
                    goto fail;
                }
3
                status = _cairo_array_append_multiple (&new_elem->content, p, end - p);
3
                p = end + 1;
            } else {
                parse_error (svg_render, svg_document, p, "Invalid");
                goto fail;
            }
132
            if (!add_child_element (svg_render, open_elem, new_elem))
                goto fail;
132
            new_elem = NULL;
132
            continue;
        }
1788
        if (*p == '?') {
3
            p++;
3
            new_elem = create_element (PROCESSING_INSTRUCTION, NULL);
3
            end = strstr (p, "?>");
3
            if (!end) {
                parse_error (svg_render, svg_document, p, "Could not find '?>'");
                goto fail;
            }
3
            status = _cairo_array_append_multiple (&new_elem->content, p, end - p);
3
            p = end + 2;
3
            if (!add_child_element (svg_render, open_elem, new_elem))
                goto fail;
3
            new_elem = NULL;
3
            continue;
        }
1785
        if (*p == '/') {
            /* Closing tag */
543
            p++;
            /* find end of tag name */
543
            end = strpbrk(p, WHITE_SPACE_CHARS ">");
543
            if (!end) {
                parse_error (svg_render, svg_document, p, "Could not find '>'");
                goto fail;
            }
543
            name = _cairo_strndup (p, end - p);
543
            p = end;
543
            p = skip_space (p);
543
            if (*p != '>') {
                parse_error (svg_render, svg_document, p, "Could not find '>'");
                free (name);
                goto fail;
            }
543
            p++;
543
            if (nesting == 0) {
                parse_error (svg_render, svg_document, p, "parse_elements: parsed </%s> but no matching start tag", name);
                free (name);
                goto fail;
            }
543
            if (!string_equal (name, open_elem->tag)) {
                parse_error (svg_render, svg_document, p,
                             "parse_elements: found </%s> but current open tag is <%s>",
                             name, open_elem->tag);
                free (name);
                goto fail;
            }
            /* pop top element on open elements stack into new_elem */
543
            new_elem = open_elem;
543
            open_elem = open_elem->next;
543
            new_elem->next = NULL;
543
            nesting--;
543
            free (name);
543
            if (!add_child_element (svg_render, open_elem, new_elem))
                goto fail;
543
            new_elem = NULL;
543
            continue;
        }
        /* We should now be in a start or empty element tag */
        /* find end of tag name */
1242
        end = strpbrk(p, WHITE_SPACE_CHARS "/>");
1242
        if (!end) {
            parse_error (svg_render, svg_document, p, "Could not find '>'");
            goto fail;
        }
1242
        name = _cairo_strndup (p, end - p);
1242
        p = end;
1242
        new_elem = create_element (CONTAINER_ELEMENT, name);
1242
        p = parse_attributes (svg_render, p, new_elem);
1242
        if (!p)
            goto fail;
1242
        p = skip_space (p);
1242
        if (*p == '/') {
699
            new_elem->type = EMPTY_ELEMENT;
699
            p++;
        }
1242
        if (!p || *p != '>') {
            print_error (svg_render, "Could not find '>'");
            goto fail;
        }
1242
        p++;
1242
        if (new_elem->type == EMPTY_ELEMENT) {
699
            if (!add_child_element (svg_render, open_elem, new_elem))
                goto fail;
699
            new_elem = NULL;
        } else {
            /* push new elem onto open elements stack */
543
            new_elem->next = open_elem;
543
            open_elem = new_elem;
543
            new_elem = NULL;
543
            nesting++;
        }
    }
270
    if (nesting != 0) {
        parse_error (svg_render, svg_document, p, "Missing closing tag for <%s>", open_elem->tag);
        goto fail;
    }
270
    svg_render->tree = open_elem;
270
    return TRUE;
  fail:
    if (new_elem)
        free_elements (svg_render, new_elem);
    while (open_elem) {
        cairo_svg_element_t *elem = open_elem;
        open_elem = open_elem->next;
        free_elements (svg_render, elem);
    }
    return FALSE;
}
static cairo_bool_t
174
parse_transform (const char *p, cairo_matrix_t *matrix)
{
    cairo_matrix_t m;
    double x, y, a;
    cairo_bool_t have_optional;
174
    cairo_matrix_init_identity (matrix);
324
    while (p) {
282
        while (p && (*p == ',' || _cairo_isspace (*p)))
9
            p++;
273
        if (!p || *p == 0)
            break;
150
        if (string_match (&p, "matrix")) {
3
            p = skip_char (p, '(');
3
            if (!p)
                break;
3
            p = get_floats (p, 6, 0, NULL, &m.xx, &m.yx, &m.xy, &m.yy, &m.x0, &m.y0);
3
            if (!p)
                break;
3
            p = skip_char (p, ')');
3
            if (!p)
                break;
3
            cairo_matrix_multiply (matrix, &m, matrix);
147
        } else if (string_match (&p, "translate")) {
96
            p = skip_char (p, '(');
96
            if (!p)
                break;
96
            p = get_floats (p, 1, 1, &have_optional, &x, &y);
96
            if (!p)
                break;
96
            p = skip_char (p, ')');
96
            if (!p)
                break;
96
            if (!have_optional)
                y = 0;
96
            cairo_matrix_translate (matrix, x, y);
51
        } else if (string_match (&p, "scale")) {
30
            p = skip_char (p, '(');
30
            if (!p)
                break;
30
            p = get_floats (p, 1, 1, &have_optional, &x, &y);
30
            if (!p)
                break;
30
            p = skip_char (p, ')');
30
            if (!p)
                break;
30
            if (!have_optional)
6
                y = x;
30
            cairo_matrix_scale (matrix, x, y);
21
        } else if (string_match (&p, "rotate")) {
12
            p = skip_char (p, '(');
12
            if (!p)
                break;
12
            p = get_floats (p, 1, 2, &have_optional, &a, &x, &y);
12
            if (!p)
                break;
12
            p = skip_char (p, ')');
12
            if (!p)
                break;
12
            if (!have_optional) {
9
                x = 0;
9
                y = 0;
            }
12
            a *= M_PI/180.0;
12
            cairo_matrix_translate (matrix, x, y);
12
            cairo_matrix_rotate (matrix, a);
12
            cairo_matrix_translate (matrix, -x, -y);
9
        } else if (string_match (&p, "skewX")) {
6
            p = skip_char (p, '(');
6
            if (!p)
                break;
6
            p = get_floats (p, 1, 0, NULL, &a);
6
            if (!p)
                break;
6
            p = skip_char (p, ')');
6
            if (!p)
                break;
6
            a *= M_PI/180.0;
6
            cairo_matrix_init_identity (&m);
6
            m.xy = tan (a);
6
            cairo_matrix_multiply (matrix, &m, matrix);
3
        } else if (string_match (&p, "skewY")) {
3
            p = skip_char (p, '(');
3
            if (!p)
                break;
3
            p = get_floats (p, 1, 0, NULL, &a);
3
            if (!p)
                break;
3
            p = skip_char (p, ')');
3
            if (!p)
                break;
3
            a *= M_PI/180.0;
3
            cairo_matrix_init_identity (&m);
3
            m.yx = tan (a);
3
            cairo_matrix_multiply (matrix, &m, matrix);
        } else {
            break;
        }
    }
174
    return p != NULL;
}
static void
render_element_tree (cairo_svg_glyph_render_t *svg_render,
                     cairo_svg_element_t      *element,
                     cairo_svg_element_t      *display_element,
                     cairo_bool_t              children_only);
static cairo_pattern_t *
57
create_pattern (cairo_svg_glyph_render_t *svg_render,
                cairo_svg_element_t      *paint_server)
{
57
    cairo_pattern_t *pattern = NULL;
57
    if (paint_server) {
57
        svg_render->build_pattern.paint_server = paint_server;
57
        render_element_tree (svg_render, paint_server, NULL, FALSE);
57
        pattern = svg_render->build_pattern.pattern;
57
        svg_render->build_pattern.pattern = NULL;
57
        svg_render->build_pattern.paint_server = NULL;
57
        svg_render->build_pattern.type = BUILD_PATTERN_NONE;
    }
57
    if (!pattern)
        pattern = cairo_pattern_create_rgb (0, 0, 0);
57
    return pattern;
}
static cairo_bool_t
270
render_element_svg (cairo_svg_glyph_render_t *svg_render,
                    cairo_svg_element_t      *element,
                    cairo_bool_t              end_tag)
{
    double width, height;
    double vb_x, vb_y, vb_height, vb_width;
    const char *p;
    const char *end;
270
    if (end_tag)
        return FALSE;
    /* Default viewport width, height is EM square */
270
    if (!get_float_or_percent_attribute (element, "width", svg_render->units_per_em, &width))
246
        width = svg_render->units_per_em;
270
    if (!get_float_or_percent_attribute (element, "height", svg_render->units_per_em, &height))
246
        height = svg_render->units_per_em;
    /* Transform viewport to unit square, centering it if width != height. */
270
    if (width > height) {
        cairo_scale (svg_render->cr, 1.0/width, 1.0/width);
        cairo_translate (svg_render->cr, 0, (width - height)/2.0);
    } else {
270
        cairo_scale (svg_render->cr, 1.0/height, 1.0/height);
270
        cairo_translate (svg_render->cr, (height - width)/2.0, 0);
    }
270
    svg_render->width = width;
270
    svg_render->height = height;
270
    p = get_attribute (element, "viewBox");
270
    if (p) {
        /* Transform viewport to viewbox */
6
        end = get_path_params (p, 4, &vb_x, &vb_y, &vb_width, &vb_height);
6
        if (!end) {
            print_warning (svg_render, "viewBox expected 4 numbers: %s", p);
            return FALSE;
        }
6
        cairo_translate (svg_render->cr, -vb_x * width/vb_width, -vb_y * width/vb_width);
6
        cairo_scale (svg_render->cr, width/vb_width, height/vb_height);
6
        svg_render->width = vb_width;
6
        svg_render->height = vb_height;
    }
270
    svg_render->view_port_set = TRUE;
270
    return TRUE;
}
static cairo_bool_t
96
render_element_clip_path (cairo_svg_glyph_render_t *svg_render,
                          cairo_svg_element_t      *element,
                          cairo_bool_t              end_tag)
{
96
    cairo_svg_graphics_state_t *gs = svg_render->graphics_state;
    const char *p;
96
    if (end_tag || gs->mode != GS_CLIP || svg_render->build_pattern.type != BUILD_PATTERN_NONE) {
72
        return FALSE;
    }
24
    p = get_attribute (element, "clipPathUnits");
24
    if (string_equal (p, "objectBoundingBox")) {
12
        cairo_translate (svg_render->cr,
12
                                svg_render->graphics_state->bbox.x,
12
                                svg_render->graphics_state->bbox.y);
12
        cairo_scale (svg_render->cr,
12
                     svg_render->graphics_state->bbox.width,
12
                     svg_render->graphics_state->bbox.height);
    }
24
    return TRUE;
}
static void
57
apply_gradient_attributes (cairo_svg_glyph_render_t *svg_render,
                           cairo_svg_element_t      *element)
{
57
    cairo_pattern_t *pattern = svg_render->build_pattern.pattern;
57
    cairo_bool_t object_bbox = TRUE;
    cairo_matrix_t transform;
    cairo_matrix_t mat;
    const char *p;
57
    if (!pattern)
        return;
57
    p = get_attribute (element, "gradientUnits");
57
    if (string_equal (p, "userSpaceOnUse"))
9
        object_bbox = FALSE;
57
    cairo_matrix_init_identity (&mat);
57
    if (object_bbox) {
48
        cairo_matrix_translate (&mat,
48
                                svg_render->graphics_state->bbox.x,
48
                                svg_render->graphics_state->bbox.y);
48
        cairo_matrix_scale (&mat,
48
                            svg_render->graphics_state->bbox.width,
48
                            svg_render->graphics_state->bbox.height);
    }
57
    p = get_attribute (element, "gradientTransform");
57
     if (parse_transform (p, &transform))
6
         cairo_matrix_multiply (&mat, &transform, &mat);
57
    if (cairo_matrix_invert (&mat) == CAIRO_STATUS_SUCCESS)
57
        cairo_pattern_set_matrix (pattern, &mat);
57
    p = get_attribute (element, "spreadMethod");
57
    if (string_equal (p, "reflect"))
6
        cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REFLECT);
51
    else if (string_equal (p, "repeat"))
6
        cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
}
static cairo_bool_t
54
render_element_linear_gradient (cairo_svg_glyph_render_t *svg_render,
                                cairo_svg_element_t      *element,
                                cairo_bool_t              end_tag)
{
    double x1, y1, x2, y2;
54
    if (svg_render->build_pattern.paint_server != element ||
27
        end_tag ||
27
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
27
        return FALSE;
    /* FIXME default value for userSpaceOnUse? */
27
    double width = 1.0;
27
    double height = 1.0;
27
    if (!get_float_or_percent_attribute (element, "x1", width, &x1))
3
        x1 = 0.0;
27
    if (!get_float_or_percent_attribute (element, "y1", height, &y1))
24
        y1 = 0.0;
27
    if (!get_float_or_percent_attribute (element, "x2", width, &x2))
18
        x2 = width;
27
    if (!get_float_or_percent_attribute (element, "y2", height, &y2))
24
        y2 = 0.0;
27
    if (svg_render->build_pattern.pattern)
        abort();
27
    svg_render->build_pattern.pattern = cairo_pattern_create_linear (x1, y1, x2, y2);
27
    svg_render->build_pattern.type = BUILD_PATTERN_LINEAR;
27
    apply_gradient_attributes (svg_render, element);
27
    return TRUE;
}
static cairo_bool_t
60
render_element_radial_gradient (cairo_svg_glyph_render_t *svg_render,
                                cairo_svg_element_t      *element,
                                cairo_bool_t              end_tag)
{
    double cx, cy, r, fx, fy;
60
    if (svg_render->build_pattern.paint_server != element ||
30
        end_tag ||
30
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
30
        return FALSE;
    /* FIXME default value for userSpaceOnUse? */
30
    double width = 1.0;
30
    double height = 1.0;
30
    if (!get_float_or_percent_attribute (element, "cx", width, &cx))
        cx = 0.5 * width;
30
    if (!get_float_or_percent_attribute (element, "cy", height, &cy))
        cy = 0.5 * height;
30
    if (!get_float_or_percent_attribute (element, "r", width, &r))
        r = 0.5 * width;
30
    if (!get_float_or_percent_attribute (element, "fx", width, &fx))
        fx = cx;
30
    if (!get_float_or_percent_attribute (element, "fy", height, &fy))
        fy = cy;
30
    svg_render->build_pattern.pattern = cairo_pattern_create_radial (fx, fy, 0, cx, cy, r);
30
    svg_render->build_pattern.type = BUILD_PATTERN_RADIAL;
30
    apply_gradient_attributes (svg_render, element);
30
    return TRUE;
}
static cairo_bool_t
117
render_element_stop (cairo_svg_glyph_render_t *svg_render,
                     cairo_svg_element_t      *element,
                     cairo_bool_t              end_tag)
{
    double offset, opacity;
117
    cairo_pattern_t *pattern = svg_render->build_pattern.pattern;
117
    if (!pattern)
        return FALSE;
180
    if (cairo_pattern_get_type (pattern) != CAIRO_PATTERN_TYPE_LINEAR &&
63
        cairo_pattern_get_type (pattern) != CAIRO_PATTERN_TYPE_RADIAL)
        return FALSE;
117
    if (!get_float_or_percent_attribute (element, "offset", 1.0, &offset))
        return FALSE;
117
    if (!get_float_attribute (element, "stop-opacity", &opacity))
9
        opacity = 1.0;
    cairo_svg_color_t color;
117
    get_color (svg_render, "black", &color);
117
    get_color (svg_render, get_attribute(element, "stop-color"), &color);
117
    if (color.type == RGB) {
93
        cairo_pattern_add_color_stop_rgba (pattern,
                                           offset,
                                           color.red,
                                           color.green,
                                           color.blue,
                                           opacity);
    } else { /* color.type == FOREGROUND */
        double red, green, blue, alpha;
24
        if (cairo_pattern_get_rgba (svg_render->foreground_source, &red, &green, &blue, &alpha) == CAIRO_STATUS_SUCCESS) {
24
	    svg_render->foreground_source_used = TRUE;
	} else {
            red = green = blue = 0;
            alpha = 1;
        }
24
        cairo_pattern_add_color_stop_rgba (pattern, offset, red, green, blue, alpha);
    }
117
    return TRUE;
}
static cairo_bool_t
138
render_element_g (cairo_svg_glyph_render_t *svg_render,
                  cairo_svg_element_t      *element,
                  cairo_bool_t              end_tag)
{
138
    if (svg_render->graphics_state->mode == GS_NO_RENDER ||
138
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;
138
    if (!end_tag) {
69
        cairo_push_group (svg_render->cr);
    } else {
69
        cairo_pop_group_to_source (svg_render->cr);
69
        cairo_paint_with_alpha (svg_render->cr, svg_render->graphics_state->opacity);
    }
138
    return TRUE;
}
typedef struct {
    const char *data; /* current position in base64 data */
    char buf[3]; /* decode buffer */
    int buf_pos; /* current position in buf_pos. */
} base64_decode_t;
static cairo_status_t
150
_read_png_from_base64 (void *closure, unsigned char *data, unsigned int length)
{
150
    base64_decode_t *decode = closure;
    int n, c;
    unsigned val;
39690
    while (length) {
39540
        if (decode->buf_pos >= 0) {
39498
            *data++ = decode->buf[decode->buf_pos++];
39498
            length--;
39498
            if (decode->buf_pos == 3)
13164
                decode->buf_pos = -1;
        }
39540
        if (length > 0 && decode->buf_pos < 0) {
13170
            n = 0;
13170
            val = 0;
66546
            while (*decode->data && n < 4) {
53376
                c = *decode->data++;
53376
                if (c >='A' && c <='Z') {
21744
                    val = (val << 6) | (c -'A');
21744
                    n++;
31632
                } else if (c >='a' && c <='z') {
21222
                    val = (val << 6) | (c -'a' + 26);
21222
                    n++;
10410
                } else if (c >='0' && c <='9') {
8088
                    val = (val << 6) | (c -'0' + 52);
8088
                    n++;
2322
                } else if (c =='+') {
786
                    val = (val << 6) | 62;
786
                    n++;
1536
                } else if (c =='/') {
828
                    val = (val << 6) | 63;
828
                    n++;
708
                } else if (c == '=') {
12
                    val = (val << 6);
12
                    n++;
                }
            }
13170
            if (n < 4)
                return CAIRO_STATUS_READ_ERROR;
13170
            decode->buf[0] = val >> 16;
13170
            decode->buf[1] = val >> 8;
13170
            decode->buf[2] = val >> 0;
13170
            decode->buf_pos = 0;
        }
    }
150
    return CAIRO_STATUS_SUCCESS;
}
static cairo_bool_t
6
render_element_image (cairo_svg_glyph_render_t *svg_render,
                      cairo_svg_element_t      *element,
                      cairo_bool_t              end_tag)
{
    double x, y, width, height;
    int w, h;
    const char *data;
    cairo_surface_t *surface;
    base64_decode_t decode;
6
    if (svg_render->graphics_state->mode == GS_NO_RENDER ||
6
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;
6
    if (!get_float_attribute (element, "x", &x))
        x = 0;
6
    if (!get_float_attribute (element, "y", &y))
        y = 0;
6
    if (!get_float_attribute (element, "width", &width))
        return FALSE;
6
    if (!get_float_attribute (element, "height", &height))
        return FALSE;
6
    data = get_href_attribute (element);
6
    if (!data)
        return FALSE;
6
    if (!string_match (&data, "data:image/png;base64,"))
        return FALSE;
6
    decode.data = data;
6
    decode.buf_pos = -1;
6
    surface = cairo_image_surface_create_from_png_stream (_read_png_from_base64, &decode);
6
    if (cairo_surface_status (surface)) {
        print_warning (svg_render, "Unable to decode PNG");
        cairo_surface_destroy (surface);
        return FALSE;
    }
6
    w = cairo_image_surface_get_width (surface);
6
    h = cairo_image_surface_get_height (surface);
6
    if (w > 0 && h > 0) {
6
        cairo_translate (svg_render->cr, x, y);
6
        cairo_scale (svg_render->cr, width/w, height/h);
6
        cairo_set_source_surface (svg_render->cr, surface, 0, 0);
6
        cairo_paint (svg_render->cr);
    }
6
    cairo_surface_destroy (surface);
6
    return FALSE;
}
static cairo_bool_t
72
render_element_use (cairo_svg_glyph_render_t *svg_render,
                    cairo_svg_element_t      *element,
                    cairo_bool_t              end_tag)
{
72
    double x = 0;
72
    double y = 0;
    const char *id;
72
    if (end_tag || svg_render->graphics_state->mode == GS_NO_RENDER ||
72
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;
72
    get_float_attribute (element, "x", &x);
72
    get_float_attribute (element, "y", &y);
72
    id = get_href_attribute (element);
72
    if (!id)
        return FALSE;
72
    cairo_svg_element_t *use_element = lookup_element (svg_render, id);
72
    cairo_translate (svg_render->cr, x, y);
72
    render_element_tree (svg_render, use_element, NULL, FALSE);
72
    return TRUE;
}
static cairo_bool_t
504
draw_path (cairo_svg_glyph_render_t *svg_render)
{
504
    cairo_svg_graphics_state_t *gs = svg_render->graphics_state;
    cairo_pattern_t *pattern;
504
    cairo_bool_t opacity_group = FALSE;
504
    if (gs->mode == GS_COMPUTE_BBOX) {
60
        cairo_set_source_rgb (svg_render->cr, 0, 0, 0);
60
        cairo_set_fill_rule (svg_render->cr, gs->fill_rule);
60
        cairo_fill (svg_render->cr);
60
        return FALSE;
444
    } else if (gs->mode == GS_CLIP) {
30
        return FALSE;
    }
414
    if (gs->opacity < 1.0) {
        cairo_push_group (svg_render->cr);
        opacity_group = TRUE;
    }
414
    cairo_path_t *path = cairo_copy_path (svg_render->cr);
414
    cairo_new_path (svg_render->cr);
414
    if (gs->fill.type != PAINT_NONE) {
300
        cairo_bool_t group = FALSE;
300
        if (gs->fill.type == PAINT_COLOR) {
243
            if (gs->fill.color.type == RGB) {
234
                cairo_set_source_rgba (svg_render->cr,
                                       gs->fill.color.red,
                                       gs->fill.color.green,
                                       gs->fill.color.blue,
                                       gs->fill_opacity);
9
            } else if (gs->fill.color.type == FOREGROUND) {
9
		cairo_set_source (svg_render->cr, svg_render->foreground_marker);
9
		if (gs->fill_opacity < 1.0)
3
		    group = TRUE;
            }
57
        } else if (gs->fill.type == PAINT_SERVER) {
57
            pattern = create_pattern (svg_render, gs->fill.paint_server);
57
            cairo_set_source (svg_render->cr, pattern);
57
            cairo_pattern_destroy (pattern);
57
            if (gs->fill_opacity < 1.0)
                group = TRUE;
        }
300
        if (group)
3
            cairo_push_group (svg_render->cr);
300
        cairo_append_path (svg_render->cr, path);
300
        cairo_set_fill_rule (svg_render->cr, gs->fill_rule);
300
        cairo_fill (svg_render->cr);
300
        if (group) {
3
            cairo_pop_group_to_source (svg_render->cr);
3
            cairo_paint_with_alpha (svg_render->cr, gs->fill_opacity);
        }
    }
414
    if (gs->stroke.type != PAINT_NONE) {
135
        cairo_bool_t group = FALSE;
135
        if (gs->stroke.type == PAINT_COLOR) {
135
            if (gs->stroke.color.type == RGB) {
132
                cairo_set_source_rgba (svg_render->cr,
                                       gs->stroke.color.red,
                                       gs->stroke.color.green,
                                       gs->stroke.color.blue,
                                       gs->stroke_opacity);
3
            } else if (gs->fill.color.type == FOREGROUND) {
		cairo_set_source (svg_render->cr, svg_render->foreground_marker);
		if (gs->fill_opacity < 1.0)
		    group = TRUE;
            }
        } else if (gs->stroke.type == PAINT_SERVER) {
            pattern = create_pattern (svg_render, gs->stroke.paint_server);
            cairo_set_source (svg_render->cr, pattern);
            cairo_pattern_destroy (pattern);
            if (gs->stroke_opacity < 1.0)
                group = TRUE;
        }
135
        if (group)
            cairo_push_group (svg_render->cr);
135
        cairo_append_path (svg_render->cr, path);
135
        cairo_stroke (svg_render->cr);
135
        if (group) {
            cairo_pop_group_to_source (svg_render->cr);
            cairo_paint_with_alpha (svg_render->cr, gs->stroke_opacity);
        }
    }
414
    cairo_path_destroy (path);
414
    if (opacity_group) {
        cairo_pop_group_to_source (svg_render->cr);
        cairo_paint_with_alpha (svg_render->cr, gs->opacity);
    }
414
    return TRUE;
}
static void
15
elliptical_arc (cairo_svg_glyph_render_t *svg_render,
                double                    cx,
                double                    cy,
                double                    rx,
                double                    ry,
                double                    angle1,
                double                    angle2)
{
15
    cairo_save (svg_render->cr);
15
    cairo_translate (svg_render->cr, cx, cy);
15
    cairo_scale (svg_render->cr, rx, ry);
15
    cairo_arc (svg_render->cr, 0, 0, 1, angle1, angle2);
15
    cairo_restore (svg_render->cr);
15
}
static cairo_bool_t
282
render_element_rect (cairo_svg_glyph_render_t *svg_render,
                     cairo_svg_element_t      *element,
                     cairo_bool_t              end_tag)
{
282
    double x = 0;
282
    double y = 0;
282
    double width = svg_render->width;
282
    double height = svg_render->height;
282
    double rx = 0;
282
    double ry = 0;
282
    if (end_tag ||
282
        svg_render->graphics_state->mode == GS_NO_RENDER ||
282
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;
282
    get_float_or_percent_attribute (element, "x", svg_render->width, &x);
282
    get_float_or_percent_attribute (element, "y", svg_render->height, &y);
282
    get_float_or_percent_attribute (element, "width", svg_render->width, &width);
282
    get_float_or_percent_attribute (element, "height", svg_render->height, &height);
282
    get_float_or_percent_attribute (element, "rx", svg_render->width, &rx);
282
    get_float_or_percent_attribute (element, "ry", svg_render->height, &ry);
282
    if (rx == 0 && ry == 0) {
279
        cairo_rectangle (svg_render->cr, x, y, width, height);
    } else {
3
        cairo_move_to (svg_render->cr, x + rx, y);
3
        cairo_line_to (svg_render->cr, x + width - rx, y);
3
        elliptical_arc (svg_render,    x + width - rx, y + ry, rx, ry, -M_PI/2, 0);
3
        cairo_line_to (svg_render->cr, x + width, y + height - ry);
3
        elliptical_arc (svg_render,    x + width - rx, y + height - ry, rx, ry, 0, M_PI/2);
3
        cairo_line_to (svg_render->cr, x + rx, y + height);
3
        elliptical_arc (svg_render,    x + rx, y + height - ry, rx, ry, M_PI/2, M_PI);
3
        cairo_line_to (svg_render->cr, x, y + ry);
3
        elliptical_arc (svg_render,    x + rx, y + ry, rx, ry, M_PI, -M_PI/2);
    }
282
    draw_path (svg_render);
282
    return TRUE;
}
static cairo_bool_t
51
render_element_circle (cairo_svg_glyph_render_t *svg_render,
                       cairo_svg_element_t      *element,
                       cairo_bool_t              end_tag)
{
51
    double cx = 0;
51
    double cy = 0;
51
    double r = 0;
51
    if (end_tag ||
51
        svg_render->graphics_state->mode == GS_NO_RENDER ||
51
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;
51
    get_float_or_percent_attribute (element, "cx", svg_render->width, &cx);
51
    get_float_or_percent_attribute (element, "cy", svg_render->height, &cy);
51
    get_float_or_percent_attribute (element, "r", svg_render->width, &r);
51
    cairo_arc (svg_render->cr, cx, cy, r, 0, 2*M_PI);
51
    draw_path (svg_render);
51
    return TRUE;
}
static cairo_bool_t
3
render_element_ellipse (cairo_svg_glyph_render_t *svg_render,
                        cairo_svg_element_t      *element,
                        cairo_bool_t              end_tag)
{
3
    double cx = 0;
3
    double cy = 0;
3
    double rx = 0;
3
    double ry = 0;
3
    if (end_tag ||
3
        svg_render->graphics_state->mode == GS_NO_RENDER ||
3
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;
3
    get_float_or_percent_attribute (element, "cx", svg_render->width, &cx);
3
    get_float_or_percent_attribute (element, "cy", svg_render->height, &cy);
3
    get_float_or_percent_attribute (element, "rx", svg_render->width, &rx);
3
    get_float_or_percent_attribute (element, "ry", svg_render->height, &ry);
3
    elliptical_arc (svg_render, cx, cy, rx, ry, 0, 2*M_PI);
3
    draw_path (svg_render);
3
    return TRUE;
}
static cairo_bool_t
75
render_element_line (cairo_svg_glyph_render_t *svg_render,
                     cairo_svg_element_t      *element,
                     cairo_bool_t              end_tag)
{
75
    double x1 = 0;
75
    double y1 = 0;
75
    double x2 = 0;
75
    double y2 = 0;
75
    if (end_tag ||
75
        svg_render->graphics_state->mode == GS_NO_RENDER ||
75
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;
75
    get_float_or_percent_attribute (element, "x1", svg_render->width, &x1);
75
    get_float_or_percent_attribute (element, "y1", svg_render->height, &y1);
75
    get_float_or_percent_attribute (element, "x2", svg_render->width, &x2);
75
    get_float_or_percent_attribute (element, "y2", svg_render->height, &y2);
75
    cairo_move_to (svg_render->cr, x1, y1);
75
    cairo_line_to (svg_render->cr, x2, y2);
75
    draw_path (svg_render);
75
    return TRUE;
}
static cairo_bool_t
24
render_element_polyline (cairo_svg_glyph_render_t *svg_render,
                         cairo_svg_element_t      *element,
                         cairo_bool_t              end_tag)
{
    const char *p;
    const char *end;
    double x, y;
24
    cairo_bool_t have_move = FALSE;
24
    if (end_tag ||
24
        svg_render->graphics_state->mode == GS_NO_RENDER ||
24
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;
24
    p = get_attribute (element, "points");
    do {
123
        end = get_path_params (p, 2, &x, &y);
123
        if (!end) {
            print_warning (svg_render, "points expected 2 numbers: %s", p);
            break;
        }
123
        p = end;
123
        if (!have_move) {
24
            cairo_move_to (svg_render->cr, x, y);
24
            have_move = TRUE;
        } else {
99
            cairo_line_to (svg_render->cr, x, y);
        }
123
        p = skip_space (p);
123
    } while (p && *p);
24
    if (string_equal (element->tag, "polygon"))
9
        cairo_close_path (svg_render->cr);
24
    draw_path (svg_render);
24
    return TRUE;
}
static double
42
angle_between_vectors (double ux,
                       double uy,
                       double vx,
                       double vy)
{
42
    double dot = ux*vx + uy*vy;
42
    double umag = sqrt (ux*ux + uy*uy);
42
    double vmag = sqrt (vx*vx + vy*vy);
42
    double c = dot/(umag*vmag);
42
    if (c > 1.0)
        c = 1.0;
42
    if (c < -1.0)
3
        c = -1.0;
42
    double a = acos (c);
42
    if (ux * vy - uy * vx < 0.0)
12
        a = -a;
42
    return a;
}
static void
21
arc_path (cairo_t *cr,
          double x1, double y1,
          double x2, double y2,
          double rx, double ry,
          double rotate,
          cairo_bool_t large_flag,
          cairo_bool_t sweep_flag)
{
    double x1_, y1_, cx_, cy_;
    double xm, ym, cx, cy;
    double a, b, d;
    double ux, uy, vx, vy;
    double theta, delta_theta;
    double epsilon;
    cairo_matrix_t ctm;
21
    cairo_get_matrix (cr, &ctm);
21
    epsilon = _cairo_matrix_transformed_circle_major_axis (&ctm, cairo_get_tolerance (cr));
21
    rotate *= M_PI/180.0;
    /* Convert endpoint to center parameterization.
     * See SVG 1.1 Appendix F.6. Step numbers are the steps in the appendix.
     */
21
    rx = fabs (rx);
21
    ry = fabs (ry);
21
    if (rx < epsilon || ry < epsilon) {
        cairo_line_to (cr, x2, y2);
        return;
    }
21
    if (fabs(x1 - x2) < epsilon && fabs(y1 - y2) < epsilon) {
        cairo_line_to (cr, x2, y2);
        return;
    }
    /* Step 1 */
21
    xm = (x1 - x2)/2;
21
    ym = (y1 - y2)/2;
21
    x1_ = xm * cos (rotate) + ym * sin (rotate);
21
    y1_ = xm * -sin (rotate) + ym * cos (rotate);
21
    d = (x1_*x1_)/(rx*rx) + (y1_*y1_)/(ry*ry);
21
    if (d > 1.0) {
15
        d = sqrt (d);
15
        rx *= d;
15
        ry *= d;
    }
    /* Step 2 */
21
    a = (rx*rx * y1_*y1_) + (ry*ry * x1_*x1_);
21
    if (a == 0.0)
        return;
21
    b = (rx*rx * ry*ry) / a - 1.0;
21
    if (b < 0)
3
        b = 0.0;
21
    d = sqrt(b);
21
    if (large_flag == sweep_flag)
6
        d = -d;
21
    cx_ = d * rx*y1_/ry;
21
    cy_ = d * -ry*x1_/rx;
    /* Step 3 */
21
    cx = cx_ * cos (rotate) - cy_ * sin (rotate) + (x1 + x2)/2;
21
    cy = cx_ * sin (rotate) + cy_ * cos (rotate) + (y1 + y2)/2;
    /* Step 4 */
21
    ux = (x1_ - cx_)/rx;
21
    uy = (y1_ - cy_)/ry;
21
    vx = (-x1_ - cx_)/rx;
21
    vy = (-y1_ - cy_)/ry;
21
    theta = angle_between_vectors (1.0, 0, ux, uy);
21
    delta_theta = angle_between_vectors (ux, uy, vx, vy);
21
    if (!sweep_flag && delta_theta > 0)
3
        delta_theta -= 2 * M_PI;
18
    else if (sweep_flag && delta_theta < 0)
        delta_theta += 2 * M_PI;
    /* Now we can call cairo_arc() */
21
    cairo_save (cr);
21
    cairo_translate (cr, cx, cy);
21
    cairo_scale (cr, rx, ry);
21
    cairo_rotate (cr, theta);
21
    if (delta_theta >= 0.0)
18
        cairo_arc (cr, 0, 0, 1, 0, delta_theta);
    else
3
        cairo_arc_negative (cr, 0, 0, 1, 0, delta_theta);
21
    cairo_restore (cr);
}
static void
393
get_current_point (cairo_svg_glyph_render_t *svg_render, double *x, double *y)
{
393
    if (cairo_has_current_point (svg_render->cr)) {
393
        cairo_get_current_point (svg_render->cr, x, y);
    } else {
        *x = 0;
        *y = 0;
    }
393
}
static void
15
reflect_point (double origin_x, double origin_y, double *x, double *y)
{
15
    *x = 2*origin_x - *x;
15
    *y = 2*origin_y - *y;
15
}
static cairo_bool_t
69
render_element_path (cairo_svg_glyph_render_t *svg_render,
                     cairo_svg_element_t      *element,
                     cairo_bool_t              end_tag)
{
    double cur_x, cur_y;
    double last_cp_x, last_cp_y;
    double x, y, x1, y1, x2, y2;
    double qx1, qy1, qx2, qy2;
    double rx, ry, rotate, large_flag, sweep_flag;
    cairo_bool_t rel, have_move;
    enum { CUBIC, QUADRATIC, OTHER } last_op;
69
    if (end_tag ||
69
        svg_render->graphics_state->mode == GS_NO_RENDER ||
69
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;
69
    last_op = OTHER;
69
    const char *p = get_attribute (element, "d");
    const char *end;
    int op;
801
    while (p) {
990
        while (p && _cairo_isspace (*p))
189
            p++;
801
        if (!p || *p == 0)
            break;
732
        op = *p;
732
        switch (op) {
81
            case 'M':
            case 'm':
81
                rel = op == 'm';
81
                p++;
81
                have_move = FALSE;
                do {
81
                    end = get_path_params (p, 2, &x, &y);
81
                    if (!end) {
                        print_warning (svg_render, "path %c expected 2 numbers: %s", op, p);
                        break;
                    }
81
                    p = end;
81
                    if (rel) {
                        get_current_point (svg_render, &cur_x, &cur_y);
                        x += cur_x;
                        y += cur_y;
                    }
81
                    if (!have_move) {
81
                        cairo_move_to (svg_render->cr, x, y);
81
                        have_move = TRUE;
                    } else {
                        cairo_line_to (svg_render->cr, x, y);
                    }
81
                    p = skip_space (p);
81
                } while (p && *p && !_cairo_isalpha(*p));
81
                last_op = OTHER;
81
                break;
48
            case 'Z':
            case 'z':
48
                p++;
48
                cairo_close_path (svg_render->cr);
48
                last_op = OTHER;
48
                break;
114
            case 'L':
            case 'l':
114
                rel = op == 'l';
114
                p++;
                do {
114
                    end = get_path_params (p, 2, &x, &y);
114
                    if (!end) {
                        print_warning (svg_render, "path %c expected 2 numbers: %s", op, p);
                        break;
                    }
114
                    p = end;
114
                    if (rel) {
42
                        get_current_point (svg_render, &cur_x, &cur_y);
42
                        x += cur_x;
42
                        y += cur_y;
                    }
114
                    cairo_line_to (svg_render->cr, x, y);
114
                    p = skip_space (p);
114
                } while (p && *p && !_cairo_isalpha(*p));
114
                last_op = OTHER;
114
                break;
6
            case 'H':
            case 'h':
6
                rel = op == 'h';
6
                p++;
                do {
6
                    end = get_path_params (p, 1, &x1);
6
                    if (!end) {
                        print_warning (svg_render, "path %c expected a number: %s", op, p);
                        break;
                    }
6
                    p = end;
6
                    get_current_point (svg_render, &cur_x, &cur_y);
6
                    if (rel) {
3
                        x1 += cur_x;
                    }
6
                    cairo_line_to (svg_render->cr, x1, cur_y);
6
                    p = skip_space (p);
6
                } while (p && *p && !_cairo_isalpha(*p));
6
                last_op = OTHER;
6
                break;
3
            case 'V':
            case 'v':
3
                rel = op == 'v';
3
                p++;
                do {
3
                    end = get_path_params (p, 1, &y1);
3
                    if (!end) {
                        print_warning (svg_render, "path %c expected a number: %s", op, p);
                        break;
                    }
3
                    p = end;
3
                    get_current_point (svg_render, &cur_x, &cur_y);
3
                    if (rel) {
                        y1 += cur_y;
                    }
3
                    cairo_line_to (svg_render->cr, cur_x, y1);
3
                    p = skip_space (p);
3
                } while (p && *p && !_cairo_isalpha(*p));
3
                last_op = OTHER;
3
                break;
429
            case 'C':
            case 'c':
429
                rel = op == 'c';
429
                p++;
                do {
429
                    end = get_path_params (p, 6, &x1, &y1, &x2, &y2, &x, &y);
429
                    if (!end) {
                        print_warning (svg_render, "path %c expected 6 numbers: %s", op, p);
                        break;
                    }
429
                    p = end;
429
                    if (rel) {
291
                        get_current_point (svg_render, &cur_x, &cur_y);
291
                        x1 += cur_x;
291
                        y1 += cur_y;
291
                        x2 += cur_x;
291
                        y2 += cur_y;
291
                        x += cur_x;
291
                        y += cur_y;
                    }
429
                    cairo_curve_to (svg_render->cr, x1, y1, x2, y2, x, y);
429
                    p = skip_space (p);
429
                } while (p && *p && !_cairo_isalpha(*p));
429
                last_op = CUBIC;
429
                last_cp_x = x2;
429
                last_cp_y = y2;
429
                break;
12
            case 'S':
            case 's':
12
                rel = op == 's';
12
                p++;
                do {
12
                    end = get_path_params (p, 4, &x2, &y2, &x, &y);
12
                    if (!end) {
                        print_warning (svg_render, "path %c expected 4 numbers: %s", op, p);
                        break;
                    }
12
                    p = end;
12
                    get_current_point (svg_render, &cur_x, &cur_y);
12
                    if (rel) {
3
                        x2 += cur_x;
3
                        y2 += cur_y;
3
                        x += cur_x;
3
                        y += cur_y;
                    }
12
                    if (last_op == CUBIC) {
12
                        x1 = last_cp_x;
12
                        y1 = last_cp_y;
12
                        reflect_point (cur_x, cur_y, &x1, &y1);
                    } else {
                        x1 = cur_x;
                        y1 = cur_y;
                    }
12
                    cairo_curve_to (svg_render->cr, x1, y1, x2, y2, x, y);
12
                    last_op = CUBIC;
12
                    last_cp_x = x2;
12
                    last_cp_y = y2;
12
                    p = skip_space (p);
12
                } while (p && *p && !_cairo_isalpha(*p));
12
                break;
15
            case 'Q':
            case 'q':
15
                rel = op == 'q';
15
                p++;
                do {
15
                    end = get_path_params (p, 4, &x1, &y1, &x, &y);
15
                    if (!end) {
                        print_warning (svg_render, "path %c expected 4 numbers: %s", op, p);
                        break;
                    }
15
                    p = end;
15
                    get_current_point (svg_render, &cur_x, &cur_y);
15
                    if (rel) {
                        x1 += cur_x;
                        y1 += cur_y;
                        x += cur_x;
                        y += cur_y;
                    }
15
                    qx1 = cur_x + (x1 - cur_x)*2/3;
15
                    qy1 = cur_y + (y1 - cur_y)*2/3;
15
                    qx2 = x + (x1 - x)*2/3;
15
                    qy2 = y + (y1 - y)*2/3;
15
                    cairo_curve_to (svg_render->cr, qx1, qy1, qx2, qy2, x, y);
15
                    p = skip_space (p);
15
                } while (p && *p && !_cairo_isalpha(*p));
15
                last_op = QUADRATIC;
15
                last_cp_x = x1;
15
                last_cp_y = y1;
15
                break;
3
            case 'T':
            case 't':
3
                rel = op == 't';
3
                p++;
                do {
3
                    end = get_path_params (p, 2, &x, &y);
3
                    if (!end) {
                        print_warning (svg_render, "path %c expected 2 numbers: %s", op, p);
                        break;
                    }
3
                    p = end;
3
                    get_current_point (svg_render, &cur_x, &cur_y);
3
                    if (rel) {
                        x += cur_x;
                        y += cur_y;
                    }
3
                    if (last_op == QUADRATIC) {
3
                        x1 = last_cp_x;
3
                        y1 = last_cp_y;
3
                        reflect_point (cur_x, cur_y, &x1, &y1);
                    } else {
                        x1 = cur_x;
                        y1 = cur_y;
                    }
3
                    qx1 = cur_x + (x1 - cur_x)*2/3;
3
                    qy1 = cur_y + (y1 - cur_y)*2/3;
3
                    qx2 = x + (x1 - x)*2/3;
3
                    qy2 = y + (y1 - y)*2/3;
3
                    cairo_curve_to (svg_render->cr, qx1, qy1, qx2, qy2, x, y);
3
                    last_op = QUADRATIC;
3
                    last_cp_x = x1;
3
                    last_cp_y = y1;
3
                    p = skip_space (p);
3
                } while (p && *p && *p && !_cairo_isalpha(*p));
3
                break;
21
            case 'A':
            case 'a':
21
                rel = op == 'a';
21
                p++;
                do {
21
                    end = get_path_params (p, 7, &rx, &ry, &rotate, &large_flag, &sweep_flag, &x, &y);
21
                    if (!end) {
                        print_warning (svg_render, "path %c expected 7 numbers: %s", op, p);
                        break;
                    }
21
                    p = end;
21
                    get_current_point (svg_render, &cur_x, &cur_y);
21
                    if (rel) {
                        x += cur_x;
                        y += cur_y;
                    }
21
                    arc_path (svg_render->cr,
                              cur_x, cur_y,
                              x, y,
                              rx, ry,
                              rotate,
                              large_flag > 0.5,
                              sweep_flag > 0.5);
21
                    p = skip_space (p);
21
                } while (p && *p && !_cairo_isalpha(*p));
21
                last_op = OTHER;
21
                break;
            default:
                p = NULL;
                break;
        }
    }
69
    draw_path (svg_render);
69
    return TRUE;
}
static void
270
init_graphics_state (cairo_svg_glyph_render_t *svg_render)
{
    cairo_svg_graphics_state_t *gs;
270
    gs = _cairo_calloc (sizeof (cairo_svg_graphics_state_t));
270
    get_paint (svg_render, "black", &gs->fill);
270
    get_paint (svg_render, "none", &gs->stroke);
270
    gs->color.type = FOREGROUND;
270
    gs->fill_opacity = 1.0;
270
    gs->stroke_opacity = 1.0;
270
    gs->opacity = 1.0;
270
    gs->fill_rule = CAIRO_FILL_RULE_WINDING;
270
    gs->clip_rule = CAIRO_FILL_RULE_WINDING;
270
    gs->clip_path = NULL;
270
    gs->dash_array = NULL;
270
    gs->dash_offset = 0.0;
270
    gs->mode = GS_RENDER;
270
    gs->bbox.x = 0;
270
    gs->bbox.y = 0;
270
    gs->bbox.width = 0;
270
    gs->bbox.height = 0;
270
    gs->next = NULL;
270
    svg_render->graphics_state = gs;
270
    cairo_save (svg_render->cr);
270
    cairo_set_source_rgb (svg_render->cr, 0, 0, 0);
270
    cairo_set_line_width (svg_render->cr, 1.0);
270
    cairo_set_line_cap (svg_render->cr, CAIRO_LINE_CAP_BUTT);
270
    cairo_set_line_join (svg_render->cr, CAIRO_LINE_JOIN_MITER);
270
    cairo_set_miter_limit (svg_render->cr, 4.0);
270
}
#define MAX_DASHES 100
1164
static void update_dash (cairo_svg_glyph_render_t *svg_render,
                         cairo_svg_element_t      *element)
{
1164
    cairo_svg_graphics_state_t *gs = svg_render->graphics_state;
    const char *p;
    char *end;
    double value;
    double dash_array[MAX_DASHES];
1164
    int num_dashes = 0;
1164
    cairo_bool_t not_zero = FALSE;
1164
    if (gs->dash_array == NULL || string_equal (gs->dash_array, "none")) {
1140
        cairo_set_dash (svg_render->cr, NULL, 0, 0);
1140
        return;
    }
24
    p = gs->dash_array;
84
    while (*p && num_dashes < MAX_DASHES) {
96
        while (*p && (*p == ',' || _cairo_isspace (*p)))
36
            p++;
60
        if (*p == 0)
            break;
60
        value = _cairo_strtod (p, &end);
60
        if (end == p)
            break;
60
        p = end;
60
        if (*p == '%') {
            value *= svg_render->width / 100.0;
            p++;
        }
60
        if (value < 0.0)
            return;
60
        if (value > 0.0)
60
            not_zero = TRUE;
60
        dash_array[num_dashes++] = value;
    }
24
    if (not_zero)
24
        cairo_set_dash (svg_render->cr, dash_array, num_dashes, gs->dash_offset);
}
static cairo_bool_t
231
pattern_requires_bbox (cairo_svg_glyph_render_t *svg_render,
                       cairo_svg_element_t      *paint_server)
{
    const char *p;
354
    if (string_equal (paint_server->tag, "linearGradient") ||
123
        string_equal (paint_server->tag, "radialGradient"))
    {
231
        p = get_attribute (paint_server, "gradientUnits");
231
        if (string_equal (p, "userSpaceOnUse"))
39
            return FALSE;
192
        return TRUE;
    }
    return FALSE;
}
static cairo_bool_t
1110
clip_requires_bbox (cairo_svg_glyph_render_t *svg_render,
                    const char               *clip_path)
{
    cairo_svg_element_t *element;
    const char *p;
1110
    if (clip_path && strncmp (clip_path, "url", 3) == 0) {
24
        element = lookup_url_element (svg_render, clip_path);
24
        if (element) {
24
            p = get_attribute (element, "clipPathUnits");
24
            if (string_equal (p, "objectBoundingBox"))
12
                return TRUE;
        }
    }
1098
    return FALSE;
}
static cairo_bool_t
1110
need_bbox (cairo_svg_glyph_render_t *svg_render,
           cairo_svg_element_t      *element)
{
1110
    cairo_svg_graphics_state_t *gs = svg_render->graphics_state;
1110
    cairo_bool_t fill_needs_bbox = FALSE;
1110
    cairo_bool_t stroke_needs_bbox = FALSE;
1110
    cairo_bool_t clip_needs_bbox = FALSE;
1110
    if (gs->mode != GS_RENDER)
        return FALSE;
1110
    if (gs->fill.type == PAINT_SERVER && pattern_requires_bbox (svg_render, gs->fill.paint_server))
192
        fill_needs_bbox = TRUE;
1110
    if (gs->stroke.type == PAINT_SERVER && pattern_requires_bbox (svg_render, gs->stroke.paint_server))
        stroke_needs_bbox = TRUE;
1110
    if (clip_requires_bbox (svg_render, get_attribute (element, "clip-path")))
12
        clip_needs_bbox = TRUE;
2187
    if (string_equal (element->tag, "circle") ||
2151
        string_equal (element->tag, "ellipse") ||
2079
        string_equal (element->tag, "path") ||
2001
        string_equal (element->tag, "polygon") ||
996
        string_equal (element->tag, "rect"))
    {
324
        return fill_needs_bbox || stroke_needs_bbox || clip_needs_bbox;
    }
1497
    if (string_equal (element->tag, "line") ||
711
        string_equal (element->tag, "polyline"))
    {
90
        return stroke_needs_bbox || clip_needs_bbox;
    }
1326
    if (string_equal (element->tag, "g") ||
1254
        string_equal (element->tag, "image") ||
624
        string_equal (element->tag, "use"))
    {
144
        return clip_needs_bbox;
    }
552
    return FALSE;
}
static cairo_bool_t
call_element (cairo_svg_glyph_render_t *svg_render,
              cairo_svg_element_t      *element,
              cairo_bool_t              end_tag);
static void
1167
update_graphics_state (cairo_svg_glyph_render_t *svg_render,
                       cairo_svg_element_t      *element)
{
    double value;
    const char *p;
1167
    cairo_svg_graphics_state_t *gs = svg_render->graphics_state;
1167
    p = get_attribute (element, "transform");
1167
    if (p) {
        cairo_matrix_t m;
117
        if (parse_transform (p, &m))
117
            cairo_transform (svg_render->cr, &m);
    }
    /* The transform is all we need for bbox computation. The SVG spec
     * excludes clipping and stroke-width from the bbox. */
1167
    if (gs->mode == GS_COMPUTE_BBOX)
3
        return;
1164
    p = get_attribute (element, "color");
1164
    if (p)
6
        get_color (svg_render, p, &gs->color);
1164
    if (!get_float_attribute (element, "opacity", &gs->opacity))
1161
        gs->opacity = 1.0;
1164
    p = get_attribute (element, "fill");
1164
    if (p) {
246
        get_paint (svg_render, p, &gs->fill);
    }
1164
    get_float_attribute (element, "fill-opacity", &gs->fill_opacity);
1164
    gs->fill_rule = get_fill_rule_attribute (element, "fill-rule", gs->fill_rule);
1164
    gs->clip_rule = get_fill_rule_attribute (element, "fill-rule", gs->clip_rule);
1164
    p = get_attribute (element, "stroke");
1164
    if (p)
138
        get_paint (svg_render, p, &gs->stroke);
1164
    if (get_float_or_percent_attribute (element, "stroke-width", svg_render->width, &value))
132
        cairo_set_line_width (svg_render->cr, value);
1164
    p = get_attribute (element, "stroke-linecap");
1164
    if (string_equal (p, "butt"))
3
        cairo_set_line_cap (svg_render->cr, CAIRO_LINE_CAP_BUTT);
1161
    else if (string_equal (p, "round"))
3
        cairo_set_line_cap (svg_render->cr, CAIRO_LINE_CAP_ROUND);
1158
    else if (string_equal (p, "square"))
3
        cairo_set_line_cap (svg_render->cr, CAIRO_LINE_CAP_SQUARE);
1164
    p = get_attribute (element, "stroke-linejoin");
1164
    if (string_equal (p, "miter"))
6
        cairo_set_line_join (svg_render->cr, CAIRO_LINE_JOIN_MITER);
1158
    else if (string_equal (p, "round"))
3
        cairo_set_line_join (svg_render->cr, CAIRO_LINE_JOIN_ROUND);
1155
    else if (string_equal (p, "bevel"))
3
        cairo_set_line_join (svg_render->cr, CAIRO_LINE_JOIN_BEVEL);
1164
    if (get_float_attribute (element, "stroke-miterlimit", &value))
3
        cairo_set_miter_limit (svg_render->cr, value);
1164
    p = get_attribute (element, "stroke-dasharray");
1164
    if (p) {
30
        free (gs->dash_array);
30
        gs->dash_array = strdup (p);
    }
1164
    get_float_or_percent_attribute (element, "stroke-dashoffset", svg_render->width, &gs->dash_offset);
1164
    update_dash (svg_render, element);
    /* Some elements may need the bounding box of the element thay are
     * applied to.  As this recursively calls render_element on the
     * same element while we are in render_element and setting up the
     * graphics state, we check gs->mode to avoid re-entering the
     * compute bbox code. The GS_COMPUTE_MODE flag is also used by
     * render functions to ignore patterns and strokes (SVG spec
     * ignores stroke with in bbox calculations) and just use a solid
     * color.
     */
1164
    if (gs->mode == GS_RENDER && need_bbox (svg_render, element)) {
60
        cairo_surface_t *recording = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
60
        cairo_t *old_cr = svg_render->cr;
60
        svg_render->cr = cairo_create (recording);
60
        gs_mode_t old_mode = gs->mode;
60
        gs->mode = GS_COMPUTE_BBOX;
        /* To avoid recursing back into this function, we call the
         * element directory then use render_element_tree to render
         * the children */
60
        call_element (svg_render, element, FALSE);
60
        render_element_tree (svg_render, element, NULL, TRUE);
60
        if (element->type == CONTAINER_ELEMENT)
3
            call_element (svg_render, element, TRUE);
60
        gs->mode = old_mode;
60
        cairo_destroy (svg_render->cr);
60
        svg_render->cr = old_cr;
60
        cairo_recording_surface_ink_extents (recording,
                                             &gs->bbox.x,
                                             &gs->bbox.y,
                                             &gs->bbox.width,
                                             &gs->bbox.height);
60
        cairo_surface_destroy (recording);
    }
    /* clip-path may require bbox */
1164
    p = get_attribute (element, "clip-path");
1164
    if (p && strncmp (p, "url", 3) == 0) {
24
        element = lookup_url_element (svg_render, p);
24
        if (element) {
24
            gs_mode_t old_mode = gs->mode;
24
            gs->mode = GS_CLIP;
24
            render_element_tree (svg_render, element, NULL, FALSE);
24
            cairo_set_fill_rule (svg_render->cr, gs->clip_rule);
24
            cairo_clip (svg_render->cr);
24
            gs->mode = old_mode;
        }
    }
}
static void
1167
save_graphics_state (cairo_svg_glyph_render_t *svg_render)
{
    cairo_svg_graphics_state_t *gs;
1167
    cairo_save (svg_render->cr);
1167
    gs = _cairo_calloc (sizeof (cairo_svg_graphics_state_t));
1167
    gs->fill           = svg_render->graphics_state->fill;
1167
    gs->stroke         = svg_render->graphics_state->stroke;
1167
    gs->color          = svg_render->graphics_state->color;
1167
    gs->fill_opacity   = svg_render->graphics_state->fill_opacity;
1167
    gs->stroke_opacity = svg_render->graphics_state->stroke_opacity;
1167
    gs->opacity        = svg_render->graphics_state->opacity;
1167
    gs->fill_rule      = svg_render->graphics_state->fill_rule;
1167
    gs->clip_rule      = svg_render->graphics_state->clip_rule;
1167
    gs->clip_path      = NULL;
1167
    gs->dash_array     = NULL;
1167
    if (svg_render->graphics_state->dash_array)
        gs->dash_array = strdup (svg_render->graphics_state->dash_array);
1167
    gs->dash_offset    = svg_render->graphics_state->dash_offset;
1167
    gs->mode           = svg_render->graphics_state->mode;
1167
    gs->bbox           = svg_render->graphics_state->bbox;
1167
    gs->next           = svg_render->graphics_state;
1167
    svg_render->graphics_state = gs;
1167
}
static void
1437
restore_graphics_state (cairo_svg_glyph_render_t *svg_render)
{
    cairo_svg_graphics_state_t *gs;
1437
    gs = svg_render->graphics_state;
1437
    svg_render->graphics_state = gs->next;
1437
    if (gs->clip_path)
        cairo_path_destroy (gs->clip_path);
1437
    free (gs->dash_array);
1437
    free (gs);
1437
    cairo_restore (svg_render->cr);
1437
}
/* render function returns TRUE if render_element_tree() is to render
 * the child nodes, FALSE if render_element_tree() is to skip the
 * child nodes.
 */
struct render_func {
    const char *tag;
    cairo_bool_t (*render) (cairo_svg_glyph_render_t *, cairo_svg_element_t *, cairo_bool_t);
};
/* Must be sorted */
static const struct render_func render_funcs[] = {
    { "circle", render_element_circle },
    { "clipPath", render_element_clip_path },
    { "defs", NULL },
    { "desc", NULL },
    { "ellipse", render_element_ellipse },
    { "g", render_element_g },
    { "image", render_element_image },
    { "line", render_element_line },
    { "linearGradient", render_element_linear_gradient },
    { "metadata", NULL },
    { "path", render_element_path },
    { "polygon", render_element_polyline },
    { "polyline", render_element_polyline },
    { "radialGradient", render_element_radial_gradient },
    { "rect", render_element_rect },
    { "stop", render_element_stop },
    { "svg", render_element_svg },
    { "title", NULL },
    { "use", render_element_use },
};
static int
5487
_render_func_compare (const void *a, const void *b)
{
5487
    const struct render_func *render_func_a = a;
5487
    const struct render_func *render_func_b = b;
5487
    return strcmp (render_func_a->tag, render_func_b->tag);
}
static cairo_bool_t
1485
call_element (cairo_svg_glyph_render_t *svg_render,
              cairo_svg_element_t      *element,
              cairo_bool_t              end_tag)
{
    const struct render_func *func;
    struct render_func key;
1485
    cairo_bool_t recurse = FALSE;
1485
    key.tag = element->tag;
1485
    key.render = NULL;
1485
    func = bsearch (&key,
                    render_funcs,
                    ARRAY_LENGTH (render_funcs),
                    sizeof (struct render_func),
                    _render_func_compare);
1485
    if (func) {
1485
        if (func->render) {
1317
            recurse = func->render (svg_render, element, end_tag);
        }
    } else {
        print_warning (svg_render, "Unsupported element: %s", element->tag);
    }
1485
    return recurse;
}
static cairo_bool_t
1692
render_element (cairo_svg_glyph_render_t *svg_render,
                cairo_svg_element_t      *element,
                cairo_bool_t              end_tag,
                cairo_svg_element_t      *display_element)
{
1692
    cairo_bool_t recurse = FALSE;
    cairo_svg_graphics_state_t *gs;
    /* Ignore elements if we have not seen "<svg>". Ignore
      * "<svg>" if we have seen it */
1692
    if (svg_render->view_port_set) {
1422
        if (string_equal (element->tag, "svg"))
270
            return FALSE;
    } else {
270
        if (!string_equal (element->tag, "svg"))
            return FALSE;
    }
1422
    if (element->type == EMPTY_ELEMENT ||
780
        (element->type == CONTAINER_ELEMENT && !end_tag))
    {
1167
        save_graphics_state (svg_render);
1167
        update_graphics_state (svg_render, element);
    }
1422
    gs = svg_render->graphics_state;
1422
    if (gs->mode == GS_NO_RENDER && element == display_element)
        gs->mode = GS_RENDER;
1422
    recurse = call_element (svg_render, element, end_tag);
1422
    if (element->type == EMPTY_ELEMENT ||
780
        (element->type == CONTAINER_ELEMENT && end_tag))
    {
897
        restore_graphics_state (svg_render);
    }
1422
    return recurse;
}
#define MAX_DEPTH 100
static void
1551
render_element_tree (cairo_svg_glyph_render_t *svg_render,
                     cairo_svg_element_t      *element,
                     cairo_svg_element_t      *display_element,
                     cairo_bool_t              children_only)
{
1551
    if (!element)
        return;
    /* Avoid circular references by limiting the number of recursive
     * calls to this function. */
1551
    if (svg_render->render_element_tree_depth > MAX_DEPTH)
        return;
1551
    svg_render->render_element_tree_depth++;
1551
    if (element->type == EMPTY_ELEMENT && !children_only) {
642
        render_element (svg_render, element, FALSE, display_element);
909
    } else if (element->type == CONTAINER_ELEMENT) {
        int num_elems;
798
        cairo_bool_t recurse = TRUE;;
798
        if (!children_only)
525
            recurse = render_element (svg_render, element, FALSE, display_element);
        /* We only render the children if the parent returned
         * success. This is how we avoid rendering non display
         * elements like gradients, <defs>, and anything not
         * implemented. */
798
        if (recurse) {
690
            num_elems = _cairo_array_num_elements (&element->children);
1758
            for (int i = 0; i < num_elems; i++) {
                cairo_svg_element_t *child;
1068
                _cairo_array_copy_element (&element->children, i, &child);
1068
                render_element_tree (svg_render, child, display_element, FALSE);
            }
        }
798
        if (!children_only)
525
            render_element (svg_render, element, TRUE, display_element);
    }
1551
    svg_render->render_element_tree_depth--;
}
static void
270
render_element_tree_id (cairo_svg_glyph_render_t *svg_render,
                        const char               *element_id)
{
270
    cairo_svg_element_t *glyph_element = NULL;
270
    if (element_id)
        glyph_element = lookup_element (svg_render, element_id);
270
    if (glyph_element)
        svg_render->graphics_state->mode = GS_NO_RENDER;
    else
270
        svg_render->graphics_state->mode = GS_RENDER;
270
    render_element_tree (svg_render, svg_render->tree, glyph_element, TRUE);
270
}
cairo_status_t
270
_cairo_render_svg_glyph (const char           *svg_document,
                         unsigned long         first_glyph,
                         unsigned long         last_glyph,
                         unsigned long         glyph,
                         double                units_per_em,
                         FT_Color             *palette,
                         int                   num_palette_entries,
                         cairo_t              *cr,
                         cairo_pattern_t      *foreground_source,
			 cairo_bool_t         *foreground_source_used)
{
270
    cairo_status_t status = CAIRO_STATUS_SUCCESS;
270
    cairo_svg_glyph_render_t *svg_render = _cairo_calloc (sizeof (cairo_svg_glyph_render_t));
270
    if (unlikely (svg_render == NULL))
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
270
    svg_render->tree = NULL;
270
    svg_render->ids = _cairo_hash_table_create (_element_id_equal);
270
    if (unlikely (svg_render->ids == NULL)) {
        free (svg_render);
	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
    }
270
    svg_render->debug = 0;
270
    const char *s = getenv ("CAIRO_DEBUG_SVG_RENDER");
270
    if (s) {
        if (strlen (s) > 0)
            svg_render->debug = atoi (s);
        else
            svg_render->debug = SVG_RENDER_ERROR;
    }
270
    svg_render->cr = cr;
270
    svg_render->units_per_em = units_per_em;
270
    svg_render->build_pattern.paint_server = NULL;
270
    svg_render->build_pattern.pattern = NULL;
270
    svg_render->build_pattern.type = BUILD_PATTERN_NONE;
270
    svg_render->render_element_tree_depth = 0;
270
    svg_render->view_port_set = FALSE;
270
    svg_render->num_palette_entries = num_palette_entries;
270
    svg_render->palette = palette;
270
    svg_render->foreground_marker = _cairo_pattern_create_foreground_marker ();
270
    svg_render->foreground_source = cairo_pattern_reference (foreground_source);;
270
    svg_render->foreground_source_used = FALSE;
270
    init_graphics_state (svg_render);
270
    print_info (svg_render, "Glyph ID: %ld", glyph);
270
    print_info (svg_render, "Palette Entries: %d", num_palette_entries);
270
    print_info (svg_render, "Units per EM: %f", units_per_em);
270
    print_info (svg_render, "SVG Document:\n%s\n", svg_document);
    /* First parse elements into a tree and populate ids hash table */
270
    if (!parse_svg (svg_render, svg_document)) {
        print_error (svg_render, "Parse SVG document failed");
        status = CAIRO_STATUS_SVG_FONT_ERROR;
        goto cleanup;
    }
#if SVG_RENDER_PRINT_FUNCTIONS
    printf("\nTREE\n");
    if (svg_render->tree) {
        print_element (svg_render->tree, TRUE, 0);
        printf("\n");
    }
#endif
    /* Next, render glyph */
270
    if (first_glyph == last_glyph) {
        /* Render whole document */
270
        render_element_tree_id (svg_render, NULL);
    } else {
        /* Render element with id "glyphID" where ID is glyph number. */
        char glyph_id[30];
        snprintf(glyph_id, sizeof(glyph_id), "#glyph%ld", glyph);
        render_element_tree_id (svg_render, glyph_id);
    }
270
  cleanup:
270
    if (svg_render->build_pattern.pattern)
        cairo_pattern_destroy (svg_render->build_pattern.pattern);
270
    if (svg_render->tree)
270
        free_elements (svg_render, svg_render->tree);
810
    while (svg_render->graphics_state)
540
        restore_graphics_state (svg_render);
270
    cairo_pattern_destroy (svg_render->foreground_marker);
270
    cairo_pattern_destroy (svg_render->foreground_source);
270
    *foreground_source_used = svg_render->foreground_source_used;
    /* The hash entry for each element with an id is removed by
     * free_elements() */
270
    _cairo_hash_table_destroy (svg_render->ids);
270
    free (svg_render);
270
    return status;
}
#ifdef DEBUG_SVG_RENDER
/**
 * _cairo_debug_svg_render:
 *
 * Debug function for cairo-svg-glyph-render.c. Allows invoking the renderer from outside
 * cairo to test with SVG documents, and to facilitate comparison with librsvg rendering.
 * The viewport is .
 *
 * @cr: render target
 * @svg_document: SVG Document
 * @element: element within svg_document to render (eg "#glyph8"), or NULL to render entire document.
 * @debug_level: 0 - quiet, 1 - print errors, 2 - print warnings, 3 - info
 * @return TRUE on success, ie no errors, FALSE if error
 **/
cairo_public cairo_bool_t
_cairo_debug_svg_render (cairo_t       *cr,
                         const char    *svg_document,
                         const char    *element,
                         double         units_per_em,
                         int            debug_level);
cairo_bool_t
_cairo_debug_svg_render (cairo_t       *cr,
                         const char    *svg_document,
                         const char    *element,
                         double         units_per_em,
                         int            debug_level)
{
    cairo_status_t status;
    cairo_bool_t foreground_source_used;
    cairo_pattern_t *foreground = _cairo_pattern_create_foreground_marker ();
    status = _cairo_render_svg_glyph (svg_document,
				      1, 1, 1,
				      units_per_em,
				      NULL, 0,
				      cr,
				      foreground,
				      &foreground_source_used);
    cairo_pattern_destroy (foreground);
    return status == CAIRO_STATUS_SUCCESS;
}
#endif /* DEBUG_SVG_RENDER */
#endif /* HAVE_FT_SVG_DOCUMENT */