Compare commits

..

21 Commits

Author SHA1 Message Date
Matthias Clasen
b533dfa0da Make points stack-allocated
This does the necessary reshuffling to make GskPathPoint
a stack-allocated struct.
2023-08-05 15:45:53 -04:00
Matthias Clasen
1de378655e tools: Add gtk4-path-tool
This comes in handy for testing, among other things.

For now, this supports decomposing,
reversing, restricting, rendering and preview.
2023-08-05 15:03:20 -04:00
Benjamin Otte
9cdc549bf9 demos: Add cute maze demo 2023-08-05 15:03:20 -04:00
Benjamin Otte
9725d33196 demos: Add a text-on-path demo 2023-08-05 15:03:20 -04:00
Benjamin Otte
2384c57051 demos: Add a simple demo filling a path 2023-08-05 15:03:20 -04:00
Benjamin Otte
0d178b6a4d WIP: css: Replace border rendering code with GskPath
The weight is wrong still, I need to compute the correct one to get real
45deg circle corners and not just roughly correct ones.
2023-08-05 15:03:20 -04:00
Matthias Clasen
bb98216951 gsk: Add tests for gsk_path_dash 2023-08-05 15:03:20 -04:00
Matthias Clasen
c01f3d24ed Add gsk_path_dash
This is a function like gsk_path_foreach, but
it provides a dashed version of the path instead
of the original.
2023-08-05 15:03:20 -04:00
Benjamin Otte
068163939e snapshot: Add gtk_snapshot_push_stroke()
This is the obvious GtkSnapshot API to go
along with the new stroke nodes.
2023-08-05 15:03:20 -04:00
Benjamin Otte
cbee6ee382 gsk: Add GskStrokeNode
Take a rendernode as source and a GskPath and GskStroke,
and fill the area that is covered when stroking the path
with the given stroke parameters, like cairo_stroke() would.
2023-08-05 15:03:20 -04:00
Matthias Clasen
c4c6d72375 Add gsk_path_get_stroke_bounds
This is a help to compute the bounds for
stroke nodes. We keep it private for now.
2023-08-05 15:03:20 -04:00
Matthias Clasen
a25484bb8c Add GskStroke
A GskStroke struct collects the parameters that are
needed for stroking a path.
2023-08-05 15:03:20 -04:00
Matthias Clasen
0582da02b3 Add another curve decomposition test
This one uses GskPathMeasure to check that
our conic approximations look roughly right.
2023-08-05 15:03:20 -04:00
Matthias Clasen
1dd3986b03 Add tests for GskPathMeasure 2023-08-05 15:03:20 -04:00
Matthias Clasen
44119a7a57 Add GskPathMeasure
GskPathMeasure is an auxiliary object for
measuring path lengths.
2023-08-05 15:03:20 -04:00
Benjamin Otte
cc60d519f3 gtk: Add gtk_snapshot_push_fill()
This is the obvious GtkSnapshot API to go
along with the new fill nodes.
2023-08-05 15:03:20 -04:00
Benjamin Otte
a592e52fab gsk: Add GskFillNode
Take a rendernode as source and a GskPath and fill
the region inside the path with the source, just like
cairo_fill() would.
2023-08-05 15:03:20 -04:00
Matthias Clasen
366abbb930 gsk: Add tests for GskPath 2023-08-05 15:03:20 -04:00
Matthias Clasen
bac372d2a4 gsk: Add tests for GskCurve 2023-08-05 15:03:20 -04:00
Matthias Clasen
b48ec3ad8b gsk: Add basic path infrastructure
This commit adds the basic infrastructure for paths.
The public APIs consists of GskPath, GskPathPoint and
GskPathBuilder.

GskPath is a data structure for paths that consists
of contours, which in turn might contain Bézier curves.

The Bezier data structure is inspired by Skia, with separate
arrays for points and operations. One advantage of this
arrangement is that start and end points are shared
between adjacent curves.

In addition to the usual contours comprised of Bézier
segments, GskPath supports specialized contours directly,
such as rectangles, rounded rectangles and circles.
2023-08-05 15:03:20 -04:00
Matthias Clasen
471cbc8c41 Add a bounding box type
graphene_rect_t is not well-suited for this purpose,
since you end up with floating-point precision problems
at the upper bound (x + width, y + height).
2023-08-05 15:03:20 -04:00
77 changed files with 10695 additions and 8124 deletions

150
LOG
View File

