Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  gskpathbuilder.c

  Sprache: 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 <math.h>

#include "gskpathbuilder.h"

#include "gskpathprivate.h"
#include "gskcurveprivate.h"
#include "gskpathpointprivate.h"
#include "gskcontourprivate.h"

/**
 * GskPathBuilder:
 *
 * Constructs `GskPath` objects.
 *
 * A path is constructed like this:
 *
 * ```c
 * GskPath *
 * construct_path (void)
 * {
 *   GskPathBuilder *builder;
 *
 *   builder = gsk_path_builder_new ();
 *
 *   // add contours to the path here
 *
 *   return gsk_path_builder_free_to_path (builder);
 * ```
 *
 * Adding contours to the path can be done in two ways.
 * The easiest option is to use the `gsk_path_builder_add_*` group
 * of functions that add predefined contours to the current path,
 * either common shapes like [method@Gsk.PathBuilder.add_circle]
 * or by adding from other paths like [method@Gsk.PathBuilder.add_path].
 *
 * The `gsk_path_builder_add_*` methods always add complete contours,
 * and do not use or modify the current point.
 *
 * The other option is to define each line and curve manually with
 * the `gsk_path_builder_*_to` group of functions. You start with
 * a call to [method@Gsk.PathBuilder.move_to] to set the starting point
 * and then use multiple calls to any of the drawing functions to
 * move the pen along the plane. Once you are done, you can call
 * [method@Gsk.PathBuilder.close] to close the path by connecting it
 * back with a line to the starting point.
 *
 * This is similar to how paths are drawn in Cairo.
 *
 * Note that `GskPathBuilder` will reduce the degree of added Bézier
 * curves as much as possible, to simplify rendering.
 *
 * Since: 4.14
 */


#define GDK_ARRAY_NAME gsk_path_ops
#define GDK_ARRAY_TYPE_NAME GskPathOps
#define GDK_ARRAY_ELEMENT_TYPE gskpathop
#define GDK_ARRAY_BY_VALUE 1
#define GDK_ARRAY_PREALLOC 150
#define GDK_ARRAY_NO_MEMSET 1
#include "gdk/gdkarrayimpl.c"

#define GDK_ARRAY_NAME gsk_points
#define GDK_ARRAY_TYPE_NAME GskPoints
#define GDK_ARRAY_ELEMENT_TYPE GskAlignedPoint
#define GDK_ARRAY_BY_VALUE 1
#define GDK_ARRAY_PREALLOC 350
#define GDK_ARRAY_NO_MEMSET 1
#include "gdk/gdkarrayimpl.c"


struct _GskPathBuilder
{
  int ref_count;

  GSList *contours; /* (reverse) list of already recorded contours */

  GskPathFlags flags; /* flags for the current path */
  GskAlignedPoint current_point; /* the point all drawing ops start from */
  GskPathOps ops; /* operations for current contour - size == 0 means no current contour */
  GskPoints points; /* points for the operations */
};

/* We want to choose the array preallocations above so that the struct fits in 1 page */
G_STATIC_ASSERT (sizeof (GskPathBuilder) < 4096);

G_DEFINE_BOXED_TYPE (GskPathBuilder,
                     gsk_path_builder,
                     gsk_path_builder_ref,
                     gsk_path_builder_unref)

/**
 * gsk_path_builder_new:
 *
 * Create a new `GskPathBuilder` object.
 *
 * The resulting builder would create an empty `GskPath`.
 * Use addition functions to add types to it.
 *
 * Returns: a new `GskPathBuilder`
 *
 * Since: 4.14
 */

GskPathBuilder *
gsk_path_builder_new (void)
{
  GskPathBuilder *self;

  self = g_slice_new0 (GskPathBuilder);
  self->ref_count = 1;

  gsk_path_ops_init (&self->ops);
  gsk_points_init (&self->points);

  /* Be explicit here */
  self->current_point.pt = GRAPHENE_POINT_INIT (00);

  return self;
}

/**
 * gsk_path_builder_ref:
 * @self: a path builder
 *
 * Acquires a reference on the given builder.
 *
 * This function is intended primarily for language bindings.
 * `GskPathBuilder` objects should not be kept around.
 *
 * Returns: (transfer none): the given path builder with
 *   its reference count increased
 *
 * Since: 4.14
 */

GskPathBuilder *
gsk_path_builder_ref (GskPathBuilder *self)
{
  g_return_val_if_fail (self != NULL, NULL);
  g_return_val_if_fail (self->ref_count > 0, NULL);

  self->ref_count += 1;

  return self;
}

/* We're cheating here. Out pathops are relative to the NULL pointer,
 * so that we can not care about the points array reallocating itself
 * until we create the contour.
 * This does however mean that we need to not use gsk_pathop_get_points()
 * without offsetting the returned pointer.
 */

static inline gskpathop
gsk_pathop_encode_index (GskPathOperation op,
                         gsize            index)
{
  return gsk_pathop_encode (op, ((GskAlignedPoint *) NULL) + index);
}

static void
gsk_path_builder_ensure_current (GskPathBuilder *self)
{
  if (gsk_path_ops_get_size (&self->ops) != 0)
    return;

  self->flags = GSK_PATH_FLAT | GSK_PATH_ZERO_LENGTH;

  gsk_path_ops_append (&self->ops, (gskpathop[1]) { gsk_pathop_encode_index (GSK_PATH_MOVE, 0) });
  gsk_points_append (&self->points, &self->current_point);
}

static void
gsk_path_builder_append_current (GskPathBuilder         *self,
                                 GskPathOperation        op,
                                 gsize                   n_points,
                                 const GskAlignedPoint  *points)
{
  gsk_path_builder_ensure_current (self);

  gsk_path_ops_append (&self->ops, (gskpathop[1]) { gsk_pathop_encode_index (op, gsk_points_get_size (&self->points) - 1) } );
  gsk_points_splice (&self->points, gsk_points_get_size (&self->points), 0FALSE, points, n_points);

  self->current_point = points[n_points - 1];
}

