450 lines
10 KiB
C
450 lines
10 KiB
C
/*
|
|
* Copyright © 2020 Benjamin Otte
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors: Benjamin Otte <otte@gnome.org>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "gskstrokeprivate.h"
|
|
|
|
/**
|
|
* SECTION:gskstroke
|
|
* @Title: Stroke
|
|
* @Short_description: Properties of a stroke operation
|
|
* @See_also: #GskPath, gsk_stroke_node_new()
|
|
*
|
|
* This section describes the #GskStroke structure that is used to
|
|
* describe lines and curves that are more complex than simple rectangles.
|
|
*
|
|
* #GskStroke is an immutable struct. After creation, you cannot change
|
|
* the types it represents. Instead, new #GskStroke have to be created.
|
|
* The #GskStrokeBuilder structure is meant to help in this endeavor.
|
|
*/
|
|
|
|
/**
|
|
* GskStroke:
|
|
*
|
|
* A #GskStroke struct is an opaque struct that should be copied
|
|
* on use.
|
|
*/
|
|
|
|
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 gsk_stroke_free() to free it.
|
|
**/
|
|
GskStroke *
|
|
gsk_stroke_copy (const GskStroke *other)
|
|
{
|
|
GskStroke *self;
|
|
|
|
g_return_val_if_fail (other != NULL, NULL);
|
|
|
|
self = g_new (GskStroke, 1);
|
|
|
|
gsk_stroke_init_copy (self, other);
|
|
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
* gsk_stroke_free:
|
|
* @self: a #GskStroke
|
|
*
|
|
* Frees a #GskStroke.
|
|
**/
|
|
void
|
|
gsk_stroke_free (GskStroke *self)
|
|
{
|
|
if (self == NULL)
|
|
return;
|
|
|
|
gsk_stroke_clear (self);
|
|
|
|
g_free (self);
|
|
}
|
|
|
|
/**
|
|
* gsk_stroke_to_cairo:
|
|
* @self: a #GskStroke
|
|
* @cr: the cairo context to configure
|
|
*
|
|
* A helper function that sets the stroke parameters
|
|
* of @cr from the values found in @self.
|
|
*/
|
|
void
|
|
gsk_stroke_to_cairo (const GskStroke *self,
|
|
cairo_t *cr)
|
|
{
|
|
cairo_set_line_width (cr, self->line_width);
|
|
|
|
/* gcc can optimize that to a direct case. This catches later additions to the enum */
|
|
switch (self->line_cap)
|
|
{
|
|
case GSK_LINE_CAP_BUTT:
|
|
cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
|
|
break;
|
|
case GSK_LINE_CAP_ROUND:
|
|
cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
|
|
break;
|
|
case GSK_LINE_CAP_SQUARE:
|
|
cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
/* gcc can optimize that to a direct case. This catches later additions to the enum */
|
|
switch (self->line_join)
|
|
{
|
|
case GSK_LINE_JOIN_MITER:
|
|
case GSK_LINE_JOIN_MITER_CLIP:
|
|
cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
|
|
break;
|
|
case GSK_LINE_JOIN_ROUND:
|
|
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
|
|
break;
|
|
case GSK_LINE_JOIN_BEVEL:
|
|
cairo_set_line_join (cr, CAIRO_LINE_JOIN_BEVEL);
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
cairo_set_miter_limit (cr, self->miter_limit);
|
|
|
|
if (self->dash_length)
|
|
{
|
|
gsize i;
|
|
double *dash = g_newa (double, self->n_dash);
|
|
|
|
for (i = 0; i < self->n_dash; i++)
|
|
{
|
|
dash[i] = self->dash[i];
|
|
}
|
|
cairo_set_dash (cr, dash, self->n_dash, self->dash_offset);
|
|
}
|
|
else
|
|
cairo_set_dash (cr, NULL, 0, 0.0);
|
|
}
|
|
|
|
/**
|
|
* gsk_stroke_equal:
|
|
* @stroke1: the first #GskStroke
|
|
* @stroke2: the second #GskStroke
|
|
*
|
|
* Checks if 2 strokes are identical.
|
|
*
|
|
* Returns: %TRUE if the 2 strokes are equal, %FALSE otherwise
|
|
**/
|
|
gboolean
|
|
gsk_stroke_equal (gconstpointer stroke1,
|
|
gconstpointer stroke2)
|
|
{
|
|
const GskStroke *self1 = stroke1;
|
|
const GskStroke *self2 = stroke2;
|
|
|
|
return self1->line_width == self2->line_width;
|
|
}
|
|
|
|
/**
|
|
* gsk_stroke_set_line_width:
|
|
* @self: a #GskStroke
|
|
* @line_width: width of the line in pixels
|
|
*
|
|
* Sets the line width to be used when stroking. The line width
|
|
* must be > 0.
|
|
**/
|
|
void
|
|
gsk_stroke_set_line_width (GskStroke *self,
|
|
float line_width)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (line_width > 0);
|
|
|
|
self->line_width = line_width;
|
|
}
|
|
|
|
/**
|
|
* gsk_stroke_get_line_width:
|
|
* @self: a #GskStroke
|
|
*
|
|
* Gets the line width used.
|
|
*
|
|
* Returns: The line width
|
|
**/
|
|
float
|
|
gsk_stroke_get_line_width (const GskStroke *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, 0.0);
|
|
|
|
return self->line_width;
|
|
}
|
|
|
|
/**
|
|
* gsk_stroke_set_line_cap:
|
|
* @self: a #GskStroke
|
|
* @line_cap: the #GskLineCap
|
|
*
|
|
* Sets the line cap to be used when stroking.
|
|
* See #GskLineCap 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 #GskLineCap 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 #GskLineJoin 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 #GskLineJoin for details.
|
|
*
|
|
* Returns: The line join
|
|
**/
|
|
GskLineJoin
|
|
gsk_stroke_get_line_join (const GskStroke *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, 0.0);
|
|
|
|
return self->line_join;
|
|
}
|
|
|
|
/**
|
|
* gsk_stroke_set_miter_limit:
|
|
* @self: a #GskStroke
|
|
* @limit: the miter limit, must be non-negative
|
|
*
|
|
* Sets the limit for the distance from the corner where sharp
|
|
* turns of joins get cut off. The miter limit is in units of
|
|
* line width.
|
|
*
|
|
* For joins of type %GSK_LINE_JOIN_MITER that exceed the miter
|
|
* limit, the join gets rendered as if it was of type
|
|
* %GSK_LINE_JOIN_BEVEL. For joins of type %GSK_LINE_JOIN_MITER_CLIP,
|
|
* the miter is clipped at a distance of half the miter limit.
|
|
*/
|
|
void
|
|
gsk_stroke_set_miter_limit (GskStroke *self,
|
|
float limit)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (limit >= 0);
|
|
|
|
self->miter_limit = limit;
|
|
}
|
|
|
|
/**
|
|
* gsk_stroke_get_miter_limit:
|
|
* @self: a #GskStroke
|
|
*
|
|
* Returns the miter limit of a #GskStroke.
|
|
*/
|
|
float
|
|
gsk_stroke_get_miter_limit (const GskStroke *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, 4.f);
|
|
|
|
return self->miter_limit;
|
|
}
|
|
|
|
/**
|
|
* gsk_stroke_set_dash:
|
|
* @self: a #GskStroke
|
|
* @dash: (array length=n_dash) (transfer none) (nullable):
|
|
* the array of dashes
|
|
* @n_dash: number of elements in @dash
|
|
*
|
|
* Sets the dash pattern to use by this stroke. A dash pattern is specified by
|
|
* an array of alternating non-negative values. Each value provides the length
|
|
* of alternate "on" and "off" portions of the stroke.
|
|
*
|
|
* Each "on" segment will have caps applied as if the segment were a separate
|
|
* contour. In particular, it is valid to use an "on" length of 0 with
|
|
* @GSK_LINE_CAP_ROUND or @GSK_LINE_CAP_SQUARE to draw dots or squares along
|
|
* a path.
|
|
*
|
|
* If @n_dash is 0, if all elements in @dash are 0, or if there are negative
|
|
* values in @dash, then dashing is disabled.
|
|
*
|
|
* If @n_dash is 1, an alternating "on" and "off" pattern with the single
|
|
* dash length provided is assumed.
|
|
*
|
|
* If @n_dash is uneven, the dash array will be used with the first element
|
|
* in @dash defining an "on" or "off" in alternating passes through the array.
|
|
*
|
|
* You can specify a starting offset into the dash with
|
|
* @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 set via gsk_stroke_set_dash() 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.
|
|
**/
|
|
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;
|
|
}
|