@@ -1,150 +0,0 @@
TAP version 13
# random seed: R02S22611f6aefc1121b0ab2dc5286960449
# GLib-GIO-DEBUG: _g_io_module_get_default: Found default implementation dconf (DConfSettingsBackend) for gsettings-backend
# GLib-GIO-DEBUG: Using cross-namespace EXTERNAL authentication (this will deadlock if server is GDBus < 2.73.3)
# GLib-GIO-DEBUG: Using cross-namespace EXTERNAL authentication (this will deadlock if server is GDBus < 2.73.3)
1..1
# Start of ops tests
# testcase 0 op 0
collecting#
Cubic 0: # M 100.000000 100.000000 C 150.000000 100.000000 200.000000 100.000000 250.000000 100.000000
Cubic 1: # M 250.000000 100.000000 C 300.000000 100.000000 350.000000 100.000000 400.000000 100.000000
Line 2: # M 400 100 L 200 300
Line 3: # M 200 300 L 100 100
Cubic 4: # M 200.000000 100.000000 C 250.000000 100.000000 300.000000 100.000000 350.000000 100.000000
Cubic 5: # M 350.000000 100.000000 C 400.000000 100.000000 450.000000 100.000000 500.000000 100.000000
Line 6: # M 500 100 L 300 300
Line 7: # M 300 300 L 200 100
splitting#
1 intersections between Cubic 0 and Cubic 1#
1 intersections between Cubic 0 and Line 3#
9 intersections between Cubic 0 and Cubic 4#
# split Cubic 0.0 from Cubic 0 at 0.666666: M 100.000000 100.000000 C 133.333298 100.000000 166.666595 100.000000 199.999893 100.000000
split Cubic 0.1 from Cubic 0 at 0.666666: M 199.999893 100.000000 C 216.666595 100.000000 233.333298 100.000000 250.000000 100.000000split Cubic 0.4 from Cubic 0 at 0.00600814: M 200.300323 100.000000 C 216.866882 100.000000 233.433441 100.000000 250.000000 100.000000split Cubic 0.7 from Cubic 0 at 0.00604445: M 200.600708 100.000000 C 217.067139 100.000000 233.533569 100.000000 250.000000 100.000000# split Cubic 4.9 from Cubic 4 at 0.00533867: M 200.800812 100.000000 C 250.533875 100.000000 300.266937 100.000000 350.000000 100.000000
1 intersections between Cubic 0.1 and Line 7#
1 intersections between Cubic 1 and Line 2#
9 intersections between Cubic 1 and Cubic 4.9#
split Cubic 1.9 from Cubic 1 at 0.00534248: M 250.801361 100.000000 C 300.534241 100.000000 350.267120 100.000000 400.000000 100.000000# split Cubic 4.9.0 from Cubic 4.9 at 0.329756: M 200.800812 100.000000 C 217.200577 100.000000 233.600342 100.000000 250.000122 100.000000
# split Cubic 4.9.1 from Cubic 4.9 at 0.329756: M 250.000122 100.000000 C 283.333435 100.000000 316.666718 100.000000 350.000000 100.000000
# split Cubic 4.9.6 from Cubic 4.9 at 0.00500283: M 250.500412 100.000000 C 283.666931 100.000000 316.833466 100.000000 350.000000 100.000000
9 intersections between Cubic 1.9 and Cubic 5#
# split Cubic 1.9.0 from Cubic 1.9 at 0.664877: M 250.801361 100.000000 C 283.867615 100.000000 316.933868 100.000000 350.000122 100.000000
split Cubic 1.9.1 from Cubic 1.9 at 0.664877: M 350.000122 100.000000 C 366.666748 100.000000 383.333374 100.000000 400.000000 100.000000split Cubic 1.9.4 from Cubic 1.9 at 0.00601021: M 350.300598 100.000000 C 366.867065 100.000000 383.433533 100.000000 400.000000 100.000000split Cubic 1.9.7 from Cubic 1.9 at 0.00604655: M 350.601074 100.000000 C 367.067383 100.000000 383.533691 100.000000 400.000000 100.000000# split Cubic 5.9 from Cubic 5 at 0.00534248: M 350.801361 100.000000 C 400.534241 100.000000 450.267120 100.000000 500.000000 100.000000
1 intersections between Line 2 and Line 3#
1 intersections between Line 2 and Line 7#
# split Line 2.0 from Line 2 at 0.666667: M 400 100 L 266.667 233.333
split Line 2.1 from Line 2 at 0.666667: M 266.667 233.333 L 200 300# split Line 7.0 from Line 7 at 0.333333: M 300 300 L 266.667 233.333
# split Line 7.1 from Line 7 at 0.333333: M 266.667 233.333 L 200 100
1 intersections between Cubic 4 and Cubic 4.9.0#
1 intersections between Cubic 4 and Line 7.1#
1 intersections between Cubic 4.9.0 and Cubic 4.9.1#
1 intersections between Cubic 4.9.1 and Cubic 4.9.6#
1 intersections between Cubic 4.9.6 and Cubic 5#
1 intersections between Cubic 5 and Cubic 5.9#
1 intersections between Cubic 5.9 and Line 6#
1 intersections between Line 6 and Line 7.0#
Cubic 0.0: # M 100.000000 100.000000 C 133.333298 100.000000 166.666595 100.000000 199.999893 100.000000
Cubic 0.1: # M 199.999893 100.000000 C 200.100037 100.000000 200.200180 100.000000 200.300323 100.000000
Cubic 0.4: # M 200.300323 100.000000 C 200.400452 100.000000 200.500580 100.000000 200.600708 100.000000
Cubic 0.7: # M 200.600708 100.000000 C 217.067139 100.000000 233.533569 100.000000 250.000000 100.000000
Cubic 1: # M 250.000000 100.000000 C 250.267120 100.000000 250.534241 100.000000 250.801361 100.000000
Cubic 1.9.0: # M 250.801361 100.000000 C 283.867615 100.000000 316.933868 100.000000 350.000122 100.000000
Cubic 1.9.1: # M 350.000122 100.000000 C 350.100281 100.000000 350.200439 100.000000 350.300598 100.000000
Cubic 1.9.4: # M 350.300598 100.000000 C 350.400757 100.000000 350.500916 100.000000 350.601074 100.000000
Cubic 1.9.7: # M 350.601074 100.000000 C 367.067383 100.000000 383.533691 100.000000 400.000000 100.000000
Line 2.0: # M 400 100 L 266.667 233.333
Line 2.1: # M 266.667 233.333 L 200 300
Line 3: # M 200 300 L 100 100
Cubic 4: # M 200.000000 100.000000 C 200.266937 100.000000 200.533875 100.000000 200.800812 100.000000
Cubic 4.9.0: # M 200.800812 100.000000 C 217.200577 100.000000 233.600342 100.000000 250.000122 100.000000
Cubic 4.9.1: # M 250.000122 100.000000 C 250.166885 100.000000 250.333649 100.000000 250.500412 100.000000
Cubic 4.9.6: # M 250.500412 100.000000 C 283.666931 100.000000 316.833466 100.000000 350.000000 100.000000
Cubic 5: # M 350.000000 100.000000 C 350.267120 100.000000 350.534241 100.000000 350.801361 100.000000
Cubic 5.9: # M 350.801361 100.000000 C 400.534241 100.000000 450.267120 100.000000 500.000000 100.000000
Line 6: # M 500 100 L 300 300
Line 7.0: # M 300 300 L 266.667 233.333
Line 7.1: # M 266.667 233.333 L 200 100
classifying#
01 Cubic 0.0: # M 100.000000 100.000000 C 133.333298 100.000000 166.666595 100.000000 199.999893 100.000000
[11 Cubic 0.7: # M 200.600708 100.000000 C 217.067139 100.000000 233.533569 100.000000 250.000000 100.000000 ]
[11 Cubic 1.9.0: # M 250.801361 100.000000 C 283.867615 100.000000 316.933868 100.000000 350.000122 100.000000 ]
[11 Cubic 1.9.7: # M 350.601074 100.000000 C 367.067383 100.000000 383.533691 100.000000 400.000000 100.000000 ]
[11 Line 2.0: # M 400 100 L 266.667 233.333 ]
01 Line 2.1: # M 266.667 233.333 L 200 300
01 Line 3: # M 200 300 L 100 100
[11 Cubic 4: # M 200.000000 100.000000 C 200.266937 100.000000 200.533875 100.000000 200.800812 100.000000 ]
[11 Cubic 4.9.0: # M 200.800812 100.000000 C 217.200577 100.000000 233.600342 100.000000 250.000122 100.000000 ]
[11 Cubic 4.9.6: # M 250.500412 100.000000 C 283.666931 100.000000 316.833466 100.000000 350.000000 100.000000 ]
[11 Cubic 5: # M 350.000000 100.000000 C 350.267120 100.000000 350.534241 100.000000 350.801361 100.000000 ]
01 Cubic 5.9: # M 350.801361 100.000000 C 400.534241 100.000000 450.267120 100.000000 500.000000 100.000000
01 Line 6: # M 500 100 L 300 300
01 Line 7.0: # M 300 300 L 266.667 233.333
[11 Line 7.1: # M 266.667 233.333 L 200 100 ]
fixups#
# found 3 bad nodes
# split Cubic 0/Cubic 4 BAD 200.600723 100.000000
# [11 Line 7.1 ] 116.565
# [11 Cubic 0.7 ] 180
# [11 Cubic 4 ] 180
# [11 Cubic 4 ] 180
# [11 Cubic 4.9.0 ] 180
# >01 Cubic 0.0 360
# split Cubic 1/Cubic 4.9 BAD 250.801376 100.000000
# [11 Cubic 1.9.0 ] 180
# [11 Cubic 4.9.6 ] 180
# [11 Cubic 0.7 ] 360
# [11 Cubic 4.9.0 ] 360
# split Cubic 1.9/Cubic 5 BAD 350.601105 100.000000
# [11 Cubic 1.9.7 ] 180
# [11 Cubic 5 ] 180
# [11 Cubic 5 ] 180
# <01 Cubic 5.9 180
# [11 Cubic 1.9.0 ] 360
# [11 Cubic 4.9.6 ] 360
reassembling#
start new contour Cubic 0.0#
# Cubic 0.0 ends at:
# start 0 100.000000 100.000000
# >01 Line 3 116.565
# (10 Cubic 0.0 ) 180
picking cw#
append Line 3#
# Line 3 ends at:
# end 2 200.000000 300.000000
# >01 Line 2.1 225
# (10 Line 3 ) 296.565
picking cw#
append Line 2.1#
# Line 2.1 ends at:
# split Line 2/Line 7 266.666656 233.333344
# (10 Line 2.1 ) 45
# >01 Line 7.0 116.565
# [11 Line 2.0 ] 225
# [11 Line 7.1 ] 296.565
picking cw#
append Line 7.0#
# Line 7.0 ends at:
# end 6 300.000000 300.000000
# >01 Line 6 225
# (10 Line 7.0 ) 296.565
picking cw#
append Line 6#
# Line 6 ends at:
# end 5 500.000000 100.000000
# (10 Line 6 ) 45
# >01 Cubic 5.9 360
picking cw#
append Cubic 5.9#
# Cubic 5.9 ends at:
# split Cubic 1.9/Cubic 5 BAD 350.601105 100.000000
# [11 Cubic 1.9.7 ] 180
# [11 Cubic 5 ] 180
# [11 Cubic 5 ] 180
# (10 Cubic 5.9 ) 180
# [11 Cubic 1.9.0 ] 360
# [11 Cubic 4.9.6 ] 360
picking cw#
**
ERROR:../testsuite/gsk/path-ops.c:359:test_ops_simple: assertion failed (s == tests[i].out): ("M 354.60110473632812 100 A 4 4 0 0 0 346.60110473632812 100 A 4 4 0 0 0 354.60110473632812 100 z M 252.80137634277344 100 A 2 2 0 0 0 248.80137634277344 100 A 2 2 0 0 0 252.80137634277344 100 z M 204.60072326660156 100 A 4 4 0 0 0 196.60072326660156 100 A 4 4 0 0 0 204.60072326660156 100 z" == "M 100 100 z")
not ok /ops/simple - ERROR:../testsuite/gsk/path-ops.c:359:test_ops_simple: assertion failed (s == tests[i].out): ("M 354.60110473632812 100 A 4 4 0 0 0 346.60110473632812 100 A 4 4 0 0 0 354.60110473632812 100 z M 252.80137634277344 100 A 2 2 0 0 0 248.80137634277344 100 A 2 2 0 0 0 252.80137634277344 100 z M 204.60072326660156 100 A 4 4 0 0 0 196.60072326660156 100 A 4 4 0 0 0 204.60072326660156 100 z" == "M 100 100 z")
Bail out!

View File

@@ -336,6 +336,7 @@
<file>panes.c</file>
<file>password_entry.c</file>
<file>path_fill.c</file>
<file>path_maze.c</file>
<file>path_text.c</file>
<file>peg_solitaire.c</file>
<file>pickers.c</file>

View File

@@ -73,6 +73,7 @@ demos = files([
'panes.c',
'password_entry.c',
'path_fill.c',
'path_maze.c',
'path_text.c',
'peg_solitaire.c',
'pickers.c',

View File

@@ -1,7 +1,7 @@
/* Path/Fill and Stroke
/* Path/Fill
*
* This demo shows how to use GskPath to draw shapes that are (a bit)
* more complex than a rounded rectangle.
* This demo shows how to use PangoCairo to draw text with more than
* just a single color.
*/
#include <glib/gi18n.h>
@@ -9,162 +9,305 @@
#include "paintable.h"
#define GTK_TYPE_LOGO_PAINTABLE (gtk_logo_paintable_get_type ())
G_DECLARE_FINAL_TYPE (GtkLogoPaintable, gtk_logo_paintable, GTK, LOGO_PAINTABLE, GObject)
#define GTK_TYPE_PATH_PAINTABLE (gtk_path_paintable_get_type ())
G_DECLARE_FINAL_TYPE (GtkPathPaintable, gtk_path_paintable, GTK, PATH_PAINTABLE, GObject)
struct _GtkLogoPaintable
struct _GtkPathPaintable
{
GObject parent_instance;
int width;
int height;
GskPath *path[3];
GdkRGBA color[3];
GskPath *stroke_path;
GskStroke *stroke1;
GskStroke *stroke2;
GdkRGBA stroke_color;
GskPath *path;
GdkPaintable *background;
};
struct _GtkLogoPaintableClass
struct _GtkPathPaintableClass
{
GObjectClass parent_class;
};
static int
gtk_logo_paintable_get_intrinsic_width (GdkPaintable *paintable)
gtk_path_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (paintable);
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
return self->width;
if (self->background)
return MAX (gdk_paintable_get_intrinsic_width (self->background), self->width);
else
return self->width;
}
static int
gtk_logo_paintable_get_intrinsic_height (GdkPaintable *paintable)
gtk_path_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (paintable);
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
return self->height;
if (self->background)
return MAX (gdk_paintable_get_intrinsic_height (self->background), self->height);
else
return self->height;
}
static void
gtk_logo_paintable_snapshot (GdkPaintable *paintable,
gtk_path_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (paintable);
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
for (unsigned int i = 0; i < 3; i++)
#if 0
gtk_snapshot_push_fill (snapshot, self->path, GSK_FILL_RULE_WINDING);
#else
GskStroke *stroke = gsk_stroke_new (2.0);
gtk_snapshot_push_stroke (snapshot, self->path, stroke);
gsk_stroke_free (stroke);
#endif
if (self->background)
{
gtk_snapshot_push_fill (snapshot, self->path[i], GSK_FILL_RULE_WINDING);
gtk_snapshot_append_color (snapshot,
&self->color[i],
&GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gdk_paintable_snapshot (self->background, snapshot, width, height);
}
for (unsigned int i = 0; i < 3; i++)
else
{
gtk_snapshot_push_stroke (snapshot, self->stroke_path, self->stroke1);
gtk_snapshot_append_color (snapshot,
&self->stroke_color,
&GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (0, 0, width, height),
&GRAPHENE_POINT_INIT (0, 0),
&GRAPHENE_POINT_INIT (width, height),
(GskColorStop[8]) {
{ 0.0, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.2, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.3, { 1.0, 1.0, 0.0, 1.0 } },
{ 0.4, { 0.0, 1.0, 0.0, 1.0 } },
{ 0.6, { 0.0, 1.0, 1.0, 1.0 } },
{ 0.7, { 0.0, 0.0, 1.0, 1.0 } },
{ 0.8, { 1.0, 0.0, 1.0, 1.0 } },
{ 1.0, { 1.0, 0.0, 1.0, 1.0 } }
},
8);
}
gtk_snapshot_push_stroke (snapshot, self->stroke_path, self->stroke2);
gtk_snapshot_append_color (snapshot,
&self->stroke_color,
&GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
}
static GdkPaintableFlags
gtk_logo_paintable_get_flags (GdkPaintable *paintable)
gtk_path_paintable_get_flags (GdkPaintable *paintable)
{
return GDK_PAINTABLE_STATIC_CONTENTS | GDK_PAINTABLE_STATIC_SIZE;
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
if (self->background)
return gdk_paintable_get_flags (self->background);
else
return GDK_PAINTABLE_STATIC_CONTENTS | GDK_PAINTABLE_STATIC_SIZE;
}
static void
gtk_logo_paintable_paintable_init (GdkPaintableInterface *iface)
gtk_path_paintable_paintable_init (GdkPaintableInterface *iface)
{
iface->get_intrinsic_width = gtk_logo_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gtk_logo_paintable_get_intrinsic_height;
iface->snapshot = gtk_logo_paintable_snapshot;
iface->get_flags = gtk_logo_paintable_get_flags;
iface->get_intrinsic_width = gtk_path_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gtk_path_paintable_get_intrinsic_height;
iface->snapshot = gtk_path_paintable_snapshot;
iface->get_flags = gtk_path_paintable_get_flags;
}
/* When defining the GType, we need to implement the GdkPaintable interface */
G_DEFINE_TYPE_WITH_CODE (GtkLogoPaintable, gtk_logo_paintable, G_TYPE_OBJECT,
G_DEFINE_TYPE_WITH_CODE (GtkPathPaintable, gtk_path_paintable, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gtk_logo_paintable_paintable_init))
gtk_path_paintable_paintable_init))
static void
gtk_logo_paintable_dispose (GObject *object)
/* Here's the boilerplate for the GObject declaration.
* We need to disconnect the signals here that we set up elsewhere
*/
static void
gtk_path_paintable_dispose (GObject *object)
{
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (object);
GtkPathPaintable *self = GTK_PATH_PAINTABLE (object);
for (unsigned int i = 0; i < 3; i++)
gsk_path_unref (self->path[i]);
if (self->background)
{
g_signal_handlers_disconnect_by_func (self->background, gdk_paintable_invalidate_contents, self);
g_signal_handlers_disconnect_by_func (self->background, gdk_paintable_invalidate_size, self);
g_clear_object (&self->background);
}
gsk_path_unref (self->stroke_path);
gsk_stroke_free (self->stroke1);
gsk_stroke_free (self->stroke2);
G_OBJECT_CLASS (gtk_logo_paintable_parent_class)->dispose (object);
G_OBJECT_CLASS (gtk_path_paintable_parent_class)->dispose (object);
}
static void
gtk_logo_paintable_class_init (GtkLogoPaintableClass *klass)
gtk_path_paintable_class_init (GtkPathPaintableClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_logo_paintable_dispose;
object_class->dispose = gtk_path_paintable_dispose;
}
static void
gtk_logo_paintable_init (GtkLogoPaintable *self)
gtk_path_paintable_init (GtkPathPaintable *self)
{
}
static GdkPaintable *
gtk_logo_paintable_new (void)
/* And finally, we add a simple constructor.
* It is declared in the header so that the other examples
* can use it.
*/
GdkPaintable *
gtk_path_paintable_new (GskPath *path,
GdkPaintable *background,
int width,
int height)
{
GtkLogoPaintable *self;
graphene_rect_t bounds, bounds2;
GtkPathPaintable *self;
self = g_object_new (GTK_TYPE_LOGO_PAINTABLE, NULL);
/* Paths and colors extracted from gtk-logo.svg */
self->path[0] = gsk_path_parse ("m3.12,66.17 -2.06,-51.46 32.93,24.7 v55.58 l-30.87,-28.82 z");
self->path[1] = gsk_path_parse ("m34,95 49.4,-20.58 4.12,-51.46 -53.52,16.47 v55.58 z");
self->path[2] = gsk_path_parse ("m1.06,14.71 32.93,24.7 53.52,-16.47 -36.75,-21.88 -49.7,13.65 z");
gdk_rgba_parse (&self->color[0], "#e40000");
gdk_rgba_parse (&self->color[1], "#7fe719");
gdk_rgba_parse (&self->color[2], "#729fcf");
self->stroke_path = gsk_path_parse ("m50.6,51.3 -47.3,14 z l33,23 z v-50");
self->stroke1 = gsk_stroke_new (2.12);
self->stroke2 = gsk_stroke_new (1.25);
gdk_rgba_parse (&self->stroke_color, "#ffffff");
gsk_path_get_stroke_bounds (self->path[0], self->stroke1, &bounds);
gsk_path_get_stroke_bounds (self->path[1], self->stroke1, &bounds2);
graphene_rect_union (&bounds, &bounds2, &bounds);
gsk_path_get_stroke_bounds (self->path[2], self->stroke1, &bounds2);
graphene_rect_union (&bounds, &bounds2, &bounds);
gsk_path_get_stroke_bounds (self->stroke_path, self->stroke2, &bounds2);
graphene_rect_union (&bounds, &bounds2, &bounds);
self->width = bounds.origin.x + bounds.size.width;
self->height = bounds.origin.y + bounds.size.height;
self = g_object_new (GTK_TYPE_PATH_PAINTABLE, NULL);
self->path = path;
self->background = background;
if (self->background)
{
g_object_ref (self->background);
g_signal_connect_swapped (self->background, "invalidate-contents", G_CALLBACK (gdk_paintable_invalidate_contents), self);
g_signal_connect_swapped (self->background, "invalidate-size", G_CALLBACK (gdk_paintable_invalidate_size), self);
}
self->width = width;
self->height = height;
return GDK_PAINTABLE (self);
}
void
gtk_path_paintable_set_path (GtkPathPaintable *self,
GskPath *path)
{
g_clear_pointer (&self->path, gsk_path_unref);
self->path = gsk_path_ref (path);
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
}
static GskPath *
create_hexagon (GtkWidget *widget)
{
GskPathBuilder *builder;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, 120, 0);
gsk_path_builder_line_to (builder, 360, 0);
gsk_path_builder_line_to (builder, 480, 208);
gsk_path_builder_line_to (builder, 360, 416);
gsk_path_builder_line_to (builder, 120, 416);
gsk_path_builder_line_to (builder, 0, 208);
gsk_path_builder_close (builder);
return gsk_path_builder_free_to_path (builder);
}
static GskPath *
create_path_from_text (GtkWidget *widget)
{
PangoLayout *layout;
PangoFontDescription *desc;
GskPathBuilder *builder;
layout = gtk_widget_create_pango_layout (widget, "Pango power!\nPango power!\nPango power!");
desc = pango_font_description_from_string ("sans bold 36");
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
builder = gsk_path_builder_new ();
gsk_path_builder_add_layout (builder, layout);
return gsk_path_builder_free_to_path (builder);
}
static gboolean
build_path (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathBuilder *builder = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builder);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
break;
case GSK_PATH_CUBIC:
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_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
break;
default:
g_assert_not_reached ();
break;
}
return TRUE;
}
static gboolean
update_path (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer measure)
{
float progress = gdk_frame_clock_get_frame_time (frame_clock) % (60 * G_USEC_PER_SEC) / (float) (30 * G_USEC_PER_SEC);
GskPathBuilder *builder;
GskPath *path;
GskPathPoint point;
graphene_point_t pos;
graphene_vec2_t tangent;
GskStroke *stroke;
path = gsk_path_measure_get_path (measure);
stroke = gsk_stroke_new (1);
gsk_stroke_set_dash (stroke, (float[2]) { 10, 5 }, 2);
gsk_stroke_set_dash_offset (stroke, - (gdk_frame_clock_get_frame_time (frame_clock) % G_USEC_PER_SEC) * 15. / G_USEC_PER_SEC);
builder = gsk_path_builder_new ();
gsk_path_dash (path, stroke, 0.2, build_path, builder);
if (gsk_path_measure_get_point (measure,
(progress > 1 ? (progress - 1) : progress) * gsk_path_measure_get_length (measure), &point))
{
gsk_path_point_get_position (&point, &pos);
gsk_path_point_get_tangent (&point, GSK_PATH_END, &tangent);
gsk_path_builder_move_to (builder, pos.x + 5 * graphene_vec2_get_x (&tangent), pos.y + 5 * graphene_vec2_get_y (&tangent));
gsk_path_builder_line_to (builder, pos.x + 3 * graphene_vec2_get_y (&tangent), pos.y + 3 * graphene_vec2_get_x (&tangent));
gsk_path_builder_line_to (builder, pos.x - 3 * graphene_vec2_get_y (&tangent), pos.y - 3 * graphene_vec2_get_x (&tangent));
gsk_path_builder_close (builder);
path = gsk_path_builder_free_to_path (builder);
gtk_path_paintable_set_path (GTK_PATH_PAINTABLE (gtk_picture_get_paintable (GTK_PICTURE (widget))),
path);
gsk_path_unref (path);
}
return G_SOURCE_CONTINUE;
}
GtkWidget *
do_path_fill (GtkWidget *do_widget)
{
@@ -174,14 +317,35 @@ do_path_fill (GtkWidget *do_widget)
{
GtkWidget *picture;
GdkPaintable *paintable;
GtkMediaStream *stream;
GskPath *path;
graphene_rect_t bounds;
GskPathMeasure *measure;
window = gtk_window_new ();
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_window_set_title (GTK_WINDOW (window), "Path Fill");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
paintable = gtk_logo_paintable_new ();
#if 0
stream = gtk_media_file_new_for_resource ("/images/gtk-logo.webm");
#else
stream = gtk_nuclear_media_stream_new ();
#endif
gtk_media_stream_play (stream);
gtk_media_stream_set_loop (stream, TRUE);
path = create_hexagon (window);
path = create_path_from_text (window);
gsk_path_get_bounds (path, &bounds);
paintable = gtk_path_paintable_new (path,
GDK_PAINTABLE (stream),
bounds.origin.x + bounds.size.width,
bounds.origin.y + bounds.size.height);
picture = gtk_picture_new_for_paintable (paintable);
measure = gsk_path_measure_new (path);
gtk_widget_add_tick_callback (picture, update_path, measure, (GDestroyNotify) gsk_path_measure_unref);
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);

346
demos/gtk-demo/path_maze.c Normal file
View File

@@ -0,0 +1,346 @@
/* Path/Maze
*
* This demo shows how to use a GskPath to create a maze and use
* gsk_path_measure_get_closest_point() to check the mouse stays
* on the path.
*
* It also shows off the performance of GskPath (or not) as this
* is a rather complex path.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "paintable.h"
#define MAZE_GRID_SIZE 20
#define MAZE_STROKE_SIZE_ACTIVE (MAZE_GRID_SIZE - 4)
#define MAZE_STROKE_SIZE_INACTIVE (MAZE_GRID_SIZE - 12)
#define MAZE_WIDTH 31
#define MAZE_HEIGHT 21
#define GTK_TYPE_MAZE (gtk_maze_get_type ())
G_DECLARE_FINAL_TYPE (GtkMaze, gtk_maze, GTK, MAZE, GtkWidget)
struct _GtkMaze
{
GtkWidget parent_instance;
int width;
int height;
GskPath *path;
GskPathMeasure *measure;
GdkPaintable *background;
gboolean active;
};
struct _GtkMazeClass
{
GtkWidgetClass parent_class;
};
G_DEFINE_TYPE (GtkMaze, gtk_maze, GTK_TYPE_WIDGET)
static void
gtk_maze_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkMaze *self = GTK_MAZE (widget);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
*minimum = *natural = self->width;
else
*minimum = *natural = self->height;
}
static void
gtk_maze_snapshot (GtkWidget *widget,
GdkSnapshot *snapshot)
{
GtkMaze *self = GTK_MAZE (widget);
double width = gtk_widget_get_width (widget);
double height = gtk_widget_get_height (widget);
GskStroke *stroke;
stroke = gsk_stroke_new (MAZE_STROKE_SIZE_INACTIVE);
if (self->active)
gsk_stroke_set_line_width (stroke, MAZE_STROKE_SIZE_ACTIVE);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_ROUND);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_ROUND);
gtk_snapshot_push_stroke (snapshot, self->path, stroke);
gsk_stroke_free (stroke);
if (self->background)
{
gdk_paintable_snapshot (self->background, snapshot, width, height);
}
else
{
gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (0, 0, width, height),
&GRAPHENE_POINT_INIT (0, 0),
&GRAPHENE_POINT_INIT (width, height),
(GskColorStop[8]) {
{ 0.0, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.2, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.3, { 1.0, 1.0, 0.0, 1.0 } },
{ 0.4, { 0.0, 1.0, 0.0, 1.0 } },
{ 0.6, { 0.0, 1.0, 1.0, 1.0 } },
{ 0.7, { 0.0, 0.0, 1.0, 1.0 } },
{ 0.8, { 1.0, 0.0, 1.0, 1.0 } },
{ 1.0, { 1.0, 0.0, 1.0, 1.0 } }
},
8);
}
gtk_snapshot_pop (snapshot);
}
static void
gtk_maze_dispose (GObject *object)
{
GtkMaze *self = GTK_MAZE (object);
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->measure, gsk_path_measure_unref);
if (self->background)
{
g_signal_handlers_disconnect_matched (self->background, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
g_clear_object (&self->background);
}
G_OBJECT_CLASS (gtk_maze_parent_class)->dispose (object);
}
static void
gtk_maze_class_init (GtkMazeClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_maze_dispose;
widget_class->measure = gtk_maze_measure;
widget_class->snapshot = gtk_maze_snapshot;
}
static void
pointer_motion (GtkEventControllerMotion *controller,
double x,
double y,
GtkMaze *self)
{
GskPathPoint point;
graphene_point_t pos;
if (!self->active)
return;
if (gsk_path_get_closest_point (self->path, &GRAPHENE_POINT_INIT (x, y), INFINITY, &point))
{
gsk_path_point_get_position (&point, &pos);
if (graphene_point_distance (&pos, &GRAPHENE_POINT_INIT (x, y), NULL, NULL) <= MAZE_STROKE_SIZE_ACTIVE / 2.0f)
return;
self->active = FALSE;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
}
static void
pointer_leave (GtkEventControllerMotion *controller,
GtkMaze *self)
{
if (!self->active)
{
self->active = TRUE;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
}
static void
gtk_maze_init (GtkMaze *self)
{
GtkEventController *controller;
controller = GTK_EVENT_CONTROLLER (gtk_event_controller_motion_new ());
g_signal_connect (controller, "motion", G_CALLBACK (pointer_motion), self);
g_signal_connect (controller, "leave", G_CALLBACK (pointer_leave), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
self->active = TRUE;
}
static void
gtk_maze_set_path (GtkMaze *self,
GskPath *path)
{
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->measure, gsk_path_measure_unref);
self->path = gsk_path_ref (path);
self->measure = gsk_path_measure_new (path);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
GtkWidget *
gtk_maze_new (GskPath *path,
GdkPaintable *background,
int width,
int height)
{
GtkMaze *self;
self = g_object_new (GTK_TYPE_MAZE, NULL);
gtk_maze_set_path (self, path);
gsk_path_unref (path);
self->background = background;
if (self->background)
{
g_signal_connect_swapped (self->background, "invalidate-contents", G_CALLBACK (gtk_widget_queue_draw), self);
g_signal_connect_swapped (self->background, "invalidate-size", G_CALLBACK (gtk_widget_queue_resize), self);
}
self->width = width;
self->height = height;
return GTK_WIDGET (self);
}
static void
add_point_to_maze (GtkBitset *maze,
GskPathBuilder *builder,
guint x,
guint y)
{
gboolean set[4] = { FALSE, FALSE, FALSE, FALSE };
guint dir;
gtk_bitset_add (maze, y * MAZE_WIDTH + x);
while (TRUE)
{
set[0] = set[0] || x == 0 || gtk_bitset_contains (maze, y * MAZE_WIDTH + x - 1);
set[1] = set[1] || y == 0 || gtk_bitset_contains (maze, (y - 1) * MAZE_WIDTH + x);
set[2] = set[2] || x + 1 == MAZE_WIDTH || gtk_bitset_contains (maze, y * MAZE_WIDTH + x + 1);
set[3] = set[3] || y + 1 == MAZE_HEIGHT || gtk_bitset_contains (maze, (y + 1) * MAZE_WIDTH + x);
if (set[0] && set[1] && set[2] && set[3])
return;
do
{
dir = g_random_int_range (0, 4);
}
while (set[dir]);
switch (dir)
{
case 0:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x - 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x - 1, y);
break;
case 1:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y - 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x, y - 1);
break;
case 2:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 1.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x + 1, y);
break;
case 3:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 1.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x, y + 1);
break;
default:
g_assert_not_reached ();
break;
}
}
}
static GskPath *
create_path_for_maze (GtkWidget *widget)
{
GskPathBuilder *builder;
GtkBitset *maze;
builder = gsk_path_builder_new ();
maze = gtk_bitset_new_empty ();
/* make sure the outer lines are unreachable:
* Set the full range, then remove the center again. */
gtk_bitset_add_range (maze, 0, MAZE_WIDTH * MAZE_HEIGHT);
gtk_bitset_remove_rectangle (maze, MAZE_WIDTH + 1, MAZE_WIDTH - 2, MAZE_HEIGHT - 2, MAZE_WIDTH);
/* Fill the maze */
add_point_to_maze (maze, builder, MAZE_WIDTH / 2, MAZE_HEIGHT / 2);
/* Add start and stop lines */
gsk_path_builder_move_to (builder, 1.5 * MAZE_GRID_SIZE, -0.5 * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, 1.5 * MAZE_GRID_SIZE, 1.5 * MAZE_GRID_SIZE);
gsk_path_builder_move_to (builder, (MAZE_WIDTH - 1.5) * MAZE_GRID_SIZE, (MAZE_HEIGHT - 1.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (MAZE_WIDTH - 1.5) * MAZE_GRID_SIZE, (MAZE_HEIGHT + 0.5) * MAZE_GRID_SIZE);
gtk_bitset_unref (maze);
return gsk_path_builder_free_to_path (builder);
}
GtkWidget *
do_path_maze (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkWidget *maze;
GtkMediaStream *stream;
GskPath *path;
window = gtk_window_new ();
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_window_set_title (GTK_WINDOW (window), "Follow the maze with the mouse");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
#if 0
stream = gtk_media_file_new_for_resource ("/images/gtk-logo.webm");
#else
stream = gtk_nuclear_media_stream_new ();
#endif
gtk_media_stream_play (stream);
gtk_media_stream_set_loop (stream, TRUE);
path = create_path_for_maze (window);
maze = gtk_maze_new (path,
GDK_PAINTABLE (stream),
MAZE_WIDTH * MAZE_GRID_SIZE,
MAZE_HEIGHT * MAZE_GRID_SIZE);
gtk_window_set_child (GTK_WINDOW (window), maze);
}
if (!gtk_widget_get_visible (window))
gtk_window_present (GTK_WINDOW (window));
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -92,10 +92,8 @@ gtk_path_transform_point (GskPathMeasure *measure,
if (gsk_path_measure_get_point (measure, (pt->x + offset->x) * scale, &point))
{
GskPath *path = gsk_path_measure_get_path (measure);
gsk_path_point_get_position (path, &point, res);
gsk_path_point_get_tangent (path, &point, GSK_PATH_END, &tangent);
gsk_path_point_get_position (&point, res);
gsk_path_point_get_tangent (&point, GSK_PATH_END, &tangent);
res->x -= (pt->y + offset->y) * scale * graphene_vec2_get_y (&tangent);
res->y += (pt->y + offset->y) * scale * graphene_vec2_get_x (&tangent);
@@ -106,6 +104,7 @@ static gboolean
gtk_path_transform_op (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
GtkPathTransform *transform = data;
@@ -147,6 +146,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->offset, transform->scale, &res[0]);
gtk_path_transform_point (transform->measure, &pts[2], &transform->offset, 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;
@@ -324,7 +332,7 @@ gtk_path_widget_snapshot (GtkWidget *widget,
builder = gsk_path_builder_new ();
if (gsk_path_measure_get_point (self->line_measure, self->line_closest, &point))
{
gsk_path_point_get_position (self->line_path, &point, &closest);
gsk_path_point_get_position (&point, &closest);
gsk_path_builder_add_circle (builder, &closest, POINT_SIZE);
path = gsk_path_builder_free_to_path (builder);
@@ -521,7 +529,7 @@ pointer_motion (GtkEventControllerMotion *controller,
INFINITY,
&point))
{
gsk_path_point_get_position (self->line_path, &point, &pos);
gsk_path_point_get_position (&point, &pos);
self->line_closest = graphene_point_distance (&pos, &GRAPHENE_POINT_INIT (x, y), NULL, NULL);
gtk_widget_queue_draw (GTK_WIDGET (self));

View File

@@ -0,0 +1,121 @@
.. _gtk4-path-tool(1):
=================
gtk4-path-tool
=================
-----------------------
GskPath Utility
-----------------------
SYNOPSIS
--------
| **gtk4-path-tool** <COMMAND> [OPTIONS...] <PATH>
|
| **gtk4-path-tool** decompose [OPTIONS...] <PATH>
| **gtk4-path-tool** restrict [OPTIONS...] <PATH>
| **gtk4-path-tool** show [OPTIONS...] <PATH>
| **gtk4-path-tool** render [OPTIONS...] <PATH>
| **gtk4-path-tool** info [OPTIONS...] <PATH>
DESCRIPTION
-----------
``gtk4-path-tool`` can perform various tasks on paths. Paths are specified
in SVG syntax, as strings like "M 100 100 C 100 200 200 200 200 100 Z".
To read a path from a file, use a filename that starts with a '.' or a '/'.
To read a path from stdin, use '-'.
COMMANDS
--------
Decomposing
^^^^^^^^^^^
The ``decompose`` command approximates the path by one with simpler elements.
When used without options, the curves of the path are approximated by line
segments.
``--allow-curves``
Allow cubic Bézier curves to be used in the generated path.
``--allow-conics``
Allow rational quadratic Bézier curves to be used in the generated path.
Restricting
^^^^^^^^^^^
The ``restrict`` command creates a path that traces a segment of the original
path. Note that the start and the end of the segment are specified as
path length from the beginning of the path.
``--start=LENGTH``
The distance from the beginning of the path where the segment begins. The
default values is 0.
``--end=LENGTH``
The distance from the beginning of the path where the segment ends. The
default value is the length of path.
Showing
^^^^^^^
The ``show`` command displays the given path in a window. The interior
of the path is filled.
``--fill-rule=VALUE``
The fill rule that is used to determine what areas are inside the path.
The possible values are ``winding`` or ``even-odd``. The default is ``winding``.
``--fg-color=COLOR``
The color that is used to fill the interior of the path.
If not specified, black is used.
``--bg-color=COLOR``
The color that is used to render the background behind the path.
If not specified, white is used.
Rendering
^^^^^^^^^
The ``render`` command renders the given path as a PNG image.
The interior of the path is filled.
``--fill-rule=VALUE``
The fill rule that is used to determine what areas are inside the path.
The possible values are ``winding`` or ``even-odd``. The default is ``winding``.
``--fg-color=COLOR``
The color that is used to fill the interior of the path.
If not specified, black is used.
``--bg-color=COLOR``
The color that is used to render the background behind the path.
If not specified, white is used.
``--output-file=FILE``
The file to save the PNG image to.
If not specified, "path.png" is used.
Info
^^^^
The ``info`` command shows various information about the given path,
such as its bounding box and and its length.
REFERENCES
----------
- SVG Path Specification, https://www.w3.org/TR/SVG2/paths.html

View File

@@ -77,6 +77,7 @@ if get_option('man-pages') and rst2man.found()
[ 'gtk4-query-settings', '1', ],
[ 'gtk4-rendernode-tool', '1' ],
[ 'gtk4-update-icon-cache', '1', ],
[ 'gtk4-path-tool', '1', ],
]
if get_option('demos')

View File

@@ -222,7 +222,7 @@ It is possible to set accessible attributes in UI files as well:
<accessibility>
<property name="label">Download</property>
<relation name="labelled-by">label1</relation>
</accessibility>
/accessibility>
</object>
```

View File

@@ -27,7 +27,9 @@
#include "gdkmacosdisplay.h"
#include "gdkmacossurface.h"
#import <epoxy/gl.h>
#import <OpenGL/OpenGL.h>
#import <OpenGL/gl3.h>
#import <AppKit/AppKit.h>
G_BEGIN_DECLS

View File

@@ -21,6 +21,7 @@
#include "gdkconfig.h"
#include <OpenGL/gl3.h>
#include <OpenGL/CGLIOSurface.h>
#include <QuartzCore/QuartzCore.h>
@@ -498,7 +499,7 @@ gdk_macos_gl_context_begin_frame (GdkDrawContext *context,
gdk_gl_context_make_current (GDK_GL_CONTEXT (self));
gdk_macos_gl_context_allocate (self);
GDK_DRAW_CONTEXT_CLASS (gdk_macos_gl_context_parent_class)->begin_frame (context, depth, region);
GDK_DRAW_CONTEXT_CLASS (gdk_macos_gl_context_parent_class)->begin_frame (context, prefers_high_depth, region);
gdk_gl_context_make_current (GDK_GL_CONTEXT (self));
CHECK_GL (NULL, glBindFramebuffer (GL_FRAMEBUFFER, self->fbo));

View File

@@ -1245,9 +1245,9 @@ gsk_gl_render_job_visit_as_fallback (GskGLRenderJob *job,
done:
if (scale_x < 0 || scale_y < 0)
{
GskTransform *transform = gsk_transform_translate (gsk_transform_scale (NULL, scale_x < 0 ? -1 : 1, scale_y < 0 ? -1 : 1),
&GRAPHENE_POINT_INIT (scale_x < 0 ? - (node->bounds.size.width + 2 * node->bounds.origin.x) : 0,
scale_y < 0 ? - (node->bounds.size.height + 2 * node->bounds.origin.y) : 0));
GskTransform *transform = gsk_transform_translate (NULL,
&GRAPHENE_POINT_INIT (scale_x < 0 ? - surface_width : 0,
scale_y < 0 ? - surface_height : 0));
gsk_gl_render_job_push_modelview (job, transform);
gsk_transform_unref (transform);
}

View File

@@ -97,19 +97,4 @@ gsk_bounding_box_intersection (const GskBoundingBox *a,
return min.x <= max.x && min.y <= max.y;
}
static inline void
gsk_bounding_box_union (const GskBoundingBox *a,
const GskBoundingBox *b,
GskBoundingBox *res)
{
graphene_point_t min, max;
min.x = MIN (a->min.x, b->min.x);
min.y = MIN (a->min.y, b->min.y);
max.x = MAX (a->max.x, b->max.x);
max.y = MAX (a->max.y, b->max.y);
gsk_bounding_box_init (res, &min, &max);
}
G_END_DECLS

File diff suppressed because it is too large Load Diff

View File

@@ -23,10 +23,15 @@
#include "gskpathprivate.h"
#include "gskpathpointprivate.h"
#include "gskpathopprivate.h"
#include "gskboundingboxprivate.h"
G_BEGIN_DECLS
GskContour * gsk_rect_contour_new (const graphene_rect_t *rect);
GskContour * gsk_rounded_rect_contour_new (const GskRoundedRect *rounded_rect);
GskContour * gsk_circle_contour_new (const graphene_point_t *center,
float radius,
float start_angle,
float end_angle);
GskContour * gsk_standard_contour_new (GskPathFlags flags,
const graphene_point_t *points,
gsize n_points,
@@ -44,10 +49,10 @@ GskPathFlags gsk_contour_get_flags (const GskContou
void gsk_contour_print (const GskContour *self,
GString *string);
gboolean gsk_contour_get_bounds (const GskContour *self,
GskBoundingBox *bounds);
graphene_rect_t *bounds);
gboolean gsk_contour_get_stroke_bounds (const GskContour *self,
const GskStroke *stroke,
GskBoundingBox *bounds);
graphene_rect_t *bounds);
gboolean gsk_contour_foreach (const GskContour *self,
float tolerance,
GskPathForeachFunc func,
@@ -80,32 +85,23 @@ gpointer gsk_contour_init_measure (const GskContou
float *out_length);
void gsk_contour_free_measure (const GskContour *self,
gpointer data);
int gsk_contour_point_compare (const GskContour *self,
GskRealPathPoint *p1,
GskRealPathPoint *p2);
void gsk_contour_add_segment (const GskContour *self,
GskPathBuilder *builder,
gpointer measure_data,
gboolean emit_move_to,
GskRealPathPoint *start,
GskRealPathPoint *end);
float start,
float end);
void gsk_contour_get_point (const GskContour *self,
gpointer measure_data,
float offset,
GskRealPathPoint *result);
void gsk_contour_get_start_point (const GskContour *self,
GskRealPathPoint *result);
void gsk_contour_get_end_point (const GskContour *self,
GskRealPathPoint *result);
gboolean gsk_contour_get_previous_point (const GskContour *self,
GskRealPathPoint *point,
GskRealPathPoint *result);
gboolean gsk_contour_get_next_point (const GskContour *self,
GskRealPathPoint *point,
GskRealPathPoint *result);
float gsk_contour_get_distance (const GskContour *self,
GskRealPathPoint *point,
gpointer measure_data);
gboolean gsk_contour_dash (const GskContour *contour,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data);
G_END_DECLS

View File

@@ -35,7 +35,8 @@ struct _GskCurveClass
void (* init_foreach) (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts);
gsize n_pts,
float weight);
void (* print) (const GskCurve *curve,
GString *string);
gskpathop (* pathop) (const GskCurve *curve);
@@ -156,7 +157,8 @@ static void
gsk_line_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts)
gsize n_pts,
float weight)
{
GskLineCurve *self = &curve->line;
@@ -298,7 +300,7 @@ gsk_line_curve_decompose_curve (const GskCurve *curve,
{
const GskLineCurve *self = &curve->line;
return add_curve_func (GSK_PATH_LINE, self->points, 2, user_data);
return add_curve_func (GSK_PATH_LINE, self->points, 2, 0.f, user_data);
}
static void
@@ -375,7 +377,8 @@ static void
gsk_quad_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts)
gsize n_pts,
float weight)
{
GskQuadCurve *self = &curve->quad;
@@ -588,7 +591,7 @@ gsk_curve_add_line_cb (const graphene_point_t *from,
AddLineData *data = user_data;
graphene_point_t p[2] = { *from, *to };
return data->add_curve (GSK_PATH_LINE, p, 2, data->user_data);
return data->add_curve (GSK_PATH_LINE, p, 2, 0.f, data->user_data);
}
static gboolean
@@ -601,13 +604,20 @@ gsk_quad_curve_decompose_curve (const GskCurve *curve,
const GskQuadCurve *self = &curve->quad;
if (flags & GSK_PATH_FOREACH_ALLOW_QUAD)
return add_curve_func (GSK_PATH_QUAD, self->points, 3, user_data);
return add_curve_func (GSK_PATH_QUAD, self->points, 3, 0.f, user_data);
else if (flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
{
GskCurve c;
gsk_curve_elevate (curve, &c);
return add_curve_func (GSK_PATH_CUBIC, c.cubic.points, 4, user_data);
return add_curve_func (GSK_PATH_CUBIC, c.cubic.points, 4, 0.f, user_data);
}
else if (flags & GSK_PATH_FOREACH_ALLOW_CONIC)
{
GskCurve c;
gsk_curve_init_foreach (&c, GSK_PATH_CONIC, self->points, 3, 1.f);
return add_curve_func (GSK_PATH_CUBIC, c.cubic.points, 4, 0.f, user_data);
}
else
{
@@ -736,7 +746,8 @@ static void
gsk_cubic_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts)
gsize n_pts,
float weight)
{
GskCubicCurve *self = &curve->cubic;
@@ -909,6 +920,7 @@ gsk_curve_get_derivative (const GskCurve *curve,
case GSK_PATH_MOVE:
case GSK_PATH_CLOSE:
case GSK_PATH_CONIC:
default:
g_assert_not_reached ();
}
@@ -1056,7 +1068,7 @@ gsk_cubic_curve_decompose_curve (const GskCurve *curve,
const GskCubicCurve *self = &curve->cubic;
if (flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
return add_curve_func (GSK_PATH_CUBIC, self->points, 4, user_data);
return add_curve_func (GSK_PATH_CUBIC, self->points, 4, 0.f, user_data);
/* FIXME: Quadratic (or conic?) approximation */
return gsk_cubic_curve_decompose (curve,
@@ -1171,6 +1183,665 @@ static const GskCurveClass GSK_CUBIC_CURVE_CLASS = {
gsk_cubic_curve_get_tight_bounds,
};
/* }}} */
/* {{{ Conic */
static inline float
gsk_conic_curve_get_weight (const GskConicCurve *self)
{
return self->points[2].x;
}
static void
gsk_conic_curve_ensure_coefficents (const GskConicCurve *curve)
{
GskConicCurve *self = (GskConicCurve *) curve;
float w = gsk_conic_curve_get_weight (self);
const graphene_point_t *pts = self->points;
graphene_point_t pw = GRAPHENE_POINT_INIT (w * pts[1].x, w * pts[1].y);
if (self->has_coefficients)
return;
self->num[2] = pts[0];
self->num[1] = GRAPHENE_POINT_INIT (2 * (pw.x - pts[0].x),
2 * (pw.y - pts[0].y));
self->num[0] = GRAPHENE_POINT_INIT (pts[3].x - 2 * pw.x + pts[0].x,
pts[3].y - 2 * pw.y + pts[0].y);
self->denom[2] = GRAPHENE_POINT_INIT (1, 1);
self->denom[1] = GRAPHENE_POINT_INIT (2 * (w - 1), 2 * (w - 1));
self->denom[0] = GRAPHENE_POINT_INIT (-self->denom[1].x, -self->denom[1].y);
self->has_coefficients = TRUE;
}
static void
gsk_conic_curve_init_from_points (GskConicCurve *self,
const graphene_point_t pts[4])
{
self->op = GSK_PATH_CONIC;
self->has_coefficients = FALSE;
memcpy (self->points, pts, sizeof (graphene_point_t) * 4);
}
static void
gsk_conic_curve_init (GskCurve *curve,
gskpathop op)
{
GskConicCurve *self = &curve->conic;
gsk_conic_curve_init_from_points (self, gsk_pathop_points (op));
}
static void
gsk_conic_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
{
GskConicCurve *self = &curve->conic;
g_assert (n_pts == 3);
gsk_conic_curve_init_from_points (self,
(graphene_point_t[4]) {
pts[0],
pts[1],
GRAPHENE_POINT_INIT (weight, 0),
pts[2]
});
}
static void
gsk_conic_curve_print (const GskCurve *curve,
GString *string)
{
g_string_append_printf (string,
"M %g %g O %g %g %g %g %g",
curve->conic.points[0].x, curve->conic.points[0].y,
curve->conic.points[1].x, curve->conic.points[1].y,
curve->conic.points[3].x, curve->conic.points[3].y,
curve->conic.points[2].x);
}
static gskpathop
gsk_conic_curve_pathop (const GskCurve *curve)
{
const GskConicCurve *self = &curve->conic;
return gsk_pathop_encode (self->op, self->points);
}
static const graphene_point_t *
gsk_conic_curve_get_start_point (const GskCurve *curve)
{
const GskConicCurve *self = &curve->conic;
return &self->points[0];
}
static const graphene_point_t *
gsk_conic_curve_get_end_point (const GskCurve *curve)
{
const GskConicCurve *self = &curve->conic;
return &self->points[3];
}
static void
gsk_conic_curve_get_start_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskConicCurve *self = &curve->conic;
get_tangent (&self->points[0], &self->points[1], tangent);
}
static void
gsk_conic_curve_get_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskConicCurve *self = &curve->conic;
get_tangent (&self->points[1], &self->points[3], tangent);
}
static inline void
gsk_curve_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_conic_curve_eval_point (const GskConicCurve *self,
float progress,
graphene_point_t *point)
{
graphene_point_t num, denom;
gsk_curve_eval_quad (self->num, progress, &num);
gsk_curve_eval_quad (self->denom, progress, &denom);
*point = GRAPHENE_POINT_INIT (num.x / denom.x, num.y / denom.y);
}
static void
gsk_conic_curve_get_point (const GskCurve *curve,
float t,
graphene_point_t *pos)
{
const GskConicCurve *self = &curve->conic;
gsk_conic_curve_ensure_coefficents (self);
gsk_conic_curve_eval_point (self, t, pos);
}
static void
gsk_conic_curve_get_tangent (const GskCurve *curve,
float t,
graphene_vec2_t *tangent)
{
const GskConicCurve *self = &curve->conic;
graphene_point_t tmp;
float w = gsk_conic_curve_get_weight (self);
const graphene_point_t *pts = self->points;
/* The tangent will be 0 in these corner cases, just
* treat it like a line here. */
if ((t <= 0.f && graphene_point_equal (&pts[0], &pts[1])) ||
(t >= 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_curve_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))
},
t,
&tmp);
graphene_vec2_init (tangent, tmp.x, tmp.y);
graphene_vec2_normalize (tangent, tangent);
}
/* See M. Floater, Derivatives of rational Bezier curves */
static float
gsk_conic_curve_get_curvature (const GskCurve *curve,
float t)
{
graphene_point_t p[3], p1[2];
float w, w1[2], w2;
graphene_vec2_t t1, t2, t3;
w = curve->conic.points[2].x;
p[0] = curve->conic.points[0];
p[1] = curve->conic.points[1];
p[2] = curve->conic.points[3];
w1[0] = (1 - t) + t*w;
w1[1] = (1 - t)*w + t;
w2 = (1 - t)*w1[0] + t*w1[1];
p1[0].x = ((1 - t)*p[0].x + t*w*p[1].x)/w1[0];
p1[0].y = ((1 - t)*p[0].y + t*w*p[1].y)/w1[0];
p1[1].x = ((1 - t)*w*p[1].x + t*p[2].x)/w1[1];
p1[1].y = ((1 - t)*w*p[1].y + t*p[2].y)/w1[1];
graphene_vec2_init (&t1, p[1].x - p[0].x, p[1].y - p[0].y);
graphene_vec2_init (&t2, p[2].x - p[1].x, p[2].y - p[1].y);
graphene_vec2_init (&t3, p1[1].x - p1[0].x, p1[1].y - p1[0].y);
return 0.5 * ((w*pow3 (w2))/(pow3 (w1[0])*pow3 (w1[1]))) * (cross (&t1, &t2) / pow3 (graphene_vec2_length (&t3)));
}
static void
gsk_conic_curve_reverse (const GskCurve *curve,
GskCurve *reverse)
{
const GskConicCurve *self = &curve->conic;
reverse->op = GSK_PATH_CONIC;
reverse->conic.points[0] = self->points[3];
reverse->conic.points[1] = self->points[1];
reverse->conic.points[2] = self->points[2];
reverse->conic.points[3] = self->points[0];
reverse->conic.has_coefficients = FALSE;
}
static void
split_bezier3d_recurse (const graphene_point3d_t *p,
int l,
float t,
graphene_point3d_t *left,
graphene_point3d_t *right,
int *lpos,
int *rpos)
{
if (l == 1)
{
left[*lpos] = p[0];
right[*rpos] = p[0];
}
else
{
graphene_point3d_t *np;
int i;
np = g_alloca (sizeof (graphene_point3d_t) * (l - 1));
for (i = 0; i < l - 1; i++)
{
if (i == 0)
{
left[*lpos] = p[i];
(*lpos)++;
}
if (i + 1 == l - 1)
{
right[*rpos] = p[i + 1];
(*rpos)--;
}
graphene_point3d_interpolate (&p[i], &p[i + 1], t, &np[i]);
}
split_bezier3d_recurse (np, l - 1, t, left, right, lpos, rpos);
}
}
static void
split_bezier3d (const graphene_point3d_t *p,
int l,
float t,
graphene_point3d_t *left,
graphene_point3d_t *right)
{
int lpos = 0;
int rpos = l - 1;
split_bezier3d_recurse (p, l, t, left, right, &lpos, &rpos);
}
static void
gsk_conic_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end)
{
const GskConicCurve *self = &curve->conic;
graphene_point3d_t p[3];
graphene_point3d_t l[3], r[3];
graphene_point_t left[4], right[4];
float w;
/* do de Casteljau in homogeneous coordinates... */
w = self->points[2].x;
p[0] = GRAPHENE_POINT3D_INIT (self->points[0].x, self->points[0].y, 1);
p[1] = GRAPHENE_POINT3D_INIT (self->points[1].x * w, self->points[1].y * w, w);
p[2] = GRAPHENE_POINT3D_INIT (self->points[3].x, self->points[3].y, 1);
split_bezier3d (p, 3, progress, l, r);
/* then project the control points down */
left[0] = GRAPHENE_POINT_INIT (l[0].x / l[0].z, l[0].y / l[0].z);
left[1] = GRAPHENE_POINT_INIT (l[1].x / l[1].z, l[1].y / l[1].z);
left[3] = GRAPHENE_POINT_INIT (l[2].x / l[2].z, l[2].y / l[2].z);
right[0] = GRAPHENE_POINT_INIT (r[0].x / r[0].z, r[0].y / r[0].z);
right[1] = GRAPHENE_POINT_INIT (r[1].x / r[1].z, r[1].y / r[1].z);
right[3] = GRAPHENE_POINT_INIT (r[2].x / r[2].z, r[2].y / r[2].z);
/* normalize the outer weights to be 1 by using
* the fact that weights w_i and c*w_i are equivalent
* for any nonzero constant c
*/
for (int i = 0; i < 3; i++)
{
l[i].z /= l[0].z;
r[i].z /= r[2].z;
}
/* normalize the inner weight to be 1 by using
* the fact that w_0*w_2/w_1^2 is a constant for
* all equivalent weights.
*/
left[2] = GRAPHENE_POINT_INIT (l[1].z / sqrt (l[2].z), 0);
right[2] = GRAPHENE_POINT_INIT (r[1].z / sqrt (r[0].z), 0);
if (start)
gsk_curve_init (start, gsk_pathop_encode (GSK_PATH_CONIC, left));
if (end)
gsk_curve_init (end, gsk_pathop_encode (GSK_PATH_CONIC, right));
}
static void
gsk_conic_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment)
{
const GskConicCurve *self = &curve->conic;
graphene_point_t start_num, start_denom;
graphene_point_t mid_num, mid_denom;
graphene_point_t end_num, end_denom;
graphene_point_t ctrl_num, ctrl_denom;
float mid;
if (start <= 0.0f)
return gsk_conic_curve_split (curve, end, segment, NULL);
else if (end >= 1.0f)
return gsk_conic_curve_split (curve, start, NULL, segment);
gsk_conic_curve_ensure_coefficents (self);
gsk_curve_eval_quad (self->num, start, &start_num);
gsk_curve_eval_quad (self->denom, start, &start_denom);
mid = (start + end) / 2;
gsk_curve_eval_quad (self->num, mid, &mid_num);
gsk_curve_eval_quad (self->denom, mid, &mid_denom);
gsk_curve_eval_quad (self->num, end, &end_num);
gsk_curve_eval_quad (self->denom, end, &end_denom);
ctrl_num = GRAPHENE_POINT_INIT (2 * mid_num.x - (start_num.x + end_num.x) / 2,
2 * mid_num.y - (start_num.y + end_num.y) / 2);
ctrl_denom = GRAPHENE_POINT_INIT (2 * mid_denom.x - (start_denom.x + end_denom.x) / 2,
2 * mid_denom.y - (start_denom.y + end_denom.y) / 2);
gsk_conic_curve_init_from_points (&segment->conic,
(graphene_point_t[4]) {
GRAPHENE_POINT_INIT (start_num.x / start_denom.x,
start_num.y / start_denom.y),
GRAPHENE_POINT_INIT (ctrl_num.x / ctrl_denom.x,
ctrl_num.y / ctrl_denom.y),
GRAPHENE_POINT_INIT (ctrl_denom.x / sqrtf (start_denom.x * end_denom.x),
0),
GRAPHENE_POINT_INIT (end_num.x / end_denom.x,
end_num.y / end_denom.y)
});
}
/* taken from Skia, including the very descriptive name */
static gboolean
gsk_conic_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_conic_curve_decompose_subdivide (const GskConicCurve *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_conic_curve_eval_point (self, mid_progress, &mid);
if (!gsk_conic_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_conic_curve_decompose_subdivide (self, tolerance,
start, start_progress, &mid, mid_progress,
add_line_func, user_data)
&& gsk_conic_curve_decompose_subdivide (self, tolerance,
&mid, mid_progress, end, end_progress,
add_line_func, user_data);
}
static gboolean
gsk_conic_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
const GskConicCurve *self = &curve->conic;
graphene_point_t mid;
gsk_conic_curve_ensure_coefficents (self);
gsk_conic_curve_eval_point (self, 0.5, &mid);
return gsk_conic_curve_decompose_subdivide (self,
tolerance,
&self->points[0],
0.0f,
&mid,
0.5f,
add_line_func,
user_data)
&& gsk_conic_curve_decompose_subdivide (self,
tolerance,
&mid,
0.5f,
&self->points[3],
1.0f,
add_line_func,
user_data);
}
/* See Floater, M: An analysis of cubic approximation schemes
* for conic sections
*/
static void
cubic_approximation (const GskCurve *curve,
GskCurve *cubic)
{
const GskConicCurve *self = &curve->conic;
graphene_point_t p[4];
float w = self->points[2].x;
float w2 = w*w;
float lambda;
lambda = 2 * (6*w2 + 1 - sqrt (3*w2 + 1)) / (12*w2 + 3);
p[0] = self->points[0];
p[3] = self->points[3];
graphene_point_interpolate (&self->points[0], &self->points[1], lambda, &p[1]);
graphene_point_interpolate (&self->points[3], &self->points[1], lambda, &p[2]);
gsk_curve_init (cubic, gsk_pathop_encode (GSK_PATH_CUBIC, p));
}
static gboolean
gsk_conic_is_close_to_cubic (const GskCurve *conic,
const GskCurve *cubic,
float tolerance)
{
float t[] = { 0.1, 0.5, 0.9 };
graphene_point_t p0, p1;
for (int i = 0; i < G_N_ELEMENTS (t); i++)
{
gsk_curve_get_point (conic, t[i], &p0);
gsk_curve_get_point (cubic, t[i], &p1);
if (graphene_point_distance (&p0, &p1, NULL, NULL) > tolerance)
return FALSE;
}
return TRUE;
}
static gboolean gsk_conic_curve_decompose_curve (const GskCurve *curve,
GskPathForeachFlags flags,
float tolerance,
GskCurveAddCurveFunc add_curve_func,
gpointer user_data);
static gboolean
gsk_conic_curve_decompose_or_add (const GskCurve *curve,
const GskCurve *cubic,
float tolerance,
GskCurveAddCurveFunc add_curve_func,
gpointer user_data)
{
if (gsk_conic_is_close_to_cubic (curve, cubic, tolerance))
return add_curve_func (GSK_PATH_CUBIC, cubic->cubic.points, 4, 0.f, user_data);
else
{
GskCurve c1, c2;
GskCurve cc1, cc2;
gsk_conic_curve_split (curve, 0.5, &c1, &c2);
cubic_approximation (&c1, &cc1);
cubic_approximation (&c2, &cc2);
return gsk_conic_curve_decompose_or_add (&c1, &cc1, tolerance, add_curve_func, user_data) &&
gsk_conic_curve_decompose_or_add (&c2, &cc2, tolerance, add_curve_func, user_data);
}
}
static gboolean
gsk_conic_curve_decompose_curve (const GskCurve *curve,
GskPathForeachFlags flags,
float tolerance,
GskCurveAddCurveFunc add_curve_func,
gpointer user_data)
{
const GskConicCurve *self = &curve->conic;
GskCurve c;
if (flags & GSK_PATH_FOREACH_ALLOW_CONIC)
return add_curve_func (GSK_PATH_CONIC,
(const graphene_point_t[3]) { self->points[0],
self->points[1],
self->points[3] },
3,
self->points[2].x,
user_data);
if (flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
{
cubic_approximation (curve, &c);
return gsk_conic_curve_decompose_or_add (curve, &c, tolerance, add_curve_func, user_data);
}
/* FIXME: Quadratic (or conic?) approximation */
return gsk_conic_curve_decompose (curve,
tolerance,
gsk_curve_add_line_cb,
&(AddLineData) { add_curve_func, user_data });
}
static void
gsk_conic_curve_get_bounds (const GskCurve *curve,
GskBoundingBox *bounds)
{
const GskConicCurve *self = &curve->conic;
const graphene_point_t *pts = self->points;
gsk_bounding_box_init (bounds, &pts[0], &pts[3]);
gsk_bounding_box_expand (bounds, &pts[1]);
}
/* Solve N = 0 where N is the numerator of (P/Q)', with
* P = (1-t)^2*a + 2*t*(1-t)*w*b + t^2*c
* Q = (1-t)^2 + 2*t*(1-t)*w + t^2
*/
static int
get_conic_extrema (float a, float b, float c, float w, float t[4])
{
float q, tt;
int n = 0;
float w2 = w*w;
float wac = (w - 1)*(a - c);
if (wac != 0)
{
q = - sqrt (a*a - 4*a*b*w2 + 4*a*c*w2 - 2*a*c + 4*b*b*w2 - 4*b*c*w2 + c*c);
tt = (- q + 2*a*w - a - 2*b*w + c)/(2*wac);
if (acceptable (tt))
t[n++] = tt;
tt = (q + 2*a*w - a - 2*b*w + c)/(2*wac);
if (acceptable (tt))
t[n++] = tt;
}
if (w * (b - c) != 0 && a == c)
t[n++] = 0.5;
if (w == 1 && a - 2*b + c != 0)
{
tt = (a - b) / (a - 2*b + c);
if (acceptable (tt))
t[n++] = tt;
}
return n;
}
static void
gsk_conic_curve_get_tight_bounds (const GskCurve *curve,
GskBoundingBox *bounds)
{
const GskConicCurve *self = &curve->conic;
float w = gsk_conic_curve_get_weight (self);
const graphene_point_t *pts = self->points;
float t[8];
int n;
gsk_bounding_box_init (bounds, &pts[0], &pts[3]);
n = 0;
n += get_conic_extrema (pts[0].x, pts[1].x, pts[3].x, w, &t[n]);
n += get_conic_extrema (pts[0].y, pts[1].y, pts[3].y, w, &t[n]);
for (int i = 0; i < n; i++)
{
graphene_point_t p;
gsk_conic_curve_get_point (curve, t[i], &p);
gsk_bounding_box_expand (bounds, &p);
}
}
static const GskCurveClass GSK_CONIC_CURVE_CLASS = {
gsk_conic_curve_init,
gsk_conic_curve_init_foreach,
gsk_conic_curve_print,
gsk_conic_curve_pathop,
gsk_conic_curve_get_start_point,
gsk_conic_curve_get_end_point,
gsk_conic_curve_get_start_tangent,
gsk_conic_curve_get_end_tangent,
gsk_conic_curve_get_point,
gsk_conic_curve_get_tangent,
gsk_conic_curve_reverse,
gsk_conic_curve_get_curvature,
gsk_conic_curve_split,
gsk_conic_curve_segment,
gsk_conic_curve_decompose,
gsk_conic_curve_decompose_curve,
gsk_conic_curve_get_bounds,
gsk_conic_curve_get_tight_bounds,
};
/* }}} */
/* {{{ API */
@@ -1182,6 +1853,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_CONIC] = &GSK_CONIC_CURVE_CLASS,
};
g_assert (op < G_N_ELEMENTS (klasses) && klasses[op] != NULL);
@@ -1201,10 +1873,11 @@ void
gsk_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts)
gsize n_pts,
float weight)
{
memset (curve, 0, sizeof (GskCurve));
get_class (op)->init_foreach (curve, op, pts, n_pts);
get_class (op)->init_foreach (curve, op, pts, n_pts, weight);
}
void

View File

@@ -33,6 +33,7 @@ typedef union _GskCurve GskCurve;
typedef struct _GskLineCurve GskLineCurve;
typedef struct _GskQuadCurve GskQuadCurve;
typedef struct _GskCubicCurve GskCubicCurve;
typedef struct _GskConicCurve GskConicCurve;
struct _GskLineCurve
{
@@ -65,12 +66,28 @@ struct _GskCubicCurve
graphene_point_t coeffs[4];
};
struct _GskConicCurve
{
GskPathOperation op;
gboolean has_coefficients;
/* points[0], points[1], points[3] are the control points,
* points[2].x is the weight
*/
graphene_point_t points[4];
graphene_point_t num[3];
graphene_point_t denom[3];
};
union _GskCurve
{
GskPathOperation op;
GskLineCurve line;
GskQuadCurve quad;
GskCubicCurve cubic;
GskConicCurve conic;
};
typedef enum {
@@ -88,6 +105,7 @@ typedef gboolean (* GskCurveAddLineFunc) (const graphene_point_t *from,
typedef gboolean (* GskCurveAddCurveFunc) (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data);
void gsk_curve_init (GskCurve *curve,
@@ -95,7 +113,8 @@ void gsk_curve_init (GskCurve
void gsk_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts);
gsize n_pts,
float weight);
void gsk_curve_print (const GskCurve *curve,
GString *string);

View File

@@ -233,6 +233,7 @@ typedef enum {
/**
* GskLineJoin:
* @GSK_LINE_JOIN_MITER: Use a sharp angled corner
* @GSK_LINE_JOIN_MITER_CLIP: Use a sharp, angled corner, at a distance
* @GSK_LINE_JOIN_ROUND: Use a round join, the center of the circle is
* the join point
* @GSK_LINE_JOIN_BEVEL: use a cut-off join, the join is cut off at half
@@ -240,6 +241,9 @@ typedef enum {
*
* Specifies how to render the junction of two lines when stroking.
*
* See [method@Gsk.Stroke.set_miter_limit] for details on the difference
* between `GSK_LINE_JOIN_MITER` and `GSK_LINE_JOIN_MITER_CLIP`.
*
* The default line join style is `GSK_LINE_JOIN_MITER`.
*
* New entries may be added in future versions.
@@ -248,6 +252,7 @@ typedef enum {
*/
typedef enum {
GSK_LINE_JOIN_MITER,
GSK_LINE_JOIN_MITER_CLIP,
GSK_LINE_JOIN_ROUND,
GSK_LINE_JOIN_BEVEL,
} GskLineJoin;
@@ -265,6 +270,9 @@ 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_CONIC: A weighted quadratic Bézier 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 are used to described segments of a `GskPath`.
*
@@ -278,6 +286,7 @@ typedef enum {
GSK_PATH_LINE,
GSK_PATH_QUAD,
GSK_PATH_CUBIC,
GSK_PATH_CONIC,
} GskPathOperation;
/**

View File

@@ -24,7 +24,6 @@
#include "gskcurveprivate.h"
#include "gskpathbuilder.h"
#include "gskpathpointprivate.h"
#include "gsksplineprivate.h"
/**
* GskPath:
@@ -146,17 +145,14 @@ gsk_path_unref (GskPath *self)
}
const GskContour *
gsk_path_get_contour (const GskPath *self,
gsize i)
gsk_path_get_contour (GskPath *path,
gsize i)
{
if (i < self->n_contours)
return self->contours[i];
else
return NULL;
return path->contours[i];
}
GskPathFlags
gsk_path_get_flags (const GskPath *self)
gsk_path_get_flags (GskPath *self)
{
return self->flags;
}
@@ -170,7 +166,10 @@ 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),
* with the exception that conic curves will generate a string of the
* form "O x1 y1, x2 y2, w" where x1, y1 is the control point, x2, y2
* is the end point, and w is the weight.
*
* Since: 4.14
*/
@@ -225,6 +224,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)
@@ -256,6 +256,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;
@@ -303,9 +304,9 @@ gsk_path_to_cairo (GskPath *self,
* Returns: the number of contours in @path
*/
gsize
gsk_path_get_n_contours (const GskPath *self)
gsk_path_get_n_contours (GskPath *path)
{
return self->n_contours;
return path->n_contours;
}
/**
@@ -378,33 +379,35 @@ gboolean
gsk_path_get_bounds (GskPath *self,
graphene_rect_t *bounds)
{
GskBoundingBox b;
gsize i;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (bounds != NULL, FALSE);
if (self->n_contours == 0)
for (i = 0; i < self->n_contours; i++)
{
if (gsk_contour_get_bounds (self->contours[i], bounds))
break;
}
if (i >= self->n_contours)
{
graphene_rect_init_from_rect (bounds, graphene_rect_zero ());
return FALSE;
}
gsk_contour_get_bounds (self->contours[0], &b);
for (gsize i = 1; i < self->n_contours; i++)
for (i++; i < self->n_contours; i++)
{
GskBoundingBox tmp;
graphene_rect_t tmp;
gsk_contour_get_bounds (self->contours[i], &tmp);
gsk_bounding_box_union (&b, &tmp, &b);
if (gsk_contour_get_bounds (self->contours[i], &tmp))
graphene_rect_union (bounds, &tmp, bounds);
}
gsk_bounding_box_to_rect (&b, bounds);
return TRUE;
}
/**
/*< private >
* gsk_path_get_stroke_bounds:
* @self: a #GtkPath
* @stroke: stroke parameters
@@ -419,36 +422,38 @@ gsk_path_get_bounds (GskPath *self,
* like miters.
*
* Returns: `TRUE` if the path has bounds, `FALSE` if the path is known
* to be empty and have no bounds.
* to be empty and have no bounds.
*/
gboolean
gsk_path_get_stroke_bounds (GskPath *self,
const GskStroke *stroke,
graphene_rect_t *bounds)
{
GskBoundingBox b;
gsize i;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (bounds != NULL, FALSE);
if (self->n_contours == 0)
for (i = 0; i < self->n_contours; i++)
{
if (gsk_contour_get_stroke_bounds (self->contours[i], stroke, bounds))
break;
}
if (i >= self->n_contours)
{
graphene_rect_init_from_rect (bounds, graphene_rect_zero ());
return FALSE;
}
gsk_contour_get_stroke_bounds (self->contours[0], stroke, &b);
for (gsize i = 1; i < self->n_contours; i++)
for (i++; i < self->n_contours; i++)
{
GskBoundingBox tmp;
graphene_rect_t tmp;
if (gsk_contour_get_stroke_bounds (self->contours[i], stroke, &tmp))
gsk_bounding_box_union (&b, &tmp, &b);
graphene_rect_union (bounds, &tmp, bounds);
}
gsk_bounding_box_to_rect (&b, bounds);
return TRUE;
}
@@ -515,97 +520,27 @@ gsk_path_get_closest_point (GskPath *self,
GskPathPoint *result)
{
GskRealPathPoint *res = (GskRealPathPoint *) result;
gboolean found;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (point != NULL, FALSE);
g_return_val_if_fail (threshold >= 0, FALSE);
g_return_val_if_fail (result != NULL, FALSE);
found = FALSE;
for (int i = 0; i < self->n_contours; i++)
{
float distance;
if (gsk_contour_get_closest_point (self->contours[i], point, threshold, res, &distance))
{
found = TRUE;
res->contour = i;
res->path = self;
res->contour = self->contours[i];
threshold = distance;
}
}
return found;
}
if (res->contour != NULL)
return TRUE;
/**
* gsk_path_get_start_point:
* @self: a `GskPath`
* @result: (out caller-allocates): return location for point
*
* Gets the start point of the path.
*
* An empty path has no points, so `FALSE`
* is returned in this case.
*
* Returns: `TRUE` if @result was filled
*
* Since: 4.14
*/
gboolean
gsk_path_get_start_point (GskPath *self,
GskPathPoint *result)
{
GskRealPathPoint *res = (GskRealPathPoint *) result;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (result != NULL, FALSE);
if (self->n_contours == 0)
return FALSE;
gsk_contour_get_start_point (self->contours[0], res);
res->path = self;
res->contour = 0;
return TRUE;
}
/**
* gsk_path_get_end_point:
* @self: a `GskPath`
* @result: (out caller-allocates): return location for point
*
* Gets the end point of the path.
*
* An empty path has no points, so `FALSE`
* is returned in this case.
*
* Returns: `TRUE` if @result was filled
*
* Since: 4.14
*/
gboolean
gsk_path_get_end_point (GskPath *self,
GskPathPoint *result)
{
GskRealPathPoint *res = (GskRealPathPoint *) result;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (result!= NULL, FALSE);
if (self->n_contours == 0)
return FALSE;
gsk_contour_get_end_point (self->contours[self->n_contours - 1], res);
res->path = self;
res->contour = self->n_contours - 1;
return TRUE;
return FALSE;
}
/**
@@ -665,6 +600,7 @@ gsk_path_foreach_trampoline_add_line (const graphene_point_t *from,
return trampoline->func (GSK_PATH_LINE,
(graphene_point_t[2]) { *from, *to },
2,
0.0f,
trampoline->user_data);
}
@@ -672,17 +608,19 @@ static gboolean
gsk_path_foreach_trampoline_add_curve (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
GskPathForeachTrampoline *trampoline = data;
return trampoline->func (op, pts, n_pts, trampoline->user_data);
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
}
static gboolean
gsk_path_foreach_trampoline (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
GskPathForeachTrampoline *trampoline = data;
@@ -692,28 +630,14 @@ 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_QUAD:
{
GskCurve curve;
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_QUAD)
return trampoline->func (op, pts, n_pts, trampoline->user_data);
else if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
{
return trampoline->func (GSK_PATH_CUBIC,
(graphene_point_t[4]) {
pts[0],
GRAPHENE_POINT_INIT ((pts[0].x + 2 * pts[1].x) / 3,
(pts[0].y + 2 * pts[1].y) / 3),
GRAPHENE_POINT_INIT ((pts[2].x + 2 * pts[1].x) / 3,
(pts[2].y + 2 * pts[1].y) / 3),
pts[2],
},
4,
trampoline->user_data);
}
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_QUAD, pts));
return gsk_curve_decompose (&curve,
@@ -727,10 +651,10 @@ gsk_path_foreach_trampoline (GskPathOperation op,
GskCurve curve;
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
return trampoline->func (op, pts, n_pts, trampoline->user_data);
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_CUBIC, pts));
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_QUAD)
if (trampoline->flags & (GSK_PATH_FOREACH_ALLOW_QUAD|GSK_PATH_FOREACH_ALLOW_CONIC))
return gsk_curve_decompose_curve (&curve,
trampoline->flags,
trampoline->tolerance,
@@ -743,6 +667,28 @@ gsk_path_foreach_trampoline (GskPathOperation op,
trampoline);
}
case GSK_PATH_CONIC:
{
GskCurve curve;
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CONIC)
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_CONIC, (graphene_point_t[4]) { pts[0], pts[1], { weight, }, pts[2] } ));
if (trampoline->flags & (GSK_PATH_FOREACH_ALLOW_QUAD|GSK_PATH_FOREACH_ALLOW_CUBIC))
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;
@@ -750,7 +696,8 @@ gsk_path_foreach_trampoline (GskPathOperation op,
}
#define ALLOW_ANY (GSK_PATH_FOREACH_ALLOW_QUAD| \
GSK_PATH_FOREACH_ALLOW_CUBIC)
GSK_PATH_FOREACH_ALLOW_CUBIC| \
GSK_PATH_FOREACH_ALLOW_CONIC)
gboolean
gsk_path_foreach_with_tolerance (GskPath *self,
@@ -1334,6 +1281,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':
{
@@ -1391,174 +1369,3 @@ error:
return NULL;
}
/**
* gsk_path_builder_add_segment:
* @self: a `GskPathBuilder`
* @path: the `GskPath` to take the segment to
* @start: the point on @path to start at
* @end: the point on @path to end at
*
* Adds to @self the segment of @path from @start to @end.
*
* If @start is after @end, the path will first add the segment
* from @start to the end of the path, and then add the segment from
* the beginning to @end. If the path is closed, these segments will
* be connected.
*
* Since: 4.14
*/
void
gsk_path_builder_add_segment (GskPathBuilder *self,
GskPath *path,
const GskPathPoint *start,
const GskPathPoint *end)
{
GskRealPathPoint *s = (GskRealPathPoint *) start;
GskRealPathPoint *e = (GskRealPathPoint *) end;
const GskContour *contour;
g_return_if_fail (self != NULL);
g_return_if_fail (path != NULL);
g_return_if_fail (path == s->path);
g_return_if_fail (path == e->path);
contour = gsk_path_get_contour (path, s->contour);
if (s->contour == e->contour)
{
if (gsk_contour_point_compare (contour, s, e) < 0)
{
gsk_contour_add_segment (contour, self, TRUE, s, e);
return;
}
else if (path->n_contours == 1)
{
gsk_contour_add_segment (contour, self, TRUE, s, NULL);
gsk_contour_add_segment (contour, self, FALSE, NULL, e);
return;
}
}
gsk_contour_add_segment (contour, self, TRUE, s, NULL);
for (gsize i = (s->contour + 1) % path->n_contours; i != e->contour; i = (i + 1) % path->n_contours)
gsk_path_builder_add_contour (self, gsk_contour_dup (gsk_path_get_contour (path, i)));
contour = gsk_path_get_contour (path, e->contour);
gsk_contour_add_segment (contour, self, FALSE, NULL, e);
}
/**
* gsk_path_get_previous_point:
* @self: a `GskPath`
* @point: a point on @self
* @result: (out caller-allocates): Return location for the result
*
* Gets the previous 'significant' point on @self before @point.
*
* The 'significant' points of a path are typically the
* on-curve points that have been specified when the
* path was created.
*
* For example, in a path with 3 Bézier segments, the
* significant points are the start of the first segment,
* the start point of the second segment (which coincides
* with the end point of the first segment), the start
* point of the third segment, and the end point of the
* last segment.
*
* If @point is the start point of the path, there is no
* prior point, and this function returns `FALSE`.
*
* Returns: `TRUE` if @result has been set to a point
*/
gboolean
gsk_path_get_previous_point (GskPath *self,
const GskPathPoint *point,
GskPathPoint *result)
{
GskRealPathPoint *p = (GskRealPathPoint *) point;
GskRealPathPoint *res = (GskRealPathPoint *) result;
const GskContour *contour;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (self == p->path, FALSE);
contour = gsk_path_get_contour (self, p->contour);
if (gsk_contour_get_previous_point (contour, p, res))
{
res->path = self;
res->contour = p->contour;
return TRUE;
}
if (p->contour > 0)
{
contour = gsk_path_get_contour (self, p->contour - 1);
gsk_contour_get_end_point (contour, res);
res->path = self;
res->contour = p->contour - 1;
return TRUE;
}
return FALSE;
}
/**
* gsk_path_get_next_point:
* @self: a `GskPath`
* @point: a point on @self
* @result: (out caller-allocates): Return location for the result
*
* Gets the next 'significant' point on @self after @point.
*
* The 'significant' points of a path are typically the
* on-curve points that have been specified when the
* path was created.
*
* For example, in a path with 3 Bézier segments, the
* significant points are the start of the first segment,
* the start point of the second segment (which coincides
* with the end point of the first segment), the start
* point of the third segment, and the end point of the
* last segment.
*
* If @point is the end point of the path, there is no
* next point, and this function returns `FALSE`.
*
* Returns: `TRUE` if @result has been set to a point
*/
gboolean
gsk_path_get_next_point (GskPath *self,
const GskPathPoint *point,
GskPathPoint *result)
{
GskRealPathPoint *p = (GskRealPathPoint *) point;
GskRealPathPoint *res = (GskRealPathPoint *) result;
const GskContour *contour;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (self == p->path, FALSE);
contour = gsk_path_get_contour (self, p->contour);
if (gsk_contour_get_next_point (contour, p, res))
{
res->path = self;
res->contour = p->contour;
return TRUE;
}
if (p->contour < self->n_contours - 1)
{
contour = gsk_path_get_contour (self, p->contour + 1);
gsk_contour_get_start_point (contour, res);
res->path = self;
res->contour = p->contour + 1;
return TRUE;
}
return FALSE;
}

View File

@@ -30,10 +30,9 @@ G_BEGIN_DECLS
/**
* GskPathForeachFlags:
* @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_ANY: Allow emission of any kind of operation.
* @GSK_PATH_FOREACH_ALLOW_CONIC: Allow emission of `GSK_PATH_CONIC` operations.
*
* Flags that can be passed to gsk_path_foreach() to enable additional
* features.
@@ -44,9 +43,9 @@ G_BEGIN_DECLS
*/
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_QUAD = (1 << 0),
GSK_PATH_FOREACH_ALLOW_CUBIC = (1 << 1),
GSK_PATH_FOREACH_ALLOW_CONIC = (1 << 2),
} GskPathForeachFlags;
/**
@@ -54,17 +53,19 @@ 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
* a path.
*
* Returns: %TRUE to continue evaluating the path, %FALSE to
* immediately abort and not call the function again.
* immediately abort and not call the function again.
*/
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 ())
@@ -100,11 +101,6 @@ GDK_AVAILABLE_IN_4_14
gboolean gsk_path_get_bounds (GskPath *self,
graphene_rect_t *bounds);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_get_stroke_bounds (GskPath *self,
const GskStroke *stroke,
graphene_rect_t *bounds);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_in_fill (GskPath *self,
const graphene_point_t *point,
@@ -116,29 +112,19 @@ gboolean gsk_path_get_closest_point (GskPath
float threshold,
GskPathPoint *result);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_get_previous_point (GskPath *self,
const GskPathPoint *point,
GskPathPoint *result);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_get_next_point (GskPath *self,
const GskPathPoint *point,
GskPathPoint *result);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_get_start_point (GskPath *self,
GskPathPoint *result);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_get_end_point (GskPath *self,
GskPathPoint *result);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_foreach (GskPath *self,
GskPathForeachFlags flags,
GskPathForeachFunc func,
gpointer user_data);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_dash (GskPath *self,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
G_END_DECLS

View File

@@ -25,7 +25,6 @@
#include "gskpathprivate.h"
#include "gskcontourprivate.h"
#include "gsksplineprivate.h"
/**
* GskPathBuilder:
@@ -310,8 +309,6 @@ gsk_path_builder_add_contour (GskPathBuilder *self,
* drawing commands and updated after every operation.
*
* When the builder is created, the default current point is set to (0, 0).
* Note that this is different from cairo, which starts out without
* a current point.
*
* Returns: (transfer none): The current point
*
@@ -442,29 +439,36 @@ void
gsk_path_builder_add_rect (GskPathBuilder *self,
const graphene_rect_t *rect)
{
GskContour *contour;
g_return_if_fail (self != NULL);
gsk_path_builder_move_to (self, rect->origin.x, rect->origin.y);
contour = gsk_rect_contour_new (rect);
gsk_path_builder_add_contour (self, contour);
gsk_path_builder_rel_line_to (self, rect->size.width, 0);
gsk_path_builder_rel_line_to (self, 0, rect->size.height);
gsk_path_builder_rel_line_to (self, - rect->size.width, 0);
gsk_path_builder_close (self);
gsk_contour_get_start_end (contour, NULL, &self->current_point);
}
static gboolean
circle_contour_curve (const graphene_point_t pts[4],
gpointer data)
/**
* gsk_path_builder_add_rounded_rect:
* @self: a #GskPathBuilder
* @rect: the rounded rect
*
* Adds @rect as a new contour to the path built in @self.
*
* Since: 4.14
*/
void
gsk_path_builder_add_rounded_rect (GskPathBuilder *self,
const GskRoundedRect *rect)
{
GskPathBuilder *self = data;
GskContour *contour;
gsk_path_builder_cubic_to (self,
pts[1].x, pts[1].y,
pts[2].x, pts[2].y,
pts[3].x, pts[3].y);
g_return_if_fail (self != NULL);
g_return_if_fail (rect != NULL);
return TRUE;
contour = gsk_rounded_rect_contour_new (rect);
gsk_path_builder_add_contour (self, contour);
}
/**
@@ -482,15 +486,57 @@ gsk_path_builder_add_circle (GskPathBuilder *self,
const graphene_point_t *center,
float radius)
{
GskContour *contour;
g_return_if_fail (self != NULL);
g_return_if_fail (center != NULL);
g_return_if_fail (radius > 0);
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);
contour = gsk_circle_contour_new (center, radius, 0, 360);
gsk_path_builder_add_contour (self, contour);
}
/**
* gsk_path_builder_add_ellipse:
* @self: a #GskPathBuilder
* @center: the center point of the ellipse
* @radius: the radius of the ellipse in x/y direction
*
* Adds an ellipse with the given @center and the @radius in
* x/y direction.
*
* Since: 4.14
*/
void
gsk_path_builder_add_ellipse (GskPathBuilder *self,
const graphene_point_t *center,
const graphene_size_t *radius)
{
const float weight = M_SQRT1_2;
graphene_point_t pts[8];
float w, h;
g_return_if_fail (self != NULL);
g_return_if_fail (center != NULL);
g_return_if_fail (radius != NULL);
w = radius->width / 2;
h = radius->height / 2;
pts[0] = GRAPHENE_POINT_INIT (center->x + w, center->y);
pts[1] = GRAPHENE_POINT_INIT (center->x + w, center->y + h);
pts[2] = GRAPHENE_POINT_INIT (center->x, center->y + h);
pts[3] = GRAPHENE_POINT_INIT (center->x - w, center->y + h);
pts[4] = GRAPHENE_POINT_INIT (center->x - w, center->y);
pts[5] = GRAPHENE_POINT_INIT (center->x - w, center->y - h);
pts[6] = GRAPHENE_POINT_INIT (center->x, center->y - h);
pts[7] = GRAPHENE_POINT_INIT (center->x + w, center->y - h);
gsk_path_builder_move_to (self, pts[0].x, pts[0].y);
gsk_path_builder_conic_to (self, pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
gsk_path_builder_conic_to (self, pts[3].x, pts[3].y, pts[4].x, pts[4].y, weight);
gsk_path_builder_conic_to (self, pts[5].x, pts[5].y, pts[6].x, pts[6].y, weight);
gsk_path_builder_conic_to (self, pts[7].x, pts[7].y, pts[0].x, pts[0].y, weight);
gsk_path_builder_close (self);
}
@@ -745,6 +791,82 @@ gsk_path_builder_rel_cubic_to (GskPathBuilder *self,
self->current_point.y + y3);
}
/**
* gsk_path_builder_conic_to:
* @self: 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.
*
* Since: 4.14
*/
void
gsk_path_builder_conic_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2,
float weight)
{
g_return_if_fail (self != NULL);
g_return_if_fail (weight >= 0);
self->flags &= ~GSK_PATH_FLAT;
gsk_path_builder_append_current (self,
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:
* @self: 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 [method@Gsk.PathBuilder.conic_to].
*
* Since: 4.14
*/
void
gsk_path_builder_rel_conic_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2,
float weight)
{
g_return_if_fail (self != NULL);
g_return_if_fail (weight >= 0);
gsk_path_builder_conic_to (self,
self->current_point.x + x1,
self->current_point.y + y1,
self->current_point.x + x2,
self->current_point.y + y2,
weight);
}
/**
* gsk_path_builder_close:
* @self: a `GskPathBuilder`

View File

@@ -60,19 +60,27 @@ void gsk_path_builder_add_cairo_path (GskPathBuilder
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_add_layout (GskPathBuilder *self,
PangoLayout *layout);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_add_rect (GskPathBuilder *self,
const graphene_rect_t *rect);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_add_rounded_rect (GskPathBuilder *self,
const GskRoundedRect *rect);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_add_circle (GskPathBuilder *self,
const graphene_point_t *center,
float radius);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_add_ellipse (GskPathBuilder *self,
const graphene_point_t *center,
const graphene_size_t *radius);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_add_segment (GskPathBuilder *self,
GskPath *path,
const GskPathPoint *start,
const GskPathPoint *end);
GskPathMeasure *measure,
float start,
float end);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_move_to (GskPathBuilder *self,
float x,
@@ -118,6 +126,20 @@ void gsk_path_builder_rel_cubic_to (GskPathBuilder
float x3,
float y3);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_conic_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2,
float weight);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_rel_conic_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2,
float weight);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_close (GskPathBuilder *self);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathBuilder, gsk_path_builder_unref)

306
gsk/gskpathdash.c Normal file
View File

@@ -0,0 +1,306 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskcontourprivate.h"
#include "gskcurveprivate.h"
#include "gskpathprivate.h"
#include "gskstrokeprivate.h"
typedef struct
{
float offset; /* how much of the current dash we've spent */
gsize dash_index; /* goes from 0 to n_dash * 2, so we don't have to care about on/off
for uneven dashes */
gboolean on; /* If we're currently dashing or not */
gboolean may_close; /* TRUE if we haven't turned the dash off in this contour */
gboolean needs_move_to; /* If we have emitted the initial move_to() yet */
enum {
NORMAL, /* no special behavior required */
SKIP, /* skip the next dash */
ONLY, /* only do the first dash */
DONE /* done with the first dash */
} first_dash_behavior; /* How to handle the first dash in the loop. We loop closed contours
twice to make sure the first dash and the last dash can get joined */
GskCurve curve; /* Curve we are currently processing */
float collect_start; /* We're collecting multiple line segments when decomposing. */
float collect_length; /* No need to emit a curve for every line segment when the dash is long enough. */
/* from the stroke */
float *dash;
gsize n_dash;
float dash_length;
float dash_offset;
float tolerance;
GskPathForeachFunc func;
gpointer user_data;
} GskPathDash;
static void
gsk_path_dash_setup (GskPathDash *self)
{
self->offset = fmodf (self->dash_offset, 2 * self->dash_length);
self->dash_index = 0;
self->on = TRUE;
self->may_close = TRUE;
while (self->offset > self->dash[self->dash_index % self->n_dash])
{
self->offset -= self->dash[self->dash_index % self->n_dash];
self->dash_index++;
self->on = !self->on;
}
if (self->first_dash_behavior != ONLY)
self->needs_move_to = TRUE;
}
static gboolean
gsk_path_dash_ensure_move_to (GskPathDash *self,
const graphene_point_t *pt)
{
if (!self->needs_move_to)
return TRUE;
if (!self->func (GSK_PATH_MOVE, pt, 1, 0, self->user_data))
return FALSE;
self->needs_move_to = FALSE;
return TRUE;
}
static gboolean
gsk_path_dash_add_line_segment (const graphene_point_t *start,
const graphene_point_t *end,
float t_start,
float t_end,
GskCurveLineReason reason,
gpointer user_data)
{
GskPathDash *self = user_data;
float remaining, length, t_step;
length = graphene_point_distance (start, end, NULL, NULL);
if (self->collect_length)
{
t_start = self->collect_start;
length += self->collect_length;
self->collect_length = 0;
}
t_step = t_end - t_start;
remaining = length;
while (remaining)
{
float piece;
if (self->offset + remaining <= self->dash[self->dash_index % self->n_dash])
{
/* try collecting multiple line segments */
if (t_end < 1.0)
{
self->collect_start = t_start + t_step * (length - remaining) / length;
self->collect_length = remaining;
return TRUE;
}
piece = remaining;
}
else
piece = self->dash[self->dash_index % self->n_dash] - self->offset;
if (self->on)
{
if (self->first_dash_behavior != SKIP)
{
GskCurve segment;
if (piece)
{
gsk_curve_segment (&self->curve,
t_start + t_step * (length - remaining) / length,
t_start + t_step * (length - (remaining - piece)) / length,
&segment);
if (!gsk_path_dash_ensure_move_to (self, gsk_curve_get_start_point (&segment)))
return FALSE;
if (!gsk_pathop_foreach (gsk_curve_pathop (&segment), self->func, self->user_data))
return FALSE;
}
else
{
graphene_point_t p;
gsk_curve_get_point (&self->curve, t_start + t_step * (length - remaining) / length, &p);
if (!gsk_path_dash_ensure_move_to (self, &p))
return FALSE;
}
}
}
else
{
self->may_close = FALSE;
if (self->first_dash_behavior == ONLY)
{
self->first_dash_behavior = DONE;
return FALSE;
}
self->first_dash_behavior = NORMAL;
}
if (self->offset + remaining <= self->dash[self->dash_index % self->n_dash])
{
self->offset += remaining;
remaining = 0;
}
else
{
remaining -= piece;
self->offset = 0;
self->dash_index++;
self->dash_index %= 2 * self->n_dash;
self->on = !self->on;
self->needs_move_to = TRUE;
}
}
return TRUE;
}
static gboolean
gsk_path_dash_foreach (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathDash *self = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_dash_setup (self);
break;
case GSK_PATH_CLOSE:
if (self->may_close)
{
if (graphene_point_equal (&pts[0], &pts[1]))
return self->func (GSK_PATH_CLOSE, pts, 2, 0, self->user_data);
}
else
op = GSK_PATH_LINE;
G_GNUC_FALLTHROUGH;
case GSK_PATH_LINE:
case GSK_PATH_QUAD:
case GSK_PATH_CUBIC:
case GSK_PATH_CONIC:
gsk_curve_init_foreach (&self->curve, op, pts, n_pts, weight);
if (!gsk_curve_decompose (&self->curve, self->tolerance, gsk_path_dash_add_line_segment, self))
return FALSE;
break;
default:
g_assert_not_reached ();
break;
}
return TRUE;
}
gboolean
gsk_contour_dash (const GskContour *contour,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data)
{
GskPathDash self = {
.offset = 0,
.dash = stroke->dash,
.n_dash = stroke->n_dash,
.dash_length = stroke->dash_length,
.dash_offset = stroke->dash_offset,
.tolerance = tolerance,
.func = func,
.user_data = user_data
};
gboolean is_closed = gsk_contour_get_flags (contour) & GSK_PATH_CLOSED ? TRUE : FALSE;
self.first_dash_behavior = is_closed ? SKIP : NORMAL;
if (!gsk_contour_foreach (contour, tolerance, gsk_path_dash_foreach, &self))
return FALSE;
if (is_closed)
{
if (self.first_dash_behavior == NORMAL)
self.first_dash_behavior = ONLY;
else
self.first_dash_behavior = NORMAL;
self.needs_move_to = !self.on;
if (!gsk_contour_foreach (contour, tolerance, gsk_path_dash_foreach, &self) &&
self.first_dash_behavior != DONE)
return FALSE;
}
return TRUE;
}
/**
* gsk_path_dash:
* @self: the `GskPath` to dash
* @stroke: the stroke containing the dash parameters
* @tolerance: tolerance to use while dashing
* @func: (scope call) (closure user_data): the function to call for operations
* @user_data: (nullable): user data passed to @func
*
* Calls @func for every operation of the path that is the result
* of dashing @self with the dash pattern from @stroke.
*
* Returns: `FALSE` if @func returned FALSE`, `TRUE` otherwise.
*
* Since: 4.14
*/
gboolean
gsk_path_dash (GskPath *self,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data)
{
gsize i;
/* Dashing disabled, no need to do any work */
if (stroke->dash_length <= 0)
return gsk_path_foreach (self, -1, func, user_data);
for (i = 0; i < gsk_path_get_n_contours (self); i++)
{
if (!gsk_contour_dash (gsk_path_get_contour (self, i), stroke, tolerance, func, user_data))
return FALSE;
}
return TRUE;
}

View File

@@ -236,6 +236,104 @@ gsk_path_measure_clamp_distance (GskPathMeasure *self,
return CLAMP (distance, 0, self->length);
}
static void
gsk_path_builder_add_segment_chunk (GskPathBuilder *self,
GskPathMeasure *measure,
gboolean emit_move_to,
float start,
float end)
{
g_assert (start < end);
for (gsize i = 0; i < measure->n_contours; i++)
{
if (measure->measures[i].length < start)
{
start -= measure->measures[i].length;
end -= measure->measures[i].length;
}
else if (start > 0 || end < measure->measures[i].length)
{
float len = MIN (end, measure->measures[i].length);
gsk_contour_add_segment (gsk_path_get_contour (measure->path, i),
self,
measure->measures[i].contour_data,
emit_move_to,
start,
len);
end -= len;
start = 0;
if (end <= 0)
break;
}
else
{
end -= measure->measures[i].length;
gsk_path_builder_add_contour (self, gsk_contour_dup (gsk_path_get_contour (measure->path, i)));
}
emit_move_to = TRUE;
}
}
/**
* gsk_path_builder_add_segment:
* @self: a `GskPathBuilder`
* @measure: the `GskPathMeasure` to take the segment to
* @start: start distance into the path
* @end: end distance into the path
*
* Adds to @self the segment of @measure from @start to @end.
*
* The distances are given relative to the length of @measure's path,
* from 0 for the beginning of the path to its length for the end
* of the path. The values will be clamped to that range. The length
* can be obtained with [method@Gsk.PathMeasure.get_length].
*
* If @start >= @end after clamping, the path will first add the segment
* from @start to the end of the path, and then add the segment from
* the beginning to @end. If the path is closed, these segments will
* be connected.
*
* Since: 4.14
*/
void
gsk_path_builder_add_segment (GskPathBuilder *self,
GskPathMeasure *measure,
float start,
float end)
{
g_return_if_fail (self != NULL);
g_return_if_fail (measure != NULL);
start = gsk_path_measure_clamp_distance (measure, start);
end = gsk_path_measure_clamp_distance (measure, end);
if (start < end)
{
gsk_path_builder_add_segment_chunk (self, measure, TRUE, start, end);
}
else
{
/* If the path is closed, we can connect the 2 subpaths. */
gboolean closed = gsk_path_is_closed (measure->path);
gboolean need_move_to = !closed;
if (start < measure->length)
gsk_path_builder_add_segment_chunk (self, measure,
TRUE,
start, measure->length);
else
need_move_to = TRUE;
if (end > 0)
gsk_path_builder_add_segment_chunk (self, measure,
need_move_to,
0, end);
if (start == end && closed)
gsk_path_builder_close (self);
}
}
/**
* gsk_path_measure_get_point:
* @self: a `GskPathMeasure`
@@ -284,44 +382,29 @@ gsk_path_measure_get_point (GskPathMeasure *self,
gsk_contour_get_point (contour, self->measures[i].contour_data, offset, res);
res->path = self->path;
res->contour = i;
return TRUE;
}
/**
* gsk_path_point_get_distance:
* @measure: a `GskPathMeasure`
* @point: a `GskPathPoint on the path of @self
*
* Returns the distance from the beginning of the path
* to @point.
*
* Returns: the distance of @point
*
* Since: 4.14
*/
float
gsk_path_point_get_distance (GskPathMeasure *measure,
const GskPathPoint *point)
gsk_path_measure_get_distance (GskPathMeasure *self,
GskPathPoint *point)
{
GskRealPathPoint *p = (GskRealPathPoint *)point;
const GskContour *contour = gsk_path_get_contour (measure->path, p->contour);
const GskContour *contour = p->contour;
float contour_offset = 0;
g_return_val_if_fail (measure != NULL, 0);
g_return_val_if_fail (measure->path == p->path, 0);
g_return_val_if_fail (contour != NULL, 0);
g_return_val_if_fail (self != NULL, 0);
g_return_val_if_fail (point != NULL, 0);
g_return_val_if_fail (self->path == p->path, 0);
for (gsize i = 0; i < measure->n_contours; i++)
for (gsize i = 0; i < self->n_contours; i++)
{
if (contour == gsk_path_get_contour (measure->path, i))
if (contour == gsk_path_get_contour (self->path, i))
return contour_offset + gsk_contour_get_distance (contour,
p,
measure->measures[i].contour_data);
self->measures[i].contour_data);
contour_offset += measure->measures[i].length;
contour_offset += self->measures[i].length;
}
g_return_val_if_reached (0);

View File

@@ -55,11 +55,11 @@ float gsk_path_measure_get_length (GskPathMeasure
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_measure_get_point (GskPathMeasure *self,
float distance,
GskPathPoint *result);
GskPathPoint *point);
GDK_AVAILABLE_IN_4_14
float gsk_path_measure_get_distance (GskPathMeasure *self,
const GskPathPoint *point);
GskPathPoint *point);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathMeasure, gsk_path_measure_unref)

View File

@@ -82,17 +82,23 @@ gsk_pathop_foreach (gskpathop pop,
switch (gsk_pathop_op (pop))
{
case GSK_PATH_MOVE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 1, user_data);
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 1, 0, user_data);
case GSK_PATH_CLOSE:
case GSK_PATH_LINE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 2, user_data);
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 2, 0, user_data);
case GSK_PATH_QUAD:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 3, user_data);
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 3, 0, user_data);
case GSK_PATH_CUBIC:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 4, user_data);
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 4, 0, user_data);
case GSK_PATH_CONIC:
{
const graphene_point_t *pts = gsk_pathop_points (pop);
return func (gsk_pathop_op (pop), (graphene_point_t[3]) { pts[0], pts[1], pts[3] }, 3, pts[2].x, user_data);
}
default:
g_assert_not_reached ();
@@ -128,6 +134,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_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[3].x, pts[3].y, pts[2].x);
break;
default:
g_assert_not_reached ();
break;
@@ -162,6 +172,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_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[0].x, pts[0].y, pts[2].x);
break;
default:
g_assert_not_reached ();
break;

View File

@@ -26,6 +26,7 @@
#include "gdk/gdkprivate.h"
/**
* GskPathPoint:
*
@@ -66,10 +67,25 @@ gsk_path_point_free (GskPathPoint *point)
g_free (point);
}
GskPath *
gsk_path_point_get_path (GskPathPoint *point)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
return self->path;
}
const GskContour *
gsk_path_point_get_contour (GskPathPoint *point)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
return self->contour;
}
/**
* gsk_path_point_get_position:
* @path: a `GskPath`
* @point: a `GskPathPoint` on @path
* @point: a `GskPathPoint`
* @position: (out caller-allocates): Return location for
* the coordinates of the point
*
@@ -78,23 +94,17 @@ gsk_path_point_free (GskPathPoint *point)
* Since: 4.14
*/
void
gsk_path_point_get_position (GskPath *path,
const GskPathPoint *point,
graphene_point_t *position)
gsk_path_point_get_position (GskPathPoint *point,
graphene_point_t *position)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
const GskContour *contour = gsk_path_get_contour (path, self->contour);
g_return_if_fail (path == self->path);
g_return_if_fail (contour != NULL);
gsk_contour_get_position (contour, self, position);
gsk_contour_get_position (self->contour, self, position);
}
/**
* gsk_path_point_get_tangent:
* @path: a `GskPath`
* @point: a `GskPathPoint` on @path
* @point: a `GskPathPoint`
* @direction: the direction for which to return the tangent
* @tangent: (out caller-allocates): Return location for
* the tangent at the point
@@ -110,24 +120,18 @@ gsk_path_point_get_position (GskPath *path,
* Since: 4.14
*/
void
gsk_path_point_get_tangent (GskPath *path,
const GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent)
gsk_path_point_get_tangent (GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
const GskContour *contour = gsk_path_get_contour (path, self->contour);
g_return_if_fail (path == self->path);
g_return_if_fail (contour != NULL);
gsk_contour_get_tangent (contour, self, direction, tangent);
gsk_contour_get_tangent (self->contour, self, direction, tangent);
}
/**
* gsk_path_point_get_curvature:
* @path: a `GskPath`
* @point: a `GskPathPoint` on @path
* @point: a `GskPathPoint`
* @center: (out caller-allocates): Return location for
* the center of the osculating circle
*
@@ -143,15 +147,19 @@ gsk_path_point_get_tangent (GskPath *path,
* Since: 4.14
*/
float
gsk_path_point_get_curvature (GskPath *path,
const GskPathPoint *point,
graphene_point_t *center)
gsk_path_point_get_curvature (GskPathPoint *point,
graphene_point_t *center)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
const GskContour *contour = gsk_path_get_contour (path, self->contour);
g_return_val_if_fail (path == self->path, 0);
g_return_val_if_fail (contour != NULL, 0);
return gsk_contour_get_curvature (contour, self, center);
return gsk_contour_get_curvature (self->contour, self, center);
}
float
gsk_path_point_get_distance (GskPathPoint *point,
gpointer measure_data)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
return gsk_contour_get_distance (self->contour, self, measure_data);
}