static void
gsk_path_builder_end_current (GskPathBuilder *self)
{
  GskContour *contour;
  graphene_point_t *a, *b;

  if (gsk_path_ops_get_size (&self->ops) == 0)
   return;

  if (gsk_path_ops_get_size (&self->ops) == 1)
    {
      /* empty paths aren't zero-length */
      self->flags &= ~GSK_PATH_ZERO_LENGTH;
    }
  else
    {
      a = &gsk_points_index (&self->points, 0)->pt;
      for (size_t i = 1; i < gsk_points_get_size (&self->points); i++)
        {
          b = &gsk_points_index (&self->points, i)->pt;
          if (a->x != b->x || a->y != b->y)
            {
              self->flags &= ~GSK_PATH_ZERO_LENGTH;
              break;
            }
        }
    }
  contour = gsk_standard_contour_new (self->flags,
                                      gsk_points_get_data (&self->points),
                                      gsk_points_get_size (&self->points),
                                      (gskpathop *) gsk_path_ops_get_data (&self->ops),
                                      gsk_path_ops_get_size (&self->ops),
                                      gsk_points_get_data (&self->points) - (GskAlignedPoint *) NULL);

  gsk_path_ops_set_size (&self->ops, 0);
  gsk_points_set_size (&self->points, 0);

  /* do this at the end to avoid inflooping when add_contour calls back here */
  gsk_path_builder_add_contour (self, contour);
}

static void
gsk_path_builder_clear (GskPathBuilder *self)
{
  gsk_path_builder_end_current (self);

  g_slist_free_full (self->contours, g_free);
  self->contours = NULL;
}

/**
 * gsk_path_builder_unref:
 * @self: a path builder
 *
 * Releases a reference on the given builder.
 *
 * Since: 4.14
 */

void
gsk_path_builder_unref (GskPathBuilder *self)
{
  g_return_if_fail (self != NULL);
  g_return_if_fail (self->ref_count > 0);

  self->ref_count -= 1;

  if (self->ref_count > 0)
    return;

  gsk_path_builder_clear (self);
  gsk_path_ops_clear (&self->ops);
  gsk_points_clear (&self->points);
  g_slice_free (GskPathBuilder, self);
}

/**
 * gsk_path_builder_free_to_path: (skip)
 * @self: (transfer full): a path builder
 *
 * Creates a new path from the current state of the
 * builder, and unrefs the builder.
 *
 * Returns: (transfer full): the newly created path
 *   with all the contours added to the builder
 *
 * Since: 4.14
 */

GskPath *
gsk_path_builder_free_to_path (GskPathBuilder *self)
{
  GskPath *res;

  g_return_val_if_fail (self != NULL, NULL);

  res = gsk_path_builder_to_path (self);

  gsk_path_builder_unref (self);

  return res;
}

/**
 * gsk_path_builder_to_path:
 * @self: a path builder
 *
 * Creates a new path from the given builder.
 *
 * The given `GskPathBuilder` is reset to the initial state once this
 * function returns. Calling this function again on the same builder
 * instance will therefore produce an empty path, not a copy of the same
 * path.
 *
 * This function is intended primarily for language bindings.
 * C code should use [method@Gsk.PathBuilder.free_to_path].
 *
 * Returns: (transfer full): the newly created path
 *   with all the contours added to the builder
 *
 * Since: 4.14
 */

GskPath *
gsk_path_builder_to_path (GskPathBuilder *self)
{
  GskPath *path;

  g_return_val_if_fail (self != NULL, NULL);

  gsk_path_builder_end_current (self);

  self->contours = g_slist_reverse (self->contours);

  path = gsk_path_new_from_contours (self->contours);

  gsk_path_builder_clear (self);

  return path;
}

void
gsk_path_builder_add_contour (GskPathBuilder *self,
                              GskContour     *contour)
{
  gsk_path_builder_end_current (self);

  self->contours = g_slist_prepend (self->contours, contour);
}

/**
 * gsk_path_builder_get_current_point:
 * @self: a path builder
 *
 * Gets the current point.
 *
 * The current point is used for relative drawing commands and
 * updated after every operation.
 *
 * When the builder is created, the default current point is set
 * to `0, 0`. Note that this is different from cairo, which starts
 * out without a current point.
 *
 * Returns: (transfer none): the current point
 *
 * Since: 4.14
 */

const graphene_point_t *
gsk_path_builder_get_current_point (GskPathBuilder *self)
{
  g_return_val_if_fail (self != NULL, NULL);

  return &self->current_point.pt;
}

/**
 * gsk_path_builder_add_path:
 * @self: a path builder
 * @path: (transfer none): the path to append
 *
 * Appends all of @path to the builder.
 *
 * Since: 4.14
 */

void
gsk_path_builder_add_path (GskPathBuilder *self,
                           GskPath        *path)
{
  g_return_if_fail (self != NULL);
  g_return_if_fail (path != NULL);

  for (gsize i = 0; i < gsk_path_get_n_contours (path); i++)
    {
      const GskContour *contour = gsk_path_get_contour (path, i);

      gsk_path_builder_add_contour (self, gsk_contour_dup (contour));
    }
}

/**
 * gsk_path_builder_add_reverse_path:
 * @self: a path builder
 * @path: (transfer none): the path to append
 *
 * Appends all of @path to the builder, in reverse order.
 *
 * Since: 4.14
 */

void
gsk_path_builder_add_reverse_path (GskPathBuilder *self,
                                   GskPath        *path)
{
  g_return_if_fail (self != NULL);
  g_return_if_fail (path != NULL);

  for (gsize i = gsk_path_get_n_contours (path); i > 0; i--)
    {
      const GskContour *contour = gsk_path_get_contour (path, i - 1);

      gsk_path_builder_add_contour (self, gsk_contour_reverse (contour));
    }
}

/**
 * gsk_path_builder_add_cairo_path:
 * @self: a path builder
 * @path: a path
 *
 * Adds a Cairo path to the builder.
 *
 * You can use cairo_copy_path() to access the path
 * from a Cairo context.
 *
 * Since: 4.14
 */

void
gsk_path_builder_add_cairo_path (GskPathBuilder     *self,
                                 const cairo_path_t *path)
{
  GskAlignedPoint current;

  g_return_if_fail (self != NULL);
  g_return_if_fail (path != NULL);

  current = self->current_point;

  for (gsize i = 0; i < path->num_data; i += path->data[i].header.length)
    {
      const cairo_path_data_t *data = &path->data[i];

      switch (data->header.type)
        {
        case CAIRO_PATH_MOVE_TO:
          gsk_path_builder_move_to (self, data[1].point.x, data[1].point.y);
          break;

        case CAIRO_PATH_LINE_TO:
          gsk_path_builder_line_to (self, data[1].point.x, data[1].point.y);
          break;

        case CAIRO_PATH_CURVE_TO:
          gsk_path_builder_cubic_to (self,
                                     data[1].point.x, data[1].point.y,
                                     data[2].point.x, data[2].point.y,
                                     data[3].point.x, data[3].point.y);
          break;

        case CAIRO_PATH_CLOSE_PATH:
          gsk_path_builder_close (self);
          break;

        default:
          g_assert_not_reached ();
          break;
        }
    }

  gsk_path_builder_end_current (self);
  self->current_point = current;
}

