From 7f902a67959d4b13dc4d36f2703cb488f3676d64 Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Wed, 17 Jul 2024 22:47:45 +0100 Subject: [PATCH] refactor path parse --- src/Makefile | 2 +- src/svgtiny.c | 1708 +++++++++++----------------------------- src/svgtiny_internal.h | 8 + src/svgtiny_parse.c | 68 +- src/svgtiny_parse.h | 46 ++ src/svgtiny_path.c | 1183 ++++++++++++++++++++++++++++ test/data/arcs01.mvg | 5 + test/data/arcs01.svg | 21 + test/data/arcs02.svg | 57 ++ test/data/cubic01.mvg | 16 + test/data/cubic01.svg | 27 + test/data/cubic02.svg | 91 +++ test/data/quad01.mvg | 9 + test/data/quad01.svg | 27 + 14 files changed, 1972 insertions(+), 1296 deletions(-) create mode 100644 src/svgtiny_parse.h create mode 100644 src/svgtiny_path.c create mode 100644 test/data/arcs01.mvg create mode 100644 test/data/arcs01.svg create mode 100644 test/data/arcs02.svg create mode 100644 test/data/cubic01.mvg create mode 100644 test/data/cubic01.svg create mode 100644 test/data/cubic02.svg create mode 100644 test/data/quad01.mvg create mode 100644 test/data/quad01.svg diff --git a/src/Makefile b/src/Makefile index 15cdbe7..25be071 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,5 +1,5 @@ # Sources -DIR_SOURCES := svgtiny.c svgtiny_gradient.c svgtiny_list.c svgtiny_parse.c +DIR_SOURCES := svgtiny.c svgtiny_gradient.c svgtiny_list.c svgtiny_parse.c svgtiny_path.c SOURCES := $(SOURCES) diff --git a/src/svgtiny.c b/src/svgtiny.c index cf88544..979a140 100644 --- a/src/svgtiny.c +++ b/src/svgtiny.c @@ -4,6 +4,7 @@ * http://opensource.org/licenses/mit-license.php * Copyright 2008-2009 James Bursa * Copyright 2012 Daniel Silverstone + * Copyright 2024 Vincent Sanders */ #include @@ -21,20 +22,13 @@ #include "svgtiny_internal.h" -#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 +/* circles are approximated with four bezier curves + * + * The optimal distance to the control points is the constant (4/3)*tan(pi/(2n)) + * (where n is 4) + */ +#define KAPPA 0.5522847498 -#define degToRad(angleInDegrees) ((angleInDegrees) * M_PI / 180.0) -#define radToDeg(angleInRadians) ((angleInRadians) * 180.0 / M_PI) #if (defined(_GNU_SOURCE) && !defined(__APPLE__) || defined(__amigaos4__) || defined(__HAIKU__) || (defined(_POSIX_C_SOURCE) && ((_POSIX_C_SOURCE - 0) >= 200809L))) #define HAVE_STRNDUP @@ -44,474 +38,73 @@ char *svgtiny_strndup(const char *s, size_t n); #define strndup svgtiny_strndup #endif -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, - 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); - -/** - * 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) +#ifndef HAVE_STRNDUP +char *svgtiny_strndup(const char *s, size_t n) { - 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) - */ + size_t len; + char *s2; - 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); - } -} + for (len = 0; len != n && s[len]; len++) + continue; + s2 = malloc(len + 1); + if (s2 == NULL) + return NULL; -/** - * 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; + memcpy(s2, s, len); + s2[len] = '\0'; - } - return bzsegments; + return s2; } +#endif /** - * 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 + * Parse x, y, width, and height attributes, if present. */ 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) +svgtiny_parse_position_attributes(dom_element *node, + struct svgtiny_parse_state state, + float *x, float *y, + float *width, float *height) { - 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); + struct svgtiny_parse_internal_operation styles[] = { + { + /* x */ + state.interned_x, + SVGTIOP_LENGTH, + &state.viewport_width, + x + },{ + /* y */ + state.interned_y, + SVGTIOP_LENGTH, + &state.viewport_height, + y + },{ + /* width */ + state.interned_width, + SVGTIOP_LENGTH, + &state.viewport_width, + width + },{ + /* height */ + state.interned_height, + SVGTIOP_LENGTH, + &state.viewport_height, + height + },{ + NULL, SVGTIOP_NONE, NULL, NULL + }, + }; - /* transform the bezier curves */ - scale_rotate_translate_points(rx, ry, - radangle, - cx, cy, - bzsegments * 6, - bzpoints); + *x = 0; + *y = 0; + *width = state.viewport_width; + *height = state.viewport_height; - return bzsegments; + svgtiny_parse_attributes(node, &state, styles); } @@ -534,11 +127,12 @@ static void svgtiny_grad_string_ref(struct svgtiny_parse_state_gradient *grad) } } + /** * Call this to clean up the strings in a gradient state. */ static void svgtiny_grad_string_cleanup( - struct svgtiny_parse_state_gradient *grad) + struct svgtiny_parse_state_gradient *grad) { if (grad->gradient_x1 != NULL) { dom_string_unref(grad->gradient_x1); @@ -583,24 +177,6 @@ static void svgtiny_cleanup_state_local(struct svgtiny_parse_state *state) } -/** - * Create a new svgtiny_diagram structure. - */ - -struct svgtiny_diagram *svgtiny_create(void) -{ - struct svgtiny_diagram *diagram; - - diagram = calloc(1, sizeof(*diagram)); - if (!diagram) - return 0; - - return diagram; - free(diagram); - return NULL; -} - - static void ignore_msg(uint32_t severity, void *ctx, const char *msg, ...) { UNUSED(severity); @@ -610,254 +186,140 @@ static void ignore_msg(uint32_t severity, void *ctx, const char *msg, ...) /** - * Parse a block of memory into a svgtiny_diagram. + * Parse paint attributes, if present. */ +static void +svgtiny_parse_paint_attributes(dom_element *node, + struct svgtiny_parse_state *state) +{ + struct svgtiny_parse_internal_operation ops[] = { + { + /* fill color */ + state->interned_fill, + SVGTIOP_PAINT, + &state->fill_grad, + &state->fill + }, { + /* stroke color */ + state->interned_stroke, + SVGTIOP_PAINT, + &state->stroke_grad, + &state->stroke + }, { + /* stroke width */ + state->interned_stroke_width, + SVGTIOP_INTLENGTH, + &state->viewport_width, + &state->stroke_width + },{ + NULL, SVGTIOP_NONE, NULL, NULL + }, + }; -svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, - const char *buffer, size_t size, const char *url, - int viewport_width, int viewport_height) + svgtiny_parse_attributes(node, state, ops); + svgtiny_parse_inline_style(node, state, ops); +} + + +/** + * Parse font attributes, if present. + */ +static void +svgtiny_parse_font_attributes(dom_element *node, + struct svgtiny_parse_state *state) { - 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; + /* TODO: Implement this, it never used to be */ + UNUSED(node); + UNUSED(state); +#ifdef WRITTEN_THIS_PROPERLY + const xmlAttr *attr; - assert(diagram); - assert(buffer); - assert(url); + UNUSED(state); - UNUSED(url); + 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 +} - parser = dom_xml_parser_create(NULL, NULL, - ignore_msg, NULL, &document); - if (parser == NULL) - return svgtiny_LIBDOM_ERROR; +/** + * Parse transform attributes, if present. + * + * http://www.w3.org/TR/SVG11/coords#TransformAttribute + */ +static void +svgtiny_parse_transform_attributes(dom_element *node, + struct svgtiny_parse_state *state) +{ + dom_string *attr; + dom_exception exc; - 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; - } + exc = dom_element_get_attribute(node, state->interned_transform, &attr); + if (exc == DOM_NO_ERR && attr != NULL) { + svgtiny_parse_transform(dom_string_data(attr), + dom_string_byte_length(attr), + &state->ctm); - err = dom_xml_parser_completed(parser); - if (err != DOM_XML_OK) { - dom_node_unref(document); - dom_xml_parser_destroy(parser); - return svgtiny_LIBDOM_ERROR; + dom_string_unref(attr); } - - /* 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. + * Add a path to the svgtiny_diagram. */ - -svgtiny_code svgtiny_parse_svg(dom_element *svg, - struct svgtiny_parse_state state) +static svgtiny_code +svgtiny_add_path(float *p, unsigned int n, 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); + struct svgtiny_shape *shape; + svgtiny_code res = svgtiny_OK; - svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height); - svgtiny_parse_paint_attributes(svg, &state); - svgtiny_parse_font_attributes(svg, &state); + if (state->fill == svgtiny_LINEAR_GRADIENT) { + /* adds a shape to fill the path with a linear gradient */ + res = svgtiny_gradient_add_fill_path(p, n, state); + } + if (res != svgtiny_OK) { + free(p); + return res; + } - 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 (state->stroke == svgtiny_LINEAR_GRADIENT) { + /* adds a shape to stroke the path with a linear gradient */ + res = svgtiny_gradient_add_stroke_path(p, n, state); + } + if (res != svgtiny_OK) { + free(p); + return res; } - if (view_box) { - svgtiny_parse_viewbox(dom_string_data(view_box), - dom_string_byte_length(view_box), - state.viewport_width, - state.viewport_height, - &state.ctm); - dom_string_unref(view_box); + /* if stroke and fill are transparent do not add a shape */ + if ((state->fill == svgtiny_TRANSPARENT) && + (state->stroke == svgtiny_TRANSPARENT)) { + free(p); + return res; } - svgtiny_parse_transform_attributes(svg, &state); + svgtiny_transform_path(p, n, 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; + shape = svgtiny_add_shape(state); + if (shape == NULL) { + free(p); + return svgtiny_OUT_OF_MEMORY; } - while (child != NULL) { - dom_element *next; - dom_node_type nodetype; - svgtiny_code code = svgtiny_OK; + shape->path = p; + shape->path_length = n; + state->diagram->shape_count++; - 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; } @@ -865,23 +327,16 @@ svgtiny_code svgtiny_parse_svg(dom_element *svg, /** * Parse a element node. * - * http://www.w3.org/TR/SVG11/paths#PathElement + * https://svgwg.org/svg2-draft/paths.html#PathElement */ - -svgtiny_code svgtiny_parse_path(dom_element *path, - struct svgtiny_parse_state state) +static svgtiny_code +svgtiny_parse_path(dom_element *path, struct svgtiny_parse_state state) { - svgtiny_code err; + svgtiny_code res; 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); @@ -904,322 +359,28 @@ svgtiny_code svgtiny_parse_path(dom_element *path, 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); + res = svgtiny_parse_path_data(dom_string_data(path_d_str), + dom_string_byte_length(path_d_str), + &p, + &i); + if (res != svgtiny_OK) { 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; - } + return res; } - free(path_d); - if (i <= 4) { - /* no real segments in path */ - free(p); - svgtiny_cleanup_state_local(&state); - return svgtiny_OK; + /* insufficient segments in path treated as none */ + if (i > 0) { + free(p); + } + res = svgtiny_OK; + } else { + res = 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; + return res; } @@ -1228,9 +389,8 @@ svgtiny_code svgtiny_parse_path(dom_element *path, * * http://www.w3.org/TR/SVG11/shapes#RectElement */ - -svgtiny_code svgtiny_parse_rect(dom_element *rect, - struct svgtiny_parse_state state) +static svgtiny_code +svgtiny_parse_rect(dom_element *rect, struct svgtiny_parse_state state) { svgtiny_code err; float x, y, width, height; @@ -1239,7 +399,7 @@ svgtiny_code svgtiny_parse_rect(dom_element *rect, svgtiny_setup_state_local(&state); svgtiny_parse_position_attributes(rect, state, - &x, &y, &width, &height); + &x, &y, &width, &height); svgtiny_parse_paint_attributes(rect, &state); svgtiny_parse_transform_attributes(rect, &state); @@ -1274,9 +434,8 @@ svgtiny_code svgtiny_parse_rect(dom_element *rect, /** * Parse a element node. */ - -svgtiny_code svgtiny_parse_circle(dom_element *circle, - struct svgtiny_parse_state state) +static svgtiny_code +svgtiny_parse_circle(dom_element *circle, struct svgtiny_parse_state state) { svgtiny_code err; float x = 0, y = 0, r = -1; @@ -1374,9 +533,8 @@ svgtiny_code svgtiny_parse_circle(dom_element *circle, /** * Parse an element node. */ - -svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse, - struct svgtiny_parse_state state) +static 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; @@ -1421,7 +579,7 @@ svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse, if (rx < 0 || ry < 0) { state.diagram->error_line = -1; /* ellipse->line; */ state.diagram->error_message = "ellipse: rx or ry missing " - "or negative"; + "or negative"; svgtiny_cleanup_state_local(&state); return svgtiny_SVG_ERROR; } @@ -1480,9 +638,8 @@ svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse, /** * Parse a element node. */ - -svgtiny_code svgtiny_parse_line(dom_element *line, - struct svgtiny_parse_state state) +static 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; @@ -1552,9 +709,10 @@ svgtiny_code svgtiny_parse_line(dom_element *line, * 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) +static svgtiny_code +svgtiny_parse_poly(dom_element *poly, + struct svgtiny_parse_state state, + bool polygon) { svgtiny_code err; dom_string *points_str; @@ -1577,7 +735,7 @@ svgtiny_code svgtiny_parse_poly(dom_element *poly, if (points_str == NULL) { state.diagram->error_line = -1; /* poly->line; */ state.diagram->error_message = - "polyline/polygon: missing points attribute"; + "polyline/polygon: missing points attribute"; svgtiny_cleanup_state_local(&state); return svgtiny_SVG_ERROR; } @@ -1601,7 +759,7 @@ svgtiny_code svgtiny_parse_poly(dom_element *poly, free(pointv); state.diagram->error_line = -1; /* poly->line; */ state.diagram->error_message = - "polyline/polygon: failed to parse points"; + "polyline/polygon: failed to parse points"; } else { if (pointc > 0) { pointv[0] = svgtiny_PATH_MOVE; @@ -1621,9 +779,8 @@ svgtiny_code svgtiny_parse_poly(dom_element *poly, /** * Parse a or element node. */ - -svgtiny_code svgtiny_parse_text(dom_element *text, - struct svgtiny_parse_state state) +static svgtiny_code +svgtiny_parse_text(dom_element *text, struct svgtiny_parse_state state) { float x, y, width, height; float px, py; @@ -1633,7 +790,7 @@ svgtiny_code svgtiny_parse_text(dom_element *text, svgtiny_setup_state_local(&state); svgtiny_parse_position_attributes(text, state, - &x, &y, &width, &height); + &x, &y, &width, &height); svgtiny_parse_font_attributes(text, &state); svgtiny_parse_transform_attributes(text, &state); @@ -1643,7 +800,7 @@ svgtiny_code svgtiny_parse_text(dom_element *text, /* state.ctm.f = py - state.origin_y; */ /*struct css_style style = state.style; - style.font_size.value.length.value *= state.ctm.a;*/ + style.font_size.value.length.value *= state.ctm.a;*/ exc = dom_node_get_first_child(text, &child); if (exc != DOM_NO_ERR) { @@ -1721,195 +878,123 @@ svgtiny_code svgtiny_parse_text(dom_element *text, /** - * Parse x, y, width, and height attributes, if present. + * Parse a or element node. */ - -void svgtiny_parse_position_attributes(dom_element *node, - struct svgtiny_parse_state state, - float *x, float *y, float *width, float *height) +static svgtiny_code +svgtiny_parse_svg(dom_element *svg, struct svgtiny_parse_state state) { - struct svgtiny_parse_internal_operation styles[] = { - { - /* x */ - state.interned_x, - SVGTIOP_LENGTH, - &state.viewport_width, - x - },{ - /* y */ - state.interned_y, - SVGTIOP_LENGTH, - &state.viewport_height, - y - },{ - /* width */ - state.interned_width, - SVGTIOP_LENGTH, - &state.viewport_width, - width - },{ - /* height */ - state.interned_height, - SVGTIOP_LENGTH, - &state.viewport_height, - height - },{ - NULL, SVGTIOP_NONE, NULL, NULL - }, - }; - - *x = 0; - *y = 0; - *width = state.viewport_width; - *height = state.viewport_height; - - svgtiny_parse_attributes(node, &state, styles); -} - - -/** - * Parse paint attributes, if present. - */ -void svgtiny_parse_paint_attributes(dom_element *node, - struct svgtiny_parse_state *state) -{ - struct svgtiny_parse_internal_operation ops[] = { - { - /* fill color */ - state->interned_fill, - SVGTIOP_PAINT, - &state->fill_grad, - &state->fill - }, { - /* stroke color */ - state->interned_stroke, - SVGTIOP_PAINT, - &state->stroke_grad, - &state->stroke - }, { - /* stroke width */ - state->interned_stroke_width, - SVGTIOP_INTLENGTH, - &state->viewport_width, - &state->stroke_width - },{ - NULL, SVGTIOP_NONE, NULL, NULL - }, - }; - - svgtiny_parse_attributes(node, state, ops); - svgtiny_parse_inline_style(node, state, ops); -} - - -/** - * 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) -{ - dom_string *attr; + float x, y, width, height; + dom_string *view_box; + dom_element *child; dom_exception exc; - exc = dom_element_get_attribute(node, state->interned_transform, &attr); - if (exc == DOM_NO_ERR && attr != NULL) { - svgtiny_parse_transform(dom_string_data(attr), - dom_string_byte_length(attr), - &state->ctm); - - dom_string_unref(attr); - } -} - - -/** - * 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; - svgtiny_code res = svgtiny_OK; + svgtiny_setup_state_local(&state); - if (state->fill == svgtiny_LINEAR_GRADIENT) { - /* adds a shape to fill the path with a linear gradient */ - res = svgtiny_gradient_add_fill_path(p, n, state); - } - if (res != svgtiny_OK) { - free(p); - return res; - } + svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height); + svgtiny_parse_paint_attributes(svg, &state); + svgtiny_parse_font_attributes(svg, &state); - if (state->stroke == svgtiny_LINEAR_GRADIENT) { - /* adds a shape to stroke the path with a linear gradient */ - res = svgtiny_gradient_add_stroke_path(p, n, state); - } - if (res != svgtiny_OK) { - free(p); - return res; + 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 stroke and fill are transparent do not add a shape */ - if ((state->fill == svgtiny_TRANSPARENT) && - (state->stroke == svgtiny_TRANSPARENT)) { - free(p); - return res; + if (view_box) { + svgtiny_parse_viewbox(dom_string_data(view_box), + dom_string_byte_length(view_box), + state.viewport_width, + state.viewport_height, + &state.ctm); + dom_string_unref(view_box); } - svgtiny_transform_path(p, n, state); + svgtiny_parse_transform_attributes(svg, &state); - shape = svgtiny_add_shape(state); - if (shape == NULL) { - free(p); - return svgtiny_OUT_OF_MEMORY; + 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; } - shape->path = p; - shape->path_length = n; - state->diagram->shape_count++; + 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; } /** * Add a svgtiny_shape to the svgtiny_diagram. + * + * library internal interface */ - struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state) { struct svgtiny_shape *shape; @@ -1937,10 +1022,13 @@ struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state) /** * Apply the current transformation matrix to a path. + * + * library internal interface */ - -void svgtiny_transform_path(float *p, unsigned int n, - struct svgtiny_parse_state *state) +void +svgtiny_transform_path(float *p, + unsigned int n, + struct svgtiny_parse_state *state) { unsigned int j; @@ -1977,9 +1065,163 @@ void svgtiny_transform_path(float *p, unsigned int n, /** - * Free all memory used by a diagram. + * Create a new svgtiny_diagram structure. + */ +struct svgtiny_diagram *svgtiny_create(void) +{ + struct svgtiny_diagram *diagram; + + diagram = calloc(1, sizeof(*diagram)); + if (!diagram) + return 0; + + return diagram; + free(diagram); + return NULL; +} + + +/** + * 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; +} + + +/** + * Free all memory used by a diagram. + */ void svgtiny_free(struct svgtiny_diagram *svg) { unsigned int i; @@ -1994,23 +1236,3 @@ void svgtiny_free(struct svgtiny_diagram *svg) 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 diff --git a/src/svgtiny_internal.h b/src/svgtiny_internal.h index 41879f2..6a57140 100644 --- a/src/svgtiny_internal.h +++ b/src/svgtiny_internal.h @@ -113,6 +113,14 @@ svgtiny_code svgtiny_parse_inline_style(dom_element *node, svgtiny_code svgtiny_parse_attributes(dom_element *node, struct svgtiny_parse_state *state, struct svgtiny_parse_internal_operation *ops); +svgtiny_code svgtiny_parse_none(const char *cursor, const char *textend); +svgtiny_code svgtiny_parse_number(const char *text, const char **textend, + float *value); + + +/* svgtiny_path.c */ +svgtiny_code svgtiny_parse_path_data(const char *text, size_t textlen, + float **pointv, unsigned int *pointc); /* svgtiny_gradient.c */ svgtiny_code svgtiny_find_gradient(const char *id, diff --git a/src/svgtiny_parse.c b/src/svgtiny_parse.c index 974df8c..3d33cc7 100644 --- a/src/svgtiny_parse.c +++ b/src/svgtiny_parse.c @@ -10,12 +10,15 @@ #include #include + #include "svgtiny.h" #include "svgtiny_internal.h" +#include "svgtiny_parse.h" /* Source file generated by `gperf`. */ #include "autogenerated_colors.c" + #define SIGNIFICAND_MAX 100000000 #define EXPONENT_MAX 38 #define EXPONENT_MIN -38 @@ -54,8 +57,8 @@ * decimal numbers (not hex etc.). These limitations are necessary to process * the input correctly. */ -static svgtiny_code -parse_number(const char *text, const char **textend, float *value) +svgtiny_code +svgtiny_parse_number(const char *text, const char **textend, float *value) { const char *cur; /* text cursor */ enum { @@ -281,42 +284,6 @@ svgtiny_parse_number_end: } -/** - * skip SVG spec defined whitespace - * - * \param cursor current cursor - * \param textend end of buffer - */ -static inline void advance_whitespace(const char **cursor, const char *textend) -{ - while((*cursor) < textend) { - if ((**cursor != 0x20) && - (**cursor != 0x09) && - (**cursor != 0x0A) && - (**cursor != 0x0D)) { - break; - } - (*cursor)++; - } -} - - -/** - * advance cursor across SVG spec defined comma and whitespace - * - * \param cursor current cursor - * \param textend end of buffer - */ -static inline void advance_comma_whitespace(const char **cursor, const char *textend) -{ - advance_whitespace(cursor, textend); - if (((*cursor) < textend) && (**cursor == 0x2C /* , */)) { - (*cursor)++; - advance_whitespace(cursor, textend); - } -} - - /** * advance across hexidecimal characters * @@ -575,7 +542,7 @@ parse_transform_parameters(const char **cursor, for(param_idx = 0; param_idx < param_max; param_idx++) { tokend = textend; - err = parse_number(*cursor, &tokend, ¶mv[param_idx]); + err = svgtiny_parse_number(*cursor, &tokend, ¶mv[param_idx]); if (err != svgtiny_OK) { /* failed to parse number */ return err; @@ -742,7 +709,7 @@ parse_color_function(const char **cursorout, for (idx = 0; idx < 4; idx++) { argend = textend; - res = parse_number(cursor, &argend, &argf[idx]); + res = svgtiny_parse_number(cursor, &argend, &argf[idx]); if (res != svgtiny_OK) { break; } @@ -863,13 +830,13 @@ parse_paint_url(const char **cursorout, return res; } + /** * parse a none token * * \return svgtiny_OK if none found else svgtiny_SVG_ERROR if not */ -static svgtiny_code -parse_none(const char *cursor, const char *textend) +svgtiny_code svgtiny_parse_none(const char *cursor, const char *textend) { const char *noneend; if ((textend - cursor) < 4) { @@ -914,7 +881,7 @@ svgtiny_parse_paint(const char *text, advance_whitespace(&cursor, textend); - res = parse_none(cursor, textend); + res = svgtiny_parse_none(cursor, textend); if (res == svgtiny_OK) { *c = svgtiny_TRANSPARENT; return res; @@ -941,7 +908,7 @@ svgtiny_parse_offset(const char *text, size_t textlen, float *offset) const char *numend; numend = text + textlen; - err = parse_number(text, &numend, &number); + err = svgtiny_parse_number(text, &numend, &number); if (err != svgtiny_OK) { return err; } @@ -1011,7 +978,6 @@ dispatch_op(const char *value, return res; } - /** * parse a declaration in a style * @@ -1110,7 +1076,7 @@ svgtiny_parse_poly_points(const char *text, while (cursor < textend) { numberend=textend; - err = parse_number(cursor, &numberend, &point); + err = svgtiny_parse_number(cursor, &numberend, &point); if (err != svgtiny_OK) { break; } @@ -1150,7 +1116,7 @@ svgtiny_parse_length(const char *text, float font_size = 20; /*css_len2px(&state.style.font_size.value.length, 0);*/ unit = text + textlen; - err = parse_number(text, &unit, &number); + err = svgtiny_parse_number(text, &unit, &number); if (err != svgtiny_OK) { unitlen = -1; } else { @@ -1336,8 +1302,6 @@ transform_parse_complete: } - - /** * Parse a color. * @@ -1374,7 +1338,7 @@ svgtiny_parse_color(const char *text, size_t textlen, svgtiny_colour *c) return res; } - named_color = svgtiny_color_lookup(text, textlen); + named_color = svgtiny_color_lookup(cursor, textend - cursor); if (named_color) { *c = named_color->color; return svgtiny_OK; @@ -1412,7 +1376,7 @@ svgtiny_parse_viewbox(const char *text, for (paramidx = 0; paramidx < 3; paramidx++) { paramend = textend; - res = parse_number(cursor, ¶mend, ¶mv[paramidx]); + res = svgtiny_parse_number(cursor, ¶mend, ¶mv[paramidx]); if (res != svgtiny_OK) { /* failed to parse number */ return res; @@ -1421,7 +1385,7 @@ svgtiny_parse_viewbox(const char *text, advance_comma_whitespace(&cursor, textend); } paramend = textend; - res = parse_number(cursor, ¶mend, ¶mv[paramidx]); + res = svgtiny_parse_number(cursor, ¶mend, ¶mv[paramidx]); if (res != svgtiny_OK) { /* failed to parse number */ return res; diff --git a/src/svgtiny_parse.h b/src/svgtiny_parse.h new file mode 100644 index 0000000..6de8e50 --- /dev/null +++ b/src/svgtiny_parse.h @@ -0,0 +1,46 @@ +/* + * This file is part of Libsvgtiny + * Licensed under the MIT License, + * http://opensource.org/licenses/mit-license.php + * Copyright 2024 Vincent Sanders + */ + +#ifndef SVGTINY_PARSE_H +#define SVGTINY_PARSE_H + +/** + * skip SVG spec defined whitespace + * + * \param cursor current cursor + * \param textend end of buffer + */ +static inline void advance_whitespace(const char **cursor, const char *textend) +{ + while((*cursor) < textend) { + if ((**cursor != 0x20) && + (**cursor != 0x09) && + (**cursor != 0x0A) && + (**cursor != 0x0D)) { + break; + } + (*cursor)++; + } +} + + +/** + * advance cursor across SVG spec defined comma and whitespace + * + * \param cursor current cursor + * \param textend end of buffer + */ +static inline void advance_comma_whitespace(const char **cursor, const char *textend) +{ + advance_whitespace(cursor, textend); + if (((*cursor) < textend) && (**cursor == 0x2C /* , */)) { + (*cursor)++; + advance_whitespace(cursor, textend); + } +} + +#endif diff --git a/src/svgtiny_path.c b/src/svgtiny_path.c new file mode 100644 index 0000000..e193d0c --- /dev/null +++ b/src/svgtiny_path.c @@ -0,0 +1,1183 @@ +/* + * This file is part of Libsvgtiny + * Licensed under the MIT License, + * http://opensource.org/licenses/mit-license.php + * Copyright 2024 Vincent Sanders + */ + +#include +#include +#include + +#include "svgtiny.h" +#include "svgtiny_internal.h" +#include "svgtiny_parse.h" + +#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 degToRad(angleInDegrees) ((angleInDegrees) * M_PI / 180.0) +#define radToDeg(angleInRadians) ((angleInRadians) * 180.0 / M_PI) + + +struct internal_points { + float *p; /* points */ + unsigned int used; /* number of used points */ + size_t alloc; /* number of allocated points */ +}; + +/** + * internal path representation + */ +struct internal_path_state { + struct internal_points path;/* generated path */ + struct internal_points cmd; /* parameters to current command */ + float prev_x; /* previous x coordinate */ + float prev_y; /* previous y coordinate */ + float subpath_x; /* subpath start x coordinate */ + float subpath_y; /* subpath start y coordinate */ + float cubic_x; /* previous cubic x control point */ + float cubic_y; /* previous cubic y control point */ + float quad_x; /* previous quadratic x control point */ + float quad_y; /* previous quadratic y control point */ +}; + +/** + * ensure there is enough storage allocated to make points available + * + * \param ipts internal points to enaure space in + * \param count The number of points of space to ensure. + * \return svgtiny_OK on success else svgtiny_OUT_OF_MEMORY if allocation fails + */ +static inline svgtiny_code +ensure_internal_points(struct internal_points *ipts, unsigned int count) +{ + svgtiny_code res = svgtiny_OK; + float *nalloc; + + if ((ipts->used + count) > ipts->alloc) { + nalloc = realloc(ipts->p, sizeof ipts->p[0] * (ipts->alloc + 84)); + if (nalloc == NULL) { + return svgtiny_OUT_OF_MEMORY; + } + ipts->p = nalloc; + ipts->alloc = ipts->alloc + 84; + } + return res; +} + +/** + * moveto + * + * command and parameters: M|m (x, y)+ + */ +static inline svgtiny_code +generate_path_move(struct internal_path_state *state, int relative) +{ + svgtiny_code res; + unsigned int cmdpc = 0; /* command parameter count */ + + if ((state->cmd.used < 2) || ((state->cmd.used % 2) != 0)) { + /* wrong number of parameters */ + return svgtiny_SVG_ERROR; + } + + for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc += 2) { + res = ensure_internal_points(&state->path, 3); + if (res != svgtiny_OK) { + return res; + } + + if (relative != 0) { + state->cmd.p[cmdpc] += state->prev_x; + state->cmd.p[cmdpc + 1] += state->prev_y; + } + + if (cmdpc == 0) { + state->path.p[state->path.used++] = svgtiny_PATH_MOVE; + /* the move starts a subpath */ + state->path.p[state->path.used++] = + state->subpath_x = + state->cubic_x = + state->prev_x = state->cmd.p[cmdpc]; + state->path.p[state->path.used++] = + state->subpath_y = + state->cubic_y = + state->prev_y = state->cmd.p[cmdpc + 1]; + } else { + state->path.p[state->path.used++] = svgtiny_PATH_LINE; + + state->path.p[state->path.used++] = + state->cubic_x = + state->quad_x = + state->prev_x = state->cmd.p[cmdpc]; + state->path.p[state->path.used++] = + state->cubic_y = + state->quad_y = + state->prev_y = state->cmd.p[cmdpc + 1]; + } + + } + return svgtiny_OK; +} + +/** + * closepath + * + * command and parameters: Z|z + */ +static inline svgtiny_code +generate_path_close(struct internal_path_state *state) +{ + svgtiny_code res; + + if (state->cmd.used != 0) { + /* wrong number of parameters */ + return svgtiny_SVG_ERROR; + } + + res = ensure_internal_points(&state->path, 1); + if (res != svgtiny_OK) { + return res; + } + + state->path.p[state->path.used++] = svgtiny_PATH_CLOSE; + + state->cubic_x = state->quad_x = state->prev_x = state->subpath_x; + state->cubic_y = state->quad_y = state->prev_y = state->subpath_y; + + return svgtiny_OK; +} + + +/** + * lineto + * + * command and parameters: L|l (x, y)+ + */ +static inline svgtiny_code +generate_path_line(struct internal_path_state *state, int relative) +{ + svgtiny_code res; + unsigned int cmdpc = 0; /* command parameter count */ + + if ((state->cmd.used < 2) || ((state->cmd.used % 2) != 0)) { + /* wrong number of parameters */ + return svgtiny_SVG_ERROR; + } + + for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc += 2) { + res = ensure_internal_points(&state->path, 3); + if (res != svgtiny_OK) { + return res; + } + + if (relative != 0) { + state->cmd.p[cmdpc] += state->prev_x; + state->cmd.p[cmdpc + 1] += state->prev_y; + } + state->path.p[state->path.used++] = svgtiny_PATH_LINE; + state->path.p[state->path.used++] = + state->cubic_x = + state->quad_x = + state->prev_x = state->cmd.p[cmdpc]; + state->path.p[state->path.used++] = + state->cubic_y = + state->quad_y = + state->prev_y = state->cmd.p[cmdpc + 1]; + } + return svgtiny_OK; +} + + +/** + * horizontal lineto + * + * command and parameters: H|h x+ + */ +static inline svgtiny_code +generate_path_hline(struct internal_path_state *state, int relative) +{ + svgtiny_code res; + unsigned int cmdpc = 0; /* command parameter count */ + + if (state->cmd.used < 1) { + /* wrong number of parameters */ + return svgtiny_SVG_ERROR; + } + + for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc++) { + res = ensure_internal_points(&state->path, 3); + if (res != svgtiny_OK) { + return res; + } + + if (relative != 0) { + state->cmd.p[cmdpc] += state->prev_x; + } + state->path.p[state->path.used++] = svgtiny_PATH_LINE; + state->path.p[state->path.used++] = + state->cubic_x = + state->quad_x = + state->prev_x = state->cmd.p[cmdpc]; + state->path.p[state->path.used++] = + state->cubic_y = + state->quad_x = state->prev_y; + } + return svgtiny_OK; +} + +/** + * vertical lineto + * + * command and parameters: V|v y+ + */ +static inline svgtiny_code +generate_path_vline(struct internal_path_state *state, int relative) +{ + svgtiny_code res; + unsigned int cmdpc = 0; /* command parameter count */ + + if (state->cmd.used < 1) { + /* wrong number of parameters */ + return svgtiny_SVG_ERROR; + } + + for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc++) { + res = ensure_internal_points(&state->path, 3); + if (res != svgtiny_OK) { + return res; + } + + if (relative != 0) { + state->cmd.p[cmdpc] += state->prev_y; + } + state->path.p[state->path.used++] = svgtiny_PATH_LINE; + state->path.p[state->path.used++] = + state->cubic_x = + state->quad_x = state->prev_x; + state->path.p[state->path.used++] = + state->cubic_y = + state->quad_y = + state->prev_y = state->cmd.p[cmdpc]; + } + return svgtiny_OK; +} + +/** + * curveto + * + * command and parameters: C|c (x1, y1, x2, y2, x, y)+ + */ +static inline svgtiny_code +generate_path_curveto(struct internal_path_state *state, int relative) +{ + svgtiny_code res; + unsigned int cmdpc = 0; /* command parameter count */ + + if ((state->cmd.used < 6) || ((state->cmd.used % 6) != 0)) { + /* wrong number of parameters */ + return svgtiny_SVG_ERROR; + } + + for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc += 6) { + res = ensure_internal_points(&state->path, 7); + if (res != svgtiny_OK) { + return res; + } + + if (relative != 0) { + state->cmd.p[cmdpc + 0] += state->prev_x; /* x1 */ + state->cmd.p[cmdpc + 1] += state->prev_y; /* y1 */ + state->cmd.p[cmdpc + 2] += state->prev_x; /* x2 */ + state->cmd.p[cmdpc + 3] += state->prev_y; /* y2 */ + state->cmd.p[cmdpc + 4] += state->prev_x; /* x */ + state->cmd.p[cmdpc + 5] += state->prev_y; /* y */ + } + + state->path.p[state->path.used++] = svgtiny_PATH_BEZIER; + state->path.p[state->path.used++] = state->cmd.p[cmdpc + 0]; + state->path.p[state->path.used++] = state->cmd.p[cmdpc + 1]; + state->path.p[state->path.used++] = + state->cubic_x = state->cmd.p[cmdpc + 2]; + state->path.p[state->path.used++] = + state->cubic_y = state->cmd.p[cmdpc + 3]; + state->path.p[state->path.used++] = + state->quad_x = state->prev_x = state->cmd.p[cmdpc + 4]; + state->path.p[state->path.used++] = + state->quad_y = state->prev_y = state->cmd.p[cmdpc + 5]; + } + return svgtiny_OK; +} + +/** + * smooth curveto + * + * command and parameters: S|s (x2, y2, x, y)+ + */ +static inline svgtiny_code +generate_path_scurveto(struct internal_path_state *state, int relative) +{ + svgtiny_code res; + unsigned int cmdpc = 0; /* command parameter count */ + + if ((state->cmd.used < 4) || ((state->cmd.used % 4) != 0)) { + /* wrong number of parameters */ + return svgtiny_SVG_ERROR; + } + + for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc += 4) { + float x1; + float y1; + + res = ensure_internal_points(&state->path, 7); + if (res != svgtiny_OK) { + return res; + } + + x1 = state->prev_x + (state->prev_x - state->cubic_x); + y1 = state->prev_y + (state->prev_y - state->cubic_y); + if (relative != 0) { + state->cmd.p[cmdpc + 0] += state->prev_x; /* x2 */ + state->cmd.p[cmdpc + 1] += state->prev_y; /* y2 */ + state->cmd.p[cmdpc + 2] += state->prev_x; /* x */ + state->cmd.p[cmdpc + 3] += state->prev_y; /* y */ + } + + state->path.p[state->path.used++] = svgtiny_PATH_BEZIER; + state->path.p[state->path.used++] = x1; + state->path.p[state->path.used++] = y1; + state->path.p[state->path.used++] = + state->cubic_x = state->cmd.p[cmdpc + 0]; + state->path.p[state->path.used++] = + state->cubic_x = state->cmd.p[cmdpc + 1]; + state->path.p[state->path.used++] = + state->quad_x = + state->prev_x = state->cmd.p[cmdpc + 2]; + state->path.p[state->path.used++] = + state->quad_y = + state->prev_y = state->cmd.p[cmdpc + 3]; + } + return svgtiny_OK; +} + +/** + * quadratic Bezier curveto + * + * command and parameters: Q|q (x1, y1, x ,y)+ + */ +static inline svgtiny_code +generate_path_bcurveto(struct internal_path_state *state, int relative) +{ + svgtiny_code res; + unsigned int cmdpc = 0; /* command parameter count */ + + if ((state->cmd.used < 4) || ((state->cmd.used % 4) != 0)) { + /* wrong number of parameters */ + return svgtiny_SVG_ERROR; + } + + for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc += 4) { + res = ensure_internal_points(&state->path, 7); + if (res != svgtiny_OK) { + return res; + } + + state->quad_x = state->cmd.p[cmdpc + 0] /* x1 */; + state->quad_y = state->cmd.p[cmdpc + 1] /* y1 */; + if (relative != 0) { + state->cmd.p[cmdpc + 0] += state->prev_x; /* x1 */ + state->cmd.p[cmdpc + 1] += state->prev_y; /* y1 */ + state->cmd.p[cmdpc + 2] += state->prev_x; /* x */ + state->cmd.p[cmdpc + 3] += state->prev_y; /* y */ + } + + state->path.p[state->path.used++] = svgtiny_PATH_BEZIER; + state->path.p[state->path.used++] = + 1./3 * state->prev_x + + 2./3 * state->cmd.p[cmdpc + 0]; + state->path.p[state->path.used++] = + 1./3 * state->prev_y + + 2./3 * state->cmd.p[cmdpc + 1]; + state->path.p[state->path.used++] = + 2./3 * state->cmd.p[cmdpc + 0] + + 1./3 * state->cmd.p[cmdpc + 2]; + state->path.p[state->path.used++] = + 2./3 * state->cmd.p[cmdpc + 1] + + 1./3 * state->cmd.p[cmdpc + 3]; + state->path.p[state->path.used++] = + state->cubic_x = + state->prev_x = state->cmd.p[cmdpc + 2]; + state->path.p[state->path.used++] = + state->cubic_y = + state->prev_y = state->cmd.p[cmdpc + 3]; + } + return svgtiny_OK; +} + +/** + * smooth quadratic Bezier curveto + * + * command and parameters: T|t (x ,y)+ + */ +static inline svgtiny_code +generate_path_sbcurveto(struct internal_path_state *state, int relative) +{ + svgtiny_code res; + unsigned int cmdpc = 0; /* command parameter count */ + + if ((state->cmd.used < 2) || ((state->cmd.used % 2) != 0)) { + /* wrong number of parameters */ + return svgtiny_SVG_ERROR; + } + + for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc += 2) { + float x1; + float y1; + + res = ensure_internal_points(&state->path, 7); + if (res != svgtiny_OK) { + return res; + } + + x1 = state->prev_x + (state->prev_x - state->quad_x); + y1 = state->prev_y + (state->prev_y - state->quad_y); + state->quad_x = x1; + state->quad_y = y1; + + if (relative != 0) { + x1 += state->prev_x; + y1 += state->prev_y; + state->cmd.p[cmdpc + 0] += state->prev_x; /* x */ + state->cmd.p[cmdpc + 1] += state->prev_y; /* y */ + } + + state->path.p[state->path.used++] = svgtiny_PATH_BEZIER; + state->path.p[state->path.used++] = + 1./3 * state->prev_x + + 2./3 * x1; + state->path.p[state->path.used++] = + 1./3 * state->prev_y + + 2./3 * y1; + state->path.p[state->path.used++] = + 2./3 * x1 + + 1./3 * state->cmd.p[cmdpc + 0]; + state->path.p[state->path.used++] = + 2./3 * y1 + + 1./3 * state->cmd.p[cmdpc + 1]; + state->path.p[state->path.used++] = + state->cubic_x = + state->prev_x = state->cmd.p[cmdpc + 0]; + state->path.p[state->path.used++] = + state->cubic_y = + state->prev_y = state->cmd.p[cmdpc + 1]; + } + return svgtiny_OK; +} + + +/** + * 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; +} + +/** + * elliptical arc + * + * command and parameters: A|a (rx, ry, rotation, largearc, sweep, x, y)+ + */ +static svgtiny_code +generate_path_ellipticalarc(struct internal_path_state *state, int relative) +{ + svgtiny_code res; + unsigned int cmdpc = 0; /* command parameter count */ + + if ((state->cmd.used < 7) || ((state->cmd.used % 7) != 0)) { + /* wrong number of parameters */ + return svgtiny_SVG_ERROR; + } + + for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc += 7) { + int bzsegments; + double bzpoints[6*4]; /* allow for up to four bezier segments per arc */ + if (relative != 0) { + state->cmd.p[cmdpc + 5] += state->prev_x; /* x */ + state->cmd.p[cmdpc + 6] += state->prev_y; /* y */ + } + + bzsegments = svgarc_to_bezier(state->prev_x, state->prev_y, + state->cmd.p[cmdpc + 5], /* x */ + state->cmd.p[cmdpc + 6], /* y */ + state->cmd.p[cmdpc + 0], /* rx */ + state->cmd.p[cmdpc + 1], /* ry */ + state->cmd.p[cmdpc + 2], /* rotation */ + state->cmd.p[cmdpc + 3], /* large_arc */ + state->cmd.p[cmdpc + 4], /* sweep */ + bzpoints); + + if (bzsegments == -1) { + /* failed to convert arc to bezier replace with line */ + res = ensure_internal_points(&state->path, 3); + if (res != svgtiny_OK) { + return res; + } + state->path.p[state->path.used++] = svgtiny_PATH_LINE; + state->path.p[state->path.used++] = + state->cubic_x = + state->quad_x = + state->prev_x = state->cmd.p[cmdpc + 5] /* x */; + state->path.p[state->path.used++] = + state->cubic_y = + state->quad_y = + state->prev_y = state->cmd.p[cmdpc + 6] /* y */; + } else if (bzsegments > 0){ + int bzpnt; + for (bzpnt = 0;bzpnt < (bzsegments * 6); bzpnt+=6) { + res = ensure_internal_points(&state->path, 7); + if (res != svgtiny_OK) { + return res; + } + state->path.p[state->path.used++] = svgtiny_PATH_BEZIER; + state->path.p[state->path.used++] = bzpoints[bzpnt]; + state->path.p[state->path.used++] = bzpoints[bzpnt+1]; + state->path.p[state->path.used++] = bzpoints[bzpnt+2]; + state->path.p[state->path.used++] = bzpoints[bzpnt+3]; + state->path.p[state->path.used++] = + state->cubic_y = + state->quad_x = + state->prev_x = bzpoints[bzpnt+4]; + state->path.p[state->path.used++] = + state->cubic_y = + state->quad_y = + state->prev_y = bzpoints[bzpnt+5]; + } + } + } + return svgtiny_OK; +} + + +/** + * parse parameters to a path command + */ +static inline svgtiny_code +parse_path_parameters(const char **cursor, + const char *textend, + struct internal_points *cmdp) +{ + const char *numend; + svgtiny_code res; + + cmdp->used = 0; + + do { + res = ensure_internal_points(cmdp, 1); + if (res != svgtiny_OK) { + return res; + } + + numend = textend; + res = svgtiny_parse_number(*cursor, + &numend, + cmdp->p + cmdp->used); + if (res != svgtiny_OK) { + break; + } + cmdp->used++; + *cursor = numend; + + advance_comma_whitespace(cursor, textend); + + } while (res == svgtiny_OK); + + return svgtiny_OK; +} + + +/** + * parse a path command + */ +static inline svgtiny_code +parse_path_command(const char **cursor, + const char *textend, + char *pathcommand, + int *relative) +{ + advance_whitespace(cursor, textend); + + if ((**cursor != 'M') && (**cursor != 'm') && + (**cursor != 'Z') && (**cursor != 'z') && + (**cursor != 'L') && (**cursor != 'l') && + (**cursor != 'H') && (**cursor != 'h') && + (**cursor != 'V') && (**cursor != 'v') && + (**cursor != 'C') && (**cursor != 'c') && + (**cursor != 'S') && (**cursor != 's') && + (**cursor != 'Q') && (**cursor != 'q') && + (**cursor != 'T') && (**cursor != 't') && + (**cursor != 'A') && (**cursor != 'a')) { + return svgtiny_SVG_ERROR; + } + + if ((**cursor >= 0x61 /* a */) && (**cursor <= 0x7A /* z */)) { + *pathcommand = (**cursor) & ~(1 << 5); /* upper case char */ + *relative = 1; + } else { + *pathcommand = **cursor; + *relative = 0; + } + (*cursor)++; + + advance_whitespace(cursor, textend); + + return svgtiny_OK; +} + + +/** + * parse path data attribute into a shape list + */ +svgtiny_code +svgtiny_parse_path_data(const char *text, + size_t textlen, + float **pointv, + unsigned int *pointc) +{ + const char *cursor = text; /* text cursor */ + const char *textend = text + textlen; + svgtiny_code res; + char pathcmd = 0; + int relative = 0; + struct internal_path_state pathstate = { + {NULL, 0, 0}, + {NULL, 0, 0}, + 0.0, 0.0, + 0.0, 0.0, + 0.0, 0.0, + 0.0, 0.0 + }; + + advance_whitespace(&cursor, textend); + + /* empty path data - equivalent to none */ + if (cursor == textend) { + *pointc = 0; + return svgtiny_OK; + } + + /* none */ + res = svgtiny_parse_none(cursor, textend); + if (res == svgtiny_OK) { + *pointc = 0; + return res; + } + + while (cursor < textend) { + res = parse_path_command(&cursor, textend, &pathcmd, &relative); + if (res != svgtiny_OK) { + goto parse_path_data_error; + } + + res = parse_path_parameters(&cursor, textend, &pathstate.cmd); + if (res != svgtiny_OK) { + goto parse_path_data_error; + } + + switch (pathcmd) { + case 'M': + res = generate_path_move(&pathstate, relative); + break; + case 'Z': + res = generate_path_close(&pathstate); + break; + case 'L': + res = generate_path_line(&pathstate, relative); + break; + case 'H': + res = generate_path_hline(&pathstate, relative); + break; + case 'V': + res = generate_path_vline(&pathstate, relative); + break; + case 'C': + res = generate_path_curveto(&pathstate, relative); + break; + case 'S': + res = generate_path_scurveto(&pathstate, relative); + break; + case 'Q': + res = generate_path_bcurveto(&pathstate, relative); + break; + case 'T': + res = generate_path_sbcurveto(&pathstate, relative); + break; + case 'A': + res = generate_path_ellipticalarc(&pathstate, relative); + break; + } + + if (res != svgtiny_OK) { + goto parse_path_data_error; + } + + } + *pointv = pathstate.path.p; + *pointc = pathstate.path.used; + + if (pathstate.cmd.alloc > 0) { + free(pathstate.cmd.p); + } + return svgtiny_OK; + +parse_path_data_error: + /* free any local state */ + if (pathstate.path.alloc > 0) { + free(pathstate.path.p); + } + if (pathstate.cmd.alloc > 0) { + free(pathstate.cmd.p); + } + return res; +} diff --git a/test/data/arcs01.mvg b/test/data/arcs01.mvg new file mode 100644 index 0000000..02562b2 --- /dev/null +++ b/test/data/arcs01.mvg @@ -0,0 +1,5 @@ +viewbox 0 0 425 186 +fill none stroke #0000ff stroke-width 1 path 'M 0.354331 0.465059 L 424.842 0.465059 L 424.842 185.559 L 0.354331 185.559 Z ' +fill #ff0000 stroke #0000ff stroke-width 2 path 'M 106.299 93.0118 L 53.1496 93.0118 C 53.1496 131.539 76.9455 162.771 106.299 162.771 C 135.653 162.771 159.449 131.539 159.449 93.0118 C 159.449 54.4851 135.653 23.253 106.299 23.253 Z ' +fill #ffff00 stroke #0000ff stroke-width 2 path 'M 97.4409 81.3853 L 97.4409 11.6265 C 68.0872 11.6265 44.2913 42.8586 44.2913 81.3853 Z ' +fill none stroke #ff0000 stroke-width 2 path 'M 212.598 162.771 L 230.315 151.144 C 227.898 144.729 229.887 136.957 234.764 133.756 C 239.641 130.556 245.571 133.131 248.031 139.518 L 265.748 127.891 C 260.469 115.397 260.143 102.711 265.02 99.5103 C 269.897 96.3099 278.142 103.799 283.465 116.265 L 301.181 104.638 C 293.133 85.9932 290.57 68.3356 295.447 65.1351 C 300.324 61.9347 310.806 74.3954 318.898 93.0118 L 336.614 81.3853 C 325.821 56.5716 321.04 33.9279 325.916 30.7275 C 330.793 27.527 343.494 44.9738 354.331 69.7589 L 372.047 58.1324 ' diff --git a/test/data/arcs01.svg b/test/data/arcs01.svg new file mode 100644 index 0000000..edec92e --- /dev/null +++ b/test/data/arcs01.svg @@ -0,0 +1,21 @@ + + + Example arcs01 - arc commands in path data + Picture of a pie chart with two pie wedges and + a picture of a line with arc blips + + + + + + + diff --git a/test/data/arcs02.svg b/test/data/arcs02.svg new file mode 100644 index 0000000..f58f5d6 --- /dev/null +++ b/test/data/arcs02.svg @@ -0,0 +1,57 @@ + + + Example arcs02 - arc options in paths + Pictures showing the result of setting + large-arc-flag and sweep-flag to the four + possible combinations of 0 and 1. + + + + + + Arc start + Arc end + + + + + + + + + + large-arc-flag=0 + sweep-flag=0 + + + + + large-arc-flag=0 + sweep-flag=1 + + + + + large-arc-flag=1 + sweep-flag=0 + + + + + large-arc-flag=1 + sweep-flag=1 + + + + + + + diff --git a/test/data/cubic01.mvg b/test/data/cubic01.mvg new file mode 100644 index 0000000..f5d41a9 --- /dev/null +++ b/test/data/cubic01.mvg @@ -0,0 +1,16 @@ +viewbox 0 0 177 141 +fill none stroke #0000ff stroke-width 1 path 'M 0.354331 0.354331 L 176.811 0.354331 L 176.811 141.378 L 0.354331 141.378 Z ' +fill none stroke #888888 stroke-width 1 path 'M 35.4331 70.8661 L 35.4331 35.4331 ' +fill none stroke #888888 stroke-width 1 path 'M 88.5827 35.4331 L 88.5827 70.8661 ' +fill none stroke #888888 stroke-width 1 path 'M 88.5827 70.8661 L 88.5827 106.299 ' +fill none stroke #888888 stroke-width 1 path 'M 141.732 106.299 L 141.732 70.8661 ' +fill none stroke #ff0000 stroke-width 2 path 'M 35.4331 70.8661 C 35.4331 35.4331 88.5827 35.4331 88.5827 70.8661 C 88.5827 106.299 141.732 106.299 141.732 70.8661 ' +fill none stroke #888888 stroke-width 1 path 'M 38.9764 70.8661 C 38.9764 72.8231 37.39 74.4095 35.4331 74.4095 C 33.4762 74.4095 31.8898 72.8231 31.8898 70.8661 C 31.8898 68.9092 33.4762 67.3228 35.4331 67.3228 C 37.39 67.3228 38.9764 68.9092 38.9764 70.8661 Z ' +fill none stroke #888888 stroke-width 1 path 'M 92.126 70.8661 C 92.126 72.8231 90.5396 74.4095 88.5827 74.4095 C 86.6258 74.4095 85.0394 72.8231 85.0394 70.8661 C 85.0394 68.9092 86.6258 67.3228 88.5827 67.3228 C 90.5396 67.3228 92.126 68.9092 92.126 70.8661 Z ' +fill none stroke #888888 stroke-width 1 path 'M 145.276 70.8661 C 145.276 72.8231 143.689 74.4095 141.732 74.4095 C 139.775 74.4095 138.189 72.8231 138.189 70.8661 C 138.189 68.9092 139.775 67.3228 141.732 67.3228 C 143.689 67.3228 145.276 68.9092 145.276 70.8661 Z ' +fill #888888 stroke none stroke-width 1 path 'M 38.9764 35.4331 C 38.9764 37.39 37.39 38.9764 35.4331 38.9764 C 33.4762 38.9764 31.8898 37.39 31.8898 35.4331 C 31.8898 33.4762 33.4762 31.8898 35.4331 31.8898 C 37.39 31.8898 38.9764 33.4762 38.9764 35.4331 Z ' +fill #888888 stroke none stroke-width 1 path 'M 92.126 35.4331 C 92.126 37.39 90.5396 38.9764 88.5827 38.9764 C 86.6258 38.9764 85.0394 37.39 85.0394 35.4331 C 85.0394 33.4762 86.6258 31.8898 88.5827 31.8898 C 90.5396 31.8898 92.126 33.4762 92.126 35.4331 Z ' +fill #888888 stroke none stroke-width 1 path 'M 145.276 106.299 C 145.276 108.256 143.689 109.843 141.732 109.843 C 139.775 109.843 138.189 108.256 138.189 106.299 C 138.189 104.342 139.775 102.756 141.732 102.756 C 143.689 102.756 145.276 104.342 145.276 106.299 Z ' +fill none stroke #0000ff stroke-width 1 path 'M 91.7716 106.299 C 91.7716 108.06 90.3439 109.488 88.5827 109.488 C 86.8214 109.488 85.3937 108.06 85.3937 106.299 C 85.3937 104.538 86.8214 103.11 88.5827 103.11 C 90.3439 103.11 91.7716 104.538 91.7716 106.299 Z ' +fill #000000 stroke none stroke-width 1 text 8.85827 24.8032 'M100,200 C100,100 250,100 250,200' +fill #000000 stroke none stroke-width 1 text 115.157 124.016 'S400,300 400,200' diff --git a/test/data/cubic01.svg b/test/data/cubic01.svg new file mode 100644 index 0000000..c818009 --- /dev/null +++ b/test/data/cubic01.svg @@ -0,0 +1,27 @@ + + + Example cubic01- cubic Bézier commands in path data + Picture showing a simple example of path data + using both a "C" and an "S" command, + along with annotations showing the control points + and end points + + + + + + + + + + + + + + + + M100,200 C100,100 250,100 250,200 + S400,300 400,200 + diff --git a/test/data/cubic02.svg b/test/data/cubic02.svg new file mode 100644 index 0000000..a6728f1 --- /dev/null +++ b/test/data/cubic02.svg @@ -0,0 +1,91 @@ + + + Example cubic02 - cubic Bézier commands in path data + Picture showing examples of "C" and "S" commands, + along with annotations showing the control points + and end points + + + + + + + + + + + + + M100,200 C100,100 400,100 400,200 + + + + + + + + + + M100,500 C25,400 475,400 400,500 + + + + + + + + + + M100,800 C175,700 325,700 400,800 + + + + + + + + + + M600,200 C675,100 975,100 900,200 + + + + + + + + + + M600,500 C600,350 900,650 900,500 + + + + + + + + + + + + + + + M600,800 C625,700 725,700 750,800 + S875,900 900,800 + diff --git a/test/data/quad01.mvg b/test/data/quad01.mvg new file mode 100644 index 0000000..0567ba3 --- /dev/null +++ b/test/data/quad01.mvg @@ -0,0 +1,9 @@ +viewbox 0 0 425 212 +fill none stroke #0000ff stroke-width 1 path 'M 0.354331 0.354331 L 424.842 0.354331 L 424.842 212.244 L 0.354331 212.244 Z ' +fill none stroke #ff0000 stroke-width 2 path 'M 70.8661 106.299 C 118.11 47.2441 165.354 47.2441 212.598 106.299 C 259.842 165.354 307.087 165.354 354.331 106.299 ' +fill #000000 stroke none stroke-width 1 path 'M 74.4094 106.299 C 74.4094 108.256 72.8231 109.843 70.8661 109.843 C 68.9092 109.843 67.3228 108.256 67.3228 106.299 C 67.3228 104.342 68.9092 102.756 70.8661 102.756 C 72.8231 102.756 74.4094 104.342 74.4094 106.299 Z ' +fill #000000 stroke none stroke-width 1 path 'M 216.142 106.299 C 216.142 108.256 214.555 109.843 212.598 109.843 C 210.642 109.843 209.055 108.256 209.055 106.299 C 209.055 104.342 210.642 102.756 212.598 102.756 C 214.555 102.756 216.142 104.342 216.142 106.299 Z ' +fill #000000 stroke none stroke-width 1 path 'M 357.874 106.299 C 357.874 108.256 356.288 109.843 354.331 109.843 C 352.374 109.843 350.787 108.256 350.787 106.299 C 350.787 104.342 352.374 102.756 354.331 102.756 C 356.288 102.756 357.874 104.342 357.874 106.299 Z ' +fill #888888 stroke none stroke-width 1 path 'M 145.276 17.7165 C 145.276 19.6734 143.689 21.2598 141.732 21.2598 C 139.775 21.2598 138.189 19.6734 138.189 17.7165 C 138.189 15.7596 139.775 14.1732 141.732 14.1732 C 143.689 14.1732 145.276 15.7596 145.276 17.7165 Z ' +fill #888888 stroke none stroke-width 1 path 'M 287.008 194.882 C 287.008 196.839 285.421 198.425 283.465 198.425 C 281.508 198.425 279.921 196.839 279.921 194.882 C 279.921 192.925 281.508 191.339 283.465 191.339 C 285.421 191.339 287.008 192.925 287.008 194.882 Z ' +fill none stroke #888888 stroke-width 1 path 'M 70.8661 106.299 L 141.732 17.7165 L 212.598 106.299 L 283.465 194.882 L 354.331 106.299 ' diff --git a/test/data/quad01.svg b/test/data/quad01.svg new file mode 100644 index 0000000..48f0dfc --- /dev/null +++ b/test/data/quad01.svg @@ -0,0 +1,27 @@ + + + Example quad01 - quadratic Bézier commands in path data + Picture showing a "Q" a "T" command, + along with annotations showing the control points + and end points + + + + + + + + + + + + + + + + -- 2.49.0