Compare commits
6 Commits
matthiasc/
...
path-for-m
Author | SHA1 | Date | |
---|---|---|---|
|
87a6505031 | ||
|
1967a04e4d | ||
|
4110f81220 | ||
|
a6b5f3a065 | ||
|
2a51e6cd46 | ||
|
b4473cd795 |
@@ -336,6 +336,7 @@
|
|||||||
<file>panes.c</file>
|
<file>panes.c</file>
|
||||||
<file>password_entry.c</file>
|
<file>password_entry.c</file>
|
||||||
<file>path_fill.c</file>
|
<file>path_fill.c</file>
|
||||||
|
<file>path_text.c</file>
|
||||||
<file>peg_solitaire.c</file>
|
<file>peg_solitaire.c</file>
|
||||||
<file>pickers.c</file>
|
<file>pickers.c</file>
|
||||||
<file>printing.c</file>
|
<file>printing.c</file>
|
||||||
@@ -421,6 +422,9 @@
|
|||||||
<gresource prefix="/fontrendering">
|
<gresource prefix="/fontrendering">
|
||||||
<file>fontrendering.ui</file>
|
<file>fontrendering.ui</file>
|
||||||
</gresource>
|
</gresource>
|
||||||
|
<gresource prefix="/path_text">
|
||||||
|
<file>path_text.ui</file>
|
||||||
|
</gresource>
|
||||||
<gresource prefix="/org/gtk/Demo4">
|
<gresource prefix="/org/gtk/Demo4">
|
||||||
<file>icons/16x16/actions/application-exit.png</file>
|
<file>icons/16x16/actions/application-exit.png</file>
|
||||||
<file>icons/16x16/actions/document-new.png</file>
|
<file>icons/16x16/actions/document-new.png</file>
|
||||||
|
@@ -73,6 +73,7 @@ demos = files([
|
|||||||
'panes.c',
|
'panes.c',
|
||||||
'password_entry.c',
|
'password_entry.c',
|
||||||
'path_fill.c',
|
'path_fill.c',
|
||||||
|
'path_text.c',
|
||||||
'peg_solitaire.c',
|
'peg_solitaire.c',
|
||||||
'pickers.c',
|
'pickers.c',
|
||||||
'printing.c',
|
'printing.c',
|
||||||
|
604
demos/gtk-demo/path_text.c
Normal file
604
demos/gtk-demo/path_text.c
Normal file
@@ -0,0 +1,604 @@
|
|||||||
|
/* Path/Text
|
||||||
|
*
|
||||||
|
* This demo shows how to use GskPath to animate a path along another path.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <glib/gi18n.h>
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
#define GTK_TYPE_PATH_WIDGET (gtk_path_widget_get_type ())
|
||||||
|
G_DECLARE_FINAL_TYPE (GtkPathWidget, gtk_path_widget, GTK, PATH_WIDGET, GtkWidget)
|
||||||
|
|
||||||
|
#define POINT_SIZE 8
|
||||||
|
|
||||||
|
enum {
|
||||||
|
PROP_0,
|
||||||
|
PROP_TEXT,
|
||||||
|
PROP_EDITABLE,
|
||||||
|
N_PROPS
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _GtkPathWidget
|
||||||
|
{
|
||||||
|
GtkWidget parent_instance;
|
||||||
|
|
||||||
|
char *text;
|
||||||
|
gboolean editable;
|
||||||
|
|
||||||
|
graphene_point_t points[4];
|
||||||
|
|
||||||
|
guint active_point;
|
||||||
|
float line_closest;
|
||||||
|
|
||||||
|
GskPath *line_path;
|
||||||
|
GskPathMeasure *line_measure;
|
||||||
|
GskPath *text_path;
|
||||||
|
|
||||||
|
GdkPaintable *background;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _GtkPathWidgetClass
|
||||||
|
{
|
||||||
|
GtkWidgetClass parent_class;
|
||||||
|
};
|
||||||
|
|
||||||
|
static GParamSpec *properties[N_PROPS] = { NULL, };
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (GtkPathWidget, gtk_path_widget, GTK_TYPE_WIDGET)
|
||||||
|
|
||||||
|
static GskPath *
|
||||||
|
create_path_from_text (GtkWidget *widget,
|
||||||
|
const char *text,
|
||||||
|
graphene_point_t *out_offset)
|
||||||
|
{
|
||||||
|
PangoLayout *layout;
|
||||||
|
PangoFontDescription *desc;
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
GskPath *result;
|
||||||
|
|
||||||
|
layout = gtk_widget_create_pango_layout (widget, text);
|
||||||
|
desc = pango_font_description_from_string ("sans bold 36");
|
||||||
|
pango_layout_set_font_description (layout, desc);
|
||||||
|
pango_font_description_free (desc);
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_layout (builder, layout);
|
||||||
|
result = gsk_path_builder_free_to_path (builder);
|
||||||
|
|
||||||
|
if (out_offset)
|
||||||
|
graphene_point_init (out_offset, 0, - pango_layout_get_baseline (layout) / (double) PANGO_SCALE);
|
||||||
|
g_object_unref (layout);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
GskPathMeasure *measure;
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
graphene_point_t offset;
|
||||||
|
double scale;
|
||||||
|
} GtkPathTransform;
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_path_transform_point (GskPathMeasure *measure,
|
||||||
|
const graphene_point_t *pt,
|
||||||
|
const graphene_point_t *offset,
|
||||||
|
float scale,
|
||||||
|
graphene_point_t *res)
|
||||||
|
{
|
||||||
|
graphene_vec2_t tangent;
|
||||||
|
GskPathPoint point;
|
||||||
|
|
||||||
|
if (gsk_path_measure_get_point (measure, (pt->x + offset->x) * scale, &point))
|
||||||
|
{
|
||||||
|
GskPath *path = gsk_path_measure_get_path (measure);
|
||||||
|
|
||||||
|
gsk_path_point_get_position (path, &point, res);
|
||||||
|
gsk_path_point_get_tangent (path, &point, GSK_PATH_END, &tangent);
|
||||||
|
|
||||||
|
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,
|
||||||
|
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_CLOSE:
|
||||||
|
gsk_path_builder_close (transform->builder);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
g_assert_not_reached();
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GskPath *
|
||||||
|
gtk_path_transform (GskPathMeasure *measure,
|
||||||
|
GskPath *path,
|
||||||
|
const graphene_point_t *offset)
|
||||||
|
{
|
||||||
|
GtkPathTransform transform = { measure, gsk_path_builder_new (), *offset };
|
||||||
|
graphene_rect_t bounds;
|
||||||
|
|
||||||
|
gsk_path_get_bounds (path, &bounds);
|
||||||
|
if (bounds.origin.x + bounds.size.width > 0)
|
||||||
|
transform.scale = gsk_path_measure_get_length (measure) / (bounds.origin.x + bounds.size.width);
|
||||||
|
else
|
||||||
|
transform.scale = 1.0f;
|
||||||
|
|
||||||
|
gsk_path_foreach (path, -1, gtk_path_transform_op, &transform);
|
||||||
|
|
||||||
|
return gsk_path_builder_free_to_path (transform.builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_path_widget_clear_text_path (GtkPathWidget *self)
|
||||||
|
{
|
||||||
|
g_clear_pointer (&self->text_path, gsk_path_unref);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_path_widget_clear_paths (GtkPathWidget *self)
|
||||||
|
{
|
||||||
|
gtk_path_widget_clear_text_path (self);
|
||||||
|
|
||||||
|
g_clear_pointer (&self->line_path, gsk_path_unref);
|
||||||
|
g_clear_pointer (&self->line_measure, gsk_path_measure_unref);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_path_widget_create_text_path (GtkPathWidget *self)
|
||||||
|
{
|
||||||
|
GskPath *path;
|
||||||
|
graphene_point_t offset;
|
||||||
|
|
||||||
|
gtk_path_widget_clear_text_path (self);
|
||||||
|
|
||||||
|
if (self->line_measure == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
path = create_path_from_text (GTK_WIDGET (self), self->text, &offset);
|
||||||
|
self->text_path = gtk_path_transform (self->line_measure, path, &offset);
|
||||||
|
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_path_widget_create_paths (GtkPathWidget *self)
|
||||||
|
{
|
||||||
|
double width = gtk_widget_get_width (GTK_WIDGET (self));
|
||||||
|
double height = gtk_widget_get_height (GTK_WIDGET (self));
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
|
||||||
|
gtk_path_widget_clear_paths (self);
|
||||||
|
|
||||||
|
if (width <= 0 || height <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_move_to (builder,
|
||||||
|
self->points[0].x * width, self->points[0].y * height);
|
||||||
|
gsk_path_builder_cubic_to (builder,
|
||||||
|
self->points[1].x * width, self->points[1].y * height,
|
||||||
|
self->points[2].x * width, self->points[2].y * height,
|
||||||
|
self->points[3].x * width, self->points[3].y * height);
|
||||||
|
self->line_path = gsk_path_builder_free_to_path (builder);
|
||||||
|
|
||||||
|
self->line_measure = gsk_path_measure_new (self->line_path);
|
||||||
|
|
||||||
|
gtk_path_widget_create_text_path (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_path_widget_allocate (GtkWidget *widget,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int baseline)
|
||||||
|
{
|
||||||
|
GtkPathWidget *self = GTK_PATH_WIDGET (widget);
|
||||||
|
|
||||||
|
GTK_WIDGET_CLASS (gtk_path_widget_parent_class)->size_allocate (widget, width, height, baseline);
|
||||||
|
|
||||||
|
gtk_path_widget_create_paths (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_path_widget_snapshot (GtkWidget *widget,
|
||||||
|
GtkSnapshot *snapshot)
|
||||||
|
{
|
||||||
|
GtkPathWidget *self = GTK_PATH_WIDGET (widget);
|
||||||
|
double width = gtk_widget_get_width (widget);
|
||||||
|
double height = gtk_widget_get_height (widget);
|
||||||
|
GskPath *path;
|
||||||
|
GskStroke *stroke;
|
||||||
|
gsize i;
|
||||||
|
|
||||||
|
/* frosted glass the background */
|
||||||
|
gtk_snapshot_push_blur (snapshot, 100);
|
||||||
|
gdk_paintable_snapshot (self->background, snapshot, width, height);
|
||||||
|
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 1, 1, 1, 0.6 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
|
||||||
|
gtk_snapshot_pop (snapshot);
|
||||||
|
|
||||||
|
/* draw the text */
|
||||||
|
if (self->text_path)
|
||||||
|
{
|
||||||
|
gtk_snapshot_push_fill (snapshot, self->text_path, GSK_FILL_RULE_WINDING);
|
||||||
|
gdk_paintable_snapshot (self->background, snapshot, width, height);
|
||||||
|
|
||||||
|
/* ... with an emboss effect */
|
||||||
|
stroke = gsk_stroke_new (2.0);
|
||||||
|
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT(1, 1));
|
||||||
|
gtk_snapshot_push_stroke (snapshot, self->text_path, stroke);
|
||||||
|
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 0.2 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
|
||||||
|
gsk_stroke_free (stroke);
|
||||||
|
gtk_snapshot_pop (snapshot);
|
||||||
|
|
||||||
|
gtk_snapshot_pop (snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self->editable && self->line_path)
|
||||||
|
{
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
|
||||||
|
/* draw the control line */
|
||||||
|
stroke = gsk_stroke_new (1.0);
|
||||||
|
gtk_snapshot_push_stroke (snapshot, self->line_path, stroke);
|
||||||
|
gsk_stroke_free (stroke);
|
||||||
|
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
|
||||||
|
gtk_snapshot_pop (snapshot);
|
||||||
|
|
||||||
|
/* draw the points */
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
for (i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (self->points[i].x * width, self->points[i].y * height), POINT_SIZE);
|
||||||
|
}
|
||||||
|
path = gsk_path_builder_free_to_path (builder);
|
||||||
|
|
||||||
|
gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
|
||||||
|
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 1, 1, 1, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
|
||||||
|
gtk_snapshot_pop (snapshot);
|
||||||
|
|
||||||
|
stroke = gsk_stroke_new (1.0);
|
||||||
|
gtk_snapshot_push_stroke (snapshot, path, stroke);
|
||||||
|
gsk_stroke_free (stroke);
|
||||||
|
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
|
||||||
|
gtk_snapshot_pop (snapshot);
|
||||||
|
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self->line_closest >= 0)
|
||||||
|
{
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
GskPathPoint point;
|
||||||
|
graphene_point_t closest;
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
if (gsk_path_measure_get_point (self->line_measure, self->line_closest, &point))
|
||||||
|
{
|
||||||
|
gsk_path_point_get_position (self->line_path, &point, &closest);
|
||||||
|
gsk_path_builder_add_circle (builder, &closest, POINT_SIZE);
|
||||||
|
path = gsk_path_builder_free_to_path (builder);
|
||||||
|
|
||||||
|
gtk_snapshot_push_fill (snapshot, path, GSK_FILL_RULE_WINDING);
|
||||||
|
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 1, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
|
||||||
|
gtk_snapshot_pop (snapshot);
|
||||||
|
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_path_widget_set_text (GtkPathWidget *self,
|
||||||
|
const char *text)
|
||||||
|
{
|
||||||
|
if (g_strcmp0 (self->text, text) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_free (self->text);
|
||||||
|
self->text = g_strdup (text);
|
||||||
|
|
||||||
|
gtk_path_widget_create_paths (self);
|
||||||
|
|
||||||
|
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||||
|
|
||||||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TEXT]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_path_widget_set_editable (GtkPathWidget *self,
|
||||||
|
gboolean editable)
|
||||||
|
{
|
||||||
|
if (self->editable == editable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
self->editable = editable;
|
||||||
|
|
||||||
|
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||||
|
|
||||||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EDITABLE]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_path_widget_set_property (GObject *object,
|
||||||
|
guint prop_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
|
||||||
|
{
|
||||||
|
GtkPathWidget *self = GTK_PATH_WIDGET (object);
|
||||||
|
|
||||||
|
switch (prop_id)
|
||||||
|
{
|
||||||
|
case PROP_TEXT:
|
||||||
|
gtk_path_widget_set_text (self, g_value_get_string (value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PROP_EDITABLE:
|
||||||
|
gtk_path_widget_set_editable (self, g_value_get_boolean (value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_path_widget_get_property (GObject *object,
|
||||||
|
guint prop_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
GtkPathWidget *self = GTK_PATH_WIDGET (object);
|
||||||
|
|
||||||
|
switch (prop_id)
|
||||||
|
{
|
||||||
|
case PROP_TEXT:
|
||||||
|
g_value_set_string (value, self->text);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PROP_EDITABLE:
|
||||||
|
g_value_set_boolean (value, self->editable);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_path_widget_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
GtkPathWidget *self = GTK_PATH_WIDGET (object);
|
||||||
|
|
||||||
|
gtk_path_widget_clear_paths (self);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (gtk_path_widget_parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_path_widget_class_init (GtkPathWidgetClass *klass)
|
||||||
|
{
|
||||||
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->dispose = gtk_path_widget_dispose;
|
||||||
|
object_class->set_property = gtk_path_widget_set_property;
|
||||||
|
object_class->get_property = gtk_path_widget_get_property;
|
||||||
|
|
||||||
|
widget_class->size_allocate = gtk_path_widget_allocate;
|
||||||
|
widget_class->snapshot = gtk_path_widget_snapshot;
|
||||||
|
|
||||||
|
properties[PROP_TEXT] =
|
||||||
|
g_param_spec_string ("text",
|
||||||
|
"text",
|
||||||
|
"Text transformed along a path",
|
||||||
|
NULL,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
properties[PROP_EDITABLE] =
|
||||||
|
g_param_spec_boolean ("editable",
|
||||||
|
"editable",
|
||||||
|
"If the path can be edited by the user",
|
||||||
|
FALSE,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
drag_begin (GtkGestureDrag *gesture,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
GtkPathWidget *self)
|
||||||
|
{
|
||||||
|
graphene_point_t mouse = GRAPHENE_POINT_INIT (x, y);
|
||||||
|
double width = gtk_widget_get_width (GTK_WIDGET (self));
|
||||||
|
double height = gtk_widget_get_height (GTK_WIDGET (self));
|
||||||
|
gsize i;
|
||||||
|
|
||||||
|
for (i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
if (graphene_point_distance (&GRAPHENE_POINT_INIT (self->points[i].x * width, self->points[i].y * height), &mouse, NULL, NULL) <= POINT_SIZE)
|
||||||
|
{
|
||||||
|
self->active_point = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == 4)
|
||||||
|
{
|
||||||
|
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
drag_update (GtkGestureDrag *drag,
|
||||||
|
double offset_x,
|
||||||
|
double offset_y,
|
||||||
|
GtkPathWidget *self)
|
||||||
|
{
|
||||||
|
double width = gtk_widget_get_width (GTK_WIDGET (self));
|
||||||
|
double height = gtk_widget_get_height (GTK_WIDGET (self));
|
||||||
|
double start_x, start_y;
|
||||||
|
|
||||||
|
gtk_gesture_drag_get_start_point (drag, &start_x, &start_y);
|
||||||
|
|
||||||
|
self->points[self->active_point] = GRAPHENE_POINT_INIT ((start_x + offset_x) / width,
|
||||||
|
(start_y + offset_y) / height);
|
||||||
|
self->points[self->active_point].x = CLAMP (self->points[self->active_point].x, 0, 1);
|
||||||
|
self->points[self->active_point].y = CLAMP (self->points[self->active_point].y, 0, 1);
|
||||||
|
|
||||||
|
gtk_path_widget_create_paths (self);
|
||||||
|
|
||||||
|
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pointer_motion (GtkEventControllerMotion *controller,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
GtkPathWidget *self)
|
||||||
|
{
|
||||||
|
GskPathPoint point;
|
||||||
|
graphene_point_t pos;
|
||||||
|
|
||||||
|
if (gsk_path_get_closest_point (gsk_path_measure_get_path (self->line_measure),
|
||||||
|
&GRAPHENE_POINT_INIT (x, y),
|
||||||
|
INFINITY,
|
||||||
|
&point))
|
||||||
|
{
|
||||||
|
gsk_path_point_get_position (self->line_path, &point, &pos);
|
||||||
|
self->line_closest = graphene_point_distance (&pos, &GRAPHENE_POINT_INIT (x, y), NULL, NULL);
|
||||||
|
|
||||||
|
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pointer_leave (GtkEventControllerMotion *controller,
|
||||||
|
GtkPathWidget *self)
|
||||||
|
{
|
||||||
|
self->line_closest = -1;
|
||||||
|
|
||||||
|
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gtk_path_widget_init (GtkPathWidget *self)
|
||||||
|
{
|
||||||
|
GtkEventController *controller;
|
||||||
|
|
||||||
|
controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
|
||||||
|
g_signal_connect (controller, "drag-begin", G_CALLBACK (drag_begin), self);
|
||||||
|
g_signal_connect (controller, "drag-update", G_CALLBACK (drag_update), self);
|
||||||
|
g_signal_connect (controller, "drag-end", G_CALLBACK (drag_update), self);
|
||||||
|
gtk_widget_add_controller (GTK_WIDGET (self), controller);
|
||||||
|
|
||||||
|
controller = GTK_EVENT_CONTROLLER (gtk_event_controller_motion_new ());
|
||||||
|
g_signal_connect (controller, "enter", G_CALLBACK (pointer_motion), self);
|
||||||
|
g_signal_connect (controller, "motion", G_CALLBACK (pointer_motion), self);
|
||||||
|
g_signal_connect (controller, "leave", G_CALLBACK (pointer_leave), self);
|
||||||
|
gtk_widget_add_controller (GTK_WIDGET (self), controller);
|
||||||
|
|
||||||
|
self->line_closest = -1;
|
||||||
|
|
||||||
|
self->points[0] = GRAPHENE_POINT_INIT (0.1, 0.9);
|
||||||
|
self->points[1] = GRAPHENE_POINT_INIT (0.3, 0.1);
|
||||||
|
self->points[2] = GRAPHENE_POINT_INIT (0.7, 0.1);
|
||||||
|
self->points[3] = GRAPHENE_POINT_INIT (0.9, 0.9);
|
||||||
|
|
||||||
|
self->background = GDK_PAINTABLE (gdk_texture_new_from_resource ("/sliding_puzzle/portland-rose.jpg"));
|
||||||
|
|
||||||
|
gtk_path_widget_set_text (self, "It's almost working");
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *
|
||||||
|
gtk_path_widget_new (void)
|
||||||
|
{
|
||||||
|
GtkPathWidget *self;
|
||||||
|
|
||||||
|
self = g_object_new (GTK_TYPE_PATH_WIDGET, NULL);
|
||||||
|
|
||||||
|
return GTK_WIDGET (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkWidget *
|
||||||
|
do_path_text (GtkWidget *do_widget)
|
||||||
|
{
|
||||||
|
static GtkWidget *window = NULL;
|
||||||
|
|
||||||
|
if (!window)
|
||||||
|
{
|
||||||
|
GtkBuilder *builder;
|
||||||
|
|
||||||
|
g_type_ensure (GTK_TYPE_PATH_WIDGET);
|
||||||
|
|
||||||
|
builder = gtk_builder_new_from_resource ("/path_text/path_text.ui");
|
||||||
|
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
|
||||||
|
gtk_window_set_display (GTK_WINDOW (window),
|
||||||
|
gtk_widget_get_display (do_widget));
|
||||||
|
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
|
||||||
|
g_object_unref (builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gtk_widget_get_visible (window))
|
||||||
|
gtk_window_present (GTK_WINDOW (window));
|
||||||
|
else
|
||||||
|
gtk_window_destroy (GTK_WINDOW (window));
|
||||||
|
|
||||||
|
return window;
|
||||||
|
}
|
38
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>
|
@@ -22,6 +22,7 @@
|
|||||||
#include <gsk/gskenums.h>
|
#include <gsk/gskenums.h>
|
||||||
#include <gsk/gskpath.h>
|
#include <gsk/gskpath.h>
|
||||||
#include <gsk/gskpathbuilder.h>
|
#include <gsk/gskpathbuilder.h>
|
||||||
|
#include <gsk/gskpathmeasure.h>
|
||||||
#include <gsk/gskpathpoint.h>
|
#include <gsk/gskpathpoint.h>
|
||||||
#include <gsk/gskrenderer.h>
|
#include <gsk/gskrenderer.h>
|
||||||
#include <gsk/gskrendernode.h>
|
#include <gsk/gskrendernode.h>
|
||||||
|
944
gsk/gskcontour.c
944
gsk/gskcontour.c
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,7 @@ GskContour * gsk_standard_contour_new (GskPathFlags
|
|||||||
const gskpathop *ops,
|
const gskpathop *ops,
|
||||||
gsize n_ops,
|
gsize n_ops,
|
||||||
gssize offset);
|
gssize offset);
|
||||||
|
GskContour * gsk_rect_contour_new (const graphene_rect_t *rect);
|
||||||
|
|
||||||
void gsk_contour_copy (GskContour * dest,
|
void gsk_contour_copy (GskContour * dest,
|
||||||
const GskContour *src);
|
const GskContour *src);
|
||||||
@@ -57,14 +58,17 @@ void gsk_contour_get_start_end (const GskContou
|
|||||||
graphene_point_t *end);
|
graphene_point_t *end);
|
||||||
int gsk_contour_get_winding (const GskContour *self,
|
int gsk_contour_get_winding (const GskContour *self,
|
||||||
const graphene_point_t *point);
|
const graphene_point_t *point);
|
||||||
|
|
||||||
gboolean gsk_contour_get_closest_point (const GskContour *self,
|
gboolean gsk_contour_get_closest_point (const GskContour *self,
|
||||||
const graphene_point_t *point,
|
const graphene_point_t *point,
|
||||||
float threshold,
|
float threshold,
|
||||||
GskRealPathPoint *result,
|
GskRealPathPoint *result,
|
||||||
float *out_dist);
|
float *out_dist);
|
||||||
|
|
||||||
void gsk_contour_get_position (const GskContour *self,
|
void gsk_contour_get_position (const GskContour *self,
|
||||||
GskRealPathPoint *point,
|
GskRealPathPoint *point,
|
||||||
graphene_point_t *pos);
|
graphene_point_t *pos);
|
||||||
|
|
||||||
void gsk_contour_get_tangent (const GskContour *self,
|
void gsk_contour_get_tangent (const GskContour *self,
|
||||||
GskRealPathPoint *point,
|
GskRealPathPoint *point,
|
||||||
GskPathDirection direction,
|
GskPathDirection direction,
|
||||||
@@ -72,5 +76,23 @@ void gsk_contour_get_tangent (const GskContou
|
|||||||
float gsk_contour_get_curvature (const GskContour *self,
|
float gsk_contour_get_curvature (const GskContour *self,
|
||||||
GskRealPathPoint *point,
|
GskRealPathPoint *point,
|
||||||
graphene_point_t *center);
|
graphene_point_t *center);
|
||||||
|
gpointer gsk_contour_init_measure (const GskContour *self,
|
||||||
|
float tolerance,
|
||||||
|
float *out_length);
|
||||||
|
void gsk_contour_free_measure (const GskContour *self,
|
||||||
|
gpointer data);
|
||||||
|
void gsk_contour_add_segment (const GskContour *self,
|
||||||
|
GskPathBuilder *builder,
|
||||||
|
gpointer measure_data,
|
||||||
|
gboolean emit_move_to,
|
||||||
|
float start,
|
||||||
|
float end);
|
||||||
|
void gsk_contour_get_point (const GskContour *self,
|
||||||
|
gpointer measure_data,
|
||||||
|
float offset,
|
||||||
|
GskRealPathPoint *result);
|
||||||
|
float gsk_contour_get_distance (const GskContour *self,
|
||||||
|
GskRealPathPoint *point,
|
||||||
|
gpointer measure_data);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
@@ -442,15 +442,14 @@ void
|
|||||||
gsk_path_builder_add_rect (GskPathBuilder *self,
|
gsk_path_builder_add_rect (GskPathBuilder *self,
|
||||||
const graphene_rect_t *rect)
|
const graphene_rect_t *rect)
|
||||||
{
|
{
|
||||||
|
GskContour *contour;
|
||||||
|
|
||||||
g_return_if_fail (self != NULL);
|
g_return_if_fail (self != NULL);
|
||||||
|
|
||||||
gsk_path_builder_move_to (self, rect->origin.x, rect->origin.y);
|
contour = gsk_rect_contour_new (rect);
|
||||||
|
gsk_path_builder_add_contour (self, contour);
|
||||||
|
|
||||||
gsk_path_builder_rel_line_to (self, rect->size.width, 0);
|
gsk_contour_get_start_end (contour, NULL, &self->current_point);
|
||||||
gsk_path_builder_rel_line_to (self, 0, rect->size.height);
|
|
||||||
gsk_path_builder_rel_line_to (self, - rect->size.width, 0);
|
|
||||||
|
|
||||||
gsk_path_builder_close (self);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
|
@@ -69,6 +69,12 @@ void gsk_path_builder_add_circle (GskPathBuilder
|
|||||||
const graphene_point_t *center,
|
const graphene_point_t *center,
|
||||||
float radius);
|
float radius);
|
||||||
|
|
||||||
|
GDK_AVAILABLE_IN_4_14
|
||||||
|
void gsk_path_builder_add_segment (GskPathBuilder *self,
|
||||||
|
GskPathMeasure *measure,
|
||||||
|
float start,
|
||||||
|
float end);
|
||||||
|
|
||||||
GDK_AVAILABLE_IN_4_14
|
GDK_AVAILABLE_IN_4_14
|
||||||
void gsk_path_builder_move_to (GskPathBuilder *self,
|
void gsk_path_builder_move_to (GskPathBuilder *self,
|
||||||
float x,
|
float x,
|
||||||
|
426
gsk/gskpathmeasure.c
Normal file
426
gsk/gskpathmeasure.c
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2020 Benjamin Otte
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Authors: Benjamin Otte <otte@gnome.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "gskpathmeasure.h"
|
||||||
|
|
||||||
|
#include "gskpathbuilder.h"
|
||||||
|
#include "gskpathpointprivate.h"
|
||||||
|
#include "gskpathprivate.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GskPathMeasure:
|
||||||
|
*
|
||||||
|
* `GskPathMeasure` is an object that allows measurements
|
||||||
|
* on `GskPath`s such as determining the length of the path.
|
||||||
|
*
|
||||||
|
* Many measuring operations require approximating the path
|
||||||
|
* with simpler shapes. Therefore, a `GskPathMeasure` has
|
||||||
|
* a tolerance that determines what precision is required
|
||||||
|
* for such approximations.
|
||||||
|
*
|
||||||
|
* A `GskPathMeasure` struct is a reference counted struct
|
||||||
|
* and should be treated as opaque.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct _GskContourMeasure GskContourMeasure;
|
||||||
|
|
||||||
|
struct _GskContourMeasure
|
||||||
|
{
|
||||||
|
float length;
|
||||||
|
gpointer contour_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _GskPathMeasure
|
||||||
|
{
|
||||||
|
/*< private >*/
|
||||||
|
guint ref_count;
|
||||||
|
|
||||||
|
GskPath *path;
|
||||||
|
float tolerance;
|
||||||
|
|
||||||
|
float length;
|
||||||
|
gsize n_contours;
|
||||||
|
GskContourMeasure measures[];
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_BOXED_TYPE (GskPathMeasure, gsk_path_measure,
|
||||||
|
gsk_path_measure_ref,
|
||||||
|
gsk_path_measure_unref)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gsk_path_measure_new:
|
||||||
|
* @path: the path to measure
|
||||||
|
*
|
||||||
|
* Creates a measure object for the given @path.
|
||||||
|
*
|
||||||
|
* Returns: a new `GskPathMeasure` representing @path
|
||||||
|
*
|
||||||
|
* Since: 4.14
|
||||||
|
*/
|
||||||
|
GskPathMeasure *
|
||||||
|
gsk_path_measure_new (GskPath *path)
|
||||||
|
{
|
||||||
|
return gsk_path_measure_new_with_tolerance (path, GSK_PATH_TOLERANCE_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gsk_path_measure_new_with_tolerance:
|
||||||
|
* @path: the path to measure
|
||||||
|
* @tolerance: the tolerance for measuring operations
|
||||||
|
*
|
||||||
|
* Creates a measure object for the given @path and @tolerance.
|
||||||
|
*
|
||||||
|
* Returns: a new `GskPathMeasure` representing @path
|
||||||
|
*
|
||||||
|
* Since: 4.14
|
||||||
|
*/
|
||||||
|
GskPathMeasure *
|
||||||
|
gsk_path_measure_new_with_tolerance (GskPath *path,
|
||||||
|
float tolerance)
|
||||||
|
{
|
||||||
|
GskPathMeasure *self;
|
||||||
|
gsize i, n_contours;
|
||||||
|
|
||||||
|
g_return_val_if_fail (path != NULL, NULL);
|
||||||
|
g_return_val_if_fail (tolerance > 0, NULL);
|
||||||
|
|
||||||
|
n_contours = gsk_path_get_n_contours (path);
|
||||||
|
|
||||||
|
self = g_malloc0 (sizeof (GskPathMeasure) + n_contours * sizeof (GskContourMeasure));
|
||||||
|
|
||||||
|
self->ref_count = 1;
|
||||||
|
self->path = gsk_path_ref (path);
|
||||||
|
self->tolerance = tolerance;
|
||||||
|
self->n_contours = n_contours;
|
||||||
|
|
||||||
|
for (i = 0; i < n_contours; i++)
|
||||||
|
{
|
||||||
|
self->measures[i].contour_data = gsk_contour_init_measure (gsk_path_get_contour (path, i),
|
||||||
|
self->tolerance,
|
||||||
|
&self->measures[i].length);
|
||||||
|
self->length += self->measures[i].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gsk_path_measure_ref:
|
||||||
|
* @self: a `GskPathMeasure`
|
||||||
|
*
|
||||||
|
* Increases the reference count of a `GskPathMeasure` by one.
|
||||||
|
*
|
||||||
|
* Returns: the passed in `GskPathMeasure`.
|
||||||
|
*
|
||||||
|
* Since: 4.14
|
||||||
|
*/
|
||||||
|
GskPathMeasure *
|
||||||
|
gsk_path_measure_ref (GskPathMeasure *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (self != NULL, NULL);
|
||||||
|
|
||||||
|
self->ref_count++;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gsk_path_measure_unref:
|
||||||
|
* @self: a `GskPathMeasure`
|
||||||
|
*
|
||||||
|
* Decreases the reference count of a `GskPathMeasure` by one.
|
||||||
|
*
|
||||||
|
* If the resulting reference count is zero, frees the object.
|
||||||
|
*
|
||||||
|
* Since: 4.14
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
gsk_path_measure_unref (GskPathMeasure *self)
|
||||||
|
{
|
||||||
|
gsize i;
|
||||||
|
|
||||||
|
g_return_if_fail (self != NULL);
|
||||||
|
g_return_if_fail (self->ref_count > 0);
|
||||||
|
|
||||||
|
self->ref_count--;
|
||||||
|
if (self->ref_count > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (i = 0; i < self->n_contours; i++)
|
||||||
|
{
|
||||||
|
gsk_contour_free_measure (gsk_path_get_contour (self->path, i),
|
||||||
|
self->measures[i].contour_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
gsk_path_unref (self->path);
|
||||||
|
g_free (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gsk_path_measure_get_path:
|
||||||
|
* @self: a `GskPathMeasure`
|
||||||
|
*
|
||||||
|
* Returns the path that the measure was created for.
|
||||||
|
*
|
||||||
|
* Returns: (transfer none): the path of @self
|
||||||
|
*
|
||||||
|
* Since: 4.14
|
||||||
|
*/
|
||||||
|
GskPath *
|
||||||
|
gsk_path_measure_get_path (GskPathMeasure *self)
|
||||||
|
{
|
||||||
|
return self->path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gsk_path_measure_get_tolerance:
|
||||||
|
* @self: a `GskPathMeasure`
|
||||||
|
*
|
||||||
|
* Returns the tolerance that the measure was created with.
|
||||||
|
*
|
||||||
|
* Returns: the tolerance of @self
|
||||||
|
*
|
||||||
|
* Since: 4.14
|
||||||
|
*/
|
||||||
|
float
|
||||||
|
gsk_path_measure_get_tolerance (GskPathMeasure *self)
|
||||||
|
{
|
||||||
|
return self->tolerance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gsk_path_measure_get_length:
|
||||||
|
* @self: a `GskPathMeasure`
|
||||||
|
*
|
||||||
|
* Gets the length of the path being measured.
|
||||||
|
*
|
||||||
|
* The length is cached, so this function does not do any work.
|
||||||
|
*
|
||||||
|
* Returns: The length of the path measured by @self
|
||||||
|
*
|
||||||
|
* Since: 4.14
|
||||||
|
*/
|
||||||
|
float
|
||||||
|
gsk_path_measure_get_length (GskPathMeasure *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (self != NULL, 0);
|
||||||
|
|
||||||
|
return self->length;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float
|
||||||
|
gsk_path_measure_clamp_distance (GskPathMeasure *self,
|
||||||
|
float distance)
|
||||||
|
{
|
||||||
|
if (isnan (distance))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return CLAMP (distance, 0, self->length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gsk_path_builder_add_segment_chunk (GskPathBuilder *self,
|
||||||
|
GskPathMeasure *measure,
|
||||||
|
gboolean emit_move_to,
|
||||||
|
float start,
|
||||||
|
float end)
|
||||||
|
{
|
||||||
|
g_assert (start < end);
|
||||||
|
|
||||||
|
for (gsize i = 0; i < measure->n_contours; i++)
|
||||||
|
{
|
||||||
|
if (measure->measures[i].length < start)
|
||||||
|
{
|
||||||
|
start -= measure->measures[i].length;
|
||||||
|
end -= measure->measures[i].length;
|
||||||
|
}
|
||||||
|
else if (start > 0 || end < measure->measures[i].length)
|
||||||
|
{
|
||||||
|
float len = MIN (end, measure->measures[i].length);
|
||||||
|
gsk_contour_add_segment (gsk_path_get_contour (measure->path, i),
|
||||||
|
self,
|
||||||
|
measure->measures[i].contour_data,
|
||||||
|
emit_move_to,
|
||||||
|
start,
|
||||||
|
len);
|
||||||
|
end -= len;
|
||||||
|
start = 0;
|
||||||
|
if (end <= 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
end -= measure->measures[i].length;
|
||||||
|
gsk_path_builder_add_contour (self, gsk_contour_dup (gsk_path_get_contour (measure->path, i)));
|
||||||
|
}
|
||||||
|
emit_move_to = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gsk_path_builder_add_segment:
|
||||||
|
* @self: a `GskPathBuilder`
|
||||||
|
* @measure: the `GskPathMeasure` to take the segment to
|
||||||
|
* @start: start distance into the path
|
||||||
|
* @end: end distance into the path
|
||||||
|
*
|
||||||
|
* Adds to @self the segment of @measure from @start to @end.
|
||||||
|
*
|
||||||
|
* The distances are given relative to the length of @measure's path,
|
||||||
|
* from 0 for the beginning of the path to its length for the end
|
||||||
|
* of the path. The values will be clamped to that range. The length
|
||||||
|
* can be obtained with [method@Gsk.PathMeasure.get_length].
|
||||||
|
*
|
||||||
|
* If @start >= @end after clamping, the path will first add the segment
|
||||||
|
* from @start to the end of the path, and then add the segment from
|
||||||
|
* the beginning to @end. If the path is closed, these segments will
|
||||||
|
* be connected.
|
||||||
|
*
|
||||||
|
* Since: 4.14
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
gsk_path_builder_add_segment (GskPathBuilder *self,
|
||||||
|
GskPathMeasure *measure,
|
||||||
|
float start,
|
||||||
|
float end)
|
||||||
|
{
|
||||||
|
g_return_if_fail (self != NULL);
|
||||||
|
g_return_if_fail (measure != NULL);
|
||||||
|
|
||||||
|
start = gsk_path_measure_clamp_distance (measure, start);
|
||||||
|
end = gsk_path_measure_clamp_distance (measure, end);
|
||||||
|
|
||||||
|
if (start < end)
|
||||||
|
{
|
||||||
|
gsk_path_builder_add_segment_chunk (self, measure, TRUE, start, end);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* If the path is closed, we can connect the 2 subpaths. */
|
||||||
|
gboolean closed = gsk_path_is_closed (measure->path);
|
||||||
|
gboolean need_move_to = !closed;
|
||||||
|
|
||||||
|
if (start < measure->length)
|
||||||
|
gsk_path_builder_add_segment_chunk (self, measure,
|
||||||
|
TRUE,
|
||||||
|
start, measure->length);
|
||||||
|
else
|
||||||
|
need_move_to = TRUE;
|
||||||
|
|
||||||
|
if (end > 0)
|
||||||
|
gsk_path_builder_add_segment_chunk (self, measure,
|
||||||
|
need_move_to,
|
||||||
|
0, end);
|
||||||
|
if (start == end && closed)
|
||||||
|
gsk_path_builder_close (self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gsk_path_measure_get_point:
|
||||||
|
* @self: a `GskPathMeasure`
|
||||||
|
* @distance: the distance
|
||||||
|
* @result: (out caller-allocates): return location for the result
|
||||||
|
*
|
||||||
|
* Sets @result to the point at the given distance into the path.
|
||||||
|
*
|
||||||
|
* An empty path has no points, so `FALSE` is returned in that case.
|
||||||
|
*
|
||||||
|
* Returns: `TRUE` if @result was set
|
||||||
|
*
|
||||||
|
* Since: 4.14
|
||||||
|
*/
|
||||||
|
gboolean
|
||||||
|
gsk_path_measure_get_point (GskPathMeasure *self,
|
||||||
|
float distance,
|
||||||
|
GskPathPoint *result)
|
||||||
|
{
|
||||||
|
GskRealPathPoint *res = (GskRealPathPoint *) result;
|
||||||
|
gsize i;
|
||||||
|
float offset;
|
||||||
|
const GskContour *contour;
|
||||||
|
|
||||||
|
g_return_val_if_fail (self != NULL, FALSE);
|
||||||
|
g_return_val_if_fail (result != NULL, FALSE);
|
||||||
|
|
||||||
|
if (self->n_contours == 0)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
offset = gsk_path_measure_clamp_distance (self, distance);
|
||||||
|
|
||||||
|
for (i = 0; i < self->n_contours - 1; i++)
|
||||||
|
{
|
||||||
|
if (offset < self->measures[i].length)
|
||||||
|
break;
|
||||||
|
|
||||||
|
offset -= self->measures[i].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_assert (0 <= i && i < self->n_contours);
|
||||||
|
|
||||||
|
offset = CLAMP (offset, 0, self->measures[i].length);
|
||||||
|
|
||||||
|
contour = gsk_path_get_contour (self->path, i);
|
||||||
|
|
||||||
|
gsk_contour_get_point (contour, self->measures[i].contour_data, offset, res);
|
||||||
|
|
||||||
|
res->path = self->path;
|
||||||
|
res->contour = i;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gsk_path_measure_get_distance:
|
||||||
|
* @self: a `GskPathMeasure`
|
||||||
|
* @point: a `GskPathPoint on the path of @self
|
||||||
|
*
|
||||||
|
* Returns the distance from the beginning of the path
|
||||||
|
* to @point.
|
||||||
|
*
|
||||||
|
* Returns: the distance of @point
|
||||||
|
*
|
||||||
|
* Since: 4.14
|
||||||
|
*/
|
||||||
|
float
|
||||||
|
gsk_path_measure_get_distance (GskPathMeasure *self,
|
||||||
|
const GskPathPoint *point)
|
||||||
|
{
|
||||||
|
GskRealPathPoint *p = (GskRealPathPoint *)point;
|
||||||
|
const GskContour *contour = gsk_path_get_contour (self->path, p->contour);
|
||||||
|
float contour_offset = 0;
|
||||||
|
|
||||||
|
g_return_val_if_fail (self != NULL, 0);
|
||||||
|
g_return_val_if_fail (self->path == p->path, 0);
|
||||||
|
g_return_val_if_fail (contour != NULL, 0);
|
||||||
|
|
||||||
|
for (gsize i = 0; i < self->n_contours; i++)
|
||||||
|
{
|
||||||
|
if (contour == gsk_path_get_contour (self->path, i))
|
||||||
|
return contour_offset + gsk_contour_get_distance (contour,
|
||||||
|
p,
|
||||||
|
self->measures[i].contour_data);
|
||||||
|
|
||||||
|
contour_offset += self->measures[i].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_return_val_if_reached (0);
|
||||||
|
}
|
66
gsk/gskpathmeasure.h
Normal file
66
gsk/gskpathmeasure.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2020 Benjamin Otte
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Authors: Benjamin Otte <otte@gnome.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION)
|
||||||
|
#error "Only <gsk/gsk.h> can be included directly."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#include <gsk/gskpath.h>
|
||||||
|
#include <gsk/gskpathpoint.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define GSK_TYPE_PATH_MEASURE (gsk_path_measure_get_type ())
|
||||||
|
|
||||||
|
GDK_AVAILABLE_IN_4_14
|
||||||
|
GType gsk_path_measure_get_type (void) G_GNUC_CONST;
|
||||||
|
GDK_AVAILABLE_IN_4_14
|
||||||
|
GskPathMeasure * gsk_path_measure_new (GskPath *path);
|
||||||
|
GDK_AVAILABLE_IN_4_14
|
||||||
|
GskPathMeasure * gsk_path_measure_new_with_tolerance (GskPath *path,
|
||||||
|
float tolerance);
|
||||||
|
|
||||||
|
GDK_AVAILABLE_IN_4_14
|
||||||
|
GskPathMeasure * gsk_path_measure_ref (GskPathMeasure *self);
|
||||||
|
GDK_AVAILABLE_IN_4_14
|
||||||
|
void gsk_path_measure_unref (GskPathMeasure *self);
|
||||||
|
|
||||||
|
GDK_AVAILABLE_IN_4_14
|
||||||
|
GskPath * gsk_path_measure_get_path (GskPathMeasure *self) G_GNUC_PURE;
|
||||||
|
GDK_AVAILABLE_IN_4_14
|
||||||
|
float gsk_path_measure_get_tolerance (GskPathMeasure *self) G_GNUC_PURE;
|
||||||
|
|
||||||
|
GDK_AVAILABLE_IN_4_14
|
||||||
|
float gsk_path_measure_get_length (GskPathMeasure *self);
|
||||||
|
|
||||||
|
GDK_AVAILABLE_IN_4_14
|
||||||
|
gboolean gsk_path_measure_get_point (GskPathMeasure *self,
|
||||||
|
float distance,
|
||||||
|
GskPathPoint *result);
|
||||||
|
|
||||||
|
GDK_AVAILABLE_IN_4_14
|
||||||
|
float gsk_path_measure_get_distance (GskPathMeasure *self,
|
||||||
|
const GskPathPoint *point);
|
||||||
|
|
||||||
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathMeasure, gsk_path_measure_unref)
|
||||||
|
|
||||||
|
G_END_DECLS
|
@@ -22,6 +22,7 @@
|
|||||||
#include "gskpathpointprivate.h"
|
#include "gskpathpointprivate.h"
|
||||||
|
|
||||||
#include "gskcontourprivate.h"
|
#include "gskcontourprivate.h"
|
||||||
|
#include "gskpathmeasure.h"
|
||||||
|
|
||||||
#include "gdk/gdkprivate.h"
|
#include "gdk/gdkprivate.h"
|
||||||
|
|
||||||
@@ -33,7 +34,8 @@
|
|||||||
* It can be queried for properties of the path at that point, such as its
|
* It can be queried for properties of the path at that point, such as its
|
||||||
* tangent or its curvature.
|
* tangent or its curvature.
|
||||||
*
|
*
|
||||||
* To obtain a `GskPathPoint`, use [method@Gsk.Path.get_closest_point].
|
* To obtain a `GskPathPoint`, use [method@Gsk.Path.get_closest_point]
|
||||||
|
* or [method@Gsk.PathMeasure.get_point].
|
||||||
*
|
*
|
||||||
* Note that `GskPathPoint` structs are meant to be stack-allocated, and
|
* Note that `GskPathPoint` structs are meant to be stack-allocated, and
|
||||||
* don't a reference to the path object they are obtained from. It is the
|
* don't a reference to the path object they are obtained from. It is the
|
||||||
|
@@ -15,6 +15,9 @@ struct _GskRealPathPoint
|
|||||||
unsigned int idx;
|
unsigned int idx;
|
||||||
float t;
|
float t;
|
||||||
} std;
|
} std;
|
||||||
|
struct {
|
||||||
|
float distance;
|
||||||
|
} rect;
|
||||||
} data;
|
} data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
typedef struct _GskPath GskPath;
|
typedef struct _GskPath GskPath;
|
||||||
typedef struct _GskPathBuilder GskPathBuilder;
|
typedef struct _GskPathBuilder GskPathBuilder;
|
||||||
|
typedef struct _GskPathMeasure GskPathMeasure;
|
||||||
typedef struct _GskPathPoint GskPathPoint;
|
typedef struct _GskPathPoint GskPathPoint;
|
||||||
typedef struct _GskRenderer GskRenderer;
|
typedef struct _GskRenderer GskRenderer;
|
||||||
typedef struct _GskStroke GskStroke;
|
typedef struct _GskStroke GskStroke;
|
||||||
|
@@ -28,6 +28,7 @@ gsk_public_sources = files([
|
|||||||
'gskglshader.c',
|
'gskglshader.c',
|
||||||
'gskpath.c',
|
'gskpath.c',
|
||||||
'gskpathbuilder.c',
|
'gskpathbuilder.c',
|
||||||
|
'gskpathmeasure.c',
|
||||||
'gskpathpoint.c',
|
'gskpathpoint.c',
|
||||||
'gskrenderer.c',
|
'gskrenderer.c',
|
||||||
'gskrendernode.c',
|
'gskrendernode.c',
|
||||||
@@ -75,6 +76,7 @@ gsk_public_headers = files([
|
|||||||
'gskglshader.h',
|
'gskglshader.h',
|
||||||
'gskpath.h',
|
'gskpath.h',
|
||||||
'gskpathbuilder.h',
|
'gskpathbuilder.h',
|
||||||
|
'gskpathmeasure.h',
|
||||||
'gskpathpoint.h',
|
'gskpathpoint.h',
|
||||||
'gskrenderer.h',
|
'gskrenderer.h',
|
||||||
'gskrendernode.h',
|
'gskrendernode.h',
|
||||||
|
265
testsuite/gsk/measure-special-cases.c
Normal file
265
testsuite/gsk/measure-special-cases.c
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2020 Benjamin Otte
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Authors: Benjamin Otte <otte@gnome.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_bad_split (void)
|
||||||
|
{
|
||||||
|
GskPath *path, *path1;
|
||||||
|
GskPathMeasure *measure, *measure1;
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
float split, length, epsilon;
|
||||||
|
|
||||||
|
/* An example that was isolated from the /path/segment test path.c
|
||||||
|
* It shows how uneven parametrization of cubics can lead to bad
|
||||||
|
* lengths reported by the measure.
|
||||||
|
*/
|
||||||
|
|
||||||
|
path = gsk_path_parse ("M 0 0 C 2 0 4 0 4 0");
|
||||||
|
|
||||||
|
measure = gsk_path_measure_new (path);
|
||||||
|
split = 2.962588;
|
||||||
|
length = gsk_path_measure_get_length (measure);
|
||||||
|
epsilon = MAX (length / 256, 1.f / 1024);
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_segment (builder, measure, 0, split);
|
||||||
|
path1 = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure1 = gsk_path_measure_new (path1);
|
||||||
|
|
||||||
|
g_assert_cmpfloat_with_epsilon (split, gsk_path_measure_get_length (measure1), epsilon);
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measure1);
|
||||||
|
gsk_path_unref (path1);
|
||||||
|
gsk_path_measure_unref (measure);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_bad_in_fill (void)
|
||||||
|
{
|
||||||
|
GskPath *path;
|
||||||
|
gboolean inside;
|
||||||
|
|
||||||
|
/* A fat Cantarell W */
|
||||||
|
path = gsk_path_parse ("M -2 694 M 206.1748046875 704 L 390.9371337890625 704 L 551.1888427734375 99.5035400390625 L 473.0489501953125 99.5035400390625 L 649.1048583984375 704 L 828.965087890625 704 L 1028.3077392578125 10 L 857.8111572265625 10 L 710.0489501953125 621.251708984375 L 775.9720458984375 598.426513671875 L 614.5245361328125 14.0489501953125 L 430.2237548828125 14.0489501953125 L 278.6783447265625 602.230712890625 L 330.0909423828125 602.230712890625 L 195.88818359375 10 L 5.7342529296875 10 L 206.1748046875 704 Z");
|
||||||
|
|
||||||
|
/* The midpoint of the right foot of a fat Cantarell X */
|
||||||
|
inside = gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (552.360107, 704.000000), GSK_FILL_RULE_WINDING);
|
||||||
|
|
||||||
|
g_assert_false (inside);
|
||||||
|
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Test that path_in_fill implicitly closes contours. I think this is wrong,
|
||||||
|
* but it is what "everybody" does.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
test_unclosed_in_fill (void)
|
||||||
|
{
|
||||||
|
GskPath *path;
|
||||||
|
|
||||||
|
path = gsk_path_parse ("M 0 0 L 0 100 L 100 100 L 100 0 Z");
|
||||||
|
g_assert_true (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (50, 50), GSK_FILL_RULE_WINDING));
|
||||||
|
gsk_path_unref (path);
|
||||||
|
|
||||||
|
path = gsk_path_parse ("M 0 0 L 0 100 L 100 100 L 100 0");
|
||||||
|
g_assert_true (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (50, 50), GSK_FILL_RULE_WINDING));
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_rect (void)
|
||||||
|
{
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
GskPath *path;
|
||||||
|
GskPathMeasure *measure;
|
||||||
|
GskPathPoint point;
|
||||||
|
graphene_point_t p;
|
||||||
|
graphene_vec2_t tangent, expected_tangent;
|
||||||
|
float length;
|
||||||
|
gboolean ret;
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 100, 50));
|
||||||
|
path = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure = gsk_path_measure_new (path);
|
||||||
|
length = gsk_path_measure_get_length (measure);
|
||||||
|
|
||||||
|
g_assert_true (length == 300);
|
||||||
|
|
||||||
|
#define TEST_POS_AT(distance, X, Y) \
|
||||||
|
ret = gsk_path_measure_get_point (measure, distance, &point); \
|
||||||
|
g_assert_true (ret); \
|
||||||
|
gsk_path_point_get_position (path, &point, &p); \
|
||||||
|
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
|
||||||
|
ret = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (X, Y), INFINITY, &point); \
|
||||||
|
g_assert_true (ret); \
|
||||||
|
if (distance < length) \
|
||||||
|
g_assert_true (fabs (gsk_path_measure_get_distance (measure, &point) - distance) < 0.01); \
|
||||||
|
else \
|
||||||
|
g_assert_true (fabs (gsk_path_measure_get_distance (measure, &point)) < 0.01); \
|
||||||
|
gsk_path_point_get_position (path, &point, &p); \
|
||||||
|
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
|
||||||
|
|
||||||
|
#define TEST_TANGENT_AT(distance, x1, y1, x2, y2) \
|
||||||
|
ret = gsk_path_measure_get_point (measure, distance, &point); \
|
||||||
|
g_assert_true (ret); \
|
||||||
|
gsk_path_point_get_tangent (path, &point, GSK_PATH_START, &tangent); \
|
||||||
|
g_assert_true (graphene_vec2_near (&tangent, graphene_vec2_init (&expected_tangent, x1, y1), 0.01)); \
|
||||||
|
gsk_path_point_get_tangent (path, &point, GSK_PATH_END, &tangent); \
|
||||||
|
g_assert_true (graphene_vec2_near (&tangent, graphene_vec2_init (&expected_tangent, x2, y2), 0.01)); \
|
||||||
|
|
||||||
|
#define TEST_POS_AT2(distance, X, Y, expected_distance) \
|
||||||
|
ret = gsk_path_measure_get_point (measure, distance, &point); \
|
||||||
|
g_assert_true (ret); \
|
||||||
|
gsk_path_point_get_position (path, &point, &p); \
|
||||||
|
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
|
||||||
|
ret = gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (X, Y), INFINITY, &point); \
|
||||||
|
g_assert_true (ret); \
|
||||||
|
g_assert_true (fabs (gsk_path_measure_get_distance (measure, &point) - expected_distance) < 0.01); \
|
||||||
|
gsk_path_point_get_position (path, &point, &p); \
|
||||||
|
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (X, Y), 0.01)); \
|
||||||
|
|
||||||
|
TEST_POS_AT (0, 0, 0)
|
||||||
|
TEST_POS_AT (25, 25, 0)
|
||||||
|
TEST_POS_AT (100, 100, 0)
|
||||||
|
TEST_POS_AT (110, 100, 10)
|
||||||
|
TEST_POS_AT (150, 100, 50)
|
||||||
|
TEST_POS_AT (175, 75, 50)
|
||||||
|
TEST_POS_AT (250, 0, 50)
|
||||||
|
TEST_POS_AT (260, 0, 40)
|
||||||
|
TEST_POS_AT2 (300, 0, 0, 0)
|
||||||
|
|
||||||
|
TEST_TANGENT_AT (0, 0, -1, 1, 0)
|
||||||
|
TEST_TANGENT_AT (50, 1, 0, 1, 0)
|
||||||
|
TEST_TANGENT_AT (100, 1, 0, 0, 1)
|
||||||
|
TEST_TANGENT_AT (125, 0, 1, 0, 1)
|
||||||
|
TEST_TANGENT_AT (150, 0, 1, -1, 0)
|
||||||
|
TEST_TANGENT_AT (200, -1, 0, -1, 0)
|
||||||
|
TEST_TANGENT_AT (250, -1, 0, 0, -1)
|
||||||
|
TEST_TANGENT_AT (275, 0, -1, 0, -1)
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measure);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 50, -100, -50));
|
||||||
|
path = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure = gsk_path_measure_new (path);
|
||||||
|
length = gsk_path_measure_get_length (measure);
|
||||||
|
|
||||||
|
g_assert_true (length == 300);
|
||||||
|
|
||||||
|
TEST_POS_AT (0, 100, 50)
|
||||||
|
TEST_POS_AT (25, 75, 50)
|
||||||
|
TEST_POS_AT (100, 0, 50)
|
||||||
|
TEST_POS_AT (110, 0, 40)
|
||||||
|
TEST_POS_AT (150, 0, 0)
|
||||||
|
TEST_POS_AT (175, 25, 0)
|
||||||
|
TEST_POS_AT (250, 100, 0)
|
||||||
|
TEST_POS_AT (260, 100, 10)
|
||||||
|
TEST_POS_AT (300, 100, 50)
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 0, -100, 50));
|
||||||
|
path = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure = gsk_path_measure_new (path);
|
||||||
|
length = gsk_path_measure_get_length (measure);
|
||||||
|
|
||||||
|
g_assert_true (length == 300);
|
||||||
|
|
||||||
|
TEST_POS_AT (0, 100, 0)
|
||||||
|
TEST_POS_AT (25, 75, 0)
|
||||||
|
TEST_POS_AT (100, 0, 0)
|
||||||
|
TEST_POS_AT (110, 0, 10)
|
||||||
|
TEST_POS_AT (150, 0, 50)
|
||||||
|
TEST_POS_AT (175, 25, 50)
|
||||||
|
TEST_POS_AT (250, 100, 50)
|
||||||
|
TEST_POS_AT (260, 100, 40)
|
||||||
|
TEST_POS_AT (300, 100, 0)
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 100, 0));
|
||||||
|
path = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure = gsk_path_measure_new (path);
|
||||||
|
length = gsk_path_measure_get_length (measure);
|
||||||
|
|
||||||
|
g_assert_true (length == 200);
|
||||||
|
|
||||||
|
TEST_POS_AT2 (0, 0, 0, 0)
|
||||||
|
TEST_POS_AT2 (25, 25, 0, 25)
|
||||||
|
TEST_POS_AT2 (100, 100, 0, 100)
|
||||||
|
TEST_POS_AT2 (110, 90, 0, 90)
|
||||||
|
TEST_POS_AT2 (200, 0, 0, 0)
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (100, 0, -100, 0));
|
||||||
|
path = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure = gsk_path_measure_new (path);
|
||||||
|
length = gsk_path_measure_get_length (measure);
|
||||||
|
|
||||||
|
g_assert_true (length == 200);
|
||||||
|
|
||||||
|
/* These cases are ambiguous */
|
||||||
|
TEST_POS_AT2 (0, 100, 0, 0)
|
||||||
|
TEST_POS_AT2 (25, 75, 0, 25)
|
||||||
|
TEST_POS_AT2 (100, 0, 0, 100)
|
||||||
|
TEST_POS_AT2 (110, 10, 0, 110)
|
||||||
|
TEST_POS_AT2 (200, 100, 0, 0)
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 100, 0, -100));
|
||||||
|
path = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure = gsk_path_measure_new (path);
|
||||||
|
length = gsk_path_measure_get_length (measure);
|
||||||
|
|
||||||
|
g_assert_true (length == 200);
|
||||||
|
|
||||||
|
/* These cases are ambiguous */
|
||||||
|
TEST_POS_AT2 (0, 0, 100, 0)
|
||||||
|
TEST_POS_AT2 (25, 0, 75, 25)
|
||||||
|
TEST_POS_AT2 (100, 0, 0, 100)
|
||||||
|
TEST_POS_AT2 (110, 0, 10, 110)
|
||||||
|
TEST_POS_AT2 (200, 0, 100, 0)
|
||||||
|
|
||||||
|
#undef TEST_POS_AT
|
||||||
|
#undef TEST_POS_AT2
|
||||||
|
#undef TEST_TANGENT_AT
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measure);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc,
|
||||||
|
char *argv[])
|
||||||
|
{
|
||||||
|
gtk_test_init (&argc, &argv, NULL);
|
||||||
|
|
||||||
|
g_test_add_func ("/measure/bad-split", test_bad_split);
|
||||||
|
g_test_add_func ("/measure/bad-in-fill", test_bad_in_fill);
|
||||||
|
g_test_add_func ("/measure/unclosed-in-fill", test_unclosed_in_fill);
|
||||||
|
g_test_add_func ("/measure/rect", test_rect);
|
||||||
|
|
||||||
|
return g_test_run ();
|
||||||
|
}
|
870
testsuite/gsk/measure.c
Normal file
870
testsuite/gsk/measure.c
Normal file
@@ -0,0 +1,870 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2020 Benjamin Otte
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Authors: Benjamin Otte <otte@gnome.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
static GskPath *
|
||||||
|
create_random_degenerate_path (guint max_contours)
|
||||||
|
{
|
||||||
|
#define N_DEGENERATE_PATHS 14
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
guint i;
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
|
||||||
|
switch (g_test_rand_int_range (0, N_DEGENERATE_PATHS))
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
/* empty path */
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
/* a single point */
|
||||||
|
gsk_path_builder_move_to (builder,
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
/* N points */
|
||||||
|
for (i = 0; i < MIN (10, max_contours); i++)
|
||||||
|
{
|
||||||
|
gsk_path_builder_move_to (builder,
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
/* 1 closed point */
|
||||||
|
gsk_path_builder_move_to (builder,
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
gsk_path_builder_close (builder);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
/* the same point closed N times */
|
||||||
|
gsk_path_builder_move_to (builder,
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
for (i = 0; i < MIN (10, max_contours); i++)
|
||||||
|
{
|
||||||
|
gsk_path_builder_close (builder);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
/* a zero-width and zero-height rect */
|
||||||
|
gsk_path_builder_add_rect (builder,
|
||||||
|
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
0, 0));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
/* a zero-width rect */
|
||||||
|
gsk_path_builder_add_rect (builder,
|
||||||
|
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
0,
|
||||||
|
g_test_rand_double_range (-1000, 1000)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
/* a zero-height rect */
|
||||||
|
gsk_path_builder_add_rect (builder,
|
||||||
|
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
0));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
/* a negative-size rect */
|
||||||
|
gsk_path_builder_add_rect (builder,
|
||||||
|
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 0),
|
||||||
|
g_test_rand_double_range (-1000, 0)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 9:
|
||||||
|
/* an absolutely random rect */
|
||||||
|
gsk_path_builder_add_rect (builder,
|
||||||
|
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 10:
|
||||||
|
/* an absolutely random rect */
|
||||||
|
gsk_path_builder_add_rect (builder,
|
||||||
|
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 11:
|
||||||
|
/* an absolutely random circle */
|
||||||
|
gsk_path_builder_add_circle (builder,
|
||||||
|
&GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000)),
|
||||||
|
g_test_rand_double_range (1, 1000));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 12:
|
||||||
|
/* a zero-length line */
|
||||||
|
{
|
||||||
|
graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
gsk_path_builder_move_to (builder, point.x, point.y);
|
||||||
|
gsk_path_builder_line_to (builder, point.x, point.y);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 13:
|
||||||
|
/* a curve with start == end */
|
||||||
|
{
|
||||||
|
graphene_point_t point = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
gsk_path_builder_move_to (builder, point.x, point.y);
|
||||||
|
gsk_path_builder_cubic_to (builder,
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
point.x, point.y);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case N_DEGENERATE_PATHS:
|
||||||
|
default:
|
||||||
|
g_assert_not_reached ();
|
||||||
|
}
|
||||||
|
|
||||||
|
return gsk_path_builder_free_to_path (builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GskPath *
|
||||||
|
create_random_path (guint max_contours);
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_shape_contour (GskPathBuilder *builder)
|
||||||
|
{
|
||||||
|
#define N_SHAPE_CONTOURS 3
|
||||||
|
switch (g_test_rand_int_range (0, N_SHAPE_CONTOURS))
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
gsk_path_builder_add_rect (builder,
|
||||||
|
&GRAPHENE_RECT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (1, 1000),
|
||||||
|
g_test_rand_double_range (1, 1000)));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
gsk_path_builder_add_circle (builder,
|
||||||
|
&GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000)),
|
||||||
|
g_test_rand_double_range (1, 1000));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
{
|
||||||
|
GskPath *path = create_random_path (1);
|
||||||
|
gsk_path_builder_add_path (builder, path);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case N_SHAPE_CONTOURS:
|
||||||
|
default:
|
||||||
|
g_assert_not_reached ();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
add_standard_contour (GskPathBuilder *builder)
|
||||||
|
{
|
||||||
|
guint i, n;
|
||||||
|
|
||||||
|
if (g_test_rand_bit ())
|
||||||
|
{
|
||||||
|
if (g_test_rand_bit ())
|
||||||
|
gsk_path_builder_move_to (builder,
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
else
|
||||||
|
gsk_path_builder_rel_move_to (builder,
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* that 20 is random, but should be enough to get some
|
||||||
|
* crazy self-intersecting shapes */
|
||||||
|
n = g_test_rand_int_range (1, 20);
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
switch (g_test_rand_int_range (0, 6))
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
gsk_path_builder_line_to (builder,
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
gsk_path_builder_rel_line_to (builder,
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
gsk_path_builder_quad_to (builder,
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
gsk_path_builder_rel_quad_to (builder,
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
gsk_path_builder_cubic_to (builder,
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
gsk_path_builder_rel_cubic_to (builder,
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
g_assert_not_reached();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_test_rand_bit ())
|
||||||
|
gsk_path_builder_close (builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GskPath *
|
||||||
|
create_random_path (guint max_contours)
|
||||||
|
{
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
guint i, n;
|
||||||
|
|
||||||
|
/* 5% chance for a weird shape */
|
||||||
|
if (!g_test_rand_int_range (0, 20))
|
||||||
|
return create_random_degenerate_path (max_contours);
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
n = g_test_rand_int_range (1, 10);
|
||||||
|
n = MIN (n, max_contours);
|
||||||
|
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
/* 2/3 of shapes are standard contours */
|
||||||
|
if (g_test_rand_int_range (0, 3))
|
||||||
|
add_standard_contour (builder);
|
||||||
|
else
|
||||||
|
add_shape_contour (builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gsk_path_builder_free_to_path (builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
GskPathOperation op;
|
||||||
|
graphene_point_t pts[4];
|
||||||
|
float weight;
|
||||||
|
} PathOperation;
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_segment_start (void)
|
||||||
|
{
|
||||||
|
GskPath *path, *path1;
|
||||||
|
GskPathMeasure *measure, *measure1;
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
float epsilon, length;
|
||||||
|
guint i;
|
||||||
|
|
||||||
|
path = create_random_path (G_MAXUINT);
|
||||||
|
measure = gsk_path_measure_new (path);
|
||||||
|
length = gsk_path_measure_get_length (measure);
|
||||||
|
epsilon = MAX (length / 1024, G_MINFLOAT);
|
||||||
|
|
||||||
|
for (i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
float seg_length = length * i / 100.0f;
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_segment (builder, measure, 0, seg_length);
|
||||||
|
path1 = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure1 = gsk_path_measure_new (path1);
|
||||||
|
|
||||||
|
if (seg_length == 0)
|
||||||
|
g_assert_cmpfloat_with_epsilon (gsk_path_measure_get_length (measure), gsk_path_measure_get_length (measure1), epsilon);
|
||||||
|
else
|
||||||
|
g_assert_cmpfloat_with_epsilon (seg_length, gsk_path_measure_get_length (measure1), epsilon);
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measure1);
|
||||||
|
gsk_path_unref (path1);
|
||||||
|
}
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measure);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_segment_end (void)
|
||||||
|
{
|
||||||
|
GskPath *path, *path1;
|
||||||
|
GskPathMeasure *measure, *measure1;
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
float epsilon, length;
|
||||||
|
guint i;
|
||||||
|
|
||||||
|
path = create_random_path (G_MAXUINT);
|
||||||
|
measure = gsk_path_measure_new (path);
|
||||||
|
length = gsk_path_measure_get_length (measure);
|
||||||
|
epsilon = MAX (length / 1024, G_MINFLOAT);
|
||||||
|
|
||||||
|
for (i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
float seg_length = length * i / 100.0f;
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_segment (builder, measure, length - seg_length, length);
|
||||||
|
path1 = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure1 = gsk_path_measure_new (path1);
|
||||||
|
|
||||||
|
if (seg_length == 0)
|
||||||
|
g_assert_cmpfloat_with_epsilon (gsk_path_measure_get_length (measure), gsk_path_measure_get_length (measure1), epsilon);
|
||||||
|
else
|
||||||
|
g_assert_cmpfloat_with_epsilon (seg_length, gsk_path_measure_get_length (measure1), epsilon);
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measure1);
|
||||||
|
gsk_path_unref (path1);
|
||||||
|
}
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measure);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_segment_chunk (void)
|
||||||
|
{
|
||||||
|
GskPath *path, *path1, *path2;
|
||||||
|
GskPathMeasure *measure, *measure1, *measure2;
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
float epsilon, length;
|
||||||
|
guint i;
|
||||||
|
|
||||||
|
path = create_random_path (G_MAXUINT);
|
||||||
|
measure = gsk_path_measure_new (path);
|
||||||
|
length = gsk_path_measure_get_length (measure);
|
||||||
|
epsilon = MAX (length / 1024, G_MINFLOAT);
|
||||||
|
|
||||||
|
for (i = 0; i <= 100; i++)
|
||||||
|
{
|
||||||
|
float seg_start = length * i / 200.0f;
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_segment (builder, measure, seg_start, seg_start + length / 2);
|
||||||
|
path1 = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure1 = gsk_path_measure_new (path1);
|
||||||
|
|
||||||
|
g_assert_cmpfloat_with_epsilon (length / 2, gsk_path_measure_get_length (measure1), epsilon);
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_segment (builder, measure, seg_start + length / 2, seg_start);
|
||||||
|
path2 = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure2 = gsk_path_measure_new (path2);
|
||||||
|
|
||||||
|
g_assert_cmpfloat_with_epsilon (length / 2, gsk_path_measure_get_length (measure2), epsilon);
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measure2);
|
||||||
|
gsk_path_unref (path2);
|
||||||
|
gsk_path_measure_unref (measure1);
|
||||||
|
gsk_path_unref (path1);
|
||||||
|
}
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measure);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_segment (void)
|
||||||
|
{
|
||||||
|
GskPath *path, *path1, *path2, *path3;
|
||||||
|
GskPathMeasure *measure, *measure1, *measure2, *measure3;
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
guint i;
|
||||||
|
float split1, split2, epsilon, length;
|
||||||
|
|
||||||
|
for (i = 0; i < 1000; i++)
|
||||||
|
{
|
||||||
|
path = create_random_path (G_MAXUINT);
|
||||||
|
measure = gsk_path_measure_new (path);
|
||||||
|
length = gsk_path_measure_get_length (measure);
|
||||||
|
/* chosen high enough to stop the testsuite from failing */
|
||||||
|
epsilon = MAX (length / 64, 1.f / 1024);
|
||||||
|
|
||||||
|
split1 = g_test_rand_double_range (0, length);
|
||||||
|
split2 = g_test_rand_double_range (split1, length);
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_segment (builder, measure, 0, split1);
|
||||||
|
path1 = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure1 = gsk_path_measure_new (path1);
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_segment (builder, measure, split1, split2);
|
||||||
|
path2 = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure2 = gsk_path_measure_new (path2);
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_segment (builder, measure, split2, length);
|
||||||
|
path3 = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure3 = gsk_path_measure_new (path3);
|
||||||
|
|
||||||
|
g_assert_cmpfloat_with_epsilon (split1, gsk_path_measure_get_length (measure1), epsilon);
|
||||||
|
g_assert_cmpfloat_with_epsilon (split2 - split1, gsk_path_measure_get_length (measure2), epsilon);
|
||||||
|
g_assert_cmpfloat_with_epsilon (length - split2, gsk_path_measure_get_length (measure3), epsilon);
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measure2);
|
||||||
|
gsk_path_measure_unref (measure1);
|
||||||
|
gsk_path_measure_unref (measure);
|
||||||
|
gsk_path_unref (path2);
|
||||||
|
gsk_path_unref (path1);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_get_point (void)
|
||||||
|
{
|
||||||
|
static const guint max_contours = 5;
|
||||||
|
static const float tolerance = 1.0;
|
||||||
|
GskPath *path;
|
||||||
|
GskPathMeasure *measure;
|
||||||
|
GskPathPoint point;
|
||||||
|
guint n_discontinuities;
|
||||||
|
float length, offset, last_offset;
|
||||||
|
graphene_point_t p, last_point;
|
||||||
|
guint i, j;
|
||||||
|
gboolean ret;
|
||||||
|
|
||||||
|
for (i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
path = create_random_path (max_contours);
|
||||||
|
measure = gsk_path_measure_new_with_tolerance (path, tolerance);
|
||||||
|
length = gsk_path_measure_get_length (measure);
|
||||||
|
n_discontinuities = 0;
|
||||||
|
|
||||||
|
ret = gsk_path_measure_get_point (measure, 0, &point);
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
g_assert_true (gsk_path_is_empty (path));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
gsk_path_point_get_position (path, &point, &last_point);
|
||||||
|
|
||||||
|
/* FIXME: anything we can test with tangents here? */
|
||||||
|
last_offset = 0;
|
||||||
|
|
||||||
|
for (j = 1; j <= 1024; j++)
|
||||||
|
{
|
||||||
|
offset = length * j / 1024;
|
||||||
|
ret = gsk_path_measure_get_point (measure, offset, &point);
|
||||||
|
g_assert_true (ret);
|
||||||
|
gsk_path_point_get_position (path, &point, &p);
|
||||||
|
|
||||||
|
if (graphene_point_distance (&last_point, &p, NULL, NULL) > 2 * (offset - last_offset))
|
||||||
|
{
|
||||||
|
n_discontinuities++;
|
||||||
|
g_assert_cmpint (n_discontinuities, <, max_contours);
|
||||||
|
}
|
||||||
|
|
||||||
|
last_offset = offset;
|
||||||
|
last_point = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measure);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_closest_point (void)
|
||||||
|
{
|
||||||
|
static const float tolerance = 0.5;
|
||||||
|
GskPath *path, *path1, *path2;
|
||||||
|
GskPathMeasure *measure, *measure1, *measure2;
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
GskPathPoint point;
|
||||||
|
guint i, j;
|
||||||
|
gboolean ret;
|
||||||
|
|
||||||
|
if (!g_test_slow ())
|
||||||
|
{
|
||||||
|
g_test_skip ("Skipping slow test");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
path1 = create_random_path (G_MAXUINT);
|
||||||
|
measure1 = gsk_path_measure_new_with_tolerance (path1, tolerance);
|
||||||
|
path2 = create_random_path (G_MAXUINT);
|
||||||
|
measure2 = gsk_path_measure_new_with_tolerance (path2, tolerance);
|
||||||
|
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
gsk_path_builder_add_path (builder, path1);
|
||||||
|
gsk_path_builder_add_path (builder, path2);
|
||||||
|
path = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure = gsk_path_measure_new_with_tolerance (path, tolerance);
|
||||||
|
|
||||||
|
for (j = 0; j < 100; j++)
|
||||||
|
{
|
||||||
|
graphene_point_t test = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
graphene_point_t p1, p2, p;
|
||||||
|
graphene_vec2_t t1, t2, t;
|
||||||
|
float offset1, offset2, offset;
|
||||||
|
float distance1, distance2, distance;
|
||||||
|
|
||||||
|
offset1 = offset2 = offset = 0;
|
||||||
|
distance1 = distance2 = distance = 0;
|
||||||
|
ret = gsk_path_get_closest_point (path1, &test, INFINITY, &point);
|
||||||
|
g_assert_true (ret);
|
||||||
|
|
||||||
|
gsk_path_point_get_position (path1, &point, &p1);
|
||||||
|
gsk_path_point_get_tangent (path1, &point, GSK_PATH_END, &t1);
|
||||||
|
offset1 = gsk_path_measure_get_distance (measure1, &point);
|
||||||
|
distance1 = graphene_point_distance (&p1, &test, NULL, NULL);
|
||||||
|
|
||||||
|
ret = gsk_path_get_closest_point (path2, &test, INFINITY, &point);
|
||||||
|
g_assert_true (ret);
|
||||||
|
|
||||||
|
gsk_path_point_get_position (path2, &point, &p2);
|
||||||
|
gsk_path_point_get_tangent (path2, &point, GSK_PATH_END, &t2);
|
||||||
|
offset2 = gsk_path_measure_get_distance (measure2, &point);
|
||||||
|
distance2 = graphene_point_distance (&p2, &test, NULL, NULL);
|
||||||
|
|
||||||
|
ret = gsk_path_get_closest_point (path, &test, INFINITY, &point);
|
||||||
|
g_assert_true (ret);
|
||||||
|
|
||||||
|
gsk_path_point_get_position (path, &point, &p);
|
||||||
|
gsk_path_point_get_tangent (path, &point, GSK_PATH_END, &t);
|
||||||
|
offset = gsk_path_measure_get_distance (measure, &point);
|
||||||
|
distance = graphene_point_distance (&p, &test, NULL, NULL);
|
||||||
|
|
||||||
|
if (distance1 == distance)
|
||||||
|
{
|
||||||
|
g_assert_cmpfloat (distance1, ==, distance);
|
||||||
|
g_assert_cmpfloat (p1.x, ==, p.x);
|
||||||
|
g_assert_cmpfloat (p1.y, ==, p.y);
|
||||||
|
g_assert_cmpfloat (offset1, ==, offset);
|
||||||
|
g_assert_true (graphene_vec2_equal (&t1, &t));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_assert_cmpfloat (distance2, ==, distance);
|
||||||
|
g_assert_cmpfloat (p2.x, ==, p.x);
|
||||||
|
g_assert_cmpfloat (p2.y, ==, p.y);
|
||||||
|
g_assert_cmpfloat_with_epsilon (offset2 + gsk_path_measure_get_length (measure1), offset, MAX (G_MINFLOAT, offset / 1024));
|
||||||
|
g_assert_true (graphene_vec2_equal (&t2, &t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measure2);
|
||||||
|
gsk_path_measure_unref (measure1);
|
||||||
|
gsk_path_measure_unref (measure);
|
||||||
|
gsk_path_unref (path2);
|
||||||
|
gsk_path_unref (path1);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_closest_point_for_point (void)
|
||||||
|
{
|
||||||
|
static const float tolerance = 0.5;
|
||||||
|
GskPath *path;
|
||||||
|
GskPathMeasure *measure;
|
||||||
|
GskPathPoint point;
|
||||||
|
float length, offset, distance;
|
||||||
|
graphene_point_t p, closest_point;
|
||||||
|
guint i, j;
|
||||||
|
gboolean ret;
|
||||||
|
|
||||||
|
if (!g_test_slow ())
|
||||||
|
{
|
||||||
|
g_test_skip ("Skipping slow test");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
path = create_random_path (G_MAXUINT);
|
||||||
|
if (gsk_path_is_empty (path))
|
||||||
|
{
|
||||||
|
/* empty paths have no closest point to anything */
|
||||||
|
gsk_path_unref (path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
measure = gsk_path_measure_new_with_tolerance (path, tolerance);
|
||||||
|
length = gsk_path_measure_get_length (measure);
|
||||||
|
|
||||||
|
for (j = 0; j < 100; j++)
|
||||||
|
{
|
||||||
|
offset = g_test_rand_double_range (0, length);
|
||||||
|
ret = gsk_path_measure_get_point (measure, offset, &point);
|
||||||
|
g_assert_true (ret);
|
||||||
|
gsk_path_point_get_position (path, &point, &p);
|
||||||
|
ret = gsk_path_get_closest_point (path, &p, 2 * tolerance, &point);
|
||||||
|
g_assert_true (ret);
|
||||||
|
gsk_path_point_get_position (path, &point, &closest_point);
|
||||||
|
//closest_offset = gsk_path_measure_get_distance (measure, &point);
|
||||||
|
distance = graphene_point_distance (&p, &closest_point, NULL, NULL);
|
||||||
|
|
||||||
|
/* should be given due to the TRUE return above, but who knows... */
|
||||||
|
g_assert_cmpfloat (distance, <=, 2 * tolerance);
|
||||||
|
g_assert_cmpfloat (graphene_point_distance (&p, &closest_point, NULL, NULL), <=, 2 * tolerance);
|
||||||
|
/* we can't check offsets here, since we might hit self-intersections
|
||||||
|
g_assert_cmpfloat_with_epsilon (closest_offset, offset, 2 * tolerance);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measure);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define N_PATHS 3
|
||||||
|
static void
|
||||||
|
test_in_fill_union (void)
|
||||||
|
{
|
||||||
|
GskPath *path;
|
||||||
|
GskPathMeasure *measure, *measures[N_PATHS];
|
||||||
|
GskPathBuilder *builder;
|
||||||
|
guint i, j, k;
|
||||||
|
|
||||||
|
for (i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
builder = gsk_path_builder_new ();
|
||||||
|
for (k = 0; k < N_PATHS; k++)
|
||||||
|
{
|
||||||
|
path = create_random_path (G_MAXUINT);
|
||||||
|
measures[k] = gsk_path_measure_new (path);
|
||||||
|
gsk_path_builder_add_path (builder, path);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
}
|
||||||
|
path = gsk_path_builder_free_to_path (builder);
|
||||||
|
measure = gsk_path_measure_new (path);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
|
||||||
|
for (j = 0; j < 100; j++)
|
||||||
|
{
|
||||||
|
graphene_point_t test = GRAPHENE_POINT_INIT (g_test_rand_double_range (-1000, 1000),
|
||||||
|
g_test_rand_double_range (-1000, 1000));
|
||||||
|
GskFillRule fill_rule;
|
||||||
|
|
||||||
|
for (fill_rule = GSK_FILL_RULE_WINDING; fill_rule <= GSK_FILL_RULE_EVEN_ODD; fill_rule++)
|
||||||
|
{
|
||||||
|
guint n_in_fill = 0;
|
||||||
|
gboolean in_fill;
|
||||||
|
|
||||||
|
for (k = 0; k < N_PATHS; k++)
|
||||||
|
{
|
||||||
|
if (gsk_path_in_fill (gsk_path_measure_get_path (measures[k]), &test, GSK_FILL_RULE_EVEN_ODD))
|
||||||
|
n_in_fill++;
|
||||||
|
}
|
||||||
|
|
||||||
|
in_fill = gsk_path_in_fill (gsk_path_measure_get_path (measure), &test, GSK_FILL_RULE_EVEN_ODD);
|
||||||
|
|
||||||
|
switch (fill_rule)
|
||||||
|
{
|
||||||
|
case GSK_FILL_RULE_WINDING:
|
||||||
|
if (n_in_fill == 0)
|
||||||
|
g_assert_false (in_fill);
|
||||||
|
else if (n_in_fill == 1)
|
||||||
|
g_assert_true (in_fill);
|
||||||
|
/* else we can't say anything because the winding rule doesn't give enough info */
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GSK_FILL_RULE_EVEN_ODD:
|
||||||
|
g_assert_cmpint (in_fill, ==, n_in_fill & 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
g_assert_not_reached ();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measure);
|
||||||
|
for (k = 0; k < N_PATHS; k++)
|
||||||
|
{
|
||||||
|
gsk_path_measure_unref (measures[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef N_PATHS
|
||||||
|
|
||||||
|
/* This is somewhat sucky because using foreach breaks up the contours
|
||||||
|
* (like rects and circles) and replaces everything with the standard
|
||||||
|
* contour.
|
||||||
|
* But at least it extensively tests the standard contour.
|
||||||
|
*/
|
||||||
|
static gboolean
|
||||||
|
rotate_path_cb (GskPathOperation op,
|
||||||
|
const graphene_point_t *pts,
|
||||||
|
gsize n_pts,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
GskPathBuilder **builders = user_data;
|
||||||
|
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case GSK_PATH_MOVE:
|
||||||
|
gsk_path_builder_move_to (builders[0], pts[0].x, pts[0].y);
|
||||||
|
gsk_path_builder_move_to (builders[1], pts[0].y, -pts[0].x);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GSK_PATH_CLOSE:
|
||||||
|
gsk_path_builder_close (builders[0]);
|
||||||
|
gsk_path_builder_close (builders[1]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GSK_PATH_LINE:
|
||||||
|
gsk_path_builder_line_to (builders[0], pts[1].x, pts[1].y);
|
||||||
|
gsk_path_builder_line_to (builders[1], pts[1].y, -pts[1].x);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GSK_PATH_QUAD:
|
||||||
|
gsk_path_builder_quad_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y);
|
||||||
|
gsk_path_builder_quad_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GSK_PATH_CUBIC:
|
||||||
|
gsk_path_builder_cubic_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
|
||||||
|
gsk_path_builder_cubic_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x, pts[3].y, -pts[3].x);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
g_assert_not_reached ();
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_in_fill_rotated (void)
|
||||||
|
{
|
||||||
|
GskPath *path;
|
||||||
|
GskPathBuilder *builders[2];
|
||||||
|
GskPathMeasure *measures[2];
|
||||||
|
guint i, j;
|
||||||
|
|
||||||
|
#define N_FILL_RULES 2
|
||||||
|
/* if this triggers, you added a new enum value to GskFillRule, so the define above needs
|
||||||
|
* an update */
|
||||||
|
g_assert_null (g_enum_get_value (g_type_class_ref (GSK_TYPE_FILL_RULE), N_FILL_RULES));
|
||||||
|
|
||||||
|
for (i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
path = create_random_path (G_MAXUINT);
|
||||||
|
builders[0] = gsk_path_builder_new ();
|
||||||
|
builders[1] = gsk_path_builder_new ();
|
||||||
|
/* Use -1 here because we want all the flags, even future additions */
|
||||||
|
gsk_path_foreach (path, -1, rotate_path_cb, builders);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
|
||||||
|
path = gsk_path_builder_free_to_path (builders[0]);
|
||||||
|
measures[0] = gsk_path_measure_new (path);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
path = gsk_path_builder_free_to_path (builders[1]);
|
||||||
|
measures[1] = gsk_path_measure_new (path);
|
||||||
|
gsk_path_unref (path);
|
||||||
|
|
||||||
|
for (j = 0; j < 100; j++)
|
||||||
|
{
|
||||||
|
GskFillRule fill_rule = g_random_int_range (0, N_FILL_RULES);
|
||||||
|
float x = g_test_rand_double_range (-1000, 1000);
|
||||||
|
float y = g_test_rand_double_range (-1000, 1000);
|
||||||
|
|
||||||
|
g_assert_cmpint (gsk_path_in_fill (gsk_path_measure_get_path (measures[0]), &GRAPHENE_POINT_INIT (x, y), fill_rule),
|
||||||
|
==,
|
||||||
|
gsk_path_in_fill (gsk_path_measure_get_path (measures[1]), &GRAPHENE_POINT_INIT (y, -x), fill_rule));
|
||||||
|
g_assert_cmpint (gsk_path_in_fill (gsk_path_measure_get_path (measures[0]), &GRAPHENE_POINT_INIT (y, x), fill_rule),
|
||||||
|
==,
|
||||||
|
gsk_path_in_fill (gsk_path_measure_get_path (measures[1]), &GRAPHENE_POINT_INIT (x, -y), fill_rule));
|
||||||
|
}
|
||||||
|
|
||||||
|
gsk_path_measure_unref (measures[0]);
|
||||||
|
gsk_path_measure_unref (measures[1]);
|
||||||
|
}
|
||||||
|
#undef N_FILL_RULES
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc,
|
||||||
|
char *argv[])
|
||||||
|
{
|
||||||
|
gtk_test_init (&argc, &argv, NULL);
|
||||||
|
|
||||||
|
g_test_add_func ("/measure/segment_start", test_segment_start);
|
||||||
|
g_test_add_func ("/measure/segment_end", test_segment_end);
|
||||||
|
g_test_add_func ("/measure/segment_chunk", test_segment_chunk);
|
||||||
|
g_test_add_func ("/measure/segment", test_segment);
|
||||||
|
g_test_add_func ("/measure/get_point", test_get_point);
|
||||||
|
g_test_add_func ("/measure/closest_point", test_closest_point);
|
||||||
|
g_test_add_func ("/measure/closest_point_for_point", test_closest_point_for_point);
|
||||||
|
g_test_add_func ("/measure/in-fill-union", test_in_fill_union);
|
||||||
|
g_test_add_func ("/measure/in-fill-rotated", test_in_fill_rotated);
|
||||||
|
|
||||||
|
return g_test_run ();
|
||||||
|
}
|
@@ -371,6 +371,8 @@ tests = [
|
|||||||
['shader'],
|
['shader'],
|
||||||
['path'],
|
['path'],
|
||||||
['path-special-cases'],
|
['path-special-cases'],
|
||||||
|
['measure'],
|
||||||
|
['measure-special-cases'],
|
||||||
]
|
]
|
||||||
|
|
||||||
test_cargs = []
|
test_cargs = []
|
||||||
|
Reference in New Issue
Block a user