Merge branch 'wip/matthiasc/arcs' into 'main'
path: Add arcs See merge request GNOME/gtk!6313
This commit is contained in:
@@ -336,6 +336,7 @@
|
||||
<file>panes.c</file>
|
||||
<file>password_entry.c</file>
|
||||
<file>path_fill.c</file>
|
||||
<file>path_spinner.c</file>
|
||||
<file>path_walk.c</file>
|
||||
<file>peg_solitaire.c</file>
|
||||
<file>pickers.c</file>
|
||||
|
||||
@@ -73,6 +73,7 @@ demos = files([
|
||||
'panes.c',
|
||||
'password_entry.c',
|
||||
'path_fill.c',
|
||||
'path_spinner.c',
|
||||
'path_walk.c',
|
||||
'peg_solitaire.c',
|
||||
'pickers.c',
|
||||
|
||||
322
demos/gtk-demo/path_spinner.c
Normal file
322
demos/gtk-demo/path_spinner.c
Normal file
@@ -0,0 +1,322 @@
|
||||
/* Path/Spinner
|
||||
*
|
||||
* This demo shows how to use GskPath to draw a simple animation
|
||||
* that could be used as a spinner.
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "paintable.h"
|
||||
|
||||
#define GTK_TYPE_SPINNER_PAINTABLE (gtk_spinner_paintable_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (GtkSpinnerPaintable, gtk_spinner_paintable, GTK, SPINNER_PAINTABLE, GObject)
|
||||
|
||||
struct _GtkSpinnerPaintable
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
gint64 start_time;
|
||||
|
||||
int width;
|
||||
double angle;
|
||||
double completion;
|
||||
|
||||
GskPath *circle;
|
||||
GskPath *path;
|
||||
GskStroke *stroke;
|
||||
GdkRGBA color;
|
||||
GdkRGBA circle_color;
|
||||
#ifdef SHOW_CONTROLS
|
||||
GskPath *controls;
|
||||
GdkRGBA control_color;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct _GtkSpinnerPaintableClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
static int
|
||||
gtk_spinner_paintable_get_intrinsic_width (GdkPaintable *paintable)
|
||||
{
|
||||
GtkSpinnerPaintable *self = GTK_SPINNER_PAINTABLE (paintable);
|
||||
|
||||
return self->width;
|
||||
}
|
||||
|
||||
static int
|
||||
gtk_spinner_paintable_get_intrinsic_height (GdkPaintable *paintable)
|
||||
{
|
||||
GtkSpinnerPaintable *self = GTK_SPINNER_PAINTABLE (paintable);
|
||||
|
||||
return self->width;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_spinner_paintable_snapshot (GdkPaintable *paintable,
|
||||
GdkSnapshot *snapshot,
|
||||
double width,
|
||||
double height)
|
||||
{
|
||||
GtkSpinnerPaintable *self = GTK_SPINNER_PAINTABLE (paintable);
|
||||
|
||||
gtk_snapshot_append_stroke (snapshot, self->circle, self->stroke, &self->circle_color);
|
||||
gtk_snapshot_append_stroke (snapshot, self->path, self->stroke, &self->color);
|
||||
#ifdef SHOW_CONTROLS
|
||||
GskStroke *stroke = gsk_stroke_new (1);
|
||||
gtk_snapshot_append_stroke (snapshot, self->controls, stroke, &self->control_color);
|
||||
gsk_stroke_free (stroke);
|
||||
#endif
|
||||
}
|
||||
|
||||
static GdkPaintableFlags
|
||||
gtk_spinner_paintable_get_flags (GdkPaintable *paintable)
|
||||
{
|
||||
return GDK_PAINTABLE_STATIC_SIZE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_spinner_paintable_paintable_init (GdkPaintableInterface *iface)
|
||||
{
|
||||
iface->get_intrinsic_width = gtk_spinner_paintable_get_intrinsic_width;
|
||||
iface->get_intrinsic_height = gtk_spinner_paintable_get_intrinsic_height;
|
||||
iface->snapshot = gtk_spinner_paintable_snapshot;
|
||||
iface->get_flags = gtk_spinner_paintable_get_flags;
|
||||
}
|
||||
|
||||
/* When defining the GType, we need to implement the GdkPaintable interface */
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkSpinnerPaintable, gtk_spinner_paintable, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
|
||||
gtk_spinner_paintable_paintable_init))
|
||||
|
||||
static void
|
||||
gtk_spinner_paintable_dispose (GObject *object)
|
||||
{
|
||||
GtkSpinnerPaintable *self = GTK_SPINNER_PAINTABLE (object);
|
||||
|
||||
gsk_path_unref (self->circle);
|
||||
gsk_path_unref (self->path);
|
||||
#ifdef SHOW_CONTROLS
|
||||
gsk_path_unref (self->controls);
|
||||
#endif
|
||||
|
||||
gsk_stroke_free (self->stroke);
|
||||
|
||||
G_OBJECT_CLASS (gtk_spinner_paintable_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_spinner_paintable_class_init (GtkSpinnerPaintableClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = gtk_spinner_paintable_dispose;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_spinner_paintable_init (GtkSpinnerPaintable *self)
|
||||
{
|
||||
}
|
||||
|
||||
static GdkPaintable *
|
||||
gtk_spinner_paintable_new (void)
|
||||
{
|
||||
GtkSpinnerPaintable *self;
|
||||
GskPathBuilder *builder;
|
||||
|
||||
self = g_object_new (GTK_TYPE_SPINNER_PAINTABLE, NULL);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (50, 50), 40);
|
||||
self->circle = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
self->width = 100;
|
||||
self->angle = 0;
|
||||
self->completion = 1;
|
||||
gdk_rgba_parse (&self->color, "green");
|
||||
gdk_rgba_parse (&self->circle_color, "lightgray");
|
||||
#ifdef SHOW_CONTROLS
|
||||
gdk_rgba_parse (&self->control_color, "black");
|
||||
#endif
|
||||
self->stroke = gsk_stroke_new (5);
|
||||
|
||||
return GDK_PAINTABLE (self);
|
||||
}
|
||||
|
||||
#ifdef SHOW_CONTROLS
|
||||
static gboolean
|
||||
add_controls (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
gpointer data)
|
||||
{
|
||||
GskPathBuilder *builder = data;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
case GSK_PATH_LINE:
|
||||
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
case GSK_PATH_ARC:
|
||||
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
|
||||
gsk_path_builder_line_to (builder, pts[2].x, pts[2].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
|
||||
gsk_path_builder_line_to (builder, pts[2].x, pts[2].y);
|
||||
gsk_path_builder_line_to (builder, pts[3].x, pts[3].y);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
update_path (GtkSpinnerPaintable *self)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPathPoint start, end;
|
||||
GskTransform *t;
|
||||
graphene_point_t p, p0, p1;
|
||||
float start_angle, end_angle;
|
||||
|
||||
p = GRAPHENE_POINT_INIT (40, 0);
|
||||
start_angle = self->angle;
|
||||
end_angle = fmod (self->angle + 360 * self->completion / 100, 360);
|
||||
|
||||
t = gsk_transform_translate (
|
||||
gsk_transform_rotate (
|
||||
gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (50, 50)),
|
||||
start_angle),
|
||||
&GRAPHENE_POINT_INIT (-50, -50));
|
||||
gsk_transform_transform_point (t, &p, &p0);
|
||||
|
||||
t = gsk_transform_translate (
|
||||
gsk_transform_rotate (
|
||||
gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (50, 50)),
|
||||
end_angle),
|
||||
&GRAPHENE_POINT_INIT (-50, -50));
|
||||
gsk_transform_transform_point (t, &p, &p1);
|
||||
|
||||
g_clear_pointer (&self->path, gsk_path_unref);
|
||||
|
||||
gsk_path_get_closest_point (self->circle, &p0, INFINITY, &start);
|
||||
gsk_path_get_closest_point (self->circle, &p1, INFINITY, &end);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_segment (builder, self->circle, &start, &end);
|
||||
self->path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
#ifdef SHOW_CONTROLS
|
||||
g_clear_pointer (&self->controls, gsk_path_unref);
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_foreach (self->path, -1, add_controls, builder);
|
||||
self->controls = gsk_path_builder_free_to_path (builder);
|
||||
#endif
|
||||
|
||||
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_spinner_paintable_set_completion (GtkSpinnerPaintable *self,
|
||||
float completion)
|
||||
{
|
||||
self->completion = CLAMP (completion, 0, 100);
|
||||
update_path (self);
|
||||
}
|
||||
|
||||
static float
|
||||
gtk_spinner_paintable_get_completion (GtkSpinnerPaintable *self)
|
||||
{
|
||||
return self->completion;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_spinner_paintable_set_frame_time (GtkSpinnerPaintable *self,
|
||||
gint64 time)
|
||||
{
|
||||
double delta;
|
||||
|
||||
if (self->start_time == 0)
|
||||
self->start_time = time;
|
||||
|
||||
delta = (time - self->start_time) / (double) G_TIME_SPAN_SECOND;
|
||||
self->angle = fmod (60 * delta, 360);
|
||||
update_path (self);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
tick_cb (GtkWidget *widget,
|
||||
GdkFrameClock *clock,
|
||||
gpointer data)
|
||||
{
|
||||
GtkSpinnerPaintable *self = data;
|
||||
|
||||
gtk_spinner_paintable_set_frame_time (self, gdk_frame_clock_get_frame_time (clock));
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
progress_timeout (gpointer data)
|
||||
{
|
||||
GtkSpinnerPaintable *self = data;
|
||||
static float progress_delta = 0.5;
|
||||
float progress;
|
||||
|
||||
progress = gtk_spinner_paintable_get_completion (self);
|
||||
if (progress >= 100 || progress <= 0)
|
||||
progress_delta = -progress_delta;
|
||||
|
||||
gtk_spinner_paintable_set_completion (self, progress + progress_delta);
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
do_path_spinner (GtkWidget *do_widget)
|
||||
{
|
||||
static GtkWidget *window = NULL;
|
||||
|
||||
if (!window)
|
||||
{
|
||||
GtkWidget *picture;
|
||||
GdkPaintable *paintable;
|
||||
|
||||
window = gtk_window_new ();
|
||||
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
|
||||
gtk_window_set_title (GTK_WINDOW (window), "Path Spinner");
|
||||
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
|
||||
|
||||
paintable = gtk_spinner_paintable_new ();
|
||||
picture = gtk_picture_new_for_paintable (paintable);
|
||||
gtk_picture_set_content_fit (GTK_PICTURE (picture), GTK_CONTENT_FIT_CONTAIN);
|
||||
gtk_picture_set_can_shrink (GTK_PICTURE (picture), FALSE);
|
||||
g_object_unref (paintable);
|
||||
|
||||
gtk_widget_add_tick_callback (picture, tick_cb, paintable, NULL);
|
||||
g_timeout_add (100, progress_timeout, paintable);
|
||||
|
||||
gtk_window_set_child (GTK_WINDOW (window), picture);
|
||||
}
|
||||
|
||||
if (!gtk_widget_get_visible (window))
|
||||
gtk_window_present (GTK_WINDOW (window));
|
||||
else
|
||||
gtk_window_destroy (GTK_WINDOW (window));
|
||||
|
||||
return window;
|
||||
}
|
||||
@@ -37,6 +37,8 @@ content_files = [
|
||||
]
|
||||
content_images = [
|
||||
"gtk-logo.svg",
|
||||
"images/arc-dark.png",
|
||||
"images/arc-light.png",
|
||||
"images/caps-dark.png",
|
||||
"images/caps-light.png",
|
||||
"images/cubic-dark.png",
|
||||
|
||||
BIN
docs/reference/gsk/images/arc-dark.png
Normal file
BIN
docs/reference/gsk/images/arc-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
docs/reference/gsk/images/arc-light.png
Normal file
BIN
docs/reference/gsk/images/arc-light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
92
docs/reference/gsk/images/arc.svg
Normal file
92
docs/reference/gsk/images/arc.svg
Normal file
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 210 297"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
sodipodi:docname="arcto.svg"
|
||||
inkscape:export-filename="cubic-light.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.75989759"
|
||||
inkscape:cx="399.39592"
|
||||
inkscape:cy="466.51023"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1123"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false" />
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
d="M 58.033485,123.96862 C 75.231194,113.95411 92.489919,103.26728 107.81401,113.89786"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<ellipse
|
||||
style="fill:none;stroke:#000000;stroke-width:0.20061772;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
id="path2"
|
||||
cx="-16.837238"
|
||||
cy="154.55043"
|
||||
rx="35.832706"
|
||||
ry="17.920988"
|
||||
transform="matrix(0.86643544,-0.49928912,0.59215321,0.8058254,0,0)" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
d="M 57.432798,124.38759 86.59638,102.1496 107.25717,113.71009"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<circle
|
||||
style="fill:#ff0404;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
id="path2-8-7-9"
|
||||
cx="86.580566"
|
||||
cy="102.81618"
|
||||
r="1.5" />
|
||||
<path
|
||||
style="fill:#f90048;stroke:#a2a2a2;stroke-width:1;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 45.061871,134.70541 12.60825,-10.20893"
|
||||
id="path4-4"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:#f90048;stroke:#a2a2a2;stroke-width:1;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:1, 3;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 35.405008,142.35717 8.95641,-7.26298"
|
||||
id="path4-8-8"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<circle
|
||||
style="fill:#818181;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
id="path2-8-7-61-1"
|
||||
cx="57.597607"
|
||||
cy="124.23325"
|
||||
r="1.5" />
|
||||
<circle
|
||||
style="fill:#ff0404;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
|
||||
id="path2-8-7-6-5"
|
||||
cx="108.01463"
|
||||
cy="114.17829"
|
||||
r="1.5" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -44,6 +44,10 @@ segments.
|
||||
|
||||
Allow cubic Bézier curves to be used in the generated path.
|
||||
|
||||
``--allow-arc``
|
||||
|
||||
Allow elliptical arcs to be used in the generated path.
|
||||
|
||||
Showing
|
||||
^^^^^^^
|
||||
|
||||
|
||||
@@ -287,6 +287,13 @@ gsk_standard_contour_print (const GskContour *contour,
|
||||
_g_string_append_point (string, &pt[3]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_ARC:
|
||||
g_string_append (string, " E ");
|
||||
_g_string_append_point (string, &pt[1]);
|
||||
g_string_append (string, ", ");
|
||||
_g_string_append_point (string, &pt[2]);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
return;
|
||||
|
||||
494
gsk/gskcurve.c
494
gsk/gskcurve.c
@@ -84,6 +84,9 @@ struct _GskCurveClass
|
||||
|
||||
/* {{{ Utilities */
|
||||
|
||||
#define RAD_TO_DEG(r) ((r)*180.f/M_PI)
|
||||
#define DEG_TO_RAD(d) ((d)*M_PI/180.f)
|
||||
|
||||
static void
|
||||
get_tangent (const graphene_point_t *p0,
|
||||
const graphene_point_t *p1,
|
||||
@@ -93,6 +96,37 @@ get_tangent (const graphene_point_t *p0,
|
||||
graphene_vec2_normalize (t, t);
|
||||
}
|
||||
|
||||
static int
|
||||
line_intersection (const graphene_point_t *a,
|
||||
const graphene_vec2_t *ta,
|
||||
const graphene_point_t *c,
|
||||
const graphene_vec2_t *tc,
|
||||
graphene_point_t *p)
|
||||
{
|
||||
float a1 = graphene_vec2_get_y (ta);
|
||||
float b1 = - graphene_vec2_get_x (ta);
|
||||
float c1 = a1*a->x + b1*a->y;
|
||||
|
||||
float a2 = graphene_vec2_get_y (tc);
|
||||
float b2 = - graphene_vec2_get_x (tc);
|
||||
float c2 = a2*c->x+ b2*c->y;
|
||||
|
||||
float det = a1*b2 - a2*b1;
|
||||
|
||||
if (fabs (det) < 0.001)
|
||||
{
|
||||
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 int
|
||||
line_get_crossing (const graphene_point_t *p,
|
||||
const graphene_point_t *p1,
|
||||
@@ -177,8 +211,19 @@ gsk_curve_elevate (const GskCurve *curve,
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
static inline void
|
||||
_sincosf (float angle, float *s, float *c)
|
||||
{
|
||||
#ifdef HAVE_SINCOSF
|
||||
sincosf (angle, s, c);
|
||||
#else
|
||||
*s = sinf (angle);
|
||||
*c = cosf (angle);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ Line */
|
||||
/* {{{ Line */
|
||||
|
||||
static void
|
||||
gsk_line_curve_init_from_points (GskLineCurve *self,
|
||||
@@ -1101,7 +1146,7 @@ gsk_cubic_curve_decompose_curve (const GskCurve *curve,
|
||||
if (flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
|
||||
return add_curve_func (GSK_PATH_CUBIC, self->points, 4, user_data);
|
||||
|
||||
/* FIXME: Quadratic (or conic?) approximation */
|
||||
/* FIXME: Quadratic or arc approximation */
|
||||
return gsk_cubic_curve_decompose (curve,
|
||||
tolerance,
|
||||
gsk_curve_add_line_cb,
|
||||
@@ -1240,6 +1285,448 @@ static const GskCurveClass GSK_CUBIC_CURVE_CLASS = {
|
||||
gsk_cubic_curve_get_crossing,
|
||||
};
|
||||
|
||||
/* }}} */
|
||||
/* {{{ Arc */
|
||||
|
||||
static void
|
||||
gsk_arc_curve_ensure_matrix (const GskArcCurve *curve)
|
||||
{
|
||||
GskArcCurve *self = (GskArcCurve *)curve;
|
||||
const graphene_point_t *pts = self->points;
|
||||
graphene_matrix_t m1, m2, m3, tmp;
|
||||
|
||||
if (self->has_matrix)
|
||||
return;
|
||||
|
||||
/* Compute a matrix that maps (1, 0), (1, 1), (0, 1) to pts[0..2] */
|
||||
graphene_matrix_init_from_2d (&m1, 1, 0, 0, 1, -1, -1);
|
||||
graphene_matrix_init_from_2d (&m2, -pts[2].x + pts[1].x, -pts[2].y + pts[1].y,
|
||||
-pts[0].x + pts[1].x, -pts[0].y + pts[1].y,
|
||||
0, 0);
|
||||
graphene_matrix_init_from_2d (&m3, 1, 0, 0, 1, pts[1].x, pts[1].y);
|
||||
|
||||
graphene_matrix_multiply (&m1, &m2, &tmp);
|
||||
graphene_matrix_multiply (&tmp, &m3, &self->m);
|
||||
|
||||
self->has_matrix = TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_arc_curve_init_from_points (GskArcCurve *self,
|
||||
const graphene_point_t pts[3])
|
||||
{
|
||||
self->op = GSK_PATH_ARC;
|
||||
self->has_matrix = FALSE;
|
||||
|
||||
memcpy (self->points, pts, sizeof (graphene_point_t) * 3);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_arc_curve_init (GskCurve *curve,
|
||||
gskpathop op)
|
||||
{
|
||||
GskArcCurve *self = &curve->arc;
|
||||
|
||||
gsk_arc_curve_init_from_points (self, gsk_pathop_points (op));
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_arc_curve_init_foreach (GskCurve *curve,
|
||||
GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts)
|
||||
{
|
||||
GskArcCurve *self = &curve->arc;
|
||||
|
||||
g_assert (n_pts == 3);
|
||||
|
||||
gsk_arc_curve_init_from_points (self, pts);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_arc_curve_print (const GskCurve *curve,
|
||||
GString *string)
|
||||
{
|
||||
const GskArcCurve *self = &curve->arc;
|
||||
|
||||
g_string_append_printf (string,
|
||||
"M %g %g E %g %g %g %g",
|
||||
self->points[0].x, self->points[0].y,
|
||||
self->points[1].x, self->points[1].y,
|
||||
self->points[2].x, self->points[2].y);
|
||||
}
|
||||
|
||||
static gskpathop
|
||||
gsk_arc_curve_pathop (const GskCurve *curve)
|
||||
{
|
||||
const GskArcCurve *self = &curve->arc;
|
||||
|
||||
return gsk_pathop_encode (self->op, self->points);
|
||||
}
|
||||
|
||||
static const graphene_point_t *
|
||||
gsk_arc_curve_get_start_point (const GskCurve *curve)
|
||||
{
|
||||
const GskArcCurve *self = &curve->arc;
|
||||
|
||||
return &self->points[0];
|
||||
}
|
||||
|
||||
static const graphene_point_t *
|
||||
gsk_arc_curve_get_end_point (const GskCurve *curve)
|
||||
{
|
||||
const GskArcCurve *self = &curve->arc;
|
||||
|
||||
return &self->points[2];
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_arc_curve_get_start_tangent (const GskCurve *curve,
|
||||
graphene_vec2_t *tangent)
|
||||
{
|
||||
const GskArcCurve *self = &curve->arc;
|
||||
|
||||
get_tangent (&self->points[0], &self->points[1], tangent);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_arc_curve_get_end_tangent (const GskCurve *curve,
|
||||
graphene_vec2_t *tangent)
|
||||
{
|
||||
const GskArcCurve *self = &curve->arc;
|
||||
|
||||
get_tangent (&self->points[1], &self->points[2], tangent);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_arc_curve_get_point (const GskCurve *curve,
|
||||
float t,
|
||||
graphene_point_t *pos)
|
||||
{
|
||||
const GskArcCurve *self = &curve->arc;
|
||||
float s, c;
|
||||
|
||||
gsk_arc_curve_ensure_matrix (self);
|
||||
|
||||
_sincosf (t * M_PI_2, &s, &c);
|
||||
graphene_matrix_transform_point (&self->m, &GRAPHENE_POINT_INIT (c, s), pos);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_arc_curve_get_tangent (const GskCurve *curve,
|
||||
float t,
|
||||
graphene_vec2_t *tangent)
|
||||
{
|
||||
const GskArcCurve *self = &curve->arc;
|
||||
graphene_vec3_t tmp, tmp2;
|
||||
float s, c;
|
||||
|
||||
gsk_arc_curve_ensure_matrix (self);
|
||||
|
||||
_sincosf (t * M_PI_2, &s, &c);
|
||||
graphene_matrix_transform_vec3 (&self->m, graphene_vec3_init (&tmp, -s, c, 0), &tmp2);
|
||||
graphene_vec2_init (tangent, graphene_vec3_get_x (&tmp2), graphene_vec3_get_y (&tmp2));
|
||||
graphene_vec2_normalize (tangent, tangent);
|
||||
}
|
||||
|
||||
static float
|
||||
gsk_arc_curve_get_curvature (const GskCurve *curve,
|
||||
float t)
|
||||
{
|
||||
float t2;
|
||||
graphene_point_t p, p2, q;
|
||||
graphene_vec2_t tan, tan2;
|
||||
|
||||
if (t < 1)
|
||||
t2 = CLAMP (t + 0.05, 0, 1);
|
||||
else
|
||||
t2 = CLAMP (t - 0.05, 0, 1);
|
||||
|
||||
gsk_arc_curve_get_point (curve, t, &p);
|
||||
gsk_arc_curve_get_tangent (curve, t, &tan);
|
||||
gsk_arc_curve_get_point (curve, t2, &p2);
|
||||
gsk_arc_curve_get_tangent (curve, t2, &tan2);
|
||||
|
||||
graphene_vec2_init (&tan, - graphene_vec2_get_y (&tan), graphene_vec2_get_x (&tan));
|
||||
graphene_vec2_init (&tan2, - graphene_vec2_get_y (&tan2), graphene_vec2_get_x (&tan2));
|
||||
|
||||
line_intersection (&p, &tan, &p2, &tan2, &q);
|
||||
|
||||
return 1.f / graphene_point_distance (&p, &q, NULL, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_arc_curve_reverse (const GskCurve *curve,
|
||||
GskCurve *reverse)
|
||||
{
|
||||
const GskArcCurve *self = &curve->arc;
|
||||
|
||||
reverse->op = GSK_PATH_ARC;
|
||||
reverse->arc.points[0] = self->points[2];
|
||||
reverse->arc.points[1] = self->points[1];
|
||||
reverse->arc.points[2] = self->points[0];
|
||||
reverse->arc.has_matrix = FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_arc_curve_split (const GskCurve *curve,
|
||||
float progress,
|
||||
GskCurve *start,
|
||||
GskCurve *end)
|
||||
{
|
||||
const graphene_point_t *p0, *p1;
|
||||
graphene_point_t p, q;
|
||||
graphene_vec2_t t0, t1, t;
|
||||
|
||||
p0 = gsk_curve_get_start_point (curve);
|
||||
gsk_curve_get_start_tangent (curve, &t0);
|
||||
|
||||
p1 = gsk_curve_get_end_point (curve);
|
||||
gsk_curve_get_end_tangent (curve, &t1);
|
||||
|
||||
gsk_arc_curve_get_point (curve, progress, &p);
|
||||
gsk_arc_curve_get_tangent (curve, progress, &t);
|
||||
|
||||
if (start)
|
||||
{
|
||||
if (line_intersection (p0, &t0, &p, &t, &q))
|
||||
gsk_arc_curve_init_from_points ((GskArcCurve *)start,
|
||||
(const graphene_point_t[3]) { *p0, q, p });
|
||||
else
|
||||
gsk_line_curve_init_from_points ((GskLineCurve *)start, GSK_PATH_LINE, p0, &p);
|
||||
}
|
||||
|
||||
if (end)
|
||||
{
|
||||
if (line_intersection (&p, &t, p1, &t1, &q))
|
||||
gsk_arc_curve_init_from_points ((GskArcCurve *)end,
|
||||
(const graphene_point_t[3]) { p, q, *p1 });
|
||||
else
|
||||
gsk_line_curve_init_from_points ((GskLineCurve *)end, GSK_PATH_LINE, &p, p1);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_arc_curve_segment (const GskCurve *curve,
|
||||
float start,
|
||||
float end,
|
||||
GskCurve *segment)
|
||||
{
|
||||
graphene_point_t p0, p1, q;
|
||||
graphene_vec2_t t0, t1;
|
||||
|
||||
if (start <= 0.0f)
|
||||
return gsk_arc_curve_split (curve, end, segment, NULL);
|
||||
else if (end >= 1.0f)
|
||||
return gsk_arc_curve_split (curve, start, NULL, segment);
|
||||
|
||||
gsk_curve_get_point (curve, start, &p0);
|
||||
gsk_curve_get_tangent (curve, start, &t0);
|
||||
gsk_curve_get_point (curve, end, &p1);
|
||||
gsk_curve_get_tangent (curve, end, &t1);
|
||||
|
||||
if (line_intersection (&p0, &t0, &p1, &t1, &q))
|
||||
gsk_arc_curve_init_from_points ((GskArcCurve *)segment,
|
||||
(const graphene_point_t[3]) { p0, q, p1 });
|
||||
else
|
||||
gsk_line_curve_init_from_points ((GskLineCurve *)segment, GSK_PATH_LINE, &p0, &p1);
|
||||
}
|
||||
|
||||
/* taken from Skia, including the very descriptive name */
|
||||
static gboolean
|
||||
gsk_arc_curve_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 gboolean
|
||||
gsk_arc_curve_decompose_subdivide (const GskArcCurve *self,
|
||||
float tolerance,
|
||||
const graphene_point_t *start,
|
||||
float start_progress,
|
||||
const graphene_point_t *end,
|
||||
float end_progress,
|
||||
GskCurveAddLineFunc add_line_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
graphene_point_t mid;
|
||||
float mid_progress;
|
||||
|
||||
mid_progress = (start_progress + end_progress) / 2;
|
||||
gsk_arc_curve_get_point ((const GskCurve *)self, mid_progress, &mid);
|
||||
|
||||
if (!gsk_arc_curve_too_curvy (start, &mid, end, tolerance))
|
||||
return add_line_func (start, end, start_progress, end_progress, GSK_CURVE_LINE_REASON_STRAIGHT, user_data);
|
||||
if (end_progress - start_progress <= MIN_PROGRESS)
|
||||
return add_line_func (start, end, start_progress, end_progress, GSK_CURVE_LINE_REASON_SHORT, user_data);
|
||||
|
||||
return gsk_arc_curve_decompose_subdivide (self, tolerance,
|
||||
start, start_progress, &mid, mid_progress,
|
||||
add_line_func, user_data)
|
||||
&& gsk_arc_curve_decompose_subdivide (self, tolerance,
|
||||
&mid, mid_progress, end, end_progress,
|
||||
add_line_func, user_data);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_arc_curve_decompose (const GskCurve *curve,
|
||||
float tolerance,
|
||||
GskCurveAddLineFunc add_line_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
const GskArcCurve *self = &curve->arc;
|
||||
graphene_point_t mid;
|
||||
|
||||
gsk_arc_curve_get_point (curve, 0.5, &mid);
|
||||
|
||||
return gsk_arc_curve_decompose_subdivide (self,
|
||||
tolerance,
|
||||
&self->points[0],
|
||||
0.0f,
|
||||
&mid,
|
||||
0.5f,
|
||||
add_line_func,
|
||||
user_data) &&
|
||||
gsk_arc_curve_decompose_subdivide (self,
|
||||
tolerance,
|
||||
&mid,
|
||||
0.5f,
|
||||
&self->points[3],
|
||||
1.0f,
|
||||
add_line_func,
|
||||
user_data);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_arc_curve_decompose_curve (const GskCurve *curve,
|
||||
GskPathForeachFlags flags,
|
||||
float tolerance,
|
||||
GskCurveAddCurveFunc add_curve_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
const GskArcCurve *self = &curve->arc;
|
||||
|
||||
gsk_arc_curve_ensure_matrix (self);
|
||||
|
||||
if (flags & GSK_PATH_FOREACH_ALLOW_ARC)
|
||||
return add_curve_func (GSK_PATH_ARC, self->points, 3, user_data);
|
||||
|
||||
if (flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
|
||||
{
|
||||
graphene_point_t p[4];
|
||||
float k = 0.55228474983;
|
||||
|
||||
p[0] = GRAPHENE_POINT_INIT (1, 0);
|
||||
p[1] = GRAPHENE_POINT_INIT (1, k);
|
||||
p[2] = GRAPHENE_POINT_INIT (k, 1);
|
||||
p[3] = GRAPHENE_POINT_INIT (0, 1);
|
||||
for (int i = 0; i < 4; i++)
|
||||
graphene_matrix_transform_point (&self->m, &p[i], &p[i]);
|
||||
|
||||
return add_curve_func (GSK_PATH_CUBIC, p, 4, user_data);
|
||||
}
|
||||
|
||||
if (flags & GSK_PATH_FOREACH_ALLOW_QUAD)
|
||||
{
|
||||
graphene_point_t p[5];
|
||||
float s, c, a;
|
||||
|
||||
_sincosf ((float) DEG_TO_RAD (45), &s, &c);
|
||||
a = (1 - c) / s;
|
||||
|
||||
p[0] = GRAPHENE_POINT_INIT (1, 0);
|
||||
p[1] = GRAPHENE_POINT_INIT (1, a);
|
||||
p[2] = GRAPHENE_POINT_INIT (c, s);
|
||||
p[3] = GRAPHENE_POINT_INIT (a, 1);
|
||||
p[4] = GRAPHENE_POINT_INIT (0, 1);
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
graphene_matrix_transform_point (&self->m, &p[i], &p[i]);
|
||||
|
||||
return add_curve_func (GSK_PATH_QUAD, p, 3, user_data) &&
|
||||
add_curve_func (GSK_PATH_QUAD, &p[2], 3, user_data);
|
||||
}
|
||||
|
||||
return gsk_arc_curve_decompose (curve,
|
||||
tolerance,
|
||||
gsk_curve_add_line_cb,
|
||||
&(AddLineData) { add_curve_func, user_data });
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_arc_curve_get_bounds (const GskCurve *curve,
|
||||
GskBoundingBox *bounds)
|
||||
{
|
||||
const GskArcCurve *self = &curve->arc;
|
||||
const graphene_point_t *pts = self->points;
|
||||
|
||||
gsk_bounding_box_init (bounds, &pts[0], &pts[2]);
|
||||
gsk_bounding_box_expand (bounds, &pts[1]);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_arc_curve_get_tight_bounds (const GskCurve *curve,
|
||||
GskBoundingBox *bounds)
|
||||
{
|
||||
// FIXME
|
||||
gsk_arc_curve_get_bounds (curve, bounds);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_arc_curve_get_derivative (const GskCurve *curve,
|
||||
GskCurve *derivative)
|
||||
{
|
||||
const GskArcCurve *self = &curve->arc;
|
||||
graphene_vec3_t t, t1, t2, t3;
|
||||
|
||||
gsk_arc_curve_ensure_matrix (self);
|
||||
|
||||
graphene_matrix_transform_vec3 (&self->m, graphene_vec3_init (&t, 0, 1, 0), &t1);
|
||||
graphene_matrix_transform_vec3 (&self->m, graphene_vec3_init (&t, -1, 1, 0), &t2);
|
||||
graphene_matrix_transform_vec3 (&self->m, graphene_vec3_init (&t, -1, 0, 0), &t3);
|
||||
|
||||
gsk_arc_curve_init_from_points ((GskArcCurve *)derivative,
|
||||
(const graphene_point_t[3]) {
|
||||
GRAPHENE_POINT_INIT (graphene_vec3_get_x (&t1), graphene_vec3_get_y (&t1)),
|
||||
GRAPHENE_POINT_INIT (graphene_vec3_get_x (&t2), graphene_vec3_get_y (&t2)),
|
||||
GRAPHENE_POINT_INIT (graphene_vec3_get_x (&t3), graphene_vec3_get_y (&t3)),
|
||||
});
|
||||
}
|
||||
|
||||
static int
|
||||
gsk_arc_curve_get_crossing (const GskCurve *curve,
|
||||
const graphene_point_t *point)
|
||||
{
|
||||
return get_crossing_by_bisection (curve, point);
|
||||
}
|
||||
|
||||
static const GskCurveClass GSK_ARC_CURVE_CLASS = {
|
||||
gsk_arc_curve_init,
|
||||
gsk_arc_curve_init_foreach,
|
||||
gsk_arc_curve_print,
|
||||
gsk_arc_curve_pathop,
|
||||
gsk_arc_curve_get_start_point,
|
||||
gsk_arc_curve_get_end_point,
|
||||
gsk_arc_curve_get_start_tangent,
|
||||
gsk_arc_curve_get_end_tangent,
|
||||
gsk_arc_curve_get_point,
|
||||
gsk_arc_curve_get_tangent,
|
||||
gsk_arc_curve_reverse,
|
||||
gsk_arc_curve_get_curvature,
|
||||
gsk_arc_curve_split,
|
||||
gsk_arc_curve_segment,
|
||||
gsk_arc_curve_decompose,
|
||||
gsk_arc_curve_decompose_curve,
|
||||
gsk_arc_curve_get_bounds,
|
||||
gsk_arc_curve_get_tight_bounds,
|
||||
gsk_arc_curve_get_derivative,
|
||||
gsk_arc_curve_get_crossing,
|
||||
};
|
||||
|
||||
/* }}} */
|
||||
/* {{{ API */
|
||||
|
||||
@@ -1251,6 +1738,7 @@ get_class (GskPathOperation op)
|
||||
[GSK_PATH_LINE] = &GSK_LINE_CURVE_CLASS,
|
||||
[GSK_PATH_QUAD] = &GSK_QUAD_CURVE_CLASS,
|
||||
[GSK_PATH_CUBIC] = &GSK_CUBIC_CURVE_CLASS,
|
||||
[GSK_PATH_ARC] = &GSK_ARC_CURVE_CLASS,
|
||||
};
|
||||
|
||||
g_assert (op < G_N_ELEMENTS (klasses) && klasses[op] != NULL);
|
||||
@@ -1509,7 +1997,7 @@ find_closest_point (const GskCurve *curve,
|
||||
d = INFINITY;
|
||||
t = (t1 + t2) / 2;
|
||||
|
||||
if (radius < 1)
|
||||
if (fabs (t1 - t2) < 0.001 || radius < 1)
|
||||
{
|
||||
graphene_point_t p;
|
||||
gsk_curve_get_point (curve, t, &p);
|
||||
|
||||
@@ -33,6 +33,7 @@ typedef union _GskCurve GskCurve;
|
||||
typedef struct _GskLineCurve GskLineCurve;
|
||||
typedef struct _GskQuadCurve GskQuadCurve;
|
||||
typedef struct _GskCubicCurve GskCubicCurve;
|
||||
typedef struct _GskArcCurve GskArcCurve;
|
||||
|
||||
struct _GskLineCurve
|
||||
{
|
||||
@@ -65,12 +66,24 @@ struct _GskCubicCurve
|
||||
graphene_point_t coeffs[4];
|
||||
};
|
||||
|
||||
struct _GskArcCurve
|
||||
{
|
||||
GskPathOperation op;
|
||||
|
||||
gboolean has_matrix;
|
||||
|
||||
graphene_point_t points[3];
|
||||
|
||||
graphene_matrix_t m;
|
||||
};
|
||||
|
||||
union _GskCurve
|
||||
{
|
||||
GskPathOperation op;
|
||||
GskLineCurve line;
|
||||
GskQuadCurve quad;
|
||||
GskCubicCurve cubic;
|
||||
GskArcCurve arc;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -281,6 +281,10 @@ typedef enum {
|
||||
* @GSK_PATH_CUBIC: 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_ARC: A curve-to operation describing an elliptical arc with 3 points
|
||||
* (more precisely, 2 points with their tangents). Note that an ellipse is not
|
||||
* uniquely determined by this data; GTK picks the ellipse segment that is the
|
||||
* affine transform of a quarter circle.
|
||||
*
|
||||
* Path operations are used to described segments of a `GskPath`.
|
||||
*
|
||||
@@ -294,6 +298,7 @@ typedef enum {
|
||||
GSK_PATH_LINE,
|
||||
GSK_PATH_QUAD,
|
||||
GSK_PATH_CUBIC,
|
||||
GSK_PATH_ARC,
|
||||
} GskPathOperation;
|
||||
|
||||
/**
|
||||
|
||||
@@ -178,7 +178,8 @@ gsk_path_get_flags (const GskPath *self)
|
||||
* for printing.
|
||||
*
|
||||
* The string is compatible with
|
||||
* [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData).
|
||||
* [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData),
|
||||
* see [func@Gsk.Path.parse] for a summary of the syntax.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
@@ -249,21 +250,12 @@ gsk_path_to_cairo_add_op (GskPathOperation op,
|
||||
cairo_line_to (cr, pts[1].x, pts[1].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
{
|
||||
double x, y;
|
||||
cairo_get_current_point (cr, &x, &y);
|
||||
cairo_curve_to (cr,
|
||||
1/3.f * x + 2/3.f * pts[1].x, 1/3.f * y + 2/3.f * pts[1].y,
|
||||
2/3.f * pts[1].x + 1/3.f * pts[2].x, 2/3.f * pts[1].y + 1/3.f * pts[2].y,
|
||||
pts[2].x, pts[2].y);
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
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_QUAD:
|
||||
case GSK_PATH_ARC:
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
return FALSE;
|
||||
@@ -749,14 +741,36 @@ gsk_path_foreach_trampoline (GskPathOperation op,
|
||||
trampoline);
|
||||
}
|
||||
|
||||
case GSK_PATH_ARC:
|
||||
{
|
||||
GskCurve curve;
|
||||
|
||||
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_ARC)
|
||||
return trampoline->func (op, pts, n_pts, trampoline->user_data);
|
||||
|
||||
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_ARC, pts));
|
||||
if (trampoline->flags & (GSK_PATH_FOREACH_ALLOW_CUBIC|GSK_PATH_FOREACH_ALLOW_QUAD))
|
||||
return gsk_curve_decompose_curve (&curve,
|
||||
trampoline->flags,
|
||||
trampoline->tolerance,
|
||||
gsk_path_foreach_trampoline_add_curve,
|
||||
trampoline);
|
||||
|
||||
return gsk_curve_decompose (&curve,
|
||||
trampoline->tolerance,
|
||||
gsk_path_foreach_trampoline_add_line,
|
||||
trampoline);
|
||||
}
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
#define ALLOW_ANY (GSK_PATH_FOREACH_ALLOW_QUAD| \
|
||||
GSK_PATH_FOREACH_ALLOW_CUBIC)
|
||||
#define ALLOW_ANY (GSK_PATH_FOREACH_ALLOW_QUAD | \
|
||||
GSK_PATH_FOREACH_ALLOW_CUBIC | \
|
||||
GSK_PATH_FOREACH_ALLOW_ARC)
|
||||
|
||||
gboolean
|
||||
gsk_path_foreach_with_tolerance (GskPath *self,
|
||||
@@ -914,7 +928,7 @@ parse_command (const char **p,
|
||||
if (*cmd == 'X')
|
||||
allowed = "mM";
|
||||
else
|
||||
allowed = "mMhHvVzZlLcCsStTqQaA";
|
||||
allowed = "mMhHvVzZlLcCsStTqQaAeE";
|
||||
|
||||
skip_whitespace (p);
|
||||
s = _strchr (allowed, **p);
|
||||
@@ -934,7 +948,7 @@ parse_command (const char **p,
|
||||
* This is a convenience function that constructs a `GskPath`
|
||||
* from a serialized form.
|
||||
*
|
||||
* The string is expected to be in
|
||||
* The string is expected to be in (a superset of)
|
||||
* [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData),
|
||||
* as e.g. produced by [method@Gsk.Path.to_string].
|
||||
*
|
||||
@@ -950,10 +964,13 @@ parse_command (const char **p,
|
||||
* - `T x2 y2` Add a quadratic Bézier, using the reflection of the previous segments' control point as control point
|
||||
* - `S x2 y2 x3 y3` Add a cubic Bézier, using the reflection of the previous segments' second control point as first control point
|
||||
* - `A rx ry r l s x y` Add an elliptical arc from the current point to `(x, y)` with radii rx and ry. See the SVG documentation for how the other parameters influence the arc.
|
||||
* - `E x1 y1 x2 y2` Add an elliptical arc from the current point to `(x2, y2)` with tangents that are dermined by the point `(x1, y1)`.
|
||||
*
|
||||
* All the commands have lowercase variants that interpret coordinates
|
||||
* relative to the current point.
|
||||
*
|
||||
* The `E` command is an extension that is not supported in SVG.
|
||||
*
|
||||
* Returns: (nullable): a new `GskPath`, or `NULL`
|
||||
* if @string could not be parsed
|
||||
*
|
||||
@@ -1298,6 +1315,38 @@ gsk_path_parse (const char *string)
|
||||
}
|
||||
break;
|
||||
|
||||
case 'E':
|
||||
case 'e':
|
||||
{
|
||||
double x1, y1, x2, y2;
|
||||
|
||||
if (parse_coordinate_pair (&p, &x1, &y1) &&
|
||||
parse_coordinate_pair (&p, &x2, &y2))
|
||||
{
|
||||
if (cmd == 'e')
|
||||
{
|
||||
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_arc_to (builder, x1, y1, x2, y2);
|
||||
prev_x1 = x1;
|
||||
prev_y1 = y1;
|
||||
x = x2;
|
||||
y = y2;
|
||||
}
|
||||
else
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ G_BEGIN_DECLS
|
||||
* @GSK_PATH_FOREACH_ALLOW_ONLY_LINES: The default behavior, only allow lines.
|
||||
* @GSK_PATH_FOREACH_ALLOW_QUAD: Allow emission of `GSK_PATH_QUAD` operations
|
||||
* @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.
|
||||
@@ -48,6 +49,7 @@ typedef enum
|
||||
GSK_PATH_FOREACH_ALLOW_ONLY_LINES = 0,
|
||||
GSK_PATH_FOREACH_ALLOW_QUAD = (1 << 0),
|
||||
GSK_PATH_FOREACH_ALLOW_CUBIC = (1 << 1),
|
||||
GSK_PATH_FOREACH_ALLOW_ARC = (1 << 2),
|
||||
} GskPathForeachFlags;
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
#include "gskpathprivate.h"
|
||||
#include "gskcontourprivate.h"
|
||||
#include "gsksplineprivate.h"
|
||||
|
||||
/**
|
||||
* GskPathBuilder:
|
||||
@@ -502,64 +501,46 @@ gsk_path_builder_add_rounded_rect (GskPathBuilder *self,
|
||||
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_TOP_RIGHT].width,
|
||||
rect->bounds.origin.y);
|
||||
/* topright corner */
|
||||
gsk_path_builder_svg_arc_to (self,
|
||||
rect->corner[GSK_CORNER_TOP_RIGHT].width,
|
||||
rect->corner[GSK_CORNER_TOP_RIGHT].height,
|
||||
0, FALSE, TRUE,
|
||||
rect->bounds.origin.x + rect->bounds.size.width,
|
||||
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_RIGHT].height);
|
||||
gsk_path_builder_arc_to (self,
|
||||
rect->bounds.origin.x + rect->bounds.size.width,
|
||||
rect->bounds.origin.y,
|
||||
rect->bounds.origin.x + rect->bounds.size.width,
|
||||
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_RIGHT].height);
|
||||
/* right */
|
||||
gsk_path_builder_line_to (self,
|
||||
rect->bounds.origin.x + rect->bounds.size.width,
|
||||
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_RIGHT].height);
|
||||
/* bottomright corner */
|
||||
gsk_path_builder_svg_arc_to (self,
|
||||
rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
|
||||
rect->corner[GSK_CORNER_BOTTOM_RIGHT].height,
|
||||
0, FALSE, TRUE,
|
||||
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
|
||||
rect->bounds.origin.y + rect->bounds.size.height);
|
||||
gsk_path_builder_arc_to (self,
|
||||
rect->bounds.origin.x + rect->bounds.size.width,
|
||||
rect->bounds.origin.y + rect->bounds.size.height,
|
||||
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
|
||||
rect->bounds.origin.y + rect->bounds.size.height);
|
||||
/* bottom */
|
||||
gsk_path_builder_line_to (self,
|
||||
rect->bounds.origin.x + rect->corner[GSK_CORNER_BOTTOM_LEFT].width,
|
||||
rect->bounds.origin.y + rect->bounds.size.height);
|
||||
/* bottomleft corner */
|
||||
gsk_path_builder_svg_arc_to (self,
|
||||
rect->corner[GSK_CORNER_BOTTOM_LEFT].width,
|
||||
rect->corner[GSK_CORNER_BOTTOM_LEFT].height,
|
||||
0, FALSE, TRUE,
|
||||
rect->bounds.origin.x,
|
||||
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_LEFT].height);
|
||||
gsk_path_builder_arc_to (self,
|
||||
rect->bounds.origin.x,
|
||||
rect->bounds.origin.y + rect->bounds.size.height,
|
||||
rect->bounds.origin.x,
|
||||
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_LEFT].height);
|
||||
/* left */
|
||||
gsk_path_builder_line_to (self,
|
||||
rect->bounds.origin.x,
|
||||
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_LEFT].height);
|
||||
/* topleft corner */
|
||||
gsk_path_builder_svg_arc_to (self,
|
||||
rect->corner[GSK_CORNER_TOP_LEFT].width,
|
||||
rect->corner[GSK_CORNER_TOP_LEFT].height,
|
||||
0, FALSE, TRUE,
|
||||
rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width,
|
||||
rect->bounds.origin.y);
|
||||
gsk_path_builder_arc_to (self,
|
||||
rect->bounds.origin.x,
|
||||
rect->bounds.origin.y,
|
||||
rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width,
|
||||
rect->bounds.origin.y);
|
||||
/* done */
|
||||
gsk_path_builder_close (self);
|
||||
self->current_point = current;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
circle_contour_curve (const graphene_point_t pts[4],
|
||||
gpointer data)
|
||||
{
|
||||
GskPathBuilder *self = data;
|
||||
|
||||
gsk_path_builder_cubic_to (self,
|
||||
pts[1].x, pts[1].y,
|
||||
pts[2].x, pts[2].y,
|
||||
pts[3].x, pts[3].y);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_builder_add_circle:
|
||||
* @self: a `GskPathBuilder`
|
||||
@@ -586,11 +567,19 @@ gsk_path_builder_add_circle (GskPathBuilder *self,
|
||||
current = self->current_point;
|
||||
|
||||
gsk_path_builder_move_to (self, center->x + radius, center->y);
|
||||
gsk_spline_decompose_arc (center, radius,
|
||||
GSK_PATH_TOLERANCE_DEFAULT,
|
||||
0, 2 * M_PI,
|
||||
circle_contour_curve, self);
|
||||
|
||||
// bottom right quarter
|
||||
gsk_path_builder_arc_to (self, center->x + radius, center->y + radius,
|
||||
center->x, center->y + radius);
|
||||
// bottom left quarter
|
||||
gsk_path_builder_arc_to (self, center->x - radius, center->y + radius,
|
||||
center->x - radius, center->y);
|
||||
// top left quarter
|
||||
gsk_path_builder_arc_to (self, center->x - radius, center->y - radius,
|
||||
center->x, center->y - radius);
|
||||
// top right quarter
|
||||
gsk_path_builder_arc_to (self, center->x + radius, center->y - radius,
|
||||
center->x + radius, center->y);
|
||||
// done
|
||||
gsk_path_builder_close (self);
|
||||
self->current_point = current;
|
||||
}
|
||||
@@ -861,6 +850,120 @@ gsk_path_builder_rel_cubic_to (GskPathBuilder *self,
|
||||
self->current_point.y + y3);
|
||||
}
|
||||
|
||||
/* Return the angle between t1 and t2 in radians, such that
|
||||
* 0 means straight continuation
|
||||
* < 0 means right turn
|
||||
* > 0 means left turn
|
||||
*/
|
||||
static float
|
||||
angle_between (const graphene_vec2_t *t1,
|
||||
const graphene_vec2_t *t2)
|
||||
{
|
||||
float angle = atan2 (graphene_vec2_get_y (t2), graphene_vec2_get_x (t2))
|
||||
- atan2 (graphene_vec2_get_y (t1), graphene_vec2_get_x (t1));
|
||||
|
||||
if (angle > M_PI)
|
||||
angle -= 2 * M_PI;
|
||||
if (angle < - M_PI)
|
||||
angle += 2 * M_PI;
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
#define RAD_TO_DEG(r) ((r)*180.f/M_PI)
|
||||
#define DEG_TO_RAD(d) ((d)*M_PI/180.f)
|
||||
|
||||
static float
|
||||
angle_between_points (const graphene_point_t *c,
|
||||
const graphene_point_t *a,
|
||||
const graphene_point_t *b)
|
||||
{
|
||||
graphene_vec2_t t1, t2;
|
||||
|
||||
graphene_vec2_init (&t1, a->x - c->x, a->y - c->y);
|
||||
graphene_vec2_init (&t2, b->x - c->x, b->y - c->y);
|
||||
|
||||
return (float) RAD_TO_DEG (angle_between (&t1, &t2));
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_builder_arc_to:
|
||||
* @self: a `GskPathBuilder`
|
||||
* @x1: x coordinate of first control point
|
||||
* @y1: y coordinate of first control point
|
||||
* @x2: x coordinate of second control point
|
||||
* @y2: y coordinate of second control point
|
||||
*
|
||||
* Adds an elliptical arc from the current point to @x3, @y3
|
||||
* with @x1, @y1 determining the tangent directions.
|
||||
*
|
||||
* After this, @x3, @y3 will be the new current point.
|
||||
*
|
||||
* <picture>
|
||||
* <source srcset="arc-dark.png" media="(prefers-color-scheme: dark)">
|
||||
* <img alt="Arc To" src="arc-light.png">
|
||||
* </picture>
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gsk_path_builder_arc_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
if (fabsf (angle_between_points (&GRAPHENE_POINT_INIT (x1, y1),
|
||||
&self->current_point,
|
||||
&GRAPHENE_POINT_INIT (x2, y2))) < 3)
|
||||
{
|
||||
gsk_path_builder_line_to (self, x2, y2);
|
||||
return;
|
||||
}
|
||||
|
||||
self->flags &= ~GSK_PATH_FLAT;
|
||||
gsk_path_builder_append_current (self,
|
||||
GSK_PATH_ARC,
|
||||
2, (graphene_point_t[2]) {
|
||||
GRAPHENE_POINT_INIT (x1, y1),
|
||||
GRAPHENE_POINT_INIT (x2, y2),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_builder_rel_arc_to:
|
||||
* @self: a `GskPathBuilder`
|
||||
* @x1: x coordinate of first control point
|
||||
* @y1: y coordinate of first control point
|
||||
* @x2: x coordinate of second control point
|
||||
* @y2: y coordinate of second control point
|
||||
*
|
||||
* Adds an elliptical arc from the current point to @x3, @y3
|
||||
* with @x1, @y1 determining the tangent directions. All coordinates
|
||||
* are given relative to the current point.
|
||||
*
|
||||
* This is the relative version of [method@Gsk.PathBuilder.arc_to].
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gsk_path_builder_rel_arc_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
gsk_path_builder_arc_to (self,
|
||||
self->current_point.x + x1,
|
||||
self->current_point.y + y1,
|
||||
self->current_point.x + x2,
|
||||
self->current_point.y + y2);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_builder_close:
|
||||
* @self: a `GskPathBuilder`
|
||||
@@ -938,6 +1041,25 @@ _sincos (double angle,
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_builder_svg_arc_to:
|
||||
* @self: a `GskPathBuilder`
|
||||
* @rx: X radius
|
||||
* @ry: Y radius
|
||||
* @x_axis_rotation: the rotation of the ellipsis
|
||||
* @large_arc: whether to add the large arc
|
||||
* @positive_sweep: whether to sweep in the positive direction
|
||||
* @x: the X coordinate of the endpoint
|
||||
* @y: the Y coordinate of the endpoint
|
||||
*
|
||||
* Implements arc-to according to the SVG spec.
|
||||
*
|
||||
* A convenience function that implements the
|
||||
* [SVG arc_to](https://www.w3.org/TR/SVG11/paths.html#PathDataEllipticalArcCommands)
|
||||
* functionality.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gsk_path_builder_svg_arc_to (GskPathBuilder *self,
|
||||
float rx,
|
||||
@@ -970,6 +1092,8 @@ gsk_path_builder_svg_arc_to (GskPathBuilder *self,
|
||||
double th_half;
|
||||
double t;
|
||||
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
if (self->points->len > 0)
|
||||
{
|
||||
current = &g_array_index (self->points, graphene_point_t, self->points->len - 1);
|
||||
@@ -1069,6 +1193,67 @@ gsk_path_builder_svg_arc_to (GskPathBuilder *self,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_builder_html_arc_to:
|
||||
* @self: a `GskPathBuilder`
|
||||
* @x1: X coordinate of first control point
|
||||
* @y1: Y coordinate of first control point
|
||||
* @x2: X coordinate of second control point
|
||||
* @y2: Y coordinate of second control point
|
||||
* @radius: Radius of the circle
|
||||
*
|
||||
* Implements arc-to according to the HTML Canvas spec.
|
||||
*
|
||||
* A convenience function that implements the
|
||||
* [HTML arc_to](https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-arcto-dev)
|
||||
* functionality.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
void
|
||||
gsk_path_builder_html_arc_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2,
|
||||
float radius)
|
||||
{
|
||||
float angle, b;
|
||||
graphene_vec2_t t;
|
||||
graphene_point_t p, q;
|
||||
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (radius > 0);
|
||||
|
||||
angle = angle_between_points (&GRAPHENE_POINT_INIT (x1, y1),
|
||||
&self->current_point,
|
||||
&GRAPHENE_POINT_INIT (x2, y2));
|
||||
|
||||
if (fabsf (angle) < 3)
|
||||
{
|
||||
gsk_path_builder_line_to (self, x2, y2);
|
||||
return;
|
||||
}
|
||||
|
||||
b = radius / tanf (fabsf ((float) DEG_TO_RAD (angle / 2)));
|
||||
|
||||
graphene_vec2_init (&t, self->current_point.x - x1, self->current_point.y - y1);
|
||||
graphene_vec2_normalize (&t, &t);
|
||||
|
||||
p.x = x1 + b * graphene_vec2_get_x (&t);
|
||||
p.y = y1 + b * graphene_vec2_get_y (&t);
|
||||
|
||||
graphene_vec2_init (&t, x2 - x1, y2 - y1);
|
||||
graphene_vec2_normalize (&t, &t);
|
||||
|
||||
q.x = x1 + b * graphene_vec2_get_x (&t);
|
||||
q.y = y1 + b * graphene_vec2_get_y (&t);
|
||||
|
||||
gsk_path_builder_line_to (self, p.x, p.y);
|
||||
|
||||
gsk_path_builder_svg_arc_to (self, radius, radius, 0, FALSE, angle < 0, q.x, q.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_builder_add_layout:
|
||||
* @self: a #GskPathBuilder
|
||||
|
||||
@@ -121,6 +121,37 @@ void gsk_path_builder_rel_cubic_to (GskPathBuilder
|
||||
float x3,
|
||||
float y3);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_arc_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2);
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_rel_arc_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_svg_arc_to (GskPathBuilder *self,
|
||||
float rx,
|
||||
float ry,
|
||||
float x_axis_rotation,
|
||||
gboolean large_arc,
|
||||
gboolean positive_sweep,
|
||||
float x,
|
||||
float y);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_html_arc_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2,
|
||||
float radius);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
void gsk_path_builder_close (GskPathBuilder *self);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathBuilder, gsk_path_builder_unref)
|
||||
|
||||
@@ -94,6 +94,9 @@ gsk_pathop_foreach (gskpathop pop,
|
||||
case GSK_PATH_CUBIC:
|
||||
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 4, user_data);
|
||||
|
||||
case GSK_PATH_ARC:
|
||||
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 3, user_data);
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
return TRUE;
|
||||
@@ -128,6 +131,10 @@ gsk_path_builder_pathop_to (GskPathBuilder *builder,
|
||||
gsk_path_builder_cubic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_ARC:
|
||||
gsk_path_builder_arc_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
@@ -162,6 +169,10 @@ gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
|
||||
gsk_path_builder_cubic_to (builder, pts[2].x, pts[2].y, pts[1].x, pts[1].y, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_ARC:
|
||||
gsk_path_builder_arc_to (builder, pts[1].x, pts[1].y, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
|
||||
@@ -56,15 +56,5 @@ gboolean gsk_path_foreach_with_tolerance (GskPath
|
||||
void gsk_path_builder_add_contour (GskPathBuilder *builder,
|
||||
GskContour *contour);
|
||||
|
||||
void gsk_path_builder_svg_arc_to (GskPathBuilder *builder,
|
||||
float rx,
|
||||
float ry,
|
||||
float x_axis_rotation,
|
||||
gboolean large_arc,
|
||||
gboolean positive_sweep,
|
||||
float x,
|
||||
float y);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
||||
@@ -145,6 +145,28 @@ test_curve_crossing (void)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_arc (void)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_point_t p;
|
||||
|
||||
parse_curve (&c, "M 1 0 E 1 1 0 1");
|
||||
g_assert_true (graphene_point_equal (gsk_curve_get_start_point (&c), &GRAPHENE_POINT_INIT (1, 0)));
|
||||
g_assert_true (graphene_point_equal (gsk_curve_get_end_point (&c), &GRAPHENE_POINT_INIT (0, 1)));
|
||||
gsk_curve_get_point (&c, 0.5, &p);
|
||||
g_assert_true (graphene_point_near (&p,
|
||||
&GRAPHENE_POINT_INIT (cos (M_PI/4), sin (M_PI/4)), 0.001));
|
||||
|
||||
|
||||
parse_curve (&c, "M 100 100 E 200 100 200 200");
|
||||
g_assert_true (graphene_point_equal (gsk_curve_get_start_point (&c), &GRAPHENE_POINT_INIT (100, 100)));
|
||||
g_assert_true (graphene_point_equal (gsk_curve_get_end_point (&c), &GRAPHENE_POINT_INIT (200, 200)));
|
||||
gsk_curve_get_point (&c, 0.5, &p);
|
||||
g_assert_true (graphene_point_near (&p,
|
||||
&GRAPHENE_POINT_INIT (100 + 100 * sin (M_PI/4), 100 + 100 * (1 - cos (M_PI/4))), 0.001));
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
@@ -154,6 +176,7 @@ main (int argc,
|
||||
g_test_add_func ("/curve/special/tangents", test_curve_tangents);
|
||||
g_test_add_func ("/curve/special/degenerate-tangents", test_curve_degenerate_tangents);
|
||||
g_test_add_func ("/curve/special/crossing", test_curve_crossing);
|
||||
g_test_add_func ("/curve/special/arc", test_arc);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
||||
@@ -249,6 +249,9 @@ test_curve_decompose_into (GskPathForeachFlags flags)
|
||||
case GSK_PATH_CUBIC:
|
||||
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_CUBIC);
|
||||
break;
|
||||
case GSK_PATH_ARC:
|
||||
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_ARC);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
@@ -373,6 +373,11 @@ collect_path (GskPathOperation op,
|
||||
pts[3].x, pts[3].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_ARC:
|
||||
gsk_path_builder_arc_to (builder, pts[1].x, pts[1].y,
|
||||
pts[2].x, pts[2].y);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
@@ -689,6 +694,118 @@ test_path_builder_add (void)
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
rotate_path_cb (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
gpointer user_data)
|
||||
{
|
||||
GskPathBuilder **builders = user_data;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
gsk_path_builder_move_to (builders[0], pts[0].x, pts[0].y);
|
||||
gsk_path_builder_move_to (builders[1], pts[0].y, -pts[0].x);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
gsk_path_builder_close (builders[0]);
|
||||
gsk_path_builder_close (builders[1]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
gsk_path_builder_line_to (builders[0], pts[1].x, pts[1].y);
|
||||
gsk_path_builder_line_to (builders[1], pts[1].y, -pts[1].x);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
gsk_path_builder_quad_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y);
|
||||
gsk_path_builder_quad_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
gsk_path_builder_cubic_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
|
||||
gsk_path_builder_cubic_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_ARC:
|
||||
gsk_path_builder_arc_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y);
|
||||
gsk_path_builder_arc_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_rotated_arc (void)
|
||||
{
|
||||
GskPath *path;
|
||||
GskPathBuilder *builders[2];
|
||||
GskPath *paths[2];
|
||||
float x, y;
|
||||
GskFillRule fill_rule;
|
||||
|
||||
path = gsk_path_parse ("M -963 186 E -375 -757, 537 -607");
|
||||
|
||||
x = -626;
|
||||
y = -274;
|
||||
|
||||
builders[0] = gsk_path_builder_new ();
|
||||
builders[1] = gsk_path_builder_new ();
|
||||
|
||||
/* Use -1 here because we want all the flags, even future additions */
|
||||
gsk_path_foreach (path, -1, rotate_path_cb, builders);
|
||||
gsk_path_unref (path);
|
||||
|
||||
paths[0] = gsk_path_builder_free_to_path (builders[0]);
|
||||
paths[1] = gsk_path_builder_free_to_path (builders[1]);
|
||||
|
||||
fill_rule = GSK_FILL_RULE_EVEN_ODD;
|
||||
|
||||
g_assert_true (gsk_path_in_fill (paths[0], &GRAPHENE_POINT_INIT (x, y), fill_rule)
|
||||
==
|
||||
gsk_path_in_fill (paths[1], &GRAPHENE_POINT_INIT (y, -x), fill_rule));
|
||||
|
||||
gsk_path_unref (paths[0]);
|
||||
gsk_path_unref (paths[1]);
|
||||
}
|
||||
|
||||
static void
|
||||
test_rounded_rect (void)
|
||||
{
|
||||
GskRoundedRect rect;
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path;
|
||||
|
||||
gsk_rounded_rect_init (&rect, &GRAPHENE_RECT_INIT (10, 10, 100, 50),
|
||||
&GRAPHENE_SIZE_INIT (0, 0),
|
||||
&GRAPHENE_SIZE_INIT (10, 10),
|
||||
&GRAPHENE_SIZE_INIT (10, 30),
|
||||
&GRAPHENE_SIZE_INIT (30, 0));
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
|
||||
gsk_path_builder_add_rounded_rect (builder, &rect);
|
||||
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
graphene_point_t p = GRAPHENE_POINT_INIT (g_test_rand_double_range (0, 200),
|
||||
g_test_rand_double_range (0, 200));
|
||||
|
||||
g_assert_true (gsk_rounded_rect_contains_point (&rect, &p) == gsk_path_in_fill (path, &p, GSK_FILL_RULE_WINDING));
|
||||
}
|
||||
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
@@ -703,6 +820,8 @@ main (int argc, char *argv[])
|
||||
g_test_add_func ("/path/bad-in-fill", test_bad_in_fill);
|
||||
g_test_add_func ("/path/unclosed-in-fill", test_unclosed_in_fill);
|
||||
g_test_add_func ("/path/builder/add", test_path_builder_add);
|
||||
g_test_add_func ("/path/rotated-arc", test_rotated_arc);
|
||||
g_test_add_func ("/path/rounded-rect", test_rounded_rect);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ add_standard_contour (GskPathBuilder *builder)
|
||||
n = g_test_rand_int_range (1, 20);
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
switch (g_test_rand_int_range (0, 6))
|
||||
switch (g_test_rand_int_range (0, 8))
|
||||
{
|
||||
case 0:
|
||||
gsk_path_builder_line_to (builder,
|
||||
@@ -275,6 +275,22 @@ add_standard_contour (GskPathBuilder *builder)
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 6:
|
||||
gsk_path_builder_arc_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
case 7:
|
||||
gsk_path_builder_rel_arc_to (builder,
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000),
|
||||
g_test_rand_double_range (-1000, 1000));
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
break;
|
||||
@@ -371,6 +387,13 @@ path_operation_print (const PathOperation *p,
|
||||
_g_string_append_point (string, &p->pts[3]);
|
||||
break;
|
||||
|
||||
case GSK_PATH_ARC:
|
||||
g_string_append (string, " E ");
|
||||
_g_string_append_point (string, &p->pts[1]);
|
||||
g_string_append (string, ", ");
|
||||
_g_string_append_point (string, &p->pts[2]);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
return;
|
||||
@@ -405,6 +428,10 @@ 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_ARC:
|
||||
return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon)
|
||||
&& graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon);
|
||||
|
||||
default:
|
||||
g_return_val_if_reached (FALSE);
|
||||
}
|
||||
@@ -689,6 +716,11 @@ rotate_path_cb (GskPathOperation op,
|
||||
gsk_path_builder_cubic_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_ARC:
|
||||
gsk_path_builder_arc_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y);
|
||||
gsk_path_builder_arc_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
return FALSE;
|
||||
@@ -727,7 +759,7 @@ test_in_fill_rotated (void)
|
||||
GskFillRule fill_rule = g_random_int_range (0, N_FILL_RULES);
|
||||
float x = g_test_rand_double_range (-1000, 1000);
|
||||
float y = g_test_rand_double_range (-1000, 1000);
|
||||
|
||||
|
||||
g_assert_cmpint (gsk_path_in_fill (paths[0], &GRAPHENE_POINT_INIT (x, y), fill_rule),
|
||||
==,
|
||||
gsk_path_in_fill (paths[1], &GRAPHENE_POINT_INIT (y, -x), fill_rule));
|
||||
|
||||
@@ -56,6 +56,10 @@ foreach_cb (GskPathOperation op,
|
||||
pts[3].x, pts[3].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_ARC:
|
||||
gsk_path_builder_arc_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
@@ -68,12 +72,14 @@ do_decompose (int *argc, const char ***argv)
|
||||
{
|
||||
GError *error = NULL;
|
||||
gboolean allow_quad = FALSE;
|
||||
gboolean allow_curve = FALSE;
|
||||
gboolean allow_cubic = FALSE;
|
||||
gboolean allow_arc = FALSE;
|
||||
char **args = NULL;
|
||||
GOptionContext *context;
|
||||
GOptionEntry entries[] = {
|
||||
{ "allow-quad", 0, 0, G_OPTION_ARG_NONE, &allow_quad, N_("Allow quadratic Bézier curves"), NULL },
|
||||
{ "allow-cubic", 0, 0, G_OPTION_ARG_NONE, &allow_curve, N_("Allow cubic Bézier curves"), NULL },
|
||||
{ "allow-cubic", 0, 0, G_OPTION_ARG_NONE, &allow_cubic, N_("Allow cubic Bézier curves"), NULL },
|
||||
{ "allow-arc", 0, 0, G_OPTION_ARG_NONE, &allow_arc, N_("Allow elliptical arcs"), NULL },
|
||||
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &args, NULL, N_("PATH") },
|
||||
{ NULL, },
|
||||
};
|
||||
@@ -108,8 +114,10 @@ do_decompose (int *argc, const char ***argv)
|
||||
flags = 0;
|
||||
if (allow_quad)
|
||||
flags |= GSK_PATH_FOREACH_ALLOW_QUAD;
|
||||
if (allow_curve)
|
||||
if (allow_cubic)
|
||||
flags |= GSK_PATH_FOREACH_ALLOW_CUBIC;
|
||||
if (allow_arc)
|
||||
flags |= GSK_PATH_FOREACH_ALLOW_ARC;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ typedef struct
|
||||
int lines;
|
||||
int quads;
|
||||
int cubics;
|
||||
int arcs;
|
||||
} Statistics;
|
||||
|
||||
static gboolean
|
||||
@@ -58,6 +59,9 @@ stats_cb (GskPathOperation op,
|
||||
case GSK_PATH_CUBIC:
|
||||
stats->cubics++;
|
||||
break;
|
||||
case GSK_PATH_ARC:
|
||||
stats->arcs++;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
@@ -74,6 +78,7 @@ collect_statistics (GskPath *path,
|
||||
stats->lines = 0;
|
||||
stats->quads = 0;
|
||||
stats->cubics = 0;
|
||||
stats->arcs = 0;
|
||||
|
||||
gsk_path_foreach (path, -1, stats_cb, stats);
|
||||
}
|
||||
@@ -150,5 +155,10 @@ do_info (int *argc, const char ***argv)
|
||||
g_print (_("%d cubics"), stats.cubics);
|
||||
g_print ("\n");
|
||||
}
|
||||
if (stats.arcs)
|
||||
{
|
||||
g_print (_("%d arcs"), stats.arcs);
|
||||
g_print ("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user