Compare commits

..

21 Commits

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

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

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

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

In addition to the usual contours comprised of Bézier
segments, GskPath supports specialized contours directly,
such as rectangles, rounded rectangles and circles.
2023-08-05 15:03:20 -04:00
Matthias Clasen 471cbc8c41 Add a bounding box type
graphene_rect_t is not well-suited for this purpose,
since you end up with floating-point precision problems
at the upper bound (x + width, y + height).
2023-08-05 15:03:20 -04:00
115 changed files with 16203 additions and 13460 deletions
+5
View File
@@ -336,6 +336,8 @@
<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>
@@ -421,6 +423,9 @@
<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>
+2
View File
@@ -73,6 +73,8 @@ demos = files([
'panes.c',
'password_entry.c',
'path_fill.c',
'path_maze.c',
'path_text.c',
'peg_solitaire.c',
'pickers.c',
'printing.c',
+256 -92
View File
@@ -1,7 +1,7 @@
/* Path/Fill and Stroke
/* Path/Fill
*
* This demo shows how to use GskPath to draw shapes that are (a bit)
* more complex than a rounded rectangle.
* This demo shows how to use PangoCairo to draw text with more than
* just a single color.
*/
#include <glib/gi18n.h>
@@ -9,162 +9,305 @@
#include "paintable.h"
#define GTK_TYPE_LOGO_PAINTABLE (gtk_logo_paintable_get_type ())
G_DECLARE_FINAL_TYPE (GtkLogoPaintable, gtk_logo_paintable, GTK, LOGO_PAINTABLE, GObject)
#define GTK_TYPE_PATH_PAINTABLE (gtk_path_paintable_get_type ())
G_DECLARE_FINAL_TYPE (GtkPathPaintable, gtk_path_paintable, GTK, PATH_PAINTABLE, GObject)
struct _GtkLogoPaintable
struct _GtkPathPaintable
{
GObject parent_instance;
int width;
int height;
GskPath *path[3];
GdkRGBA color[3];
GskPath *stroke_path;
GskStroke *stroke1;
GskStroke *stroke2;
GdkRGBA stroke_color;
GskPath *path;
GdkPaintable *background;
};
struct _GtkLogoPaintableClass
struct _GtkPathPaintableClass
{
GObjectClass parent_class;
};
static int
gtk_logo_paintable_get_intrinsic_width (GdkPaintable *paintable)
gtk_path_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (paintable);
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
return self->width;
if (self->background)
return MAX (gdk_paintable_get_intrinsic_width (self->background), self->width);
else
return self->width;
}
static int
gtk_logo_paintable_get_intrinsic_height (GdkPaintable *paintable)
gtk_path_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (paintable);
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
return self->height;
if (self->background)
return MAX (gdk_paintable_get_intrinsic_height (self->background), self->height);
else
return self->height;
}
static void
gtk_logo_paintable_snapshot (GdkPaintable *paintable,
gtk_path_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (paintable);
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
for (unsigned int i = 0; i < 3; i++)
#if 0
gtk_snapshot_push_fill (snapshot, self->path, GSK_FILL_RULE_WINDING);
#else
GskStroke *stroke = gsk_stroke_new (2.0);
gtk_snapshot_push_stroke (snapshot, self->path, stroke);
gsk_stroke_free (stroke);
#endif
if (self->background)
{
gtk_snapshot_push_fill (snapshot, self->path[i], GSK_FILL_RULE_WINDING);
gtk_snapshot_append_color (snapshot,
&self->color[i],
&GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gdk_paintable_snapshot (self->background, snapshot, width, height);
}
for (unsigned int i = 0; i < 3; i++)
else
{
gtk_snapshot_push_stroke (snapshot, self->stroke_path, self->stroke1);
gtk_snapshot_append_color (snapshot,
&self->stroke_color,
&GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (0, 0, width, height),
&GRAPHENE_POINT_INIT (0, 0),
&GRAPHENE_POINT_INIT (width, height),
(GskColorStop[8]) {
{ 0.0, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.2, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.3, { 1.0, 1.0, 0.0, 1.0 } },
{ 0.4, { 0.0, 1.0, 0.0, 1.0 } },
{ 0.6, { 0.0, 1.0, 1.0, 1.0 } },
{ 0.7, { 0.0, 0.0, 1.0, 1.0 } },
{ 0.8, { 1.0, 0.0, 1.0, 1.0 } },
{ 1.0, { 1.0, 0.0, 1.0, 1.0 } }
},
8);
}
gtk_snapshot_push_stroke (snapshot, self->stroke_path, self->stroke2);
gtk_snapshot_append_color (snapshot,
&self->stroke_color,
&GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
}
static GdkPaintableFlags
gtk_logo_paintable_get_flags (GdkPaintable *paintable)
gtk_path_paintable_get_flags (GdkPaintable *paintable)
{
return GDK_PAINTABLE_STATIC_CONTENTS | GDK_PAINTABLE_STATIC_SIZE;
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
if (self->background)
return gdk_paintable_get_flags (self->background);
else
return GDK_PAINTABLE_STATIC_CONTENTS | GDK_PAINTABLE_STATIC_SIZE;
}
static void
gtk_logo_paintable_paintable_init (GdkPaintableInterface *iface)
gtk_path_paintable_paintable_init (GdkPaintableInterface *iface)
{
iface->get_intrinsic_width = gtk_logo_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gtk_logo_paintable_get_intrinsic_height;
iface->snapshot = gtk_logo_paintable_snapshot;
iface->get_flags = gtk_logo_paintable_get_flags;
iface->get_intrinsic_width = gtk_path_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gtk_path_paintable_get_intrinsic_height;
iface->snapshot = gtk_path_paintable_snapshot;
iface->get_flags = gtk_path_paintable_get_flags;
}
/* When defining the GType, we need to implement the GdkPaintable interface */
G_DEFINE_TYPE_WITH_CODE (GtkLogoPaintable, gtk_logo_paintable, G_TYPE_OBJECT,
G_DEFINE_TYPE_WITH_CODE (GtkPathPaintable, gtk_path_paintable, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gtk_logo_paintable_paintable_init))
gtk_path_paintable_paintable_init))
static void
gtk_logo_paintable_dispose (GObject *object)
/* Here's the boilerplate for the GObject declaration.
* We need to disconnect the signals here that we set up elsewhere
*/
static void
gtk_path_paintable_dispose (GObject *object)
{
GtkLogoPaintable *self = GTK_LOGO_PAINTABLE (object);
GtkPathPaintable *self = GTK_PATH_PAINTABLE (object);
for (unsigned int i = 0; i < 3; i++)
gsk_path_unref (self->path[i]);
if (self->background)
{
g_signal_handlers_disconnect_by_func (self->background, gdk_paintable_invalidate_contents, self);
g_signal_handlers_disconnect_by_func (self->background, gdk_paintable_invalidate_size, self);
g_clear_object (&self->background);
}
gsk_path_unref (self->stroke_path);
gsk_stroke_free (self->stroke1);
gsk_stroke_free (self->stroke2);
G_OBJECT_CLASS (gtk_logo_paintable_parent_class)->dispose (object);
G_OBJECT_CLASS (gtk_path_paintable_parent_class)->dispose (object);
}
static void
gtk_logo_paintable_class_init (GtkLogoPaintableClass *klass)
gtk_path_paintable_class_init (GtkPathPaintableClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_logo_paintable_dispose;
object_class->dispose = gtk_path_paintable_dispose;
}
static void
gtk_logo_paintable_init (GtkLogoPaintable *self)
gtk_path_paintable_init (GtkPathPaintable *self)
{
}
static GdkPaintable *
gtk_logo_paintable_new (void)
/* And finally, we add a simple constructor.
* It is declared in the header so that the other examples
* can use it.
*/
GdkPaintable *
gtk_path_paintable_new (GskPath *path,
GdkPaintable *background,
int width,
int height)
{
GtkLogoPaintable *self;
graphene_rect_t bounds, bounds2;
GtkPathPaintable *self;
self = g_object_new (GTK_TYPE_LOGO_PAINTABLE, NULL);
/* Paths and colors extracted from gtk-logo.svg */
self->path[0] = gsk_path_parse ("m3.12,66.17 -2.06,-51.46 32.93,24.7 v55.58 l-30.87,-28.82 z");
self->path[1] = gsk_path_parse ("m34,95 49.4,-20.58 4.12,-51.46 -53.52,16.47 v55.58 z");
self->path[2] = gsk_path_parse ("m1.06,14.71 32.93,24.7 53.52,-16.47 -36.75,-21.88 -49.7,13.65 z");
gdk_rgba_parse (&self->color[0], "#e40000");
gdk_rgba_parse (&self->color[1], "#7fe719");
gdk_rgba_parse (&self->color[2], "#729fcf");
self->stroke_path = gsk_path_parse ("m50.6,51.3 -47.3,14 z l33,23 z v-50");
self->stroke1 = gsk_stroke_new (2.12);
self->stroke2 = gsk_stroke_new (1.25);
gdk_rgba_parse (&self->stroke_color, "#ffffff");
gsk_path_get_stroke_bounds (self->path[0], self->stroke1, &bounds);
gsk_path_get_stroke_bounds (self->path[1], self->stroke1, &bounds2);
graphene_rect_union (&bounds, &bounds2, &bounds);
gsk_path_get_stroke_bounds (self->path[2], self->stroke1, &bounds2);
graphene_rect_union (&bounds, &bounds2, &bounds);
gsk_path_get_stroke_bounds (self->stroke_path, self->stroke2, &bounds2);
graphene_rect_union (&bounds, &bounds2, &bounds);
self->width = bounds.origin.x + bounds.size.width;
self->height = bounds.origin.y + bounds.size.height;
self = g_object_new (GTK_TYPE_PATH_PAINTABLE, NULL);
self->path = path;
self->background = background;
if (self->background)
{
g_object_ref (self->background);
g_signal_connect_swapped (self->background, "invalidate-contents", G_CALLBACK (gdk_paintable_invalidate_contents), self);
g_signal_connect_swapped (self->background, "invalidate-size", G_CALLBACK (gdk_paintable_invalidate_size), self);
}
self->width = width;
self->height = height;
return GDK_PAINTABLE (self);
}
void
gtk_path_paintable_set_path (GtkPathPaintable *self,
GskPath *path)
{
g_clear_pointer (&self->path, gsk_path_unref);
self->path = gsk_path_ref (path);
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
}
static GskPath *
create_hexagon (GtkWidget *widget)
{
GskPathBuilder *builder;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, 120, 0);
gsk_path_builder_line_to (builder, 360, 0);
gsk_path_builder_line_to (builder, 480, 208);
gsk_path_builder_line_to (builder, 360, 416);
gsk_path_builder_line_to (builder, 120, 416);
gsk_path_builder_line_to (builder, 0, 208);
gsk_path_builder_close (builder);
return gsk_path_builder_free_to_path (builder);
}
static GskPath *
create_path_from_text (GtkWidget *widget)
{
PangoLayout *layout;
PangoFontDescription *desc;
GskPathBuilder *builder;
layout = gtk_widget_create_pango_layout (widget, "Pango power!\nPango power!\nPango power!");
desc = pango_font_description_from_string ("sans bold 36");
pango_layout_set_font_description (layout, desc);
pango_font_description_free (desc);
builder = gsk_path_builder_new ();
gsk_path_builder_add_layout (builder, layout);
return gsk_path_builder_free_to_path (builder);
}
static gboolean
build_path (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathBuilder *builder = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builder);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
break;
case GSK_PATH_CUBIC:
gsk_path_builder_cubic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
break;
default:
g_assert_not_reached ();
break;
}
return TRUE;
}
static gboolean
update_path (GtkWidget *widget,
GdkFrameClock *frame_clock,
gpointer measure)
{
float progress = gdk_frame_clock_get_frame_time (frame_clock) % (60 * G_USEC_PER_SEC) / (float) (30 * G_USEC_PER_SEC);
GskPathBuilder *builder;
GskPath *path;
GskPathPoint point;
graphene_point_t pos;
graphene_vec2_t tangent;
GskStroke *stroke;
path = gsk_path_measure_get_path (measure);
stroke = gsk_stroke_new (1);
gsk_stroke_set_dash (stroke, (float[2]) { 10, 5 }, 2);
gsk_stroke_set_dash_offset (stroke, - (gdk_frame_clock_get_frame_time (frame_clock) % G_USEC_PER_SEC) * 15. / G_USEC_PER_SEC);
builder = gsk_path_builder_new ();
gsk_path_dash (path, stroke, 0.2, build_path, builder);
if (gsk_path_measure_get_point (measure,
(progress > 1 ? (progress - 1) : progress) * gsk_path_measure_get_length (measure), &point))
{
gsk_path_point_get_position (&point, &pos);
gsk_path_point_get_tangent (&point, GSK_PATH_END, &tangent);
gsk_path_builder_move_to (builder, pos.x + 5 * graphene_vec2_get_x (&tangent), pos.y + 5 * graphene_vec2_get_y (&tangent));
gsk_path_builder_line_to (builder, pos.x + 3 * graphene_vec2_get_y (&tangent), pos.y + 3 * graphene_vec2_get_x (&tangent));
gsk_path_builder_line_to (builder, pos.x - 3 * graphene_vec2_get_y (&tangent), pos.y - 3 * graphene_vec2_get_x (&tangent));
gsk_path_builder_close (builder);
path = gsk_path_builder_free_to_path (builder);
gtk_path_paintable_set_path (GTK_PATH_PAINTABLE (gtk_picture_get_paintable (GTK_PICTURE (widget))),
path);
gsk_path_unref (path);
}
return G_SOURCE_CONTINUE;
}
GtkWidget *
do_path_fill (GtkWidget *do_widget)
{
@@ -174,14 +317,35 @@ do_path_fill (GtkWidget *do_widget)
{
GtkWidget *picture;
GdkPaintable *paintable;
GtkMediaStream *stream;
GskPath *path;
graphene_rect_t bounds;
GskPathMeasure *measure;
window = gtk_window_new ();
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_window_set_title (GTK_WINDOW (window), "Path Fill");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
paintable = gtk_logo_paintable_new ();
#if 0
stream = gtk_media_file_new_for_resource ("/images/gtk-logo.webm");
#else
stream = gtk_nuclear_media_stream_new ();
#endif
gtk_media_stream_play (stream);
gtk_media_stream_set_loop (stream, TRUE);
path = create_hexagon (window);
path = create_path_from_text (window);
gsk_path_get_bounds (path, &bounds);
paintable = gtk_path_paintable_new (path,
GDK_PAINTABLE (stream),
bounds.origin.x + bounds.size.width,
bounds.origin.y + bounds.size.height);
picture = gtk_picture_new_for_paintable (paintable);
measure = gsk_path_measure_new (path);
gtk_widget_add_tick_callback (picture, update_path, measure, (GDestroyNotify) gsk_path_measure_unref);
gtk_picture_set_content_fit (GTK_PICTURE (picture), GTK_CONTENT_FIT_CONTAIN);
gtk_picture_set_can_shrink (GTK_PICTURE (picture), FALSE);
g_object_unref (paintable);
+346
View File
@@ -0,0 +1,346 @@
/* Path/Maze
*
* This demo shows how to use a GskPath to create a maze and use
* gsk_path_measure_get_closest_point() to check the mouse stays
* on the path.
*
* It also shows off the performance of GskPath (or not) as this
* is a rather complex path.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "paintable.h"
#define MAZE_GRID_SIZE 20
#define MAZE_STROKE_SIZE_ACTIVE (MAZE_GRID_SIZE - 4)
#define MAZE_STROKE_SIZE_INACTIVE (MAZE_GRID_SIZE - 12)
#define MAZE_WIDTH 31
#define MAZE_HEIGHT 21
#define GTK_TYPE_MAZE (gtk_maze_get_type ())
G_DECLARE_FINAL_TYPE (GtkMaze, gtk_maze, GTK, MAZE, GtkWidget)
struct _GtkMaze
{
GtkWidget parent_instance;
int width;
int height;
GskPath *path;
GskPathMeasure *measure;
GdkPaintable *background;
gboolean active;
};
struct _GtkMazeClass
{
GtkWidgetClass parent_class;
};
G_DEFINE_TYPE (GtkMaze, gtk_maze, GTK_TYPE_WIDGET)
static void
gtk_maze_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkMaze *self = GTK_MAZE (widget);
if (orientation == GTK_ORIENTATION_HORIZONTAL)
*minimum = *natural = self->width;
else
*minimum = *natural = self->height;
}
static void
gtk_maze_snapshot (GtkWidget *widget,
GdkSnapshot *snapshot)
{
GtkMaze *self = GTK_MAZE (widget);
double width = gtk_widget_get_width (widget);
double height = gtk_widget_get_height (widget);
GskStroke *stroke;
stroke = gsk_stroke_new (MAZE_STROKE_SIZE_INACTIVE);
if (self->active)
gsk_stroke_set_line_width (stroke, MAZE_STROKE_SIZE_ACTIVE);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_ROUND);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_ROUND);
gtk_snapshot_push_stroke (snapshot, self->path, stroke);
gsk_stroke_free (stroke);
if (self->background)
{
gdk_paintable_snapshot (self->background, snapshot, width, height);
}
else
{
gtk_snapshot_append_linear_gradient (snapshot,
&GRAPHENE_RECT_INIT (0, 0, width, height),
&GRAPHENE_POINT_INIT (0, 0),
&GRAPHENE_POINT_INIT (width, height),
(GskColorStop[8]) {
{ 0.0, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.2, { 1.0, 0.0, 0.0, 1.0 } },
{ 0.3, { 1.0, 1.0, 0.0, 1.0 } },
{ 0.4, { 0.0, 1.0, 0.0, 1.0 } },
{ 0.6, { 0.0, 1.0, 1.0, 1.0 } },
{ 0.7, { 0.0, 0.0, 1.0, 1.0 } },
{ 0.8, { 1.0, 0.0, 1.0, 1.0 } },
{ 1.0, { 1.0, 0.0, 1.0, 1.0 } }
},
8);
}
gtk_snapshot_pop (snapshot);
}
static void
gtk_maze_dispose (GObject *object)
{
GtkMaze *self = GTK_MAZE (object);
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->measure, gsk_path_measure_unref);
if (self->background)
{
g_signal_handlers_disconnect_matched (self->background, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self);
g_clear_object (&self->background);
}
G_OBJECT_CLASS (gtk_maze_parent_class)->dispose (object);
}
static void
gtk_maze_class_init (GtkMazeClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_maze_dispose;
widget_class->measure = gtk_maze_measure;
widget_class->snapshot = gtk_maze_snapshot;
}
static void
pointer_motion (GtkEventControllerMotion *controller,
double x,
double y,
GtkMaze *self)
{
GskPathPoint point;
graphene_point_t pos;
if (!self->active)
return;
if (gsk_path_get_closest_point (self->path, &GRAPHENE_POINT_INIT (x, y), INFINITY, &point))
{
gsk_path_point_get_position (&point, &pos);
if (graphene_point_distance (&pos, &GRAPHENE_POINT_INIT (x, y), NULL, NULL) <= MAZE_STROKE_SIZE_ACTIVE / 2.0f)
return;
self->active = FALSE;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
}
static void
pointer_leave (GtkEventControllerMotion *controller,
GtkMaze *self)
{
if (!self->active)
{
self->active = TRUE;
gtk_widget_queue_draw (GTK_WIDGET (self));
}
}
static void
gtk_maze_init (GtkMaze *self)
{
GtkEventController *controller;
controller = GTK_EVENT_CONTROLLER (gtk_event_controller_motion_new ());
g_signal_connect (controller, "motion", G_CALLBACK (pointer_motion), self);
g_signal_connect (controller, "leave", G_CALLBACK (pointer_leave), self);
gtk_widget_add_controller (GTK_WIDGET (self), controller);
self->active = TRUE;
}
static void
gtk_maze_set_path (GtkMaze *self,
GskPath *path)
{
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->measure, gsk_path_measure_unref);
self->path = gsk_path_ref (path);
self->measure = gsk_path_measure_new (path);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
GtkWidget *
gtk_maze_new (GskPath *path,
GdkPaintable *background,
int width,
int height)
{
GtkMaze *self;
self = g_object_new (GTK_TYPE_MAZE, NULL);
gtk_maze_set_path (self, path);
gsk_path_unref (path);
self->background = background;
if (self->background)
{
g_signal_connect_swapped (self->background, "invalidate-contents", G_CALLBACK (gtk_widget_queue_draw), self);
g_signal_connect_swapped (self->background, "invalidate-size", G_CALLBACK (gtk_widget_queue_resize), self);
}
self->width = width;
self->height = height;
return GTK_WIDGET (self);
}
static void
add_point_to_maze (GtkBitset *maze,
GskPathBuilder *builder,
guint x,
guint y)
{
gboolean set[4] = { FALSE, FALSE, FALSE, FALSE };
guint dir;
gtk_bitset_add (maze, y * MAZE_WIDTH + x);
while (TRUE)
{
set[0] = set[0] || x == 0 || gtk_bitset_contains (maze, y * MAZE_WIDTH + x - 1);
set[1] = set[1] || y == 0 || gtk_bitset_contains (maze, (y - 1) * MAZE_WIDTH + x);
set[2] = set[2] || x + 1 == MAZE_WIDTH || gtk_bitset_contains (maze, y * MAZE_WIDTH + x + 1);
set[3] = set[3] || y + 1 == MAZE_HEIGHT || gtk_bitset_contains (maze, (y + 1) * MAZE_WIDTH + x);
if (set[0] && set[1] && set[2] && set[3])
return;
do
{
dir = g_random_int_range (0, 4);
}
while (set[dir]);
switch (dir)
{
case 0:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x - 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x - 1, y);
break;
case 1:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y - 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x, y - 1);
break;
case 2:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 1.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x + 1, y);
break;
case 3:
gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 1.5) * MAZE_GRID_SIZE);
add_point_to_maze (maze, builder, x, y + 1);
break;
default:
g_assert_not_reached ();
break;
}
}
}
static GskPath *
create_path_for_maze (GtkWidget *widget)
{
GskPathBuilder *builder;
GtkBitset *maze;
builder = gsk_path_builder_new ();
maze = gtk_bitset_new_empty ();
/* make sure the outer lines are unreachable:
* Set the full range, then remove the center again. */
gtk_bitset_add_range (maze, 0, MAZE_WIDTH * MAZE_HEIGHT);
gtk_bitset_remove_rectangle (maze, MAZE_WIDTH + 1, MAZE_WIDTH - 2, MAZE_HEIGHT - 2, MAZE_WIDTH);
/* Fill the maze */
add_point_to_maze (maze, builder, MAZE_WIDTH / 2, MAZE_HEIGHT / 2);
/* Add start and stop lines */
gsk_path_builder_move_to (builder, 1.5 * MAZE_GRID_SIZE, -0.5 * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, 1.5 * MAZE_GRID_SIZE, 1.5 * MAZE_GRID_SIZE);
gsk_path_builder_move_to (builder, (MAZE_WIDTH - 1.5) * MAZE_GRID_SIZE, (MAZE_HEIGHT - 1.5) * MAZE_GRID_SIZE);
gsk_path_builder_line_to (builder, (MAZE_WIDTH - 1.5) * MAZE_GRID_SIZE, (MAZE_HEIGHT + 0.5) * MAZE_GRID_SIZE);
gtk_bitset_unref (maze);
return gsk_path_builder_free_to_path (builder);
}
GtkWidget *
do_path_maze (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkWidget *maze;
GtkMediaStream *stream;
GskPath *path;
window = gtk_window_new ();
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_window_set_title (GTK_WINDOW (window), "Follow the maze with the mouse");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
#if 0
stream = gtk_media_file_new_for_resource ("/images/gtk-logo.webm");
#else
stream = gtk_nuclear_media_stream_new ();
#endif
gtk_media_stream_play (stream);
gtk_media_stream_set_loop (stream, TRUE);
path = create_path_for_maze (window);
maze = gtk_maze_new (path,
GDK_PAINTABLE (stream),
MAZE_WIDTH * MAZE_GRID_SIZE,
MAZE_HEIGHT * MAZE_GRID_SIZE);
gtk_window_set_child (GTK_WINDOW (window), maze);
}
if (!gtk_widget_get_visible (window))
gtk_window_present (GTK_WINDOW (window));
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}
+612
View File
@@ -0,0 +1,612 @@
/* 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;
}
+38
View File
@@ -0,0 +1,38 @@
<?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>
-6
View File
@@ -34,11 +34,5 @@ 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.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

-81
View File
@@ -1,81 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

-72
View File
@@ -1,72 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

-108
View File
@@ -1,108 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 4.4 KiB

+26 -86
View File
@@ -13,6 +13,7 @@ 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>
@@ -36,14 +37,31 @@ 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-quad``
Allow quadratic Bézier curves to be used in the generated path.
``--allow-cubic``
``--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
^^^^^^^
@@ -57,7 +75,7 @@ of the path is filled.
``--fg-color=COLOR``
The color that is used to fill the interior of the path or stroke the path.
The color that is used to fill the interior of the path.
If not specified, black is used.
``--bg-color=COLOR``
@@ -65,45 +83,6 @@ of the path is filled.
The color that is used to render the background behind the path.
If not specified, white is used.
``--stroke``
Stroke the path instead of filling it.
``--line-width=VALUE``
The line width to use for the stroke. ``VALUE`` must be a positive number.
The default line width is 1.
``--line-cap=VALUE``
The cap style to use at line ends. The possible values are ``butt``, ``round``
or ``square``. See the SVG specification for details on these styles.
The default cap style is ``butt``.
``--line-join=VALUE``
The join style to use at line joins. The possible values are ``miter``,
``miter-clip``, ``round``, ``bevel`` or ``arcs``. See the SVG specification
for details on these styles.
The default join style is ``miter``.
``--miter-limit=VALUE``
The limit at which to clip miters at line joins. The default value is 4.
``--dashes=VALUE``
The dash pattern to use for this stroke. A dash pattern is specified by
a comma-separated list of alternating non-negative numbers. Each number
provides the length of alternate "on" and "off" portions of the stroke.
If the dash pattern is empty, dashing is disabled, which is the default.
See the SVG specification for details on dashing.
``--dash-offset=VALUE``
The offset into the dash pattern where dashing should begin.
The default value is 0.
Rendering
^^^^^^^^^
@@ -117,7 +96,7 @@ The interior of the path is filled.
``--fg-color=COLOR``
The color that is used to fill the interior of the path or stroke the path.
The color that is used to fill the interior of the path.
If not specified, black is used.
``--bg-color=COLOR``
@@ -130,50 +109,11 @@ The interior of the path is filled.
The file to save the PNG image to.
If not specified, "path.png" is used.
``--stroke``
Stroke the path instead of filling it.
``--line-width=VALUE``
The line width to use for the stroke. ``VALUE`` must be a positive number.
The default line width is 1.
``--line-cap=VALUE``
The cap style to use at line ends. The possible values are ``butt``, ``round``
or ``square``. See the SVG specification for details on these styles.
The default cap style is ``butt``.
``--line-join=VALUE``
The join style to use at line joins. The possible values are ``miter``,
``miter-clip``, ``round``, ``bevel`` or ``arcs``. See the SVG specification
for details on these styles.
The default join style is ``miter``.
``--miter-limit=VALUE``
The limit at which to clip miters at line joins. The default value is 4.
``--dashes=VALUE``
The dash pattern to use for this stroke. A dash pattern is specified by
a comma-separated list of alternating non-negative numbers. Each number
provides the length of alternate "on" and "off" portions of the stroke.
If the dash pattern is empty, dashing is disabled, which is the default.
See the SVG specification for details on dashing.
``--dash-offset=VALUE``
The offset into the dash pattern where dashing should begin.
The default value is 0.
Info
^^^^
The ``info`` command shows various information about the given path,
such as its bounding box.
such as its bounding box and and its length.
REFERENCES
----------
+1 -1
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>
```
+10 -10
View File
@@ -297,7 +297,7 @@ gdk_clipboard_read_local_async (GdkClipboard *clipboard,
{
GOutputStream *output_stream;
GIOStream *stream;
stream = gdk_pipe_io_stream_new ();
output_stream = g_io_stream_get_output_stream (stream);
gdk_clipboard_write_async (clipboard,
@@ -767,7 +767,7 @@ gdk_clipboard_read_value_internal (GdkClipboard *clipboard,
GdkContentFormats *formats;
GValue *value;
GTask *task;
task = g_task_new (clipboard, cancellable, callback, user_data);
g_task_set_priority (task, io_priority);
g_task_set_source_tag (task, source_tag);
@@ -946,7 +946,7 @@ gdk_clipboard_read_texture_finish (GdkClipboard *clipboard,
value = g_task_propagate_pointer (G_TASK (result), error);
if (!value)
return NULL;
return g_value_dup_object (value);
}
@@ -1011,7 +1011,7 @@ gdk_clipboard_read_text_finish (GdkClipboard *clipboard,
value = g_task_propagate_pointer (G_TASK (result), error);
if (!value)
return NULL;
return g_value_dup_string (value);
}
@@ -1110,7 +1110,7 @@ gdk_clipboard_write_async (GdkClipboard *clipboard,
GError *error = NULL;
g_assert (gtype != G_TYPE_INVALID);
g_value_init (&value, gtype);
if (gdk_content_provider_get_value (priv->content, &value, &error))
{
@@ -1126,7 +1126,7 @@ gdk_clipboard_write_async (GdkClipboard *clipboard,
{
g_task_return_error (task, error);
}
g_value_unset (&value);
}
else
@@ -1148,7 +1148,7 @@ gdk_clipboard_write_finish (GdkClipboard *clipboard,
g_return_val_if_fail (g_task_is_valid (result, clipboard), FALSE);
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_clipboard_write_async, FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
return g_task_propagate_boolean (G_TASK (result), error);
}
static gboolean
@@ -1248,8 +1248,8 @@ gdk_clipboard_set_content (GdkClipboard *clipboard,
* Sets the clipboard to contain the value collected from the given varargs.
*
* Values should be passed the same way they are passed to other value
* collecting APIs, such as [method@GObject.Object.set] or
* [func@GObject.signal_emit].
* collecting APIs, such as [`method@GObject.Object.set`] or
* [`func@GObject.signal_emit`].
*
* ```c
* gdk_clipboard_set (clipboard, GTK_TYPE_STRING, "Hello World");
@@ -1263,7 +1263,7 @@ gdk_clipboard_set (GdkClipboard *clipboard,
...)
{
va_list args;
g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
va_start (args, type);
+1 -2
View File
@@ -1528,8 +1528,7 @@ gdk_gl_context_check_extensions (GdkGLContext *context)
if (gdk_gl_context_get_use_es (context))
{
priv->has_unpack_subimage = gdk_gl_version_greater_equal (&priv->gl_version, &GDK_GL_VERSION_INIT (3, 0)) ||
epoxy_has_gl_extension ("GL_EXT_unpack_subimage");
priv->has_unpack_subimage = epoxy_has_gl_extension ("GL_EXT_unpack_subimage");
priv->has_khr_debug = epoxy_has_gl_extension ("GL_KHR_debug");
}
else
+2 -2
View File
@@ -153,7 +153,7 @@ gdk_gl_texture_find_format (gboolean use_es,
if (gdk_memory_format_alpha (format) != alpha)
continue;
if (!gdk_memory_format_gl_format (format, use_es, gl_major, gl_minor, &q_internal_format, &q_format, &q_type, q_swizzle))
if (!gdk_memory_format_gl_format (format, use_es, gl_major, gl_minor, &q_internal_format, &q_format, &q_type, &q_swizzle))
continue;
if (q_format != gl_format || q_type != gl_type)
@@ -188,7 +188,7 @@ gdk_gl_texture_do_download (GdkGLTexture *self,
FALSE,
major, minor,
&gl_internal_format,
&gl_format, &gl_type, gl_swizzle))
&gl_format, &gl_type, &gl_swizzle))
{
if (download->stride == expected_stride &&
download->format == format)
+2 -2
View File
@@ -739,12 +739,12 @@ gdk_memory_format_gl_format (GdkMemoryFormat format,
guint *out_internal_format,
guint *out_format,
guint *out_type,
GLint out_swizzle[4])
GLint (*out_swizzle)[4])
{
*out_internal_format = memory_formats[format].gl.internal_format;
*out_format = memory_formats[format].gl.format;
*out_type = memory_formats[format].gl.type;
memcpy (out_swizzle, memory_formats[format].gl.swizzle, sizeof(GLint) * 4);
memcpy (out_swizzle, &memory_formats[format].gl.swizzle, sizeof(GLint) * 4);
if (gles)
{
+1 -1
View File
@@ -52,7 +52,7 @@ gboolean gdk_memory_format_gl_format (GdkMemoryFormat
guint *out_internal_format,
guint *out_format,
guint *out_type,
GLint out_gizzle[4]);
GLint (*out_gizzle)[4]);
void gdk_memory_convert (guchar *dest_data,
gsize dest_stride,
+3 -1
View File
@@ -27,7 +27,9 @@
#include "gdkmacosdisplay.h"
#include "gdkmacossurface.h"
#import <epoxy/gl.h>
#import <OpenGL/OpenGL.h>
#import <OpenGL/gl3.h>
#import <AppKit/AppKit.h>
G_BEGIN_DECLS
+2 -1
View File
@@ -21,6 +21,7 @@
#include "gdkconfig.h"
#include <OpenGL/gl3.h>
#include <OpenGL/CGLIOSurface.h>
#include <QuartzCore/QuartzCore.h>
@@ -498,7 +499,7 @@ gdk_macos_gl_context_begin_frame (GdkDrawContext *context,
gdk_gl_context_make_current (GDK_GL_CONTEXT (self));
gdk_macos_gl_context_allocate (self);
GDK_DRAW_CONTEXT_CLASS (gdk_macos_gl_context_parent_class)->begin_frame (context, depth, region);
GDK_DRAW_CONTEXT_CLASS (gdk_macos_gl_context_parent_class)->begin_frame (context, prefers_high_depth, region);
gdk_gl_context_make_current (GDK_GL_CONTEXT (self));
CHECK_GL (NULL, glBindFramebuffer (GL_FRAMEBUFFER, self->fbo));
+1 -12
View File
@@ -2857,10 +2857,7 @@ gdk_event_translate (MSG *msg,
break;
case WM_SYSCOMMAND:
/* 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)
switch (msg->wParam)
{
case SC_MINIMIZE:
case SC_RESTORE:
@@ -2967,14 +2964,6 @@ 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;
+8 -1
View File
@@ -1230,11 +1230,18 @@ 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;
*decoration = (GDK_DECOR_ALL | GDK_DECOR_MINIMIZE | GDK_DECOR_MAXIMIZE);
return TRUE;
}
+5 -4
View File
@@ -1456,7 +1456,7 @@ memory_format_gl_format (GdkMemoryFormat data_format,
guint *gl_internalformat,
guint *gl_format,
guint *gl_type,
GLint gl_swizzle[4])
GLint (*gl_swizzle)[4])
{
GdkMemoryDepth depth;
@@ -1577,7 +1577,7 @@ gsk_gl_command_queue_do_upload_texture_chunk (GskGLCommandQueue *self,
&gl_internalformat,
&gl_format,
&gl_type,
gl_swizzle);
&gl_swizzle);
gdk_texture_downloader_init (&downloader, texture);
gdk_texture_downloader_set_format (&downloader, data_format);
@@ -1595,7 +1595,8 @@ gsk_gl_command_queue_do_upload_texture_chunk (GskGLCommandQueue *self,
{
glTexSubImage2D (GL_TEXTURE_2D, 0, x, y, width, height, gl_format, gl_type, data);
}
else if (stride % bpp == 0 && gdk_gl_context_has_unpack_subimage (self->context))
else if (stride % bpp == 0 &&
(gdk_gl_context_check_version (self->context, NULL, "3.0") || gdk_gl_context_has_unpack_subimage (self->context)))
{
glPixelStorei (GL_UNPACK_ROW_LENGTH, stride / bpp);
@@ -1683,7 +1684,7 @@ gsk_gl_command_queue_upload_texture_chunks (GskGLCommandQueue *self,
&gl_internalformat,
&gl_format,
&gl_type,
gl_swizzle);
&gl_swizzle);
glTexImage2D (GL_TEXTURE_2D, 0, gl_internalformat, width, height, 0, gl_format, gl_type, NULL);
+3 -3
View File
@@ -1245,9 +1245,9 @@ gsk_gl_render_job_visit_as_fallback (GskGLRenderJob *job,
done:
if (scale_x < 0 || scale_y < 0)
{
GskTransform *transform = gsk_transform_translate (gsk_transform_scale (NULL, scale_x < 0 ? -1 : 1, scale_y < 0 ? -1 : 1),
&GRAPHENE_POINT_INIT (scale_x < 0 ? - (node->bounds.size.width + 2 * node->bounds.origin.x) : 0,
scale_y < 0 ? - (node->bounds.size.height + 2 * node->bounds.origin.y) : 0));
GskTransform *transform = gsk_transform_translate (NULL,
&GRAPHENE_POINT_INIT (scale_x < 0 ? - surface_width : 0,
scale_y < 0 ? - surface_height : 0));
gsk_gl_render_job_push_modelview (job, transform);
gsk_transform_unref (transform);
}
+1
View File
@@ -22,6 +22,7 @@
#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>
-15
View File
@@ -97,19 +97,4 @@ gsk_bounding_box_intersection (const GskBoundingBox *a,
return min.x <= max.x && min.y <= max.y;
}
static inline void
gsk_bounding_box_union (const GskBoundingBox *a,
const GskBoundingBox *b,
GskBoundingBox *res)
{
graphene_point_t min, max;
min.x = MIN (a->min.x, b->min.x);
min.y = MIN (a->min.y, b->min.y);
max.x = MAX (a->max.x, b->max.x);
max.y = MAX (a->max.y, b->max.y);
gsk_bounding_box_init (res, &min, &max);
}
G_END_DECLS
+1928 -153
View File
File diff suppressed because it is too large Load Diff
+31 -7
View File
@@ -23,10 +23,15 @@
#include "gskpathprivate.h"
#include "gskpathpointprivate.h"
#include "gskpathopprivate.h"
#include "gskboundingboxprivate.h"
G_BEGIN_DECLS
GskContour * gsk_rect_contour_new (const graphene_rect_t *rect);
GskContour * gsk_rounded_rect_contour_new (const GskRoundedRect *rounded_rect);
GskContour * gsk_circle_contour_new (const graphene_point_t *center,
float radius,
float start_angle,
float end_angle);
GskContour * gsk_standard_contour_new (GskPathFlags flags,
const graphene_point_t *points,
gsize n_points,
@@ -44,10 +49,10 @@ GskPathFlags gsk_contour_get_flags (const GskContou
void gsk_contour_print (const GskContour *self,
GString *string);
gboolean gsk_contour_get_bounds (const GskContour *self,
GskBoundingBox *bounds);
graphene_rect_t *bounds);
gboolean gsk_contour_get_stroke_bounds (const GskContour *self,
const GskStroke *stroke,
GskBoundingBox *bounds);
graphene_rect_t *bounds);
gboolean gsk_contour_foreach (const GskContour *self,
float tolerance,
GskPathForeachFunc func,
@@ -57,15 +62,17 @@ 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,
@@ -73,11 +80,28 @@ 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,
GskRealPathPoint *start,
GskRealPathPoint *end);
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);
G_END_DECLS
+684 -11
View File
@@ -35,7 +35,8 @@ struct _GskCurveClass
void (* init_foreach) (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts);
gsize n_pts,
float weight);
void (* print) (const GskCurve *curve,
GString *string);
gskpathop (* pathop) (const GskCurve *curve);
@@ -156,7 +157,8 @@ static void
gsk_line_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts)
gsize n_pts,
float weight)
{
GskLineCurve *self = &curve->line;
@@ -298,7 +300,7 @@ gsk_line_curve_decompose_curve (const GskCurve *curve,
{
const GskLineCurve *self = &curve->line;
return add_curve_func (GSK_PATH_LINE, self->points, 2, user_data);
return add_curve_func (GSK_PATH_LINE, self->points, 2, 0.f, user_data);
}
static void
@@ -375,7 +377,8 @@ static void
gsk_quad_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts)
gsize n_pts,
float weight)
{
GskQuadCurve *self = &curve->quad;
@@ -588,7 +591,7 @@ gsk_curve_add_line_cb (const graphene_point_t *from,
AddLineData *data = user_data;
graphene_point_t p[2] = { *from, *to };
return data->add_curve (GSK_PATH_LINE, p, 2, data->user_data);
return data->add_curve (GSK_PATH_LINE, p, 2, 0.f, data->user_data);
}
static gboolean
@@ -601,13 +604,20 @@ gsk_quad_curve_decompose_curve (const GskCurve *curve,
const GskQuadCurve *self = &curve->quad;
if (flags & GSK_PATH_FOREACH_ALLOW_QUAD)
return add_curve_func (GSK_PATH_QUAD, self->points, 3, user_data);
return add_curve_func (GSK_PATH_QUAD, self->points, 3, 0.f, user_data);
else if (flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
{
GskCurve c;
gsk_curve_elevate (curve, &c);
return add_curve_func (GSK_PATH_CUBIC, c.cubic.points, 4, user_data);
return add_curve_func (GSK_PATH_CUBIC, c.cubic.points, 4, 0.f, user_data);
}
else if (flags & GSK_PATH_FOREACH_ALLOW_CONIC)
{
GskCurve c;
gsk_curve_init_foreach (&c, GSK_PATH_CONIC, self->points, 3, 1.f);
return add_curve_func (GSK_PATH_CUBIC, c.cubic.points, 4, 0.f, user_data);
}
else
{
@@ -736,7 +746,8 @@ static void
gsk_cubic_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts)
gsize n_pts,
float weight)
{
GskCubicCurve *self = &curve->cubic;
@@ -909,6 +920,7 @@ gsk_curve_get_derivative (const GskCurve *curve,
case GSK_PATH_MOVE:
case GSK_PATH_CLOSE:
case GSK_PATH_CONIC:
default:
g_assert_not_reached ();
}
@@ -1056,7 +1068,7 @@ gsk_cubic_curve_decompose_curve (const GskCurve *curve,
const GskCubicCurve *self = &curve->cubic;
if (flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
return add_curve_func (GSK_PATH_CUBIC, self->points, 4, user_data);
return add_curve_func (GSK_PATH_CUBIC, self->points, 4, 0.f, user_data);
/* FIXME: Quadratic (or conic?) approximation */
return gsk_cubic_curve_decompose (curve,
@@ -1171,6 +1183,665 @@ static const GskCurveClass GSK_CUBIC_CURVE_CLASS = {
gsk_cubic_curve_get_tight_bounds,
};
/* }}} */
/* {{{ Conic */
static inline float
gsk_conic_curve_get_weight (const GskConicCurve *self)
{
return self->points[2].x;
}
static void
gsk_conic_curve_ensure_coefficents (const GskConicCurve *curve)
{
GskConicCurve *self = (GskConicCurve *) curve;
float w = gsk_conic_curve_get_weight (self);
const graphene_point_t *pts = self->points;
graphene_point_t pw = GRAPHENE_POINT_INIT (w * pts[1].x, w * pts[1].y);
if (self->has_coefficients)
return;
self->num[2] = pts[0];
self->num[1] = GRAPHENE_POINT_INIT (2 * (pw.x - pts[0].x),
2 * (pw.y - pts[0].y));
self->num[0] = GRAPHENE_POINT_INIT (pts[3].x - 2 * pw.x + pts[0].x,
pts[3].y - 2 * pw.y + pts[0].y);
self->denom[2] = GRAPHENE_POINT_INIT (1, 1);
self->denom[1] = GRAPHENE_POINT_INIT (2 * (w - 1), 2 * (w - 1));
self->denom[0] = GRAPHENE_POINT_INIT (-self->denom[1].x, -self->denom[1].y);
self->has_coefficients = TRUE;
}
static void
gsk_conic_curve_init_from_points (GskConicCurve *self,
const graphene_point_t pts[4])
{
self->op = GSK_PATH_CONIC;
self->has_coefficients = FALSE;
memcpy (self->points, pts, sizeof (graphene_point_t) * 4);
}
static void
gsk_conic_curve_init (GskCurve *curve,
gskpathop op)
{
GskConicCurve *self = &curve->conic;
gsk_conic_curve_init_from_points (self, gsk_pathop_points (op));
}
static void
gsk_conic_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
{
GskConicCurve *self = &curve->conic;
g_assert (n_pts == 3);
gsk_conic_curve_init_from_points (self,
(graphene_point_t[4]) {
pts[0],
pts[1],
GRAPHENE_POINT_INIT (weight, 0),
pts[2]
});
}
static void
gsk_conic_curve_print (const GskCurve *curve,
GString *string)
{
g_string_append_printf (string,
"M %g %g O %g %g %g %g %g",
curve->conic.points[0].x, curve->conic.points[0].y,
curve->conic.points[1].x, curve->conic.points[1].y,
curve->conic.points[3].x, curve->conic.points[3].y,
curve->conic.points[2].x);
}
static gskpathop
gsk_conic_curve_pathop (const GskCurve *curve)
{
const GskConicCurve *self = &curve->conic;
return gsk_pathop_encode (self->op, self->points);
}
static const graphene_point_t *
gsk_conic_curve_get_start_point (const GskCurve *curve)
{
const GskConicCurve *self = &curve->conic;
return &self->points[0];
}
static const graphene_point_t *
gsk_conic_curve_get_end_point (const GskCurve *curve)
{
const GskConicCurve *self = &curve->conic;
return &self->points[3];
}
static void
gsk_conic_curve_get_start_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskConicCurve *self = &curve->conic;
get_tangent (&self->points[0], &self->points[1], tangent);
}
static void
gsk_conic_curve_get_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskConicCurve *self = &curve->conic;
get_tangent (&self->points[1], &self->points[3], tangent);
}
static inline void
gsk_curve_eval_quad (const graphene_point_t quad[3],
float progress,
graphene_point_t *result)
{
*result = GRAPHENE_POINT_INIT ((quad[0].x * progress + quad[1].x) * progress + quad[2].x,
(quad[0].y * progress + quad[1].y) * progress + quad[2].y);
}
static inline void
gsk_conic_curve_eval_point (const GskConicCurve *self,
float progress,
graphene_point_t *point)
{
graphene_point_t num, denom;
gsk_curve_eval_quad (self->num, progress, &num);
gsk_curve_eval_quad (self->denom, progress, &denom);
*point = GRAPHENE_POINT_INIT (num.x / denom.x, num.y / denom.y);
}
static void
gsk_conic_curve_get_point (const GskCurve *curve,
float t,
graphene_point_t *pos)
{
const GskConicCurve *self = &curve->conic;
gsk_conic_curve_ensure_coefficents (self);
gsk_conic_curve_eval_point (self, t, pos);
}
static void
gsk_conic_curve_get_tangent (const GskCurve *curve,
float t,
graphene_vec2_t *tangent)
{
const GskConicCurve *self = &curve->conic;
graphene_point_t tmp;
float w = gsk_conic_curve_get_weight (self);
const graphene_point_t *pts = self->points;
/* The tangent will be 0 in these corner cases, just
* treat it like a line here. */
if ((t <= 0.f && graphene_point_equal (&pts[0], &pts[1])) ||
(t >= 1.f && graphene_point_equal (&pts[1], &pts[3])))
{
graphene_vec2_init (tangent, pts[3].x - pts[0].x, pts[3].y - pts[0].y);
return;
}
gsk_curve_eval_quad ((graphene_point_t[3]) {
GRAPHENE_POINT_INIT ((w - 1) * (pts[3].x - pts[0].x),
(w - 1) * (pts[3].y - pts[0].y)),
GRAPHENE_POINT_INIT (pts[3].x - pts[0].x - 2 * w * (pts[1].x - pts[0].x),
pts[3].y - pts[0].y - 2 * w * (pts[1].y - pts[0].y)),
GRAPHENE_POINT_INIT (w * (pts[1].x - pts[0].x),
w * (pts[1].y - pts[0].y))
},
t,
&tmp);
graphene_vec2_init (tangent, tmp.x, tmp.y);
graphene_vec2_normalize (tangent, tangent);
}
/* See M. Floater, Derivatives of rational Bezier curves */
static float
gsk_conic_curve_get_curvature (const GskCurve *curve,
float t)
{
graphene_point_t p[3], p1[2];
float w, w1[2], w2;
graphene_vec2_t t1, t2, t3;
w = curve->conic.points[2].x;
p[0] = curve->conic.points[0];
p[1] = curve->conic.points[1];
p[2] = curve->conic.points[3];
w1[0] = (1 - t) + t*w;
w1[1] = (1 - t)*w + t;
w2 = (1 - t)*w1[0] + t*w1[1];
p1[0].x = ((1 - t)*p[0].x + t*w*p[1].x)/w1[0];
p1[0].y = ((1 - t)*p[0].y + t*w*p[1].y)/w1[0];
p1[1].x = ((1 - t)*w*p[1].x + t*p[2].x)/w1[1];
p1[1].y = ((1 - t)*w*p[1].y + t*p[2].y)/w1[1];
graphene_vec2_init (&t1, p[1].x - p[0].x, p[1].y - p[0].y);
graphene_vec2_init (&t2, p[2].x - p[1].x, p[2].y - p[1].y);
graphene_vec2_init (&t3, p1[1].x - p1[0].x, p1[1].y - p1[0].y);
return 0.5 * ((w*pow3 (w2))/(pow3 (w1[0])*pow3 (w1[1]))) * (cross (&t1, &t2) / pow3 (graphene_vec2_length (&t3)));
}
static void
gsk_conic_curve_reverse (const GskCurve *curve,
GskCurve *reverse)
{
const GskConicCurve *self = &curve->conic;
reverse->op = GSK_PATH_CONIC;
reverse->conic.points[0] = self->points[3];
reverse->conic.points[1] = self->points[1];
reverse->conic.points[2] = self->points[2];
reverse->conic.points[3] = self->points[0];
reverse->conic.has_coefficients = FALSE;
}
static void
split_bezier3d_recurse (const graphene_point3d_t *p,
int l,
float t,
graphene_point3d_t *left,
graphene_point3d_t *right,
int *lpos,
int *rpos)
{
if (l == 1)
{
left[*lpos] = p[0];
right[*rpos] = p[0];
}
else
{
graphene_point3d_t *np;
int i;
np = g_alloca (sizeof (graphene_point3d_t) * (l - 1));
for (i = 0; i < l - 1; i++)
{
if (i == 0)
{
left[*lpos] = p[i];
(*lpos)++;
}
if (i + 1 == l - 1)
{
right[*rpos] = p[i + 1];
(*rpos)--;
}
graphene_point3d_interpolate (&p[i], &p[i + 1], t, &np[i]);
}
split_bezier3d_recurse (np, l - 1, t, left, right, lpos, rpos);
}
}
static void
split_bezier3d (const graphene_point3d_t *p,
int l,
float t,
graphene_point3d_t *left,
graphene_point3d_t *right)
{
int lpos = 0;
int rpos = l - 1;
split_bezier3d_recurse (p, l, t, left, right, &lpos, &rpos);
}
static void
gsk_conic_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end)
{
const GskConicCurve *self = &curve->conic;
graphene_point3d_t p[3];
graphene_point3d_t l[3], r[3];
graphene_point_t left[4], right[4];
float w;
/* do de Casteljau in homogeneous coordinates... */
w = self->points[2].x;
p[0] = GRAPHENE_POINT3D_INIT (self->points[0].x, self->points[0].y, 1);
p[1] = GRAPHENE_POINT3D_INIT (self->points[1].x * w, self->points[1].y * w, w);
p[2] = GRAPHENE_POINT3D_INIT (self->points[3].x, self->points[3].y, 1);
split_bezier3d (p, 3, progress, l, r);
/* then project the control points down */
left[0] = GRAPHENE_POINT_INIT (l[0].x / l[0].z, l[0].y / l[0].z);
left[1] = GRAPHENE_POINT_INIT (l[1].x / l[1].z, l[1].y / l[1].z);
left[3] = GRAPHENE_POINT_INIT (l[2].x / l[2].z, l[2].y / l[2].z);
right[0] = GRAPHENE_POINT_INIT (r[0].x / r[0].z, r[0].y / r[0].z);
right[1] = GRAPHENE_POINT_INIT (r[1].x / r[1].z, r[1].y / r[1].z);
right[3] = GRAPHENE_POINT_INIT (r[2].x / r[2].z, r[2].y / r[2].z);
/* normalize the outer weights to be 1 by using
* the fact that weights w_i and c*w_i are equivalent
* for any nonzero constant c
*/
for (int i = 0; i < 3; i++)
{
l[i].z /= l[0].z;
r[i].z /= r[2].z;
}
/* normalize the inner weight to be 1 by using
* the fact that w_0*w_2/w_1^2 is a constant for
* all equivalent weights.
*/
left[2] = GRAPHENE_POINT_INIT (l[1].z / sqrt (l[2].z), 0);
right[2] = GRAPHENE_POINT_INIT (r[1].z / sqrt (r[0].z), 0);
if (start)
gsk_curve_init (start, gsk_pathop_encode (GSK_PATH_CONIC, left));
if (end)
gsk_curve_init (end, gsk_pathop_encode (GSK_PATH_CONIC, right));
}
static void
gsk_conic_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment)
{
const GskConicCurve *self = &curve->conic;
graphene_point_t start_num, start_denom;
graphene_point_t mid_num, mid_denom;
graphene_point_t end_num, end_denom;
graphene_point_t ctrl_num, ctrl_denom;
float mid;
if (start <= 0.0f)
return gsk_conic_curve_split (curve, end, segment, NULL);
else if (end >= 1.0f)
return gsk_conic_curve_split (curve, start, NULL, segment);
gsk_conic_curve_ensure_coefficents (self);
gsk_curve_eval_quad (self->num, start, &start_num);
gsk_curve_eval_quad (self->denom, start, &start_denom);
mid = (start + end) / 2;
gsk_curve_eval_quad (self->num, mid, &mid_num);
gsk_curve_eval_quad (self->denom, mid, &mid_denom);
gsk_curve_eval_quad (self->num, end, &end_num);
gsk_curve_eval_quad (self->denom, end, &end_denom);
ctrl_num = GRAPHENE_POINT_INIT (2 * mid_num.x - (start_num.x + end_num.x) / 2,
2 * mid_num.y - (start_num.y + end_num.y) / 2);
ctrl_denom = GRAPHENE_POINT_INIT (2 * mid_denom.x - (start_denom.x + end_denom.x) / 2,
2 * mid_denom.y - (start_denom.y + end_denom.y) / 2);
gsk_conic_curve_init_from_points (&segment->conic,
(graphene_point_t[4]) {
GRAPHENE_POINT_INIT (start_num.x / start_denom.x,
start_num.y / start_denom.y),
GRAPHENE_POINT_INIT (ctrl_num.x / ctrl_denom.x,
ctrl_num.y / ctrl_denom.y),
GRAPHENE_POINT_INIT (ctrl_denom.x / sqrtf (start_denom.x * end_denom.x),
0),
GRAPHENE_POINT_INIT (end_num.x / end_denom.x,
end_num.y / end_denom.y)
});
}
/* taken from Skia, including the very descriptive name */
static gboolean
gsk_conic_curve_too_curvy (const graphene_point_t *start,
const graphene_point_t *mid,
const graphene_point_t *end,
float tolerance)
{
return fabs ((start->x + end->x) * 0.5 - mid->x) > tolerance
|| fabs ((start->y + end->y) * 0.5 - mid->y) > tolerance;
}
static gboolean
gsk_conic_curve_decompose_subdivide (const GskConicCurve *self,
float tolerance,
const graphene_point_t *start,
float start_progress,
const graphene_point_t *end,
float end_progress,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
graphene_point_t mid;
float mid_progress;
mid_progress = (start_progress + end_progress) / 2;
gsk_conic_curve_eval_point (self, mid_progress, &mid);
if (!gsk_conic_curve_too_curvy (start, &mid, end, tolerance))
return add_line_func (start, end, start_progress, end_progress, GSK_CURVE_LINE_REASON_STRAIGHT, user_data);
if (end_progress - start_progress <= MIN_PROGRESS)
return add_line_func (start, end, start_progress, end_progress, GSK_CURVE_LINE_REASON_SHORT, user_data);
return gsk_conic_curve_decompose_subdivide (self, tolerance,
start, start_progress, &mid, mid_progress,
add_line_func, user_data)
&& gsk_conic_curve_decompose_subdivide (self, tolerance,
&mid, mid_progress, end, end_progress,
add_line_func, user_data);
}
static gboolean
gsk_conic_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
const GskConicCurve *self = &curve->conic;
graphene_point_t mid;
gsk_conic_curve_ensure_coefficents (self);
gsk_conic_curve_eval_point (self, 0.5, &mid);
return gsk_conic_curve_decompose_subdivide (self,
tolerance,
&self->points[0],
0.0f,
&mid,
0.5f,
add_line_func,
user_data)
&& gsk_conic_curve_decompose_subdivide (self,
tolerance,
&mid,
0.5f,
&self->points[3],
1.0f,
add_line_func,
user_data);
}
/* See Floater, M: An analysis of cubic approximation schemes
* for conic sections
*/
static void
cubic_approximation (const GskCurve *curve,
GskCurve *cubic)
{
const GskConicCurve *self = &curve->conic;
graphene_point_t p[4];
float w = self->points[2].x;
float w2 = w*w;
float lambda;
lambda = 2 * (6*w2 + 1 - sqrt (3*w2 + 1)) / (12*w2 + 3);
p[0] = self->points[0];
p[3] = self->points[3];
graphene_point_interpolate (&self->points[0], &self->points[1], lambda, &p[1]);
graphene_point_interpolate (&self->points[3], &self->points[1], lambda, &p[2]);
gsk_curve_init (cubic, gsk_pathop_encode (GSK_PATH_CUBIC, p));
}
static gboolean
gsk_conic_is_close_to_cubic (const GskCurve *conic,
const GskCurve *cubic,
float tolerance)
{
float t[] = { 0.1, 0.5, 0.9 };
graphene_point_t p0, p1;
for (int i = 0; i < G_N_ELEMENTS (t); i++)
{
gsk_curve_get_point (conic, t[i], &p0);
gsk_curve_get_point (cubic, t[i], &p1);
if (graphene_point_distance (&p0, &p1, NULL, NULL) > tolerance)
return FALSE;
}
return TRUE;
}
static gboolean gsk_conic_curve_decompose_curve (const GskCurve *curve,
GskPathForeachFlags flags,
float tolerance,
GskCurveAddCurveFunc add_curve_func,
gpointer user_data);
static gboolean
gsk_conic_curve_decompose_or_add (const GskCurve *curve,
const GskCurve *cubic,
float tolerance,
GskCurveAddCurveFunc add_curve_func,
gpointer user_data)
{
if (gsk_conic_is_close_to_cubic (curve, cubic, tolerance))
return add_curve_func (GSK_PATH_CUBIC, cubic->cubic.points, 4, 0.f, user_data);
else
{
GskCurve c1, c2;
GskCurve cc1, cc2;
gsk_conic_curve_split (curve, 0.5, &c1, &c2);
cubic_approximation (&c1, &cc1);
cubic_approximation (&c2, &cc2);
return gsk_conic_curve_decompose_or_add (&c1, &cc1, tolerance, add_curve_func, user_data) &&
gsk_conic_curve_decompose_or_add (&c2, &cc2, tolerance, add_curve_func, user_data);
}
}
static gboolean
gsk_conic_curve_decompose_curve (const GskCurve *curve,
GskPathForeachFlags flags,
float tolerance,
GskCurveAddCurveFunc add_curve_func,
gpointer user_data)
{
const GskConicCurve *self = &curve->conic;
GskCurve c;
if (flags & GSK_PATH_FOREACH_ALLOW_CONIC)
return add_curve_func (GSK_PATH_CONIC,
(const graphene_point_t[3]) { self->points[0],
self->points[1],
self->points[3] },
3,
self->points[2].x,
user_data);
if (flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
{
cubic_approximation (curve, &c);
return gsk_conic_curve_decompose_or_add (curve, &c, tolerance, add_curve_func, user_data);
}
/* FIXME: Quadratic (or conic?) approximation */
return gsk_conic_curve_decompose (curve,
tolerance,
gsk_curve_add_line_cb,
&(AddLineData) { add_curve_func, user_data });
}
static void
gsk_conic_curve_get_bounds (const GskCurve *curve,
GskBoundingBox *bounds)
{
const GskConicCurve *self = &curve->conic;
const graphene_point_t *pts = self->points;
gsk_bounding_box_init (bounds, &pts[0], &pts[3]);
gsk_bounding_box_expand (bounds, &pts[1]);
}
/* Solve N = 0 where N is the numerator of (P/Q)', with
* P = (1-t)^2*a + 2*t*(1-t)*w*b + t^2*c
* Q = (1-t)^2 + 2*t*(1-t)*w + t^2
*/
static int
get_conic_extrema (float a, float b, float c, float w, float t[4])
{
float q, tt;
int n = 0;
float w2 = w*w;
float wac = (w - 1)*(a - c);
if (wac != 0)
{
q = - sqrt (a*a - 4*a*b*w2 + 4*a*c*w2 - 2*a*c + 4*b*b*w2 - 4*b*c*w2 + c*c);
tt = (- q + 2*a*w - a - 2*b*w + c)/(2*wac);
if (acceptable (tt))
t[n++] = tt;
tt = (q + 2*a*w - a - 2*b*w + c)/(2*wac);
if (acceptable (tt))
t[n++] = tt;
}
if (w * (b - c) != 0 && a == c)
t[n++] = 0.5;
if (w == 1 && a - 2*b + c != 0)
{
tt = (a - b) / (a - 2*b + c);
if (acceptable (tt))
t[n++] = tt;
}
return n;
}
static void
gsk_conic_curve_get_tight_bounds (const GskCurve *curve,
GskBoundingBox *bounds)
{
const GskConicCurve *self = &curve->conic;
float w = gsk_conic_curve_get_weight (self);
const graphene_point_t *pts = self->points;
float t[8];
int n;
gsk_bounding_box_init (bounds, &pts[0], &pts[3]);
n = 0;
n += get_conic_extrema (pts[0].x, pts[1].x, pts[3].x, w, &t[n]);
n += get_conic_extrema (pts[0].y, pts[1].y, pts[3].y, w, &t[n]);
for (int i = 0; i < n; i++)
{
graphene_point_t p;
gsk_conic_curve_get_point (curve, t[i], &p);
gsk_bounding_box_expand (bounds, &p);
}
}
static const GskCurveClass GSK_CONIC_CURVE_CLASS = {
gsk_conic_curve_init,
gsk_conic_curve_init_foreach,
gsk_conic_curve_print,
gsk_conic_curve_pathop,
gsk_conic_curve_get_start_point,
gsk_conic_curve_get_end_point,
gsk_conic_curve_get_start_tangent,
gsk_conic_curve_get_end_tangent,
gsk_conic_curve_get_point,
gsk_conic_curve_get_tangent,
gsk_conic_curve_reverse,
gsk_conic_curve_get_curvature,
gsk_conic_curve_split,
gsk_conic_curve_segment,
gsk_conic_curve_decompose,
gsk_conic_curve_decompose_curve,
gsk_conic_curve_get_bounds,
gsk_conic_curve_get_tight_bounds,
};
/* }}} */
/* {{{ API */
@@ -1182,6 +1853,7 @@ get_class (GskPathOperation op)
[GSK_PATH_LINE] = &GSK_LINE_CURVE_CLASS,
[GSK_PATH_QUAD] = &GSK_QUAD_CURVE_CLASS,
[GSK_PATH_CUBIC] = &GSK_CUBIC_CURVE_CLASS,
[GSK_PATH_CONIC] = &GSK_CONIC_CURVE_CLASS,
};
g_assert (op < G_N_ELEMENTS (klasses) && klasses[op] != NULL);
@@ -1201,10 +1873,11 @@ void
gsk_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts)
gsize n_pts,
float weight)
{
memset (curve, 0, sizeof (GskCurve));
get_class (op)->init_foreach (curve, op, pts, n_pts);
get_class (op)->init_foreach (curve, op, pts, n_pts, weight);
}
void
+20 -1
View File
@@ -33,6 +33,7 @@ typedef union _GskCurve GskCurve;
typedef struct _GskLineCurve GskLineCurve;
typedef struct _GskQuadCurve GskQuadCurve;
typedef struct _GskCubicCurve GskCubicCurve;
typedef struct _GskConicCurve GskConicCurve;
struct _GskLineCurve
{
@@ -65,12 +66,28 @@ struct _GskCubicCurve
graphene_point_t coeffs[4];
};
struct _GskConicCurve
{
GskPathOperation op;
gboolean has_coefficients;
/* points[0], points[1], points[3] are the control points,
* points[2].x is the weight
*/
graphene_point_t points[4];
graphene_point_t num[3];
graphene_point_t denom[3];
};
union _GskCurve
{
GskPathOperation op;
GskLineCurve line;
GskQuadCurve quad;
GskCubicCurve cubic;
GskConicCurve conic;
};
typedef enum {
@@ -88,6 +105,7 @@ typedef gboolean (* GskCurveAddLineFunc) (const graphene_point_t *from,
typedef gboolean (* GskCurveAddCurveFunc) (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data);
void gsk_curve_init (GskCurve *curve,
@@ -95,7 +113,8 @@ void gsk_curve_init (GskCurve
void gsk_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts);
gsize n_pts,
float weight);
void gsk_curve_print (const GskCurve *curve,
GString *string);
+16 -17
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,6 +42,8 @@
* @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
@@ -51,9 +53,7 @@
* @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,6 +76,8 @@ 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,
@@ -84,9 +86,7 @@ typedef enum {
GSK_DEBUG_NODE,
GSK_GL_SHADER_NODE,
GSK_TEXTURE_SCALE_NODE,
GSK_MASK_NODE,
GSK_FILL_NODE,
GSK_STROKE_NODE
GSK_MASK_NODE
} GskRenderNodeType;
/**
@@ -220,11 +220,6 @@ 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
@@ -238,6 +233,7 @@ typedef enum {
/**
* GskLineJoin:
* @GSK_LINE_JOIN_MITER: Use a sharp angled corner
* @GSK_LINE_JOIN_MITER_CLIP: Use a sharp, angled corner, at a distance
* @GSK_LINE_JOIN_ROUND: Use a round join, the center of the circle is
* the join point
* @GSK_LINE_JOIN_BEVEL: use a cut-off join, the join is cut off at half
@@ -245,12 +241,10 @@ typedef enum {
*
* Specifies how to render the junction of two lines when stroking.
*
* The default line join style is `GSK_LINE_JOIN_MITER`.
* See [method@Gsk.Stroke.set_miter_limit] for details on the difference
* between `GSK_LINE_JOIN_MITER` and `GSK_LINE_JOIN_MITER_CLIP`.
*
* <picture>
* <source srcset="join-dark.png" media="(prefers-color-scheme: dark)">
* <img alt="Line Join Styles" src="join-light.png">
* </picture>
* The default line join style is `GSK_LINE_JOIN_MITER`.
*
* New entries may be added in future versions.
*
@@ -258,6 +252,7 @@ typedef enum {
*/
typedef enum {
GSK_LINE_JOIN_MITER,
GSK_LINE_JOIN_MITER_CLIP,
GSK_LINE_JOIN_ROUND,
GSK_LINE_JOIN_BEVEL,
} GskLineJoin;
@@ -275,6 +270,9 @@ typedef enum {
* @GSK_PATH_CUBIC: A curve-to operation describing a cubic Bézier curve with 4
* points describing the start point, the two control points and the end point
* of the curve.
* @GSK_PATH_CONIC: A weighted quadratic Bézier curve with 3 points describing
* the start point, control point and end point of the curve. A weight for the
* curve will be passed, too.
*
* Path operations are used to described segments of a `GskPath`.
*
@@ -288,6 +286,7 @@ typedef enum {
GSK_PATH_LINE,
GSK_PATH_QUAD,
GSK_PATH_CUBIC,
GSK_PATH_CONIC,
} GskPathOperation;
/**
+111 -247
View File
@@ -24,32 +24,23 @@
#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
@@ -154,17 +145,14 @@ gsk_path_unref (GskPath *self)
}
const GskContour *
gsk_path_get_contour (const GskPath *self,
gsize i)
gsk_path_get_contour (GskPath *path,
gsize i)
{
if (i < self->n_contours)
return self->contours[i];
else
return NULL;
return path->contours[i];
}
GskPathFlags
gsk_path_get_flags (const GskPath *self)
gsk_path_get_flags (GskPath *self)
{
return self->flags;
}
@@ -178,7 +166,10 @@ gsk_path_get_flags (const GskPath *self)
* for printing.
*
* The string is compatible with
* [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData).
* [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData),
* with the exception that conic curves will generate a string of the
* form "O x1 y1, x2 y2, w" where x1, y1 is the control point, x2, y2
* is the end point, and w is the weight.
*
* Since: 4.14
*/
@@ -233,6 +224,7 @@ static gboolean
gsk_path_to_cairo_add_op (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer cr)
{
switch (op)
@@ -264,6 +256,7 @@ gsk_path_to_cairo_add_op (GskPathOperation op,
cairo_curve_to (cr, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;
case GSK_PATH_CONIC:
default:
g_assert_not_reached ();
return FALSE;
@@ -311,9 +304,9 @@ gsk_path_to_cairo (GskPath *self,
* Returns: the number of contours in @path
*/
gsize
gsk_path_get_n_contours (const GskPath *self)
gsk_path_get_n_contours (GskPath *path)
{
return self->n_contours;
return path->n_contours;
}
/**
@@ -386,33 +379,35 @@ gboolean
gsk_path_get_bounds (GskPath *self,
graphene_rect_t *bounds)
{
GskBoundingBox b;
gsize i;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (bounds != NULL, FALSE);
if (self->n_contours == 0)
for (i = 0; i < self->n_contours; i++)
{
if (gsk_contour_get_bounds (self->contours[i], bounds))
break;
}
if (i >= self->n_contours)
{
graphene_rect_init_from_rect (bounds, graphene_rect_zero ());
return FALSE;
}
gsk_contour_get_bounds (self->contours[0], &b);
for (gsize i = 1; i < self->n_contours; i++)
for (i++; i < self->n_contours; i++)
{
GskBoundingBox tmp;
graphene_rect_t tmp;
gsk_contour_get_bounds (self->contours[i], &tmp);
gsk_bounding_box_union (&b, &tmp, &b);
if (gsk_contour_get_bounds (self->contours[i], &tmp))
graphene_rect_union (bounds, &tmp, bounds);
}
gsk_bounding_box_to_rect (&b, bounds);
return TRUE;
}
/**
/*< private >
* gsk_path_get_stroke_bounds:
* @self: a #GtkPath
* @stroke: stroke parameters
@@ -427,37 +422,38 @@ gsk_path_get_bounds (GskPath *self,
* like miters.
*
* Returns: `TRUE` if the path has bounds, `FALSE` if the path is known
* to be empty and have no bounds.
* Since: 4.14
* to be empty and have no bounds.
*/
gboolean
gsk_path_get_stroke_bounds (GskPath *self,
const GskStroke *stroke,
graphene_rect_t *bounds)
{
GskBoundingBox b;
gsize i;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (bounds != NULL, FALSE);
if (self->n_contours == 0)
for (i = 0; i < self->n_contours; i++)
{
if (gsk_contour_get_stroke_bounds (self->contours[i], stroke, bounds))
break;
}
if (i >= self->n_contours)
{
graphene_rect_init_from_rect (bounds, graphene_rect_zero ());
return FALSE;
}
gsk_contour_get_stroke_bounds (self->contours[0], stroke, &b);
for (gsize i = 1; i < self->n_contours; i++)
for (i++; i < self->n_contours; i++)
{
GskBoundingBox tmp;
graphene_rect_t tmp;
if (gsk_contour_get_stroke_bounds (self->contours[i], stroke, &tmp))
gsk_bounding_box_union (&b, &tmp, &b);
graphene_rect_union (bounds, &tmp, bounds);
}
gsk_bounding_box_to_rect (&b, bounds);
return TRUE;
}
@@ -499,72 +495,6 @@ 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`
@@ -590,28 +520,27 @@ gsk_path_get_closest_point (GskPath *self,
GskPathPoint *result)
{
GskRealPathPoint *res = (GskRealPathPoint *) result;
gboolean found;
g_return_val_if_fail (self != NULL, FALSE);
g_return_val_if_fail (point != NULL, FALSE);
g_return_val_if_fail (threshold >= 0, FALSE);
g_return_val_if_fail (result != NULL, FALSE);
found = FALSE;
for (int i = 0; i < self->n_contours; i++)
{
float distance;
if (gsk_contour_get_closest_point (self->contours[i], point, threshold, res, &distance))
{
found = TRUE;
res->contour = i;
res->contour = self->contours[i];
threshold = distance;
}
}
return found;
if (res->contour != NULL)
return TRUE;
return FALSE;
}
/**
@@ -671,6 +600,7 @@ gsk_path_foreach_trampoline_add_line (const graphene_point_t *from,
return trampoline->func (GSK_PATH_LINE,
(graphene_point_t[2]) { *from, *to },
2,
0.0f,
trampoline->user_data);
}
@@ -678,17 +608,19 @@ static gboolean
gsk_path_foreach_trampoline_add_curve (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
GskPathForeachTrampoline *trampoline = data;
return trampoline->func (op, pts, n_pts, trampoline->user_data);
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
}
static gboolean
gsk_path_foreach_trampoline (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
GskPathForeachTrampoline *trampoline = data;
@@ -698,28 +630,14 @@ gsk_path_foreach_trampoline (GskPathOperation op,
case GSK_PATH_MOVE:
case GSK_PATH_CLOSE:
case GSK_PATH_LINE:
return trampoline->func (op, pts, n_pts, trampoline->user_data);
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
case GSK_PATH_QUAD:
{
GskCurve curve;
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_QUAD)
return trampoline->func (op, pts, n_pts, trampoline->user_data);
else if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
{
return trampoline->func (GSK_PATH_CUBIC,
(graphene_point_t[4]) {
pts[0],
GRAPHENE_POINT_INIT ((pts[0].x + 2 * pts[1].x) / 3,
(pts[0].y + 2 * pts[1].y) / 3),
GRAPHENE_POINT_INIT ((pts[2].x + 2 * pts[1].x) / 3,
(pts[2].y + 2 * pts[1].y) / 3),
pts[2],
},
4,
trampoline->user_data);
}
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_QUAD, pts));
return gsk_curve_decompose (&curve,
@@ -733,10 +651,10 @@ gsk_path_foreach_trampoline (GskPathOperation op,
GskCurve curve;
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
return trampoline->func (op, pts, n_pts, trampoline->user_data);
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_CUBIC, pts));
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_QUAD)
if (trampoline->flags & (GSK_PATH_FOREACH_ALLOW_QUAD|GSK_PATH_FOREACH_ALLOW_CONIC))
return gsk_curve_decompose_curve (&curve,
trampoline->flags,
trampoline->tolerance,
@@ -749,6 +667,28 @@ gsk_path_foreach_trampoline (GskPathOperation op,
trampoline);
}
case GSK_PATH_CONIC:
{
GskCurve curve;
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CONIC)
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_CONIC, (graphene_point_t[4]) { pts[0], pts[1], { weight, }, pts[2] } ));
if (trampoline->flags & (GSK_PATH_FOREACH_ALLOW_QUAD|GSK_PATH_FOREACH_ALLOW_CUBIC))
return gsk_curve_decompose_curve (&curve,
trampoline->flags,
trampoline->tolerance,
gsk_path_foreach_trampoline_add_curve,
trampoline);
return gsk_curve_decompose (&curve,
trampoline->tolerance,
gsk_path_foreach_trampoline_add_line,
trampoline);
}
default:
g_assert_not_reached ();
return FALSE;
@@ -756,7 +696,8 @@ gsk_path_foreach_trampoline (GskPathOperation op,
}
#define ALLOW_ANY (GSK_PATH_FOREACH_ALLOW_QUAD| \
GSK_PATH_FOREACH_ALLOW_CUBIC)
GSK_PATH_FOREACH_ALLOW_CUBIC| \
GSK_PATH_FOREACH_ALLOW_CONIC)
gboolean
gsk_path_foreach_with_tolerance (GskPath *self,
@@ -1013,22 +954,6 @@ 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
*
@@ -1356,6 +1281,37 @@ gsk_path_parse (const char *string)
}
break;
case 'O':
case 'o':
{
double x1, y1, x2, y2, weight;
if (parse_coordinate_pair (&p, &x1, &y1) &&
parse_coordinate_pair (&p, &x2, &y2) &&
parse_nonnegative_number (&p, &weight))
{
if (cmd == 'c')
{
x1 += x;
y1 += y;
x2 += x;
y2 += y;
}
if (_strchr ("zZ", prev_cmd))
{
gsk_path_builder_move_to (builder, x, y);
path_x = x;
path_y = y;
}
gsk_path_builder_conic_to (builder, x1, y1, x2, y2, weight);
x = x2;
y = y2;
}
else
goto error;
}
break;
case 'A':
case 'a':
{
@@ -1413,95 +1369,3 @@ error:
return NULL;
}
/**
* gsk_path_get_position:
* @self: a `GskPath`
* @point: a `GskPathPoint` on @path
* @position: (out caller-allocates): Return location for
* the coordinates of the point
*
* Gets the position of the point.
*
* Since: 4.14
*/
void
gsk_path_get_position (GskPath *self,
const GskPathPoint *point,
graphene_point_t *position)
{
GskRealPathPoint *p = (GskRealPathPoint *) point;
g_return_if_fail (self != NULL);
g_return_if_fail (point != NULL);
g_return_if_fail (position != NULL);
g_return_if_fail (p->contour < self->n_contours);
gsk_contour_get_position (self->contours[p->contour], p, position);
}
/**
* gsk_path_get_tangent:
* @self: 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
*
* Gets the tangent of the path at the point.
*
* Note that certain points on a path may not have a single
* tangent, such as sharp turns. At such points, there are
* two tangents -- the direction of the path going into the
* point, and the direction coming out of it. The @direction
* argument lets you choose which one to get.
*
* Since: 4.14
*/
void
gsk_path_get_tangent (GskPath *self,
const GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent)
{
GskRealPathPoint *p = (GskRealPathPoint *) point;
g_return_if_fail (self != NULL);
g_return_if_fail (point != NULL);
g_return_if_fail (tangent != NULL);
g_return_if_fail (p->contour < self->n_contours);
gsk_contour_get_tangent (self->contours[p->contour], p, direction, tangent);
}
/**
* gsk_path_get_curvature:
* @self: 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.
*
* Optionally, returns the center of the osculating circle as well.
*
* If the curvature is infinite (at line segments), zero is returned,
* and @center is not modified.
*
* Returns: The curvature of the path at the given point
*
* Since: 4.14
*/
float
gsk_path_get_curvature (GskPath *self,
const GskPathPoint *point,
graphene_point_t *center)
{
GskRealPathPoint *p = (GskRealPathPoint *) point;
g_return_val_if_fail (self != NULL, 0);
g_return_val_if_fail (point != NULL, 0);
g_return_val_if_fail (p->contour < self->n_contours, 0);
return gsk_contour_get_curvature (self->contours[p->contour], p, center);
}
+14 -36
View File
@@ -30,10 +30,9 @@ G_BEGIN_DECLS
/**
* GskPathForeachFlags:
* @GSK_PATH_FOREACH_ALLOW_ONLY_LINES: The default behavior, only allow lines.
* @GSK_PATH_FOREACH_ALLOW_QUAD: Allow emission of `GSK_PATH_QUAD` operations
* @GSK_PATH_FOREACH_ALLOW_CUBIC: Allow emission of `GSK_PATH_CUBIC` operations.
* @GSK_PATH_FOREACH_ALLOW_ANY: Allow emission of any kind of operation.
* @GSK_PATH_FOREACH_ALLOW_CONIC: Allow emission of `GSK_PATH_CONIC` operations.
*
* Flags that can be passed to gsk_path_foreach() to enable additional
* features.
@@ -41,14 +40,12 @@ 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_ONLY_LINES = 0,
GSK_PATH_FOREACH_ALLOW_QUAD = (1 << 0),
GSK_PATH_FOREACH_ALLOW_CUBIC = (1 << 1),
GSK_PATH_FOREACH_ALLOW_QUAD = (1 << 0),
GSK_PATH_FOREACH_ALLOW_CUBIC = (1 << 1),
GSK_PATH_FOREACH_ALLOW_CONIC = (1 << 2),
} GskPathForeachFlags;
/**
@@ -56,17 +53,19 @@ typedef enum
* @op: The operation to perform
* @pts: The points of the operation
* @n_pts: The number of points
* @weight: The weight for conic curves, or unused if not a conic curve.
* @user_data: The user data provided with the function
*
* Prototype of the callback to iterate throught the operations of
* a path.
*
* Returns: %TRUE to continue evaluating the path, %FALSE to
* immediately abort and not call the function again.
* immediately abort and not call the function again.
*/
typedef gboolean (* GskPathForeachFunc) (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data);
#define GSK_TYPE_PATH (gsk_path_get_type ())
@@ -102,51 +101,30 @@ 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,
float threshold,
GskPathPoint *result);
GDK_AVAILABLE_IN_4_14
void gsk_path_get_position (GskPath *self,
const GskPathPoint *point,
graphene_point_t *position);
GDK_AVAILABLE_IN_4_14
void gsk_path_get_tangent (GskPath *self,
const GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent);
GDK_AVAILABLE_IN_4_14
float gsk_path_get_curvature (GskPath *self,
const GskPathPoint *point,
graphene_point_t *center);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_foreach (GskPath *self,
GskPathForeachFlags flags,
GskPathForeachFunc func,
gpointer user_data);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_dash (GskPath *self,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
G_END_DECLS
+128 -179
View File
@@ -25,7 +25,6 @@
#include "gskpathprivate.h"
#include "gskcontourprivate.h"
#include "gsksplineprivate.h"
/**
* GskPathBuilder:
@@ -54,9 +53,6 @@
* 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
@@ -66,8 +62,6 @@
* back with a line to the starting point.
*
* This is similar for how paths are drawn in Cairo.
*
* Since: 4.14
*/
struct _GskPathBuilder
@@ -315,8 +309,6 @@ gsk_path_builder_add_contour (GskPathBuilder *self,
* drawing commands and updated after every operation.
*
* When the builder is created, the default current point is set to (0, 0).
* Note that this is different from cairo, which starts out without
* a current point.
*
* Returns: (transfer none): The current point
*
@@ -393,13 +385,9 @@ 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];
@@ -430,9 +418,6 @@ gsk_path_builder_add_cairo_path (GskPathBuilder *self,
break;
}
}
gsk_path_builder_end_current (self);
self->current_point = current;
}
/**
@@ -454,20 +439,14 @@ void
gsk_path_builder_add_rect (GskPathBuilder *self,
const graphene_rect_t *rect)
{
graphene_point_t current;
GskContour *contour;
g_return_if_fail (self != NULL);
current = self->current_point;
contour = gsk_rect_contour_new (rect);
gsk_path_builder_add_contour (self, contour);
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;
gsk_contour_get_start_end (contour, NULL, &self->current_point);
}
/**
@@ -478,82 +457,18 @@ gsk_path_builder_add_rect (GskPathBuilder *self,
* 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)
{
graphene_point_t current;
GskContour *contour;
g_return_if_fail (self != NULL);
g_return_if_fail (rect != NULL);
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;
contour = gsk_rounded_rect_contour_new (rect);
gsk_path_builder_add_contour (self, contour);
}
/**
@@ -571,22 +486,58 @@ gsk_path_builder_add_circle (GskPathBuilder *self,
const graphene_point_t *center,
float radius)
{
graphene_point_t current;
GskContour *contour;
g_return_if_fail (self != NULL);
g_return_if_fail (center != NULL);
g_return_if_fail (radius > 0);
current = self->current_point;
contour = gsk_circle_contour_new (center, radius, 0, 360);
gsk_path_builder_add_contour (self, contour);
}
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);
/**
* gsk_path_builder_add_ellipse:
* @self: a #GskPathBuilder
* @center: the center point of the ellipse
* @radius: the radius of the ellipse in x/y direction
*
* Adds an ellipse with the given @center and the @radius in
* x/y direction.
*
* Since: 4.14
*/
void
gsk_path_builder_add_ellipse (GskPathBuilder *self,
const graphene_point_t *center,
const graphene_size_t *radius)
{
const float weight = M_SQRT1_2;
graphene_point_t pts[8];
float w, h;
g_return_if_fail (self != NULL);
g_return_if_fail (center != NULL);
g_return_if_fail (radius != NULL);
w = radius->width / 2;
h = radius->height / 2;
pts[0] = GRAPHENE_POINT_INIT (center->x + w, center->y);
pts[1] = GRAPHENE_POINT_INIT (center->x + w, center->y + h);
pts[2] = GRAPHENE_POINT_INIT (center->x, center->y + h);
pts[3] = GRAPHENE_POINT_INIT (center->x - w, center->y + h);
pts[4] = GRAPHENE_POINT_INIT (center->x - w, center->y);
pts[5] = GRAPHENE_POINT_INIT (center->x - w, center->y - h);
pts[6] = GRAPHENE_POINT_INIT (center->x, center->y - h);
pts[7] = GRAPHENE_POINT_INIT (center->x + w, center->y - h);
gsk_path_builder_move_to (self, pts[0].x, pts[0].y);
gsk_path_builder_conic_to (self, pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
gsk_path_builder_conic_to (self, pts[3].x, pts[3].y, pts[4].x, pts[4].y, weight);
gsk_path_builder_conic_to (self, pts[5].x, pts[5].y, pts[6].x, pts[6].y, weight);
gsk_path_builder_conic_to (self, pts[7].x, pts[7].y, pts[0].x, pts[0].y, weight);
gsk_path_builder_close (self);
self->current_point = current;
}
/**
@@ -840,6 +791,82 @@ gsk_path_builder_rel_cubic_to (GskPathBuilder *self,
self->current_point.y + y3);
}
/**
* gsk_path_builder_conic_to:
* @self: a `GskPathBuilder`
* @x1: x coordinate of control point
* @y1: y coordinate of control point
* @x2: x coordinate of the end of the curve
* @y2: y coordinate of the end of the curve
* @weight: weight of the curve
*
* Adds a [conic curve](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline)
* from the current point to @x2, @y2 with the given
* @weight and @x1, @y1 as the single control point.
*
* Conic curves can be used to draw ellipses and circles.
*
* After this, @x2, @y2 will be the new current point.
*
* Since: 4.14
*/
void
gsk_path_builder_conic_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2,
float weight)
{
g_return_if_fail (self != NULL);
g_return_if_fail (weight >= 0);
self->flags &= ~GSK_PATH_FLAT;
gsk_path_builder_append_current (self,
GSK_PATH_CONIC,
3, (graphene_point_t[3]) {
GRAPHENE_POINT_INIT (x1, y1),
GRAPHENE_POINT_INIT (weight, 0),
GRAPHENE_POINT_INIT (x2, y2)
});
}
/**
* gsk_path_builder_rel_conic_to:
* @self: a `GskPathBuilder`
* @x1: x offset of control point
* @y1: y offset of control point
* @x2: x offset of the end of the curve
* @y2: y offset of the end of the curve
* @weight: weight of the curve
*
* Adds a [conic curve](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline)
* from the current point to @x2, @y2 with the given
* @weight and @x1, @y1 as the single control point.
*
* This is the relative version of [method@Gsk.PathBuilder.conic_to].
*
* Since: 4.14
*/
void
gsk_path_builder_rel_conic_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2,
float weight)
{
g_return_if_fail (self != NULL);
g_return_if_fail (weight >= 0);
gsk_path_builder_conic_to (self,
self->current_point.x + x1,
self->current_point.y + y1,
self->current_point.x + x2,
self->current_point.y + y2,
weight);
}
/**
* gsk_path_builder_close:
* @self: a `GskPathBuilder`
@@ -1078,81 +1105,3 @@ 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;
}
+23 -4
View File
@@ -60,7 +60,6 @@ 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);
@@ -72,10 +71,16 @@ 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,
GskPath *path,
const GskPathPoint *start,
const GskPathPoint *end);
GskPathMeasure *measure,
float start,
float end);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_move_to (GskPathBuilder *self,
float x,
@@ -121,6 +126,20 @@ void gsk_path_builder_rel_cubic_to (GskPathBuilder
float x3,
float y3);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_conic_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2,
float weight);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_rel_conic_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2,
float weight);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_close (GskPathBuilder *self);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathBuilder, gsk_path_builder_unref)
+306
View File
@@ -0,0 +1,306 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include "config.h"
#include "gskcontourprivate.h"
#include "gskcurveprivate.h"
#include "gskpathprivate.h"
#include "gskstrokeprivate.h"
typedef struct
{
float offset; /* how much of the current dash we've spent */
gsize dash_index; /* goes from 0 to n_dash * 2, so we don't have to care about on/off
for uneven dashes */
gboolean on; /* If we're currently dashing or not */
gboolean may_close; /* TRUE if we haven't turned the dash off in this contour */
gboolean needs_move_to; /* If we have emitted the initial move_to() yet */
enum {
NORMAL, /* no special behavior required */
SKIP, /* skip the next dash */
ONLY, /* only do the first dash */
DONE /* done with the first dash */
} first_dash_behavior; /* How to handle the first dash in the loop. We loop closed contours
twice to make sure the first dash and the last dash can get joined */
GskCurve curve; /* Curve we are currently processing */
float collect_start; /* We're collecting multiple line segments when decomposing. */
float collect_length; /* No need to emit a curve for every line segment when the dash is long enough. */
/* from the stroke */
float *dash;
gsize n_dash;
float dash_length;
float dash_offset;
float tolerance;
GskPathForeachFunc func;
gpointer user_data;
} GskPathDash;
static void
gsk_path_dash_setup (GskPathDash *self)
{
self->offset = fmodf (self->dash_offset, 2 * self->dash_length);
self->dash_index = 0;
self->on = TRUE;
self->may_close = TRUE;
while (self->offset > self->dash[self->dash_index % self->n_dash])
{
self->offset -= self->dash[self->dash_index % self->n_dash];
self->dash_index++;
self->on = !self->on;
}
if (self->first_dash_behavior != ONLY)
self->needs_move_to = TRUE;
}
static gboolean
gsk_path_dash_ensure_move_to (GskPathDash *self,
const graphene_point_t *pt)
{
if (!self->needs_move_to)
return TRUE;
if (!self->func (GSK_PATH_MOVE, pt, 1, 0, self->user_data))
return FALSE;
self->needs_move_to = FALSE;
return TRUE;
}
static gboolean
gsk_path_dash_add_line_segment (const graphene_point_t *start,
const graphene_point_t *end,
float t_start,
float t_end,
GskCurveLineReason reason,
gpointer user_data)
{
GskPathDash *self = user_data;
float remaining, length, t_step;
length = graphene_point_distance (start, end, NULL, NULL);
if (self->collect_length)
{
t_start = self->collect_start;
length += self->collect_length;
self->collect_length = 0;
}
t_step = t_end - t_start;
remaining = length;
while (remaining)
{
float piece;
if (self->offset + remaining <= self->dash[self->dash_index % self->n_dash])
{
/* try collecting multiple line segments */
if (t_end < 1.0)
{
self->collect_start = t_start + t_step * (length - remaining) / length;
self->collect_length = remaining;
return TRUE;
}
piece = remaining;
}
else
piece = self->dash[self->dash_index % self->n_dash] - self->offset;
if (self->on)
{
if (self->first_dash_behavior != SKIP)
{
GskCurve segment;
if (piece)
{
gsk_curve_segment (&self->curve,
t_start + t_step * (length - remaining) / length,
t_start + t_step * (length - (remaining - piece)) / length,
&segment);
if (!gsk_path_dash_ensure_move_to (self, gsk_curve_get_start_point (&segment)))
return FALSE;
if (!gsk_pathop_foreach (gsk_curve_pathop (&segment), self->func, self->user_data))
return FALSE;
}
else
{
graphene_point_t p;
gsk_curve_get_point (&self->curve, t_start + t_step * (length - remaining) / length, &p);
if (!gsk_path_dash_ensure_move_to (self, &p))
return FALSE;
}
}
}
else
{
self->may_close = FALSE;
if (self->first_dash_behavior == ONLY)
{
self->first_dash_behavior = DONE;
return FALSE;
}
self->first_dash_behavior = NORMAL;
}
if (self->offset + remaining <= self->dash[self->dash_index % self->n_dash])
{
self->offset += remaining;
remaining = 0;
}
else
{
remaining -= piece;
self->offset = 0;
self->dash_index++;
self->dash_index %= 2 * self->n_dash;
self->on = !self->on;
self->needs_move_to = TRUE;
}
}
return TRUE;
}
static gboolean
gsk_path_dash_foreach (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathDash *self = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_dash_setup (self);
break;
case GSK_PATH_CLOSE:
if (self->may_close)
{
if (graphene_point_equal (&pts[0], &pts[1]))
return self->func (GSK_PATH_CLOSE, pts, 2, 0, self->user_data);
}
else
op = GSK_PATH_LINE;
G_GNUC_FALLTHROUGH;
case GSK_PATH_LINE:
case GSK_PATH_QUAD:
case GSK_PATH_CUBIC:
case GSK_PATH_CONIC:
gsk_curve_init_foreach (&self->curve, op, pts, n_pts, weight);
if (!gsk_curve_decompose (&self->curve, self->tolerance, gsk_path_dash_add_line_segment, self))
return FALSE;
break;
default:
g_assert_not_reached ();
break;
}
return TRUE;
}
gboolean
gsk_contour_dash (const GskContour *contour,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data)
{
GskPathDash self = {
.offset = 0,
.dash = stroke->dash,
.n_dash = stroke->n_dash,
.dash_length = stroke->dash_length,
.dash_offset = stroke->dash_offset,
.tolerance = tolerance,
.func = func,
.user_data = user_data
};
gboolean is_closed = gsk_contour_get_flags (contour) & GSK_PATH_CLOSED ? TRUE : FALSE;
self.first_dash_behavior = is_closed ? SKIP : NORMAL;
if (!gsk_contour_foreach (contour, tolerance, gsk_path_dash_foreach, &self))
return FALSE;
if (is_closed)
{
if (self.first_dash_behavior == NORMAL)
self.first_dash_behavior = ONLY;
else
self.first_dash_behavior = NORMAL;
self.needs_move_to = !self.on;
if (!gsk_contour_foreach (contour, tolerance, gsk_path_dash_foreach, &self) &&
self.first_dash_behavior != DONE)
return FALSE;
}
return TRUE;
}
/**
* gsk_path_dash:
* @self: the `GskPath` to dash
* @stroke: the stroke containing the dash parameters
* @tolerance: tolerance to use while dashing
* @func: (scope call) (closure user_data): the function to call for operations
* @user_data: (nullable): user data passed to @func
*
* Calls @func for every operation of the path that is the result
* of dashing @self with the dash pattern from @stroke.
*
* Returns: `FALSE` if @func returned FALSE`, `TRUE` otherwise.
*
* Since: 4.14
*/
gboolean
gsk_path_dash (GskPath *self,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data)
{
gsize i;
/* Dashing disabled, no need to do any work */
if (stroke->dash_length <= 0)
return gsk_path_foreach (self, -1, func, user_data);
for (i = 0; i < gsk_path_get_n_contours (self); i++)
{
if (!gsk_contour_dash (gsk_path_get_contour (self, i), stroke, tolerance, func, user_data))
return FALSE;
}
return TRUE;
}
+411
View File
@@ -0,0 +1,411 @@
/*
* 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);
}
+66
View File
@@ -0,0 +1,66 @@
/*
* 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
+18 -4
View File
@@ -82,17 +82,23 @@ gsk_pathop_foreach (gskpathop pop,
switch (gsk_pathop_op (pop))
{
case GSK_PATH_MOVE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 1, user_data);
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 1, 0, user_data);
case GSK_PATH_CLOSE:
case GSK_PATH_LINE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 2, user_data);
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 2, 0, user_data);
case GSK_PATH_QUAD:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 3, user_data);
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 3, 0, user_data);
case GSK_PATH_CUBIC:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 4, user_data);
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 4, 0, user_data);
case GSK_PATH_CONIC:
{
const graphene_point_t *pts = gsk_pathop_points (pop);
return func (gsk_pathop_op (pop), (graphene_point_t[3]) { pts[0], pts[1], pts[3] }, 3, pts[2].x, user_data);
}
default:
g_assert_not_reached ();
@@ -128,6 +134,10 @@ gsk_path_builder_pathop_to (GskPathBuilder *builder,
gsk_path_builder_cubic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[3].x, pts[3].y, pts[2].x);
break;
default:
g_assert_not_reached ();
break;
@@ -162,6 +172,10 @@ gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
gsk_path_builder_cubic_to (builder, pts[2].x, pts[2].y, pts[1].x, pts[1].y, pts[0].x, pts[0].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[0].x, pts[0].y, pts[2].x);
break;
default:
g_assert_not_reached ();
break;
+88 -61
View File
@@ -22,9 +22,11 @@
#include "gskpathpointprivate.h"
#include "gskcontourprivate.h"
#include "gskpathmeasure.h"
#include "gdk/gdkprivate.h"
/**
* GskPathPoint:
*
@@ -33,14 +35,13 @@
* 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].
* To obtain a `GskPathPoint`, use [method@Gsk.Path.get_closest_point]
* or [method@Gsk.PathMeasure.get_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,
@@ -66,73 +67,99 @@ gsk_path_point_free (GskPathPoint *point)
g_free (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)
GskPath *
gsk_path_point_get_path (GskPathPoint *point)
{
const GskRealPathPoint *p1 = (const GskRealPathPoint *) point1;
const GskRealPathPoint *p2 = (const GskRealPathPoint *) point2;
GskRealPathPoint *self = (GskRealPathPoint *) point;
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 self->path;
}
return FALSE;
const GskContour *
gsk_path_point_get_contour (GskPathPoint *point)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
return self->contour;
}
/**
* gsk_path_point_compare:
* @point1: a `GskPathPoint`
* @point2: another `GskPathPoint`
* gsk_path_point_get_position:
* @point: a `GskPathPoint`
* @position: (out caller-allocates): Return location for
* the coordinates of the point
*
* Returns whether @point1 is before or after @point2.
* Gets the position of the point.
*
* Returns: -1 if @point1 is before @point2,
* 1 if @point1 is after @point2,
* 0 if they are equal
* Since: 4.14
*/
int
gsk_path_point_compare (const GskPathPoint *point1,
const GskPathPoint *point2)
void
gsk_path_point_get_position (GskPathPoint *point,
graphene_point_t *position)
{
const GskRealPathPoint *p1 = (const GskRealPathPoint *) point1;
const GskRealPathPoint *p2 = (const GskRealPathPoint *) point2;
GskRealPathPoint *self = (GskRealPathPoint *) point;
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_contour_get_position (self->contour, self, position);
}
/**
* gsk_path_point_get_tangent:
* @point: a `GskPathPoint`
* @direction: the direction for which to return the tangent
* @tangent: (out caller-allocates): Return location for
* the tangent at the point
*
* Gets the tangent of the path at the point.
*
* Note that certain points on a path may not have a single
* tangent, such as sharp turns. At such points, there are
* two tangents -- the direction of the path going into the
* point, and the direction coming out of it. The @direction
* argument lets you choose which one to get.
*
* Since: 4.14
*/
void
gsk_path_point_get_tangent (GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
gsk_contour_get_tangent (self->contour, self, direction, tangent);
}
/**
* gsk_path_point_get_curvature:
* @point: a `GskPathPoint`
* @center: (out caller-allocates): Return location for
* the center of the osculating circle
*
* Calculates the curvature of the path at the point.
*
* Optionally, returns the center of the osculating circle as well.
*
* If the curvature is infinite (at line segments), zero is returned,
* and @center is not modified.
*
* Returns: The curvature of the path at the given point
*
* Since: 4.14
*/
float
gsk_path_point_get_curvature (GskPathPoint *point,
graphene_point_t *center)
{
GskRealPathPoint *self = (GskRealPathPoint *) point;
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);
}
+11 -6
View File
@@ -43,17 +43,22 @@ GDK_AVAILABLE_IN_4_14
GType gsk_path_point_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_4_14
GskPathPoint * gsk_path_point_copy (GskPathPoint *point);
GskPathPoint * gsk_path_point_copy (GskPathPoint *point);
GDK_AVAILABLE_IN_4_14
void gsk_path_point_free (GskPathPoint *point);
void gsk_path_point_free (GskPathPoint *point);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_point_equal (const GskPathPoint *point1,
const GskPathPoint *point2);
void gsk_path_point_get_position (GskPathPoint *point,
graphene_point_t *position);
GDK_AVAILABLE_IN_4_14
int gsk_path_point_compare (const GskPathPoint *point1,
const GskPathPoint *point2);
void gsk_path_point_get_tangent (GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent);
GDK_AVAILABLE_IN_4_14
float gsk_path_point_get_curvature (GskPathPoint *point,
graphene_point_t *center);
G_END_DECLS
+19 -4
View File
@@ -7,12 +7,27 @@ G_BEGIN_DECLS
struct _GskRealPathPoint
{
gsize contour;
gsize idx;
float t;
GskPath *path;
const GskContour *contour;
union {
struct {
unsigned int idx;
float t;
} std;
struct {
float distance;
} rect;
struct {
float angle;
} circle;
} data;
};
G_STATIC_ASSERT (sizeof (GskRealPathPoint) <= sizeof (GskPathPoint));
GskPath * gsk_path_point_get_path (GskPathPoint *self);
const GskContour * gsk_path_point_get_contour (GskPathPoint *self);
float gsk_path_point_get_distance (GskPathPoint *self,
gpointer measure_data);
G_END_DECLS
+6 -3
View File
@@ -40,11 +40,11 @@ typedef struct _GskRealPathPoint GskRealPathPoint;
GskPath * gsk_path_new_from_contours (const GSList *contours);
gsize gsk_path_get_n_contours (const GskPath *self);
const GskContour * gsk_path_get_contour (const GskPath *self,
gsize gsk_path_get_n_contours (GskPath *path);
const GskContour * gsk_path_get_contour (GskPath *path,
gsize i);
GskPathFlags gsk_path_get_flags (const GskPath *self);
GskPathFlags gsk_path_get_flags (GskPath *self);
gboolean gsk_path_foreach_with_tolerance (GskPath *self,
GskPathForeachFlags flags,
@@ -65,6 +65,9 @@ void gsk_path_builder_svg_arc_to (GskPathBuilder
float x,
float y);
gboolean gsk_path_get_stroke_bounds (GskPath *self,
const GskStroke *stroke,
graphene_rect_t *bounds);
G_END_DECLS
+1
View File
@@ -35,6 +35,7 @@ G_BEGIN_DECLS
#define GSK_SERIALIZATION_ERROR (gsk_serialization_error_quark ())
typedef struct _GskRenderNode GskRenderNode;
typedef struct _GskColorStop GskColorStop;
typedef struct _GskShadow GskShadow;
+16 -53
View File
@@ -26,7 +26,6 @@
#include "gskdiffprivate.h"
#include "gl/gskglrenderer.h"
#include "gskpathprivate.h"
#include "gskrectprivate.h"
#include "gskrendererprivate.h"
#include "gskroundedrectprivate.h"
#include "gskstrokeprivate.h"
@@ -3970,7 +3969,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);
@@ -4141,7 +4140,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)
@@ -4372,15 +4371,6 @@ 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;
@@ -4423,17 +4413,9 @@ gsk_fill_node_draw (GskRenderNode *node,
break;
}
gsk_path_to_cairo (self->path, cr);
if (gsk_render_node_get_node_type (self->child) == GSK_COLOR_NODE &&
gsk_rect_contains_rect (&self->child->bounds, &node->bounds))
{
gdk_cairo_set_source_rgba (cr, gsk_color_node_get_color (self->child));
cairo_fill (cr);
}
else
{
cairo_clip (cr);
gsk_render_node_draw (self->child, cr);
}
cairo_clip (cr);
gsk_render_node_draw (self->child, cr);
cairo_restore (cr);
}
@@ -4450,10 +4432,12 @@ gsk_fill_node_diff (GskRenderNode *node1,
{
cairo_region_t *sub;
cairo_rectangle_int_t clip_rect;
graphene_rect_t rect;
sub = cairo_region_create();
gsk_render_node_diff (self1->child, self2->child, sub);
rectangle_init_from_graphene (&clip_rect, &node1->bounds);
graphene_rect_union (&node1->bounds, &node2->bounds, &rect);
rectangle_init_from_graphene (&clip_rect, &rect);
cairo_region_intersect_rectangle (sub, &clip_rect);
cairo_region_union (region, sub);
cairo_region_destroy (sub);
@@ -4581,15 +4565,6 @@ 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;
@@ -4620,19 +4595,12 @@ gsk_stroke_node_draw (GskRenderNode *node,
cairo_save (cr);
if (gsk_render_node_get_node_type (self->child) == GSK_COLOR_NODE &&
gsk_rect_contains_rect (&self->child->bounds, &node->bounds))
{
gdk_cairo_set_source_rgba (cr, gsk_color_node_get_color (self->child));
}
else
{
gsk_cairo_rectangle (cr, &self->child->bounds);
cairo_clip (cr);
cairo_push_group (cr);
gsk_render_node_draw (self->child, cr);
cairo_pop_group_to_source (cr);
}
gsk_cairo_rectangle (cr, &self->child->bounds);
cairo_clip (cr);
cairo_push_group (cr);
gsk_render_node_draw (self->child, cr);
cairo_pop_group_to_source (cr);
gsk_stroke_to_cairo (&self->stroke, cr);
@@ -4654,12 +4622,9 @@ gsk_stroke_node_diff (GskRenderNode *node1,
gsk_stroke_equal (&self1->stroke, &self2->stroke))
{
cairo_region_t *sub;
cairo_rectangle_int_t clip_rect;
sub = cairo_region_create();
gsk_render_node_diff (self1->child, self2->child, sub);
rectangle_init_from_graphene (&clip_rect, &node1->bounds);
cairo_region_intersect_rectangle (sub, &clip_rect);
cairo_region_union (region, sub);
cairo_region_destroy (sub);
}
@@ -4688,10 +4653,8 @@ 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 fill the outline generated by stroking
* the given @path using the attributes defined in @stroke.
*
* The area is filled with @child.
* Creates a #GskRenderNode that will stroke the @child along the given
* @path using the attributes defined in @stroke.
*
* Returns: (transfer none) (type GskStrokeNode): A new #GskRenderNode
*
+12 -244
View File
@@ -24,12 +24,10 @@
#include "gskrendernodeparserprivate.h"
#include "gskpath.h"
#include "gskpathbuilder.h"
#include "gskroundedrectprivate.h"
#include "gskrendernodeprivate.h"
#include "gskstroke.h"
#include "gsktransformprivate.h"
#include "gskenumtypes.h"
#include "gdk/gdkrgbaprivate.h"
#include "gdk/gdktextureprivate.h"
@@ -1178,26 +1176,6 @@ create_default_render_node (void)
return gsk_color_node_new (&GDK_RGBA("FF00CC"), &GRAPHENE_RECT_INIT (0, 0, 50, 50));
}
static GskPath *
create_default_path (void)
{
GskPathBuilder *builder;
guint i;
builder = gsk_path_builder_new ();
gsk_path_builder_move_to (builder, 25, 0);
for (i = 1; i < 5; i++)
{
gsk_path_builder_line_to (builder,
sin (i * G_PI * 0.8) * 25 + 25,
-cos (i * G_PI * 0.8) * 25 + 25);
}
gsk_path_builder_close (builder);
return gsk_path_builder_free_to_path (builder);
}
static GskRenderNode *
parse_color_node (GtkCssParser *parser,
Context *context)
@@ -2121,165 +2099,6 @@ parse_rounded_clip_node (GtkCssParser *parser,
return result;
}
static gboolean
parse_path (GtkCssParser *parser,
Context *context,
gpointer out_path)
{
GskPath *path;
char *str = NULL;
if (!parse_string (parser, context, &str))
return FALSE;
path = gsk_path_parse (str);
g_free (str);
if (path == NULL)
{
gtk_css_parser_error_value (parser, "Invalid path");
return FALSE;
}
*((GskPath **) out_path) = path;
return TRUE;
}
static void
clear_path (gpointer inout_path)
{
g_clear_pointer ((GskPath **) inout_path, gsk_path_unref);
}
static gboolean
parse_enum (GtkCssParser *parser,
GType type,
gpointer out_value)
{
GEnumClass *class;
GEnumValue *v;
char *enum_name;
enum_name = gtk_css_parser_consume_ident (parser);
if (enum_name == NULL)
return FALSE;
class = g_type_class_ref (type);
v = g_enum_get_value_by_nick (class, enum_name);
if (v == NULL)
{
gtk_css_parser_error_value (parser, "Unknown value \"%s\" for enum \"%s\"",
enum_name, g_type_name (type));
g_free (enum_name);
g_type_class_unref (class);
return FALSE;
}
*(int*)out_value = v->value;
g_free (enum_name);
g_type_class_unref (class);
return TRUE;
}
static gboolean
parse_fill_rule (GtkCssParser *parser,
Context *context,
gpointer out_rule)
{
return parse_enum (parser, GSK_TYPE_FILL_RULE, out_rule);
}
static GskRenderNode *
parse_fill_node (GtkCssParser *parser,
Context *context)
{
GskRenderNode *child = NULL;
GskPath *path = NULL;
int rule = GSK_FILL_RULE_WINDING;
const Declaration declarations[] = {
{ "child", parse_node, clear_node, &child },
{ "path", parse_path, clear_path, &path },
{ "fill-rule", parse_fill_rule, NULL, &rule },
};
GskRenderNode *result;
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
if (child == NULL)
child = create_default_render_node ();
if (path == NULL)
path = create_default_path ();
result = gsk_fill_node_new (child, path, rule);
gsk_path_unref (path);
gsk_render_node_unref (child);
return result;
}
static gboolean
parse_line_cap (GtkCssParser *parser,
Context *context,
gpointer out)
{
return parse_enum (parser, GSK_TYPE_LINE_CAP, out);
}
static gboolean
parse_line_join (GtkCssParser *parser,
Context *context,
gpointer out)
{
return parse_enum (parser, GSK_TYPE_LINE_JOIN, out);
}
static GskRenderNode *
parse_stroke_node (GtkCssParser *parser,
Context *context)
{
GskRenderNode *child = NULL;
GskPath *path = NULL;
double line_width = 1.0;
int line_cap = GSK_LINE_CAP_BUTT;
int line_join = GSK_LINE_JOIN_MITER;
double miter_limit = 4.0;
GskStroke *stroke;
const Declaration declarations[] = {
{ "child", parse_node, clear_node, &child },
{ "path", parse_path, clear_path, &path },
{ "line-width", parse_positive_double, NULL, &line_width },
{ "line-cap", parse_line_cap, NULL, &line_cap },
{ "line-join", parse_line_join, NULL, &line_join },
{ "miter-limit", parse_positive_double, NULL, &miter_limit }
};
GskRenderNode *result;
parse_declarations (parser, context, declarations, G_N_ELEMENTS (declarations));
if (child == NULL)
child = create_default_render_node ();
if (path == NULL)
path = create_default_path ();
stroke = gsk_stroke_new (line_width);
gsk_stroke_set_line_cap (stroke, line_cap);
gsk_stroke_set_line_join (stroke, line_join);
gsk_stroke_set_miter_limit (stroke, miter_limit);
result = gsk_stroke_node_new (child, path, stroke);
gsk_path_unref (path);
gsk_stroke_free (stroke);
gsk_render_node_unref (child);
return result;
}
static GskRenderNode *
parse_shadow_node (GtkCssParser *parser,
Context *context)
@@ -2362,8 +2181,6 @@ parse_node (GtkCssParser *parser,
{ "repeating-linear-gradient", parse_repeating_linear_gradient_node },
{ "repeating-radial-gradient", parse_repeating_radial_gradient_node },
{ "rounded-clip", parse_rounded_clip_node },
{ "fill", parse_fill_node },
{ "stroke", parse_stroke_node },
{ "shadow", parse_shadow_node },
{ "text", parse_text_node },
{ "texture", parse_texture_node },
@@ -3026,11 +2843,8 @@ append_escaping_newlines (GString *str,
len = strcspn (string, "\n");
g_string_append_len (str, string, len);
string += len;
if (*string)
{
g_string_append (str, "\\\n");
string++;
}
g_string_append (str, "\\\n");
string++;
} while (*string);
}
@@ -3231,55 +3045,6 @@ gsk_text_node_serialize_glyphs (GskRenderNode *node,
pango_glyph_string_free (ascii);
}
static const char *
enum_to_nick (GType type,
int value)
{
GEnumClass *class;
GEnumValue *v;
class = g_type_class_ref (type);
v = g_enum_get_value (class, value);
g_type_class_unref (class);
return v->value_nick;
}
static void
append_enum_param (Printer *p,
const char *param_name,
GType type,
int value)
{
_indent (p);
g_string_append_printf (p->str, "%s: ", param_name);
g_string_append (p->str, enum_to_nick (type, value));
g_string_append_c (p->str, ';');
g_string_append_c (p->str, '\n');
}
static void
append_path_param (Printer *p,
const char *param_name,
GskPath *path)
{
char *str, *s;
_indent (p);
g_string_append (p->str, "path: \"\\\n");
str = gsk_path_to_string (path);
/* Put each command on a new line */
for (s = str; *s; s++)
{
if (*s == ' ' &&
(s[1] == 'M' || s[1] == 'C' || s[1] == 'Z' || s[1] == 'L'))
*s = '\n';
}
append_escaping_newlines (p->str, str);
g_string_append (p->str, "\";\n");
g_free (str);
}
static void
render_node_print (Printer *p,
GskRenderNode *node)
@@ -3455,11 +3220,14 @@ render_node_print (Printer *p,
case GSK_FILL_NODE:
{
char *path_str;
start_node (p, "fill", node_name);
append_node_param (p, "child", gsk_fill_node_get_child (node));
append_path_param (p, "path", gsk_fill_node_get_path (node));
append_enum_param (p, "fill-rule", GSK_TYPE_FILL_RULE, gsk_fill_node_get_fill_rule (node));
path_str = gsk_path_to_string (gsk_fill_node_get_path (node));
append_string_param (p, "path", path_str);
g_free (path_str);
end_node (p);
}
@@ -3468,17 +3236,17 @@ render_node_print (Printer *p,
case GSK_STROKE_NODE:
{
const GskStroke *stroke;
char *path_str;
start_node (p, "stroke", node_name);
append_node_param (p, "child", gsk_stroke_node_get_child (node));
append_path_param (p, "path", gsk_stroke_node_get_path (node));
path_str = gsk_path_to_string (gsk_stroke_node_get_path (node));
append_string_param (p, "path", path_str);
g_free (path_str);
stroke = gsk_stroke_node_get_stroke (node);
append_float_param (p, "line-width", gsk_stroke_get_line_width (stroke), 0.0f);
append_enum_param (p, "line-cap", GSK_TYPE_LINE_CAP, gsk_stroke_get_line_cap (stroke));
append_enum_param (p, "line-join", GSK_TYPE_LINE_JOIN, gsk_stroke_get_line_join (stroke));
append_float_param (p, "miter-limit", gsk_stroke_get_miter_limit (stroke), 4.0f);
append_float_param (p, "line-width", gsk_stroke_get_line_width (stroke), 0.0);
end_node (p);
}
+1 -1
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_STROKE_NODE + 1)
#define GSK_RENDER_NODE_TYPE_N_TYPES (GSK_MASK_NODE + 1)
extern GType gsk_render_node_types[];
+5 -63
View File
@@ -26,8 +26,6 @@
*
* 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)
@@ -140,6 +138,7 @@ gsk_stroke_to_cairo (const GskStroke *self,
switch (self->line_join)
{
case GSK_LINE_JOIN_MITER:
case GSK_LINE_JOIN_MITER_CLIP:
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
break;
case GSK_LINE_JOIN_ROUND:
@@ -188,19 +187,7 @@ gsk_stroke_equal (gconstpointer stroke1,
const GskStroke *self1 = stroke1;
const GskStroke *self2 = stroke2;
if (self1->line_width != self2->line_width ||
self1->line_cap != self2->line_cap ||
self1->line_join != self2->line_join ||
self1->miter_limit != self2->miter_limit ||
self1->n_dash != self2->n_dash ||
self1->dash_offset != self2->dash_offset)
return FALSE;
for (gsize i = 0; i < self1->n_dash; i++)
if (self1->dash[i] != self2->dash[i])
return FALSE;
return TRUE;
return self1->line_width == self2->line_width;
}
/**
@@ -334,7 +321,8 @@ gsk_stroke_get_line_join (const GskStroke *self)
*
* For joins of type `GSK_LINE_JOIN_MITER` that exceed the miter
* limit, the join gets rendered as if it was of type
* `GSK_LINE_JOIN_BEVEL`.
* `GSK_LINE_JOIN_BEVEL`. For joins of type `GSK_LINE_JOIN_MITER_CLIP`,
* the miter is clipped at a distance of half the miter limit.
*
* Since: 4.14
*/
@@ -423,6 +411,7 @@ gsk_stroke_set_dash (GskStroke *self,
g_free (self->dash);
self->dash = g_memdup (dash, sizeof (gfloat) * n_dash);
self->n_dash = n_dash;
}
/**
@@ -488,50 +477,3 @@ gsk_stroke_get_dash_offset (const GskStroke *self)
return self->dash_offset;
}
/*< private >
* gsk_stroke_get_join_width:
* @stroke: a `GskStroke`
*
* Return a width that is sufficient to use
* when calculating stroke bounds around joins
* and caps.
*
* Returns: the join width
*/
float
gsk_stroke_get_join_width (const GskStroke *stroke)
{
float width;
switch (stroke->line_cap)
{
case GSK_LINE_CAP_BUTT:
width = 0;
break;
case GSK_LINE_CAP_ROUND:
width = stroke->line_width;
break;
case GSK_LINE_CAP_SQUARE:
width = G_SQRT2 * stroke->line_width;
break;
default:
g_assert_not_reached ();
}
switch (stroke->line_join)
{
case GSK_LINE_JOIN_MITER:
width = MAX (width, MAX (stroke->miter_limit, 1.f) * stroke->line_width);
break;
case GSK_LINE_JOIN_ROUND:
case GSK_LINE_JOIN_BEVEL:
width = MAX (width, stroke->line_width);
break;
default:
g_assert_not_reached ();
}
return width;
}
-2
View File
@@ -53,6 +53,4 @@ gsk_stroke_clear (GskStroke *stroke)
stroke->n_dash = 0; /* better safe than sorry */
}
float gsk_stroke_get_join_width (const GskStroke *stroke);
G_END_DECLS
+1 -1
View File
@@ -27,9 +27,9 @@
typedef struct _GskPath GskPath;
typedef struct _GskPathBuilder GskPathBuilder;
typedef struct _GskPathMeasure GskPathMeasure;
typedef struct _GskPathPoint GskPathPoint;
typedef struct _GskRenderer GskRenderer;
typedef struct _GskRenderNode GskRenderNode;
typedef struct _GskStroke GskStroke;
typedef struct _GskTransform GskTransform;
+3
View File
@@ -27,7 +27,9 @@ gsk_public_sources = files([
'gskdiff.c',
'gskglshader.c',
'gskpath.c',
'gskpathdash.c',
'gskpathbuilder.c',
'gskpathmeasure.c',
'gskpathpoint.c',
'gskrenderer.c',
'gskrendernode.c',
@@ -75,6 +77,7 @@ gsk_public_headers = files([
'gskglshader.h',
'gskpath.h',
'gskpathbuilder.h',
'gskpathmeasure.h',
'gskpathpoint.h',
'gskrenderer.h',
'gskrendernode.h',
+2 -2
View File
@@ -234,8 +234,8 @@
* API as they come in. Usually `GtkCellArea` is only interested in
* button events, however some customized derived areas can be implemented
* who are interested in handling other events. Handling an event can
* trigger the [signal@Gtk.CellArea::focus-changed] signal to fire; as well
* as [signal@Gtk.CellArea::add-editable] in the case that an editable cell
* trigger the [`signal@Gtk.CellArea::focus-changed`] signal to fire; as well
* as [`signal@Gtk.CellArea::add-editable`] in the case that an editable cell
* was clicked and needs to start editing. You can call
* [method@Gtk.CellArea.stop_editing] at any time to cancel any cell editing
* that is currently in progress.
+1 -1
View File
@@ -1140,7 +1140,7 @@ gtk_tree_view_class_init (GtkTreeViewClass *class)
* @column: (nullable): the `GtkTreeViewColumn` in which the activation occurred
*
* The "row-activated" signal is emitted when the method
* [method@Gtk.TreeView.row_activated] is called.
* [`method@Gtk.TreeView.row_activated`] is called.
*
* This signal is emitted when the user double-clicks a treeview row with the
* [property@Gtk.TreeView:activate-on-single-click] property set to %FALSE,
+4 -4
View File
@@ -937,13 +937,13 @@ gtk_tree_view_column_update_button (GtkTreeViewColumn *tree_column)
if ((!alternative && priv->sort_order == GTK_SORT_ASCENDING) ||
(alternative && priv->sort_order == GTK_SORT_DESCENDING))
{
gtk_widget_remove_css_class (arrow, "ascending");
gtk_widget_add_css_class (arrow, "descending");
gtk_widget_remove_css_class (arrow, "descending");
gtk_widget_add_css_class (arrow, "ascending");
}
else
{
gtk_widget_remove_css_class (arrow, "descending");
gtk_widget_add_css_class (arrow, "ascending");
gtk_widget_remove_css_class (arrow, "ascending");
gtk_widget_add_css_class (arrow, "descending");
}
}
+1 -8
View File
@@ -1798,8 +1798,6 @@ gtk_column_view_scroll_to_column (GtkColumnView *self,
gtk_adjustment_get_page_size (self->hadjustment));
gtk_adjustment_set_value (self->hadjustment, new_value);
g_clear_pointer (&scroll_info, gtk_scroll_info_unref);
}
void
@@ -2216,14 +2214,9 @@ gtk_column_view_scroll_to (GtkColumnView *self,
if (column && (flags & GTK_LIST_SCROLL_FOCUS))
gtk_column_view_set_focus_column (self, column, FALSE);
gtk_list_view_scroll_to (self->listview,
pos,
flags,
scroll ? gtk_scroll_info_ref (scroll) : NULL);
gtk_list_view_scroll_to (self->listview, pos, flags, scroll);
if (column)
gtk_column_view_scroll_to_column (self, column, scroll);
else
g_clear_pointer (&scroll, gtk_scroll_info_unref);
}
+1 -1
View File
@@ -411,7 +411,7 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base,
}
pos = gtk_list_tile_get_position (self->item_manager, tile);
if (tile->n_items > 1 && tile->area.width > 0 && tile->area.height > 0)
if (tile->n_items > 1)
{
int xspacing, yspacing;
+1 -1
View File
@@ -1202,7 +1202,7 @@ my_pango_layout_get_width_for_height (PangoLayout *layout,
else if (text_height > for_height)
min = mid + 1;
else
max = text_width;
max = mid;
}
return min * PANGO_SCALE;
+3 -3
View File
@@ -27,9 +27,9 @@
* `GtkListHeader` is used by list widgets to represent the headers they
* display.
*
* `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.
* 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.
*
* Since: 4.12
*/
+3 -4
View File
@@ -27,14 +27,13 @@
/**
* GtkListItem:
*
* `GtkListItem` is used by list widgets to represent items in a
* [iface@Gio.ListModel].
* `GtkListItem` is used by list widgets to represent items in a `GListModel`.
*
* `GtkListItem` objects are managed by the list widget (with its factory)
* The `GtkListItem`s 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` objects exist in 2 stages:
* `GtkListItem`s 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]
+2 -2
View File
@@ -1510,8 +1510,8 @@ gtk_menu_button_set_child (GtkMenuButton *menu_button,
NULL,
NULL,
NULL,
(GtkGizmoFocusFunc)gtk_widget_focus_child,
NULL);
(GtkGizmoFocusFunc)gtk_widget_focus_self,
(GtkGizmoGrabFocusFunc)gtk_widget_grab_focus_self);
gtk_widget_set_layout_manager (inner_widget, gtk_bin_layout_new ());
gtk_widget_set_hexpand (inner_widget, TRUE);
+2 -2
View File
@@ -33,12 +33,12 @@
/**
* GtkOverlayLayout:
*
* `GtkOverlayLayout` is the layout manager used by [class@Gtk.Overlay].
* `GtkOverlayLayout` is the layout manager used by `GtkOverlay`.
*
* 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 is only listed here so that its layout
* to be a `GtkOverlay`. It only listed here so that its layout
* properties get documented.
*/
+141 -74
View File
@@ -357,16 +357,16 @@ snapshot_frame_fill (GtkSnapshot *snapshot,
gtk_snapshot_append_border (snapshot, outline, border_width, colors);
}
static void
set_stroke_style (cairo_t *cr,
double line_width,
GtkBorderStyle style,
double length)
static GskStroke *
create_stroke_style (double line_width,
GtkBorderStyle style,
double length)
{
double segments[2];
GskStroke *stroke;
float segments[2];
double n;
cairo_set_line_width (cr, line_width);
stroke = gsk_stroke_new (line_width);
if (style == GTK_BORDER_STYLE_DOTTED)
{
@@ -374,12 +374,12 @@ set_stroke_style (cairo_t *cr,
segments[0] = 0;
segments[1] = n ? length / n : 2;
cairo_set_dash (cr, segments, G_N_ELEMENTS (segments), 0);
gsk_stroke_set_dash (stroke, segments, 2);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_ROUND);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_ROUND);
}
else
else if (style == GTK_BORDER_STYLE_DASHED)
{
n = length / line_width;
/* Optimize the common case of an integer-sized rectangle
@@ -397,32 +397,33 @@ set_stroke_style (cairo_t *cr,
segments[0] = n ? (1. / 3) * length / n : 1;
segments[1] = 2 * segments[0];
}
cairo_set_dash (cr, segments, G_N_ELEMENTS (segments), 0);
gsk_stroke_set_dash (stroke, segments, 2);
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_SQUARE);
gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_MITER);
}
else
{
g_assert_not_reached ();
}
return stroke;
}
static void
render_frame_stroke (cairo_t *cr,
const GskRoundedRect *border_box,
const double border_width[4],
GdkRGBA colors[4],
guint hidden_side,
GtkBorderStyle stroke_style)
snapshot_frame_stroke (GtkSnapshot *snapshot,
const GskRoundedRect *border_box,
const float border_width[4],
GdkRGBA colors[4],
guint hidden_side,
GtkBorderStyle stroke_style)
{
gboolean different_colors, different_borders;
GskRoundedRect stroke_box;
GskPathBuilder *builder;
GskPath *path;
GskStroke *stroke;
guint i;
different_colors = !gdk_rgba_equal (&colors[0], &colors[1]) ||
!gdk_rgba_equal (&colors[0], &colors[2]) ||
!gdk_rgba_equal (&colors[0], &colors[3]);
different_borders = border_width[0] != border_width[1] ||
border_width[0] != border_width[2] ||
border_width[0] != border_width[3] ;
stroke_box = *border_box;
gsk_rounded_rect_shrink (&stroke_box,
border_width[GTK_CSS_TOP] / 2.0,
@@ -430,32 +431,36 @@ render_frame_stroke (cairo_t *cr,
border_width[GTK_CSS_BOTTOM] / 2.0,
border_width[GTK_CSS_LEFT] / 2.0);
if (!different_colors && !different_borders && hidden_side == 0)
if (border_width[0] == border_width[1] &&
border_width[0] == border_width[2] &&
border_width[0] == border_width[3] &&
hidden_side == 0)
{
double length = 0;
/* FAST PATH:
* Mostly expected to trigger for focus rectangles */
for (i = 0; i < 4; i++)
for (i = 0; i < 4; i++)
{
length += _gtk_rounded_box_guess_length (&stroke_box, i);
}
gsk_rounded_rect_path (&stroke_box, cr);
gdk_cairo_set_source_rgba (cr, &colors[0]);
set_stroke_style (cr, border_width[0], stroke_style, length);
cairo_stroke (cr);
builder = gsk_path_builder_new ();
gsk_path_builder_add_rounded_rect (builder, &stroke_box);
path = gsk_path_builder_free_to_path (builder);
stroke = create_stroke_style (border_width[0],
stroke_style, length);
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gsk_path_unref (path);
gtk_snapshot_append_border (snapshot, border_box, border_width, colors);
gtk_snapshot_pop (snapshot);
}
else
{
GskRoundedRect padding_box;
padding_box = *border_box;
gsk_rounded_rect_shrink (&padding_box,
border_width[GTK_CSS_TOP],
border_width[GTK_CSS_RIGHT],
border_width[GTK_CSS_BOTTOM],
border_width[GTK_CSS_LEFT]);
const float weight = sqrtf(2)/2.0;
for (i = 0; i < 4; i++)
{
@@ -465,49 +470,111 @@ render_frame_stroke (cairo_t *cr,
if (border_width[i] == 0)
continue;
cairo_save (cr);
builder = gsk_path_builder_new ();
if (i == 0)
_gtk_rounded_box_path_top (border_box, &padding_box, cr);
{
/* top */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_TOP_LEFT].width / 2,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_LEFT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_TOP_LEFT].width,
stroke_box.bounds.origin.y,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_TOP_RIGHT].width,
stroke_box.bounds.origin.y);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_TOP_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_RIGHT].height / 2,
weight);
}
else if (i == 1)
_gtk_rounded_box_path_right (border_box, &padding_box, cr);
{
/* right */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_TOP_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_RIGHT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_RIGHT].height,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].height);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].height / 2,
weight);
}
else if (i == 2)
_gtk_rounded_box_path_bottom (border_box, &padding_box, cr);
{
/* bottom */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x + stroke_box.bounds.size.width - stroke_box.corner[GSK_CORNER_BOTTOM_RIGHT].width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].width,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].height / 2,
weight);
}
else if (i == 3)
_gtk_rounded_box_path_left (border_box, &padding_box, cr);
cairo_clip (cr);
{
/* left */
gsk_path_builder_move_to (builder,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].width / 2,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].height / 2);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.bounds.size.height - stroke_box.corner[GSK_CORNER_BOTTOM_LEFT].height,
weight);
gsk_path_builder_line_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y + stroke_box.corner[GSK_CORNER_TOP_LEFT].height);
gsk_path_builder_conic_to (builder,
stroke_box.bounds.origin.x,
stroke_box.bounds.origin.y,
stroke_box.bounds.origin.x + stroke_box.corner[GSK_CORNER_TOP_LEFT].width,
stroke_box.bounds.origin.y,
weight);
}
_gtk_rounded_box_path_side (&stroke_box, cr, i);
path = gsk_path_builder_free_to_path (builder);
stroke = create_stroke_style (border_width[i],
stroke_style,
_gtk_rounded_box_guess_length (&stroke_box, i));
gtk_snapshot_push_stroke (snapshot, path, stroke);
gsk_stroke_free (stroke);
gsk_path_unref (path);
gdk_cairo_set_source_rgba (cr, &colors[i]);
set_stroke_style (cr,
border_width[i],
stroke_style,
_gtk_rounded_box_guess_length (&stroke_box, i));
cairo_stroke (cr);
gtk_snapshot_append_border (snapshot, border_box, border_width, colors);
cairo_restore (cr);
gtk_snapshot_pop (snapshot);
}
}
}
static void
snapshot_frame_stroke (GtkSnapshot *snapshot,
const GskRoundedRect *outline,
const float border_width[4],
GdkRGBA colors[4],
guint hidden_side,
GtkBorderStyle stroke_style)
{
double double_width[4] = { border_width[0], border_width[1], border_width[2], border_width[3] };
cairo_t *cr;
cr = gtk_snapshot_append_cairo (snapshot,
&outline->bounds);
render_frame_stroke (cr, outline, double_width, colors, hidden_side, stroke_style);
cairo_destroy (cr);
}
static void
color_shade (const GdkRGBA *color,
double factor,
+2 -4
View File
@@ -1160,6 +1160,7 @@ gtk_snapshot_push_fill (GtkSnapshot *snapshot,
{
GtkSnapshotState *state;
/* FIXME: Is it worth calling ensure_affine() and transforming the path here? */
gtk_snapshot_ensure_identity (snapshot);
state = gtk_snapshot_push_state (snapshot,
@@ -1218,10 +1219,6 @@ gtk_snapshot_clear_stroke (GtkSnapshotState *state)
*
* The image is recorded until the next call to [method@Gtk.Snapshot.pop].
*
* Note that the strokes are subject to the same transformation as
* everything else, so uneven scaling will cause horizontal and vertical
* strokes to have different widths.
*
* Since: 4.14
*/
void
@@ -1231,6 +1228,7 @@ gtk_snapshot_push_stroke (GtkSnapshot *snapshot,
{
GtkSnapshotState *state;
/* FIXME: Is it worth calling ensure_affine() and transforming the path here? */
gtk_snapshot_ensure_identity (snapshot);
state = gtk_snapshot_push_state (snapshot,
+5 -25
View File
@@ -3977,11 +3977,9 @@ gtk_text_insert_at_cursor (GtkText *self,
if (priv->editable)
{
begin_change (self);
gtk_text_reset_im_context (self);
gtk_editable_insert_text (GTK_EDITABLE (self), str, -1, &pos);
gtk_text_set_selection_bounds (self, pos, pos);
end_change (self);
}
}
@@ -4001,14 +3999,12 @@ gtk_text_delete_from_cursor (GtkText *self,
return;
}
begin_change (self);
if (priv->selection_bound != priv->current_pos)
{
gtk_text_delete_selection (self);
gtk_text_schedule_im_reset (self);
gtk_text_reset_im_context (self);
goto done;
return;
}
switch (type)
@@ -4078,8 +4074,6 @@ gtk_text_delete_from_cursor (GtkText *self,
gtk_text_reset_im_context (self);
}
done:
end_change (self);
gtk_text_pend_cursor_blink (self);
}
@@ -4095,14 +4089,12 @@ gtk_text_backspace (GtkText *self)
return;
}
begin_change (self);
if (priv->selection_bound != priv->current_pos)
{
gtk_text_delete_selection (self);
gtk_text_schedule_im_reset (self);
gtk_text_reset_im_context (self);
goto done;
return;
}
prev_pos = gtk_text_move_logically (self, priv->current_pos, -1);
@@ -4155,8 +4147,6 @@ gtk_text_backspace (GtkText *self)
gtk_widget_error_bell (GTK_WIDGET (self));
}
done:
end_change (self);
gtk_text_pend_cursor_blink (self);
}
@@ -4201,11 +4191,7 @@ gtk_text_cut_clipboard (GtkText *self)
if (priv->editable)
{
if (priv->selection_bound != priv->current_pos)
{
begin_change (self);
gtk_text_delete_selection (self);
end_change (self);
}
gtk_text_delete_selection (self);
}
else
{
@@ -4223,15 +4209,9 @@ gtk_text_paste_clipboard (GtkText *self)
GtkTextPrivate *priv = gtk_text_get_instance_private (self);
if (priv->editable)
{
begin_change (self);
gtk_text_paste (self, gtk_widget_get_clipboard (GTK_WIDGET (self)));
end_change (self);
}
gtk_text_paste (self, gtk_widget_get_clipboard (GTK_WIDGET (self)));
else
{
gtk_widget_error_bell (GTK_WIDGET (self));
}
gtk_widget_error_bell (GTK_WIDGET (self));
gtk_text_update_handles (self);
}
-2
View File
@@ -744,7 +744,5 @@ gtk_viewport_scroll_to (GtkViewport *viewport,
gtk_adjustment_animate_to_value (viewport->adjustment[GTK_ORIENTATION_HORIZONTAL], x);
gtk_adjustment_animate_to_value (viewport->adjustment[GTK_ORIENTATION_VERTICAL], y);
g_clear_pointer (&scroll, gtk_scroll_info_unref);
}
+1 -14
View File
@@ -1312,22 +1312,10 @@ gtk_inspector_object_tree_select_object (GtkInspectorObjectTree *wt,
GTK_LIST_SCROLL_SELECT | GTK_LIST_SCROLL_FOCUS,
NULL);
g_signal_emit (wt, signals[OBJECT_SELECTED], 0, object);
g_signal_emit (wt, signals[OBJECT_SELECTED], 0, object); // FIXME
g_object_unref (row_item);
}
static void
on_selected_item (GtkSingleSelection *selection,
GParamSpec *pspec,
GtkInspectorObjectTree *wt)
{
GObject *selected = gtk_single_selection_get_selected_item (selection);
GtkTreeListRow *row = GTK_TREE_LIST_ROW (selected);
GObject *object = gtk_tree_list_row_get_item (row);
g_signal_emit (wt, signals[OBJECT_SELECTED], 0, object);
g_object_unref (object);
}
void
gtk_inspector_object_tree_set_display (GtkInspectorObjectTree *wt,
GdkDisplay *display)
@@ -1341,5 +1329,4 @@ gtk_inspector_object_tree_set_display (GtkInspectorObjectTree *wt,
wt->priv->selection = gtk_single_selection_new (g_object_ref (G_LIST_MODEL (wt->priv->tree_model)));
gtk_column_view_set_model (GTK_COLUMN_VIEW (wt->priv->list),
GTK_SELECTION_MODEL (wt->priv->selection));
g_signal_connect (wt->priv->selection, "notify::selected-item", G_CALLBACK (on_selected_item), wt);
}
-12
View File
@@ -27,7 +27,6 @@
#endif
#include <glib-object.h>
#include <gdk/gdk.h>
G_BEGIN_DECLS
@@ -88,39 +87,28 @@ struct _GtkPrinterOptionClass
void (*_gtk_reserved4) (void);
};
GDK_AVAILABLE_IN_ALL
GType gtk_printer_option_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_ALL
GtkPrinterOption *gtk_printer_option_new (const char *name,
const char *display_text,
GtkPrinterOptionType type);
GDK_AVAILABLE_IN_ALL
void gtk_printer_option_set (GtkPrinterOption *option,
const char *value);
GDK_AVAILABLE_IN_ALL
void gtk_printer_option_set_has_conflict (GtkPrinterOption *option,
gboolean has_conflict);
GDK_AVAILABLE_IN_ALL
void gtk_printer_option_clear_has_conflict (GtkPrinterOption *option);
GDK_AVAILABLE_IN_ALL
void gtk_printer_option_set_boolean (GtkPrinterOption *option,
gboolean value);
GDK_AVAILABLE_IN_ALL
void gtk_printer_option_allocate_choices (GtkPrinterOption *option,
int num);
GDK_AVAILABLE_IN_ALL
void gtk_printer_option_choices_from_array (GtkPrinterOption *option,
int num_choices,
const char **choices,
const char **choices_display);
GDK_AVAILABLE_IN_ALL
gboolean gtk_printer_option_has_choice (GtkPrinterOption *option,
const char *choice);
GDK_AVAILABLE_IN_ALL
void gtk_printer_option_set_activates_default (GtkPrinterOption *option,
gboolean activates);
GDK_AVAILABLE_IN_ALL
gboolean gtk_printer_option_get_activates_default (GtkPrinterOption *option);
Binary file not shown.
+1 -1
View File
@@ -23,7 +23,7 @@
#include <glib-object.h>
#include <gtk/gtk.h>
#include "gtk/print/gtkprintbackendprivate.h"
#include "gtkprintbackendprivate.h"
G_BEGIN_DECLS
+2 -2
View File
@@ -21,7 +21,7 @@
#include <glib-object.h>
#include <cpdb/cpdb-frontend.h>
#include <gtk/print/gtkprinterprivate.h>
#include <gtk/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__ */
+1 -7
View File
@@ -425,15 +425,9 @@ tools/gtk-builder-tool-screenshot.c
tools/gtk-builder-tool-simplify.c
tools/gtk-builder-tool-validate.c
tools/gtk-launch.c
tools/gtk-path-tool.c
tools/gtk-path-tool-decompose.c
tools/gtk-path-tool-info.c
tools/gtk-path-tool-render.c
tools/gtk-path-tool-show.c
tools/gtk-path-tool-utils.c
tools/gtk-rendernode-tool.c
tools/gtk-rendernode-tool-info.c
tools/gtk-rendernode-tool-render.c
tools/gtk-rendernode-tool-show.c
tools/gtk-rendernode-tool-render.c
tools/gtk-rendernode-tool-utils.c
tools/updateiconcache.c
+741 -1083
View File
File diff suppressed because it is too large Load Diff
+812 -1074
View File
File diff suppressed because it is too large Load Diff
+896 -1195
View File
File diff suppressed because it is too large Load Diff
+771 -1057
View File
File diff suppressed because it is too large Load Diff
+741 -1013
View File
File diff suppressed because it is too large Load Diff
+1669 -1207
View File
File diff suppressed because it is too large Load Diff
+740 -1010
View File
File diff suppressed because it is too large Load Diff
+2688 -3050
View File
File diff suppressed because it is too large Load Diff
-92
View File
@@ -1,92 +0,0 @@
/*
* Copyright © 2023 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Matthias Clasen <mclasen@redhat.com>
*/
#include <gtk/gtk.h>
#include <gsk/gskboundingboxprivate.h>
static void
init_random_rect (graphene_rect_t *r)
{
r->origin.x = g_test_rand_double_range (0, 1000);
r->origin.y = g_test_rand_double_range (0, 1000);
r->size.width = g_test_rand_double_range (0, 1000);
r->size.height = g_test_rand_double_range (0, 1000);
}
static void
test_to_rect (void)
{
graphene_rect_t rect, rect2;
GskBoundingBox bb;
graphene_point_t p, p2;
for (unsigned int i = 0; i < 100; i++)
{
init_random_rect (&rect);
gsk_bounding_box_init_from_rect (&bb, &rect);
gsk_bounding_box_to_rect (&bb, &rect2);
graphene_rect_get_top_left (&rect, &p);
graphene_rect_get_top_left (&rect2, &p2);
/* Note: that we can't assert equality here is the reason
* GskBoundingBox exists.
*/
g_assert_true (graphene_point_near (&p, &p2, 0.001));
graphene_rect_get_bottom_right (&rect, &p);
graphene_rect_get_bottom_right (&rect2, &p2);
g_assert_true (graphene_point_near (&p, &p2, 0.001));
}
}
static void
test_contains (void)
{
graphene_rect_t rect;
GskBoundingBox bb;
graphene_point_t p;
for (unsigned int i = 0; i < 100; i++)
{
init_random_rect (&rect);
gsk_bounding_box_init_from_rect (&bb, &rect);
g_assert_true (gsk_bounding_box_contains_point (&bb, &bb.min));
g_assert_true (gsk_bounding_box_contains_point (&bb, &bb.max));
graphene_point_interpolate (&bb.min, &bb.max, 0.5, &p);
g_assert_true (gsk_bounding_box_contains_point (&bb, &p));
}
}
int
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/bounding-box/to-rect", test_to_rect);
g_test_add_func ("/bounding-box/contains", test_contains);
return g_test_run ();
}
-13
View File
@@ -1,13 +0,0 @@
fill {
child: color {
bounds: 0 0 100 100;
color: rgb(255,0,0);
}
path: "\
M 10 10\
L 90 10\
L 90 90\
L 10 90\
Z";
fill-rule: winding;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 B

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

Before

Width:  |  Height:  |  Size: 346 B

+65 -1
View File
@@ -21,6 +21,57 @@
#include "gsk/gskcurveprivate.h"
static gboolean
measure_segment (const graphene_point_t *from,
const graphene_point_t *to,
float from_t,
float to_t,
GskCurveLineReason reason,
gpointer data)
{
float *length = data;
*length += graphene_point_distance (from, to, NULL, NULL);
return TRUE;
}
static float
measure_length (const GskCurve *curve)
{
float result = 0;
gsk_curve_decompose (curve, 0.5, measure_segment, &result);
return result;
}
/* This is a pretty nasty conic that makes it obvious that split()
* does not respect the progress values, so split() twice with
* scaled factor won't work.
*/
static void
test_conic_segment (void)
{
GskCurve c, s, e, m;
graphene_point_t pts[4] = {
GRAPHENE_POINT_INIT (-1856.131591796875, 46.217609405517578125),
GRAPHENE_POINT_INIT (-1555.9866943359375, 966.0810546875),
GRAPHENE_POINT_INIT (98.94945526123046875, 0),
GRAPHENE_POINT_INIT (-1471.33154296875, 526.701171875)
};
float start = 0.02222645096480846405029296875;
float end = 0.982032716274261474609375;
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CONIC, pts));
gsk_curve_split (&c, start, &s, NULL);
gsk_curve_segment (&c, start, end, &m);
gsk_curve_split (&c, end, NULL, &e);
g_assert_cmpfloat_with_epsilon (measure_length (&c), measure_length (&s) + measure_length (&m) + measure_length (&e), 0.03125);
}
static void
test_curve_tangents (void)
{
@@ -46,6 +97,17 @@ test_curve_tangents (void)
gsk_curve_get_end_tangent (&c, &t);
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
graphene_point_init (&p[0], 0, 0);
graphene_point_init (&p[1], 100, 0);
p[2] = GRAPHENE_POINT_INIT (g_test_rand_double_range (0, 20), 0);
graphene_point_init (&p[3], 100, 100);
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CONIC, p));
gsk_curve_get_start_tangent (&c, &t);
g_assert_true (graphene_vec2_near (&t, graphene_vec2_x_axis (), 0.0001));
gsk_curve_get_end_tangent (&c, &t);
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_axis (), 0.0001));
graphene_point_init (&p[0], 0, 0);
graphene_point_init (&p[1], 50, 0);
graphene_point_init (&p[2], 100, 50);
@@ -92,6 +154,7 @@ static gboolean
pathop_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskCurve *curve = user_data;
@@ -101,7 +164,7 @@ pathop_cb (GskPathOperation op,
if (op == GSK_PATH_MOVE)
return TRUE;
gsk_curve_init_foreach (curve, op, pts, n_pts);
gsk_curve_init_foreach (curve, op, pts, n_pts, weight);
return FALSE;
}
@@ -151,6 +214,7 @@ main (int argc,
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/curve/special/conic-segment", test_conic_segment);
g_test_add_func ("/curve/special/tangents", test_curve_tangents);
g_test_add_func ("/curve/special/degenerate-tangents", test_curve_degenerate_tangents);
g_test_add_func ("/curve/special/crossing", test_curve_crossing);
+87 -2
View File
@@ -8,6 +8,15 @@ init_random_point (graphene_point_t *p)
p->y = g_test_rand_double_range (0, 1000);
}
static float
random_weight (void)
{
if (g_test_rand_bit ())
return g_test_rand_double_range (1, 20);
else
return 1.0 / g_test_rand_double_range (1, 20);
}
static void
init_random_curve_with_op (GskCurve *curve,
GskPathOperation min_op,
@@ -48,6 +57,18 @@ init_random_curve_with_op (GskCurve *curve,
}
break;
case GSK_PATH_CONIC:
{
graphene_point_t p[4];
init_random_point (&p[0]);
init_random_point (&p[1]);
p[2] = GRAPHENE_POINT_INIT (random_weight(), 0);
init_random_point (&p[3]);
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_CONIC, p));
}
break;
default:
g_assert_not_reached ();
}
@@ -56,7 +77,7 @@ init_random_curve_with_op (GskCurve *curve,
static void
init_random_curve (GskCurve *curve)
{
init_random_curve_with_op (curve, GSK_PATH_LINE, GSK_PATH_CUBIC);
init_random_curve_with_op (curve, GSK_PATH_LINE, GSK_PATH_CONIC);
}
static void
@@ -196,17 +217,77 @@ static gboolean
add_curve_to_array (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GArray *array = user_data;
GskCurve c;
gsk_curve_init_foreach (&c, op, pts, n_pts);
gsk_curve_init_foreach (&c, op, pts, n_pts, weight);
g_array_append_val (array, c);
return TRUE;
}
static void
test_curve_decompose_conic (void)
{
g_test_skip ("No good error bounds for decomposing conics");
return;
for (int i = 0; i < 100; i++)
{
GArray *array;
GskCurve c;
GskPathBuilder *builder;
GskPath *path;
GskPathMeasure *measure;
const graphene_point_t *s;
init_random_curve_with_op (&c, GSK_PATH_CONIC, GSK_PATH_CONIC);
builder = gsk_path_builder_new ();
s = gsk_curve_get_start_point (&c);
gsk_path_builder_move_to (builder, s->x, s->y);
gsk_curve_builder_to (&c, builder);
path = gsk_path_builder_free_to_path (builder);
measure = gsk_path_measure_new_with_tolerance (path, 0.1);
array = g_array_new (FALSE, FALSE, sizeof (GskCurve));
g_assert_true (gsk_curve_decompose_curve (&c, GSK_PATH_FOREACH_ALLOW_CUBIC, 0.1, add_curve_to_array, array));
g_assert_cmpint (array->len, >=, 1);
for (int j = 0; j < array->len; j++)
{
GskCurve *c2 = &g_array_index (array, GskCurve, j);
g_assert_true (c2->op == GSK_PATH_CUBIC);
/* Check that the curves we got are approximating the conic */
for (int k = 0; k < 11; k++)
{
GskPathPoint point;
graphene_point_t p, q;
gsk_curve_get_point (c2, k/10.0, &p);
if (gsk_path_get_closest_point (path, &p, INFINITY, &point))
{
gsk_path_point_get_position (&point, &q);
g_assert_true (graphene_point_near (&p, &q, 0.5));
}
}
}
g_array_unref (array);
gsk_path_measure_unref (measure);
gsk_path_unref (path);
}
}
static void
test_curve_decompose_into (GskPathForeachFlags flags)
{
@@ -249,6 +330,9 @@ test_curve_decompose_into (GskPathForeachFlags flags)
case GSK_PATH_CUBIC:
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_CUBIC);
break;
case GSK_PATH_CONIC:
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_CONIC);
break;
default:
g_assert_not_reached ();
}
@@ -328,6 +412,7 @@ main (int argc, char *argv[])
g_test_add_func ("/curve/points", test_curve_points);
g_test_add_func ("/curve/tangents", test_curve_tangents);
g_test_add_func ("/curve/decompose", test_curve_decompose);
g_test_add_func ("/curve/decompose/conic", test_curve_decompose_conic);
g_test_add_func ("/curve/decompose/into/line", test_curve_decompose_into_line);
g_test_add_func ("/curve/decompose/into/quad", test_curve_decompose_into_quad);
g_test_add_func ("/curve/decompose/into/cubic", test_curve_decompose_into_cubic);
+183
View File
@@ -0,0 +1,183 @@
/*
* Copyright © 2020 Benjamin Otte
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#include <gtk/gtk.h>
static gboolean
build_path (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathBuilder *builder = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builder);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
break;
case GSK_PATH_CUBIC:
gsk_path_builder_cubic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
break;
default:
g_assert_not_reached ();
break;
}
return TRUE;
}
static void
test_simple (void)
{
const struct {
const char *test;
float dash[4];
gsize n_dash;
float dash_offset;
const char *result;
} tests[] = {
/* a line with a dash of a quarter its size, very simple test */
{
"M 0 0 L 20 0",
{ 5, }, 1, 0.f,
"M 0 0 L 5 0 M 10 0 L 15 0",
},
/* a square with a dash of half its size, another simple test */
{
"M 0 0 h 10 v 10 h -10 z",
{ 5, }, 1, 0.f,
"M 10 0 L 10 5 M 10 10 L 5 10 M 0 10 L 0 5 M 0 0 L 5 0"
},
/* a square smaller than the dash, make sure it closes */
{
"M 0 0 h 10 v 10 h -10 z",
{ 50, }, 1, 0.f,
"M 0 0 L 10 0 L 10 10 L 0 10 Z"
},
/* a square exactly the dash's size, make sure it still closes */
{
"M 0 0 h 10 v 10 h -10 z",
{ 40, }, 1, 0.f,
"M 0 0 L 10 0 L 10 10 L 0 10 Z"
},
/* a dash with offset */
{
"M 0 0 h 10 v 10 h -10 z",
{ 5, }, 1, 2.5f,
"M 7.5 0 L 10 0 L 10 2.5 M 10 7.5 L 10 10 L 7.5 10 M 2.5 10 L 0 10 L 0 7.5 M 0 2.5 L 0 0 L 2.5 0"
},
/* a dash with offset, but this time the rect isn't closed */
{
"M 0 0 L 10 0 L 10 10 L 0 10 L 0 0",
{ 5, }, 1, 2.5f,
"M 0 0 L 2.5 0 M 7.5 0 L 10 0 L 10 2.5 M 10 7.5 L 10 10 L 7.5 10 M 2.5 10 L 0 10 L 0 7.5 M 0 2.5 L 0 0"
},
/* a dash with offset into an empty dash */
{
"M 0 0 h 10 v 10 h -10 z",
{ 5, }, 1, 7.5f,
"M 2.5 0 L 7.5 0 M 10 2.5 L 10 7.5 M 7.5 10 L 2.5 10 M 0 7.5 L 0 2.5"
},
/* a dash with offset where the whole rectangle fits into one element - make sure it closes */
{
"M 0 0 h 10 v 10 h -10 z",
{ 1, 1, 100 }, 3, 3.f,
"M 0 0 L 10 0 L 10 10 L 0 10 Z"
},
/* a dash with 0-length elements, aka dotting */
{
"M 0 0 h 10 v 10 h -10 z",
{ 0, 5 }, 2, 0.f,
"M 5 0 M 10 0 M 10 5 M 10 10 M 5 10 M 0 10 M 0 5 M 0 0"
},
/* a dash of a circle */
{
"M 10 5 O 10 10, 5 10, 0.70710676908493042 O 0 10, 0 5, 0.70710676908493042 O 0 0, 5 0, 0.70710676908493042 O 10 0, 10 5, 0.70710676908493042 Z",
{ 32, }, 1, 0.f,
"M 10 5 O 10 10, 5 10, 0.70710676908493042 O 0 10, 0 5, 0.70710676908493042 O 0 0, 5 0, 0.70710676908493042 O 10 0, 10 5, 0.70710676908493042 Z",
},
/* a dash with offset and 2 contours */
{
"M 10 10 h 10 v 10 h -10 z M 20 20 h 10 v 10 h -10 z",
{ 5, }, 1, 2.5f,
"M 17.5 10 L 20 10 L 20 12.5 M 20 17.5 L 20 20 L 17.5 20 M 12.5 20 L 10 20 L 10 17.5 M 10 12.5 L 10 10 L 12.5 10 "
"M 27.5 20 L 30 20 L 30 22.5 M 30 27.5 L 30 30 L 27.5 30 M 22.5 30 L 20 30 L 20 27.5 M 20 22.5 L 20 20 L 22.5 20"
},
};
GskPath *path, *result;
GskPathBuilder *builder;
GskStroke *stroke;
char *s;
for (gsize i = 0; i < G_N_ELEMENTS(tests); i++)
{
stroke = gsk_stroke_new (1);
gsk_stroke_set_dash (stroke, tests[i].dash, tests[i].n_dash);
gsk_stroke_set_dash_offset (stroke, tests[i].dash_offset);
path = gsk_path_parse (tests[i].test);
g_assert_nonnull (path);
s = gsk_path_to_string (path);
g_assert_cmpstr (s, ==, tests[i].test);
g_free (s);
builder = gsk_path_builder_new ();
gsk_path_dash (path, stroke, 0.5, build_path, builder);
result = gsk_path_builder_free_to_path (builder);
s = gsk_path_to_string (result);
g_assert_cmpstr (s, ==, tests[i].result);
g_free (s);
gsk_path_unref (result);
gsk_stroke_free (stroke);
gsk_path_unref (path);
}
}
int
main (int argc,
char *argv[])
{
gtk_test_init (&argc, &argv, NULL);
g_test_add_func ("/dash/simple", test_simple);
return g_test_run ();
}
+265
View File
@@ -0,0 +1,265 @@
/*
* 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 ();
}
+917
View File
@@ -0,0 +1,917 @@
/*
* 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 ();
}
+3 -11
View File
@@ -59,7 +59,6 @@ compare_render_tests = [
'empty-shadow',
'empty-texture',
'empty-transform',
'fill',
'huge-height',
'huge-width',
'inset-shadow-multiple',
@@ -89,7 +88,6 @@ compare_render_tests = [
'scaled-cairo',
'scaled-texture',
'shadow-in-opacity',
'stroke',
'texture-scale-magnify-10000x',
'texture-scale-magnify-rotate',
'texture-scale-stripes',
@@ -272,8 +270,6 @@ node_parser_tests = [
'empty-cross-fade.ref.node',
'empty-debug.node',
'empty-debug.ref.node',
'empty-fill.node',
'empty-fill.ref.node',
'empty-inset-shadow.node',
'empty-inset-shadow.ref.node',
'empty-linear-gradient.node',
@@ -290,8 +286,6 @@ node_parser_tests = [
'empty-rounded-clip.ref.node',
'empty-shadow.node',
'empty-shadow.ref.node',
'empty-stroke.node',
'empty-stroke.ref.node',
'empty-text.node',
'empty-text.ref.node',
'empty-texture.node',
@@ -300,8 +294,6 @@ node_parser_tests = [
'empty-texture-scale.ref.node',
'empty-transform.node',
'empty-transform.ref.node',
'fill.node',
'fill.ref.node',
'glshader.node',
'glshader.ref.node',
'glshader.errors',
@@ -328,8 +320,6 @@ node_parser_tests = [
'string-error.errors',
'string-error.node',
'string-error.ref.node',
'stroke.node',
'stroke.ref.node',
'testswitch.node',
'text-fail.node',
'text-fail.ref.node',
@@ -371,6 +361,9 @@ tests = [
['shader'],
['path'],
['path-special-cases'],
['measure'],
['measure-special-cases'],
['dash'],
]
test_cargs = []
@@ -407,7 +400,6 @@ internal_tests = [
[ 'half-float' ],
['rounded-rect'],
['misc'],
['boundingbox'],
]
foreach t : internal_tests
-1
View File
@@ -1 +0,0 @@
fill { }
@@ -1,14 +0,0 @@
fill {
child: color {
bounds: 0 0 50 50;
color: rgb(255,0,204);
}
path: "\
M 25 0\
L 39.694629669189453 45.225425720214844\
L 1.2235870361328125 17.274574279785156\
L 48.776412963867188 17.274574279785156\
L 10.305368423461914 45.225425720214844\
Z";
fill-rule: winding;
}
@@ -1 +0,0 @@
stroke { }
@@ -1,16 +0,0 @@
stroke {
child: color {
bounds: 0 0 50 50;
color: rgb(255,0,204);
}
path: "\
M 25 0\
L 39.694629669189453 45.225425720214844\
L 1.2235870361328125 17.274574279785156\
L 48.776412963867188 17.274574279785156\
L 10.305368423461914 45.225425720214844\
Z";
line-width: 1;
line-cap: butt;
line-join: miter;
}
-4
View File
@@ -1,4 +0,0 @@
fill {
path: "M0,0L50,0C50,50 0,50 0,0Z";
fill-rule: winding;
}

Some files were not shown because too many files have changed in this diff Show More