Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/gfx/cairo/cairo/src/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 97 kB image not shown  

Quelle  cairo-svg-glyph-render.c   Sprache: C

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


#include "cairoint.h"
#include "cairo-array-private.h"
#include "cairo-ft-private.h"
#include "cairo-pattern-private.h"
#include "cairo-scaled-font-subsets-private.h"

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#if HAVE_FT_SVG_DOCUMENT

#include <ft2build.h>
#include FT_COLOR_H

/* #define SVG_RENDER_PRINT_FUNCTIONS 1 */

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

typedef struct {
    const char *name;
    int red;
    int green;
    int blue;
} color_name_t;

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

typedef struct {
    char *name;
    char *value;
} svg_attribute_t;

typedef enum {
    CONTAINER_ELEMENT,
    EMPTY_ELEMENT,
    PROCESSING_INSTRUCTION,
    DOCTYPE,
    CDATA,
    COMMENT
} tag_type_t;

#define TOP_ELEMENT_TAG "_top"

typedef struct _cairo_svg_element {
    cairo_hash_entry_t base;
    tag_type_t type;
    char *tag;
    char *id;
    cairo_array_t attributes; /* svg_attribute_t */
    cairo_array_t children; /* cairo_svg_element_t* */
    cairo_array_t  content; /* char */
    cairo_pattern_t *pattern; /* defined if a paint server */
    struct _cairo_svg_element *next; /* next on element stack */
} cairo_svg_element_t;

typedef struct _cairo_svg_color {
    enum { RGB, FOREGROUND } type;
    double red;
    double green;
    double blue;
} cairo_svg_color_t;

typedef struct _cairo_svg_paint {
    enum { PAINT_COLOR, PAINT_SERVER, PAINT_NONE } type;
    cairo_svg_color_t color;
    cairo_svg_element_t *paint_server;
} cairo_svg_paint_t;

typedef enum {
    GS_RENDER,
    GS_NO_RENDER,
    GS_COMPUTE_BBOX,
    GS_CLIP
} gs_mode_t;

typedef struct _cairo_svg_graphics_state {
    cairo_svg_paint_t fill;
    cairo_svg_paint_t stroke;
    cairo_svg_color_t color;
    double fill_opacity;
    double stroke_opacity;
    double opacity;
    cairo_fill_rule_t fill_rule;
    cairo_fill_rule_t clip_rule;
    cairo_path_t *clip_path;
    char *dash_array;
    double dash_offset;
    gs_mode_t mode;
    struct {
        double x;
        double y;
        double width;
        double height;
    } bbox;
    struct _cairo_svg_graphics_state *next;
} cairo_svg_graphics_state_t;

typedef enum {
    BUILD_PATTERN_NONE,
    BUILD_PATTERN_LINEAR,
    BUILD_PATTERN_RADIAL
} build_pattern_t;