View File

@@ -43,29 +43,22 @@ GDK_AVAILABLE_IN_4_14
GType gsk_path_point_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_4_14
GskPathPoint * gsk_path_point_copy (GskPathPoint *point);
GskPathPoint * gsk_path_point_copy (GskPathPoint *point);
GDK_AVAILABLE_IN_4_14
void gsk_path_point_free (GskPathPoint *point);
void gsk_path_point_free (GskPathPoint *point);
GDK_AVAILABLE_IN_4_14
void gsk_path_point_get_position (GskPath *path,
const GskPathPoint *point,
graphene_point_t *position);
void gsk_path_point_get_position (GskPathPoint *point,
graphene_point_t *position);
GDK_AVAILABLE_IN_4_14
void gsk_path_point_get_tangent (GskPath *path,
const GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent);
void gsk_path_point_get_tangent (GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent);
GDK_AVAILABLE_IN_4_14
float gsk_path_point_get_curvature (GskPath *path,
const GskPathPoint *point,
graphene_point_t *center);
GDK_AVAILABLE_IN_4_14
float gsk_path_point_get_distance (GskPathMeasure *measure,
const GskPathPoint *point);
float gsk_path_point_get_curvature (GskPathPoint *point,
graphene_point_t *center);
G_END_DECLS

