Compare commits

..

3 Commits

Author SHA1 Message Date
Matthias Clasen
1b4073e7b5 Neuter some tests that assume special contours 2023-08-10 08:09:18 -04:00
Matthias Clasen
bd986332a8 gsk: Add tests for gsk_path_dash 2023-08-10 08:02:48 -04:00
Matthias Clasen
18bc43b2e1 Add gsk_path_dash
This is a function like gsk_path_foreach, but
it provides a dashed version of the path instead
of the original.
2023-08-10 08:02:48 -04:00
11 changed files with 504 additions and 389 deletions

View File

@@ -337,7 +337,6 @@
<file>password_entry.c</file>
<file>path_fill.c</file>
<file>path_text.c</file>
<file>path_walk.c</file>
<file>peg_solitaire.c</file>
<file>pickers.c</file>
<file>printing.c</file>
@@ -426,10 +425,6 @@
<gresource prefix="/path_text">
<file>path_text.ui</file>
</gresource>
<gresource prefix="/path_walk">
<file>path_walk.ui</file>
<file>path_world.c</file>
</gresource>
<gresource prefix="/org/gtk/Demo4">
<file>icons/16x16/actions/application-exit.png</file>
<file>icons/16x16/actions/document-new.png</file>

View File

@@ -74,7 +74,6 @@ demos = files([
'password_entry.c',
'path_fill.c',
'path_text.c',
'path_walk.c',
'peg_solitaire.c',
'pickers.c',
'printing.c',

View File

@@ -1,346 +0,0 @@
/* Path/Walk
*
* This demo shows how to animate objects along a GskPath
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "path_world.c"
#define GTK_TYPE_PATH_WALK (gtk_path_walk_get_type ())
G_DECLARE_FINAL_TYPE (GtkPathWalk, gtk_path_walk, GTK, PATH_WALK, GtkWidget)
#define POINT_SIZE 8
enum {
PROP_0,
PROP_N_POINTS,
PROP_PATH,
N_PROPS
};
struct _GtkPathWalk
{
GtkWidget parent_instance;
GskPath *path;
GskPathMeasure *measure;
GskPath *arrow_path;
guint n_points;
};
struct _GtkPathWalkClass
{
GtkWidgetClass parent_class;
};
static GParamSpec *properties[N_PROPS] = { NULL, };
G_DEFINE_TYPE (GtkPathWalk, gtk_path_walk, GTK_TYPE_WIDGET)
static void
rgba_init_from_hsla (GdkRGBA *rgba,
float hue,
float saturation,
float lightness,
float alpha)
{
float m1, m2;
if (lightness <= 0.5)
m2 = lightness * (1 + saturation);
else
m2 = lightness + saturation - lightness * saturation;
m1 = 2 * lightness - m2;
rgba->alpha = alpha;
if (saturation == 0)
{
rgba->red = lightness;
rgba->green = lightness;
rgba->blue = lightness;
}
else
{
hue = hue + 120;
while (hue > 360)
hue -= 360;
while (hue < 0)
hue += 360;
if (hue < 60)
rgba->red = m1 + (m2 - m1) * hue / 60;
else if (hue < 180)
rgba->red = m2;
else if (hue < 240)
rgba->red = m1 + (m2 - m1) * (240 - hue) / 60;
else
rgba->red = m1;
hue -= 120;
if (hue < 0)
hue += 360;
if (hue < 60)
rgba->green = m1 + (m2 - m1) * hue / 60;
else if (hue < 180)
rgba->green = m2;
else if (hue < 240)
rgba->green = m1 + (m2 - m1) * (240 - hue) / 60;
else
rgba->green = m1;
hue -= 120;
if (hue < 0)
hue += 360;
if (hue < 60)
rgba->blue = m1 + (m2 - m1) * hue / 60;
else if (hue < 180)
rgba->blue = m2;
else if (hue < 240)
rgba->blue = m1 + (m2 - m1) * (240 - hue) / 60;
else
rgba->blue = m1;
}
}
static void
gtk_path_walk_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
GtkPathWalk *self = GTK_PATH_WALK (widget);
double width = gtk_widget_get_width (widget);
double height = gtk_widget_get_height (widget);
float length, progress;
GskStroke *stroke;
guint i;
if (self->path == NULL)
return;
gtk_snapshot_save (snapshot);
stroke = gsk_stroke_new (2.0);
gtk_snapshot_push_stroke (snapshot, self->path, stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (0, 0, width, height));
gtk_snapshot_pop (snapshot);
gsk_stroke_free (stroke);
length = gsk_path_measure_get_length (self->measure);
progress = 25.f * gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (widget)) / G_USEC_PER_SEC;
stroke = gsk_stroke_new (1.0);
for (i = 0; i < self->n_points; i++)
{
GskPathPoint point;
graphene_point_t position;
graphene_vec2_t tangent;
GdkRGBA color;
float distance;
distance = i * length / self->n_points;
distance = fmod (distance + progress, length);
gsk_path_measure_get_point (self->measure, distance, &point);
gsk_path_point_get_position (self->path, &point, &position);
gsk_path_point_get_tangent (self->path, &point, GSK_PATH_START, &tangent);
rgba_init_from_hsla (&color, 360.f * i / self->n_points, 1, 0.5, 1);
gtk_snapshot_save (snapshot);
gtk_snapshot_translate (snapshot, &position);
gtk_snapshot_rotate (snapshot, 180 / G_PI * atan2 (graphene_vec2_get_y (&tangent), graphene_vec2_get_x (&tangent)));
gtk_snapshot_push_fill (snapshot, self->arrow_path, GSK_FILL_RULE_EVEN_ODD);
gtk_snapshot_append_color (snapshot, &color, &GRAPHENE_RECT_INIT (-1000, -1000, 2000, 2000));
gtk_snapshot_pop (snapshot);
gtk_snapshot_push_stroke (snapshot, self->arrow_path, stroke);
gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0, 0, 0, 1 }, &GRAPHENE_RECT_INIT (-1000, -1000, 2000, 2000));
gtk_snapshot_pop (snapshot);
gtk_snapshot_restore (snapshot);
}
gsk_stroke_free (stroke);
gtk_snapshot_restore (snapshot);
}
static void
gtk_path_walk_set_n_points (GtkPathWalk *self,
gsize n_points)
{
if (self->n_points == n_points)
return;
self->n_points = n_points;
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_POINTS]);
}
static void
gtk_path_walk_set_path (GtkPathWalk *self,
GskPath *path)
{
if (self->path == path)
return;
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->measure, gsk_path_measure_unref);
if (path)
{
self->path = gsk_path_ref (path);
self->measure = gsk_path_measure_new (self->path);
}
gtk_widget_queue_draw (GTK_WIDGET (self));
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PATH]);
}
static void
gtk_path_walk_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkPathWalk *self = GTK_PATH_WALK (object);
switch (prop_id)
{
case PROP_N_POINTS:
gtk_path_walk_set_n_points (self, g_value_get_uint (value));
break;
case PROP_PATH:
gtk_path_walk_set_path (self, g_value_get_boxed (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_walk_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkPathWalk *self = GTK_PATH_WALK (object);
switch (prop_id)
{
case PROP_N_POINTS:
g_value_set_uint (value, self->n_points);
break;
case PROP_PATH:
g_value_set_boxed (value, self->path);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_path_walk_dispose (GObject *object)
{
GtkPathWalk *self = GTK_PATH_WALK (object);
g_clear_pointer (&self->path, gsk_path_unref);
g_clear_pointer (&self->measure, gsk_path_measure_unref);
g_clear_pointer (&self->arrow_path, gsk_path_unref);
G_OBJECT_CLASS (gtk_path_walk_parent_class)->dispose (object);
}
static void
gtk_path_walk_class_init (GtkPathWalkClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_path_walk_dispose;
object_class->set_property = gtk_path_walk_set_property;
object_class->get_property = gtk_path_walk_get_property;
widget_class->snapshot = gtk_path_walk_snapshot;
properties[PROP_N_POINTS] =
g_param_spec_uint ("n-points",
NULL, NULL,
1, G_MAXUINT,
500,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
properties[PROP_PATH] =
g_param_spec_boxed ("path",
NULL, NULL,
GSK_TYPE_PATH,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static gboolean
tick_tick_tick (GtkWidget *self,
GdkFrameClock *frame_clock,
gpointer unused)
{
gtk_widget_queue_draw (GTK_WIDGET (self));
return G_SOURCE_CONTINUE;
}
static void
gtk_path_walk_init (GtkPathWalk *self)
{
self->path = gsk_path_parse (path_world);
self->measure = gsk_path_measure_new (self->path);
self->arrow_path = gsk_path_parse ("M 5 0 L 0 -5. 0 -2, -5 -2, -5 2, 0 2, 0 5 Z");
self->n_points = 500;
gtk_widget_add_tick_callback (GTK_WIDGET (self), tick_tick_tick, NULL, NULL);
}
GtkWidget *
gtk_path_walk_new (void)
{
GtkPathWalk *self;
self = g_object_new (GTK_TYPE_PATH_WALK, NULL);
return GTK_WIDGET (self);
}
GtkWidget *
do_path_walk (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkBuilder *builder;
g_type_ensure (GTK_TYPE_PATH_WALK);
builder = gtk_builder_new_from_resource ("/path_walk/path_walk.ui");
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
gtk_window_set_display (GTK_WINDOW (window),
gtk_widget_get_display (do_widget));
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
g_object_unref (builder);
}
if (!gtk_widget_get_visible (window))
gtk_window_present (GTK_WINDOW (window));
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkWindow" id="window">
<property name="title" translatable="yes">Animation 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="GtkPathWalk" id="view">
<property name="hexpand">true</property>
<property name="vexpand">true</property>
</object>
</child>
</object>
</child>
</object>
</interface>

File diff suppressed because one or more lines are too long

View File

@@ -95,5 +95,10 @@ void gsk_contour_get_point (const GskContou
float gsk_contour_get_distance (const GskContour *self,
GskRealPathPoint *point,
gpointer measure_data);
gboolean gsk_contour_dash (const GskContour *contour,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data);
G_END_DECLS

View File

@@ -131,6 +131,13 @@ gboolean gsk_path_foreach (GskPath
GskPathForeachFunc func,
gpointer user_data);
GDK_AVAILABLE_IN_4_14
gboolean gsk_path_dash (GskPath *self,
GskStroke *stroke,
float tolerance,
GskPathForeachFunc func,
gpointer user_data);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
G_END_DECLS

304
gsk/gskpathdash.c Normal file
View File

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

View File

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

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

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

View File

@@ -373,6 +373,7 @@ tests = [
['path-special-cases'],
['measure'],
['measure-special-cases'],
['dash'],
]
test_cargs = []