X-Git-Url: https://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=src%2Fsvgtiny.c;h=ee0c59c1eff64a7fa434d22b25d5d1f9dcf7c11f;hb=f66051cab457438eefd23e1e2c6e2197894b2d52;hp=ac13d7a5e2ee552fcd63adb580b4ddd26d7a00b1;hpb=d03a7471001c4701e1fea976aee162b0de375f52;p=libsvgtiny.git diff --git a/src/svgtiny.c b/src/svgtiny.c index ac13d7a..ee0c59c 100644 --- a/src/svgtiny.c +++ b/src/svgtiny.c @@ -20,39 +20,562 @@ #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 -static svgtiny_code svgtiny_parse_svg(dom_node *svg, +#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_node *path, +static svgtiny_code svgtiny_parse_path(dom_element *path, struct svgtiny_parse_state state); -static svgtiny_code svgtiny_parse_rect(dom_node *rect, +static svgtiny_code svgtiny_parse_rect(dom_element *rect, struct svgtiny_parse_state state); -static svgtiny_code svgtiny_parse_circle(dom_node *circle, +static svgtiny_code svgtiny_parse_circle(dom_element *circle, struct svgtiny_parse_state state); -static svgtiny_code svgtiny_parse_ellipse(dom_node *ellipse, +static svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse, struct svgtiny_parse_state state); -static svgtiny_code svgtiny_parse_line(dom_node *line, +static svgtiny_code svgtiny_parse_line(dom_element *line, struct svgtiny_parse_state state); -static svgtiny_code svgtiny_parse_poly(dom_node *poly, +static svgtiny_code svgtiny_parse_poly(dom_element *poly, struct svgtiny_parse_state state, bool polygon); -static svgtiny_code svgtiny_parse_text(dom_node *text, +static svgtiny_code svgtiny_parse_text(dom_element *text, struct svgtiny_parse_state state); -static void svgtiny_parse_position_attributes(const dom_node *node, +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(const dom_node *node, +static void svgtiny_parse_paint_attributes(dom_element *node, struct svgtiny_parse_state *state); -static void svgtiny_parse_font_attributes(const dom_node *node, +static void svgtiny_parse_font_attributes(dom_element *node, struct svgtiny_parse_state *state); -static void svgtiny_parse_transform_attributes(dom_node *node, +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)); +} /** @@ -63,16 +586,13 @@ struct svgtiny_diagram *svgtiny_create(void) { struct svgtiny_diagram *diagram; - diagram = malloc(sizeof *diagram); + diagram = calloc(sizeof(*diagram), 1); if (!diagram) return 0; - diagram->shape = 0; - diagram->shape_count = 0; - diagram->error_line = 0; - diagram->error_message = 0; - return diagram; + free(diagram); + return NULL; } static void ignore_msg(uint32_t severity, void *ctx, const char *msg, ...) @@ -94,7 +614,7 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, dom_exception exc; dom_xml_parser *parser; dom_xml_error err; - dom_node *svg; + dom_element *svg; dom_string *svg_name; lwc_string *svg_name_lwc; struct svgtiny_parse_state state; @@ -138,6 +658,12 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, 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); @@ -163,10 +689,22 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, 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; @@ -185,7 +723,6 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, state.fill = 0x000000; state.stroke = svgtiny_TRANSPARENT; state.stroke_width = 1; - state.linear_gradient_stop_count = 0; /* parse tree */ code = svgtiny_parse_svg(svg, state); @@ -193,6 +730,13 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, 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; } @@ -201,21 +745,30 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, * Parse a or element node. */ -svgtiny_code svgtiny_parse_svg(dom_node *svg, +svgtiny_code svgtiny_parse_svg(dom_element *svg, struct svgtiny_parse_state state) { float x, y, width, height; - xmlAttr *view_box; - dom_node *child; + 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); - /* parse viewBox */ - view_box = xmlHasProp(svg, (const xmlChar *) "viewBox"); + 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) { - const char *s = (const char *) view_box->children->content; + 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 || @@ -226,43 +779,86 @@ svgtiny_code svgtiny_parse_svg(dom_node *svg, 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); - for (child = svg->children; child; child = child->next) { + 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; - if (child->type == XML_ELEMENT_NODE) { - const char *name = (const char *) child->name; - if (strcmp(name, "svg") == 0) + 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 (strcmp(name, "g") == 0) + else if (dom_string_caseless_isequal(state.interned_g, + nodename)) code = svgtiny_parse_svg(child, state); - else if (strcmp(name, "a") == 0) + else if (dom_string_caseless_isequal(state.interned_a, + nodename)) code = svgtiny_parse_svg(child, state); - else if (strcmp(name, "path") == 0) + else if (dom_string_caseless_isequal(state.interned_path, + nodename)) code = svgtiny_parse_path(child, state); - else if (strcmp(name, "rect") == 0) + else if (dom_string_caseless_isequal(state.interned_rect, + nodename)) code = svgtiny_parse_rect(child, state); - else if (strcmp(name, "circle") == 0) + else if (dom_string_caseless_isequal(state.interned_circle, + nodename)) code = svgtiny_parse_circle(child, state); - else if (strcmp(name, "ellipse") == 0) + else if (dom_string_caseless_isequal(state.interned_ellipse, + nodename)) code = svgtiny_parse_ellipse(child, state); - else if (strcmp(name, "line") == 0) + else if (dom_string_caseless_isequal(state.interned_line, + nodename)) code = svgtiny_parse_line(child, state); - else if (strcmp(name, "polyline") == 0) + else if (dom_string_caseless_isequal(state.interned_polyline, + nodename)) code = svgtiny_parse_poly(child, state, false); - else if (strcmp(name, "polygon") == 0) + else if (dom_string_caseless_isequal(state.interned_polygon, + nodename)) code = svgtiny_parse_poly(child, state, true); - else if (strcmp(name, "text") == 0) + else if (dom_string_caseless_isequal(state.interned_text, + nodename)) code = svgtiny_parse_text(child, state); + dom_string_unref(nodename); } - - if (code != svgtiny_OK) + 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; } @@ -274,31 +870,72 @@ svgtiny_code svgtiny_parse_svg(dom_node *svg, * http://www.w3.org/TR/SVG11/paths#PathElement */ -svgtiny_code svgtiny_parse_path(dom_node *path, +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; + 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 */ - s = path_d = (char *) xmlGetProp(path, (const xmlChar *) "d"); - if (!s) { - state.diagram->error_line = path->line; + 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; } - /* allocate space for path: it will never have more elements than d */ - p = malloc(sizeof p[0] * strlen(s)); - if (!p) + /* 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++) @@ -311,6 +948,24 @@ svgtiny_code svgtiny_parse_path(dom_node *path, 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"));*/ @@ -319,11 +974,16 @@ svgtiny_code svgtiny_parse_path(dom_node *path, 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 @@ -335,13 +995,19 @@ svgtiny_code svgtiny_parse_path(dom_node *path, /* 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; @@ -355,6 +1021,8 @@ svgtiny_code svgtiny_parse_path(dom_node *path, } 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; @@ -362,13 +1030,15 @@ svgtiny_code svgtiny_parse_path(dom_node *path, p[i++] = last_cubic_y = last_quad_y = last_y = y; s += n; - } while (sscanf(s, "%f %n", &x, &n) == 1); + } 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; @@ -393,6 +1063,8 @@ svgtiny_code svgtiny_parse_path(dom_node *path, &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); @@ -417,6 +1089,8 @@ svgtiny_code svgtiny_parse_path(dom_node *path, &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; @@ -442,6 +1116,8 @@ svgtiny_code svgtiny_parse_path(dom_node *path, &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); @@ -468,35 +1144,84 @@ svgtiny_code svgtiny_parse_path(dom_node *path, &rx, &ry, &rotation, &large_arc, &sweep, &x, &y, &n) == 8) { do { - p[i++] = svgtiny_PATH_LINE; + int bzsegments; + double bzpoints[6*4]; /* allow for up to four bezier segments per arc */ + if (*command == 'a') { x += last_x; y += last_y; } - p[i++] = last_cubic_x = last_quad_x = last_x - = x; - p[i++] = last_cubic_y = last_quad_y = last_y - = 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); + /* fprintf(stderr, "parse failed at \"%s\"\n", s); */ break; } } - xmlFree(path_d); + free(path_d); if (i <= 4) { /* no real segments in path */ free(p); + svgtiny_cleanup_state_local(&state); return svgtiny_OK; } - return svgtiny_add_path(p, i, &state); + /* 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; } @@ -506,20 +1231,25 @@ svgtiny_code svgtiny_parse_path(dom_node *path, * http://www.w3.org/TR/SVG11/shapes#RectElement */ -svgtiny_code svgtiny_parse_rect(dom_node *rect, +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) + if (!p) { + svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; + } p[0] = svgtiny_PATH_MOVE; p[1] = x; @@ -535,7 +1265,11 @@ svgtiny_code svgtiny_parse_rect(dom_node *rect, p[11] = y + height; p[12] = svgtiny_PATH_CLOSE; - return svgtiny_add_path(p, 13, &state); + err = svgtiny_add_path(p, 13, &state); + + svgtiny_cleanup_state_local(&state); + + return err; } @@ -543,40 +1277,66 @@ svgtiny_code svgtiny_parse_rect(dom_node *rect, * Parse a element node. */ -svgtiny_code svgtiny_parse_circle(dom_node *circle, +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; - xmlAttr *attr; - - for (attr = circle->properties; attr; attr = attr->next) { - const char *name = (const char *) attr->name; - const char *content = (const char *) attr->children->content; - if (strcmp(name, "cx") == 0) - x = svgtiny_parse_length(content, - state.viewport_width, state); - else if (strcmp(name, "cy") == 0) - y = svgtiny_parse_length(content, - state.viewport_height, state); - else if (strcmp(name, "r") == 0) - r = svgtiny_parse_length(content, - state.viewport_width, state); - } + 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 = circle->line; + 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) + if (r == 0) { + svgtiny_cleanup_state_local(&state); return svgtiny_OK; + } p = malloc(32 * sizeof p[0]); - if (!p) + if (!p) { + svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; + } p[0] = svgtiny_PATH_MOVE; p[1] = x + r; @@ -610,8 +1370,12 @@ svgtiny_code svgtiny_parse_circle(dom_node *circle, p[29] = x + r; p[30] = y; p[31] = svgtiny_PATH_CLOSE; - - return svgtiny_add_path(p, 32, &state); + + err = svgtiny_add_path(p, 32, &state); + + svgtiny_cleanup_state_local(&state); + + return err; } @@ -619,44 +1383,77 @@ svgtiny_code svgtiny_parse_circle(dom_node *circle, * Parse an element node. */ -svgtiny_code svgtiny_parse_ellipse(dom_node *ellipse, +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; - xmlAttr *attr; - - for (attr = ellipse->properties; attr; attr = attr->next) { - const char *name = (const char *) attr->name; - const char *content = (const char *) attr->children->content; - if (strcmp(name, "cx") == 0) - x = svgtiny_parse_length(content, - state.viewport_width, state); - else if (strcmp(name, "cy") == 0) - y = svgtiny_parse_length(content, - state.viewport_height, state); - else if (strcmp(name, "rx") == 0) - rx = svgtiny_parse_length(content, - state.viewport_width, state); - else if (strcmp(name, "ry") == 0) - ry = svgtiny_parse_length(content, - state.viewport_width, state); - } + 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 = ellipse->line; + 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) + if (rx == 0 || ry == 0) { + svgtiny_cleanup_state_local(&state); return svgtiny_OK; + } p = malloc(32 * sizeof p[0]); - if (!p) + if (!p) { + svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; + } p[0] = svgtiny_PATH_MOVE; p[1] = x + rx; @@ -690,8 +1487,12 @@ svgtiny_code svgtiny_parse_ellipse(dom_node *ellipse, p[29] = x + rx; p[30] = y; p[31] = svgtiny_PATH_CLOSE; - - return svgtiny_add_path(p, 32, &state); + + err = svgtiny_add_path(p, 32, &state); + + svgtiny_cleanup_state_local(&state); + + return err; } @@ -699,35 +1500,65 @@ svgtiny_code svgtiny_parse_ellipse(dom_node *ellipse, * Parse a element node. */ -svgtiny_code svgtiny_parse_line(dom_node *line, +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; - xmlAttr *attr; - - for (attr = line->properties; attr; attr = attr->next) { - const char *name = (const char *) attr->name; - const char *content = (const char *) attr->children->content; - if (strcmp(name, "x1") == 0) - x1 = svgtiny_parse_length(content, - state.viewport_width, state); - else if (strcmp(name, "y1") == 0) - y1 = svgtiny_parse_length(content, - state.viewport_height, state); - else if (strcmp(name, "x2") == 0) - x2 = svgtiny_parse_length(content, - state.viewport_width, state); - else if (strcmp(name, "y2") == 0) - y2 = svgtiny_parse_length(content, - state.viewport_height, state); - } + 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) + if (!p) { + svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; + } p[0] = svgtiny_PATH_MOVE; p[1] = x1; @@ -737,7 +1568,11 @@ svgtiny_code svgtiny_parse_line(dom_node *line, p[5] = y2; p[6] = svgtiny_PATH_CLOSE; - return svgtiny_add_path(p, 7, &state); + err = svgtiny_add_path(p, 7, &state); + + svgtiny_cleanup_state_local(&state); + + return err; } @@ -748,29 +1583,49 @@ svgtiny_code svgtiny_parse_line(dom_node *line, * http://www.w3.org/TR/SVG11/shapes#PolygonElement */ -svgtiny_code svgtiny_parse_poly(dom_node *poly, +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); - /* read points attribute */ - s = points = (char *) xmlGetProp(poly, (const xmlChar *) "points"); - if (!s) { - state.diagram->error_line = poly->line; + 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) { - xmlFree(points); + free(points); + svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; } @@ -792,15 +1647,19 @@ svgtiny_code svgtiny_parse_poly(dom_node *poly, p[i++] = y; s += n; } else { - break; + break; } } if (polygon) p[i++] = svgtiny_PATH_CLOSE; - xmlFree(points); + free(points); + + err = svgtiny_add_path(p, i, &state); - return svgtiny_add_path(p, i, &state); + svgtiny_cleanup_state_local(&state); + + return err; } @@ -808,12 +1667,15 @@ svgtiny_code svgtiny_parse_poly(dom_node *poly, * Parse a or element node. */ -svgtiny_code svgtiny_parse_text(dom_node *text, +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); @@ -822,34 +1684,83 @@ svgtiny_code svgtiny_parse_text(dom_node *text, 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; */ +/* 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 (child = text->children; child; child = child->next) { + 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; - if (child->type == XML_TEXT_NODE) { + 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); - if (!shape) + dom_string *content; + if (shape == NULL) { + dom_node_unref(child); + svgtiny_cleanup_state_local(&state); return svgtiny_OUT_OF_MEMORY; - shape->text = strdup((const char *) child->content); + } + 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++; - - } else if (child->type == XML_ELEMENT_NODE && - strcmp((const char *) child->name, - "tspan") == 0) { - code = svgtiny_parse_text(child, state); } - if (!code != svgtiny_OK) + 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; } @@ -858,32 +1769,42 @@ svgtiny_code svgtiny_parse_text(dom_node *text, * Parse x, y, width, and height attributes, if present. */ -void svgtiny_parse_position_attributes(const dom_node *node, +void svgtiny_parse_position_attributes(dom_element *node, const struct svgtiny_parse_state state, float *x, float *y, float *width, float *height) { - xmlAttr *attr; + dom_string *attr; + dom_exception exc; *x = 0; *y = 0; *width = state.viewport_width; *height = state.viewport_height; - for (attr = node->properties; attr; attr = attr->next) { - const char *name = (const char *) attr->name; - const char *content = (const char *) attr->children->content; - if (strcmp(name, "x") == 0) - *x = svgtiny_parse_length(content, - state.viewport_width, state); - else if (strcmp(name, "y") == 0) - *y = svgtiny_parse_length(content, - state.viewport_height, state); - else if (strcmp(name, "width") == 0) - *width = svgtiny_parse_length(content, - state.viewport_width, state); - else if (strcmp(name, "height") == 0) - *height = svgtiny_parse_length(content, - state.viewport_height, state); + 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); } } @@ -892,8 +1813,8 @@ void svgtiny_parse_position_attributes(const dom_node *node, * Parse a length as a number of pixels. */ -float svgtiny_parse_length(const char *s, int viewport_size, - const struct svgtiny_parse_state state) +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; @@ -927,57 +1848,77 @@ float svgtiny_parse_length(const char *s, int viewport_size, 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(const dom_node *node, +void svgtiny_parse_paint_attributes(dom_element *node, struct svgtiny_parse_state *state) { - const xmlAttr *attr; + dom_string *attr; + dom_exception exc; - for (attr = node->properties; attr; attr = attr->next) { - const char *name = (const char *) attr->name; - const char *content = (const char *) attr->children->content; - if (strcmp(name, "fill") == 0) - svgtiny_parse_color(content, &state->fill, state); - else if (strcmp(name, "stroke") == 0) - svgtiny_parse_color(content, &state->stroke, state); - else if (strcmp(name, "stroke-width") == 0) - state->stroke_width = svgtiny_parse_length(content, - state->viewport_width, *state); - else if (strcmp(name, "style") == 0) { - const char *style = (const char *) - attr->children->content; - 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); - free(value); - } - if ((s = strstr(style, "stroke:"))) { - s += 7; - while (*s == ' ') - s++; - value = strndup(s, strcspn(s, "; ")); - svgtiny_parse_color(value, &state->stroke, 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, + 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); - free(value); - } + 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); } } @@ -986,7 +1927,8 @@ void svgtiny_parse_paint_attributes(const dom_node *node, * Parse a colour. */ -void svgtiny_parse_color(const char *s, svgtiny_colour *c, +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; @@ -1018,21 +1960,21 @@ void svgtiny_parse_color(const char *s, svgtiny_colour *c, } else if (5 < len && s[0] == 'u' && s[1] == 'r' && s[2] == 'l' && s[3] == '(') { - if (s[4] == '#') { + 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, state); + svgtiny_find_gradient(id, grad, state); free(id); - fprintf(stderr, "linear_gradient_stop_count %i\n", - state->linear_gradient_stop_count); - if (state->linear_gradient_stop_count == 0) + if (grad->linear_gradient_stop_count == 0) *c = svgtiny_TRANSPARENT; - else if (state->linear_gradient_stop_count == 1) - *c = state->gradient_stop[0].color; + else if (grad->linear_gradient_stop_count == 1) + *c = grad->gradient_stop[0].color; else *c = svgtiny_LINEAR_GRADIENT; } @@ -1045,14 +1987,26 @@ void svgtiny_parse_color(const char *s, svgtiny_colour *c, } } +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(const dom_node *node, +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); @@ -1068,6 +2022,7 @@ void svgtiny_parse_font_attributes(const dom_node *node, }*/ } } +#endif } @@ -1077,18 +2032,23 @@ void svgtiny_parse_font_attributes(const dom_node *node, * http://www.w3.org/TR/SVG11/coords#TransformAttribute */ -void svgtiny_parse_transform_attributes(dom_node *node, +void svgtiny_parse_transform_attributes(dom_element *node, struct svgtiny_parse_state *state) { char *transform; + dom_string *attr; + dom_exception exc; - /* parse transform */ - transform = (char *) xmlGetProp(node, (const xmlChar *) "transform"); - if (transform) { + 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); - xmlFree(transform); + free(transform); + dom_string_unref(attr); } } @@ -1114,23 +2074,24 @@ void svgtiny_parse_transform(char *s, float *ma, float *mb, a = d = 1; b = c = 0; e = f = 0; - if (sscanf(s, "matrix (%f %f %f %f %f %f) %n", - &a, &b, &c, &d, &e, &f, &n) == 6) + 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) + else if ((sscanf(s, " translate (%f %f ) %n", + &e, &f, &n) == 2) && (n > 0)) ; - else if (sscanf(s, "translate (%f) %n", - &e, &n) == 1) + else if ((sscanf(s, " translate (%f ) %n", + &e, &n) == 1) && (n > 0)) ; - else if (sscanf(s, "scale (%f %f) %n", - &a, &d, &n) == 2) + else if ((sscanf(s, " scale (%f %f ) %n", + &a, &d, &n) == 2) && (n > 0)) ; - else if (sscanf(s, "scale (%f) %n", - &a, &n) == 1) + 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) { + 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); @@ -1138,19 +2099,19 @@ void svgtiny_parse_transform(char *s, float *ma, float *mb, 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) { + } 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) { + } 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) { + } else if ((sscanf(s, " skewY (%f ) %n", + &angle, &n) == 1) && (n > 0)) { angle = angle / 180 * M_PI; b = tan(angle); } else @@ -1281,7 +2242,7 @@ void svgtiny_free(struct svgtiny_diagram *svg) free(svg->shape[i].path); free(svg->shape[i].text); } - + free(svg->shape); free(svg); @@ -1306,4 +2267,3 @@ char *svgtiny_strndup(const char *s, size_t n) return s2; } #endif -