View File

@@ -8,17 +8,26 @@ G_BEGIN_DECLS
struct _GskRealPathPoint
{
GskPath *path;
gsize contour;
const GskContour *contour;
union {
struct {
unsigned int idx;
float t;
} std;
struct {
float distance;
} rect;
struct {
float angle;
} circle;
} data;
};
G_STATIC_ASSERT (sizeof (GskRealPathPoint) <= sizeof (GskPathPoint));
GskPath * gsk_path_point_get_path (GskPathPoint *self);
const GskContour * gsk_path_point_get_contour (GskPathPoint *self);
float gsk_path_point_get_distance (GskPathPoint *self,
gpointer measure_data);
G_END_DECLS

View File

@@ -40,11 +40,11 @@ typedef struct _GskRealPathPoint GskRealPathPoint;
GskPath * gsk_path_new_from_contours (const GSList *contours);
gsize gsk_path_get_n_contours (const GskPath *self);
const GskContour * gsk_path_get_contour (const GskPath *self,
gsize gsk_path_get_n_contours (GskPath *path);
const GskContour * gsk_path_get_contour (GskPath *path,
gsize i);
GskPathFlags gsk_path_get_flags (const GskPath *self);
GskPathFlags gsk_path_get_flags (GskPath *self);
gboolean gsk_path_foreach_with_tolerance (GskPath *self,
GskPathForeachFlags flags,
@@ -65,6 +65,9 @@ void gsk_path_builder_svg_arc_to (GskPathBuilder
float x,
float y);
gboolean gsk_path_get_stroke_bounds (GskPath *self,
const GskStroke *stroke,
graphene_rect_t *bounds);
G_END_DECLS

View File

@@ -26,7 +26,6 @@
#include "gskdiffprivate.h"
#include "gl/gskglrenderer.h"
#include "gskpathprivate.h"
#include "gskrectprivate.h"
#include "gskrendererprivate.h"
#include "gskroundedrectprivate.h"
#include "gskstrokeprivate.h"
@@ -4414,17 +4413,9 @@ gsk_fill_node_draw (GskRenderNode *node,
break;
}
gsk_path_to_cairo (self->path, cr);
if (gsk_render_node_get_node_type (self->child) == GSK_COLOR_NODE &&
gsk_rect_contains_rect (&self->child->bounds, &node->bounds))
{
gdk_cairo_set_source_rgba (cr, gsk_color_node_get_color (self->child));
cairo_fill (cr);
}
else
{
cairo_clip (cr);
gsk_render_node_draw (self->child, cr);
}
cairo_clip (cr);
gsk_render_node_draw (self->child, cr);
cairo_restore (cr);
}
@@ -4441,10 +4432,12 @@ gsk_fill_node_diff (GskRenderNode *node1,
{
cairo_region_t *sub;
cairo_rectangle_int_t clip_rect;
graphene_rect_t rect;
sub = cairo_region_create();
gsk_render_node_diff (self1->child, self2->child, sub);
rectangle_init_from_graphene (&clip_rect, &node1->bounds);
graphene_rect_union (&node1->bounds, &node2->bounds, &rect);
rectangle_init_from_graphene (&clip_rect, &rect);
cairo_region_intersect_rectangle (sub, &clip_rect);
cairo_region_union (region, sub);
cairo_region_destroy (sub);
@@ -4602,19 +4595,12 @@ gsk_stroke_node_draw (GskRenderNode *node,
cairo_save (cr);
if (gsk_render_node_get_node_type (self->child) == GSK_COLOR_NODE &&
gsk_rect_contains_rect (&self->child->bounds, &node->bounds))
{
gdk_cairo_set_source_rgba (cr, gsk_color_node_get_color (self->child));
}
else
{
gsk_cairo_rectangle (cr, &self->child->bounds);
cairo_clip (cr);
cairo_push_group (cr);
gsk_render_node_draw (self->child, cr);
cairo_pop_group_to_source (cr);
}
gsk_cairo_rectangle (cr, &self->child->bounds);
cairo_clip (cr);
cairo_push_group (cr);
gsk_render_node_draw (self->child, cr);
cairo_pop_group_to_source (cr);
gsk_stroke_to_cairo (&self->stroke, cr);
@@ -4636,12 +4622,9 @@ gsk_stroke_node_diff (GskRenderNode *node1,
gsk_stroke_equal (&self1->stroke, &self2->stroke))
{
cairo_region_t *sub;
cairo_rectangle_int_t clip_rect;
sub = cairo_region_create();
gsk_render_node_diff (self1->child, self2->child, sub);
rectangle_init_from_graphene (&clip_rect, &node1->bounds);
cairo_region_intersect_rectangle (sub, &clip_rect);
cairo_region_union (region, sub);
cairo_region_destroy (sub);
}

View File

@@ -24,12 +24,10 @@
#include "gskrendernodeparserprivate.h"
#include "gskpath.h"
#include "gskpathbuilder.h"
#include "gskroundedrectprivate.h"
#include "gskrendernodeprivate.h"
#include "gskstroke.h"
#include "gsktransformprivate.h"
#include "gskenumtypes.h"
#include "gdk/gdkrgbaprivate.h"
#include "gdk/gdktextureprivate.h"
@@ -1178,26 +1176,6 @@ create_default_render_node (void)
return gsk_color_node_new (&GDK_RGBA("FF00CC"), &GRAPHENE_RECT_INIT (0, 0, 50, 50));
}
static GskPath *
create_default_path (void)
{
GskPathBuilder *builder;
guint i;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, 25, 0);
for (i = 1; i < 5; i++)
{
gsk_path_builder_line_to (builder,
sin (i * G_PI * 0.8) * 25 + 25,
-cos (i * G_PI * 0.8) * 25 + 25);
}
gsk_path_builder_close (builder);
return gsk_path_builder_free_to_path (builder);
}
static GskRenderNode *
parse_color_node (GtkCssParser *parser,
Context *context)
@@ -2121,165 +2099,6 @@ parse_rounded_clip_node (GtkCssParser *parser,
return result;
}
static gboolean
parse_path (GtkCssParser *parser,
Context *context,
gpointer out_path)
{
GskPath *path;
char *str = NULL;
if (!parse_string (parser, context, &str))
return FALSE;
path = gsk_path_parse (str);
g_free (str);
if (path == NULL)
{
gtk_css_parser_error_value (parser, "Invalid path");
return FALSE;
}
*((GskPath **) out_path) = path;
return TRUE;
}
static void
clear_path (gpointer inout_path)
{
g_clear_pointer ((GskPath **) inout_path, gsk_path_unref);
}
static gboolean
parse_enum (GtkCssParser *parser,
GType type,
gpointer out_value)
{
GEnumClass *class;
GEnumValue *v;
char *enum_name;
enum_name = gtk_css_parser_consume_ident (parser);
if (enum_name == NULL)
return FALSE;
class = g_type_class_ref (type);
v = g_enum_get_value_by_nick (class, enum_name);
if (v == NULL)
{
gtk_css_parser_error_value (parser, "Unknown value \"%s\" for enum \"%s\"",
enum_name, g_type_name (type));
g_free (enum_name);
g_type_class_unref (class);
return FALSE;
}
*(int*)out_value = v->value;
g_free (enum_name);
g_type_class_unref (class);
return TRUE;
}
static gboolean
parse_fill_rule (GtkCssParser *parser,
Context *context,
gpointer out_rule)
{
return parse_enum (parser, GSK_TYPE_FILL_RULE, out_rule);
}
static GskRenderNode *
parse_fill_node (GtkCssParser *parser,
Context *context)
{
GskRenderNode *child = NULL;
GskPath *path = NULL;
int rule = GSK_FILL_RULE_WINDING;
const Declaration declarations[] = {
{ "child", parse_node, clear_node, &child },
{ "path", parse_path, clear_path, &path },
{ "fill-rule", parse_fill_rule, NULL, &rule },
};
GskRenderNode *result;
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
if (child == NULL)
child = create_default_render_node ();
if (path == NULL)
path = create_default_path ();
result = gsk_fill_node_new (child, path, rule);
gsk_path_unref (path);
gsk_render_node_unref (child);
return result;
}
static gboolean
parse_line_cap (GtkCssParser *parser,
Context *context,
gpointer out)
{
return parse_enum (parser, GSK_TYPE_LINE_CAP, out);
}
static gboolean
parse_line_join (GtkCssParser *parser,
Context *context,
gpointer out)
{
return parse_enum (parser, GSK_TYPE_LINE_JOIN, out);
}
static GskRenderNode *
parse_stroke_node (GtkCssParser *parser,
Context *context)
{
GskRenderNode *child = NULL;
GskPath *path = NULL;
double line_width = 1.0;
int line_cap = GSK_LINE_CAP_BUTT;
int line_join = GSK_LINE_JOIN_MITER;
double miter_limit = 4.0;
GskStroke *stroke;
const Declaration declarations[] = {
{ "child", parse_node, clear_node, &child },
{ "path", parse_path, clear_path, &path },
{ "line-width", parse_positive_double, NULL, &line_width },
{ "line-cap", parse_line_cap, NULL, &line_cap },
{ "line-join", parse_line_join, NULL, &line_join },
{ "miter-limit", parse_positive_double, NULL, &miter_limit }
};
GskRenderNode *result;
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
if (child == NULL)
child = create_default_render_node ();
if (path == NULL)
path = create_default_path ();
stroke = gsk_stroke_new (line_width);
gsk_stroke_set_line_cap (stroke, line_cap);
gsk_stroke_set_line_join (stroke, line_join);
gsk_stroke_set_miter_limit (stroke, miter_limit);
result = gsk_stroke_node_new (child, path, stroke);
gsk_path_unref (path);
gsk_stroke_free (stroke);
gsk_render_node_unref (child);
return result;
}
static GskRenderNode *
parse_shadow_node (GtkCssParser *parser,
Context *context)
@@ -2362,8 +2181,6 @@ parse_node (GtkCssParser *parser,
{ "repeating-linear-gradient", parse_repeating_linear_gradient_node },
{ "repeating-radial-gradient", parse_repeating_radial_gradient_node },
{ "rounded-clip", parse_rounded_clip_node },
{ "fill", parse_fill_node },
{ "stroke", parse_stroke_node },
{ "shadow", parse_shadow_node },
{ "text", parse_text_node },
{ "texture", parse_texture_node },
@@ -3026,11 +2843,8 @@ append_escaping_newlines (GString *str,
len = strcspn (string, "\n");
g_string_append_len (str, string, len);
string += len;
if (*string)
{
g_string_append (str, "\\\n");
string++;
}
g_string_append (str, "\\\n");
string++;
} while (*string);
}
@@ -3231,55 +3045,6 @@ gsk_text_node_serialize_glyphs (GskRenderNode *node,
pango_glyph_string_free (ascii);
}
static const char *
enum_to_nick (GType type,
int value)
{
GEnumClass *class;
GEnumValue *v;
class = g_type_class_ref (type);
v = g_enum_get_value (class, value);
g_type_class_unref (class);
return v->value_nick;
}
static void
append_enum_param (Printer *p,
const char *param_name,
GType type,
int value)
{
_indent (p);
g_string_append_printf (p->str, "%s: ", param_name);
g_string_append (p->str, enum_to_nick (type, value));
g_string_append_c (p->str, ';');
g_string_append_c (p->str, '\n');
}
static void
append_path_param (Printer *p,
const char *param_name,
GskPath *path)
{
char *str, *s;
_indent (p);
g_string_append (p->str, "path: \"\\\n");
str = gsk_path_to_string (path);
/* Put each command on a new line */
for (s = str; *s; s++)
{
if (*s == ' ' &&
(s[1] == 'M' || s[1] == 'C' || s[1] == 'Z' || s[1] == 'L'))
*s = '\n';
}
append_escaping_newlines (p->str, str);
g_string_append (p->str, "\";\n");
g_free (str);
}
static void
render_node_print (Printer *p,
GskRenderNode *node)
@@ -3455,11 +3220,14 @@ render_node_print (Printer *p,
case GSK_FILL_NODE:
{
char *path_str;
start_node (p, "fill", node_name);
append_node_param (p, "child", gsk_fill_node_get_child (node));
append_path_param (p, "path", gsk_fill_node_get_path (node));
append_enum_param (p, "fill-rule", GSK_TYPE_FILL_RULE, gsk_fill_node_get_fill_rule (node));
path_str = gsk_path_to_string (gsk_fill_node_get_path (node));
append_string_param (p, "path", path_str);
g_free (path_str);
end_node (p);
}
@@ -3468,17 +3236,17 @@ render_node_print (Printer *p,
case GSK_STROKE_NODE:
{
const GskStroke *stroke;
char *path_str;
start_node (p, "stroke", node_name);
append_node_param (p, "child", gsk_stroke_node_get_child (node));
append_path_param (p, "path", gsk_stroke_node_get_path (node));
path_str = gsk_path_to_string (gsk_stroke_node_get_path (node));
append_string_param (p, "path", path_str);
g_free (path_str);
stroke = gsk_stroke_node_get_stroke (node);
append_float_param (p, "line-width", gsk_stroke_get_line_width (stroke), 0.0f);
append_enum_param (p, "line-cap", GSK_TYPE_LINE_CAP, gsk_stroke_get_line_cap (stroke));
append_enum_param (p, "line-join", GSK_TYPE_LINE_JOIN, gsk_stroke_get_line_join (stroke));
append_float_param (p, "miter-limit", gsk_stroke_get_miter_limit (stroke), 4.0f);
append_float_param (p, "line-width", gsk_stroke_get_line_width (stroke), 0.0);
end_node (p);
}

View File

@@ -138,6 +138,7 @@ gsk_stroke_to_cairo (const GskStroke *self,
switch (self->line_join)
{
case GSK_LINE_JOIN_MITER:
case GSK_LINE_JOIN_MITER_CLIP:
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
break;
case GSK_LINE_JOIN_ROUND:
@@ -186,19 +187,7 @@ gsk_stroke_equal (gconstpointer stroke1,
const GskStroke *self1 = stroke1;
const GskStroke *self2 = stroke2;
if (self1->line_width != self2->line_width ||
self1->line_cap != self2->line_cap ||
self1->line_join != self2->line_join ||
self1->miter_limit != self2->miter_limit ||
self1->n_dash != self2->n_dash ||
self1->dash_offset != self2->dash_offset)
return FALSE;
for (gsize i = 0; i < self1->n_dash; i++)
if (self1->dash[i] != self2->dash[i])
return FALSE;
return TRUE;
return self1->line_width == self2->line_width;
}
/**
@@ -332,7 +321,8 @@ gsk_stroke_get_line_join (const GskStroke *self)
*
* For joins of type `GSK_LINE_JOIN_MITER` that exceed the miter
* limit, the join gets rendered as if it was of type
* `GSK_LINE_JOIN_BEVEL`.
* `GSK_LINE_JOIN_BEVEL`. For joins of type `GSK_LINE_JOIN_MITER_CLIP`,
* the miter is clipped at a distance of half the miter limit.
*
* Since: 4.14
*/
@@ -421,6 +411,7 @@ gsk_stroke_set_dash (GskStroke *self,
g_free (self->dash);
self->dash = g_memdup (dash, sizeof (gfloat) * n_dash);
self->n_dash = n_dash;
}
/**
@@ -486,50 +477,3 @@ gsk_stroke_get_dash_offset (const GskStroke *self)
return self->dash_offset;
}
/*< private >
* gsk_stroke_get_join_width:
* @stroke: a `GskStroke`
*
* Return a width that is sufficient to use
* when calculating stroke bounds around joins
* and caps.
*
* Returns: the join width
*/
float
gsk_stroke_get_join_width (const GskStroke *stroke)
{
float width;
switch (stroke->line_cap)
{
case GSK_LINE_CAP_BUTT:
width = 0;
break;
case GSK_LINE_CAP_ROUND:
width = stroke->line_width;
break;
case GSK_LINE_CAP_SQUARE:
width = G_SQRT2 * stroke->line_width;
break;
default:
g_assert_not_reached ();
}
switch (stroke->line_join)
{
case GSK_LINE_JOIN_MITER:
width = MAX (width, MAX (stroke->miter_limit, 1.f) * stroke->line_width);
break;
case GSK_LINE_JOIN_ROUND:
case GSK_LINE_JOIN_BEVEL:
width = MAX (width, stroke->line_width);
break;
default:
g_assert_not_reached ();
}
return width;
}

View File

@@ -53,6 +53,4 @@ gsk_stroke_clear (GskStroke *stroke)
stroke->n_dash = 0; /* better safe than sorry */
}
float gsk_stroke_get_join_width (const GskStroke *stroke);
G_END_DECLS

View File

@@ -27,6 +27,7 @@ gsk_public_sources = files([
'gskdiff.c',
'gskglshader.c',
'gskpath.c',
'gskpathdash.c',
'gskpathbuilder.c',
'gskpathmeasure.c',
'gskpathpoint.c',

View File

@@ -411,7 +411,7 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base,
}
pos = gtk_list_tile_get_position (self->item_manager, tile);
if (tile->n_items > 1 && tile->area.width > 0 && tile->area.height > 0)
if (tile->n_items > 1)
{
int xspacing, yspacing;

View File

@@ -1202,7 +1202,7 @@ my_pango_layout_get_width_for_height (PangoLayout *layout,
else if (text_height > for_height)
min = mid + 1;
else
max = text_width;
max = mid;
}
return min * PANGO_SCALE;

View File

@@ -357,16 +357,16 @@ snapshot_frame_fill (GtkSnapshot *snapshot,
gtk_snapshot_append_border (snapshot, outline, border_width, colors);
}
static void
set_stroke_style (cairo_t *cr,
double line_width,
GtkBorderStyle style,
double length)
static GskStroke *
create_stroke_style (double line_width,
GtkBorderStyle style,
double length)
{
double segments[2];
GskStroke *stroke;
float segments[2];
double n;
cairo_set_line_width (cr, line_width);
stroke = gsk_stroke_new (line_width);
if (style == GTK_BORDER_STYLE_DOTTED)
{
@@ -374,12 +374,12 @@ set_stroke_style (cairo_t *cr,
segments[0] = 0;
segments[1] = n ? length / n : 2;
cairo_set_dash (cr, segments, G_N_ELEMENTS (segments), 0);
gsk_stroke_set_dash (stroke, segments, 2);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_ROUND);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_ROUND);
}
else
else if (style == GTK_BORDER_STYLE_DASHED)
{
n = length / line_width;
/* Optimize the common case of an integer-sized rectangle
@@ -397,32 +397,33 @@ set_stroke_style (cairo_t *cr,
segments[0] = n ? (1. / 3) * length / n : 1;
segments[1] = 2 * segments[0];
}
cairo_set_dash (cr, segments, G_N_ELEMENTS (segments), 0);
gsk_stroke_set_dash (stroke, segments, 2);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_SQUARE);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_MITER);
}
else
{
g_assert_not_reached ();
}
return stroke;
}
static void
render_frame_stroke (cairo_t *cr,
const GskRoundedRect *border_box,
const double border_width[4],
GdkRGBA colors[4],
guint hidden_side,
GtkBorderStyle stroke_style)
snapshot_frame_stroke (GtkSnapshot *snapshot,
const GskRoundedRect *border_box,
const float border_width[4],
GdkRGBA colors[4],
guint hidden_side,
GtkBorderStyle stroke_style)
{
gboolean different_colors, different_borders;
GskRoundedRect stroke_box;
GskPathBuilder *builder;
GskPath *path;
GskStroke *stroke;
guint i;
different_colors = !gdk_rgba_equal (&colors[0], &colors[1]) ||
!gdk_rgba_equal (&colors[0], &colors[2]) ||
!gdk_rgba_equal (&colors[0], &colors[3]);
different_borders = border_width[0] != border_width[1] ||
border_width[0] != border_width[2] ||
border_width[0] != border_width[3] ;
stroke_box = *border_box;
gsk_rounded_rect_shrink (&stroke_box,
border_width[GTK_CSS_TOP] / 2.0,
@@ -430,32 +431,36 @@ render_frame_stroke (cairo_t *cr,
border_width[GTK_CSS_BOTTOM] / 2.0,
border_width[GTK_CSS_LEFT] / 2.0);
if (!different_colors && !different_borders && hidden_side == 0)
if (border_width[0] == border_width[1] &&
border_width[0] == border_width[2] &&
border_width[0] == border_width[3] &&
hidden_side == 0)
{
double length = 0;
/* FAST PATH:
* Mostly expected to trigger for focus rectangles */
for (i = 0; i < 4; i++)
for (i = 0; i < 4; i++)
{
length += _gtk_rounded_box_guess_length (&stroke_box, i);
}
gsk_rounded_rect_path (&stroke_box, cr);
gdk_cairo_set_source_rgba (cr, &colors[0]);
set_stroke_style (cr, border_width[0], stroke_style, length);
cairo_stroke (cr);
builder = gsk_path_builder_new ();
gsk_path_builder_add_rounded_rect (builder, &stroke_box);
path = gsk_path_builder_free_to_path (builder);
stroke = create_stroke_style (border_width[0],
stroke_style, length);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gsk_path_unref (path);
gtk_snapshot_append_border (snapshot, border_box, border_width, colors);
gtk_snapshot_pop (snapshot);
}
else
{
GskRoundedRect padding_box;
padding_box = *border_box;
gsk_rounded_rect_shrink (&padding_box,
border_width[GTK_CSS_TOP],
border_width[GTK_CSS_RIGHT],
border_width[GTK_CSS_BOTTOM],
border_width[GTK_CSS_LEFT]);
const float weight = sqrtf(2)/2.0;
for (i = 0; i < 4; i++)
{
@@ -465,49 +470,111 @@ render_frame_stroke (cairo_t *cr,
if (border_width[i] == 0)
continue;
cairo_save (cr);
builder = gsk_path_builder_new ();
if (i == 0)
_gtk_rounded_box_path_top (border_box, &padding_box, cr);
{
/* top */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_TOP_LEFT].width / 2,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_LEFT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_TOP_LEFT].width,
stroke_box.bounds.origin.y,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_TOP_RIGHT].width,
stroke_box.bounds.origin.y);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_TOP_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_RIGHT].height / 2,
weight);
}
else if (i == 1)
_gtk_rounded_box_path_right (border_box, &padding_box, cr);
{
/* right */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_TOP_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_RIGHT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_RIGHT].height,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].height);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].height / 2,
weight);
}
else if (i == 2)
_gtk_rounded_box_path_bottom (border_box, &padding_box, cr);
{
/* bottom */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].height / 2,
weight);
}
else if (i == 3)
_gtk_rounded_box_path_left (border_box, &padding_box, cr);
cairo_clip (cr);
{
/* left */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].height,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_LEFT].height);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_TOP_LEFT].width,
stroke_box.bounds.origin.y,
weight);
}
_gtk_rounded_box_path_side (&stroke_box, cr, i);
path = gsk_path_builder_free_to_path (builder);
stroke = create_stroke_style (border_width[i],
stroke_style,
_gtk_rounded_box_guess_length (&stroke_box, i));
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gsk_path_unref (path);
gdk_cairo_set_source_rgba (cr, &colors[i]);
set_stroke_style (cr,
border_width[i],
stroke_style,
_gtk_rounded_box_guess_length (&stroke_box, i));
cairo_stroke (cr);
gtk_snapshot_append_border (snapshot, border_box, border_width, colors);
cairo_restore (cr);
gtk_snapshot_pop (snapshot);
}
}
}
static void
snapshot_frame_stroke (GtkSnapshot *snapshot,
const GskRoundedRect *outline,
const float border_width[4],
GdkRGBA colors[4],
guint hidden_side,
GtkBorderStyle stroke_style)
{
double double_width[4] = { border_width[0], border_width[1], border_width[2], border_width[3] };
cairo_t *cr;
cr = gtk_snapshot_append_cairo (snapshot,
&outline->bounds);
render_frame_stroke (cr, outline, double_width, colors, hidden_side, stroke_style);
cairo_destroy (cr);
}
static void
color_shade (const GdkRGBA *color,
double factor,

View File

@@ -1160,6 +1160,7 @@ gtk_snapshot_push_fill (GtkSnapshot *snapshot,
{
GtkSnapshotState *state;
/* FIXME: Is it worth calling ensure_affine() and transforming the path here? */
gtk_snapshot_ensure_identity (snapshot);
state = gtk_snapshot_push_state (snapshot,
@@ -1218,10 +1219,6 @@ gtk_snapshot_clear_stroke (GtkSnapshotState *state)
*
* The image is recorded until the next call to [method@Gtk.Snapshot.pop].
*
* Note that the strokes are subject to the same transformation as
* everything else, so uneven scaling will cause horizontal and vertical
* strokes to have different widths.
*
* Since: 4.14
*/
void
@@ -1231,6 +1228,7 @@ gtk_snapshot_push_stroke (GtkSnapshot *snapshot,
{
GtkSnapshotState *state;
/* FIXME: Is it worth calling ensure_affine() and transforming the path here? */
gtk_snapshot_ensure_identity (snapshot);
state = gtk_snapshot_push_state (snapshot,

View File

@@ -27,7 +27,6 @@
#endif
#include <glib-object.h>
#include <gdk/gdk.h>
G_BEGIN_DECLS
@@ -88,39 +87,28 @@ struct _GtkPrinterOptionClass
void (*_gtk_reserved4) (void);
};
GDK_AVAILABLE_IN_ALL
GType gtk_printer_option_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GtkPrinterOption *gtk_printer_option_new (const char *name,
const char *display_text,
GtkPrinterOptionType type);
GDK_AVAILABLE_IN_ALL
void gtk_printer_option_set (GtkPrinterOption *option,
const char *value);
GDK_AVAILABLE_IN_ALL
void gtk_printer_option_set_has_conflict (GtkPrinterOption *option,
gboolean has_conflict);
GDK_AVAILABLE_IN_ALL
void gtk_printer_option_clear_has_conflict (GtkPrinterOption *option);
GDK_AVAILABLE_IN_ALL
void gtk_printer_option_set_boolean (GtkPrinterOption *option,
gboolean value);
GDK_AVAILABLE_IN_ALL
void gtk_printer_option_allocate_choices (GtkPrinterOption *option,
int num);
GDK_AVAILABLE_IN_ALL
void gtk_printer_option_choices_from_array (GtkPrinterOption *option,
int num_choices,
const char **choices,
const char **choices_display);
GDK_AVAILABLE_IN_ALL
gboolean gtk_printer_option_has_choice (GtkPrinterOption *option,
const char *choice);
GDK_AVAILABLE_IN_ALL
void gtk_printer_option_set_activates_default (GtkPrinterOption *option,
gboolean activates);
GDK_AVAILABLE_IN_ALL
gboolean gtk_printer_option_get_activates_default (GtkPrinterOption *option);

BIN
matthiasc@master.gnome.org Normal file

Binary file not shown.

1824
po/ca.po

File diff suppressed because it is too large Load Diff

1828
po/eu.po

File diff suppressed because it is too large Load Diff

1750
po/pl.po

File diff suppressed because it is too large Load Diff

5738
po/ro.po

File diff suppressed because it is too large Load Diff

View File

@@ -1,92 +0,0 @@
/*
* Copyright © 2023 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Matthias Clasen <mclasen@redhat.com>
*/
#include <gtk/gtk.h>
#include <gsk/gskboundingboxprivate.h>
static void
init_random_rect (graphene_rect_t *r)
{
r->origin.x = g_test_rand_double_range (0, 1000);
r->origin.y = g_test_rand_double_range (0, 1000);
r->size.width = g_test_rand_double_range (0, 1000);
r->size.height = g_test_rand_double_range (0, 1000);
}
static void
test_to_rect (void)
{
graphene_rect_t rect, rect2;
GskBoundingBox bb;
graphene_point_t p, p2;
for (unsigned int i = 0; i < 100; i++)
{
init_random_rect (&rect);
gsk_bounding_box_init_from_rect (&bb, &rect);
gsk_bounding_box_to_rect (&bb, &rect2);
graphene_rect_get_top_left (&rect, &p);
graphene_rect_get_top_left (&rect2, &p2);
/* Note: that we can't assert equality here is the reason
* GskBoundingBox exists.
*/
g_assert_true (graphene_point_near (&p, &p2, 0.001));
graphene_rect_get_bottom_right (&rect, &p);
graphene_rect_get_bottom_right (&rect2, &p2);
g_assert_true (graphene_point_near (&p, &p2, 0.001));
}
}
static void
test_contains (void)
{
graphene_rect_t rect;
GskBoundingBox bb;
graphene_point_t p;
for (unsigned int i = 0; i < 100; i++)
{
init_random_rect (&rect);
gsk_bounding_box_init_from_rect (&bb, &rect);
g_assert_true (gsk_bounding_box_contains_point (&bb, &bb.min));
g_assert_true (gsk_bounding_box_contains_point (&bb, &bb.max));
graphene_point_interpolate (&bb.min, &bb.max, 0.5, &p);
g_assert_true (gsk_bounding_box_contains_point (&bb, &p));
}
}
int
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/bounding-box/to-rect", test_to_rect);
g_test_add_func ("/bounding-box/contains", test_contains);
return g_test_run ();
}

View File

@@ -1,13 +0,0 @@
fill {
child: color {
bounds: 0 0 100 100;
color: rgb(255,0,0);
}
path: "\
M 10 10\
L 90 10\
L 90 90\
L 10 90\
Z";
fill-rule: winding;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 B

View File

@@ -1,19 +0,0 @@
color {
bounds: 0 0 100 100;
color: rgb(0,0,0);
}
stroke {
child: color {
bounds: 0 0 100 100;
color: rgb(255,0,0);
}
path: "\
M 10 10\
L 90 10\
L 90 90\
L 10 90\
Z";
line-width: 2;
line-cap: butt;
line-join: miter;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 B

View File

@@ -21,6 +21,57 @@
#include "gsk/gskcurveprivate.h"
static gboolean
measure_segment (const graphene_point_t *from,
const graphene_point_t *to,
float from_t,
float to_t,
GskCurveLineReason reason,
gpointer data)
{
float *length = data;
*length += graphene_point_distance (from, to, NULL, NULL);
return TRUE;
}
static float
measure_length (const GskCurve *curve)
{
float result = 0;
gsk_curve_decompose (curve, 0.5, measure_segment, &result);
return result;
}
/* This is a pretty nasty conic that makes it obvious that split()
* does not respect the progress values, so split() twice with
* scaled factor won't work.
*/
static void
test_conic_segment (void)
{
GskCurve c, s, e, m;
graphene_point_t pts[4] = {
GRAPHENE_POINT_INIT (-1856.131591796875, 46.217609405517578125),
GRAPHENE_POINT_INIT (-1555.9866943359375, 966.0810546875),
GRAPHENE_POINT_INIT (98.94945526123046875, 0),
GRAPHENE_POINT_INIT (-1471.33154296875, 526.701171875)
};
float start = 0.02222645096480846405029296875;
float end = 0.982032716274261474609375;
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CONIC, pts));
gsk_curve_split (&c, start, &s, NULL);
gsk_curve_segment (&c, start, end, &m);
gsk_curve_split (&c, end, NULL, &e);
g_assert_cmpfloat_with_epsilon (measure_length (&c), measure_length (&s) + measure_length (&m) + measure_length (&e), 0.03125);
}
static void
test_curve_tangents (void)
{
@@ -46,6 +97,17 @@ test_curve_tangents (void)
gsk_curve_get_end_tangent (&c, &t);
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
graphene_point_init (&p[0], 0, 0);
graphene_point_init (&p[1], 100, 0);
p[2] = GRAPHENE_POINT_INIT (g_test_rand_double_range (0, 20), 0);
graphene_point_init (&p[3], 100, 100);
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CONIC, p));
gsk_curve_get_start_tangent (&c, &t);
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
gsk_curve_get_end_tangent (&c, &t);
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
graphene_point_init (&p[0], 0, 0);
graphene_point_init (&p[1], 50, 0);
graphene_point_init (&p[2], 100, 50);
@@ -92,6 +154,7 @@ static gboolean
pathop_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskCurve *curve = user_data;
@@ -101,7 +164,7 @@ pathop_cb (GskPathOperation op,
if (op == GSK_PATH_MOVE)
return TRUE;
gsk_curve_init_foreach (curve, op, pts, n_pts);
gsk_curve_init_foreach (curve, op, pts, n_pts, weight);
return FALSE;
}
@@ -151,6 +214,7 @@ main (int argc,
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/curve/special/conic-segment", test_conic_segment);
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);

View File

@@ -8,6 +8,15 @@ init_random_point (graphene_point_t *p)
p->y = g_test_rand_double_range (0, 1000);
}
static float
random_weight (void)
{
if (g_test_rand_bit ())
return g_test_rand_double_range (1, 20);
else
return 1.0 / g_test_rand_double_range (1, 20);
}
static void
init_random_curve_with_op (GskCurve *curve,
GskPathOperation min_op,
@@ -48,6 +57,18 @@ init_random_curve_with_op (GskCurve *curve,
}
break;
case GSK_PATH_CONIC:
{
graphene_point_t p[4];
init_random_point (&p[0]);
init_random_point (&p[1]);
p[2] = GRAPHENE_POINT_INIT (random_weight(), 0);
init_random_point (&p[3]);
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_CONIC, p));
}
break;
default:
g_assert_not_reached ();
}
@@ -56,7 +77,7 @@ init_random_curve_with_op (GskCurve *curve,
static void
init_random_curve (GskCurve *curve)
{
init_random_curve_with_op (curve, GSK_PATH_LINE, GSK_PATH_CUBIC);
init_random_curve_with_op (curve, GSK_PATH_LINE, GSK_PATH_CONIC);
}
static void
@@ -196,17 +217,77 @@ static gboolean
add_curve_to_array (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GArray *array = user_data;
GskCurve c;
gsk_curve_init_foreach (&c, op, pts, n_pts);
gsk_curve_init_foreach (&c, op, pts, n_pts, weight);
g_array_append_val (array, c);
return TRUE;
}
static void
test_curve_decompose_conic (void)
{
g_test_skip ("No good error bounds for decomposing conics");
return;
for (int i = 0; i < 100; i++)
{
GArray *array;
GskCurve c;
GskPathBuilder *builder;
GskPath *path;
GskPathMeasure *measure;
const graphene_point_t *s;
init_random_curve_with_op (&c, GSK_PATH_CONIC, GSK_PATH_CONIC);
builder = gsk_path_builder_new ();
s = gsk_curve_get_start_point (&c);
gsk_path_builder_move_to (builder, s->x, s->y);
gsk_curve_builder_to (&c, builder);
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new_with_tolerance (path, 0.1);
array = g_array_new (FALSE, FALSE, sizeof (GskCurve));
g_assert_true (gsk_curve_decompose_curve (&c, GSK_PATH_FOREACH_ALLOW_CUBIC, 0.1, add_curve_to_array, array));
g_assert_cmpint (array->len, >=, 1);
for (int j = 0; j < array->len; j++)
{
GskCurve *c2 = &g_array_index (array, GskCurve, j);
g_assert_true (c2->op == GSK_PATH_CUBIC);
/* Check that the curves we got are approximating the conic */
for (int k = 0; k < 11; k++)
{
GskPathPoint point;
graphene_point_t p, q;
gsk_curve_get_point (c2, k/10.0, &p);
if (gsk_path_get_closest_point (path, &p, INFINITY, &point))
{
gsk_path_point_get_position (&point, &q);
g_assert_true (graphene_point_near (&p, &q, 0.5));
}
}
}
g_array_unref (array);
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
}
static void
test_curve_decompose_into (GskPathForeachFlags flags)
{
@@ -249,6 +330,9 @@ test_curve_decompose_into (GskPathForeachFlags flags)
case GSK_PATH_CUBIC:
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_CUBIC);
break;
case GSK_PATH_CONIC:
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_CONIC);
break;
default:
g_assert_not_reached ();
}
@@ -328,6 +412,7 @@ main (int argc, char *argv[])
g_test_add_func ("/curve/points", test_curve_points);
g_test_add_func ("/curve/tangents", test_curve_tangents);
g_test_add_func ("/curve/decompose", test_curve_decompose);
g_test_add_func ("/curve/decompose/conic", test_curve_decompose_conic);
g_test_add_func ("/curve/decompose/into/line", test_curve_decompose_into_line);
g_test_add_func ("/curve/decompose/into/quad", test_curve_decompose_into_quad);
g_test_add_func ("/curve/decompose/into/cubic", test_curve_decompose_into_cubic);

183
testsuite/gsk/dash.c Normal file
View File

@@ -0,0 +1,183 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include <gtk/gtk.h>
static gboolean
build_path (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathBuilder *builder = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builder);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
break;
case GSK_PATH_CUBIC:
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_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
break;
default:
g_assert_not_reached ();
break;
}
return TRUE;
}
static void
test_simple (void)
{
const struct {
const char *test;
float dash[4];
gsize n_dash;
float dash_offset;
const char *result;
} tests[] = {
/* a line with a dash of a quarter its size, very simple test */
{
"M 0 0 L 20 0",
{ 5, }, 1, 0.f,
"M 0 0 L 5 0 M 10 0 L 15 0",
},
/* a square with a dash of half its size, another simple test */
{
"M 0 0 h 10 v 10 h -10 z",
{ 5, }, 1, 0.f,
"M 10 0 L 10 5 M 10 10 L 5 10 M 0 10 L 0 5 M 0 0 L 5 0"
},
/* a square smaller than the dash, make sure it closes */
{
"M 0 0 h 10 v 10 h -10 z",
{ 50, }, 1, 0.f,
"M 0 0 L 10 0 L 10 10 L 0 10 Z"
},
/* a square exactly the dash's size, make sure it still closes */
{
"M 0 0 h 10 v 10 h -10 z",
{ 40, }, 1, 0.f,
"M 0 0 L 10 0 L 10 10 L 0 10 Z"
},
/* a dash with offset */
{
"M 0 0 h 10 v 10 h -10 z",
{ 5, }, 1, 2.5f,
"M 7.5 0 L 10 0 L 10 2.5 M 10 7.5 L 10 10 L 7.5 10 M 2.5 10 L 0 10 L 0 7.5 M 0 2.5 L 0 0 L 2.5 0"
},
/* a dash with offset, but this time the rect isn't closed */
{
"M 0 0 L 10 0 L 10 10 L 0 10 L 0 0",
{ 5, }, 1, 2.5f,
"M 0 0 L 2.5 0 M 7.5 0 L 10 0 L 10 2.5 M 10 7.5 L 10 10 L 7.5 10 M 2.5 10 L 0 10 L 0 7.5 M 0 2.5 L 0 0"
},
/* a dash with offset into an empty dash */
{
"M 0 0 h 10 v 10 h -10 z",
{ 5, }, 1, 7.5f,
"M 2.5 0 L 7.5 0 M 10 2.5 L 10 7.5 M 7.5 10 L 2.5 10 M 0 7.5 L 0 2.5"
},
/* a dash with offset where the whole rectangle fits into one element - make sure it closes */
{
"M 0 0 h 10 v 10 h -10 z",
{ 1, 1, 100 }, 3, 3.f,
"M 0 0 L 10 0 L 10 10 L 0 10 Z"
},
/* a dash with 0-length elements, aka dotting */
{
"M 0 0 h 10 v 10 h -10 z",
{ 0, 5 }, 2, 0.f,
"M 5 0 M 10 0 M 10 5 M 10 10 M 5 10 M 0 10 M 0 5 M 0 0"
},
/* a dash of a circle */
{
"M 10 5 O 10 10, 5 10, 0.70710676908493042 O 0 10, 0 5, 0.70710676908493042 O 0 0, 5 0, 0.70710676908493042 O 10 0, 10 5, 0.70710676908493042 Z",
{ 32, }, 1, 0.f,
"M 10 5 O 10 10, 5 10, 0.70710676908493042 O 0 10, 0 5, 0.70710676908493042 O 0 0, 5 0, 0.70710676908493042 O 10 0, 10 5, 0.70710676908493042 Z",
},
/* a dash with offset and 2 contours */
{
"M 10 10 h 10 v 10 h -10 z M 20 20 h 10 v 10 h -10 z",
{ 5, }, 1, 2.5f,
"M 17.5 10 L 20 10 L 20 12.5 M 20 17.5 L 20 20 L 17.5 20 M 12.5 20 L 10 20 L 10 17.5 M 10 12.5 L 10 10 L 12.5 10 "
"M 27.5 20 L 30 20 L 30 22.5 M 30 27.5 L 30 30 L 27.5 30 M 22.5 30 L 20 30 L 20 27.5 M 20 22.5 L 20 20 L 22.5 20"
},
};
GskPath *path, *result;
GskPathBuilder *builder;
GskStroke *stroke;
char *s;
for (gsize i = 0; i < G_N_ELEMENTS(tests); i++)
{
stroke = gsk_stroke_new (1);
gsk_stroke_set_dash (stroke, tests[i].dash, tests[i].n_dash);
gsk_stroke_set_dash_offset (stroke, tests[i].dash_offset);
path = gsk_path_parse (tests[i].test);
g_assert_nonnull (path);
s = gsk_path_to_string (path);
g_assert_cmpstr (s, ==, tests[i].test);
g_free (s);
builder = gsk_path_builder_new ();
gsk_path_dash (path, stroke, 0.5, build_path, builder);
result = gsk_path_builder_free_to_path (builder);
s = gsk_path_to_string (result);
g_assert_cmpstr (s, ==, tests[i].result);
g_free (s);
gsk_path_unref (result);
gsk_stroke_free (stroke);
gsk_path_unref (path);
}
}
int
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/dash/simple", test_simple);
return g_test_run ();
}