/**
 * gsk_path_builder_add_rect:
 * @self: a path builder
 * @rect: the rectangle to create a path for
 *
 * Adds a rectangle as a new contour.
 *
 * The path is going around the rectangle in clockwise direction.
 *
 * If the the width or height are 0, the path will be a closed
 * horizontal or vertical line. If both are 0, it'll be a closed dot.
 *
 * Since: 4.14
 */

void
gsk_path_builder_add_rect (GskPathBuilder        *self,
                           const graphene_rect_t *rect)
{
  graphene_rect_t r;

  g_return_if_fail (self != NULL);
  g_return_if_fail (rect != NULL);

  graphene_rect_normalize_r (rect, &r);
  gsk_path_builder_add_contour (self, gsk_rect_contour_new (&r));
}

/**
 * gsk_path_builder_add_rounded_rect:
 * @self: a path builder
 * @rect: the rounded rect
 *
 * Adds a rounded rectangle as a new contour.
 *
 * The path is going around the rectangle in clockwise direction.
 *
 * Since: 4.14
 */

void
gsk_path_builder_add_rounded_rect (GskPathBuilder       *self,
                                   const GskRoundedRect *rect)
{
  g_return_if_fail (self != NULL);
  g_return_if_fail (rect != NULL);

  gsk_path_builder_add_contour (self, gsk_rounded_rect_contour_new (rect));
}

/**
 * gsk_path_builder_add_circle:
 * @self: a path builder
 * @center: the center of the circle
 * @radius: the radius of the circle
 *
 * Adds a circle as a new contour.
 *
 * The path is going around the circle in clockwise direction.
 *
 * If @radius is zero, the contour will be a closed point.
 *
 * Since: 4.14
 */

void
gsk_path_builder_add_circle (GskPathBuilder         *self,
                             const graphene_point_t *center,
                             float                   radius)
{
  g_return_if_fail (self != NULL);
  g_return_if_fail (center != NULL);
  g_return_if_fail (radius >= 0);

  gsk_path_builder_add_contour (self, gsk_circle_contour_new (center, radius));
}

/**
 * gsk_path_builder_move_to:
 * @self: a path builder
 * @x: x coordinate
 * @y: y coordinate
 *
 * Starts a new contour by placing the pen at @x, @y.
 *
 * If this function is called twice in succession, the first
 * call will result in a contour made up of a single point.
 * The second call will start a new contour.
 *
 * Since: 4.14
 */

void
gsk_path_builder_move_to (GskPathBuilder *self,
                          float           x,
                          float           y)
{
  g_return_if_fail (self != NULL);

  gsk_path_builder_end_current (self);

  self->current_point.pt = GRAPHENE_POINT_INIT(x, y);

  gsk_path_builder_ensure_current (self);
}

/**
 * gsk_path_builder_rel_move_to:
 * @self: a path builder
 * @x: x offset
 * @y: y offset
 *
 * Starts a new contour by placing the pen at @x, @y
 * relative to the current point.
 *
 * This is the relative version of [method@Gsk.PathBuilder.move_to].
 *
 * Since: 4.14
 */

void
gsk_path_builder_rel_move_to (GskPathBuilder *self,
                              float           x,
                              float           y)
{
  g_return_if_fail (self != NULL);

  gsk_path_builder_move_to (self,
                            self->current_point.pt.x + x,
                            self->current_point.pt.y + y);
}

/**
 * gsk_path_builder_line_to:
 * @self: a path builder
 * @x: x coordinate
 * @y: y coordinate
 *
 * Draws a line from the current point to @x, @y and makes it
 * the new current point.
 *
 * <picture>
 *   <source srcset="line-dark.png" media="(prefers-color-scheme: dark)">
 *   <img alt="Line To" src="line-light.png">
 * </picture>
 *
 * Since: 4.14
 */

void
gsk_path_builder_line_to (GskPathBuilder *self,
                          float           x,
                          float           y)
{
  g_return_if_fail (self != NULL);

  gsk_path_builder_append_current (self,
                                   GSK_PATH_LINE,
                                   1, (GskAlignedPoint[1]) {{
                                     GRAPHENE_POINT_INIT (x, y)
                                   }});
}

/**
 * gsk_path_builder_rel_line_to:
 * @self: a path builder
 * @x: x offset
 * @y: y offset
 *
 * Draws a line from the current point to a point offset from it
 * by @x, @y and makes it the new current point.
 *
 * This is the relative version of [method@Gsk.PathBuilder.line_to].
 *
 * Since: 4.14
 */

void
gsk_path_builder_rel_line_to (GskPathBuilder *self,
                              float           x,
                              float           y)
{
  g_return_if_fail (self != NULL);

  gsk_path_builder_line_to (self,
                            self->current_point.pt.x + x,
                            self->current_point.pt.y + y);
}

static inline void
closest_point (const graphene_point_t *p,
               const graphene_point_t *a,
               const graphene_point_t *b,
               graphene_point_t       *q)
{
  graphene_vec2_t n;
  graphene_vec2_t ap;
  float t;

  graphene_vec2_init (&n, b->x - a->x, b->y - a->y);
  graphene_vec2_init (&ap, p->x - a->x, p->y - a->y);

  t = graphene_vec2_dot (&ap, &n) / graphene_vec2_dot (&n, &n);

  q->x = a->x + t * (b->x - a->x);
  q->y = a->y + t * (b->y - a->y);
}

static inline gboolean
collinear (const graphene_point_t *p,
           const graphene_point_t *a,
           const graphene_point_t *b)
{
  graphene_point_t q;

  if (graphene_point_equal (a, b))
    return TRUE;

  closest_point (p, a, b, &q);

  return graphene_point_near (p, &q, 0.001);
}

