diff --git a/docs/reference/gsk/gsk4.toml.in b/docs/reference/gsk/gsk4.toml.in
index be0d478958..cbc6f79b44 100644
--- a/docs/reference/gsk/gsk4.toml.in
+++ b/docs/reference/gsk/gsk4.toml.in
@@ -43,6 +43,8 @@ content_images = [
"images/caps-light.png",
"images/cubic-dark.png",
"images/cubic-light.png",
+ "images/curvature-dark.png",
+ "images/curvature-light.png",
"images/directions-dark.png",
"images/directions-light.png",
"images/fill-even-odd.png",
diff --git a/docs/reference/gsk/images/curvature-dark.png b/docs/reference/gsk/images/curvature-dark.png
new file mode 100644
index 0000000000..2a3a985751
Binary files /dev/null and b/docs/reference/gsk/images/curvature-dark.png differ
diff --git a/docs/reference/gsk/images/curvature-light.png b/docs/reference/gsk/images/curvature-light.png
new file mode 100644
index 0000000000..1055f324e0
Binary files /dev/null and b/docs/reference/gsk/images/curvature-light.png differ
diff --git a/docs/reference/gsk/images/curvature.svg b/docs/reference/gsk/images/curvature.svg
new file mode 100644
index 0000000000..3f744a8b27
--- /dev/null
+++ b/docs/reference/gsk/images/curvature.svg
@@ -0,0 +1,65 @@
+
+
+
+
diff --git a/docs/reference/gsk/paths.md b/docs/reference/gsk/paths.md
index 9a921313b7..dd672f888b 100644
--- a/docs/reference/gsk/paths.md
+++ b/docs/reference/gsk/paths.md
@@ -80,8 +80,8 @@ to the path, which can be selected with a [enum@Gsk.FillRule] value.
The same path, filled with GSK_FILL_RULE_EVEN_ODD
-To fill a path, use [gtk_snapshot_append_fill](../gtk4/method.Snapshot.append_fill.html)
-or the more general [gtk_snapshot_push_fill](../gtk4/method.Snapshot.push_fill.html).
+To fill a path, use [gtk_snapshot_append_fill()](../gtk4/method.Snapshot.append_fill.html)
+or the more general [gtk_snapshot_push_fill()](../gtk4/method.Snapshot.push_fill.html).
Alternatively, a path can be **_stroked_**, which means to emulate drawing
with an idealized pen along the path. The result of stroking a path is another
@@ -106,8 +106,8 @@ of line joins and line caps, and a dash pattern.
To stroke a path, use
-[gtk_snapshot_append_stroke](../gtk4/method.Snapshot.append_stroke.html)
-or [gtk_snapshot_push_stroke](../gtk4/method.Snapshot.push_stroke.html).
+[gtk_snapshot_append_stroke()](../gtk4/method.Snapshot.append_stroke.html)
+or [gtk_snapshot_push_stroke()](../gtk4/method.Snapshot.push_stroke.html).
## Hit testing
@@ -132,3 +132,19 @@ To obtain a `GskPathPoint`, use [method@Gsk.Path.get_closest_point], [method@Gsk
To query properties of the path at a point, use [method@Gsk.PathPoint.get_position],
[method@Gsk.PathPoint.get_tangent], [method@Gsk.PathPoint.get_rotation] and
[method@Gsk.PathPoint.get_curvature].
+
+## Going beyond GskPath
+
+Lots of powerful functionality can be implemented for paths:
+
+- Finding intersections
+- Offsetting curves
+- Molding curves (making them pass through a given point)
+
+GSK does not provide API for all of these, but it does offer a way to get at
+the underlying Bézier curves, so you can implement such functionality yourself.
+You can use [method@Gsk.Path.foreach] to iterate over the operations of the
+path, and get the points needed to reconstruct or modify the path piece by piece.
+
+See e.g. the [Primer on Bézier curves](https://pomax.github.io/bezierinfo/)
+for inspiration of useful things to explore.
diff --git a/gsk/gskcontour.c b/gsk/gskcontour.c
index a8ec1f60d3..0fb6ad32ba 100644
--- a/gsk/gskcontour.c
+++ b/gsk/gskcontour.c
@@ -76,6 +76,7 @@ struct _GskContourClass
graphene_vec2_t *tangent);
float (* get_curvature) (const GskContour *contour,
GskRealPathPoint *point,
+ GskPathDirection direction,
graphene_point_t *center);
void (* add_segment) (const GskContour *contour,
GskPathBuilder *builder,
@@ -530,16 +531,35 @@ gsk_standard_contour_get_tangent (const GskContour *contour,
static float
gsk_standard_contour_get_curvature (const GskContour *contour,
GskRealPathPoint *point,
+ GskPathDirection direction,
graphene_point_t *center)
{
GskStandardContour *self = (GskStandardContour *) contour;
GskCurve curve;
+ gsize idx;
+ float t;
if (G_UNLIKELY (point->idx == 0))
return 0;
- gsk_curve_init (&curve, self->ops[point->idx]);
- return gsk_curve_get_curvature (&curve, point->t, center);
+ idx = point->idx;
+ t = point->t;
+
+ if (t == 0 && idx > 1 &&
+ (direction == GSK_PATH_FROM_START || direction == GSK_PATH_TO_START))
+ {
+ idx--;
+ t = 1;
+ }
+ else if (t == 1 && idx + 1 < self->n_ops &&
+ (direction == GSK_PATH_FROM_END || direction == GSK_PATH_TO_END))
+ {
+ idx++;
+ t = 0;
+ }
+
+ gsk_curve_init (&curve, self->ops[idx]);
+ return gsk_curve_get_curvature (&curve, t, center);
}
static void
@@ -802,9 +822,10 @@ gsk_contour_get_tangent (const GskContour *self,
float
gsk_contour_get_curvature (const GskContour *self,
GskRealPathPoint *point,
+ GskPathDirection direction,
graphene_point_t *center)
{
- return self->klass->get_curvature (self, point, center);
+ return self->klass->get_curvature (self, point, direction, center);
}
void
diff --git a/gsk/gskcontourprivate.h b/gsk/gskcontourprivate.h
index 2e5bf7ee5d..7193e4bf1f 100644
--- a/gsk/gskcontourprivate.h
+++ b/gsk/gskcontourprivate.h
@@ -72,6 +72,7 @@ void gsk_contour_get_tangent (const GskContou
graphene_vec2_t *tangent);
float gsk_contour_get_curvature (const GskContour *self,
GskRealPathPoint *point,
+ GskPathDirection direction,
graphene_point_t *center);
void gsk_contour_add_segment (const GskContour *self,
GskPathBuilder *builder,
diff --git a/gsk/gskpath.c b/gsk/gskpath.c
index 1f8d5059d7..8d4c5994f5 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -367,7 +367,7 @@ gsk_path_is_closed (GskPath *self)
* If the path is empty, `FALSE` is returned and @bounds are set to
* graphene_rect_zero(). This is different from the case where the path
* is a single point at the origin, where the @bounds will also be set to
- * the zero rectangle but 0 will be returned.
+ * the zero rectangle but `TRUE` will be returned.
*
* Returns: `TRUE` if the path has bounds, `FALSE` if the path is known
* to be empty and have no bounds.
@@ -616,10 +616,17 @@ gsk_path_get_closest_point (GskPath *self,
*
* Calls @func for every operation of the path.
*
- * Note that this only approximates @self, because paths can contain
- * optimizations for various specialized contours, and depending on
- * the @flags, the path may be decomposed into simpler curves than
- * the ones that it contained originally.
+ * Note that this may only approximate @self, because paths can contain
+ * optimizations for various specialized contours, and depending on the
+ * @flags, the path may be decomposed into simpler curves than the ones
+ * that it contained originally.
+ *
+ * This function serves two purposes:
+ *
+ * - When the @flags allow everything, it provides access to the raw,
+ * unmodified data of the path.
+ * - When the @flags disallow certain operations, it provides
+ * an approximation of the path using just the allowed operations.
*
* Returns: `FALSE` if @func returned FALSE`, `TRUE` otherwise.
*
diff --git a/gsk/gskpath.h b/gsk/gskpath.h
index 5ea38c29e2..b386984e96 100644
--- a/gsk/gskpath.h
+++ b/gsk/gskpath.h
@@ -35,12 +35,13 @@ G_BEGIN_DECLS
* @GSK_PATH_FOREACH_ALLOW_CUBIC: Allow emission of `GSK_PATH_CUBIC` operations.
* @GSK_PATH_FOREACH_ALLOW_ARC: Allow emission of `GSK_PATH_ARC` operations.
*
- * Flags that can be passed to gsk_path_foreach() to enable additional
- * features.
+ * Flags that can be passed to gsk_path_foreach() to influence what
+ * kinds of operations the path is decomposed into.
*
- * By default, [method@Gsk.Path.foreach] will only emit a path with all operations
- * flattened to straight lines to allow for maximum compatibility. The only
- * operations emitted will be `GSK_PATH_MOVE`, `GSK_PATH_LINE` and `GSK_PATH_CLOSE`.
+ * By default, [method@Gsk.Path.foreach] will only emit a path with all
+ * operations flattened to straight lines to allow for maximum compatibility.
+ * The only operations emitted will be `GSK_PATH_MOVE`, `GSK_PATH_LINE` and
+ * `GSK_PATH_CLOSE`.
*
* Since: 4.14
*/
@@ -54,15 +55,23 @@ typedef enum
/**
* GskPathForeachFunc:
- * @op: The operation to perform
+ * @op: The operation
* @pts: The points of the operation
* @n_pts: The number of points
* @user_data: The user data provided with the function
*
- * Prototype of the callback to iterate throught the operations of
+ * Prototype of the callback to iterate through the operations of
* a path.
*
- * Returns: %TRUE to continue evaluating the path, %FALSE to
+ * For each operation, the callback is given the @op itself,
+ * and the points that the operation is applied to in @pts. The
+ * @n_pts argument is somewhat redundant, since the number of points
+ * can be inferred from the operation.
+ *
+ * Each contour of the path starts with a @GSK_PATH_MOVE operation.
+ * Closed contours end with a @GSK_PATH_CLOSE operation.
+ *
+ * Returns: %TRUE to continue iterating the path, %FALSE to
* immediately abort and not call the function again.
*/
typedef gboolean (* GskPathForeachFunc) (GskPathOperation op,
diff --git a/gsk/gskpathpoint.c b/gsk/gskpathpoint.c
index d1835743a1..28f481ae09 100644
--- a/gsk/gskpathpoint.c
+++ b/gsk/gskpathpoint.c
@@ -219,7 +219,8 @@ gsk_path_point_get_tangent (const GskPathPoint *point,
*
* This is a convenience variant of [method@Gsk.PathPoint.get_tangent]
* that returns the angle between the tangent and the X axis. The angle
- * can e.g. be used in [method@Gtk.Snapshot.rotate].
+ * can e.g. be used in
+ * [gtk_snapshot_rotate()](../gtk4/method.Snapshot.rotate.html).
*
* Returns: the angle between the tangent and the X axis, in degrees
*
@@ -247,16 +248,29 @@ gsk_path_point_get_rotation (const GskPathPoint *point,
* gsk_path_point_get_curvature:
* @point: a `GskPathPoint`
* @path: the path that @point is on
+ * @direction: the direction for which to return the curvature
* @center: (out caller-allocates) (nullable): Return location for
* the center of the osculating circle
*
* Calculates the curvature of the path at the point.
*
* Optionally, returns the center of the osculating circle as well.
+ * The curvature is the inverse of the radius of the osculating circle.
*
- * If the curvature is infinite (at line segments), zero is returned,
- * and @center is not modified.
+ * Lines have a curvature of zero (indicating an osculating circle of
+ * infinite radius. In this case, the @center is not modified.
*
+ * Note that certain points on a path may not have a single curvature,
+ * such as sharp turns. At such points, there are two curvatures --
+ * the (limit of) the curvature of the path going into the point,
+ * and the (limit of) the curvature of the path coming out of it.
+ * The @direction argument lets you choose which one to get.
+ *
+ *
+ *
+ *
+ *
+
* Returns: The curvature of the path at the given point
*
* Since: 4.14
@@ -264,6 +278,7 @@ gsk_path_point_get_rotation (const GskPathPoint *point,
float
gsk_path_point_get_curvature (const GskPathPoint *point,
GskPath *path,
+ GskPathDirection direction,
graphene_point_t *center)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
@@ -274,5 +289,5 @@ gsk_path_point_get_curvature (const GskPathPoint *point,
g_return_val_if_fail (self->contour < gsk_path_get_n_contours (path), 0);
contour = gsk_path_get_contour (path, self->contour);
- return gsk_contour_get_curvature (contour, self, center);
+ return gsk_contour_get_curvature (contour, self, direction, center);
}
diff --git a/gsk/gskpathpoint.h b/gsk/gskpathpoint.h
index a2164645d8..13711b768a 100644
--- a/gsk/gskpathpoint.h
+++ b/gsk/gskpathpoint.h
@@ -75,6 +75,7 @@ float gsk_path_point_get_rotation (const GskPathPoint *poin
GDK_AVAILABLE_IN_4_14
float gsk_path_point_get_curvature (const GskPathPoint *point,
GskPath *path,
+ GskPathDirection direction,
graphene_point_t *center);
diff --git a/testsuite/gsk/path-special-cases.c b/testsuite/gsk/path-special-cases.c
index 30230ba6b9..8627dca036 100644
--- a/testsuite/gsk/path-special-cases.c
+++ b/testsuite/gsk/path-special-cases.c
@@ -473,7 +473,7 @@ test_path_point (void)
gsk_path_point_get_position (&point, path, &pos);
gsk_path_point_get_tangent (&point, path, GSK_PATH_FROM_START, &t1);
gsk_path_point_get_tangent (&point, path, GSK_PATH_TO_END, &t2);
- curvature = gsk_path_point_get_curvature (&point, path, ¢er);
+ curvature = gsk_path_point_get_curvature (&point, path, GSK_PATH_FROM_START, ¢er);
g_assert_true (graphene_point_equal (&pos, &GRAPHENE_POINT_INIT (100, 100)));
g_assert_true (graphene_vec2_equal (&t1, graphene_vec2_y_axis ()));