View File

@@ -27,7 +27,6 @@ test_bad_split (void)
GskPathMeasure *measure, *measure1;
GskPathBuilder *builder;
float split, length, epsilon;
GskPathPoint point0, point1;
/* An example that was isolated from the /path/segment test path.c
* It shows how uneven parametrization of cubics can lead to bad
@@ -38,13 +37,11 @@ test_bad_split (void)
measure = gsk_path_measure_new (path);
split = 2.962588;
gsk_path_measure_get_point (measure, 0, &point0);
gsk_path_measure_get_point (measure, split, &point1);
length = gsk_path_measure_get_length (measure);
epsilon = MAX (length / 256, 1.f / 1024);
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, path, &point0, &point1);;
gsk_path_builder_add_segment (builder, measure, 0, split);
path1 = gsk_path_builder_free_to_path (builder);
measure1 = gsk_path_measure_new (path1);
@@ -113,34 +110,34 @@ test_rect (void)
#define TEST_POS_AT(distance, X, Y) \
ret = gsk_path_measure_get_point (measure, distance, &point); \
g_assert_true (ret); \
gsk_path_point_get_position (path, &point, &p); \
gsk_path_point_get_position (&point, &p); \
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
ret = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (X, Y), INFINITY, &point); \
g_assert_true (ret); \
if (distance < length) \
g_assert_true (fabs (gsk_path_point_get_distance (measure, &point) - distance) < 0.01); \
g_assert_true (fabs (gsk_path_measure_get_distance (measure, &point) - distance) < 0.01); \
else \
g_assert_true (fabs (gsk_path_point_get_distance (measure, &point)) < 0.01); \
gsk_path_point_get_position (path, &point, &p); \
g_assert_true (fabs (gsk_path_measure_get_distance (measure, &point)) < 0.01); \
gsk_path_point_get_position (&point, &p); \
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
#define TEST_TANGENT_AT(distance, x1, y1, x2, y2) \
ret = gsk_path_measure_get_point (measure, distance, &point); \
g_assert_true (ret); \
gsk_path_point_get_tangent (path, &point, GSK_PATH_START, &tangent); \
gsk_path_point_get_tangent (&point, GSK_PATH_START, &tangent); \
g_assert_true (graphene_vec2_near (&tangent, graphene_vec2_init (&expected_tangent, x1, y1), 0.01)); \
gsk_path_point_get_tangent (path, &point, GSK_PATH_END, &tangent); \
gsk_path_point_get_tangent (&point, GSK_PATH_END, &tangent); \
g_assert_true (graphene_vec2_near (&tangent, graphene_vec2_init (&expected_tangent, x2, y2), 0.01)); \
#define TEST_POS_AT2(distance, X, Y, expected_distance) \
ret = gsk_path_measure_get_point (measure, distance, &point); \
g_assert_true (ret); \
gsk_path_point_get_position (path, &point, &p); \
gsk_path_point_get_position (&point, &p); \
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
ret = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (X, Y), INFINITY, &point); \
g_assert_true (ret); \
g_assert_true (fabs (gsk_path_point_get_distance (measure, &point) - expected_distance) < 0.01); \
gsk_path_point_get_position (path, &point, &p); \
g_assert_true (fabs (gsk_path_measure_get_distance (measure, &point) - expected_distance) < 0.01); \
gsk_path_point_get_position (&point, &p); \
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
TEST_POS_AT (0, 0, 0)
@@ -151,7 +148,7 @@ test_rect (void)
TEST_POS_AT (175, 75, 50)
TEST_POS_AT (250, 0, 50)
TEST_POS_AT (260, 0, 40)
TEST_POS_AT2 (300, 0, 0, 0)
TEST_POS_AT (300, 0, 0)
TEST_TANGENT_AT (0, 0, -1, 1, 0)
TEST_TANGENT_AT (50, 1, 0, 1, 0)
@@ -227,7 +224,7 @@ test_rect (void)
TEST_POS_AT2 (0, 100, 0, 0)
TEST_POS_AT2 (25, 75, 0, 25)
TEST_POS_AT2 (100, 0, 0, 100)
TEST_POS_AT2 (110, 10, 0, 110)
TEST_POS_AT2 (110, 10, 0, 90)
TEST_POS_AT2 (200, 100, 0, 0)
builder = gsk_path_builder_new ();
@@ -240,7 +237,7 @@ test_rect (void)
/* These cases are ambiguous */
TEST_POS_AT2 (0, 0, 100, 0)
TEST_POS_AT2 (25, 0, 75, 25)
TEST_POS_AT2 (25, 0, 75, 175)
TEST_POS_AT2 (100, 0, 0, 100)
TEST_POS_AT2 (110, 0, 10, 110)
TEST_POS_AT2 (200, 0, 100, 0)

