diff --git a/gsk/gsk.h b/gsk/gsk.h index 48dfbeb979..9f2ee451dd 100644 --- a/gsk/gsk.h +++ b/gsk/gsk.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include diff --git a/gsk/gskenums.h b/gsk/gskenums.h index 9c873e0412..4125d50cd8 100644 --- a/gsk/gskenums.h +++ b/gsk/gskenums.h @@ -202,6 +202,51 @@ typedef enum { GSK_FILL_RULE_EVEN_ODD } GskFillRule; +/** + * GskLineCap: + * @GSK_LINE_CAP_BUTT: Start and stop the line exactly at the start + * and end point + * @GSK_LINE_CAP_ROUND: Use a round ending, the center of the circle + * is the start or end point + * @GSK_LINE_CAP_SQUARE: use squared ending, the center of the square + * is the start or end point + * + * Specifies how to render the start and end points of contours or + * dashes when stroking. + * + * The default line cap style is `GSK_LINE_CAP_BUTT`. + */ +typedef enum { + GSK_LINE_CAP_BUTT, + GSK_LINE_CAP_ROUND, + GSK_LINE_CAP_SQUARE +} GskLineCap; + +/** + * GskLineJoin: + * @GSK_LINE_JOIN_MITER: Use a sharp angled corner + * @GSK_LINE_JOIN_MITER_CLIP: Use a sharp, angled corner, at a distance + * @GSK_LINE_JOIN_ROUND: Use a round join, the center of the circle is + * the join point + * @GSK_LINE_JOIN_BEVEL: use a cut-off join, the join is cut off at half + * the line width from the joint point + * @GSK_LINE_JOIN_ARCS: Use a sharp angled corner made from circles + * + * Specifies how to render the junction of two lines when stroking. + * + * See [method@Gsk.Stroke.set_miter_limit] for details on the difference + * between `GSK_LINE_JOIN_MITER` and `GSK_LINE_JOIN_MITER_CLIP`. + * + * The default line join style is `GSK_LINE_JOIN_MITER`. + **/ +typedef enum { + GSK_LINE_JOIN_MITER, + GSK_LINE_JOIN_MITER_CLIP, + GSK_LINE_JOIN_ROUND, + GSK_LINE_JOIN_BEVEL, + GSK_LINE_JOIN_ARCS +} GskLineJoin; + /** * GskPathOperation: * @GSK_PATH_MOVE: A move-to operation, with 1 point describing the target point. diff --git a/gsk/gskstroke.c b/gsk/gskstroke.c new file mode 100644 index 0000000000..d5a601cf06 --- /dev/null +++ b/gsk/gskstroke.c @@ -0,0 +1,446 @@ +/* + * 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 . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gskstrokeprivate.h" + +/** + * GskStroke: + * + * A `GskStroke` struct collects the parameters that influence + * the operation of stroking a path. + */ + +G_DEFINE_BOXED_TYPE (GskStroke, gsk_stroke, gsk_stroke_copy, gsk_stroke_free) + + +/** + * gsk_stroke_new: + * @line_width: line width of the stroke. Must be > 0 + * + * Creates a new `GskStroke` with the given @line_width. + * + * Returns: a new `GskStroke` + */ +GskStroke * +gsk_stroke_new (float line_width) +{ + GskStroke *self; + + g_return_val_if_fail (line_width > 0, NULL); + + self = g_new0 (GskStroke, 1); + + self->line_width = line_width; + self->line_cap = GSK_LINE_CAP_BUTT; + self->line_join = GSK_LINE_JOIN_MITER; + self->miter_limit = 4.f; /* following svg */ + + return self; +} + +/** + * gsk_stroke_copy: + * @other: `GskStroke` to copy + * + * Creates a copy of the given @other stroke. + * + * Returns: a new `GskStroke`. Use [method@Gsk.Stroke.free] to free it + */ +GskStroke * +gsk_stroke_copy (const GskStroke *other) +{ + GskStroke *self; + + g_return_val_if_fail (other != NULL, NULL); + + self = g_new (GskStroke, 1); + + gsk_stroke_init_copy (self, other); + + return self; +} + +/** + * gsk_stroke_free: + * @self: a `GskStroke` + * + * Frees a `GskStroke`. + */ +void +gsk_stroke_free (GskStroke *self) +{ + if (self == NULL) + return; + + gsk_stroke_clear (self); + + g_free (self); +} + +/** + * gsk_stroke_to_cairo: + * @self: a `GskStroke` + * @cr: the cairo context to configure + * + * A helper function that sets the stroke parameters + * of @cr from the values found in @self. + */ +void +gsk_stroke_to_cairo (const GskStroke *self, + cairo_t *cr) +{ + cairo_set_line_width (cr, self->line_width); + + /* gcc can optimize that to a direct case. This catches later additions to the enum */ + switch (self->line_cap) + { + case GSK_LINE_CAP_BUTT: + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + break; + case GSK_LINE_CAP_ROUND: + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + break; + case GSK_LINE_CAP_SQUARE: + cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE); + break; + default: + g_assert_not_reached (); + break; + } + + /* gcc can optimize that to a direct case. This catches later additions to the enum */ + switch (self->line_join) + { + case GSK_LINE_JOIN_MITER: + case GSK_LINE_JOIN_MITER_CLIP: + case GSK_LINE_JOIN_ARCS: + cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER); + break; + case GSK_LINE_JOIN_ROUND: + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + break; + case GSK_LINE_JOIN_BEVEL: + cairo_set_line_join (cr, CAIRO_LINE_JOIN_BEVEL); + break; + default: + g_assert_not_reached (); + break; + } + + cairo_set_miter_limit (cr, self->miter_limit); + + if (self->dash_length) + { + gsize i; + double *dash = g_newa (double, self->n_dash); + + for (i = 0; i < self->n_dash; i++) + { + dash[i] = self->dash[i]; + } + cairo_set_dash (cr, dash, self->n_dash, self->dash_offset); + } + else + cairo_set_dash (cr, NULL, 0, 0.0); +} + +/** + * gsk_stroke_equal: + * @stroke1: the first `GskStroke` + * @stroke2: the second `GskStroke` + * + * Checks if 2 strokes are identical. + * + * Returns: `TRUE` if the 2 strokes are equal, `FALSE` otherwise + */ +gboolean +gsk_stroke_equal (gconstpointer stroke1, + gconstpointer stroke2) +{ + const GskStroke *self1 = stroke1; + const GskStroke *self2 = stroke2; + + return self1->line_width == self2->line_width; +} + +/** + * gsk_stroke_set_line_width: + * @self: a `GskStroke` + * @line_width: width of the line in pixels + * + * Sets the line width to be used when stroking. + * + * The line width must be > 0. + */ +void +gsk_stroke_set_line_width (GskStroke *self, + float line_width) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (line_width > 0); + + self->line_width = line_width; +} + +/** + * gsk_stroke_get_line_width: + * @self: a `GskStroke` + * + * Gets the line width used. + * + * Returns: The line width + */ +float +gsk_stroke_get_line_width (const GskStroke *self) +{ + g_return_val_if_fail (self != NULL, 0.0); + + return self->line_width; +} + +/** + * gsk_stroke_set_line_cap: + * @self: a`GskStroke` + * @line_cap: the `GskLineCap` + * + * Sets the line cap to be used when stroking. + * + * See [enum@Gsk.LineCap] for details. + */ +void +gsk_stroke_set_line_cap (GskStroke *self, + GskLineCap line_cap) +{ + g_return_if_fail (self != NULL); + + self->line_cap = line_cap; +} + +/** + * gsk_stroke_get_line_cap: + * @self: a `GskStroke` + * + * Gets the line cap used. + * + * See [enum@Gsk.LineCap] for details. + * + * Returns: The line cap + */ +GskLineCap +gsk_stroke_get_line_cap (const GskStroke *self) +{ + g_return_val_if_fail (self != NULL, 0.0); + + return self->line_cap; +} + +/** + * gsk_stroke_set_line_join: + * @self: a `GskStroke` + * @line_join: The line join to use + * + * Sets the line join to be used when stroking. + * + * See [enum@Gsk.LineJoin] for details. + */ +void +gsk_stroke_set_line_join (GskStroke *self, + GskLineJoin line_join) +{ + g_return_if_fail (self != NULL); + + self->line_join = line_join; +} + +/** + * gsk_stroke_get_line_join: + * @self: a `GskStroke` + * + * Gets the line join used. + * + * See [enum@Gsk.LineJoin] for details. + * + * Returns: The line join + */ +GskLineJoin +gsk_stroke_get_line_join (const GskStroke *self) +{ + g_return_val_if_fail (self != NULL, 0.0); + + return self->line_join; +} + +/** + * gsk_stroke_set_miter_limit: + * @self: a `GskStroke` + * @limit: the miter limit + * + * Sets the limit for the distance from the corner where sharp + * turns of joins get cut off. + * + * The miter limit is in units of line width and must be non-negative. + * + * For joins of type `GSK_LINE_JOIN_MITER` that exceed the miter + * limit, the join gets rendered as if it was of type + * `GSK_LINE_JOIN_BEVEL`. For joins of type `GSK_LINE_JOIN_MITER_CLIP`, + * the miter is clipped at a distance of half the miter limit. + */ +void +gsk_stroke_set_miter_limit (GskStroke *self, + float limit) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (limit >= 0); + + self->miter_limit = limit; +} + +/** + * gsk_stroke_get_miter_limit: + * @self: a `GskStroke` + * + * Returns the miter limit of a `GskStroke`. + */ +float +gsk_stroke_get_miter_limit (const GskStroke *self) +{ + g_return_val_if_fail (self != NULL, 4.f); + + return self->miter_limit; +} + +/** + * gsk_stroke_set_dash: + * @self: a `GskStroke` + * @dash: (array length=n_dash) (transfer none) (nullable): + * the array of dashes + * @n_dash: number of elements in @dash + * + * Sets the dash pattern to use by this stroke. + * + * A dash pattern is specified by an array of alternating non-negative + * values. Each value provides the length of alternate "on" and "off" + * portions of the stroke. + * + * Each "on" segment will have caps applied as if the segment were a + * separate contour. In particular, it is valid to use an "on" length + * of 0 with `GSK_LINE_CAP_ROUND` or `GSK_LINE_CAP_SQUARE` to draw dots + * or squares along a path. + * + * If @n_dash is 0, if all elements in @dash are 0, or if there are + * negative values in @dash, then dashing is disabled. + * + * If @n_dash is 1, an alternating "on" and "off" pattern with the + * single dash length provided is assumed. + * + * If @n_dash is uneven, the dash array will be used with the first + * element in @dash defining an "on" or "off" in alternating passes + * through the array. + * + * You can specify a starting offset into the dash with + * [method@Gsk.Stroke.set_dash_offset]. + */ +void +gsk_stroke_set_dash (GskStroke *self, + const float *dash, + gsize n_dash) +{ + float dash_length; + gsize i; + + g_return_if_fail (self != NULL); + g_return_if_fail (dash != NULL || n_dash == 0); + + dash_length = 0; + for (i = 0; i < n_dash; i++) + { + if (!(dash[i] >= 0)) /* should catch NaN */ + { + g_critical ("invalid value in dash array at position %zu", i); + return; + } + dash_length += dash[i]; + } + + self->dash_length = dash_length; + g_free (self->dash); + self->dash = g_memdup (dash, sizeof (gfloat) * n_dash); + self->n_dash = n_dash; + +} + +/** + * gsk_stroke_get_dash: + * @self: a `GskStroke` + * @n_dash: (out caller-allocates): number of elements + * in the array returned + * + * Gets the dash array in use or `NULL` if dashing is disabled. + * + * Returns: (array length=n_dash) (transfer none) (nullable): + * The dash array or `NULL` if the dash array is empty. + */ +const float * +gsk_stroke_get_dash (const GskStroke *self, + gsize *n_dash) +{ + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (n_dash != NULL, NULL); + + *n_dash = self->n_dash; + + return self->dash; +} + +/** + * gsk_stroke_set_dash_offset: + * @self: a `GskStroke` + * @offset: offset into the dash pattern + * + * Sets the offset into the dash pattern where dashing should begin. + * + * This is an offset into the length of the path, not an index into + * the array values of the dash array. + * + * See [method@Gsk.Stroke.set_dash] for more details on dashing. + */ +void +gsk_stroke_set_dash_offset (GskStroke *self, + float offset) +{ + g_return_if_fail (self != NULL); + + self->dash_offset = offset; +} + +/** + * gsk_stroke_get_dash_offset: + * @self: a `GskStroke` + * + * Returns the dash_offset of a `GskStroke`. + */ +float +gsk_stroke_get_dash_offset (const GskStroke *self) +{ + g_return_val_if_fail (self != NULL, 4.f); + + return self->dash_offset; +} diff --git a/gsk/gskstroke.h b/gsk/gskstroke.h new file mode 100644 index 0000000000..e32c7446c3 --- /dev/null +++ b/gsk/gskstroke.h @@ -0,0 +1,85 @@ +/* + * 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 . + * + * Authors: Benjamin Otte + */ + +#pragma once + +#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + + +#include + +G_BEGIN_DECLS + +#define GSK_TYPE_STROKE (gsk_stroke_get_type ()) + +GDK_AVAILABLE_IN_ALL +GType gsk_stroke_get_type (void) G_GNUC_CONST; +GDK_AVAILABLE_IN_ALL +GskStroke * gsk_stroke_new (float line_width); +GDK_AVAILABLE_IN_ALL +GskStroke * gsk_stroke_copy (const GskStroke *other); +GDK_AVAILABLE_IN_ALL +void gsk_stroke_free (GskStroke *self); + +GDK_AVAILABLE_IN_ALL +gboolean gsk_stroke_equal (gconstpointer stroke1, + gconstpointer stroke2); + +GDK_AVAILABLE_IN_ALL +void gsk_stroke_set_line_width (GskStroke *self, + float line_width); +GDK_AVAILABLE_IN_ALL +float gsk_stroke_get_line_width (const GskStroke *self); +GDK_AVAILABLE_IN_ALL +void gsk_stroke_set_line_cap (GskStroke *self, + GskLineCap line_cap); +GDK_AVAILABLE_IN_ALL +GskLineCap gsk_stroke_get_line_cap (const GskStroke *self); +GDK_AVAILABLE_IN_ALL +void gsk_stroke_set_line_join (GskStroke *self, + GskLineJoin line_join); +GDK_AVAILABLE_IN_ALL +GskLineJoin gsk_stroke_get_line_join (const GskStroke *self); + +void gsk_stroke_set_miter_limit (GskStroke *self, + float limit); +GDK_AVAILABLE_IN_ALL +float gsk_stroke_get_miter_limit (const GskStroke *self); + +void gsk_stroke_set_dash (GskStroke *self, + const float *dash, + gsize n_dash); +GDK_AVAILABLE_IN_ALL +const float * gsk_stroke_get_dash (const GskStroke *self, + gsize *n_dash); + +GDK_AVAILABLE_IN_ALL +void gsk_stroke_set_dash_offset (GskStroke *self, + float offset); +GDK_AVAILABLE_IN_ALL +float gsk_stroke_get_dash_offset (const GskStroke *self); + +GDK_AVAILABLE_IN_ALL +void gsk_stroke_to_cairo (const GskStroke *self, + cairo_t *cr); + + +G_END_DECLS diff --git a/gsk/gskstrokeprivate.h b/gsk/gskstrokeprivate.h new file mode 100644 index 0000000000..23d56f64e2 --- /dev/null +++ b/gsk/gskstrokeprivate.h @@ -0,0 +1,56 @@ +/* + * 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 . + * + * Authors: Benjamin Otte + */ + + +#pragma once + +#include "gskstroke.h" + +G_BEGIN_DECLS + +struct _GskStroke +{ + float line_width; + GskLineCap line_cap; + GskLineJoin line_join; + float miter_limit; + + float *dash; + gsize n_dash; + float dash_length; /* sum of all dashes in the array */ + float dash_offset; +}; + +static inline void +gsk_stroke_init_copy (GskStroke *stroke, + const GskStroke *other) +{ + *stroke = *other; + + stroke->dash = g_memdup (other->dash, stroke->n_dash * sizeof (float)); +} + +static inline void +gsk_stroke_clear (GskStroke *stroke) +{ + g_clear_pointer (&stroke->dash, g_free); + stroke->n_dash = 0; /* better safe than sorry */ +} + +G_END_DECLS diff --git a/gsk/gsktypes.h b/gsk/gsktypes.h index 1a3b3e32c5..7733b46872 100644 --- a/gsk/gsktypes.h +++ b/gsk/gsktypes.h @@ -30,5 +30,6 @@ typedef struct _GskPathBuilder GskPathBuilder; typedef struct _GskPathMeasure GskPathMeasure; typedef struct _GskPathPoint GskPathPoint; typedef struct _GskRenderer GskRenderer; +typedef struct _GskStroke GskStroke; typedef struct _GskTransform GskTransform; diff --git a/gsk/meson.build b/gsk/meson.build index 574b25a265..ccf3b98e62 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -35,6 +35,7 @@ gsk_public_sources = files([ 'gskrendernodeimpl.c', 'gskrendernodeparser.c', 'gskroundedrect.c', + 'gskstroke.c', 'gsktransform.c', 'gl/gskglrenderer.c', ]) @@ -80,6 +81,7 @@ gsk_public_headers = files([ 'gskrenderer.h', 'gskrendernode.h', 'gskroundedrect.h', + 'gskstroke.h', 'gsktransform.h', 'gsktypes.h', ])