Compare commits

..

82 Commits

Author SHA1 Message Date
Matthias Clasen
44bc5a139c Add rounded rects to the tests 2023-08-12 16:09:31 -04:00
Matthias Clasen
11f48f3c5b Add gsk_path_builder_add_rounded_rect
For now, this is using gsk_path_builder_svg_arc_to
to approximate elliptical arcs.
2023-08-12 15:34:54 -04:00
Matthias Clasen
fa4ad1590d Merge branch 'matthiasc/for-main' into 'main'
path: Some documentation improvements

See merge request GNOME/gtk!6273
2023-08-11 16:42:25 +00:00
Matthias Clasen
47ddc742fc path: Some documentation improvements
Among other things, add a quick summary of
SVG path syntax, and add a few illustrations.
2023-08-11 12:09:12 -04:00
Matthias Clasen
31d994837b Merge branch 'path-tests-3' into 'main'
path builder: Fix add_cairo_path

Closes #6011

See merge request GNOME/gtk!6270
2023-08-11 12:44:00 +00:00
Matthias Clasen
83d692e2d3 path: Add tests for gsk_path_builder_add methods
Check that they all end the current contour.

Fixes: #6011
2023-08-11 07:24:19 -04:00
Matthias Clasen
f2835bb48b pathbuilder: Treat current point consistently
Arrange that none of the 'add' methods change
the current point.
2023-08-10 22:56:47 -04:00
Fran Dieguez
857c22db32 Update Galician translation 2023-08-11 00:35:58 +00:00
Matthias Clasen
fff1645060 path builder: Fix add_cairo_path
We were forgetting to end the current contour.
2023-08-10 16:08:32 -04:00
Matthias Clasen
4f0c3c81fb Merge branch 'matthiasc/for-main' into 'main'
Matthiasc/for main

See merge request GNOME/gtk!6269
2023-08-10 04:28:26 +00:00
Matthias Clasen
421365cfc3 Add more path point tests 2023-08-09 23:55:08 -04:00
Matthias Clasen
59c5b41f3d Cosmetics 2023-08-09 23:48:50 -04:00
Matthias Clasen
267367355f Merge branch 'path-fill-tests2' into 'main'
Add more path fill tests

See merge request GNOME/gtk!6268
2023-08-10 03:10:14 +00:00
Matthias Clasen
0aa3356499 Add more path fill tests
These also used to live with measure tests.
2023-08-09 22:47:13 -04:00
Matthias Clasen
14a3b35333 Merge branch 'path-fill-tests' into 'main'
Add path fill tests

See merge request GNOME/gtk!6267
2023-08-10 02:21:34 +00:00
Matthias Clasen
77def95659 Add path fill tests
These used to live with the measure tests, but
path_in_fill no longer involves a measure.
2023-08-09 21:50:29 -04:00
Matthias Clasen
4e91cd1155 Merge branch 'matthiasc/for-main' into 'main'
pathpoint: Use the right type

See merge request GNOME/gtk!6266
2023-08-09 20:37:14 +00:00
Matthias Clasen
2d923d362f Merge branch 'bilelmoussaoui/gi-gsk' into 'main'
gsk/gi: new Path api fixes

See merge request GNOME/gtk!6265
2023-08-09 19:40:53 +00:00
Matthias Clasen
b7544636d4 contour: Skip the move
When jumping between segments, we always
need to skip index 0, since thats the move.

This fixes the tangent checks in the
rectangle tests.
2023-08-09 15:12:58 -04:00
Matthias Clasen
6220baf230 get_closest_point: Prefer earlier points
Only switch to a later point if it is strictly
closer than the existing match. This fixes
start- vs endpoint confusion in the rectangle
tests.
2023-08-09 15:12:52 -04:00
Matthias Clasen
917307a6a4 pathpoint: Use the right type 2023-08-09 14:37:14 -04:00
Matthias Clasen
5f49b7b492 Merge branch 'matthiasc/for-main' into 'main'
pathpoint: Clarify docs

See merge request GNOME/gtk!6262
2023-08-09 18:36:05 +00:00
Bilal Elmoussaoui
b03abea402 gsk: Don't change the GskRenderNodeType order
As we use their values to check that a node can be downcasted to a node
type in the Rust bindings
such change would break existing code
2023-08-09 20:26:46 +02:00
Bilal Elmoussaoui
d4fcc883cb gsk/gi: Add missing since annotations & docs 2023-08-09 20:26:46 +02:00
Daniel Boles
ca2a2697d6 Merge branch 'dboles/for-main' into 'main'
docs: Add/fix some links; avoid pluralisation

See merge request GNOME/gtk!6261
2023-08-09 16:57:19 +00:00
Matthias Clasen
68c6f63e90 pathpoint: Clarify docs
Add a paragraph explaining the situation
for comparing the start- and endpoint of
a closed path.
2023-08-09 11:30:26 -04:00
Daniel Boles
0e89fed8ed docs: Add/fix some links; avoid pluralisation
Appending `s` breaks the [type@NS.Object] notation, so fix that in
ListHeader. Add links to ListItem and Overlay, and avoid appending `s`
after `backtick`s just for consistency with the [type@NS.Object] issue.
2023-08-09 15:35:12 +01:00
Benjamin Otte
4d726543d0 Merge branch 'path-for-merge-6' into 'main'
Simplify GskPathPoint

See merge request GNOME/gtk!6260
2023-08-09 14:24:33 +00:00
Matthias Clasen
2bcc998e90 Add tests for gsk_path_builder_add_segment
Test at least some of the special cases:
- start and end point on the same contour
- start and end point on the same segment
- end before start
2023-08-09 07:01:01 -04:00
Matthias Clasen
f707185248 Add gsk_path_builder_add_segment
With GskPathPoint, we can subset paths without
a measure object.
2023-08-09 07:01:01 -04:00
Matthias Clasen
ded30049db path: Add gsk_path_get_start/end_point
These are useful to have, now that we are
relying more on GskPathPoint in our api.
2023-08-08 22:05:34 -04:00
Matthias Clasen
5c55e9d11e Add gsk_path_point_equal/compare
These just make sense to have.
2023-08-08 22:05:34 -04:00
Matthias Clasen
c35bb11c18 Simplify GskPathPoint
Use simple representation of just
{ contour index, segment index, t value }
2023-08-08 20:31:56 -04:00
Matthias Clasen
cc19781d27 Merge branch 'cpdb-fix' into 'main'
print: Update printerprivate header location for cpdb backend

See merge request GNOME/gtk!6259
2023-08-08 23:01:12 +00:00
Jeremy Bícha
08d08678e8 print: Update printerprivate header location for cpdb backend
This fixes the print-cpdb build option
2023-08-08 17:55:54 -04:00
Matthias Clasen
11b632a691 Merge branch 'timezone' into 'main'
test-calendar: Update timezone name

See merge request GNOME/gtk!6258
2023-08-08 19:31:31 +00:00
Jeremy Bícha
4881ef251e test-calendar: Update timezone name
Debian no longer provides the MET timezone
but instead provides geographical region and city name
2023-08-08 15:01:42 -04:00
Luca Bacci
5b39abd1a4 Merge branch 'fix-server-side-decorations' into 'main'
win32: Fix minimize and maximize buttons missing on ssd

Closes #5142

See merge request GNOME/gtk!6052
2023-08-08 15:17:09 +00:00
Daniel Mustieles
f0089097dd Update Spanish translation 2023-08-08 10:41:17 +00:00
Fran Dieguez
d603925ab2 Update Galician translation 2023-08-08 00:05:18 +00:00
Matthias Clasen
6af9dc8612 Merge branch 'xry111/issue6003' into 'main'
print: Revert "Start sorting apart includes" change for gtkprinteroptionprivate.h

Closes #6003

See merge request GNOME/gtk!6250
2023-08-07 22:52:43 +00:00
Benjamin Otte
35b313f9b0 Merge branch 'path-for-merge-3' into 'main'
gskpath: Add stroking

See merge request GNOME/gtk!6248
2023-08-07 22:17:53 +00:00
Matthias Clasen
fe84ce8686 gsk: Try to fix flipped fallback rendering
This patch is pretty much a blind guess, but
it seems to fix flipped rendering of fill
and stroke nodes.
2023-08-07 17:53:49 -04:00
Matthias Clasen
02a4e5a457 Add compare tests for fill and stroke nodes 2023-08-07 13:02:57 -04:00
Matthias Clasen
a548a91e6d Add nodeparser tests for fill and stroke nodes 2023-08-07 13:02:57 -04:00
Matthias Clasen
fc3c2e0942 gsk: Implement parsing fill and stroke nodes
Make serialization and deserialization work for stroke and
fill nodes.
2023-08-07 13:02:57 -04:00
Matthias Clasen
60e07335a1 demos: Add stroking to the path fill demo 2023-08-07 13:02:57 -04:00
Benjamin Otte
cb6b968fb5 snapshot: Add gtk_snapshot_push_stroke()
This is the obvious GtkSnapshot API to go
along with the new stroke nodes.
2023-08-07 13:02:57 -04:00
Benjamin Otte
3aa3816125 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-07 13:02:57 -04:00
Matthias Clasen
c37c29422a Add gsk_path_get_stroke_bounds
This is a helper to compute the bounds for
stroke nodes. We keep it private for now.
2023-08-07 13:02:57 -04:00
Matthias Clasen
db71c07f8f Add GskStroke
A GskStroke struct collects the parameters that are
needed for stroking a path.
2023-08-07 13:02:57 -04:00
Emmanuele Bassi
bf1b97efd3 Merge branch 'gdk-macos-depth-fix' into 'main'
gdk: Fix compilation on macos

See merge request GNOME/gtk!6208
2023-08-07 14:46:47 +00:00
Xi Ruoyao
4d7277f72c print: Revert "Start sorting apart includes" change for gtkprinteroptionprivate.h
The print backends needs the functions in gtkprinteroptionprivate.h to
be exported.

Fixes #6003.
2023-08-07 22:29:10 +08:00
Matthias Clasen
3296f01edf Merge branch 'path-fixes' into 'main'
Fix path point preconditions

See merge request GNOME/gtk!6251
2023-08-07 13:00:18 +00:00
Matthias Clasen
dbcbf0523b Add tests for GskPathPoint
As always, untested code doesn't work...
2023-08-07 08:33:05 -04:00
Matthias Clasen
888abb6f24 path: Make get_closest_point work
We weren't setting point->path. Oops.
Good to have tests.
2023-08-07 08:33:05 -04:00
Matthias Clasen
9b916b329a pathpoint: Implement get_tangent fully
We were not taking the direction into account
at all.
2023-08-07 08:33:05 -04:00
Matthias Clasen
eb3ada6386 Fix path point preconditions
These were inverted and did not work. Oops.
2023-08-07 08:32:50 -04:00
Matthias Clasen
2a725b658b Merge branch 'path-for-merge-2' into 'main'
GskPath for merge, part 0

See merge request GNOME/gtk!6247
2023-08-07 01:31:37 +00:00
Benjamin Otte
97f605f811 demos: Add a simple demo filling a path 2023-08-06 21:01:11 -04:00
Benjamin Otte
4e7d578881 rendernode: Implement fast-path for solid fills
They happen so often that it's worth avoiding the clip + paint path
there.
2023-08-06 21:01:11 -04:00
Benjamin Otte
438d936f57 gtk: Add gtk_snapshot_push_fill()
This is the obvious GtkSnapshot API to go
along with the new fill nodes.
2023-08-06 21:01:11 -04:00
Benjamin Otte
fc4a464b47 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-06 21:01:11 -04:00
Matthias Clasen
785b9541f6 gsk: Add tests for GskPath 2023-08-06 21:01:11 -04:00
Matthias Clasen
a4cbabb80f gsk: Add tests for GskCurve 2023-08-06 20:48:09 -04:00
Matthias Clasen
1b5dfcba7e gsk: Add GskPath
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.

A GskPathPoint represents a point on a path, which can
be queried for various properties.

GskPathBuilder is an auxiliary builder object for paths.
2023-08-06 20:48:09 -04:00
Matthias Clasen
ba41edf531 gsk: Add tests for GskBoundingBox 2023-08-06 17:16:46 -04:00
Matthias Clasen
bf3892caed gsk: 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-06 17:16:30 -04:00
Benjamin Otte
1e6a95aa1b Merge branch 'grid-scroll-crash' into 'main'
gtkgridview: Fix crash on scroll to 0 sized tile

Closes #5945

See merge request GNOME/gtk!6228
2023-08-06 17:56:57 +00:00
tszymanski
70a9d08e21 gtkgridview: return empty area for empty tiles during scroll 2023-08-06 10:26:32 -07:00
Piotr Drąg
7511d4e281 Update Polish translation 2023-08-06 14:22:51 +02:00
Asier Sarasua Garmendia
53ea97decb Update Basque translation 2023-08-06 07:20:22 +00:00
Matthias Clasen
28190ded71 Merge branch 'matthiasc/for-main' into 'main'
Update tests

See merge request GNOME/gtk!6244
2023-08-05 19:32:56 +00:00
Matthias Clasen
8083cb0e47 Merge branch 'smaller-than' into 'main'
section-accessibility: Close tag

See merge request GNOME/gtk!6243
2023-08-05 19:21:44 +00:00
Hari Rana
544320a961 section-accessibility: Close tag 2023-08-05 12:25:23 -04:00
Florentina Musat
ee5a95ba24 Update Romanian translation 2023-08-05 15:54:45 +00:00
Jordi Mas
232260b86b Update Catalan translation 2023-08-05 17:30:28 +02:00
Matthias Clasen
857f50649c Merge branch 'microoptimize-label-wfh' into 'main'
Microoptimize GtkLabel width-for-height computation

See merge request GNOME/gtk!6219
2023-08-05 15:15:55 +00:00
tszymanski
a483fb2d96 gtkgridview: Fix crash on scroll to 0 sized tile
No longer crashes with my listview_clocks demo or in real scrolling in
my application. "GtkGridView failed to scroll to given position. Ignoring..."
warnings are printed when it would have crashed.

Sometimes the scroll jumps incorrectly when it doesn't crash, but that's
a separate bug but is probably related to whatever is causing this
crash.

Fixes https://gitlab.gnome.org/GNOME/gtk/-/issues/5945, at least in
terms of the immediate crash.
2023-08-01 22:17:54 -07:00
Sergey Bugaev
d27e7e0180 label: Microoptimize width-for-height computation
We told Pango to limit width to mid pixels, and it returned a layout
size of text_width by text_height; text_width can be considerably
smaller than mid. If the layout fits, we know that it fits at
text_width, so set max to that. This lets us skip many iterations in a
typical case.
2023-07-28 18:42:17 +03:00
Jayson Reis
aa888c0b3f gdk: Remove a leftover reference to the renamed variable prefers_high_depth 2023-07-23 23:13:47 +02:00
dgsasha
5240d8cdf5 win32: Fix minimize and maximize buttons missing on ssd
Completely fixes https://gitlab.gnome.org/GNOME/gtk/-/issues/5142
2023-06-10 23:08:21 -04:00
100 changed files with 11061 additions and 15054 deletions

View File

@@ -336,8 +336,6 @@
<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>
<file>printing.c</file>
@@ -423,9 +421,6 @@
<gresource prefix="/fontrendering">
<file>fontrendering.ui</file>
</gresource>
<gresource prefix="/path_text">
<file>path_text.ui</file>
</gresource>
<gresource prefix="/org/gtk/Demo4">
<file>icons/16x16/actions/application-exit.png</file>
<file>icons/16x16/actions/document-new.png</file>

View File

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

View File

