Compare commits
4 Commits
wip/matthi
...
path-dash
Author | SHA1 | Date | |
---|---|---|---|
|
027461b209 | ||
|
ff17fd0e82 | ||
|
dea4d52d1a | ||
|
f7e248c7fe |
@@ -94,5 +94,9 @@ void gsk_contour_get_point (const GskContou
|
||||
float gsk_contour_get_distance (const GskContour *self,
|
||||
const GskPathPoint *point,
|
||||
gpointer measure_data);
|
||||
gboolean gsk_contour_dash (const GskContour *contour,
|
||||
GskStroke *stroke,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
110
gsk/gskcurve.c
110
gsk/gskcurve.c
@@ -84,6 +84,9 @@ struct _GskCurveClass
|
||||
const graphene_point_t *point);
|
||||
float (* get_length_to) (const GskCurve *curve,
|
||||
float t);
|
||||
float (* get_at_length) (const GskCurve *curve,
|
||||
float distance,
|
||||
float epsilon);
|
||||
};
|
||||
|
||||
/* {{{ Utilities */
|
||||
@@ -208,6 +211,41 @@ get_length_by_approximation (const GskCurve *curve,
|
||||
return z * sum;
|
||||
}
|
||||
|
||||
/* Compute the inverse of the arclength using bisection,
|
||||
* to a given precision
|
||||
*/
|
||||
static float
|
||||
get_t_by_bisection (const GskCurve *curve,
|
||||
float length,
|
||||
float epsilon)
|
||||
{
|
||||
float t1, t2, t, l;
|
||||
GskCurve c1;
|
||||
|
||||
g_assert (epsilon >= FLT_EPSILON);
|
||||
|
||||
t1 = 0;
|
||||
t2 = 1;
|
||||
|
||||
while (t1 < t2)
|
||||
{
|
||||
t = (t1 + t2) / 2;
|
||||
if (t == t1 || t == t2)
|
||||
break;
|
||||
|
||||
gsk_curve_split (curve, t, &c1, NULL);
|
||||
|
||||
l = gsk_curve_get_length (&c1);
|
||||
if (fabsf (length - l) < epsilon)
|
||||
break;
|
||||
else if (l < length)
|
||||
t1 = t;
|
||||
else
|
||||
t2 = t;
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
/* }}} */
|
||||
/* {{{ Line */
|
||||
|
||||
@@ -426,6 +464,23 @@ gsk_line_curve_get_length_to (const GskCurve *curve,
|
||||
return t * graphene_point_distance (&pts[0], &pts[1], NULL, NULL);
|
||||
}
|
||||
|
||||
static float
|
||||
gsk_line_curve_get_at_length (const GskCurve *curve,
|
||||
float distance,
|
||||
float epsilon)
|
||||
{
|
||||
const GskLineCurve *self = &curve->line;
|
||||
const graphene_point_t *pts = self->points;
|
||||
float length;
|
||||
|
||||
length = graphene_point_distance (&pts[0], &pts[1], NULL, NULL);
|
||||
|
||||
if (length == 0)
|
||||
return 0;
|
||||
|
||||
return CLAMP (distance / length, 0, 1);
|
||||
}
|
||||
|
||||
static const GskCurveClass GSK_LINE_CURVE_CLASS = {
|
||||
gsk_line_curve_init,
|
||||
gsk_line_curve_init_foreach,
|
||||
@@ -448,6 +503,7 @@ static const GskCurveClass GSK_LINE_CURVE_CLASS = {
|
||||
gsk_line_curve_get_derivative_at,
|
||||
gsk_line_curve_get_crossing,
|
||||
gsk_line_curve_get_length_to,
|
||||
gsk_line_curve_get_at_length,
|
||||
};
|
||||
|
||||
/* }}} */
|
||||
@@ -841,6 +897,14 @@ gsk_quad_curve_get_length_to (const GskCurve *curve,
|
||||
return get_length_by_approximation (curve, t);
|
||||
}
|
||||
|
||||
static float
|
||||
gsk_quad_curve_get_at_length (const GskCurve *curve,
|
||||
float t,
|
||||
float epsilon)
|
||||
{
|
||||
return get_t_by_bisection (curve, t, epsilon);
|
||||
}
|
||||
|
||||
static const GskCurveClass GSK_QUAD_CURVE_CLASS = {
|
||||
gsk_quad_curve_init,
|
||||
gsk_quad_curve_init_foreach,
|
||||
@@ -863,6 +927,7 @@ static const GskCurveClass GSK_QUAD_CURVE_CLASS = {
|
||||
gsk_quad_curve_get_derivative_at,
|
||||
gsk_quad_curve_get_crossing,
|
||||
gsk_quad_curve_get_length_to,
|
||||
gsk_quad_curve_get_at_length,
|
||||
};
|
||||
|
||||
/* }}} */
|
||||
@@ -1311,6 +1376,14 @@ gsk_cubic_curve_get_length_to (const GskCurve *curve,
|
||||
return get_length_by_approximation (curve, t);
|
||||
}
|
||||
|
||||
static float
|
||||
gsk_cubic_curve_get_at_length (const GskCurve *curve,
|
||||
float t,
|
||||
float epsilon)
|
||||
{
|
||||
return get_t_by_bisection (curve, t, epsilon);
|
||||
}
|
||||
|
||||
static const GskCurveClass GSK_CUBIC_CURVE_CLASS = {
|
||||
gsk_cubic_curve_init,
|
||||
gsk_cubic_curve_init_foreach,
|
||||
@@ -1333,6 +1406,7 @@ static const GskCurveClass GSK_CUBIC_CURVE_CLASS = {
|
||||
gsk_cubic_curve_get_derivative_at,
|
||||
gsk_cubic_curve_get_crossing,
|
||||
gsk_cubic_curve_get_length_to,
|
||||
gsk_cubic_curve_get_at_length,
|
||||
};
|
||||
|
||||
/* }}} */
|
||||
@@ -2018,6 +2092,14 @@ gsk_conic_curve_get_length_to (const GskCurve *curve,
|
||||
return get_length_by_approximation (curve, t);
|
||||
}
|
||||
|
||||
static float
|
||||
gsk_conic_curve_get_at_length (const GskCurve *curve,
|
||||
float t,
|
||||
float epsilon)
|
||||
{
|
||||
return get_t_by_bisection (curve, t, epsilon);
|
||||
}
|
||||
|
||||
static const GskCurveClass GSK_CONIC_CURVE_CLASS = {
|
||||
gsk_conic_curve_init,
|
||||
gsk_conic_curve_init_foreach,
|
||||
@@ -2040,6 +2122,7 @@ static const GskCurveClass GSK_CONIC_CURVE_CLASS = {
|
||||
gsk_conic_curve_get_derivative_at,
|
||||
gsk_conic_curve_get_crossing,
|
||||
gsk_conic_curve_get_length_to,
|
||||
gsk_conic_curve_get_at_length,
|
||||
};
|
||||
|
||||
/* }}} */
|
||||
@@ -2389,32 +2472,7 @@ gsk_curve_at_length (const GskCurve *curve,
|
||||
float length,
|
||||
float epsilon)
|
||||
{
|
||||
float t1, t2, t, l;
|
||||
GskCurve c1;
|
||||
|
||||
g_assert (epsilon >= FLT_EPSILON);
|
||||
|
||||
t1 = 0;
|
||||
t2 = 1;
|
||||
|
||||
while (t1 < t2)
|
||||
{
|
||||
t = (t1 + t2) / 2;
|
||||
if (t == t1 || t == t2)
|
||||
break;
|
||||
|
||||
gsk_curve_split (curve, t, &c1, NULL);
|
||||
|
||||
l = gsk_curve_get_length (&c1);
|
||||
if (fabsf (length - l) < epsilon)
|
||||
break;
|
||||
else if (l < length)
|
||||
t1 = t;
|
||||
else
|
||||
t2 = t;
|
||||
}
|
||||
|
||||
return t;
|
||||
return get_class (curve->op)->get_at_length (curve, length, epsilon);
|
||||
}
|
||||
|
||||
static inline void
|
||||
|
@@ -144,6 +144,12 @@ gboolean gsk_path_foreach (GskPath
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
gboolean gsk_path_dash (GskPath *self,
|
||||
GskStroke *stroke,
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
|
||||
|
||||
G_END_DECLS
|
||||
|
280
gsk/gskpathdash.c
Normal file
280
gsk/gskpathdash.c
Normal file
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
float collect_start; /* We're collecting multiple line segments when decomposing. */
|
||||
|
||||
/* from the stroke */
|
||||
float *dash;
|
||||
gsize n_dash;
|
||||
float dash_length;
|
||||
float dash_offset;
|
||||
|
||||
GskPathForeachFunc func;
|
||||
gpointer user_data;
|
||||
} GskPathDash;
|
||||
|
||||
static void
|
||||
gsk_path_dash_setup (GskPathDash *self)
|
||||
{
|
||||
self->offset = fmodf (self->dash_offset, 2 * self->dash_length);
|
||||
|
||||
self->dash_index = 0;
|
||||
self->on = TRUE;
|
||||
self->may_close = TRUE;
|
||||
while (self->offset > self->dash[self->dash_index % self->n_dash])
|
||||
{
|
||||
self->offset -= self->dash[self->dash_index % self->n_dash];
|
||||
self->dash_index++;
|
||||
self->on = !self->on;
|
||||
}
|
||||
if (self->first_dash_behavior != ONLY)
|
||||
self->needs_move_to = TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_path_dash_ensure_move_to (GskPathDash *self,
|
||||
const graphene_point_t *pt)
|
||||
{
|
||||
if (!self->needs_move_to)
|
||||
return TRUE;
|
||||
|
||||
if (!self->func (GSK_PATH_MOVE, pt, 1, 0.f, self->user_data))
|
||||
return FALSE;
|
||||
|
||||
self->needs_move_to = FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_path_dash_add_curve (const GskCurve *curve,
|
||||
GskPathDash *self)
|
||||
{
|
||||
float remaining, t_start, t_end;
|
||||
float used;
|
||||
|
||||
remaining = gsk_curve_get_length (curve);
|
||||
used = 0;
|
||||
t_start = 0;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
float piece;
|
||||
|
||||
if (self->offset + remaining <= self->dash[self->dash_index % self->n_dash])
|
||||
piece = remaining;
|
||||
else
|
||||
piece = self->dash[self->dash_index % self->n_dash] - self->offset;
|
||||
|
||||
t_end = gsk_curve_at_length (curve, used + piece, 0.001);
|
||||
|
||||
if (self->on)
|
||||
{
|
||||
if (self->first_dash_behavior != SKIP)
|
||||
{
|
||||
GskCurve segment;
|
||||
|
||||
if (piece > 0)
|
||||
{
|
||||
gsk_curve_segment (curve, t_start, t_end, &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 (curve, t_start, &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
|
||||
{
|
||||
t_start = t_end;
|
||||
remaining -= piece;
|
||||
used += piece;
|
||||
self->offset = 0;
|
||||
self->dash_index++;
|
||||
self->dash_index %= 2 * self->n_dash;
|
||||
self->on = !self->on;
|
||||
self->needs_move_to = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_path_dash_foreach (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
float weight,
|
||||
gpointer user_data)
|
||||
{
|
||||
GskPathDash *self = user_data;
|
||||
GskCurve curve;
|
||||
|
||||
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, weight, self->user_data);
|
||||
}
|
||||
else
|
||||
op = GSK_PATH_LINE;
|
||||
G_GNUC_FALLTHROUGH;
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
case GSK_PATH_QUAD:
|
||||
case GSK_PATH_CUBIC:
|
||||
case GSK_PATH_CONIC:
|
||||
gsk_curve_init_foreach (&curve, op, pts, n_pts, weight);
|
||||
if (!gsk_path_dash_add_curve (&curve, self))
|
||||
return FALSE;
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
gsk_contour_dash (const GskContour *contour,
|
||||
GskStroke *stroke,
|
||||
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,
|
||||
.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, 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, 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
|
||||
* @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,
|
||||
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, func, user_data))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
@@ -27,6 +27,7 @@ gsk_public_sources = files([
|
||||
'gskdiff.c',
|
||||
'gskglshader.c',
|
||||
'gskpath.c',
|
||||
'gskpathdash.c',
|
||||
'gskpathbuilder.c',
|
||||
'gskpathmeasure.c',
|
||||
'gskpathpoint.c',
|
||||
|
191
testsuite/gsk/dash.c
Normal file
191
testsuite/gsk/dash.c
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
static gboolean
|
||||
build_path (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
float weight,
|
||||
gpointer user_data)
|
||||
{
|
||||
GskPathBuilder *builder = user_data;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
gsk_path_builder_close (builder);
|
||||
break;
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
gsk_path_builder_cubic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CONIC:
|
||||
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_simple (void)
|
||||
{
|
||||
const struct {
|
||||
const char *test;
|
||||
float dash[4];
|
||||
gsize n_dash;
|
||||
float dash_offset;
|
||||
const char *result;
|
||||
} tests[] = {
|
||||
/* a line with a dash of a quarter its size, very simple test */
|
||||
{
|
||||
"M 0 0 L 20 0",
|
||||
{ 5, }, 1, 0.f,
|
||||
"M 0 0 L 5 0 M 10 0 L 15 0",
|
||||
},
|
||||
/* a square with a dash of half its size, another simple test */
|
||||
{
|
||||
"M 0 0 h 10 v 10 h -10 z",
|
||||
{ 5, }, 1, 0.f,
|
||||
"M 10 0 L 10 5 M 10 10 L 5 10 M 0 10 L 0 5 M 0 0 L 5 0"
|
||||
},
|
||||
/* a square smaller than the dash, make sure it closes */
|
||||
{
|
||||
"M 0 0 h 10 v 10 h -10 z",
|
||||
{ 50, }, 1, 0.f,
|
||||
"M 0 0 L 10 0 L 10 10 L 0 10 Z"
|
||||
},
|
||||
/* a square exactly the dash's size, make sure it still closes */
|
||||
{
|
||||
"M 0 0 h 10 v 10 h -10 z",
|
||||
{ 40, }, 1, 0.f,
|
||||
"M 0 0 L 10 0 L 10 10 L 0 10 Z"
|
||||
},
|
||||
/* a dash with offset */
|
||||
{
|
||||
"M 0 0 h 10 v 10 h -10 z",
|
||||
{ 5, }, 1, 2.5f,
|
||||
"M 7.5 0 L 10 0 L 10 2.5 M 10 7.5 L 10 10 L 7.5 10 M 2.5 10 L 0 10 L 0 7.5 M 0 2.5 L 0 0 L 2.5 0"
|
||||
},
|
||||
/* a dash with offset, but this time the rect isn't closed */
|
||||
{
|
||||
"M 0 0 L 10 0 L 10 10 L 0 10 L 0 0",
|
||||
{ 5, }, 1, 2.5f,
|
||||
"M 0 0 L 2.5 0 M 7.5 0 L 10 0 L 10 2.5 M 10 7.5 L 10 10 L 7.5 10 M 2.5 10 L 0 10 L 0 7.5 M 0 2.5 L 0 0"
|
||||
},
|
||||
/* a dash with offset into an empty dash */
|
||||
{
|
||||
"M 0 0 h 10 v 10 h -10 z",
|
||||
{ 5, }, 1, 7.5f,
|
||||
"M 2.5 0 L 7.5 0 M 10 2.5 L 10 7.5 M 7.5 10 L 2.5 10 M 0 7.5 L 0 2.5"
|
||||
},
|
||||
/* a dash with offset where the whole rectangle fits into one element - make sure it closes */
|
||||
{
|
||||
"M 0 0 h 10 v 10 h -10 z",
|
||||
{ 1, 1, 100 }, 3, 3.f,
|
||||
"M 0 0 L 10 0 L 10 10 L 0 10 Z"
|
||||
},
|
||||
/* a dash with 0-length elements, aka dotting */
|
||||
{
|
||||
"M 0 0 h 10 v 10 h -10 z",
|
||||
{ 0, 5 }, 2, 0.f,
|
||||
"M 5 0 M 10 0 M 10 5 M 10 10 M 5 10 M 0 10 M 0 5 M 0 0"
|
||||
},
|
||||
#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, 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 ();
|
||||
}
|
@@ -366,6 +366,7 @@ tests = [
|
||||
['shader'],
|
||||
['path'],
|
||||
['path-special-cases'],
|
||||
['dash'],
|
||||
]
|
||||
|
||||
test_cargs = []
|
||||
|
168
tools/gtk-path-tool-dash.c
Normal file
168
tools/gtk-path-tool-dash.c
Normal file
@@ -0,0 +1,168 @@
|
||||
/* Copyright 2023 Red Hat, Inc.
|
||||
*
|
||||
* GTK+ is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* GLib is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with GTK+; see the file COPYING. If not,
|
||||
* see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Matthias Clasen
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include "gtk-path-tool.h"
|
||||
|
||||
#include <glib/gi18n-lib.h>
|
||||
|
||||
static gboolean
|
||||
collect_path (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
float weight,
|
||||
gpointer user_data)
|
||||
{
|
||||
GskPathBuilder *builder = user_data;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case GSK_PATH_MOVE:
|
||||
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CLOSE:
|
||||
gsk_path_builder_close (builder);
|
||||
break;
|
||||
|
||||
case GSK_PATH_LINE:
|
||||
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_QUAD:
|
||||
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y,
|
||||
pts[2].x, pts[2].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CUBIC:
|
||||
gsk_path_builder_cubic_to (builder, pts[1].x, pts[1].y,
|
||||
pts[2].x, pts[2].y,
|
||||
pts[3].x, pts[3].y);
|
||||
break;
|
||||
|
||||
case GSK_PATH_CONIC:
|
||||
gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y,
|
||||
pts[2].x, pts[2].y,
|
||||
weight);
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
do_dash (int *argc, const char ***argv)
|
||||
{
|
||||
GError *error = NULL;
|
||||
char **args = NULL;
|
||||
const char *dashes = NULL;
|
||||
double dash_offset = 0;
|
||||
GOptionContext *context;
|
||||
GOptionEntry entries[] = {
|
||||
{ "dashes", 0, 0, G_OPTION_ARG_STRING, &dashes, N_("Dash pattern (comma-separated numbers)"), N_("VALUE") },
|
||||
{ "dash-offset", 0, 0, G_OPTION_ARG_DOUBLE, &dash_offset, N_("Dash offset (number)"), N_("VALUE") },
|
||||
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &args, NULL, N_("PATH") },
|
||||
{ NULL, },
|
||||
};
|
||||
GskPath *path, *result;
|
||||
GskPathBuilder *builder;
|
||||
GskStroke *stroke;
|
||||
|
||||
g_set_prgname ("gtk4-path-tool dash");
|
||||
|
||||
context = g_option_context_new (NULL);
|
||||
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
|
||||
g_option_context_add_main_entries (context, entries, NULL);
|
||||
g_option_context_set_summary (context, _("Dash a path."));
|
||||
|
||||
if (!g_option_context_parse (context, argc, (char ***)argv, &error))
|
||||
{
|
||||
g_printerr ("%s\n", error->message);
|
||||
g_error_free (error);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
g_option_context_free (context);
|
||||
|
||||
if (args == NULL)
|
||||
{
|
||||
g_printerr ("%s\n", _("No paths given."));
|
||||
exit (1);
|
||||
}
|
||||
|
||||
path = get_path (args[0]);
|
||||
|
||||
stroke = gsk_stroke_new (1);
|
||||
|
||||
if (dashes != NULL)
|
||||
{
|
||||
GArray *d = g_array_new (FALSE, FALSE, sizeof (float));
|
||||
char **strings;
|
||||
|
||||
strings = g_strsplit (dashes, ",", 0);
|
||||
|
||||
for (unsigned int i = 0; strings[i]; i++)
|
||||
{
|
||||
char *end = NULL;
|
||||
float f;
|
||||
|
||||
f = (float) g_ascii_strtod (strings[i], &end);
|
||||
|
||||
if (*end != '\0')
|
||||
{
|
||||
char *msg = g_strdup_printf (_("Failed to parse '%s' as number"), strings[i]);
|
||||
g_printerr ("%s\n", msg);
|
||||
exit (1);
|
||||
}
|
||||
|
||||
g_array_append_val (d, f);
|
||||
}
|
||||
|
||||
g_strfreev (strings);
|
||||
|
||||
gsk_stroke_set_dash (stroke, (const float *)d->data, d->len);
|
||||
|
||||
g_array_unref (d);
|
||||
}
|
||||
|
||||
gsk_stroke_set_dash_offset (stroke, dash_offset);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
|
||||
gsk_path_dash (path, stroke, collect_path, builder);
|
||||
|
||||
result = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
if (result)
|
||||
{
|
||||
char *str = gsk_path_to_string (result);
|
||||
g_print ("%s\n", str);
|
||||
g_free (str);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_printerr ("%s\n", _("That didn't work out."));
|
||||
exit (1);
|
||||
}
|
||||
}
|
@@ -40,6 +40,7 @@ usage (void)
|
||||
"Commands:\n"
|
||||
" decompose Decompose the path\n"
|
||||
" reverse Reverse the path\n"
|
||||
" dash Dash the path\n"
|
||||
" restrict Restrict the path to a segment\n"
|
||||
" show Display the path in a window\n"
|
||||
" render Render the path as an image\n"
|
||||
@@ -126,7 +127,9 @@ main (int argc, const char *argv[])
|
||||
argv++;
|
||||
argc--;
|
||||
|
||||
if (strcmp (argv[0], "decompose") == 0)
|
||||
if (strcmp (argv[0], "dash") == 0)
|
||||
do_dash (&argc, &argv);
|
||||
else if (strcmp (argv[0], "decompose") == 0)
|
||||
do_decompose (&argc, &argv);
|
||||
else if (strcmp (argv[0], "info") == 0)
|
||||
do_info (&argc, &argv);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
void do_info (int *argc, const char ***argv);
|
||||
void do_dash (int *argc, const char ***argv);
|
||||
void do_decompose (int *argc, const char ***argv);
|
||||
void do_restrict (int *argc, const char ***argv);
|
||||
void do_reverse (int *argc, const char ***argv);
|
||||
|
@@ -24,6 +24,7 @@ endif
|
||||
|
||||
gtk_tools = [
|
||||
['gtk4-path-tool', ['gtk-path-tool.c',
|
||||
'gtk-path-tool-dash.c',
|
||||
'gtk-path-tool-decompose.c',
|
||||
'gtk-path-tool-info.c',
|
||||
'gtk-path-tool-render.c',
|
||||
|
Reference in New Issue
Block a user