[gtk/wip/otte/lottie: 39/43] WIP: path: Add conic curves




commit 43361958635cb6edea2aecfc907277ae0fb19d25
Author: Benjamin Otte <otte redhat com>
Date:   Sat Nov 28 07:24:05 2020 +0100

    WIP: path: Add conic curves
    
    So far this just adds the API, if you use it, you'll get lots of
    g_warnings().

 demos/gtk-demo/path_text.c           |  12 ++-
 docs/reference/gsk/gsk4-sections.txt |   2 +
 gsk/gskcontour.c                     |  88 ++++++++++++++--
 gsk/gskenums.h                       |   4 +
 gsk/gskpath.c                        |  58 +++++++++--
 gsk/gskpath.h                        |   9 +-
 gsk/gskpathbuilder.c                 |  72 +++++++++++++
 gsk/gskpathbuilder.h                 |  14 +++
 gsk/gskspline.c                      | 197 ++++++++++++++++++++++++++++++++---
 gsk/gsksplineprivate.h               |  13 +++
 testsuite/gsk/path.c                 |  23 ++++
 11 files changed, 459 insertions(+), 33 deletions(-)
---
diff --git a/demos/gtk-demo/path_text.c b/demos/gtk-demo/path_text.c
index cb006eb34d..905fd148a4 100644
--- a/demos/gtk-demo/path_text.c
+++ b/demos/gtk-demo/path_text.c
@@ -103,6 +103,7 @@ static gboolean
 gtk_path_transform_op (GskPathOperation        op,
                        const graphene_point_t *pts,
                        gsize                   n_pts,
+                       float                   weight,
                        gpointer                data)
 {
   GtkPathTransform *transform = data;
@@ -135,6 +136,15 @@ gtk_path_transform_op (GskPathOperation        op,
       }
       break;
 
+    case GSK_PATH_CONIC:
+      {
+        graphene_point_t res[2];
+        gtk_path_transform_point (transform->measure, &pts[1], transform->scale, &res[0]);
+        gtk_path_transform_point (transform->measure, &pts[2], transform->scale, &res[1]);
+        gsk_path_builder_conic_to (transform->builder, res[0].x, res[0].y, res[1].x, res[1].y, weight);
+      }
+      break;
+
     case GSK_PATH_CLOSE:
       gsk_path_builder_close (transform->builder);
       break;
@@ -160,7 +170,7 @@ gtk_path_transform (GskPathMeasure *measure,
   else
     transform.scale = 1.0f;
 
-  gsk_path_foreach (path, GSK_PATH_FOREACH_ALLOW_CURVES, gtk_path_transform_op, &transform);
+  gsk_path_foreach (path, GSK_PATH_FOREACH_ALLOW_CURVE, gtk_path_transform_op, &transform);
 
   return gsk_path_builder_free_to_path (transform.builder);
 }
diff --git a/docs/reference/gsk/gsk4-sections.txt b/docs/reference/gsk/gsk4-sections.txt
index bedbe6aa5d..4fe09d9edb 100644
--- a/docs/reference/gsk/gsk4-sections.txt
+++ b/docs/reference/gsk/gsk4-sections.txt
@@ -312,6 +312,8 @@ gsk_path_builder_line_to
 gsk_path_builder_rel_line_to
 gsk_path_builder_curve_to
 gsk_path_builder_rel_curve_to
+gsk_path_builder_conic_to
+gsk_path_builder_rel_conic_to
 gsk_path_builder_close
 <SUBSECTION Private>
 GSK_TYPE_PATH_BUILDER
diff --git a/gsk/gskcontour.c b/gsk/gskcontour.c
index 6c4f2228ff..76595e1145 100644
--- a/gsk/gskcontour.c
+++ b/gsk/gskcontour.c
@@ -217,11 +217,11 @@ gsk_rect_contour_foreach (const GskContour   *contour,
     GRAPHENE_POINT_INIT (self->x,               self->y)
   };
 
-  return func (GSK_PATH_MOVE, &pts[0], 1, user_data)
-      && func (GSK_PATH_LINE, &pts[0], 2, user_data)
-      && func (GSK_PATH_LINE, &pts[1], 2, user_data)
-      && func (GSK_PATH_LINE, &pts[2], 2, user_data)
-      && func (GSK_PATH_CLOSE, &pts[3], 2, user_data);
+  return func (GSK_PATH_MOVE, &pts[0], 1, 0, user_data)
+      && func (GSK_PATH_LINE, &pts[0], 2, 0, user_data)
+      && func (GSK_PATH_LINE, &pts[1], 2, 0, user_data)
+      && func (GSK_PATH_LINE, &pts[2], 2, 0, user_data)
+      && func (GSK_PATH_CLOSE, &pts[3], 2, 0, user_data);
 }
 
 static gpointer
