Files
pcsx2/3rdparty/plutosvg/plutovg/source/plutovg-path.c
2025-11-18 14:18:26 -07:00

925 lines
31 KiB
C

#include "plutovg-private.h"
#include "plutovg-utils.h"
#include <assert.h>
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, &current_x, &current_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, &current_x, &current_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;
}