View File

@@ -19,10 +19,19 @@
#include <gtk/gtk.h>
static float
random_weight (void)
{
if (g_test_rand_bit ())
return g_test_rand_double_range (1, 20);
else
return 1.0 / g_test_rand_double_range (1, 20);
}
static GskPath *
create_random_degenerate_path (guint max_contours)
{
#define N_DEGENERATE_PATHS 14
#define N_DEGENERATE_PATHS 15
GskPathBuilder *builder;
guint i;
@@ -156,6 +165,20 @@ create_random_degenerate_path (guint max_contours)
}
break;
case 14:
/* a conic with start == end */
{
graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
gsk_path_builder_move_to (builder, point.x, point.y);
gsk_path_builder_conic_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
point.x, point.y,
random_weight ());
}
break;
case N_DEGENERATE_PATHS:
default:
g_assert_not_reached ();
@@ -225,7 +248,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 +298,24 @@ add_standard_contour (GskPathBuilder *builder)
g_test_rand_double_range (-1000, 1000));
break;
case 6:
gsk_path_builder_conic_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),
random_weight ());
break;
case 7:
gsk_path_builder_rel_conic_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),
random_weight ());
break;
default:
g_assert_not_reached();
break;
@@ -325,22 +366,18 @@ test_segment_start (void)
GskPathBuilder *builder;
float epsilon, length;
guint i;
GskPathPoint point0, point1;
path = create_random_path (G_MAXUINT);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
epsilon = MAX (length / 1024, G_MINFLOAT);
gsk_path_measure_get_point (measure, 0, &point0);
for (i = 0; i < 100; i++)
{
float seg_length = length * i / 100.0f;
gsk_path_measure_get_point (measure, seg_length, &point1);
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, path, &point0, &point1);
gsk_path_builder_add_segment (builder, measure, 0, seg_length);
path1 = gsk_path_builder_free_to_path (builder);
measure1 = gsk_path_measure_new (path1);
@@ -365,7 +402,6 @@ test_segment_end (void)
GskPathBuilder *builder;
float epsilon, length;
guint i;
GskPathPoint point0, point1;
path = create_random_path (G_MAXUINT);
measure = gsk_path_measure_new (path);
@@ -376,10 +412,8 @@ test_segment_end (void)
{
float seg_length = length * i / 100.0f;
gsk_path_measure_get_point (measure, length - seg_length, &point0);
gsk_path_measure_get_point (measure, length, &point1);
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, path, &point0, &point1);
gsk_path_builder_add_segment (builder, measure, length - seg_length, length);
path1 = gsk_path_builder_free_to_path (builder);
measure1 = gsk_path_measure_new (path1);
@@ -404,7 +438,6 @@ test_segment_chunk (void)
GskPathBuilder *builder;
float epsilon, length;
guint i;
GskPathPoint point0, point1;
path = create_random_path (G_MAXUINT);
measure = gsk_path_measure_new (path);
@@ -415,19 +448,15 @@ test_segment_chunk (void)
{
float seg_start = length * i / 200.0f;
gsk_path_measure_get_point (measure, seg_start, &point0);
gsk_path_measure_get_point (measure, seg_start + length / 2, &point1);
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, path, &point0, &point1);
gsk_path_builder_add_segment (builder, measure, seg_start, seg_start + length / 2);
path1 = gsk_path_builder_free_to_path (builder);
measure1 = gsk_path_measure_new (path1);
g_assert_cmpfloat_with_epsilon (length / 2, gsk_path_measure_get_length (measure1), epsilon);
gsk_path_measure_get_point (measure, seg_start + length / 2, &point0);
gsk_path_measure_get_point (measure, seg_start, &point1);
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, path, &point0, &point1);
gsk_path_builder_add_segment (builder, measure, seg_start + length / 2, seg_start);
path2 = gsk_path_builder_free_to_path (builder);
measure2 = gsk_path_measure_new (path2);
@@ -451,7 +480,6 @@ test_segment (void)
GskPathBuilder *builder;
guint i;
float split1, split2, epsilon, length;
GskPathPoint point0, point1, point2, point3;
for (i = 0; i < 1000; i++)
{
@@ -464,22 +492,18 @@ test_segment (void)
split1 = g_test_rand_double_range (0, length);
split2 = g_test_rand_double_range (split1, length);
gsk_path_measure_get_point (measure, 0, &point0);
gsk_path_measure_get_point (measure, split1, &point1);
gsk_path_measure_get_point (measure, split2, &point2);
gsk_path_measure_get_point (measure, length, &point3);
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, path, &point0, &point1);
gsk_path_builder_add_segment (builder, measure, 0, split1);
path1 = gsk_path_builder_free_to_path (builder);
measure1 = gsk_path_measure_new (path1);
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, path, &point1, &point2);
gsk_path_builder_add_segment (builder, measure, split1, split2);
path2 = gsk_path_builder_free_to_path (builder);
measure2 = gsk_path_measure_new (path2);
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, path, &point2, &point3);
gsk_path_builder_add_segment (builder, measure, split2, length);
path3 = gsk_path_builder_free_to_path (builder);
measure3 = gsk_path_measure_new (path3);
@@ -523,7 +547,7 @@ test_get_point (void)
g_assert_true (gsk_path_is_empty (path));
continue;
}
gsk_path_point_get_position (path, &point, &last_point);
gsk_path_point_get_position (&point, &last_point);
/* FIXME: anything we can test with tangents here? */
last_offset = 0;
@@ -533,7 +557,7 @@ test_get_point (void)
offset = length * j / 1024;
ret = gsk_path_measure_get_point (measure, offset, &point);
g_assert_true (ret);
gsk_path_point_get_position (path, &point, &p);
gsk_path_point_get_position (&point, &p);
if (graphene_point_distance (&last_point, &p, NULL, NULL) > 2 * (offset - last_offset))
{
@@ -594,25 +618,25 @@ test_closest_point (void)
ret = gsk_path_get_closest_point (path1, &test, INFINITY, &point);
g_assert_true (ret);
gsk_path_point_get_position (path1, &point, &p1);
gsk_path_point_get_tangent (path1, &point, GSK_PATH_END, &t1);
offset1 = gsk_path_point_get_distance (measure1, &point);
gsk_path_point_get_position (&point, &p1);
gsk_path_point_get_tangent (&point, GSK_PATH_END, &t1);
offset1 = gsk_path_measure_get_distance (measure1, &point);
distance1 = graphene_point_distance (&p1, &test, NULL, NULL);
ret = gsk_path_get_closest_point (path2, &test, INFINITY, &point);
g_assert_true (ret);
gsk_path_point_get_position (path2, &point, &p2);
gsk_path_point_get_tangent (path2, &point, GSK_PATH_END, &t2);
offset2 = gsk_path_point_get_distance (measure2, &point);
gsk_path_point_get_position (&point, &p2);
gsk_path_point_get_tangent (&point, GSK_PATH_END, &t2);
offset2 = gsk_path_measure_get_distance (measure2, &point);
distance2 = graphene_point_distance (&p2, &test, NULL, NULL);
ret = gsk_path_get_closest_point (path, &test, INFINITY, &point);
g_assert_true (ret);
gsk_path_point_get_position (path, &point, &p);
gsk_path_point_get_tangent (path, &point, GSK_PATH_END, &t);
offset = gsk_path_point_get_distance (measure, &point);
gsk_path_point_get_position (&point, &p);
gsk_path_point_get_tangent (&point, GSK_PATH_END, &t);
offset = gsk_path_measure_get_distance (measure, &point);
distance = graphene_point_distance (&p, &test, NULL, NULL);
if (distance1 == distance)
@@ -678,11 +702,11 @@ test_closest_point_for_point (void)
offset = g_test_rand_double_range (0, length);
ret = gsk_path_measure_get_point (measure, offset, &point);
g_assert_true (ret);
gsk_path_point_get_position (path, &point, &p);
gsk_path_point_get_position (&point, &p);
ret = gsk_path_get_closest_point (path, &p, 2 * tolerance, &point);
g_assert_true (ret);
gsk_path_point_get_position (path, &point, &closest_point);
//closest_offset = gsk_path_point_get_distance (measure, &point);
gsk_path_point_get_position (&point, &closest_point);
//closest_offset = gsk_path_measure_get_distance (measure, &point);
distance = graphene_point_distance (&p, &closest_point, NULL, NULL);
/* should be given due to the TRUE return above, but who knows... */
@@ -779,6 +803,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;
@@ -810,6 +835,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_CONIC:
gsk_path_builder_conic_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
gsk_path_builder_conic_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x, weight);
break;
default:
g_assert_not_reached ();
return FALSE;