/**
 * gsk_path_builder_quad_to:
 * @self: a path builder
 * @x1: x coordinate of control point
 * @y1: y coordinate of control point
 * @x2: x coordinate of the end of the curve
 * @y2: y coordinate of the end of the curve
 *
 * Adds a [quadratic Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)
 * from the current point to @x2, @y2 with @x1, @y1 as the control point.
 *
 * After this, @x2, @y2 will be the new current point.
 *
 * <picture>
 *   <source srcset="quad-dark.png" media="(prefers-color-scheme: dark)">
 *   <img alt="Quad To" src="quad-light.png">
 * </picture>
 *
 * Since: 4.14
 */

void
gsk_path_builder_quad_to (GskPathBuilder *self,
                          float           x1,
                          float           y1,
                          float           x2,
                          float           y2)
{
  graphene_point_t p0 = self->current_point.pt;
  graphene_point_t p1 = GRAPHENE_POINT_INIT (x1, y1);
  graphene_point_t p2 = GRAPHENE_POINT_INIT (x2, y2);

  g_return_if_fail (self != NULL);

  if (collinear (&p0, &p1, &p2))
    {
      GskBoundingBox bb;

      /* We simplify degenerate quads to one or two lines */
      if (!gsk_bounding_box_contains_point (gsk_bounding_box_init (&bb, &p0, &p2), &p1))
        {
          GskCurve c;

          gsk_curve_init_foreach (&c, GSK_PATH_QUAD,
                                  (const graphene_point_t []) { p0, p1, p2 },
                                  30.f);
          gsk_curve_get_tight_bounds (&c, &bb);
          for (int i = 0; i < 4; i++)
            {
              graphene_point_t q;

              gsk_bounding_box_get_corner (&bb, i, &q);
              if (graphene_point_equal (&p0, &q) ||
                  graphene_point_equal (&p2, &q))
                {
                  gsk_bounding_box_get_corner (&bb, (i + 2) % 4, &q);
                  gsk_path_builder_line_to (self, q.x, q.y);
                  break;
                }
            }
        }

      gsk_path_builder_line_to (self, x2, y2);

      return;
    }

  self->flags &= ~GSK_PATH_FLAT;
  gsk_path_builder_append_current (self,
                                   GSK_PATH_QUAD,
                                   2, (GskAlignedPoint[2]) {{ p1 }, { p2 }});
}

/**
 * gsk_path_builder_rel_quad_to:
 * @self: a path builder
 * @x1: x offset of control point
 * @y1: y offset of control point
 * @x2: x offset of the end of the curve
 * @y2: y offset of the end of the curve
 *
 * Adds a [quadratic Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)
 * from the current point to @x2, @y2 with @x1, @y1 the control point.
 *
 * All coordinates are given relative to the current point.
 *
 * This is the relative version of [method@Gsk.PathBuilder.quad_to].
 *
 * Since: 4.14
 */

void
gsk_path_builder_rel_quad_to (GskPathBuilder *self,
                              float           x1,
                              float           y1,
                              float           x2,
                              float           y2)
{
  g_return_if_fail (self != NULL);

  gsk_path_builder_quad_to (self,
                            self->current_point.pt.x + x1,
                            self->current_point.pt.y + y1,
                            self->current_point.pt.x + x2,
                            self->current_point.pt.y + y2);
}

static gboolean
point_is_between (const graphene_point_t *q,
                  const graphene_point_t *p0,
                  const graphene_point_t *p1)
{
  return collinear (p0, p1, q) &&
         fabsf (graphene_point_distance (p0, q, NULL, NULL) + graphene_point_distance (p1, q, NULL, NULL) - graphene_point_distance (p0, p1, NULL, NULL)) < 0.001;
}

static gboolean
bounding_box_corner_between (const GskBoundingBox   *bb,
                             const graphene_point_t *p0,
                             const graphene_point_t *p1,
                             graphene_point_t       *p)
{
  for (int i = 0; i < 4; i++)
    {
      graphene_point_t q;

      gsk_bounding_box_get_corner (bb, i, &q);

      if (point_is_between (&q, p0, p1))
        {
          *p = q;
          return TRUE;
        }
    }

  return FALSE;
}


/**
 * gsk_path_builder_cubic_to:
 * @self: a path builder
 * @x1: x coordinate of first control point
 * @y1: y coordinate of first control point
 * @x2: x coordinate of second control point
 * @y2: y coordinate of second control point
 * @x3: x coordinate of the end of the curve
 * @y3: y coordinate of the end of the curve
 *
 * Adds a [cubic Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)
 * from the current point to @x3, @y3 with @x1, @y1 and @x2, @y2 as the control
 * points.
 *
 * After this, @x3, @y3 will be the new current point.
 *
 * <picture>
 *   <source srcset="cubic-dark.png" media="(prefers-color-scheme: dark)">
 *   <img alt="Cubic To" src="cubic-light.png">
 * </picture>
 *
 * Since: 4.14
 */

void
gsk_path_builder_cubic_to (GskPathBuilder *self,
                           float           x1,
                           float           y1,
                           float           x2,
                           float           y2,
                           float           x3,
                           float           y3)
{
  graphene_point_t p0 = self->current_point.pt;
  graphene_point_t p1 = GRAPHENE_POINT_INIT (x1, y1);
  graphene_point_t p2 = GRAPHENE_POINT_INIT (x2, y2);
  graphene_point_t p3 = GRAPHENE_POINT_INIT (x3, y3);
  graphene_point_t p, q;
  gboolean p01, p12, p23;

  g_return_if_fail (self != NULL);

  p01 = graphene_point_equal (&p0, &p1);
  p12 = graphene_point_equal (&p1, &p2);
  p23 = graphene_point_equal (&p2, &p3);

  if (p01 && p12 && p23)
    return;

  if ((p01 && p23) || (p12 && (p01 || p23)))
    {
      gsk_path_builder_line_to (self, x3, y3);
      return;
    }

  if (collinear (&p0, &p1, &p2) &&
      collinear (&p1, &p2, &p3) &&
      (!p12 || collinear (&p0, &p1, &p3)))
    {
      GskBoundingBox bb;
      gboolean p1in, p2in;

      gsk_bounding_box_init (&bb, &p0, &p3);
      p1in = gsk_bounding_box_contains_point (&bb, &p1);
      p2in = gsk_bounding_box_contains_point (&bb, &p2);
      if (p1in && p2in)
        {
          gsk_path_builder_line_to (self, x3, y3);
        }
      else
        {
          GskCurve c;

          gsk_curve_init_foreach (&c,
                                  GSK_PATH_CUBIC,
                                  (const graphene_point_t[]) { p0, p1, p2, p3 },
                                  4,
                                  0.f);
          gsk_curve_get_tight_bounds (&c, &bb);
          if (!p1in)
            {
              /* Find the intersection of bb with p0 - p1.
               * It must be a corner
               */

              bounding_box_corner_between (&bb, &p0, &p1, &p);
              gsk_path_builder_line_to (self, p.x, p.y);
            }
          if (!p2in)
            {
              /* Find the intersection of bb with p2 - p3. */
              bounding_box_corner_between (&bb, &p3, &p2, &p);
              gsk_path_builder_line_to (self, p.x, p.y);
            }
          gsk_path_builder_line_to (self, x3, y3);
        }

      return;
    }

  /* reduce to a quadratic if possible */
  graphene_point_interpolate (&p0, &p1, 1.5, &p);
  graphene_point_interpolate (&p3, &p2, 1.5, &q);
  if (graphene_point_near (&p, &q, 0.001))
    {
      gsk_path_builder_quad_to (self, p.x, p.y, x3, y3);
      return;
    }

  self->flags &= ~GSK_PATH_FLAT;
  gsk_path_builder_append_current (self,
                                   GSK_PATH_CUBIC,
                                   3, (GskAlignedPoint[3]) {{ p1 }, { p2 }, { p3 }});
}