typedef struct _cairo_svg_glyph_render {
    cairo_svg_element_t *tree;
    cairo_hash_table_t *ids;
    cairo_svg_graphics_state_t *graphics_state;
    cairo_t *cr;
    double units_per_em;
    struct {
        cairo_svg_element_t *paint_server;
        cairo_pattern_t *pattern;
        build_pattern_t type;
    } build_pattern;
    int render_element_tree_depth;
    int num_palette_entries;
    FT_Color* palette;

    /* Viewport */
    double width;
    double height;
    cairo_bool_t view_port_set;

    cairo_pattern_t *foreground_marker;
    cairo_pattern_t *foreground_source;
    cairo_bool_t foreground_source_used;

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


#define SVG_RENDER_ERROR 1
#define SVG_RENDER_WARNING 2
#define SVG_RENDER_INFO 3

#define print_error(render, ...) cairo_svg_glyph_render_printf(render, SVG_RENDER_ERROR, ##__VA_ARGS__)
#define print_warning(render, ...) cairo_svg_glyph_render_printf(render, SVG_RENDER_WARNING, ##__VA_ARGS__)
#define print_info(render, ...) cairo_svg_glyph_render_printf(render, SVG_RENDER_INFO, ##__VA_ARGS__)

static void
cairo_svg_glyph_render_printf (cairo_svg_glyph_render_t *svg_render,
                               int level,
                               const char *fmt, ...) CAIRO_PRINTF_FORMAT (3, 4);

static void
cairo_svg_glyph_render_printf (cairo_svg_glyph_render_t *svg_render,
                               int level,
                               const char *fmt, ...)
{
    va_list ap;

    if (svg_render->debug >= level ) {
        switch (level) {
            case SVG_RENDER_ERROR:
                printf("ERROR: ");
                break;
            case SVG_RENDER_WARNING:
                printf("WARNING: ");
                break;
        }
 va_start (ap, fmt);
 vprintf (fmt, ap);
 va_end (ap);
 printf ("\n");
    }
}

static cairo_bool_t
string_equal (const char *s1, const char *s2)
{
    if (s1 && s2)
        return strcmp (s1, s2) == 0;

    if (!s1 && !s2)
        return TRUE;

    return FALSE;
}

static cairo_bool_t
string_match (const char **p, const char *str)
{
    if (*p && strncmp (*p, str, strlen (str)) == 0) {
        *p += strlen (str);
        return TRUE;
    }
    return FALSE;
}

static const char *
skip_space (const char *p)
{
    while (*p && _cairo_isspace (*p))
        p++;

    return p;
}

/* Skip over character c and and whitespace before or after. Returns
 * NULL if c not found. */

static const char *
skip_char (const char *p, char c)
{
    while (_cairo_isspace (*p))
        p++;

    if (*p != c)
        return NULL;

    p++;

    while (_cairo_isspace (*p))
        p++;

    return p;
}

static int
_color_name_compare (const void *a, const void *b)
{
    const color_name_t *a_color = a;
    const color_name_t *b_color = b;

    return strcmp (a_color->name, b_color->name);
}

static void
init_element_id_key (cairo_svg_element_t *element)
{
    element->base.hash = _cairo_hash_string (element->id);
}

static cairo_bool_t
_element_id_equal (const void *key_a, const void *key_b)
{
    const cairo_svg_element_t *a = key_a;
    const cairo_svg_element_t *b = key_b;

    return string_equal (a->id, b->id);
}

/* Find element with the "id" attribute matching id. id may have the
 * '#' prefix. It will be stripped before searching.
 */

static cairo_svg_element_t *
lookup_element (cairo_svg_glyph_render_t *svg_render, const char *id)
{
    cairo_svg_element_t key;

    if (!id || strlen (id) < 1)
        return NULL;

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

/* Find element with the "id" attribute matching url where url is of
 * the form "url(#id)".
 */

static cairo_svg_element_t *
lookup_url_element (cairo_svg_glyph_render_t *svg_render, const char *url)
{
    const char *p = url;
    cairo_svg_element_t *element = NULL;

    if (p && string_match (&p, "url")) {
        p = skip_char (p, '(');
        if (!p)
            return NULL;

        const char *end = strpbrk(p, WHITE_SPACE_CHARS ")");
        if (end) {
            char *id = _cairo_strndup (p, end - p);
            element = lookup_element (svg_render, id);
            free (id);
        }
    }
    return element;
}

static const char *
get_attribute (const cairo_svg_element_t *element, const char *name)
{
    svg_attribute_t attr;
    int num_elems, i;

    num_elems = _cairo_array_num_elements (&element->attributes);
    for (i = 0; i < num_elems; i++) {
 _cairo_array_copy_element (&element->attributes, i, &attr);
        if (string_equal (attr.name, name))
            return attr.value;
    }
    return NULL;
}

static const char *
get_href_attribute (const cairo_svg_element_t *element)
{
    svg_attribute_t attr;
    int num_elems, i, len;

    /* SVG2 requires the href attribute to be "href". Older versions
     * used "xlink:href". I have seen at least one font that used an
     * alternative name space eg "ns1:href". To keep things simple we
     * search for an attribute named "href" or ending in ":href".
     */

    num_elems = _cairo_array_num_elements (&element->attributes);
    for (i = 0; i < num_elems; i++) {
 _cairo_array_copy_element (&element->attributes, i, &attr);
        if (string_equal (attr.name, "href"))
            return attr.value;

        len = strlen (attr.name);
        if (len > 4 && string_equal (attr.name + len - 5, ":href"))
            return attr.value;
    }
    return NULL;
}

/* Get a float attribute or float percentage. If attribute is a
 * percentage, the returned value is percentage * scale.  Does not
 * modify value if it returns FALSE. This allows value to be set to a
 * default before calling get_float_attribute(), then used without
 * checking the return value of this function.
 */

static cairo_bool_t
get_float_or_percent_attribute (const cairo_svg_element_t *element,
                                const char *name,
                                double scale,
                                double *value)
{
    const char *p;
    char *end;
    double v;

    p = get_attribute (element, name);
    if (p) {
        v = _cairo_strtod (p, &end);
        if (end != p) {
            *value = v;
            if (*end == '%')
                *value *= scale / 100.0;
            return TRUE;
        }
    }
    return FALSE;
}

/* Does not modify value if it returns FALSE. This allows value to be
 * set to a default before calling get_float_attribute(), then used
 * without checking the return value of this function.
 */

static cairo_bool_t
get_float_attribute (const cairo_svg_element_t *element, const char *name, double *value)
{
    const char *p;
    char *end;
    double v;

    p = get_attribute (element, name);
    if (p) {
        v = _cairo_strtod (p, &end);
        if (end != p) {
            *value = v;
            return TRUE;
        }
    }
    return FALSE;
}

static cairo_fill_rule_t
get_fill_rule_attribute (const cairo_svg_element_t *element, const char *name, cairo_fill_rule_t default_value)
{
    const char *p;

    p = get_attribute (element, name);
    if (string_equal (p, "nonzero"))
        return CAIRO_FILL_RULE_WINDING;
    else if (string_equal (p, "evenodd"))
        return CAIRO_FILL_RULE_EVEN_ODD;
    else
        return default_value;
}

static void
free_elements (cairo_svg_glyph_render_t *svg_render,
              cairo_svg_element_t      *element)
{
    int num_elems;

    num_elems = _cairo_array_num_elements (&element->children);
    for (int i = 0; i < num_elems; i++) {
 cairo_svg_element_t *child;
        _cairo_array_copy_element (&element->children, i, &child);
 free_elements (svg_render, child);
    }
    _cairo_array_fini (&element->children);

    num_elems = _cairo_array_num_elements (&element->attributes);
    for (int i = 0; i < num_elems; i++) {
 svg_attribute_t *attr = _cairo_array_index (&element->attributes, i);
 free (attr->name);
 free (attr->value);
    }
    _cairo_array_fini (&element->attributes);
    _cairo_array_fini (&element->content);

    free (element->tag);

    if (element->id) {
        _cairo_hash_table_remove (svg_render->ids, &element->base);
        free (element->id);
    }

    if (element->pattern)
        cairo_pattern_destroy (element->pattern);

    free (element);
}

#if SVG_RENDER_PRINT_FUNCTIONS

static void indent(int level)
{
    for (int i = 1; i < level; i++)
        printf(" ");
}

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

    indent(level);
    if (element->type == COMMENT) {
        printf("\n", content);
    } else if (element->type == CDATA) {
        printf("\n", content);
    } else if (element->type == DOCTYPE) {
        printf("\n", content);
    } else if (element->type == PROCESSING_INSTRUCTION) {
        printf("\n", content);
    } else {
        cairo_bool_t top_element = string_equal (element->tag, TOP_ELEMENT_TAG);

        if (!top_element) {
            printf("<%s", element->tag);
            int num_elems = _cairo_array_num_elements (&element->attributes);
            for (int i = 0; i < num_elems; i++) {
                svg_attribute_t *attr = _cairo_array_index (&element->attributes, i);
                printf(" %s=\"%s\"", attr->name, attr->value);
            }
            if (num_elems > 0)
                printf(" ");

            if (element->type == EMPTY_ELEMENT)
                printf("/>\n");
            else
                printf(">\n");
        }

        if (element->type == CONTAINER_ELEMENT) {
            if (recurse) {
                int num_elems = _cairo_array_num_elements (&element->children);
                for (int i = 0; i < num_elems; i++) {
                    cairo_svg_element_t *child;
                    _cairo_array_copy_element (&element->children, i, &child);
                    print_element (child, TRUE, level + 1);
                }
            }
            if (!top_element)
                printf("\n", element->tag);
        }
    }
    free (content);
}
#endif

static const char *
parse_list_of_floats (const char *p,
                      int num_required,
                      int num_optional,
                      cairo_bool_t *have_optional,
                      va_list ap)
{
    double d;
    double *dp;
    char *end;
    const char *q = NULL;
    int num_found = 0;

    for (int i = 0; i < num_required + num_optional; i++) {
        while (p && (*p == ',' || _cairo_isspace (*p)))
            p++;

        if (!p)
            break;

        d = _cairo_strtod (p, &end);
        if (end == p) {
            p = NULL;
            break;
        }
        p = end;
        dp = va_arg (ap, double *);
        *dp = d;
        num_found++;
        if (num_found == num_required)
            q = p;
    }

    if (num_optional > 0) {
        if (num_found == num_required + num_optional) {
            *have_optional = TRUE;
        } else {
            *have_optional = FALSE;
            /* restore pointer to end of required floats */
            p = q;
        }
    }

    return p;
}

static const char *
get_floats (const char *p,
            int num_required,
            int num_optional,
            cairo_bool_t *have_optional,
            ...)
{
    va_list ap;

    va_start (ap, have_optional);
    p = parse_list_of_floats (p, num_required, num_optional, have_optional, ap);
    va_end (ap);
    return p;
}

static const char *
get_path_params (const char *p, int num_params, ...)
{
    va_list ap;

    va_start (ap, num_params);
    p = parse_list_of_floats (p, num_params, 0, NULL, ap);
    va_end (ap);
    return p;
}

static cairo_bool_t
get_color (cairo_svg_glyph_render_t *svg_render,
           const char               *s,
           cairo_svg_color_t        *color)
{
    int len, matched;
    unsigned r = 0, g = 0, b = 0;

    if (!s)
        return FALSE;

    len = strlen(s);

    if (string_equal (s, "inherit")) {
 return FALSE;
    } else if (string_equal (s, "currentColor") ||
        string_equal (s, "context-fill") ||
        string_equal (s, "context-stroke"))
    {
 *color = svg_render->graphics_state->color;
        return TRUE;
    } else if (len > 0 && s[0] == '#') {
        if (len == 4) {
            matched = sscanf (s + 1, "%1x%1x%1x", &r, &g, &b);
            if (matched == 3) {
                /* Each digit is repeated to convert to 6 digits. eg 0x123 -> 0x112233 */
                color->type = RGB;
                color->red = 0x11*r/255.0;
                color->green = 0x11*g/255.0;
                color->blue = 0x11*b/255.0;
                return TRUE;
            }
        } else if (len == 7) {
            matched = sscanf (s + 1, "%2x%2x%2x", &r, &g, &b);
            if (matched == 3) {
                color->type = RGB;
                color->red = r/255.0;
                color->green = g/255.0;
                color->blue = b/255.0;
                return TRUE;
            }
        }
    } else if (strncmp (s, "rgb", 3) == 0) {
        matched = sscanf (s, "rgb ( %u , %u , %u )", &r, &g, &b);
        if (matched == 3) {
            color->type = RGB;
            color->red = r/255.0;
            color->green = g/255.0;
            color->blue = b/255.0;
            return TRUE;
        }
    } else if (strncmp (s, "var", 3) == 0) {
        /* CPAL palettes colors. eg "var(--color0, yellow)" */
        s += 3;
        s = skip_char (s, '(');
        if (!string_match (&s, "--color"))
            return FALSE;

        char *end;
        int entry = strtol (s, &end, 10);
        if (end == s)
            return FALSE;

        if (svg_render->palette && entry >= 0 && entry < svg_render->num_palette_entries) {
            FT_Color *palette_color = &svg_render->palette[entry];
            color->type = RGB;
            color->red = palette_color->red / 255.0;
            color->green = palette_color->green/ 255.0;
            color->blue = palette_color->blue / 255.0;
            return TRUE;
        } else {
            /* Fallback color */
            s = skip_char (end, ',');
            if (!s)
            return FALSE;

            end = strpbrk(s, WHITE_SPACE_CHARS ")");
            if (!end || end == s)
  return FALSE;

            char *fallback = _cairo_strndup (s, end - s);
            cairo_bool_t success = get_color (svg_render, fallback, color);
            free (fallback);
            return success;
        }
    } else {
        const color_name_t *color_name;
        color_name_t color_name_key;

        color_name_key.name = (char *) s;
        color_name = bsearch (&color_name_key,
                              color_names,
                              ARRAY_LENGTH (color_names),
                              sizeof (color_name_t),
                             _color_name_compare);
        if (color_name) {
            color->type = RGB;
            color->red = color_name->red/255.0;
            color->green = color_name->green/255.0;
            color->blue = color_name->blue/255.0;
            return TRUE;
        }
    }
    return FALSE;
}

static void
get_paint (cairo_svg_glyph_render_t *svg_render,
           const char *p,
           cairo_svg_paint_t *paint)
{
    cairo_svg_element_t *element;

    if (string_match (&p, "none")) {
        paint->type = PAINT_NONE;
        paint->paint_server = NULL;
    } else if (p && strncmp (p, "url", 3) == 0) {
        element = lookup_url_element (svg_render, p);
        if (element) {
            paint->type = PAINT_SERVER;
            paint->paint_server = element;
        }
    } else {
        if (get_color (svg_render, p, &paint->color)) {
            paint->type = PAINT_COLOR;
            paint->paint_server = NULL;
        }
    }
}

#ifdef SVG_RENDER_PRINT_FUNCTIONS

static void
print_color (cairo_svg_color_t *color)
{
    switch (color->type) {
        case FOREGROUND_COLOR:
            printf("foreground");
            break;
        case RGB:
            printf("#%02x%02x%02x",
                   (int)(color->red*255),
                   (int)(color->red*255),
                   (int)(color->red*255));
            break;
    }
}

static void
print_paint (cairo_svg_paint_t *paint)
{
    printf("Paint: ");
    switch (paint->type) {
        case PAINT_COLOR:
            printf("color: ");
            print_color (&paint->color);
            break;
        case PAINT_SERVER:
            printf("server: %s", paint->paint_server->tag);
            break;
        case PAINT_NONE:
            printf("none");
            break;
    }
    printf("\n");
}

#endif

static void
parse_error (cairo_svg_glyph_render_t *svg_render,
             const char *string,
             const char *location,
             const char *fmt,
             ...) CAIRO_PRINTF_FORMAT (4, 5);

static void
parse_error (cairo_svg_glyph_render_t *svg_render,
             const char *string,
             const char *location,
             const char *fmt,
             ...)
{
    va_list ap;
    const int context = 40;
    const char *start;
    const char *end;

    if (svg_render->debug >= SVG_RENDER_ERROR) {
        printf("ERROR: ");
 va_start (ap, fmt);
 vprintf (fmt, ap);
 va_end (ap);
        putchar ('\n');
        start = location - context;
        if (start < string)
            start = string;

        end = location + strlen (location);
        if (end - location > context)
            end = location + context;

        for (const char *p = start; p < end; p++) {
            if (_cairo_isspace (*p))
                putchar (' ');
            else
                putchar (*p);
        }
        putchar ('\n');

        for (int i = 0; i < location - start; i++)
            putchar(' ');
        putchar ('^');
        putchar ('\n');
 printf (" at position %td\n", location - string);
    }
}

static cairo_bool_t
append_attribute (cairo_svg_element_t *element, svg_attribute_t *attribute)
{
    const char *p;
    const char *end;
    svg_attribute_t attr;

    memset (&attr, 0, sizeof (attr));
    if (string_equal (attribute->name, "style")) {
        /* split style into individual attributes */
        p = attribute->value;
        while (*p) {
            end = strchr (p, ':');
            if (!end || end == p)
                break;
            attr.name = _cairo_strndup (p, end - p);
            p = end + 1;
            p = skip_space(p);
            end = strchr (p, ';');
            if (!end)
                end = strchr (p, 0);
            if (end == p)
                goto split_style_fail;

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

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

            memset (&attr, 0, sizeof (attr));
            p = skip_space (p);
        }
    }

    if (_cairo_array_append (&element->attributes, attribute))
        return FALSE;

    return TRUE;

  split_style_fail:
    free (attr.name);
    free (attr.value);
    return FALSE;
}

static cairo_bool_t
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;

    id = get_attribute (child, "id");
    if (id) {
        child->id = strdup (id);
        init_element_id_key (child);
 status = _cairo_hash_table_insert (svg_render->ids, &child->base);
 if (unlikely (status))
            return FALSE;
    }

    status = _cairo_array_append (&parent->children, &child);
    return status == CAIRO_STATUS_SUCCESS;
}