View File

@@ -59,7 +59,6 @@ compare_render_tests = [
'empty-shadow',
'empty-texture',
'empty-transform',
'fill',
'huge-height',
'huge-width',
'inset-shadow-multiple',
@@ -89,7 +88,6 @@ compare_render_tests = [
'scaled-cairo',
'scaled-texture',
'shadow-in-opacity',
'stroke',
'texture-scale-magnify-10000x',
'texture-scale-magnify-rotate',
'texture-scale-stripes',
@@ -272,8 +270,6 @@ node_parser_tests = [
'empty-cross-fade.ref.node',
'empty-debug.node',
'empty-debug.ref.node',
'empty-fill.node',
'empty-fill.ref.node',
'empty-inset-shadow.node',
'empty-inset-shadow.ref.node',
'empty-linear-gradient.node',
@@ -290,8 +286,6 @@ node_parser_tests = [
'empty-rounded-clip.ref.node',
'empty-shadow.node',
'empty-shadow.ref.node',
'empty-stroke.node',
'empty-stroke.ref.node',
'empty-text.node',
'empty-text.ref.node',
'empty-texture.node',
@@ -300,8 +294,6 @@ node_parser_tests = [
'empty-texture-scale.ref.node',
'empty-transform.node',
'empty-transform.ref.node',
'fill.node',
'fill.ref.node',
'glshader.node',
'glshader.ref.node',
'glshader.errors',
@@ -328,8 +320,6 @@ node_parser_tests = [
'string-error.errors',
'string-error.node',
'string-error.ref.node',
'stroke.node',
'stroke.ref.node',
'testswitch.node',
'text-fail.node',
'text-fail.ref.node',
@@ -373,6 +363,7 @@ tests = [
['path-special-cases'],
['measure'],
['measure-special-cases'],
['dash'],
]
test_cargs = []
@@ -409,7 +400,6 @@ internal_tests = [
[ 'half-float' ],
['rounded-rect'],
['misc'],
['boundingbox'],
]
foreach t : internal_tests

View File

@@ -1 +0,0 @@
fill { }

View File

@@ -1,14 +0,0 @@
fill {
child: color {
bounds: 0 0 50 50;
color: rgb(255,0,204);
}
path: "\
M 25 0\
L 39.694629669189453 45.225425720214844\
L 1.2235870361328125 17.274574279785156\
L 48.776412963867188 17.274574279785156\
L 10.305368423461914 45.225425720214844\
Z";
fill-rule: winding;
}

View File

@@ -1 +0,0 @@
stroke { }

View File

@@ -1,16 +0,0 @@
stroke {
child: color {
bounds: 0 0 50 50;
color: rgb(255,0,204);
}
path: "\
M 25 0\
L 39.694629669189453 45.225425720214844\
L 1.2235870361328125 17.274574279785156\
L 48.776412963867188 17.274574279785156\
L 10.305368423461914 45.225425720214844\
Z";
line-width: 1;
line-cap: butt;
line-join: miter;
}

View File

@@ -1,4 +0,0 @@
fill {
path: "M0,0L50,0C50,50 0,50 0,0Z";
fill-rule: winding;
}

View File

@@ -1,12 +0,0 @@
fill {
child: color {
bounds: 0 0 50 50;
color: rgb(255,0,204);
}
path: "\
M 0 0\
L 50 0\
C 50 50, 0 50, 0 0\
Z";
fill-rule: winding;
}

View File

@@ -1,6 +0,0 @@
stroke {
child: color { bounds: 0 0 100 100; color: red; }
path: "M10,10L90,90M90,10L10,90";
line-width: 10;
line-cap: round;
}

View File

@@ -1,14 +0,0 @@
stroke {
child: color {
bounds: 0 0 100 100;
color: rgb(255,0,0);
}
path: "\
M 10 10\
L 90 90\
M 90 10\
L 10 90";
line-width: 10;
line-cap: round;
line-join: miter;
}

View File

@@ -277,252 +277,44 @@ test_rsvg_parse (void)
}
}
/* Test that circles and rectangles serialize as expected and can be
* round-tripped through strings.
*/
static void
test_empty_path (void)
test_serialize_custom_contours (void)
{
GskPathBuilder *builder;
GskPath *path;
char *s;
graphene_rect_t bounds;
GskPathPoint point;
GskPath *path1;
char *string;
char *string1;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 50);
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (111, 222, 333, 444));
path = gsk_path_builder_free_to_path (builder);
g_assert_true (gsk_path_is_empty (path));
g_assert_false (gsk_path_is_closed (path));
string = gsk_path_to_string (path);
g_assert_cmpstr ("M 150 100 A 50 50 0 0 0 50 100 A 50 50 0 0 0 150 100 z M 111 222 h 333 v 444 h -333 z", ==, string);
s = gsk_path_to_string (path);
g_assert_cmpstr (s, ==, "");
g_free (s);
g_assert_false (gsk_path_get_bounds (path, &bounds));
g_assert_true (graphene_rect_equal (&bounds, graphene_rect_zero ()));
g_assert_false (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (0, 0), GSK_FILL_RULE_WINDING));
g_assert_false (gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (0, 0), INFINITY, &point));
path1 = gsk_path_parse (string);
string1 = gsk_path_to_string (path1);
g_assert_cmpstr (string, ==, string1);
g_free (string);
g_free (string1);
gsk_path_unref (path);
}
static void
test_rect_path (void)
{
GskPathBuilder *builder;
GskPath *path;
char *s;
graphene_rect_t bounds;
GskPathPoint point;
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 200, 100));
path = gsk_path_builder_free_to_path (builder);
g_assert_false (gsk_path_is_empty (path));
g_assert_true (gsk_path_is_closed (path));
s = gsk_path_to_string (path);
g_assert_cmpstr (s, ==, "M 0 0 L 200 0 L 200 100 L 0 100 Z");
g_free (s);
g_assert_true (gsk_path_get_bounds (path, &bounds));
g_assert_true (graphene_rect_equal (&bounds, &GRAPHENE_RECT_INIT (0, 0, 200, 100)));
g_assert_true (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (50, 50), GSK_FILL_RULE_WINDING));
g_assert_false (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (200, 200), GSK_FILL_RULE_WINDING));
g_assert_true (gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (200, 200), INFINITY, &point));
gsk_path_unref (path);
}
/* test quad <> cubic conversions */
static gboolean
collect_path (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
gpointer user_data)
{
GskPathBuilder *builder = user_data;
if (op == GSK_PATH_MOVE)
return TRUE;
switch (op)
{
case GSK_PATH_MOVE:
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builder);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y,
pts[2].x, pts[2].y);
break;
case GSK_PATH_CUBIC:
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;
default:
g_assert_not_reached ();
}
return TRUE;
}
static void
test_foreach (void)
{
const char *s = "M 0 0 Q 9 0, 9 9 Q 99 9, 99 18 Z";
const char *sp = "M 0 0 C 6 0, 9 3, 9 9 C 69 9, 99 12, 99 18 Z";
GskPath *path, *path2;
char *s2;
GskPathBuilder *builder;
path = gsk_path_parse (s);
builder = gsk_path_builder_new ();
gsk_path_foreach (path, GSK_PATH_FOREACH_ALLOW_QUAD, collect_path, builder);
path2 = gsk_path_builder_free_to_path (builder);
s2 = gsk_path_to_string (path2);
g_assert_cmpstr (s, ==, s2);
gsk_path_unref (path2);
g_free (s2);
builder = gsk_path_builder_new ();
gsk_path_foreach (path, GSK_PATH_FOREACH_ALLOW_QUAD|GSK_PATH_FOREACH_ALLOW_CUBIC, collect_path, builder);
path2 = gsk_path_builder_free_to_path (builder);
s2 = gsk_path_to_string (path2);
g_assert_cmpstr (s, ==, s2);
gsk_path_unref (path2);
g_free (s2);
builder = gsk_path_builder_new ();
gsk_path_foreach (path, GSK_PATH_FOREACH_ALLOW_CUBIC, collect_path, builder);
path2 = gsk_path_builder_free_to_path (builder);
s2 = gsk_path_to_string (path2);
g_assert_cmpstr (sp, ==, s2);
gsk_path_unref (path2);
g_free (s2);
}
/* Test the basics of the path point api */
static void
test_path_point (void)
{
GskPath *path;
GskPathPoint point;
gboolean ret;
graphene_point_t pos, center;
graphene_vec2_t t1, t2, mx;
float curvature;
path = gsk_path_parse ("M0,0L100,0L100,100L0,100Z");
ret = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (200, 200), INFINITY, &point);
g_assert_true (ret);
gsk_path_point_get_position (path, &point, &pos);
gsk_path_point_get_tangent (path, &point, GSK_PATH_START, &t1);
gsk_path_point_get_tangent (path, &point, GSK_PATH_END, &t2);
curvature = gsk_path_point_get_curvature (path, &point, &center);
g_assert_true (graphene_point_equal (&pos, &GRAPHENE_POINT_INIT (100, 100)));
g_assert_true (graphene_vec2_equal (&t1, graphene_vec2_y_axis ()));
graphene_vec2_negate (graphene_vec2_x_axis (), &mx);
g_assert_true (graphene_vec2_equal (&t2, &mx));
g_assert_true (curvature == 0);
gsk_path_unref (path);
}
/* Test that gsk_path_builder_add_segment yields the expected results */
static void
test_path_segments (void)
{
struct {
const char *path;
graphene_point_t p1;
graphene_point_t p2;
const char *result;
} tests[] = {
{
"M 0 0 L 100 0 L 50 50 Z",
{ 100, 0 },
{ 50, 50 },
"M 100 0 L 50 50"
},
{
"M 0 0 L 100 0 L 50 50 Z",
{ 50, 0 },
{ 70, 0 },
"M 50 0 L 70 0"
},
{
"M 0 0 L 100 0 L 50 50 Z",
{ 70, 0 },
{ 50, 0 },
"M 70 0 L 100 0 L 50 50 L 0 0 L 50 0"
},
{
"M 0 0 L 100 0 L 50 50 Z",
{ 50, 0 },
{ 50, 50 },
"M 50 0 L 100 0 L 50 50"
}
};
for (unsigned int i = 0; i < G_N_ELEMENTS (tests); i++)
{
GskPath *path;
GskPathPoint p1, p2;
GskPathBuilder *builder;
GskPath *result;
char *str;
path = gsk_path_parse (tests[i].path);
g_assert_true (gsk_path_get_closest_point (path, &tests[i].p1, INFINITY, &p1));
g_assert_true (gsk_path_get_closest_point (path, &tests[i].p2, INFINITY, &p2));
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, path, &p1, &p2);
result = gsk_path_builder_free_to_path (builder);
str = gsk_path_to_string (result);
g_assert_cmpstr (str, ==, tests[i].result);
g_free (str);
gsk_path_unref (path);
}
gsk_path_unref (path1);
}
int
main (int argc, char *argv[])
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/path/rsvg-parse", test_rsvg_parse);
g_test_add_func ("/path/empty", test_empty_path);
g_test_add_func ("/path/rect", test_rect_path);
g_test_add_func ("/path/foreach", test_foreach);
g_test_add_func ("/path/point", test_path_point);
g_test_add_func ("/path/segments", test_path_segments);
g_test_add_func ("/path/serialize-custom-contours", test_serialize_custom_contours);
return g_test_run ();
}

View File

@@ -19,10 +19,19 @@
#include <gtk/gtk.h>
static float
random_weight (void)
{
if (g_test_rand_bit ())
return g_test_rand_double_range (1, 20);
else
return 1.0 / g_test_rand_double_range (1, 20);
}
static GskPath *
create_random_degenerate_path (guint max_contours)
{
#define N_DEGENERATE_PATHS 14
#define N_DEGENERATE_PATHS 15
GskPathBuilder *builder;
guint i;
@@ -142,7 +151,7 @@ create_random_degenerate_path (guint max_contours)
break;
case 13:
/* a cubic with start == end */
/* a curve with start == end */
{
graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
@@ -156,6 +165,20 @@ create_random_degenerate_path (guint max_contours)
}
break;
case 14:
/* a conic with start == end */
{
graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
gsk_path_builder_move_to (builder, point.x, point.y);
gsk_path_builder_conic_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
point.x, point.y,
random_weight ());
}
break;
case N_DEGENERATE_PATHS:
default:
g_assert_not_reached ();
@@ -225,7 +248,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 +298,24 @@ add_standard_contour (GskPathBuilder *builder)
g_test_rand_double_range (-1000, 1000));
break;
case 6:
gsk_path_builder_conic_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),
random_weight ());
break;
case 7:
gsk_path_builder_rel_conic_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),
random_weight ());
break;
default:
g_assert_not_reached();
break;
@@ -314,6 +355,7 @@ create_random_path (guint max_contours)
typedef struct {
GskPathOperation op;
graphene_point_t pts[4];
float weight;
} PathOperation;
static void
@@ -371,6 +413,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;
@@ -405,6 +457,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);
}
@@ -414,6 +471,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,
@@ -428,6 +486,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;