/**
 * gsk_path_builder_rel_cubic_to:
 * @self: a path builder
 * @x1: x offset of first control point
 * @y1: y offset of first control point
 * @x2: x offset of second control point
 * @y2: y offset of second control point
 * @x3: x offset of the end of the curve
 * @y3: y offset of the end of the curve
 *
 * Adds a [cubic Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)
 * from the current point to @x3, @y3 with @x1, @y1 and @x2, @y2 as the control
 * points.
 *
 * All coordinates are given relative to the current point.
 *
 * This is the relative version of [method@Gsk.PathBuilder.cubic_to].
 *
 * Since: 4.14
 */

void
gsk_path_builder_rel_cubic_to (GskPathBuilder *self,
                               float           x1,
                               float           y1,
                               float           x2,
                               float           y2,
                               float           x3,
                               float           y3)
{
  g_return_if_fail (self != NULL);

  gsk_path_builder_cubic_to (self,
                             self->current_point.pt.x + x1,
                             self->current_point.pt.y + y1,
                             self->current_point.pt.x + x2,
                             self->current_point.pt.y + y2,
                             self->current_point.pt.x + x3,
                             self->current_point.pt.y + y3);
}

/**
 * gsk_path_builder_conic_to:
 * @self: a path builder
 * @x1: x coordinate of control point
 * @y1: y coordinate of control point
 * @x2: x coordinate of the end of the curve
 * @y2: y coordinate of the end of the curve
 * @weight: weight of the control point, must be greater than zero
 *
 * Adds a [conic curve](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline)
 * from the current point to @x2, @y2 with the given @weight and @x1, @y1 as the
 * control point.
 *
 * The weight determines how strongly the curve is pulled towards the control point.
 * A conic with weight 1 is identical to a quadratic Bézier curve with the same points.
 *
 * Conic curves can be used to draw ellipses and circles. They are also known as
 * rational quadratic Bézier curves.
 *
 * After this, @x2, @y2 will be the new current point.
 *
 * <picture>
 *   <source srcset="conic-dark.png" media="(prefers-color-scheme: dark)">
 *   <img alt="Conic To" src="conic-light.png">
 * </picture>
 *
 * Since: 4.14
 */

void
gsk_path_builder_conic_to (GskPathBuilder *self,
                           float           x1,
                           float           y1,
                           float           x2,
                           float           y2,
                           float           weight)
{
  graphene_point_t p0 = self->current_point.pt;
  graphene_point_t p1 = GRAPHENE_POINT_INIT (x1, y1);
  graphene_point_t p2 = GRAPHENE_POINT_INIT (x2, y2);

  g_return_if_fail (self != NULL);
  g_return_if_fail (weight > 0);

  if (weight == 1)
    {
      gsk_path_builder_quad_to (self, x1, y1, x2, y2);
      return;
    }

  if (collinear (&p0, &p1, &p2))
    {
      GskBoundingBox bb;

      /* We simplify degenerate quads to one or two lines
       * (two lines are needed if there's a cusp).
       */

      if (!gsk_bounding_box_contains_point (gsk_bounding_box_init (&bb, &p0, &p2), &p1))
        {
          GskCurve c;

          gsk_curve_init_foreach (&c, GSK_PATH_CONIC,
                                  (const graphene_point_t []) { p0, p1, p2 },
                                  3, weight);
          gsk_curve_get_tight_bounds (&c, &bb);
          for (int i = 0; i < 4; i++)
            {
              graphene_point_t q;

              gsk_bounding_box_get_corner (&bb, i, &q);
              if (graphene_point_equal (&p0, &q) ||
                  graphene_point_equal (&p2, &q))
                {
                  gsk_bounding_box_get_corner (&bb, (i + 2) % 4, &q);
                  gsk_path_builder_line_to (self, q.x, q.y);
                  break;
                }
            }
        }

      gsk_path_builder_line_to (self, x2, y2);

      return;
    }

  self->flags &= ~GSK_PATH_FLAT;
  gsk_path_builder_append_current (self,
                                   GSK_PATH_CONIC,
                                   3, (GskAlignedPoint[3]) {
                                     { GRAPHENE_POINT_INIT (x1, y1) },
                                     { GRAPHENE_POINT_INIT (weight, 0) },
                                     { GRAPHENE_POINT_INIT (x2, y2) }
                                   });
}

/**
 * gsk_path_builder_rel_conic_to:
 * @self: a path builder
 * @x1: x offset of control point
 * @y1: y offset of control point
 * @x2: x offset of the end of the curve
 * @y2: y offset of the end of the curve
 * @weight: weight of the curve, must be greater than zero
 *
 * Adds a [conic curve](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline)
 * from the current point to @x2, @y2 with the given @weight and @x1, @y1 as the
 * control point.
 *
 * All coordinates are given relative to the current point.
 *
 * This is the relative version of [method@Gsk.PathBuilder.conic_to].
 *
 * Since: 4.14
 */