@@ -1,7 +1,7 @@
/* Path/Fill
/* Path/Fill and Stroke
*
* This demo shows how to use PangoCairo to draw text with more than
* just a single color.
* This demo shows how to use GskPath to draw shapes that are (a bit)
* more complex than a rounded rectangle.
*/
#include <glib/gi18n.h>
@@ -9,305 +9,162 @@
#include "paintable.h"
#define GTK_TYPE_PATH_PAINTABLE (gtk_path_paintable_get_type ())
G_DECLARE_FINAL_TYPE (GtkPathPaintable, gtk_path_paintable, GTK, PATH_PAINTABLE, GObject)
#define GTK_TYPE_LOGO_PAINTABLE (gtk_logo_paintable_get_type ())
G_DECLARE_FINAL_TYPE (GtkLogoPaintable, gtk_logo_paintable, GTK, LOGO_PAINTABLE, GObject)
struct _GtkPathPaintable
struct _GtkLogoPaintable
{
GObject parent_instance;
int width;
int height;
GskPath *path;
GdkPaintable *background;
GskPath *path[3];
GdkRGBA color[3];
GskPath *stroke_path;
GskStroke *stroke1;
GskStroke *stroke2;
GdkRGBA stroke_color;
};
struct _GtkPathPaintableClass
struct _GtkLogoPaintableClass
{
GObjectClass parent_class;
};
static int
gtk_path_paintable_get_intrinsic_width (GdkPaintable *paintable)
gtk_logo_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (paintable);
if (self->background)
return MAX (gdk_paintable_get_intrinsic_width (self->background), self->width);
else
return self->width;
return self->width;
}
static int
gtk_path_paintable_get_intrinsic_height (GdkPaintable *paintable)
gtk_logo_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (paintable);
if (self->background)
return MAX (gdk_paintable_get_intrinsic_height (self->background), self->height);
else
return self->height;
return self->height;
}
static void
gtk_path_paintable_snapshot (GdkPaintable *paintable,
gtk_logo_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (paintable);
#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)
for (unsigned int i = 0; i < 3; i++)
{
gdk_paintable_snapshot (self->background, snapshot, width, height);
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);
}
else
for (unsigned int i = 0; i < 3; i++)
{
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->stroke1);
gtk_snapshot_append_color (snapshot,
&self->stroke_color,
&GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
}
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_path_paintable_get_flags (GdkPaintable *paintable)
gtk_logo_paintable_get_flags (GdkPaintable *paintable)
{
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;
return GDK_PAINTABLE_STATIC_CONTENTS | GDK_PAINTABLE_STATIC_SIZE;
}
static void
gtk_path_paintable_paintable_init (GdkPaintableInterface *iface)
gtk_logo_paintable_paintable_init (GdkPaintableInterface *iface)
{
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;
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;
}
/* When defining the GType, we need to implement the GdkPaintable interface */
G_DEFINE_TYPE_WITH_CODE (GtkPathPaintable, gtk_path_paintable, G_TYPE_OBJECT,
G_DEFINE_TYPE_WITH_CODE (GtkLogoPaintable, gtk_logo_paintable, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gtk_path_paintable_paintable_init))
gtk_logo_paintable_paintable_init))
/* 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)
static void
gtk_logo_paintable_dispose (GObject *object)
{
GtkPathPaintable *self = GTK_PATH_PAINTABLE (object);
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (object);
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);
}
for (unsigned int i = 0; i < 3; i++)
gsk_path_unref (self->path[i]);
G_OBJECT_CLASS (gtk_path_paintable_parent_class)->dispose (object);
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);
}
static void
gtk_path_paintable_class_init (GtkPathPaintableClass *klass)
gtk_logo_paintable_class_init (GtkLogoPaintableClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_path_paintable_dispose;
object_class->dispose = gtk_logo_paintable_dispose;
}
static void
gtk_path_paintable_init (GtkPathPaintable *self)
gtk_logo_paintable_init (GtkLogoPaintable *self)
{
}
/* 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)
static GdkPaintable *
gtk_logo_paintable_new (void)
{
GtkPathPaintable *self;
GtkLogoPaintable *self;
graphene_rect_t bounds, bounds2;
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;
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;
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)
{
@@ -317,35 +174,14 @@ 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);
#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);
paintable = gtk_logo_paintable_new ();
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);

View File

@@ -1,346 +0,0 @@
/* 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

@@ -1,612 +0,0 @@
/* Path/Text
*
* This demo shows how to use GskPath to animate a path along another path.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#define GTK_TYPE_PATH_WIDGET (gtk_path_widget_get_type ())
G_DECLARE_FINAL_TYPE (GtkPathWidget, gtk_path_widget, GTK, PATH_WIDGET, GtkWidget)
#define POINT_SIZE 8
enum {
PROP_0,
PROP_TEXT,
PROP_EDITABLE,
N_PROPS
};
struct _GtkPathWidget
{
GtkWidget parent_instance;
char *text;
gboolean editable;
graphene_point_t points[4];
guint active_point;
float line_closest;
GskPath *line_path;
GskPathMeasure *line_measure;
GskPath *text_path;
GdkPaintable *background;
};
struct _GtkPathWidgetClass
{
GtkWidgetClass parent_class;
};
static GParamSpec *properties[N_PROPS] = { NULL, };
G_DEFINE_TYPE (GtkPathWidget, gtk_path_widget, GTK_TYPE_WIDGET)
static GskPath *
create_path_from_text (GtkWidget *widget,
const char *text,
graphene_point_t *out_offset)
{
PangoLayout *layout;
PangoFontDescription *desc;
GskPathBuilder *builder;
GskPath *result;
layout = gtk_widget_create_pango_layout (widget, text);
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);
result = gsk_path_builder_free_to_path (builder);
if (out_offset)
graphene_point_init (out_offset, 0, - pango_layout_get_baseline (layout) / (double) PANGO_SCALE);
g_object_unref (layout);
return result;
}
typedef struct
{
GskPathMeasure *measure;
GskPathBuilder *builder;
graphene_point_t offset;
double scale;
} GtkPathTransform;
static void
gtk_path_transform_point (GskPathMeasure *measure,
const graphene_point_t *pt,
const graphene_point_t *offset,
float scale,
graphene_point_t *res)
{
graphene_vec2_t tangent;
GskPathPoint point;
if (gsk_path_measure_get_point (measure, (pt->x + offset->x) * scale, &point))
{
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);
}
}
static gboolean
gtk_path_transform_op (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
GtkPathTransform *transform = data;
switch (op)
{
case GSK_PATH_MOVE:
{
graphene_point_t res;
gtk_path_transform_point (transform->measure, &pts[0], &transform->offset, transform->scale, &res);
gsk_path_builder_move_to (transform->builder, res.x, res.y);
}
break;
case GSK_PATH_LINE:
{
graphene_point_t res;
gtk_path_transform_point (transform->measure, &pts[1], &transform->offset, transform->scale, &res);
gsk_path_builder_line_to (transform->builder, res.x, res.y);
}
break;
case GSK_PATH_QUAD:
{
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_quad_to (transform->builder, res[0].x, res[0].y, res[1].x, res[1].y);
}
break;
case GSK_PATH_CUBIC:
{
graphene_point_t res[3];
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]);
gtk_path_transform_point (transform->measure, &pts[3], &transform->offset, transform->scale, &res[2]);
gsk_path_builder_cubic_to (transform->builder, res[0].x, res[0].y, res[1].x, res[1].y, res[2].x, res[2].y);
}
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;
default:
g_assert_not_reached();
return FALSE;
}
return TRUE;
}
static GskPath *
gtk_path_transform (GskPathMeasure *measure,
GskPath *path,
const graphene_point_t *offset)
{
GtkPathTransform transform = { measure, gsk_path_builder_new (), *offset };
graphene_rect_t bounds;
gsk_path_get_bounds (path, &bounds);
if (bounds.origin.x + bounds.size.width > 0)
transform.scale = gsk_path_measure_get_length (measure) / (bounds.origin.x + bounds.size.width);
else
transform.scale = 1.0f;
gsk_path_foreach (path, -1, gtk_path_transform_op, &transform);
return gsk_path_builder_free_to_path (transform.builder);
}
static void
gtk_path_widget_clear_text_path (GtkPathWidget *self)
{
g_clear_pointer (&self->text_path, gsk_path_unref);
}
static void
gtk_path_widget_clear_paths (GtkPathWidget *self)
{
gtk_path_widget_clear_text_path (self);
g_clear_pointer (&self->line_path, gsk_path_unref);
g_clear_pointer (&self->line_measure, gsk_path_measure_unref);
}
static void
gtk_path_widget_create_text_path (GtkPathWidget *self)
{
GskPath *path;
graphene_point_t offset;
gtk_path_widget_clear_text_path (self);
if (self->line_measure == NULL)
return;
path = create_path_from_text (GTK_WIDGET (self), self->text, &offset);
self->text_path = gtk_path_transform (self->line_measure, path, &offset);
gsk_path_unref (path);
}
static void
gtk_path_widget_create_paths (GtkPathWidget *self)
{
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
GskPathBuilder *builder;
gtk_path_widget_clear_paths (self);
if (width <= 0 || height <= 0)
return;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder,
self->points[0].x * width, self->points[0].y * height);
gsk_path_builder_cubic_to (builder,
self->points[1].x * width, self->points[1].y * height,
self->points[2].x * width, self->points[2].y * height,
self->points[3].x * width, self->points[3].y * height);
self->line_path = gsk_path_builder_free_to_path (builder);
self->line_measure = gsk_path_measure_new (self->line_path);
gtk_path_widget_create_text_path (self);
}
static void
gtk_path_widget_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkPathWidget *self = GTK_PATH_WIDGET (widget);
GTK_WIDGET_CLASS (gtk_path_widget_parent_class)->size_allocate (widget, width, height, baseline);
gtk_path_widget_create_paths (self);
}
static void
gtk_path_widget_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkPathWidget *self = GTK_PATH_WIDGET (widget);
double width = gtk_widget_get_width (widget);
double height = gtk_widget_get_height (widget);
GskPath *path;
GskStroke *stroke;
gsize i;
/* frosted glass the background */
gtk_snapshot_push_blur (snapshot, 100);
gdk_paintable_snapshot (self->background, snapshot, width, height);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 1, 1, 1, 0.6 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
/* draw the text */
if (self->text_path)
{
gtk_snapshot_push_fill (snapshot, self->text_path, GSK_FILL_RULE_WINDING);
gdk_paintable_snapshot (self->background, snapshot, width, height);
/* ... with an emboss effect */
stroke = gsk_stroke_new (2.0);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT(1, 1));
gtk_snapshot_push_stroke (snapshot, self->text_path, stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 0.2 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gsk_stroke_free (stroke);
gtk_snapshot_pop (snapshot);
gtk_snapshot_pop (snapshot);
}
if (self->editable && self->line_path)
{
GskPathBuilder *builder;
/* draw the control line */
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, self->line_path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
/* draw the points */
builder = gsk_path_builder_new ();
for (i = 0; i < 4; i++)
{
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (self->points[i].x * width, self->points[i].y * height), POINT_SIZE);
}
path = gsk_path_builder_free_to_path (builder);
gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 1, 1, 1, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
stroke = gsk_stroke_new (1.0);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
}
if (self->line_closest >= 0)
{
GskPathBuilder *builder;
GskPathPoint point;
graphene_point_t closest;
builder = gsk_path_builder_new ();
if (gsk_path_measure_get_point (self->line_measure, self->line_closest, &point))
{
gsk_path_point_get_position (&point, &closest);
gsk_path_builder_add_circle (builder, &closest, POINT_SIZE);
path = gsk_path_builder_free_to_path (builder);
gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 1, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gsk_path_unref (path);
}
}
}
static void
gtk_path_widget_set_text (GtkPathWidget *self,
const char *text)
{
if (g_strcmp0 (self->text, text) == 0)
return;
g_free (self->text);
self->text = g_strdup (text);
gtk_path_widget_create_paths (self);
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TEXT]);
}
static void
gtk_path_widget_set_editable (GtkPathWidget *self,
gboolean editable)
{
if (self->editable == editable)
return;
self->editable = editable;
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EDITABLE]);
}
static void
gtk_path_widget_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
switch (prop_id)
{
case PROP_TEXT:
gtk_path_widget_set_text (self, g_value_get_string (value));
break;
case PROP_EDITABLE:
gtk_path_widget_set_editable (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_widget_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
switch (prop_id)
{
case PROP_TEXT:
g_value_set_string (value, self->text);
break;
case PROP_EDITABLE:
g_value_set_boolean (value, self->editable);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_widget_dispose (GObject *object)
{
GtkPathWidget *self = GTK_PATH_WIDGET (object);
gtk_path_widget_clear_paths (self);
G_OBJECT_CLASS (gtk_path_widget_parent_class)->dispose (object);
}
static void
gtk_path_widget_class_init (GtkPathWidgetClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_path_widget_dispose;
object_class->set_property = gtk_path_widget_set_property;
object_class->get_property = gtk_path_widget_get_property;
widget_class->size_allocate = gtk_path_widget_allocate;
widget_class->snapshot = gtk_path_widget_snapshot;
properties[PROP_TEXT] =
g_param_spec_string ("text",
"text",
"Text transformed along a path",
NULL,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
properties[PROP_EDITABLE] =
g_param_spec_boolean ("editable",
"editable",
"If the path can be edited by the user",
FALSE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
drag_begin (GtkGestureDrag *gesture,
double x,
double y,
GtkPathWidget *self)
{
graphene_point_t mouse = GRAPHENE_POINT_INIT (x, y);
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
gsize i;
for (i = 0; i < 4; i++)
{
if (graphene_point_distance (&GRAPHENE_POINT_INIT (self->points[i].x * width, self->points[i].y * height), &mouse, NULL, NULL) <= POINT_SIZE)
{
self->active_point = i;
break;
}
}
if (i == 4)
{
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
return;
}
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
drag_update (GtkGestureDrag *drag,
double offset_x,
double offset_y,
GtkPathWidget *self)
{
double width = gtk_widget_get_width (GTK_WIDGET (self));
double height = gtk_widget_get_height (GTK_WIDGET (self));
double start_x, start_y;
gtk_gesture_drag_get_start_point (drag, &start_x, &start_y);
self->points[self->active_point] = GRAPHENE_POINT_INIT ((start_x + offset_x) / width,
(start_y + offset_y) / height);
self->points[self->active_point].x = CLAMP (self->points[self->active_point].x, 0, 1);
self->points[self->active_point].y = CLAMP (self->points[self->active_point].y, 0, 1);
gtk_path_widget_create_paths (self);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
pointer_motion (GtkEventControllerMotion *controller,
double x,
double y,
GtkPathWidget *self)
{
GskPathPoint point;
graphene_point_t pos;
if (gsk_path_get_closest_point (gsk_path_measure_get_path (self->line_measure),
&GRAPHENE_POINT_INIT (x, y),
INFINITY,
&point))
{
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));
}
}
static void
pointer_leave (GtkEventControllerMotion *controller,
GtkPathWidget *self)
{
self->line_closest = -1;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
gtk_path_widget_init (GtkPathWidget *self)
{
GtkEventController *controller;
controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
g_signal_connect (controller, "drag-begin", G_CALLBACK (drag_begin), self);
g_signal_connect (controller, "drag-update", G_CALLBACK (drag_update), self);
g_signal_connect (controller, "drag-end", G_CALLBACK (drag_update), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
controller = GTK_EVENT_CONTROLLER (gtk_event_controller_motion_new ());
g_signal_connect (controller, "enter", G_CALLBACK (pointer_motion), self);
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->line_closest = -1;
self->points[0] = GRAPHENE_POINT_INIT (0.1, 0.9);
self->points[1] = GRAPHENE_POINT_INIT (0.3, 0.1);
self->points[2] = GRAPHENE_POINT_INIT (0.7, 0.1);
self->points[3] = GRAPHENE_POINT_INIT (0.9, 0.9);
self->background = GDK_PAINTABLE (gdk_texture_new_from_resource ("/sliding_puzzle/portland-rose.jpg"));
gtk_path_widget_set_text (self, "It's almost working");
}
GtkWidget *
gtk_path_widget_new (void)
{
GtkPathWidget *self;
self = g_object_new (GTK_TYPE_PATH_WIDGET, NULL);
return GTK_WIDGET (self);
}
GtkWidget *
do_path_text (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkBuilder *builder;
g_type_ensure (GTK_TYPE_PATH_WIDGET);
builder = gtk_builder_new_from_resource ("/path_text/path_text.ui");
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
g_object_unref (builder);
}
if (!gtk_widget_get_visible (window))
gtk_window_present (GTK_WINDOW (window));
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkWindow" id="window">
<property name="title" translatable="yes">Text along a Path</property>
<child type="titlebar">
<object class="GtkHeaderBar">
<child type="end">
<object class="GtkToggleButton" id="edit-toggle">
<property name="icon-name">document-edit-symbolic</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkRevealer">
<property name="reveal-child" bind-source="edit-toggle" bind-property="active" bind-flags="sync-create"></property>
<child>
<object class="GtkEntry" id="text">
<property name="text">Through the looking glass</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkPathWidget" id="view">
<property name="editable" bind-source="edit-toggle" bind-property="active" bind-flags="sync-create"></property>
<property name="text" bind-source="text" bind-property="text" bind-flags="sync-create"></property>
<property name="hexpand">true</property>
<property name="vexpand">true</property>
</object>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -34,5 +34,11 @@ base_url = "https://gitlab.gnome.org/GNOME/gtk/-/blob/main/"
[extra]
content_images = [
"gtk-logo.svg",
"images/caps-dark.png",
"images/caps-light.png",
"images/join-dark.png",
"images/join-light.png",
"images/path-dark.png",
"images/path-light.png",
]
urlmap_file = "urlmap.js"

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg1"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
sodipodi:docname="caps.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="2.1493149"
inkscape:cx="438.74445"
inkscape:cy="288.69664"
inkscape:window-width="1920"
inkscape:window-height="1123"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1">
<linearGradient
id="swatch1"
inkscape:swatch="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop1" />
</linearGradient>
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;stroke:#000064;stroke-width:1;stroke-miterlimit:0;stroke-dasharray:none"
d="m 73.096455,77.084329 c 0,0 6.439436,18.711677 18.172676,26.635721"
id="path1"
sodipodi:nodetypes="cc" />
<path
style="color:#000000;fill:none;stroke:#000064;stroke-width:0.2;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 94.214979,99.722163 c -4.705477,-3.177832 -9.013472,-9.233699 -11.888672,-14.531169 -2.8752,-5.29748 -4.353516,-9.587833 -4.353516,-9.587833 L 68.51576,78.85704 c 0,0 1.743663,5.064163 5.021484,11.103457 3.277822,6.039297 8.05428,13.302573 15.082031,18.048773"
id="path1-1"
sodipodi:nodetypes="csccsc" />
<path
style="fill:none;stroke:#000064;stroke-width:1;stroke-miterlimit:0;stroke-dasharray:none"
d="m 112.14213,75.736452 c 0,0 6.43944,18.711675 18.17268,26.635728"
id="path1-4"
sodipodi:nodetypes="cc" />
<path
style="color:#000000;fill:none;stroke:#000064;stroke-width:0.2;stroke-linecap:round;stroke-miterlimit:0;stroke-dasharray:none;stroke-opacity:1"
d="m 132.83284,98.590758 c -4.70551,-3.177807 -9.01347,-9.233742 -11.88867,-14.53125 -2.8752,-5.297508 -4.35351,-9.587891 -4.35351,-9.587891 -0.89828,-2.611719 -3.74396,-4.000453 -6.35547,-3.101562 -2.61172,0.898277 -4.00046,3.743958 -3.10157,6.355468 0,0 1.74367,5.064185 5.02149,11.103516 3.27782,6.039331 8.05428,13.302641 15.08203,18.048851"
id="path1-4-5"
sodipodi:nodetypes="cscccsc" />
<path
style="fill:none;stroke:#000064;stroke-width:1;stroke-miterlimit:0;stroke-dasharray:none"
d="m 154.10911,73.84561 c 0,0 6.43943,18.711677 18.17267,26.63572"
id="path1-0"
sodipodi:nodetypes="cc" />
<path
style="color:#000000;fill:none;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="M 175.16683,96.47344 C 170.46135,93.295592 166.1514,87.239699 163.2762,81.94219 160.401,76.644682 158.92269,72.3543 158.92269,72.3543 l -1.62696,-4.728516 -9.45508,3.253906 1.62696,4.728516 c 0,0 1.74171,5.066138 5.01953,11.105469 3.27782,6.039331 8.05428,13.300675 15.08203,18.046895"
id="path1-4-1"
sodipodi:nodetypes="csccccsc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg1"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
sodipodi:docname="join.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.75989759"
inkscape:cx="118.43701"
inkscape:cy="570.47161"
inkscape:window-width="1920"
inkscape:window-height="1123"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;stroke:#000064;stroke-width:1;stroke-linecap:square;stroke-miterlimit:10;stroke-dasharray:none"
d="m 12.748727,122.15935 c 4.449842,-15.44496 4.175632,-15.92044 16.908788,-26.568585 0,0 8.478091,16.421195 35.150804,33.660325"
id="path1"
sodipodi:nodetypes="ccc" />
<path
style="color:#000000;fill:none;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 17.59375,123.72266 c 3.810222,-13.2249 3.591775,-14.13123 11.107422,-20.53516 1.289688,1.82186 2.604788,3.71164 4.800781,6.23242 5.743296,6.59272 14.891452,15.32766 28.632813,24.20899 M 67.5625,125.23047 C 54.631174,116.87268 46.204933,108.77726 41.041016,102.84961 35.877098,96.921954 34.140625,93.474609 34.140625,93.474609 l -2.861328,-5.544922 -4.789063,4.003907 C 13.673794,102.6514 12.411529,105.58692 7.984375,120.95313"
id="path1-3"
sodipodi:nodetypes="ccsccscccc" />
<path
style="fill:none;stroke:#000064;stroke-width:1;stroke-linecap:square;stroke-miterlimit:10;stroke-dasharray:none"
d="m 147.99215,122.60257 c 4.44988,-15.44499 4.17566,-15.92048 16.90889,-26.568657 0,0 8.47814,16.421267 35.15101,33.660397"
id="path1-5"
sodipodi:nodetypes="ccc" />
<path
style="color:#000000;fill:none;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 152.79687,123.98633 c 3.81053,-13.22586 3.59005,-14.12993 11.10743,-20.53516 1.28968,1.82184 2.6029,3.71173 4.79882,6.23242 5.74333,6.59274 14.89333,15.32961 28.63477,24.21094 m 5.42773,-8.40039 c -12.9314,-8.35778 -21.35753,-16.45123 -26.52148,-22.37891 -5.16395,-5.927668 -6.90039,-9.374996 -6.90039,-9.374996 l -7.65039,-1.541015 C 148.87685,102.91706 147.61469,105.85252 143.1875,121.21875"
id="path1-5-8"
sodipodi:nodetypes="ccsccsccc" />
<path
style="fill:none;stroke:#000064;stroke-width:1;stroke-linecap:square;stroke-miterlimit:10;stroke-dasharray:none"
d="m 79.729342,122.0734 c 4.449844,-15.44499 4.175634,-15.92048 16.908794,-26.568654 0,0 8.478114,16.421264 35.150984,33.660394"
id="path1-7"
sodipodi:nodetypes="ccc" />
<path
style="color:#000000;fill:none;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 84.533203,123.19336 c 3.810222,-13.22495 3.591776,-14.1312 11.107422,-20.53516 1.289696,1.82187 2.604775,3.71164 4.800785,6.23243 5.74333,6.59275 14.89136,15.32765 28.63281,24.20898 m 5.42969,-8.39844 c -12.93141,-8.35778 -21.35949,-16.45122 -26.52344,-22.3789 -5.16395,-5.927686 -6.90039,-9.375004 -6.90039,-9.375004 -0.69636,-1.348401 -1.967393,-2.307601 -3.45508,-2.607422 -1.487509,-0.300132 -3.03083,0.09145 -4.195313,1.064453 C 80.61325,102.12215 79.350982,105.05756 74.923828,120.42383"
id="path1-7-4"
sodipodi:nodetypes="ccsccscccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg1"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
sodipodi:docname="path.svg"
inkscape:export-filename="path-dark.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.75989759"
inkscape:cx="397.42198"
inkscape:cy="561.25984"
inkscape:window-width="1920"
inkscape:window-height="1123"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="M 128.28233,149.47912 154.23127,90.244656 86.340809,68.820468"
id="path3" />
<path
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;fill-opacity:1;stroke-dasharray:none;stroke-opacity:1"
d="m 33.956804,113.31099 c -9.049189,-11.90893 5.40551,-40.570358 20.351208,-39.990271 26.687501,1.035822 4.06495,71.984581 30.695656,74.009711 19.642072,1.49368 41.962402,-34.42048 30.634382,-50.536241 C 99.663884,74.068616 50.763019,135.42833 33.956804,113.31099 Z"
id="path1"
sodipodi:nodetypes="sssss" />
<circle
style="fill:#ff0404;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path2"
cx="34.517788"
cy="113.66589"
r="1.5" />
<circle
style="fill:#ff0404;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
id="path2-8"
cx="55.045853"
cy="73.566689"
r="1.5" />
<circle
style="fill:#ff0404;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
id="path2-8-5"
cx="55.045834"
cy="73.831245"
r="1.5" />
<circle
style="fill:#ff0404;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path2-8-9"
cx="55.045834"
cy="73.831245"
r="1.5" />
<circle
style="fill:#ff0404;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path2-8-9-7"
cx="86.398613"
cy="68.897667"
r="1.5" />
<circle
style="fill:#ff0404;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path2-8-9-7-8"
cx="153.87553"
cy="90.112595"
r="1.5" />
<circle
style="fill:#ff0404;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
id="path2-8-7"
cx="84.803238"
cy="147.51736"
r="1.5" />
<circle
style="fill:#ff0404;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path2-8-7-2"
cx="115.28235"
cy="96.705284"
r="1.5" />
<circle
style="fill:#ff0404;fill-opacity:1;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path2-8-9-7-9"
cx="127.92362"
cy="150.03427"
r="1.5" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -1,121 +0,0 @@
.. _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,7 +77,6 @@ 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,9 +27,7 @@
#include "gdkmacosdisplay.h"
#include "gdkmacossurface.h"
#import <OpenGL/OpenGL.h>
#import <OpenGL/gl3.h>
#import <AppKit/AppKit.h>
#import <epoxy/gl.h>
G_BEGIN_DECLS

View File

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

View File

@@ -2857,7 +2857,10 @@ gdk_event_translate (MSG *msg,
break;
case WM_SYSCOMMAND:
switch (msg->wParam)
/* From: https://learn.microsoft.com/en-us/windows/win32/menurc/wm-syscommand?redirectedfrom=MSDN
* To obtain the correct result when testing the value of wParam,
* an application must combine the value 0xFFF0 with the wParam value by using the bitwise AND operator. */
switch (msg->wParam & 0xFFF0)
{
case SC_MINIMIZE:
case SC_RESTORE:
@@ -2964,6 +2967,14 @@ gdk_event_translate (MSG *msg,
windowpos = (WINDOWPOS *) msg->lParam;
windowpos->cx = our_mmi.ptMaxSize.x;
windowpos->cy = our_mmi.ptMaxSize.y;
if (!_gdk_win32_surface_lacks_wm_decorations (window) &&
!(windowpos->flags & SWP_NOCLIENTSIZE) &&
window->width == impl->next_layout.configured_width &&
window->height == impl->next_layout.configured_height)
{
impl->inhibit_configure = TRUE;
}
}
impl->maximizing = FALSE;

View File

@@ -1230,18 +1230,11 @@ get_effective_window_decorations (GdkSurface *window,
*decoration |= GDK_DECOR_MINIMIZE;
return TRUE;
}
else if (impl->hint_flags & GDK_HINT_MAX_SIZE)
{
*decoration = GDK_DECOR_ALL | GDK_DECOR_MAXIMIZE;
*decoration |= GDK_DECOR_MINIMIZE;
return TRUE;
}
else
{
*decoration = (GDK_DECOR_ALL | GDK_DECOR_MINIMIZE | GDK_DECOR_MAXIMIZE);
*decoration = GDK_DECOR_ALL;
return TRUE;
}

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 (NULL,
&GRAPHENE_POINT_INIT (scale_x < 0 ? - surface_width : 0,
scale_y < 0 ? - surface_height : 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));
gsk_gl_render_job_push_modelview (job, transform);
gsk_transform_unref (transform);
}

View File

@@ -22,7 +22,6 @@
#include <gsk/gskenums.h>
#include <gsk/gskpath.h>
#include <gsk/gskpathbuilder.h>
#include <gsk/gskpathmeasure.h>
#include <gsk/gskpathpoint.h>
#include <gsk/gskrenderer.h>
#include <gsk/gskrendernode.h>

View File

@@ -97,4 +97,19 @@ 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,15 +23,10 @@
#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,
@@ -49,10 +44,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,
graphene_rect_t *bounds);
GskBoundingBox *bounds);
gboolean gsk_contour_get_stroke_bounds (const GskContour *self,
const GskStroke *stroke,
graphene_rect_t *bounds);
GskBoundingBox *bounds);
gboolean gsk_contour_foreach (const GskContour *self,
float tolerance,
GskPathForeachFunc func,
@@ -62,17 +57,15 @@ void gsk_contour_get_start_end (const GskContou
graphene_point_t *end);
int gsk_contour_get_winding (const GskContour *self,
const graphene_point_t *point);
gsize gsk_contour_get_n_points (const GskContour *self);
gboolean gsk_contour_get_closest_point (const GskContour *self,
const graphene_point_t *point,
float threshold,
GskRealPathPoint *result,
float *out_dist);
void gsk_contour_get_position (const GskContour *self,
GskRealPathPoint *point,
graphene_point_t *pos);
void gsk_contour_get_tangent (const GskContour *self,
GskRealPathPoint *point,
GskPathDirection direction,
@@ -80,28 +73,11 @@ void gsk_contour_get_tangent (const GskContou
float gsk_contour_get_curvature (const GskContour *self,
GskRealPathPoint *point,
graphene_point_t *center);
gpointer gsk_contour_init_measure (const GskContour *self,
float tolerance,
float *out_length);
void gsk_contour_free_measure (const GskContour *self,
gpointer data);
void gsk_contour_add_segment (const GskContour *self,
GskPathBuilder *builder,
gpointer measure_data,
gboolean emit_move_to,
float start,
float end);
void gsk_contour_get_point (const GskContour *self,
gpointer measure_data,
float offset,
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);
GskRealPathPoint *start,
GskRealPathPoint *end);
G_END_DECLS

View File

@@ -35,8 +35,7 @@ struct _GskCurveClass
void (* init_foreach) (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight);
gsize n_pts);
void (* print) (const GskCurve *curve,
GString *string);
gskpathop (* pathop) (const GskCurve *curve);
@@ -157,8 +156,7 @@ static void
gsk_line_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
gsize n_pts)
{
GskLineCurve *self = &curve->line;
@@ -300,7 +298,7 @@ gsk_line_curve_decompose_curve (const GskCurve *curve,
{
const GskLineCurve *self = &curve->line;
return add_curve_func (GSK_PATH_LINE, self->points, 2, 0.f, user_data);
return add_curve_func (GSK_PATH_LINE, self->points, 2, user_data);
}
static void
@@ -377,8 +375,7 @@ static void
gsk_quad_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
gsize n_pts)
{
GskQuadCurve *self = &curve->quad;
@@ -591,7 +588,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, 0.f, data->user_data);
return data->add_curve (GSK_PATH_LINE, p, 2, data->user_data);
}
static gboolean
@@ -604,20 +601,13 @@ 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, 0.f, user_data);
return add_curve_func (GSK_PATH_QUAD, self->points, 3, 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, 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);
return add_curve_func (GSK_PATH_CUBIC, c.cubic.points, 4, user_data);
}
else
{
@@ -746,8 +736,7 @@ static void
gsk_cubic_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
gsize n_pts)
{
GskCubicCurve *self = &curve->cubic;
@@ -920,7 +909,6 @@ gsk_curve_get_derivative (const GskCurve *curve,
case GSK_PATH_MOVE:
case GSK_PATH_CLOSE:
case GSK_PATH_CONIC:
default:
g_assert_not_reached ();
}
@@ -1068,7 +1056,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, 0.f, user_data);
return add_curve_func (GSK_PATH_CUBIC, self->points, 4, user_data);
/* FIXME: Quadratic (or conic?) approximation */
return gsk_cubic_curve_decompose (curve,
@@ -1183,665 +1171,6 @@ 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 */
@@ -1853,7 +1182,6 @@ 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);
@@ -1873,11 +1201,10 @@ void
gsk_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
gsize n_pts)
{
memset (curve, 0, sizeof (GskCurve));
get_class (op)->init_foreach (curve, op, pts, n_pts, weight);
get_class (op)->init_foreach (curve, op, pts, n_pts);
}
void

View File

@@ -33,7 +33,6 @@ typedef union _GskCurve GskCurve;
typedef struct _GskLineCurve GskLineCurve;
typedef struct _GskQuadCurve GskQuadCurve;
typedef struct _GskCubicCurve GskCubicCurve;
typedef struct _GskConicCurve GskConicCurve;
struct _GskLineCurve
{
@@ -66,28 +65,12 @@ 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 {
@@ -105,7 +88,6 @@ 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,
@@ -113,8 +95,7 @@ void gsk_curve_init (GskCurve
void gsk_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight);
gsize n_pts);
void gsk_curve_print (const GskCurve *curve,
GString *string);

View File

@@ -1,5 +1,5 @@
/* GSK - The GTK Scene Kit
* Copyright 2016 Endless
* Copyright 2016 Endless
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -42,8 +42,6 @@
* @GSK_REPEAT_NODE: A node that repeats the child's contents
* @GSK_CLIP_NODE: A node that clips its child to a rectangular area
* @GSK_ROUNDED_CLIP_NODE: A node that clips its child to a rounded rectangle
* @GSK_FILL_NODE: A node that fills a path
* @GSK_STROKE_NODE: A node that strokes a path
* @GSK_SHADOW_NODE: A node that draws a shadow below its child
* @GSK_BLEND_NODE: A node that blends two children together
* @GSK_CROSS_FADE_NODE: A node that cross-fades between two children
@@ -53,7 +51,9 @@
* @GSK_GL_SHADER_NODE: A node that uses OpenGL fragment shaders to render
* @GSK_TEXTURE_SCALE_NODE: A node drawing a `GdkTexture` scaled and filtered (Since: 4.10)
* @GSK_MASK_NODE: A node that masks one child with another (Since: 4.10)
* @GSK_FILL_NODE: A node that fills a path
* @GSK_STROKE_NODE: A node that strokes a path
* The type of a node determines what the node is rendering.
*/
typedef enum {
@@ -76,8 +76,6 @@ typedef enum {
GSK_REPEAT_NODE,
GSK_CLIP_NODE,
GSK_ROUNDED_CLIP_NODE,
GSK_FILL_NODE,
GSK_STROKE_NODE,
GSK_SHADOW_NODE,
GSK_BLEND_NODE,
GSK_CROSS_FADE_NODE,
@@ -86,7 +84,9 @@ typedef enum {
GSK_DEBUG_NODE,
GSK_GL_SHADER_NODE,
GSK_TEXTURE_SCALE_NODE,
GSK_MASK_NODE
GSK_MASK_NODE,
GSK_FILL_NODE,
GSK_STROKE_NODE
} GskRenderNodeType;
/**
@@ -220,6 +220,11 @@ typedef enum {
*
* The default line cap style is `GSK_LINE_CAP_BUTT`.
*
* <picture>
* <source srcset="caps-dark.png" media="(prefers-color-scheme: dark)">
* <img alt="Line Cap Styles" src="caps-light.png">
* </picture>
*
* New entries may be added in future versions.
*
* Since: 4.14
@@ -233,7 +238,6 @@ 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
@@ -241,18 +245,19 @@ 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`.
*
* <picture>
* <source srcset="join-dark.png" media="(prefers-color-scheme: dark)">
* <img alt="Line Join Styles" src="join-light.png">
* </picture>
*
* New entries may be added in future versions.
*
* Since: 4.14
*/
typedef enum {
GSK_LINE_JOIN_MITER,
GSK_LINE_JOIN_MITER_CLIP,
GSK_LINE_JOIN_ROUND,
GSK_LINE_JOIN_BEVEL,
} GskLineJoin;
@@ -270,9 +275,6 @@ 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`.
*
@@ -286,7 +288,6 @@ typedef enum {
GSK_PATH_LINE,
GSK_PATH_QUAD,
GSK_PATH_CUBIC,
GSK_PATH_CONIC,
} GskPathOperation;
/**

View File

@@ -24,23 +24,32 @@
#include "gskcurveprivate.h"
#include "gskpathbuilder.h"
#include "gskpathpointprivate.h"
#include "gsksplineprivate.h"
/**
* GskPath:
*
* A `GskPath` describes lines and curves that are more complex
* than simple rectangles. Paths can used for rendering (filling or
* stroking) and for animations (e.g. as trajectories).
* than simple rectangles.
*
* Paths can used for rendering (filling or stroking) and for animations
* (e.g. as trajectories).
*
* `GskPath` is an immutable, opaque, reference-counted struct.
* After creation, you cannot change the types it represents.
* Instead, new `GskPath` objects have to be created.
*
* The [struct@Gsk.PathBuilder] structure is meant to help in this endeavor.
* After creation, you cannot change the types it represents. Instead,
* new `GskPath` objects have to be created. The [struct@Gsk.PathBuilder]
* structure is meant to help in this endeavor.
*
* Conceptually, a path consists of zero or more contours (continous, connected
* curves), each of which may or may not be closed. Contours are typically
* constructed from Bézier segments.
*
* <picture>
* <source srcset="path-dark.png" media="(prefers-color-scheme: dark)">
* <img alt="A Path" src="path-light.png">
* </picture>
*
* Since: 4.14
*/
struct _GskPath
@@ -145,14 +154,17 @@ gsk_path_unref (GskPath *self)
}
const GskContour *
gsk_path_get_contour (GskPath *path,
gsize i)
gsk_path_get_contour (const GskPath *self,
gsize i)
{
return path->contours[i];
if (i < self->n_contours)
return self->contours[i];
else
return NULL;
}
GskPathFlags
gsk_path_get_flags (GskPath *self)
gsk_path_get_flags (const GskPath *self)
{
return self->flags;
}
@@ -166,10 +178,7 @@ gsk_path_get_flags (GskPath *self)
* for printing.
*
* The string is compatible with
* [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.
* [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData).
*
* Since: 4.14
*/
@@ -224,7 +233,6 @@ 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,7 +264,6 @@ 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;
@@ -304,9 +311,9 @@ gsk_path_to_cairo (GskPath *self,
* Returns: the number of contours in @path
*/
gsize
gsk_path_get_n_contours (GskPath *path)
gsk_path_get_n_contours (const GskPath *self)
{
return path->n_contours;
return self->n_contours;
}
/**
@@ -379,35 +386,33 @@ gboolean
gsk_path_get_bounds (GskPath *self,
graphene_rect_t *bounds)
{
gsize i;
GskBoundingBox b;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (bounds != NULL, FALSE);
for (i = 0; i < self->n_contours; i++)
{
if (gsk_contour_get_bounds (self->contours[i], bounds))
break;
}
if (i >= self->n_contours)
if (self->n_contours == 0)
{
graphene_rect_init_from_rect (bounds, graphene_rect_zero ());
return FALSE;
}
for (i++; i < self->n_contours; i++)
{
graphene_rect_t tmp;
gsk_contour_get_bounds (self->contours[0], &b);
if (gsk_contour_get_bounds (self->contours[i], &tmp))
graphene_rect_union (bounds, &tmp, bounds);
for (gsize i = 1; i < self->n_contours; i++)
{
GskBoundingBox tmp;
gsk_contour_get_bounds (self->contours[i], &tmp);
gsk_bounding_box_union (&b, &tmp, &b);
}
gsk_bounding_box_to_rect (&b, bounds);
return TRUE;
}
/*< private >
/**
* gsk_path_get_stroke_bounds:
* @self: a #GtkPath
* @stroke: stroke parameters
@@ -422,38 +427,37 @@ 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.
* Since: 4.14
*/
gboolean
gsk_path_get_stroke_bounds (GskPath *self,
const GskStroke *stroke,
graphene_rect_t *bounds)
{
gsize i;
GskBoundingBox b;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (bounds != NULL, FALSE);
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)
if (self->n_contours == 0)
{
graphene_rect_init_from_rect (bounds, graphene_rect_zero ());
return FALSE;
}
for (i++; i < self->n_contours; i++)
gsk_contour_get_stroke_bounds (self->contours[0], stroke, &b);
for (gsize i = 1; i < self->n_contours; i++)
{
graphene_rect_t tmp;
GskBoundingBox tmp;
if (gsk_contour_get_stroke_bounds (self->contours[i], stroke, &tmp))
graphene_rect_union (bounds, &tmp, bounds);
gsk_bounding_box_union (&b, &tmp, &b);
}
gsk_bounding_box_to_rect (&b, bounds);
return TRUE;
}
@@ -495,6 +499,72 @@ gsk_path_in_fill (GskPath *self,
}
}
/**
* 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;
res->contour = 0;
res->idx = 1;
res->t = 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;
res->contour = self->n_contours - 1;
res->idx = gsk_contour_get_n_points (self->contours[self->n_contours - 1]) - 1;
res->t = 1;
return TRUE;
}
/**
* gsk_path_get_closest_point:
* @self: a `GskPath`
@@ -520,27 +590,28 @@ 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))
{
res->contour = self->contours[i];
found = TRUE;
res->contour = i;
threshold = distance;
}
}
if (res->contour != NULL)
return TRUE;
return FALSE;
return found;
}
/**
@@ -600,7 +671,6 @@ 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);
}
@@ -608,19 +678,17 @@ 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, weight, trampoline->user_data);
return trampoline->func (op, pts, n_pts, 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;
@@ -630,14 +698,28 @@ 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, weight, trampoline->user_data);
return trampoline->func (op, pts, n_pts, trampoline->user_data);
case GSK_PATH_QUAD:
{
GskCurve curve;
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_QUAD)
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
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);
}
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_QUAD, pts));
return gsk_curve_decompose (&curve,
@@ -651,10 +733,10 @@ gsk_path_foreach_trampoline (GskPathOperation op,
GskCurve curve;
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
return trampoline->func (op, pts, n_pts, trampoline->user_data);
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_CUBIC, pts));
if (trampoline->flags & (GSK_PATH_FOREACH_ALLOW_QUAD|GSK_PATH_FOREACH_ALLOW_CONIC))
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_QUAD)
return gsk_curve_decompose_curve (&curve,
trampoline->flags,
trampoline->tolerance,
@@ -667,28 +749,6 @@ 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;
@@ -696,8 +756,7 @@ gsk_path_foreach_trampoline (GskPathOperation op,
}
#define ALLOW_ANY (GSK_PATH_FOREACH_ALLOW_QUAD| \
GSK_PATH_FOREACH_ALLOW_CUBIC| \
GSK_PATH_FOREACH_ALLOW_CONIC)
GSK_PATH_FOREACH_ALLOW_CUBIC)
gboolean
gsk_path_foreach_with_tolerance (GskPath *self,
@@ -954,6 +1013,22 @@ parse_circle (const char **p,
* [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData),
* as e.g. produced by [method@Gsk.Path.to_string].
*
* A high-level summary of the syntax:
*
* - `M x y` Move to `(x, y)`
* - `L x y` Add a line from the current point to `(x, y)`
* - `Q x1 y1 x2 y2` Add a quadratic Bézier from the current point to `(x2, y2)`, with control point `(x1, y1)`
* - `C x1 y1 x2 y2 x3 y3` Add a cubic Bézier from the current point to `(x3, y3)`, with control points `(x1, y1)` and `(x2, y2)`
* - `Z` Close the contour by drawing a line back to the start point
* - `H x` Add a horizontal line from the current point to the given x value
* - `V y` Add a vertical line from the current point to the given y value
* - `T x2 y2` Add a quadratic Bézier, using the reflection of the previous segments' control point as control point
* - `S x2 y2 x3 y3` Add a cubic Bézier, using the reflection of the previous segments' second control point as first control point
* - `A rx ry r l s x y` Add an elliptical arc from the current point to `(x, y)` with radii rx and ry. See the SVG documentation for how the other parameters influence the arc.
*
* All the commands have lowercase variants that interpret coordinates
* relative to the current point.
*
* Returns: (nullable): a new `GskPath`, or `NULL`
* if @string could not be parsed
*
@@ -1281,37 +1356,6 @@ 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':
{

View File

@@ -30,9 +30,10 @@ 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_CONIC: Allow emission of `GSK_PATH_CONIC` operations.
* @GSK_PATH_FOREACH_ALLOW_ANY: Allow emission of any kind of operation.
*
* Flags that can be passed to gsk_path_foreach() to enable additional
* features.
@@ -40,12 +41,14 @@ G_BEGIN_DECLS
* By default, [method@Gsk.Path.foreach] will only emit a path with all operations
* flattened to straight lines to allow for maximum compatibility. The only
* operations emitted will be `GSK_PATH_MOVE`, `GSK_PATH_LINE` and `GSK_PATH_CLOSE`.
*
* Since: 4.14
*/
typedef enum
{
GSK_PATH_FOREACH_ALLOW_QUAD = (1 << 0),
GSK_PATH_FOREACH_ALLOW_CUBIC = (1 << 1),
GSK_PATH_FOREACH_ALLOW_CONIC = (1 << 2),
GSK_PATH_FOREACH_ALLOW_ONLY_LINES = 0,
GSK_PATH_FOREACH_ALLOW_QUAD = (1 << 0),
GSK_PATH_FOREACH_ALLOW_CUBIC = (1 << 1),
} GskPathForeachFlags;
/**
@@ -53,19 +56,17 @@ 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 ())
@@ -101,11 +102,23 @@ 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,
GskFillRule fill_rule);
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_get_closest_point (GskPath *self,
const graphene_point_t *point,
@@ -118,13 +131,6 @@ gboolean gsk_path_foreach (GskPath
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,6 +25,7 @@
#include "gskpathprivate.h"
#include "gskcontourprivate.h"
#include "gsksplineprivate.h"
/**
* GskPathBuilder:
@@ -53,6 +54,9 @@
* either common shapes like [method@Gsk.PathBuilder.add_circle]
* or by adding from other paths like [method@Gsk.PathBuilder.add_path].
*
* The `gsk_path_builder_add_*` methods always add complete contours,
* and do not use or modify the current point.
*
* The other option is to define each line and curve manually with
* the `gsk_path_builder_*_to` group of functions. You start with
* a call to [method@Gsk.PathBuilder.move_to] to set the starting point
@@ -62,6 +66,8 @@
* back with a line to the starting point.
*
* This is similar for how paths are drawn in Cairo.
*
* Since: 4.14
*/
struct _GskPathBuilder
@@ -309,6 +315,8 @@ 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
*
@@ -385,9 +393,13 @@ void
gsk_path_builder_add_cairo_path (GskPathBuilder *self,
const cairo_path_t *path)
{
graphene_point_t current;
g_return_if_fail (self != NULL);
g_return_if_fail (path != NULL);
current = self->current_point;
for (gsize i = 0; i < path->num_data; i += path->data[i].header.length)
{
const cairo_path_data_t *data = &path->data[i];
@@ -418,6 +430,9 @@ gsk_path_builder_add_cairo_path (GskPathBuilder *self,
break;
}
}
gsk_path_builder_end_current (self);
self->current_point = current;
}
/**
@@ -439,14 +454,20 @@ void
gsk_path_builder_add_rect (GskPathBuilder *self,
const graphene_rect_t *rect)
{
GskContour *contour;
graphene_point_t current;
g_return_if_fail (self != NULL);
contour = gsk_rect_contour_new (rect);
gsk_path_builder_add_contour (self, contour);
current = self->current_point;
gsk_contour_get_start_end (contour, NULL, &self->current_point);
gsk_path_builder_move_to (self, rect->origin.x, rect->origin.y);
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);
self->current_point = current;
}
/**
@@ -455,20 +476,82 @@ gsk_path_builder_add_rect (GskPathBuilder *self,
* @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)
{
GskContour *contour;
graphene_point_t current;
g_return_if_fail (self != NULL);
g_return_if_fail (rect != NULL);
contour = gsk_rounded_rect_contour_new (rect);
gsk_path_builder_add_contour (self, contour);
current = self->current_point;
gsk_path_builder_move_to (self,
rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width,
rect->bounds.origin.y);
/* top */
gsk_path_builder_line_to (self,
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_TOP_RIGHT].width,
rect->bounds.origin.y);
/* topright corner */
gsk_path_builder_svg_arc_to (self,
rect->corner[GSK_CORNER_TOP_RIGHT].width,
rect->corner[GSK_CORNER_TOP_RIGHT].height,
0, FALSE, TRUE,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_RIGHT].height);
/* right */
gsk_path_builder_line_to (self,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_RIGHT].height);
/* bottomright corner */
gsk_path_builder_svg_arc_to (self,
rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
rect->corner[GSK_CORNER_BOTTOM_RIGHT].height,
0, FALSE, TRUE,
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
rect->bounds.origin.y + rect->bounds.size.height);
/* bottom */
gsk_path_builder_line_to (self,
rect->bounds.origin.x + rect->corner[GSK_CORNER_BOTTOM_LEFT].width,
rect->bounds.origin.y + rect->bounds.size.height);
/* bottomleft corner */
gsk_path_builder_svg_arc_to (self,
rect->corner[GSK_CORNER_BOTTOM_LEFT].width,
rect->corner[GSK_CORNER_BOTTOM_LEFT].height,
0, FALSE, TRUE,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_LEFT].height);
/* left */
gsk_path_builder_line_to (self,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_LEFT].height);
/* topleft corner */
gsk_path_builder_svg_arc_to (self,
rect->corner[GSK_CORNER_TOP_LEFT].width,
rect->corner[GSK_CORNER_TOP_LEFT].height,
0, FALSE, TRUE,
rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width,
rect->bounds.origin.y);
/* done */
gsk_path_builder_close (self);
self->current_point = current;
}
static gboolean
circle_contour_curve (const graphene_point_t pts[4],
gpointer data)
{
GskPathBuilder *self = data;
gsk_path_builder_cubic_to (self,
pts[1].x, pts[1].y,
pts[2].x, pts[2].y,
pts[3].x, pts[3].y);
return TRUE;
}
/**
@@ -486,58 +569,22 @@ gsk_path_builder_add_circle (GskPathBuilder *self,
const graphene_point_t *center,
float radius)
{
GskContour *contour;
graphene_point_t current;
g_return_if_fail (self != NULL);
g_return_if_fail (center != NULL);
g_return_if_fail (radius > 0);
contour = gsk_circle_contour_new (center, radius, 0, 360);
gsk_path_builder_add_contour (self, contour);
}
current = self->current_point;
/**
* 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;
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);
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);
self->current_point = current;
}
/**
@@ -791,82 +838,6 @@ 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`
@@ -1105,3 +1076,81 @@ gsk_path_builder_add_layout (GskPathBuilder *self,
cairo_destroy (cr);
cairo_surface_destroy (surface);
}
/**
* 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 equal to or 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.
*
* Note that this method always adds a path with the given start point
* and end point. To add a closed path, use [method@Gsk.PathBuilder.add_path].
*
* 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;
gsize n_contours = gsk_path_get_n_contours (path);
graphene_point_t current;
g_return_if_fail (self != NULL);
g_return_if_fail (path != NULL);
g_return_if_fail (start != NULL);
g_return_if_fail (end != NULL);
g_return_if_fail (s->contour < n_contours);
g_return_if_fail (e->contour < n_contours);
current = self->current_point;
contour = gsk_path_get_contour (path, s->contour);
if (s->contour == e->contour)
{
if (gsk_path_point_compare (start, end) < 0)
{
gsk_contour_add_segment (contour, self, TRUE, s, e);
goto out;
}
else if (n_contours == 1)
{
gsk_contour_add_segment (contour, self, TRUE,
s,
&(GskRealPathPoint) { s->contour, gsk_contour_get_n_points (contour) - 1, 1 });
gsk_contour_add_segment (contour, self, FALSE,
&(GskRealPathPoint) { s->contour, 1, 0 },
e);
goto out;
}
}
gsk_contour_add_segment (contour, self, TRUE,
s,
&(GskRealPathPoint) { s->contour, gsk_contour_get_n_points (contour) - 1, 1 });
for (gsize i = (s->contour + 1) % n_contours; i != e->contour; i = (i + 1) % 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,
&(GskRealPathPoint) { e->contour, 1, 0 },
e);
out:
gsk_path_builder_end_current (self);
self->current_point = current;
}

View File

@@ -60,6 +60,7 @@ 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);
@@ -71,16 +72,10 @@ void gsk_path_builder_add_circle (GskPathBuilder
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,
GskPathMeasure *measure,
float start,
float end);
GskPath *path,
const GskPathPoint *start,
const GskPathPoint *end);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_move_to (GskPathBuilder *self,
float x,
@@ -126,20 +121,6 @@ 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)

View File

@@ -1,306 +0,0 @@
/*
* 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

@@ -1,411 +0,0 @@
/*
* 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 "gskpathmeasure.h"
#include "gskpathbuilder.h"
#include "gskpathpointprivate.h"
#include "gskpathprivate.h"
/**
* GskPathMeasure:
*
* `GskPathMeasure` is an object that allows measurements
* on `GskPath`s such as determining the length of the path.
*
* Many measuring operations require approximating the path
* with simpler shapes. Therefore, a `GskPathMeasure` has
* a tolerance that determines what precision is required
* for such approximations.
*
* A `GskPathMeasure` struct is a reference counted struct
* and should be treated as opaque.
*/
typedef struct _GskContourMeasure GskContourMeasure;
struct _GskContourMeasure
{
float length;
gpointer contour_data;
};
struct _GskPathMeasure
{
/*< private >*/
guint ref_count;
GskPath *path;
float tolerance;
float length;
gsize n_contours;
GskContourMeasure measures[];
};
G_DEFINE_BOXED_TYPE (GskPathMeasure, gsk_path_measure,
gsk_path_measure_ref,
gsk_path_measure_unref)
/**
* gsk_path_measure_new:
* @path: the path to measure
*
* Creates a measure object for the given @path.
*
* Returns: a new `GskPathMeasure` representing @path
*
* Since: 4.14
*/
GskPathMeasure *
gsk_path_measure_new (GskPath *path)
{
return gsk_path_measure_new_with_tolerance (path, GSK_PATH_TOLERANCE_DEFAULT);
}
/**
* gsk_path_measure_new_with_tolerance:
* @path: the path to measure
* @tolerance: the tolerance for measuring operations
*
* Creates a measure object for the given @path and @tolerance.
*
* Returns: a new `GskPathMeasure` representing @path
*
* Since: 4.14
*/
GskPathMeasure *
gsk_path_measure_new_with_tolerance (GskPath *path,
float tolerance)
{
GskPathMeasure *self;
gsize i, n_contours;
g_return_val_if_fail (path != NULL, NULL);
g_return_val_if_fail (tolerance > 0, NULL);
n_contours = gsk_path_get_n_contours (path);
self = g_malloc0 (sizeof (GskPathMeasure) + n_contours * sizeof (GskContourMeasure));
self->ref_count = 1;
self->path = gsk_path_ref (path);
self->tolerance = tolerance;
self->n_contours = n_contours;
for (i = 0; i < n_contours; i++)
{
self->measures[i].contour_data = gsk_contour_init_measure (gsk_path_get_contour (path, i),
self->tolerance,
&self->measures[i].length);
self->length += self->measures[i].length;
}
return self;
}
/**
* gsk_path_measure_ref:
* @self: a `GskPathMeasure`
*
* Increases the reference count of a `GskPathMeasure` by one.
*
* Returns: the passed in `GskPathMeasure`.
*
* Since: 4.14
*/
GskPathMeasure *
gsk_path_measure_ref (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, NULL);
self->ref_count++;
return self;
}
/**
* gsk_path_measure_unref:
* @self: a `GskPathMeasure`
*
* Decreases the reference count of a `GskPathMeasure` by one.
*
* If the resulting reference count is zero, frees the object.
*
* Since: 4.14
*/
void
gsk_path_measure_unref (GskPathMeasure *self)
{
gsize i;
g_return_if_fail (self != NULL);
g_return_if_fail (self->ref_count > 0);
self->ref_count--;
if (self->ref_count > 0)
return;
for (i = 0; i < self->n_contours; i++)
{
gsk_contour_free_measure (gsk_path_get_contour (self->path, i),
self->measures[i].contour_data);
}
gsk_path_unref (self->path);
g_free (self);
}
/**
* gsk_path_measure_get_path:
* @self: a `GskPathMeasure`
*
* Returns the path that the measure was created for.
*
* Returns: (transfer none): the path of @self
*
* Since: 4.14
*/
GskPath *
gsk_path_measure_get_path (GskPathMeasure *self)
{
return self->path;
}
/**
* gsk_path_measure_get_tolerance:
* @self: a `GskPathMeasure`
*
* Returns the tolerance that the measure was created with.
*
* Returns: the tolerance of @self
*
* Since: 4.14
*/
float
gsk_path_measure_get_tolerance (GskPathMeasure *self)
{
return self->tolerance;
}
/**
* gsk_path_measure_get_length:
* @self: a `GskPathMeasure`
*
* Gets the length of the path being measured.
*
* The length is cached, so this function does not do any work.
*
* Returns: The length of the path measured by @self
*
* Since: 4.14
*/
float
gsk_path_measure_get_length (GskPathMeasure *self)
{
g_return_val_if_fail (self != NULL, 0);
return self->length;
}
static float
gsk_path_measure_clamp_distance (GskPathMeasure *self,
float distance)
{
if (isnan (distance))
return 0;
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`
* @distance: the distance
* @result: (out caller-allocates): return location for the result
*
* Sets @result to the point at the given distance into the path.
*
* An empty path has no points, so `FALSE` is returned in that case.
*
* Returns: `TRUE` if @result was set
*
* Since: 4.14
*/
gboolean
gsk_path_measure_get_point (GskPathMeasure *self,
float distance,
GskPathPoint *result)
{
GskRealPathPoint *res = (GskRealPathPoint *) result;
gsize i;
float offset;
const GskContour *contour;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (result != NULL, FALSE);
if (self->n_contours == 0)
return FALSE;
offset = gsk_path_measure_clamp_distance (self, distance);
for (i = 0; i < self->n_contours - 1; i++)
{
if (offset < self->measures[i].length)
break;
offset -= self->measures[i].length;
}
g_assert (0 <= i && i < self->n_contours);
offset = CLAMP (offset, 0, self->measures[i].length);
contour = gsk_path_get_contour (self->path, i);
gsk_contour_get_point (contour, self->measures[i].contour_data, offset, res);
return TRUE;
}
float
gsk_path_measure_get_distance (GskPathMeasure *self,
GskPathPoint *point)
{
GskRealPathPoint *p = (GskRealPathPoint *)point;
const GskContour *contour = p->contour;
float contour_offset = 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 < self->n_contours; i++)
{
if (contour == gsk_path_get_contour (self->path, i))
return contour_offset + gsk_contour_get_distance (contour,
p,
self->measures[i].contour_data);
contour_offset += self->measures[i].length;
}
g_return_val_if_reached (0);
}

View File

@@ -1,66 +0,0 @@
/*
* 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>
*/
#pragma once
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gsk/gsk.h> can be included directly."
#endif
#include <gsk/gskpath.h>
#include <gsk/gskpathpoint.h>
G_BEGIN_DECLS
#define GSK_TYPE_PATH_MEASURE (gsk_path_measure_get_type ())
GDK_AVAILABLE_IN_4_14
GType gsk_path_measure_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_4_14
GskPathMeasure * gsk_path_measure_new (GskPath *path);
GDK_AVAILABLE_IN_4_14
GskPathMeasure * gsk_path_measure_new_with_tolerance (GskPath *path,
float tolerance);
GDK_AVAILABLE_IN_4_14
GskPathMeasure * gsk_path_measure_ref (GskPathMeasure *self);
GDK_AVAILABLE_IN_4_14
void gsk_path_measure_unref (GskPathMeasure *self);
GDK_AVAILABLE_IN_4_14
GskPath * gsk_path_measure_get_path (GskPathMeasure *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_4_14
float gsk_path_measure_get_tolerance (GskPathMeasure *self) G_GNUC_PURE;
GDK_AVAILABLE_IN_4_14
float gsk_path_measure_get_length (GskPathMeasure *self);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_measure_get_point (GskPathMeasure *self,
float distance,
GskPathPoint *point);
GDK_AVAILABLE_IN_4_14
float gsk_path_measure_get_distance (GskPathMeasure *self,
GskPathPoint *point);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathMeasure, gsk_path_measure_unref)
G_END_DECLS

View File

@@ -82,23 +82,17 @@ 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, 0, user_data);
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 1, user_data);
case GSK_PATH_CLOSE:
case GSK_PATH_LINE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 2, 0, user_data);
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 2, user_data);
case GSK_PATH_QUAD:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 3, 0, user_data);
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 3, user_data);
case GSK_PATH_CUBIC:
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);
}
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 4, user_data);
default:
g_assert_not_reached ();
@@ -134,10 +128,6 @@ 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;
@@ -172,10 +162,6 @@ 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

@@ -22,11 +22,9 @@
#include "gskpathpointprivate.h"
#include "gskcontourprivate.h"
#include "gskpathmeasure.h"
#include "gdk/gdkprivate.h"
/**
* GskPathPoint:
*
@@ -35,13 +33,14 @@
* It can be queried for properties of the path at that point, such as its
* tangent or its curvature.
*
* To obtain a `GskPathPoint`, use [method@Gsk.Path.get_closest_point]
* or [method@Gsk.PathMeasure.get_point].
* To obtain a `GskPathPoint`, use [method@Gsk.Path.get_closest_point].
*
* Note that `GskPathPoint` structs are meant to be stack-allocated, and
* don't a reference to the path object they are obtained from. It is the
* callers responsibility to keep a reference to the path as long as the
* `GskPathPoint` is used.
*
* Since: 4.14
*/
G_DEFINE_BOXED_TYPE (GskPathPoint, gsk_path_point,
@@ -67,25 +66,81 @@ gsk_path_point_free (GskPathPoint *point)
g_free (point);
}
GskPath *
gsk_path_point_get_path (GskPathPoint *point)
/**
* gsk_path_point_equal:
* @point1: a `GskPathPoint`
* @point2: another `GskPathPoint`
*
* Returns whether the two path points refer to the same
* location on all paths.
*
* Note that the start- and endpoint of a closed contour
* will compare nonequal according to this definition.
* Use [method@Gsk.Path.is_closed] to find out if the
* start- and endpoint of a concrete path refer to the
* same location.
*
* Return: `TRUE` if @point1 and @point2 are equal
*/
gboolean
gsk_path_point_equal (const GskPathPoint *point1,
const GskPathPoint *point2)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
const GskRealPathPoint *p1 = (const GskRealPathPoint *) point1;
const GskRealPathPoint *p2 = (const GskRealPathPoint *) point2;
return self->path;
if (p1->contour == p2->contour)
{
if ((p1->idx == p2->idx && p1->t == p2->t) ||
(p1->idx + 1 == p2->idx && p1->t == 1 && p2->t == 0) ||
(p1->idx == p2->idx + 1 && p1->t == 0 && p2->t == 1))
return TRUE;
}
return FALSE;
}
const GskContour *
gsk_path_point_get_contour (GskPathPoint *point)
/**
* gsk_path_point_compare:
* @point1: a `GskPathPoint`
* @point2: another `GskPathPoint`
*
* Returns whether @point1 is before or after @point2.
*
* Returns: -1 if @point1 is before @point2,
* 1 if @point1 is after @point2,
* 0 if they are equal
*/
int
gsk_path_point_compare (const GskPathPoint *point1,
const GskPathPoint *point2)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
const GskRealPathPoint *p1 = (const GskRealPathPoint *) point1;
const GskRealPathPoint *p2 = (const GskRealPathPoint *) point2;
return self->contour;
if (gsk_path_point_equal (point1, point2))
return 0;
if (p1->contour < p2->contour)
return -1;
else if (p1->contour > p2->contour)
return 1;
else if (p1->idx < p2->idx)
return -1;
else if (p1->idx > p2->idx)
return 1;
else if (p1->t < p2->t)
return -1;
else if (p1->t > p2->t)
return 1;
return 0;
}
/**
* gsk_path_point_get_position:
* @point: a `GskPathPoint`
* @path: a `GskPath`
* @point: a `GskPathPoint` on @path
* @position: (out caller-allocates): Return location for
* the coordinates of the point
*
@@ -94,17 +149,26 @@ gsk_path_point_get_contour (GskPathPoint *point)
* Since: 4.14
*/
void
gsk_path_point_get_position (GskPathPoint *point,
graphene_point_t *position)
gsk_path_point_get_position (GskPath *path,
const GskPathPoint *point,
graphene_point_t *position)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
const GskContour *contour;
gsk_contour_get_position (self->contour, self, position);
g_return_if_fail (path != NULL);
g_return_if_fail (point != NULL);
g_return_if_fail (position != NULL);
g_return_if_fail (self->contour < gsk_path_get_n_contours (path));
contour = gsk_path_get_contour (path, self->contour),
gsk_contour_get_position (contour, self, position);
}
/**
* gsk_path_point_get_tangent:
* @point: a `GskPathPoint`
* @path: a `GskPath`
* @point: a `GskPathPoint` on @path
* @direction: the direction for which to return the tangent
* @tangent: (out caller-allocates): Return location for
* the tangent at the point
@@ -120,19 +184,28 @@ gsk_path_point_get_position (GskPathPoint *point,
* Since: 4.14
*/
void
gsk_path_point_get_tangent (GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent)
gsk_path_point_get_tangent (GskPath *path,
const GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
const GskContour *contour;
gsk_contour_get_tangent (self->contour, self, direction, tangent);
g_return_if_fail (path != NULL);
g_return_if_fail (point != NULL);
g_return_if_fail (tangent != NULL);
g_return_if_fail (self->contour < gsk_path_get_n_contours (path));
contour = gsk_path_get_contour (path, self->contour),
gsk_contour_get_tangent (contour, self, direction, tangent);
}
/**
* gsk_path_point_get_curvature:
* @point: a `GskPathPoint`
* @center: (out caller-allocates): Return location for
* @path: a `GskPath`
* @point: a `GskPathPoint` on @path
* @center: (out caller-allocates) (nullable): Return location for
* the center of the osculating circle
*
* Calculates the curvature of the path at the point.
@@ -147,19 +220,17 @@ gsk_path_point_get_tangent (GskPathPoint *point,
* Since: 4.14
*/
float
gsk_path_point_get_curvature (GskPathPoint *point,
graphene_point_t *center)
gsk_path_point_get_curvature (GskPath *path,
const GskPathPoint *point,
graphene_point_t *center)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
const GskContour *contour;
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);
g_return_val_if_fail (path != NULL, 0);
g_return_val_if_fail (point != NULL, 0);
g_return_val_if_fail (self->contour < gsk_path_get_n_contours (path), 0);
contour = gsk_path_get_contour (path, self->contour);
return gsk_contour_get_curvature (contour, self, center);
}

View File

@@ -43,22 +43,34 @@ 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 (GskPathPoint *point,
graphene_point_t *position);
gboolean gsk_path_point_equal (const GskPathPoint *point1,
const GskPathPoint *point2);
GDK_AVAILABLE_IN_4_14
void gsk_path_point_get_tangent (GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent);
int gsk_path_point_compare (const GskPathPoint *point1,
const GskPathPoint *point2);
GDK_AVAILABLE_IN_4_14
float gsk_path_point_get_curvature (GskPathPoint *point,
graphene_point_t *center);
void gsk_path_point_get_position (GskPath *path,
const 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);
GDK_AVAILABLE_IN_4_14
float gsk_path_point_get_curvature (GskPath *path,
const GskPathPoint *point,
graphene_point_t *center);
G_END_DECLS

View File

@@ -7,27 +7,12 @@ G_BEGIN_DECLS
struct _GskRealPathPoint
{
GskPath *path;
const GskContour *contour;
union {
struct {
unsigned int idx;
float t;
} std;
struct {
float distance;
} rect;
struct {
float angle;
} circle;
} data;
gsize contour;
gsize idx;
float t;
};
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_STATIC_ASSERT (sizeof (GskRealPathPoint) <= sizeof (GskPathPoint));
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 (GskPath *path);
const GskContour * gsk_path_get_contour (GskPath *path,
gsize gsk_path_get_n_contours (const GskPath *self);
const GskContour * gsk_path_get_contour (const GskPath *self,
gsize i);
GskPathFlags gsk_path_get_flags (GskPath *self);
GskPathFlags gsk_path_get_flags (const GskPath *self);
gboolean gsk_path_foreach_with_tolerance (GskPath *self,
GskPathForeachFlags flags,
@@ -65,9 +65,6 @@ 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,6 +26,7 @@
#include "gskdiffprivate.h"
#include "gl/gskglrenderer.h"
#include "gskpathprivate.h"
#include "gskrectprivate.h"
#include "gskrendererprivate.h"
#include "gskroundedrectprivate.h"
#include "gskstrokeprivate.h"
@@ -3969,7 +3970,7 @@ gsk_repeat_node_draw (GskRenderNode *node,
scale_x *= width / self->child_bounds.size.width;
scale_y *= height / self->child_bounds.size.height;
cairo_surface_set_device_scale (surface, scale_x, scale_y);
cairo_surface_set_device_offset (surface,
cairo_surface_set_device_offset (surface,
- self->child_bounds.origin.x * scale_x,
- self->child_bounds.origin.y * scale_y);
@@ -4140,7 +4141,7 @@ gsk_clip_node_diff (GskRenderNode *node1,
gsk_render_node_diff_impossible (node1, node2, region);
}
}
static void
gsk_clip_node_class_init (gpointer g_class,
gpointer class_data)
@@ -4371,6 +4372,15 @@ gsk_rounded_clip_node_get_clip (const GskRenderNode *node)
/* }}} */
/* {{{ GSK_FILL_NODE */
/**
* GskFillNode:
*
* A render node filling the area given by [struct@Gsk.Path]
* and [enum@Gsk.FillRule] with the child node.
*
* Since: 4.14
*/
struct _GskFillNode
{
GskRenderNode render_node;
@@ -4413,9 +4423,17 @@ gsk_fill_node_draw (GskRenderNode *node,
break;
}
gsk_path_to_cairo (self->path, cr);
cairo_clip (cr);
gsk_render_node_draw (self->child, 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_restore (cr);
}
@@ -4432,12 +4450,10 @@ 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);
graphene_rect_union (&node1->bounds, &node2->bounds, &rect);
rectangle_init_from_graphene (&clip_rect, &rect);
rectangle_init_from_graphene (&clip_rect, &node1->bounds);
cairo_region_intersect_rectangle (sub, &clip_rect);
cairo_region_union (region, sub);
cairo_region_destroy (sub);
@@ -4565,6 +4581,15 @@ gsk_fill_node_get_fill_rule (const GskRenderNode *node)
/* }}} */
/* {{{ GSK_STROKE_NODE */
/**
* GskStrokeNode:
*
* A render node that will fill the area determined by stroking the the given
* [struct@Gsk.Path] using the [struct@Gsk.Stroke] attributes.
*
* Since: 4.14
*/
struct _GskStrokeNode
{
GskRenderNode render_node;
@@ -4595,12 +4620,19 @@ gsk_stroke_node_draw (GskRenderNode *node,
cairo_save (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);
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_stroke_to_cairo (&self->stroke, cr);
@@ -4622,9 +4654,12 @@ 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);
}
@@ -4653,8 +4688,10 @@ gsk_stroke_node_class_init (gpointer g_class,
* @path: (transfer none): The path describing the area to stroke
* @stroke: (transfer none): The stroke attributes to use
*
* Creates a #GskRenderNode that will stroke the @child along the given
* @path using the attributes defined in @stroke.
* Creates a #GskRenderNode that will fill the outline generated by stroking
* the given @path using the attributes defined in @stroke.
*
* The area is filled with @child.
*
* Returns: (transfer none) (type GskStrokeNode): A new #GskRenderNode
*

View File

@@ -24,10 +24,12 @@
#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"
@@ -1176,6 +1178,26 @@ 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)
@@ -2099,6 +2121,165 @@ 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)
@@ -2181,6 +2362,8 @@ 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 },
@@ -2843,8 +3026,11 @@ append_escaping_newlines (GString *str,
len = strcspn (string, "\n");
g_string_append_len (str, string, len);
string += len;
g_string_append (str, "\\\n");
string++;
if (*string)
{
g_string_append (str, "\\\n");
string++;
}
} while (*string);
}
@@ -3045,6 +3231,55 @@ 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)
@@ -3220,14 +3455,11 @@ 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));
path_str = gsk_path_to_string (gsk_fill_node_get_path (node));
append_string_param (p, "path", path_str);
g_free (path_str);
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));
end_node (p);
}
@@ -3236,17 +3468,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));
path_str = gsk_path_to_string (gsk_stroke_node_get_path (node));
append_string_param (p, "path", path_str);
g_free (path_str);
append_path_param (p, "path", gsk_stroke_node_get_path (node));
stroke = gsk_stroke_node_get_stroke (node);
append_float_param (p, "line-width", gsk_stroke_get_line_width (stroke), 0.0);
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);
end_node (p);
}

View File

@@ -14,7 +14,7 @@ typedef struct _GskRenderNodeClass GskRenderNodeClass;
* We don't add an "n-types" value to avoid having to handle
* it in every single switch.
*/
#define GSK_RENDER_NODE_TYPE_N_TYPES (GSK_MASK_NODE + 1)
#define GSK_RENDER_NODE_TYPE_N_TYPES (GSK_STROKE_NODE + 1)
extern GType gsk_render_node_types[];

View File

@@ -26,6 +26,8 @@
*
* A `GskStroke` struct collects the parameters that influence
* the operation of stroking a path.
*
* Since: 4.14
*/
G_DEFINE_BOXED_TYPE (GskStroke, gsk_stroke, gsk_stroke_copy, gsk_stroke_free)
@@ -138,7 +140,6 @@ 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:
@@ -187,7 +188,19 @@ gsk_stroke_equal (gconstpointer stroke1,
const GskStroke *self1 = stroke1;
const GskStroke *self2 = stroke2;
return self1->line_width == self2->line_width;
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;
}
/**
@@ -321,8 +334,7 @@ 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`. For joins of type `GSK_LINE_JOIN_MITER_CLIP`,
* the miter is clipped at a distance of half the miter limit.
* `GSK_LINE_JOIN_BEVEL`.
*
* Since: 4.14
*/
@@ -411,7 +423,6 @@ gsk_stroke_set_dash (GskStroke *self,
g_free (self->dash);
self->dash = g_memdup (dash, sizeof (gfloat) * n_dash);
self->n_dash = n_dash;
}
/**
@@ -477,3 +488,50 @@ 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,4 +53,6 @@ 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,7 +27,6 @@
typedef struct _GskPath GskPath;
typedef struct _GskPathBuilder GskPathBuilder;
typedef struct _GskPathMeasure GskPathMeasure;
typedef struct _GskPathPoint GskPathPoint;
typedef struct _GskRenderer GskRenderer;
typedef struct _GskStroke GskStroke;

View File

@@ -27,9 +27,7 @@ gsk_public_sources = files([
'gskdiff.c',
'gskglshader.c',
'gskpath.c',
'gskpathdash.c',
'gskpathbuilder.c',
'gskpathmeasure.c',
'gskpathpoint.c',
'gskrenderer.c',
'gskrendernode.c',
@@ -77,7 +75,6 @@ gsk_public_headers = files([
'gskglshader.h',
'gskpath.h',
'gskpathbuilder.h',
'gskpathmeasure.h',
'gskpathpoint.h',
'gskrenderer.h',
'gskrendernode.h',

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)
if (tile->n_items > 1 && tile->area.width > 0 && tile->area.height > 0)
{
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 = mid;
max = text_width;
}
return min * PANGO_SCALE;

View File

@@ -27,9 +27,9 @@
* `GtkListHeader` is used by list widgets to represent the headers they
* display.
*
* The `GtkListHeader`s are managed just like [class@gtk.ListItem]s via
* their factory, but provide a different set of properties suitable for
* managing the header instead of individual items.
* `GtkListHeader` objects are managed just like [class@Gtk.ListItem]
* objects via their factory, but provide a different set of properties suitable
* for managing the header instead of individual items.
*
* Since: 4.12
*/

View File

@@ -27,13 +27,14 @@
/**
* GtkListItem:
*
* `GtkListItem` is used by list widgets to represent items in a `GListModel`.
* `GtkListItem` is used by list widgets to represent items in a
* [iface@Gio.ListModel].
*
* The `GtkListItem`s are managed by the list widget (with its factory)
* `GtkListItem` objects are managed by the list widget (with its factory)
* and cannot be created by applications, but they need to be populated
* by application code. This is done by calling [method@Gtk.ListItem.set_child].
*
* `GtkListItem`s exist in 2 stages:
* `GtkListItem` objects exist in 2 stages:
*
* 1. The unbound stage where the listitem is not currently connected to
* an item in the list. In that case, the [property@Gtk.ListItem:item]

View File

@@ -33,12 +33,12 @@
/**
* GtkOverlayLayout:
*
* `GtkOverlayLayout` is the layout manager used by `GtkOverlay`.
* `GtkOverlayLayout` is the layout manager used by [class@Gtk.Overlay].
*
* It places widgets as overlays on top of the main child.
*
* This is not a reusable layout manager, since it expects its widget
* to be a `GtkOverlay`. It only listed here so that its layout
* to be a `GtkOverlay`. It is only listed here so that its layout
* properties get documented.
*/

View File

@@ -357,16 +357,16 @@ snapshot_frame_fill (GtkSnapshot *snapshot,
gtk_snapshot_append_border (snapshot, outline, border_width, colors);
}
static GskStroke *
create_stroke_style (double line_width,
GtkBorderStyle style,
double length)
static void
set_stroke_style (cairo_t *cr,
double line_width,
GtkBorderStyle style,
double length)
{
GskStroke *stroke;
float segments[2];
double segments[2];
double n;
stroke = gsk_stroke_new (line_width);
cairo_set_line_width (cr, line_width);
if (style == GTK_BORDER_STYLE_DOTTED)
{
@@ -374,12 +374,12 @@ create_stroke_style (double line_width,
segments[0] = 0;
segments[1] = n ? length / n : 2;
gsk_stroke_set_dash (stroke, segments, 2);
cairo_set_dash (cr, segments, G_N_ELEMENTS (segments), 0);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_ROUND);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_ROUND);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
}
else if (style == GTK_BORDER_STYLE_DASHED)
else
{
n = length / line_width;
/* Optimize the common case of an integer-sized rectangle
@@ -397,33 +397,32 @@ create_stroke_style (double line_width,
segments[0] = n ? (1. / 3) * length / n : 1;
segments[1] = 2 * segments[0];
}
gsk_stroke_set_dash (stroke, segments, 2);
cairo_set_dash (cr, segments, G_N_ELEMENTS (segments), 0);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_SQUARE);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_MITER);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
}
else
{
g_assert_not_reached ();
}
return stroke;
}
static void
snapshot_frame_stroke (GtkSnapshot *snapshot,
const GskRoundedRect *border_box,
const float border_width[4],
GdkRGBA colors[4],
guint hidden_side,
GtkBorderStyle stroke_style)
render_frame_stroke (cairo_t *cr,
const GskRoundedRect *border_box,
const double 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,
@@ -431,36 +430,32 @@ snapshot_frame_stroke (GtkSnapshot *snapshot,
border_width[GTK_CSS_BOTTOM] / 2.0,
border_width[GTK_CSS_LEFT] / 2.0);
if (border_width[0] == border_width[1] &&
border_width[0] == border_width[2] &&
border_width[0] == border_width[3] &&
hidden_side == 0)
if (!different_colors && !different_borders && 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);
}
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);
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);
}
else
{
const float weight = sqrtf(2)/2.0;
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]);
for (i = 0; i < 4; i++)
{
@@ -470,111 +465,49 @@ snapshot_frame_stroke (GtkSnapshot *snapshot,
if (border_width[i] == 0)
continue;
builder = gsk_path_builder_new ();
cairo_save (cr);
if (i == 0)
{
/* 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);
}
_gtk_rounded_box_path_top (border_box, &padding_box, cr);
else if (i == 1)
{
/* 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);
}
_gtk_rounded_box_path_right (border_box, &padding_box, cr);
else if (i == 2)
{
/* 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);
}
_gtk_rounded_box_path_bottom (border_box, &padding_box, cr);
else if (i == 3)
{
/* 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_left (border_box, &padding_box, cr);
cairo_clip (cr);
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);
_gtk_rounded_box_path_side (&stroke_box, cr, i);
gtk_snapshot_append_border (snapshot, border_box, border_width, colors);
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_pop (snapshot);
cairo_restore (cr);
}
}
}
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,7 +1160,6 @@ 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,
@@ -1219,6 +1218,10 @@ 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
@@ -1228,7 +1231,6 @@ 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,6 +27,7 @@
#endif
#include <glib-object.h>
#include <gdk/gdk.h>
G_BEGIN_DECLS
@@ -87,28 +88,39 @@ 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);

Binary file not shown.

View File

@@ -23,7 +23,7 @@
#include <glib-object.h>
#include <gtk/gtk.h>
#include "gtkprintbackendprivate.h"
#include "gtk/print/gtkprintbackendprivate.h"
G_BEGIN_DECLS

View File

@@ -21,7 +21,7 @@
#include <glib-object.h>
#include <cpdb/cpdb-frontend.h>
#include <gtk/gtkprinterprivate.h>
#include <gtk/print/gtkprinterprivate.h>
G_BEGIN_DECLS
@@ -43,4 +43,4 @@ void gtk_printer_cpdb_set_printer_obj (GtkPrinterCpdb
G_END_DECLS
#endif /* __GTK_PRINTER_CPDB_H__ */
#endif /* __GTK_PRINTER_CPDB_H__ */

1824
po/ca.po

File diff suppressed because it is too large Load Diff

2091
po/es.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

1754
po/gl.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

6396
po/ro.po

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
/*
* 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

@@ -0,0 +1,13 @@
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.

After

Width:  |  Height:  |  Size: 250 B

View File

@@ -0,0 +1,19 @@
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.

After

Width:  |  Height:  |  Size: 346 B

View File

@@ -21,57 +21,6 @@
#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)
{
@@ -97,17 +46,6 @@ 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);
@@ -154,7 +92,6 @@ static gboolean
pathop_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskCurve *curve = user_data;
@@ -164,7 +101,7 @@ pathop_cb (GskPathOperation op,
if (op == GSK_PATH_MOVE)
return TRUE;
gsk_curve_init_foreach (curve, op, pts, n_pts, weight);
gsk_curve_init_foreach (curve, op, pts, n_pts);
return FALSE;
}
@@ -214,7 +151,6 @@ 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,15 +8,6 @@ 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,
@@ -57,18 +48,6 @@ 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 ();
}
@@ -77,7 +56,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_CONIC);
init_random_curve_with_op (curve, GSK_PATH_LINE, GSK_PATH_CUBIC);
}
static void
@@ -217,77 +196,17 @@ 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, weight);
gsk_curve_init_foreach (&c, op, pts, n_pts);
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)
{
@@ -330,9 +249,6 @@ 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 ();
}
@@ -412,7 +328,6 @@ 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);

View File

@@ -1,183 +0,0 @@
/*
* 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

@@ -1,265 +0,0 @@
/*
* 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 void
test_bad_split (void)
{
GskPath *path, *path1;
GskPathMeasure *measure, *measure1;
GskPathBuilder *builder;
float split, length, epsilon;
/* An example that was isolated from the /path/segment test path.c
* It shows how uneven parametrization of cubics can lead to bad
* lengths reported by the measure.
*/
path = gsk_path_parse ("M 0 0 C 2 0 4 0 4 0");
measure = gsk_path_measure_new (path);
split = 2.962588;
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, measure, 0, split);
path1 = gsk_path_builder_free_to_path (builder);
measure1 = gsk_path_measure_new (path1);
g_assert_cmpfloat_with_epsilon (split, gsk_path_measure_get_length (measure1), epsilon);
gsk_path_measure_unref (measure1);
gsk_path_unref (path1);
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
static void
test_bad_in_fill (void)
{
GskPath *path;
gboolean inside;
/* A fat Cantarell W */
path = gsk_path_parse ("M -2 694 M 206.1748046875 704 L 390.9371337890625 704 L 551.1888427734375 99.5035400390625 L 473.0489501953125 99.5035400390625 L 649.1048583984375 704 L 828.965087890625 704 L 1028.3077392578125 10 L 857.8111572265625 10 L 710.0489501953125 621.251708984375 L 775.9720458984375 598.426513671875 L 614.5245361328125 14.0489501953125 L 430.2237548828125 14.0489501953125 L 278.6783447265625 602.230712890625 L 330.0909423828125 602.230712890625 L 195.88818359375 10 L 5.7342529296875 10 L 206.1748046875 704 Z");
/* The midpoint of the right foot of a fat Cantarell X */
inside = gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (552.360107, 704.000000), GSK_FILL_RULE_WINDING);
g_assert_false (inside);
gsk_path_unref (path);
}
/* Test that path_in_fill implicitly closes contours. I think this is wrong,
* but it is what "everybody" does.
*/
static void
test_unclosed_in_fill (void)
{
GskPath *path;
path = gsk_path_parse ("M 0 0 L 0 100 L 100 100 L 100 0 Z");
g_assert_true (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (50, 50), GSK_FILL_RULE_WINDING));
gsk_path_unref (path);
path = gsk_path_parse ("M 0 0 L 0 100 L 100 100 L 100 0");
g_assert_true (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (50, 50), GSK_FILL_RULE_WINDING));
gsk_path_unref (path);
}
static void
test_rect (void)
{
GskPathBuilder *builder;
GskPath *path;
GskPathMeasure *measure;
GskPathPoint point;
graphene_point_t p;
graphene_vec2_t tangent, expected_tangent;
float length;
gboolean ret;
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 100, 50));
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
g_assert_true (length == 300);
#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 (&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_measure_get_distance (measure, &point) - distance) < 0.01); \
else \
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 (&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 (&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 (&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_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)
TEST_POS_AT (25, 25, 0)
TEST_POS_AT (100, 100, 0)
TEST_POS_AT (110, 100, 10)
TEST_POS_AT (150, 100, 50)
TEST_POS_AT (175, 75, 50)
TEST_POS_AT (250, 0, 50)
TEST_POS_AT (260, 0, 40)
TEST_POS_AT (300, 0, 0)
TEST_TANGENT_AT (0, 0, -1, 1, 0)
TEST_TANGENT_AT (50, 1, 0, 1, 0)
TEST_TANGENT_AT (100, 1, 0, 0, 1)
TEST_TANGENT_AT (125, 0, 1, 0, 1)
TEST_TANGENT_AT (150, 0, 1, -1, 0)
TEST_TANGENT_AT (200, -1, 0, -1, 0)
TEST_TANGENT_AT (250, -1, 0, 0, -1)
TEST_TANGENT_AT (275, 0, -1, 0, -1)
gsk_path_measure_unref (measure);
gsk_path_unref (path);
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 50, -100, -50));
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
g_assert_true (length == 300);
TEST_POS_AT (0, 100, 50)
TEST_POS_AT (25, 75, 50)
TEST_POS_AT (100, 0, 50)
TEST_POS_AT (110, 0, 40)
TEST_POS_AT (150, 0, 0)
TEST_POS_AT (175, 25, 0)
TEST_POS_AT (250, 100, 0)
TEST_POS_AT (260, 100, 10)
TEST_POS_AT (300, 100, 50)
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 0, -100, 50));
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
g_assert_true (length == 300);
TEST_POS_AT (0, 100, 0)
TEST_POS_AT (25, 75, 0)
TEST_POS_AT (100, 0, 0)
TEST_POS_AT (110, 0, 10)
TEST_POS_AT (150, 0, 50)
TEST_POS_AT (175, 25, 50)
TEST_POS_AT (250, 100, 50)
TEST_POS_AT (260, 100, 40)
TEST_POS_AT (300, 100, 0)
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 100, 0));
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
g_assert_true (length == 200);
TEST_POS_AT2 (0, 0, 0, 0)
TEST_POS_AT2 (25, 25, 0, 25)
TEST_POS_AT2 (100, 100, 0, 100)
TEST_POS_AT2 (110, 90, 0, 90)
TEST_POS_AT2 (200, 0, 0, 0)
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 0, -100, 0));
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
g_assert_true (length == 200);
/* These cases are ambiguous */
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, 90)
TEST_POS_AT2 (200, 100, 0, 0)
builder = gsk_path_builder_new ();
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 100, 0, -100));
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
g_assert_true (length == 200);
/* These cases are ambiguous */
TEST_POS_AT2 (0, 0, 100, 0)
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)
#undef TEST_POS_AT
#undef TEST_POS_AT2
#undef TEST_TANGENT_AT
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
int
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/measure/bad-split", test_bad_split);
g_test_add_func ("/measure/bad-in-fill", test_bad_in_fill);
g_test_add_func ("/measure/unclosed-in-fill", test_unclosed_in_fill);
g_test_add_func ("/measure/rect", test_rect);
return g_test_run ();
}

