X-Git-Url: https://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=src%2Fsvgtiny.c;h=74ce7eb1775cb92315ac8d7d521ef1533ab2c4a2;hb=aae96e5998971764de34a5e1755a8b27568e5605;hp=13249b62cdeb45068dfe7deb5b6ba8572ad2c1c2;hpb=15f6e2250a9c70e622ff2973a7b359b9e9c4619c;p=libsvgtiny.git diff --git a/src/svgtiny.c b/src/svgtiny.c index 13249b6..74ce7eb 100644 --- a/src/svgtiny.c +++ b/src/svgtiny.c @@ -17,15 +17,35 @@ #include #include +#include + #include "svgtiny.h" #include "svgtiny_internal.h" +/* Source file generated by `gperf`. */ +#include "autogenerated_colors.c" + +#define TAU 6.28318530717958647692 + #ifndef M_PI #define M_PI 3.14159265358979323846 #endif +#ifndef M_PI_2 +#define M_PI_2 1.57079632679489661923 +#endif + #define KAPPA 0.5522847498 +#define degToRad(angleInDegrees) ((angleInDegrees) * M_PI / 180.0) +#define radToDeg(angleInRadians) ((angleInRadians) * 180.0 / M_PI) + +static svgtiny_code svgtiny_parse_style_element(dom_element *style, + struct svgtiny_parse_state state); +static css_stylesheet *svgtiny_parse_style_inline(const uint8_t *data, + size_t len); +static svgtiny_code svgtiny_preparse_styles(dom_element *svg, + struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_svg(dom_element *svg, struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_path(dom_element *path, @@ -54,54 +74,517 @@ static void svgtiny_parse_transform_attributes(dom_element *node, static svgtiny_code svgtiny_add_path(float *p, unsigned int n, struct svgtiny_parse_state *state); static void _svgtiny_parse_color(const char *s, svgtiny_colour *c, + struct svgtiny_parse_state_gradient *grad, struct svgtiny_parse_state *state); /** - * Set the local externally-stored parts of a parse state. - * Call this in functions that made a new state on the stack. - * Doesn't make own copy of global state, such as the interned string list. + * rotate midpoint vector */ -static void svgtiny_setup_state_local(struct svgtiny_parse_state *state) +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) { - if (state->gradient_x1 != NULL) { - dom_string_ref(state->gradient_x1); + 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); } - if (state->gradient_y1 != NULL) { - dom_string_ref(state->gradient_y1); +} + + +/** + * 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); } - if (state->gradient_x2 != NULL) { - dom_string_ref(state->gradient_x2); +} + + +/** + * 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; } - if (state->gradient_y2 != NULL) { - dom_string_ref(state->gradient_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; } + /** - * Cleanup the local externally-stored parts of a parse state. - * Call this in functions that made a new state on the stack. - * Doesn't cleanup global state, such as the interned string list. + * Call this to ref the strings in a gradient state. */ -static void svgtiny_cleanup_state_local(struct svgtiny_parse_state *state) +static void svgtiny_grad_string_ref(struct svgtiny_parse_state_gradient *grad) +{ + if (grad->gradient_x1 != NULL) { + dom_string_ref(grad->gradient_x1); + } + if (grad->gradient_y1 != NULL) { + dom_string_ref(grad->gradient_y1); + } + if (grad->gradient_x2 != NULL) { + dom_string_ref(grad->gradient_x2); + } + if (grad->gradient_y2 != NULL) { + dom_string_ref(grad->gradient_y2); + } +} + +/** + * Call this to clean up the strings in a gradient state. + */ +static void svgtiny_grad_string_cleanup( + struct svgtiny_parse_state_gradient *grad) { - if (state->gradient_x1 != NULL) { - dom_string_unref(state->gradient_x1); - state->gradient_x1 = NULL; + if (grad->gradient_x1 != NULL) { + dom_string_unref(grad->gradient_x1); + grad->gradient_x1 = NULL; } - if (state->gradient_y1 != NULL) { - dom_string_unref(state->gradient_y1); - state->gradient_y1 = NULL; + if (grad->gradient_y1 != NULL) { + dom_string_unref(grad->gradient_y1); + grad->gradient_y1 = NULL; } - if (state->gradient_x2 != NULL) { - dom_string_unref(state->gradient_x2); - state->gradient_x2 = NULL; + if (grad->gradient_x2 != NULL) { + dom_string_unref(grad->gradient_x2); + grad->gradient_x2 = NULL; } - if (state->gradient_y2 != NULL) { - dom_string_unref(state->gradient_y2); - state->gradient_y2 = NULL; + if (grad->gradient_y2 != NULL) { + dom_string_unref(grad->gradient_y2); + grad->gradient_y2 = NULL; } } +/** + * Set the local externally-stored parts of a parse state. + * Call this in functions that made a new state on the stack. + * Doesn't make own copy of global state, such as the interned string list. + */ +static void svgtiny_setup_state_local(struct svgtiny_parse_state *state) +{ + svgtiny_grad_string_ref(&(state->fill_grad)); + svgtiny_grad_string_ref(&(state->stroke_grad)); +} + +/** + * Cleanup the local externally-stored parts of a parse state. + * Call this in functions that made a new state on the stack. + * Doesn't cleanup global state, such as the interned string list. + */ +static void svgtiny_cleanup_state_local(struct svgtiny_parse_state *state) +{ + svgtiny_grad_string_cleanup(&(state->fill_grad)); + svgtiny_grad_string_cleanup(&(state->stroke_grad)); +} + /** * Create a new svgtiny_diagram structure. @@ -135,6 +618,7 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, const char *buffer, size_t size, const char *url, int viewport_width, int viewport_height) { + css_error css_code; dom_document *document; dom_exception exc; dom_xml_parser *parser; @@ -152,11 +636,6 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, UNUSED(url); - state.gradient_x1 = NULL; - state.gradient_y1 = NULL; - state.gradient_x2 = NULL; - state.gradient_y2 = NULL; - parser = dom_xml_parser_create(NULL, NULL, ignore_msg, NULL, &document); @@ -188,6 +667,12 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, dom_node_unref(document); return svgtiny_LIBDOM_ERROR; } + if (svg == NULL) { + /* no root svg element */ + dom_node_unref(document); + return svgtiny_SVG_ERROR; + } + exc = dom_node_get_node_name(svg, &svg_name); if (exc != DOM_NO_ERR) { dom_node_unref(svg); @@ -212,13 +697,23 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, lwc_string_unref(svg_name_lwc); dom_string_unref(svg_name); - /* get graphic dimensions */ + /* initialize the state struct with zeros */ memset(&state, 0, sizeof(state)); + + /* get graphic dimensions */ state.diagram = diagram; state.document = document; state.viewport_width = viewport_width; state.viewport_height = viewport_height; + /* Initialize CSS context */ + css_code = css_select_ctx_create(&state.select_ctx); + if (css_code != CSS_OK) { + dom_node_unref(svg); + dom_node_unref(document); + return svgtiny_LIBCSS_ERROR; + } + #define SVGTINY_STRING_ACTION2(s,n) \ if (dom_string_create_interned((const uint8_t *) #n, \ strlen(#n), &state.interned_##s) \ @@ -229,6 +724,19 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, #include "svgtiny_strings.h" #undef SVGTINY_STRING_ACTION2 + /* Intern SVG's xmlns separately because it's an lwc_string + * and not a dom_string. We initialize its pointer to NULL + * because the "cleanup:" test to see if it needs to be free'd + * looks for NULL. Returning a LIBDOM_ERROR on failure is not + * perfect but it's the closest of the available options. */ + state.interned_svg_xmlns = NULL; + if (lwc_intern_string("http://www.w3.org/2000/svg", + 26, + &state.interned_svg_xmlns) != lwc_error_ok) { + code = svgtiny_LIBDOM_ERROR; + goto cleanup; + } + svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height); diagram->width = width; diagram->height = height; @@ -242,18 +750,22 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, 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; - state.linear_gradient_stop_count = 0; /* parse tree */ - code = svgtiny_parse_svg(svg, state); + code = svgtiny_preparse_styles(svg, state); + if (code == svgtiny_OK) { + code = svgtiny_parse_svg(svg, state); + } dom_node_unref(svg); dom_node_unref(document); + css_code = css_select_ctx_destroy(state.select_ctx); + if (css_code != CSS_OK) { + code = svgtiny_LIBCSS_ERROR; + } cleanup: svgtiny_cleanup_state_local(&state); @@ -262,10 +774,181 @@ cleanup: dom_string_unref(state.interned_##s); #include "svgtiny_strings.h" #undef SVGTINY_STRING_ACTION2 + + if (state.interned_svg_xmlns != NULL) { + lwc_string_unref(state.interned_svg_xmlns); + } + return code; } +/** + * Parse a single