void
gsk_path_builder_rel_conic_to (GskPathBuilder *self,
                               float           x1,
                               float           y1,
                               float           x2,
                               float           y2,
                               float           weight)
{
  g_return_if_fail (self != NULL);
  g_return_if_fail (weight > 0);

  gsk_path_builder_conic_to (self,
                             self->current_point.pt.x + x1,
                             self->current_point.pt.y + y1,
                             self->current_point.pt.x + x2,
                             self->current_point.pt.y + y2,
                             weight);
}

/**
 * gsk_path_builder_arc_to:
 * @self: a path builder
 * @x1: x coordinate of first control point
 * @y1: y coordinate of first control point
 * @x2: x coordinate of second control point
 * @y2: y coordinate of second control point
 *
 * Adds an elliptical arc from the current point to @x2, @y2
 * with @x1, @y1 determining the tangent directions.
 *
 * After this, @x2, @y2 will be the new current point.
 *
 * Note: Two points and their tangents do not determine
 * a unique ellipse, so GSK just picks one. If you need more
 * precise control, use [method@Gsk.PathBuilder.conic_to]
 * or [method@Gsk.PathBuilder.svg_arc_to].
 *
 * <picture>
 *   <source srcset="arc-dark.png" media="(prefers-color-scheme: dark)">
 *   <img alt="Arc To" src="arc-light.png">
 * </picture>
 *
 * Since: 4.14
 */

void
gsk_path_builder_arc_to (GskPathBuilder *self,
                         float           x1,
                         float           y1,
                         float           x2,
                         float           y2)
{
  g_return_if_fail (self != NULL);

  gsk_path_builder_conic_to (self, x1, y1, x2, y2, M_SQRT1_2);
}

/**
 * gsk_path_builder_rel_arc_to:
 * @self: a path builder
 * @x1: x coordinate of first control point
 * @y1: y coordinate of first control point
 * @x2: x coordinate of second control point
 * @y2: y coordinate of second control point
 *
 * Adds an elliptical arc from the current point to @x2, @y2
 * with @x1, @y1 determining the tangent directions.
 *
 * All coordinates are given relative to the current point.
 *
 * This is the relative version of [method@Gsk.PathBuilder.arc_to].
 *
 * Since: 4.14
 */

void
gsk_path_builder_rel_arc_to (GskPathBuilder *self,
                             float           x1,
                             float           y1,
                             float           x2,
                             float           y2)
{
  g_return_if_fail (self != NULL);

  gsk_path_builder_arc_to (self,
                           self->current_point.pt.x + x1,
                           self->current_point.pt.y + y1,
                           self->current_point.pt.x + x2,
                           self->current_point.pt.y + y2);
}

/**
 * gsk_path_builder_close:
 * @self: a path builder
 *
 * Ends the current contour with a line back to the start point.
 *
 * Note that this is different from calling [method@Gsk.PathBuilder.line_to]
 * with the start point in that the contour will be closed. A closed
 * contour behaves differently from an open one. When stroking, its
 * start and end point are considered connected, so they will be
 * joined via the line join, and not ended with line caps.
 *
 * Since: 4.14
 */

void
gsk_path_builder_close (GskPathBuilder *self)
{
  g_return_if_fail (self != NULL);

  if (gsk_path_ops_get_size (&self->ops) == 0)
    return;

  self->flags |= GSK_PATH_CLOSED;
  gsk_path_builder_append_current (self,
                                   GSK_PATH_CLOSE,
                                   1, (GskAlignedPoint[1]) {
                                     *gsk_points_index (&self->points, 0)
                                   });

  gsk_path_builder_end_current (self);
}

static void
arc_segment (GskPathBuilder *self,
             double          cx,
             double          cy,
             double          rx,
             double          ry,
             double          sin_phi,
             double          cos_phi,
             double          sin_th0,
             double          cos_th0,
             double          sin_th1,
             double          cos_th1,
             double          t)
{
  double x1, y1, x2, y2, x3, y3;

  x1 = rx * (cos_th0 - t * sin_th0);
  y1 = ry * (sin_th0 + t * cos_th0);
  x3 = rx * cos_th1;
  y3 = ry * sin_th1;
  x2 = x3 + rx * (t * sin_th1);
  y2 = y3 + ry * (-t * cos_th1);

  gsk_path_builder_cubic_to (self,
                             cx + cos_phi * x1 - sin_phi * y1,
                             cy + sin_phi * x1 + cos_phi * y1,
                             cx + cos_phi * x2 - sin_phi * y2,
                             cy + sin_phi * x2 + cos_phi * y2,
                             cx + cos_phi * x3 - sin_phi * y3,
                             cy + sin_phi * x3 + cos_phi * y3);
}

static inline void
_sincos (double angle,
         double *y,
         double *x)
{
#ifdef HAVE_SINCOS
  sincos (angle, y, x);
#else
  *x = cos (angle);
  *y = sin (angle);
#endif
}

/**
 * gsk_path_builder_svg_arc_to:
 * @self: a path builder
 * @rx: x radius
 * @ry: y radius
 * @x_axis_rotation: the rotation of the ellipsis
 * @large_arc: whether to add the large arc
 * @positive_sweep: whether to sweep in the positive direction
 * @x: x coordinate of the endpoint
 * @y: y coordinate of the endpoint
 *
 * Implements arc-to according to the SVG spec.
 *
 * A convenience function that implements the
 * [SVG arc_to](https://www.w3.org/TR/SVG11/paths.html#PathDataEllipticalArcCommands)
 * functionality.
 *
 * After this, @x, @y will be the new current point.
 *
 * Since: 4.14
 */

