/* -*- 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_ER
ROR, ## __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("%s?>\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("%s>\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 %s> 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 %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 */
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.35 Sekunden
(vorverarbeitet)
¤
*© Formatika GbR, Deutschland