/* * 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 #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_style_element(dom_element *style, struct svgtiny_parse_state state); static css_stylesheet *svgtiny_parse_style_inline(const uint8_t *data, size_t len); static svgtiny_code svgtiny_preparse_styles(dom_element *svg, struct svgtiny_parse_state state); 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 css_select_results *svgtiny_parse_styles(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) { css_error css_code; 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 = { /* Initialize the unit context here because it has a * const member and doing it any other way subverts * the type system. The magic numbers below were taken * from the libcss example program without much * thought, because at the moment we don't support any * properties with units. */ .unit_ctx = { .font_size_default = FLTTOFIX(16.0), .font_size_minimum = FLTTOFIX(6.0), .device_dpi = FLTTOFIX(96.0), .root_style = NULL, .pw = NULL, .measure = NULL, } }; 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_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); /* initialize the state struct with zeros */ memset(&state, 0, sizeof(state)); /* get graphic dimensions */ state.diagram = diagram; state.document = document; state.viewport_width = viewport_width; state.viewport_height = viewport_height; /* Initialize CSS context */ css_code = css_select_ctx_create(&state.select_ctx); if (css_code != CSS_OK) { dom_node_unref(svg); dom_node_unref(document); return svgtiny_LIBCSS_ERROR; } /* ...and the unit context, whose other fields were * initialized along with the parser state itself */ state.unit_ctx.viewport_width = FLTTOFIX(viewport_width); state.unit_ctx.viewport_height = FLTTOFIX(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 /* Intern SVG's xmlns separately because it's an lwc_string * and not a dom_string. We initialize its pointer to NULL * because the "cleanup:" test to see if it needs to be free'd * looks for NULL. Returning a LIBDOM_ERROR on failure is not * perfect but it's the closest of the available options. */ state.interned_svg_xmlns = NULL; if (lwc_intern_string("http://www.w3.org/2000/svg", 26, &state.interned_svg_xmlns) != lwc_error_ok) { code = svgtiny_LIBDOM_ERROR; goto cleanup; } 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.fill = 0x000000; state.stroke = svgtiny_TRANSPARENT; state.stroke_width = 1; /* parse tree */ code = svgtiny_preparse_styles(svg, state); if (code == svgtiny_OK) { code = svgtiny_parse_svg(svg, state); } dom_node_unref(svg); dom_node_unref(document); css_code = css_select_ctx_destroy(state.select_ctx); if (css_code != CSS_OK) { code = svgtiny_LIBCSS_ERROR; } 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 if (state.interned_svg_xmlns != NULL) { lwc_string_unref(state.interned_svg_xmlns); } return code; } /** * Parse a single