/*
* 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 "gskpathprivate.h"
#include "gskcurveprivate.h"
#include "gskpathbuilder.h"
#include "gskpathpoint.h"
#include "gskcontourprivate.h"
/**
* GskPath :
*
* Describes lines and curves that are more complex than simple rectangles .
*
* Paths can used for rendering ( filling or stroking ) and for animations
* ( e . g . as trajectories ) .
*
* ` GskPath ` is an immutable , opaque , reference - counted struct .
* After creation , you cannot change the types it represents . Instead ,
* new ` GskPath ` objects have to be created . The [ struct @ Gsk . PathBuilder ]
* structure is meant to help in this endeavor .
*
* Conceptually , a path consists of zero or more contours ( continuous , connected
* curves ) , each of which may or may not be closed . Contours are typically
* constructed from B é zier segments .
*
* < picture >
* < source srcset = " path - dark . png " media = " ( prefers - color - scheme : dark ) " >
* < img alt = " A Path " src = " path - light . png " >
* < / picture >
*
* Since : 4 . 14
*/
struct _GskPath
{
/*< private >*/
guint ref_count;
GskPathFlags flags;
gsize n_contours;
GskContour *contours[];
/* followed by the contours data */
};
G_DEFINE_BOXED_TYPE (GskPath, gsk_path, gsk_path_ref, gsk_path_unref)
/* {{{ Private API */
GskPath *
gsk_path_new_from_contours (const GSList *contours)
{
GskPath *path;
const GSList *l;
gsize size;
gsize n_contours;
guint8 *contour_data;
GskPathFlags flags;
flags = GSK_PATH_CLOSED | GSK_PATH_FLAT | GSK_PATH_ZERO_LENGTH;
size = 0 ;
n_contours = 0 ;
for (l = contours; l; l = l->next)
{
GskContour *contour = l->data;
n_contours++;
size += sizeof (GskContour *);
size += gsk_contour_get_size (contour);
flags &= gsk_contour_get_flags (contour);
}
path = g_malloc0 (sizeof (GskPath) + size);
path->ref_count = 1 ;
path->flags = flags;
path->n_contours = n_contours;
contour_data = (guint8 *) &path->contours[n_contours];
n_contours = 0 ;
for (l = contours; l; l = l->next)
{
GskContour *contour = l->data;
path->contours[n_contours] = (GskContour *) contour_data;
gsk_contour_copy ((GskContour *) contour_data, contour);
size = gsk_contour_get_size (contour);
contour_data += size;
n_contours++;
}
return path;
}
const GskContour *
gsk_path_get_contour (const GskPath *self,
gsize i)
{
if (i < self->n_contours)
return self->contours[i];
else
return NULL;
}
GskPathFlags
gsk_path_get_flags (const GskPath *self)
{
return self->flags;
}
gsize
gsk_path_get_n_contours (const GskPath *self)
{
return self->n_contours;
}
/* }}} */
/* {{{ Public API */
/**
* gsk_path_ref :
* @ self : a path
*
* Increases the reference count of a path by one .
*
* Returns : the passed in ` GskPath `
*
* Since : 4 . 14
*/
GskPath *
gsk_path_ref (GskPath *self)
{
g_return_val_if_fail (self != NULL, NULL);
self->ref_count++;
return self;
}
/**
* gsk_path_unref :
* @ self : a path
*
* Decreases the reference count of a path by one .
*
* If the resulting reference count is zero , frees the path .
*
* Since : 4 . 14
*/
void
gsk_path_unref (GskPath *self)
{
g_return_if_fail (self != NULL);
g_return_if_fail (self->ref_count > 0 );
self->ref_count--;
if (self->ref_count > 0 )
return ;
g_free (self);
}
/**
* gsk_path_print :
* @ self : a path
* @ string : the string to print into
*
* Converts the path into a human - readable representation .
*
* The string is compatible with ( a superset of )
* [ SVG path syntax ] ( https : //www.w3.org/TR/SVG11/paths.html#PathData),
* see [ func @ Gsk . Path . parse ] for a summary of the syntax .
*
* Since : 4 . 14
*/
void
gsk_path_print (GskPath *self,
GString *string)
{
gsize i;
g_return_if_fail (self != NULL);
g_return_if_fail (string != NULL);
for (i = 0 ; i < self->n_contours; i++)
{
if (i > 0 )
g_string_append_c (string, ' ' );
gsk_contour_print (self->contours[i], string);
}
}
/**
* gsk_path_to_string :
* @ self : a path
*
* Converts the path into a human - readable string .
*
* You can use this function in a debugger to get a quick overview
* of the path .
*
* This is a wrapper around [ method @ Gsk . Path . print ] , see that function
* for details .
*
* Returns : a new string for @ self
*
* Since : 4 . 14
*/
char *
gsk_path_to_string (GskPath *self)
{
GString *string;
g_return_val_if_fail (self != NULL, NULL);
string = g_string_new ("" );
gsk_path_print (self, string);
return g_string_free (string, FALSE );
}
static gboolean
gsk_path_to_cairo_add_op (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer cr)
{
switch (op)
{
case GSK_PATH_MOVE:
cairo_move_to (cr, pts[0 ].x, pts[0 ].y);
break ;
case GSK_PATH_CLOSE:
cairo_close_path (cr);
break ;
case GSK_PATH_LINE:
cairo_line_to (cr, pts[1 ].x, pts[1 ].y);
break ;
case GSK_PATH_CUBIC:
cairo_curve_to (cr, pts[1 ].x, pts[1 ].y, pts[2 ].x, pts[2 ].y, pts[3 ].x, pts[3 ].y);
break ;
case GSK_PATH_QUAD:
case GSK_PATH_CONIC:
default :
g_assert_not_reached ();
return FALSE ;
}
return TRUE ;
}
/**
* gsk_path_to_cairo :
* @ self : a path
* @ cr : a cairo context
*
* Appends the path to a cairo context for drawing with Cairo .
*
* This may cause some suboptimal conversions to be performed as
* Cairo does not support all features of ` GskPath ` .
*
* This function does not clear the existing Cairo path . Call
* cairo_new_path ( ) if you want this .
*
* Since : 4 . 14
*/
void
gsk_path_to_cairo (GskPath *self,
cairo_t *cr)
{
g_return_if_fail (self != NULL);
g_return_if_fail (cr != NULL);
gsk_path_foreach_with_tolerance (self,
GSK_PATH_FOREACH_ALLOW_CUBIC,
cairo_get_tolerance (cr),
gsk_path_to_cairo_add_op,
cr);
}
/**
* gsk_path_is_empty :
* @ self : a path
*
* Checks if the path is empty , i . e . contains no lines or curves .
*
* Returns : true if the path is empty
*
* Since : 4 . 14
*/
gboolean
gsk_path_is_empty (GskPath *self)
{
g_return_val_if_fail (self != NULL, FALSE );
return self->n_contours == 0 ;
}
/**
* gsk_path_is_closed :
* @ self : a path
*
* Returns if the path represents a single closed contour .
*
* Returns : true if the path is closed
*
* Since : 4 . 14
*/
gboolean
gsk_path_is_closed (GskPath *self)
{
g_return_val_if_fail (self != NULL, FALSE );
/* XXX: is the empty path closed? Currently it's not */
if (self->n_contours != 1 )
return FALSE ;
return gsk_contour_get_flags (self->contours[0 ]) & GSK_PATH_CLOSED ? TRUE : FALSE ;
}
/**
* gsk_path_get_bounds :
* @ self : a path
* @ bounds : ( out caller - allocates ) : return location for the bounds
*
* Computes the bounds of the given path .
*
* The returned bounds may be larger than necessary , because this
* function aims to be fast , not accurate . The bounds are guaranteed
* to contain the path . For accurate bounds , use
* [ method @ Gsk . Path . get_tight_bounds ] .
*
* It is possible that the returned rectangle has 0 width and / or height .
* This can happen when the path only describes a point or an
* axis - aligned line .
*
* If the path is empty , false is returned and @ bounds are set to
* graphene_rect_zero ( ) . This is different from the case where the path
* is a single point at the origin , where the @ bounds will also be set to
* the zero rectangle but true will be returned .
*
* Returns : true if the path has bounds , false if the path is known
* to be empty and have no bounds
*
* Since : 4 . 14
*/
gboolean
gsk_path_get_bounds (GskPath *self,
graphene_rect_t *bounds)
{
GskBoundingBox b;
g_return_val_if_fail (self != NULL, FALSE );
g_return_val_if_fail (bounds != NULL, FALSE );
if (self->n_contours == 0 )
{
graphene_rect_init_from_rect (bounds, graphene_rect_zero ());
return FALSE ;
}
gsk_contour_get_bounds (self->contours[0 ], &b);
for (gsize i = 1 ; i < self->n_contours; i++)
{
GskBoundingBox tmp;
gsk_contour_get_bounds (self->contours[i], &tmp);
gsk_bounding_box_union (&b, &tmp, &b);
}
gsk_bounding_box_to_rect (&b, bounds);
return TRUE ;
}
/**
* gsk_path_get_tight_bounds :
* @ self : a path
* @ bounds : ( out caller - allocates ) : return location for the bounds
*
* Computes the tight bounds of the given path .
*
* This function works harder than [ method @ Gsk . Path . get_bounds ] to
* produce the smallest possible bounds .
*
* Returns : true if the path has bounds , false if the path is known
* to be empty and have no bounds
*
* Since : 4 . 22
*/
gboolean
gsk_path_get_tight_bounds (GskPath *self,
graphene_rect_t *bounds)
{
GskBoundingBox b;
g_return_val_if_fail (self != NULL, FALSE );
g_return_val_if_fail (bounds != NULL, FALSE );
if (self->n_contours == 0 )
{
graphene_rect_init_from_rect (bounds, graphene_rect_zero ());
return FALSE ;
}
gsk_contour_get_tight_bounds (self->contours[0 ], &b);
for (gsize i = 1 ; i < self->n_contours; i++)
{
GskBoundingBox tmp;
gsk_contour_get_tight_bounds (self->contours[i], &tmp);
gsk_bounding_box_union (&b, &tmp, &b);
}
gsk_bounding_box_to_rect (&b, bounds);
return TRUE ;
}
/**
* gsk_path_get_stroke_bounds :
* @ self : a path
* @ stroke : stroke parameters
* @ bounds : ( out caller - allocates ) : the bounds to fill in
*
* Computes the bounds for stroking the given path with the
* given parameters .
*
* The returned bounds may be larger than necessary , because this
* function aims to be fast , not accurate . The bounds are guaranteed
* to contain the area affected by the stroke , including protrusions
* like miters .
*
* Returns : true if the path has bounds , false if the path is known
* to be empty and have no bounds .
*
* Since : 4 . 14
*/
gboolean
gsk_path_get_stroke_bounds (GskPath *self,
const GskStroke *stroke,
graphene_rect_t *bounds)
{
GskBoundingBox b;
g_return_val_if_fail (self != NULL, FALSE );
g_return_val_if_fail (bounds != NULL, FALSE );
if (self->n_contours == 0 )
{
graphene_rect_init_from_rect (bounds, graphene_rect_zero ());
return FALSE ;
}
gsk_contour_get_stroke_bounds (self->contours[0 ], stroke, &b);
for (gsize i = 1 ; i < self->n_contours; i++)
{
GskBoundingBox tmp;
if (gsk_contour_get_stroke_bounds (self->contours[i], stroke, &tmp))
gsk_bounding_box_union (&b, &tmp, &b);
}
gsk_bounding_box_to_rect (&b, bounds);
return TRUE ;
}
/**
* gsk_path_in_fill :
* @ self : a path
* @ point : the point to test
* @ fill_rule : the fill rule to follow
*
* Returns whether a point is inside the fill area of a path .
*
* Note that this function assumes that filling a contour
* implicitly closes it .
*
* Returns : true if @ point is inside
*
* Since : 4 . 14
*/
gboolean
gsk_path_in_fill (GskPath *self,
const graphene_point_t *point,
GskFillRule fill_rule)
{
int winding = 0 ;
for (int i = 0 ; i < self->n_contours; i++)
winding += gsk_contour_get_winding (self->contours[i], point);
switch (fill_rule)
{
case GSK_FILL_RULE_EVEN_ODD:
return winding & 1 ;
case GSK_FILL_RULE_WINDING:
return winding != 0 ;
default :
g_assert_not_reached ();
}
}
/**
* gsk_path_get_start_point :
* @ self : a path
* @ result : ( out caller - allocates ) : return location for point
*
* Gets the start point of the path .
*
* An empty path has no points , so false
* is returned in this case .
*
* Returns : true if @ result was filled
*
* Since : 4 . 14
*/
gboolean
gsk_path_get_start_point (GskPath *self,
GskPathPoint *result)
{
g_return_val_if_fail (self != NULL, FALSE );
g_return_val_if_fail (result != NULL, FALSE );
if (self->n_contours == 0 )
return FALSE ;
/* Conceptually, there is always a move at the
* beginning , which jumps from where to the start
* point of the contour , so we use idx = = 1 here .
*/
result->contour = 0 ;
result->idx = 1 ;
result->idx = MIN (1 , gsk_contour_get_n_ops (self->contours[0 ]) - 1 );
result->t = 0 ;
return TRUE ;
}
/**
* gsk_path_get_end_point :
* @ self : a path
* @ result : ( out caller - allocates ) : return location for point
*
* Gets the end point of the path .
*
* An empty path has no points , so false
* is returned in this case .
*
* Returns : true if @ result was filled
*
* Since : 4 . 14
*/
gboolean
gsk_path_get_end_point (GskPath *self,
GskPathPoint *result)
{
g_return_val_if_fail (self != NULL, FALSE );
g_return_val_if_fail (result != NULL, FALSE );
if (self->n_contours == 0 )
return FALSE ;
result->contour = self->n_contours - 1 ;
result->idx = gsk_contour_get_n_ops (self->contours[self->n_contours - 1 ]) - 1 ;
result->t = 1 ;
return TRUE ;
}
/**
* gsk_path_get_next :
* @ self : a path
* @ point : ( inout ) : the current point
*
* Moves @ point to the next vertex .
*
* An empty path has no points , so false
* is returned in this case .
*
* Returns : true if @ point was set
*
* Since : 4 . 22
*/
gboolean
gsk_path_get_next (GskPath *self,
GskPathPoint *point)
{
g_return_val_if_fail (self != NULL, FALSE );
g_return_val_if_fail (point != NULL, FALSE );
if (self->n_contours == 0 )
return FALSE ;
if (point->t < 1 )
{
point->t = 1 ;
}
else if (point->idx < gsk_contour_get_n_ops (self->contours[point->contour]) - 1 )
{
point->idx++;
}
else if (point->contour < self->n_contours - 1 )
{
point->contour++;
point->idx = 0 ;
point->t = 0 ;
}
else
{
return FALSE ;
}
return TRUE ;
}
/**
* gsk_path_get_previous :
* @ self : a path
* @ point : ( inout ) : the current point
*
* Moves @ point to the previous vertex .
*
* An empty path has no points , so false
* is returned in this case .
*
* Returns : true if @ point was set
*
* Since : 4 . 22
*/
gboolean
gsk_path_get_previous (GskPath *self,
GskPathPoint *point)
{
g_return_val_if_fail (self != NULL, FALSE );
g_return_val_if_fail (point != NULL, FALSE );
if (self->n_contours == 0 )
return FALSE ;
if (point->t > 0 )
{
point->t = 0 ;
}
else if (point->idx > 0 )
{
point->idx--;
}
else if (point->contour > 0 )
{
point->contour--;
point->idx = gsk_contour_get_n_ops (self->contours[point->contour]) - 1 ;
point->t = 1 ;
}
else
{
return FALSE ;
}
return TRUE ;
}
/**
* gsk_path_get_closest_point :
* @ self : a path
* @ point : the point
* @ threshold : maximum allowed distance
* @ result : ( out caller - allocates ) : return location for the closest point
* @ distance : ( out ) ( optional ) : return location for the distance
*
* Computes the closest point on the path to the given point .
*
* If there is no point closer than the given threshold ,
* false is returned .
*
* Returns : true if @ point was set to the closest point
* on @ self , false if no point is closer than @ threshold
*
* Since : 4 . 14
*/
gboolean
gsk_path_get_closest_point (GskPath *self,
const graphene_point_t *point,
float threshold,
GskPathPoint *result,
float *distance)
{
gboolean found;
g_return_val_if_fail (self != NULL, FALSE );
g_return_val_if_fail (point != NULL, FALSE );
g_return_val_if_fail (threshold >= 0 , FALSE );
g_return_val_if_fail (result != NULL, FALSE );
found = FALSE ;
for (int i = 0 ; i < self->n_contours; i++)
{
float dist;
if (gsk_contour_get_closest_point (self->contours[i], point, threshold, result, &dist))
{
found = TRUE ;
g_assert (0 <= result->t && result->t <= 1 );
result->contour = i;
threshold = dist;
if (distance)
*distance = dist;
}
}
return found;
}
/**
* gsk_path_equal :
* @ path1 : a path
* @ path2 : another path
*
* Returns whether two paths have identical structure .
*
* Note that it is possible to construct paths that render
* identical even though they don ' t have the same structure .
*
* Returns : true if @ path1 and @ path2 have identical structure
*
* Since : 4 . 22
*/
gboolean
gsk_path_equal (const GskPath *path1,
const GskPath *path2)
{
if (path1 == path2)
return TRUE ;
if (path1->n_contours != path2->n_contours)
return FALSE ;
for (int i = 0 ; i < path1->n_contours; i++)
if (!gsk_contour_equal (path1->contours[i], path2->contours[i]))
return FALSE ;
return TRUE ;
}
/* }}} */
/* {{{ Foreach and decomposition */
/**
* gsk_path_foreach :
* @ self : a path
* @ flags : flags to pass to the foreach function
* @ 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 .
*
* Note that this may only approximate @ self , because paths can contain
* optimizations for various specialized contours , and depending on the
* @ flags , the path may be decomposed into simpler curves than the ones
* that it contained originally .
*
* This function serves two purposes :
*
* - When the @ flags allow everything , it provides access to the raw ,
* unmodified data of the path .
* - When the @ flags disallow certain operations , it provides
* an approximation of the path using just the allowed operations .
*
* Returns : false if @ func returned false , true otherwise .
*
* Since : 4 . 14
*/
gboolean
gsk_path_foreach (GskPath *self,
GskPathForeachFlags flags,
GskPathForeachFunc func,
gpointer user_data)
{
g_return_val_if_fail (self != NULL, FALSE );
g_return_val_if_fail (func, FALSE );
return gsk_path_foreach_with_tolerance (self,
flags,
GSK_PATH_TOLERANCE_DEFAULT,
func,
user_data);
}
typedef struct _GskPathForeachTrampoline GskPathForeachTrampoline;
struct _GskPathForeachTrampoline
{
GskPathForeachFlags flags;
double tolerance;
GskPathForeachFunc func;
gpointer user_data;
};
static gboolean
gsk_path_foreach_trampoline_add_line (const graphene_point_t *from,
const graphene_point_t *to,
float from_progress,
float to_progress,
GskCurveLineReason reason,
gpointer data)
{
GskPathForeachTrampoline *trampoline = data;
return trampoline->func (GSK_PATH_LINE,
(graphene_point_t[2 ]) { *from, *to },
2 ,
0 .f,
trampoline->user_data);
}
static gboolean
gsk_path_foreach_trampoline_add_curve (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
GskPathForeachTrampoline *trampoline = data;
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
}
static gboolean
gsk_path_foreach_trampoline (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
GskPathForeachTrampoline *trampoline = data;
GskAlignedPoint *aligned = g_alloca (sizeof (graphene_point_t) * n_pts);
/* We can't necessarily guarantee that pts is 8-byte aligned
* ( probably it is , but we ' ve been through too many layers of
* indirection to be sure ) so copy it into a buffer that is
* definitely suitably-aligned. */
memcpy (aligned, pts, sizeof (graphene_point_t) * n_pts);
switch (op)
{
case GSK_PATH_MOVE:
case GSK_PATH_CLOSE:
case GSK_PATH_LINE:
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
case GSK_PATH_QUAD:
{
GskCurve curve;
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_QUAD)
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
else if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
{
return trampoline->func (GSK_PATH_CUBIC,
(graphene_point_t[4 ]) {
pts[0 ],
GRAPHENE_POINT_INIT ((pts[0 ].x + 2 * pts[1 ].x) / 3 ,
(pts[0 ].y + 2 * pts[1 ].y) / 3 ),
GRAPHENE_POINT_INIT ((pts[2 ].x + 2 * pts[1 ].x) / 3 ,
(pts[2 ].y + 2 * pts[1 ].y) / 3 ),
pts[2 ],
},
4 ,
weight,
trampoline->user_data);
}
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_QUAD, aligned));
return gsk_curve_decompose (&curve,
trampoline->tolerance,
gsk_path_foreach_trampoline_add_line,
trampoline);
}
case GSK_PATH_CUBIC:
{
GskCurve curve;
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_CUBIC, aligned));
if (trampoline->flags & (GSK_PATH_FOREACH_ALLOW_QUAD|GSK_PATH_FOREACH_ALLOW_CONIC))
return gsk_curve_decompose_curve (&curve,
trampoline->flags,
trampoline->tolerance,
gsk_path_foreach_trampoline_add_curve,
trampoline);
return gsk_curve_decompose (&curve,
trampoline->tolerance,
gsk_path_foreach_trampoline_add_line,
trampoline);
}
case GSK_PATH_CONIC:
{
GskCurve curve;
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CONIC)
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_CONIC, (GskAlignedPoint[4 ]) { { pts[0 ] }, { pts[1 ] }, { { weight, 0 .f } }, { pts[2 ] } } ));
if (trampoline->flags & (GSK_PATH_FOREACH_ALLOW_QUAD|GSK_PATH_FOREACH_ALLOW_CUBIC))
return gsk_curve_decompose_curve (&curve,
trampoline->flags,
trampoline->tolerance,
gsk_path_foreach_trampoline_add_curve,
trampoline);
return gsk_curve_decompose (&curve,
trampoline->tolerance,
gsk_path_foreach_trampoline_add_line,
trampoline);
}
default :
g_assert_not_reached ();
return FALSE ;
}
}
#define ALLOW_ANY (GSK_PATH_FOREACH_ALLOW_QUAD | \
GSK_PATH_FOREACH_ALLOW_CUBIC | \
GSK_PATH_FOREACH_ALLOW_CONIC)
gboolean
gsk_path_foreach_with_tolerance (GskPath *self,
GskPathForeachFlags flags,
double tolerance,
GskPathForeachFunc func,
gpointer user_data)
{
GskPathForeachTrampoline trampoline;
gsize i;
/* If we need to massage the data, set up a trampoline here */
if ((flags & ALLOW_ANY) != ALLOW_ANY)
{
trampoline = (GskPathForeachTrampoline) { flags, tolerance, func, user_data };
func = gsk_path_foreach_trampoline;
user_data = &trampoline;
}
for (i = 0 ; i < self->n_contours; i++)
{
if (!gsk_contour_foreach (self->contours[i], func, user_data))
return FALSE ;
}
return TRUE ;
}
/* }}} */
/* vim:set foldmethod=marker: */
Messung V0.5 in Prozent C=98 H=96 G=96
¤ Dauer der Verarbeitung: 0.35 Sekunden
(vorverarbeitet am 2026-07-03)
¤
*© Formatika GbR, Deutschland