├── LICENSE ├── README.md ├── ok_path.c ├── ok_path.h └── test ├── .gitignore ├── CMakeLists.txt ├── README.md └── main.c /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2017 David Brackeen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ok-path 2 | -------------------------------------------------------------------------------- /ok_path.c: -------------------------------------------------------------------------------- 1 | /* 2 | ok-path 3 | https://github.com/brackeen/ok-path 4 | Copyright (c) 2016-2020 David Brackeen 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 7 | associated documentation files (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 9 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or 13 | substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 16 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 19 | OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #include "ok_path.h" 23 | #include 24 | #include 25 | #include 26 | 27 | #ifndef NDEBUG 28 | #include // For snprintf 29 | #endif 30 | 31 | #ifndef MIN 32 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 33 | #endif 34 | #ifndef MAX 35 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 36 | #endif 37 | 38 | // MARK: Vector 39 | 40 | // Examples: 41 | // typedef struct vector_of(int) vector_int_t; 42 | // struct vector_of_ints vector_of(int); 43 | #define vector_of(type) \ 44 | { type *values; size_t length; size_t capacity; } 45 | 46 | #define vector_free(v) \ 47 | free((v)->values) 48 | 49 | #define vector_last(v) \ 50 | ((v)->values + ((v)->length - 1)) 51 | 52 | #define vector_at(v, i) \ 53 | ((v)->values + (i)) 54 | 55 | #define vector_push_new(v) \ 56 | (vector_ensure_capacity(v, 1) ? ((v)->values + ((v)->length++)) : NULL) 57 | 58 | #define vector_ensure_capacity(v, additional_count) \ 59 | (((v)->length + (size_t)(additional_count) <= (v)->capacity) ? true : \ 60 | vector_realloc((void **)&(v)->values, (v)->length + (size_t)(additional_count), \ 61 | sizeof(*(v)->values), &(v)->capacity)) 62 | 63 | static bool vector_realloc(void **values, size_t min_capacity, size_t element_size, 64 | size_t *capacity) { 65 | size_t new_capacity = MAX(8, MAX(min_capacity, *capacity << 1)); 66 | void *new_values = realloc(*values, element_size * new_capacity); 67 | if (new_values) { 68 | *values = new_values; 69 | *capacity = new_capacity; 70 | return true; 71 | } else { 72 | return false; 73 | } 74 | } 75 | 76 | // MARK: Path 77 | 78 | static const double OK_PATH_DEFAULT_FLATNESS = 1.0; 79 | 80 | #define ok_path_type_is_curve(type) ((type) == OK_PATH_QUAD_CURVE_TO || \ 81 | (type) == OK_PATH_CUBIC_CURVE_TO) 82 | 83 | struct ok_path_element { 84 | enum ok_path_element_type type; 85 | double x, y; 86 | 87 | // Control points. Valid if type is CURVE_TO, otherwise these are undefined. 88 | double cx1, cy1; 89 | double cx2, cy2; 90 | }; 91 | 92 | struct ok_subpath { 93 | size_t first_index; 94 | size_t last_index; 95 | double origin_x; 96 | double origin_y; 97 | bool has_curves; 98 | }; 99 | 100 | struct vector_of_path_elements vector_of(struct ok_path_element); 101 | 102 | struct ok_path { 103 | struct vector_of_path_elements elements; 104 | struct vector_of(struct ok_subpath) subpaths; 105 | bool has_curves; 106 | double flatness; 107 | }; 108 | 109 | static ok_path_t *ok_path_create_with_flatness(double flatness) { 110 | ok_path_t *path = calloc(1, sizeof(ok_path_t)); 111 | path->flatness = flatness; 112 | return path; 113 | } 114 | 115 | ok_path_t *ok_path_create() { 116 | return ok_path_create_with_flatness(OK_PATH_DEFAULT_FLATNESS); 117 | } 118 | 119 | void ok_path_free(ok_path_t *path) { 120 | if (path) { 121 | vector_free(&path->elements); 122 | vector_free(&path->subpaths); 123 | free(path); 124 | } 125 | } 126 | 127 | double ok_path_get_flatness(const ok_path_t *path) { 128 | return path->flatness; 129 | } 130 | 131 | void ok_path_set_flatness(ok_path_t *path, double flatness) { 132 | path->flatness = MAX(0.01, flatness); 133 | } 134 | 135 | void ok_path_reset(ok_path_t *path) { 136 | path->elements.length = 0; 137 | path->subpaths.length = 0; 138 | path->has_curves = false; 139 | } 140 | 141 | static bool _ok_equals(double a, double b) { 142 | // Could be improved by using an epislon or something better. 143 | return (float)a == (float)b; 144 | } 145 | 146 | bool ok_path_equals(const ok_path_t *path1, const ok_path_t *path2) { 147 | 148 | if (path1->elements.length != path2->elements.length || 149 | path1->subpaths.length != path2->subpaths.length) { 150 | return false; 151 | } 152 | struct ok_path_element *element1 = path1->elements.values; 153 | struct ok_path_element *element2 = path2->elements.values; 154 | for (size_t i = 0; i < path1->elements.length; i++) { 155 | enum ok_path_element_type type1 = element1->type; 156 | enum ok_path_element_type type2 = element2->type; 157 | if (type1 == OK_PATH_CLOSE) { 158 | type1 = OK_PATH_LINE_TO; 159 | } 160 | if (type2 == OK_PATH_CLOSE) { 161 | type2 = OK_PATH_LINE_TO; 162 | } 163 | 164 | if (type1 != type2) { 165 | return false; 166 | } 167 | 168 | switch (type1) { 169 | case OK_PATH_LINE_TO: 170 | case OK_PATH_MOVE_TO: 171 | case OK_PATH_CLOSE: 172 | if (!_ok_equals(element1->x, element2->x) || 173 | !_ok_equals(element1->y, element2->y)) { 174 | return false; 175 | } 176 | break; 177 | case OK_PATH_QUAD_CURVE_TO: 178 | if (!_ok_equals(element1->cx1, element2->cx1) || 179 | !_ok_equals(element1->cy1, element2->cy1)) { 180 | return false; 181 | } 182 | break; 183 | case OK_PATH_CUBIC_CURVE_TO: 184 | if (!_ok_equals(element1->cx1, element2->cx1) || 185 | !_ok_equals(element1->cy1, element2->cy1) || 186 | !_ok_equals(element1->cx2, element2->cx2) || 187 | !_ok_equals(element1->cy2, element2->cy2)) { 188 | return false; 189 | } 190 | break; 191 | } 192 | 193 | element1++; 194 | element2++; 195 | } 196 | return true; 197 | } 198 | 199 | bool ok_path_is_flat(const ok_path_t *path) { 200 | return !path->has_curves; 201 | } 202 | 203 | size_t ok_path_element_count(const ok_path_t *path) { 204 | return path->elements.length; 205 | } 206 | 207 | enum ok_path_element_type ok_path_element_get(const ok_path_t *path, size_t index, 208 | double *out_cx1, double *out_cy1, 209 | double *out_cx2, double *out_cy2, 210 | double *out_x, double *out_y) { 211 | struct ok_path_element *element = path->elements.values + index; 212 | *out_x = element->x; 213 | *out_y = element->y; 214 | if (element->type == OK_PATH_QUAD_CURVE_TO) { 215 | *out_cx1 = element->cx1; 216 | *out_cy1 = element->cy1; 217 | } else if (element->type == OK_PATH_CUBIC_CURVE_TO) { 218 | *out_cx1 = element->cx1; 219 | *out_cy1 = element->cy1; 220 | *out_cx2 = element->cx2; 221 | *out_cy2 = element->cy2; 222 | } 223 | return element->type; 224 | } 225 | 226 | void ok_path_element_set(const ok_path_t *path, size_t index, 227 | double cx1, double cy1, double cx2, double cy2, double x, double y) { 228 | struct ok_path_element *element = path->elements.values + index; 229 | element->cx1 = cx1; 230 | element->cy1 = cy1; 231 | element->cx2 = cx2; 232 | element->cy2 = cy2; 233 | element->x = x; 234 | element->y = y; 235 | } 236 | 237 | static double ok_path_last_x(const ok_path_t *path) { 238 | if (path->elements.length) { 239 | return vector_last(&path->elements)->x; 240 | } else { 241 | return 0; 242 | } 243 | } 244 | 245 | static double ok_path_last_y(const ok_path_t *path) { 246 | if (path->elements.length) { 247 | return vector_last(&path->elements)->y; 248 | } else { 249 | return 0; 250 | } 251 | } 252 | 253 | // MARK: Subpaths 254 | 255 | size_t ok_subpath_count(const ok_path_t *path) { 256 | return path->subpaths.length; 257 | } 258 | 259 | ok_path_t *ok_subpath_create(const ok_path_t *path, size_t subpath_index) { 260 | ok_path_t *new_path = ok_path_create_with_flatness(path->flatness); 261 | const struct ok_subpath *subpath = vector_at(&path->subpaths, subpath_index); 262 | size_t count = subpath->last_index - subpath->first_index + 1; 263 | if (count > 0 && vector_ensure_capacity(&new_path->elements, count)) { 264 | struct ok_path_element *src_values = path->elements.values + subpath->first_index; 265 | struct ok_path_element *dst_values = new_path->elements.values; 266 | memcpy(dst_values, src_values, count * sizeof(struct ok_path_element)); 267 | new_path->elements.length += count; 268 | new_path->has_curves = subpath->has_curves; 269 | 270 | struct ok_subpath *new_subpath = vector_push_new(&new_path->subpaths); 271 | new_subpath->first_index = 0; 272 | new_subpath->last_index = count - 1; 273 | new_subpath->origin_x = subpath->origin_x; 274 | new_subpath->origin_y = subpath->origin_y; 275 | new_subpath->has_curves = subpath->has_curves; 276 | } else { 277 | ok_path_free(new_path); 278 | new_path = NULL; 279 | } 280 | return new_path; 281 | } 282 | 283 | size_t ok_subpath_first_element_index(const ok_path_t *path, size_t subpath_index) { 284 | return vector_at(&path->subpaths, subpath_index)->first_index; 285 | } 286 | 287 | size_t ok_subpath_last_element_index(const ok_path_t *path, size_t subpath_index) { 288 | return vector_at(&path->subpaths, subpath_index)->last_index; 289 | } 290 | 291 | void ok_subpath_origin(const ok_path_t *path, size_t subpath_index, double *out_x, double *out_y) { 292 | *out_x = vector_at(&path->subpaths, subpath_index)->origin_x; 293 | *out_y = vector_at(&path->subpaths, subpath_index)->origin_y; 294 | } 295 | 296 | bool ok_subpath_is_flat(const ok_path_t *path, size_t subpath_index) { 297 | return !vector_at(&path->subpaths, subpath_index)->has_curves; 298 | } 299 | 300 | bool ok_subpath_is_closed(const ok_path_t *path, size_t subpath_index) { 301 | size_t element_index = ok_subpath_last_element_index(path, subpath_index); 302 | return vector_at(&path->elements, element_index)->type == OK_PATH_CLOSE; 303 | } 304 | 305 | // MARK: Modifying paths 306 | 307 | static struct ok_path_element * _ok_path_new_element(ok_path_t *path, 308 | enum ok_path_element_type type, 309 | double x, double y) { 310 | // New subpath if current is MOVE, previous is CLOSE, or this is the first element 311 | if (type == OK_PATH_MOVE_TO) { 312 | struct ok_subpath *subpath = vector_push_new(&path->subpaths); 313 | subpath->first_index = path->elements.length; 314 | subpath->has_curves = false; 315 | subpath->origin_x = x; 316 | subpath->origin_y = y; 317 | } else if (path->elements.length == 0) { 318 | struct ok_subpath *subpath = vector_push_new(&path->subpaths); 319 | subpath->first_index = 0; 320 | subpath->has_curves = false; 321 | subpath->origin_x = 0.0; 322 | subpath->origin_y = 0.0; 323 | } else if (vector_last(&path->elements)->type == OK_PATH_CLOSE) { 324 | struct ok_subpath *subpath = vector_push_new(&path->subpaths); 325 | subpath->first_index = path->elements.length; 326 | subpath->has_curves = false; 327 | subpath->origin_x = vector_last(&path->elements)->x; 328 | subpath->origin_y = vector_last(&path->elements)->y; 329 | } 330 | struct ok_path_element *element = vector_push_new(&path->elements); 331 | element->type = type; 332 | element->x = x; 333 | element->y = y; 334 | vector_last(&path->subpaths)->last_index = path->elements.length - 1; 335 | if (ok_path_type_is_curve(type)) { 336 | path->has_curves = true; 337 | vector_last(&path->subpaths)->has_curves = true; 338 | } 339 | return element; 340 | } 341 | 342 | void ok_path_move_to(ok_path_t *path, double x, double y) { 343 | _ok_path_new_element(path, OK_PATH_MOVE_TO, x, y); 344 | } 345 | 346 | void ok_path_line_to(ok_path_t *path, double x, double y) { 347 | _ok_path_new_element(path, OK_PATH_LINE_TO, x, y); 348 | } 349 | 350 | void ok_path_quad_curve_to(ok_path_t *path, double cx, double cy, double x, double y) { 351 | struct ok_path_element *element = _ok_path_new_element(path, OK_PATH_QUAD_CURVE_TO, x, y); 352 | element->cx1 = cx; 353 | element->cy1 = cy; 354 | } 355 | 356 | void ok_path_curve_to(ok_path_t *path, double cx1, double cy1, double cx2, double cy2, 357 | double x, double y) { 358 | struct ok_path_element *element = _ok_path_new_element(path, OK_PATH_CUBIC_CURVE_TO, x, y); 359 | element->cx1 = cx1; 360 | element->cy1 = cy1; 361 | element->cx2 = cx2; 362 | element->cy2 = cy2; 363 | } 364 | 365 | void ok_path_close(ok_path_t *path) { 366 | double x, y; 367 | if (path->subpaths.length == 0) { 368 | x = 0.0; 369 | y = 0.0; 370 | } else { 371 | x = vector_last(&path->subpaths)->origin_x; 372 | y = vector_last(&path->subpaths)->origin_y; 373 | } 374 | _ok_path_new_element(path, OK_PATH_CLOSE, x, y); 375 | } 376 | 377 | void ok_path_append(ok_path_t *path, const ok_path_t *path_to_append) { 378 | size_t count = path_to_append->elements.length; 379 | if (vector_ensure_capacity(&path->elements, count)) { 380 | // Append one element at a time, because elements are "commands" that may have different 381 | // meaning depending on the previous elements. For example, the first command of the 382 | // appended path could be line-to, which could (or could not) create a new subpath, 383 | // depending on context. 384 | for (size_t i = 0; i < count; i++) { 385 | struct ok_path_element *element = path_to_append->elements.values + i; 386 | switch (element->type) { 387 | case OK_PATH_MOVE_TO: 388 | ok_path_move_to(path, element->x, element->y); 389 | break; 390 | case OK_PATH_LINE_TO: 391 | ok_path_line_to(path, element->x, element->y); 392 | break; 393 | case OK_PATH_QUAD_CURVE_TO: 394 | ok_path_quad_curve_to(path, element->cx1, element->cy1, element->x, element->y); 395 | break; 396 | case OK_PATH_CUBIC_CURVE_TO: 397 | ok_path_curve_to(path, element->cx1, element->cy1, element->cx2, element->cy2, 398 | element->x, element->y); 399 | break; 400 | case OK_PATH_CLOSE: 401 | ok_path_close(path); 402 | break; 403 | } 404 | } 405 | } 406 | } 407 | 408 | void ok_path_append_lines(ok_path_t *path, enum ok_path_element_type first_type, 409 | const double (*points)[2], size_t num_points) { 410 | struct vector_of_path_elements *path_elements = &path->elements; 411 | if (num_points > 0 && vector_ensure_capacity(path_elements, num_points)) { 412 | if (first_type == OK_PATH_MOVE_TO) { 413 | ok_path_move_to(path, (*points)[0], (*points)[1]); 414 | } else { 415 | ok_path_line_to(path, (*points)[0], (*points)[1]); 416 | } 417 | for (size_t i = 1; i < num_points; i++) { 418 | points++; 419 | ok_path_line_to(path, (*points)[0], (*points)[1]); 420 | } 421 | } 422 | } 423 | 424 | // MARK: Arc conversion 425 | 426 | void ok_path_arc_to(ok_path_t *path, double radius, bool large_arc, bool sweep, 427 | double x, double y) { 428 | ok_path_elliptical_arc_to(path, radius, radius, 0.0, large_arc, sweep, x, y); 429 | } 430 | 431 | void ok_path_elliptical_arc_to(ok_path_t *path, double radius_x, double radius_y, 432 | double rotation_radians, bool large_arc, 433 | bool sweep, double x, double y) { 434 | // Convert arc to a series of bezier curves. 435 | // Interface is the same as the SVG spec, except xAxisRotation is in radians, not degrees. 436 | // See http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands 437 | // See http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes 438 | if (radius_x == 0.0 || radius_y == 0.0) { 439 | ok_path_line_to(path, x, y); 440 | return; 441 | } 442 | const double curr_x = ok_path_last_x(path); 443 | const double curr_y = ok_path_last_y(path); 444 | const double end_x = x; 445 | const double end_y = y; 446 | const double dx2 = (curr_x - end_x) / 2.0; 447 | const double dy2 = (curr_y - end_y) / 2.0; 448 | const double cos_a = cos(rotation_radians); 449 | const double sin_a = sin(rotation_radians); 450 | 451 | const double x1p = cos_a * dx2 + sin_a * dy2; 452 | const double y1p = -sin_a * dx2 + cos_a * dy2; 453 | const double x1p2 = x1p * x1p; 454 | const double y1p2 = y1p * y1p; 455 | 456 | // Correct out-of-range radii 457 | double rx = fabs(radius_x); 458 | double ry = fabs(radius_y); 459 | double rx2 = rx * rx; 460 | double ry2 = ry * ry; 461 | const double v = x1p2 / rx2 + y1p2 / ry2; 462 | if (v > 1.0) { 463 | const double v_sq = sqrt(v); 464 | rx = v_sq * rx; 465 | ry = v_sq * ry; 466 | rx2 = rx * rx; 467 | ry2 = ry * ry; 468 | } 469 | 470 | // Find center point (cx, cy) 471 | double S = sqrt(fmax(0, (rx2 * ry2 - rx2 * y1p2 - ry2 * x1p2) / (rx2 * y1p2 + ry2 * x1p2))); 472 | if (large_arc == sweep) { 473 | S = -S; 474 | } 475 | const double cxp = S * rx * y1p / ry; 476 | const double cyp = -S * ry * x1p / rx; 477 | const double cx = cos_a * cxp - sin_a * cyp + (curr_x + end_x) / 2.0; 478 | const double cy = sin_a * cxp + cos_a * cyp + (curr_y + end_y) / 2.0; 479 | 480 | // Find start_angle and end_angle 481 | const double ux = (x1p - cxp) / rx; 482 | const double uy = (y1p - cyp) / ry; 483 | const double vx = (-x1p - cxp) / rx; 484 | const double vy = (-y1p - cyp) / ry; 485 | double n = sqrt(ux * ux + uy * uy); 486 | double p = ux; 487 | double angle_start = acos(p / n); 488 | if (uy < 0.0) { 489 | angle_start = -angle_start; 490 | } 491 | n = sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)); 492 | p = ux * vx + uy * vy; 493 | double angle_extent = acos(p / n); 494 | if (ux * vy - uy * vx < 0.0) { 495 | angle_extent = -angle_extent; 496 | } 497 | if (!sweep && angle_extent > 0.0) { 498 | angle_extent -= 2.0 * M_PI; 499 | } else if (sweep && angle_extent < 0.0) { 500 | angle_extent += 2.0 * M_PI; 501 | } 502 | if (angle_extent == 0.0) { 503 | ok_path_line_to(path, x, y); 504 | return; 505 | } 506 | double angle_end = angle_start + angle_extent; 507 | 508 | // Create one bezier for each quadrant 509 | double cos_eta_b = cos(angle_start); 510 | double sin_eta_b = sin(angle_start); 511 | double a_cos_eta_b = rx * cos_eta_b; 512 | double b_sin_eta_b = ry * sin_eta_b; 513 | double a_sin_eta_b = rx * sin_eta_b; 514 | double b_cos_eta_b = ry * cos_eta_b; 515 | double x_b = cx + a_cos_eta_b * cos_a - b_sin_eta_b * sin_a; 516 | double y_b = cy + a_cos_eta_b * sin_a + b_sin_eta_b * cos_a; 517 | double x_b_dot = -a_sin_eta_b * cos_a - b_cos_eta_b * sin_a; 518 | double y_b_dot = -a_sin_eta_b * sin_a + b_cos_eta_b * cos_a; 519 | 520 | double prev_angle = angle_start; 521 | double d = (sweep ? M_PI_2 : -M_PI_2); 522 | bool done = false; 523 | int quad = 0; 524 | while (!done) { 525 | double angle = prev_angle + d; 526 | if ((++quad == 4) || (sweep && angle >= angle_end) || (!sweep && angle <= angle_end)) { 527 | angle = angle_end; 528 | done = true; 529 | } 530 | 531 | double da = angle - prev_angle; 532 | double alpha = 4.0 * tan(da / 4.0) / 3.0; 533 | prev_angle = angle; 534 | 535 | // Alternative alpha from: http://www.spaceroots.org/documents/ellipse/ 536 | // XXX: test for very large arcs to see which alpha is better 537 | // double t = tan(da / 2.0); 538 | // double alpha = sin(da) * (fsqrt(4.0 + 3.0 * t * t) - 1.0) / 3.0; 539 | 540 | double x_a = x_b; 541 | double y_a = y_b; 542 | double x_a_dot = x_b_dot; 543 | double y_a_dot = y_b_dot; 544 | 545 | cos_eta_b = cos(angle); 546 | sin_eta_b = sin(angle); 547 | 548 | a_cos_eta_b = rx * cos_eta_b; 549 | b_sin_eta_b = ry * sin_eta_b; 550 | a_sin_eta_b = rx * sin_eta_b; 551 | b_cos_eta_b = ry * cos_eta_b; 552 | 553 | x_b_dot = -a_sin_eta_b * cos_a - b_cos_eta_b * sin_a; 554 | y_b_dot = -a_sin_eta_b * sin_a + b_cos_eta_b * cos_a; 555 | 556 | if (done) { 557 | x_b = end_x; 558 | y_b = end_y; 559 | } else { 560 | x_b = cx + a_cos_eta_b * cos_a - b_sin_eta_b * sin_a; 561 | y_b = cy + a_cos_eta_b * sin_a + b_sin_eta_b * cos_a; 562 | } 563 | 564 | ok_path_curve_to(path, 565 | x_a + alpha * x_a_dot, y_a + alpha * y_a_dot, 566 | x_b - alpha * x_b_dot, y_b - alpha * y_b_dot, 567 | x_b, y_b); 568 | } 569 | } 570 | 571 | // MARK: SVG path parsing 572 | 573 | static const char * const OK_PATH_SVG_COMMANDS = "MmLlHhVvAaQqTtCcSsZz"; 574 | static const unsigned int OK_PATH_SVG_COMMAND_VALUES[] = { 575 | 2, 2, 2, 2, 1, 1, 1, 1, 7, 7, 576 | 4, 4, 2, 2, 6, 6, 4, 4, 0, 0 577 | }; 578 | static const char * const OK_PATH_SVG_WHITESPACE = " \t\r\n"; 579 | static const char * const OK_PATH_SVG_WHITESPACE_OR_COMMA = ", \t\r\n"; 580 | 581 | #ifdef NDEBUG 582 | #define ok_path_error(out_error_message, format, ...) do { \ 583 | if (out_error_message) *out_error_message = "ok_path_error"; \ 584 | } while (0) 585 | #else 586 | static char OK_PATH_SVG_ERROR_MESSAGE[80]; 587 | #define ok_path_error(out_error_message, format, ...) do { \ 588 | if (out_error_message) { \ 589 | snprintf(OK_PATH_SVG_ERROR_MESSAGE, sizeof(OK_PATH_SVG_ERROR_MESSAGE), \ 590 | format, __VA_ARGS__); \ 591 | *out_error_message = OK_PATH_SVG_ERROR_MESSAGE; \ 592 | } \ 593 | } while (0) 594 | #endif 595 | 596 | bool ok_path_append_svg(ok_path_t *path, const char *svg_path, char **out_error_message) { 597 | const char *str = svg_path; 598 | double values[7] = { 0 }; 599 | char last_command = 0; 600 | unsigned int last_values_required = 0; 601 | bool last_control_set = false; 602 | double curr_x = 0.0, curr_y = 0.0; 603 | double control_x = 0.0, control_y = 0.0; 604 | 605 | while (str && *str) { 606 | bool command_required = last_values_required == 0; 607 | const char *skip_chars = (command_required ? OK_PATH_SVG_WHITESPACE : 608 | OK_PATH_SVG_WHITESPACE_OR_COMMA); 609 | if (strchr(skip_chars, *str)) { 610 | str++; 611 | } else { 612 | // Get command 613 | char command; 614 | unsigned int values_required; 615 | char *found = strchr(OK_PATH_SVG_COMMANDS, *str); 616 | if (found) { 617 | command = *str++; 618 | values_required = OK_PATH_SVG_COMMAND_VALUES[found - OK_PATH_SVG_COMMANDS]; 619 | } else if (command_required) { 620 | command = *str++; 621 | values_required = 0; 622 | } else { 623 | command = last_command; 624 | values_required = last_values_required; 625 | } 626 | 627 | // Parse numbers 628 | if (values_required > 0) { 629 | unsigned int count = 0; 630 | while (*str && count < values_required) { 631 | if (count > 0 && strchr(OK_PATH_SVG_WHITESPACE_OR_COMMA, *str)) { 632 | str++; 633 | } else { 634 | char *endptr; 635 | values[count++] = strtod(str, &endptr); 636 | if (str == endptr) { 637 | ok_path_error(out_error_message, 638 | "Could not parse number at: %li", (long)(str - svg_path)); 639 | return false; 640 | } 641 | str = endptr; 642 | } 643 | } 644 | if (count < values_required) { 645 | ok_path_error(out_error_message, "Unexpected EOF: Needed %i more numbers", 646 | (values_required - count)); 647 | return false; 648 | } 649 | } 650 | 651 | // Execute command 652 | bool control_set = false; 653 | switch (command) { 654 | case 'Z': 655 | case 'z': { 656 | ok_path_close(path); 657 | curr_x = vector_last(&path->subpaths)->origin_x; 658 | curr_y = vector_last(&path->subpaths)->origin_y; 659 | break; 660 | } 661 | case 'M': { 662 | curr_x = values[0]; 663 | curr_y = values[1]; 664 | ok_path_move_to(path, curr_x, curr_y); 665 | command = 'L'; 666 | break; 667 | } 668 | case 'm': { 669 | curr_x += values[0]; 670 | curr_y += values[1]; 671 | ok_path_move_to(path, curr_x, curr_y); 672 | command = 'l'; 673 | break; 674 | } 675 | case 'L': { 676 | curr_x = values[0]; 677 | curr_y = values[1]; 678 | ok_path_line_to(path, curr_x, curr_y); 679 | break; 680 | } 681 | case 'l': { 682 | curr_x += values[0]; 683 | curr_y += values[1]; 684 | ok_path_line_to(path, curr_x, curr_y); 685 | break; 686 | } 687 | case 'H': { 688 | curr_x = values[0]; 689 | ok_path_line_to(path, curr_x, curr_y); 690 | break; 691 | } 692 | case 'h': { 693 | curr_x += values[0]; 694 | ok_path_line_to(path, curr_x, curr_y); 695 | break; 696 | } 697 | case 'V': { 698 | curr_y = values[0]; 699 | ok_path_line_to(path, curr_x, curr_y); 700 | break; 701 | } 702 | case 'v': { 703 | curr_y += values[0]; 704 | ok_path_line_to(path, curr_x, curr_y); 705 | break; 706 | } 707 | case 'C': { 708 | double c1x = values[0]; 709 | double c1y = values[1]; 710 | control_x = values[2]; 711 | control_y = values[3]; 712 | curr_x = values[4]; 713 | curr_y = values[5]; 714 | ok_path_curve_to(path, c1x, c1y, control_x, control_y, curr_x, curr_y); 715 | control_set = true; 716 | break; 717 | } 718 | case 'c': { 719 | double c1x = curr_x + values[0]; 720 | double c1y = curr_y + values[1]; 721 | control_x = curr_x + values[2]; 722 | control_y = curr_y + values[3]; 723 | curr_x += values[4]; 724 | curr_y += values[5]; 725 | ok_path_curve_to(path, c1x, c1y, control_x, control_y, curr_x, curr_y); 726 | control_set = true; 727 | break; 728 | } 729 | case 'S': 730 | case 's': { 731 | double c1x; 732 | double c1y; 733 | if (last_control_set) { 734 | // "The reflection of the second control point on the previous command 735 | // relative to the current point" 736 | c1x = curr_x * 2.0 - control_x; 737 | c1y = curr_y * 2.0 - control_y; 738 | } else { 739 | // "Coincident with the current point" 740 | c1x = curr_x; 741 | c1y = curr_y; 742 | } 743 | if (command == 'S') { 744 | control_x = values[0]; 745 | control_y = values[1]; 746 | curr_x = values[2]; 747 | curr_y = values[3]; 748 | } else { 749 | control_x = curr_x + values[0]; 750 | control_y = curr_y + values[1]; 751 | curr_x += values[2]; 752 | curr_y += values[3]; 753 | } 754 | ok_path_curve_to(path, c1x, c1y, control_x, control_y, curr_x, curr_y); 755 | control_set = true; 756 | break; 757 | } 758 | case 'Q': { 759 | control_x = values[0]; 760 | control_y = values[1]; 761 | curr_x = values[2]; 762 | curr_y = values[3]; 763 | ok_path_quad_curve_to(path, control_x, control_y, curr_x, curr_y); 764 | control_set = true; 765 | break; 766 | } 767 | case 'q': { 768 | control_x = curr_x + values[0]; 769 | control_y = curr_y + values[1]; 770 | curr_x += values[2]; 771 | curr_y += values[3]; 772 | ok_path_quad_curve_to(path, control_x, control_y, curr_x, curr_y); 773 | control_set = true; 774 | break; 775 | } 776 | case 'T': 777 | case 't': { 778 | if (last_control_set) { 779 | // "The reflection of the second control point on the previous command 780 | // relative to the current point" 781 | control_x = curr_x * 2.0 - control_x; 782 | control_y = curr_y * 2.0 - control_y; 783 | } else { 784 | // "Coincident with the current point" 785 | control_x = curr_x; 786 | control_y = curr_y; 787 | } 788 | if (command == 'T') { 789 | curr_x = values[0]; 790 | curr_y = values[1]; 791 | } else { 792 | curr_x += values[0]; 793 | curr_y += values[1]; 794 | } 795 | ok_path_quad_curve_to(path, control_x, control_y, curr_x, curr_y); 796 | control_set = true; 797 | break; 798 | } 799 | case 'A': 800 | case 'a': { 801 | double radius_x = values[0]; 802 | double radius_y = values[1]; 803 | double angle = values[2] * M_PI / 180.0; 804 | bool large_arc = values[3] != 0.0; 805 | bool sweep = values[4] != 0.0; 806 | if (command == 'A') { 807 | curr_x = values[5]; 808 | curr_y = values[6]; 809 | } else { 810 | curr_x += values[5]; 811 | curr_y += values[6]; 812 | } 813 | ok_path_elliptical_arc_to(path, radius_x, radius_y, angle, large_arc, sweep, 814 | curr_x, curr_y); 815 | break; 816 | } 817 | default: { 818 | ok_path_error(out_error_message, "Invalid SVG command %c at position %li", 819 | command, (long)(str - svg_path)); 820 | return false; 821 | } 822 | } 823 | last_command = command; 824 | last_values_required = values_required; 825 | last_control_set = control_set; 826 | } 827 | } 828 | return (str != NULL); 829 | } 830 | 831 | // MARK: Transforms 832 | 833 | void ok_path_scale(ok_path_t *path, double scale_x, double scale_y) { 834 | for (size_t i = 0; i < ok_subpath_count(path); i++) { 835 | struct ok_subpath *subpath = path->subpaths.values + i; 836 | subpath->origin_x *= scale_x; 837 | subpath->origin_y *= scale_y; 838 | } 839 | for (size_t i = 0; i < ok_path_element_count(path); i++) { 840 | struct ok_path_element *element = path->elements.values + i; 841 | element->x *= scale_x; 842 | element->y *= scale_y; 843 | element->cx1 *= scale_x; 844 | element->cy1 *= scale_y; 845 | element->cx2 *= scale_x; 846 | element->cy2 *= scale_y; 847 | } 848 | } 849 | 850 | void ok_path_translate(ok_path_t *path, double translate_x, double translate_y) { 851 | for (size_t i = 0; i < ok_subpath_count(path); i++) { 852 | struct ok_subpath *subpath = path->subpaths.values + i; 853 | subpath->origin_x += translate_x; 854 | subpath->origin_y += translate_y; 855 | } 856 | for (size_t i = 0; i < ok_path_element_count(path); i++) { 857 | struct ok_path_element *element = path->elements.values + i; 858 | element->x += translate_x; 859 | element->y += translate_y; 860 | element->cx1 += translate_x; 861 | element->cy1 += translate_y; 862 | element->cx2 += translate_x; 863 | element->cy2 += translate_y; 864 | } 865 | } 866 | 867 | // MARK: Path flattening 868 | 869 | typedef void (*ok_add_point_func)(enum ok_path_element_type type, double x, double y, 870 | void *userData); 871 | 872 | static double _ok_approx_dist(double px, double py, double ax, double ay, 873 | double bx, double by) { 874 | // Distance from a point to a line approximation. From Graphics Gems II, page 11. 875 | double dx = fabs(bx - ax); 876 | double dy = fabs(by - ay); 877 | 878 | double div = dx + dy - fmin(dx, dy) / 2.0; 879 | 880 | if (div == 0.0) { 881 | return 0.0; 882 | } 883 | 884 | double a2 = (py - ay) * (bx - ax) - (px - ax) * (by - ay); 885 | 886 | return fabs(a2) / div; 887 | } 888 | 889 | static size_t _ok_num_segments(double flatness, 890 | double x0, double y0, double x1, double y1, 891 | double x2, double y2, double x3, double y3) { 892 | double dist = fmax(_ok_approx_dist(x1, y1, x0, y0, x3, y3), 893 | _ok_approx_dist(x2, y2, x0, y0, x3, y3)); 894 | if (dist <= 0) { 895 | return 1; 896 | } else { 897 | // Found by trial and error when attempting to match Apple's Core Graphics. 898 | double num_segments = ceil(dist * (5 / (flatness + 1))); 899 | if (num_segments <= 1) { 900 | return 1; 901 | } else if (num_segments >= 256) { 902 | return 256; // XXX: Max of 256 segments? Why? 903 | } else { 904 | return (size_t)num_segments; 905 | } 906 | } 907 | } 908 | 909 | static void _ok_path_flatten_curve_division(ok_add_point_func add_point, void *userData, 910 | enum ok_path_element_type type, 911 | size_t num_segments, 912 | double x0, double y0, double x1, double y1, 913 | double x2, double y2, double x3, double y3) { 914 | const double t = 1.0 / num_segments; 915 | const double t2 = t * t; 916 | const double t3 = t2 * t; 917 | 918 | double xf = x0; 919 | double xfd = 3.0 * (x1 - x0) * t; 920 | double xfdd2 = 3.0 * (x0 - 2.0 * x1 + x2) * t2; 921 | double xfddd6 = (3.0 * (x1 - x2) + x3 - x0) * t3; 922 | double xfddd2 = 3.0 * xfddd6; 923 | double xfdd = 2.0 * xfdd2; 924 | double xfddd = 2.0 * xfddd2; 925 | 926 | double yf = y0; 927 | double yfd = 3.0 * (y1 - y0) * t; 928 | double yfdd2 = 3.0 * (y0 - 2.0 * y1 + y2) * t2; 929 | double yfddd6 = (3.0 * (y1 - y2) + y3 - y0) * t3; 930 | double yfddd2 = 3.0 * yfddd6; 931 | double yfdd = 2.0 * yfdd2; 932 | double yfddd = 2.0 * yfddd2; 933 | 934 | for (size_t i = 1; i < num_segments; i++) { 935 | xf += xfd + xfdd2 + xfddd6; 936 | xfd += xfdd + xfddd2; 937 | xfdd += xfddd; 938 | xfdd2 += xfddd2; 939 | 940 | yf += yfd + yfdd2 + yfddd6; 941 | yfd += yfdd + yfddd2; 942 | yfdd += yfddd; 943 | yfdd2 += yfddd2; 944 | 945 | add_point(type, xf, yf, userData); 946 | } 947 | 948 | add_point(type, x3, y3, userData); 949 | } 950 | 951 | static size_t _ok_path_flatten_curve(double flatness, 952 | ok_add_point_func add_point, void *userData, 953 | enum ok_path_element_type type, 954 | double x1, double y1, double x2, double y2, 955 | double x3, double y3, double x4, double y4) { 956 | // First division 957 | const double x12 = (x1 + x2) / 2; 958 | const double y12 = (y1 + y2) / 2; 959 | const double x23 = (x2 + x3) / 2; 960 | const double y23 = (y2 + y3) / 2; 961 | const double x34 = (x3 + x4) / 2; 962 | const double y34 = (y3 + y4) / 2; 963 | const double x123 = (x12 + x23) / 2; 964 | const double y123 = (y12 + y23) / 2; 965 | const double x234 = (x23 + x34) / 2; 966 | const double y234 = (y23 + y34) / 2; 967 | const double x1234 = (x123 + x234) / 2; 968 | const double y1234 = (y123 + y234) / 2; 969 | 970 | // Left division 971 | const double lx12 = (x1 + x12) / 2; 972 | const double ly12 = (y1 + y12) / 2; 973 | const double lx23 = (x12 + x123) / 2; 974 | const double ly23 = (y12 + y123) / 2; 975 | const double lx34 = (x123 + x1234) / 2; 976 | const double ly34 = (y123 + y1234) / 2; 977 | const double lx123 = (lx12 + lx23) / 2; 978 | const double ly123 = (ly12 + ly23) / 2; 979 | const double lx234 = (lx23 + lx34) / 2; 980 | const double ly234 = (ly23 + ly34) / 2; 981 | const double lx1234 = (lx123 + lx234) / 2; 982 | const double ly1234 = (ly123 + ly234) / 2; 983 | 984 | // Right division 985 | const double rx12 = (x1234 + x234) / 2; 986 | const double ry12 = (y1234 + y234) / 2; 987 | const double rx23 = (x234 + x34) / 2; 988 | const double ry23 = (y234 + y34) / 2; 989 | const double rx34 = (x34 + x4) / 2; 990 | const double ry34 = (y34 + y4) / 2; 991 | const double rx123 = (rx12 + rx23) / 2; 992 | const double ry123 = (ry12 + ry23) / 2; 993 | const double rx234 = (rx23 + rx34) / 2; 994 | const double ry234 = (ry23 + ry34) / 2; 995 | const double rx1234 = (rx123 + rx234) / 2; 996 | const double ry1234 = (ry123 + ry234) / 2; 997 | 998 | // Determine the number of segments for each division 999 | size_t num_segments1 = _ok_num_segments(flatness, x1, y1, lx12, ly12, lx123, ly123, lx1234, ly1234); 1000 | size_t num_segments2 = _ok_num_segments(flatness, lx1234, ly1234, lx234, ly234, lx34, ly34, x1234, y1234); 1001 | size_t num_segments3 = _ok_num_segments(flatness, x1234, y1234, rx12, ry12, rx123, ry123, rx1234, ry1234); 1002 | size_t num_segments4 = _ok_num_segments(flatness, rx1234, ry1234, rx234, ry234, rx34, ry34, x4, y4); 1003 | 1004 | // Convert to lines 1005 | if (add_point) { 1006 | _ok_path_flatten_curve_division(add_point, userData, type, num_segments1, 1007 | x1, y1, lx12, ly12, lx123, ly123, lx1234, ly1234); 1008 | _ok_path_flatten_curve_division(add_point, userData, type, num_segments2, 1009 | lx1234, ly1234, lx234, ly234, lx34, ly34, x1234, y1234); 1010 | _ok_path_flatten_curve_division(add_point, userData, type, num_segments3, 1011 | x1234, y1234, rx12, ry12, rx123, ry123, rx1234, ry1234); 1012 | _ok_path_flatten_curve_division(add_point, userData, type, num_segments4, 1013 | rx1234, ry1234, rx234, ry234, rx34, ry34, x4, y4); 1014 | } 1015 | return num_segments1 + num_segments2 + num_segments3 + num_segments4; 1016 | } 1017 | 1018 | static size_t _ok_path_flatten_generic(const ok_path_t *path, 1019 | size_t first_subpath, size_t last_subpath, 1020 | bool normalize_subpaths, bool close_subpaths, 1021 | ok_add_point_func add_point, void *userData) { 1022 | size_t count = 0; 1023 | for (size_t subpath_index = first_subpath; subpath_index <= last_subpath; subpath_index++) { 1024 | size_t first_index = ok_subpath_first_element_index(path, subpath_index); 1025 | size_t last_index = ok_subpath_last_element_index(path, subpath_index); 1026 | 1027 | double x, y; 1028 | ok_subpath_origin(path, subpath_index, &x, &y); 1029 | 1030 | if (normalize_subpaths && (path->elements.values + first_index)->type != OK_PATH_MOVE_TO) { 1031 | if (add_point) { 1032 | add_point(OK_PATH_MOVE_TO, x, y, userData); 1033 | } 1034 | count++; 1035 | } 1036 | 1037 | if (ok_subpath_is_flat(path, subpath_index)) { 1038 | if (add_point) { 1039 | for (size_t i = first_index; i <= last_index; i++) { 1040 | struct ok_path_element *element = &path->elements.values[i]; 1041 | add_point(element->type, element->x, element->y, userData); 1042 | } 1043 | } 1044 | count += last_index - first_index + 1; 1045 | } else { 1046 | for (size_t i = first_index; i <= last_index; i++) { 1047 | struct ok_path_element *element = &path->elements.values[i]; 1048 | if (element->type == OK_PATH_QUAD_CURVE_TO) { 1049 | double cx1 = (x + element->cx1 * 2.0) / 3.0; 1050 | double cy1 = (y + element->cy1 * 2.0) / 3.0; 1051 | double cx2 = (element->x + element->cx1 * 2.0) / 3.0; 1052 | double cy2 = (element->y + element->cy1 * 2.0) / 3.0; 1053 | count += _ok_path_flatten_curve(path->flatness, add_point, userData, 1054 | OK_PATH_QUAD_CURVE_TO, x, y, 1055 | cx1, cy1, cx2, cy2, element->x, element->y); 1056 | } else if (element->type == OK_PATH_CUBIC_CURVE_TO) { 1057 | count += _ok_path_flatten_curve(path->flatness, add_point, userData, 1058 | OK_PATH_CUBIC_CURVE_TO, x, y, 1059 | element->cx1, element->cy1, 1060 | element->cx2, element->cy2, 1061 | element->x, element->y); 1062 | } else { 1063 | if (add_point) { 1064 | add_point(element->type, element->x, element->y, userData); 1065 | } 1066 | count++; 1067 | } 1068 | x = element->x; 1069 | y = element->y; 1070 | } 1071 | } 1072 | if (close_subpaths && !ok_subpath_is_closed(path, subpath_index)) { 1073 | if (add_point) { 1074 | struct ok_path_element *element = &path->elements.values[first_index]; 1075 | add_point(OK_PATH_CLOSE, element->x, element->y, userData); 1076 | } 1077 | count++; 1078 | } 1079 | } 1080 | return count; 1081 | } 1082 | 1083 | static void _ok_path_add_point(enum ok_path_element_type type, double x, double y, void *userData) { 1084 | ok_path_t *flattened_path = userData; 1085 | if (type == OK_PATH_MOVE_TO) { 1086 | ok_path_move_to(flattened_path, x, y); 1087 | } else if (type == OK_PATH_CLOSE) { 1088 | ok_path_close(flattened_path); 1089 | } else { 1090 | ok_path_line_to(flattened_path, x, y); 1091 | } 1092 | } 1093 | 1094 | static ok_path_t *_ok_path_flatten(const ok_path_t *path, size_t first_subpath, 1095 | size_t last_subpath, bool close_subpaths) { 1096 | size_t count = _ok_path_flatten_generic(path, first_subpath, last_subpath, false, 1097 | close_subpaths, NULL, NULL); 1098 | ok_path_t *flattened_path = ok_path_create_with_flatness(path->flatness); 1099 | if (!vector_ensure_capacity(&flattened_path->elements, count)) { 1100 | ok_path_free(flattened_path); 1101 | return NULL; 1102 | } else { 1103 | _ok_path_flatten_generic(path, first_subpath, last_subpath, false, close_subpaths, 1104 | _ok_path_add_point, flattened_path); 1105 | return flattened_path; 1106 | } 1107 | } 1108 | 1109 | ok_path_t *ok_path_flatten(const ok_path_t *path) { 1110 | if (path->has_curves) { 1111 | return _ok_path_flatten(path, 0, path->subpaths.length - 1, false); 1112 | } else { 1113 | ok_path_t *new_path = ok_path_create_with_flatness(path->flatness); 1114 | ok_path_append(new_path, path); 1115 | return new_path; 1116 | } 1117 | } 1118 | 1119 | ok_path_t *ok_subpath_flatten(const ok_path_t *path, size_t subpath_index) { 1120 | const struct ok_subpath *subpath = vector_at(&path->subpaths, subpath_index); 1121 | if (subpath->has_curves) { 1122 | return _ok_path_flatten(path, subpath_index, subpath_index, false); 1123 | } else { 1124 | return ok_subpath_create(path, subpath_index); 1125 | } 1126 | } 1127 | 1128 | struct ok_point_list_context { 1129 | void *buffer; 1130 | size_t offset; 1131 | size_t stride; 1132 | size_t count; 1133 | }; 1134 | 1135 | static void _ok_subpath_add_point_to_list(enum ok_path_element_type type, double x, double y, 1136 | void *userData) { 1137 | (void)type; 1138 | struct ok_point_list_context *context = userData; 1139 | double point[2] = { x, y }; 1140 | size_t offset = context->offset + context->stride * context->count; 1141 | if (context->count > 0) { 1142 | // Don't add duplicate points 1143 | void *prev_point = (uint8_t *)context->buffer + (offset - context->stride); 1144 | if (memcmp(point, prev_point, sizeof(point)) == 0) { 1145 | return; 1146 | } 1147 | } 1148 | void *dst = (uint8_t *)context->buffer + offset; 1149 | memcpy(dst, point, sizeof(point)); 1150 | context->count++; 1151 | } 1152 | 1153 | void ok_subpath_create_point_list_generic(const ok_path_t *path, size_t subpath_index, 1154 | size_t offset, size_t stride, 1155 | void **out_points, size_t *out_num_points) { 1156 | if (stride < offset + sizeof(double[2])) { 1157 | *out_points = NULL; 1158 | *out_num_points = 0; 1159 | return; 1160 | } 1161 | size_t count = _ok_path_flatten_generic(path, subpath_index, subpath_index, true, false, 1162 | NULL, NULL); 1163 | void *buffer = malloc(count * stride); 1164 | if (!buffer) { 1165 | *out_points = NULL; 1166 | *out_num_points = count; 1167 | return; 1168 | } 1169 | struct ok_point_list_context context = { 1170 | .buffer = buffer, .offset = offset, .stride = stride, .count = 0 }; 1171 | _ok_path_flatten_generic(path, subpath_index, subpath_index, true, false, 1172 | _ok_subpath_add_point_to_list, &context); 1173 | 1174 | // NOTE: This would be a waste of memory if the path has many consecutive duplicate points. 1175 | // However, this is an unlikely scenario. 1176 | *out_points = buffer; 1177 | *out_num_points = context.count; 1178 | } 1179 | 1180 | // MARK: Motion Paths 1181 | 1182 | struct ok_motion_path_segment { 1183 | // The type of command this line segment originated from. 1184 | enum ok_path_element_type type; 1185 | 1186 | // Point location. 1187 | double x, y; 1188 | 1189 | // Entire length of the path up to this point. 1190 | double length_to; 1191 | 1192 | // Angle from previous point to this point. 1193 | double angle_to; 1194 | }; 1195 | 1196 | struct ok_motion_path { 1197 | struct vector_of(struct ok_motion_path_segment) segments; 1198 | }; 1199 | 1200 | void ok_motion_path_free(ok_motion_path_t *path) { 1201 | if (path) { 1202 | vector_free(&path->segments); 1203 | free(path); 1204 | } 1205 | } 1206 | 1207 | static void _ok_motion_path_add_point(enum ok_path_element_type type, double x, double y, 1208 | void *userData) { 1209 | ok_motion_path_t *path = userData; 1210 | double dx; 1211 | double dy; 1212 | double prev_length; 1213 | if (path->segments.length == 0) { 1214 | dx = x; 1215 | dy = y; 1216 | prev_length = 0.0; 1217 | } else { 1218 | struct ok_motion_path_segment *segment = vector_last(&path->segments); 1219 | dx = x - segment->x; 1220 | dy = y - segment->y; 1221 | prev_length = segment->length_to; 1222 | } 1223 | 1224 | struct ok_motion_path_segment *segment = vector_push_new(&path->segments); 1225 | if (segment) { 1226 | segment->type = type; 1227 | segment->x = x; 1228 | segment->y = y; 1229 | segment->angle_to = atan2(dy, dx); 1230 | if (type == OK_PATH_MOVE_TO) { 1231 | segment->length_to = prev_length; 1232 | } else { 1233 | segment->length_to = prev_length + sqrt(dx * dx + dy * dy); 1234 | } 1235 | } 1236 | } 1237 | 1238 | ok_motion_path_t *ok_motion_path_create(const ok_path_t *path) { 1239 | size_t first_subpath = 0; 1240 | size_t last_subpath = path->subpaths.length - 1; 1241 | size_t count = _ok_path_flatten_generic(path, first_subpath, last_subpath, true, false, 1242 | NULL, NULL); 1243 | ok_motion_path_t *out_path = calloc(1, sizeof(ok_motion_path_t)); 1244 | if (!vector_ensure_capacity(&out_path->segments, count)) { 1245 | ok_motion_path_free(out_path); 1246 | return NULL; 1247 | } else { 1248 | _ok_path_flatten_generic(path, first_subpath, last_subpath, true, false, 1249 | _ok_motion_path_add_point, out_path); 1250 | return out_path; 1251 | } 1252 | } 1253 | 1254 | double ok_motion_path_length(const ok_motion_path_t *path) { 1255 | if (path->segments.length > 0) { 1256 | return vector_last(&path->segments)->length_to; 1257 | } else { 1258 | return 0.0; 1259 | } 1260 | } 1261 | 1262 | static double _ok_normalize_angle(double radians) { 1263 | if (radians < -M_PI || radians > M_PI) { 1264 | return radians - (2.0 * M_PI) * floor((radians + M_PI) / (2.0 * M_PI)); 1265 | } else { 1266 | return radians; 1267 | } 1268 | } 1269 | 1270 | void ok_motion_path_location(const ok_motion_path_t *path, double p, 1271 | double *out_x, double *out_y, double *out_angle) { 1272 | const size_t count = path->segments.length; 1273 | if (count == 0) { 1274 | if (out_x) { 1275 | *out_x = 0.0; 1276 | } 1277 | if (out_y) { 1278 | *out_y = 0.0; 1279 | } 1280 | if (out_angle) { 1281 | *out_angle = 0.0; 1282 | } 1283 | } else { 1284 | struct ok_motion_path_segment *segments = path->segments.values; 1285 | const double length = segments[count - 1].length_to; 1286 | size_t p_low; 1287 | size_t p_high; 1288 | if (p <= 0.0) { 1289 | p_low = 0; 1290 | p_high = MIN(1, count - 1); 1291 | } else if (p >= 1.0) { 1292 | p_low = count - 1; 1293 | p_high = count - 1; 1294 | } else { 1295 | // Binary search 1296 | const double l = p * length; 1297 | p_low = 0; 1298 | p_high = count - 1; 1299 | while (p_high - p_low > 1) { 1300 | size_t index = (p_high + p_low) / 2; 1301 | if (segments[index].length_to > l) { 1302 | p_high = index; 1303 | } else { 1304 | p_low = index; 1305 | } 1306 | } 1307 | } 1308 | 1309 | struct ok_motion_path_segment *s1 = &segments[p_low]; 1310 | struct ok_motion_path_segment *s2 = &segments[p_high]; 1311 | if (p_low == p_high || length == 0.0) { 1312 | if (out_x) { 1313 | *out_x = s1->x; 1314 | } 1315 | if (out_y) { 1316 | *out_y = s1->y; 1317 | } 1318 | if (out_angle) { 1319 | *out_angle = s2->angle_to; 1320 | } 1321 | } else { 1322 | const double p1 = s1->length_to / length; 1323 | const double p2 = s2->length_to / length; 1324 | 1325 | if (p1 == p2) { 1326 | if (out_x) { 1327 | *out_x = s1->x; 1328 | } 1329 | if (out_y) { 1330 | *out_y = s1->y; 1331 | } 1332 | if (out_angle) { 1333 | *out_angle = s2->angle_to; 1334 | } 1335 | } else { 1336 | if (out_x) { 1337 | *out_x = s1->x + (s2->x - s1->x) * (p - p1) / (p2 - p1); 1338 | } 1339 | if (out_y) { 1340 | *out_y = s1->y + (s2->y - s1->y) * (p - p1) / (p2 - p1); 1341 | } 1342 | if (out_angle) { 1343 | if (s1->type == OK_PATH_MOVE_TO || !ok_path_type_is_curve(s2->type)) { 1344 | *out_angle = s2->angle_to; 1345 | } else { 1346 | const double angle1 = s1->angle_to; 1347 | const double angle2 = s2->angle_to; 1348 | const double d_angle = _ok_normalize_angle(angle2 - angle1); 1349 | *out_angle = angle1 + d_angle * (p - p1) / (p2 - p1); 1350 | } 1351 | } 1352 | } 1353 | } 1354 | } 1355 | } 1356 | 1357 | // MARK: Planar straight-line graph 1358 | 1359 | struct ok_pslg_context { 1360 | float *points; 1361 | size_t num_points; 1362 | size_t max_points; 1363 | 1364 | void *segments; 1365 | size_t num_segments; 1366 | size_t max_segments; 1367 | 1368 | size_t index_size; 1369 | }; 1370 | 1371 | static bool _ok_index_size_valid(size_t index_size) { 1372 | // On 32-bit targets, index_size must be 1, 2, or 4. 1373 | // On 64-bit targets, index_size must be 1, 2, 4, or 8. 1374 | size_t test_index_size = 1; 1375 | while (test_index_size <= sizeof(size_t)) { 1376 | if (index_size == test_index_size) { 1377 | return true; 1378 | } else { 1379 | test_index_size <<= 1; 1380 | } 1381 | } 1382 | return false; 1383 | } 1384 | 1385 | static void _ok_pslg_add_point(enum ok_path_element_type type, double x, double y, void *userData) { 1386 | struct ok_pslg_context *context = userData; 1387 | if (context->num_points < context->max_points) { 1388 | // Add point 1389 | size_t i = context->num_points * 2; 1390 | context->points[i] = (float)x; 1391 | context->points[i + 1] = (float)y; 1392 | 1393 | // Add segment 1394 | if (type != OK_PATH_MOVE_TO && context->num_points > 0 && 1395 | context->num_segments < context->max_segments) { 1396 | i = context->num_segments * 2; 1397 | switch (context->index_size) { 1398 | case 1: { 1399 | uint8_t *segments = context->segments; 1400 | segments[i] = (uint8_t)context->num_points - 1; 1401 | segments[i + 1] = (uint8_t)context->num_points; 1402 | break; 1403 | } 1404 | case 2: { 1405 | uint16_t *segments = context->segments; 1406 | segments[i] = (uint16_t)context->num_points - 1; 1407 | segments[i + 1] = (uint16_t)context->num_points; 1408 | break; 1409 | } 1410 | case 4: { 1411 | uint32_t *segments = context->segments; 1412 | segments[i] = (uint32_t)context->num_points - 1; 1413 | segments[i + 1] = (uint32_t)context->num_points; 1414 | break; 1415 | } 1416 | case 8: { 1417 | uint64_t *segments = context->segments; 1418 | segments[i] = (uint64_t)context->num_points - 1; 1419 | segments[i + 1] = (uint64_t)context->num_points; 1420 | break; 1421 | } 1422 | } 1423 | context->num_segments++; 1424 | } 1425 | 1426 | context->num_points++; 1427 | } 1428 | } 1429 | 1430 | void ok_path_create_pslg_generic(const ok_path_t *path, bool close_subpaths, size_t index_size, 1431 | float **out_points, size_t *out_num_points, 1432 | void **out_segment_indices, size_t *out_num_segments) { 1433 | // Validate input 1434 | if (!_ok_index_size_valid(index_size) || path->elements.length == 0) { 1435 | *out_points = NULL; 1436 | *out_segment_indices = NULL; 1437 | *out_num_points = 0; 1438 | *out_num_segments = 0; 1439 | return; 1440 | } 1441 | 1442 | size_t first_subpath = 0; 1443 | size_t last_subpath = path->subpaths.length - 1; 1444 | 1445 | // Initialize context 1446 | struct ok_pslg_context context; 1447 | context.index_size = index_size; 1448 | context.points = NULL; 1449 | context.segments = NULL; 1450 | context.num_points = 0; 1451 | context.num_segments = 0; 1452 | context.max_points = _ok_path_flatten_generic(path, first_subpath, last_subpath, 1453 | true, close_subpaths, NULL, NULL); 1454 | context.max_segments = MIN((context.max_points - 1), (((size_t)1) << (index_size * 8 - 1))); 1455 | 1456 | // Allocate 1457 | context.points = malloc(context.max_points * sizeof(float[2])); 1458 | context.segments = malloc(context.max_segments * 2 * index_size); 1459 | if (!context.points || !context.segments) { 1460 | free(context.points); 1461 | free(context.segments); 1462 | *out_points = NULL; 1463 | *out_segment_indices = NULL; 1464 | *out_num_points = 0; 1465 | *out_num_segments = 0; 1466 | return; 1467 | } 1468 | 1469 | // Convert 1470 | _ok_path_flatten_generic(path, first_subpath, last_subpath, true, close_subpaths, 1471 | _ok_pslg_add_point, &context); 1472 | *out_points = context.points; 1473 | *out_num_points = context.num_points; 1474 | *out_segment_indices = context.segments; 1475 | *out_num_segments = context.num_segments; 1476 | } 1477 | 1478 | void ok_path_append_pslg_generic(ok_path_t *path, size_t index_size, const float *points, 1479 | const void *segments, size_t num_segments) { 1480 | if (!_ok_index_size_valid(index_size) || num_segments == 0) { 1481 | return; 1482 | } 1483 | size_t last_segment = (size_t)-1; 1484 | for (size_t i = 0; i < num_segments; i++) { 1485 | size_t p1 = 0; 1486 | size_t p2 = 0; 1487 | switch (index_size) { 1488 | case 1: { 1489 | const uint8_t *segments8 = segments; 1490 | p1 = segments8[i * 2]; 1491 | p2 = segments8[i * 2 + 1]; 1492 | break; 1493 | } 1494 | case 2: { 1495 | const uint16_t *segments16 = segments; 1496 | p1 = segments16[i * 2]; 1497 | p2 = segments16[i * 2 + 1]; 1498 | break; 1499 | } 1500 | case 4: { 1501 | const uint32_t *segments32 = segments; 1502 | p1 = segments32[i * 2]; 1503 | p2 = segments32[i * 2 + 1]; 1504 | break; 1505 | } 1506 | case 8: { 1507 | const uint64_t *segments64 = segments; 1508 | p1 = (size_t)segments64[i * 2]; 1509 | p2 = (size_t)segments64[i * 2 + 1]; 1510 | break; 1511 | } 1512 | } 1513 | 1514 | if (p1 != last_segment) { 1515 | ok_path_move_to(path, (double)points[p1 * 2], (double)points[p1 * 2 + 1]); 1516 | } 1517 | ok_path_line_to(path, (double)points[p2 * 2], (double)points[p2 * 2 + 1]); 1518 | 1519 | last_segment = p2; 1520 | } 1521 | } 1522 | -------------------------------------------------------------------------------- /ok_path.h: -------------------------------------------------------------------------------- 1 | /* 2 | ok-path 3 | https://github.com/brackeen/ok-path 4 | Copyright (c) 2016-2020 David Brackeen 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 7 | associated documentation files (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 9 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or 13 | substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 16 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 19 | OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef OK_PATH_H 23 | #define OK_PATH_H 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | enum ok_path_element_type { 34 | OK_PATH_MOVE_TO = 0, 35 | OK_PATH_LINE_TO, 36 | OK_PATH_QUAD_CURVE_TO, 37 | OK_PATH_CUBIC_CURVE_TO, 38 | OK_PATH_CLOSE 39 | }; 40 | 41 | /** 42 | * An `ok_path_t` is a series of line segments and Bézier curves. 43 | */ 44 | typedef struct ok_path ok_path_t; 45 | 46 | typedef struct ok_motion_path ok_motion_path_t; 47 | 48 | // MARK: Creating paths 49 | 50 | /** 51 | * Creates a new #ok_path_t. 52 | * The path should be freed with #ok_path_free(ok_path_t *). 53 | */ 54 | ok_path_t *ok_path_create(void); 55 | 56 | /** 57 | * Frees an #ok_path_t. If the path is NULL, this function does nothing. 58 | */ 59 | void ok_path_free(ok_path_t *path); 60 | 61 | /** 62 | * Gets the flatness value used when flattening curves of the path. 63 | * A larger flatness value results in fewer segments with flattening curves or creating a motion path. The default is 1.0. 64 | */ 65 | double ok_path_get_flatness(const ok_path_t *path); 66 | 67 | /** 68 | * Sets the flatness value used when flattening curves of the path. 69 | * A larger flatness value results in fewer segments with flattening curves or creating a motion path. 70 | */ 71 | void ok_path_set_flatness(ok_path_t *path, double flatness); 72 | 73 | // MARK: Modifying paths 74 | 75 | /** 76 | * Moves the current location of the path, creating a new subpath. 77 | * 78 | * @param path The path. 79 | * @param x The x location to move to. 80 | * @param y The y location to move to. 81 | */ 82 | void ok_path_move_to(ok_path_t *path, double x, double y); 83 | 84 | /** 85 | * Adds a line segment to the path. 86 | * 87 | * @param path The path. 88 | * @param x The x location of the end point of the line segment. 89 | * @param y The y location of the end point of the line segment. 90 | */ 91 | void ok_path_line_to(ok_path_t *path, double x, double y); 92 | 93 | /** 94 | * Adds a cubic Bézier curve to the path. 95 | * 96 | * @param path The path. 97 | * @param cx1 The x location of the first control point. 98 | * @param cy1 The y location of the first control point. 99 | * @param cx2 The x location of the second control point. 100 | * @param cy2 The y location of the second control point. 101 | * @param x The x location of the end point of the curve. 102 | * @param y The y location of the end point of the curve. 103 | */ 104 | void ok_path_curve_to(ok_path_t *path, double cx1, double cy1, double cx2, double cy2, 105 | double x, double y); 106 | 107 | /** 108 | * Adds a quadratic Bézier curve to the path. 109 | * 110 | * @param path The path. 111 | * @param cx The x location of the control point. 112 | * @param cy The y location of the control point. 113 | * @param x The x location of the end point of the curve. 114 | * @param y The y location of the end point of the curve. 115 | */ 116 | void ok_path_quad_curve_to(ok_path_t *path, double cx, double cy, double x, double y); 117 | 118 | /** 119 | * Adds an arc to the path. 120 | * 121 | * @param path The path. 122 | * @param radius The radius of the arc. 123 | * @param large_arc A flag indicating that the larger arc (greater than 180 degrees) is drawn. 124 | * @param sweep A flag indicating that the arc is drawn in the "positive angle" direction. 125 | * @param x The x location of the end point of the arc. 126 | * @param y The y location of the end point of the arc. 127 | * @see http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands 128 | */ 129 | void ok_path_arc_to(ok_path_t *path, double radius, bool large_arc, bool sweep, double x, double y); 130 | 131 | /** 132 | * Adds an elliptical arc to the path. 133 | * 134 | * @param path The path. 135 | * @param radius_x The radius on the x axis. 136 | * @param radius_y The radius on the y axis. 137 | * @param rotation_radians The angle, in radians, of the arc. 138 | * @param large_arc A flag indicating that the larger arc (greater than 180 degrees) is drawn. 139 | * @param sweep A flag indicating that the arc is drawn in the "positive angle" direction. 140 | * @param x The x location of the end point of the arc. 141 | * @param y The y location of the end point of the arc. 142 | * @see http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands 143 | */ 144 | void ok_path_elliptical_arc_to(ok_path_t *path, double radius_x, double radius_y, 145 | double rotation_radians, bool large_arc, bool sweep, 146 | double x, double y); 147 | 148 | /** 149 | * Appends a path to the path. 150 | * @param path The path. 151 | * @param path_to_append The path to append. 152 | */ 153 | void ok_path_append(ok_path_t *path, const ok_path_t *path_to_append); 154 | 155 | /** 156 | * Appends a series of line segments to the path. Example: 157 | * 158 | * double points[][2] = {{0, 0}, {10, 20}, {40, 20}, {30, 10}, {50, 0}, {0, 0}}; 159 | * ok_path_append_lines(path, OK_PATH_MOVE_TO, points, sizeof(points) / sizeof(*points)); 160 | * 161 | * @param path The path. 162 | * @param first_type The element type of the first point, which can be either #OK_PATH_MOVE_TO 163 | * or #OK_PATH_LINE_TO. 164 | * @param points The array of points. 165 | * @param num_points The number of points. 166 | */ 167 | void ok_path_append_lines(ok_path_t *path, enum ok_path_element_type first_type, 168 | const double (*points)[2], size_t num_points); 169 | 170 | /** 171 | * Appends an SVG path, like "M 100,100 L 200,100 200,200 100,200 Z". 172 | * The entire specification defined at http://www.w3.org/TR/SVG/paths.html is accepted. 173 | * 174 | * If successful, returns `true`. Otherwise, returns `false` and `out_error_message` is set to an 175 | * error string. The error string is maintained internally and should not be freed. 176 | * 177 | * @param path The path. 178 | * @param svg_path The SVG path string to parse. 179 | * @param[out] out_error_message The pointer where the error string should be stored. May be `NULL`. 180 | * @return `true` if successful. 181 | */ 182 | bool ok_path_append_svg(ok_path_t *path, const char *svg_path, char **out_error_message); 183 | 184 | /** 185 | * Closes the current subpath. 186 | */ 187 | void ok_path_close(ok_path_t *path); 188 | 189 | /** 190 | * Remove all elements from the path. 191 | */ 192 | void ok_path_reset(ok_path_t *path); 193 | 194 | /** 195 | * Creates a flattened version of this path. All curved segments of the path are converted to a 196 | * series of straight lines that approximates the curve. 197 | */ 198 | ok_path_t *ok_path_flatten(const ok_path_t *path); 199 | 200 | // MARK: Transforms 201 | 202 | /** 203 | * Scales a path. The path is scaled with (0,0) as the center. 204 | */ 205 | void ok_path_scale(ok_path_t *path, double scale_x, double scale_y); 206 | 207 | /** 208 | * Translates a path. 209 | */ 210 | void ok_path_translate(ok_path_t *path, double translate_x, double translate_y); 211 | 212 | // MARK: Getting information about paths 213 | 214 | /** 215 | * Checks if two paths are equal. 216 | * 217 | * @return `true` if both paths contain the same sequence of path elements. 218 | */ 219 | bool ok_path_equals(const ok_path_t *path1, const ok_path_t *path2); 220 | 221 | /** 222 | * Checks if a path is flat (consists of only #OK_PATH_MOVE_TO, #OK_PATH_LINE_TO, and #OK_PATH_CLOSE 223 | * elements). 224 | */ 225 | bool ok_path_is_flat(const ok_path_t *path); 226 | 227 | /** 228 | * Gets the number of elements in the path. 229 | */ 230 | size_t ok_path_element_count(const ok_path_t *path); 231 | 232 | /** 233 | * Gets an element in the path. 234 | * 235 | * @param path The path. 236 | * @param index The element index, from `0` to `count-1`, where `count` is the value returned from 237 | * #ok_path_element_count. 238 | * @param[out] out_cx1 The x location of the first control point. This value is only set if the 239 | * element type is #OK_PATH_QUAD_CURVE_TO or #OK_PATH_CUBIC_CURVE_TO. 240 | * @param[out] out_cy1 The y location of the first control point. This value is only set if the 241 | * element type is #OK_PATH_QUAD_CURVE_TO or #OK_PATH_CUBIC_CURVE_TO. 242 | * @param[out] out_cx2 The x location of the second control point. This value is only set if the 243 | * element type is #OK_PATH_CUBIC_CURVE_TO. 244 | * @param[out] out_cy2 The y location of the second control point. This value is only set if the 245 | * element type is #OK_PATH_CUBIC_CURVE_TO. 246 | * @param[out] out_x The x location of the element's end point. 247 | * @param[out] out_y The y location of the element's end point. 248 | * @return the element type. 249 | */ 250 | enum ok_path_element_type ok_path_element_get(const ok_path_t *path, size_t index, 251 | double *out_cx1, double *out_cy1, 252 | double *out_cx2, double *out_cy2, 253 | double *out_x, double *out_y); 254 | 255 | /** 256 | * Sets the points associated with an element in the path. 257 | * @param index The element index, from `0` to `count-1`, where `count` is the value returned from 258 | * #ok_path_element_count. 259 | * @param cx1 The x location of the first control point. This value is only used if the 260 | * element type is #OK_PATH_QUAD_CURVE_TO or #OK_PATH_CUBIC_CURVE_TO. 261 | * @param cy1 The y location of the first control point. This value is only used if the 262 | * element type is #OK_PATH_QUAD_CURVE_TO or #OK_PATH_CUBIC_CURVE_TO. 263 | * @param cx2 The x location of the second control point. This value is only used if the 264 | * element type is #OK_PATH_CUBIC_CURVE_TO. 265 | * @param cy2 The y location of the second control point. This value is only used if the 266 | * element type is #OK_PATH_CUBIC_CURVE_TO. 267 | * @param x The x location of the element's end point. 268 | * @param y The y location of the element's end point. 269 | */ 270 | void ok_path_element_set(const ok_path_t *path, size_t index, 271 | double cx1, double cy1, double cx2, double cy2, double x, double y); 272 | 273 | // MARK: Subpaths 274 | 275 | /** 276 | * Gets the number of subpaths in a path. 277 | * 278 | * Subpaths have at least one element. Subpaths may have no more than one #OK_PATH_MOVE_TO command (always 279 | * as the first elemeent) and no more than one #OK_PATH_CLOSE command (always as the last element). 280 | */ 281 | size_t ok_subpath_count(const ok_path_t *path); 282 | 283 | /** 284 | * Creates a copy of a subpath from the path. 285 | * 286 | * @param path The path. 287 | * @param subpath_index The subpath index, from `0` to `count-1`, where `count` is the value 288 | * returned from #ok_subpath_count. 289 | */ 290 | ok_path_t *ok_subpath_create(const ok_path_t *path, size_t subpath_index); 291 | 292 | /** 293 | * Creates a flattened version of a subpath. All curved segments of the subpath are converted to a 294 | * series of straight lines that approximates the curve. 295 | * 296 | * @param path The path. 297 | * @param subpath_index The subpath index, from `0` to `count-1`, where `count` is the value 298 | * returned from #ok_subpath_count. 299 | */ 300 | ok_path_t *ok_subpath_flatten(const ok_path_t *path, size_t subpath_index); 301 | 302 | /** 303 | * Gets the index of the first element of the specified subpath in the path. 304 | * @param path The path. 305 | * @param subpath_index The subpath index, from `0` to `count-1`, where `count` is the value 306 | * returned from #ok_subpath_count. 307 | */ 308 | size_t ok_subpath_first_element_index(const ok_path_t *path, size_t subpath_index); 309 | 310 | /** 311 | * Gets the index of the last element of the specified subpath in the path. 312 | * @param path The path. 313 | * @param subpath_index The subpath index, from `0` to `count-1`, where `count` is the value 314 | * returned from #ok_subpath_count. 315 | */ 316 | size_t ok_subpath_last_element_index(const ok_path_t *path, size_t subpath_index); 317 | 318 | /** 319 | * Gets the origin of a subpath. If the first element of the subpath is a #OK_PATH_MOVE_TO command, the origin is the 320 | * same. Otherwise, the origin may be the last point of the previous subpath or (0, 0). 321 | * @param path The path. 322 | * @param subpath_index The subpath index, from `0` to `count-1`, where `count` is the value 323 | * returned from #ok_subpath_count. 324 | * @param[out] out_x The pointer where the x location should be stored. 325 | * @param[out] out_y The pointer where the y location should be stored. 326 | */ 327 | void ok_subpath_origin(const ok_path_t *path, size_t subpath_index, double *out_x, double *out_y); 328 | 329 | /** 330 | * Checks if a subpath is flat (consists of only #OK_PATH_MOVE_TO, #OK_PATH_LINE_TO, and 331 | * #OK_PATH_CLOSE elements). 332 | * @param path The path. 333 | * @param subpath_index The subpath index, from `0` to `count-1`, where `count` is the value 334 | * returned from #ok_subpath_count. 335 | */ 336 | bool ok_subpath_is_flat(const ok_path_t *path, size_t subpath_index); 337 | 338 | /** 339 | * Checks if a subpath is closed (the last element is of type #OK_PATH_CLOSE). 340 | * @param path The path. 341 | * @param subpath_index The subpath index, from `0` to `count-1`, where `count` is the value 342 | * returned from #ok_subpath_count. 343 | */ 344 | bool ok_subpath_is_closed(const ok_path_t *path, size_t subpath_index); 345 | 346 | /** 347 | * Converts a subpath to a point list, flattening if needed. Consecutive duplicate points are not included. 348 | * 349 | * This function returns a newly allocated array of points that should be freed by the caller. 350 | * 351 | * @param path The path. 352 | * @param subpath_index The subpath index, from `0` to `count-1`, where `count` is the value 353 | * returned from #ok_subpath_count. 354 | * @param stride The number of bytes from the start of one point to another. The stride must be at least 355 | * `sizeof(double[2]) + offset` bytes. 356 | * @param[out] out_points The location to return a newly allocated array of points. The length of 357 | * the array will be `out_num_points * stride`. The array should be freed by the 358 | * caller. 359 | * @param[out] out_num_points The number of points returned. 360 | * If the `stride` value is invalid, this value is zero. 361 | */ 362 | void ok_subpath_create_point_list_generic(const ok_path_t *path, size_t subpath_index, 363 | size_t offset, size_t stride, 364 | void **out_points, size_t *out_num_points); 365 | 366 | /** 367 | * Converts a subpath to a point list, flattening if needed. Consecutive duplicate points are not included. 368 | * 369 | * This function returns a newly allocated array of points that should be freed by the caller. 370 | * 371 | * @param path The path. 372 | * @param subpath_index The subpath index, from `0` to `count-1`, where `count` is the value 373 | * returned from #ok_subpath_count. 374 | * @param[out] out_points The location to return a newly allocated array of points. The length of 375 | * the array will be `out_num_points * sizeof(double[2])`. The array should be freed by the 376 | * caller. 377 | * @param[out] out_num_points The number of points returned. 378 | */ 379 | static inline void ok_subpath_create_point_list(const ok_path_t *path, size_t subpath_index, 380 | double (**out_points)[2], size_t *out_num_points) { 381 | ok_subpath_create_point_list_generic(path, subpath_index, 0, sizeof(double[2]), 382 | (void **)out_points, out_num_points); 383 | } 384 | 385 | // MARK: Motion paths 386 | 387 | /** 388 | * Creates a motion path from an existing path. A motion path can be used to get a location 389 | * along the path, get the angle of the path at that location, or to animate a point 390 | * along the path at a constant rate. 391 | */ 392 | ok_motion_path_t *ok_motion_path_create(const ok_path_t *path); 393 | 394 | /** 395 | * Frees the path. 396 | */ 397 | void ok_motion_path_free(ok_motion_path_t *path); 398 | 399 | /** 400 | * Gets the length of the path. 401 | */ 402 | double ok_motion_path_length(const ok_motion_path_t *path); 403 | 404 | /** 405 | * Gets the location on the path at location `p`, where `p` is a value from 0.0 to 1.0. 406 | * 407 | * @param path The path. 408 | * @param p The location on the path, from 0.0 to 1.0. 409 | * @param[out] out_x The pointer where the x location should be stored. May be `NULL`. 410 | * @param[out] out_y The pointer where the y location should be stored. May be `NULL`. 411 | * @param[out] out_angle The pointer where the angle, in radians, should be stored. May be `NULL`. 412 | */ 413 | void ok_motion_path_location(const ok_motion_path_t *path, double p, 414 | double *out_x, double *out_y, double *out_angle); 415 | 416 | // MARK: Planar straight-line graph 417 | 418 | /** 419 | * Converts a path to a planar straight-line graph, flattening if needed. 420 | * 421 | * This function returns a newly allocated array of points and a newly allocated array of segment 422 | * indices. Each segment is a pair of indices of the point array, and the segments are in the same 423 | * order as the path. 424 | * 425 | * This function is the same as #ok_path_to_pslg but using a generic size for segment indices. 426 | * 427 | * @param path The path. 428 | * @param close_subpaths If true, an additional segment is added to close all subpaths. 429 | * @param index_size The size, in bytes, of the point indices. The value `sizeof(uint16_t)` will 430 | * allow up to 65536 points. On 32-bit targets, this value must be 1, 2, or 4. On 64-bit 431 | * targets, this value must be 1, 2, 4, or 8. 432 | * @param[out] out_points The location to return a newly allocated array of points. The length of 433 | * the array will be `out_num_points * sizeof(float[2])`. The array should be freed by the 434 | * caller. 435 | * @param[out] out_num_points The number of points returned. 436 | * @param[out] out_segment_indices The location to return a newly allocated array of segment 437 | * indices. The length of the array will be `out_num_segments * 2 * index_size`. The array 438 | * should be freed by the caller. 439 | * @param[out] out_num_segments The number of segments returned. 440 | */ 441 | void ok_path_create_pslg_generic(const ok_path_t *path, bool close_subpaths, size_t index_size, 442 | float **out_points, size_t *out_num_points, 443 | void **out_segment_indices, size_t *out_num_segments); 444 | 445 | /** 446 | * Converts a path to a planar straight-line graph, flattening if needed. 447 | * 448 | * This function returns a newly allocated array of points and a newly allocated array of segment 449 | * indices. Each segment is a pair of indices of the point array, and the segments are in the same 450 | * order as the path. 451 | * 452 | * This function is the same as #ok_path_to_pslg_generic using segment indices of type `size_t`. 453 | * 454 | * @param path The path. 455 | * @param close_subpaths If true, an additional segment is added to close all subpaths. 456 | * @param[out] out_points The location to return a newly allocated array of points. The length of 457 | * the array will be `out_num_points * sizeof(float[2])`. The array should be freed by the 458 | * caller. 459 | * @param[out] out_num_points The number of points returned. 460 | * @param[out] out_segment_indices The location to return a newly allocated array of segment 461 | * indices. The length of the array will be `out_num_segments * 2`. The array should be freed 462 | * by the caller. 463 | * @param[out] out_num_segments The number of segments returned. 464 | */ 465 | static inline void ok_path_create_pslg(const ok_path_t *path, bool close_subpaths, 466 | float **out_points, size_t *out_num_points, 467 | size_t **out_segment_indices, size_t *out_num_segments) { 468 | ok_path_create_pslg_generic(path, close_subpaths, sizeof(**out_segment_indices), 469 | out_points, out_num_points, 470 | (void **)out_segment_indices, out_num_segments); 471 | } 472 | 473 | /** 474 | * Appends a planar straight-line graph to this path. 475 | * 476 | * This function is the same as #ok_path_append_pslg but using a generic size for segment indices. 477 | * 478 | * @param path The path. 479 | * @param index_size The size, in bytes, of the point indices. On 32-bit targets, this value must 480 | * be 1, 2, or 4. On 64-bit targets, this value must be 1, 2, 4, or 8. 481 | * @param points The array of points. 482 | * @param segments The segment indices. Each segment is specified by two indices into the point 483 | * array. The length of this array should be `num_segments * 2 * index_size`. 484 | * @param num_segments The number of segments. 485 | */ 486 | void ok_path_append_pslg_generic(ok_path_t *path, size_t index_size, const float *points, 487 | const void *segments, size_t num_segments); 488 | 489 | /** 490 | * Appends a planar straight-line graph to this path. 491 | * 492 | * This function is the same as #ok_path_append_pslg_generic using segment indices of type `size_t`. 493 | * 494 | * @param path The path. 495 | * @param points The array of points. 496 | * @param segments The segment indices. Each segment is specified by two indices into the point 497 | * array. The length of this array should be `num_segments * 2`. 498 | * @param num_segments The number of segments. 499 | */ 500 | static inline void ok_path_append_pslg(ok_path_t *path, const float *points, 501 | const size_t *segments, size_t num_segments) { 502 | ok_path_append_pslg_generic(path, sizeof(*segments), points, segments, num_segments); 503 | } 504 | 505 | #ifdef __cplusplus 506 | } 507 | #endif 508 | 509 | #endif 510 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # mkdir -p build && cd build && cmake -G Xcode .. 2 | cmake_minimum_required(VERSION 3.6.0) 3 | 4 | project(ok_path_test C) 5 | 6 | include_directories(..) 7 | file(GLOB ok_path_test_files "main.c" "../ok_path.c" "../ok_path.h") 8 | add_executable(ok_path_test ${ok_path_test_files}) 9 | source_group("" FILES ${ok_path_test_files}) 10 | 11 | # Enable warnings 12 | if (CMAKE_C_COMPILER_ID MATCHES "Clang") 13 | # Enable -Wwrite-strings because -Weverything doesn't enable it in all versions of Clang 14 | set_target_properties(ok_path_test PROPERTIES COMPILE_FLAGS "-Weverything -Wwrite-strings -Wno-padded") 15 | elseif (CMAKE_C_COMPILER_ID MATCHES "GNU") 16 | # Disable unused-functions because of this GCC bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64079 17 | set_target_properties(ok_path_test PROPERTIES COMPILE_FLAGS "-Wall -Wextra -Wwrite-strings -Wno-unused-function") 18 | elseif (CMAKE_C_COMPILER_ID MATCHES "MSVC") 19 | # Disable 'struct padding' 20 | set_target_properties(ok_path_test PROPERTIES COMPILE_FLAGS "/Wall /wd4820 /wd4324") 21 | endif() 22 | 23 | # CTest setup, using valgrind if found. 24 | enable_testing() 25 | if (NOT CMAKE_SYSTEM_NAME MATCHES "Darwin") 26 | find_program(MEMCHECK_COMMAND valgrind) 27 | set(MEMCHECK_COMMAND_OPTIONS "--error-exitcode=1" "--leak-check=full") 28 | endif() 29 | if (NOT MEMCHECK_COMMAND) 30 | add_test(NAME ok_path_test COMMAND ok_path_test) 31 | else() 32 | add_test(NAME ok_path_test_memcheck COMMAND ${MEMCHECK_COMMAND} ${MEMCHECK_COMMAND_OPTIONS} ./ok_path_test) 33 | endif() 34 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | ## Test (Linux, Mac) 2 | ``` 3 | mkdir tmp && cd tmp && cmake .. && cmake --build . && ctest --verbose; cd .. && rm -Rf tmp 4 | ``` 5 | 6 | ## Generate project 7 | 8 | NOTE: The `build` folder is ignored via `.gitignore`. 9 | 10 | ``` 11 | mkdir -p build && cd build && cmake -G Xcode .. 12 | ``` 13 | -------------------------------------------------------------------------------- /test/main.c: -------------------------------------------------------------------------------- 1 | #include "ok_path.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static const char *svg_path = 8 | "M 100,100" 9 | "L100,200 " 10 | "h-100, " // Test superfluous comma 11 | "v-100-100 " // Test no space or comma before negative number 12 | "L0.25e-4,0.25E+2" // Test exponents 13 | "M 0 0 a25,25 -30 0,1 50,-25 " 14 | "M 200,300 Q400,50 600,300 T1000,300" 15 | "M 100,200 C100,100 250,100 250,200 S400,300 400,200" 16 | "Z"; 17 | 18 | static const double svg_path_start_x = 100; 19 | static const double svg_path_start_y = 100; 20 | static const double svg_path_end_x = 100; 21 | static const double svg_path_end_y = 200; 22 | 23 | static int test_svg_parse() { 24 | ok_path_t *path1 = ok_path_create(); 25 | ok_path_t *path2 = ok_path_create(); 26 | 27 | // Test if two empty paths are equal 28 | if (!ok_path_equals(path1, path2)) { 29 | printf("Failure: %s: Empty paths not equal\n", __func__); 30 | ok_path_free(path1); 31 | ok_path_free(path2); 32 | return 1; 33 | } 34 | 35 | char *error; 36 | if (!ok_path_append_svg(path1, svg_path, &error)) { 37 | printf("Failure: %s: SVG parse error: %s\n", error, __func__); 38 | ok_path_free(path1); 39 | ok_path_free(path2); 40 | return 1; 41 | } 42 | 43 | ok_path_move_to(path2, 100, 100); 44 | ok_path_line_to(path2, 100, 200); 45 | ok_path_line_to(path2, 0, 200); 46 | ok_path_line_to(path2, 0, 100); 47 | ok_path_line_to(path2, 0, 0); 48 | ok_path_line_to(path2, 0.25e-4, 0.25e+2); 49 | ok_path_move_to(path2, 0, 0); 50 | ok_path_elliptical_arc_to(path2, 25, 25, -30 * M_PI / 180, false, true, 50, -25); 51 | ok_path_move_to(path2, 200, 300); 52 | ok_path_quad_curve_to(path2, 400, 50, 600, 300); 53 | ok_path_quad_curve_to(path2, 800, 550, 1000, 300); 54 | ok_path_move_to(path2, 100, 200); 55 | ok_path_curve_to(path2, 100, 100, 250, 100, 250, 200); 56 | ok_path_curve_to(path2, 250, 300, 400, 300, 400, 200); 57 | ok_path_close(path2); 58 | 59 | if (!ok_path_equals(path1, path2)) { 60 | printf("Failure: %s: SVG path is not identical to programatically constructed path.\n", 61 | __func__); 62 | ok_path_free(path1); 63 | ok_path_free(path2); 64 | return 1; 65 | } 66 | 67 | printf("Success: %s\n", __func__); 68 | ok_path_free(path1); 69 | ok_path_free(path2); 70 | return 0; 71 | } 72 | 73 | static int test_iteration() { 74 | ok_path_t *path1 = ok_path_create(); 75 | ok_path_t *path2 = ok_path_create(); 76 | 77 | char *error; 78 | if (!ok_path_append_svg(path2, svg_path, &error)) { 79 | printf("Failure: %s: SVG parse error: %s\n", error, __func__); 80 | ok_path_free(path1); 81 | ok_path_free(path2); 82 | return 1; 83 | } 84 | 85 | for (size_t i = 0; i < ok_path_element_count(path2); i++) { 86 | double cx1, cy1; 87 | double cx2, cy2; 88 | double x, y; 89 | switch (ok_path_element_get(path2, i, &cx1, &cy1, &cx2, &cy2, &x, &y)) { 90 | case OK_PATH_MOVE_TO: 91 | ok_path_move_to(path1, x, y); 92 | break; 93 | case OK_PATH_LINE_TO: 94 | ok_path_line_to(path1, x, y); 95 | break; 96 | case OK_PATH_QUAD_CURVE_TO: 97 | ok_path_quad_curve_to(path1, cx1, cy1, x, y); 98 | break; 99 | case OK_PATH_CUBIC_CURVE_TO: 100 | ok_path_curve_to(path1, cx1, cy1, cx2, cy2, x, y); 101 | break; 102 | case OK_PATH_CLOSE: 103 | ok_path_close(path1); 104 | break; 105 | } 106 | } 107 | 108 | if (!ok_path_equals(path1, path2)) { 109 | printf("Failure: %s: Paths not equal\n", __func__); 110 | ok_path_free(path1); 111 | ok_path_free(path2); 112 | return 1; 113 | } 114 | 115 | printf("Success: %s\n", __func__); 116 | ok_path_free(path1); 117 | ok_path_free(path2); 118 | return 0; 119 | } 120 | 121 | static int test_append_lines() { 122 | ok_path_t *path1 = ok_path_create(); 123 | ok_path_t *path2; 124 | 125 | char *error; 126 | const char *svg_path_simple = "M 0,0 L 10,20, 40,20, 30,10, 50,0 Z"; 127 | if (!ok_path_append_svg(path1, svg_path_simple, &error)) { 128 | printf("Failure: %s: SVG parse error: %s\n", error, __func__); 129 | ok_path_free(path1); 130 | return 1; 131 | } 132 | 133 | // Using 2D array 134 | double points[][2] = {{0, 0}, {10, 20}, {40, 20}, {30, 10}, {50, 0}, {0, 0}}; 135 | path2 = ok_path_create(); 136 | ok_path_append_lines(path2, OK_PATH_MOVE_TO, points, sizeof(points) / sizeof(*points)); 137 | if (!ok_path_equals(path1, path2)) { 138 | printf("Failure: %s: Paths not equal\n", __func__); 139 | ok_path_free(path1); 140 | ok_path_free(path2); 141 | return 1; 142 | } 143 | 144 | // Using 1D array 145 | double points2[] = {0, 0, 10, 20, 40, 20, 30, 10, 50, 0, 0, 0}; 146 | ok_path_reset(path2); 147 | ok_path_append_lines(path2, OK_PATH_MOVE_TO, points, sizeof(points2) / (sizeof(*points2) * 2)); 148 | if (!ok_path_equals(path1, path2)) { 149 | printf("Failure: %s: Paths not equal\n", __func__); 150 | ok_path_free(path1); 151 | ok_path_free(path2); 152 | return 1; 153 | } 154 | 155 | printf("Success: %s\n", __func__); 156 | ok_path_free(path1); 157 | ok_path_free(path2); 158 | return 0; 159 | } 160 | 161 | static int test_flatten() { 162 | ok_path_t *path = ok_path_create(); 163 | 164 | char *error; 165 | if (!ok_path_append_svg(path, svg_path, &error)) { 166 | printf("Failure: %s: SVG parse error: %s\n", error, __func__); 167 | ok_path_free(path); 168 | return 1; 169 | } 170 | 171 | ok_path_t *flattened_path1 = ok_path_flatten(path); 172 | for (size_t i = 0; i < ok_path_element_count(flattened_path1); i++) { 173 | double x, y; 174 | enum ok_path_element_type type; 175 | type = ok_path_element_get(flattened_path1, i, NULL, NULL, NULL,NULL, &x, &y); 176 | if (type != OK_PATH_MOVE_TO && type != OK_PATH_LINE_TO && type != OK_PATH_CLOSE) { 177 | printf("Failure: Flattened path contains curves: %s\n", __func__); 178 | ok_path_free(path); 179 | ok_path_free(flattened_path1); 180 | return 1; 181 | } 182 | } 183 | 184 | ok_path_t *flattened_path2 = ok_path_create(); 185 | for (size_t i = 0; i < ok_subpath_count(path); i++) { 186 | ok_path_t *subpath = ok_subpath_flatten(path, i); 187 | ok_path_append(flattened_path2, subpath); 188 | ok_path_free(subpath); 189 | } 190 | 191 | if (!ok_path_equals(flattened_path1, flattened_path2)) { 192 | printf("Failure: %s: flattened subpaths\n", __func__); 193 | ok_path_free(path); 194 | ok_path_free(flattened_path1); 195 | ok_path_free(flattened_path2); 196 | return 1; 197 | } 198 | 199 | printf("Success: %s\n", __func__); 200 | ok_path_free(path); 201 | ok_path_free(flattened_path1); 202 | ok_path_free(flattened_path2); 203 | return 0; 204 | } 205 | 206 | static int test_subpath() { 207 | ok_path_t *path1 = ok_path_create(); 208 | ok_path_t *path2 = ok_path_create(); 209 | 210 | char *error; 211 | if (!ok_path_append_svg(path1, svg_path, &error)) { 212 | printf("Failure: %s: SVG parse error: %s\n", error, __func__); 213 | ok_path_free(path1); 214 | ok_path_free(path2); 215 | return 1; 216 | } 217 | 218 | for (size_t i = 0; i < ok_subpath_count(path1); i++) { 219 | ok_path_t *subpath = ok_subpath_create(path1, i); 220 | ok_path_append(path2, subpath); 221 | ok_path_free(subpath); 222 | } 223 | 224 | if (!ok_path_equals(path1, path2)) { 225 | printf("Failure: %s: subpaths\n", __func__); 226 | ok_path_free(path1); 227 | ok_path_free(path2); 228 | return 1; 229 | } 230 | 231 | printf("Success: %s\n", __func__); 232 | ok_path_free(path1); 233 | ok_path_free(path2); 234 | return 0; 235 | } 236 | 237 | static int test_motion_path() { 238 | ok_path_t *path = ok_path_create(); 239 | 240 | char *error; 241 | if (!ok_path_append_svg(path, svg_path, &error)) { 242 | printf("Failure: %s: SVG parse error: %s\n", error, __func__); 243 | ok_path_free(path); 244 | return 1; 245 | } 246 | 247 | ok_motion_path_t *motion_path = ok_motion_path_create(path); 248 | ok_path_free(path); 249 | 250 | double x, y; 251 | ok_motion_path_location(motion_path, 0.0, &x, &y, NULL); 252 | if (x != svg_path_start_x || y != svg_path_start_y) { 253 | printf("Failure: Flattened path error (start): %s\n", __func__); 254 | ok_motion_path_free(motion_path); 255 | return 1; 256 | } 257 | ok_motion_path_location(motion_path, 1.0, &x, &y, NULL); 258 | if (x != svg_path_end_x || y != svg_path_end_y) { 259 | printf("Failure: Flattened path error (end): %s\n", __func__); 260 | ok_motion_path_free(motion_path); 261 | return 1; 262 | } 263 | 264 | printf("Success: %s\n", __func__); 265 | ok_motion_path_free(motion_path); 266 | return 0; 267 | } 268 | 269 | static int test_point_list() { 270 | ok_path_t *path1 = ok_path_create(); 271 | ok_path_t *path2 = ok_path_create(); 272 | 273 | char *error; 274 | if (!ok_path_append_svg(path1, svg_path, &error)) { 275 | printf("Failure: %s: SVG parse error: %s\n", error, __func__); 276 | ok_path_free(path1); 277 | ok_path_free(path2); 278 | return 1; 279 | } 280 | ok_path_t *path1_flattened = ok_path_flatten(path1); 281 | 282 | // Plain double[2] array test 283 | for (size_t i = 0; i < ok_subpath_count(path1); i++) { 284 | double (*points)[2]; 285 | size_t num_points; 286 | ok_subpath_create_point_list(path1, i, &points, &num_points); 287 | ok_path_append_lines(path2, OK_PATH_MOVE_TO, points, num_points); 288 | free(points); 289 | } 290 | if (!ok_path_equals(path1_flattened, path2)) { 291 | printf("Failure: %s: point list\n", __func__); 292 | ok_path_free(path1_flattened); 293 | ok_path_free(path1); 294 | ok_path_free(path2); 295 | return 1; 296 | } 297 | 298 | // Cleanup 299 | ok_path_free(path2); 300 | path2 = ok_path_create(); 301 | 302 | // Specialized test 303 | struct my_point { 304 | int random_data1[3]; 305 | double x; 306 | double y; 307 | int random_data2[8]; 308 | }; 309 | for (size_t i = 0; i < ok_subpath_count(path1); i++) { 310 | struct my_point *points; 311 | size_t num_points; 312 | ok_subpath_create_point_list_generic(path1, i, offsetof(struct my_point, x), 313 | sizeof(struct my_point), 314 | (void **)&points, &num_points); 315 | if (num_points > 0) { 316 | ok_path_move_to(path2, points[0].x, points[0].y); 317 | } 318 | for (size_t j = 1; j < num_points; j++) { 319 | ok_path_line_to(path2, points[j].x, points[j].y); 320 | } 321 | free(points); 322 | } 323 | if (!ok_path_equals(path1_flattened, path2)) { 324 | printf("Failure: %s: point list\n", __func__); 325 | ok_path_free(path1_flattened); 326 | ok_path_free(path1); 327 | ok_path_free(path2); 328 | return 1; 329 | } 330 | 331 | printf("Success: %s\n", __func__); 332 | ok_path_free(path1_flattened); 333 | ok_path_free(path1); 334 | ok_path_free(path2); 335 | return 0; 336 | } 337 | 338 | static int test_pslg() { 339 | ok_path_t *path1 = ok_path_create(); 340 | ok_path_t *path2 = ok_path_create(); 341 | 342 | char *error; 343 | if (!ok_path_append_svg(path1, svg_path, &error)) { 344 | printf("Failure: %s: SVG parse error: %s\n", error, __func__); 345 | ok_path_free(path1); 346 | ok_path_free(path2); 347 | return 1; 348 | } 349 | 350 | float *points = NULL; 351 | size_t *segments = NULL; 352 | size_t num_points = 0; 353 | size_t num_segments = 0; 354 | ok_path_create_pslg(path1, false, &points, &num_points, &segments, &num_segments); 355 | ok_path_append_pslg(path2, points, segments, num_segments); 356 | free(points); 357 | free(segments); 358 | 359 | ok_path_t *path1_flattened = ok_path_flatten(path1); 360 | ok_path_free(path1); 361 | 362 | if (!ok_path_equals(path1_flattened, path2)) { 363 | printf("Failure: %s: pslg\n", __func__); 364 | ok_path_free(path1_flattened); 365 | ok_path_free(path2); 366 | return 1; 367 | } 368 | 369 | printf("Success: %s\n", __func__); 370 | ok_path_free(path1_flattened); 371 | ok_path_free(path2); 372 | return 0; 373 | } 374 | 375 | int main() { 376 | return (test_svg_parse() || test_iteration() || test_append_lines() || test_flatten() || 377 | test_subpath() || test_motion_path() || test_point_list() || test_pslg()); 378 | } 379 | --------------------------------------------------------------------------------