/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim: set sw=4 sts=4 ts=4 expandtab: */ /* rsvg-styles.c: Handle SVG styles Copyright (C) 2000 Eazel, Inc. Copyright (C) 2002 Dom Lachowicz This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Author: Raph Levien */ #include "config.h" #include #include #include "rsvg.h" #include "rsvg-private.h" #include "rsvg-filter.h" #include "rsvg-css.h" #include "rsvg-styles.h" #include "rsvg-shapes.h" #include "rsvg-mask.h" #include "rsvg-marker.h" #include #define RSVG_DEFAULT_FONT "Times New Roman" enum { SHAPE_RENDERING_AUTO = CAIRO_ANTIALIAS_DEFAULT, SHAPE_RENDERING_OPTIMIZE_SPEED = CAIRO_ANTIALIAS_NONE, SHAPE_RENDERING_CRISP_EDGES = CAIRO_ANTIALIAS_NONE, SHAPE_RENDERING_GEOMETRIC_PRECISION = CAIRO_ANTIALIAS_DEFAULT }; enum { TEXT_RENDERING_AUTO = CAIRO_ANTIALIAS_DEFAULT, TEXT_RENDERING_OPTIMIZE_SPEED = CAIRO_ANTIALIAS_NONE, TEXT_RENDERING_OPTIMIZE_LEGIBILITY = CAIRO_ANTIALIAS_DEFAULT, TEXT_RENDERING_GEOMETRIC_PRECISION = CAIRO_ANTIALIAS_DEFAULT }; typedef struct _StyleValueData { gchar *value; gboolean important; } StyleValueData; /* * _rsvg_cairo_matrix_init_shear: Set up a shearing matrix. * @dst: Where to store the resulting affine transform. * @theta: Shear angle in degrees. * * Sets up a shearing matrix. In the standard libart coordinate system * and a small value for theta, || becomes \\. Horizontal lines remain * unchanged. **/ static void _rsvg_cairo_matrix_init_shear (cairo_matrix_t *dst, double theta) { cairo_matrix_init (dst, 1., 0., tan (theta * M_PI / 180.0), 1., 0., 0); } static StyleValueData * style_value_data_new (const gchar *value, gboolean important) { StyleValueData *ret; ret = g_new (StyleValueData, 1); ret->value = g_strdup (value); ret->important = important; return ret; } static void style_value_data_free (StyleValueData *value) { if (!value) return; g_free (value->value); g_free (value); } gdouble rsvg_viewport_percentage (gdouble width, gdouble height) { return sqrt (width * height); } gdouble rsvg_dpi_percentage (RsvgHandle * ctx) { return sqrt (ctx->priv->dpi_x * ctx->priv->dpi_y); } void rsvg_state_init (RsvgState * state) { memset (state, 0, sizeof (RsvgState)); state->parent = NULL; cairo_matrix_init_identity (&state->affine); cairo_matrix_init_identity (&state->personal_affine); state->mask = NULL; state->opacity = 0xff; state->baseline_shift = 0.; state->current_color = 0xff000000; /* See bgo#764808; we don't inherit CSS * from the public API, so start off with * opaque black instead of transparent. */ state->fill = rsvg_paint_server_parse (NULL, "#000"); state->fill_opacity = 0xff; state->stroke_opacity = 0xff; state->stroke_width = _rsvg_css_parse_length ("1"); state->miter_limit = 4; state->cap = CAIRO_LINE_CAP_BUTT; state->join = CAIRO_LINE_JOIN_MITER; state->stop_opacity = 0xff; state->fill_rule = CAIRO_FILL_RULE_WINDING; state->clip_rule = CAIRO_FILL_RULE_WINDING; state->enable_background = RSVG_ENABLE_BACKGROUND_ACCUMULATE; state->comp_op = CAIRO_OPERATOR_OVER; state->overflow = FALSE; state->flood_color = 0; state->flood_opacity = 255; state->font_family = g_strdup (RSVG_DEFAULT_FONT); state->font_size = _rsvg_css_parse_length ("12.0"); state->font_style = PANGO_STYLE_NORMAL; state->font_variant = PANGO_VARIANT_NORMAL; state->font_weight = PANGO_WEIGHT_NORMAL; state->font_stretch = PANGO_STRETCH_NORMAL; state->text_dir = PANGO_DIRECTION_LTR; state->text_gravity = PANGO_GRAVITY_SOUTH; state->unicode_bidi = UNICODE_BIDI_NORMAL; state->text_anchor = TEXT_ANCHOR_START; state->letter_spacing = _rsvg_css_parse_length ("0.0"); state->visible = TRUE; state->cond_true = TRUE; state->filter = NULL; state->clip_path = NULL; state->startMarker = NULL; state->middleMarker = NULL; state->endMarker = NULL; state->has_baseline_shift = FALSE; state->has_current_color = FALSE; state->has_flood_color = FALSE; state->has_flood_opacity = FALSE; state->has_fill_server = FALSE; state->has_fill_opacity = FALSE; state->has_fill_rule = FALSE; state->has_clip_rule = FALSE; state->has_stroke_server = FALSE; state->has_stroke_opacity = FALSE; state->has_stroke_width = FALSE; state->has_miter_limit = FALSE; state->has_cap = FALSE; state->has_join = FALSE; state->has_dash = FALSE; state->has_dashoffset = FALSE; state->has_visible = FALSE; state->has_cond = FALSE; state->has_stop_color = FALSE; state->has_stop_opacity = FALSE; state->has_font_size = FALSE; state->has_font_family = FALSE; state->has_lang = FALSE; state->has_font_style = FALSE; state->has_font_variant = FALSE; state->has_font_weight = FALSE; state->has_font_stretch = FALSE; state->has_font_decor = FALSE; state->has_text_dir = FALSE; state->has_text_gravity = FALSE; state->has_unicode_bidi = FALSE; state->has_text_anchor = FALSE; state->has_letter_spacing = FALSE; state->has_startMarker = FALSE; state->has_middleMarker = FALSE; state->has_endMarker = FALSE; state->has_overflow = FALSE; state->shape_rendering_type = SHAPE_RENDERING_AUTO; state->has_shape_rendering_type = FALSE; state->text_rendering_type = TEXT_RENDERING_AUTO; state->has_text_rendering_type = FALSE; state->styles = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) style_value_data_free); } void rsvg_state_reinit (RsvgState * state) { RsvgState *parent = state->parent; rsvg_state_finalize (state); rsvg_state_init (state); state->parent = parent; } typedef int (*InheritanceFunction) (int dst, int src); void rsvg_state_clone (RsvgState * dst, const RsvgState * src) { gint i; RsvgState *parent = dst->parent; rsvg_state_finalize (dst); *dst = *src; dst->parent = parent; dst->filter = g_strdup (src->filter); dst->mask = g_strdup (src->mask); dst->clip_path = g_strdup (src->clip_path); dst->font_family = g_strdup (src->font_family); dst->lang = g_strdup (src->lang); dst->startMarker = g_strdup (src->startMarker); dst->middleMarker = g_strdup (src->middleMarker); dst->endMarker = g_strdup (src->endMarker); rsvg_paint_server_ref (dst->fill); rsvg_paint_server_ref (dst->stroke); dst->styles = g_hash_table_ref (src->styles); if (src->dash.n_dash > 0) { dst->dash.dash = g_new (gdouble, src->dash.n_dash); for (i = 0; i < src->dash.n_dash; i++) dst->dash.dash[i] = src->dash.dash[i]; } } /* This function is where all inheritance takes place. It is given a base and a modifier state, as well as a function to determine how the base is modified and a flag as to whether things that can not be inherited are copied streight over, or ignored. */ static void rsvg_state_inherit_run (RsvgState * dst, const RsvgState * src, const InheritanceFunction function, const gboolean inherituninheritables) { gint i; if (function (dst->has_baseline_shift, src->has_baseline_shift)) dst->baseline_shift = src->baseline_shift; if (function (dst->has_current_color, src->has_current_color)) dst->current_color = src->current_color; if (function (dst->has_flood_color, src->has_flood_color)) dst->flood_color = src->flood_color; if (function (dst->has_flood_opacity, src->has_flood_opacity)) dst->flood_opacity = src->flood_opacity; if (function (dst->has_fill_server, src->has_fill_server)) { rsvg_paint_server_ref (src->fill); if (dst->fill) rsvg_paint_server_unref (dst->fill); dst->fill = src->fill; } if (function (dst->has_fill_opacity, src->has_fill_opacity)) dst->fill_opacity = src->fill_opacity; if (function (dst->has_fill_rule, src->has_fill_rule)) dst->fill_rule = src->fill_rule; if (function (dst->has_clip_rule, src->has_clip_rule)) dst->clip_rule = src->clip_rule; if (function (dst->overflow, src->overflow)) dst->overflow = src->overflow; if (function (dst->has_stroke_server, src->has_stroke_server)) { rsvg_paint_server_ref (src->stroke); if (dst->stroke) rsvg_paint_server_unref (dst->stroke); dst->stroke = src->stroke; } if (function (dst->has_stroke_opacity, src->has_stroke_opacity)) dst->stroke_opacity = src->stroke_opacity; if (function (dst->has_stroke_width, src->has_stroke_width)) dst->stroke_width = src->stroke_width; if (function (dst->has_miter_limit, src->has_miter_limit)) dst->miter_limit = src->miter_limit; if (function (dst->has_cap, src->has_cap)) dst->cap = src->cap; if (function (dst->has_join, src->has_join)) dst->join = src->join; if (function (dst->has_stop_color, src->has_stop_color)) dst->stop_color = src->stop_color; if (function (dst->has_stop_opacity, src->has_stop_opacity)) dst->stop_opacity = src->stop_opacity; if (function (dst->has_cond, src->has_cond)) dst->cond_true = src->cond_true; if (function (dst->has_font_size, src->has_font_size)) dst->font_size = src->font_size; if (function (dst->has_font_style, src->has_font_style)) dst->font_style = src->font_style; if (function (dst->has_font_variant, src->has_font_variant)) dst->font_variant = src->font_variant; if (function (dst->has_font_weight, src->has_font_weight)) dst->font_weight = src->font_weight; if (function (dst->has_font_stretch, src->has_font_stretch)) dst->font_stretch = src->font_stretch; if (function (dst->has_font_decor, src->has_font_decor)) dst->font_decor = src->font_decor; if (function (dst->has_text_dir, src->has_text_dir)) dst->text_dir = src->text_dir; if (function (dst->has_text_gravity, src->has_text_gravity)) dst->text_gravity = src->text_gravity; if (function (dst->has_unicode_bidi, src->has_unicode_bidi)) dst->unicode_bidi = src->unicode_bidi; if (function (dst->has_text_anchor, src->has_text_anchor)) dst->text_anchor = src->text_anchor; if (function (dst->has_letter_spacing, src->has_letter_spacing)) dst->letter_spacing = src->letter_spacing; if (function (dst->has_startMarker, src->has_startMarker)) { g_free (dst->startMarker); dst->startMarker = g_strdup (src->startMarker); } if (function (dst->has_middleMarker, src->has_middleMarker)) { g_free (dst->middleMarker); dst->middleMarker = g_strdup (src->middleMarker); } if (function (dst->has_endMarker, src->has_endMarker)) { g_free (dst->endMarker); dst->endMarker = g_strdup (src->endMarker); } if (function (dst->has_shape_rendering_type, src->has_shape_rendering_type)) dst->shape_rendering_type = src->shape_rendering_type; if (function (dst->has_text_rendering_type, src->has_text_rendering_type)) dst->text_rendering_type = src->text_rendering_type; if (function (dst->has_font_family, src->has_font_family)) { g_free (dst->font_family); /* font_family is always set to something */ dst->font_family = g_strdup (src->font_family); } if (function (dst->has_space_preserve, src->has_space_preserve)) dst->space_preserve = src->space_preserve; if (function (dst->has_visible, src->has_visible)) dst->visible = src->visible; if (function (dst->has_lang, src->has_lang)) { if (dst->has_lang) g_free (dst->lang); dst->lang = g_strdup (src->lang); } if (src->dash.n_dash > 0 && (function (dst->has_dash, src->has_dash))) { if (dst->has_dash) g_free (dst->dash.dash); dst->dash.dash = g_new (gdouble, src->dash.n_dash); dst->dash.n_dash = src->dash.n_dash; for (i = 0; i < src->dash.n_dash; i++) dst->dash.dash[i] = src->dash.dash[i]; } if (function (dst->has_dashoffset, src->has_dashoffset)) { dst->dash.offset = src->dash.offset; } if (inherituninheritables) { g_free (dst->clip_path); dst->clip_path = g_strdup (src->clip_path); g_free (dst->mask); dst->mask = g_strdup (src->mask); g_free (dst->filter); dst->filter = g_strdup (src->filter); dst->enable_background = src->enable_background; dst->opacity = src->opacity; dst->comp_op = src->comp_op; } } /* reinherit is given dst which is the top of the state stack and src which is the layer before in the state stack from which it should be inherited from */ static int reinheritfunction (int dst, int src) { if (!dst) return 1; return 0; } void rsvg_state_reinherit (RsvgState * dst, const RsvgState * src) { rsvg_state_inherit_run (dst, src, reinheritfunction, 0); } /* dominate is given dst which is the top of the state stack and src which is the layer before in the state stack from which it should be inherited from, however if anything is directly specified in src (the second last layer) it will override anything on the top layer, this is for overrides in use tags */ static int dominatefunction (int dst, int src) { if (!dst || src) return 1; return 0; } void rsvg_state_dominate (RsvgState * dst, const RsvgState * src) { rsvg_state_inherit_run (dst, src, dominatefunction, 0); } /* copy everything inheritable from the src to the dst */ static int clonefunction (int dst, int src) { return 1; } void rsvg_state_override (RsvgState * dst, const RsvgState * src) { rsvg_state_inherit_run (dst, src, clonefunction, 0); } /* put something new on the inheritance stack, dst is the top of the stack, src is the state to be integrated, this is essentially the opposite of reinherit, because it is being given stuff to be integrated on the top, rather than the context underneath. */ static int inheritfunction (int dst, int src) { return src; } void rsvg_state_inherit (RsvgState * dst, const RsvgState * src) { rsvg_state_inherit_run (dst, src, inheritfunction, 1); } void rsvg_state_finalize (RsvgState * state) { g_free (state->filter); g_free (state->mask); g_free (state->clip_path); g_free (state->font_family); g_free (state->lang); g_free (state->startMarker); g_free (state->middleMarker); g_free (state->endMarker); rsvg_paint_server_unref (state->fill); rsvg_paint_server_unref (state->stroke); if (state->dash.n_dash != 0) g_free (state->dash.dash); if (state->styles) { g_hash_table_unref (state->styles); state->styles = NULL; } } /* Parse a CSS2 style argument, setting the SVG context attributes. */ static void rsvg_parse_style_pair (RsvgHandle * ctx, RsvgState * state, const gchar * name, const gchar * value, gboolean important) { StyleValueData *data; data = g_hash_table_lookup (state->styles, name); if (data && data->important && !important) return; if (name == NULL || value == NULL) return; g_hash_table_insert (state->styles, (gpointer) g_strdup (name), (gpointer) style_value_data_new (value, important)); if (g_str_equal (name, "color")) state->current_color = rsvg_css_parse_color (value, &state->has_current_color); else if (g_str_equal (name, "opacity")) state->opacity = rsvg_css_parse_opacity (value); else if (g_str_equal (name, "flood-color")) state->flood_color = rsvg_css_parse_color (value, &state->has_flood_color); else if (g_str_equal (name, "flood-opacity")) { state->flood_opacity = rsvg_css_parse_opacity (value); state->has_flood_opacity = TRUE; } else if (g_str_equal (name, "filter")) { g_free (state->filter); state->filter = rsvg_get_url_string (value); } else if (g_str_equal (name, "mask")) { g_free (state->mask); state->mask = rsvg_get_url_string (value); } else if (g_str_equal (name, "baseline-shift")) { /* These values come from Inkscape's SP_CSS_BASELINE_SHIFT_(SUB/SUPER/BASELINE); * see sp_style_merge_baseline_shift_from_parent() */ if (g_str_equal (value, "sub")) { state->has_baseline_shift = TRUE; state->baseline_shift = -0.2; } else if (g_str_equal (value, "super")) { state->has_baseline_shift = TRUE; state->baseline_shift = 0.4; } else if (g_str_equal (value, "baseline")) { state->has_baseline_shift = TRUE; state->baseline_shift = 0.; } else { g_warning ("value \'%s\' for attribute \'baseline-shift\' is not supported; only 'sub', 'super', and 'baseline' are supported\n", value); } } else if (g_str_equal (name, "clip-path")) { g_free (state->clip_path); state->clip_path = rsvg_get_url_string (value); } else if (g_str_equal (name, "overflow")) { if (!g_str_equal (value, "inherit")) { state->overflow = rsvg_css_parse_overflow (value, &state->has_overflow); } } else if (g_str_equal (name, "enable-background")) { if (g_str_equal (value, "new")) state->enable_background = RSVG_ENABLE_BACKGROUND_NEW; else state->enable_background = RSVG_ENABLE_BACKGROUND_ACCUMULATE; } else if (g_str_equal (name, "comp-op")) { if (g_str_equal (value, "clear")) state->comp_op = CAIRO_OPERATOR_CLEAR; else if (g_str_equal (value, "src")) state->comp_op = CAIRO_OPERATOR_SOURCE; else if (g_str_equal (value, "dst")) state->comp_op = CAIRO_OPERATOR_DEST; else if (g_str_equal (value, "src-over")) state->comp_op = CAIRO_OPERATOR_OVER; else if (g_str_equal (value, "dst-over")) state->comp_op = CAIRO_OPERATOR_DEST_OVER; else if (g_str_equal (value, "src-in")) state->comp_op = CAIRO_OPERATOR_IN; else if (g_str_equal (value, "dst-in")) state->comp_op = CAIRO_OPERATOR_DEST_IN; else if (g_str_equal (value, "src-out")) state->comp_op = CAIRO_OPERATOR_OUT; else if (g_str_equal (value, "dst-out")) state->comp_op = CAIRO_OPERATOR_DEST_OUT; else if (g_str_equal (value, "src-atop")) state->comp_op = CAIRO_OPERATOR_ATOP; else if (g_str_equal (value, "dst-atop")) state->comp_op = CAIRO_OPERATOR_DEST_ATOP; else if (g_str_equal (value, "xor")) state->comp_op = CAIRO_OPERATOR_XOR; else if (g_str_equal (value, "plus")) state->comp_op = CAIRO_OPERATOR_ADD; else if (g_str_equal (value, "multiply")) state->comp_op = CAIRO_OPERATOR_MULTIPLY; else if (g_str_equal (value, "screen")) state->comp_op = CAIRO_OPERATOR_SCREEN; else if (g_str_equal (value, "overlay")) state->comp_op = CAIRO_OPERATOR_OVERLAY; else if (g_str_equal (value, "darken")) state->comp_op = CAIRO_OPERATOR_DARKEN; else if (g_str_equal (value, "lighten")) state->comp_op = CAIRO_OPERATOR_LIGHTEN; else if (g_str_equal (value, "color-dodge")) state->comp_op = CAIRO_OPERATOR_COLOR_DODGE; else if (g_str_equal (value, "color-burn")) state->comp_op = CAIRO_OPERATOR_COLOR_BURN; else if (g_str_equal (value, "hard-light")) state->comp_op = CAIRO_OPERATOR_HARD_LIGHT; else if (g_str_equal (value, "soft-light")) state->comp_op = CAIRO_OPERATOR_SOFT_LIGHT; else if (g_str_equal (value, "difference")) state->comp_op = CAIRO_OPERATOR_DIFFERENCE; else if (g_str_equal (value, "exclusion")) state->comp_op = CAIRO_OPERATOR_EXCLUSION; else state->comp_op = CAIRO_OPERATOR_OVER; } else if (g_str_equal (name, "display")) { state->has_visible = TRUE; if (g_str_equal (value, "none")) state->visible = FALSE; else if (!g_str_equal (value, "inherit") != 0) state->visible = TRUE; else state->has_visible = FALSE; } else if (g_str_equal (name, "xml:space")) { state->has_space_preserve = TRUE; if (g_str_equal (value, "default")) state->space_preserve = FALSE; else if (!g_str_equal (value, "preserve") == 0) state->space_preserve = TRUE; else state->space_preserve = FALSE; } else if (g_str_equal (name, "visibility")) { state->has_visible = TRUE; if (g_str_equal (value, "visible")) state->visible = TRUE; else if (!g_str_equal (value, "inherit") != 0) state->visible = FALSE; /* collapse or hidden */ else state->has_visible = FALSE; } else if (g_str_equal (name, "fill")) { RsvgPaintServer *fill = state->fill; state->fill = rsvg_paint_server_parse (&state->has_fill_server, value); rsvg_paint_server_unref (fill); } else if (g_str_equal (name, "fill-opacity")) { state->fill_opacity = rsvg_css_parse_opacity (value); state->has_fill_opacity = TRUE; } else if (g_str_equal (name, "fill-rule")) { state->has_fill_rule = TRUE; if (g_str_equal (value, "nonzero")) state->fill_rule = CAIRO_FILL_RULE_WINDING; else if (g_str_equal (value, "evenodd")) state->fill_rule = CAIRO_FILL_RULE_EVEN_ODD; else state->has_fill_rule = FALSE; } else if (g_str_equal (name, "clip-rule")) { state->has_clip_rule = TRUE; if (g_str_equal (value, "nonzero")) state->clip_rule = CAIRO_FILL_RULE_WINDING; else if (g_str_equal (value, "evenodd")) state->clip_rule = CAIRO_FILL_RULE_EVEN_ODD; else state->has_clip_rule = FALSE; } else if (g_str_equal (name, "stroke")) { RsvgPaintServer *stroke = state->stroke; state->stroke = rsvg_paint_server_parse (&state->has_stroke_server, value); rsvg_paint_server_unref (stroke); } else if (g_str_equal (name, "stroke-width")) { state->stroke_width = _rsvg_css_parse_length (value); state->has_stroke_width = TRUE; } else if (g_str_equal (name, "stroke-linecap")) { state->has_cap = TRUE; if (g_str_equal (value, "butt")) state->cap = CAIRO_LINE_CAP_BUTT; else if (g_str_equal (value, "round")) state->cap = CAIRO_LINE_CAP_ROUND; else if (g_str_equal (value, "square")) state->cap = CAIRO_LINE_CAP_SQUARE; else g_warning (_("unknown line cap style %s\n"), value); } else if (g_str_equal (name, "stroke-opacity")) { state->stroke_opacity = rsvg_css_parse_opacity (value); state->has_stroke_opacity = TRUE; } else if (g_str_equal (name, "stroke-linejoin")) { state->has_join = TRUE; if (g_str_equal (value, "miter")) state->join = CAIRO_LINE_JOIN_MITER; else if (g_str_equal (value, "round")) state->join = CAIRO_LINE_JOIN_ROUND; else if (g_str_equal (value, "bevel")) state->join = CAIRO_LINE_JOIN_BEVEL; else g_warning (_("unknown line join style %s\n"), value); } else if (g_str_equal (name, "font-size")) { state->font_size = _rsvg_css_parse_length (value); state->has_font_size = TRUE; } else if (g_str_equal (name, "font-family")) { char *save = g_strdup (rsvg_css_parse_font_family (value, &state->has_font_family)); g_free (state->font_family); state->font_family = save; } else if (g_str_equal (name, "xml:lang")) { char *save = g_strdup (value); g_free (state->lang); state->lang = save; state->has_lang = TRUE; } else if (g_str_equal (name, "font-style")) { state->font_style = rsvg_css_parse_font_style (value, &state->has_font_style); } else if (g_str_equal (name, "font-variant")) { state->font_variant = rsvg_css_parse_font_variant (value, &state->has_font_variant); } else if (g_str_equal (name, "font-weight")) { state->font_weight = rsvg_css_parse_font_weight (value, &state->has_font_weight); } else if (g_str_equal (name, "font-stretch")) { state->font_stretch = rsvg_css_parse_font_stretch (value, &state->has_font_stretch); } else if (g_str_equal (name, "text-decoration")) { if (g_str_equal (value, "inherit")) { state->has_font_decor = FALSE; state->font_decor = TEXT_NORMAL; } else { if (strstr (value, "underline")) state->font_decor |= TEXT_UNDERLINE; if (strstr (value, "overline")) state->font_decor |= TEXT_OVERLINE; if (strstr (value, "strike") || strstr (value, "line-through")) /* strike though or line-through */ state->font_decor |= TEXT_STRIKE; state->has_font_decor = TRUE; } } else if (g_str_equal (name, "direction")) { state->has_text_dir = TRUE; if (g_str_equal (value, "inherit")) { state->text_dir = PANGO_DIRECTION_LTR; state->has_text_dir = FALSE; } else if (g_str_equal (value, "rtl")) state->text_dir = PANGO_DIRECTION_RTL; else /* ltr */ state->text_dir = PANGO_DIRECTION_LTR; } else if (g_str_equal (name, "unicode-bidi")) { state->has_unicode_bidi = TRUE; if (g_str_equal (value, "inherit")) { state->unicode_bidi = UNICODE_BIDI_NORMAL; state->has_unicode_bidi = FALSE; } else if (g_str_equal (value, "embed")) state->unicode_bidi = UNICODE_BIDI_EMBED; else if (g_str_equal (value, "bidi-override")) state->unicode_bidi = UNICODE_BIDI_OVERRIDE; else /* normal */ state->unicode_bidi = UNICODE_BIDI_NORMAL; } else if (g_str_equal (name, "writing-mode")) { /* TODO: these aren't quite right... */ state->has_text_dir = TRUE; state->has_text_gravity = TRUE; if (g_str_equal (value, "inherit")) { state->text_dir = PANGO_DIRECTION_LTR; state->has_text_dir = FALSE; state->text_gravity = PANGO_GRAVITY_SOUTH; state->has_text_gravity = FALSE; } else if (g_str_equal (value, "lr-tb") || g_str_equal (value, "lr")) { state->text_dir = PANGO_DIRECTION_LTR; state->text_gravity = PANGO_GRAVITY_SOUTH; } else if (g_str_equal (value, "rl-tb") || g_str_equal (value, "rl")) { state->text_dir = PANGO_DIRECTION_RTL; state->text_gravity = PANGO_GRAVITY_SOUTH; } else if (g_str_equal (value, "tb-rl") || g_str_equal (value, "tb")) { state->text_dir = PANGO_DIRECTION_LTR; state->text_gravity = PANGO_GRAVITY_EAST; } } else if (g_str_equal (name, "text-anchor")) { state->has_text_anchor = TRUE; if (g_str_equal (value, "inherit")) { state->text_anchor = TEXT_ANCHOR_START; state->has_text_anchor = FALSE; } else { if (strstr (value, "start")) state->text_anchor = TEXT_ANCHOR_START; else if (strstr (value, "middle")) state->text_anchor = TEXT_ANCHOR_MIDDLE; else if (strstr (value, "end")) state->text_anchor = TEXT_ANCHOR_END; } } else if (g_str_equal (name, "letter-spacing")) { state->has_letter_spacing = TRUE; state->letter_spacing = _rsvg_css_parse_length (value); } else if (g_str_equal (name, "stop-color")) { if (!g_str_equal (value, "inherit")) { state->stop_color = rsvg_css_parse_color (value, &state->has_stop_color); } } else if (g_str_equal (name, "stop-opacity")) { if (!g_str_equal (value, "inherit")) { state->has_stop_opacity = TRUE; state->stop_opacity = rsvg_css_parse_opacity (value); } } else if (g_str_equal (name, "marker-start")) { g_free (state->startMarker); state->startMarker = rsvg_get_url_string (value); state->has_startMarker = TRUE; } else if (g_str_equal (name, "marker-mid")) { g_free (state->middleMarker); state->middleMarker = rsvg_get_url_string (value); state->has_middleMarker = TRUE; } else if (g_str_equal (name, "marker-end")) { g_free (state->endMarker); state->endMarker = rsvg_get_url_string (value); state->has_endMarker = TRUE; } else if (g_str_equal (name, "stroke-miterlimit")) { state->has_miter_limit = TRUE; state->miter_limit = g_ascii_strtod (value, NULL); } else if (g_str_equal (name, "stroke-dashoffset")) { state->has_dashoffset = TRUE; state->dash.offset = _rsvg_css_parse_length (value); if (state->dash.offset.length < 0.) state->dash.offset.length = 0.; } else if (g_str_equal (name, "shape-rendering")) { state->has_shape_rendering_type = TRUE; if (g_str_equal (value, "auto") || g_str_equal (value, "default")) state->shape_rendering_type = SHAPE_RENDERING_AUTO; else if (g_str_equal (value, "optimizeSpeed")) state->shape_rendering_type = SHAPE_RENDERING_OPTIMIZE_SPEED; else if (g_str_equal (value, "crispEdges")) state->shape_rendering_type = SHAPE_RENDERING_CRISP_EDGES; else if (g_str_equal (value, "geometricPrecision")) state->shape_rendering_type = SHAPE_RENDERING_GEOMETRIC_PRECISION; } else if (g_str_equal (name, "text-rendering")) { state->has_text_rendering_type = TRUE; if (g_str_equal (value, "auto") || g_str_equal (value, "default")) state->text_rendering_type = TEXT_RENDERING_AUTO; else if (g_str_equal (value, "optimizeSpeed")) state->text_rendering_type = TEXT_RENDERING_OPTIMIZE_SPEED; else if (g_str_equal (value, "optimizeLegibility")) state->text_rendering_type = TEXT_RENDERING_OPTIMIZE_LEGIBILITY; else if (g_str_equal (value, "geometricPrecision")) state->text_rendering_type = TEXT_RENDERING_GEOMETRIC_PRECISION; } else if (g_str_equal (name, "stroke-dasharray")) { state->has_dash = TRUE; if (g_str_equal (value, "none")) { if (state->dash.n_dash != 0) { /* free any cloned dash data */ g_free (state->dash.dash); state->dash.dash = NULL; state->dash.n_dash = 0; } } else { gchar **dashes = g_strsplit (value, ",", -1); if (NULL != dashes) { gint n_dashes, i; gboolean is_even = FALSE; gdouble total = 0; /* count the #dashes */ for (n_dashes = 0; dashes[n_dashes] != NULL; n_dashes++); is_even = (n_dashes % 2 == 0); state->dash.n_dash = (is_even ? n_dashes : n_dashes * 2); state->dash.dash = g_new (double, state->dash.n_dash); /* TODO: handle negative value == error case */ /* the even and base case */ for (i = 0; i < n_dashes; i++) { state->dash.dash[i] = g_ascii_strtod (dashes[i], NULL); total += state->dash.dash[i]; } /* if an odd number of dashes is found, it gets repeated */ if (!is_even) for (; i < state->dash.n_dash; i++) state->dash.dash[i] = state->dash.dash[i - n_dashes]; g_strfreev (dashes); /* If the dashes add up to 0, then it should be ignored */ if (total == 0) { g_free (state->dash.dash); state->dash.dash = NULL; state->dash.n_dash = 0; } } } } } static void rsvg_lookup_parse_style_pair (RsvgHandle * ctx, RsvgState * state, const char *key, RsvgPropertyBag * atts) { const char *value; if ((value = rsvg_property_bag_lookup (atts, key)) != NULL) rsvg_parse_style_pair (ctx, state, key, value, FALSE); } /* take a pair of the form (fill="#ff00ff") and parse it as a style */ void rsvg_parse_style_pairs (RsvgHandle * ctx, RsvgState * state, RsvgPropertyBag * atts) { rsvg_lookup_parse_style_pair (ctx, state, "baseline-shift", atts); rsvg_lookup_parse_style_pair (ctx, state, "clip-path", atts); rsvg_lookup_parse_style_pair (ctx, state, "clip-rule", atts); rsvg_lookup_parse_style_pair (ctx, state, "color", atts); rsvg_lookup_parse_style_pair (ctx, state, "direction", atts); rsvg_lookup_parse_style_pair (ctx, state, "display", atts); rsvg_lookup_parse_style_pair (ctx, state, "enable-background", atts); rsvg_lookup_parse_style_pair (ctx, state, "comp-op", atts); rsvg_lookup_parse_style_pair (ctx, state, "fill", atts); rsvg_lookup_parse_style_pair (ctx, state, "fill-opacity", atts); rsvg_lookup_parse_style_pair (ctx, state, "fill-rule", atts); rsvg_lookup_parse_style_pair (ctx, state, "filter", atts); rsvg_lookup_parse_style_pair (ctx, state, "flood-color", atts); rsvg_lookup_parse_style_pair (ctx, state, "flood-opacity", atts); rsvg_lookup_parse_style_pair (ctx, state, "font-family", atts); rsvg_lookup_parse_style_pair (ctx, state, "font-size", atts); rsvg_lookup_parse_style_pair (ctx, state, "font-stretch", atts); rsvg_lookup_parse_style_pair (ctx, state, "font-style", atts); rsvg_lookup_parse_style_pair (ctx, state, "font-variant", atts); rsvg_lookup_parse_style_pair (ctx, state, "font-weight", atts); rsvg_lookup_parse_style_pair (ctx, state, "marker-end", atts); rsvg_lookup_parse_style_pair (ctx, state, "mask", atts); rsvg_lookup_parse_style_pair (ctx, state, "marker-mid", atts); rsvg_lookup_parse_style_pair (ctx, state, "marker-start", atts); rsvg_lookup_parse_style_pair (ctx, state, "opacity", atts); rsvg_lookup_parse_style_pair (ctx, state, "overflow", atts); rsvg_lookup_parse_style_pair (ctx, state, "shape-rendering", atts); rsvg_lookup_parse_style_pair (ctx, state, "stop-color", atts); rsvg_lookup_parse_style_pair (ctx, state, "stop-opacity", atts); rsvg_lookup_parse_style_pair (ctx, state, "stroke", atts); rsvg_lookup_parse_style_pair (ctx, state, "stroke-dasharray", atts); rsvg_lookup_parse_style_pair (ctx, state, "stroke-dashoffset", atts); rsvg_lookup_parse_style_pair (ctx, state, "stroke-linecap", atts); rsvg_lookup_parse_style_pair (ctx, state, "stroke-linejoin", atts); rsvg_lookup_parse_style_pair (ctx, state, "stroke-miterlimit", atts); rsvg_lookup_parse_style_pair (ctx, state, "stroke-opacity", atts); rsvg_lookup_parse_style_pair (ctx, state, "stroke-width", atts); rsvg_lookup_parse_style_pair (ctx, state, "text-anchor", atts); rsvg_lookup_parse_style_pair (ctx, state, "text-decoration", atts); rsvg_lookup_parse_style_pair (ctx, state, "unicode-bidi", atts); rsvg_lookup_parse_style_pair (ctx, state, "letter-spacing", atts); rsvg_lookup_parse_style_pair (ctx, state, "visibility", atts); rsvg_lookup_parse_style_pair (ctx, state, "writing-mode", atts); rsvg_lookup_parse_style_pair (ctx, state, "xml:lang", atts); rsvg_lookup_parse_style_pair (ctx, state, "xml:space", atts); { /* TODO: this conditional behavior isn't quite correct, and i'm not sure it should reside here */ gboolean cond_true, has_cond; cond_true = rsvg_eval_switch_attributes (atts, &has_cond); if (has_cond) { state->cond_true = cond_true; state->has_cond = TRUE; } } } static gboolean parse_style_value (const gchar *string, gchar **value, gboolean *important) { gchar **strings; strings = g_strsplit (string, "!", 2); if (strings == NULL || strings[0] == NULL) { g_strfreev (strings); return FALSE; } if (strings[1] != NULL && strings[2] == NULL && g_str_equal (g_strstrip (strings[1]), "important")) { *important = TRUE; } else { *important = FALSE; } *value = g_strdup (g_strstrip (strings[0])); g_strfreev (strings); return TRUE; } /* Split a CSS2 style into individual style arguments, setting attributes in the SVG context. It's known that this is _way_ out of spec. A more complete CSS2 implementation will happen later. */ void rsvg_parse_style (RsvgHandle * ctx, RsvgState * state, const char *str) { gchar **styles; guint i; styles = g_strsplit (str, ";", -1); for (i = 0; i < g_strv_length (styles); i++) { gchar **values; values = g_strsplit (styles[i], ":", 2); if (!values) continue; if (g_strv_length (values) == 2) { gboolean important; gchar *style_value = NULL; gchar *first_value = values[0]; gchar *second_value = values[1]; gchar **split_list; /* Just remove single quotes in a trivial way. No handling for any * special character inside the quotes is done. This relates * especially to font-family names but cases with special characters * are rare. * * We need a real CSS parser, sigh. */ split_list = g_strsplit (second_value, "'", -1); second_value = g_strjoinv(NULL, split_list); g_strfreev(split_list); if (parse_style_value (second_value, &style_value, &important)) rsvg_parse_style_pair (ctx, state, g_strstrip (first_value), style_value, important); g_free (style_value); g_free (second_value); } g_strfreev (values); } g_strfreev (styles); } static void rsvg_css_define_style (RsvgHandle * ctx, const gchar * selector, const gchar * style_name, const gchar * style_value, gboolean important) { GHashTable *styles; gboolean need_insert = FALSE; /* push name/style pair into HT */ styles = g_hash_table_lookup (ctx->priv->css_props, selector); if (styles == NULL) { styles = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) style_value_data_free); g_hash_table_insert (ctx->priv->css_props, (gpointer) g_strdup (selector), styles); need_insert = TRUE; } else { StyleValueData *current_value; current_value = g_hash_table_lookup (styles, style_name); if (current_value == NULL || !current_value->important) need_insert = TRUE; } if (need_insert) { g_hash_table_insert (styles, (gpointer) g_strdup (style_name), (gpointer) style_value_data_new (style_value, important)); } } typedef struct _CSSUserData { RsvgHandle *ctx; CRSelector *selector; } CSSUserData; static void css_user_data_init (CSSUserData * user_data, RsvgHandle * ctx) { user_data->ctx = ctx; user_data->selector = NULL; } static void ccss_start_selector (CRDocHandler * a_handler, CRSelector * a_selector_list) { CSSUserData *user_data; g_return_if_fail (a_handler); user_data = (CSSUserData *) a_handler->app_data; cr_selector_ref (a_selector_list); user_data->selector = a_selector_list; } static void ccss_end_selector (CRDocHandler * a_handler, CRSelector * a_selector_list) { CSSUserData *user_data; g_return_if_fail (a_handler); user_data = (CSSUserData *) a_handler->app_data; cr_selector_unref (user_data->selector); user_data->selector = NULL; } static void ccss_property (CRDocHandler * a_handler, CRString * a_name, CRTerm * a_expr, gboolean a_important) { CSSUserData *user_data; gchar *name = NULL; size_t len = 0; g_return_if_fail (a_handler); user_data = (CSSUserData *) a_handler->app_data; if (a_name && a_expr && user_data->selector) { CRSelector *cur; for (cur = user_data->selector; cur; cur = cur->next) { if (cur->simple_sel) { gchar *selector = (gchar *) cr_simple_sel_to_string (cur->simple_sel); if (selector) { gchar *style_name, *style_value; name = (gchar *) cr_string_peek_raw_str (a_name); len = cr_string_peek_raw_str_len (a_name); style_name = g_strndup (name, len); style_value = (gchar *)cr_term_to_string (a_expr); rsvg_css_define_style (user_data->ctx, selector, style_name, style_value, a_important); g_free (selector); g_free (style_name); g_free (style_value); } } } } } static void ccss_error (CRDocHandler * a_handler) { /* yup, like i care about CSS parsing errors ;-) ignore, chug along */ g_warning (_("CSS parsing error\n")); } static void ccss_unrecoverable_error (CRDocHandler * a_handler) { /* yup, like i care about CSS parsing errors ;-) ignore, chug along */ g_warning (_("CSS unrecoverable error\n")); } static void ccss_import_style (CRDocHandler * a_this, GList * a_media_list, CRString * a_uri, CRString * a_uri_default_ns, CRParsingLocation * a_location); static void init_sac_handler (CRDocHandler * a_handler) { a_handler->start_document = NULL; a_handler->end_document = NULL; a_handler->import_style = ccss_import_style; a_handler->namespace_declaration = NULL; a_handler->comment = NULL; a_handler->start_selector = ccss_start_selector; a_handler->end_selector = ccss_end_selector; a_handler->property = ccss_property; a_handler->start_font_face = NULL; a_handler->end_font_face = NULL; a_handler->start_media = NULL; a_handler->end_media = NULL; a_handler->start_page = NULL; a_handler->end_page = NULL; a_handler->ignorable_at_rule = NULL; a_handler->error = ccss_error; a_handler->unrecoverable_error = ccss_unrecoverable_error; } void rsvg_parse_cssbuffer (RsvgHandle * ctx, const char *buff, size_t buflen) { CRParser *parser = NULL; CRDocHandler *css_handler = NULL; CSSUserData user_data; if (buff == NULL || buflen == 0) return; css_handler = cr_doc_handler_new (); init_sac_handler (css_handler); css_user_data_init (&user_data, ctx); css_handler->app_data = &user_data; /* TODO: fix libcroco to take in const strings */ parser = cr_parser_new_from_buf ((guchar *) buff, (gulong) buflen, CR_UTF_8, FALSE); if (parser == NULL) { cr_doc_handler_unref (css_handler); return; } cr_parser_set_sac_handler (parser, css_handler); cr_doc_handler_unref (css_handler); cr_parser_set_use_core_grammar (parser, FALSE); cr_parser_parse (parser); cr_parser_destroy (parser); } static void ccss_import_style (CRDocHandler * a_this, GList * a_media_list, CRString * a_uri, CRString * a_uri_default_ns, CRParsingLocation * a_location) { CSSUserData *user_data = (CSSUserData *) a_this->app_data; char *stylesheet_data; gsize stylesheet_data_len; char *mime_type = NULL; if (a_uri == NULL) return; stylesheet_data = _rsvg_handle_acquire_data (user_data->ctx, cr_string_peek_raw_str (a_uri), &mime_type, &stylesheet_data_len, NULL); if (stylesheet_data == NULL || mime_type == NULL || strcmp (mime_type, "text/css") != 0) { g_free (stylesheet_data); g_free (mime_type); return; } rsvg_parse_cssbuffer (user_data->ctx, stylesheet_data, stylesheet_data_len); g_free (stylesheet_data); g_free (mime_type); } /* Parse an SVG transform string into an affine matrix. Reference: SVG working draft dated 1999-07-06, section 8.5. Return TRUE on success. */ gboolean rsvg_parse_transform (cairo_matrix_t *dst, const char *src) { int idx; char keyword[32]; double args[6]; int n_args; guint key_len; cairo_matrix_t affine; cairo_matrix_init_identity (dst); idx = 0; while (src[idx]) { /* skip initial whitespace */ while (g_ascii_isspace (src[idx])) idx++; if (src[idx] == '\0') break; /* parse keyword */ for (key_len = 0; key_len < sizeof (keyword); key_len++) { char c; c = src[idx]; if (g_ascii_isalpha (c) || c == '-') keyword[key_len] = src[idx++]; else break; } if (key_len >= sizeof (keyword)) return FALSE; keyword[key_len] = '\0'; /* skip whitespace */ while (g_ascii_isspace (src[idx])) idx++; if (src[idx] != '(') return FALSE; idx++; for (n_args = 0;; n_args++) { char c; char *end_ptr; /* skip whitespace */ while (g_ascii_isspace (src[idx])) idx++; c = src[idx]; if (g_ascii_isdigit (c) || c == '+' || c == '-' || c == '.') { if (n_args == sizeof (args) / sizeof (args[0])) return FALSE; /* too many args */ args[n_args] = g_ascii_strtod (src + idx, &end_ptr); idx = end_ptr - src; while (g_ascii_isspace (src[idx])) idx++; /* skip optional comma */ if (src[idx] == ',') idx++; } else if (c == ')') break; else return FALSE; } idx++; /* ok, have parsed keyword and args, now modify the transform */ if (!strcmp (keyword, "matrix")) { if (n_args != 6) return FALSE; cairo_matrix_init (&affine, args[0], args[1], args[2], args[3], args[4], args[5]); cairo_matrix_multiply (dst, &affine, dst); } else if (!strcmp (keyword, "translate")) { if (n_args == 1) args[1] = 0; else if (n_args != 2) return FALSE; cairo_matrix_init_translate (&affine, args[0], args[1]); cairo_matrix_multiply (dst, &affine, dst); } else if (!strcmp (keyword, "scale")) { if (n_args == 1) args[1] = args[0]; else if (n_args != 2) return FALSE; cairo_matrix_init_scale (&affine, args[0], args[1]); cairo_matrix_multiply (dst, &affine, dst); } else if (!strcmp (keyword, "rotate")) { if (n_args == 1) { cairo_matrix_init_rotate (&affine, args[0] * M_PI / 180.); cairo_matrix_multiply (dst, &affine, dst); } else if (n_args == 3) { cairo_matrix_init_translate (&affine, args[1], args[2]); cairo_matrix_multiply (dst, &affine, dst); cairo_matrix_init_rotate (&affine, args[0] * M_PI / 180.); cairo_matrix_multiply (dst, &affine, dst); cairo_matrix_init_translate (&affine, -args[1], -args[2]); cairo_matrix_multiply (dst, &affine, dst); } else return FALSE; } else if (!strcmp (keyword, "skewX")) { if (n_args != 1) return FALSE; _rsvg_cairo_matrix_init_shear (&affine, args[0]); cairo_matrix_multiply (dst, &affine, dst); } else if (!strcmp (keyword, "skewY")) { if (n_args != 1) return FALSE; _rsvg_cairo_matrix_init_shear (&affine, args[0]); /* transpose the affine, given that we know [1] is zero */ affine.yx = affine.xy; affine.xy = 0.; cairo_matrix_multiply (dst, &affine, dst); } else return FALSE; /* unknown keyword */ } return TRUE; } /** * rsvg_parse_transform_attr: * @ctx: Rsvg context. * @state: State in which to apply the transform. * @str: String containing transform. * * Parses the transform attribute in @str and applies it to @state. **/ static void rsvg_parse_transform_attr (RsvgHandle * ctx, RsvgState * state, const char *str) { cairo_matrix_t affine; if (rsvg_parse_transform (&affine, str)) { cairo_matrix_multiply (&state->personal_affine, &affine, &state->personal_affine); cairo_matrix_multiply (&state->affine, &affine, &state->affine); } } typedef struct _StylesData { RsvgHandle *ctx; RsvgState *state; } StylesData; static void apply_style (const gchar *key, StyleValueData *value, gpointer user_data) { StylesData *data = (StylesData *) user_data; rsvg_parse_style_pair (data->ctx, data->state, key, value->value, value->important); } static gboolean rsvg_lookup_apply_css_style (RsvgHandle * ctx, const char *target, RsvgState * state) { GHashTable *styles; styles = g_hash_table_lookup (ctx->priv->css_props, target); if (styles != NULL) { StylesData *data = g_new (StylesData, 1); data->ctx = ctx; data->state = state; g_hash_table_foreach (styles, (GHFunc) apply_style, data); g_free (data); return TRUE; } return FALSE; } /** * rsvg_parse_style_attrs: * @ctx: Rsvg context. * @state: Rsvg state * @tag: (nullable): The SVG tag we're processing (eg: circle, ellipse), optionally %NULL * @klazz: (nullable): The space delimited class list, optionally %NULL * @atts: Attributes in SAX style. * * Parses style and transform attributes and modifies state at top of * stack. **/ void rsvg_parse_style_attrs (RsvgHandle * ctx, RsvgState * state, const char *tag, const char *klazz, const char *id, RsvgPropertyBag * atts) { int i = 0, j = 0; char *target = NULL; gboolean found = FALSE; GString *klazz_list = NULL; if (rsvg_property_bag_size (atts) > 0) rsvg_parse_style_pairs (ctx, state, atts); /* Try to properly support all of the following, including inheritance: * * * #id * tag * tag#id * tag.class * tag.class#id * * This is basically a semi-compliant CSS2 selection engine */ /* * */ rsvg_lookup_apply_css_style (ctx, "*", state); /* tag */ if (tag != NULL) { rsvg_lookup_apply_css_style (ctx, tag, state); } if (klazz != NULL) { i = strlen (klazz); while (j < i) { found = FALSE; klazz_list = g_string_new ("."); while (j < i && g_ascii_isspace (klazz[j])) j++; while (j < i && !g_ascii_isspace (klazz[j])) g_string_append_c (klazz_list, klazz[j++]); /* tag.class#id */ if (tag != NULL && klazz_list->len != 1 && id != NULL) { target = g_strdup_printf ("%s%s#%s", tag, klazz_list->str, id); found = found || rsvg_lookup_apply_css_style (ctx, target, state); g_free (target); } /* class#id */ if (klazz_list->len != 1 && id != NULL) { target = g_strdup_printf ("%s#%s", klazz_list->str, id); found = found || rsvg_lookup_apply_css_style (ctx, target, state); g_free (target); } /* tag.class */ if (tag != NULL && klazz_list->len != 1) { target = g_strdup_printf ("%s%s", tag, klazz_list->str); found = found || rsvg_lookup_apply_css_style (ctx, target, state); g_free (target); } /* didn't find anything more specific, just apply the class style */ if (!found) { found = found || rsvg_lookup_apply_css_style (ctx, klazz_list->str, state); } g_string_free (klazz_list, TRUE); } } /* #id */ if (id != NULL) { target = g_strdup_printf ("#%s", id); rsvg_lookup_apply_css_style (ctx, target, state); g_free (target); } /* tag#id */ if (tag != NULL && id != NULL) { target = g_strdup_printf ("%s#%s", tag, id); rsvg_lookup_apply_css_style (ctx, target, state); g_free (target); } if (rsvg_property_bag_size (atts) > 0) { const char *value; if ((value = rsvg_property_bag_lookup (atts, "style")) != NULL) rsvg_parse_style (ctx, state, value); if ((value = rsvg_property_bag_lookup (atts, "transform")) != NULL) rsvg_parse_transform_attr (ctx, state, value); } } RsvgState * rsvg_current_state (RsvgDrawingCtx * ctx) { return ctx->state; } RsvgState * rsvg_state_parent (RsvgState * state) { return state->parent; } void rsvg_state_free_all (RsvgState * state) { while (state) { RsvgState *parent = state->parent; rsvg_state_finalize (state); g_slice_free (RsvgState, state); state = parent; } } /** * rsvg_property_bag_new: * @atts: (array zero-terminated=1): list of alternating attributes * and values * * The property bag will NOT copy the attributes and values. If you need * to store them for later, use rsvg_property_bag_dup(). * * Returns: (transfer full): a new property bag */ RsvgPropertyBag * rsvg_property_bag_new (const char **atts) { RsvgPropertyBag *bag; int i; bag = g_hash_table_new (g_str_hash, g_str_equal); if (atts != NULL) { for (i = 0; atts[i] != NULL; i += 2) g_hash_table_insert (bag, (gpointer) atts[i], (gpointer) atts[i + 1]); } return bag; } /** * rsvg_property_bag_dup: * @bag: property bag to duplicate * * Returns a copy of @bag that owns the attributes and values. * * Returns: (transfer full): a new property bag */ RsvgPropertyBag * rsvg_property_bag_dup (RsvgPropertyBag * bag) { RsvgPropertyBag *dup; GHashTableIter iter; gpointer key, value; dup = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); g_hash_table_iter_init (&iter, bag); while (g_hash_table_iter_next (&iter, &key, &value)) g_hash_table_insert (dup, (gpointer) g_strdup ((char *) key), (gpointer) g_strdup ((char *) value)); return dup; } void rsvg_property_bag_free (RsvgPropertyBag * bag) { g_hash_table_unref (bag); } const char * rsvg_property_bag_lookup (RsvgPropertyBag * bag, const char *key) { return (const char *) g_hash_table_lookup (bag, (gconstpointer) key); } guint rsvg_property_bag_size (RsvgPropertyBag * bag) { return g_hash_table_size (bag); } void rsvg_property_bag_enumerate (RsvgPropertyBag * bag, RsvgPropertyBagEnumFunc func, gpointer user_data) { g_hash_table_foreach (bag, (GHFunc) func, user_data); } void rsvg_state_push (RsvgDrawingCtx * ctx) { RsvgState *data; RsvgState *baseon; baseon = ctx->state; data = g_slice_new (RsvgState); rsvg_state_init (data); if (baseon) { rsvg_state_reinherit (data, baseon); data->affine = baseon->affine; data->parent = baseon; } ctx->state = data; } void rsvg_state_pop (RsvgDrawingCtx * ctx) { RsvgState *dead_state = ctx->state; ctx->state = dead_state->parent; rsvg_state_finalize (dead_state); g_slice_free (RsvgState, dead_state); } /* A function for modifying the top of the state stack depending on a flag given. If that flag is 0, style and transform will inherit normally. If that flag is 1, style will inherit normally with the exception that any value explicity set on the second last level will have a higher precedence than values set on the last level. If the flag equals two then the style will be overridden totally however the transform will be left as is. This is because of patterns which are not based on the context of their use and are rather based wholly on their own loading context. Other things may want to have this totally disabled, and a value of three will achieve this. */ void rsvg_state_reinherit_top (RsvgDrawingCtx * ctx, RsvgState * state, int dominate) { RsvgState *current; if (dominate == 3) return; current = rsvg_current_state (ctx); /*This is a special domination mode for patterns, the transform is simply left as is, wheras the style is totally overridden */ if (dominate == 2) { rsvg_state_override (current, state); } else { RsvgState *parent= rsvg_state_parent (current); rsvg_state_clone (current, state); if (parent) { if (dominate) rsvg_state_dominate (current, parent); else rsvg_state_reinherit (current, parent); cairo_matrix_multiply (¤t->affine, ¤t->affine, &parent->affine); } } } void rsvg_state_reconstruct (RsvgState * state, RsvgNode * current) { if (current == NULL) return; rsvg_state_reconstruct (state, current->parent); rsvg_state_inherit (state, current->state); }