Compare commits
112 Commits
ebassi/css
...
wip/matthi
Author | SHA1 | Date | |
---|---|---|---|
|
60e78cb210 | ||
|
ee0414f243 | ||
|
f813bd808b | ||
|
40d0c1d73f | ||
|
b4cd601baa | ||
|
6428b6654d | ||
|
06503f9604 | ||
|
4b0313c1cd | ||
|
32a934ecaa | ||
|
69d1826a6d | ||
|
1fac0f5391 | ||
|
ae085c3ae9 | ||
|
47c471cffa | ||
|
08d6e24370 | ||
|
3d65c0da84 | ||
|
d5ab5336aa | ||
|
726ebecc82 | ||
|
e6ba20951b | ||
|
8876b44369 | ||
|
7cbd15a70f | ||
|
f1624351ba | ||
|
24edad6eb9 | ||
|
d82522da89 | ||
|
ccc41d67a4 | ||
|
02d13963a7 | ||
|
b052b5d5c7 | ||
|
15cd57727e | ||
|
3d8bf36945 | ||
|
bd9a93d549 | ||
|
826efc4dff | ||
|
d4705216ed | ||
|
caf1b0b86c | ||
|
5f634ee30f | ||
|
1054485afb | ||
|
4bb65eaf4c | ||
|
043f5db9de | ||
|
5c3a5761b4 | ||
|
9ef17b9b32 | ||
|
215488527c | ||
|
07241f44c2 | ||
|
e1bee80600 | ||
|
c56989410e | ||
|
2f281f19b6 | ||
|
6b4d7d3891 | ||
|
bd554f6c92 | ||
|
31fd7933b7 | ||
|
91e11c4e8d | ||
|
e8bad9db7b | ||
|
1186fea22f | ||
|
b5f3aebdd4 | ||
|
22c3c2e7ed | ||
|
fcf3874ddb | ||
|
bcdece047b | ||
|
233a2eb1bc | ||
|
28e1e71330 | ||
|
a51ad6c640 | ||
|
2056a8bdd0 | ||
|
726c7af23c | ||
|
d6184416cf | ||
|
c3cd956ae1 | ||
|
666debf505 | ||
|
e7763919d6 | ||
|
79d2743af9 | ||
|
bad799fb6d | ||
|
ca2825c933 | ||
|
6b00f9180b | ||
|
8a8de18b1b | ||
|
3840737aa1 | ||
|
d211c29ee2 | ||
|
f62cc24397 | ||
|
8395204f5e | ||
|
85e4bd95c1 | ||
|
ce2505fe0e | ||
|
6ec73db87c | ||
|
f83d5a9d20 | ||
|
d0e8214c5b | ||
|
0f8cb3255b | ||
|
f2e19bc111 | ||
|
f737332514 | ||
|
381685b1af | ||
|
3ba7373205 | ||
|
872d3c20f0 | ||
|
59686706b2 | ||
|
7b5161e520 | ||
|
9d95f674ea | ||
|
1fb69de26c | ||
|
73e51f0013 | ||
|
77b064fb08 | ||
|
ff27c6f21c | ||
|
b979be8dd1 | ||
|
eed833f841 | ||
|
d998d07250 | ||
|
341cc3ab30 | ||
|
0ed1482cce | ||
|
cd4915e290 | ||
|
27062bd90b | ||
|
6a93c0da41 | ||
|
60cc408d3e | ||
|
0978e65839 | ||
|
42ff7fad9f | ||
|
6cec73f759 | ||
|
50b7b20a6c | ||
|
1efc48e3e6 | ||
|
645f745f68 | ||
|
df48976185 | ||
|
937f57985a | ||
|
19670b643d | ||
|
11aa1ac271 | ||
|
c8880b3bfb | ||
|
f7c58b6b61 | ||
|
4097327387 | ||
|
50c16ac137 |
@@ -335,6 +335,9 @@
|
||||
<file>paintable_symbolic.c</file>
|
||||
<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>
|
||||
@@ -420,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>
|
||||
|
@@ -72,6 +72,9 @@ demos = files([
|
||||
'paintable_symbolic.c',
|
||||
'panes.c',
|
||||
'password_entry.c',
|
||||
'path_fill.c',
|
||||
'path_maze.c',
|
||||
'path_text.c',
|
||||
'peg_solitaire.c',
|
||||
'pickers.c',
|
||||
'printing.c',
|
||||
|
370
demos/gtk-demo/path_fill.c
Normal file
370
demos/gtk-demo/path_fill.c
Normal file
@@ -0,0 +1,370 @@
|
||||
/* Path/Fill
|
||||
*
|
||||
* This demo shows how to use PangoCairo to draw text with more than
|
||||
* just a single color.
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "paintable.h"
|
||||
#include "gsk/gskpathdashprivate.h"
|
||||
|
||||
#define GTK_TYPE_PATH_PAINTABLE (gtk_path_paintable_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (GtkPathPaintable, gtk_path_paintable, GTK, PATH_PAINTABLE, GObject)
|
||||
|
||||
struct _GtkPathPaintable
|
||||
{
|
||||
GObject parent_instance;
|
||||
|
||||
int width;
|
||||
int height;
|
||||
GskPath *path;
|
||||
GdkPaintable *background;
|
||||
};
|
||||
|
||||
struct _GtkPathPaintableClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
static int
|
||||
gtk_path_paintable_get_intrinsic_width (GdkPaintable *paintable)
|
||||
{
|
||||
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
|
||||
|
||||
if (self->background)
|
||||
return MAX (gdk_paintable_get_intrinsic_width (self->background), self->width);
|
||||
else
|
||||
return self->width;
|
||||
}
|
||||
|
||||
static int
|
||||
gtk_path_paintable_get_intrinsic_height (GdkPaintable *paintable)
|
||||
{
|
||||
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
|
||||
|
||||
if (self->background)
|
||||
return MAX (gdk_paintable_get_intrinsic_height (self->background), self->height);
|
||||
else
|
||||
return self->height;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_paintable_snapshot (GdkPaintable *paintable,
|
||||
GdkSnapshot *snapshot,
|
||||
double width,
|
||||
double height)
|
||||
{
|
||||
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
|
||||
|
||||
#if 0
|
||||
gtk_snapshot_push_fill (snapshot, self->path, GSK_FILL_RULE_WINDING);
|
||||
#else
|
||||
GskStroke *stroke = gsk_stroke_new (2.0);
|
||||
gtk_snapshot_push_stroke (snapshot, self->path, stroke);
|
||||
gsk_stroke_free (stroke);
|
||||
#endif
|
||||
|
||||
if (self->background)
|
||||
{
|
||||
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 GdkPaintableFlags
|
||||
gtk_path_paintable_get_flags (GdkPaintable *paintable)
|
||||
{
|
||||
GtkPathPaintable *self = GTK_PATH_PAINTABLE (paintable);
|
||||
|
||||
if (self->background)
|
||||
return gdk_paintable_get_flags (self->background);
|
||||
else
|
||||
return GDK_PAINTABLE_STATIC_CONTENTS | GDK_PAINTABLE_STATIC_SIZE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_paintable_paintable_init (GdkPaintableInterface *iface)
|
||||
{
|
||||
iface->get_intrinsic_width = gtk_path_paintable_get_intrinsic_width;
|
||||
iface->get_intrinsic_height = gtk_path_paintable_get_intrinsic_height;
|
||||
iface->snapshot = gtk_path_paintable_snapshot;
|
||||
iface->get_flags = gtk_path_paintable_get_flags;
|
||||
}
|
||||
|
||||
/* When defining the GType, we need to implement the GdkPaintable interface */
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkPathPaintable, gtk_path_paintable, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
|
||||
gtk_path_paintable_paintable_init))
|
||||
|
||||
/* Here's the boilerplate for the GObject declaration.
|
||||
* We need to disconnect the signals here that we set up elsewhere
|
||||
*/
|
||||
static void
|
||||
gtk_path_paintable_dispose (GObject *object)
|
||||
{
|
||||
GtkPathPaintable *self = GTK_PATH_PAINTABLE (object);
|
||||
|
||||
if (self->background)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (self->background, gdk_paintable_invalidate_contents, self);
|
||||
g_signal_handlers_disconnect_by_func (self->background, gdk_paintable_invalidate_size, self);
|
||||
g_clear_object (&self->background);
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (gtk_path_paintable_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_paintable_class_init (GtkPathPaintableClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = gtk_path_paintable_dispose;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_paintable_init (GtkPathPaintable *self)
|
||||
{
|
||||
}
|
||||
|
||||
/* And finally, we add a simple constructor.
|
||||
* It is declared in the header so that the other examples
|
||||
* can use it.
|
||||
*/
|
||||
GdkPaintable *
|
||||
gtk_path_paintable_new (GskPath *path,
|
||||
GdkPaintable *background,
|
||||
int width,
|
||||
int height)
|
||||
{
|
||||
GtkPathPaintable *self;
|
||||
|
||||
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;
|
||||
graphene_point_t pos;
|
||||
graphene_vec2_t tangent;
|
||||
GskStroke *stroke;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_segment (builder,
|
||||
measure,
|
||||
#if 1
|
||||
0.0, gsk_path_measure_get_length (measure));
|
||||
#else
|
||||
progress > 1 ? (progress - 1) * gsk_path_measure_get_length (measure) : 0.0,
|
||||
(progress < 1 ? progress : 1.0) * gsk_path_measure_get_length (measure));
|
||||
#endif
|
||||
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
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);
|
||||
gsk_path_unref (path);
|
||||
|
||||
gsk_path_measure_get_point (measure,
|
||||
(progress > 1 ? (progress - 1) : progress) * gsk_path_measure_get_length (measure),
|
||||
&pos,
|
||||
&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)
|
||||
{
|
||||
static GtkWidget *window = NULL;
|
||||
|
||||
if (!window)
|
||||
{
|
||||
GtkWidget *picture;
|
||||
GdkPaintable *paintable;
|
||||
GtkMediaStream *stream;
|
||||
GskPath *path;
|
||||
graphene_rect_t bounds;
|
||||
GskPathMeasure *measure;
|
||||
|
||||
window = gtk_window_new ();
|
||||
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
|
||||
gtk_window_set_title (GTK_WINDOW (window), "Path Fill");
|
||||
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
|
||||
|
||||
#if 0
|
||||
stream = gtk_media_file_new_for_resource ("/images/gtk-logo.webm");
|
||||
#else
|
||||
stream = gtk_nuclear_media_stream_new ();
|
||||
#endif
|
||||
gtk_media_stream_play (stream);
|
||||
gtk_media_stream_set_loop (stream, TRUE);
|
||||
|
||||
path = create_hexagon (window);
|
||||
path = create_path_from_text (window);
|
||||
gsk_path_get_bounds (path, &bounds);
|
||||
|
||||
paintable = gtk_path_paintable_new (path,
|
||||
GDK_PAINTABLE (stream),
|
||||
bounds.origin.x + bounds.size.width,
|
||||
bounds.origin.y + bounds.size.height);
|
||||
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);
|
||||
|
||||
gtk_window_set_child (GTK_WINDOW (window), picture);
|
||||
}
|
||||
|
||||
if (!gtk_widget_get_visible (window))
|
||||
gtk_window_present (GTK_WINDOW (window));
|
||||
else
|
||||
gtk_window_destroy (GTK_WINDOW (window));
|
||||
|
||||
return window;
|
||||
}
|
338
demos/gtk-demo/path_maze.c
Normal file
338
demos/gtk-demo/path_maze.c
Normal file
@@ -0,0 +1,338 @@
|
||||
/* 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)
|
||||
{
|
||||
if (!self->active)
|
||||
return;
|
||||
|
||||
if (gsk_path_measure_get_closest_point (self->measure, &GRAPHENE_POINT_INIT (x, y), 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;
|
||||
}
|
596
demos/gtk-demo/path_text.c
Normal file
596
demos/gtk-demo/path_text.c
Normal file
@@ -0,0 +1,596 @@
|
||||
/* 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;
|
||||
|
||||
gsk_path_measure_get_point (measure, (pt->x + offset->x) * scale, res, &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;
|
||||
graphene_point_t closest;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_measure_get_point (self->line_measure, self->line_closest, &closest, NULL);
|
||||
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)
|
||||
{
|
||||
gsk_path_measure_get_closest_point_full (self->line_measure,
|
||||
&GRAPHENE_POINT_INIT (x, y),
|
||||
INFINITY,
|
||||
&self->line_closest,
|
||||
NULL, 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
demos/gtk-demo/path_text.ui
Normal file
38
demos/gtk-demo/path_text.ui
Normal 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>
|
@@ -7,68 +7,186 @@
|
||||
#include <glib/gi18n.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
static void
|
||||
draw_text (GtkDrawingArea *da,
|
||||
cairo_t *cr,
|
||||
int width,
|
||||
int height,
|
||||
gpointer data)
|
||||
#define TEXT_TYPE_MASK_DEMO (text_mask_demo_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (TextMaskDemo, text_mask_demo, TEXT, MASK_DEMO, GtkWidget)
|
||||
|
||||
struct _TextMaskDemo
|
||||
{
|
||||
cairo_pattern_t *pattern;
|
||||
PangoLayout *layout;
|
||||
GtkWidget parent_instance;
|
||||
|
||||
char *text;
|
||||
PangoFontDescription *desc;
|
||||
GskPath *path;
|
||||
};
|
||||
|
||||
cairo_save (cr);
|
||||
struct _TextMaskDemoClass
|
||||
{
|
||||
GtkWidgetClass parent_class;
|
||||
};
|
||||
|
||||
layout = gtk_widget_create_pango_layout (GTK_WIDGET (da), "Pango power!\nPango power!\nPango power!");
|
||||
desc = pango_font_description_from_string ("sans bold 34");
|
||||
pango_layout_set_font_description (layout, desc);
|
||||
pango_font_description_free (desc);
|
||||
G_DEFINE_TYPE (TextMaskDemo, text_mask_demo, GTK_TYPE_WIDGET)
|
||||
|
||||
cairo_move_to (cr, 30, 20);
|
||||
pango_cairo_layout_path (cr, layout);
|
||||
g_object_unref (layout);
|
||||
static void
|
||||
update_path (TextMaskDemo *demo)
|
||||
{
|
||||
PangoLayout *layout;
|
||||
GskPathBuilder *builder;
|
||||
|
||||
pattern = cairo_pattern_create_linear (0.0, 0.0, width, height);
|
||||
cairo_pattern_add_color_stop_rgb (pattern, 0.0, 1.0, 0.0, 0.0);
|
||||
cairo_pattern_add_color_stop_rgb (pattern, 0.2, 1.0, 0.0, 0.0);
|
||||
cairo_pattern_add_color_stop_rgb (pattern, 0.3, 1.0, 1.0, 0.0);
|
||||
cairo_pattern_add_color_stop_rgb (pattern, 0.4, 0.0, 1.0, 0.0);
|
||||
cairo_pattern_add_color_stop_rgb (pattern, 0.6, 0.0, 1.0, 1.0);
|
||||
cairo_pattern_add_color_stop_rgb (pattern, 0.7, 0.0, 0.0, 1.0);
|
||||
cairo_pattern_add_color_stop_rgb (pattern, 0.8, 1.0, 0.0, 1.0);
|
||||
cairo_pattern_add_color_stop_rgb (pattern, 1.0, 1.0, 0.0, 1.0);
|
||||
g_clear_pointer (&demo->path, gsk_path_unref);
|
||||
|
||||
cairo_set_source (cr, pattern);
|
||||
cairo_fill_preserve (cr);
|
||||
layout = gtk_widget_create_pango_layout (GTK_WIDGET (demo), demo->text);
|
||||
pango_layout_set_font_description (layout, demo->desc);
|
||||
|
||||
cairo_pattern_destroy (pattern);
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_layout (builder, layout);
|
||||
demo->path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
|
||||
cairo_set_line_width (cr, 0.5);
|
||||
cairo_stroke (cr);
|
||||
gtk_widget_queue_draw (GTK_WIDGET (demo));
|
||||
}
|
||||
|
||||
cairo_restore (cr);
|
||||
static void
|
||||
text_mask_demo_init (TextMaskDemo *demo)
|
||||
{
|
||||
demo->text = g_strdup ("No text. No fun");
|
||||
demo->desc = pango_font_description_from_string ("Cantarell 20");
|
||||
update_path (demo);
|
||||
}
|
||||
|
||||
static void
|
||||
text_mask_demo_snapshot (GtkWidget *widget,
|
||||
GtkSnapshot *snapshot)
|
||||
{
|
||||
TextMaskDemo *demo = TEXT_MASK_DEMO (widget);
|
||||
float width, height;
|
||||
GskColorStop stops[4];
|
||||
GskStroke *stroke;
|
||||
|
||||
gdk_rgba_parse (&stops[0].color, "red");
|
||||
gdk_rgba_parse (&stops[1].color, "green");
|
||||
gdk_rgba_parse (&stops[2].color, "yellow");
|
||||
gdk_rgba_parse (&stops[3].color, "blue");
|
||||
|
||||
width = gtk_widget_get_width (widget);
|
||||
height = gtk_widget_get_height (widget);
|
||||
|
||||
stops[0].offset = 0;
|
||||
stops[1].offset = 0.25;
|
||||
stops[2].offset = 0.50;
|
||||
stops[3].offset = 0.75;
|
||||
|
||||
gtk_snapshot_push_fill (snapshot, demo->path, GSK_FILL_RULE_WINDING);
|
||||
|
||||
gtk_snapshot_append_linear_gradient (snapshot,
|
||||
&GRAPHENE_RECT_INIT (0, 0, width, height),
|
||||
&GRAPHENE_POINT_INIT (0, 0),
|
||||
&GRAPHENE_POINT_INIT (width, height),
|
||||
stops, 4);
|
||||
gtk_snapshot_pop (snapshot);
|
||||
|
||||
stroke = gsk_stroke_new (1.0);
|
||||
gtk_snapshot_push_stroke (snapshot, demo->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);
|
||||
}
|
||||
|
||||
void
|
||||
text_mask_demo_measure (GtkWidget *widget,
|
||||
GtkOrientation orientation,
|
||||
int for_size,
|
||||
int *minimum_size,
|
||||
int *natural_size,
|
||||
int *minimum_baseline,
|
||||
int *natural_baseline)
|
||||
{
|
||||
TextMaskDemo *demo = TEXT_MASK_DEMO (widget);
|
||||
graphene_rect_t rect;
|
||||
|
||||
gsk_path_get_bounds (demo->path, &rect);
|
||||
|
||||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||||
*minimum_size = *natural_size = rect.size.width;
|
||||
else
|
||||
*minimum_size = *natural_size = rect.size.height;
|
||||
}
|
||||
|
||||
static void
|
||||
text_mask_demo_finalize (GObject *object)
|
||||
{
|
||||
TextMaskDemo *demo = TEXT_MASK_DEMO (object);
|
||||
|
||||
g_free (demo->text);
|
||||
pango_font_description_free (demo->desc);
|
||||
gsk_path_unref (demo->path);
|
||||
|
||||
G_OBJECT_CLASS (text_mask_demo_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
text_mask_demo_class_init (TextMaskDemoClass *class)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
|
||||
|
||||
object_class->finalize = text_mask_demo_finalize;
|
||||
|
||||
widget_class->snapshot = text_mask_demo_snapshot;
|
||||
widget_class->measure = text_mask_demo_measure;
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
text_mask_demo_new (void)
|
||||
{
|
||||
return g_object_new (text_mask_demo_get_type (), NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
text_mask_demo_set_text (TextMaskDemo *demo,
|
||||
const char *text)
|
||||
{
|
||||
g_free (demo->text);
|
||||
demo->text = g_strdup (text);
|
||||
|
||||
update_path (demo);
|
||||
}
|
||||
|
||||
static void
|
||||
text_mask_demo_set_font (TextMaskDemo *demo,
|
||||
PangoFontDescription *desc)
|
||||
{
|
||||
pango_font_description_free (demo->desc);
|
||||
demo->desc = pango_font_description_copy (desc);
|
||||
|
||||
update_path (demo);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
do_textmask (GtkWidget *do_widget)
|
||||
{
|
||||
static GtkWidget *window = NULL;
|
||||
static GtkWidget *da;
|
||||
static GtkWidget *demo;
|
||||
|
||||
if (!window)
|
||||
{
|
||||
PangoFontDescription *desc;
|
||||
|
||||
window = gtk_window_new ();
|
||||
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
|
||||
gtk_widget_set_size_request (window, 400, 240);
|
||||
gtk_window_set_title (GTK_WINDOW (window), "Text Mask");
|
||||
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
|
||||
|
||||
da = gtk_drawing_area_new ();
|
||||
demo = text_mask_demo_new ();
|
||||
text_mask_demo_set_text (TEXT_MASK_DEMO (demo), "Pango power!\nPango power!\nPango power!");
|
||||
desc = pango_font_description_from_string ("Sans Bold 34");
|
||||
text_mask_demo_set_font (TEXT_MASK_DEMO (demo), desc);
|
||||
pango_font_description_free (desc);
|
||||
|
||||
gtk_window_set_child (GTK_WINDOW (window), da);
|
||||
gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_text, NULL, NULL);
|
||||
gtk_window_set_child (GTK_WINDOW (window), demo);
|
||||
}
|
||||
|
||||
if (!gtk_widget_get_visible (window))
|
||||
|
@@ -271,6 +271,8 @@ collect_reused_child_nodes (GskRenderer *renderer,
|
||||
case GSK_CROSS_FADE_NODE:
|
||||
case GSK_BLUR_NODE:
|
||||
case GSK_MASK_NODE:
|
||||
case GSK_FILL_NODE:
|
||||
case GSK_STROKE_NODE:
|
||||
|
||||
default:
|
||||
|
||||
@@ -859,6 +861,8 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer,
|
||||
case GSK_CROSS_FADE_NODE:
|
||||
case GSK_BLUR_NODE:
|
||||
case GSK_GL_SHADER_NODE:
|
||||
case GSK_FILL_NODE:
|
||||
case GSK_STROKE_NODE:
|
||||
default:
|
||||
break; /* Fallback */
|
||||
}
|
||||
|
@@ -165,7 +165,7 @@ void half_to_float4 (const guint16 h[4], float f[4]) __attribute__((ifunc ("reso
|
||||
void float_to_half (const float *f, guint16 *h, int n) __attribute__((ifunc ("resolve_float_to_half")));
|
||||
void half_to_float (const guint16 *h, float *f, int n) __attribute__((ifunc ("resolve_half_to_float")));
|
||||
|
||||
static void *
|
||||
static void * __attribute__ ((no_sanitize_address))
|
||||
resolve_float_to_half4 (void)
|
||||
{
|
||||
__builtin_cpu_init ();
|
||||
@@ -175,7 +175,7 @@ resolve_float_to_half4 (void)
|
||||
return float_to_half4_c;
|
||||
}
|
||||
|
||||
static void *
|
||||
static void * __attribute__ ((no_sanitize_address))
|
||||
resolve_half_to_float4 (void)
|
||||
{
|
||||
__builtin_cpu_init ();
|
||||
@@ -185,7 +185,7 @@ resolve_half_to_float4 (void)
|
||||
return half_to_float4_c;
|
||||
}
|
||||
|
||||
static void *
|
||||
static void * __attribute__ ((no_sanitize_address))
|
||||
resolve_float_to_half (void)
|
||||
{
|
||||
__builtin_cpu_init ();
|
||||
@@ -195,7 +195,7 @@ resolve_float_to_half (void)
|
||||
return float_to_half_c;
|
||||
}
|
||||
|
||||
static void *
|
||||
static void * __attribute__ ((no_sanitize_address))
|
||||
resolve_half_to_float (void)
|
||||
{
|
||||
__builtin_cpu_init ();
|
||||
|
@@ -254,6 +254,8 @@ node_supports_2d_transform (const GskRenderNode *node)
|
||||
case GSK_BLEND_NODE:
|
||||
case GSK_BLUR_NODE:
|
||||
case GSK_MASK_NODE:
|
||||
case GSK_FILL_NODE:
|
||||
case GSK_STROKE_NODE:
|
||||
return TRUE;
|
||||
|
||||
case GSK_SHADOW_NODE:
|
||||
@@ -308,6 +310,8 @@ node_supports_transform (const GskRenderNode *node)
|
||||
case GSK_BLEND_NODE:
|
||||
case GSK_BLUR_NODE:
|
||||
case GSK_MASK_NODE:
|
||||
case GSK_FILL_NODE:
|
||||
case GSK_STROKE_NODE:
|
||||
return TRUE;
|
||||
|
||||
case GSK_SHADOW_NODE:
|
||||
@@ -4111,6 +4115,14 @@ gsk_gl_render_job_visit_node (GskGLRenderJob *job,
|
||||
gsk_gl_render_job_visit_as_fallback (job, node);
|
||||
break;
|
||||
|
||||
case GSK_FILL_NODE:
|
||||
gsk_gl_render_job_visit_as_fallback (job, node);
|
||||
break;
|
||||
|
||||
case GSK_STROKE_NODE:
|
||||
gsk_gl_render_job_visit_as_fallback (job, node);
|
||||
break;
|
||||
|
||||
case GSK_NOT_A_RENDER_NODE:
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
|
@@ -20,11 +20,15 @@
|
||||
#define __GSK_H_INSIDE__
|
||||
|
||||
#include <gsk/gskenums.h>
|
||||
#include <gsk/gskglshader.h>
|
||||
#include <gsk/gskpath.h>
|
||||
#include <gsk/gskpathbuilder.h>
|
||||
#include <gsk/gskpathmeasure.h>
|
||||
#include <gsk/gskrenderer.h>
|
||||
#include <gsk/gskrendernode.h>
|
||||
#include <gsk/gskroundedrect.h>
|
||||
#include <gsk/gskstroke.h>
|
||||
#include <gsk/gsktransform.h>
|
||||
#include <gsk/gskglshader.h>
|
||||
|
||||
#include <gsk/gskcairorenderer.h>
|
||||
|
||||
|
1862
gsk/gskcontour.c
Normal file
1862
gsk/gskcontour.c
Normal file
File diff suppressed because it is too large
Load Diff
101
gsk/gskcontourprivate.h
Normal file
101
gsk/gskcontourprivate.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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>
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GSK_CONTOUR_PRIVATE_H__
|
||||
#define __GSK_CONTOUR_PRIVATE_H__
|
||||
|
||||
#include "gskpath.h"
|
||||
#include "gskpathopprivate.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GSK_PATH_FLAT,
|
||||
GSK_PATH_CLOSED
|
||||
} GskPathFlags;
|
||||
|
||||
typedef struct _GskContour GskContour;
|
||||
|
||||
GskContour * gsk_rect_contour_new (const graphene_rect_t *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,
|
||||
const gskpathop *ops,
|
||||
gsize n_ops,
|
||||
gssize offset);
|
||||
|
||||
void gsk_contour_copy (GskContour * dest,
|
||||
const GskContour *src);
|
||||
GskContour * gsk_contour_dup (const GskContour *src);
|
||||
|
||||
gsize gsk_contour_get_size (const GskContour *self);
|
||||
GskPathFlags gsk_contour_get_flags (const GskContour *self);
|
||||
void gsk_contour_print (const GskContour *self,
|
||||
GString *string);
|
||||
gboolean gsk_contour_get_bounds (const GskContour *self,
|
||||
graphene_rect_t *bounds);
|
||||
gpointer gsk_contour_init_measure (const GskContour *self,
|
||||
float tolerance,
|
||||
float *out_length);
|
||||
void gsk_contour_free_measure (const GskContour *self,
|
||||
gpointer data);
|
||||
gboolean gsk_contour_foreach (const GskContour *self,
|
||||
float tolerance,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
void gsk_contour_get_start_end (const GskContour *self,
|
||||
graphene_point_t *start,
|
||||
graphene_point_t *end);
|
||||
void gsk_contour_get_point (const GskContour *self,
|
||||
gpointer measure_data,
|
||||
float distance,
|
||||
graphene_point_t *pos,
|
||||
graphene_vec2_t *tangent);
|
||||
gboolean gsk_contour_get_closest_point (const GskContour *self,
|
||||
gpointer measure_data,
|
||||
float tolerance,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
float *out_distance,
|
||||
graphene_point_t *out_pos,
|
||||
float *out_offset,
|
||||
graphene_vec2_t *out_tangent);
|
||||
int gsk_contour_get_winding (const GskContour *self,
|
||||
gpointer measure_data,
|
||||
const graphene_point_t *point);
|
||||
void gsk_contour_add_segment (const GskContour *self,
|
||||
GskPathBuilder *builder,
|
||||
gpointer measure_data,
|
||||
gboolean emit_move_to,
|
||||
float start,
|
||||
float end);
|
||||
gboolean gsk_contour_get_stroke_bounds (const GskContour *self,
|
||||
const GskStroke *stroke,
|
||||
graphene_rect_t *bounds);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GSK_CONTOUR_PRIVATE_H__ */
|
||||
|
1201
gsk/gskcurve.c
Normal file
1201
gsk/gskcurve.c
Normal file
File diff suppressed because it is too large
Load Diff
145
gsk/gskcurveprivate.h
Normal file
145
gsk/gskcurveprivate.h
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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>
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GSK_CURVE_PRIVATE_H__
|
||||
#define __GSK_CURVE_PRIVATE_H__
|
||||
|
||||
#include "gskpathopprivate.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef gpointer gskpathop;
|
||||
|
||||
typedef union _GskCurve GskCurve;
|
||||
|
||||
typedef struct _GskLineCurve GskLineCurve;
|
||||
typedef struct _GskQuadCurve GskQuadCurve;
|
||||
typedef struct _GskCubicCurve GskCubicCurve;
|
||||
typedef struct _GskConicCurve GskConicCurve;
|
||||
|
||||
struct _GskLineCurve
|
||||
{
|
||||
GskPathOperation op;
|
||||
|
||||
gboolean padding;
|
||||
|
||||
graphene_point_t points[2];
|
||||
};
|
||||
|
||||
struct _GskQuadCurve
|
||||
{
|
||||
GskPathOperation op;
|
||||
|
||||
gboolean has_coefficients;
|
||||
|
||||
graphene_point_t points[3];
|
||||
|
||||
graphene_point_t coeffs[3];
|
||||
};
|
||||
|
||||
struct _GskCubicCurve
|
||||
{
|
||||
GskPathOperation op;
|
||||
|
||||
gboolean has_coefficients;
|
||||
|
||||
graphene_point_t points[4];
|
||||
|
||||
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 {
|
||||
GSK_CURVE_LINE_REASON_STRAIGHT,
|
||||
GSK_CURVE_LINE_REASON_SHORT
|
||||
} GskCurveLineReason;
|
||||
|
||||
typedef gboolean (* GskCurveAddLineFunc) (const graphene_point_t *from,
|
||||
const graphene_point_t *to,
|
||||
float from_progress,
|
||||
float to_progress,
|
||||
GskCurveLineReason reason,
|
||||
gpointer user_data);
|
||||
|
||||
void gsk_curve_init (GskCurve *curve,
|
||||
gskpathop op);
|
||||
void gsk_curve_init_foreach (GskCurve *curve,
|
||||
GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
float weight);
|
||||
|
||||
void gsk_curve_get_point (const GskCurve *curve,
|
||||
float progress,
|
||||
graphene_point_t *pos);
|
||||
void gsk_curve_get_tangent (const GskCurve *curve,
|
||||
float progress,
|
||||
graphene_vec2_t *tangent);
|
||||
void gsk_curve_split (const GskCurve *curve,
|
||||
float progress,
|
||||
GskCurve *start,
|
||||
GskCurve *end);
|
||||
void gsk_curve_segment (const GskCurve *curve,
|
||||
float start,
|
||||
float end,
|
||||
GskCurve *segment);
|
||||
gboolean gsk_curve_decompose (const GskCurve *curve,
|
||||
float tolerance,
|
||||
GskCurveAddLineFunc add_line_func,
|
||||
gpointer user_data);
|
||||
gskpathop gsk_curve_pathop (const GskCurve *curve);
|
||||
#define gsk_curve_builder_to(curve, builder) gsk_path_builder_pathop_to ((builder), gsk_curve_pathop (curve))
|
||||
const graphene_point_t *gsk_curve_get_start_point (const GskCurve *curve);
|
||||
const graphene_point_t *gsk_curve_get_end_point (const GskCurve *curve);
|
||||
void gsk_curve_get_start_tangent (const GskCurve *curve,
|
||||
graphene_vec2_t *tangent);
|
||||
void gsk_curve_get_end_tangent (const GskCurve *curve,
|
||||
graphene_vec2_t *tangent);
|
||||
|
||||
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GSK_CURVE_PRIVATE_H__ */
|
||||
|
110
gsk/gskenums.h
110
gsk/gskenums.h
@@ -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
|
||||
@@ -74,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,
|
||||
@@ -170,6 +174,112 @@ typedef enum {
|
||||
GSK_CORNER_BOTTOM_LEFT
|
||||
} GskCorner;
|
||||
|
||||
/**
|
||||
* GskFillRule:
|
||||
* @GSK_FILL_RULE_WINDING: If the path crosses the ray from
|
||||
* left-to-right, counts +1. If the path crosses the ray
|
||||
* from right to left, counts -1. (Left and right are determined
|
||||
* from the perspective of looking along the ray from the starting
|
||||
* point.) If the total count is non-zero, the point will be filled.
|
||||
* @GSK_FILL_RULE_EVEN_ODD: Counts the total number of
|
||||
* intersections, without regard to the orientation of the contour. If
|
||||
* the total number of intersections is odd, the point will be
|
||||
* filled.
|
||||
*
|
||||
* #GskFillRule is used to select how paths are filled, for example in
|
||||
* gsk_fill_node_new().
|
||||
*
|
||||
* Whether or not a point is included in the fill is determined by taking
|
||||
* a ray from that point to infinity and looking at intersections with the
|
||||
* path. The ray can be in any direction, as long as it doesn't pass through
|
||||
* the end point of a segment or have a tricky intersection such as
|
||||
* intersecting tangent to the path.
|
||||
*
|
||||
* (Note that filling is not actually implemented in this way. This
|
||||
* is just a description of the rule that is applied.)
|
||||
*
|
||||
* New entries may be added in future versions.
|
||||
*/
|
||||
typedef enum {
|
||||
GSK_FILL_RULE_WINDING,
|
||||
GSK_FILL_RULE_EVEN_ODD
|
||||
} GskFillRule;
|
||||
|
||||
/**
|
||||
* GskLineCap:
|
||||
* @GSK_LINE_CAP_BUTT: Start and stop the line exactly at the start
|
||||
* and end point
|
||||
* @GSK_LINE_CAP_ROUND: Use a round ending, the center of the circle
|
||||
* is the start or end point.
|
||||
* @GSK_LINE_CAP_SQUARE: use squared ending, the center of the square
|
||||
* is the start or end point.
|
||||
*
|
||||
* Specifies how to render the start and end points of contours or
|
||||
* dashes when stroking.
|
||||
*
|
||||
* The default line cap style is %GSK_LINE_CAP_BUTT.
|
||||
*/
|
||||
typedef enum {
|
||||
GSK_LINE_CAP_BUTT,
|
||||
GSK_LINE_CAP_ROUND,
|
||||
GSK_LINE_CAP_SQUARE
|
||||
} GskLineCap;
|
||||
|
||||
/**
|
||||
* 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 joint point
|
||||
* @GSK_LINE_JOIN_BEVEL: Use a cut-off join, the join is cut off at half
|
||||
* the line width from the joint point
|
||||
*
|
||||
* Specifies how to render the junction of two lines when stroking.
|
||||
*
|
||||
* See [method@Gsk.Stroke.set_miter_limit] for details on the difference
|
||||
* between @GSK_LINE_JOIN_MITER and @GSK_LINE_JOIN_MITER_CLIP.
|
||||
*
|
||||
* The default line join style is %GSK_LINE_JOIN_MITER.
|
||||
**/
|
||||
typedef enum {
|
||||
GSK_LINE_JOIN_MITER,
|
||||
GSK_LINE_JOIN_MITER_CLIP,
|
||||
GSK_LINE_JOIN_ROUND,
|
||||
GSK_LINE_JOIN_BEVEL
|
||||
} GskLineJoin;
|
||||
|
||||
/**
|
||||
* GskPathOperation:
|
||||
* @GSK_PATH_MOVE: A move-to operation, with 1 point describing the
|
||||
* target point.
|
||||
* @GSK_PATH_LINE: A line-to operation, with 2 points describing the
|
||||
* start and end point of a straight line.
|
||||
* @GSK_PATH_CLOSE: A close operation ending the current contour with
|
||||
* a line back to the starting point. Two points describe the start
|
||||
* and end of the line.
|
||||
* @GSK_PATH_QUAD: A curve-to operation describing a quadratic Bézier curve
|
||||
* with 3 points describing the start point, the control point
|
||||
* and the end point of the curve.
|
||||
* @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 can be used to approximate a #GskPath.
|
||||
*
|
||||
* More values may be added in the future.
|
||||
**/
|
||||
typedef enum {
|
||||
GSK_PATH_MOVE,
|
||||
GSK_PATH_CLOSE,
|
||||
GSK_PATH_LINE,
|
||||
GSK_PATH_QUAD,
|
||||
GSK_PATH_CUBIC,
|
||||
GSK_PATH_CONIC,
|
||||
} GskPathOperation;
|
||||
|
||||
/**
|
||||
* GskSerializationError:
|
||||
* @GSK_SERIALIZATION_UNSUPPORTED_FORMAT: The format can not be identified
|
||||
|
1209
gsk/gskpath.c
Normal file
1209
gsk/gskpath.c
Normal file
File diff suppressed because it is too large
Load Diff
117
gsk/gskpath.h
Normal file
117
gsk/gskpath.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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>
|
||||
*/
|
||||
|
||||
#ifndef __GSK_PATH_H__
|
||||
#define __GSK_PATH_H__
|
||||
|
||||
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||
#error "Only <gsk/gsk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
|
||||
#include <gsk/gsktypes.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* GskPathForeachFlags:
|
||||
* @GSK_PATH_FOREACH_ALLOW_QUAD: Allow emission of %GSK_PATH_QUAD operations
|
||||
* @GSK_PATH_FOREACH_ALLOW_CUBIC: Allow emission of %GSK_PATH_CUBIC operations
|
||||
* @GSK_PATH_FOREACH_ALLOW_CONIC: Allow emission of %GSK_PATH_CONIC operations
|
||||
*
|
||||
* Flags that can be passed to gsk_path_foreach() to enable additional
|
||||
* features.
|
||||
*
|
||||
* By default, 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.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
GSK_PATH_FOREACH_ALLOW_QUAD = (1 << 0),
|
||||
GSK_PATH_FOREACH_ALLOW_CUBIC = (1 << 1),
|
||||
GSK_PATH_FOREACH_ALLOW_CONIC = (1 << 2)
|
||||
} GskPathForeachFlags;
|
||||
|
||||
/**
|
||||
* GskPathForeachFunc:
|
||||
* @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.
|
||||
*/
|
||||
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 ())
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GType gsk_path_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskPath * gsk_path_ref (GskPath *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_unref (GskPath *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_print (GskPath *self,
|
||||
GString *string);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
char * gsk_path_to_string (GskPath *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskPath * gsk_path_parse (const char *string);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_to_cairo (GskPath *self,
|
||||
cairo_t *cr);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gsk_path_is_empty (GskPath *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gsk_path_is_closed (GskPath *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gsk_path_get_bounds (GskPath *self,
|
||||
graphene_rect_t *bounds);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gsk_path_get_stroke_bounds (GskPath *self,
|
||||
const GskStroke *stroke,
|
||||
graphene_rect_t *bounds);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gsk_path_foreach (GskPath *self,
|
||||
GskPathForeachFlags flags,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GSK_PATH_H__ */
|
1091
gsk/gskpathbuilder.c
Normal file
1091
gsk/gskpathbuilder.c
Normal file
File diff suppressed because it is too large
Load Diff
148
gsk/gskpathbuilder.h
Normal file
148
gsk/gskpathbuilder.h
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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>
|
||||
*/
|
||||
|
||||
#ifndef __GSK_PATH_BUILDER_H__
|
||||
#define __GSK_PATH_BUILDER_H__
|
||||
|
||||
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||
#error "Only <gsk/gsk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
|
||||
#include <gsk/gskroundedrect.h>
|
||||
#include <gsk/gsktypes.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GSK_TYPE_PATH_BUILDER (gsk_path_builder_get_type ())
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GType gsk_path_builder_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskPathBuilder * gsk_path_builder_new (void);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskPathBuilder * gsk_path_builder_ref (GskPathBuilder *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_unref (GskPathBuilder *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskPath * gsk_path_builder_free_to_path (GskPathBuilder *self) G_GNUC_WARN_UNUSED_RESULT;
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskPath * gsk_path_builder_to_path (GskPathBuilder *self) G_GNUC_WARN_UNUSED_RESULT;
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
const graphene_point_t *gsk_path_builder_get_current_point (GskPathBuilder *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_add_path (GskPathBuilder *self,
|
||||
GskPath *path);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_add_cairo_path (GskPathBuilder *self,
|
||||
const cairo_path_t *path);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_add_layout (GskPathBuilder *self,
|
||||
PangoLayout *layout);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_add_rect (GskPathBuilder *self,
|
||||
const graphene_rect_t *rect);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_add_rounded_rect (GskPathBuilder *self,
|
||||
const GskRoundedRect *rect);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_add_circle (GskPathBuilder *self,
|
||||
const graphene_point_t *center,
|
||||
float radius);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_add_ellipse (GskPathBuilder *self,
|
||||
const graphene_point_t *center,
|
||||
const graphene_size_t *radius);
|
||||
/* next function implemented in gskpathmeasure.c */
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_add_segment (GskPathBuilder *self,
|
||||
GskPathMeasure *measure,
|
||||
float start,
|
||||
float end);
|
||||
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_move_to (GskPathBuilder *self,
|
||||
float x,
|
||||
float y);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_rel_move_to (GskPathBuilder *self,
|
||||
float x,
|
||||
float y);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_line_to (GskPathBuilder *self,
|
||||
float x,
|
||||
float y);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_rel_line_to (GskPathBuilder *self,
|
||||
float x,
|
||||
float y);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_quad_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_rel_quad_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_cubic_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2,
|
||||
float x3,
|
||||
float y3);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_rel_cubic_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2,
|
||||
float x3,
|
||||
float y3);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_conic_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2,
|
||||
float weight);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_rel_conic_to (GskPathBuilder *self,
|
||||
float x1,
|
||||
float y1,
|
||||
float x2,
|
||||
float y2,
|
||||
float weight);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_builder_close (GskPathBuilder *self);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathBuilder, gsk_path_builder_unref)
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GSK_PATH_BUILDER_H__ */
|
293
gsk/gskpathdash.c
Normal file
293
gsk/gskpathdash.c
Normal file
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* 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 "gskpathdashprivate.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;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gsk_path_dash (GskPath *path,
|
||||
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 (path, -1, func, user_data);
|
||||
|
||||
for (i = 0; i < gsk_path_get_n_contours (path); i++)
|
||||
{
|
||||
if (!gsk_contour_dash (gsk_path_get_contour (path, i), stroke, tolerance, func, user_data))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
50
gsk/gskpathdashprivate.h
Normal file
50
gsk/gskpathdashprivate.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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>
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GSK_PATH_DASH_PRIVATE_H__
|
||||
#define __GSK_PATH_DASH_PRIVATE_H__
|
||||
|
||||
#include "gskpath.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gsk_path_dash (GskPath *path,
|
||||
GskStroke *stroke,
|
||||
float tolerance,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
#ifdef GTK_COMPILATION
|
||||
|
||||
#include "gskcontourprivate.h"
|
||||
|
||||
gboolean gsk_contour_dash (const GskContour *contour,
|
||||
GskStroke *stroke,
|
||||
float tolerance,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
#endif /* GTK_COMPILATION */
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GSK_PATH_DASH_PRIVATE_H__ */
|
||||
|
539
gsk/gskpathmeasure.c
Normal file
539
gsk/gskpathmeasure.c
Normal file
@@ -0,0 +1,539 @@
|
||||
/*
|
||||
* 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 "gskpathprivate.h"
|
||||
|
||||
/**
|
||||
* GskPathMeasure:
|
||||
*
|
||||
* A `GskPathMeasure` struct is a reference-counted struct
|
||||
* and should be treated as opaque.
|
||||
*
|
||||
* `GskPathMeasure` is an object that allows measuring operations
|
||||
* on `GskPath`. These operations are useful when implementing
|
||||
* animations.
|
||||
*/
|
||||
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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;
|
||||
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`.
|
||||
*/
|
||||
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 path_measure.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
GskPath *
|
||||
gsk_path_measure_get_path (GskPathMeasure *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, NULL);
|
||||
|
||||
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
|
||||
*/
|
||||
float
|
||||
gsk_path_measure_get_tolerance (GskPathMeasure *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, 0.f);
|
||||
|
||||
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
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_measure_get_point:
|
||||
* @self: a `GskPathMeasure`
|
||||
* @distance: distance into the path
|
||||
* @pos: (out caller-allocates) (optional): The coordinates
|
||||
* of the position at @distance
|
||||
* @tangent: (out caller-allocates) (optional): The tangent
|
||||
* to the position at @distance
|
||||
*
|
||||
* Calculates the coordinates and tangent of the point @distance
|
||||
* units into the path. The value will be clamped to the length
|
||||
* of the path.
|
||||
*
|
||||
* If the point is a discontinuous edge in the path, the returned
|
||||
* point and tangent will describe the line starting at that point
|
||||
* going forward.
|
||||
*
|
||||
* If @self describes an empty path, the returned point will be
|
||||
* set to `(0, 0)` and the tangent will be the x axis or `(1, 0)`.
|
||||
*/
|
||||
void
|
||||
gsk_path_measure_get_point (GskPathMeasure *self,
|
||||
float distance,
|
||||
graphene_point_t *pos,
|
||||
graphene_vec2_t *tangent)
|
||||
{
|
||||
gsize i;
|
||||
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
if (pos == NULL && tangent == NULL)
|
||||
return;
|
||||
|
||||
distance = gsk_path_measure_clamp_distance (self, distance);
|
||||
|
||||
for (i = 0; i < self->n_contours; i++)
|
||||
{
|
||||
if (distance < self->measures[i].length)
|
||||
break;
|
||||
|
||||
distance -= self->measures[i].length;
|
||||
}
|
||||
|
||||
/* weird corner cases */
|
||||
if (i == self->n_contours)
|
||||
{
|
||||
/* the empty path goes here */
|
||||
if (0 == self->n_contours)
|
||||
{
|
||||
if (pos)
|
||||
graphene_point_init (pos, 0.f, 0.f);
|
||||
if (tangent)
|
||||
graphene_vec2_init (tangent, 1.f, 0.f);
|
||||
return;
|
||||
}
|
||||
/* rounding errors can make this happen */
|
||||
i = self->n_contours - 1;
|
||||
distance = self->measures[i].length;
|
||||
}
|
||||
|
||||
gsk_contour_get_point (gsk_path_get_contour (self->path, i),
|
||||
self->measures[i].contour_data,
|
||||
distance,
|
||||
pos,
|
||||
tangent);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_measure_get_closest_point:
|
||||
* @self: a `GskPathMeasure`
|
||||
* @point: the point to fond the closest point to
|
||||
* @out_pos: (out caller-allocates) (optional): return location
|
||||
* for the closest point
|
||||
*
|
||||
* Gets the point on the path that is closest to @point.
|
||||
*
|
||||
* If the path being measured is empty, return 0 and set
|
||||
* @out_pos to (0, 0).
|
||||
*
|
||||
* This is a simpler version of
|
||||
* [method@Gsk.PathMeasure.get_closest_point_full].
|
||||
* Use that one if you need more control.
|
||||
*
|
||||
* Returns: The offset into the path of the closest point
|
||||
*/
|
||||
float
|
||||
gsk_path_measure_get_closest_point (GskPathMeasure *self,
|
||||
const graphene_point_t *point,
|
||||
graphene_point_t *out_pos)
|
||||
{
|
||||
float result;
|
||||
|
||||
g_return_val_if_fail (self != NULL, 0.0f);
|
||||
|
||||
if (gsk_path_measure_get_closest_point_full (self,
|
||||
point,
|
||||
INFINITY,
|
||||
&result,
|
||||
out_pos,
|
||||
NULL,
|
||||
NULL))
|
||||
return result;
|
||||
|
||||
if (out_pos)
|
||||
*out_pos = GRAPHENE_POINT_INIT (0, 0);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_measure_get_closest_point_full:
|
||||
* @self: a `GskPathMeasure`
|
||||
* @point: the point to fond the closest point to
|
||||
* @threshold: The maximum allowed distance between the path and @point.
|
||||
* Use `INFINITY` to look for any point.
|
||||
* @out_distance: (out caller-allocates) (optional): The
|
||||
* distance between the found closest point on the path and the given
|
||||
* @point.
|
||||
* @out_pos: (out caller-allocates) (optional): return location
|
||||
* for the closest point
|
||||
* @out_offset: (out caller-allocates) (optional): The offset into
|
||||
* the path of the found point
|
||||
* @out_tangent: (out caller-allocates) (optional): return location for
|
||||
* the tangent at the closest point
|
||||
*
|
||||
* Gets the point on the path that is closest to @point.
|
||||
*
|
||||
* If no point on path is closer to @point than @threshold,
|
||||
* return `FALSE`.
|
||||
*
|
||||
* Returns: `TRUE` if a point was found, `FALSE` otherwise.
|
||||
*/
|
||||
gboolean
|
||||
gsk_path_measure_get_closest_point_full (GskPathMeasure *self,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
float *out_distance,
|
||||
graphene_point_t *out_pos,
|
||||
float *out_offset,
|
||||
graphene_vec2_t *out_tangent)
|
||||
{
|
||||
gboolean result;
|
||||
gsize i;
|
||||
float distance, length;
|
||||
|
||||
g_return_val_if_fail (self != NULL, FALSE);
|
||||
g_return_val_if_fail (point != NULL, FALSE);
|
||||
|
||||
result = FALSE;
|
||||
length = 0;
|
||||
|
||||
for (i = 0; i < self->n_contours; i++)
|
||||
{
|
||||
if (gsk_contour_get_closest_point (gsk_path_get_contour (self->path, i),
|
||||
self->measures[i].contour_data,
|
||||
self->tolerance,
|
||||
point,
|
||||
threshold,
|
||||
&distance,
|
||||
out_pos,
|
||||
out_offset,
|
||||
out_tangent))
|
||||
{
|
||||
result = TRUE;
|
||||
if (out_offset)
|
||||
*out_offset += length;
|
||||
|
||||
if (distance < self->tolerance)
|
||||
break;
|
||||
threshold = distance - self->tolerance;
|
||||
}
|
||||
|
||||
length += self->measures[i].length;
|
||||
}
|
||||
|
||||
if (result && out_distance)
|
||||
*out_distance = distance;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_measure_in_fill:
|
||||
* @self: a `GskPathMeasure`
|
||||
* @point: the point to test
|
||||
* @fill_rule: the fill rule to follow
|
||||
*
|
||||
* Returns whether the given point is inside the area that would be
|
||||
* affected if the path of @self was filled according to @fill_rule.
|
||||
*
|
||||
* Returns: `TRUE` if @point is inside
|
||||
*/
|
||||
gboolean
|
||||
gsk_path_measure_in_fill (GskPathMeasure *self,
|
||||
const graphene_point_t *point,
|
||||
GskFillRule fill_rule)
|
||||
{
|
||||
int winding = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < self->n_contours; i++)
|
||||
winding += gsk_contour_get_winding (gsk_path_get_contour (self->path, i),
|
||||
self->measures[i].contour_data,
|
||||
point);
|
||||
|
||||
switch (fill_rule)
|
||||
{
|
||||
case GSK_FILL_RULE_EVEN_ODD:
|
||||
return winding & 1;
|
||||
case GSK_FILL_RULE_WINDING:
|
||||
return winding != 0;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
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 [method@Gsk.PathMeasure.get_length]
|
||||
* for the end of the path. The values will be clamped to that range.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
79
gsk/gskpathmeasure.h
Normal file
79
gsk/gskpathmeasure.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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>
|
||||
*/
|
||||
|
||||
#ifndef __GSK_PATH_MEASURE_H__
|
||||
#define __GSK_PATH_MEASURE_H__
|
||||
|
||||
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||
#error "Only <gsk/gsk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
|
||||
#include <gsk/gskpath.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GSK_TYPE_PATH_MEASURE (gsk_path_measure_get_type ())
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GType gsk_path_measure_get_type (void) G_GNUC_CONST;
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskPathMeasure * gsk_path_measure_new (GskPath *path);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskPathMeasure * gsk_path_measure_new_with_tolerance (GskPath *path,
|
||||
float tolerance);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskPathMeasure * gsk_path_measure_ref (GskPathMeasure *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_measure_unref (GskPathMeasure *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskPath * gsk_path_measure_get_path (GskPathMeasure *self) G_GNUC_PURE;
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
float gsk_path_measure_get_tolerance (GskPathMeasure *self) G_GNUC_PURE;
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
float gsk_path_measure_get_length (GskPathMeasure *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_measure_get_point (GskPathMeasure *self,
|
||||
float distance,
|
||||
graphene_point_t *pos,
|
||||
graphene_vec2_t *tangent);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
float gsk_path_measure_get_closest_point (GskPathMeasure *self,
|
||||
const graphene_point_t *point,
|
||||
graphene_point_t *out_pos);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gsk_path_measure_get_closest_point_full (GskPathMeasure *self,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
float *out_distance,
|
||||
graphene_point_t *out_pos,
|
||||
float *out_offset,
|
||||
graphene_vec2_t *out_tangent);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gsk_path_measure_in_fill (GskPathMeasure *self,
|
||||
const graphene_point_t *point,
|
||||
GskFillRule fill_rule);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathMeasure, gsk_path_measure_unref)
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GSK_PATH_MEASURE_H__ */
|
190
gsk/gskpathopprivate.h
Normal file
190
gsk/gskpathopprivate.h
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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>
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GSK_PATHOP_PRIVATE_H__
|
||||
#define __GSK_PATHOP_PRIVATE_H__
|
||||
|
||||
#include "gskpath.h"
|
||||
#include "gskpathbuilder.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef gpointer gskpathop;
|
||||
|
||||
static inline
|
||||
gskpathop gsk_pathop_encode (GskPathOperation op,
|
||||
const graphene_point_t *pts);
|
||||
static inline
|
||||
const graphene_point_t *gsk_pathop_points (gskpathop pop);
|
||||
static inline
|
||||
GskPathOperation gsk_pathop_op (gskpathop pop);
|
||||
|
||||
static inline
|
||||
gboolean gsk_pathop_foreach (gskpathop pop,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
/* included inline so tests can use them */
|
||||
static inline
|
||||
void gsk_path_builder_pathop_to (GskPathBuilder *builder,
|
||||
gskpathop op);
|
||||
static inline
|
||||
void gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
|
||||
gskpathop op);
|
||||
|
||||
/* IMPLEMENTATION */
|
||||
|
||||
#define GSK_PATHOP_OPERATION_MASK (0x7)
|
||||
|
||||
static inline gskpathop
|
||||
gsk_pathop_encode (GskPathOperation op,
|
||||
const graphene_point_t *pts)
|
||||
{
|
||||
/* g_assert (op & GSK_PATHOP_OPERATION_MASK == op); */
|
||||
g_assert ((GPOINTER_TO_SIZE (pts) & GSK_PATHOP_OPERATION_MASK) == 0);
|
||||
|
||||
return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (pts) | op);
|
||||
}
|
||||
|
||||
static inline const graphene_point_t *
|
||||
gsk_pathop_points (gskpathop pop)
|
||||
{
|
||||
return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (pop) & ~GSK_PATHOP_OPERATION_MASK);
|
||||
}
|
||||
|
||||
static inline
|
||||
GskPathOperation gsk_pathop_op (gskpathop pop)
|
||||
{
|
||||
return GPOINTER_TO_SIZE (pop) & GSK_PATHOP_OPERATION_MASK;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
gsk_pathop_foreach (gskpathop pop,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data)
|
||||
{
|
||||
switch (gsk_pathop_op (pop))
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
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, 0, user_data);
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
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, 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 ();
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
gsk_path_builder_pathop_to (GskPathBuilder *builder,
|
||||
gskpathop op)
|
||||
{
|
||||
const graphene_point_t *pts = gsk_pathop_points (op);
|
||||
|
||||
switch (gsk_pathop_op (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[3].x, pts[3].y, pts[2].x);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
|
||||
gskpathop op)
|
||||
{
|
||||
const graphene_point_t *pts = gsk_pathop_points (op);
|
||||
|
||||
switch (gsk_pathop_op (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_line_to (builder, pts[0].x, pts[0].y);
|
||||
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[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GSK_PATHOP_PRIVATE_H__ */
|
||||
|
61
gsk/gskpathprivate.h
Normal file
61
gsk/gskpathprivate.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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>
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GSK_PATH_PRIVATE_H__
|
||||
#define __GSK_PATH_PRIVATE_H__
|
||||
|
||||
#include "gskpath.h"
|
||||
|
||||
#include "gskcontourprivate.h"
|
||||
#include "gskpathopprivate.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/* Same as Skia, so looks like a good value. ¯\_(ツ)_/¯ */
|
||||
#define GSK_PATH_TOLERANCE_DEFAULT (0.5)
|
||||
|
||||
GskPath * gsk_path_new_from_contours (const GSList *contours);
|
||||
|
||||
gsize gsk_path_get_n_contours (GskPath *path);
|
||||
const GskContour * gsk_path_get_contour (GskPath *path,
|
||||
gsize i);
|
||||
gboolean gsk_path_foreach_with_tolerance (GskPath *self,
|
||||
GskPathForeachFlags flags,
|
||||
double tolerance,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
|
||||
void gsk_path_builder_add_contour (GskPathBuilder *builder,
|
||||
GskContour *contour);
|
||||
|
||||
void gsk_path_builder_svg_arc_to (GskPathBuilder *builder,
|
||||
float rx,
|
||||
float ry,
|
||||
float x_axis_rotation,
|
||||
gboolean large_arc,
|
||||
gboolean positive_sweep,
|
||||
float x,
|
||||
float y);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GSK_PATH_PRIVATE_H__ */
|
||||
|
@@ -158,6 +158,8 @@ GskRenderNode * gsk_render_node_deserialize (GBytes
|
||||
#define GSK_TYPE_REPEAT_NODE (gsk_repeat_node_get_type())
|
||||
#define GSK_TYPE_CLIP_NODE (gsk_clip_node_get_type())
|
||||
#define GSK_TYPE_ROUNDED_CLIP_NODE (gsk_rounded_clip_node_get_type())
|
||||
#define GSK_TYPE_FILL_NODE (gsk_fill_node_get_type())
|
||||
#define GSK_TYPE_STROKE_NODE (gsk_stroke_node_get_type())
|
||||
#define GSK_TYPE_SHADOW_NODE (gsk_shadow_node_get_type())
|
||||
#define GSK_TYPE_BLEND_NODE (gsk_blend_node_get_type())
|
||||
#define GSK_TYPE_CROSS_FADE_NODE (gsk_cross_fade_node_get_type())
|
||||
@@ -186,6 +188,8 @@ typedef struct _GskColorMatrixNode GskColorMatrixNode;
|
||||
typedef struct _GskRepeatNode GskRepeatNode;
|
||||
typedef struct _GskClipNode GskClipNode;
|
||||
typedef struct _GskRoundedClipNode GskRoundedClipNode;
|
||||
typedef struct _GskFillNode GskFillNode;
|
||||
typedef struct _GskStrokeNode GskStrokeNode;
|
||||
typedef struct _GskShadowNode GskShadowNode;
|
||||
typedef struct _GskBlendNode GskBlendNode;
|
||||
typedef struct _GskCrossFadeNode GskCrossFadeNode;
|
||||
@@ -459,6 +463,32 @@ GskRenderNode * gsk_rounded_clip_node_get_child (const GskRender
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
const GskRoundedRect * gsk_rounded_clip_node_get_clip (const GskRenderNode *node) G_GNUC_PURE;
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GType gsk_fill_node_get_type (void) G_GNUC_CONST;
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskRenderNode * gsk_fill_node_new (GskRenderNode *child,
|
||||
GskPath *path,
|
||||
GskFillRule fill_rule);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskRenderNode * gsk_fill_node_get_child (const GskRenderNode *node);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskPath * gsk_fill_node_get_path (const GskRenderNode *node);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskFillRule gsk_fill_node_get_fill_rule (const GskRenderNode *node);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GType gsk_stroke_node_get_type (void) G_GNUC_CONST;
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskRenderNode * gsk_stroke_node_new (GskRenderNode *child,
|
||||
GskPath *path,
|
||||
const GskStroke *stroke);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskRenderNode * gsk_stroke_node_get_child (const GskRenderNode *node);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskPath * gsk_stroke_node_get_path (const GskRenderNode *node);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
const GskStroke * gsk_stroke_node_get_stroke (const GskRenderNode *node);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GType gsk_shadow_node_get_type (void) G_GNUC_CONST;
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
|
@@ -25,8 +25,10 @@
|
||||
#include "gskdebugprivate.h"
|
||||
#include "gskdiffprivate.h"
|
||||
#include "gl/gskglrenderer.h"
|
||||
#include "gskpath.h"
|
||||
#include "gskrendererprivate.h"
|
||||
#include "gskroundedrectprivate.h"
|
||||
#include "gskstrokeprivate.h"
|
||||
#include "gsktransformprivate.h"
|
||||
|
||||
#include "gdk/gdktextureprivate.h"
|
||||
@@ -4370,6 +4372,371 @@ gsk_rounded_clip_node_get_clip (const GskRenderNode *node)
|
||||
return &self->clip;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ GSK_FILL_NODE */
|
||||
|
||||
struct _GskFillNode
|
||||
{
|
||||
GskRenderNode render_node;
|
||||
|
||||
GskRenderNode *child;
|
||||
GskPath *path;
|
||||
GskFillRule fill_rule;
|
||||
};
|
||||
|
||||
static void
|
||||
gsk_fill_node_finalize (GskRenderNode *node)
|
||||
{
|
||||
GskFillNode *self = (GskFillNode *) node;
|
||||
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_FILL_NODE));
|
||||
|
||||
gsk_render_node_unref (self->child);
|
||||
gsk_path_unref (self->path);
|
||||
|
||||
parent_class->finalize (node);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_fill_node_draw (GskRenderNode *node,
|
||||
cairo_t *cr)
|
||||
{
|
||||
GskFillNode *self = (GskFillNode *) node;
|
||||
|
||||
cairo_save (cr);
|
||||
|
||||
switch (self->fill_rule)
|
||||
{
|
||||
case GSK_FILL_RULE_WINDING:
|
||||
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_WINDING);
|
||||
break;
|
||||
case GSK_FILL_RULE_EVEN_ODD:
|
||||
cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
gsk_path_to_cairo (self->path, cr);
|
||||
cairo_clip (cr);
|
||||
|
||||
gsk_render_node_draw (self->child, cr);
|
||||
|
||||
cairo_restore (cr);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_fill_node_diff (GskRenderNode *node1,
|
||||
GskRenderNode *node2,
|
||||
cairo_region_t *region)
|
||||
{
|
||||
GskFillNode *self1 = (GskFillNode *) node1;
|
||||
GskFillNode *self2 = (GskFillNode *) node2;
|
||||
|
||||
if (self1->path == self2->path)
|
||||
{
|
||||
cairo_region_t *sub;
|
||||
cairo_rectangle_int_t clip_rect;
|
||||
graphene_rect_t rect;
|
||||
|
||||
sub = cairo_region_create();
|
||||
gsk_render_node_diff (self1->child, self2->child, sub);
|
||||
graphene_rect_union (&node1->bounds, &node2->bounds, &rect);
|
||||
rectangle_init_from_graphene (&clip_rect, &rect);
|
||||
cairo_region_intersect_rectangle (sub, &clip_rect);
|
||||
cairo_region_union (region, sub);
|
||||
cairo_region_destroy (sub);
|
||||
}
|
||||
else
|
||||
{
|
||||
gsk_render_node_diff_impossible (node1, node2, region);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_fill_node_class_init (gpointer g_class,
|
||||
gpointer class_data)
|
||||
{
|
||||
GskRenderNodeClass *node_class = g_class;
|
||||
|
||||
node_class->node_type = GSK_FILL_NODE;
|
||||
|
||||
node_class->finalize = gsk_fill_node_finalize;
|
||||
node_class->draw = gsk_fill_node_draw;
|
||||
node_class->diff = gsk_fill_node_diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_fill_node_new:
|
||||
* @child: The node to fill the area with
|
||||
* @path: The path describing the area to fill
|
||||
* @fill_rule: The fill rule to use
|
||||
*
|
||||
* Creates a #GskRenderNode that will fill the @child in the area
|
||||
* given by @path and @fill_rule.
|
||||
*
|
||||
* Returns: (transfer none) (type GskFillNode): A new #GskRenderNode
|
||||
*/
|
||||
GskRenderNode *
|
||||
gsk_fill_node_new (GskRenderNode *child,
|
||||
GskPath *path,
|
||||
GskFillRule fill_rule)
|
||||
{
|
||||
GskFillNode *self;
|
||||
GskRenderNode *node;
|
||||
graphene_rect_t path_bounds;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
||||
g_return_val_if_fail (path != NULL, NULL);
|
||||
|
||||
self = gsk_render_node_alloc (GSK_FILL_NODE);
|
||||
node = (GskRenderNode *) self;
|
||||
|
||||
self->child = gsk_render_node_ref (child);
|
||||
self->path = gsk_path_ref (path);
|
||||
self->fill_rule = fill_rule;
|
||||
|
||||
if (gsk_path_get_bounds (path, &path_bounds))
|
||||
graphene_rect_intersection (&path_bounds, &child->bounds, &node->bounds);
|
||||
else
|
||||
graphene_rect_init_from_rect (&node->bounds, graphene_rect_zero ());
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_fill_node_get_child:
|
||||
* @node: (type GskFillNode): a fill #GskRenderNode
|
||||
*
|
||||
* Gets the child node that is getting drawn by the given @node.
|
||||
*
|
||||
* Returns: (transfer none): The child that is getting drawn
|
||||
**/
|
||||
GskRenderNode *
|
||||
gsk_fill_node_get_child (const GskRenderNode *node)
|
||||
{
|
||||
const GskFillNode *self = (const GskFillNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), NULL);
|
||||
|
||||
return self->child;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_fill_node_get_path:
|
||||
* @node: (type GskFillNode): a fill #GskRenderNode
|
||||
*
|
||||
* Retrieves the path used to describe the area filled with the contents of
|
||||
* the @node.
|
||||
*
|
||||
* Returns: (transfer none): a #GskPath
|
||||
*/
|
||||
GskPath *
|
||||
gsk_fill_node_get_path (const GskRenderNode *node)
|
||||
{
|
||||
const GskFillNode *self = (const GskFillNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), NULL);
|
||||
|
||||
return self->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_fill_node_get_fill_rule:
|
||||
* @node: (type GskFillNode): a fill #GskRenderNode
|
||||
*
|
||||
* Retrieves the fill rule used to determine how the path is filled.
|
||||
*
|
||||
* Returns: a #GskFillRule
|
||||
*/
|
||||
GskFillRule
|
||||
gsk_fill_node_get_fill_rule (const GskRenderNode *node)
|
||||
{
|
||||
const GskFillNode *self = (const GskFillNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_FILL_NODE), GSK_FILL_RULE_WINDING);
|
||||
|
||||
return self->fill_rule;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ GSK_STROKE_NODE */
|
||||
|
||||
struct _GskStrokeNode
|
||||
{
|
||||
GskRenderNode render_node;
|
||||
|
||||
GskRenderNode *child;
|
||||
GskPath *path;
|
||||
GskStroke stroke;
|
||||
};
|
||||
|
||||
static void
|
||||
gsk_stroke_node_finalize (GskRenderNode *node)
|
||||
{
|
||||
GskStrokeNode *self = (GskStrokeNode *) node;
|
||||
GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_STROKE_NODE));
|
||||
|
||||
gsk_render_node_unref (self->child);
|
||||
gsk_path_unref (self->path);
|
||||
gsk_stroke_clear (&self->stroke);
|
||||
|
||||
parent_class->finalize (node);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_stroke_node_draw (GskRenderNode *node,
|
||||
cairo_t *cr)
|
||||
{
|
||||
GskStrokeNode *self = (GskStrokeNode *) node;
|
||||
|
||||
cairo_save (cr);
|
||||
|
||||
gsk_cairo_rectangle (cr, &self->child->bounds);
|
||||
cairo_clip (cr);
|
||||
|
||||
cairo_push_group (cr);
|
||||
gsk_render_node_draw (self->child, cr);
|
||||
cairo_pop_group_to_source (cr);
|
||||
|
||||
gsk_stroke_to_cairo (&self->stroke, cr);
|
||||
|
||||
gsk_path_to_cairo (self->path, cr);
|
||||
cairo_stroke (cr);
|
||||
|
||||
cairo_restore (cr);
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_stroke_node_diff (GskRenderNode *node1,
|
||||
GskRenderNode *node2,
|
||||
cairo_region_t *region)
|
||||
{
|
||||
GskStrokeNode *self1 = (GskStrokeNode *) node1;
|
||||
GskStrokeNode *self2 = (GskStrokeNode *) node2;
|
||||
|
||||
if (self1->path == self2->path &&
|
||||
gsk_stroke_equal (&self1->stroke, &self2->stroke))
|
||||
{
|
||||
cairo_region_t *sub;
|
||||
|
||||
sub = cairo_region_create();
|
||||
gsk_render_node_diff (self1->child, self2->child, sub);
|
||||
cairo_region_union (region, sub);
|
||||
cairo_region_destroy (sub);
|
||||
}
|
||||
else
|
||||
{
|
||||
gsk_render_node_diff_impossible (node1, node2, region);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_stroke_node_class_init (gpointer g_class,
|
||||
gpointer class_data)
|
||||
{
|
||||
GskRenderNodeClass *node_class = g_class;
|
||||
|
||||
node_class->node_type = GSK_STROKE_NODE;
|
||||
|
||||
node_class->finalize = gsk_stroke_node_finalize;
|
||||
node_class->draw = gsk_stroke_node_draw;
|
||||
node_class->diff = gsk_stroke_node_diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_node_new:
|
||||
* @child: The node to stroke the area with
|
||||
* @path: (transfer none): The path describing the area to stroke
|
||||
* @stroke: (transfer none): The stroke attributes to use
|
||||
*
|
||||
* Creates a #GskRenderNode that will stroke the @child along the given
|
||||
* @path using the attributes defined in @stroke.
|
||||
*
|
||||
* Returns: (transfer none) (type GskStrokeNode): A new #GskRenderNode
|
||||
*/
|
||||
GskRenderNode *
|
||||
gsk_stroke_node_new (GskRenderNode *child,
|
||||
GskPath *path,
|
||||
const GskStroke *stroke)
|
||||
{
|
||||
GskStrokeNode *self;
|
||||
GskRenderNode *node;
|
||||
graphene_rect_t stroke_bounds;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
|
||||
g_return_val_if_fail (path != NULL, NULL);
|
||||
g_return_val_if_fail (stroke != NULL, NULL);
|
||||
|
||||
self = gsk_render_node_alloc (GSK_STROKE_NODE);
|
||||
node = (GskRenderNode *) self;
|
||||
|
||||
self->child = gsk_render_node_ref (child);
|
||||
self->path = gsk_path_ref (path);
|
||||
gsk_stroke_init_copy (&self->stroke, stroke);
|
||||
|
||||
if (gsk_path_get_stroke_bounds (self->path, &self->stroke, &stroke_bounds))
|
||||
graphene_rect_intersection (&stroke_bounds, &child->bounds, &node->bounds);
|
||||
else
|
||||
graphene_rect_init_from_rect (&node->bounds, graphene_rect_zero ());
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_node_get_child:
|
||||
* @node: (type GskStrokeNode): a stroke #GskRenderNode
|
||||
*
|
||||
* Gets the child node that is getting drawn by the given @node.
|
||||
*
|
||||
* Returns: (transfer none): The child that is getting drawn
|
||||
**/
|
||||
GskRenderNode *
|
||||
gsk_stroke_node_get_child (const GskRenderNode *node)
|
||||
{
|
||||
const GskStrokeNode *self = (const GskStrokeNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
|
||||
|
||||
return self->child;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_node_get_path:
|
||||
* @node: (type GskStrokeNode): a stroke #GskRenderNode
|
||||
*
|
||||
* Retrieves the path that will be stroked with the contents of
|
||||
* the @node.
|
||||
*
|
||||
* Returns: (transfer none): a #GskPath
|
||||
*/
|
||||
GskPath *
|
||||
gsk_stroke_node_get_path (const GskRenderNode *node)
|
||||
{
|
||||
const GskStrokeNode *self = (const GskStrokeNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
|
||||
|
||||
return self->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_node_get_stroke:
|
||||
* @node: (type GskStrokeNode): a stroke #GskRenderNode
|
||||
*
|
||||
* Retrieves the stroke attributes used in this @node.
|
||||
*
|
||||
* Returns: a #GskStroke
|
||||
*/
|
||||
const GskStroke *
|
||||
gsk_stroke_node_get_stroke (const GskRenderNode *node)
|
||||
{
|
||||
const GskStrokeNode *self = (const GskStrokeNode *) node;
|
||||
|
||||
g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL);
|
||||
|
||||
return &self->stroke;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ GSK_SHADOW_NODE */
|
||||
|
||||
@@ -6221,6 +6588,8 @@ GSK_DEFINE_RENDER_NODE_TYPE (gsk_color_matrix_node, GSK_COLOR_MATRIX_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_repeat_node, GSK_REPEAT_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_clip_node, GSK_CLIP_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_rounded_clip_node, GSK_ROUNDED_CLIP_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_fill_node, GSK_FILL_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_stroke_node, GSK_STROKE_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_shadow_node, GSK_SHADOW_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_blend_node, GSK_BLEND_NODE)
|
||||
GSK_DEFINE_RENDER_NODE_TYPE (gsk_cross_fade_node, GSK_CROSS_FADE_NODE)
|
||||
@@ -6369,6 +6738,16 @@ gsk_render_node_init_types_once (void)
|
||||
sizeof (GskDebugNode),
|
||||
gsk_debug_node_class_init);
|
||||
gsk_render_node_types[GSK_DEBUG_NODE] = node_type;
|
||||
|
||||
node_type = gsk_render_node_type_register_static (I_("GskFillNode"),
|
||||
sizeof (GskFillNode),
|
||||
gsk_fill_node_class_init);
|
||||
gsk_render_node_types[GSK_FILL_NODE] = node_type;
|
||||
|
||||
node_type = gsk_render_node_type_register_static (I_("GskStrokeNode"),
|
||||
sizeof (GskStrokeNode),
|
||||
gsk_stroke_node_class_init);
|
||||
gsk_render_node_types[GSK_STROKE_NODE] = node_type;
|
||||
}
|
||||
|
||||
static void
|
||||
|
@@ -23,9 +23,13 @@
|
||||
|
||||
#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"
|
||||
@@ -1159,6 +1163,26 @@ create_default_render_node (void)
|
||||
return gsk_color_node_new (&GDK_RGBA("FF00CC"), &GRAPHENE_RECT_INIT (0, 0, 50, 50));
|
||||
}
|
||||
|
||||
static GskPath *
|
||||
create_default_path (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
guint i;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
|
||||
gsk_path_builder_move_to (builder, 25, 0);
|
||||
for (i = 1; i < 5; i++)
|
||||
{
|
||||
gsk_path_builder_line_to (builder,
|
||||
sin (i * G_PI * 0.8) * 25 + 25,
|
||||
-cos (i * G_PI * 0.8) * 25 + 25);
|
||||
}
|
||||
gsk_path_builder_close (builder);
|
||||
|
||||
return gsk_path_builder_free_to_path (builder);
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
parse_color_node (GtkCssParser *parser,
|
||||
Context *context)
|
||||
@@ -2082,6 +2106,215 @@ 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_dash (GtkCssParser *parser,
|
||||
Context *context,
|
||||
gpointer out_dash)
|
||||
{
|
||||
GArray *dash;
|
||||
double d;
|
||||
|
||||
/* because CSS does this, too */
|
||||
if (gtk_css_parser_try_ident (parser, "none"))
|
||||
{
|
||||
*((GArray **) out_dash) = NULL;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
dash = g_array_new (FALSE, FALSE, sizeof (float));
|
||||
do {
|
||||
if (!gtk_css_parser_consume_number (parser, &d))
|
||||
{
|
||||
g_array_free (dash, TRUE);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_array_append_vals (dash, (float[1]) { d }, 1);
|
||||
} while (gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_NUMBER) ||
|
||||
gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SIGNLESS_INTEGER));
|
||||
|
||||
*((GArray **) out_dash) = dash;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
clear_dash (gpointer inout_array)
|
||||
{
|
||||
g_clear_pointer ((GArray **) inout_array, g_array_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);
|
||||
|
||||
gtk_css_parser_consume_token (parser);
|
||||
|
||||
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;
|
||||
GArray *dash = NULL;
|
||||
double dash_offset = 0.0;
|
||||
GskStroke *stroke;
|
||||
|
||||
const Declaration declarations[] = {
|
||||
{ "child", parse_node, clear_node, &child },
|
||||
{ "path", parse_path, clear_path, &path },
|
||||
{ "line-width", parse_double, NULL, &line_width },
|
||||
{ "line-cap", parse_line_cap, NULL, &line_cap },
|
||||
{ "line-join", parse_line_join, NULL, &line_join },
|
||||
{ "miter-limit", parse_double, NULL, &miter_limit },
|
||||
{ "dash", parse_dash, clear_dash, &dash },
|
||||
{ "dash-offset", parse_double, NULL, &dash_offset},
|
||||
};
|
||||
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);
|
||||
if (dash)
|
||||
{
|
||||
gsk_stroke_set_dash (stroke, (float *) dash->data, dash->len);
|
||||
g_array_free (dash, TRUE);
|
||||
}
|
||||
gsk_stroke_set_dash_offset (stroke, dash_offset);
|
||||
|
||||
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)
|
||||
@@ -2164,6 +2397,8 @@ parse_node (GtkCssParser *parser,
|
||||
{ "repeating-linear-gradient", parse_repeating_linear_gradient_node },
|
||||
{ "repeating-radial-gradient", parse_repeating_radial_gradient_node },
|
||||
{ "rounded-clip", parse_rounded_clip_node },
|
||||
{ "fill", parse_fill_node },
|
||||
{ "stroke", parse_stroke_node },
|
||||
{ "shadow", parse_shadow_node },
|
||||
{ "text", parse_text_node },
|
||||
{ "texture", parse_texture_node },
|
||||
@@ -2418,6 +2653,14 @@ printer_init_duplicates_for_node (Printer *printer,
|
||||
printer_init_duplicates_for_node (printer, gsk_debug_node_get_child (node));
|
||||
break;
|
||||
|
||||
case GSK_FILL_NODE:
|
||||
printer_init_duplicates_for_node (printer, gsk_fill_node_get_child (node));
|
||||
break;
|
||||
|
||||
case GSK_STROKE_NODE:
|
||||
printer_init_duplicates_for_node (printer, gsk_stroke_node_get_child (node));
|
||||
break;
|
||||
|
||||
case GSK_BLEND_NODE:
|
||||
printer_init_duplicates_for_node (printer, gsk_blend_node_get_bottom_child (node));
|
||||
printer_init_duplicates_for_node (printer, gsk_blend_node_get_top_child (node));
|
||||
@@ -2634,7 +2877,7 @@ append_float_param (Printer *p,
|
||||
float value,
|
||||
float default_value)
|
||||
{
|
||||
/* Don't approximate-compare here, better be topo verbose */
|
||||
/* Don't approximate-compare here, better be too verbose */
|
||||
if (value == default_value)
|
||||
return;
|
||||
|
||||
@@ -2809,8 +3052,11 @@ append_escaping_newlines (GString *str,
|
||||
len = strcspn (string, "\n");
|
||||
g_string_append_len (str, string, len);
|
||||
string += len;
|
||||
g_string_append (str, "\\\n");
|
||||
string++;
|
||||
if (*string)
|
||||
{
|
||||
g_string_append (str, "\\\n");
|
||||
string++;
|
||||
}
|
||||
} while (*string);
|
||||
}
|
||||
|
||||
@@ -3009,6 +3255,83 @@ 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[1] == 'O'))
|
||||
*s = '\n';
|
||||
}
|
||||
append_escaping_newlines (p->str, str);
|
||||
g_string_append (p->str, "\";\n");
|
||||
g_free (str);
|
||||
}
|
||||
|
||||
static void
|
||||
append_dash_param (Printer *p,
|
||||
const char *param_name,
|
||||
const float *dash,
|
||||
gsize n_dash)
|
||||
{
|
||||
_indent (p);
|
||||
g_string_append (p->str, "dash: ");
|
||||
|
||||
if (n_dash == 0)
|
||||
{
|
||||
g_string_append (p->str, "none");
|
||||
}
|
||||
else
|
||||
{
|
||||
gsize i;
|
||||
|
||||
string_append_double (p->str, dash[0]);
|
||||
for (i = 1; i < n_dash; i++)
|
||||
{
|
||||
g_string_append_c (p->str, ' ');
|
||||
string_append_double (p->str, dash[i]);
|
||||
}
|
||||
}
|
||||
|
||||
g_string_append (p->str, ";\n");
|
||||
}
|
||||
|
||||
static void
|
||||
render_node_print (Printer *p,
|
||||
GskRenderNode *node)
|
||||
@@ -3178,6 +3501,42 @@ render_node_print (Printer *p,
|
||||
append_rounded_rect_param (p, "clip", gsk_rounded_clip_node_get_clip (node));
|
||||
append_node_param (p, "child", gsk_rounded_clip_node_get_child (node));
|
||||
|
||||
end_node (p);
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_FILL_NODE:
|
||||
{
|
||||
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));
|
||||
|
||||
end_node (p);
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_STROKE_NODE:
|
||||
{
|
||||
const GskStroke *stroke;
|
||||
const float *dash;
|
||||
gsize n_dash;
|
||||
|
||||
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));
|
||||
|
||||
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);
|
||||
dash = gsk_stroke_get_dash (stroke, &n_dash);
|
||||
if (dash)
|
||||
append_dash_param (p, "dash", dash, n_dash);
|
||||
append_float_param (p, "dash-offset", gsk_stroke_get_dash_offset (stroke), 0.0f);
|
||||
|
||||
end_node (p);
|
||||
}
|
||||
|
208
gsk/gskspline.c
Normal file
208
gsk/gskspline.c
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright © 2002 University of Southern California
|
||||
* 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>
|
||||
* Carl D. Worth <cworth@cworth.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gsksplineprivate.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
/* Spline deviation from the circle in radius would be given by:
|
||||
|
||||
error = sqrt (x**2 + y**2) - 1
|
||||
|
||||
A simpler error function to work with is:
|
||||
|
||||
e = x**2 + y**2 - 1
|
||||
|
||||
From "Good approximation of circles by curvature-continuous Bezier
|
||||
curves", Tor Dokken and Morten Daehlen, Computer Aided Geometric
|
||||
Design 8 (1990) 22-41, we learn:
|
||||
|
||||
abs (max(e)) = 4/27 * sin**6(angle/4) / cos**2(angle/4)
|
||||
|
||||
and
|
||||
abs (error) =~ 1/2 * e
|
||||
|
||||
Of course, this error value applies only for the particular spline
|
||||
approximation that is used in _cairo_gstate_arc_segment.
|
||||
*/
|
||||
static float
|
||||
arc_error_normalized (float angle)
|
||||
{
|
||||
return 2.0/27.0 * pow (sin (angle / 4), 6) / pow (cos (angle / 4), 2);
|
||||
}
|
||||
|
||||
static float
|
||||
arc_max_angle_for_tolerance_normalized (float tolerance)
|
||||
{
|
||||
float angle, error;
|
||||
guint i;
|
||||
|
||||
/* Use table lookup to reduce search time in most cases. */
|
||||
struct {
|
||||
float angle;
|
||||
float error;
|
||||
} table[] = {
|
||||
{ G_PI / 1.0, 0.0185185185185185036127 },
|
||||
{ G_PI / 2.0, 0.000272567143730179811158 },
|
||||
{ G_PI / 3.0, 2.38647043651461047433e-05 },
|
||||
{ G_PI / 4.0, 4.2455377443222443279e-06 },
|
||||
{ G_PI / 5.0, 1.11281001494389081528e-06 },
|
||||
{ G_PI / 6.0, 3.72662000942734705475e-07 },
|
||||
{ G_PI / 7.0, 1.47783685574284411325e-07 },
|
||||
{ G_PI / 8.0, 6.63240432022601149057e-08 },
|
||||
{ G_PI / 9.0, 3.2715520137536980553e-08 },
|
||||
{ G_PI / 10.0, 1.73863223499021216974e-08 },
|
||||
{ G_PI / 11.0, 9.81410988043554039085e-09 },
|
||||
};
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (table); i++)
|
||||
{
|
||||
if (table[i].error < tolerance)
|
||||
return table[i].angle;
|
||||
}
|
||||
|
||||
i++;
|
||||
do {
|
||||
angle = G_PI / i++;
|
||||
error = arc_error_normalized (angle);
|
||||
} while (error > tolerance);
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
static guint
|
||||
arc_segments_needed (float angle,
|
||||
float radius,
|
||||
float tolerance)
|
||||
{
|
||||
float max_angle;
|
||||
|
||||
/* the error is amplified by at most the length of the
|
||||
* major axis of the circle; see cairo-pen.c for a more detailed analysis
|
||||
* of this. */
|
||||
max_angle = arc_max_angle_for_tolerance_normalized (tolerance / radius);
|
||||
|
||||
return ceil (fabs (angle) / max_angle);
|
||||
}
|
||||
|
||||
/* We want to draw a single spline approximating a circular arc radius
|
||||
R from angle A to angle B. Since we want a symmetric spline that
|
||||
matches the endpoints of the arc in position and slope, we know
|
||||
that the spline control points must be:
|
||||
|
||||
(R * cos(A), R * sin(A))
|
||||
(R * cos(A) - h * sin(A), R * sin(A) + h * cos (A))
|
||||
(R * cos(B) + h * sin(B), R * sin(B) - h * cos (B))
|
||||
(R * cos(B), R * sin(B))
|
||||
|
||||
for some value of h.
|
||||
|
||||
"Approximation of circular arcs by cubic polynomials", Michael
|
||||
Goldapp, Computer Aided Geometric Design 8 (1991) 227-238, provides
|
||||
various values of h along with error analysis for each.
|
||||
|
||||
From that paper, a very practical value of h is:
|
||||
|
||||
h = 4/3 * R * tan(angle/4)
|
||||
|
||||
This value does not give the spline with minimal error, but it does
|
||||
provide a very good approximation, (6th-order convergence), and the
|
||||
error expression is quite simple, (see the comment for
|
||||
_arc_error_normalized).
|
||||
*/
|
||||
static gboolean
|
||||
gsk_spline_decompose_arc_segment (const graphene_point_t *center,
|
||||
float radius,
|
||||
float angle_A,
|
||||
float angle_B,
|
||||
GskSplineAddCurveFunc curve_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
float r_sin_A, r_cos_A;
|
||||
float r_sin_B, r_cos_B;
|
||||
float h;
|
||||
|
||||
r_sin_A = radius * sin (angle_A);
|
||||
r_cos_A = radius * cos (angle_A);
|
||||
r_sin_B = radius * sin (angle_B);
|
||||
r_cos_B = radius * cos (angle_B);
|
||||
|
||||
h = 4.0/3.0 * tan ((angle_B - angle_A) / 4.0);
|
||||
|
||||
return curve_func ((graphene_point_t[4]) {
|
||||
GRAPHENE_POINT_INIT (
|
||||
center->x + r_cos_A,
|
||||
center->y + r_sin_A
|
||||
),
|
||||
GRAPHENE_POINT_INIT (
|
||||
center->x + r_cos_A - h * r_sin_A,
|
||||
center->y + r_sin_A + h * r_cos_A
|
||||
),
|
||||
GRAPHENE_POINT_INIT (
|
||||
center->x + r_cos_B + h * r_sin_B,
|
||||
center->y + r_sin_B - h * r_cos_B
|
||||
),
|
||||
GRAPHENE_POINT_INIT (
|
||||
center->x + r_cos_B,
|
||||
center->y + r_sin_B
|
||||
)
|
||||
},
|
||||
user_data);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gsk_spline_decompose_arc (const graphene_point_t *center,
|
||||
float radius,
|
||||
float tolerance,
|
||||
float start_angle,
|
||||
float end_angle,
|
||||
GskSplineAddCurveFunc curve_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
float step = start_angle - end_angle;
|
||||
guint i, n_segments;
|
||||
|
||||
/* Recurse if drawing arc larger than pi */
|
||||
if (ABS (step) > G_PI)
|
||||
{
|
||||
float mid_angle = (start_angle + end_angle) / 2.0;
|
||||
|
||||
return gsk_spline_decompose_arc (center, radius, tolerance, start_angle, mid_angle, curve_func, user_data)
|
||||
&& gsk_spline_decompose_arc (center, radius, tolerance, mid_angle, end_angle, curve_func, user_data);
|
||||
}
|
||||
else if (ABS (step) < tolerance)
|
||||
{
|
||||
return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data);
|
||||
}
|
||||
|
||||
n_segments = arc_segments_needed (ABS (step), radius, tolerance);
|
||||
step = (end_angle - start_angle) / n_segments;
|
||||
|
||||
for (i = 0; i < n_segments - 1; i++, start_angle += step)
|
||||
{
|
||||
if (!gsk_spline_decompose_arc_segment (center, radius, start_angle, start_angle + step, curve_func, user_data))
|
||||
return FALSE;
|
||||
}
|
||||
return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data);
|
||||
}
|
||||
|
41
gsk/gsksplineprivate.h
Normal file
41
gsk/gsksplineprivate.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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>
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GSK_SPLINE_PRIVATE_H__
|
||||
#define __GSK_SPLINE_PRIVATE_H__
|
||||
|
||||
#include "gskpath.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef gboolean (* GskSplineAddCurveFunc) (const graphene_point_t curve[4],
|
||||
gpointer user_data);
|
||||
gboolean gsk_spline_decompose_arc (const graphene_point_t *center,
|
||||
float radius,
|
||||
float tolerance,
|
||||
float start_angle,
|
||||
float end_angle,
|
||||
GskSplineAddCurveFunc curve_func,
|
||||
gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GSK_SPLINE_PRIVATE_H__ */
|
||||
|
444
gsk/gskstroke.c
Normal file
444
gsk/gskstroke.c
Normal file
@@ -0,0 +1,444 @@
|
||||
/*
|
||||
* 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 "gskstrokeprivate.h"
|
||||
|
||||
/**
|
||||
* A `GskStroke` struct collects the parameters that influence
|
||||
* the operation of stroking a path.
|
||||
*/
|
||||
|
||||
G_DEFINE_BOXED_TYPE (GskStroke, gsk_stroke,
|
||||
gsk_stroke_copy,
|
||||
gsk_stroke_free)
|
||||
|
||||
|
||||
/**
|
||||
* gsk_stroke_new:
|
||||
* @line_width: line width of the stroke. Must be > 0
|
||||
*
|
||||
* Creates a new #GskStroke with the given @line_width.
|
||||
*
|
||||
* Returns: a new `GskStroke`
|
||||
*/
|
||||
GskStroke *
|
||||
gsk_stroke_new (float line_width)
|
||||
{
|
||||
GskStroke *self;
|
||||
|
||||
g_return_val_if_fail (line_width > 0, NULL);
|
||||
|
||||
self = g_new0 (GskStroke, 1);
|
||||
|
||||
self->line_width = line_width;
|
||||
self->line_cap = GSK_LINE_CAP_BUTT;
|
||||
self->line_join = GSK_LINE_JOIN_MITER;
|
||||
self->miter_limit = 4.f; /* following svg */
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_copy:
|
||||
* @other: `GskStroke` to copy
|
||||
*
|
||||
* Creates a copy of the given @other stroke.
|
||||
*
|
||||
* Returns: a new `GskStroke`. Use [method@Gsk.Stroke.free] to free it
|
||||
*/
|
||||
GskStroke *
|
||||
gsk_stroke_copy (const GskStroke *other)
|
||||
{
|
||||
GskStroke *self;
|
||||
|
||||
g_return_val_if_fail (other != NULL, NULL);
|
||||
|
||||
self = g_new (GskStroke, 1);
|
||||
|
||||
gsk_stroke_init_copy (self, other);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_free:
|
||||
* @self: a `GskStroke`
|
||||
*
|
||||
* Frees a `GskStroke`.
|
||||
*/
|
||||
void
|
||||
gsk_stroke_free (GskStroke *self)
|
||||
{
|
||||
if (self == NULL)
|
||||
return;
|
||||
|
||||
gsk_stroke_clear (self);
|
||||
|
||||
g_free (self);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_to_cairo:
|
||||
* @self: a #GskStroke
|
||||
* @cr: the cairo context to configure
|
||||
*
|
||||
* A helper function that sets the stroke parameters
|
||||
* of @cr from the values found in @self.
|
||||
*/
|
||||
void
|
||||
gsk_stroke_to_cairo (const GskStroke *self,
|
||||
cairo_t *cr)
|
||||
{
|
||||
cairo_set_line_width (cr, self->line_width);
|
||||
|
||||
/* gcc can optimize that to a direct case. This catches later additions to the enum */
|
||||
switch (self->line_cap)
|
||||
{
|
||||
case GSK_LINE_CAP_BUTT:
|
||||
cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
|
||||
break;
|
||||
case GSK_LINE_CAP_ROUND:
|
||||
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
|
||||
break;
|
||||
case GSK_LINE_CAP_SQUARE:
|
||||
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
|
||||
/* gcc can optimize that to a direct case. This catches later additions to the enum */
|
||||
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:
|
||||
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
|
||||
break;
|
||||
case GSK_LINE_JOIN_BEVEL:
|
||||
cairo_set_line_join (cr, CAIRO_LINE_JOIN_BEVEL);
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
|
||||
cairo_set_miter_limit (cr, self->miter_limit);
|
||||
|
||||
if (self->dash_length)
|
||||
{
|
||||
gsize i;
|
||||
double *dash = g_newa (double, self->n_dash);
|
||||
|
||||
for (i = 0; i < self->n_dash; i++)
|
||||
{
|
||||
dash[i] = self->dash[i];
|
||||
}
|
||||
cairo_set_dash (cr, dash, self->n_dash, self->dash_offset);
|
||||
}
|
||||
else
|
||||
cairo_set_dash (cr, NULL, 0, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_equal:
|
||||
* @stroke1: the first #GskStroke
|
||||
* @stroke2: the second #GskStroke
|
||||
*
|
||||
* Checks if 2 strokes are identical.
|
||||
*
|
||||
* Returns: `TRUE` if the 2 strokes are equal,
|
||||
* `FALSE` otherwise
|
||||
*/
|
||||
gboolean
|
||||
gsk_stroke_equal (gconstpointer stroke1,
|
||||
gconstpointer stroke2)
|
||||
{
|
||||
const GskStroke *self1 = stroke1;
|
||||
const GskStroke *self2 = stroke2;
|
||||
|
||||
return self1->line_width == self2->line_width;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_set_line_width:
|
||||
* @self: a `GskStroke`
|
||||
* @line_width: width of the line in pixels
|
||||
*
|
||||
* Sets the line width to be used when stroking. The line width
|
||||
* must be > 0.
|
||||
*/
|
||||
void
|
||||
gsk_stroke_set_line_width (GskStroke *self,
|
||||
float line_width)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (line_width > 0);
|
||||
|
||||
self->line_width = line_width;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_get_line_width:
|
||||
* @self: a `GskStroke`
|
||||
*
|
||||
* Gets the line width used.
|
||||
*
|
||||
* Returns: The line width
|
||||
*/
|
||||
float
|
||||
gsk_stroke_get_line_width (const GskStroke *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, 0.0);
|
||||
|
||||
return self->line_width;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_set_line_cap:
|
||||
* @self: a`GskStroke`
|
||||
* @line_cap: the `GskLineCap`
|
||||
*
|
||||
* Sets the line cap to be used when stroking.
|
||||
*
|
||||
* See [enum@Gsk.LineCap] for details.
|
||||
*/
|
||||
void
|
||||
gsk_stroke_set_line_cap (GskStroke *self,
|
||||
GskLineCap line_cap)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
self->line_cap = line_cap;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_get_line_cap:
|
||||
* @self: a `GskStroke`
|
||||
*
|
||||
* Gets the line cap used.
|
||||
*
|
||||
* See [enum@Gsk.LineCap] for details.
|
||||
*
|
||||
* Returns: The line cap
|
||||
*/
|
||||
GskLineCap
|
||||
gsk_stroke_get_line_cap (const GskStroke *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, 0.0);
|
||||
|
||||
return self->line_cap;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_set_line_join:
|
||||
* @self: a `GskStroke`
|
||||
* @line_join: The line join to use
|
||||
*
|
||||
* Sets the line join to be used when stroking.
|
||||
*
|
||||
* See [enum@Gsk.LineJoin] for details.
|
||||
*/
|
||||
void
|
||||
gsk_stroke_set_line_join (GskStroke *self,
|
||||
GskLineJoin line_join)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
self->line_join = line_join;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_get_line_join:
|
||||
* @self: a `GskStroke`
|
||||
*
|
||||
* Gets the line join used.
|
||||
*
|
||||
* See [enum@Gsk.LineJoin] for details.
|
||||
*
|
||||
* Returns: The line join
|
||||
*/
|
||||
GskLineJoin
|
||||
gsk_stroke_get_line_join (const GskStroke *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, 0.0);
|
||||
|
||||
return self->line_join;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_set_miter_limit:
|
||||
* @self: a `GskStroke`
|
||||
* @limit: the miter limit, must be non-negative
|
||||
*
|
||||
* Sets the limit for the distance from the corner where sharp
|
||||
* turns of joins get cut off. The miter limit is in units of
|
||||
* line width.
|
||||
*
|
||||
* For joins of type `GSK_LINE_JOIN_MITER` that exceed the miter
|
||||
* limit, the join gets rendered as if it was of type
|
||||
* `GSK_LINE_JOIN_BEVEL`. For joins of type `GSK_LINE_JOIN_MITER_CLIP`,
|
||||
* the miter is clipped at a distance of half the miter limit.
|
||||
*/
|
||||
void
|
||||
gsk_stroke_set_miter_limit (GskStroke *self,
|
||||
float limit)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (limit >= 0);
|
||||
|
||||
self->miter_limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_get_miter_limit:
|
||||
* @self: a `GskStroke`
|
||||
*
|
||||
* Returns the miter limit of a `GskStroke`.
|
||||
*/
|
||||
float
|
||||
gsk_stroke_get_miter_limit (const GskStroke *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, 4.f);
|
||||
|
||||
return self->miter_limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_set_dash:
|
||||
* @self: a `GskStroke`
|
||||
* @dash: (array length=n_dash) (transfer none) (nullable):
|
||||
* the array of dashes
|
||||
* @n_dash: number of elements in @dash
|
||||
*
|
||||
* Sets the dash pattern to use by this stroke.
|
||||
*
|
||||
* A dash pattern is specified by an array of alternating non-negative
|
||||
* values. Each value provides the length of alternate "on" and "off"
|
||||
* portions of the stroke.
|
||||
*
|
||||
* Each "on" segment will have caps applied as if the segment were a
|
||||
* separate contour. In particular, it is valid to use an "on" length
|
||||
* of 0 with `GSK_LINE_CAP_ROUND` or `GSK_LINE_CAP_SQUARE` to draw dots
|
||||
* or squares along a path.
|
||||
*
|
||||
* If @n_dash is 0, if all elements in @dash are 0, or if there are
|
||||
* negative values in @dash, then dashing is disabled.
|
||||
*
|
||||
* If @n_dash is 1, an alternating "on" and "off" pattern with the
|
||||
* single dash length provided is assumed.
|
||||
*
|
||||
* If @n_dash is uneven, the dash array will be used with the first
|
||||
* element in @dash defining an "on" or "off" in alternating passes
|
||||
* through the array.
|
||||
*
|
||||
* You can specify a starting offset into the dash with
|
||||
* [method@Gsk.Stroke.set_dash_offset].
|
||||
*/
|
||||
void
|
||||
gsk_stroke_set_dash (GskStroke *self,
|
||||
const float *dash,
|
||||
gsize n_dash)
|
||||
{
|
||||
float dash_length;
|
||||
gsize i;
|
||||
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (dash != NULL || n_dash == 0);
|
||||
|
||||
dash_length = 0;
|
||||
for (i = 0; i < n_dash; i++)
|
||||
{
|
||||
if (!(dash[i] >= 0)) /* should catch NaN */
|
||||
{
|
||||
g_critical ("invalid value in dash array at position %zu", i);
|
||||
return;
|
||||
}
|
||||
dash_length += dash[i];
|
||||
}
|
||||
|
||||
self->dash_length = dash_length;
|
||||
g_free (self->dash);
|
||||
self->dash = g_memdup (dash, sizeof (gfloat) * n_dash);
|
||||
self->n_dash = n_dash;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_get_dash:
|
||||
* @self: a `GskStroke`
|
||||
* @n_dash: (out caller-allocates): number of elements
|
||||
* in the array returned
|
||||
*
|
||||
* Gets the dash array in use or `NULL` if dashing is disabled.
|
||||
*
|
||||
* Returns: (array length=n_dash) (transfer none) (nullable):
|
||||
* The dash array or `NULL` if the dash array is empty.
|
||||
*/
|
||||
const float *
|
||||
gsk_stroke_get_dash (const GskStroke *self,
|
||||
gsize *n_dash)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, NULL);
|
||||
g_return_val_if_fail (n_dash != NULL, NULL);
|
||||
|
||||
*n_dash = self->n_dash;
|
||||
|
||||
return self->dash;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_set_dash_offset:
|
||||
* @self: a `GskStroke`
|
||||
* @offset: offset into the dash pattern
|
||||
*
|
||||
* Sets the offset into the dash pattern where dashing should begin.
|
||||
*
|
||||
* This is an offset into the length of the path, not an index into
|
||||
* the array values of the dash array.
|
||||
*
|
||||
* See [method@Gsk.Stroke.set_dash] for more details on dashing.
|
||||
*/
|
||||
void
|
||||
gsk_stroke_set_dash_offset (GskStroke *self,
|
||||
float offset)
|
||||
{
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
self->dash_offset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_stroke_get_dash_offset:
|
||||
* @self: a `GskStroke`
|
||||
*
|
||||
* Returns the dash_offset of a `GskStroke`.
|
||||
*/
|
||||
float
|
||||
gsk_stroke_get_dash_offset (const GskStroke *self)
|
||||
{
|
||||
g_return_val_if_fail (self != NULL, 4.f);
|
||||
|
||||
return self->dash_offset;
|
||||
}
|
91
gsk/gskstroke.h
Normal file
91
gsk/gskstroke.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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>
|
||||
*/
|
||||
|
||||
#ifndef __GSK_STROKE_H__
|
||||
#define __GSK_STROKE_H__
|
||||
|
||||
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||
#error "Only <gsk/gsk.h> can be included directly."
|
||||
#endif
|
||||
|
||||
|
||||
#include <gsk/gsktypes.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GSK_TYPE_STROKE (gsk_stroke_get_type ())
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GType gsk_stroke_get_type (void) G_GNUC_CONST;
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskStroke * gsk_stroke_new (float line_width);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskStroke * gsk_stroke_copy (const GskStroke *other);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_stroke_free (GskStroke *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gsk_stroke_equal (gconstpointer stroke1,
|
||||
gconstpointer stroke2);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_stroke_set_line_width (GskStroke *self,
|
||||
float line_width);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
float gsk_stroke_get_line_width (const GskStroke *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_stroke_set_line_cap (GskStroke *self,
|
||||
GskLineCap line_cap);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskLineCap gsk_stroke_get_line_cap (const GskStroke *self);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_stroke_set_line_join (GskStroke *self,
|
||||
GskLineJoin line_join);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
GskLineJoin gsk_stroke_get_line_join (const GskStroke *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_stroke_set_miter_limit (GskStroke *self,
|
||||
float limit);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
float gsk_stroke_get_miter_limit (const GskStroke *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_stroke_set_dash (GskStroke *self,
|
||||
const float *dash,
|
||||
gsize n_dash);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
const float * gsk_stroke_get_dash (const GskStroke *self,
|
||||
gsize *n_dash);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_stroke_set_dash_offset (GskStroke *self,
|
||||
float offset);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
float gsk_stroke_get_dash_offset (const GskStroke *self);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_stroke_to_cairo (const GskStroke *self,
|
||||
cairo_t *cr);
|
||||
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GSK_STROKE_H__ */
|
60
gsk/gskstrokeprivate.h
Normal file
60
gsk/gskstrokeprivate.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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>
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __GSK_STROKE_PRIVATE_H__
|
||||
#define __GSK_STROKE_PRIVATE_H__
|
||||
|
||||
#include "gskstroke.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
struct _GskStroke
|
||||
{
|
||||
float line_width;
|
||||
GskLineCap line_cap;
|
||||
GskLineJoin line_join;
|
||||
float miter_limit;
|
||||
|
||||
float *dash;
|
||||
gsize n_dash;
|
||||
float dash_length; /* sum of all dashes in the array */
|
||||
float dash_offset;
|
||||
};
|
||||
|
||||
static inline void
|
||||
gsk_stroke_init_copy (GskStroke *stroke,
|
||||
const GskStroke *other)
|
||||
{
|
||||
*stroke = *other;
|
||||
|
||||
stroke->dash = g_memdup (other->dash, stroke->n_dash * sizeof (float));
|
||||
}
|
||||
|
||||
static inline void
|
||||
gsk_stroke_clear (GskStroke *stroke)
|
||||
{
|
||||
g_clear_pointer (&stroke->dash, g_free);
|
||||
stroke->n_dash = 0; /* better safe than sorry */
|
||||
}
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GSK_STROKE_PRIVATE_H__ */
|
||||
|
@@ -25,6 +25,10 @@
|
||||
#include <gdk/gdk.h>
|
||||
#include <gsk/gskenums.h>
|
||||
|
||||
typedef struct _GskPath GskPath;
|
||||
typedef struct _GskPathBuilder GskPathBuilder;
|
||||
typedef struct _GskPathMeasure GskPathMeasure;
|
||||
typedef struct _GskRenderer GskRenderer;
|
||||
typedef struct _GskStroke GskStroke;
|
||||
typedef struct _GskTransform GskTransform;
|
||||
|
||||
|
@@ -23,23 +23,31 @@ gsk_private_gl_shaders = [
|
||||
]
|
||||
|
||||
gsk_public_sources = files([
|
||||
'gskdiff.c',
|
||||
'gskcairorenderer.c',
|
||||
'gskdiff.c',
|
||||
'gskglshader.c',
|
||||
'gskpath.c',
|
||||
'gskpathbuilder.c',
|
||||
'gskpathdash.c',
|
||||
'gskpathmeasure.c',
|
||||
'gskrenderer.c',
|
||||
'gskrendernode.c',
|
||||
'gskrendernodeimpl.c',
|
||||
'gskrendernodeparser.c',
|
||||
'gskroundedrect.c',
|
||||
'gskstroke.c',
|
||||
'gsktransform.c',
|
||||
'gl/gskglrenderer.c',
|
||||
])
|
||||
|
||||
gsk_private_sources = files([
|
||||
'gskcairoblur.c',
|
||||
'gskcontour.c',
|
||||
'gskcurve.c',
|
||||
'gskdebug.c',
|
||||
'gskprivate.c',
|
||||
'gskprofiler.c',
|
||||
'gskspline.c',
|
||||
'gl/gskglattachmentstate.c',
|
||||
'gl/gskglbuffer.c',
|
||||
'gl/gskglcommandqueue.c',
|
||||
@@ -66,9 +74,13 @@ gsk_public_headers = files([
|
||||
'gskcairorenderer.h',
|
||||
'gskenums.h',
|
||||
'gskglshader.h',
|
||||
'gskpath.h',
|
||||
'gskpathbuilder.h',
|
||||
'gskpathmeasure.h',
|
||||
'gskrenderer.h',
|
||||
'gskrendernode.h',
|
||||
'gskroundedrect.h',
|
||||
'gskstroke.h',
|
||||
'gsktransform.h',
|
||||
'gsktypes.h',
|
||||
])
|
||||
|
@@ -1179,6 +1179,8 @@ static const GskVulkanRenderPassNodeFunc nodes_vtable[] = {
|
||||
[GSK_GL_SHADER_NODE] = NULL,
|
||||
[GSK_TEXTURE_SCALE_NODE] = gsk_vulkan_render_pass_add_texture_scale_node,
|
||||
[GSK_MASK_NODE] = NULL,
|
||||
[GSK_FILL_NODE] = NULL,
|
||||
[GSK_STROKE_NODE] = NULL,
|
||||
};
|
||||
|
||||
static void
|
||||
|
@@ -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,
|
||||
|
@@ -30,6 +30,7 @@
|
||||
|
||||
#include "gsk/gskrendernodeprivate.h"
|
||||
#include "gsk/gskroundedrectprivate.h"
|
||||
#include "gsk/gskstrokeprivate.h"
|
||||
|
||||
#include "gtk/gskpangoprivate.h"
|
||||
|
||||
@@ -105,6 +106,14 @@ struct _GtkSnapshotState {
|
||||
struct {
|
||||
GskRoundedRect bounds;
|
||||
} rounded_clip;
|
||||
struct {
|
||||
GskPath *path;
|
||||
GskFillRule fill_rule;
|
||||
} fill;
|
||||
struct {
|
||||
GskPath *path;
|
||||
GskStroke stroke;
|
||||
} stroke;
|
||||
struct {
|
||||
gsize n_shadows;
|
||||
GskShadow *shadows;
|
||||
@@ -1096,6 +1105,135 @@ gtk_snapshot_push_rounded_clip (GtkSnapshot *snapshot,
|
||||
gsk_rounded_rect_scale_affine (&state->data.rounded_clip.bounds, bounds, scale_x, scale_y, dx, dy);
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
gtk_snapshot_collect_fill (GtkSnapshot *snapshot,
|
||||
GtkSnapshotState *state,
|
||||
GskRenderNode **nodes,
|
||||
guint n_nodes)
|
||||
{
|
||||
GskRenderNode *node, *fill_node;
|
||||
|
||||
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
|
||||
fill_node = gsk_fill_node_new (node,
|
||||
state->data.fill.path,
|
||||
state->data.fill.fill_rule);
|
||||
|
||||
if (fill_node->bounds.size.width == 0 ||
|
||||
fill_node->bounds.size.height == 0)
|
||||
{
|
||||
gsk_render_node_unref (node);
|
||||
gsk_render_node_unref (fill_node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gsk_render_node_unref (node);
|
||||
|
||||
return fill_node;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_snapshot_clear_fill (GtkSnapshotState *state)
|
||||
{
|
||||
gsk_path_unref (state->data.fill.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_snapshot_push_fill:
|
||||
* @snapshot: a #GtkSnapshot
|
||||
* @path: The path describing the area to fill
|
||||
* @fill_rule: The fill rule to use
|
||||
*
|
||||
* Fills the area given by @path and @fill_rule with an image and discards everything
|
||||
* outside of it.
|
||||
*
|
||||
* The image is recorded until the next call to gtk_snapshot_pop().
|
||||
*/
|
||||
void
|
||||
gtk_snapshot_push_fill (GtkSnapshot *snapshot,
|
||||
GskPath *path,
|
||||
GskFillRule fill_rule)
|
||||
{
|
||||
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,
|
||||
gtk_snapshot_get_current_state (snapshot)->transform,
|
||||
gtk_snapshot_collect_fill,
|
||||
gtk_snapshot_clear_fill);
|
||||
|
||||
state->data.fill.path = gsk_path_ref (path);
|
||||
state->data.fill.fill_rule = fill_rule;
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
gtk_snapshot_collect_stroke (GtkSnapshot *snapshot,
|
||||
GtkSnapshotState *state,
|
||||
GskRenderNode **nodes,
|
||||
guint n_nodes)
|
||||
{
|
||||
GskRenderNode *node, *stroke_node;
|
||||
|
||||
node = gtk_snapshot_collect_default (snapshot, state, nodes, n_nodes);
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
|
||||
stroke_node = gsk_stroke_node_new (node,
|
||||
state->data.stroke.path,
|
||||
&state->data.stroke.stroke);
|
||||
|
||||
if (stroke_node->bounds.size.width == 0 ||
|
||||
stroke_node->bounds.size.height == 0)
|
||||
{
|
||||
gsk_render_node_unref (node);
|
||||
gsk_render_node_unref (stroke_node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gsk_render_node_unref (node);
|
||||
|
||||
return stroke_node;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_snapshot_clear_stroke (GtkSnapshotState *state)
|
||||
{
|
||||
gsk_path_unref (state->data.stroke.path);
|
||||
gsk_stroke_clear (&state->data.stroke.stroke);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_snapshot_push_stroke:
|
||||
* @snapshot: a #GtkSnapshot
|
||||
* @path: The path to stroke
|
||||
* @stroke: The stroke attributes
|
||||
*
|
||||
* Strokes the given @path with the attributes given by @stroke and the
|
||||
* image being recorded until the next call to gtk_snapshot_pop().
|
||||
*/
|
||||
void
|
||||
gtk_snapshot_push_stroke (GtkSnapshot *snapshot,
|
||||
GskPath *path,
|
||||
const GskStroke *stroke)
|
||||
{
|
||||
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,
|
||||
gtk_snapshot_get_current_state (snapshot)->transform,
|
||||
gtk_snapshot_collect_stroke,
|
||||
gtk_snapshot_clear_stroke);
|
||||
|
||||
state->data.stroke.path = gsk_path_ref (path);
|
||||
gsk_stroke_init_copy (&state->data.stroke.stroke, stroke);
|
||||
}
|
||||
|
||||
static GskRenderNode *
|
||||
gtk_snapshot_collect_shadow (GtkSnapshot *snapshot,
|
||||
GtkSnapshotState *state,
|
||||
|
@@ -88,6 +88,14 @@ GDK_AVAILABLE_IN_ALL
|
||||
void gtk_snapshot_push_rounded_clip (GtkSnapshot *snapshot,
|
||||
const GskRoundedRect *bounds);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_snapshot_push_fill (GtkSnapshot *snapshot,
|
||||
GskPath *path,
|
||||
GskFillRule fill_rule);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_snapshot_push_stroke (GtkSnapshot *snapshot,
|
||||
GskPath *path,
|
||||
const GskStroke *stroke);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gtk_snapshot_push_shadow (GtkSnapshot *snapshot,
|
||||
const GskShadow *shadow,
|
||||
gsize n_shadows);
|
||||
|
@@ -298,6 +298,12 @@ create_list_model_for_render_node (GskRenderNode *node)
|
||||
case GSK_ROUNDED_CLIP_NODE:
|
||||
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_rounded_clip_node_get_child (node) }, 1);
|
||||
|
||||
case GSK_FILL_NODE:
|
||||
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_fill_node_get_child (node) }, 1);
|
||||
|
||||
case GSK_STROKE_NODE:
|
||||
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_stroke_node_get_child (node) }, 1);
|
||||
|
||||
case GSK_SHADOW_NODE:
|
||||
return create_render_node_list_model ((GskRenderNode *[1]) { gsk_shadow_node_get_child (node) }, 1);
|
||||
|
||||
@@ -425,6 +431,10 @@ node_type_name (GskRenderNodeType type)
|
||||
return "Clip";
|
||||
case GSK_ROUNDED_CLIP_NODE:
|
||||
return "Rounded Clip";
|
||||
case GSK_FILL_NODE:
|
||||
return "Fill";
|
||||
case GSK_STROKE_NODE:
|
||||
return "Stroke";
|
||||
case GSK_SHADOW_NODE:
|
||||
return "Shadow";
|
||||
case GSK_BLEND_NODE:
|
||||
@@ -466,6 +476,8 @@ node_name (GskRenderNode *node)
|
||||
case GSK_REPEAT_NODE:
|
||||
case GSK_CLIP_NODE:
|
||||
case GSK_ROUNDED_CLIP_NODE:
|
||||
case GSK_FILL_NODE:
|
||||
case GSK_STROKE_NODE:
|
||||
case GSK_SHADOW_NODE:
|
||||
case GSK_BLEND_NODE:
|
||||
case GSK_MASK_NODE:
|
||||
@@ -894,6 +906,20 @@ add_float_row (GListStore *store,
|
||||
g_free (text);
|
||||
}
|
||||
|
||||
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
|
||||
populate_render_node_properties (GListStore *store,
|
||||
GskRenderNode *node)
|
||||
@@ -1133,9 +1159,7 @@ populate_render_node_properties (GListStore *store,
|
||||
case GSK_BLEND_NODE:
|
||||
{
|
||||
GskBlendMode mode = gsk_blend_node_get_blend_mode (node);
|
||||
tmp = g_enum_to_string (GSK_TYPE_BLEND_MODE, mode);
|
||||
add_text_row (store, "Blendmode", tmp);
|
||||
g_free (tmp);
|
||||
add_text_row (store, "Blendmode", enum_to_nick (GSK_TYPE_BLEND_MODE, mode));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1377,6 +1401,39 @@ populate_render_node_properties (GListStore *store,
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_FILL_NODE:
|
||||
{
|
||||
GskPath *path = gsk_fill_node_get_path (node);
|
||||
GskFillRule fill_rule = gsk_fill_node_get_fill_rule (node);
|
||||
|
||||
tmp = gsk_path_to_string (path);
|
||||
add_text_row (store, "Path", tmp);
|
||||
g_free (tmp);
|
||||
|
||||
add_text_row (store, "Fill rule", enum_to_nick (GSK_TYPE_FILL_RULE, fill_rule));
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_STROKE_NODE:
|
||||
{
|
||||
GskPath *path = gsk_stroke_node_get_path (node);
|
||||
const GskStroke *stroke = gsk_stroke_node_get_stroke (node);
|
||||
GskLineCap line_cap = gsk_stroke_get_line_cap (stroke);
|
||||
GskLineJoin line_join = gsk_stroke_get_line_join (stroke);
|
||||
|
||||
tmp = gsk_path_to_string (path);
|
||||
add_text_row (store, "Path", tmp);
|
||||
g_free (tmp);
|
||||
|
||||
tmp = g_strdup_printf ("%.2f", gsk_stroke_get_line_width (stroke));
|
||||
add_text_row (store, "Line width", tmp);
|
||||
g_free (tmp);
|
||||
|
||||
add_text_row (store, "Line cap", enum_to_nick (GSK_TYPE_LINE_CAP, line_cap));
|
||||
add_text_row (store, "Line join", enum_to_nick (GSK_TYPE_LINE_JOIN, line_join));
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_CONTAINER_NODE:
|
||||
tmp = g_strdup_printf ("%d", gsk_container_node_get_n_children (node));
|
||||
add_text_row (store, "Children", tmp);
|
||||
|
134
testsuite/gsk/curve-special-cases.c
Normal file
134
testsuite/gsk/curve-special-cases.c
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
#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)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_point_t p[4];
|
||||
graphene_vec2_t t;
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 100, 0);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_LINE, 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_x_axis (), 0.0001));
|
||||
|
||||
|
||||
graphene_point_init (&p[0], 0, 0);
|
||||
graphene_point_init (&p[1], 0, 100);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_LINE, p));
|
||||
|
||||
gsk_curve_get_start_tangent (&c, &t);
|
||||
g_assert_true (graphene_vec2_near (&t, graphene_vec2_y_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], 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);
|
||||
graphene_point_init (&p[3], 100, 100);
|
||||
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CUBIC, 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));
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *argv[])
|
||||
{
|
||||
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);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
216
testsuite/gsk/curve.c
Normal file
216
testsuite/gsk/curve.c
Normal file
@@ -0,0 +1,216 @@
|
||||
#include <gtk/gtk.h>
|
||||
#include "gsk/gskcurveprivate.h"
|
||||
|
||||
static void
|
||||
init_random_point (graphene_point_t *p)
|
||||
{
|
||||
p->x = g_test_rand_double_range (0, 1000);
|
||||
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 (GskCurve *curve)
|
||||
{
|
||||
switch (g_test_rand_int_range (GSK_PATH_LINE, GSK_PATH_CONIC + 1))
|
||||
{
|
||||
case GSK_PATH_LINE:
|
||||
{
|
||||
graphene_point_t p[2];
|
||||
|
||||
init_random_point (&p[0]);
|
||||
init_random_point (&p[1]);
|
||||
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_LINE, p));
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
{
|
||||
graphene_point_t p[3];
|
||||
|
||||
init_random_point (&p[0]);
|
||||
init_random_point (&p[1]);
|
||||
init_random_point (&p[2]);
|
||||
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_QUAD, p));
|
||||
}
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
{
|
||||
graphene_point_t p[4];
|
||||
|
||||
init_random_point (&p[0]);
|
||||
init_random_point (&p[1]);
|
||||
init_random_point (&p[2]);
|
||||
init_random_point (&p[3]);
|
||||
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_CUBIC, p));
|
||||
}
|
||||
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 ();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_tangents (void)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_vec2_t vec, exact;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
gsk_curve_get_tangent (&c, 0, &vec);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&vec), 1.0f, 0.00001);
|
||||
gsk_curve_get_start_tangent (&c, &exact);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&exact), 1.0f, 0.00001);
|
||||
g_assert_true (graphene_vec2_near (&vec, &exact, 0.05));
|
||||
|
||||
gsk_curve_get_tangent (&c, 1, &vec);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&vec), 1.0f, 0.00001);
|
||||
gsk_curve_get_end_tangent (&c, &exact);
|
||||
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&exact), 1.0f, 0.00001);
|
||||
g_assert_true (graphene_vec2_near (&vec, &exact, 0.05));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_points (void)
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GskCurve c;
|
||||
graphene_point_t p;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
/* We can assert equality here because evaluating the polynomials with 0
|
||||
* has no effect on accuracy.
|
||||
*/
|
||||
gsk_curve_get_point (&c, 0, &p);
|
||||
g_assert_true (graphene_point_equal (gsk_curve_get_start_point (&c), &p));
|
||||
/* But here we evaluate the polynomials with 1 which gives the highest possible
|
||||
* accuracy error. So we'll just be generous here.
|
||||
*/
|
||||
gsk_curve_get_point (&c, 1, &p);
|
||||
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c), &p, 0.05));
|
||||
}
|
||||
}
|
||||
|
||||
/* at this point the subdivision stops and the decomposer
|
||||
* violates tolerance rules
|
||||
*/
|
||||
#define MIN_PROGRESS (1/1024.f)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
graphene_point_t p;
|
||||
float t;
|
||||
} PointOnLine;
|
||||
|
||||
static gboolean
|
||||
add_line_to_array (const graphene_point_t *from,
|
||||
const graphene_point_t *to,
|
||||
float from_progress,
|
||||
float to_progress,
|
||||
GskCurveLineReason reason,
|
||||
gpointer user_data)
|
||||
{
|
||||
GArray *array = user_data;
|
||||
PointOnLine *last = &g_array_index (array, PointOnLine, array->len - 1);
|
||||
|
||||
g_assert_true (array->len > 0);
|
||||
g_assert_cmpfloat (from_progress, >=, 0.0f);
|
||||
g_assert_cmpfloat (from_progress, <, to_progress);
|
||||
g_assert_cmpfloat (to_progress, <=, 1.0f);
|
||||
|
||||
g_assert_true (graphene_point_equal (&last->p, from));
|
||||
g_assert_cmpfloat (last->t, ==, from_progress);
|
||||
|
||||
g_array_append_vals (array, (PointOnLine[1]) { { *to, to_progress } }, 1);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_curve_decompose (void)
|
||||
{
|
||||
static const float tolerance = 0.5;
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
GArray *array;
|
||||
GskCurve c;
|
||||
|
||||
init_random_curve (&c);
|
||||
|
||||
array = g_array_new (FALSE, FALSE, sizeof (PointOnLine));
|
||||
g_array_append_vals (array, (PointOnLine[1]) { { *gsk_curve_get_start_point (&c), 0.f } }, 1);
|
||||
|
||||
g_assert_true (gsk_curve_decompose (&c, tolerance, add_line_to_array, array));
|
||||
|
||||
g_assert_cmpint (array->len, >=, 2); /* We at least got a line to the end */
|
||||
g_assert_cmpfloat (g_array_index (array, PointOnLine, array->len - 1).t, ==, 1.0);
|
||||
|
||||
for (int j = 0; j < array->len; j++)
|
||||
{
|
||||
PointOnLine *pol = &g_array_index (array, PointOnLine, j);
|
||||
graphene_point_t p;
|
||||
|
||||
/* Check that the points we got are actually on the line */
|
||||
gsk_curve_get_point (&c, pol->t, &p);
|
||||
g_assert_true (graphene_point_near (&pol->p, &p, 0.05));
|
||||
|
||||
/* Check that the mid point is not further than the tolerance */
|
||||
if (j > 0)
|
||||
{
|
||||
PointOnLine *last = &g_array_index (array, PointOnLine, j - 1);
|
||||
graphene_point_t mid;
|
||||
|
||||
if (pol->t - last->t > MIN_PROGRESS)
|
||||
{
|
||||
graphene_point_interpolate (&last->p, &pol->p, 0.5, &mid);
|
||||
gsk_curve_get_point (&c, (pol->t + last->t) / 2, &p);
|
||||
/* The decomposer does this cheaper Manhattan distance test,
|
||||
* so graphene_point_near() does not work */
|
||||
g_assert_cmpfloat (fabs (mid.x - p.x), <=, tolerance);
|
||||
g_assert_cmpfloat (fabs (mid.y - p.y), <=, tolerance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
(g_test_init) (&argc, &argv, NULL);
|
||||
|
||||
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);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
185
testsuite/gsk/dash.c
Normal file
185
testsuite/gsk/dash.c
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
#include "gsk/gskpathdashprivate.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 ();
|
||||
}
|
@@ -355,6 +355,9 @@ foreach test : node_parser_tests
|
||||
endforeach
|
||||
|
||||
tests = [
|
||||
['dash'],
|
||||
['path'],
|
||||
['path-special-cases'],
|
||||
['transform'],
|
||||
['shader'],
|
||||
]
|
||||
@@ -387,6 +390,8 @@ foreach t : tests
|
||||
endforeach
|
||||
|
||||
internal_tests = [
|
||||
[ 'curve' ],
|
||||
[ 'curve-special-cases' ],
|
||||
[ 'diff' ],
|
||||
[ 'half-float' ],
|
||||
['rounded-rect'],
|
||||
|
377
testsuite/gsk/path-special-cases.c
Normal file
377
testsuite/gsk/path-special-cases.c
Normal file
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
/* testcases from path_parser.rs in librsvg */
|
||||
static void
|
||||
test_rsvg_parse (void)
|
||||
{
|
||||
struct {
|
||||
const char *in;
|
||||
const char *out;
|
||||
} tests[] = {
|
||||
{ "", "" },
|
||||
// numbers
|
||||
{ "M 10 20", "M 10 20" },
|
||||
{ "M -10 -20", "M -10 -20" },
|
||||
{ "M .10 0.20", "M 0.1 0.2" },
|
||||
{ "M -.10 -0.20", "M -0.1 -0.2" },
|
||||
{ "M-.10-0.20", "M -0.1 -0.2" },
|
||||
{ "M10.5.50", "M 10.5 0.5" },
|
||||
{ "M.10.20", "M 0.1 0.2" },
|
||||
{ "M .10E1 .20e-4", "M 1 2e-05" },
|
||||
{ "M-.10E1-.20", "M -1 -0.2" },
|
||||
{ "M10.10E2 -0.20e3", "M 1010 -200" },
|
||||
{ "M-10.10E2-0.20e-3", "M -1010 -0.0002" },
|
||||
{ "M1e2.5", "M 100 0.5" },
|
||||
{ "M1e-2.5", "M 0.01 0.5" },
|
||||
{ "M1e+2.5", "M 100 0.5" },
|
||||
// bogus numbers
|
||||
{ "M+", NULL },
|
||||
{ "M-", NULL },
|
||||
{ "M+x", NULL },
|
||||
{ "M10e", NULL },
|
||||
{ "M10ex", NULL },
|
||||
{ "M10e-", NULL },
|
||||
{ "M10e+x", NULL },
|
||||
// numbers with comma
|
||||
{ "M 10, 20", "M 10 20" },
|
||||
{ "M -10,-20", "M -10 -20" },
|
||||
{ "M.10 , 0.20", "M 0.1 0.2" },
|
||||
{ "M -.10, -0.20 ", "M -0.1 -0.2" },
|
||||
{ "M-.10-0.20", "M -0.1 -0.2" },
|
||||
{ "M.10.20", "M 0.1 0.2" },
|
||||
{ "M .10E1,.20e-4", "M 1 2e-05" },
|
||||
{ "M-.10E-2,-.20", "M -0.001 -0.2" },
|
||||
{ "M10.10E2,-0.20e3", "M 1010 -200" },
|
||||
{ "M-10.10E2,-0.20e-3", "M -1010 -0.0002" },
|
||||
// single moveto
|
||||
{ "M 10 20 ", "M 10 20" },
|
||||
{ "M10,20 ", "M 10 20" },
|
||||
{ "M10 20 ", "M 10 20" },
|
||||
{ " M10,20 ", "M 10 20" },
|
||||
// relative moveto
|
||||
{ "m10 20", "M 10 20" },
|
||||
// absolute moveto with implicit lineto
|
||||
{ "M10 20 30 40", "M 10 20 L 30 40" },
|
||||
{ "M10,20,30,40", "M 10 20 L 30 40" },
|
||||
{ "M.1-2,3E2-4", "M 0.1 -2 L 300 -4" },
|
||||
// relative moveto with implicit lineto
|
||||
{ "m10 20 30 40", "M 10 20 L 40 60" },
|
||||
// relative moveto with relative lineto sequence
|
||||
{ "m 46,447 l 0,0.5 -1,0 -1,0 0,1 0,12",
|
||||
"M 46 447 L 46 447.5 L 45 447.5 L 44 447.5 L 44 448.5 L 44 460.5" },
|
||||
// absolute moveto with implicit linetos
|
||||
{ "M10,20 30,40,50 60", "M 10 20 L 30 40 L 50 60" },
|
||||
// relative moveto with implicit linetos
|
||||
{ "m10 20 30 40 50 60", "M 10 20 L 40 60 L 90 120" },
|
||||
// absolute moveto moveto
|
||||
{ "M10 20 M 30 40", "M 10 20 M 30 40" },
|
||||
// relative moveto moveto
|
||||
{ "m10 20 m 30 40", "M 10 20 M 40 60" },
|
||||
// relative moveto lineto moveto
|
||||
{ "m10 20 30 40 m 50 60", "M 10 20 L 40 60 M 90 120" },
|
||||
// absolute moveto lineto
|
||||
{ "M10 20 L30,40", "M 10 20 L 30 40" },
|
||||
// relative moveto lineto
|
||||
{ "m10 20 l30,40", "M 10 20 L 40 60" },
|
||||
// relative moveto lineto lineto abs lineto
|
||||
{ "m10 20 30 40l30,40,50 60L200,300",
|
||||
"M 10 20 L 40 60 L 70 100 L 120 160 L 200 300" },
|
||||
// horizontal lineto
|
||||
{ "M10 20 H30", "M 10 20 L 30 20" },
|
||||
{ "M 10 20 H 30 40", "M 10 20 L 30 20 L 40 20" },
|
||||
{ "M10 20 H30,40-50", "M 10 20 L 30 20 L 40 20 L -50 20" },
|
||||
{ "m10 20 h30,40-50", "M 10 20 L 40 20 L 80 20 L 30 20" },
|
||||
// vertical lineto
|
||||
{ "M10 20 V30", "M 10 20 L 10 30" },
|
||||
{ "M10 20 V30 40", "M 10 20 L 10 30 L 10 40" },
|
||||
{ "M10 20 V30,40-50", "M 10 20 L 10 30 L 10 40 L 10 -50" },
|
||||
{ "m10 20 v30,40-50", "M 10 20 L 10 50 L 10 90 L 10 40" },
|
||||
// curveto
|
||||
{ "M10 20 C 30,40 50 60-70,80", "M 10 20 C 30 40, 50 60, -70 80" },
|
||||
{ "M10 20 C 30,40 50 60-70,80,90 100,110 120,130,140",
|
||||
"M 10 20 C 30 40, 50 60, -70 80 C 90 100, 110 120, 130 140" },
|
||||
{ "m10 20 c 30,40 50 60-70,80,90 100,110 120,130,140",
|
||||
"M 10 20 C 40 60, 60 80, -60 100 C 30 200, 50 220, 70 240" },
|
||||
{ "m10 20 c 30,40 50 60-70,80 90 100,110 120,130,140",
|
||||
"M 10 20 C 40 60, 60 80, -60 100 C 30 200, 50 220, 70 240" },
|
||||
// smooth curveto
|
||||
{ "M10 20 S 30,40-50,60", "M 10 20 C 10 20, 30 40, -50 60" },
|
||||
{ "M10 20 S 30,40 50 60-70,80,90 100",
|
||||
"M 10 20 C 10 20, 30 40, 50 60 C 70 80, -70 80, 90 100" },
|
||||
// quadratic curveto
|
||||
{ "M10 20 Q30 40 50 60", "M 10 20 Q 30 40, 50 60" },
|
||||
{ "M10 20 Q30 40 50 60,70,80-90 100",
|
||||
"M 10 20 Q 30 40, 50 60 Q 70 80, -90 100" },
|
||||
{ "m10 20 q 30,40 50 60-70,80 90 100",
|
||||
"M 10 20 Q 40 60, 60 80 Q -10 160, 150 180" },
|
||||
// smooth quadratic curveto
|
||||
{ "M10 20 T30 40", "M 10 20 Q 10 20, 30 40" },
|
||||
{ "M10 20 Q30 40 50 60 T70 80", "M 10 20 Q 30 40, 50 60 Q 70 80, 70 80" },
|
||||
{ "m10 20 q 30,40 50 60t-70,80",
|
||||
"M 10 20 Q 40 60, 60 80 Q 80 100, -10 160" },
|
||||
// elliptical arc. Exact numbers depend on too much math, so just verify
|
||||
// that these parse successfully
|
||||
{ "M 1 3 A 1 2 3 00 6 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 016 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 10,6 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 1,1 6 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 1 1 6 7", "path" },
|
||||
{ "M 1 2 A 1 2 3 1 16 7", "path" },
|
||||
// close path
|
||||
{ "M10 20 Z", "M 10 20 Z" },
|
||||
{ "m10 20 30 40 m 50 60 70 80 90 100z", "M 10 20 L 40 60 M 90 120 L 160 200 L 250 300 Z" },
|
||||
// must start with moveto
|
||||
{ " L10 20", NULL },
|
||||
// moveto args
|
||||
{ "M", NULL },
|
||||
{ "M,", NULL },
|
||||
{ "M10", NULL },
|
||||
{ "M10,", NULL },
|
||||
{ "M10x", NULL },
|
||||
{ "M10,x", NULL },
|
||||
{ "M10-20,", NULL },
|
||||
{ "M10-20-30", NULL },
|
||||
{ "M10-20-30 x", NULL },
|
||||
// closepath args
|
||||
{ "M10-20z10", NULL },
|
||||
{ "M10-20z,", NULL },
|
||||
// lineto args
|
||||
{ "M10-20L10", NULL },
|
||||
{ "M 10,10 L 20,20,30", NULL },
|
||||
{ "M 10,10 L 20,20,", NULL },
|
||||
// horizontal lineto args
|
||||
{ "M10-20H", NULL },
|
||||
{ "M10-20H,", NULL },
|
||||
{ "M10-20H30,", NULL },
|
||||
// vertical lineto args
|
||||
{ "M10-20v", NULL },
|
||||
{ "M10-20v,", NULL },
|
||||
{ "M10-20v30,", NULL },
|
||||
// curveto args
|
||||
{ "M10-20C1", NULL },
|
||||
{ "M10-20C1,", NULL },
|
||||
{ "M10-20C1 2", NULL },
|
||||
{ "M10-20C1,2,", NULL },
|
||||
{ "M10-20C1 2 3", NULL },
|
||||
{ "M10-20C1,2,3", NULL },
|
||||
{ "M10-20C1,2,3,", NULL },
|
||||
{ "M10-20C1 2 3 4", NULL },
|
||||
{ "M10-20C1,2,3,4", NULL },
|
||||
{ "M10-20C1,2,3,4,", NULL },
|
||||
{ "M10-20C1 2 3 4 5", NULL },
|
||||
{ "M10-20C1,2,3,4,5", NULL },
|
||||
{ "M10-20C1,2,3,4,5,", NULL },
|
||||
{ "M10-20C1,2,3,4,5,6,", NULL },
|
||||
// smooth curveto args
|
||||
{ "M10-20S1", NULL },
|
||||
{ "M10-20S1,", NULL },
|
||||
{ "M10-20S1 2", NULL },
|
||||
{ "M10-20S1,2,", NULL },
|
||||
{ "M10-20S1 2 3", NULL },
|
||||
{ "M10-20S1,2,3,", NULL },
|
||||
{ "M10-20S1,2,3,4,", NULL },
|
||||
// quadratic curveto args
|
||||
{ "M10-20Q1", NULL },
|
||||
{ "M10-20Q1,", NULL },
|
||||
{ "M10-20Q1 2", NULL },
|
||||
{ "M10-20Q1,2,", NULL },
|
||||
{ "M10-20Q1 2 3", NULL },
|
||||
{ "M10-20Q1,2,3", NULL },
|
||||
{ "M10-20Q1,2,3,", NULL },
|
||||
{ "M10 20 Q30 40 50 60,", NULL },
|
||||
// smooth quadratic curveto args
|
||||
{ "M10-20T1", NULL },
|
||||
{ "M10-20T1,", NULL },
|
||||
{ "M10 20 T 30 40,", NULL },
|
||||
// elliptical arc args
|
||||
{ "M10-20A1", NULL },
|
||||
{ "M10-20A1,", NULL },
|
||||
{ "M10-20A1 2", NULL },
|
||||
{ "M10-20A1 2,", NULL },
|
||||
{ "M10-20A1 2 3", NULL },
|
||||
{ "M10-20A1 2 3,", NULL },
|
||||
{ "M10-20A1 2 3 4", NULL },
|
||||
{ "M10-20A1 2 3 1", NULL },
|
||||
{ "M10-20A1 2 3,1,", NULL },
|
||||
{ "M10-20A1 2 3 1 5", NULL },
|
||||
{ "M10-20A1 2 3 1 1", NULL },
|
||||
{ "M10-20A1 2 3,1,1,", NULL },
|
||||
{ "M10-20A1 2 3 1 1 6", NULL },
|
||||
{ "M10-20A1 2 3,1,1,6,", NULL },
|
||||
{ "M 1 2 A 1 2 3 1.0 0.0 6 7", NULL },
|
||||
{ "M10-20A1 2 3,1,1,6,7,", NULL },
|
||||
// misc
|
||||
{ "M.. 1,0 0,100000", NULL },
|
||||
{ "M 10 20,M 10 20", NULL },
|
||||
{ "M 10 20, M 10 20", NULL },
|
||||
{ "M 10 20, M 10 20", NULL },
|
||||
{ "M 10 20, ", NULL },
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS (tests); i++)
|
||||
{
|
||||
GskPath *path;
|
||||
char *string;
|
||||
char *string2;
|
||||
|
||||
if (g_test_verbose ())
|
||||
g_print ("%d: %s\n", i, tests[i].in);
|
||||
|
||||
path = gsk_path_parse (tests[i].in);
|
||||
if (tests[i].out)
|
||||
{
|
||||
g_assert_nonnull (path);
|
||||
string = gsk_path_to_string (path);
|
||||
gsk_path_unref (path);
|
||||
|
||||
if (strcmp (tests[i].out, "path") != 0)
|
||||
{
|
||||
/* Preferred, but doesn't work, because
|
||||
* gsk_path_print() prints numbers with
|
||||
* insane accuracy
|
||||
*/
|
||||
/* g_assert_cmpstr (tests[i].out, ==, string); */
|
||||
path = gsk_path_parse (tests[i].out);
|
||||
g_assert_nonnull (path);
|
||||
|
||||
string2 = gsk_path_to_string (path);
|
||||
gsk_path_unref (path);
|
||||
|
||||
g_assert_cmpstr (string, ==, string2);
|
||||
|
||||
g_free (string2);
|
||||
}
|
||||
|
||||
path = gsk_path_parse (string);
|
||||
g_assert_nonnull (path);
|
||||
|
||||
string2 = gsk_path_to_string (path);
|
||||
gsk_path_unref (path);
|
||||
|
||||
g_assert_cmpstr (string, ==, string2);
|
||||
|
||||
g_free (string);
|
||||
g_free (string2);
|
||||
}
|
||||
else
|
||||
g_assert_null (path);
|
||||
}
|
||||
}
|
||||
|
||||
/* Test that circles and rectangles serialize as expected and can be
|
||||
* round-tripped through strings.
|
||||
*/
|
||||
static void
|
||||
test_serialize_custom_contours (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path;
|
||||
GskPath *path1;
|
||||
char *string;
|
||||
char *string1;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 50);
|
||||
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (111, 222, 333, 444));
|
||||
path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
string = gsk_path_to_string (path);
|
||||
g_assert_cmpstr ("M 150 100 A 50 50 0 0 0 50 100 A 50 50 0 0 0 150 100 z M 111 222 h 333 v 444 h -333 z", ==, string);
|
||||
|
||||
path1 = gsk_path_parse (string);
|
||||
string1 = gsk_path_to_string (path1);
|
||||
g_assert_cmpstr (string, ==, string1);
|
||||
|
||||
g_free (string);
|
||||
g_free (string1);
|
||||
gsk_path_unref (path);
|
||||
gsk_path_unref (path1);
|
||||
}
|
||||
|
||||
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;
|
||||
GskPathMeasure *measure;
|
||||
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");
|
||||
|
||||
measure = gsk_path_measure_new (path);
|
||||
|
||||
/* The midpoint of the right foot of a fat Cantarell X */
|
||||
inside = gsk_path_measure_in_fill (measure, &GRAPHENE_POINT_INIT (552.360107, 704.000000), GSK_FILL_RULE_WINDING);
|
||||
|
||||
g_assert_false (inside);
|
||||
|
||||
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 ("/path/rsvg-parse", test_rsvg_parse);
|
||||
g_test_add_func ("/path/serialize-custom-contours", test_serialize_custom_contours);
|
||||
g_test_add_func ("/path/bad-split", test_bad_split);
|
||||
g_test_add_func ("/path/bad-in-fill", test_bad_in_fill);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
1184
testsuite/gsk/path.c
Normal file
1184
testsuite/gsk/path.c
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user