void
gsk_path_builder_svg_arc_to (GskPathBuilder *self,
                             float           rx,
                             float           ry,
                             float           x_axis_rotation,
                             gboolean        large_arc,
                             gboolean        positive_sweep,
                             float           x,
                             float           y)
{
  graphene_point_t *current;
  double x1, y1, x2, y2;
  double phi, sin_phi, cos_phi;
  double mid_x, mid_y;
  double lambda;
  double d;
  double k;
  double x1_, y1_;
  double cx_, cy_;
  double  cx, cy;
  double ux, uy, u_len;
  double cos_theta1, theta1;
  double vx, vy, v_len;
  double dp_uv;
  double cos_delta_theta, delta_theta;
  int i, n_segs;
  double d_theta, theta;
  double sin_th0, cos_th0;
  double sin_th1, cos_th1;
  double th_half;
  double t;

  g_return_if_fail (self != NULL);

  if (gsk_points_get_size (&self->points) > 0)
    {
      current = &gsk_points_index (&self->points, gsk_points_get_size (&self->points) - 1)->pt;
      x1 = current->x;
      y1 = current->y;
    }
  else
    {
      x1 = 0;
      y1 = 0;
    }
  x2 = x;
  y2 = y;

  phi = x_axis_rotation * M_PI / 180.0;
  _sincos (phi, &sin_phi, &cos_phi);

  rx = fabs (rx);
  ry = fabs (ry);

  mid_x = (x1 - x2) / 2;
  mid_y = (y1 - y2) / 2;

  x1_ = cos_phi * mid_x + sin_phi * mid_y;
  y1_ = - sin_phi * mid_x + cos_phi * mid_y;

  lambda = (x1_ / rx) * (x1_ / rx) + (y1_ / ry) * (y1_ / ry);
  if (lambda > 1)
    {
      lambda = sqrt (lambda);
      rx *= lambda;
      ry *= lambda;
    }

  d = (rx * y1_) * (rx * y1_) + (ry * x1_) * (ry * x1_);
  if (d == 0)
    return;

  k = sqrt (fabs ((rx * ry) * (rx * ry) / d - 1.0));
  if (positive_sweep == large_arc)
    k = -k;

  cx_ = k * rx * y1_ / ry;
  cy_ = -k * ry * x1_ / rx;

  cx = cos_phi * cx_ - sin_phi * cy_ + (x1 + x2) / 2;
  cy = sin_phi * cx_ + cos_phi * cy_ + (y1 + y2) / 2;

  ux = (x1_ - cx_) / rx;
  uy = (y1_ - cy_) / ry;
  u_len = sqrt (ux * ux + uy * uy);
  if (u_len == 0)
    return;

  cos_theta1 = CLAMP (ux / u_len, -11);
  theta1 = acos (cos_theta1);
  if (uy < 0)
    theta1 = - theta1;

  vx = (- x1_ - cx_) / rx;
  vy = (- y1_ - cy_) / ry;
  v_len = sqrt (vx * vx + vy * vy);
  if (v_len == 0)
    return;

  dp_uv = ux * vx + uy * vy;
  cos_delta_theta = CLAMP (dp_uv / (u_len * v_len), -11);
  delta_theta = acos (cos_delta_theta);
  if (ux * vy - uy * vx < 0)
    delta_theta = - delta_theta;
  if (positive_sweep && delta_theta < 0)
    delta_theta += 2 * M_PI;
  else if (!positive_sweep && delta_theta > 0)
    delta_theta -= 2 * M_PI;

  n_segs = ceil (fabs (delta_theta / (M_PI_2 + 0.001)));
  d_theta = delta_theta / n_segs;
  _sincos (theta1, &sin_th1, &cos_th1);

  th_half = d_theta / 2;
  t = (8.0 / 3.0) * sin (th_half / 2) * sin (th_half / 2) / sin (th_half);

  for (i = 0; i < n_segs; i++)
    {
      theta = theta1;
      theta1 = theta + d_theta;
      sin_th0 = sin_th1;
      cos_th0 = cos_th1;
      _sincos (theta1, &sin_th1, &cos_th1);
      arc_segment (self,
                   cx, cy, rx, ry,
                   sin_phi, cos_phi,
                   sin_th0, cos_th0,
                   sin_th1, cos_th1,
                   t);
    }
}

/**
 * gsk_path_builder_rel_svg_arc_to:
 * @self: a path builder
 * @rx: x radius
 * @ry: y radius
 * @x_axis_rotation: the rotation of the ellipsis
 * @large_arc: whether to add the large arc
 * @positive_sweep: whether to sweep in the positive direction
 * @x: x coordinate of the endpoint
 * @y: y coordinate of the endpoint
 *
 * Implements arc-to according to the SVG spec.
 *
 * All coordinates are given relative to the current point.
 *
 * This is the relative version of [method@Gsk.PathBuilder.svg_arc_to].
 *
 * Since: 4.14
 */

void
gsk_path_builder_rel_svg_arc_to (GskPathBuilder *self,
                                 float           rx,
                                 float           ry,
                                 float           x_axis_rotation,
                                 gboolean        large_arc,
                                 gboolean        positive_sweep,
                                 float           x,
                                 float           y)
{
  gsk_path_builder_svg_arc_to (self,
                               rx, ry,
                               x_axis_rotation,
                               large_arc,
                               positive_sweep,
                               self->current_point.pt.x + x,
                               self->current_point.pt.y + y);
}

/* Return the angle between t1 and t2 in radians, such that
 * 0 means straight continuation
 * < 0 means right turn
 * > 0 means left turn
 */

static float
angle_between (const graphene_vec2_t *t1,
               const graphene_vec2_t *t2)
{
  float angle = atan2 (graphene_vec2_get_y (t2), graphene_vec2_get_x (t2))
                - atan2 (graphene_vec2_get_y (t1), graphene_vec2_get_x (t1));

  if (angle > M_PI)
    angle -= 2 * M_PI;
  if (angle < - M_PI)
    angle += 2 * M_PI;

  return angle;
}

#define RAD_TO_DEG(r) ((r)*180.f/M_PI)
#define DEG_TO_RAD(d) ((d)*M_PI/180.f)

static float
angle_between_points (const graphene_point_t *c,
                      const graphene_point_t *a,
                      const graphene_point_t *b)
{
  graphene_vec2_t t1, t2;

  graphene_vec2_init (&t1, a->x - c->x, a->y - c->y);
  graphene_vec2_init (&t2, b->x - c->x, b->y - c->y);

  return (float) RAD_TO_DEG (angle_between (&t1, &t2));
}

/**
 * gsk_path_builder_html_arc_to:
 * @self: a path builder
 * @x1: x coordinate of first control point
 * @y1: y coordinate of first control point
 * @x2: x coordinate of second control point
 * @y2: y coordinate of second control point
 * @radius: radius of the circle
 *
 * Implements arc-to according to the HTML Canvas spec.
 *
 * A convenience function that implements the
 * [HTML arc_to](https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-arcto-dev)
 * functionality.
 *
 * After this, the current point will be the point where
 * the circle with the given radius touches the line from
 * @x1, @y1 to @x2, @y2.
 *
 * Since: 4.14
 */

