/* * This file is part of Libsvgtiny * Licensed under the MIT License, * http://opensource.org/licenses/mit-license.php * Copyright 2008-2009 James Bursa * Copyright 2012 Daniel Silverstone */ #include #include #include #include #include #include #include #include #include #include "svgtiny.h" #include "svgtiny_internal.h" /* Source file generated by `gperf`. */ #include "autogenerated_colors.c" #define TAU 6.28318530717958647692 #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #ifndef M_PI_2 #define M_PI_2 1.57079632679489661923 #endif #define KAPPA 0.5522847498 #define degToRad(angleInDegrees) ((angleInDegrees) * M_PI / 180.0) #define radToDeg(angleInRadians) ((angleInRadians) * 180.0 / M_PI) static svgtiny_code svgtiny_parse_svg(dom_element *svg, struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_path(dom_element *path, struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_rect(dom_element *rect, struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_circle(dom_element *circle, struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse, struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_line(dom_element *line, struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_poly(dom_element *poly, struct svgtiny_parse_state state, bool polygon); static svgtiny_code svgtiny_parse_text(dom_element *text, struct svgtiny_parse_state state); static void svgtiny_parse_position_attributes(dom_element *node, const struct svgtiny_parse_state state, float *x, float *y, float *width, float *height); static void svgtiny_parse_paint_attributes(dom_element *node, struct svgtiny_parse_state *state); static void svgtiny_parse_font_attributes(dom_element *node, struct svgtiny_parse_state *state); static void svgtiny_parse_transform_attributes(dom_element *node, struct svgtiny_parse_state *state); static svgtiny_code svgtiny_add_path(float *p, unsigned int n, struct svgtiny_parse_state *state); static void _svgtiny_parse_color(const char *s, svgtiny_colour *c, struct svgtiny_parse_state_gradient *grad, struct svgtiny_parse_state *state); /** * rotate midpoint vector */ static void rotate_midpoint_vector(float ax, float ay, float bx, float by, double radangle, double *x_out, double *y_out) { double dx2; /* midpoint x coordinate */ double dy2; /* midpoint y coordinate */ double cosangle; /* cosine of rotation angle */ double sinangle; /* sine of rotation angle */ /* compute the sin and cos of the angle */ cosangle = cos(radangle); sinangle = sin(radangle); /* compute the midpoint between start and end points */ dx2 = (ax - bx) / 2.0; dy2 = (ay - by) / 2.0; /* rotate vector to remove angle */ *x_out = ((cosangle * dx2) + (sinangle * dy2)); *y_out = ((-sinangle * dx2) + (cosangle * dy2)); } /** * ensure the arc radii are large enough and scale as appropriate * * the radii need to be large enough if they are not they must be * adjusted. This allows for elimination of differences between * implementations especialy with rounding. */ static void ensure_radii_scale(double x1_sq, double y1_sq, float *rx, float *ry, double *rx_sq, double *ry_sq) { double radiisum; double radiiscale; /* set radii square values */ (*rx_sq) = (*rx) * (*rx); (*ry_sq) = (*ry) * (*ry); radiisum = (x1_sq / (*rx_sq)) + (y1_sq / (*ry_sq)); if (radiisum > 0.99999) { /* need to scale radii */ radiiscale = sqrt(radiisum) * 1.00001; *rx = (float)(radiiscale * (*rx)); *ry = (float)(radiiscale * (*ry)); /* update squares too */ (*rx_sq) = (*rx) * (*rx); (*ry_sq) = (*ry) * (*ry); } } /** * compute the transformed centre point */ static void compute_transformed_centre_point(double sign, float rx, float ry, double rx_sq, double ry_sq, double x1, double y1, double x1_sq, double y1_sq, double *cx1, double *cy1) { double sq; double coef; sq = ((rx_sq * ry_sq) - (rx_sq * y1_sq) - (ry_sq * x1_sq)) / ((rx_sq * y1_sq) + (ry_sq * x1_sq)); sq = (sq < 0) ? 0 : sq; coef = (sign * sqrt(sq)); *cx1 = coef * ((rx * y1) / ry); *cy1 = coef * -((ry * x1) / rx); } /** * compute untransformed centre point * * \param ax The first point x coordinate * \param ay The first point y coordinate * \param bx The second point x coordinate * \param ay The second point y coordinate */ static void compute_centre_point(float ax, float ay, float bx, float by, double cx1, double cy1, double radangle, double *x_out, double *y_out) { double sx2; double sy2; double cosangle; /* cosine of rotation angle */ double sinangle; /* sine of rotation angle */ /* compute the sin and cos of the angle */ cosangle = cos(radangle); sinangle = sin(radangle); sx2 = (ax + bx) / 2.0; sy2 = (ay + by) / 2.0; *x_out = sx2 + (cosangle * cx1 - sinangle * cy1); *y_out = sy2 + (sinangle * cx1 + cosangle * cy1); } /** * compute the angle start and extent */ static void compute_angle_start_extent(float rx, float ry, double x1, double y1, double cx1, double cy1, double *start, double *extent) { double sign; double ux; double uy; double vx; double vy; double p, n; double actmp; /* * Angle betwen two vectors is +/- acos( u.v / len(u) * len(v)) * Where: * '.' is the dot product. * +/- is calculated from the sign of the cross product (u x v) */ ux = (x1 - cx1) / rx; uy = (y1 - cy1) / ry; vx = (-x1 - cx1) / rx; vy = (-y1 - cy1) / ry; /* compute the start angle */ /* The angle between (ux, uy) and the 0 angle */ /* len(u) * len(1,0) == len(u) */ n = sqrt((ux * ux) + (uy * uy)); /* u.v == (ux,uy).(1,0) == (1 * ux) + (0 * uy) == ux */ p = ux; /* u x v == (1 * uy - ux * 0) == uy */ sign = (uy < 0) ? -1.0 : 1.0; /* (p >= n) so safe */ *start = sign * acos(p / n); /* compute the extent angle */ n = sqrt(((ux * ux) + (uy * uy)) * ((vx * vx) + (vy * vy))); p = (ux * vx) + (uy * vy); sign = ((ux * vy) - (uy * vx) < 0) ? -1.0f : 1.0f; /* arc cos must operate between -1 and 1 */ actmp = p / n; if (actmp < -1.0) { *extent = sign * M_PI; } else if (actmp > 1.0) { *extent = 0; } else { *extent = sign * acos(actmp); } } /** * converts a circle centered unit circle arc to a series of bezier curves * * Each bezier is stored as six values of three pairs of coordinates * * The beziers are stored without their start point as that is assumed * to be the preceding elements end point. * * \param start The start angle of the arc (in radians) * \param extent The size of the arc (in radians) * \param bzpt The array to store the bezier values in * \return The number of bezier segments output (max 4) */ static int circle_arc_to_bezier(double start, double extent, double *bzpt) { int bzsegments; double increment; double controllen; int pos = 0; int segment; double angle; double dx, dy; bzsegments = (int) ceil(fabs(extent) / M_PI_2); increment = extent / bzsegments; controllen = 4.0 / 3.0 * sin(increment / 2.0) / (1.0 + cos(increment / 2.0)); for (segment = 0; segment < bzsegments; segment++) { /* first control point */ angle = start + (segment * increment); dx = cos(angle); dy = sin(angle); bzpt[pos++] = dx - controllen * dy; bzpt[pos++] = dy + controllen * dx; /* second control point */ angle+=increment; dx = cos(angle); dy = sin(angle); bzpt[pos++] = dx + controllen * dy; bzpt[pos++] = dy - controllen * dx; /* endpoint */ bzpt[pos++] = dx; bzpt[pos++] = dy; } return bzsegments; } /** * transform coordinate list * * perform a scale, rotate and translate on list of coordinates * * scale(rx,ry) * rotate(an) * translate (cx, cy) * * homogeneous transforms * * scaling * | rx 0 0 | * S = | 0 ry 0 | * | 0 0 1 | * * rotate * | cos(an) -sin(an) 0 | * R = | sin(an) cos(an) 0 | * | 0 0 1 | * * {{cos(a), -sin(a) 0}, {sin(a), cos(a),0}, {0,0,1}} * * translate * | 1 0 cx | * T = | 0 1 cy | * | 0 0 1 | * * note order is significat here and the combined matrix is * M = T.R.S * * | cos(an) -sin(an) cx | * T.R = | sin(an) cos(an) cy | * | 0 0 1 | * * | rx * cos(an) ry * -sin(an) cx | * T.R.S = | rx * sin(an) ry * cos(an) cy | * | 0 0 1 | * * {{Cos[a], -Sin[a], c}, {Sin[a], Cos[a], d}, {0, 0, 1}} . {{r, 0, 0}, {0, s, 0}, {0, 0, 1}} * * Each point * | x1 | * P = | y1 | * | 1 | * * output * | x2 | * | y2 | = M . P * | 1 | * * x2 = cx + (rx * x1 * cos(a)) + (ry * y1 * -1 * sin(a)) * y2 = cy + (ry * y1 * cos(a)) + (rx * x1 * sin(a)) * * * \param rx X scaling to apply * \param ry Y scaling to apply * \param radangle rotation to apply (in radians) * \param cx X translation to apply * \param cy Y translation to apply * \param points The size of the bzpoints array * \param bzpoints an array of x,y values to apply the transform to */ static void scale_rotate_translate_points(double rx, double ry, double radangle, double cx, double cy, int pntsize, double *points) { int pnt; double cosangle; /* cosine of rotation angle */ double sinangle; /* sine of rotation angle */ double rxcosangle, rxsinangle, rycosangle, rynsinangle; double x2,y2; /* compute the sin and cos of the angle */ cosangle = cos(radangle); sinangle = sin(radangle); rxcosangle = rx * cosangle; rxsinangle = rx * sinangle; rycosangle = ry * cosangle; rynsinangle = ry * -1 * sinangle; for (pnt = 0; pnt < pntsize; pnt+=2) { x2 = cx + (points[pnt] * rxcosangle) + (points[pnt + 1] * rynsinangle); y2 = cy + (points[pnt + 1] * rycosangle) + (points[pnt] * rxsinangle); points[pnt] = x2; points[pnt + 1] = y2; } } /** * convert an svg path arc to a bezier curve * * This function perfoms a transform on the nine arc parameters * (coordinate pairs for start and end together with the radii of the * elipse, the rotation angle and which of the four arcs to draw) * which generates the parameters (coordinate pairs for start, * end and their control points) for a set of up to four bezier curves. * * Obviously the start and end coordinates are not altered between * representations so the aim is to calculate the coordinate pairs for * the bezier control points. * * \param bzpoints the array to fill with bezier curves * \return the number of bezier segments generated or -1 for a line */ static int svgarc_to_bezier(float start_x, float start_y, float end_x, float end_y, float rx, float ry, float angle, bool largearc, bool sweep, double *bzpoints) { double radangle; /* normalised elipsis rotation angle in radians */ double rx_sq; /* x radius squared */ double ry_sq; /* y radius squared */ double x1, y1; /* rotated midpoint vector */ double x1_sq, y1_sq; /* x1 vector squared */ double cx1,cy1; /* transformed circle center */ double cx,cy; /* circle center */ double start, extent; int bzsegments; if ((start_x == end_x) && (start_y == end_y)) { /* * if the start and end coordinates are the same the * svg spec says this is equivalent to having no segment * at all */ return 0; } if ((rx == 0) || (ry == 0)) { /* * if either radii is zero the specified behaviour is a line */ return -1; } /* obtain the absolute values of the radii */ rx = fabsf(rx); ry = fabsf(ry); /* convert normalised angle to radians */ radangle = degToRad(fmod(angle, 360.0)); /* step 1 */ /* x1,x2 is the midpoint vector rotated to remove the arc angle */ rotate_midpoint_vector(start_x, start_y, end_x, end_y, radangle, &x1, &y1); /* step 2 */ /* get squared x1 values */ x1_sq = x1 * x1; y1_sq = y1 * y1; /* ensure radii are correctly scaled */ ensure_radii_scale(x1_sq, y1_sq, &rx, &ry, &rx_sq, &ry_sq); /* compute the transformed centre point */ compute_transformed_centre_point(largearc == sweep?-1:1, rx, ry, rx_sq, ry_sq, x1, y1, x1_sq, y1_sq, &cx1, &cy1); /* step 3 */ /* get the untransformed centre point */ compute_centre_point(start_x, start_y, end_x, end_y, cx1, cy1, radangle, &cx, &cy); /* step 4 */ /* compute anglestart and extent */ compute_angle_start_extent(rx,ry, x1,y1, cx1, cy1, &start, &extent); /* extent of 0 is a straight line */ if (extent == 0) { return -1; } /* take account of sweep */ if (!sweep && extent > 0) { extent -= TAU; } else if (sweep && extent < 0) { extent += TAU; } /* normalise start and extent */ extent = fmod(extent, TAU); start = fmod(start, TAU); /* convert the arc to unit circle bezier curves */ bzsegments = circle_arc_to_bezier(start, extent, bzpoints); /* transform the bezier curves */ scale_rotate_translate_points(rx, ry, radangle, cx, cy, bzsegments * 6, bzpoints); return bzsegments; } /** * Call this to ref the strings in a gradient state. */ static void svgtiny_grad_string_ref(struct svgtiny_parse_state_gradient *grad) { if (grad->gradient_x1 != NULL) { dom_string_ref(grad->gradient_x1); } if (grad->gradient_y1 != NULL) { dom_string_ref(grad->gradient_y1); } if (grad->gradient_x2 != NULL) { dom_string_ref(grad->gradient_x2); } if (grad->gradient_y2 != NULL) { dom_string_ref(grad->gradient_y2); } } /** * Call this to clean up the strings in a gradient state. */ static void svgtiny_grad_string_cleanup( struct svgtiny_parse_state_gradient *grad) { if (grad->gradient_x1 != NULL) { dom_string_unref(grad->gradient_x1); grad->gradient_x1 = NULL; } if (grad->gradient_y1 != NULL) { dom_string_unref(grad->gradient_y1); grad->gradient_y1 = NULL; } if (grad->gradient_x2 != NULL) { dom_string_unref(grad->gradient_x2); grad->gradient_x2 = NULL; } if (grad->gradient_y2 != NULL) { dom_string_unref(grad->gradient_y2); grad->gradient_y2 = NULL; } } /** * Set the local externally-stored parts of a parse state. * Call this in functions that made a new state on the stack. * Doesn't make own copy of global state, such as the interned string list. */ static void svgtiny_setup_state_local(struct svgtiny_parse_state *state) { svgtiny_grad_string_ref(&(state->fill_grad)); svgtiny_grad_string_ref(&(state->stroke_grad)); } /** * Cleanup the local externally-stored parts of a parse state. * Call this in functions that made a new state on the stack. * Doesn't cleanup global state, such as the interned string list. */ static void svgtiny_cleanup_state_local(struct svgtiny_parse_state *state) { svgtiny_grad_string_cleanup(&(state->fill_grad)); svgtiny_grad_string_cleanup(&(state->stroke_grad)); } /** * Create a new svgtiny_diagram structure. */ struct svgtiny_diagram *svgtiny_create(void) { struct svgtiny_diagram *diagram; diagram = calloc(sizeof(*diagram), 1); if (!diagram) return 0; return diagram; free(diagram); return NULL; } static void ignore_msg(uint32_t severity, void *ctx, const char *msg, ...) { UNUSED(severity); UNUSED(ctx); UNUSED(msg); } /** * Parse a block of memory into a svgtiny_diagram. */ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, const char *buffer, size_t size, const char *url, int viewport_width, int viewport_height) { dom_document *document; dom_exception exc; dom_xml_parser *parser; dom_xml_error err; dom_element *svg; dom_string *svg_name; lwc_string *svg_name_lwc; struct svgtiny_parse_state state; float x, y, width, height; svgtiny_code code; assert(diagram); assert(buffer); assert(url); UNUSED(url); parser = dom_xml_parser_create(NULL, NULL, ignore_msg, NULL, &document); if (parser == NULL) return svgtiny_LIBDOM_ERROR; err = dom_xml_parser_parse_chunk(parser, (uint8_t *)buffer, size); if (err != DOM_XML_OK) { dom_node_unref(document); dom_xml_parser_destroy(parser); return svgtiny_LIBDOM_ERROR; } err = dom_xml_parser_completed(parser); if (err != DOM_XML_OK) { dom_node_unref(document); dom_xml_parser_destroy(parser); return svgtiny_LIBDOM_ERROR; } /* We're done parsing, drop the parser. * We now own the document entirely. */ dom_xml_parser_destroy(parser); /* find root element */ exc = dom_document_get_document_element(document, &svg); if (exc != DOM_NO_ERR) { dom_node_unref(document); return svgtiny_LIBDOM_ERROR; } if (svg == NULL) { /* no root svg element */ dom_node_unref(document); return svgtiny_SVG_ERROR; } exc = dom_node_get_node_name(svg, &svg_name); if (exc != DOM_NO_ERR) { dom_node_unref(svg); dom_node_unref(document); return svgtiny_LIBDOM_ERROR; } if (lwc_intern_string("svg", 3 /* SLEN("svg") */, &svg_name_lwc) != lwc_error_ok) { dom_string_unref(svg_name); dom_node_unref(svg); dom_node_unref(document); return svgtiny_LIBDOM_ERROR; } if (!dom_string_caseless_lwc_isequal(svg_name, svg_name_lwc)) { lwc_string_unref(svg_name_lwc); dom_string_unref(svg_name); dom_node_unref(svg); dom_node_unref(document); return svgtiny_NOT_SVG; } lwc_string_unref(svg_name_lwc); dom_string_unref(svg_name); /* get graphic dimensions */ memset(&state, 0, sizeof(state)); state.diagram = diagram; state.document = document; state.viewport_width = viewport_width; state.viewport_height = viewport_height; #define SVGTINY_STRING_ACTION2(s,n) \ if (dom_string_create_interned((const uint8_t *) #n, \ strlen(#n), &state.interned_##s) \ != DOM_NO_ERR) { \ code = svgtiny_LIBDOM_ERROR; \ goto cleanup; \ } #include "svgtiny_strings.h" #undef SVGTINY_STRING_ACTION2 svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height); diagram->width = width; diagram->height = height; /* set up parsing state */ state.viewport_width = width; state.viewport_height = height; state.ctm.a = 1; /*(float) viewport_width / (float) width;*/ state.ctm.b = 0; state.ctm.c = 0; state.ctm.d = 1; /*(float) viewport_height / (float) height;*/ state.ctm.e = 0; /*x;*/ state.ctm.f = 0; /*y;*/ /*state.style = css_base_style; state.style.font_size.value.length.value = option_font_size * 0.1;*/ state.fill = 0x000000; state.stroke = svgtiny_TRANSPARENT; state.stroke_width = 1; /* parse tree */ code = svgtiny_parse_svg(svg, state); dom_node_unref(svg); dom_node_unref(document); cleanup: svgtiny_cleanup_state_local(&state); #define SVGTINY_STRING_ACTION2(s,n) \ if (state.interned_##s != NULL) \ dom_string_unref(state.interned_##s); #include "svgtiny_strings.h" #undef SVGTINY_STRING_ACTION2 return code; } /** * Parse a or element node. */ svgtiny_code svgtiny_parse_svg(dom_element *svg, struct svgtiny_parse_state state) { float x, y, width, height; dom_string *view_box; dom_element *child; dom_exception exc; svgtiny_setup_state_local(&state); svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height); svgtiny_parse_paint_attributes(svg, &state); svgtiny_parse_font_attributes(svg, &state); exc = dom_element_get_attribute(svg, state.interned_viewBox, &view_box); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (view_box) { char *s = strndup(dom_string_data(view_box), dom_string_byte_length(view_box)); float min_x, min_y, vwidth, vheight; if (sscanf(s, "%f,%f,%f,%f", &min_x, &min_y, &vwidth, &vheight) == 4 || sscanf(s, "%f %f %f %f", &min_x, &min_y, &vwidth, &vheight) == 4) { state.ctm.a = (float) state.viewport_width / vwidth; state.ctm.d = (float) state.viewport_height / vheight; state.ctm.e += -min_x * state.ctm.a; state.ctm.f += -min_y * state.ctm.d; } free(s); dom_string_unref(view_box); } svgtiny_parse_transform_attributes(svg, &state); exc = dom_node_get_first_child(svg, (dom_node **) (void *) &child); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } while (child != NULL) { dom_element *next; dom_node_type nodetype; svgtiny_code code = svgtiny_OK; exc = dom_node_get_node_type(child, &nodetype); if (exc != DOM_NO_ERR) { dom_node_unref(child); return svgtiny_LIBDOM_ERROR; } if (nodetype == DOM_ELEMENT_NODE) { dom_string *nodename; exc = dom_node_get_node_name(child, &nodename); if (exc != DOM_NO_ERR) { dom_node_unref(child); svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (dom_string_caseless_isequal(state.interned_svg, nodename)) code = svgtiny_parse_svg(child, state); else if (dom_string_caseless_isequal(state.interned_g, nodename)) code = svgtiny_parse_svg(child, state); else if (dom_string_caseless_isequal(state.interned_a, nodename)) code = svgtiny_parse_svg(child, state); else if (dom_string_caseless_isequal(state.interned_path, nodename)) code = svgtiny_parse_path(child, state); else if (dom_string_caseless_isequal(state.interned_rect, nodename)) code = svgtiny_parse_rect(child, state); else if (dom_string_caseless_isequal(state.interned_circle, nodename)) code = svgtiny_parse_circle(child, state); else if (dom_string_caseless_isequal(state.interned_ellipse, nodename)) code = svgtiny_parse_ellipse(child, state); else if (dom_string_caseless_isequal(state.interned_line, nodename)) code = svgtiny_parse_line(child, state); else if (dom_string_caseless_isequal(state.interned_polyline, nodename)) code = svgtiny_parse_poly(child, state, false); else if (dom_string_caseless_isequal(state.interned_polygon, nodename)) code = svgtiny_parse_poly(child, state, true); else if (dom_string_caseless_isequal(state.interned_text, nodename)) code = svgtiny_parse_text(child, state); dom_string_unref(nodename); } if (code != svgtiny_OK) { dom_node_unref(child); svgtiny_cleanup_state_local(&state); return code; } exc = dom_node_get_next_sibling(child, (dom_node **) (void *) &next); dom_node_unref(child); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } child = next; } svgtiny_cleanup_state_local(&state); return svgtiny_OK; } /** * Parse a element node. * * http://www.w3.org/TR/SVG11/paths#PathElement */ svgtiny_code svgtiny_parse_path(dom_element *path, struct svgtiny_parse_state state) { svgtiny_code err; dom_string *path_d_str; dom_exception exc; char *s, *path_d; float *p; /* path elemets */ unsigned int palloc; /* number of path elements allocated */ unsigned int i; float last_x = 0, last_y = 0; float last_cubic_x = 0, last_cubic_y = 0; float last_quad_x = 0, last_quad_y = 0; float subpath_first_x = 0, subpath_first_y = 0; svgtiny_setup_state_local(&state); svgtiny_parse_paint_attributes(path, &state); svgtiny_parse_transform_attributes(path, &state); /* read d attribute */ exc = dom_element_get_attribute(path, state.interned_d, &path_d_str); if (exc != DOM_NO_ERR) { state.diagram->error_line = -1; /* path->line; */ state.diagram->error_message = "path: error retrieving d attribute"; svgtiny_cleanup_state_local(&state); return svgtiny_SVG_ERROR; } if (path_d_str == NULL) { state.diagram->error_line = -1; /* path->line; */ state.diagram->error_message = "path: missing d attribute"; svgtiny_cleanup_state_local(&state); return svgtiny_SVG_ERROR; } /* empty path is permitted it just disables the path */ palloc = dom_string_byte_length(path_d_str); if (palloc == 0) { dom_string_unref(path_d_str); svgtiny_cleanup_state_local(&state); return svgtiny_OK; } /* local copy of the path data allowing in-place modification */ s = path_d = strndup(dom_string_data(path_d_str), palloc); dom_string_unref(path_d_str); if (s == NULL) { svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; } /* ensure path element allocation is sensibly bounded */ if (palloc < 8) { palloc = 8; } else if (palloc > 64) { palloc = palloc / 2; } /* allocate initial space for path elements */ p = malloc(sizeof p[0] * palloc); if (p == NULL) { free(path_d); svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; } /* parse d and build path */ for (i = 0; s[i]; i++) if (s[i] == ',') s[i] = ' '; i = 0; while (*s) { char command[2]; int plot_command; float x, y, x1, y1, x2, y2, rx, ry, rotation, large_arc, sweep; int n; /* Ensure there is sufficient space for path elements */ #define ALLOC_PATH_ELEMENTS(NUM_ELEMENTS) \ do { \ if ((palloc - i) < NUM_ELEMENTS) { \ float *tp; \ palloc = (palloc * 2) + (palloc / 2); \ tp = realloc(p, sizeof p[0] * palloc); \ if (tp == NULL) { \ free(p); \ free(path_d); \ svgtiny_cleanup_state_local(&state); \ return svgtiny_OUT_OF_MEMORY; \ } \ p = tp; \ } \ } while(0) /* moveto (M, m), lineto (L, l) (2 arguments) */ if (sscanf(s, " %1[MmLl] %f %f %n", command, &x, &y, &n) == 3) { /*LOG(("moveto or lineto"));*/ if (*command == 'M' || *command == 'm') plot_command = svgtiny_PATH_MOVE; else plot_command = svgtiny_PATH_LINE; do { ALLOC_PATH_ELEMENTS(3); p[i++] = plot_command; if ('a' <= *command) { x += last_x; y += last_y; } if (plot_command == svgtiny_PATH_MOVE) { subpath_first_x = x; subpath_first_y = y; } p[i++] = last_cubic_x = last_quad_x = last_x = x; p[i++] = last_cubic_y = last_quad_y = last_y = y; s += n; plot_command = svgtiny_PATH_LINE; } while (sscanf(s, "%f %f %n", &x, &y, &n) == 2); /* closepath (Z, z) (no arguments) */ } else if (sscanf(s, " %1[Zz] %n", command, &n) == 1) { /*LOG(("closepath"));*/ ALLOC_PATH_ELEMENTS(1); p[i++] = svgtiny_PATH_CLOSE; s += n; last_cubic_x = last_quad_x = last_x = subpath_first_x; last_cubic_y = last_quad_y = last_y = subpath_first_y; /* horizontal lineto (H, h) (1 argument) */ } else if (sscanf(s, " %1[Hh] %f %n", command, &x, &n) == 2) { /*LOG(("horizontal lineto"));*/ do { ALLOC_PATH_ELEMENTS(3); p[i++] = svgtiny_PATH_LINE; if (*command == 'h') x += last_x; p[i++] = last_cubic_x = last_quad_x = last_x = x; p[i++] = last_cubic_y = last_quad_y = last_y; s += n; } while (sscanf(s, "%f %n", &x, &n) == 1); /* vertical lineto (V, v) (1 argument) */ } else if (sscanf(s, " %1[Vv] %f %n", command, &y, &n) == 2) { /*LOG(("vertical lineto"));*/ do { ALLOC_PATH_ELEMENTS(3); p[i++] = svgtiny_PATH_LINE; if (*command == 'v') y += last_y; p[i++] = last_cubic_x = last_quad_x = last_x; p[i++] = last_cubic_y = last_quad_y = last_y = y; s += n; } while (sscanf(s, "%f %n", &y, &n) == 1); /* curveto (C, c) (6 arguments) */ } else if (sscanf(s, " %1[Cc] %f %f %f %f %f %f %n", command, &x1, &y1, &x2, &y2, &x, &y, &n) == 7) { /*LOG(("curveto"));*/ do { ALLOC_PATH_ELEMENTS(7); p[i++] = svgtiny_PATH_BEZIER; if (*command == 'c') { x1 += last_x; y1 += last_y; x2 += last_x; y2 += last_y; x += last_x; y += last_y; } p[i++] = x1; p[i++] = y1; p[i++] = last_cubic_x = x2; p[i++] = last_cubic_y = y2; p[i++] = last_quad_x = last_x = x; p[i++] = last_quad_y = last_y = y; s += n; } while (sscanf(s, "%f %f %f %f %f %f %n", &x1, &y1, &x2, &y2, &x, &y, &n) == 6); /* shorthand/smooth curveto (S, s) (4 arguments) */ } else if (sscanf(s, " %1[Ss] %f %f %f %f %n", command, &x2, &y2, &x, &y, &n) == 5) { /*LOG(("shorthand/smooth curveto"));*/ do { ALLOC_PATH_ELEMENTS(7); p[i++] = svgtiny_PATH_BEZIER; x1 = last_x + (last_x - last_cubic_x); y1 = last_y + (last_y - last_cubic_y); if (*command == 's') { x2 += last_x; y2 += last_y; x += last_x; y += last_y; } p[i++] = x1; p[i++] = y1; p[i++] = last_cubic_x = x2; p[i++] = last_cubic_y = y2; p[i++] = last_quad_x = last_x = x; p[i++] = last_quad_y = last_y = y; s += n; } while (sscanf(s, "%f %f %f %f %n", &x2, &y2, &x, &y, &n) == 4); /* quadratic Bezier curveto (Q, q) (4 arguments) */ } else if (sscanf(s, " %1[Qq] %f %f %f %f %n", command, &x1, &y1, &x, &y, &n) == 5) { /*LOG(("quadratic Bezier curveto"));*/ do { ALLOC_PATH_ELEMENTS(7); p[i++] = svgtiny_PATH_BEZIER; last_quad_x = x1; last_quad_y = y1; if (*command == 'q') { x1 += last_x; y1 += last_y; x += last_x; y += last_y; } p[i++] = 1./3 * last_x + 2./3 * x1; p[i++] = 1./3 * last_y + 2./3 * y1; p[i++] = 2./3 * x1 + 1./3 * x; p[i++] = 2./3 * y1 + 1./3 * y; p[i++] = last_cubic_x = last_x = x; p[i++] = last_cubic_y = last_y = y; s += n; } while (sscanf(s, "%f %f %f %f %n", &x1, &y1, &x, &y, &n) == 4); /* shorthand/smooth quadratic Bezier curveto (T, t) (2 arguments) */ } else if (sscanf(s, " %1[Tt] %f %f %n", command, &x, &y, &n) == 3) { /*LOG(("shorthand/smooth quadratic Bezier curveto"));*/ do { ALLOC_PATH_ELEMENTS(7); p[i++] = svgtiny_PATH_BEZIER; x1 = last_x + (last_x - last_quad_x); y1 = last_y + (last_y - last_quad_y); last_quad_x = x1; last_quad_y = y1; if (*command == 't') { x1 += last_x; y1 += last_y; x += last_x; y += last_y; } p[i++] = 1./3 * last_x + 2./3 * x1; p[i++] = 1./3 * last_y + 2./3 * y1; p[i++] = 2./3 * x1 + 1./3 * x; p[i++] = 2./3 * y1 + 1./3 * y; p[i++] = last_cubic_x = last_x = x; p[i++] = last_cubic_y = last_y = y; s += n; } while (sscanf(s, "%f %f %n", &x, &y, &n) == 2); /* elliptical arc (A, a) (7 arguments) */ } else if (sscanf(s, " %1[Aa] %f %f %f %f %f %f %f %n", command, &rx, &ry, &rotation, &large_arc, &sweep, &x, &y, &n) == 8) { do { int bzsegments; double bzpoints[6*4]; /* allow for up to four bezier segments per arc */ if (*command == 'a') { x += last_x; y += last_y; } bzsegments = svgarc_to_bezier(last_x, last_y, x, y, rx, ry, rotation, large_arc, sweep, bzpoints); if (bzsegments == -1) { /* draw a line */ ALLOC_PATH_ELEMENTS(3); p[i++] = svgtiny_PATH_LINE; p[i++] = x; p[i++] = y; } else if (bzsegments > 0) { int bzpnt; ALLOC_PATH_ELEMENTS((unsigned int)bzsegments * 7); for (bzpnt = 0;bzpnt < (bzsegments * 6); bzpnt+=6) { p[i++] = svgtiny_PATH_BEZIER; p[i++] = bzpoints[bzpnt]; p[i++] = bzpoints[bzpnt+1]; p[i++] = bzpoints[bzpnt+2]; p[i++] = bzpoints[bzpnt+3]; p[i++] = bzpoints[bzpnt+4]; p[i++] = bzpoints[bzpnt+5]; } } if (bzsegments != 0) { last_cubic_x = last_quad_x = last_x = p[i-2]; last_cubic_y = last_quad_y = last_y = p[i-1]; } s += n; } while (sscanf(s, "%f %f %f %f %f %f %f %n", &rx, &ry, &rotation, &large_arc, &sweep, &x, &y, &n) == 7); } else { /* fprintf(stderr, "parse failed at \"%s\"\n", s); */ break; } } free(path_d); if (i <= 4) { /* no real segments in path */ free(p); svgtiny_cleanup_state_local(&state); return svgtiny_OK; } /* resize path element array to not be over allocated */ if (palloc != i) { float *tp; /* try the resize, if it fails just continue to use the old * allocation */ tp = realloc(p, sizeof p[0] * i); if (tp != NULL) { p = tp; } } err = svgtiny_add_path(p, i, &state); svgtiny_cleanup_state_local(&state); return err; } /** * Parse a element node. * * http://www.w3.org/TR/SVG11/shapes#RectElement */ svgtiny_code svgtiny_parse_rect(dom_element *rect, struct svgtiny_parse_state state) { svgtiny_code err; float x, y, width, height; float *p; svgtiny_setup_state_local(&state); svgtiny_parse_position_attributes(rect, state, &x, &y, &width, &height); svgtiny_parse_paint_attributes(rect, &state); svgtiny_parse_transform_attributes(rect, &state); p = malloc(13 * sizeof p[0]); if (!p) { svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; } p[0] = svgtiny_PATH_MOVE; p[1] = x; p[2] = y; p[3] = svgtiny_PATH_LINE; p[4] = x + width; p[5] = y; p[6] = svgtiny_PATH_LINE; p[7] = x + width; p[8] = y + height; p[9] = svgtiny_PATH_LINE; p[10] = x; p[11] = y + height; p[12] = svgtiny_PATH_CLOSE; err = svgtiny_add_path(p, 13, &state); svgtiny_cleanup_state_local(&state); return err; } /** * Parse a element node. */ svgtiny_code svgtiny_parse_circle(dom_element *circle, struct svgtiny_parse_state state) { svgtiny_code err; float x = 0, y = 0, r = -1; float *p; dom_string *attr; dom_exception exc; svgtiny_setup_state_local(&state); exc = dom_element_get_attribute(circle, state.interned_cx, &attr); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (attr != NULL) { x = svgtiny_parse_length(attr, state.viewport_width, state); } dom_string_unref(attr); exc = dom_element_get_attribute(circle, state.interned_cy, &attr); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (attr != NULL) { y = svgtiny_parse_length(attr, state.viewport_height, state); } dom_string_unref(attr); exc = dom_element_get_attribute(circle, state.interned_r, &attr); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (attr != NULL) { r = svgtiny_parse_length(attr, state.viewport_width, state); } dom_string_unref(attr); svgtiny_parse_paint_attributes(circle, &state); svgtiny_parse_transform_attributes(circle, &state); if (r < 0) { state.diagram->error_line = -1; /* circle->line; */ state.diagram->error_message = "circle: r missing or negative"; svgtiny_cleanup_state_local(&state); return svgtiny_SVG_ERROR; } if (r == 0) { svgtiny_cleanup_state_local(&state); return svgtiny_OK; } p = malloc(32 * sizeof p[0]); if (!p) { svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; } p[0] = svgtiny_PATH_MOVE; p[1] = x + r; p[2] = y; p[3] = svgtiny_PATH_BEZIER; p[4] = x + r; p[5] = y + r * KAPPA; p[6] = x + r * KAPPA; p[7] = y + r; p[8] = x; p[9] = y + r; p[10] = svgtiny_PATH_BEZIER; p[11] = x - r * KAPPA; p[12] = y + r; p[13] = x - r; p[14] = y + r * KAPPA; p[15] = x - r; p[16] = y; p[17] = svgtiny_PATH_BEZIER; p[18] = x - r; p[19] = y - r * KAPPA; p[20] = x - r * KAPPA; p[21] = y - r; p[22] = x; p[23] = y - r; p[24] = svgtiny_PATH_BEZIER; p[25] = x + r * KAPPA; p[26] = y - r; p[27] = x + r; p[28] = y - r * KAPPA; p[29] = x + r; p[30] = y; p[31] = svgtiny_PATH_CLOSE; err = svgtiny_add_path(p, 32, &state); svgtiny_cleanup_state_local(&state); return err; } /** * Parse an element node. */ svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse, struct svgtiny_parse_state state) { svgtiny_code err; float x = 0, y = 0, rx = -1, ry = -1; float *p; dom_string *attr; dom_exception exc; svgtiny_setup_state_local(&state); exc = dom_element_get_attribute(ellipse, state.interned_cx, &attr); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (attr != NULL) { x = svgtiny_parse_length(attr, state.viewport_width, state); } dom_string_unref(attr); exc = dom_element_get_attribute(ellipse, state.interned_cy, &attr); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (attr != NULL) { y = svgtiny_parse_length(attr, state.viewport_height, state); } dom_string_unref(attr); exc = dom_element_get_attribute(ellipse, state.interned_rx, &attr); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (attr != NULL) { rx = svgtiny_parse_length(attr, state.viewport_width, state); } dom_string_unref(attr); exc = dom_element_get_attribute(ellipse, state.interned_ry, &attr); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (attr != NULL) { ry = svgtiny_parse_length(attr, state.viewport_width, state); } dom_string_unref(attr); svgtiny_parse_paint_attributes(ellipse, &state); svgtiny_parse_transform_attributes(ellipse, &state); if (rx < 0 || ry < 0) { state.diagram->error_line = -1; /* ellipse->line; */ state.diagram->error_message = "ellipse: rx or ry missing " "or negative"; svgtiny_cleanup_state_local(&state); return svgtiny_SVG_ERROR; } if (rx == 0 || ry == 0) { svgtiny_cleanup_state_local(&state); return svgtiny_OK; } p = malloc(32 * sizeof p[0]); if (!p) { svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; } p[0] = svgtiny_PATH_MOVE; p[1] = x + rx; p[2] = y; p[3] = svgtiny_PATH_BEZIER; p[4] = x + rx; p[5] = y + ry * KAPPA; p[6] = x + rx * KAPPA; p[7] = y + ry; p[8] = x; p[9] = y + ry; p[10] = svgtiny_PATH_BEZIER; p[11] = x - rx * KAPPA; p[12] = y + ry; p[13] = x - rx; p[14] = y + ry * KAPPA; p[15] = x - rx; p[16] = y; p[17] = svgtiny_PATH_BEZIER; p[18] = x - rx; p[19] = y - ry * KAPPA; p[20] = x - rx * KAPPA; p[21] = y - ry; p[22] = x; p[23] = y - ry; p[24] = svgtiny_PATH_BEZIER; p[25] = x + rx * KAPPA; p[26] = y - ry; p[27] = x + rx; p[28] = y - ry * KAPPA; p[29] = x + rx; p[30] = y; p[31] = svgtiny_PATH_CLOSE; err = svgtiny_add_path(p, 32, &state); svgtiny_cleanup_state_local(&state); return err; } /** * Parse a element node. */ svgtiny_code svgtiny_parse_line(dom_element *line, struct svgtiny_parse_state state) { svgtiny_code err; float x1 = 0, y1 = 0, x2 = 0, y2 = 0; float *p; dom_string *attr; dom_exception exc; svgtiny_setup_state_local(&state); exc = dom_element_get_attribute(line, state.interned_x1, &attr); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (attr != NULL) { x1 = svgtiny_parse_length(attr, state.viewport_width, state); } dom_string_unref(attr); exc = dom_element_get_attribute(line, state.interned_y1, &attr); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (attr != NULL) { y1 = svgtiny_parse_length(attr, state.viewport_height, state); } dom_string_unref(attr); exc = dom_element_get_attribute(line, state.interned_x2, &attr); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (attr != NULL) { x2 = svgtiny_parse_length(attr, state.viewport_width, state); } dom_string_unref(attr); exc = dom_element_get_attribute(line, state.interned_y2, &attr); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (attr != NULL) { y2 = svgtiny_parse_length(attr, state.viewport_height, state); } dom_string_unref(attr); svgtiny_parse_paint_attributes(line, &state); svgtiny_parse_transform_attributes(line, &state); p = malloc(7 * sizeof p[0]); if (!p) { svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; } p[0] = svgtiny_PATH_MOVE; p[1] = x1; p[2] = y1; p[3] = svgtiny_PATH_LINE; p[4] = x2; p[5] = y2; p[6] = svgtiny_PATH_CLOSE; err = svgtiny_add_path(p, 7, &state); svgtiny_cleanup_state_local(&state); return err; } /** * Parse a or element node. * * http://www.w3.org/TR/SVG11/shapes#PolylineElement * http://www.w3.org/TR/SVG11/shapes#PolygonElement */ svgtiny_code svgtiny_parse_poly(dom_element *poly, struct svgtiny_parse_state state, bool polygon) { svgtiny_code err; dom_string *points_str; dom_exception exc; char *s, *points; float *p; unsigned int i; svgtiny_setup_state_local(&state); svgtiny_parse_paint_attributes(poly, &state); svgtiny_parse_transform_attributes(poly, &state); exc = dom_element_get_attribute(poly, state.interned_points, &points_str); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (points_str == NULL) { state.diagram->error_line = -1; /* poly->line; */ state.diagram->error_message = "polyline/polygon: missing points attribute"; svgtiny_cleanup_state_local(&state); return svgtiny_SVG_ERROR; } s = points = strndup(dom_string_data(points_str), dom_string_byte_length(points_str)); dom_string_unref(points_str); /* read points attribute */ if (s == NULL) { svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; } /* allocate space for path: it will never have more elements than s */ p = malloc(sizeof p[0] * strlen(s)); if (!p) { free(points); svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; } /* parse s and build path */ for (i = 0; s[i]; i++) if (s[i] == ',') s[i] = ' '; i = 0; while (*s) { float x, y; int n; if (sscanf(s, "%f %f %n", &x, &y, &n) == 2) { if (i == 0) p[i++] = svgtiny_PATH_MOVE; else p[i++] = svgtiny_PATH_LINE; p[i++] = x; p[i++] = y; s += n; } else { break; } } if (polygon) p[i++] = svgtiny_PATH_CLOSE; free(points); err = svgtiny_add_path(p, i, &state); svgtiny_cleanup_state_local(&state); return err; } /** * Parse a or element node. */ svgtiny_code svgtiny_parse_text(dom_element *text, struct svgtiny_parse_state state) { float x, y, width, height; float px, py; dom_node *child; dom_exception exc; svgtiny_setup_state_local(&state); svgtiny_parse_position_attributes(text, state, &x, &y, &width, &height); svgtiny_parse_font_attributes(text, &state); svgtiny_parse_transform_attributes(text, &state); px = state.ctm.a * x + state.ctm.c * y + state.ctm.e; py = 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;*/ exc = dom_node_get_first_child(text, &child); if (exc != DOM_NO_ERR) { return svgtiny_LIBDOM_ERROR; svgtiny_cleanup_state_local(&state); } while (child != NULL) { dom_node *next; dom_node_type nodetype; svgtiny_code code = svgtiny_OK; exc = dom_node_get_node_type(child, &nodetype); if (exc != DOM_NO_ERR) { dom_node_unref(child); svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (nodetype == DOM_ELEMENT_NODE) { dom_string *nodename; exc = dom_node_get_node_name(child, &nodename); if (exc != DOM_NO_ERR) { dom_node_unref(child); svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (dom_string_caseless_isequal(nodename, state.interned_tspan)) code = svgtiny_parse_text((dom_element *)child, state); dom_string_unref(nodename); } else if (nodetype == DOM_TEXT_NODE) { struct svgtiny_shape *shape = svgtiny_add_shape(&state); dom_string *content; if (shape == NULL) { dom_node_unref(child); svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; } exc = dom_text_get_whole_text(child, &content); if (exc != DOM_NO_ERR) { dom_node_unref(child); svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } if (content != NULL) { shape->text = strndup(dom_string_data(content), dom_string_byte_length(content)); dom_string_unref(content); } else { shape->text = strdup(""); } shape->text_x = px; shape->text_y = py; state.diagram->shape_count++; } if (code != svgtiny_OK) { dom_node_unref(child); svgtiny_cleanup_state_local(&state); return code; } exc = dom_node_get_next_sibling(child, &next); dom_node_unref(child); if (exc != DOM_NO_ERR) { svgtiny_cleanup_state_local(&state); return svgtiny_LIBDOM_ERROR; } child = next; } svgtiny_cleanup_state_local(&state); return svgtiny_OK; } /** * Parse x, y, width, and height attributes, if present. */ void svgtiny_parse_position_attributes(dom_element *node, const struct svgtiny_parse_state state, float *x, float *y, float *width, float *height) { dom_string *attr; dom_exception exc; *x = 0; *y = 0; *width = state.viewport_width; *height = state.viewport_height; exc = dom_element_get_attribute(node, state.interned_x, &attr); if (exc == DOM_NO_ERR && attr != NULL) { *x = svgtiny_parse_length(attr, state.viewport_width, state); dom_string_unref(attr); } exc = dom_element_get_attribute(node, state.interned_y, &attr); if (exc == DOM_NO_ERR && attr != NULL) { *y = svgtiny_parse_length(attr, state.viewport_height, state); dom_string_unref(attr); } exc = dom_element_get_attribute(node, state.interned_width, &attr); if (exc == DOM_NO_ERR && attr != NULL) { *width = svgtiny_parse_length(attr, state.viewport_width, state); dom_string_unref(attr); } exc = dom_element_get_attribute(node, state.interned_height, &attr); if (exc == DOM_NO_ERR && attr != NULL) { *height = svgtiny_parse_length(attr, state.viewport_height, state); dom_string_unref(attr); } } /** * Parse a length as a number of pixels. */ static float _svgtiny_parse_length(const char *s, int viewport_size, const struct svgtiny_parse_state state) { int num_length = strspn(s, "0123456789+-."); const char *unit = s + num_length; float n = atof((const char *) s); float font_size = 20; /*css_len2px(&state.style.font_size.value.length, 0);*/ UNUSED(state); 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; } float svgtiny_parse_length(dom_string *s, int viewport_size, const struct svgtiny_parse_state state) { char *ss = strndup(dom_string_data(s), dom_string_byte_length(s)); float ret = _svgtiny_parse_length(ss, viewport_size, state); free(ss); return ret; } /** * Parse paint attributes, if present. */ void svgtiny_parse_paint_attributes(dom_element *node, struct svgtiny_parse_state *state) { dom_string *attr; dom_exception exc; exc = dom_element_get_attribute(node, state->interned_fill, &attr); if (exc == DOM_NO_ERR && attr != NULL) { svgtiny_parse_color(attr, &state->fill, &state->fill_grad, state); dom_string_unref(attr); } exc = dom_element_get_attribute(node, state->interned_stroke, &attr); if (exc == DOM_NO_ERR && attr != NULL) { svgtiny_parse_color(attr, &state->stroke, &state->stroke_grad, state); dom_string_unref(attr); } exc = dom_element_get_attribute(node, state->interned_stroke_width, &attr); if (exc == DOM_NO_ERR && attr != NULL) { state->stroke_width = svgtiny_parse_length(attr, state->viewport_width, *state); dom_string_unref(attr); } exc = dom_element_get_attribute(node, state->interned_style, &attr); if (exc == DOM_NO_ERR && attr != NULL) { char *style = strndup(dom_string_data(attr), dom_string_byte_length(attr)); const char *s; char *value; if ((s = strstr(style, "fill:"))) { s += 5; while (*s == ' ') s++; value = strndup(s, strcspn(s, "; ")); _svgtiny_parse_color(value, &state->fill, &state->fill_grad, state); free(value); } if ((s = strstr(style, "stroke:"))) { s += 7; while (*s == ' ') s++; value = strndup(s, strcspn(s, "; ")); _svgtiny_parse_color(value, &state->stroke, &state->stroke_grad, state); free(value); } if ((s = strstr(style, "stroke-width:"))) { s += 13; while (*s == ' ') s++; value = strndup(s, strcspn(s, "; ")); state->stroke_width = _svgtiny_parse_length(value, state->viewport_width, *state); free(value); } free(style); dom_string_unref(attr); } } /** * Parse a colour. */ static void _svgtiny_parse_color(const char *s, svgtiny_colour *c, struct svgtiny_parse_state_gradient *grad, struct svgtiny_parse_state *state) { unsigned int r, g, b; float rf, gf, bf; size_t len = strlen(s); char *id = 0, *rparen; if (len == 4 && s[0] == '#') { if (sscanf(s + 1, "%1x%1x%1x", &r, &g, &b) == 3) *c = svgtiny_RGB(r | r << 4, g | g << 4, b | b << 4); } else if (len == 7 && s[0] == '#') { if (sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3) *c = svgtiny_RGB(r, g, b); } else if (10 <= len && s[0] == 'r' && s[1] == 'g' && s[2] == 'b' && s[3] == '(' && s[len - 1] == ')') { if (sscanf(s + 4, "%u,%u,%u", &r, &g, &b) == 3) *c = svgtiny_RGB(r, g, b); 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 = svgtiny_RGB(r, g, b); } } else if (len == 4 && strcmp(s, "none") == 0) { *c = svgtiny_TRANSPARENT; } else if (5 < len && s[0] == 'u' && s[1] == 'r' && s[2] == 'l' && s[3] == '(') { if (grad == NULL) { *c = svgtiny_RGB(0, 0, 0); } else if (s[4] == '#') { id = strdup(s + 5); if (!id) return; rparen = strchr(id, ')'); if (rparen) *rparen = 0; svgtiny_find_gradient(id, grad, state); free(id); if (grad->linear_gradient_stop_count == 0) *c = svgtiny_TRANSPARENT; else if (grad->linear_gradient_stop_count == 1) *c = grad->gradient_stop[0].color; else *c = svgtiny_LINEAR_GRADIENT; } } else { const struct svgtiny_named_color *named_color; named_color = svgtiny_color_lookup(s, strlen(s)); if (named_color) *c = named_color->color; } } void svgtiny_parse_color(dom_string *s, svgtiny_colour *c, struct svgtiny_parse_state_gradient *grad, struct svgtiny_parse_state *state) { dom_string_ref(s); _svgtiny_parse_color(dom_string_data(s), c, grad, state); dom_string_unref(s); } /** * Parse font attributes, if present. */ void svgtiny_parse_font_attributes(dom_element *node, struct svgtiny_parse_state *state) { /* TODO: Implement this, it never used to be */ UNUSED(node); UNUSED(state); #ifdef WRITTEN_THIS_PROPERLY const xmlAttr *attr; UNUSED(state); for (attr = node->properties; attr; attr = attr->next) { if (strcmp((const char *) 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; }*/ } } #endif } /** * Parse transform attributes, if present. * * http://www.w3.org/TR/SVG11/coords#TransformAttribute */ void svgtiny_parse_transform_attributes(dom_element *node, struct svgtiny_parse_state *state) { char *transform; dom_string *attr; dom_exception exc; exc = dom_element_get_attribute(node, state->interned_transform, &attr); if (exc == DOM_NO_ERR && attr != NULL) { transform = strndup(dom_string_data(attr), dom_string_byte_length(attr)); svgtiny_parse_transform(transform, &state->ctm.a, &state->ctm.b, &state->ctm.c, &state->ctm.d, &state->ctm.e, &state->ctm.f); free(transform); dom_string_unref(attr); } } /** * Parse a transform string. */ void svgtiny_parse_transform(char *s, float *ma, float *mb, float *mc, float *md, float *me, float *mf) { float a, b, c, d, e, f; float za, zb, zc, zd, ze, zf; float angle, x, y; int n; unsigned int i; for (i = 0; s[i]; i++) if (s[i] == ',') s[i] = ' '; while (*s) { a = d = 1; b = c = 0; e = f = 0; n = 0; if ((sscanf(s, " matrix (%f %f %f %f %f %f ) %n", &a, &b, &c, &d, &e, &f, &n) == 6) && (n > 0)) ; else if ((sscanf(s, " translate (%f %f ) %n", &e, &f, &n) == 2) && (n > 0)) ; else if ((sscanf(s, " translate (%f ) %n", &e, &n) == 1) && (n > 0)) ; else if ((sscanf(s, " scale (%f %f ) %n", &a, &d, &n) == 2) && (n > 0)) ; else if ((sscanf(s, " scale (%f ) %n", &a, &n) == 1) && (n > 0)) d = a; else if ((sscanf(s, " rotate (%f %f %f ) %n", &angle, &x, &y, &n) == 3) && (n > 0)) { angle = angle / 180 * M_PI; a = cos(angle); b = sin(angle); c = -sin(angle); d = cos(angle); e = -x * cos(angle) + y * sin(angle) + x; f = -x * sin(angle) - y * cos(angle) + y; } else if ((sscanf(s, " rotate (%f ) %n", &angle, &n) == 1) && (n > 0)) { angle = angle / 180 * M_PI; a = cos(angle); b = sin(angle); c = -sin(angle); d = cos(angle); } else if ((sscanf(s, " skewX (%f ) %n", &angle, &n) == 1) && (n > 0)) { angle = angle / 180 * M_PI; c = tan(angle); } else if ((sscanf(s, " skewY (%f ) %n", &angle, &n) == 1) && (n > 0)) { angle = angle / 180 * M_PI; b = tan(angle); } else break; za = *ma * a + *mc * b; zb = *mb * a + *md * b; zc = *ma * c + *mc * d; zd = *mb * c + *md * d; ze = *ma * e + *mc * f + *me; zf = *mb * e + *md * f + *mf; *ma = za; *mb = zb; *mc = zc; *md = zd; *me = ze; *mf = zf; s += n; } } /** * Add a path to the svgtiny_diagram. */ svgtiny_code svgtiny_add_path(float *p, unsigned int n, struct svgtiny_parse_state *state) { struct svgtiny_shape *shape; if (state->fill == svgtiny_LINEAR_GRADIENT) return svgtiny_add_path_linear_gradient(p, n, state); svgtiny_transform_path(p, n, state); shape = svgtiny_add_shape(state); if (!shape) { free(p); return svgtiny_OUT_OF_MEMORY; } shape->path = p; shape->path_length = n; state->diagram->shape_count++; return svgtiny_OK; } /** * Add a svgtiny_shape to the svgtiny_diagram. */ struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state) { struct svgtiny_shape *shape = realloc(state->diagram->shape, (state->diagram->shape_count + 1) * sizeof (state->diagram->shape[0])); if (!shape) return 0; state->diagram->shape = shape; shape += state->diagram->shape_count; shape->path = 0; shape->path_length = 0; shape->text = 0; shape->fill = state->fill; shape->stroke = state->stroke; shape->stroke_width = lroundf((float) state->stroke_width * (state->ctm.a + state->ctm.d) / 2.0); if (0 < state->stroke_width && shape->stroke_width == 0) shape->stroke_width = 1; return shape; } /** * Apply the current transformation matrix to a path. */ void svgtiny_transform_path(float *p, unsigned int n, struct svgtiny_parse_state *state) { unsigned int j; for (j = 0; j != n; ) { unsigned int points = 0; unsigned int k; switch ((int) p[j]) { case svgtiny_PATH_MOVE: case svgtiny_PATH_LINE: points = 1; break; case svgtiny_PATH_CLOSE: points = 0; break; case svgtiny_PATH_BEZIER: points = 3; break; default: assert(0); } j++; for (k = 0; k != points; k++) { float x0 = p[j], y0 = p[j + 1]; float x = state->ctm.a * x0 + state->ctm.c * y0 + state->ctm.e; float y = state->ctm.b * x0 + state->ctm.d * y0 + state->ctm.f; p[j] = x; p[j + 1] = y; j += 2; } } } /** * Free all memory used by a diagram. */ void svgtiny_free(struct svgtiny_diagram *svg) { unsigned int i; assert(svg); for (i = 0; i != svg->shape_count; i++) { free(svg->shape[i].path); free(svg->shape[i].text); } free(svg->shape); free(svg); } #ifndef HAVE_STRNDUP char *svgtiny_strndup(const char *s, size_t n) { size_t len; char *s2; for (len = 0; len != n && s[len]; len++) continue; s2 = malloc(len + 1); if (s2 == NULL) return NULL; memcpy(s2, s, len); s2[len] = '\0'; return s2; } #endif