View File

@@ -0,0 +1,142 @@
/* Copyright 2023 Red Hat, Inc.
*
* GTK+ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* GLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with GTK+; see the file COPYING. If not,
* see <http://www.gnu.org/licenses/>.
*
* Author: Matthias Clasen
*/
#include "config.h"
#include <gtk/gtk.h>
#include "gtk-path-tool.h"
#include <glib/gi18n-lib.h>
static gboolean
foreach_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathBuilder *builder = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builder);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
break;
case GSK_PATH_CUBIC:
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_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y,
pts[2].x, pts[2].y,
weight);
break;
default:
g_assert_not_reached ();
}
return TRUE;
}
void
do_decompose (int *argc, const char ***argv)
{
GError *error = NULL;
gboolean allow_quad = FALSE;
gboolean allow_curve = FALSE;
gboolean allow_conic = 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-conic", 0, 0, G_OPTION_ARG_NONE, &allow_conic, N_("Allow rational quadratic Bézier curves"), NULL },
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &args, NULL, N_("PATH") },
{ NULL, },
};
GskPathForeachFlags flags;
GskPath *path, *result;
GskPathBuilder *builder;
g_set_prgname ("gtk4-path-tool decompose");
context = g_option_context_new (NULL);
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
g_option_context_add_main_entries (context, entries, NULL);
g_option_context_set_summary (context, _("Decompose a path."));
if (!g_option_context_parse (context, argc, (char ***)argv, &error))
{
g_printerr ("%s\n", error->message);
g_error_free (error);
exit (1);
}
g_option_context_free (context);
if (args == NULL)
{
g_printerr ("%s\n", _("No paths given."));
exit (1);
}
path = get_path (args[0]);
flags = 0;
if (allow_quad)
flags |= GSK_PATH_FOREACH_ALLOW_QUAD;
if (allow_curve)
flags |= GSK_PATH_FOREACH_ALLOW_CUBIC;
if (allow_conic)
flags |= GSK_PATH_FOREACH_ALLOW_CONIC;
builder = gsk_path_builder_new ();
gsk_path_foreach (path, flags, foreach_cb, builder);
result = gsk_path_builder_free_to_path (builder);
if (result)
{
char *str = gsk_path_to_string (result);
g_print ("%s\n", str);
g_free (str);
}
else
{
g_printerr ("%s\n", _("That didn't work out."));
exit (1);
}
}

View File

@@ -0,0 +1,80 @@
/* Copyright 2023 Red Hat, Inc.
*
* GTK+ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* GLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with GTK+; see the file COPYING. If not,
* see <http://www.gnu.org/licenses/>.
*
* Author: Matthias Clasen
*/
#include "config.h"
#include <gtk/gtk.h>
#include "gtk-path-tool.h"
#include <glib/gi18n-lib.h>
void
do_info (int *argc, const char ***argv)
{
GError *error = NULL;
char **args = NULL;
GOptionContext *context;
GOptionEntry entries[] = {
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &args, NULL, N_("PATH") },
{ NULL, },
};
GskPath *path;
GskPathMeasure *measure;
graphene_rect_t bounds;
g_set_prgname ("gtk4-path-tool info");
context = g_option_context_new (NULL);
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
g_option_context_add_main_entries (context, entries, NULL);
g_option_context_set_summary (context, _("Print information about a path."));
if (!g_option_context_parse (context, argc, (char ***)argv, &error))
{
g_printerr ("%s\n", error->message);
g_error_free (error);
exit (1);
}
g_option_context_free (context);
if (args == NULL)
{
g_printerr ("%s\n", _("No paths given."));
exit (1);
}
path = get_path (args[0]);
measure = gsk_path_measure_new (path);
if (gsk_path_is_empty (path))
g_print ("%s\n", _("Path is empty."));
else
{
if (gsk_path_is_closed (path))
g_print ("%s\n", _("Path is closed"));
g_print ("%s %g\n", _("Path length"), gsk_path_measure_get_length (measure));
if (gsk_path_get_bounds (path, &bounds))
g_print ("%s: %g %g %g %g\n", _("Bounds"),
bounds.origin.x, bounds.origin.y,
bounds.size.width, bounds.size.height);
}
}

View File

@@ -0,0 +1,143 @@
/* Copyright 2023 Red Hat, Inc.
*
* GTK+ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* GLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with GTK+; see the file COPYING. If not,
* see <http://www.gnu.org/licenses/>.
*
* Author: Matthias Clasen
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <glib/gi18n-lib.h>
#include <glib/gprintf.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include "gtk-path-tool.h"
void
do_render (int *argc,
const char ***argv)
{
GError *error = NULL;
const char *fill = "winding";
const char *fg_color = "black";
const char *bg_color = "white";
const char *output_file = NULL;
char **args = NULL;
GOptionContext *context;
const GOptionEntry entries[] = {
{ "fill-rule", 0, 0, G_OPTION_ARG_STRING, &fill, N_("Fill rule (winding, even-odd)"), N_("VALUE") },
{ "fg-color", 0, 0, G_OPTION_ARG_STRING, &fg_color, N_("Foreground color"), N_("COLOR") },
{ "bg-color", 0, 0, G_OPTION_ARG_STRING, &bg_color, N_("Background color"), N_("COLOR") },
{ "output", 0, 0, G_OPTION_ARG_FILENAME, &output_file, N_("The output file"), N_("FILE") },
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args, NULL, N_("PATH") },
{ NULL, }
};
GskPath *path;
GskFillRule fill_rule;
GdkRGBA fg, bg;
graphene_rect_t bounds;
GskRenderNode *fg_node, *nodes[2], *node;
GdkSurface *surface;
GskRenderer *renderer;
GdkTexture *texture;
const char *filename;
if (gdk_display_get_default () == NULL)
{
g_printerr ("%s\n", _("Could not initialize windowing system"));
exit (1);
}
g_set_prgname ("gtk4-path-tool render");
context = g_option_context_new (NULL);
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
g_option_context_add_main_entries (context, entries, NULL);
g_option_context_set_summary (context, _("Render the path to a png image."));
if (!g_option_context_parse (context, argc, (char ***)argv, &error))
{
g_printerr ("%s\n", error->message);
g_error_free (error);
exit (1);
}
g_option_context_free (context);
if (args == NULL)
{
g_printerr ("%s\n", _("No path specified"));
exit (1);
}
if (g_strv_length (args) > 1)
{
g_printerr ("%s\n", _("Can only render a single path"));
exit (1);
}
path = get_path (args[0]);
fill_rule = get_enum_value (GSK_TYPE_FILL_RULE, _("fill rule"), fill);
get_color (&fg, fg_color);
get_color (&bg, bg_color);
gsk_path_get_bounds (path, &bounds);
graphene_rect_inset (&bounds, -10, -10);
nodes[0] = gsk_color_node_new (&bg, &bounds);
fg_node = gsk_color_node_new (&fg, &bounds);
nodes[1] = gsk_fill_node_new (fg_node, path, fill_rule);
node = gsk_container_node_new (nodes, 2);
gsk_render_node_unref (fg_node);
gsk_render_node_unref (nodes[0]);
gsk_render_node_unref (nodes[1]);
surface = gdk_surface_new_toplevel (gdk_display_get_default ());
renderer = gsk_renderer_new_for_surface (surface);
texture = gsk_renderer_render_texture (renderer, node, &bounds);
filename = output_file ? output_file : "path.png";
if (!gdk_texture_save_to_png (texture, filename))
{
char *msg = g_strdup_printf (_("Saving png to '%s' failed"), filename);
g_printerr ("%s\n", msg);
exit (1);
}
if (output_file == NULL)
{
char *msg = g_strdup_printf (_("Output written to '%s'."), filename);
g_print ("%s\n", msg);
g_free (msg);
}
g_object_unref (texture);
gsk_renderer_unrealize (renderer);
g_object_unref (renderer);
g_object_unref (surface);
gsk_render_node_unref (node);
gsk_path_unref (path);
g_strfreev (args);
}

View File

@@ -0,0 +1,93 @@
/* Copyright 2023 Red Hat, Inc.
*
* GTK+ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* GLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with GTK+; see the file COPYING. If not,
* see <http://www.gnu.org/licenses/>.
*
* Author: Matthias Clasen
*/
#include "config.h"
#include <gtk/gtk.h>
#include "gtk-path-tool.h"
#include <glib/gi18n-lib.h>
void
do_restrict (int *argc, const char ***argv)
{
GError *error = NULL;
double start = G_MAXDOUBLE;
double end = G_MAXDOUBLE;
char **args = NULL;
GOptionContext *context;
GOptionEntry entries[] = {
{ "start", 0, 0, G_OPTION_ARG_DOUBLE, &start, N_("Beginning of segment"), N_("LENGTH") },
{ "end", 0, 0, G_OPTION_ARG_DOUBLE, &start, N_("End of segment"), N_("LENGTH") },
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &args, NULL, N_("PATH") },
{ NULL, },
};
GskPath *path, *result;
GskPathMeasure *measure;
GskPathBuilder *builder;
g_set_prgname ("gtk4-path-tool restrict");
context = g_option_context_new (NULL);
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
g_option_context_add_main_entries (context, entries, NULL);
g_option_context_set_summary (context, _("Restrict a path to a segment."));
if (!g_option_context_parse (context, argc, (char ***)argv, &error))
{
g_printerr ("%s\n", error->message);
g_error_free (error);
exit (1);
}
g_option_context_free (context);
if (args == NULL)
{
g_printerr ("%s\n", _("No paths given."));
exit (1);
}
path = get_path (args[0]);
measure = gsk_path_measure_new (path);
if (start == G_MAXDOUBLE)
start = 0;
if (end == G_MAXDOUBLE)
end = gsk_path_measure_get_length (measure);
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, measure, start, end);
result = gsk_path_builder_free_to_path (builder);
if (result)
{
char *str = gsk_path_to_string (result);
g_print ("%s\n", str);
g_free (str);
}
else
{
g_printerr ("%s\n", _("That didn't work out."));
exit (1);
}
}

134
tools/gtk-path-tool-show.c Normal file
View File

@@ -0,0 +1,134 @@
/* Copyright 2023 Red Hat, Inc.
*
* GTK+ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* GLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with GTK+; see the file COPYING. If not,
* see <http://www.gnu.org/licenses/>.
*
* Author: Matthias Clasen
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <glib/gi18n-lib.h>
#include <glib/gprintf.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include "gtk-path-tool.h"
#include "path-view.h"
static void
show_path (GskPath *path,
GskFillRule fill_rule,
const GdkRGBA *fg_color,
const GdkRGBA *bg_color)
{
GtkWidget *window, *sw, *child;
window = gtk_window_new ();
gtk_window_set_title (GTK_WINDOW (window), _("Path Preview"));
// gtk_window_set_default_size (GTK_WINDOW (window), 700, 500);
sw = gtk_scrolled_window_new ();
gtk_scrolled_window_set_propagate_natural_width (GTK_SCROLLED_WINDOW (sw), TRUE);
gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (sw), TRUE);
gtk_window_set_child (GTK_WINDOW (window), sw);
child = path_view_new (path);
g_object_set (child,
"fill-rule", fill_rule,
"fg-color", fg_color,
"bg-color", bg_color,
NULL);
gtk_widget_set_hexpand (child, TRUE);
gtk_widget_set_vexpand (child, TRUE);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child);
gtk_window_present (GTK_WINDOW (window));
while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0)
g_main_context_iteration (NULL, TRUE);
}
void
do_show (int *argc,
const char ***argv)
{
GError *error = NULL;
const char *fill = "winding";
const char *fg_color = "black";
const char *bg_color = "white";
char **args = NULL;
GOptionContext *context;
const GOptionEntry entries[] = {
{ "fill-rule", 0, 0, G_OPTION_ARG_STRING, &fill, N_("Fill rule (winding, even-odd)"), N_("VALUE") },
{ "fg-color", 0, 0, G_OPTION_ARG_STRING, &fg_color, N_("Foreground color"), N_("COLOR") },
{ "bg-color", 0, 0, G_OPTION_ARG_STRING, &bg_color, N_("Background color"), N_("COLOR") },
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args, NULL, N_("PATH") },
{ NULL, }
};
GskPath *path;
GskFillRule fill_rule;
GdkRGBA fg;
GdkRGBA bg;
if (gdk_display_get_default () == NULL)
{
g_printerr ("%s\n", _("Could not initialize windowing system"));
exit (1);
}
g_set_prgname ("gtk4-path-tool show");
context = g_option_context_new (NULL);
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
g_option_context_add_main_entries (context, entries, NULL);
g_option_context_set_summary (context, _("Display the path."));
if (!g_option_context_parse (context, argc, (char ***)argv, &error))
{
g_printerr ("%s\n", error->message);
g_error_free (error);
exit (1);
}
g_option_context_free (context);
if (args == NULL)
{
g_printerr ("%s\n", _("No path specified"));
exit (1);
}
if (g_strv_length (args) > 1)
{
g_printerr ("%s\n", _("Can only show a single path"));
exit (1);
}
path = get_path (args[0]);
fill_rule = get_enum_value (GSK_TYPE_FILL_RULE, _("fill rule"), fill);
get_color (&fg, fg_color);
get_color (&bg, bg_color);
show_path (path, fill_rule, &fg, &bg);
gsk_path_unref (path);
g_strfreev (args);
}

139
tools/gtk-path-tool-utils.c Normal file
View File

@@ -0,0 +1,139 @@
/* Copyright 2023 Red Hat, Inc.
*
* GTK+ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* GLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with GTK+; see the file COPYING. If not,
* see <http://www.gnu.org/licenses/>.
*
* Author: Matthias Clasen
*/
#include "config.h"
#include <gtk/gtk.h>
#include <gio/gio.h>
#ifdef G_OS_UNIX
#include <gio/gunixinputstream.h>
#endif
#include "gtk-path-tool.h"
#include <glib/gi18n-lib.h>
GskPath *
get_path (const char *arg)
{
char *buffer = NULL;
gsize len;
GError *error = NULL;
GskPath *path;
if (arg[0] == '.' || arg[0] == '/')
{
if (!g_file_get_contents (arg, &buffer, &len, &error))
{
g_printerr ("%s\n", error->message);
exit (1);
}
}
#ifdef G_OS_UNIX
else if (strcmp (arg, "-") == 0)
{
GInputStream *in;
GOutputStream *out;
in = g_unix_input_stream_new (0, FALSE);
out = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
if (g_output_stream_splice (out, in, 0, NULL, &error) < 0)
{
g_printerr (_("Failed to read from standard input: %s\n"), error->message);
exit (1);
}
if (!g_output_stream_close (out, NULL, &error))
{
g_printerr (_("Error reading from standard input: %s\n"), error->message);
exit (1);
}
buffer = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (out));
g_object_unref (out);
g_object_unref (in);
}
#endif
else
buffer = g_strdup (arg);
g_strstrip (buffer);
path = gsk_path_parse (buffer);
if (path == NULL)
{
g_printerr (_("Failed to parse '%s' as path.\n"), arg);
exit (1);
}
g_free (buffer);
return path;
}
int
get_enum_value (GType type,
const char *type_nick,
const char *str)
{
GEnumClass *class = g_type_class_ref (type);
GEnumValue *value;
int val;
value = g_enum_get_value_by_nick (class, str);
if (value)
val = value->value;
else
{
GString *s;
s = g_string_new ("");
g_string_append_printf (s, _("Failed to parse '%s' as %s."), str, type_nick);
g_string_append (s, "\n");
g_string_append (s, _("Possible values: "));
for (unsigned int i = 0; i < class->n_values; i++)
{
if (i > 0)
g_string_append (s, ", ");
g_string_append (s, class->values[i].value_nick);
}
g_printerr ("%s\n", s->str);
g_string_free (s, TRUE);
exit (1);
}
g_type_class_unref (class);
return val;
}
void
get_color (GdkRGBA *rgba,
const char *str)
{
if (!gdk_rgba_parse (rgba, str))
{
char *msg = g_strdup_printf (_("Could not parse '%s' as color"), str);
g_printerr ("%s\n", msg);
exit (1);
}
}

142
tools/gtk-path-tool.c Normal file
View File

@@ -0,0 +1,142 @@
/* Copyright 2023 Red Hat, Inc.
*
* GTK+ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* GLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with GTK+; see the file COPYING. If not,
* see <http://www.gnu.org/licenses/>.
*
* Author: Matthias Clasen
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <glib/gi18n-lib.h>
#include <glib/gprintf.h>
#include <glib/gstdio.h>
#include <gtk/gtk.h>
#include "gtk-path-tool.h"
static void G_GNUC_NORETURN
usage (void)
{
g_print (_("Usage:\n"
" gtk4-path-tool [COMMAND] [OPTION…] PATH\n"
"\n"
"Perform various tasks on paths.\n"
"\n"
"Commands:\n"
" decompose Decompose the path\n"
" restrict Restrict the path to a segment\n"
" show Display the path in a window\n"
" render Render the path as an image\n"
" info Print information about the path\n"
"\n"));
exit (1);
}
/* A simplified version of g_log_writer_default_would_drop(), to avoid
* bumping up the required version of GLib to 2.68
*/
static gboolean
would_drop (GLogLevelFlags level,
const char *domain)
{
#if GLIB_CHECK_VERSION (2, 68, 0)
return g_log_writer_default_would_drop (level, domain);
#else
return (level & (G_LOG_LEVEL_ERROR |
G_LOG_LEVEL_CRITICAL |
G_LOG_LEVEL_WARNING)) == 0;
#endif
}
static GLogWriterOutput
log_writer_func (GLogLevelFlags level,
const GLogField *fields,
gsize n_fields,
gpointer user_data)
{
gsize i;
const char *domain = NULL;
const char *message = NULL;
for (i = 0; i < n_fields; i++)
{
if (g_strcmp0 (fields[i].key, "GLIB_DOMAIN") == 0)
domain = fields[i].value;
else if (g_strcmp0 (fields[i].key, "MESSAGE") == 0)
message = fields[i].value;
}
if (message != NULL && !would_drop (level, domain))
{
const char *prefix;
switch (level & G_LOG_LEVEL_MASK)
{
case G_LOG_LEVEL_ERROR:
prefix = "ERROR";
break;
case G_LOG_LEVEL_CRITICAL:
prefix = "CRITICAL";
break;
case G_LOG_LEVEL_WARNING:
prefix = "WARNING";
break;
default:
prefix = "INFO";
break;
}
g_printerr ("%s-%s: %s\n", domain, prefix, message);
}
return G_LOG_WRITER_HANDLED;
}
int
main (int argc, const char *argv[])
{
g_set_prgname ("gtk4-path-tool");
g_log_set_writer_func (log_writer_func, NULL, NULL);
gtk_init_check ();
gtk_test_register_all_types ();
if (argc < 2)
usage ();
if (strcmp (argv[1], "--help") == 0)
usage ();
argv++;
argc--;
if (strcmp (argv[0], "decompose") == 0)
do_decompose (&argc, &argv);
else if (strcmp (argv[0], "info") == 0)
do_info (&argc, &argv);
else if (strcmp (argv[0], "render") == 0)
do_render (&argc, &argv);
else if (strcmp (argv[0], "restrict") == 0)
do_restrict (&argc, &argv);
else if (strcmp (argv[0], "show") == 0)
do_show (&argc, &argv);
else
usage ();
return 0;
}

14
tools/gtk-path-tool.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
void do_info (int *argc, const char ***argv);
void do_decompose (int *argc, const char ***argv);
void do_restrict (int *argc, const char ***argv);
void do_render (int *argc, const char ***argv);
void do_show (int *argc, const char ***argv);
GskPath *get_path (const char *arg);
int get_enum_value (GType type,
const char *type_nick,
const char *str);
void get_color (GdkRGBA *rgba,
const char *str);

View File

@@ -23,6 +23,14 @@ if win32_enabled
endif
gtk_tools = [
['gtk4-path-tool', ['gtk-path-tool.c',
'gtk-path-tool-decompose.c',
'gtk-path-tool-info.c',
'gtk-path-tool-render.c',
'gtk-path-tool-restrict.c',
'gtk-path-tool-show.c',
'gtk-path-tool-utils.c',
'path-view.c'], [libgtk_dep]],
['gtk4-query-settings', ['gtk-query-settings.c'], [libgtk_dep]],
['gtk4-builder-tool', ['gtk-builder-tool.c',
'gtk-builder-tool-simplify.c',

221
tools/path-view.c Normal file
View File

@@ -0,0 +1,221 @@
/* Copyright 2023 Red Hat, Inc.
*
* GTK+ is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* GLib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with GTK+; see the file COPYING. If not,
* see <http://www.gnu.org/licenses/>.
*
* Author: Matthias Clasen
*/
#include "config.h"
#include "path-view.h"
struct _PathView
{
GtkWidget parent_instance;
GskPath *path;
graphene_rect_t bounds;
GskFillRule fill_rule;
GdkRGBA fg;
GdkRGBA bg;
int padding;
};
enum {
PROP_PATH = 1,
PROP_FILL_RULE,
PROP_FG_COLOR,
PROP_BG_COLOR,
N_PROPERTIES
};
static GParamSpec *properties[N_PROPERTIES] = { NULL, };
struct _PathViewClass
{
GtkWidgetClass parent_class;
};
G_DEFINE_TYPE (PathView, path_view, GTK_TYPE_WIDGET)
static void
path_view_init (PathView *self)
{
self->fill_rule = GSK_FILL_RULE_WINDING;
self->fg = (GdkRGBA) { 0, 0, 0, 1};
self->bg = (GdkRGBA) { 1, 1, 1, 1};
self->padding = 10;
}
static void
path_view_dispose (GObject *object)
{
PathView *self = PATH_VIEW (object);
g_clear_pointer (&self->path, gsk_path_unref);
G_OBJECT_CLASS (path_view_parent_class)->dispose (object);
}
static void
path_view_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
PathView *self = PATH_VIEW (object);
switch (prop_id)
{
case PROP_PATH:
g_value_set_boxed (value, self->path);
break;
case PROP_FILL_RULE:
g_value_set_enum (value, self->fill_rule);
break;
case PROP_FG_COLOR:
g_value_set_boxed (value, &self->fg);
break;
case PROP_BG_COLOR:
g_value_set_boxed (value, &self->bg);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
path_view_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
PathView *self = PATH_VIEW (object);
switch (prop_id)
{
case PROP_PATH:
g_clear_pointer (&self->path, gsk_path_unref);
self->path = g_value_dup_boxed (value);
gsk_path_get_bounds (self->path, &self->bounds);
gtk_widget_queue_resize (GTK_WIDGET (self));
break;
case PROP_FILL_RULE:
self->fill_rule = g_value_get_enum (value);
gtk_widget_queue_draw (GTK_WIDGET (self));
break;
case PROP_FG_COLOR:
self->fg = *(GdkRGBA *) g_value_get_boxed (value);
gtk_widget_queue_draw (GTK_WIDGET (self));
break;
case PROP_BG_COLOR:
self->bg = *(GdkRGBA *) g_value_get_boxed (value);
gtk_widget_queue_draw (GTK_WIDGET (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
path_view_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
PathView *self = PATH_VIEW (widget);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
*minimum = *natural = (int) ceilf (self->bounds.size.width) + 2 * self->padding;
else
*minimum = *natural = (int) ceilf (self->bounds.size.height) + 2 * self->padding;
}
static void
path_view_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
PathView *self = PATH_VIEW (widget);
graphene_rect_t bounds = self->bounds;
graphene_rect_inset (&bounds, - self->padding, - self->padding);
gtk_snapshot_save (snapshot);
gtk_snapshot_append_color (snapshot, &self->bg, &self->bounds);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (self->padding, self->padding));
gtk_snapshot_push_fill (snapshot, self->path, self->fill_rule);
gtk_snapshot_append_color (snapshot, &self->fg, &self->bounds);
gtk_snapshot_pop (snapshot);
gtk_snapshot_restore (snapshot);
}
static void
path_view_class_init (PathViewClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
object_class->dispose = path_view_dispose;
object_class->get_property = path_view_get_property;
object_class->set_property = path_view_set_property;
widget_class->measure = path_view_measure;
widget_class->snapshot = path_view_snapshot;
properties[PROP_PATH]
= g_param_spec_boxed ("path", NULL, NULL,
GSK_TYPE_PATH,
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
properties[PROP_FILL_RULE]
= g_param_spec_enum ("fill-rule", NULL, NULL,
GSK_TYPE_FILL_RULE,
GSK_FILL_RULE_WINDING,
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
properties[PROP_FG_COLOR]
= g_param_spec_boxed ("fg-color", NULL, NULL,
GDK_TYPE_RGBA,
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
properties[PROP_BG_COLOR]
= g_param_spec_boxed ("bg-color", NULL, NULL,
GDK_TYPE_RGBA,
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, N_PROPERTIES, properties);
}
GtkWidget *
path_view_new (GskPath *path)
{
return g_object_new (PATH_TYPE_VIEW,
"path", path,
NULL);
}

9
tools/path-view.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include <gtk/gtk.h>
#define PATH_TYPE_VIEW (path_view_get_type ())
G_DECLARE_FINAL_TYPE (PathView, path_view, PATH, VIEW, GtkWidget)
GtkWidget * path_view_new (GskPath *path);