void
gsk_path_builder_html_arc_to (GskPathBuilder *self,
                              float           x1,
                              float           y1,
                              float           x2,
                              float           y2,
                              float           radius)
{
  float angle, b;
  graphene_vec2_t t;
  graphene_point_t p, q;

  g_return_if_fail (self != NULL);
  g_return_if_fail (radius > 0);

  angle = angle_between_points (&GRAPHENE_POINT_INIT (x1, y1),
                                &self->current_point.pt,
                                &GRAPHENE_POINT_INIT (x2, y2));

  if (fabsf (angle) < 3)
    {
      gsk_path_builder_line_to (self, x2, y2);
      return;
    }

  b = radius / tanf (fabsf ((float) DEG_TO_RAD (angle / 2)));

  graphene_vec2_init (&t, self->current_point.pt.x - x1, self->current_point.pt.y - y1);
  graphene_vec2_normalize (&t, &t);

  p.x = x1 + b * graphene_vec2_get_x (&t);
  p.y = y1 + b * graphene_vec2_get_y (&t);

  graphene_vec2_init (&t, x2 - x1, y2 - y1);
  graphene_vec2_normalize (&t, &t);

  q.x = x1 + b * graphene_vec2_get_x (&t);
  q.y = y1 + b * graphene_vec2_get_y (&t);

  gsk_path_builder_line_to (self, p.x, p.y);

  gsk_path_builder_svg_arc_to (self, radius, radius, 0FALSE, angle < 0, q.x, q.y);
}

/**
 * gsk_path_builder_rel_html_arc_to:
 * @self: a path builder
 * @x1: x coordinate of first control point
 * @y1: y coordinate of first control point
 * @x2: x coordinate of second control point
 * @y2: y coordinate of second control point
 * @radius: radius of the circle
 *
 * Implements arc-to according to the HTML Canvas spec.
 *
 * All coordinates are given relative to the current point.
 *
 * This is the relative version of [method@Gsk.PathBuilder.html_arc_to].
 *
 * Since: 4.14
 */

void
gsk_path_builder_rel_html_arc_to (GskPathBuilder *self,
                                  float           x1,
                                  float           y1,
                                  float           x2,
                                  float           y2,
                                  float           radius)
{
  gsk_path_builder_html_arc_to (self,
                                self->current_point.pt.x + x1,
                                self->current_point.pt.y + y1,
                                self->current_point.pt.x + x2,
                                self->current_point.pt.y + y2,
                                radius);
}

/**
 * gsk_path_builder_add_layout:
 * @self: a path builder
 * @layout: the pango layout to add
 *
 * Adds the outlines for the glyphs in @layout to the builder.
 *
 * Since: 4.14
 */

void
gsk_path_builder_add_layout (GskPathBuilder *self,
                             PangoLayout    *layout)
{
  cairo_surface_t *surface;
  cairo_t *cr;
  cairo_path_t *cairo_path;

  surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
  cr = cairo_create (surface);

  pango_cairo_layout_path (cr, layout);
  cairo_path = cairo_copy_path (cr);

  gsk_path_builder_add_cairo_path (self, cairo_path);

  cairo_path_destroy (cairo_path);
  cairo_destroy (cr);
  cairo_surface_destroy (surface);
}

/**
 * gsk_path_builder_add_segment:
 * @self: a path builder
 * @path: the path to take the segment to
 * @start: the point on @path to start at
 * @end: the point on @path to end at
 *
 * Adds a segment of a path to the builder.
 *
 * If @start is equal to or after @end, the path will first add the
 * segment from @start to the end of the path, and then add the segment
 * from the beginning to @end. If the path is closed, these segments
 * will be connected.
 *
 * Note that this method always adds a path with the given start point
 * and end point. To add a closed path, use [method@Gsk.PathBuilder.add_path].
 *
 * Since: 4.14
 */

void
gsk_path_builder_add_segment (GskPathBuilder     *self,
                              GskPath            *path,
                              const GskPathPoint *start,
                              const GskPathPoint *end)
{
  const GskContour *contour;
  gsize n_contours = gsk_path_get_n_contours (path);
  GskAlignedPoint current;
  gsize n_ops;

  g_return_if_fail (self != NULL);
  g_return_if_fail (path != NULL);
  g_return_if_fail (gsk_path_point_valid (start, path));
  g_return_if_fail (gsk_path_point_valid (end, path));

  current = self->current_point;

  contour = gsk_path_get_contour (path, start->contour);
  n_ops = gsk_contour_get_n_ops (contour);

  if (start->contour == end->contour)
    {
      if (gsk_path_point_compare (start, end) < 0)
        {
          gsk_contour_add_segment (contour, self, TRUE, start, end);
          goto out;
        }
      else if (n_contours == 1)
        {
          if (n_ops > 1)
            gsk_contour_add_segment (contour, self, TRUE,
                                     start,
                                     &GSK_PATH_POINT_INIT (start->contour, n_ops - 11.f));
          gsk_contour_add_segment (contour, self, n_ops <= 1,
                                   &GSK_PATH_POINT_INIT (start->contour, 10.f),
                                   end);
          goto out;
        }
    }

  if (n_ops > 1)
    gsk_contour_add_segment (contour, self, TRUE,
                             start,
                             &GSK_PATH_POINT_INIT (start->contour, n_ops - 11.f));

  for (gsize i = (start->contour + 1) % n_contours; i != end->contour; i = (i + 1) % n_contours)
    gsk_path_builder_add_contour (self, gsk_contour_dup (gsk_path_get_contour (path, i)));

  contour = gsk_path_get_contour (path, end->contour);
  n_ops = gsk_contour_get_n_ops (contour);

  if (n_ops > 1)
    gsk_contour_add_segment (contour, self, TRUE,
                             &GSK_PATH_POINT_INIT (end->contour, 10.f),
                             end);

out:
  gsk_path_builder_end_current (self);
  self->current_point = current;
}

void
gsk_path_builder_add_op (GskPathBuilder         *builder,
                         GskPathOperation        op,
                         const graphene_point_t *pts,
                         gsize                   n_pts,
                         float                   weight)
{
  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_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_QUAD:
      gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].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 ();
    }
}

Messung V0.5 in Prozent
C=98 H=94 G=95

¤ Dauer der Verarbeitung: 0.33 Sekunden  (vorverarbeitet am  2026-07-03) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik