[gtk/wip/matthiasc/lottie-stroke: 19/24] Add gsk_path_to_stroke




commit 16c82c6876fe954e76dbf7d16bb4890bd29a5570
Author: Matthias Clasen <mclasen redhat com>
Date:   Fri Nov 27 14:05:25 2020 -0500

    Add gsk_path_to_stroke
    
    Implement gsk_path_to_stroke, which takes a path and stroke
    parameters, and returns a new path for the outline the would
    be produced by stroking the path with these parameters.
    
    There are still some stability issues with sharp joins, and
    subdividing is not done yet.

 gsk/gskpath.c | 1123 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gsk/gskpath.h |    9 +
 2 files changed, 1132 insertions(+)
---
diff --git a/gsk/gskpath.c b/gsk/gskpath.c
index c2f476ce22..0169c81589 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -24,6 +24,8 @@
 #include "gskpathbuilder.h"
 #include "gsksplineprivate.h"
 
+#include "gskstrokeprivate.h"
+
 /**
  * SECTION:gskpath
  * @Title: Path
@@ -2853,3 +2855,1124 @@ error:
 
   return NULL;
 }
+
+/* path-to-stroke */
+
+/* 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)
+{
+  graphene_vec2_init (t, p1->x - p0->x, p1->y - p0->y);
+  graphene_vec2_normalize (t, t);
+}
+
+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);
+}
+
+/* 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 gboolean
+acceptable (float t)
+{
+  return 0 <= t && t <= 1;
+}
+
+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])
+{
+  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;
+}
+
+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;
+
+  a = 3 * (pd - 3*pc + 3*pb - pa);
+  b = 6 * (pc - 2*pb + pa);
+  c = 3 * (pb - pa);
+
+  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;
+    }
+
+  return n_roots;
+}
+
+static int
+get_cubic_inflections (float pa,
+                       float pb,
+                       float pc,
+                       float pd,
+                       float roots[1])
+{
+  float a, b;
+  float t;
+  int n_roots = 0;
+
+  a = 3 * (pd - 3*pc + 3*pb - pa);
+  b = 6 * (pc - 2*pb + pa);
+
+  if (fabs (a) > 0.0001)
+    {
+      t = -b / (2 * a);
+      if (acceptable (t))
+        roots[n_roots++] = t;
+    }
+
+  return n_roots;
+}
+
+/* Compute q = p + d * n */
+static void
+scale_point (const graphene_point_t *p,
+             const graphene_vec2_t  *n,
+             float                   d,
+             graphene_point_t       *q)
+{
+  q->x = p->x + d * graphene_vec2_get_x (n);
+  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);
+}
+
+/* Place intersections between the line and the curve in q.
+ * 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],
+                         graphene_point_t        q[3])
+{
+  graphene_point_t p[4];
+  float t[3];
+  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);
+  for (i = 0; i < n; i++)
+    get_cubic (pts, t[i], &q[i]);
+
+  return n;
+}
+
+static void
+get_curve_bounds (const graphene_point_t  pts[4],
+                  graphene_rect_t        *bounds)
+{
+  graphene_point_t p;
+  float t[2];
+  int n, i;
+
+  get_cubic (pts, 0, &p);
+  graphene_rect_init (bounds, p.x, p.y, 0, 0);
+
+  get_cubic (pts, 1, &p);
+  graphene_rect_expand (bounds, &p, bounds);
+
+  n = get_cubic_extrema (pts[0].x, pts[1].x, pts[2].x, pts[3].x, t);
+  for (i = 0; i < n; i++)
+    {
+      get_cubic (pts, t[i], &p);
+      graphene_rect_expand (bounds, &p, bounds);
+    }
+
+  n = get_cubic_extrema (pts[0].y, pts[1].y, pts[2].y, pts[3].y, t);
+  for (i = 0; i < n; i++)
+    {
+      get_cubic (pts, t[i], &p);
+      graphene_rect_expand (bounds, &p, bounds);
+    }
+}
+
+static void
+curve_intersection_recurse (const graphene_point_t  pts1[4],
+                            const graphene_point_t  pts2[4],
+                            graphene_point_t        q[9],
+                            int                    *pos)
+{
+  graphene_rect_t b1, b2;
+
+  graphene_point_t p11[4];
+  graphene_point_t p12[4];
+  graphene_point_t p21[4];
+  graphene_point_t p22[4];
+
+  if (*pos == 9)
+    return;
+
+  get_curve_bounds (pts1, &b1);
+  get_curve_bounds (pts2, &b2);
+
+  if (!graphene_rect_intersection (&b1, &b2, NULL))
+    return;
+
+  if (b1.size.width < 0.1 && b1.size.height < 0.1 &&
+      b2.size.width < 0.1 && b2.size.height < 0.1)
+    {
+      q[*pos] = pts1[0];
+      (*pos)++;
+      return;
+    }
+
+  split_bezier (pts1, 4, 0.5, p11, p12);
+  split_bezier (pts2, 4, 0.5, p21, p22);
+
+  curve_intersection_recurse (p11, p21, q, pos);
+  curve_intersection_recurse (p11, p22, q, pos);
+  curve_intersection_recurse (p12, p21, q, pos);
+  curve_intersection_recurse (p12, p22, q, pos);
+}
+
+/* Place intersections between the curves in q.
+ * Return the number of intersections found (0 to 9).
+ */
+static int
+curve_intersection (const graphene_point_t  pts1[4],
+                    const graphene_point_t  pts2[4],
+                    graphene_point_t        q[9])
+{
+  int pos = 0;
+
+  curve_intersection_recurse (pts1, pts2, q, &pos);
+
+  return pos;
+}
+
+
+typedef struct
+{
+  GskPathOperation op;
+  int n_pts;
+  graphene_point_t pts[4]; /* points of the curve */
+
+  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;
+
+
+#if 0
+static const char *
+op_to_string (GskPathOperation op)
+{
+  const char *names[] = { "MOVE", "CLOSE", "LINE", "CURVE" };
+  return names[op];
+}
+
+static void
+assert_right_angle (const graphene_point_t *a,
+                    const graphene_point_t *b,
+                    const graphene_point_t *c)
+{
+  graphene_vec2_t u, v;
+
+  graphene_vec2_init (&u, a->x - b->x, a->y - b->y);
+  graphene_vec2_init (&v, a->x - c->x, a->y - c->y);
+
+  g_assert_cmpfloat_with_epsilon (graphene_vec2_dot (&u, &v), 0, 0.002);
+}
+
+static void
+assert_same_distance (const graphene_point_t *a,
+                      const graphene_point_t *b,
+                      const graphene_point_t *c)
+{
+  g_assert_cmpfloat_with_epsilon (graphene_point_distance (a, b, NULL, NULL),
+                                  graphene_point_distance (b, c, NULL, NULL),
+                                  0.001);
+}
+#endif
+
+static void
+compute_offsets (PathOpData *op,
+                 float       d)
+{
+  graphene_vec2_t n1, n2, n3;
+
+  if (op->op != GSK_PATH_MOVE)
+    {
+      normal_vector (&op->pts[0], &op->pts[1], &n1);
+      scale_point (&op->pts[0], &n1, d, &op->r[0]);
+      scale_point (&op->pts[0], &n1, -d, &op->l[0]);
+
+      normal_vector (&op->pts[op->n_pts - 1], &op->pts[op->n_pts - 2], &n3);
+      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);
+
+      /* FIXME: handle the parallel case */
+      line_intersection (&op->r[0], &m1, &m2, &m3, &op->r[1]);
+      line_intersection (&m2, &m3, &m4, &op->r[3], &op->r[2]);
+
+      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);
+
+      line_intersection (&op->l[0], &m1, &m2, &m3, &op->l[1]);
+      line_intersection (&m2, &m3, &m4, &op->l[3], &op->l[2]);
+    }
+
+  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];
+
+#if 0
+  assert_right_angle (&op->pts[0], &op->pts[1], &op->r[0]);
+  assert_right_angle (&op->pts[0], &op->pts[1], &op->l[0]);
+  assert_right_angle (&op->pts[op->n_pts - 1], &op->pts[op->n_pts - 2], &op->r[op->n_pts - 1]);
+  assert_right_angle (&op->pts[op->n_pts - 1], &op->pts[op->n_pts - 2], &op->l[op->n_pts - 1]);
+#endif
+}
+
+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]);
+
+  op2->angle[0] = op1->angle[1];
+
+  if (fabs (op1->angle[1] - 180.f) >= 1)
+    {
+      line_intersection (&op1->r[op1->n_pts - 2], &op1->r[op1->n_pts - 1],
+                         &op2->r[0], &op2->r[1],
+                         &op1->re[1]);
+      line_intersection (&op1->l[op1->n_pts - 2], &op1->l[op1->n_pts - 1],
+                         &op2->l[0], &op2->l[1],
+                         &op1->le[1]);
+
+      if (op1->n_pts == 2 && op2->n_pts == 4)
+        {
+          graphene_point_t p[3];
+          int n;
+
+          if (op1->angle[1] > 180.f)
+            {
+              n = line_curve_intersection (&op1->r[0], &op1->r[1], op2->r, p);
+              if (n > 0)
+                op1->re[1] = p[0];
+            }
+          else
+            {
+              n = line_curve_intersection (&op1->l[0], &op1->l[1], op2->l, p);
+              if (n > 0)
+                op1->le[1] = p[0];
+            }
+        }
+      else if (op1->n_pts == 4 && op2->n_pts == 2)
+        {
+          graphene_point_t p[3];
+          int n;
+
+          if (op1->angle[1] > 180.f)
+            {
+              n = line_curve_intersection (&op2->r[0], &op2->r[1], op1->r, p);
+              if (n > 0)
+                op1->re[1] = p[0];
+            }
+          else
+            {
+              n = line_curve_intersection (&op2->l[0], &op2->l[1], op1->l, p);
+              if (n > 0)
+                op1->le[1] = p[0];
+            }
+        }
+      else if (op1->n_pts == 4 && op2->n_pts == 4)
+        {
+          graphene_point_t p[9];
+          int n;
+
+          if (op1->angle[1] > 180.f)
+            {
+              n = curve_intersection (op1->r, op2->r, p);
+              if (n > 0)
+                op1->re[1] = p[0];
+            }
+          else
+            {
+              n = curve_intersection (op1->l, op2->l, p);
+              if (n > 0)
+                op1->le[1] = p[0];
+            }
+        }
+    }
+  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];
+
+#if 0
+  g_assert_true (graphene_point_near (&op1->re[1], &op2->re[0], 0.001));
+  g_assert_true (graphene_point_near (&op1->le[1], &op2->le[0], 0.001));
+  assert_same_distance (&op1->r[op1->n_pts - 1], &op1->re[1], &op2->r[0]);
+  assert_same_distance (&op1->l[op1->n_pts - 1], &op1->le[1], &op2->l[0]);
+#endif
+}
+
+static PathOpData *
+path_op_data_new (GskPathOperation        op,
+                  const graphene_point_t *pts,
+                  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;
+
+  return d;
+}
+
+typedef struct
+{
+  GList *ops;
+  GskStroke *stroke;
+} AddOpData;
+
+static gboolean
+add_op_to_list (GskPathOperation        op,
+                const graphene_point_t *pts,
+                gsize                   n_pts,
+                float                   weight,
+                gpointer                user_data)
+{
+  AddOpData *data = user_data;
+
+  switch (op)
+    {
+    case GSK_PATH_MOVE:
+      /* We have no use for the initial move, so toss it */
+      break;
+
+    case GSK_PATH_CLOSE:
+    case GSK_PATH_LINE:
+      data->ops = g_list_prepend (data->ops, path_op_data_new (op, pts, n_pts));
+      break;
+
+    case GSK_PATH_CURVE:
+      data->ops = g_list_prepend (data->ops, path_op_data_new (GSK_PATH_CURVE, pts, 4));
+      break;
+
+    case GSK_PATH_CONIC:
+      g_warning ("FIXME: support conics in the stroker");
+      g_assert_not_reached ();
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+
+  return TRUE;
+}
+
+static GList *
+contour_to_ops (GskContour *contour,
+                GskStroke  *stroke)
+{
+  AddOpData data;
+
+  data.ops = NULL;
+  data.stroke = stroke;
+
+  gsk_contour_foreach (contour, GSK_PATH_TOLERANCE_DEFAULT, add_op_to_list, &data);
+
+  return g_list_reverse (data.ops);
+}
+
+static void
+gsk_contour_to_stroke (GskContour     *contour,
+                       GskStroke      *stroke,
+                       GskPathBuilder *builder)
+{
+  GList *ops, *l;
+  GList *last = NULL;
+  PathOpData *op;
+  PathOpData *op1;
+
+  ops = contour_to_ops (contour, stroke);
+
+  /* Compute offset start and end points */
+  for (l = ops; l; l = l->next)
+    {
+      op = l->data;
+      compute_offsets (op, stroke->line_width / 2);
+    }
+
+  /* Compute intersections */
+  for (l = ops; l; l = l->next)
+    {
+      op = l->data;
+      if (l->next != NULL)
+        {
+          op1 = l->next->data;
+          compute_intersections (op, op1);
+        }
+      else
+        last = l;
+    }
+
+  if (contour->klass->get_flags (contour) & GSK_PATH_CLOSED && last)
+    {
+      op = last->data;
+      op1 = ops->data;
+      compute_intersections (op, op1);
+    }
+
+  /* Walk the ops for the right edge */
+  last = NULL;
+  for (l = ops; l; l = l->next)
+    {
+      op = l->data;
+
+      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:
+          last = l;
+          break;
+
+        case GSK_PATH_LINE:
+          if (op->angle[1] >= 180)
+            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] >= 180)
+            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:
+          g_warning ("FIXME: support conics in the stroker");
+          g_assert_not_reached ();
+          break;
+
+        default:
+          g_assert_not_reached ();
+        }
+
+      if (l->next || (contour->klass->get_flags (contour) & GSK_PATH_CLOSED))
+        {
+          op1 = l->next ? l->next->data : ops->data;
+
+          if (op->angle[1] < 180)
+            {
+              /* 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 (l->next == NULL)
+        last = l;
+    }
+
+  if (! (contour->klass->get_flags (contour) & GSK_PATH_CLOSED))
+    {
+      /* Deal with the cap at the end */
+      op = last->data;
+      switch (stroke->line_cap)
+        {
+        case GSK_LINE_CAP_BUTT:
+          gsk_path_builder_line_to (builder, op->le[1].x, op->le[1].y);
+          break;
+
+        case GSK_LINE_CAP_ROUND:
+          gsk_path_builder_svg_arc_to (builder,
+                                       stroke->line_width / 2, stroke->line_width / 2,
+                                       0, 1, 0,
+                                       op->le[1].x, op->le[1].y);
+          break;
+
+        case GSK_LINE_CAP_SQUARE:
+          {
+            float dx = op->re[1].y - op->pts[op->n_pts - 1].y;
+            float dy = - op->re[1].x + op->pts[op->n_pts - 1].x;
+
+            gsk_path_builder_line_to (builder, op->re[1].x + dx, op->re[1].y + dy);
+            gsk_path_builder_line_to (builder, op->le[1].x + dx, op->le[1].y + dy);
+            gsk_path_builder_line_to (builder, op->le[1].x, op->le[1].y);
+          }
+          break;
+
+        default:
+          g_assert_not_reached ();
+        }
+    }
+  else
+    {
+      gsk_path_builder_close (builder);
+
+      /* Start the left contour */
+      op = last->data;
+      if (op->angle[0] <= 180)
+        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:
+        case GSK_PATH_LINE:
+          if (op->angle[0] <= 180)
+            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] <= 180)
+            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:
+          g_warning ("FIXME: support conics in the stroker");
+          g_assert_not_reached ();
+          break;
+
+        default:
+          g_assert_not_reached ();
+        }
+
+      if (l->prev || (contour->klass->get_flags (contour) & GSK_PATH_CLOSED))
+        {
+          op1 = l->prev ? l->prev->data : last->data;
+
+          if (op->angle[0] > 180)
+            {
+              /* 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[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 (! (contour->klass->get_flags (contour) & GSK_PATH_CLOSED))
+    {
+      /* Deal with the cap at the beginning */
+      op = ops->data;
+      switch (stroke->line_cap)
+        {
+        case GSK_LINE_CAP_BUTT:
+          gsk_path_builder_line_to (builder, op->re[0].x, op->re[0].y);
+          break;
+
+        case GSK_LINE_CAP_ROUND:
+          gsk_path_builder_svg_arc_to (builder,
+                                       stroke->line_width / 2, stroke->line_width / 2,
+                                       0, 1, 0,
+                                       op->re[0].x, op->re[0].y);
+          break;
+
+        case GSK_LINE_CAP_SQUARE:
+          {
+            float dx = op->le[0].y - op->pts[0].y;
+            float dy = - op->le[0].x + op->pts[0].x;
+
+            gsk_path_builder_line_to (builder, op->le[0].x + dx, op->le[0].y + dy);
+            gsk_path_builder_line_to (builder, op->re[0].x + dx, op->re[0].y + dy);
+            gsk_path_builder_line_to (builder, op->re[0].x, op->re[0].y);
+          }
+          break;
+
+        default:
+          g_assert_not_reached ();
+          break;
+        }
+    }
+
+  gsk_path_builder_close (builder);
+
+  g_list_free_full (ops, g_free);
+}
+
+/**
+ * gsk_path_to_stroke:
+ * @path: a #GskPath
+ * @stroke: stroke parameters
+ *
+ * Create a new path that follows the outline of the area
+ * that would be affected by stroking along @path with
+ * the given stroke parameters.
+ *
+ * Returns: a new #GskPath
+ */
+GskPath *
+gsk_path_to_stroke (GskPath   *path,
+                    GskStroke *stroke)
+{
+  GskPathBuilder *builder;
+  int i;
+
+  builder = gsk_path_builder_new ();
+
+  for (i = 0; i < path->n_contours; i++)
+    gsk_contour_to_stroke (path->contours[i], stroke, builder);
+
+  return gsk_path_builder_free_to_path (builder);
+}
diff --git a/gsk/gskpath.h b/gsk/gskpath.h
index 27b01d4a44..033ec77bd1 100644
--- a/gsk/gskpath.h
+++ b/gsk/gskpath.h
@@ -85,6 +85,15 @@ gboolean                gsk_path_foreach                        (GskPath
                                                                  GskPathForeachFunc    func,
                                                                  gpointer              user_data);
 
+GDK_AVAILABLE_IN_ALL
+gboolean                gsk_path_in_fill                        (GskPath              *path,
+                                                                 graphene_point_t     *point,
+                                                                 GskFillRule           fill_rule);
+
+GDK_AVAILABLE_IN_ALL
+GskPath *              gsk_path_to_stroke                       (GskPath              *path,
+                                                                 GskStroke            *stroke);
+
 G_END_DECLS
 
 #endif /* __GSK_PATH_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]