925 lines
31 KiB
C
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, ¤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;
|
|
}
|