From 9cd109060a8d2e57ba5147bdef1a4561d905da46 Mon Sep 17 00:00:00 2001 From: James Bursa Date: Sun, 15 Jul 2007 23:22:54 +0000 Subject: Add SVG support for a few basic shapes. svn path=/trunk/netsurf/; revision=3420 --- image/svg.c | 414 +++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 368 insertions(+), 46 deletions(-) diff --git a/image/svg.c b/image/svg.c index 378b6d97e..65337314a 100644 --- a/image/svg.c +++ b/image/svg.c @@ -18,16 +18,52 @@ #include #include "utils/config.h" #include "content/content.h" +#include "css/css.h" #include "desktop/plotters.h" +#include "desktop/options.h" #include "image/svg.h" #include "utils/log.h" #include "utils/messages.h" #include "utils/utils.h" -static bool svg_redraw_svg(xmlNode *svg, int x, int y); -static bool svg_redraw_rect(xmlNode *rect, int x, int y); -static bool svg_redraw_text(xmlNode *text, int x, int y); +struct svg_redraw_state { + /* screen origin */ + int origin_x; + int origin_y; + + float viewport_width; + float viewport_height; + + /* current transformation matrix */ + struct { + float a, b, c, d, e, f; + } ctm; + + struct css_style style; + + /* paint attributes */ + colour fill; + colour stroke; + int stroke_width; +}; + + +static bool svg_redraw_svg(xmlNode *svg, struct svg_redraw_state state); +static bool svg_redraw_rect(xmlNode *rect, struct svg_redraw_state state); +static bool svg_redraw_circle(xmlNode *circle, struct svg_redraw_state state); +static bool svg_redraw_line(xmlNode *line, struct svg_redraw_state state); +static bool svg_redraw_text(xmlNode *text, struct svg_redraw_state state); +static void svg_parse_position_attributes(const xmlNode *node, + const struct svg_redraw_state state, + float *x, float *y, float *width, float *height); +static float svg_parse_length(const xmlChar *s, int viewport_size, + const struct svg_redraw_state state); +static void svg_parse_paint_attributes(const xmlNode *node, + struct svg_redraw_state *state); +static void svg_parse_color(const char *s, colour *c); +static void svg_parse_font_attributes(const xmlNode *node, + struct svg_redraw_state *state); /** @@ -86,21 +122,13 @@ bool svg_convert(struct content *c, int w, int h) c->data.svg.svg = svg; /* get graphic dimensions */ - xmlChar *width = xmlGetProp(svg, "width"); - if (width) { - c->width = atoi(width); - xmlFree(width); - } else { - c->width = 100; - } - - xmlChar *height = xmlGetProp(svg, "height"); - if (height) { - c->height = atoi(height); - xmlFree(height); - } else { - c->height = 100; - } + struct svg_redraw_state state; + state.viewport_width = w; + state.viewport_height = h; + float x, y, width, height; + svg_parse_position_attributes(svg, state, &x, &y, &width, &height); + c->width = width; + c->height = height; /*c->title = malloc(100); if (c->title) @@ -123,28 +151,74 @@ bool svg_redraw(struct content *c, int x, int y, { assert(c->data.svg.svg); - return svg_redraw_svg(c->data.svg.svg, x, y); + struct svg_redraw_state state; + + state.origin_x = x; + state.origin_y = y; + state.viewport_width = width; + state.viewport_height = height; + state.ctm.a = 1; + state.ctm.b = 0; + state.ctm.c = 0; + state.ctm.d = 1; + state.ctm.e = 0; + state.ctm.f = 0; + state.style = css_base_style; + state.style.font_size.value.length.value = option_font_size * 0.1; + state.fill = 0x000000; + state.stroke = TRANSPARENT; + state.stroke_width = 1; + + return svg_redraw_svg(c->data.svg.svg, state); } /** - * Redraw a element node. + * Redraw a or element node. */ -bool svg_redraw_svg(xmlNode *svg, int x, int y) +bool svg_redraw_svg(xmlNode *svg, struct svg_redraw_state state) { + float x, y; + + svg_parse_position_attributes(svg, state, + &x, &y, + &state.viewport_width, &state.viewport_height); + svg_parse_paint_attributes(svg, &state); + svg_parse_font_attributes(svg, &state); + + /* parse viewBox */ + xmlAttr *view_box = xmlHasProp(svg, (const xmlChar *) "viewBox"); + if (view_box) { + const char *s = (const char *) view_box->children->content; + float min_x, min_y, width, height; + if (sscanf(s, "%f,%f,%f,%f", + &min_x, &min_y, &width, &height) == 4 || + sscanf(s, "%f %f %f %f", + &min_x, &min_y, &width, &height) == 4) { + state.ctm.a = state.viewport_width / width; + state.ctm.d = state.viewport_height / height; + state.ctm.e = -min_x; + state.ctm.f = -min_y; + } + } + for (xmlNode *child = svg->children; child; child = child->next) { bool ok = true; if (child->type == XML_ELEMENT_NODE) { if (strcmp(child->name, "svg") == 0) - ok = svg_redraw_svg(child, x, y); + ok = svg_redraw_svg(child, state); else if (strcmp(child->name, "g") == 0) - ok = svg_redraw_svg(child, x, y); + ok = svg_redraw_svg(child, state); else if (strcmp(child->name, "rect") == 0) - ok = svg_redraw_rect(child, x, y); + ok = svg_redraw_rect(child, state); + else if (strcmp(child->name, "circle") == 0) + ok = svg_redraw_circle(child, state); + else if (strcmp(child->name, "line") == 0) + ok = svg_redraw_line(child, state); else if (strcmp(child->name, "text") == 0) - ok = svg_redraw_text(child, x, y); + ok = svg_redraw_text(child, state); } if (!ok) @@ -159,22 +233,116 @@ bool svg_redraw_svg(xmlNode *svg, int x, int y) * Redraw a element node. */ -bool svg_redraw_rect(xmlNode *rect, int x, int y) +bool svg_redraw_rect(xmlNode *rect, struct svg_redraw_state state) { - int width = 0, height = 0; + float x, y, width, height; + + svg_parse_position_attributes(rect, state, + &x, &y, &width, &height); + svg_parse_paint_attributes(rect, &state); + + int p[8] = { x, y, + x + width, y, + x + width, y + height, + x, y + height }; + for (unsigned int i = 0; i != 8; i += 2) { + p[i] = state.origin_x + state.ctm.a * p[i] + + state.ctm.c * p[i+1] + state.ctm.e; + p[i+1] = state.origin_y + state.ctm.b * p[i] + + state.ctm.d * p[i+1] + state.ctm.f; + } - for (xmlAttr *attr = rect->properties; attr; attr = attr->next) { - if (strcmp(attr->name, "x") == 0) - x += atoi(attr->children->content); - else if (strcmp(attr->name, "y") == 0) - y += atoi(attr->children->content); - else if (strcmp(attr->name, "width") == 0) - width = atoi(attr->children->content); - else if (strcmp(attr->name, "height") == 0) - height = atoi(attr->children->content); + if (state.fill != TRANSPARENT) + if (!plot.polygon(p, 4, state.fill)) + return false; + + if (state.stroke != TRANSPARENT) { + for (unsigned int i = 0; i != 8; i += 2) { + if (!plot.line(p[i], p[i+1], p[(i+2)%8], p[(i+3)%8], + state.stroke_width, state.stroke, + false, false)) + return false; + } } - return plot.rectangle(x, y, width, height, 5, 0x000000, false, false); + return true; +} + + +/** + * Redraw a element node. + */ + +bool svg_redraw_circle(xmlNode *circle, struct svg_redraw_state state) +{ + float x = 0, y = 0, r = 0; + + for (xmlAttr *attr = circle->properties; attr; attr = attr->next) { + if (strcmp(attr->name, "cx") == 0) + x = svg_parse_length(attr->children->content, + state.viewport_width, state); + else if (strcmp(attr->name, "cy") == 0) + y = svg_parse_length(attr->children->content, + state.viewport_height, state); + else if (strcmp(attr->name, "r") == 0) + r = svg_parse_length(attr->children->content, + state.viewport_width, state); + } + svg_parse_paint_attributes(circle, &state); + + int px = state.origin_x + state.ctm.a * x + + state.ctm.c * y + state.ctm.e; + int py = state.origin_y + state.ctm.b * x + + state.ctm.d * y + state.ctm.f; + int pr = r * state.ctm.a; + + if (state.fill != TRANSPARENT) + if (!plot.disc(px, py, pr, state.fill, true)) + return false; + + if (state.stroke != TRANSPARENT) + if (!plot.disc(px, py, pr, state.stroke, false)) + return false; + + return true; +} + + +/** + * Redraw a element node. + */ + +bool svg_redraw_line(xmlNode *line, struct svg_redraw_state state) +{ + float x1 = 0, y1 = 0, x2 = 0, y2 = 0; + + for (xmlAttr *attr = line->properties; attr; attr = attr->next) { + if (strcmp(attr->name, "x1") == 0) + x1 = svg_parse_length(attr->children->content, + state.viewport_width, state); + else if (strcmp(attr->name, "y1") == 0) + y1 = svg_parse_length(attr->children->content, + state.viewport_height, state); + else if (strcmp(attr->name, "x2") == 0) + x2 = svg_parse_length(attr->children->content, + state.viewport_width, state); + else if (strcmp(attr->name, "y2") == 0) + y2 = svg_parse_length(attr->children->content, + state.viewport_height, state); + } + svg_parse_paint_attributes(line, &state); + + int px1 = state.origin_x + state.ctm.a * x1 + + state.ctm.c * y1 + state.ctm.e; + int py1 = state.origin_y + state.ctm.b * x1 + + state.ctm.d * y1 + state.ctm.f; + int px2 = state.origin_x + state.ctm.a * x2 + + state.ctm.c * y2 + state.ctm.e; + int py2 = state.origin_y + state.ctm.b * x2 + + state.ctm.d * y2 + state.ctm.f; + + return plot.line(px1, py1, px2, py2, state.stroke_width, state.stroke, + false, false); } @@ -182,25 +350,34 @@ bool svg_redraw_rect(xmlNode *rect, int x, int y) * Redraw a or element node. */ -bool svg_redraw_text(xmlNode *text, int x, int y) +bool svg_redraw_text(xmlNode *text, struct svg_redraw_state state) { - for (xmlAttr *attr = text->properties; attr; attr = attr->next) { - if (strcmp(attr->name, "x") == 0) - x += atoi(attr->children->content); - else if (strcmp(attr->name, "y") == 0) - y += atoi(attr->children->content); - } + float x, y, width, height; + + svg_parse_position_attributes(text, state, + &x, &y, &width, &height); + svg_parse_font_attributes(text, &state); + int px = state.origin_x + state.ctm.a * x + + state.ctm.c * y + state.ctm.e; + int py = state.origin_y + state.ctm.b * x + + state.ctm.d * y + state.ctm.f; + state.ctm.e = px - state.origin_x; + state.ctm.f = py - state.origin_y; + + struct css_style style = state.style; + style.font_size.value.length.value *= state.ctm.a; for (xmlNode *child = text->children; child; child = child->next) { bool ok = true; if (child->type == XML_TEXT_NODE) { - ok = plot.text(x, y, &css_base_style, + ok = plot.text(px, py, + &css_base_style, child->content, strlen(child->content), 0xffffff, 0x000000); } else if (child->type == XML_ELEMENT_NODE && strcmp(child->name, "tspan") == 0) { - ok = svg_redraw_text(child, x, y); + ok = svg_redraw_text(child, state); } if (!ok) @@ -211,6 +388,151 @@ bool svg_redraw_text(xmlNode *text, int x, int y) } +/** + * Parse x, y, width, and height attributes, if present. + */ + +void svg_parse_position_attributes(const xmlNode *node, + const struct svg_redraw_state state, + float *x, float *y, float *width, float *height) +{ + *x = 0; + *y = 0; + *width = state.viewport_width; + *height = state.viewport_height; + + for (xmlAttr *attr = node->properties; attr; attr = attr->next) { + if (strcmp(attr->name, "x") == 0) + *x = svg_parse_length(attr->children->content, + state.viewport_width, state); + else if (strcmp(attr->name, "y") == 0) + *y = svg_parse_length(attr->children->content, + state.viewport_height, state); + else if (strcmp(attr->name, "width") == 0) + *width = svg_parse_length(attr->children->content, + state.viewport_width, state); + else if (strcmp(attr->name, "height") == 0) + *height = svg_parse_length(attr->children->content, + state.viewport_height, state); + } +} + + +/** + * Parse a length as a number of pixels. + */ + +float svg_parse_length(const xmlChar *s, int viewport_size, + const struct svg_redraw_state state) +{ + int num_length = strspn(s, "0123456789+-."); + const xmlChar *unit = s + num_length; + float n = atof((const char *) s); + float font_size = css_len2px(&state.style.font_size.value.length, 0); + + if (unit[0] == 0) { + return n; + } else if (unit[0] == '%') { + return n / 100.0 * viewport_size; + } else if (unit[0] == 'e' && unit[1] == 'm') { + return n * font_size; + } else if (unit[0] == 'e' && unit[1] == 'x') { + return n / 2.0 * font_size; + } else if (unit[0] == 'p' && unit[1] == 'x') { + return n; + } else if (unit[0] == 'p' && unit[1] == 't') { + return n * 1.25; + } else if (unit[0] == 'p' && unit[1] == 'c') { + return n * 15.0; + } else if (unit[0] == 'm' && unit[1] == 'm') { + return n * 3.543307; + } else if (unit[0] == 'c' && unit[1] == 'm') { + return n * 35.43307; + } else if (unit[0] == 'i' && unit[1] == 'n') { + return n * 90; + } + + return 0; +} + + +/** + * Parse paint attributes, if present. + */ + +void svg_parse_paint_attributes(const xmlNode *node, + struct svg_redraw_state *state) +{ + for (const xmlAttr *attr = node->properties; attr; attr = attr->next) { + if (strcmp(attr->name, "fill") == 0) + svg_parse_color((const char *) attr->children->content, + &state->fill); + else if (strcmp(attr->name, "stroke") == 0) + svg_parse_color((const char *) attr->children->content, + &state->stroke); + else if (strcmp(attr->name, "stroke-width") == 0) + state->stroke_width = svg_parse_length( + attr->children->content, + state->viewport_width, *state); + } +} + + +/** + * Parse a colour. + */ + +void svg_parse_color(const char *s, colour *c) +{ + unsigned int r, g, b; + float rf, gf, bf; + size_t len = strlen(s); + + if (len == 4 && s[0] == '#') { + if (sscanf(s + 1, "%1x%1x%1x", &r, &g, &b) == 3) + *c = (b << 20) | (b << 16) | + (g << 12) | (g << 8) | + (r << 4) | r; + } else if (len == 7 && s[0] == '#') { + if (sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3) + *c = (b << 16) | (g << 8) | r; + } else if (10 <= len && s[0] == 'r' && s[1] == 'g' && s[2] == 'b' && + s[3] == '(' && s[len - 1] == ')') { + if (sscanf(s + 4, "%i,%i,%i", &r, &g, &b) == 3) + *c = (b << 16) | (g << 8) | r; + else if (sscanf(s + 4, "%f%%,%f%%,%f%%", &rf, &gf, &bf) == 3) { + b = bf * 255 / 100; + g = gf * 255 / 100; + r = rf * 255 / 100; + *c = (b << 16) | (g << 8) | r; + } + } else { + *c = named_colour(s); + } +} + + +/** + * Parse font attributes, if present. + */ + +void svg_parse_font_attributes(const xmlNode *node, + struct svg_redraw_state *state) +{ + for (const xmlAttr *attr = node->properties; attr; attr = attr->next) { + if (strcmp(attr->name, "font-size") == 0) { + /*if (css_parse_length( + (const char *) attr->children->content, + &state->style.font_size.value.length, + true, true)) { + state->style.font_size.size = + CSS_FONT_SIZE_LENGTH; + }*/ + } + } +} + + /** * Destroy a CONTENT_SVG and free all resources it owns. */ -- cgit v1.2.3