static cairo_svg_element_t *
create_element (tag_type_t type, char *tag)
{
    cairo_svg_element_t *elem;
    cairo_status_t status;

    elem = _cairo_malloc (sizeof (cairo_svg_element_t));
    if (unlikely (elem == NULL)) {
 status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
        return NULL;
    }

    elem->type = type;
    elem->tag = tag;
    elem->id = NULL;
    _cairo_array_init (&elem->attributes, sizeof(svg_attribute_t));
    _cairo_array_init (&elem->children, sizeof(cairo_svg_element_t *));
    _cairo_array_init (&elem->content, sizeof(char));
    elem->pattern = NULL;
    elem->next = NULL;

    return elem;
}

static const char *
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;

    p = attributes;
    memset (&attr, 0, sizeof (svg_attribute_t));
    p = skip_space (p);
    while (*p && *p != '/' && *p != '>' && *p != '?') {
        end = strpbrk(p, WHITE_SPACE_CHARS "=");
        if (!end) {
            parse_error (svg_render, attributes, p, "Could not find '='");
            goto fail;
        }

        if (end == p) {
            parse_error (svg_render, attributes, p, "Missing attribute name");
            goto fail;
        }

        attr.name = _cairo_strndup (p, end - p);
        p = end;

        p = skip_space (p);
        if (*p != '=') {
            parse_error (svg_render, attributes, p, "Expected '='");
            goto fail;
        }

        p++;
        p = skip_space (p);
        if (*p == '\"' || *p == '\'') {
            quote_char = *p;
        } else {
            parse_error (svg_render, attributes, p, "Could not find '\"' or '''");
            goto fail;
        }

        p++;
        end = strchr (p, quote_char);
        if (!end) {
            parse_error (svg_render, attributes, p, "Could not find '%c'", quote_char);
            goto fail;
        }

        attr.value = _cairo_strndup (p, end - p);
        p = end + 1;

        if (!append_attribute (element, &attr))
            goto fail;

        memset (&attr, 0, sizeof (svg_attribute_t));

        p = skip_space (p);
    }

    return p;

  fail:
    free (attr.name);
    free (attr.value);
    return NULL;
}

static cairo_bool_t
parse_svg (cairo_svg_glyph_render_t *svg_render,
           const char               *svg_document)
{
    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 */
    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. */

    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. */

    nesting = 0;

    while (*p) {
        if (nesting > 0) {
            /* In an open element. Anything before the next '<' is content */
            end = strchr (p, '<');
            if (!end) {
                parse_error (svg_render, svg_document, p, "Could not find '<'");
                goto fail;
            }
            status = _cairo_array_append_multiple (&open_elem->content, p, end - p);
            p = end;

        } else {
            p = skip_space (p);
            if (*p == 0)
                break/* end of document */
        }

        /* We should now be at the start of a tag */
        if (*p != '<') {
            parse_error (svg_render, svg_document, p, "Could not find '<'");
            goto fail;
        }

        p++;
        if (*p == '!') {
            p++;
            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;
            } else if (string_match (&p, "--")) {
                new_elem = create_element (COMMENT, 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;
            } else if (string_match (&p, "DOCTYPE")) {
                new_elem = create_element (DOCTYPE, NULL);
                end = strchr (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 + 1;
            } else {
                parse_error (svg_render, svg_document, p, "Invalid");
                goto fail;
            }

            if (!add_child_element (svg_render, open_elem, new_elem))
                goto fail;

            new_elem = NULL;
            continue;
        }

        if (*p == '?') {
            p++;
            new_elem = create_element (PROCESSING_INSTRUCTION, 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 + 2;

            if (!add_child_element (svg_render, open_elem, new_elem))
                goto fail;

            new_elem = NULL;
            continue;
        }

        if (*p == '/') {
            /* Closing tag */
            p++;

            /* find end of tag name */
            end = strpbrk(p, WHITE_SPACE_CHARS ">");
            if (!end) {
                parse_error (svg_render, svg_document, p, "Could not find '>'");
                goto fail;
            }

            name = _cairo_strndup (p, end - p);
            p = end;
            p = skip_space (p);
            if (*p != '>') {
                parse_error (svg_render, svg_document, p, "Could not find '>'");
                free (name);
                goto fail;
            }

            p++;
            if (nesting == 0) {
                parse_error (svg_render, svg_document, p, "parse_elements: parsed but no matching start tag", name);
                free (name);
                goto fail;
            }
            if (!string_equal (name, open_elem->tag)) {
                parse_error (svg_render, svg_document, p,
                             "parse_elements: found but current open tag is <%s>",
                             name, open_elem->tag);
                free (name);
                goto fail;
            }

            /* pop top element on open elements stack into new_elem */
            new_elem = open_elem;
            open_elem = open_elem->next;
            new_elem->next = NULL;
            nesting--;

            free (name);
            if (!add_child_element (svg_render, open_elem, new_elem))
                goto fail;

            new_elem = NULL;
            continue;
        }

        /* We should now be in a start or empty element tag */

        /* find end of tag name */
        end = strpbrk(p, WHITE_SPACE_CHARS "/>");
        if (!end) {
            parse_error (svg_render, svg_document, p, "Could not find '>'");
            goto fail;
        }

        name = _cairo_strndup (p, end - p);
        p = end;

        new_elem = create_element (CONTAINER_ELEMENT, name);
        p = parse_attributes (svg_render, p, new_elem);
        if (!p)
            goto fail;

        p = skip_space (p);
        if (*p == '/') {
            new_elem->type = EMPTY_ELEMENT;
            p++;
        }

        if (!p || *p != '>') {
            print_error (svg_render, "Could not find '>'");
            goto fail;
        }

        p++;
        if (new_elem->type == EMPTY_ELEMENT) {
            if (!add_child_element (svg_render, open_elem, new_elem))
                goto fail;

            new_elem = NULL;
        } else {
            /* push new elem onto open elements stack */
            new_elem->next = open_elem;
            open_elem = new_elem;
            new_elem = NULL;
            nesting++;
        }
    }

    if (nesting != 0) {
        parse_error (svg_render, svg_document, p, "Missing closing tag for <%s>", open_elem->tag);
        goto fail;
    }

    svg_render->tree = open_elem;
    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
parse_transform (const char *p, cairo_matrix_t *matrix)
{
    cairo_matrix_t m;
    double x, y, a;
    cairo_bool_t have_optional;

    cairo_matrix_init_identity (matrix);
    while (p) {
        while (p && (*p == ',' || _cairo_isspace (*p)))
            p++;

        if (!p || *p == 0)
            break;

        if (string_match (&p, "matrix")) {
            p = skip_char (p, '(');
            if (!p)
                break;

            p = get_floats (p, 6, 0, NULL, &m.xx, &m.yx, &m.xy, &m.yy, &m.x0, &m.y0);
            if (!p)
                break;

            p = skip_char (p, ')');
            if (!p)
                break;

            cairo_matrix_multiply (matrix, &m, matrix);

        } else if (string_match (&p, "translate")) {
            p = skip_char (p, '(');
            if (!p)
                break;

            p = get_floats (p, 1, 1, &have_optional, &x, &y);
            if (!p)
                break;

            p = skip_char (p, ')');
            if (!p)
                break;

            if (!have_optional)
                y = 0;

            cairo_matrix_translate (matrix, x, y);

        } else if (string_match (&p, "scale")) {
            p = skip_char (p, '(');
            if (!p)
                break;

            p = get_floats (p, 1, 1, &have_optional, &x, &y);
            if (!p)
                break;

            p = skip_char (p, ')');
            if (!p)
                break;

            if (!have_optional)
                y = x;

            cairo_matrix_scale (matrix, x, y);

        } else if (string_match (&p, "rotate")) {
            p = skip_char (p, '(');
            if (!p)
                break;

            p = get_floats (p, 1, 2, &have_optional, &a, &x, &y);
            if (!p)
                break;

            p = skip_char (p, ')');
            if (!p)
                break;

            if (!have_optional) {
                x = 0;
                y = 0;
            }

            a *= M_PI/180.0;
            cairo_matrix_translate (matrix, x, y);
            cairo_matrix_rotate (matrix, a);
            cairo_matrix_translate (matrix, -x, -y);

        } else if (string_match (&p, "skewX")) {
            p = skip_char (p, '(');
            if (!p)
                break;

            p = get_floats (p, 1, 0, NULL, &a);
            if (!p)
                break;

            p = skip_char (p, ')');
            if (!p)
                break;

            a *= M_PI/180.0;
            cairo_matrix_init_identity (&m);
            m.xy = tan (a);
            cairo_matrix_multiply (matrix, &m, matrix);

        } else if (string_match (&p, "skewY")) {
            p = skip_char (p, '(');
            if (!p)
                break;

            p = get_floats (p, 1, 0, NULL, &a);
            if (!p)
                break;

            p = skip_char (p, ')');
            if (!p)
                break;

            a *= M_PI/180.0;
            cairo_matrix_init_identity (&m);
            m.yx = tan (a);
            cairo_matrix_multiply (matrix, &m, matrix);

        } else {
            break;
        }
    }
    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 *
create_pattern (cairo_svg_glyph_render_t *svg_render,
                cairo_svg_element_t      *paint_server)
{
    cairo_pattern_t *pattern = NULL;

    if (paint_server) {
        svg_render->build_pattern.paint_server = paint_server;
        render_element_tree (svg_render, paint_server, NULL, FALSE);
        pattern = svg_render->build_pattern.pattern;
        svg_render->build_pattern.pattern = NULL;
        svg_render->build_pattern.paint_server = NULL;
        svg_render->build_pattern.type = BUILD_PATTERN_NONE;
    }

    if (!pattern)
        pattern = cairo_pattern_create_rgb (0, 0, 0);

    return pattern;
}

static cairo_bool_t
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;

    if (end_tag)
        return FALSE;

    /* Default viewport width, height is EM square */
    if (!get_float_or_percent_attribute (element, "width", svg_render->units_per_em, &width))
        width = svg_render->units_per_em;

    if (!get_float_or_percent_attribute (element, "height", svg_render->units_per_em, &height))
        height = svg_render->units_per_em;

    /* Transform viewport to unit square, centering it if width != height. */
    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 {
        cairo_scale (svg_render->cr, 1.0/height, 1.0/height);
        cairo_translate (svg_render->cr, (height - width)/2.0, 0);
    }

    svg_render->width = width;
    svg_render->height = height;

    p = get_attribute (element, "viewBox");
    if (p) {
        /* Transform viewport to viewbox */
        end = get_path_params (p, 4, &vb_x, &vb_y, &vb_width, &vb_height);
        if (!end) {
            print_warning (svg_render, "viewBox expected 4 numbers: %s", p);
            return FALSE;
        }
        cairo_translate (svg_render->cr, -vb_x * width/vb_width, -vb_y * width/vb_width);
        cairo_scale (svg_render->cr, width/vb_width, height/vb_height);
        svg_render->width = vb_width;
        svg_render->height = vb_height;
    }

    svg_render->view_port_set = TRUE;
    return TRUE;
}

static cairo_bool_t
render_element_clip_path (cairo_svg_glyph_render_t *svg_render,
                          cairo_svg_element_t      *element,
                          cairo_bool_t              end_tag)
{
    cairo_svg_graphics_state_t *gs = svg_render->graphics_state;
    const char *p;

    if (end_tag || gs->mode != GS_CLIP || svg_render->build_pattern.type != BUILD_PATTERN_NONE) {
        return FALSE;
    }

    p = get_attribute (element, "clipPathUnits");
    if (string_equal (p, "objectBoundingBox")) {
        cairo_translate (svg_render->cr,
                                svg_render->graphics_state->bbox.x,
                                svg_render->graphics_state->bbox.y);
        cairo_scale (svg_render->cr,
                     svg_render->graphics_state->bbox.width,
                     svg_render->graphics_state->bbox.height);
    }

    return TRUE;
}

static void
apply_gradient_attributes (cairo_svg_glyph_render_t *svg_render,
                           cairo_svg_element_t      *element)
{
    cairo_pattern_t *pattern = svg_render->build_pattern.pattern;
    cairo_bool_t object_bbox = TRUE;
    cairo_matrix_t transform;
    cairo_matrix_t mat;
    const char *p;

    if (!pattern)
        return;

    p = get_attribute (element, "gradientUnits");
    if (string_equal (p, "userSpaceOnUse"))
        object_bbox = FALSE;

    cairo_matrix_init_identity (&mat);
    if (object_bbox) {
        cairo_matrix_translate (&mat,
                                svg_render->graphics_state->bbox.x,
                                svg_render->graphics_state->bbox.y);
        cairo_matrix_scale (&mat,
                            svg_render->graphics_state->bbox.width,
                            svg_render->graphics_state->bbox.height);
    }

    p = get_attribute (element, "gradientTransform");
     if (parse_transform (p, &transform))
         cairo_matrix_multiply (&mat, &transform, &mat);

    if (cairo_matrix_invert (&mat) == CAIRO_STATUS_SUCCESS)
        cairo_pattern_set_matrix (pattern, &mat);

    p = get_attribute (element, "spreadMethod");
    if (string_equal (p, "reflect"))
        cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REFLECT);
    else if (string_equal (p, "repeat"))
        cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
}

static cairo_bool_t
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;

    if (svg_render->build_pattern.paint_server != element ||
        end_tag ||
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;

    /* FIXME default value for userSpaceOnUse? */
    double width = 1.0;
    double height = 1.0;

    if (!get_float_or_percent_attribute (element, "x1", width, &x1))
        x1 = 0.0;

    if (!get_float_or_percent_attribute (element, "y1", height, &y1))
        y1 = 0.0;

    if (!get_float_or_percent_attribute (element, "x2", width, &x2))
        x2 = width;

    if (!get_float_or_percent_attribute (element, "y2", height, &y2))
        y2 = 0.0;

    if (svg_render->build_pattern.pattern)
        abort();

    svg_render->build_pattern.pattern = cairo_pattern_create_linear (x1, y1, x2, y2);
    svg_render->build_pattern.type = BUILD_PATTERN_LINEAR;
    apply_gradient_attributes (svg_render, element);
    return TRUE;
}

static cairo_bool_t
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;

    if (svg_render->build_pattern.paint_server != element ||
        end_tag ||
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;

    /* FIXME default value for userSpaceOnUse? */
    double width = 1.0;
    double height = 1.0;

    if (!get_float_or_percent_attribute (element, "cx", width, &cx))
        cx = 0.5 * width;

    if (!get_float_or_percent_attribute (element, "cy", height, &cy))
        cy = 0.5 * height;

    if (!get_float_or_percent_attribute (element, "r", width, &r))
        r = 0.5 * width;

    if (!get_float_or_percent_attribute (element, "fx", width, &fx))
        fx = cx;

    if (!get_float_or_percent_attribute (element, "fy", height, &fy))
        fy = cy;

    svg_render->build_pattern.pattern = cairo_pattern_create_radial (fx, fy, 0, cx, cy, r);
    svg_render->build_pattern.type = BUILD_PATTERN_RADIAL;
    apply_gradient_attributes (svg_render, element);
    return TRUE;
}

static cairo_bool_t
render_element_stop (cairo_svg_glyph_render_t *svg_render,
                     cairo_svg_element_t      *element,
                     cairo_bool_t              end_tag)
{
    double offset, opacity;
    cairo_pattern_t *pattern = svg_render->build_pattern.pattern;

    if (!pattern)
        return FALSE;

    if (cairo_pattern_get_type (pattern) != CAIRO_PATTERN_TYPE_LINEAR &&
        cairo_pattern_get_type (pattern) != CAIRO_PATTERN_TYPE_RADIAL)
        return FALSE;

    if (!get_float_or_percent_attribute (element, "offset", 1.0, &offset))
        return FALSE;

    if (!get_float_attribute (element, "stop-opacity", &opacity))
        opacity = 1.0;

    cairo_svg_color_t color;
    get_color (svg_render, "black", &color);
    get_color (svg_render, get_attribute(element, "stop-color"), &color);
    if (color.type == RGB) {
        cairo_pattern_add_color_stop_rgba (pattern,
                                           offset,
                                           color.red,
                                           color.green,
                                           color.blue,
                                           opacity);
    } else { /* color.type == FOREGROUND */
        double red, green, blue, alpha;
        if (cairo_pattern_get_rgba (svg_render->foreground_source, &red, &green, &blue, &alpha) == CAIRO_STATUS_SUCCESS) {
     svg_render->foreground_source_used = TRUE;
 } else {
            red = green = blue = 0;
            alpha = 1;
        }
        cairo_pattern_add_color_stop_rgba (pattern, offset, red, green, blue, alpha);
    }
    return TRUE;
}

static cairo_bool_t
render_element_g (cairo_svg_glyph_render_t *svg_render,
                  cairo_svg_element_t      *element,
                  cairo_bool_t              end_tag)
{
    if (svg_render->graphics_state->mode == GS_NO_RENDER ||
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;

    if (!end_tag) {
        cairo_push_group (svg_render->cr);
    } else {
        cairo_pop_group_to_source (svg_render->cr);
        cairo_paint_with_alpha (svg_render->cr, svg_render->graphics_state->opacity);
    }
    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
_read_png_from_base64 (void *closure, unsigned char *data, unsigned int length)
{
    base64_decode_t *decode = closure;
    int n, c;
    unsigned val;

    while (length) {
        if (decode->buf_pos >= 0) {
            *data++ = decode->buf[decode->buf_pos++];
            length--;
            if (decode->buf_pos == 3)
                decode->buf_pos = -1;
        }
        if (length > 0 && decode->buf_pos < 0) {
            n = 0;
            while (*decode->data && n < 4) {
                c = *decode->data++;
                if (c >='A' && c <='Z') {
                    val = (val << 6) | (c -'A');
                    n++;
                } else if (c >='a' && c <='z') {
                    val = (val << 6) | (c -'a' + 26);
                    n++;
                } else if (c >='0' && c <='9') {
                    val = (val << 6) | (c -'0' + 52);
                    n++;
                } else if (c =='+') {
                    val = (val << 6) | 62;
                    n++;
                } else if (c =='/') {
                    val = (val << 6) | 63;
                    n++;
                } else if (c == '=') {
                    val = (val << 6);
                    n++;
                }
            }
            if (n < 4)
                return CAIRO_STATUS_READ_ERROR;

            decode->buf[0] = val >> 16;
            decode->buf[1] = val >> 8;
            decode->buf[2] = val >> 0;
            decode->buf_pos = 0;
        }
    }

    return CAIRO_STATUS_SUCCESS;
}

static cairo_bool_t
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;

    if (svg_render->graphics_state->mode == GS_NO_RENDER ||
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;

    if (!get_float_attribute (element, "x", &x))
        x = 0;

    if (!get_float_attribute (element, "y", &y))
        y = 0;

    if (!get_float_attribute (element, "width", &width))
        return FALSE;

    if (!get_float_attribute (element, "height", &height))
        return FALSE;

    data = get_href_attribute (element);
    if (!data)
        return FALSE;

    if (!string_match (&data, "data:image/png;base64,"))
        return FALSE;

    decode.data = data;
    decode.buf_pos = -1;
    surface = cairo_image_surface_create_from_png_stream (_read_png_from_base64, &decode);
    if (cairo_surface_status (surface)) {
        print_warning (svg_render, "Unable to decode PNG");
        cairo_surface_destroy (surface);
        return FALSE;
    }

    w = cairo_image_surface_get_width (surface);
    h = cairo_image_surface_get_height (surface);

    if (w > 0 && h > 0) {
        cairo_translate (svg_render->cr, x, y);
        cairo_scale (svg_render->cr, width/w, height/h);
        cairo_set_source_surface (svg_render->cr, surface, 0, 0);
        cairo_paint (svg_render->cr);
    }

    cairo_surface_destroy (surface);

    return FALSE;
}

static cairo_bool_t
render_element_use (cairo_svg_glyph_render_t *svg_render,
                    cairo_svg_element_t      *element,
                    cairo_bool_t              end_tag)
{
    double x = 0;
    double y = 0;
    const char *id;

    if (end_tag || svg_render->graphics_state->mode == GS_NO_RENDER ||
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;

    get_float_attribute (element, "x", &x);
    get_float_attribute (element, "y", &y);

    id = get_href_attribute (element);
    if (!id)
        return FALSE;

    cairo_svg_element_t *use_element = lookup_element (svg_render, id);
    cairo_translate (svg_render->cr, x, y);
    render_element_tree (svg_render, use_element, NULL, FALSE);
    return TRUE;
}

static cairo_bool_t
draw_path (cairo_svg_glyph_render_t *svg_render)
{
    cairo_svg_graphics_state_t *gs = svg_render->graphics_state;
    cairo_pattern_t *pattern;
    cairo_bool_t opacity_group = FALSE;

    if (gs->mode == GS_COMPUTE_BBOX) {
        cairo_set_source_rgb (svg_render->cr, 0, 0, 0);
        cairo_set_fill_rule (svg_render->cr, gs->fill_rule);
        cairo_fill (svg_render->cr);
        return FALSE;
    } else if (gs->mode == GS_CLIP) {
        return FALSE;
    }

    if (gs->opacity < 1.0) {
        cairo_push_group (svg_render->cr);
        opacity_group = TRUE;
    }

    cairo_path_t *path = cairo_copy_path (svg_render->cr);
    cairo_new_path (svg_render->cr);

    if (gs->fill.type != PAINT_NONE) {
        cairo_bool_t group = FALSE;
        if (gs->fill.type == PAINT_COLOR) {
            if (gs->fill.color.type == RGB) {
                cairo_set_source_rgba (svg_render->cr,
                                       gs->fill.color.red,
                                       gs->fill.color.green,
                                       gs->fill.color.blue,
                                       gs->fill_opacity);
            } 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->fill.type == PAINT_SERVER) {
            pattern = create_pattern (svg_render, gs->fill.paint_server);
            cairo_set_source (svg_render->cr, pattern);
            cairo_pattern_destroy (pattern);
            if (gs->fill_opacity < 1.0)
                group = TRUE;
        }

        if (group)
            cairo_push_group (svg_render->cr);

        cairo_append_path (svg_render->cr, path);
        cairo_set_fill_rule (svg_render->cr, gs->fill_rule);
        cairo_fill (svg_render->cr);
        if (group) {
            cairo_pop_group_to_source (svg_render->cr);
            cairo_paint_with_alpha (svg_render->cr, gs->fill_opacity);
        }
    }

    if (gs->stroke.type != PAINT_NONE) {
        cairo_bool_t group = FALSE;
        if (gs->stroke.type == PAINT_COLOR) {
            if (gs->stroke.color.type == RGB) {
                cairo_set_source_rgba (svg_render->cr,
                                       gs->stroke.color.red,
                                       gs->stroke.color.green,
                                       gs->stroke.color.blue,
                                       gs->stroke_opacity);
            } 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;
        }

        if (group)
            cairo_push_group (svg_render->cr);

        cairo_append_path (svg_render->cr, path);
        cairo_stroke (svg_render->cr);

        if (group) {
            cairo_pop_group_to_source (svg_render->cr);
            cairo_paint_with_alpha (svg_render->cr, gs->stroke_opacity);
        }
    }

    cairo_path_destroy (path);

    if (opacity_group) {
        cairo_pop_group_to_source (svg_render->cr);
        cairo_paint_with_alpha (svg_render->cr, gs->opacity);
    }
    return TRUE;
}

static void
elliptical_arc (cairo_svg_glyph_render_t *svg_render,
                double                    cx,
                double                    cy,
                double                    rx,
                double                    ry,
                double                    angle1,
                double                    angle2)
{
    cairo_save (svg_render->cr);
    cairo_translate (svg_render->cr, cx, cy);
    cairo_scale (svg_render->cr, rx, ry);
    cairo_arc (svg_render->cr, 0, 0, 1, angle1, angle2);
    cairo_restore (svg_render->cr);
}

static cairo_bool_t
render_element_rect (cairo_svg_glyph_render_t *svg_render,
                     cairo_svg_element_t      *element,
                     cairo_bool_t              end_tag)
{
    double x = 0;
    double y = 0;
    double width = svg_render->width;
    double height = svg_render->height;
    double rx = 0;
    double ry = 0;

    if (end_tag ||
        svg_render->graphics_state->mode == GS_NO_RENDER ||
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;

    get_float_or_percent_attribute (element, "x", svg_render->width, &x);
    get_float_or_percent_attribute (element, "y", svg_render->height, &y);
    get_float_or_percent_attribute (element, "width", svg_render->width, &width);
    get_float_or_percent_attribute (element, "height", svg_render->height, &height);
    get_float_or_percent_attribute (element, "rx", svg_render->width, &rx);
    get_float_or_percent_attribute (element, "ry", svg_render->height, &ry);

    if (rx == 0 && ry == 0) {
        cairo_rectangle (svg_render->cr, x, y, width, height);
    } else {
        cairo_move_to (svg_render->cr, x + rx, y);
        cairo_line_to (svg_render->cr, x + width - rx, y);
        elliptical_arc (svg_render,    x + width - rx, y + ry, rx, ry, -M_PI/2, 0);
        cairo_line_to (svg_render->cr, x + width, y + height - ry);
        elliptical_arc (svg_render,    x + width - rx, y + height - ry, rx, ry, 0, M_PI/2);
        cairo_line_to (svg_render->cr, x + rx, y + height);
        elliptical_arc (svg_render,    x + rx, y + height - ry, rx, ry, M_PI/2, M_PI);
        cairo_line_to (svg_render->cr, x, y + ry);
        elliptical_arc (svg_render,    x + rx, y + ry, rx, ry, M_PI, -M_PI/2);
    }

    draw_path (svg_render);
    return TRUE;
}

static cairo_bool_t
render_element_circle (cairo_svg_glyph_render_t *svg_render,
                       cairo_svg_element_t      *element,
                       cairo_bool_t              end_tag)
{
    double cx = 0;
    double cy = 0;
    double r = 0;

    if (end_tag ||
        svg_render->graphics_state->mode == GS_NO_RENDER ||
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;

    get_float_or_percent_attribute (element, "cx", svg_render->width, &cx);
    get_float_or_percent_attribute (element, "cy", svg_render->height, &cy);
    get_float_or_percent_attribute (element, "r", svg_render->width, &r);

    cairo_arc (svg_render->cr, cx, cy, r, 0, 2*M_PI);

    draw_path (svg_render);
    return TRUE;
}

static cairo_bool_t
render_element_ellipse (cairo_svg_glyph_render_t *svg_render,
                        cairo_svg_element_t      *element,
                        cairo_bool_t              end_tag)
{
    double cx = 0;
    double cy = 0;
    double rx = 0;
    double ry = 0;

    if (end_tag ||
        svg_render->graphics_state->mode == GS_NO_RENDER ||
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;

    get_float_or_percent_attribute (element, "cx", svg_render->width, &cx);
    get_float_or_percent_attribute (element, "cy", svg_render->height, &cy);
    get_float_or_percent_attribute (element, "rx", svg_render->width, &rx);
    get_float_or_percent_attribute (element, "ry", svg_render->height, &ry);

    elliptical_arc (svg_render, cx, cy, rx, ry, 0, 2*M_PI);
    draw_path (svg_render);
    return TRUE;
}

static cairo_bool_t
render_element_line (cairo_svg_glyph_render_t *svg_render,
                     cairo_svg_element_t      *element,
                     cairo_bool_t              end_tag)
{
    double x1 = 0;
    double y1 = 0;
    double x2 = 0;
    double y2 = 0;

    if (end_tag ||
        svg_render->graphics_state->mode == GS_NO_RENDER ||
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;

    get_float_or_percent_attribute (element, "x1", svg_render->width, &x1);
    get_float_or_percent_attribute (element, "y1", svg_render->height, &y1);
    get_float_or_percent_attribute (element, "x2", svg_render->width, &x2);
    get_float_or_percent_attribute (element, "y2", svg_render->height, &y2);

    cairo_move_to (svg_render->cr, x1, y1);
    cairo_line_to (svg_render->cr, x2, y2);

    draw_path (svg_render);
    return TRUE;
}

static cairo_bool_t
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;
    cairo_bool_t have_move = FALSE;

    if (end_tag ||
        svg_render->graphics_state->mode == GS_NO_RENDER ||
        svg_render->build_pattern.type != BUILD_PATTERN_NONE)
        return FALSE;

    p = get_attribute (element, "points");
    do {
        end = get_path_params (p, 2, &x, &y);
        if (!end) {
            print_warning (svg_render, "points expected 2 numbers: %s", p);
            break;
        }
        p = end;
        if (!have_move) {
            cairo_move_to (svg_render->cr, x, y);
            have_move = TRUE;
        } else {
            cairo_line_to (svg_render->cr, x, y);
        }
        p = skip_space (p);
    } while (p && *p);

    if (string_equal (element->tag, "polygon"))
        cairo_close_path (svg_render->cr);

    draw_path (svg_render);
    return TRUE;
}

static double
angle_between_vectors (double ux,
                       double uy,
                       double vx,
                       double vy)
{
    double dot = ux*vx + uy*vy;
    double umag = sqrt (ux*ux + uy*uy);
    double vmag = sqrt (vx*vx + vy*vy);
    double c = dot/(umag*vmag);
    if (c > 1.0)
        c = 1.0;

    if (c < -1.0)
        c = -1.0;

    double a = acos (c);
    if (ux * vy - uy * vx < 0.0)
        a = -a;

    return a;
}

static void
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;

    cairo_get_matrix (cr, &ctm);
    epsilon = _cairo_matrix_transformed_circle_major_axis (&ctm, cairo_get_tolerance (cr));

    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.
     */


    rx = fabs (rx);
    ry = fabs (ry);
    if (rx < epsilon || ry < epsilon) {
        cairo_line_to (cr, x2, y2);
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=98 H=90 G=94

¤ Dauer der Verarbeitung: 0.36 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.