@@ -601,7 +601,7 @@ gsk_circle_contour_curve (const graphene_point_t curve[4],
 {
   ForeachWrapper *wrapper = data;
 
-  return wrapper->func (GSK_PATH_CURVE, curve, 4, wrapper->user_data);
+  return wrapper->func (GSK_PATH_CURVE, curve, 4, 0, wrapper->user_data);
 }
 
 static gboolean
@@ -613,7 +613,7 @@ gsk_circle_contour_foreach (const GskContour   *contour,
   const GskCircleContour *self = (const GskCircleContour *) contour;
   graphene_point_t start = GSK_CIRCLE_POINT_INIT (self, self->start_angle);
 
-  if (!func (GSK_PATH_MOVE, &start, 1, user_data))
+  if (!func (GSK_PATH_MOVE, &start, 1, 0, user_data))
     return FALSE;
 
   if (!gsk_spline_decompose_arc (&self->center,
@@ -627,7 +627,7 @@ gsk_circle_contour_foreach (const GskContour   *contour,
 
   if (fabs (self->start_angle - self->end_angle) >= 360)
     {
-      if (!func (GSK_PATH_CLOSE, (graphene_point_t[2]) { start, start }, 2, user_data))
+      if (!func (GSK_PATH_CLOSE, (graphene_point_t[2]) { start, start }, 2, 0, user_data))
         return FALSE;
     }
 
@@ -908,8 +908,21 @@ gsk_standard_contour_foreach (const GskContour   *contour,
 
   for (i = 0; i < self->n_ops; i ++)
     {
-      if (!func (self->ops[i].op, &self->points[self->ops[i].point], n_points[self->ops[i].op], user_data))
-        return FALSE;
+      if (self->ops[i].op == GSK_PATH_CONIC)
+        {
+          graphene_point_t pts[3] = { self->points[self->ops[i].point],
+                                      self->points[self->ops[i].point + 1],
+                                      self->points[self->ops[i].point + 3] };
+          float weight = self->points[self->ops[i].point + 2].x;
+
+          if (!func (GSK_PATH_CONIC, pts, 3, weight, user_data))
+            return FALSE;
+        }
+      else
+        {
+          if (!func (self->ops[i].op, &self->points[self->ops[i].point], n_points[self->ops[i].op], 0, 
user_data))
+            return FALSE;
+        }
     }
 
   return TRUE;
@@ -959,6 +972,16 @@ gsk_standard_contour_print (const GskContour *contour,
           _g_string_append_point (string, &pt[3]);
           break;
 
+        case GSK_PATH_CONIC:
+          /* This is not valid SVG */
+          g_string_append (string, " O ");
+          _g_string_append_point (string, &pt[1]);
+          g_string_append (string, ", ");
+          _g_string_append_point (string, &pt[3]);
+          g_string_append (string, ", ");
+          _g_string_append_double (string, pt[2].x);
+          break;
+
         default:
           g_assert_not_reached();
           return;
@@ -1111,6 +1134,14 @@ gsk_standard_contour_init_measure (const GskContour *contour,
           }
           break;
 
+        case GSK_PATH_CONIC:
+          {
+            LengthDecompose decomp = { array, { length, length, 0, 0, pt[0], i } };
+            gsk_spline_decompose_conic (pt, tolerance, gsk_standard_contour_measure_add_point, &decomp);
+            length = decomp.measure.start;
+          }
+          break;
+
         default:
           g_assert_not_reached();
           return NULL;
@@ -1171,6 +1202,10 @@ gsk_standard_contour_measure_get_point (GskStandardContour        *self,
         gsk_spline_get_point_cubic (pts, progress, pos, tangent);
         break;
 
+      case GSK_PATH_CONIC:
+        gsk_spline_get_point_conic (pts, progress, pos, tangent);
+        break;
+
       case GSK_PATH_MOVE:
       default:
         g_assert_not_reached ();
@@ -1404,6 +1439,25 @@ gsk_standard_contour_add_segment (const GskContour *contour,
           }
           break;
 
+        case GSK_PATH_CONIC:
+          {
+            graphene_point_t *pts = &self->points[self->ops[start_measure->op].point];
+            graphene_point_t curve[4], discard[4];
+
+            gsk_spline_split_conic (pts, discard, curve, start_progress);
+            if (end_measure && end_measure->op == start_measure->op)
+              {
+                graphene_point_t tiny[4];
+                gsk_spline_split_conic (curve, tiny, discard, (end_progress - start_progress) / (1 - 
start_progress));
+                gsk_path_builder_move_to (builder, tiny[0].x, tiny[0].y);
+                gsk_path_builder_conic_to (builder, tiny[1].x, tiny[1].y, tiny[3].x, tiny[3].y, tiny[2].x);
+                return;
+              }
+            gsk_path_builder_move_to (builder, curve[0].x, curve[0].y);
+            gsk_path_builder_conic_to (builder, curve[1].x, curve[1].y, curve[3].x, curve[3].y, curve[2].x);
+          }
+          break;
+
         case GSK_PATH_MOVE:
         default:
           g_assert_not_reached();
@@ -1433,6 +1487,10 @@ gsk_standard_contour_add_segment (const GskContour *contour,
           gsk_path_builder_curve_to (builder, pt[1].x, pt[1].y, pt[2].x, pt[2].y, pt[3].x, pt[3].y);
           break;
 
+        case GSK_PATH_CONIC:
+          gsk_path_builder_conic_to (builder, pt[1].x, pt[1].y, pt[3].x, pt[3].y, pt[2].x);
+          break;
+
         default:
           g_assert_not_reached();
           return;
@@ -1465,6 +1523,16 @@ gsk_standard_contour_add_segment (const GskContour *contour,
           }
           break;
 
+        case GSK_PATH_CONIC:
+          {
+            graphene_point_t *pts = &self->points[self->ops[end_measure->op].point];
+            graphene_point_t curve[4], discard[4];
+
+            gsk_spline_split_conic (pts, curve, discard, end_progress);
+            gsk_path_builder_conic_to (builder, curve[1].x, curve[1].y, curve[3].x, curve[3].y, curve[2].x);
+          }
+          break;
+
         case GSK_PATH_MOVE:
         default:
           g_assert_not_reached();
diff --git a/gsk/gskenums.h b/gsk/gskenums.h
index 9582525408..405c845eba 100644
--- a/gsk/gskenums.h
+++ b/gsk/gskenums.h
@@ -251,6 +251,9 @@ typedef enum {
  * @GSK_PATH_CURVE: A curve-to operation describing a cubic Bézier curve
  *     with 4 points describing the start point, the two control points
  *     and the end point of the curve.
+ * @GSK_PATH_CONIC: A weighted quadratic bezier curve with 3 points
+ *     describing the start point, control point and end point of the
+ *     curve. A weight for the curve will be passed, too.
  *
  * Path operations can be used to approximate a #GskPath.
  *
@@ -261,6 +264,7 @@ typedef enum {
   GSK_PATH_CLOSE,
   GSK_PATH_LINE,
   GSK_PATH_CURVE,
+  GSK_PATH_CONIC,
 } GskPathOperation;
 
 /**
diff --git a/gsk/gskpath.c b/gsk/gskpath.c
index 1e06910b60..9e9dac1023 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -262,6 +262,7 @@ static gboolean
 gsk_path_to_cairo_add_op (GskPathOperation        op,
                           const graphene_point_t *pts,
                           gsize                   n_pts,
+                          float                   weight,
                           gpointer                cr)
 {
   switch (op)
@@ -282,6 +283,7 @@ gsk_path_to_cairo_add_op (GskPathOperation        op,
       cairo_curve_to (cr, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
       break;
 
+    case GSK_PATH_CONIC:
     default:
       g_assert_not_reached ();
       return FALSE;
@@ -312,7 +314,7 @@ gsk_path_to_cairo (GskPath *self,
   g_return_if_fail (cr != NULL);
 
   gsk_path_foreach_with_tolerance (self,
-                                   GSK_PATH_FOREACH_ALLOW_CURVES,
+                                   GSK_PATH_FOREACH_ALLOW_CURVE,
                                    cairo_get_tolerance (cr),
                                    gsk_path_to_cairo_add_op,
                                    cr);
@@ -458,6 +460,7 @@ gsk_path_foreach_trampoline_add_point (const graphene_point_t *from,
   trampoline->retval = trampoline->func (GSK_PATH_LINE,
                                          (graphene_point_t[2]) { *from, *to },
                                          2,
+                                         0.0f,
                                          trampoline->user_data);
 }
 
@@ -465,6 +468,7 @@ static gboolean
 gsk_path_foreach_trampoline (GskPathOperation        op,
                              const graphene_point_t *pts,
                              gsize                   n_pts,
+                             float                   weight,
                              gpointer                data)
 {
   GskPathForeachTrampoline *trampoline = data;
@@ -474,11 +478,11 @@ gsk_path_foreach_trampoline (GskPathOperation        op,
     case GSK_PATH_MOVE:
     case GSK_PATH_CLOSE:
     case GSK_PATH_LINE:
-      return trampoline->func (op, pts, n_pts, trampoline->user_data);
+      return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
 
     case GSK_PATH_CURVE:
-      if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CURVES)
-        return trampoline->func (op, pts, n_pts, trampoline->user_data);
+      if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CURVE)
+        return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
       
       gsk_spline_decompose_cubic (pts,
                                   trampoline->tolerance,
@@ -486,6 +490,17 @@ gsk_path_foreach_trampoline (GskPathOperation        op,
                                   trampoline);
       return trampoline->retval;
 
+    case GSK_PATH_CONIC:
+      if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CONIC)
+        return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
+
+      /* XXX: decompose into curves if allowed */
+      gsk_spline_decompose_conic ((graphene_point_t[4]) { pts[0], pts[1], { weight, }, pts[2] },
+                                  trampoline->tolerance,
+                                  gsk_path_foreach_trampoline_add_point,
+                                  trampoline);
+      return trampoline->retval;
+
     default:
       g_assert_not_reached ();
       return FALSE;
@@ -503,7 +518,7 @@ gsk_path_foreach_with_tolerance (GskPath             *self,
   gsize i;
 
   /* If we need to massage the data, set up a trampoline here */
-  if (flags != GSK_PATH_FOREACH_ALLOW_CURVES)
+  if (flags != (GSK_PATH_FOREACH_ALLOW_CURVE | GSK_PATH_FOREACH_ALLOW_CONIC))
     {
       trampoline = (GskPathForeachTrampoline) { flags, tolerance, func, user_data, TRUE };
       func = gsk_path_foreach_trampoline;
@@ -628,7 +643,7 @@ parse_command (const char **p,
   if (*cmd == 'X')
     allowed = "mM";
   else
-    allowed = "mMhHvVzZlLcCsStTqQaA";
+    allowed = "mMhHvVzZlLcCsStTqQoOaA";
 
   skip_whitespace (p);
   s = strchr (allowed, **p);
@@ -1057,6 +1072,37 @@ gsk_path_parse (const char *string)
           }
           break;
 
+        case 'O':
+        case 'o':
+          {
+            double x1, y1, x2, y2, weight;
+
+            if (parse_coordinate_pair (&p, &x1, &y1) &&
+                parse_coordinate_pair (&p, &x2, &y2) &&
+                parse_nonnegative_number (&p, &weight))
+              {
+                if (cmd == 'c')
+                  {
+                    x1 += x;
+                    y1 += y;
+                    x2 += x;
+                    y2 += y;
+                  }
+                if (strchr ("zZ", prev_cmd))
+                  {
+                    gsk_path_builder_move_to (builder, x, y);
+                    path_x = x;
+                    path_y = y;
+                  }
+                gsk_path_builder_conic_to (builder, x1, y1, x2, y2, weight);
+                x = x2;
+                y = y2;
+              }
+            else
+              goto error;
+          }
+          break;
+
         case 'A':
         case 'a':
           {
diff --git a/gsk/gskpath.h b/gsk/gskpath.h
index 328da7dfbb..31f4cd10e9 100644
--- a/gsk/gskpath.h
+++ b/gsk/gskpath.h
@@ -31,7 +31,9 @@ G_BEGIN_DECLS
 
 /**
  * GskPathForeachFlags:
- * @GSK_PATH_FOREACH_ALLOW_CURVES: Allow emission of %GSK_PATH_CURVE
+ * @GSK_PATH_FOREACH_ALLOW_CURVE: Allow emission of %GSK_PATH_CURVE
+ *     operations.
+ * @GSK_PATH_FOREACH_ALLOW_CONIC: Allow emission of %GSK_PATH_CONIC
  *     operations.
  *
  * Flags that can be passed to gsk_path_foreach() to enable additional
@@ -43,7 +45,8 @@ G_BEGIN_DECLS
  */
 typedef enum
 {
-  GSK_PATH_FOREACH_ALLOW_CURVES = (1 << 0)
+  GSK_PATH_FOREACH_ALLOW_CURVE = (1 << 0),
+  GSK_PATH_FOREACH_ALLOW_CONIC = (1 << 1)
 } GskPathForeachFlags;
 
 /**
@@ -51,6 +54,7 @@ typedef enum
  * @op: The operation to perform
  * @pts: The points of the operation
  * @n_pts: The number of points
+ * @weight: The weight for conic curves, or unused if not a conic curve.
  * @user_data: The user data provided with the function
  *
  * Prototype of the callback to iterate throught the operations of
@@ -62,6 +66,7 @@ typedef enum
 typedef gboolean (* GskPathForeachFunc) (GskPathOperation        op,
                                          const graphene_point_t *pts,
                                          gsize                   n_pts,
+                                         float                   weight,
                                          gpointer                user_data);
 
 #define GSK_TYPE_PATH (gsk_path_get_type ())
diff --git a/gsk/gskpathbuilder.c b/gsk/gskpathbuilder.c
index 18d62ff73a..a7341835d3 100644
--- a/gsk/gskpathbuilder.c
+++ b/gsk/gskpathbuilder.c
@@ -549,6 +549,78 @@ gsk_path_builder_rel_curve_to (GskPathBuilder *builder,
                              builder->current_point.y + y3);
 }
 
+/**
+ * gsk_path_builder_conic_to:
+ * @builder: a #GskPathBuilder
+ * @x1: x coordinate of control point
+ * @y1: y coordinate of control point
+ * @x2: x coordinate of the end of the curve
+ * @y2: y coordinate of the end of the curve
+ * @weight: weight of the curve
+ *
+ * Adds a [conic curve](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline)
+ * from the current point to @x2, @y2 with the given
+ * @weight and @x1, @y1 as the single control point.
+ *
+ * Conic curves can be used to draw ellipses and circles.
+ *
+ * After this, @x2, @y2 will be the new current point.
+ **/
+void
+gsk_path_builder_conic_to (GskPathBuilder *builder,
+                           float           x1,
+                           float           y1,
+                           float           x2,
+                           float           y2,
+                           float           weight)
+{
+  g_return_if_fail (builder != NULL);
+  g_return_if_fail (weight >= 0);
+
+  builder->flags &= ~GSK_PATH_FLAT;
+  gsk_path_builder_append_current (builder,
+                                   GSK_PATH_CONIC,
+                                   3, (graphene_point_t[3]) {
+                                     GRAPHENE_POINT_INIT (x1, y1),
+                                     GRAPHENE_POINT_INIT (weight, 0),
+                                     GRAPHENE_POINT_INIT (x2, y2)
+                                   });
+}
+
+/**
+ * gsk_path_builder_rel_conic_to:
+ * @builder: a #GskPathBuilder
+ * @x1: x offset of control point
+ * @y1: y offset of control point
+ * @x2: x offset of the end of the curve
+ * @y2: y offset of the end of the curve
+ * @weight: weight of the curve
+ *
+ * Adds a [conic curve](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline)
+ * from the current point to @x2, @y2 with the given
+ * @weight and @x1, @y1 as the single control point.
+ *
+ * This is the relative version of gsk_path_builder_conic_to().
+ **/
+void
+gsk_path_builder_rel_conic_to (GskPathBuilder *builder,
+                               float           x1,
+                               float           y1,
+                               float           x2,
+                               float           y2,
+                               float           weight)
+{
+  g_return_if_fail (builder != NULL);
+  g_return_if_fail (weight >= 0);
+
+  gsk_path_builder_conic_to (builder,
+                             builder->current_point.x + x1,
+                             builder->current_point.y + y1,
+                             builder->current_point.x + x2,
+                             builder->current_point.y + y2,
+                             weight);
+}
+
 /**
  * gsk_path_builder_close:
  * @builder: a #GskPathBuilder
diff --git a/gsk/gskpathbuilder.h b/gsk/gskpathbuilder.h
index c2ee8157ba..fbcac022fa 100644
--- a/gsk/gskpathbuilder.h
+++ b/gsk/gskpathbuilder.h
@@ -99,6 +99,20 @@ void                    gsk_path_builder_rel_curve_to           (GskPathBuilder
                                                                  float                   x3,
                                                                  float                   y3);
 GDK_AVAILABLE_IN_ALL
+void                    gsk_path_builder_conic_to               (GskPathBuilder         *builder,
+                                                                 float                   x1,
+                                                                 float                   y1,
+                                                                 float                   x2,
+                                                                 float                   y2,
+                                                                 float                   weight);
+GDK_AVAILABLE_IN_ALL
+void                    gsk_path_builder_rel_conic_to           (GskPathBuilder         *builder,
+                                                                 float                   x1,
+                                                                 float                   y1,
+                                                                 float                   x2,
+                                                                 float                   y2,
+                                                                 float                   weight);
+GDK_AVAILABLE_IN_ALL
 void                    gsk_path_builder_close                  (GskPathBuilder         *builder);
 
 G_END_DECLS
diff --git a/gsk/gskspline.c b/gsk/gskspline.c
index ce4f5fa603..9f367cbc4b 100644
--- a/gsk/gskspline.c
+++ b/gsk/gskspline.c
@@ -25,17 +25,18 @@
 
 #include <math.h>
 
+#define MIN_PROGRESS (1/1024.f)
+
 typedef struct
 {
   graphene_point_t last_point;
   float last_progress;
-  float tolerance;
   GskSplineAddPointFunc func;
   gpointer user_data;
-} GskCubicDecomposition;
+} GskSplineDecompose;
 
 static void
-gsk_spline_decompose_add_point (GskCubicDecomposition  *decomp,
+gsk_spline_decompose_add_point (GskSplineDecompose     *decomp,
                                 const graphene_point_t *pt,
                                 float                   progress)
 {
@@ -47,6 +48,20 @@ gsk_spline_decompose_add_point (GskCubicDecomposition  *decomp,
   decomp->last_progress += progress;
 }
 
+static void
+gsk_spline_decompose_finish (GskSplineDecompose     *decomp,
+                             const graphene_point_t *end_point)
+{
+  g_assert (graphene_point_equal (&decomp->last_point, end_point));
+  g_assert (decomp->last_progress == 1.0f || decomp->last_progress == 0.0f);
+}
+
+typedef struct
+{
+  GskSplineDecompose decomp;
+  float tolerance;
+} GskCubicDecomposition;
+
 static void
 gsk_spline_cubic_get_coefficients (graphene_point_t       coeffs[4],
                                    const graphene_point_t pts[4])
@@ -202,22 +217,22 @@ gsk_spline_cubic_too_curvy (const graphene_point_t pts[4],
 }
 
 static void
-gsk_spline_decompose_into (GskCubicDecomposition  *decomp,
-                           const graphene_point_t  pts[4],
-                           float                   progress)
+gsk_spline_cubic_decompose (GskCubicDecomposition  *d,
+                            const graphene_point_t  pts[4],
+                            float                   progress)
 {
   graphene_point_t left[4], right[4];
 
-  if (!gsk_spline_cubic_too_curvy (pts, decomp->tolerance) || progress < 1 / 1024.f)
+  if (!gsk_spline_cubic_too_curvy (pts, d->tolerance) || progress < MIN_PROGRESS)
     {
-      gsk_spline_decompose_add_point (decomp, &pts[3], progress);
+      gsk_spline_decompose_add_point (&d->decomp, &pts[3], progress);
       return;
     }
 
   gsk_spline_split_cubic (pts, left, right, 0.5);
 
-  gsk_spline_decompose_into (decomp, left, progress / 2);
-  gsk_spline_decompose_into (decomp, right, progress / 2);
+  gsk_spline_cubic_decompose (d, left, progress / 2);
+  gsk_spline_cubic_decompose (d, right, progress / 2);
 }
 
 void 
@@ -226,12 +241,166 @@ gsk_spline_decompose_cubic (const graphene_point_t pts[4],
                             GskSplineAddPointFunc  add_point_func,
                             gpointer               user_data)
 {
-  GskCubicDecomposition decomp = { pts[0], 0.0f, tolerance, add_point_func, user_data };
+  GskCubicDecomposition decomp = { { pts[0], 0.0f, add_point_func, user_data }, tolerance };
+
+  gsk_spline_cubic_decompose (&decomp, pts, 1.0f);
+
+  gsk_spline_decompose_finish (&decomp.decomp, &pts[3]);
+}
+
+/* CONIC */
+
+typedef struct {
+  graphene_point_t num[3];
+  graphene_point_t denom[3];
+} ConicCoefficients;
+
+typedef struct
+{
+  GskSplineDecompose decomp;
+  float tolerance;
+  ConicCoefficients c;
+} GskConicDecomposition;
+
+
+static void
+gsk_spline_conic_get_coefficents (ConicCoefficients      *c,
+                                  const graphene_point_t  pts[4])
+{
+  float w = pts[2].x;
+  graphene_point_t pw = GRAPHENE_POINT_INIT (w * pts[1].x, w * pts[1].y);
+
+  c->num[2] = pts[0];
+  c->num[1] = GRAPHENE_POINT_INIT (2 * (pw.x - pts[0].x),
+                                   2 * (pw.y - pts[0].y));
+  c->num[0] = GRAPHENE_POINT_INIT (pts[3].x - 2 * pw.x + pts[0].x,
+                                   pts[3].y - 2 * pw.y + pts[0].y);
+
+  c->denom[2] = GRAPHENE_POINT_INIT (1, 1);
+  c->denom[1] = GRAPHENE_POINT_INIT (2 * (w - 1), 2 * (w - 1));
+  c->denom[0] = GRAPHENE_POINT_INIT (-c->denom[1].x, -c->denom[1].y);
+}
+
+static inline void
+gsk_spline_eval_quad (const graphene_point_t quad[3],
+                      float                  progress,
+                      graphene_point_t      *result)
+{
+  *result = GRAPHENE_POINT_INIT ((quad[0].x * progress + quad[1].x) * progress + quad[2].x,
+                                 (quad[0].y * progress + quad[1].y) * progress + quad[2].y);
+}
+
+static inline void
+gsk_spline_eval_conic (const ConicCoefficients *c,
+                       float                    progress,
+                       graphene_point_t        *result)
+{
+  graphene_point_t num, denom;
+
+  gsk_spline_eval_quad (c->num, progress, &num);
+  gsk_spline_eval_quad (c->denom, progress, &denom);
+  *result = GRAPHENE_POINT_INIT (num.x / denom.x, num.y / denom.y);
+}
+                       
+void
+gsk_spline_get_point_conic (const graphene_point_t  pts[4],
+                            float                   progress,
+                            graphene_point_t       *pos,
+                            graphene_vec2_t        *tangent)
+{
+  ConicCoefficients c;
+
+  gsk_spline_conic_get_coefficents (&c, pts);
+
+  if (pos)
+    gsk_spline_eval_conic (&c, progress, pos);
+
+  if (tangent)
+    {
+      graphene_point_t tmp;
+      float w = pts[2].x;
+
+      /* The tangent will be 0 in these corner cases, just
+       * treat it like a line here. */
+      if ((progress <= 0.f && graphene_point_equal (&pts[0], &pts[1])) ||
+          (progress >= 1.f && graphene_point_equal (&pts[1], &pts[3])))
+        {
+          graphene_vec2_init (tangent, pts[3].x - pts[0].x, pts[3].y - pts[0].y);
+          return;
+        }
+
+      gsk_spline_eval_quad ((graphene_point_t[3]) {
+                              GRAPHENE_POINT_INIT ((w - 1) * (pts[3].x - pts[0].x),
+                                                   (w - 1) * (pts[3].y - pts[0].y)),
+                              GRAPHENE_POINT_INIT (pts[3].x - pts[0].x - 2 * w * (pts[1].x - pts[0].x),
+                                                   pts[3].y - pts[0].y - 2 * w * (pts[1].y - pts[0].y)),
+                              GRAPHENE_POINT_INIT (w * (pts[1].x - pts[0].x),
+                                                   w * (pts[1].y - pts[0].y))
+                            },
+                            progress,
+                            &tmp);
+      graphene_vec2_init (tangent, tmp.x, tmp.y);
+      graphene_vec2_normalize (tangent, tangent);
+    }
+}
+
+void
+gsk_spline_split_conic (const graphene_point_t pts[4],
+                        graphene_point_t       result1[4],
+                        graphene_point_t       result2[4],
+                        float                  progress)
+{
+  g_warning ("FIXME: Stop treating conics as lines");
+}
+
+/* taken from Skia, including the very descriptive name */
+static gboolean
+gsk_spline_conic_too_curvy (const graphene_point_t *start,
+                            const graphene_point_t *mid,
+                            const graphene_point_t *end,
+                            float                  tolerance)
+{
+  return fabs ((start->x + end->x) * 0.5 - mid->x) > tolerance
+      || fabs ((start->y + end->y) * 0.5 - mid->y) > tolerance;
+}
+
+static void
+gsk_spline_decompose_conic_subdivide (GskConicDecomposition  *d,
+                                      const graphene_point_t *start,
+                                      float                   start_progress,
+                                      const graphene_point_t *end,
+                                      float                   end_progress)
+{
+  graphene_point_t mid;
+  float mid_progress;
+
+  mid_progress = (start_progress + end_progress) / 2;
+  gsk_spline_eval_conic (&d->c, mid_progress, &mid);
+
+  if (end_progress - start_progress < MIN_PROGRESS ||
+      !gsk_spline_conic_too_curvy (start, &mid, end, d->tolerance))
+    {
+      gsk_spline_decompose_add_point (&d->decomp, end, end_progress - start_progress);
+      return;
+    }
+
+  gsk_spline_decompose_conic_subdivide (d, start, start_progress, &mid, mid_progress);
+  gsk_spline_decompose_conic_subdivide (d, &mid, mid_progress, end, end_progress);
+}
+
+void
+gsk_spline_decompose_conic (const graphene_point_t pts[4],
+                            float                  tolerance,
+                            GskSplineAddPointFunc  add_point_func,
+                            gpointer               user_data)
+{
+  GskConicDecomposition d = { { pts[0], 0.0f, add_point_func, user_data }, tolerance, };
+
+  gsk_spline_conic_get_coefficents (&d.c, pts);
 
-  gsk_spline_decompose_into (&decomp, pts, 1.0f);
+  gsk_spline_decompose_conic_subdivide (&d, &pts[0], 0.0f, &pts[3], 1.0f);
 
-  g_assert (graphene_point_equal (&decomp.last_point, &pts[3]));
-  g_assert (decomp.last_progress == 1.0f || decomp.last_progress == 0.0f);
+  gsk_spline_decompose_finish (&d.decomp, &pts[3]);
 }
 
 /* Spline deviation from the circle in radius would be given by:
diff --git a/gsk/gsksplineprivate.h b/gsk/gsksplineprivate.h
index 7bd6282402..02556937ce 100644
--- a/gsk/gsksplineprivate.h
+++ b/gsk/gsksplineprivate.h
@@ -44,6 +44,19 @@ void                    gsk_spline_decompose_cubic              (const graphene_
                                                                  GskSplineAddPointFunc   add_point_func,
                                                                  gpointer                user_data);
 
+void                    gsk_spline_get_point_conic              (const graphene_point_t  pts[4],
+                                                                 float                   progress,
+                                                                 graphene_point_t       *pos,
+                                                                 graphene_vec2_t        *tangent);
+void                    gsk_spline_split_conic                  (const graphene_point_t  pts[4],
+                                                                 graphene_point_t        result1[4],
+                                                                 graphene_point_t        result2[4],
+                                                                 float                   progress);
+void                    gsk_spline_decompose_conic              (const graphene_point_t  pts[4],
+                                                                 float                   tolerance,
+                                                                 GskSplineAddPointFunc   add_point_func,
+                                                                 gpointer                user_data);
+
 typedef gboolean (* GskSplineAddCurveFunc) (const graphene_point_t curve[4],
                                             gpointer               user_data);
 gboolean                gsk_spline_decompose_arc                (const graphene_point_t *center,
diff --git a/testsuite/gsk/path.c b/testsuite/gsk/path.c
index 33ead68fab..6b49b862c0 100644
--- a/testsuite/gsk/path.c
+++ b/testsuite/gsk/path.c
@@ -324,6 +324,16 @@ path_operation_print (const PathOperation *p,
       _g_string_append_point (string, &p->pts[3]);
       break;
 
+    case GSK_PATH_CONIC:
+      /* This is not valid SVG */
+      g_string_append (string, " O ");
+      _g_string_append_point (string, &p->pts[1]);
+      g_string_append (string, ", ");
+      _g_string_append_point (string, &p->pts[2]);
+      g_string_append (string, ", ");
+      _g_string_append_double (string, p->weight);
+      break;
+
     default:
       g_assert_not_reached();
       return;
@@ -354,6 +364,11 @@ path_operation_equal (const PathOperation *p1,
             && graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon)
             && graphene_point_near (&p1->pts[3], &p2->pts[3], epsilon);
 
+      case GSK_PATH_CONIC:
+        return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon)
+            && graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon)
+            && G_APPROX_VALUE (p1->weight, p2->weight, epsilon);
+
       default:
         g_return_val_if_reached (FALSE);
     }
@@ -363,6 +378,7 @@ static gboolean
 collect_path_operation_cb (GskPathOperation        op,
                            const graphene_point_t *pts,
                            gsize                   n_pts,
+                           float                   weight,
                            gpointer                user_data)
 {
   g_array_append_vals (user_data,
@@ -377,6 +393,7 @@ collect_path_operation_cb (GskPathOperation        op,
                            GRAPHENE_POINT_INIT(n_pts > 3 ? pts[3].x : 0,
                                                n_pts > 3 ? pts[3].y : 0)
                          },
+                         weight
                        },
                        1);
   return TRUE;
@@ -943,6 +960,7 @@ static gboolean
 rotate_path_cb (GskPathOperation        op,
                 const graphene_point_t *pts,
                 gsize                   n_pts,
+                float                   weight,
                 gpointer                user_data)
 {
   GskPathBuilder **builders = user_data;
@@ -969,6 +987,11 @@ rotate_path_cb (GskPathOperation        op,
       gsk_path_builder_curve_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x, pts[3].y, -pts[3].x);
       break;
 
+    case GSK_PATH_CONIC:
+      gsk_path_builder_conic_to (builders[0], pts[2].x, pts[2].y, pts[3].x, pts[3].y, weight);
+      gsk_path_builder_conic_to (builders[1], pts[2].y, -pts[2].x, pts[3].y, -pts[3].x, weight);
+      break;
+
     default:
       g_assert_not_reached ();
       return FALSE;


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