/* vim: set sw=4 sts=4: -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* rsvg-css.c: Parse CSS basic data types. Copyright (C) 2000 Eazel, Inc. 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. Authors: Dom Lachowicz Raph Levien */ #include "config.h" #include "rsvg-css.h" #include "rsvg-private.h" #include "rsvg-styles.h" #include #include #include #include #include #include #include #define POINTS_PER_INCH (72.0) #define CM_PER_INCH (2.54) #define MM_PER_INCH (25.4) #define PICA_PER_INCH (6.0) #define SETINHERIT() G_STMT_START {if (inherit != NULL) *inherit = TRUE;} G_STMT_END #define UNSETINHERIT() G_STMT_START {if (inherit != NULL) *inherit = FALSE;} G_STMT_END /** * rsvg_css_parse_vbox * @vbox: The CSS viewBox * @x : The X output * @y: The Y output * @w: The Width output * @h: The Height output * * Returns: */ RsvgViewBox rsvg_css_parse_vbox (const char *vbox) { RsvgViewBox vb; gdouble *list; guint list_len; vb.active = FALSE; vb.x = vb.y = 0; vb.w = vb.h = 0; list = rsvg_css_parse_number_list (vbox, &list_len); if (!(list && list_len)) return vb; else if (list_len != 4) { g_free (list); return vb; } else { vb.x = list[0]; vb.y = list[1]; vb.w = list[2]; vb.h = list[3]; vb.active = TRUE; g_free (list); return vb; } } typedef enum _RelativeSize { RELATIVE_SIZE_NORMAL, RELATIVE_SIZE_SMALLER, RELATIVE_SIZE_LARGER } RelativeSize; static double rsvg_css_parse_raw_length (const char *str, gint * in, gint * percent, gint * em, gint * ex, RelativeSize * relative_size) { double length = 0.0; char *p = NULL; /* * The supported CSS length unit specifiers are: * em, ex, px, pt, pc, cm, mm, in, and % */ *percent = FALSE; *em = FALSE; *ex = FALSE; *relative_size = RELATIVE_SIZE_NORMAL; length = g_ascii_strtod (str, &p); if ((length == -HUGE_VAL || length == HUGE_VAL) && (ERANGE == errno)) { /* todo: error condition - figure out how to best represent it */ return 0.0; } /* test for either pixels or no unit, which is assumed to be pixels */ if (p && *p && (strcmp (p, "px") != 0)) { if (!strcmp (p, "pt")) { length /= POINTS_PER_INCH; *in = TRUE; } else if (!strcmp (p, "in")) *in = TRUE; else if (!strcmp (p, "cm")) { length /= CM_PER_INCH; *in = TRUE; } else if (!strcmp (p, "mm")) { length /= MM_PER_INCH; *in = TRUE; } else if (!strcmp (p, "pc")) { length /= PICA_PER_INCH; *in = TRUE; } else if (!strcmp (p, "em")) *em = TRUE; else if (!strcmp (p, "ex")) *ex = TRUE; else if (!strcmp (p, "%")) { *percent = TRUE; length *= 0.01; } else { double pow_factor = 0.0; if (!g_ascii_strcasecmp (p, "larger")) { *relative_size = RELATIVE_SIZE_LARGER; return 0.0; } else if (!g_ascii_strcasecmp (p, "smaller")) { *relative_size = RELATIVE_SIZE_SMALLER; return 0.0; } else if (!g_ascii_strcasecmp (p, "xx-small")) { pow_factor = -3.0; } else if (!g_ascii_strcasecmp (p, "x-small")) { pow_factor = -2.0; } else if (!g_ascii_strcasecmp (p, "small")) { pow_factor = -1.0; } else if (!g_ascii_strcasecmp (p, "medium")) { pow_factor = 0.0; } else if (!g_ascii_strcasecmp (p, "large")) { pow_factor = 1.0; } else if (!g_ascii_strcasecmp (p, "x-large")) { pow_factor = 2.0; } else if (!g_ascii_strcasecmp (p, "xx-large")) { pow_factor = 3.0; } else { return 0.0; } length = 12.0 * pow (1.2, pow_factor) / POINTS_PER_INCH; *in = TRUE; } } return length; } RsvgLength _rsvg_css_parse_length (const char *str) { RsvgLength out; gint percent, em, ex, in; RelativeSize relative_size = RELATIVE_SIZE_NORMAL; percent = em = ex = in = FALSE; out.length = rsvg_css_parse_raw_length (str, &in, &percent, &em, &ex, &relative_size); if (percent) out.factor = 'p'; else if (em) out.factor = 'm'; else if (ex) out.factor = 'x'; else if (in) out.factor = 'i'; else if (relative_size == RELATIVE_SIZE_LARGER) out.factor = 'l'; else if (relative_size == RELATIVE_SIZE_SMALLER) out.factor = 's'; else out.factor = '\0'; return out; } double _rsvg_css_normalize_length (const RsvgLength * in, RsvgDrawingCtx * ctx, char dir) { if (in->factor == '\0') return in->length; else if (in->factor == 'p') { if (dir == 'h') return in->length * ctx->vb.w; if (dir == 'v') return in->length * ctx->vb.h; if (dir == 'o') return in->length * rsvg_viewport_percentage (ctx->vb.w, ctx->vb.h); } else if (in->factor == 'm' || in->factor == 'x') { double font = _rsvg_css_hand_normalize_length (&rsvg_state_current (ctx)->font_size, ctx->dpi_y, ctx->vb.h, 1); if (in->factor == 'm') return in->length * font; else return in->length * font / 2.; } else if (in->factor == 'i') { if (dir == 'h') return in->length * ctx->dpi_x; if (dir == 'v') return in->length * ctx->dpi_y; if (dir == 'o') return in->length * rsvg_viewport_percentage (ctx->dpi_x, ctx->dpi_y); } else if (in->factor == 'l') { /* todo: "larger" */ } else if (in->factor == 's') { /* todo: "smaller" */ } return 0; } double _rsvg_css_hand_normalize_length (const RsvgLength * in, gdouble pixels_per_inch, gdouble width_or_height, gdouble font_size) { if (in->factor == '\0') return in->length; else if (in->factor == 'p') return in->length * width_or_height; else if (in->factor == 'm') return in->length * font_size; else if (in->factor == 'x') return in->length * font_size / 2.; else if (in->factor == 'i') return in->length * pixels_per_inch; return 0; } gboolean rsvg_css_param_match (const char *str, const char *param_name) { int i; for (i = 0; str[i] != '\0' && param_name[i] != '\0'; i++) if (param_name[i] != str[i]) return FALSE; return str[i] == ':' && param_name[i] == '\0'; } int rsvg_css_param_arg_offset (const char *str) { int i; int found; found = -1; i = 0; while (str[i] != '\0') { for (; str[i] != '\0' && str[i] != ':'; i++); if (str[i] != '\0') i++; for (; str[i] == ' '; i++); if (str[i] != '\0') found = i; } if (found == -1) found = i; return found; } static gint rsvg_css_clip_rgb_percent (gdouble in_percent) { /* spec says to clip these values */ if (in_percent > 100.) return 255; else if (in_percent <= 0.) return 0; return (gint) floor (255. * in_percent / 100. + 0.5); } static gint rsvg_css_clip_rgb (gint rgb) { /* spec says to clip these values */ if (rgb > 255) return 255; else if (rgb < 0) return 0; return rgb; } typedef struct { const char *const name; guint rgb; } ColorPair; /* compare function callback for bsearch */ static int rsvg_css_color_compare (const void *a, const void *b) { const char *needle = (const char *) a; const ColorPair *haystack = (const ColorPair *) b; return g_ascii_strcasecmp (needle, haystack->name); } /* pack 3 [0,255] ints into one 32 bit one */ #define PACK_RGB(r,g,b) (((r) << 16) | ((g) << 8) | (b)) /** * Parse a CSS2 color specifier, return RGB value */ guint32 rsvg_css_parse_color (const char *str, gboolean * inherit) { gint val = 0; SETINHERIT (); if (str[0] == '#') { int i; for (i = 1; str[i]; i++) { int hexval; if (str[i] >= '0' && str[i] <= '9') hexval = str[i] - '0'; else if (str[i] >= 'A' && str[i] <= 'F') hexval = str[i] - 'A' + 10; else if (str[i] >= 'a' && str[i] <= 'f') hexval = str[i] - 'a' + 10; else break; val = (val << 4) + hexval; } /* handle #rgb case */ if (i == 4) { val = ((val & 0xf00) << 8) | ((val & 0x0f0) << 4) | (val & 0x00f); val |= val << 4; } } /* i want to use g_str_has_prefix but it isn't in my gstrfuncs.h?? */ else if (strstr (str, "rgb") != NULL) { gint r, g, b; r = g = b = 0; if (strstr (str, "%") != 0) { guint i, nb_toks; char **toks; /* assume rgb (9%, 100%, 23%) */ for (i = 0; str[i] != '('; i++); i++; toks = rsvg_css_parse_list (str + i, &nb_toks); if (toks) { if (nb_toks == 3) { r = rsvg_css_clip_rgb_percent (g_ascii_strtod (toks[0], NULL)); g = rsvg_css_clip_rgb_percent (g_ascii_strtod (toks[1], NULL)); b = rsvg_css_clip_rgb_percent (g_ascii_strtod (toks[2], NULL)); } g_strfreev (toks); } } else { /* assume "rgb (r, g, b)" */ if (3 == sscanf (str, " rgb ( %d , %d , %d ) ", &r, &g, &b)) { r = rsvg_css_clip_rgb (r); g = rsvg_css_clip_rgb (g); b = rsvg_css_clip_rgb (b); } else r = g = b = 0; } val = PACK_RGB (r, g, b); } else if (!strcmp (str, "inherit")) UNSETINHERIT (); else { static const ColorPair color_list[] = { {"aliceblue", PACK_RGB (240, 248, 255)}, {"antiquewhite", PACK_RGB (250, 235, 215)}, {"aqua", PACK_RGB (0, 255, 255)}, {"aquamarine", PACK_RGB (127, 255, 212)}, {"azure", PACK_RGB (240, 255, 255)}, {"beige", PACK_RGB (245, 245, 220)}, {"bisque", PACK_RGB (255, 228, 196)}, {"black", PACK_RGB (0, 0, 0)}, {"blanchedalmond", PACK_RGB (255, 235, 205)}, {"blue", PACK_RGB (0, 0, 255)}, {"blueviolet", PACK_RGB (138, 43, 226)}, {"brown", PACK_RGB (165, 42, 42)}, {"burlywood", PACK_RGB (222, 184, 135)}, {"cadetblue", PACK_RGB (95, 158, 160)}, {"chartreuse", PACK_RGB (127, 255, 0)}, {"chocolate", PACK_RGB (210, 105, 30)}, {"coral", PACK_RGB (255, 127, 80)}, {"cornflowerblue", PACK_RGB (100, 149, 237)}, {"cornsilk", PACK_RGB (255, 248, 220)}, {"crimson", PACK_RGB (220, 20, 60)}, {"cyan", PACK_RGB (0, 255, 255)}, {"darkblue", PACK_RGB (0, 0, 139)}, {"darkcyan", PACK_RGB (0, 139, 139)}, {"darkgoldenrod", PACK_RGB (184, 132, 11)}, {"darkgray", PACK_RGB (169, 169, 169)}, {"darkgreen", PACK_RGB (0, 100, 0)}, {"darkgrey", PACK_RGB (169, 169, 169)}, {"darkkhaki", PACK_RGB (189, 183, 107)}, {"darkmagenta", PACK_RGB (139, 0, 139)}, {"darkolivegreen", PACK_RGB (85, 107, 47)}, {"darkorange", PACK_RGB (255, 140, 0)}, {"darkorchid", PACK_RGB (153, 50, 204)}, {"darkred", PACK_RGB (139, 0, 0)}, {"darksalmon", PACK_RGB (233, 150, 122)}, {"darkseagreen", PACK_RGB (143, 188, 143)}, {"darkslateblue", PACK_RGB (72, 61, 139)}, {"darkslategray", PACK_RGB (47, 79, 79)}, {"darkslategrey", PACK_RGB (47, 79, 79)}, {"darkturquoise", PACK_RGB (0, 206, 209)}, {"darkviolet", PACK_RGB (148, 0, 211)}, {"deeppink", PACK_RGB (255, 20, 147)}, {"deepskyblue", PACK_RGB (0, 191, 255)}, {"dimgray", PACK_RGB (105, 105, 105)}, {"dimgrey", PACK_RGB (105, 105, 105)}, {"dodgerblue", PACK_RGB (30, 144, 255)}, {"firebrick", PACK_RGB (178, 34, 34)}, {"floralwhite", PACK_RGB (255, 255, 240)}, {"forestgreen", PACK_RGB (34, 139, 34)}, {"fuchsia", PACK_RGB (255, 0, 255)}, {"gainsboro", PACK_RGB (220, 220, 220)}, {"ghostwhite", PACK_RGB (248, 248, 255)}, {"gold", PACK_RGB (255, 215, 0)}, {"goldenrod", PACK_RGB (218, 165, 32)}, {"gray", PACK_RGB (128, 128, 128)}, {"green", PACK_RGB (0, 128, 0)}, {"greenyellow", PACK_RGB (173, 255, 47)}, {"grey", PACK_RGB (128, 128, 128)}, {"honeydew", PACK_RGB (240, 255, 240)}, {"hotpink", PACK_RGB (255, 105, 180)}, {"indianred", PACK_RGB (205, 92, 92)}, {"indigo", PACK_RGB (75, 0, 130)}, {"ivory", PACK_RGB (255, 255, 240)}, {"khaki", PACK_RGB (240, 230, 140)}, {"lavender", PACK_RGB (230, 230, 250)}, {"lavenderblush", PACK_RGB (255, 240, 245)}, {"lawngreen", PACK_RGB (124, 252, 0)}, {"lemonchiffon", PACK_RGB (255, 250, 205)}, {"lightblue", PACK_RGB (173, 216, 230)}, {"lightcoral", PACK_RGB (240, 128, 128)}, {"lightcyan", PACK_RGB (224, 255, 255)}, {"lightgoldenrodyellow", PACK_RGB (250, 250, 210)}, {"lightgray", PACK_RGB (211, 211, 211)}, {"lightgreen", PACK_RGB (144, 238, 144)}, {"lightgrey", PACK_RGB (211, 211, 211)}, {"lightpink", PACK_RGB (255, 182, 193)}, {"lightsalmon", PACK_RGB (255, 160, 122)}, {"lightseagreen", PACK_RGB (32, 178, 170)}, {"lightskyblue", PACK_RGB (135, 206, 250)}, {"lightslategray", PACK_RGB (119, 136, 153)}, {"lightslategrey", PACK_RGB (119, 136, 153)}, {"lightsteelblue", PACK_RGB (176, 196, 222)}, {"lightyellow", PACK_RGB (255, 255, 224)}, {"lime", PACK_RGB (0, 255, 0)}, {"limegreen", PACK_RGB (50, 205, 50)}, {"linen", PACK_RGB (250, 240, 230)}, {"magenta", PACK_RGB (255, 0, 255)}, {"maroon", PACK_RGB (128, 0, 0)}, {"mediumaquamarine", PACK_RGB (102, 205, 170)}, {"mediumblue", PACK_RGB (0, 0, 205)}, {"mediumorchid", PACK_RGB (186, 85, 211)}, {"mediumpurple", PACK_RGB (147, 112, 219)}, {"mediumseagreen", PACK_RGB (60, 179, 113)}, {"mediumslateblue", PACK_RGB (123, 104, 238)}, {"mediumspringgreen", PACK_RGB (0, 250, 154)}, {"mediumturquoise", PACK_RGB (72, 209, 204)}, {"mediumvioletred", PACK_RGB (199, 21, 133)}, {"midnightblue", PACK_RGB (25, 25, 112)}, {"mintcream", PACK_RGB (245, 255, 250)}, {"mistyrose", PACK_RGB (255, 228, 225)}, {"moccasin", PACK_RGB (255, 228, 181)}, {"navajowhite", PACK_RGB (255, 222, 173)}, {"navy", PACK_RGB (0, 0, 128)}, {"oldlace", PACK_RGB (253, 245, 230)}, {"olive", PACK_RGB (128, 128, 0)}, {"olivedrab", PACK_RGB (107, 142, 35)}, {"orange", PACK_RGB (255, 165, 0)}, {"orangered", PACK_RGB (255, 69, 0)}, {"orchid", PACK_RGB (218, 112, 214)}, {"palegoldenrod", PACK_RGB (238, 232, 170)}, {"palegreen", PACK_RGB (152, 251, 152)}, {"paleturquoise", PACK_RGB (175, 238, 238)}, {"palevioletred", PACK_RGB (219, 112, 147)}, {"papayawhip", PACK_RGB (255, 239, 213)}, {"peachpuff", PACK_RGB (255, 218, 185)}, {"peru", PACK_RGB (205, 133, 63)}, {"pink", PACK_RGB (255, 192, 203)}, {"plum", PACK_RGB (221, 160, 203)}, {"powderblue", PACK_RGB (176, 224, 230)}, {"purple", PACK_RGB (128, 0, 128)}, {"red", PACK_RGB (255, 0, 0)}, {"rosybrown", PACK_RGB (188, 143, 143)}, {"royalblue", PACK_RGB (65, 105, 225)}, {"saddlebrown", PACK_RGB (139, 69, 19)}, {"salmon", PACK_RGB (250, 128, 114)}, {"sandybrown", PACK_RGB (244, 164, 96)}, {"seagreen", PACK_RGB (46, 139, 87)}, {"seashell", PACK_RGB (255, 245, 238)}, {"sienna", PACK_RGB (160, 82, 45)}, {"silver", PACK_RGB (192, 192, 192)}, {"skyblue", PACK_RGB (135, 206, 235)}, {"slateblue", PACK_RGB (106, 90, 205)}, {"slategray", PACK_RGB (119, 128, 144)}, {"slategrey", PACK_RGB (119, 128, 144)}, {"snow", PACK_RGB (255, 255, 250)}, {"springgreen", PACK_RGB (0, 255, 127)}, {"steelblue", PACK_RGB (70, 130, 180)}, {"tan", PACK_RGB (210, 180, 140)}, {"teal", PACK_RGB (0, 128, 128)}, {"thistle", PACK_RGB (216, 191, 216)}, {"tomato", PACK_RGB (255, 99, 71)}, {"turquoise", PACK_RGB (64, 224, 208)}, {"violet", PACK_RGB (238, 130, 238)}, {"wheat", PACK_RGB (245, 222, 179)}, {"white", PACK_RGB (255, 255, 255)}, {"whitesmoke", PACK_RGB (245, 245, 245)}, {"yellow", PACK_RGB (255, 255, 0)}, {"yellowgreen", PACK_RGB (154, 205, 50)} }; ColorPair *result = bsearch (str, color_list, sizeof (color_list) / sizeof (color_list[0]), sizeof (ColorPair), rsvg_css_color_compare); /* default to black on failed lookup */ if (result == NULL) { UNSETINHERIT (); val = 0; } else val = result->rgb; } return val; } #undef PACK_RGB guint rsvg_css_parse_opacity (const char *str) { char *end_ptr; double opacity; opacity = g_ascii_strtod (str, &end_ptr); if (end_ptr && end_ptr[0] == '%') opacity *= 0.01; return (guint) floor (opacity * 255. + 0.5); } /* : An angle value is a optionally followed immediately with an angle unit identifier. Angle unit identifiers are: * deg: degrees * grad: grads * rad: radians For properties defined in [CSS2], an angle unit identifier must be provided. For SVG-specific attributes and properties, the angle unit identifier is optional. If not provided, the angle value is assumed to be in degrees. */ double rsvg_css_parse_angle (const char *str) { double degrees; char *end_ptr; degrees = g_ascii_strtod (str, &end_ptr); /* todo: error condition - figure out how to best represent it */ if ((degrees == -HUGE_VAL || degrees == HUGE_VAL) && (ERANGE == errno)) return 0.0; if (end_ptr) { if (!strcmp (end_ptr, "rad")) return degrees * 180. / G_PI; else if (!strcmp (end_ptr, "grad")) return degrees * 360. / 400.; } return degrees; } /* : Frequency values are used with aural properties. The normative definition of frequency values can be found in [CSS2-AURAL]. A frequency value is a immediately followed by a frequency unit identifier. Frequency unit identifiers are: * Hz: Hertz * kHz: kilo Hertz Frequency values may not be negative. */ double rsvg_css_parse_frequency (const char *str) { double f_hz; char *end_ptr; f_hz = g_ascii_strtod (str, &end_ptr); /* todo: error condition - figure out how to best represent it */ if ((f_hz == -HUGE_VAL || f_hz == HUGE_VAL) && (ERANGE == errno)) return 0.0; if (end_ptr && !strcmp (end_ptr, "kHz")) return f_hz * 1000.; return f_hz; } /*