View File

@@ -1,917 +0,0 @@
/*
* 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 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 15
GskPathBuilder *builder;
guint i;
builder = gsk_path_builder_new ();
switch (g_test_rand_int_range (0, N_DEGENERATE_PATHS))
{
case 0:
/* empty path */
break;
case 1:
/* a single point */
gsk_path_builder_move_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
break;
case 2:
/* N points */
for (i = 0; i < MIN (10, max_contours); i++)
{
gsk_path_builder_move_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
}
break;
case 3:
/* 1 closed point */
gsk_path_builder_move_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
gsk_path_builder_close (builder);
break;
case 4:
/* the same point closed N times */
gsk_path_builder_move_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
for (i = 0; i < MIN (10, max_contours); i++)
{
gsk_path_builder_close (builder);
}
break;
case 5:
/* a zero-width and zero-height rect */
gsk_path_builder_add_rect (builder,
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
0, 0));
break;
case 6:
/* a zero-width rect */
gsk_path_builder_add_rect (builder,
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
0,
g_test_rand_double_range (-1000, 1000)));
break;
case 7:
/* a zero-height rect */
gsk_path_builder_add_rect (builder,
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
0));
break;
case 8:
/* a negative-size rect */
gsk_path_builder_add_rect (builder,
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 0),
g_test_rand_double_range (-1000, 0)));
break;
case 9:
/* an absolutely random rect */
gsk_path_builder_add_rect (builder,
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000)));
break;
case 10:
/* an absolutely random rect */
gsk_path_builder_add_rect (builder,
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000)));
break;
case 11:
/* an absolutely random circle */
gsk_path_builder_add_circle (builder,
&GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000)),
g_test_rand_double_range (1, 1000));
break;
case 12:
/* a zero-length line */
{
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_line_to (builder, point.x, point.y);
}
break;
case 13:
/* 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));
gsk_path_builder_move_to (builder, point.x, point.y);
gsk_path_builder_cubic_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),
point.x, point.y);
}
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 ();
}
return gsk_path_builder_free_to_path (builder);
}
static GskPath *
create_random_path (guint max_contours);
static void
add_shape_contour (GskPathBuilder *builder)
{
#define N_SHAPE_CONTOURS 3
switch (g_test_rand_int_range (0, N_SHAPE_CONTOURS))
{
case 0:
gsk_path_builder_add_rect (builder,
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (1, 1000),
g_test_rand_double_range (1, 1000)));
break;
case 1:
gsk_path_builder_add_circle (builder,
&GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000)),
g_test_rand_double_range (1, 1000));
break;
case 2:
{
GskPath *path = create_random_path (1);
gsk_path_builder_add_path (builder, path);
gsk_path_unref (path);
}
break;
case N_SHAPE_CONTOURS:
default:
g_assert_not_reached ();
break;
}
}
static void
add_standard_contour (GskPathBuilder *builder)
{
guint i, n;
if (g_test_rand_bit ())
{
if (g_test_rand_bit ())
gsk_path_builder_move_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
else
gsk_path_builder_rel_move_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
}
/* that 20 is random, but should be enough to get some
* crazy self-intersecting shapes */
n = g_test_rand_int_range (1, 20);
for (i = 0; i < n; i++)
{
switch (g_test_rand_int_range (0, 8))
{
case 0:
gsk_path_builder_line_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
break;
case 1:
gsk_path_builder_rel_line_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
break;
case 2:
gsk_path_builder_quad_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
break;
case 3:
gsk_path_builder_rel_quad_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
break;
case 4:
gsk_path_builder_cubic_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),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
break;
case 5:
gsk_path_builder_rel_cubic_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),
g_test_rand_double_range (-1000, 1000),
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;
}
}
if (g_test_rand_bit ())
gsk_path_builder_close (builder);
}
static GskPath *
create_random_path (guint max_contours)
{
GskPathBuilder *builder;
guint i, n;
/* 5% chance for a weird shape */
if (!g_test_rand_int_range (0, 20))
return create_random_degenerate_path (max_contours);
builder = gsk_path_builder_new ();
n = g_test_rand_int_range (1, 10);
n = MIN (n, max_contours);
for (i = 0; i < n; i++)
{
/* 2/3 of shapes are standard contours */
if (g_test_rand_int_range (0, 3))
add_standard_contour (builder);
else
add_shape_contour (builder);
}
return gsk_path_builder_free_to_path (builder);
}
typedef struct {
GskPathOperation op;
graphene_point_t pts[4];
float weight;
} PathOperation;
static void
test_segment_start (void)
{
GskPath *path, *path1;
GskPathMeasure *measure, *measure1;
GskPathBuilder *builder;
float epsilon, length;
guint i;
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);
for (i = 0; i < 100; i++)
{
float seg_length = length * i / 100.0f;
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, measure, 0, seg_length);
path1 = gsk_path_builder_free_to_path (builder);
measure1 = gsk_path_measure_new (path1);
if (seg_length == 0)
g_assert_cmpfloat_with_epsilon (gsk_path_measure_get_length (measure), gsk_path_measure_get_length (measure1), epsilon);
else
g_assert_cmpfloat_with_epsilon (seg_length, gsk_path_measure_get_length (measure1), epsilon);
gsk_path_measure_unref (measure1);
gsk_path_unref (path1);
}
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
static void
test_segment_end (void)
{
GskPath *path, *path1;
GskPathMeasure *measure, *measure1;
GskPathBuilder *builder;
float epsilon, length;
guint i;
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);
for (i = 0; i < 100; i++)
{
float seg_length = length * i / 100.0f;
builder = gsk_path_builder_new ();
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);
if (seg_length == 0)
g_assert_cmpfloat_with_epsilon (gsk_path_measure_get_length (measure), gsk_path_measure_get_length (measure1), epsilon);
else
g_assert_cmpfloat_with_epsilon (seg_length, gsk_path_measure_get_length (measure1), epsilon);
gsk_path_measure_unref (measure1);
gsk_path_unref (path1);
}
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
static void
test_segment_chunk (void)
{
GskPath *path, *path1, *path2;
GskPathMeasure *measure, *measure1, *measure2;
GskPathBuilder *builder;
float epsilon, length;
guint i;
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);
for (i = 0; i <= 100; i++)
{
float seg_start = length * i / 200.0f;
builder = gsk_path_builder_new ();
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);
builder = gsk_path_builder_new ();
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);
g_assert_cmpfloat_with_epsilon (length / 2, gsk_path_measure_get_length (measure2), epsilon);
gsk_path_measure_unref (measure2);
gsk_path_unref (path2);
gsk_path_measure_unref (measure1);
gsk_path_unref (path1);
}
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
static void
test_segment (void)
{
GskPath *path, *path1, *path2, *path3;
GskPathMeasure *measure, *measure1, *measure2, *measure3;
GskPathBuilder *builder;
guint i;
float split1, split2, epsilon, length;
for (i = 0; i < 1000; i++)
{
path = create_random_path (G_MAXUINT);
measure = gsk_path_measure_new (path);
length = gsk_path_measure_get_length (measure);
/* chosen high enough to stop the testsuite from failing */
epsilon = MAX (length / 64, 1.f / 1024);
split1 = g_test_rand_double_range (0, length);
split2 = g_test_rand_double_range (split1, length);
builder = gsk_path_builder_new ();
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, 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, measure, split2, length);
path3 = gsk_path_builder_free_to_path (builder);
measure3 = gsk_path_measure_new (path3);
g_assert_cmpfloat_with_epsilon (split1, gsk_path_measure_get_length (measure1), epsilon);
g_assert_cmpfloat_with_epsilon (split2 - split1, gsk_path_measure_get_length (measure2), epsilon);
g_assert_cmpfloat_with_epsilon (length - split2, gsk_path_measure_get_length (measure3), epsilon);
gsk_path_measure_unref (measure2);
gsk_path_measure_unref (measure1);
gsk_path_measure_unref (measure);
gsk_path_unref (path2);
gsk_path_unref (path1);
gsk_path_unref (path);
}
}
static void
test_get_point (void)
{
static const guint max_contours = 5;
static const float tolerance = 1.0;
GskPath *path;
GskPathMeasure *measure;
GskPathPoint point;
guint n_discontinuities;
float length, offset, last_offset;
graphene_point_t p, last_point;
guint i, j;
gboolean ret;
for (i = 0; i < 10; i++)
{
path = create_random_path (max_contours);
measure = gsk_path_measure_new_with_tolerance (path, tolerance);
length = gsk_path_measure_get_length (measure);
n_discontinuities = 0;
ret = gsk_path_measure_get_point (measure, 0, &point);
if (!ret)
{
g_assert_true (gsk_path_is_empty (path));
continue;
}
gsk_path_point_get_position (&point, &last_point);
/* FIXME: anything we can test with tangents here? */
last_offset = 0;
for (j = 1; j <= 1024; j++)
{
offset = length * j / 1024;
ret = gsk_path_measure_get_point (measure, offset, &point);
g_assert_true (ret);
gsk_path_point_get_position (&point, &p);
if (graphene_point_distance (&last_point, &p, NULL, NULL) > 2 * (offset - last_offset))
{
n_discontinuities++;
g_assert_cmpint (n_discontinuities, <, max_contours);
}
last_offset = offset;
last_point = p;
}
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
}
static void
test_closest_point (void)
{
static const float tolerance = 0.5;
GskPath *path, *path1, *path2;
GskPathMeasure *measure, *measure1, *measure2;
GskPathBuilder *builder;
GskPathPoint point;
guint i, j;
gboolean ret;
if (!g_test_slow ())
{
g_test_skip ("Skipping slow test");
return;
}
for (i = 0; i < 10; i++)
{
path1 = create_random_path (G_MAXUINT);
measure1 = gsk_path_measure_new_with_tolerance (path1, tolerance);
path2 = create_random_path (G_MAXUINT);
measure2 = gsk_path_measure_new_with_tolerance (path2, tolerance);
builder = gsk_path_builder_new ();
gsk_path_builder_add_path (builder, path1);
gsk_path_builder_add_path (builder, path2);
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new_with_tolerance (path, tolerance);
for (j = 0; j < 100; j++)
{
graphene_point_t test = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
graphene_point_t p1, p2, p;
graphene_vec2_t t1, t2, t;
float offset1, offset2, offset;
float distance1, distance2, distance;
offset1 = offset2 = offset = 0;
distance1 = distance2 = distance = 0;
ret = gsk_path_get_closest_point (path1, &test, INFINITY, &point);
g_assert_true (ret);
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 (&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 (&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)
{
g_assert_cmpfloat (distance1, ==, distance);
g_assert_cmpfloat (p1.x, ==, p.x);
g_assert_cmpfloat (p1.y, ==, p.y);
g_assert_cmpfloat (offset1, ==, offset);
g_assert_true (graphene_vec2_equal (&t1, &t));
}
else
{
g_assert_cmpfloat (distance2, ==, distance);
g_assert_cmpfloat (p2.x, ==, p.x);
g_assert_cmpfloat (p2.y, ==, p.y);
g_assert_cmpfloat_with_epsilon (offset2 + gsk_path_measure_get_length (measure1), offset, MAX (G_MINFLOAT, offset / 1024));
g_assert_true (graphene_vec2_equal (&t2, &t));
}
}
gsk_path_measure_unref (measure2);
gsk_path_measure_unref (measure1);
gsk_path_measure_unref (measure);
gsk_path_unref (path2);
gsk_path_unref (path1);
gsk_path_unref (path);
}
}
static void
test_closest_point_for_point (void)
{
static const float tolerance = 0.5;
GskPath *path;
GskPathMeasure *measure;
GskPathPoint point;
float length, offset, distance;
graphene_point_t p, closest_point;
guint i, j;
gboolean ret;
if (!g_test_slow ())
{
g_test_skip ("Skipping slow test");
return;
}
for (i = 0; i < 100; i++)
{
path = create_random_path (G_MAXUINT);
if (gsk_path_is_empty (path))
{
/* empty paths have no closest point to anything */
gsk_path_unref (path);
continue;
}
measure = gsk_path_measure_new_with_tolerance (path, tolerance);
length = gsk_path_measure_get_length (measure);
for (j = 0; j < 100; j++)
{
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 (&point, &p);
ret = gsk_path_get_closest_point (path, &p, 2 * tolerance, &point);
g_assert_true (ret);
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... */
g_assert_cmpfloat (distance, <=, 2 * tolerance);
g_assert_cmpfloat (graphene_point_distance (&p, &closest_point, NULL, NULL), <=, 2 * tolerance);
/* we can't check offsets here, since we might hit self-intersections
g_assert_cmpfloat_with_epsilon (closest_offset, offset, 2 * tolerance);
*/
}
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
}
#define N_PATHS 3
static void
test_in_fill_union (void)
{
GskPath *path;
GskPathMeasure *measure, *measures[N_PATHS];
GskPathBuilder *builder;
guint i, j, k;
for (i = 0; i < 100; i++)
{
builder = gsk_path_builder_new ();
for (k = 0; k < N_PATHS; k++)
{
path = create_random_path (G_MAXUINT);
measures[k] = gsk_path_measure_new (path);
gsk_path_builder_add_path (builder, path);
gsk_path_unref (path);
}
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new (path);
gsk_path_unref (path);
for (j = 0; j < 100; j++)
{
graphene_point_t test = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
GskFillRule fill_rule;
for (fill_rule = GSK_FILL_RULE_WINDING; fill_rule <= GSK_FILL_RULE_EVEN_ODD; fill_rule++)
{
guint n_in_fill = 0;
gboolean in_fill;
for (k = 0; k < N_PATHS; k++)
{
if (gsk_path_in_fill (gsk_path_measure_get_path (measures[k]), &test, GSK_FILL_RULE_EVEN_ODD))
n_in_fill++;
}
in_fill = gsk_path_in_fill (gsk_path_measure_get_path (measure), &test, GSK_FILL_RULE_EVEN_ODD);
switch (fill_rule)
{
case GSK_FILL_RULE_WINDING:
if (n_in_fill == 0)
g_assert_false (in_fill);
else if (n_in_fill == 1)
g_assert_true (in_fill);
/* else we can't say anything because the winding rule doesn't give enough info */
break;
case GSK_FILL_RULE_EVEN_ODD:
g_assert_cmpint (in_fill, ==, n_in_fill & 1);
break;
default:
g_assert_not_reached ();
break;
}
}
}
gsk_path_measure_unref (measure);
for (k = 0; k < N_PATHS; k++)
{
gsk_path_measure_unref (measures[k]);
}
}
}
#undef N_PATHS
/* This is somewhat sucky because using foreach breaks up the contours
* (like rects and circles) and replaces everything with the standard
* contour.
* But at least it extensively tests the standard contour.
*/
static gboolean
rotate_path_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathBuilder **builders = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builders[0], pts[0].x, pts[0].y);
gsk_path_builder_move_to (builders[1], pts[0].y, -pts[0].x);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builders[0]);
gsk_path_builder_close (builders[1]);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builders[0], pts[1].x, pts[1].y);
gsk_path_builder_line_to (builders[1], pts[1].y, -pts[1].x);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y);
gsk_path_builder_quad_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x);
break;
case GSK_PATH_CUBIC:
gsk_path_builder_cubic_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
gsk_path_builder_cubic_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x, pts[3].y, -pts[3].x);
break;
case GSK_PATH_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;
}
return TRUE;
}
static void
test_in_fill_rotated (void)
{
GskPath *path;
GskPathBuilder *builders[2];
GskPathMeasure *measures[2];
guint i, j;
#define N_FILL_RULES 2
/* if this triggers, you added a new enum value to GskFillRule, so the define above needs
* an update */
g_assert_null (g_enum_get_value (g_type_class_ref (GSK_TYPE_FILL_RULE), N_FILL_RULES));
for (i = 0; i < 100; i++)
{
path = create_random_path (G_MAXUINT);
builders[0] = gsk_path_builder_new ();
builders[1] = gsk_path_builder_new ();
/* Use -1 here because we want all the flags, even future additions */
gsk_path_foreach (path, -1, rotate_path_cb, builders);
gsk_path_unref (path);
path = gsk_path_builder_free_to_path (builders[0]);
measures[0] = gsk_path_measure_new (path);
gsk_path_unref (path);
path = gsk_path_builder_free_to_path (builders[1]);
measures[1] = gsk_path_measure_new (path);
gsk_path_unref (path);
for (j = 0; j < 100; j++)
{
GskFillRule fill_rule = g_random_int_range (0, N_FILL_RULES);
float x = g_test_rand_double_range (-1000, 1000);
float y = g_test_rand_double_range (-1000, 1000);
g_assert_cmpint (gsk_path_in_fill (gsk_path_measure_get_path (measures[0]), &GRAPHENE_POINT_INIT (x, y), fill_rule),
==,
gsk_path_in_fill (gsk_path_measure_get_path (measures[1]), &GRAPHENE_POINT_INIT (y, -x), fill_rule));
g_assert_cmpint (gsk_path_in_fill (gsk_path_measure_get_path (measures[0]), &GRAPHENE_POINT_INIT (y, x), fill_rule),
==,
gsk_path_in_fill (gsk_path_measure_get_path (measures[1]), &GRAPHENE_POINT_INIT (x, -y), fill_rule));
}
gsk_path_measure_unref (measures[0]);
gsk_path_measure_unref (measures[1]);
}
#undef N_FILL_RULES
}
int
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/measure/segment_start", test_segment_start);
g_test_add_func ("/measure/segment_end", test_segment_end);
g_test_add_func ("/measure/segment_chunk", test_segment_chunk);
g_test_add_func ("/measure/segment", test_segment);
g_test_add_func ("/measure/get_point", test_get_point);
g_test_add_func ("/measure/closest_point", test_closest_point);
g_test_add_func ("/measure/closest_point_for_point", test_closest_point_for_point);
g_test_add_func ("/measure/in-fill-union", test_in_fill_union);
g_test_add_func ("/measure/in-fill-rotated", test_in_fill_rotated);
return g_test_run ();
}

View File

@@ -59,6 +59,7 @@ compare_render_tests = [
'empty-shadow',
'empty-texture',
'empty-transform',
'fill',
'huge-height',
'huge-width',
'inset-shadow-multiple',
@@ -88,6 +89,7 @@ compare_render_tests = [
'scaled-cairo',
'scaled-texture',
'shadow-in-opacity',
'stroke',
'texture-scale-magnify-10000x',
'texture-scale-magnify-rotate',
'texture-scale-stripes',
@@ -270,6 +272,8 @@ 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',
@@ -286,6 +290,8 @@ 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',
@@ -294,6 +300,8 @@ 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',
@@ -320,6 +328,8 @@ 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',
@@ -361,9 +371,6 @@ tests = [
['shader'],
['path'],
['path-special-cases'],
['measure'],
['measure-special-cases'],
['dash'],
]
test_cargs = []
@@ -400,6 +407,7 @@ internal_tests = [
[ 'half-float' ],
['rounded-rect'],
['misc'],
['boundingbox'],
]
foreach t : internal_tests

View File

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

View File

@@ -0,0 +1,14 @@
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

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

View File

@@ -0,0 +1,16 @@
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

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

View File

@@ -0,0 +1,12 @@
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

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

View File

@@ -0,0 +1,14 @@
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,44 +277,432 @@ test_rsvg_parse (void)
}
}
/* Test that circles and rectangles serialize as expected and can be
* round-tripped through strings.
*/
static void
test_serialize_custom_contours (void)
test_empty_path (void)
{
GskPathBuilder *builder;
GskPath *path;
GskPath *path1;
char *string;
char *string1;
char *s;
graphene_rect_t bounds;
GskPathPoint point;
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);
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);
g_assert_true (gsk_path_is_empty (path));
g_assert_false (gsk_path_is_closed (path));
path1 = gsk_path_parse (string);
string1 = gsk_path_to_string (path1);
g_assert_cmpstr (string, ==, string1);
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));
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 */
typedef struct _GskRealPathPoint GskRealPathPoint;
struct _GskRealPathPoint
{
gsize contour;
gsize idx;
float t;
};
static void
test_path_point (void)
{
GskPath *path;
GskPathPoint point;
GskRealPathPoint *rp = (GskRealPathPoint *)&point;
gboolean ret;
graphene_point_t pos, center;
graphene_vec2_t t1, t2, mx;
float curvature;
path = gsk_path_parse ("M 0 0 L 100 0 L 100 100 L 0 100 Z");
ret = gsk_path_get_start_point (path, &point);
g_assert_true (ret);
g_assert_true (rp->contour == 0);
g_assert_true (rp->idx == 1);
g_assert_true (rp->t == 0);
ret = gsk_path_get_end_point (path, &point);
g_assert_true (ret);
g_assert_true (rp->contour == 0);
g_assert_true (rp->idx == 4);
g_assert_true (rp->t == 1);
ret = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (200, 200), INFINITY, &point);
g_assert_true (ret);
g_assert_true (rp->contour == 0);
g_assert_true (rp->idx == 2);
g_assert_true (rp->t == 1);
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);
ret = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (100, 50), INFINITY, &point);
g_assert_true (ret);
g_assert_true (rp->contour == 0);
g_assert_true (rp->idx == 2);
g_assert_true (rp->t == 0.5);
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"
},
{
"M 0 0 L 100 0 L 50 50 Z",
{ 100, 0 },
{ 100, 0 },
"M 100 0 L 50 50 L 0 0 L 100 0"
}
};
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);
}
}
static void
test_bad_in_fill (void)
{
GskPath *path;
gboolean inside;
/* A fat Cantarell W */
path = gsk_path_parse ("M -2 694 M 206.1748046875 704 L 390.9371337890625 704 L 551.1888427734375 99.5035400390625 L 473.0489501953125 99.5035400390625 L 649.1048583984375 704 L 828.965087890625 704 L 1028.3077392578125 10 L 857.8111572265625 10 L 710.0489501953125 621.251708984375 L 775.9720458984375 598.426513671875 L 614.5245361328125 14.0489501953125 L 430.2237548828125 14.0489501953125 L 278.6783447265625 602.230712890625 L 330.0909423828125 602.230712890625 L 195.88818359375 10 L 5.7342529296875 10 L 206.1748046875 704 Z");
/* The midpoint of the right foot of a fat Cantarell X */
inside = gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (552.360107, 704.000000), GSK_FILL_RULE_WINDING);
g_assert_false (inside);
gsk_path_unref (path);
}
/* Test that path_in_fill implicitly closes contours. I think this is wrong,
* but it is what "everybody" does.
*/
static void
test_unclosed_in_fill (void)
{
GskPath *path;
path = gsk_path_parse ("M 0 0 L 0 100 L 100 100 L 100 0 Z");
g_assert_true (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (50, 50), GSK_FILL_RULE_WINDING));
gsk_path_unref (path);
path = gsk_path_parse ("M 0 0 L 0 100 L 100 100 L 100 0");
g_assert_true (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (50, 50), GSK_FILL_RULE_WINDING));
gsk_path_unref (path);
}
/* Test that all the gsk_path_builder_add methods close the current
* contour in the end and do not change the current point.
*/
static void
test_path_builder_add (void)
{
GskPathBuilder *builder;
GskPath *path, *path2;
char *s;
cairo_path_t *cpath;
cairo_surface_t *surface;
cairo_t *cr;
PangoLayout *layout;
GskPathPoint point1, point2;
#define N_ADD_METHODS 8
path = gsk_path_parse ("M 10 10 L 100 100");
gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (50, 50), INFINITY, &point1);
gsk_path_get_end_point (path, &point2);
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 100, 100);
cr = cairo_create (surface);
cairo_move_to (cr, 10, 10);
cairo_line_to (cr, 20, 30);
cpath = cairo_copy_path (cr);
layout = pango_cairo_create_layout (cr);
pango_layout_set_text (layout, "ABC", -1);
for (unsigned int i = 0; i < N_ADD_METHODS; i++)
{
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, 123, 456);
switch (i)
{
case 0:
gsk_path_builder_add_path (builder, path);
break;
case 1:
gsk_path_builder_add_reverse_path (builder, path);
break;
case 2:
gsk_path_builder_add_segment (builder, path, &point1, &point2);
break;
case 3:
gsk_path_builder_add_cairo_path (builder, cpath);
break;
case 4:
gsk_path_builder_add_layout (builder, layout);
break;
case 5:
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 10, 10));
break;
case 6:
{
GskRoundedRect rect;
gsk_rounded_rect_init (&rect,
&GRAPHENE_RECT_INIT (0, 0, 100, 100),
&GRAPHENE_SIZE_INIT (10, 20),
&GRAPHENE_SIZE_INIT (20, 30),
&GRAPHENE_SIZE_INIT (0, 0),
&GRAPHENE_SIZE_INIT (10, 10));
gsk_path_builder_add_rounded_rect (builder, &rect);
}
break;
case 7:
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 10);
break;
default:
g_assert_not_reached ();
}
gsk_path_builder_rel_line_to (builder, 10, 0);
path2 = gsk_path_builder_free_to_path (builder);
s = gsk_path_to_string (path2);
g_assert_true (g_str_has_prefix (s, "M 123 456"));
g_assert_true (g_str_has_suffix (s, "M 123 456 L 133 456"));
g_free (s);
gsk_path_unref (path2);
}
g_object_unref (layout);
cairo_path_destroy (cpath);
cairo_destroy (cr);
cairo_surface_destroy (surface);
g_free (string);
g_free (string1);
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/serialize-custom-contours", test_serialize_custom_contours);
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/bad-in-fill", test_bad_in_fill);
g_test_add_func ("/path/unclosed-in-fill", test_unclosed_in_fill);
g_test_add_func ("/path/builder/add", test_path_builder_add);
return g_test_run ();
}

View File

@@ -19,19 +19,10 @@
#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 15
#define N_DEGENERATE_PATHS 14
GskPathBuilder *builder;
guint i;
@@ -151,7 +142,7 @@ create_random_degenerate_path (guint max_contours)
break;
case 13:
/* a curve with start == end */
/* a cubic with start == end */
{
graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
@@ -165,20 +156,6 @@ 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 ();
@@ -248,7 +225,7 @@ add_standard_contour (GskPathBuilder *builder)
n = g_test_rand_int_range (1, 20);
for (i = 0; i < n; i++)
{
switch (g_test_rand_int_range (0, 8))
switch (g_test_rand_int_range (0, 6))
{
case 0:
gsk_path_builder_line_to (builder,
@@ -298,24 +275,6 @@ 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;
@@ -355,7 +314,6 @@ create_random_path (guint max_contours)
typedef struct {
GskPathOperation op;
graphene_point_t pts[4];
float weight;
} PathOperation;
static void
@@ -413,16 +371,6 @@ 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;
@@ -457,11 +405,6 @@ 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);
}
@@ -471,7 +414,6 @@ 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,
@@ -486,7 +428,6 @@ 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;
@@ -641,6 +582,166 @@ test_parse (void)
}
}
#define N_PATHS 3
static void
test_in_fill_union (void)
{
GskPath *path, *paths[N_PATHS];
GskPathBuilder *builder;
guint i, j, k;
for (i = 0; i < 100; i++)
{
builder = gsk_path_builder_new ();
for (k = 0; k < N_PATHS; k++)
{
paths[k] = create_random_path (G_MAXUINT);
gsk_path_builder_add_path (builder, paths[k]);
}
path = gsk_path_builder_free_to_path (builder);
for (j = 0; j < 100; j++)
{
graphene_point_t test = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
GskFillRule fill_rule;
for (fill_rule = GSK_FILL_RULE_WINDING; fill_rule <= GSK_FILL_RULE_EVEN_ODD; fill_rule++)
{
guint n_in_fill = 0;
gboolean in_fill;
for (k = 0; k < N_PATHS; k++)
{
if (gsk_path_in_fill (paths[k], &test, GSK_FILL_RULE_EVEN_ODD))
n_in_fill++;
}
in_fill = gsk_path_in_fill (path, &test, GSK_FILL_RULE_EVEN_ODD);
switch (fill_rule)
{
case GSK_FILL_RULE_WINDING:
if (n_in_fill == 0)
g_assert_false (in_fill);
else if (n_in_fill == 1)
g_assert_true (in_fill);
/* else we can't say anything because the winding rule doesn't give enough info */
break;
case GSK_FILL_RULE_EVEN_ODD:
g_assert_cmpint (in_fill, ==, n_in_fill & 1);
break;
default:
g_assert_not_reached ();
break;
}
}
}
gsk_path_unref (path);
for (k = 0; k < N_PATHS; k++)
gsk_path_unref (paths[k]);
}
}
#undef N_PATHS
/* This is somewhat sucky because using foreach breaks up the contours
* (like rects and circles) and replaces everything with the standard
* contour.
* But at least it extensively tests the standard contour.
*/
static gboolean
rotate_path_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
gpointer user_data)
{
GskPathBuilder **builders = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builders[0], pts[0].x, pts[0].y);
gsk_path_builder_move_to (builders[1], pts[0].y, -pts[0].x);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builders[0]);
gsk_path_builder_close (builders[1]);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builders[0], pts[1].x, pts[1].y);
gsk_path_builder_line_to (builders[1], pts[1].y, -pts[1].x);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y);
gsk_path_builder_quad_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x);
break;
case GSK_PATH_CUBIC:
gsk_path_builder_cubic_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
gsk_path_builder_cubic_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x, pts[3].y, -pts[3].x);
break;
default:
g_assert_not_reached ();
return FALSE;
}
return TRUE;
}
static void
test_in_fill_rotated (void)
{
GskPath *path;
GskPathBuilder *builders[2];
GskPath *paths[2];
guint i, j;
#define N_FILL_RULES 2
/* if this triggers, you added a new enum value to GskFillRule, so the define above needs
* an update */
g_assert_null (g_enum_get_value (g_type_class_ref (GSK_TYPE_FILL_RULE), N_FILL_RULES));
for (i = 0; i < 100; i++)
{
path = create_random_path (G_MAXUINT);
builders[0] = gsk_path_builder_new ();
builders[1] = gsk_path_builder_new ();
/* Use -1 here because we want all the flags, even future additions */
gsk_path_foreach (path, -1, rotate_path_cb, builders);
gsk_path_unref (path);
paths[0] = gsk_path_builder_free_to_path (builders[0]);
paths[1] = gsk_path_builder_free_to_path (builders[1]);
for (j = 0; j < 100; j++)
{
GskFillRule fill_rule = g_random_int_range (0, N_FILL_RULES);
float x = g_test_rand_double_range (-1000, 1000);
float y = g_test_rand_double_range (-1000, 1000);
g_assert_cmpint (gsk_path_in_fill (paths[0], &GRAPHENE_POINT_INIT (x, y), fill_rule),
==,
gsk_path_in_fill (paths[1], &GRAPHENE_POINT_INIT (y, -x), fill_rule));
g_assert_cmpint (gsk_path_in_fill (paths[0], &GRAPHENE_POINT_INIT (y, x), fill_rule),
==,
gsk_path_in_fill (paths[1], &GRAPHENE_POINT_INIT (x, -y), fill_rule));
}
gsk_path_unref (paths[0]);
gsk_path_unref (paths[1]);
}
#undef N_FILL_RULES
}
int
main (int argc,
char *argv[])
@@ -649,6 +750,8 @@ main (int argc,
g_test_add_func ("/path/create", test_create);
g_test_add_func ("/path/parse", test_parse);
g_test_add_func ("/path/in-fill-union", test_in_fill_union);
g_test_add_func ("/path/in-fill-rotated", test_in_fill_rotated);
return g_test_run ();
}

View File

@@ -10,7 +10,7 @@ test_calendar_set_day (void)
cal = gtk_calendar_new ();
tz = g_time_zone_new_identifier ("MET");
tz = g_time_zone_new_identifier ("Europe/Brussels");
g_assert_nonnull (tz);
dt = g_date_time_new (tz, 1970, 3, 1, 0, 0, 0);
g_assert_nonnull (dt);

View File

@@ -1,142 +0,0 @@
/* 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

@@ -1,80 +0,0 @@
/* 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

@@ -1,143 +0,0 @@
/* 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

@@ -1,93 +0,0 @@
/* 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);
}
}

View File

@@ -1,134 +0,0 @@
/* 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);
}

View File

@@ -1,139 +0,0 @@
/* 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);
}
}

View File

@@ -1,142 +0,0 @@
/* 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;
}

View File

@@ -1,14 +0,0 @@
#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,14 +23,6 @@ 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',

View File

@@ -1,221 +0,0 @@
/* 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);
}

View File

@@ -1,9 +0,0 @@
#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);