diff options
author | Matthias Clasen <mclasen@redhat.com> | 2020-12-11 18:42:39 -0500 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2020-12-11 20:00:10 -0500 |
commit | 67de4cb59e537107acbda0a15355f6779fad0dda (patch) | |
tree | 14d405720c947d4a3e96df18d781c2eda6dadb0f | |
parent | b6c9b404c3bdcf617a4411f67ff8d86fbce84c88 (diff) | |
download | gtk+-wip/matthiasc/lottie-stroke.tar.gz |
Another strokerwip/matthiasc/lottie-stroke
-rw-r--r-- | gsk/gskpathstroke.c | 1998 |
1 files changed, 345 insertions, 1653 deletions
diff --git a/gsk/gskpathstroke.c b/gsk/gskpathstroke.c index 6d1678d505..347309f772 100644 --- a/gsk/gskpathstroke.c +++ b/gsk/gskpathstroke.c @@ -17,374 +17,31 @@ * Authors: Matthias Clasen <mclasen@redhat.com> */ -#define STROKE_DEBUG 1 - -#include "config.h" - #include "gskpathprivate.h" #include "gskpathbuilder.h" -#include "gsksplineprivate.h" #include "gskstrokeprivate.h" +#include "gskcurveprivate.h" #include "gskpathdashprivate.h" +#include "gskpathopprivate.h" -#include "gdk/gdk-private.h" - -#ifdef STROKE_DEBUG -static const char * op_to_string (GskPathOperation op) G_GNUC_UNUSED; -#endif - -#define DEG_TO_RAD(x) ((x) * (G_PI / 180.f)) -#define RAD_TO_DEG(x) ((x) / (G_PI / 180.f)) - -/* Set n to the normal of the line through p0 and p1 */ static void -normal_vector (const graphene_point_t *p0, - const graphene_point_t *p1, - graphene_vec2_t *n) -{ - graphene_vec2_init (n, p0->y - p1->y, p1->x - p0->x); - graphene_vec2_normalize (n, n); -} - -static void -direction_vector (const graphene_point_t *p0, - const graphene_point_t *p1, - graphene_vec2_t *t) +get_tangent (const graphene_point_t *p0, + const graphene_point_t *p1, + graphene_vec2_t *t) { graphene_vec2_init (t, p1->x - p0->x, p1->y - p0->y); graphene_vec2_normalize (t, t); } static void -find_point_on_line (const graphene_point_t *p1, - const graphene_point_t *p2, - const graphene_point_t *q, - float *t) -{ - float tx = p2->x - p1->x; - float ty = p2->y - p1->y; - float sx = q->x - p1->x; - float sy = q->y - p1->y; - - *t = (tx*sx + ty*sy) / (tx*tx + ty*ty); -} - -static void -get_bezier (const graphene_point_t *points, - int length, - float t, - graphene_point_t *p) -{ - if (length == 1) - *p = points[0]; - else - { - graphene_point_t *newpoints; - int i; - - newpoints = g_alloca (sizeof (graphene_point_t) * (length - 1)); - for (i = 0; i < length - 1; i++) - graphene_point_interpolate (&points[i], &points[i + 1], t, &newpoints[i]); - get_bezier (newpoints, length - 1, t, p); - } -} - -static void -get_cubic (const graphene_point_t points[4], - float t, - graphene_point_t *p) -{ - get_bezier (points, 4, t, p); -} - -static void -split_bezier_recurse (const graphene_point_t *points, - int length, - float t, - graphene_point_t *left, - graphene_point_t *right, - int *lpos, - int *rpos) -{ - if (length == 1) - { - left[*lpos] = points[0]; - (*lpos)++; - right[*rpos] = points[length - 1]; - (*rpos)--; - } - else - { - graphene_point_t *newpoints; - int i; - - newpoints = g_alloca (sizeof (graphene_point_t) * (length - 1)); - for (i = 0; i < length - 1; i++) - { - if (i == 0) - { - left[*lpos] = points[i]; - (*lpos)++; - } - if (i + 1 == length - 1) - { - right[*rpos] = points[i + 1]; - (*rpos)--; - } - graphene_point_interpolate (&points[i], &points[i + 1], t, &newpoints[i]); - } - split_bezier_recurse (newpoints, length - 1, t, left, right, lpos, rpos); - } -} - -/* Given Bezier control points and a t value between 0 and 1, - * return new Bezier control points for two segments in left - * and right that are obtained by splitting the curve at the - * point for t. - */ -static void -split_bezier (const graphene_point_t *points, - int length, - float t, - graphene_point_t *left, - graphene_point_t *right) -{ - int lpos = 0; - int rpos = length - 1; - - split_bezier_recurse (points, length, t, left, right, &lpos, &rpos); -} - -static void -get_rational_bezier (const graphene_point_t *p, - float *w, - int l, - float t, - graphene_point_t *q) -{ - if (l == 1) - { - q->x = p[0].x; - q->y = p[0].y; - } - else - { - graphene_point_t *np; - float *nw; - int i; - - np = g_alloca (sizeof (graphene_point3d_t) * (l - 1)); - nw = g_alloca (sizeof (float) * (l - 1)); - - for (i = 0; i < l - 1; i++) - { - nw[i] = (1 - t) * w[i] + t * w[i + 1]; - np[i].x = (1 - t) * (w[i]/nw[i]) * p[i].x + t * (w[i + 1]/nw[i]) * p[i + 1].x; - np[i].y = (1 - t) * (w[i]/nw[i]) * p[i].y + t * (w[i + 1]/nw[i]) * p[i + 1].y; - } - get_rational_bezier (np, nw, l - 1, t, q); - } -} - -static void -split_bezier3d_recurse (const graphene_point3d_t *p, - int l, - float t, - graphene_point3d_t *left, - graphene_point3d_t *right, - int *lpos, - int *rpos) -{ - if (l == 1) - { - left[*lpos] = p[0]; - right[*rpos] = p[0]; - } - else - { - graphene_point3d_t *np; - int i; - - np = g_alloca (sizeof (graphene_point3d_t) * (l - 1)); - for (i = 0; i < l - 1; i++) - { - if (i == 0) - { - left[*lpos] = p[i]; - (*lpos)++; - } - if (i + 1 == l - 1) - { - right[*rpos] = p[i + 1]; - (*rpos)--; - } - graphene_point3d_interpolate (&p[i], &p[i + 1], t, &np[i]); - } - split_bezier3d_recurse (np, l - 1, t, left, right, lpos, rpos); - } -} - -static void -split_bezier3d (const graphene_point3d_t *p, - int l, - float t, - graphene_point3d_t *left, - graphene_point3d_t *right) -{ - int lpos = 0; - int rpos = l - 1; - split_bezier3d_recurse (p, l, t, left, right, &lpos, &rpos); -} - -static gboolean -acceptable (float t) -{ - return 0 <= t && t <= 1; -} - -/* compute the angle between a, b and c in the range of [0, 360] */ -static float -three_point_angle (const graphene_point_t *a, - const graphene_point_t *b, - const graphene_point_t *c) -{ - float angle = atan2 (c->y - b->y, c->x - b->x) - atan2 (a->y - b->y, a->x - b->x); - - if (angle < 0) - angle += 2 * M_PI; - - return RAD_TO_DEG (angle); -} - -static gboolean -cubic_is_simple (const graphene_point_t *pts) -{ - float a1, a2, s; - graphene_vec2_t n1, n2; - - a1 = three_point_angle (&pts[0], &pts[1], &pts[2]); - a2 = three_point_angle (&pts[1], &pts[2], &pts[3]); - - if ((a1 < 180.f && a2 > 180.f) || (a1 > 180.f && a2 < 180.f)) - return FALSE; - - normal_vector (&pts[0], &pts[1], &n1); - normal_vector (&pts[2], &pts[3], &n2); - - s = graphene_vec2_dot (&n1, &n2); - - if (fabs (acos (s)) >= M_PI / 3.f) - return FALSE; - - return TRUE; -} - -static float -cuberoot (float v) -{ - if (v < 0) - return -pow (-v, 1.f / 3); - return pow (v, 1.f / 3); -} - -static int -get_cubic_roots (float pa, - float pb, - float pc, - float pd, - float roots[3]) +get_normal (const graphene_point_t *p0, + const graphene_point_t *p1, + graphene_vec2_t *n) { - float a, b, c, d; - float q, q2; - float p, p3; - float discriminant; - float u1, v1, sd; - int n_roots = 0; - - d = -pa + 3*pb - 3*pc + pd; - a = 3*pa - 6*pb + 3*pc; - b = -3*pa + 3*pb; - c = pa; - - if (fabs (d) < 0.0001) - { - if (fabs (a) < 0.0001) - { - if (fabs (b) < 0.0001) - return 0; - - if (acceptable (-c / b)) - { - roots[0] = -c / b; - return 1; - } - - return 0; - } - q = sqrt (b*b - 4*a*c); - roots[n_roots] = (-b + q) / (2 * a); - if (acceptable (roots[n_roots])) - n_roots++; - roots[n_roots] = (-b - q) / (2 * a); - if (acceptable (roots[n_roots])) - n_roots++; - return n_roots; - } - - a /= d; - b /= d; - c /= d; - - p = (3*b - a*a)/3; - p3 = p/3; - q = (2*a*a*a - 9*a*b + 27*c)/27; - q2 = q/2; - discriminant = q2*q2 + p3*p3*p3; - - if (discriminant < 0) - { - float mp3 = -p/3; - float mp33 = mp3*mp3*mp3; - float r = sqrt (mp33); - float t = -q / (2*r); - float cosphi = t < -1 ? -1 : (t > 1 ? 1 : t); - float phi = acos (cosphi); - float crtr = cuberoot (r); - float t1 = 2*crtr; - - roots[n_roots] = t1 * cos (phi/3) - a/3; - if (acceptable (roots[n_roots])) - n_roots++; - roots[n_roots] = t1 * cos ((phi + 2*M_PI) / 3) - a/3; - if (acceptable (roots[n_roots])) - n_roots++; - roots[n_roots] = t1 * cos ((phi + 4*M_PI) / 3) - a/3; - if (acceptable (roots[n_roots])) - n_roots++; - return n_roots; - } - - if (discriminant == 0) - { - u1 = q2 < 0 ? cuberoot (-q2) : -cuberoot (q2); - roots[n_roots] = 2*u1 - a/3; - if (acceptable (roots[n_roots])) - n_roots++; - roots[n_roots] = -u1 - a/3; - if (acceptable (roots[n_roots])) - n_roots++; - return n_roots; - } - - sd = sqrt (discriminant); - u1 = cuberoot (sd - q2); - v1 = cuberoot (sd + q2); - roots[n_roots] = u1 - v1 - a/3; - if (acceptable (roots[n_roots])) - n_roots++; - return n_roots; + graphene_vec2_init (n, p0->y - p1->y, p1->x - p0->x); + graphene_vec2_normalize (n, n); } /* Compute q = p + d * n */ @@ -398,1022 +55,407 @@ scale_point (const graphene_point_t *p, q->y = p->y + d * graphene_vec2_get_y (n); } -static void -midpoint (const graphene_point_t *a, - const graphene_point_t *b, - graphene_point_t *q) -{ - q->x = (a->x + b->x) / 2; - q->y = (a->y + b->y) / 2; -} - -/* Set p to the intersection of the lines through a, b and c, d. - * Return the number of intersections found (0 or 1) */ -static int -line_intersection (const graphene_point_t *a, - const graphene_point_t *b, - const graphene_point_t *c, - const graphene_point_t *d, - graphene_point_t *p) -{ - float a1 = b->y - a->y; - float b1 = a->x - b->x; - float c1 = a1*a->x + b1*a->y; - - float a2 = d->y - c->y; - float b2 = c->x - d->x; - float c2 = a2*c->x+ b2*c->y; - - float det = a1*b2 - a2*b1; - - if (det == 0) - { - p->x = NAN; - p->y = NAN; - return 0; - } - else - { - p->x = (b2*c1 - b1*c2) / det; - p->y = (a1*c2 - a2*c1) / det; - return 1; - } -} - -static void -align_point (const graphene_point_t *p0, - const graphene_point_t *a, - const graphene_point_t *b, - graphene_point_t *q) -{ - graphene_vec2_t n; - float angle; - - direction_vector (a, b, &n); - angle = -atan2 (graphene_vec2_get_y (&n), graphene_vec2_get_x (&n)); - q->x = (p0->x - a->x) * cos (angle) - (p0->y - a->y) * sin (angle); - q->y = (p0->x - a->x) * sin (angle) + (p0->y - a->y) * cos (angle); -} - -static int -cmpfloat (const void *p1, const void *p2) -{ - const float *f1 = p1; - const float *f2 = p2; - return f1 < f2 ? -1 : (f1 > f2 ? 1 : 0); -} - -/* Place intersections between the line and the curve in q, - * and their Bezier positions in t. - * Return the number of intersections found (0 to 3). - */ -static int -line_curve_intersection (const graphene_point_t *a, - const graphene_point_t *b, - const graphene_point_t pts[4], - float t[3], - graphene_point_t q[3]) -{ - graphene_point_t p[4]; - int n, i; - - align_point (&pts[0], a, b, &p[0]); - align_point (&pts[1], a, b, &p[1]); - align_point (&pts[2], a, b, &p[2]); - align_point (&pts[3], a, b, &p[3]); - - n = get_cubic_roots (p[0].y, p[1].y, p[2].y, p[3].y, t); - qsort (t, n, sizeof (float), cmpfloat); - for (i = 0; i < n; i++) - get_cubic (pts, t[i], &q[i]); - - return n; -} - -typedef struct _Curve Curve; -typedef struct _CurveClass CurveClass; - -struct _Curve +typedef struct { - CurveClass *class; - graphene_point_t p[4]; - float weight; -}; + GskPathBuilder *builder; + GskPathBuilder *left; + GskPathBuilder *right; + GskStroke *stroke; + gboolean has_current_point; + GskCurve c; + GskCurve l; + GskCurve r; -struct _CurveClass -{ - const char *name; - void (* get_bounds) (Curve *self, graphene_rect_t *bounds); - void (* get_point) (Curve *self, float t, graphene_point_t *point); - void (* split) (Curve *self, float t, Curve *left, Curve *right); -}; + graphene_point_t l0; + graphene_point_t r0; +} StrokeData; static void -line_segment_get_bounds (Curve *c, graphene_rect_t *bounds) +gsk_path_builder_move_to_point (GskPathBuilder *builder, + const graphene_point_t *point) { - graphene_rect_init (bounds, c->p[0].x, c->p[0].y, 0, 0); - graphene_rect_expand (bounds, &c->p[1], bounds); + gsk_path_builder_move_to (builder, point->x, point->y); } static void -line_segment_get_point (Curve *c, float t, graphene_point_t *point) +gsk_path_builder_line_to_point (GskPathBuilder *builder, + const graphene_point_t *point) { - point->x = (1 - t) * c->p[0].x + t * c->p[1].x; - point->y = (1 - t) * c->p[0].y + t * c->p[1].y; + gsk_path_builder_line_to (builder, point->x, point->y); } -static void init_line_segment (Curve *c, - graphene_point_t *a, - graphene_point_t *b); - static void -line_segment_split (Curve *c, float t, Curve *left, Curve *right) +gsk_path_builder_add_curve (GskPathBuilder *builder, + const GskCurve *curve) { - graphene_point_t p; + const graphene_point_t *p; - line_segment_get_point (c, t, &p); - init_line_segment (left, &c->p[0], &p); - init_line_segment (right, &p, &c->p[1]); -} - -static CurveClass LINE_SEGMENT_CLASS = -{ - "LineSegment", - line_segment_get_bounds, - line_segment_get_point, - line_segment_split -}; - -static void -init_line_segment (Curve *c, - graphene_point_t *a, - graphene_point_t *b) -{ - c->class = &LINE_SEGMENT_CLASS; - c->p[0] = *a; - c->p[1] = *b; -} - -static void -cubic_get_point (Curve *c, float t, graphene_point_t *point) -{ - get_bezier (c->p, 4, t, point); -} + switch (curve->op) + { + case GSK_PATH_LINE: + p = curve->line.points; + gsk_path_builder_line_to (builder, p[1].x, p[1].y); + break; -static int -get_cubic_extrema (float pa, - float pb, - float pc, - float pd, - float roots[2]) -{ - float a, b, c; - float d, t; - int n_roots = 0; + case GSK_PATH_CURVE: + p = curve->curve.points; + gsk_path_builder_curve_to (builder, p[1].x, p[1].y, + p[2].x, p[2].y, + p[3].x, p[3].y); + break; - a = 3 * (pd - 3*pc + 3*pb - pa); - b = 6 * (pc - 2*pb + pa); - c = 3 * (pb - pa); + case GSK_PATH_CONIC: + p = curve->conic.points; + gsk_path_builder_conic_to (builder, p[1].x, p[1].y, + p[3].x, p[3].y, + p[2].x); + break; - if (fabs (a) > 0.0001) - { - if (b*b > 4*a*c) - { - d = sqrt (b*b - 4*a*c); - t = (-b + d)/(2*a); - if (acceptable (t)) - roots[n_roots++] = t; - t = (-b - d)/(2*a); - if (acceptable (t)) - roots[n_roots++] = t; - } - else - { - t = -b / (2*a); - if (acceptable (t)) - roots[n_roots++] = t; - } - } - else if (fabs (b) > 0.0001) - { - t = -c / b; - if (acceptable (t)) - roots[n_roots++] = t; + case GSK_PATH_MOVE: + case GSK_PATH_CLOSE: + default: + g_assert_not_reached (); } - - return n_roots; } -static void -cubic_get_bounds (Curve *c, graphene_rect_t *bounds) +static gboolean +add_op (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer user_data) { - graphene_point_t p; - float t[4]; - int n, i; - - graphene_rect_init (bounds, c->p[0].x, c->p[0].y, 0, 0); - graphene_rect_expand (bounds, &c->p[3], bounds); + GskCurve *curve; + GList **ops = user_data; - n = get_cubic_extrema (c->p[0].x, c->p[1].x, c->p[2].x, c->p[3].x, t); - n += get_cubic_extrema (c->p[0].y, c->p[1].y, c->p[2].y, c->p[3].y, &t[n]); - for (i = 0; i < n; i++) + curve = g_new (GskCurve, 1); + switch (op) { - cubic_get_point (c, t[i], &p); - graphene_rect_expand (bounds, &p, bounds); + case GSK_PATH_MOVE: + return TRUE; + case GSK_PATH_LINE: + gsk_curve_init (curve, gsk_pathop_encode (op, (const graphene_point_t[2]) { pts[1], pts[0] })); + break; + case GSK_PATH_CURVE: + gsk_curve_init (curve, gsk_pathop_encode (op, (const graphene_point_t[4]) { pts[3], pts[2], pts[1], pts[0] })); + break; + case GSK_PATH_CONIC: + gsk_curve_init (curve, gsk_pathop_encode (op, (const graphene_point_t[4]) { pts[3], pts[1], pts[2], pts[0] })); + break; + case GSK_PATH_CLOSE: + default: + g_assert_not_reached (); } -} - -static void init_cubic (Curve *c, - graphene_point_t p[4]); - -static void -cubic_split (Curve *c, float t, Curve *left, Curve *right) -{ - split_bezier (c->p, 4, t, left->p, right->p); - init_cubic (left, left->p); - init_cubic (right, right->p); -} -static CurveClass CUBIC_CLASS = -{ - "Cubic", - cubic_get_bounds, - cubic_get_point, - cubic_split -}; + *ops = g_list_prepend (*ops, curve); -static void -init_cubic (Curve *c, - graphene_point_t p[4]) -{ - c->class = &CUBIC_CLASS; - c->p[0] = p[0]; - c->p[1] = p[1]; - c->p[2] = p[2]; - c->p[3] = p[3]; + return TRUE; } static void -conic_get_point (Curve *c, float t, graphene_point_t *point) -{ - get_rational_bezier (c->p, (float [3]) { 1, c->weight, 1}, 3, t, point); -} - -/* Solve N = 0 where N is the numerator of derivative of P/Q, with - * P = (1-t)^a + 2t(1-t)wb + t^2c - * Q = (1-t)^2 + 2t(1-t)w + t^2 - */ -static int -get_conic_extrema (float a, float b, float c, float w, float t[10]) +gsk_path_builder_add_reverse_path (GskPathBuilder *builder, + GskPath *path) { - float q, tt; - int n = 0; - float w2 = w*w; - float wac = (w - 1)*(a - c); - - if (wac != 0) - { - q = - sqrt (a*a - 4*a*b*w2 + 4*a*c*w2 - 2*a*c + 4*b*b*w2 - 4*b*c*w2 + c*c); - - tt = (- q + 2*a*w - a - 2*b*w + c)/(2*wac); - - if (acceptable (tt)) - t[n++] = tt; - - tt = (q + 2*a*w - a - 2*b*w + c)/(2*wac); + GList *l, *ops; - if (acceptable (tt)) - t[n++] = tt; - } - - if (w * (b - c) != 0 && a == c) - t[n++] = 0.5; - - if (w == 1 && a - 2*b + c != 0) + ops = NULL; + gsk_path_foreach (path, + GSK_PATH_FOREACH_ALLOW_CURVE | GSK_PATH_FOREACH_ALLOW_CONIC, + add_op, + &ops); + for (l = ops; l; l = l->next) { - tt = (a - b) / (a - 2*b + c); - if (acceptable (tt)) - t[n++] = tt; + GskCurve *curve = l->data; + gsk_path_builder_add_curve (builder, curve); } - - return n; + g_list_free_full (ops, g_free); } -static void -conic_get_bounds (Curve *c, - graphene_rect_t *bounds) +static float +angle_between (const graphene_vec2_t *t1, + const graphene_vec2_t *t2) { - graphene_point_t p; - float t[10]; - int i, n; - - graphene_rect_init (bounds, c->p[0].x, c->p[0].y, 0, 0); - graphene_rect_expand (bounds, &c->p[2], bounds); + float angle = atan2 (graphene_vec2_get_y (t2), graphene_vec2_get_x (t2)) + - atan2 (graphene_vec2_get_y (t1), graphene_vec2_get_x (t1)); - n = get_conic_extrema (c->p[0].x, c->p[1].x, c->p[2].x, c->weight, t); - n += get_conic_extrema (c->p[0].y, c->p[1].y, c->p[2].y, c->weight, &t[n]); + if (angle > M_PI) + angle -= 2 * M_PI; + if (angle < - M_PI) + angle += 2 * M_PI; - for (i = 0; i < n; i++) - { - conic_get_point (c, t[i], &p); - graphene_rect_expand (bounds, &p, bounds); - } + return angle; } -static void init_conic (Curve *c, - const graphene_point_t p[3], - float weight); - -static void -conic_split (Curve *c, float t, Curve *left, Curve *right) +static int +line_intersect (const graphene_point_t *a, + const graphene_vec2_t *ab, + const graphene_point_t *c, + const graphene_vec2_t *cd, + graphene_point_t *p) { - /* Given control points and weight for a rational quadratic - * Bezier and t, create two sets of the same that give the - * same curve as the original and split the curve at t. - */ - graphene_point3d_t p[3]; - graphene_point3d_t l[3], r[3]; - int i; - - /* do de Casteljau in homogeneous coordinates... */ - for (i = 0; i < 3; i++) - { - p[i].x = c->p[i].x; - p[i].y = c->p[i].y; - p[i].z = 1; - } - - p[1].x *= c->weight; - p[1].y *= c->weight; - p[1].z *= c->weight; - - split_bezier3d (p, 3, t, l, r); - - /* then project the control points down */ - for (i = 0; i < 3; i++) - { - left->p[i].x = l[i].x / l[i].z; - left->p[i].y = l[i].y / l[i].z; - right->p[i].x = r[i].x / r[i].z; - right->p[i].y = r[i].y / r[i].z; - } - - /* normalize the outer weights to be 1 by using - * the fact that weights w_i and c*w_i are equivalent - * for any nonzero constant c - */ - for (i = 0; i < 3; i++) - { - l[i].z /= l[0].z; - r[i].z /= r[2].z; - } + float a1 = graphene_vec2_get_y (ab); + float b1 = - graphene_vec2_get_x (ab); + float c1 = a1 * a->x + b1 * a->y; - /* normalize the inner weight to be 1 by using - * the fact that w_0*w_2/w_1^2 is a constant for - * all equivalent weights. - */ - left->weight = l[1].z / sqrt (l[2].z); - right->weight = r[1].z / sqrt (r[0].z); + float a2 = graphene_vec2_get_y (cd); + float b2 = - graphene_vec2_get_x (cd); + float c2 = a2 * c->x + b2 * c->y; - init_conic (left, left->p, left->weight); - init_conic (right, right->p, right->weight); -} + float det = a1 * b2 - a2 * b1; -static CurveClass CONIC_CLASS = -{ - "Conic", - conic_get_bounds, - conic_get_point, - conic_split -}; + if (det == 0) + return 0; -static void -init_conic (Curve *c, - const graphene_point_t p[3], - float weight) -{ - c->class = &CONIC_CLASS; - c->p[0] = p[0]; - c->p[1] = p[1]; - c->p[2] = p[2]; - c->weight = weight; -}; + p->x = (b2 * c1 - b1 * c2) / det; + p->y = (a1 * c2 - a2 * c1) / det; -static void -curve_get_bounds (Curve *curve, graphene_rect_t *bounds) -{ - curve->class->get_bounds (curve, bounds); + return 1; } -static void -curve_get_point (Curve *curve, float t, graphene_point_t *point) -{ - curve->class->get_point (curve, t, point); -} - -static void -curve_split (Curve *curve, float t, Curve *left, Curve *right) -{ - curve->class->split (curve, t, left, right); -} +#define RAD_TO_DEG(r) ((r)*180.0/M_PI) +#define DEG_TO_RAD(d) ((d)*M_PI/180.0) static void -init_curve (Curve *c, - GskPathOperation op, - graphene_point_t p[4], - float w) +add_line_join (GskPathBuilder *builder, + GskStroke *stroke, + const graphene_point_t *c, + const graphene_point_t *a, + const graphene_vec2_t *ta, + const graphene_point_t *b, + const graphene_vec2_t *tb, + float angle) { - switch (op) + switch (stroke->line_join) { - case GSK_PATH_CLOSE: - case GSK_PATH_LINE: - init_line_segment (c, &p[0], &p[1]); + case GSK_LINE_JOIN_MITER: + case GSK_LINE_JOIN_MITER_CLIP: + { + graphene_point_t p; + + if (line_intersect (a, ta, b, tb, &p)) + { + if (fabs (1 / sin ((angle + M_PI) / 2)) <= stroke->miter_limit) + { + gsk_path_builder_line_to_point (builder, &p); + gsk_path_builder_line_to_point (builder, b); + } + else if (stroke->line_join == GSK_LINE_JOIN_MITER_CLIP) + { + graphene_point_t q, a1, b1; + graphene_vec2_t t, n; + + get_tangent (c, &p, &t); + scale_point (c, &t, stroke->line_width * stroke->miter_limit / 2, &q); + get_normal (c, &p, &n); + + line_intersect (a, ta, &q, &n, &a1); + line_intersect (b, tb, &q, &n, &b1); + + gsk_path_builder_line_to_point (builder, &a1); + gsk_path_builder_line_to_point (builder, &b1); + gsk_path_builder_line_to_point (builder, b); + } + else + { + gsk_path_builder_line_to_point (builder, b); + } + } + } break; - case GSK_PATH_CURVE: - init_cubic (c, p); + case GSK_LINE_JOIN_ROUND: + gsk_path_builder_svg_arc_to (builder, + stroke->line_width / 2, stroke->line_width / 2, + 0, 0, angle > 0 ? 1 : 0, + b->x, b->y); break; - case GSK_PATH_CONIC: - init_conic (c, p, w); + case GSK_LINE_JOIN_BEVEL: + gsk_path_builder_line_to_point (builder, b); break; - case GSK_PATH_MOVE: default: g_assert_not_reached (); } } -static int -line_segment_intersection (const graphene_point_t *p1, - const graphene_point_t *p2, - const graphene_point_t *p3, - const graphene_point_t *p4, - float *t, - float *s, - graphene_point_t *p) -{ - float a1 = p2->x - p1->x; - float b1 = p2->y - p1->y; - - float a2 = p4->x - p3->x; - float b2 = p4->y - p3->y; - - float det = a1 * b2 - a2 * b1; - float tt, ss; - - if (det != 0) - { - tt = ((p3->x - p1->x) * b2 - (p3->y - p1->y) * a2) / det; - ss = ((p3->y - p1->y) * a2 - (p3->x - p1->x) * b1) / det; - - if (acceptable (tt) && acceptable (ss)) - { - p->x = p1->x + tt * (p2->x - p1->x); - p->y = p1->y + tt * (p2->y - p1->y); - - *t = tt; - *s = ss; - return 1; - } - } - - return 0; -} - static void -intersection_recurse (Curve *c1, - Curve *c2, - float t1, - float t2, - float s1, - float s2, - float *t, - float *s, - graphene_point_t *q, - int n, - int *pos) +add_curve (StrokeData *stroke_data, + const GskCurve *curve) { - graphene_rect_t b1, b2; - float d; - float e; - Curve p11, p12, p21, p22; - - if (*pos == 9) - return; - - curve_get_bounds (c1, &b1); - curve_get_bounds (c2, &b2); + GskCurve l, r; - if (!graphene_rect_intersection (&b1, &b2, NULL)) - return; + gsk_curve_offset (curve, - stroke_data->stroke->line_width / 2, &l); + gsk_curve_offset (curve, stroke_data->stroke->line_width / 2, &r); - d = (t2 - t1) / 2; - e = (s2 - s1) / 2; - - if (b1.size.width < 0.1 && b1.size.height < 0.1 && - b2.size.width < 0.1 && b2.size.height < 0.1) + if (!stroke_data->has_current_point) { - t[*pos] = t1 + d; - s[*pos] = s1 + e; - curve_get_point (c1, 0.5, &q[*pos]); - (*pos)++; - return; - } - - curve_split (c1, 0.5, &p11, &p12); - curve_split (c2, 0.5, &p21, &p22); - - intersection_recurse (&p11, &p21, t1, t1 + d, s1, s1 + e, t, s, q, n, pos); - intersection_recurse (&p11, &p22, t1, t1 + d, s1 + e, s2, t, s, q, n, pos); - intersection_recurse (&p12, &p21, t1 + d, t2, s1, s1 + e, t, s, q, n, pos); - intersection_recurse (&p12, &p22, t1 + d, t2, s1 + e, s2, t, s, q, n, pos); -} - -/* Place intersections between the curves in q, and - * their Bezier positions in t and s, up to n. - * Return the number of intersections found. - */ -static int -curve_intersection (Curve *c1, - Curve *c2, - float *t, - float *s, - graphene_point_t *q, - int n) -{ - int i, pos; - - if (c1->class == &LINE_SEGMENT_CLASS && c2->class == &LINE_SEGMENT_CLASS) - { - return line_segment_intersection (&c1->p[0], &c1->p[1], &c2->p[0], &c2->p[1], t, s, q); - } - else if (c1->class == &LINE_SEGMENT_CLASS && c2->class == &CUBIC_CLASS) - { - pos = line_curve_intersection (&c1->p[0], &c1->p[1], c2->p, s, q); - for (i = 0; i < pos; i++) - find_point_on_line (&c1->p[0], &c1->p[1], &q[i], &t[i]); - return pos; - } - else if (c1->class == &CUBIC_CLASS && c2->class == &LINE_SEGMENT_CLASS) - { - pos = line_curve_intersection (&c2->p[0], &c2->p[1], c1->p, t, q); - for (i = 0; i < pos; i++) - find_point_on_line (&c2->p[0], &c2->p[1], &q[i], &s[i]); - return pos; + stroke_data->r0 = *gsk_curve_get_start_point (&r); + stroke_data->l0 = *gsk_curve_get_start_point (&l); + gsk_path_builder_move_to_point (stroke_data->right, &stroke_data->r0); + gsk_path_builder_move_to_point (stroke_data->left, &stroke_data->l0); + stroke_data->has_current_point = TRUE; } else { - pos = 0; - intersection_recurse (c1, c2, 0, 1, 0, 1, t, s, q, n, &pos); - return pos; - } -} - -typedef struct -{ - GskPathOperation op; - int n_pts; - graphene_point_t pts[4]; /* points of the curve */ - float w; - - graphene_point_t r[4]; /* offset to the right */ - graphene_point_t l[4]; /* offset to the left */ - graphene_point_t re[2]; /* intersection of adjacent r lines of this and next op */ - graphene_point_t le[2]; /* intersection of adjacent l lines of this and next op */ - float angle[2]; /* angles between tangents at the both ends */ -} PathOpData; - -static void -compute_offsets (PathOpData *op, - float d) -{ - graphene_vec2_t n1, n2, n3; - - normal_vector (&op->pts[0], &op->pts[1], &n1); - normal_vector (&op->pts[op->n_pts - 1], &op->pts[op->n_pts - 2], &n3); - - scale_point (&op->pts[0], &n1, d, &op->r[0]); - scale_point (&op->pts[0], &n1, -d, &op->l[0]); - - scale_point (&op->pts[op->n_pts - 1], &n3, -d, &op->r[op->n_pts - 1]); - scale_point (&op->pts[op->n_pts - 1], &n3, d, &op->l[op->n_pts - 1]); - - if (op->op == GSK_PATH_CURVE) - { - graphene_point_t m1, m2, m3, m4; - - /* Simply scale control points, a la Tiller and Hanson */ - - normal_vector (&op->pts[1], &op->pts[2], &n2); - - scale_point (&op->pts[1], &n1, d, &m1); - scale_point (&op->pts[2], &n3, -d, &m4); - - scale_point (&op->pts[1], &n2, d, &m2); - scale_point (&op->pts[2], &n2, d, &m3); - - if (!line_intersection (&op->r[0], &m1, &m2, &m3, &op->r[1])) - op->r[1] = m1; + float angle; + float t1, t2; + graphene_vec2_t tangent1, tangent2; + graphene_point_t p; - if (!line_intersection (&m2, &m3, &m4, &op->r[3], &op->r[2])) - op->r[2] = m4; + gsk_curve_get_end_tangent (&stroke_data->c, &tangent1); + gsk_curve_get_start_tangent (curve, &tangent2); + angle = angle_between (&tangent1, &tangent2); - scale_point (&op->pts[1], &n1, -d, &m1); - scale_point (&op->pts[2], &n3, d, &m4); - - scale_point (&op->pts[1], &n2, -d, &m2); - scale_point (&op->pts[2], &n2, -d, &m3); - - if (!line_intersection (&op->l[0], &m1, &m2, &m3, &op->l[1])) - op->l[1] = m1; - - if (!line_intersection (&m2, &m3, &m4, &op->l[3], &op->l[2])) - op->l[2] = m4; - } - else if (op->op == GSK_PATH_CONIC) - { - graphene_point_t m1, m2; - - scale_point (&op->pts[1], &n1, d, &m1); - scale_point (&op->pts[1], &n3, -d, &m2); - line_intersection (&op->r[0], &m1, &op->r[2], &m2, &op->r[1]); - - scale_point (&op->pts[1], &n1, -d, &m1); - scale_point (&op->pts[1], &n3, d, &m2); - line_intersection (&op->l[0], &m1, &op->l[2], &m2, &op->l[1]); - } - - op->re[0] = op->r[0]; - op->le[0] = op->l[0]; - op->re[1] = op->r[op->n_pts - 1]; - op->le[1] = op->l[op->n_pts - 1]; - op->angle[0] = op->angle[1] = 180.f; -} - -static int -find_smallest (float t[], int n) -{ - float d = t[0]; - int i0 = 0; - int i; - - for (i = 1; i < n; i++) - { - if (t[i] < d) + if (fabs (angle) < DEG_TO_RAD (5)) { - d = t[i]; - i0 = i; - } - } - - return i0; -} - -static int -find_largest (float t[], int n) -{ - float d = t[0]; - int i0 = 0; - int i; + /* Close enough to a straight line */ + gsk_path_builder_add_curve (stroke_data->right, &stroke_data->r); + gsk_path_builder_line_to_point (stroke_data->right, gsk_curve_get_start_point (&r)); - for (i = 1; i < n; i++) - { - if (t[i] > d) - { - d = t[i]; - i0 = i; + gsk_path_builder_add_curve (stroke_data->left, &stroke_data->l); + gsk_path_builder_line_to_point (stroke_data->left, gsk_curve_get_start_point (&l)); } - } - - return i0; -} - -static int -ray_intersection (const graphene_point_t *p1, - const graphene_point_t *p2, - const graphene_point_t *p3, - const graphene_point_t *p4, - graphene_point_t *p) -{ - float a1 = p2->x - p1->x; - float b1 = p2->y - p1->y; - - float a2 = p4->x - p3->x; - float b2 = p4->y - p3->y; - - float det = a1 * b2 - a2 * b1; - float tt, ss; - - if (det != 0) - { - tt = ((p3->x - p1->x) * b2 - (p3->y - p1->y) * a2) / det; - ss = - ((p3->y - p1->y) * a2 + (p3->x - p1->x) * b1) / det; - - g_print ("tt %f ss %f\n", tt, ss); - if (tt >= 0 && ss >= 0) + else if (angle > 0) { - p->x = p1->x + tt * (p2->x - p1->x); - p->y = p1->y + tt * (p2->y - p1->y); - - return 1; - } - } - - return 0; -} - -static void -compute_intersections (PathOpData *op1, - PathOpData *op2) -{ - op1->angle[1] = three_point_angle (&op1->pts[op1->n_pts - 2], - &op1->pts[op1->n_pts - 1], - &op2->pts[1]); + /* Right turn */ + if (gsk_curve_intersect (&stroke_data->r, &r, &t1, &t2, &p, 1) > 0) + { + GskCurve c1, c2; - op2->angle[0] = op1->angle[1]; + gsk_curve_split (&stroke_data->r, t1, &c1, &c2); + stroke_data->r = c1; + gsk_curve_split (&r, t2, &c1, &c2); + r = c2; - if (fabs (op1->angle[1] - 180.f) >= 1) - { - graphene_point_t p[9]; - float t[9]; - float s[9]; - int i, n; - Curve c1, c2, cl, cr; - - if (!line_intersection (&op1->r[op1->n_pts - 2], &op1->r[op1->n_pts - 1], - &op2->r[1], &op2->r[0], - &op1->re[1])) - midpoint (&op1->r[op1->n_pts - 1], &op2->r[0], &op1->re[1]); - - if (!line_intersection (&op1->l[op1->n_pts - 2], &op1->l[op1->n_pts - 1], - &op2->l[1], &op2->l[0], - &op1->le[1])) - midpoint (&op1->l[op1->n_pts - 1], &op2->l[0], &op1->le[1]); - - if (op1->angle[1] > 180.f) - { - g_print ("right turn\n"); - init_curve (&c1, op1->op, op1->r, op1->w); - init_curve (&c2, op2->op, op2->r, op2->w); - n = curve_intersection (&c1, &c2, t, s, p, 9); - if (n > 0) - { - i = find_largest (t, n); - curve_split (&c1, t[i], &cl, &cr); - for (i = 0; i < op1->n_pts; i++) - op1->r[i] = cl.p[i]; - op1->re[1] = op1->r[op1->n_pts - 1]; - i = find_smallest (s, n); - curve_split (&c2, s[i], &cl, &cr); - for (i = 0; i < op2->n_pts; i++) - op2->r[i] = cr.p[i]; + gsk_path_builder_add_curve (stroke_data->right, &stroke_data->r); } else { - g_print ("no intersection\n"); - midpoint (&op1->r[op1->n_pts - 1], &op2->r[0], &op1->re[1]); + gsk_path_builder_add_curve (stroke_data->right, &stroke_data->r); + gsk_path_builder_line_to_point (stroke_data->right, + gsk_curve_get_start_point (&r)); } - if (!ray_intersection (&op1->l[op1->n_pts - 2], &op1->l[op1->n_pts - 1], - &op2->l[1], &op2->l[0], - &op1->le[1])) - midpoint (&op1->l[op1->n_pts - 1], &op2->l[0], &op1->le[1]); + gsk_path_builder_add_curve (stroke_data->left, &stroke_data->l); + + add_line_join (stroke_data->left, + stroke_data->stroke, + gsk_curve_get_start_point (curve), + gsk_curve_get_end_point (&stroke_data->l), + &tangent1, + gsk_curve_get_start_point (&l), + &tangent2, + angle); } else { - g_print ("left turn\n"); - init_curve (&c1, op1->op, op1->l, op1->w); - init_curve (&c2, op2->op, op2->l, op2->w); - n = curve_intersection (&c1, &c2, t, s, p, 9); - if (n > 0) + /* Left turn */ + gsk_path_builder_add_curve (stroke_data->right, &stroke_data->r); + + add_line_join (stroke_data->right, + stroke_data->stroke, + gsk_curve_get_start_point (curve), + gsk_curve_get_end_point (&stroke_data->r), + &tangent1, + gsk_curve_get_start_point (&r), + &tangent2, + angle); + + if (gsk_curve_intersect (&stroke_data->l, &l, &t1, &t2, &p, 1) > 0) { - i = find_largest (t, n); - curve_split (&c1, t[i], &cl, &cr); - for (i = 0; i < op1->n_pts; i++) - op1->l[i] = cl.p[i]; - op1->le[1] = op1->l[op1->n_pts - 1]; - i = find_smallest (s, n); - curve_split (&c2, s[i], &cl, &cr); - for (i = 0; i < op2->n_pts; i++) - op2->l[i] = cr.p[i]; + GskCurve c1, c2; + + gsk_curve_split (&stroke_data->l, t1, &c1, &c2); + stroke_data->l = c1; + gsk_curve_split (&l, t2, &c1, &c2); + l = c2; + + gsk_path_builder_add_curve (stroke_data->left, &stroke_data->l); } else { - g_print ("no intersection\n"); - midpoint (&op1->l[op1->n_pts - 1], &op2->l[0], &op1->le[1]); + gsk_path_builder_add_curve (stroke_data->left, &stroke_data->l); + gsk_path_builder_line_to_point (stroke_data->left, + gsk_curve_get_start_point (&l)); } - - if (!ray_intersection (&op1->r[op1->n_pts - 2], &op1->r[op1->n_pts - 1], - &op2->r[1], &op2->r[0], - &op1->re[1])) - midpoint (&op1->r[op1->n_pts - 1], &op2->r[0], &op1->re[1]); } } - else - { - midpoint (&op1->r[op1->n_pts - 1], &op2->r[0], &op1->re[1]); - midpoint (&op1->l[op1->n_pts - 1], &op2->l[0], &op1->le[1]); - } - op2->re[0] = op1->re[1]; - op2->le[0] = op1->le[1]; + stroke_data->c = *curve; + stroke_data->r = r; + stroke_data->l = l; } -static PathOpData * -path_op_data_new (GskPathOperation op, - const graphene_point_t *pts, - float w, - gsize n_pts) -{ - PathOpData *d; - int i; - - d = g_new0 (PathOpData, 1); - d->op = op; - for (i = 0; i < n_pts; i++) - d->pts[i] = pts[i]; - d->n_pts = n_pts; - d->w = w; - -#if STROKE_DEBUG - /* detect uninitialized values */ - for (i = 0; i < n_pts; i++) - d->l[i].x = d->l[i].y = d->r[i].x = d->r[i].y = NAN; - for (i = 0; i < 2; i++) - { - d->le[i].x = d->le[i].y = d->re[i].x = d->re[i].y = NAN; - d->angle[i] = NAN; - } -#endif - - return d; -} - -typedef struct -{ - GskPathBuilder *builder; - GskStroke *stroke; - GList *ops; - graphene_point_t start; - gboolean has_start; - gboolean print; -} AddOpData; - -static void -subdivide_and_add (const graphene_point_t pts[4], - AddOpData *data, - int level) +static gboolean +cubic_is_simple (const GskCurve *curve) { - if (level == 0 || cubic_is_simple (pts)) - data->ops = g_list_prepend (data->ops, - path_op_data_new (GSK_PATH_CURVE, pts, 0, 4)); - else - { - graphene_point_t left[4]; - graphene_point_t right[4]; + const graphene_point_t *pts = curve->curve.points; + float a1, a2, s; + graphene_vec2_t t1, t2, t3; + graphene_vec2_t n1, n2; - split_bezier (pts, 4, 0.5, left, right); + get_tangent (&pts[0], &pts[1], &t1); + get_tangent (&pts[1], &pts[2], &t2); + get_tangent (&pts[2], &pts[3], &t3); + a1 = angle_between (&t1, &t2); + a2 = angle_between (&t2, &t3); - subdivide_and_add (left, data, level - 1); - subdivide_and_add (right, data, level - 1); - } -} + if ((a1 < 0 && a2 > 0) || (a1 > 0 && a2 < 0)) + return FALSE; -static void -subdivide_and_add_conic (const graphene_point_t pts[4], - float weight, - AddOpData *data) -{ - Curve c, c1, c2; + get_normal (&pts[0], &pts[1], &n1); + get_normal (&pts[2], &pts[3], &n2); - init_conic (&c, pts, weight); - curve_split (&c, 0.5, &c1, &c2); - data->ops = g_list_prepend (data->ops, path_op_data_new (GSK_PATH_CONIC, c1.p, c1.weight, 3)); - data->ops = g_list_prepend (data->ops, path_op_data_new (GSK_PATH_CONIC, c2.p, c2.weight, 3)); -} + s = graphene_vec2_dot (&n1, &n2); -#ifdef STROKE_DEBUG + if (fabs (acos (s)) >= M_PI / 3.f) + return FALSE; -static const char * -op_to_string (GskPathOperation op) -{ - const char *names[] = { "MOVE", "CLOSE", "LINE", "CURVE" }; - return names[op]; + return TRUE; } -enum { - STROKE_DEBUG_LEFT_CURVES = 1 << 0, - STROKE_DEBUG_RIGHT_CURVES = 1 << 1, - STROKE_DEBUG_LEFT_POINTS = 1 << 2, - STROKE_DEBUG_RIGHT_POINTS = 1 << 3, - STROKE_DEBUG_OFFSET_LINES = 1 << 4, - STROKE_DEBUG_LEFT_INTERSECTIONS = 1 << 5, - STROKE_DEBUG_RIGHT_INTERSECTIONS = 1 << 6, - STROKE_DEBUG_CURVE_POINTS = 1 << 7, - STROKE_DEBUG_CURVE_LINES = 1 << 8 -}; - static void -emit_debug (GskPathBuilder *builder, - GList *ops) +subdivide_and_add_curve (StrokeData *stroke_data, + const GskCurve *curve, + int level) { - GList *l; - PathOpData *op; - int i; - const GdkDebugKey debug_keys[] = { - { "left-curves", STROKE_DEBUG_LEFT_CURVES, "Show left offset curve" }, - { "right-curves", STROKE_DEBUG_RIGHT_CURVES, "Show right offset curve" }, - { "offset-curves", STROKE_DEBUG_LEFT_CURVES|STROKE_DEBUG_RIGHT_CURVES, "Show offset curves" }, - { "left-points", STROKE_DEBUG_LEFT_POINTS, "Show left offset points" }, - { "right-points", STROKE_DEBUG_RIGHT_POINTS, "Show right offset points" }, - { "offset-points", STROKE_DEBUG_LEFT_POINTS|STROKE_DEBUG_RIGHT_POINTS, "Show offset points" }, - { "offset-lines", STROKE_DEBUG_OFFSET_LINES, "Show offset lines" }, - { "left-intersections", STROKE_DEBUG_LEFT_INTERSECTIONS, "Show left intersection" }, - { "right-intersections", STROKE_DEBUG_RIGHT_INTERSECTIONS, "Show right intersections" }, - { "intersections", STROKE_DEBUG_LEFT_INTERSECTIONS|STROKE_DEBUG_RIGHT_INTERSECTIONS, "Show intersection" }, - { "curve-points", STROKE_DEBUG_CURVE_POINTS, "Show curve points" }, - { "curve-lines", STROKE_DEBUG_CURVE_LINES, "Show curve lines" }, - }; - static guint debug; - static int initialized = 0; - - if (!initialized) + if (level == 0 || cubic_is_simple (curve)) { - debug = gdk_parse_debug_var ("STROKE_DEBUG", debug_keys, G_N_ELEMENTS (debug_keys)); - initialized = 1; + add_curve (stroke_data, curve); } - - for (l = ops; l; l = l->next) + else { - op = l->data; + GskCurve c1, c2; - if (debug & STROKE_DEBUG_LEFT_CURVES) - { - gsk_path_builder_move_to (builder, op->l[0].x, op->l[0].y); - if (op->op == GSK_PATH_CURVE) - gsk_path_builder_curve_to (builder, - op->l[1].x, op->l[1].y, - op->l[2].x, op->l[2].y, - op->l[3].x, op->l[3].y); - else if (op->op == GSK_PATH_LINE) - gsk_path_builder_line_to (builder, op->l[1].x, op->l[1].y); - } - if (debug & STROKE_DEBUG_RIGHT_CURVES) - { - gsk_path_builder_move_to (builder, op->r[0].x, op->r[0].y); - if (op->op == GSK_PATH_CURVE) - gsk_path_builder_curve_to (builder, - op->r[1].x, op->r[1].y, - op->r[2].x, op->r[2].y, - op->r[3].x, op->r[3].y); - else if (op->op == GSK_PATH_LINE) - gsk_path_builder_line_to (builder, op->r[1].x, op->r[1].y); - } + gsk_curve_split (curve, 0.5, &c1, &c2); + subdivide_and_add_curve (stroke_data, &c1, level - 1); + subdivide_and_add_curve (stroke_data, &c2, level - 1); + } +} - for (i = 0; i < op->n_pts; i++) - { - if (debug & STROKE_DEBUG_LEFT_POINTS) - gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (op->l[i].x, op->l[i].y), 2); - if (debug & STROKE_DEBUG_RIGHT_POINTS) - gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (op->r[i].x, op->r[i].y), 2); - if (debug & STROKE_DEBUG_CURVE_POINTS) - gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (op->pts[i].x, op->pts[i].y), 2); - if (debug & STROKE_DEBUG_OFFSET_LINES) - { - gsk_path_builder_move_to (builder, op->r[i].x, op->r[i].y); - gsk_path_builder_line_to (builder, op->pts[i].x, op->pts[i].y); - gsk_path_builder_line_to (builder, op->l[i].x, op->l[i].y); - } - } - for (i = 0; i < 1; i++) - { - if (debug & STROKE_DEBUG_LEFT_INTERSECTIONS) - gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (op->le[i].x, op->le[i].y), 2); - if (debug & STROKE_DEBUG_RIGHT_INTERSECTIONS) - gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (op->re[i].x, op->re[i].y), 2); - } +static void +subdivide_and_add_conic (StrokeData *stroke_data, + const GskCurve *curve) +{ + GskCurve c1, c2; - if (debug & STROKE_DEBUG_CURVE_LINES) - { - gsk_path_builder_move_to (builder, op->pts[0].x, op->pts[0].y); - for (i = 1; i < op->n_pts; i++) - gsk_path_builder_line_to (builder, op->pts[i].x, op->pts[i].y); - } - } + /* FIXME make this adaptive */ + gsk_curve_split (curve, 0.5, &c1, &c2); + add_curve (stroke_data, &c1); + add_curve (stroke_data, &c2); } -#endif static void -add_cap (GskPathBuilder *builder, - const graphene_point_t *s, - const graphene_point_t *e, - GskStroke *stroke) +add_line_cap (GskPathBuilder *builder, + GskStroke *stroke, + const graphene_point_t *s, + const graphene_point_t *e) { - switch (stroke->line_cap) + switch (stroke->line_cap) { case GSK_LINE_CAP_BUTT: - gsk_path_builder_line_to (builder, e->x, e->y); + gsk_path_builder_line_to_point (builder, e); break; case GSK_LINE_CAP_ROUND: @@ -1432,7 +474,7 @@ add_cap (GskPathBuilder *builder, gsk_path_builder_line_to (builder, s->x + dx, s->y + dy); gsk_path_builder_line_to (builder, e->x + dx, e->y + dy); - gsk_path_builder_line_to (builder, e->x, e->y); + gsk_path_builder_line_to_point (builder, e); } break; @@ -1443,418 +485,79 @@ add_cap (GskPathBuilder *builder, } static void -stroke_ops (GList *ops, - graphene_point_t *start, - GskStroke *stroke, - GskPathBuilder *builder) +close_stroke (StrokeData *stroke_data) { - GList *l; - GList *last = NULL; - PathOpData *op; - PathOpData *op1; - gboolean draw_caps; + GskPath *path; - /* Compute offset start and end points */ - for (l = ops; l; l = l->next) - { - op = l->data; - last = l; - compute_offsets (op, stroke->line_width / 2); - } + gsk_path_builder_add_curve (stroke_data->right, &stroke_data->r); + gsk_path_builder_add_curve (stroke_data->left, &stroke_data->l); - /* Compute intersections */ - for (l = ops; l; l = l->next) - { - op = l->data; - last = l; - if (l->next != NULL) - { - op1 = l->next->data; - if (op1->op == GSK_PATH_CLOSE) - { - if (graphene_point_near (&op1->pts[0], &op1->pts[1], 0.0001)) - { - compute_intersections (op, (PathOpData *)ops->data); - op1->re[0] = op1->r[0] = op->re[op1->n_pts - 1]; - op1->le[0] = op1->l[0] = op->le[op1->n_pts - 1]; - - op1->re[1] = op1->r[1] = ((PathOpData *)ops->data)->re[0]; - op1->le[1] = op1->l[1] = ((PathOpData *)ops->data)->le[0]; - } - else - { - compute_intersections (op, op1); - compute_intersections (op1, (PathOpData *)ops->data); - } - } - else - { - compute_intersections (op, op1); - } - } - } + add_line_cap (stroke_data->right, + stroke_data->stroke, + gsk_curve_get_end_point (&stroke_data->r), + gsk_curve_get_end_point (&stroke_data->l)); -#if 0 - for (l = ops; l; l = l->next) - { - int i; + path = gsk_path_builder_free_to_path (stroke_data->left); + gsk_path_builder_add_reverse_path (stroke_data->right, path); + gsk_path_unref (path); - op = l->data; + add_line_cap (stroke_data->right, + stroke_data->stroke, + &stroke_data->l0, + &stroke_data->r0); - for (i = 0; i < op->n_pts; i++) - { - g_assert_true (!isnan (op->l[i].x) && !isnan (op->l[i].y)); - g_assert_true (!isnan (op->r[i].x) && !isnan (op->r[i].y)); - } - for (i = 0; i < 2; i++) - { - g_assert_true (!isnan (op->le[i].x) && !isnan (op->le[i].y)); - g_assert_true (!isnan (op->re[i].x) && !isnan (op->re[i].y)); - g_assert_true (!isnan (op->angle[i])); - } - } -#endif + gsk_path_builder_close (stroke_data->right); - draw_caps = TRUE; + path = gsk_path_builder_free_to_path (stroke_data->right); + gsk_path_builder_add_path (stroke_data->builder, path); + gsk_path_unref (path); - if (ops == NULL && stroke->line_cap == GSK_LINE_CAP_BUTT) - draw_caps = FALSE; /* isolated points have no butts */ - - /* Walk the ops for the right edge */ - last = NULL; - for (l = ops; l; l = l->next) - { - op = l->data; - last = l; - - if (l->prev == NULL) - gsk_path_builder_move_to (builder, op->re[0].x, op->re[0].y); - switch (op->op) - { - case GSK_PATH_MOVE: - g_assert_not_reached (); - break; - - case GSK_PATH_CLOSE: - draw_caps = FALSE; - if (graphene_point_near (&op->pts[0], &op->pts[1], 0.0001)) - break; - G_GNUC_FALLTHROUGH; - - case GSK_PATH_LINE: - if (op->angle[1] >= 181) - gsk_path_builder_line_to (builder, op->re[1].x, op->re[1].y); - else - gsk_path_builder_line_to (builder, op->r[1].x, op->r[1].y); - break; - - case GSK_PATH_CURVE: - if (op->angle[1] >= 181) - gsk_path_builder_curve_to (builder, op->r[1].x, op->r[1].y, - op->r[2].x, op->r[2].y, - op->re[1].x, op->re[1].y); - else - gsk_path_builder_curve_to (builder, op->r[1].x, op->r[1].y, - op->r[2].x, op->r[2].y, - op->r[3].x, op->r[3].y); - break; - - case GSK_PATH_CONIC: - if (op->angle[1] >= 181) - gsk_path_builder_conic_to (builder, op->r[1].x, op->r[1].y, - op->re[1].x, op->re[1].y, - op->w); - else - gsk_path_builder_conic_to (builder, op->r[1].x, op->r[1].y, - op->r[2].x, op->r[2].y, - op->w); - break; - - default: - g_assert_not_reached (); - } - - if (l->next || op->op == GSK_PATH_CLOSE) - { - op1 = l->next ? l->next->data : ops->data; - - if (op->angle[1] <= 179) /* avoid rounding */ - { - /* Deal with joins */ - switch (stroke->line_join) - { - case GSK_LINE_JOIN_MITER: - case GSK_LINE_JOIN_MITER_CLIP: - if (op->angle[1] != 0 && fabs (1 / sin (DEG_TO_RAD (op->angle[1]) / 2) <= stroke->miter_limit)) - { - gsk_path_builder_line_to (builder, op->re[1].x, op->re[1].y); - gsk_path_builder_line_to (builder, op1->r[0].x, op1->r[0].y); - } - else if (stroke->line_join == GSK_LINE_JOIN_MITER_CLIP) - { - graphene_point_t p, q, p1, p2; - graphene_vec2_t t, n; - - direction_vector (&op->pts[op->n_pts - 1], &op->re[1], &t); - scale_point (&op->pts[op->n_pts - 1], &t, stroke->line_width * stroke->miter_limit / 2, &p); - - normal_vector (&op->pts[op->n_pts - 1], &op->re[1], &n); - scale_point (&p, &n, 1, &q); - - line_intersection (&p, &q, &op->r[1], &op->re[1], &p1); - line_intersection (&p, &q, &op1->r[0], &op->re[1], &p2); - - gsk_path_builder_line_to (builder, p1.x, p1.y); - gsk_path_builder_line_to (builder, p2.x, p2.y); - gsk_path_builder_line_to (builder, op1->r[0].x, op1->r[0].y); - } - else - { - gsk_path_builder_line_to (builder, op1->r[0].x, op1->r[0].y); - } - break; - - case GSK_LINE_JOIN_BEVEL: - gsk_path_builder_line_to (builder, op1->r[0].x, op1->r[0].y); - break; - - case GSK_LINE_JOIN_ROUND: - gsk_path_builder_svg_arc_to (builder, - stroke->line_width / 2, stroke->line_width / 2, - 0, 0, 0, - op1->r[0].x, op1->r[0].y); - break; - - default: - g_assert_not_reached (); - } - } - } - } - - if (draw_caps) - { - /* Deal with the cap at the end */ - if (ops) - { - add_cap (builder, &op->re[1], &op->le[1], stroke); - } - else - { - graphene_point_t s, e; - - /* an isolated point, we have it in start */ - e = s = *start; - s.y += stroke->line_width / 2; - e.y -= stroke->line_width / 2; - - gsk_path_builder_move_to (builder, s.x, s.y); - add_cap (builder, &s, &e, stroke); - } - } - else if (ops) - { - gsk_path_builder_close (builder); - - /* Start the left contour */ - op = last->data; - if (op->angle[0] <= 179) - gsk_path_builder_move_to (builder, op->le[1].x, op->le[1].y); - else - gsk_path_builder_move_to (builder, op->l[1].x, op->l[1].y); - } - - /* Walk the ops backwards for the left edge */ - for (l = last; l; l = l->prev) - { - op = l->data; - - switch (op->op) - { - case GSK_PATH_MOVE: - g_assert_not_reached (); - break; - - case GSK_PATH_CLOSE: - if (graphene_point_near (&op->pts[0], &op->pts[1], 0.0001)) - break; - G_GNUC_FALLTHROUGH; - - case GSK_PATH_LINE: - if (op->angle[0] <= 179) - gsk_path_builder_line_to (builder, op->le[0].x, op->le[0].y); - else - gsk_path_builder_line_to (builder, op->l[0].x, op->l[0].y); - break; - - case GSK_PATH_CURVE: - if (op->angle[0] <= 179) - gsk_path_builder_curve_to (builder, op->l[2].x, op->l[2].y, - op->l[1].x, op->l[1].y, - op->le[0].x, op->le[0].y); - else - gsk_path_builder_curve_to (builder, op->l[2].x, op->l[2].y, - op->l[1].x, op->l[1].y, - op->l[0].x, op->l[0].y); - break; - - case GSK_PATH_CONIC: - if (op->angle[0] <= 179) - gsk_path_builder_conic_to (builder, op->l[1].x, op->l[1].y, - op->le[0].x, op->le[0].y, - op->w); - else - gsk_path_builder_conic_to (builder, op->l[1].x, op->l[1].y, - op->l[0].x, op->l[0].y, - op->w); - break; - - default: - g_assert_not_reached (); - } - - if (l->prev || ((PathOpData *)last->data)->op == GSK_PATH_CLOSE) - { - op1 = l->prev ? l->prev->data : last->data; - - if (op->angle[0] >= 181) - { - /* Deal with joins */ - switch (stroke->line_join) - { - case GSK_LINE_JOIN_MITER: - case GSK_LINE_JOIN_MITER_CLIP: - if (op->angle[0] != 0 && fabs (1 / sin (DEG_TO_RAD (op->angle[0]) / 2) <= stroke->miter_limit)) - { - gsk_path_builder_line_to (builder, op->le[0].x, op->le[0].y); - gsk_path_builder_line_to (builder, op1->l[op1->n_pts - 1].x, op1->l[op1->n_pts - 1].y); - } - else if (stroke->line_join == GSK_LINE_JOIN_MITER_CLIP) - { - graphene_point_t p, q, p1, p2; - graphene_vec2_t t, n; - - direction_vector (&op->pts[0], &op->le[0], &t); - scale_point (&op->pts[0], &t, stroke->line_width * stroke->miter_limit / 2, &p); - - normal_vector (&op->pts[0], &op->le[0], &n); - scale_point (&p, &n, 1, &q); - - line_intersection (&p, &q, &op->l[0], &op->le[0], &p1); - line_intersection (&p, &q, &op1->l[op1->n_pts - 1], &op->le[0], &p2); - - gsk_path_builder_line_to (builder, p1.x, p1.y); - gsk_path_builder_line_to (builder, p2.x, p2.y); - gsk_path_builder_line_to (builder, op1->l[op1->n_pts - 1].x, op1->l[op1->n_pts - 1].y); - } - else - { - gsk_path_builder_line_to (builder, op1->l[op1->n_pts - 1].x, op1->l[op1->n_pts - 1].y); - } - break; - - case GSK_LINE_JOIN_BEVEL: - gsk_path_builder_line_to (builder, op1->l[op1->n_pts - 1].x, op1->l[op1->n_pts - 1].y); - break; - - case GSK_LINE_JOIN_ROUND: - gsk_path_builder_svg_arc_to (builder, - stroke->line_width / 2, stroke->line_width / 2, - 0, 0, 0, - op1->l[op1->n_pts - 1].x, op1->l[op1->n_pts - 1].y); - break; - - default: - g_assert_not_reached (); - } - } - } - } - - if (draw_caps) - { - /* Deal with the cap at the beginning */ - - if (ops) - { - op = ops->data; - add_cap (builder, &op->le[0], &op->re[0], stroke); - } - else - { - graphene_point_t s, e; - - /* an isolated point, we have it in start */ - e = s = *start; - s.y -= stroke->line_width / 2; - e.y += stroke->line_width / 2; - add_cap (builder, &s, &e, stroke); - } - } - - gsk_path_builder_close (builder); - -#ifdef STROKE_DEBUG - emit_debug (builder, ops); -#endif + stroke_data->left = NULL; + stroke_data->right = NULL; } static gboolean -add_op_to_list (GskPathOperation op, - const graphene_point_t *pts, - gsize n_pts, - float weight, - gpointer user_data) +stroke_op (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer user_data) { - AddOpData *data = user_data; + StrokeData *stroke_data = user_data; + GskCurve curve; switch (op) { case GSK_PATH_MOVE: - if (data->has_start) + if (stroke_data->has_current_point) { - if (data->print) g_print ("==\n"); - data->ops = g_list_reverse (data->ops); - stroke_ops (data->ops, &data->start, data->stroke, data->builder); - g_list_free_full (data->ops, g_free); - data->ops = NULL; - data->has_start = FALSE; + close_stroke (stroke_data); + + stroke_data->right = gsk_path_builder_new (); + stroke_data->left = gsk_path_builder_new (); } - if (data->print) g_print ("M %f %f\n", pts[0].x, pts[0].y); - data->start = pts[0]; - data->has_start = TRUE; + stroke_data->has_current_point = FALSE; break; case GSK_PATH_CLOSE: - if (data->print) g_print ("Z\n"); - data->ops = g_list_prepend (data->ops, path_op_data_new (op, pts, weight, n_pts)); - data->ops = g_list_reverse (data->ops); - if (data->print) g_print ("==\n"); - stroke_ops (data->ops, &data->start, data->stroke, data->builder); - g_list_free_full (data->ops, g_free); - data->ops = NULL; - data->has_start = FALSE; + gsk_path_builder_close (stroke_data->right); + gsk_path_builder_close (stroke_data->left); + stroke_data->has_current_point = FALSE; break; case GSK_PATH_LINE: - if (data->print) g_print ("L %f %f\n", pts[1].x, pts[1].y); - data->ops = g_list_prepend (data->ops, path_op_data_new (op, pts, weight, n_pts)); + gsk_curve_init_foreach (&curve, op, pts, n_pts, weight); + add_curve (stroke_data, &curve); break; case GSK_PATH_CURVE: - if (data->print) g_print ("C %f %f, %f %f, %f %f\n", - pts[1].x, pts[1].y, - pts[2].x, pts[2].y, - pts[3].x, pts[3].y); - subdivide_and_add (pts, data, 2); + gsk_curve_init_foreach (&curve, op, pts, n_pts, weight); + subdivide_and_add_curve (stroke_data, &curve, 2); break; case GSK_PATH_CONIC: - if (data->print) g_print ("O %f %f, %f %f, %f\n", - pts[1].x, pts[1].y, - pts[3].x, pts[3].y, - pts[2].x); - subdivide_and_add_conic (pts, weight, data); + gsk_curve_init_foreach (&curve, op, pts, n_pts, weight); + subdivide_and_add_conic (stroke_data, &curve); break; default: @@ -1869,30 +572,19 @@ gsk_contour_default_add_stroke (const GskContour *contour, GskPathBuilder *builder, GskStroke *stroke) { - AddOpData data; - - data.builder = builder; - data.stroke = stroke; - data.ops = NULL; - data.has_start = FALSE; - data.print = FALSE; + StrokeData stroke_data; - g_print ("***\n"); + stroke_data.builder = builder; + stroke_data.stroke = stroke; + stroke_data.left = gsk_path_builder_new (); + stroke_data.right = gsk_path_builder_new (); + stroke_data.has_current_point = FALSE; if (stroke->dash_length <= 0) - gsk_contour_foreach (contour, GSK_PATH_TOLERANCE_DEFAULT, add_op_to_list, &data); + gsk_contour_foreach (contour, GSK_PATH_TOLERANCE_DEFAULT, stroke_op, &stroke_data); else - { - data.print = TRUE; - g_print ("====\n"); - gsk_contour_dash (contour, stroke, GSK_PATH_TOLERANCE_DEFAULT, add_op_to_list, &data); - } + gsk_contour_dash (contour, stroke, GSK_PATH_TOLERANCE_DEFAULT, stroke_op, &stroke_data); - if (data.has_start) - { - data.ops = g_list_reverse (data.ops); - if (data.print) g_print ("==\n"); - stroke_ops (data.ops, &data.start, stroke, builder); - g_list_free_full (data.ops, g_free); - } + if (stroke_data.has_current_point) + close_stroke (&stroke_data); } |