#include "plutovg-private.h" #include "plutovg-utils.h" #include void plutovg_path_iterator_init(plutovg_path_iterator_t* it, const plutovg_path_t* path) { it->elements = path->elements.data; it->size = path->elements.size; it->index = 0; } bool plutovg_path_iterator_has_next(const plutovg_path_iterator_t* it) { return it->index < it->size; } plutovg_path_command_t plutovg_path_iterator_next(plutovg_path_iterator_t* it, plutovg_point_t points[3]) { const plutovg_path_element_t* elements = it->elements + it->index; switch(elements[0].header.command) { case PLUTOVG_PATH_COMMAND_MOVE_TO: case PLUTOVG_PATH_COMMAND_LINE_TO: case PLUTOVG_PATH_COMMAND_CLOSE: points[0] = elements[1].point; break; case PLUTOVG_PATH_COMMAND_CUBIC_TO: points[0] = elements[1].point; points[1] = elements[2].point; points[2] = elements[3].point; break; } it->index += elements[0].header.length; return elements[0].header.command; } plutovg_path_t* plutovg_path_create(void) { plutovg_path_t* path = malloc(sizeof(plutovg_path_t)); path->ref_count = 1; path->num_points = 0; path->num_contours = 0; path->num_curves = 0; path->start_point = PLUTOVG_MAKE_POINT(0, 0); plutovg_array_init(path->elements); return path; } plutovg_path_t* plutovg_path_reference(plutovg_path_t* path) { if(path == NULL) return NULL; ++path->ref_count; return path; } void plutovg_path_destroy(plutovg_path_t* path) { if(path == NULL) return; if(--path->ref_count == 0) { plutovg_array_destroy(path->elements); free(path); } } int plutovg_path_get_reference_count(const plutovg_path_t* path) { if(path) return path->ref_count; return 0; } int plutovg_path_get_elements(const plutovg_path_t* path, const plutovg_path_element_t** elements) { if(elements) *elements = path->elements.data; return path->elements.size; } static plutovg_path_element_t* plutovg_path_add_command(plutovg_path_t* path, plutovg_path_command_t command, int npoints) { const int length = npoints + 1; plutovg_array_ensure(path->elements, length); plutovg_path_element_t* elements = path->elements.data + path->elements.size; elements->header.command = command; elements->header.length = length; path->elements.size += length; path->num_points += npoints; return elements + 1; } void plutovg_path_move_to(plutovg_path_t* path, float x, float y) { plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_MOVE_TO, 1); elements[0].point = PLUTOVG_MAKE_POINT(x, y); path->start_point = PLUTOVG_MAKE_POINT(x, y); path->num_contours += 1; } void plutovg_path_line_to(plutovg_path_t* path, float x, float y) { if(path->elements.size == 0) plutovg_path_move_to(path, 0, 0); plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_LINE_TO, 1); elements[0].point = PLUTOVG_MAKE_POINT(x, y); } void plutovg_path_quad_to(plutovg_path_t* path, float x1, float y1, float x2, float y2) { float current_x, current_y; plutovg_path_get_current_point(path, ¤t_x, ¤t_y); float cp1x = 2.f / 3.f * x1 + 1.f / 3.f * current_x; float cp1y = 2.f / 3.f * y1 + 1.f / 3.f * current_y; float cp2x = 2.f / 3.f * x1 + 1.f / 3.f * x2; float cp2y = 2.f / 3.f * y1 + 1.f / 3.f * y2; plutovg_path_cubic_to(path, cp1x, cp1y, cp2x, cp2y, x2, y2); } void plutovg_path_cubic_to(plutovg_path_t* path, float x1, float y1, float x2, float y2, float x3, float y3) { if(path->elements.size == 0) plutovg_path_move_to(path, 0, 0); plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_CUBIC_TO, 3); elements[0].point = PLUTOVG_MAKE_POINT(x1, y1); elements[1].point = PLUTOVG_MAKE_POINT(x2, y2); elements[2].point = PLUTOVG_MAKE_POINT(x3, y3); path->num_curves += 1; } void plutovg_path_arc_to(plutovg_path_t* path, float rx, float ry, float angle, bool large_arc_flag, bool sweep_flag, float x, float y) { float current_x, current_y; plutovg_path_get_current_point(path, ¤t_x, ¤t_y); if(rx == 0.f || ry == 0.f || (current_x == x && current_y == y)) { plutovg_path_line_to(path, x, y); return; } if(rx < 0.f) rx = -rx; if(ry < 0.f) ry = -ry; float dx = (current_x - x) * 0.5f; float dy = (current_y - y) * 0.5f; plutovg_matrix_t matrix; plutovg_matrix_init_rotate(&matrix, -angle); plutovg_matrix_map(&matrix, dx, dy, &dx, &dy); float rxrx = rx * rx; float ryry = ry * ry; float dxdx = dx * dx; float dydy = dy * dy; float radius = dxdx / rxrx + dydy / ryry; if(radius > 1.f) { rx *= sqrtf(radius); ry *= sqrtf(radius); } plutovg_matrix_init_scale(&matrix, 1.f / rx, 1.f / ry); plutovg_matrix_rotate(&matrix, -angle); float x1, y1; float x2, y2; plutovg_matrix_map(&matrix, current_x, current_y, &x1, &y1); plutovg_matrix_map(&matrix, x, y, &x2, &y2); float dx1 = x2 - x1; float dy1 = y2 - y1; float d = dx1 * dx1 + dy1 * dy1; float scale_sq = 1.f / d - 0.25f; if(scale_sq < 0.f) scale_sq = 0.f; float scale = sqrtf(scale_sq); if(sweep_flag == large_arc_flag) scale = -scale; dx1 *= scale; dy1 *= scale; float cx1 = 0.5f * (x1 + x2) - dy1; float cy1 = 0.5f * (y1 + y2) + dx1; float th1 = atan2f(y1 - cy1, x1 - cx1); float th2 = atan2f(y2 - cy1, x2 - cx1); float th_arc = th2 - th1; if(th_arc < 0.f && sweep_flag) th_arc += PLUTOVG_TWO_PI; else if(th_arc > 0.f && !sweep_flag) th_arc -= PLUTOVG_TWO_PI; plutovg_matrix_init_rotate(&matrix, angle); plutovg_matrix_scale(&matrix, rx, ry); int segments = (int)(ceilf(fabsf(th_arc / (PLUTOVG_HALF_PI + 0.001f)))); for(int i = 0; i < segments; i++) { float th_start = th1 + i * th_arc / segments; float th_end = th1 + (i + 1) * th_arc / segments; float t = (8.f / 6.f) * tanf(0.25f * (th_end - th_start)); float x3 = cosf(th_end) + cx1; float y3 = sinf(th_end) + cy1; float cp2x = x3 + t * sinf(th_end); float cp2y = y3 - t * cosf(th_end); float cp1x = cosf(th_start) - t * sinf(th_start); float cp1y = sinf(th_start) + t * cosf(th_start); cp1x += cx1; cp1y += cy1; plutovg_matrix_map(&matrix, cp1x, cp1y, &cp1x, &cp1y); plutovg_matrix_map(&matrix, cp2x, cp2y, &cp2x, &cp2y); plutovg_matrix_map(&matrix, x3, y3, &x3, &y3); plutovg_path_cubic_to(path, cp1x, cp1y, cp2x, cp2y, x3, y3); } } void plutovg_path_close(plutovg_path_t* path) { if(path->elements.size == 0) return; plutovg_path_element_t* elements = plutovg_path_add_command(path, PLUTOVG_PATH_COMMAND_CLOSE, 1); elements[0].point = path->start_point; } void plutovg_path_get_current_point(const plutovg_path_t* path, float* x, float* y) { float xx = 0.f; float yy = 0.f; if(path->num_points > 0) { xx = path->elements.data[path->elements.size - 1].point.x; yy = path->elements.data[path->elements.size - 1].point.y; } if(x) *x = xx; if(y) *y = yy; } void plutovg_path_reserve(plutovg_path_t* path, int count) { plutovg_array_ensure(path->elements, count); } void plutovg_path_reset(plutovg_path_t* path) { plutovg_array_clear(path->elements); path->start_point = PLUTOVG_MAKE_POINT(0, 0); path->num_points = 0; path->num_contours = 0; path->num_curves = 0; } void plutovg_path_add_rect(plutovg_path_t* path, float x, float y, float w, float h) { plutovg_path_reserve(path, 6 * 2); plutovg_path_move_to(path, x, y); plutovg_path_line_to(path, x + w, y); plutovg_path_line_to(path, x + w, y + h); plutovg_path_line_to(path, x, y + h); plutovg_path_line_to(path, x, y); plutovg_path_close(path); } void plutovg_path_add_round_rect(plutovg_path_t* path, float x, float y, float w, float h, float rx, float ry) { rx = plutovg_min(rx, w * 0.5f); ry = plutovg_min(ry, h * 0.5f); if(rx == 0.f && ry == 0.f) { plutovg_path_add_rect(path, x, y, w, h); return; } float right = x + w; float bottom = y + h; float cpx = rx * PLUTOVG_KAPPA; float cpy = ry * PLUTOVG_KAPPA; plutovg_path_reserve(path, 6 * 2 + 4 * 4); plutovg_path_move_to(path, x, y+ry); plutovg_path_cubic_to(path, x, y+ry-cpy, x+rx-cpx, y, x+rx, y); plutovg_path_line_to(path, right-rx, y); plutovg_path_cubic_to(path, right-rx+cpx, y, right, y+ry-cpy, right, y+ry); plutovg_path_line_to(path, right, bottom-ry); plutovg_path_cubic_to(path, right, bottom-ry+cpy, right-rx+cpx, bottom, right-rx, bottom); plutovg_path_line_to(path, x+rx, bottom); plutovg_path_cubic_to(path, x+rx-cpx, bottom, x, bottom-ry+cpy, x, bottom-ry); plutovg_path_line_to(path, x, y+ry); plutovg_path_close(path); } void plutovg_path_add_ellipse(plutovg_path_t* path, float cx, float cy, float rx, float ry) { float left = cx - rx; float top = cy - ry; float right = cx + rx; float bottom = cy + ry; float cpx = rx * PLUTOVG_KAPPA; float cpy = ry * PLUTOVG_KAPPA; plutovg_path_reserve(path, 2 * 2 + 4 * 4); plutovg_path_move_to(path, cx, top); plutovg_path_cubic_to(path, cx+cpx, top, right, cy-cpy, right, cy); plutovg_path_cubic_to(path, right, cy+cpy, cx+cpx, bottom, cx, bottom); plutovg_path_cubic_to(path, cx-cpx, bottom, left, cy+cpy, left, cy); plutovg_path_cubic_to(path, left, cy-cpy, cx-cpx, top, cx, top); plutovg_path_close(path); } void plutovg_path_add_circle(plutovg_path_t* path, float cx, float cy, float r) { plutovg_path_add_ellipse(path, cx, cy, r, r); } void plutovg_path_add_arc(plutovg_path_t* path, float cx, float cy, float r, float a0, float a1, bool ccw) { float da = a1 - a0; if(fabsf(da) > PLUTOVG_TWO_PI) { da = PLUTOVG_TWO_PI; } else if(da != 0.f && ccw != (da < 0.f)) { da += PLUTOVG_TWO_PI * (ccw ? -1 : 1); } int seg_n = (int)(ceilf(fabsf(da) / PLUTOVG_HALF_PI)); if(seg_n == 0) return; float a = a0; float ax = cx + cosf(a) * r; float ay = cy + sinf(a) * r; float seg_a = da / seg_n; float d = (seg_a / PLUTOVG_HALF_PI) * PLUTOVG_KAPPA * r; float dx = -sinf(a) * d; float dy = cosf(a) * d; plutovg_path_reserve(path, 2 + 4 * seg_n); if(path->elements.size == 0) { plutovg_path_move_to(path, ax, ay); } else { plutovg_path_line_to(path, ax, ay); } for(int i = 0; i < seg_n; i++) { float cp1x = ax + dx; float cp1y = ay + dy; a += seg_a; ax = cx + cosf(a) * r; ay = cy + sinf(a) * r; dx = -sinf(a) * d; dy = cosf(a) * d; float cp2x = ax - dx; float cp2y = ay - dy; plutovg_path_cubic_to(path, cp1x, cp1y, cp2x, cp2y, ax, ay); } } void plutovg_path_transform(plutovg_path_t* path, const plutovg_matrix_t* matrix) { plutovg_path_element_t* elements = path->elements.data; for(int i = 0; i < path->elements.size; i += elements[i].header.length) { switch(elements[i].header.command) { case PLUTOVG_PATH_COMMAND_MOVE_TO: case PLUTOVG_PATH_COMMAND_LINE_TO: case PLUTOVG_PATH_COMMAND_CLOSE: plutovg_matrix_map_point(matrix, &elements[i + 1].point, &elements[i + 1].point); break; case PLUTOVG_PATH_COMMAND_CUBIC_TO: plutovg_matrix_map_point(matrix, &elements[i + 1].point, &elements[i + 1].point); plutovg_matrix_map_point(matrix, &elements[i + 2].point, &elements[i + 2].point); plutovg_matrix_map_point(matrix, &elements[i + 3].point, &elements[i + 3].point); break; } } } void plutovg_path_add_path(plutovg_path_t* path, const plutovg_path_t* source, const plutovg_matrix_t* matrix) { if(matrix == NULL) { plutovg_array_append(path->elements, source->elements); path->start_point = source->start_point; path->num_points += source->num_points; path->num_contours += source->num_contours; path->num_curves += source->num_curves; return; } plutovg_path_iterator_t it; plutovg_path_iterator_init(&it, source); plutovg_point_t points[3]; plutovg_array_ensure(path->elements, source->elements.size); while(plutovg_path_iterator_has_next(&it)) { switch(plutovg_path_iterator_next(&it, points)) { case PLUTOVG_PATH_COMMAND_MOVE_TO: plutovg_matrix_map_points(matrix, points, points, 1); plutovg_path_move_to(path, points[0].x, points[0].y); break; case PLUTOVG_PATH_COMMAND_LINE_TO: plutovg_matrix_map_points(matrix, points, points, 1); plutovg_path_line_to(path, points[0].x, points[0].y); break; case PLUTOVG_PATH_COMMAND_CUBIC_TO: plutovg_matrix_map_points(matrix, points, points, 3); plutovg_path_cubic_to(path, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); break; case PLUTOVG_PATH_COMMAND_CLOSE: plutovg_path_close(path); break; } } } void plutovg_path_traverse(const plutovg_path_t* path, plutovg_path_traverse_func_t traverse_func, void* closure) { plutovg_path_iterator_t it; plutovg_path_iterator_init(&it, path); plutovg_point_t points[3]; while(plutovg_path_iterator_has_next(&it)) { switch(plutovg_path_iterator_next(&it, points)) { case PLUTOVG_PATH_COMMAND_MOVE_TO: traverse_func(closure, PLUTOVG_PATH_COMMAND_MOVE_TO, points, 1); break; case PLUTOVG_PATH_COMMAND_LINE_TO: traverse_func(closure, PLUTOVG_PATH_COMMAND_LINE_TO, points, 1); break; case PLUTOVG_PATH_COMMAND_CUBIC_TO: traverse_func(closure, PLUTOVG_PATH_COMMAND_CUBIC_TO, points, 3); break; case PLUTOVG_PATH_COMMAND_CLOSE: traverse_func(closure, PLUTOVG_PATH_COMMAND_CLOSE, points, 1); break; } } } typedef struct { float x1; float y1; float x2; float y2; float x3; float y3; float x4; float y4; } bezier_t; static inline void split_bezier(const bezier_t* b, bezier_t* first, bezier_t* second) { float c = (b->x2 + b->x3) * 0.5f; first->x2 = (b->x1 + b->x2) * 0.5f; second->x3 = (b->x3 + b->x4) * 0.5f; first->x1 = b->x1; second->x4 = b->x4; first->x3 = (first->x2 + c) * 0.5f; second->x2 = (second->x3 + c) * 0.5f; first->x4 = second->x1 = (first->x3 + second->x2) * 0.5f; c = (b->y2 + b->y3) * 0.5f; first->y2 = (b->y1 + b->y2) * 0.5f; second->y3 = (b->y3 + b->y4) * 0.5f; first->y1 = b->y1; second->y4 = b->y4; first->y3 = (first->y2 + c) * 0.5f; second->y2 = (second->y3 + c) * 0.5f; first->y4 = second->y1 = (first->y3 + second->y2) * 0.5f; } void plutovg_path_traverse_flatten(const plutovg_path_t* path, plutovg_path_traverse_func_t traverse_func, void* closure) { if(path->num_curves == 0) { plutovg_path_traverse(path, traverse_func, closure); return; } const float threshold = 0.25f; plutovg_path_iterator_t it; plutovg_path_iterator_init(&it, path); bezier_t beziers[32]; plutovg_point_t points[3]; plutovg_point_t current_point = {0, 0}; while(plutovg_path_iterator_has_next(&it)) { plutovg_path_command_t command = plutovg_path_iterator_next(&it, points); switch(command) { case PLUTOVG_PATH_COMMAND_MOVE_TO: case PLUTOVG_PATH_COMMAND_LINE_TO: case PLUTOVG_PATH_COMMAND_CLOSE: traverse_func(closure, command, points, 1); current_point = points[0]; break; case PLUTOVG_PATH_COMMAND_CUBIC_TO: beziers[0].x1 = current_point.x; beziers[0].y1 = current_point.y; beziers[0].x2 = points[0].x; beziers[0].y2 = points[0].y; beziers[0].x3 = points[1].x; beziers[0].y3 = points[1].y; beziers[0].x4 = points[2].x; beziers[0].y4 = points[2].y; bezier_t* b = beziers; while(b >= beziers) { float y4y1 = b->y4 - b->y1; float x4x1 = b->x4 - b->x1; float l = fabsf(x4x1) + fabsf(y4y1); float d; if(l > 1.f) { d = fabsf((x4x1)*(b->y1 - b->y2) - (y4y1)*(b->x1 - b->x2)) + fabsf((x4x1)*(b->y1 - b->y3) - (y4y1)*(b->x1 - b->x3)); } else { d = fabsf(b->x1 - b->x2) + fabsf(b->y1 - b->y2) + fabsf(b->x1 - b->x3) + fabsf(b->y1 - b->y3); l = 1.f; } if(d < threshold*l || b == beziers + 31) { plutovg_point_t p = { b->x4, b->y4 }; traverse_func(closure, PLUTOVG_PATH_COMMAND_LINE_TO, &p, 1); --b; } else { split_bezier(b, b + 1, b); ++b; } } current_point = points[2]; break; } } } typedef struct { const float* dashes; int ndashes; float start_phase; float phase; int start_index; int index; bool start_toggle; bool toggle; plutovg_point_t current_point; plutovg_path_traverse_func_t traverse_func; void* closure; } dasher_t; static void dash_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints) { dasher_t* dasher = (dasher_t*)(closure); if(command == PLUTOVG_PATH_COMMAND_MOVE_TO) { if(dasher->start_toggle) dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_MOVE_TO, points, npoints); dasher->current_point = points[0]; dasher->phase = dasher->start_phase; dasher->index = dasher->start_index; dasher->toggle = dasher->start_toggle; return; } assert(command == PLUTOVG_PATH_COMMAND_LINE_TO || command == PLUTOVG_PATH_COMMAND_CLOSE); plutovg_point_t p0 = dasher->current_point; plutovg_point_t p1 = points[0]; float dx = p1.x - p0.x; float dy = p1.y - p0.y; float dist0 = sqrtf(dx*dx + dy*dy); float dist1 = 0.f; while(dist0 - dist1 > dasher->dashes[dasher->index % dasher->ndashes] - dasher->phase) { dist1 += dasher->dashes[dasher->index % dasher->ndashes] - dasher->phase; float a = dist1 / dist0; plutovg_point_t p = { p0.x + a * dx, p0.y + a * dy }; if(dasher->toggle) { dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_LINE_TO, &p, 1); } else { dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_MOVE_TO, &p, 1); } dasher->phase = 0.f; dasher->toggle = !dasher->toggle; dasher->index++; } if(dasher->toggle) { dasher->traverse_func(dasher->closure, PLUTOVG_PATH_COMMAND_LINE_TO, &p1, 1); } dasher->phase += dist0 - dist1; dasher->current_point = p1; } void plutovg_path_traverse_dashed(const plutovg_path_t* path, float offset, const float* dashes, int ndashes, plutovg_path_traverse_func_t traverse_func, void* closure) { float dash_sum = 0.f; for(int i = 0; i < ndashes; ++i) dash_sum += dashes[i]; if(ndashes % 2 == 1) dash_sum *= 2.f; if(dash_sum <= 0.f) { plutovg_path_traverse(path, traverse_func, closure); return; } dasher_t dasher; dasher.dashes = dashes; dasher.ndashes = ndashes; dasher.start_phase = fmodf(offset, dash_sum); if(dasher.start_phase < 0.f) dasher.start_phase += dash_sum; dasher.start_index = 0; dasher.start_toggle = true; while(dasher.start_phase > 0.f && dasher.start_phase >= dasher.dashes[dasher.start_index % dasher.ndashes]) { dasher.start_phase -= dashes[dasher.start_index % dasher.ndashes]; dasher.start_toggle = !dasher.start_toggle; dasher.start_index++; } dasher.phase = dasher.start_phase; dasher.index = dasher.start_index; dasher.toggle = dasher.start_toggle; dasher.current_point = PLUTOVG_MAKE_POINT(0, 0); dasher.traverse_func = traverse_func; dasher.closure = closure; plutovg_path_traverse_flatten(path, dash_traverse_func, &dasher); } plutovg_path_t* plutovg_path_clone(const plutovg_path_t* path) { plutovg_path_t* clone = plutovg_path_create(); plutovg_array_append(clone->elements, path->elements); clone->start_point = path->start_point; clone->num_points = path->num_points; clone->num_contours = path->num_contours; clone->num_curves = path->num_curves; return clone; } static void clone_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints) { plutovg_path_t* path = (plutovg_path_t*)(closure); switch(command) { case PLUTOVG_PATH_COMMAND_MOVE_TO: plutovg_path_move_to(path, points[0].x, points[0].y); break; case PLUTOVG_PATH_COMMAND_LINE_TO: plutovg_path_line_to(path, points[0].x, points[0].y); break; case PLUTOVG_PATH_COMMAND_CUBIC_TO: plutovg_path_cubic_to(path, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y); break; case PLUTOVG_PATH_COMMAND_CLOSE: plutovg_path_close(path); break; } } plutovg_path_t* plutovg_path_clone_flatten(const plutovg_path_t* path) { plutovg_path_t* clone = plutovg_path_create(); plutovg_path_reserve(clone, path->elements.size + path->num_curves * 32); plutovg_path_traverse_flatten(path, clone_traverse_func, clone); return clone; } plutovg_path_t* plutovg_path_clone_dashed(const plutovg_path_t* path, float offset, const float* dashes, int ndashes) { plutovg_path_t* clone = plutovg_path_create(); plutovg_path_reserve(clone, path->elements.size + path->num_curves * 32); plutovg_path_traverse_dashed(path, offset, dashes, ndashes, clone_traverse_func, clone); return clone; } typedef struct { plutovg_point_t current_point; bool is_first_point; float length; float x1; float y1; float x2; float y2; } extents_calculator_t; static void extents_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints) { extents_calculator_t* calculator = (extents_calculator_t*)(closure); if(calculator->is_first_point) { assert(command == PLUTOVG_PATH_COMMAND_MOVE_TO); calculator->is_first_point = false; calculator->current_point = points[0]; calculator->x1 = points[0].x; calculator->y1 = points[0].y; calculator->x2 = points[0].x; calculator->y2 = points[0].y; calculator->length = 0; return; } for(int i = 0; i < npoints; ++i) { calculator->x1 = plutovg_min(calculator->x1, points[i].x); calculator->y1 = plutovg_min(calculator->y1, points[i].y); calculator->x2 = plutovg_max(calculator->x2, points[i].x); calculator->y2 = plutovg_max(calculator->y2, points[i].y); if(command != PLUTOVG_PATH_COMMAND_MOVE_TO) calculator->length += hypotf(points[i].x - calculator->current_point.x, points[i].y - calculator->current_point.y); calculator->current_point = points[i]; } } float plutovg_path_extents(const plutovg_path_t* path, plutovg_rect_t* extents, bool tight) { extents_calculator_t calculator = {{0, 0}, true, 0, 0, 0, 0, 0}; if(tight) { plutovg_path_traverse_flatten(path, extents_traverse_func, &calculator); } else { plutovg_path_traverse(path, extents_traverse_func, &calculator); } if(extents) { extents->x = calculator.x1; extents->y = calculator.y1; extents->w = calculator.x2 - calculator.x1; extents->h = calculator.y2 - calculator.y1; } return calculator.length; } float plutovg_path_length(const plutovg_path_t* path) { return plutovg_path_extents(path, NULL, true); } static inline bool parse_arc_flag(const char** begin, const char* end, bool* flag) { if(plutovg_skip_delim(begin, end, '0')) *flag = 0; else if(plutovg_skip_delim(begin, end, '1')) *flag = 1; else return false; plutovg_skip_ws_or_comma(begin, end, NULL); return true; } static inline bool parse_path_coordinates(const char** begin, const char* end, float values[6], int offset, int count) { for(int i = 0; i < count; i++) { if(!plutovg_parse_number(begin, end, values + offset + i)) return false; plutovg_skip_ws_or_comma(begin, end, NULL); } return true; } bool plutovg_path_parse(plutovg_path_t* path, const char* data, int length) { if(length == -1) length = strlen(data); const char* it = data; const char* end = it + length; float values[6]; bool flags[2]; float start_x = 0; float start_y = 0; float current_x = 0; float current_y = 0; float last_control_x = 0; float last_control_y = 0; char command = 0; char last_command = 0; plutovg_skip_ws(&it, end); while(it < end) { if(PLUTOVG_IS_ALPHA(*it)) { command = *it++; plutovg_skip_ws(&it, end); } if(!last_command && !(command == 'M' || command == 'm')) return false; if(command == 'M' || command == 'm') { if(!parse_path_coordinates(&it, end, values, 0, 2)) return false; if(command == 'm') { values[0] += current_x; values[1] += current_y; } plutovg_path_move_to(path, values[0], values[1]); current_x = start_x = values[0]; current_y = start_y = values[1]; command = command == 'm' ? 'l' : 'L'; } else if(command == 'L' || command == 'l') { if(!parse_path_coordinates(&it, end, values, 0, 2)) return false; if(command == 'l') { values[0] += current_x; values[1] += current_y; } plutovg_path_line_to(path, values[0], values[1]); current_x = values[0]; current_y = values[1]; } else if(command == 'H' || command == 'h') { if(!parse_path_coordinates(&it, end, values, 0, 1)) return false; if(command == 'h') { values[0] += current_x; } plutovg_path_line_to(path, values[0], current_y); current_x = values[0]; } else if(command == 'V' || command == 'v') { if(!parse_path_coordinates(&it, end, values, 1, 1)) return false; if(command == 'v') { values[1] += current_y; } plutovg_path_line_to(path, current_x, values[1]); current_y = values[1]; } else if(command == 'Q' || command == 'q') { if(!parse_path_coordinates(&it, end, values, 0, 4)) return false; if(command == 'q') { values[0] += current_x; values[1] += current_y; values[2] += current_x; values[3] += current_y; } plutovg_path_quad_to(path, values[0], values[1], values[2], values[3]); last_control_x = values[0]; last_control_y = values[1]; current_x = values[2]; current_y = values[3]; } else if(command == 'C' || command == 'c') { if(!parse_path_coordinates(&it, end, values, 0, 6)) return false; if(command == 'c') { values[0] += current_x; values[1] += current_y; values[2] += current_x; values[3] += current_y; values[4] += current_x; values[5] += current_y; } plutovg_path_cubic_to(path, values[0], values[1], values[2], values[3], values[4], values[5]); last_control_x = values[2]; last_control_y = values[3]; current_x = values[4]; current_y = values[5]; } else if(command == 'T' || command == 't') { if(last_command != 'Q' && last_command != 'q' && last_command != 'T' && last_command != 't') { values[0] = current_x; values[1] = current_y; } else { values[0] = 2 * current_x - last_control_x; values[1] = 2 * current_y - last_control_y; } if(!parse_path_coordinates(&it, end, values, 2, 2)) return false; if(command == 't') { values[2] += current_x; values[3] += current_y; } plutovg_path_quad_to(path, values[0], values[1], values[2], values[3]); last_control_x = values[0]; last_control_y = values[1]; current_x = values[2]; current_y = values[3]; } else if(command == 'S' || command == 's') { if(last_command != 'C' && last_command != 'c' && last_command != 'S' && last_command != 's') { values[0] = current_x; values[1] = current_y; } else { values[0] = 2 * current_x - last_control_x; values[1] = 2 * current_y - last_control_y; } if(!parse_path_coordinates(&it, end, values, 2, 4)) return false; if(command == 's') { values[2] += current_x; values[3] += current_y; values[4] += current_x; values[5] += current_y; } plutovg_path_cubic_to(path, values[0], values[1], values[2], values[3], values[4], values[5]); last_control_x = values[2]; last_control_y = values[3]; current_x = values[4]; current_y = values[5]; } else if(command == 'A' || command == 'a') { if(!parse_path_coordinates(&it, end, values, 0, 3) || !parse_arc_flag(&it, end, &flags[0]) || !parse_arc_flag(&it, end, &flags[1]) || !parse_path_coordinates(&it, end, values, 3, 2)) { return false; } if(command == 'a') { values[3] += current_x; values[4] += current_y; } plutovg_path_arc_to(path, values[0], values[1], PLUTOVG_DEG2RAD(values[2]), flags[0], flags[1], values[3], values[4]); current_x = values[3]; current_y = values[4]; } else if(command == 'Z' || command == 'z') { if(last_command == 'Z' || last_command == 'z') return false; plutovg_path_close(path); current_x = start_x; current_y = start_y; } else { return false; } last_command = command; } return true; }