1498 lines
50 KiB
C
1498 lines
50 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* pikastroke.c
|
|
* Copyright (C) 2002 Simon Budig <simon@gimp.org>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <cairo.h>
|
|
#include <gegl.h>
|
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
|
|
|
#include "libpikabase/pikabase.h"
|
|
#include "libpikamath/pikamath.h"
|
|
|
|
#include "vectors-types.h"
|
|
|
|
#include "core/pika-memsize.h"
|
|
#include "core/pikacoords.h"
|
|
#include "core/pikaparamspecs.h"
|
|
#include "core/pika-transform-utils.h"
|
|
|
|
#include "pikaanchor.h"
|
|
#include "pikastroke.h"
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CONTROL_POINTS,
|
|
PROP_CLOSED
|
|
};
|
|
|
|
/* Prototypes */
|
|
|
|
static void pika_stroke_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
static void pika_stroke_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
static void pika_stroke_finalize (GObject *object);
|
|
|
|
static gint64 pika_stroke_get_memsize (PikaObject *object,
|
|
gint64 *gui_size);
|
|
|
|
static PikaAnchor * pika_stroke_real_anchor_get (PikaStroke *stroke,
|
|
const PikaCoords *coord);
|
|
static PikaAnchor * pika_stroke_real_anchor_get_next (PikaStroke *stroke,
|
|
const PikaAnchor *prev);
|
|
static void pika_stroke_real_anchor_select (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
gboolean selected,
|
|
gboolean exclusive);
|
|
static void pika_stroke_real_anchor_move_relative (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
const PikaCoords *delta,
|
|
PikaAnchorFeatureType feature);
|
|
static void pika_stroke_real_anchor_move_absolute (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
const PikaCoords *delta,
|
|
PikaAnchorFeatureType feature);
|
|
static void pika_stroke_real_anchor_convert (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
PikaAnchorFeatureType feature);
|
|
static void pika_stroke_real_anchor_delete (PikaStroke *stroke,
|
|
PikaAnchor *anchor);
|
|
static gboolean pika_stroke_real_point_is_movable
|
|
(PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position);
|
|
static void pika_stroke_real_point_move_relative
|
|
(PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position,
|
|
const PikaCoords *deltacoord,
|
|
PikaAnchorFeatureType feature);
|
|
static void pika_stroke_real_point_move_absolute
|
|
(PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position,
|
|
const PikaCoords *coord,
|
|
PikaAnchorFeatureType feature);
|
|
|
|
static void pika_stroke_real_close (PikaStroke *stroke);
|
|
static PikaStroke * pika_stroke_real_open (PikaStroke *stroke,
|
|
PikaAnchor *end_anchor);
|
|
static gboolean pika_stroke_real_anchor_is_insertable
|
|
(PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position);
|
|
static PikaAnchor * pika_stroke_real_anchor_insert (PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position);
|
|
|
|
static gboolean pika_stroke_real_is_extendable (PikaStroke *stroke,
|
|
PikaAnchor *neighbor);
|
|
|
|
static PikaAnchor * pika_stroke_real_extend (PikaStroke *stroke,
|
|
const PikaCoords *coords,
|
|
PikaAnchor *neighbor,
|
|
PikaVectorExtendMode extend_mode);
|
|
|
|
gboolean pika_stroke_real_connect_stroke (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
PikaStroke *extension,
|
|
PikaAnchor *neighbor);
|
|
|
|
|
|
static gboolean pika_stroke_real_is_empty (PikaStroke *stroke);
|
|
static gboolean pika_stroke_real_reverse (PikaStroke *stroke);
|
|
static gboolean pika_stroke_real_shift_start (PikaStroke *stroke,
|
|
PikaAnchor *anchor);
|
|
|
|
static gdouble pika_stroke_real_get_length (PikaStroke *stroke,
|
|
gdouble precision);
|
|
static gdouble pika_stroke_real_get_distance (PikaStroke *stroke,
|
|
const PikaCoords *coord);
|
|
static GArray * pika_stroke_real_interpolate (PikaStroke *stroke,
|
|
gdouble precision,
|
|
gboolean *closed);
|
|
static PikaStroke * pika_stroke_real_duplicate (PikaStroke *stroke);
|
|
static PikaBezierDesc * pika_stroke_real_make_bezier (PikaStroke *stroke);
|
|
|
|
static void pika_stroke_real_translate (PikaStroke *stroke,
|
|
gdouble offset_x,
|
|
gdouble offset_y);
|
|
static void pika_stroke_real_scale (PikaStroke *stroke,
|
|
gdouble scale_x,
|
|
gdouble scale_y);
|
|
static void pika_stroke_real_rotate (PikaStroke *stroke,
|
|
gdouble center_x,
|
|
gdouble center_y,
|
|
gdouble angle);
|
|
static void pika_stroke_real_flip (PikaStroke *stroke,
|
|
PikaOrientationType flip_type,
|
|
gdouble axis);
|
|
static void pika_stroke_real_flip_free (PikaStroke *stroke,
|
|
gdouble x1,
|
|
gdouble y1,
|
|
gdouble x2,
|
|
gdouble y2);
|
|
static void pika_stroke_real_transform (PikaStroke *stroke,
|
|
const PikaMatrix3 *matrix,
|
|
GQueue *ret_strokes);
|
|
|
|
static GList * pika_stroke_real_get_draw_anchors (PikaStroke *stroke);
|
|
static GList * pika_stroke_real_get_draw_controls (PikaStroke *stroke);
|
|
static GArray * pika_stroke_real_get_draw_lines (PikaStroke *stroke);
|
|
static GArray * pika_stroke_real_control_points_get (PikaStroke *stroke,
|
|
gboolean *ret_closed);
|
|
static gboolean pika_stroke_real_get_point_at_dist (PikaStroke *stroke,
|
|
gdouble dist,
|
|
gdouble precision,
|
|
PikaCoords *position,
|
|
gdouble *slope);
|
|
|
|
|
|
G_DEFINE_TYPE (PikaStroke, pika_stroke, PIKA_TYPE_OBJECT)
|
|
|
|
#define parent_class pika_stroke_parent_class
|
|
|
|
|
|
static void
|
|
pika_stroke_class_init (PikaStrokeClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass);
|
|
GParamSpec *param_spec;
|
|
|
|
object_class->finalize = pika_stroke_finalize;
|
|
object_class->get_property = pika_stroke_get_property;
|
|
object_class->set_property = pika_stroke_set_property;
|
|
|
|
pika_object_class->get_memsize = pika_stroke_get_memsize;
|
|
|
|
klass->changed = NULL;
|
|
klass->removed = NULL;
|
|
|
|
klass->anchor_get = pika_stroke_real_anchor_get;
|
|
klass->anchor_get_next = pika_stroke_real_anchor_get_next;
|
|
klass->anchor_select = pika_stroke_real_anchor_select;
|
|
klass->anchor_move_relative = pika_stroke_real_anchor_move_relative;
|
|
klass->anchor_move_absolute = pika_stroke_real_anchor_move_absolute;
|
|
klass->anchor_convert = pika_stroke_real_anchor_convert;
|
|
klass->anchor_delete = pika_stroke_real_anchor_delete;
|
|
|
|
klass->point_is_movable = pika_stroke_real_point_is_movable;
|
|
klass->point_move_relative = pika_stroke_real_point_move_relative;
|
|
klass->point_move_absolute = pika_stroke_real_point_move_absolute;
|
|
|
|
klass->nearest_point_get = NULL;
|
|
klass->nearest_tangent_get = NULL;
|
|
klass->nearest_intersection_get = NULL;
|
|
klass->close = pika_stroke_real_close;
|
|
klass->open = pika_stroke_real_open;
|
|
klass->anchor_is_insertable = pika_stroke_real_anchor_is_insertable;
|
|
klass->anchor_insert = pika_stroke_real_anchor_insert;
|
|
klass->is_extendable = pika_stroke_real_is_extendable;
|
|
klass->extend = pika_stroke_real_extend;
|
|
klass->connect_stroke = pika_stroke_real_connect_stroke;
|
|
|
|
klass->is_empty = pika_stroke_real_is_empty;
|
|
klass->reverse = pika_stroke_real_reverse;
|
|
klass->shift_start = pika_stroke_real_shift_start;
|
|
klass->get_length = pika_stroke_real_get_length;
|
|
klass->get_distance = pika_stroke_real_get_distance;
|
|
klass->get_point_at_dist = pika_stroke_real_get_point_at_dist;
|
|
klass->interpolate = pika_stroke_real_interpolate;
|
|
|
|
klass->duplicate = pika_stroke_real_duplicate;
|
|
klass->make_bezier = pika_stroke_real_make_bezier;
|
|
|
|
klass->translate = pika_stroke_real_translate;
|
|
klass->scale = pika_stroke_real_scale;
|
|
klass->rotate = pika_stroke_real_rotate;
|
|
klass->flip = pika_stroke_real_flip;
|
|
klass->flip_free = pika_stroke_real_flip_free;
|
|
klass->transform = pika_stroke_real_transform;
|
|
|
|
|
|
klass->get_draw_anchors = pika_stroke_real_get_draw_anchors;
|
|
klass->get_draw_controls = pika_stroke_real_get_draw_controls;
|
|
klass->get_draw_lines = pika_stroke_real_get_draw_lines;
|
|
klass->control_points_get = pika_stroke_real_control_points_get;
|
|
|
|
param_spec = g_param_spec_boxed ("pika-anchor",
|
|
"Pika Anchor",
|
|
"The control points of a Stroke",
|
|
PIKA_TYPE_ANCHOR,
|
|
PIKA_PARAM_WRITABLE |
|
|
G_PARAM_CONSTRUCT_ONLY);
|
|
g_object_class_install_property (object_class, PROP_CONTROL_POINTS,
|
|
pika_param_spec_value_array ("control-points",
|
|
"Control Points",
|
|
"This is an ValueArray "
|
|
"with the initial "
|
|
"control points of "
|
|
"the new Stroke",
|
|
param_spec,
|
|
PIKA_PARAM_WRITABLE |
|
|
G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
g_object_class_install_property (object_class, PROP_CLOSED,
|
|
g_param_spec_boolean ("closed",
|
|
"Close Flag",
|
|
"this flag indicates "
|
|
"whether the stroke "
|
|
"is closed or not",
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY));
|
|
}
|
|
|
|
static void
|
|
pika_stroke_init (PikaStroke *stroke)
|
|
{
|
|
stroke->anchors = g_queue_new ();
|
|
}
|
|
|
|
static void
|
|
pika_stroke_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaStroke *stroke = PIKA_STROKE (object);
|
|
PikaValueArray *val_array;
|
|
gint length;
|
|
gint i;
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_CLOSED:
|
|
stroke->closed = g_value_get_boolean (value);
|
|
break;
|
|
|
|
case PROP_CONTROL_POINTS:
|
|
g_return_if_fail (g_queue_is_empty (stroke->anchors));
|
|
g_return_if_fail (value != NULL);
|
|
|
|
val_array = g_value_get_boxed (value);
|
|
|
|
if (val_array == NULL)
|
|
return;
|
|
|
|
length = pika_value_array_length (val_array);
|
|
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
GValue *item = pika_value_array_index (val_array, i);
|
|
|
|
g_return_if_fail (G_VALUE_HOLDS (item, PIKA_TYPE_ANCHOR));
|
|
g_queue_push_tail (stroke->anchors, g_value_dup_boxed (item));
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_stroke_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaStroke *stroke = PIKA_STROKE (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_CLOSED:
|
|
g_value_set_boolean (value, stroke->closed);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_stroke_finalize (GObject *object)
|
|
{
|
|
PikaStroke *stroke = PIKA_STROKE (object);
|
|
|
|
g_queue_free_full (stroke->anchors, (GDestroyNotify) pika_anchor_free);
|
|
stroke->anchors = NULL;
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static gint64
|
|
pika_stroke_get_memsize (PikaObject *object,
|
|
gint64 *gui_size)
|
|
{
|
|
PikaStroke *stroke = PIKA_STROKE (object);
|
|
gint64 memsize = 0;
|
|
|
|
memsize += pika_g_queue_get_memsize (stroke->anchors, sizeof (PikaAnchor));
|
|
|
|
return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object,
|
|
gui_size);
|
|
}
|
|
|
|
void
|
|
pika_stroke_set_id (PikaStroke *stroke,
|
|
gint id)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
g_return_if_fail (stroke->id == 0 /* we don't want changing IDs... */);
|
|
|
|
stroke->id = id;
|
|
}
|
|
|
|
gint
|
|
pika_stroke_get_id (PikaStroke *stroke)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), -1);
|
|
|
|
return stroke->id;
|
|
}
|
|
|
|
|
|
PikaAnchor *
|
|
pika_stroke_anchor_get (PikaStroke *stroke,
|
|
const PikaCoords *coord)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->anchor_get (stroke, coord);
|
|
}
|
|
|
|
|
|
gdouble
|
|
pika_stroke_nearest_point_get (PikaStroke *stroke,
|
|
const PikaCoords *coord,
|
|
const gdouble precision,
|
|
PikaCoords *ret_point,
|
|
PikaAnchor **ret_segment_start,
|
|
PikaAnchor **ret_segment_end,
|
|
gdouble *ret_pos)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE);
|
|
g_return_val_if_fail (coord != NULL, FALSE);
|
|
|
|
if (PIKA_STROKE_GET_CLASS (stroke)->nearest_point_get)
|
|
return PIKA_STROKE_GET_CLASS (stroke)->nearest_point_get (stroke,
|
|
coord,
|
|
precision,
|
|
ret_point,
|
|
ret_segment_start,
|
|
ret_segment_end,
|
|
ret_pos);
|
|
return -1;
|
|
}
|
|
|
|
gdouble
|
|
pika_stroke_nearest_tangent_get (PikaStroke *stroke,
|
|
const PikaCoords *coords1,
|
|
const PikaCoords *coords2,
|
|
gdouble precision,
|
|
PikaCoords *nearest,
|
|
PikaAnchor **ret_segment_start,
|
|
PikaAnchor **ret_segment_end,
|
|
gdouble *ret_pos)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE);
|
|
g_return_val_if_fail (coords1 != NULL, FALSE);
|
|
g_return_val_if_fail (coords2 != NULL, FALSE);
|
|
|
|
if (PIKA_STROKE_GET_CLASS (stroke)->nearest_tangent_get)
|
|
return PIKA_STROKE_GET_CLASS (stroke)->nearest_tangent_get (stroke,
|
|
coords1,
|
|
coords2,
|
|
precision,
|
|
nearest,
|
|
ret_segment_start,
|
|
ret_segment_end,
|
|
ret_pos);
|
|
return -1;
|
|
}
|
|
|
|
gdouble
|
|
pika_stroke_nearest_intersection_get (PikaStroke *stroke,
|
|
const PikaCoords *coords1,
|
|
const PikaCoords *direction,
|
|
gdouble precision,
|
|
PikaCoords *nearest,
|
|
PikaAnchor **ret_segment_start,
|
|
PikaAnchor **ret_segment_end,
|
|
gdouble *ret_pos)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE);
|
|
g_return_val_if_fail (coords1 != NULL, FALSE);
|
|
g_return_val_if_fail (direction != NULL, FALSE);
|
|
|
|
if (PIKA_STROKE_GET_CLASS (stroke)->nearest_intersection_get)
|
|
return PIKA_STROKE_GET_CLASS (stroke)->nearest_intersection_get (stroke,
|
|
coords1,
|
|
direction,
|
|
precision,
|
|
nearest,
|
|
ret_segment_start,
|
|
ret_segment_end,
|
|
ret_pos);
|
|
return -1;
|
|
}
|
|
|
|
static PikaAnchor *
|
|
pika_stroke_real_anchor_get (PikaStroke *stroke,
|
|
const PikaCoords *coord)
|
|
{
|
|
gdouble dx, dy;
|
|
gdouble mindist = -1;
|
|
GList *anchors;
|
|
GList *list;
|
|
PikaAnchor *anchor = NULL;
|
|
|
|
anchors = pika_stroke_get_draw_controls (stroke);
|
|
|
|
for (list = anchors; list; list = g_list_next (list))
|
|
{
|
|
dx = coord->x - PIKA_ANCHOR (list->data)->position.x;
|
|
dy = coord->y - PIKA_ANCHOR (list->data)->position.y;
|
|
|
|
if (mindist < 0 || mindist > dx * dx + dy * dy)
|
|
{
|
|
mindist = dx * dx + dy * dy;
|
|
anchor = PIKA_ANCHOR (list->data);
|
|
}
|
|
}
|
|
|
|
g_list_free (anchors);
|
|
|
|
anchors = pika_stroke_get_draw_anchors (stroke);
|
|
|
|
for (list = anchors; list; list = g_list_next (list))
|
|
{
|
|
dx = coord->x - PIKA_ANCHOR (list->data)->position.x;
|
|
dy = coord->y - PIKA_ANCHOR (list->data)->position.y;
|
|
|
|
if (mindist < 0 || mindist > dx * dx + dy * dy)
|
|
{
|
|
mindist = dx * dx + dy * dy;
|
|
anchor = PIKA_ANCHOR (list->data);
|
|
}
|
|
}
|
|
|
|
g_list_free (anchors);
|
|
|
|
return anchor;
|
|
}
|
|
|
|
|
|
PikaAnchor *
|
|
pika_stroke_anchor_get_next (PikaStroke *stroke,
|
|
const PikaAnchor *prev)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->anchor_get_next (stroke, prev);
|
|
}
|
|
|
|
static PikaAnchor *
|
|
pika_stroke_real_anchor_get_next (PikaStroke *stroke,
|
|
const PikaAnchor *prev)
|
|
{
|
|
GList *list;
|
|
|
|
if (prev)
|
|
{
|
|
list = g_queue_find (stroke->anchors, prev);
|
|
if (list)
|
|
list = g_list_next (list);
|
|
}
|
|
else
|
|
{
|
|
list = stroke->anchors->head;
|
|
}
|
|
|
|
if (list)
|
|
return PIKA_ANCHOR (list->data);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
pika_stroke_anchor_select (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
gboolean selected,
|
|
gboolean exclusive)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
|
|
PIKA_STROKE_GET_CLASS (stroke)->anchor_select (stroke, anchor,
|
|
selected, exclusive);
|
|
}
|
|
|
|
static void
|
|
pika_stroke_real_anchor_select (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
gboolean selected,
|
|
gboolean exclusive)
|
|
{
|
|
GList *list = stroke->anchors->head;
|
|
|
|
if (exclusive)
|
|
{
|
|
while (list)
|
|
{
|
|
PIKA_ANCHOR (list->data)->selected = FALSE;
|
|
list = g_list_next (list);
|
|
}
|
|
}
|
|
|
|
list = g_queue_find (stroke->anchors, anchor);
|
|
|
|
if (list)
|
|
PIKA_ANCHOR (list->data)->selected = selected;
|
|
}
|
|
|
|
|
|
void
|
|
pika_stroke_anchor_move_relative (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
const PikaCoords *delta,
|
|
PikaAnchorFeatureType feature)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
g_return_if_fail (anchor != NULL);
|
|
g_return_if_fail (g_queue_find (stroke->anchors, anchor));
|
|
|
|
PIKA_STROKE_GET_CLASS (stroke)->anchor_move_relative (stroke, anchor,
|
|
delta, feature);
|
|
}
|
|
|
|
static void
|
|
pika_stroke_real_anchor_move_relative (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
const PikaCoords *delta,
|
|
PikaAnchorFeatureType feature)
|
|
{
|
|
anchor->position.x += delta->x;
|
|
anchor->position.y += delta->y;
|
|
}
|
|
|
|
|
|
void
|
|
pika_stroke_anchor_move_absolute (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
const PikaCoords *coord,
|
|
PikaAnchorFeatureType feature)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
g_return_if_fail (anchor != NULL);
|
|
g_return_if_fail (g_queue_find (stroke->anchors, anchor));
|
|
|
|
PIKA_STROKE_GET_CLASS (stroke)->anchor_move_absolute (stroke, anchor,
|
|
coord, feature);
|
|
}
|
|
|
|
static void
|
|
pika_stroke_real_anchor_move_absolute (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
const PikaCoords *coord,
|
|
PikaAnchorFeatureType feature)
|
|
{
|
|
anchor->position.x = coord->x;
|
|
anchor->position.y = coord->y;
|
|
}
|
|
|
|
gboolean
|
|
pika_stroke_point_is_movable (PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->point_is_movable (stroke, predec,
|
|
position);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
pika_stroke_real_point_is_movable (PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
void
|
|
pika_stroke_point_move_relative (PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position,
|
|
const PikaCoords *deltacoord,
|
|
PikaAnchorFeatureType feature)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
|
|
PIKA_STROKE_GET_CLASS (stroke)->point_move_relative (stroke, predec,
|
|
position, deltacoord,
|
|
feature);
|
|
}
|
|
|
|
|
|
static void
|
|
pika_stroke_real_point_move_relative (PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position,
|
|
const PikaCoords *deltacoord,
|
|
PikaAnchorFeatureType feature)
|
|
{
|
|
g_printerr ("pika_stroke_point_move_relative: default implementation\n");
|
|
}
|
|
|
|
|
|
void
|
|
pika_stroke_point_move_absolute (PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position,
|
|
const PikaCoords *coord,
|
|
PikaAnchorFeatureType feature)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
|
|
PIKA_STROKE_GET_CLASS (stroke)->point_move_absolute (stroke, predec,
|
|
position, coord,
|
|
feature);
|
|
}
|
|
|
|
static void
|
|
pika_stroke_real_point_move_absolute (PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position,
|
|
const PikaCoords *coord,
|
|
PikaAnchorFeatureType feature)
|
|
{
|
|
g_printerr ("pika_stroke_point_move_absolute: default implementation\n");
|
|
}
|
|
|
|
|
|
void
|
|
pika_stroke_close (PikaStroke *stroke)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
g_return_if_fail (g_queue_is_empty (stroke->anchors) == FALSE);
|
|
|
|
PIKA_STROKE_GET_CLASS (stroke)->close (stroke);
|
|
}
|
|
|
|
static void
|
|
pika_stroke_real_close (PikaStroke *stroke)
|
|
{
|
|
stroke->closed = TRUE;
|
|
g_object_notify (G_OBJECT (stroke), "closed");
|
|
}
|
|
|
|
|
|
void
|
|
pika_stroke_anchor_convert (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
PikaAnchorFeatureType feature)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
|
|
PIKA_STROKE_GET_CLASS (stroke)->anchor_convert (stroke, anchor, feature);
|
|
}
|
|
|
|
static void
|
|
pika_stroke_real_anchor_convert (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
PikaAnchorFeatureType feature)
|
|
{
|
|
g_printerr ("pika_stroke_anchor_convert: default implementation\n");
|
|
}
|
|
|
|
|
|
void
|
|
pika_stroke_anchor_delete (PikaStroke *stroke,
|
|
PikaAnchor *anchor)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
g_return_if_fail (anchor && anchor->type == PIKA_ANCHOR_ANCHOR);
|
|
|
|
PIKA_STROKE_GET_CLASS (stroke)->anchor_delete (stroke, anchor);
|
|
}
|
|
|
|
static void
|
|
pika_stroke_real_anchor_delete (PikaStroke *stroke,
|
|
PikaAnchor *anchor)
|
|
{
|
|
g_printerr ("pika_stroke_anchor_delete: default implementation\n");
|
|
}
|
|
|
|
PikaStroke *
|
|
pika_stroke_open (PikaStroke *stroke,
|
|
PikaAnchor *end_anchor)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL);
|
|
g_return_val_if_fail (end_anchor &&
|
|
end_anchor->type == PIKA_ANCHOR_ANCHOR, NULL);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->open (stroke, end_anchor);
|
|
}
|
|
|
|
static PikaStroke *
|
|
pika_stroke_real_open (PikaStroke *stroke,
|
|
PikaAnchor *end_anchor)
|
|
{
|
|
g_printerr ("pika_stroke_open: default implementation\n");
|
|
return NULL;
|
|
}
|
|
|
|
gboolean
|
|
pika_stroke_anchor_is_insertable (PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->anchor_is_insertable (stroke,
|
|
predec,
|
|
position);
|
|
}
|
|
|
|
static gboolean
|
|
pika_stroke_real_anchor_is_insertable (PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
PikaAnchor *
|
|
pika_stroke_anchor_insert (PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL);
|
|
g_return_val_if_fail (predec->type == PIKA_ANCHOR_ANCHOR, NULL);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->anchor_insert (stroke,
|
|
predec, position);
|
|
}
|
|
|
|
static PikaAnchor *
|
|
pika_stroke_real_anchor_insert (PikaStroke *stroke,
|
|
PikaAnchor *predec,
|
|
gdouble position)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
gboolean
|
|
pika_stroke_is_extendable (PikaStroke *stroke,
|
|
PikaAnchor *neighbor)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->is_extendable (stroke, neighbor);
|
|
}
|
|
|
|
static gboolean
|
|
pika_stroke_real_is_extendable (PikaStroke *stroke,
|
|
PikaAnchor *neighbor)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
PikaAnchor *
|
|
pika_stroke_extend (PikaStroke *stroke,
|
|
const PikaCoords *coords,
|
|
PikaAnchor *neighbor,
|
|
PikaVectorExtendMode extend_mode)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL);
|
|
g_return_val_if_fail (!stroke->closed, NULL);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->extend (stroke, coords,
|
|
neighbor, extend_mode);
|
|
}
|
|
|
|
static PikaAnchor *
|
|
pika_stroke_real_extend (PikaStroke *stroke,
|
|
const PikaCoords *coords,
|
|
PikaAnchor *neighbor,
|
|
PikaVectorExtendMode extend_mode)
|
|
{
|
|
g_printerr ("pika_stroke_extend: default implementation\n");
|
|
return NULL;
|
|
}
|
|
|
|
gboolean
|
|
pika_stroke_connect_stroke (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
PikaStroke *extension,
|
|
PikaAnchor *neighbor)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE);
|
|
g_return_val_if_fail (PIKA_IS_STROKE (extension), FALSE);
|
|
g_return_val_if_fail (stroke->closed == FALSE &&
|
|
extension->closed == FALSE, FALSE);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->connect_stroke (stroke, anchor,
|
|
extension, neighbor);
|
|
}
|
|
|
|
gboolean
|
|
pika_stroke_real_connect_stroke (PikaStroke *stroke,
|
|
PikaAnchor *anchor,
|
|
PikaStroke *extension,
|
|
PikaAnchor *neighbor)
|
|
{
|
|
g_printerr ("pika_stroke_connect_stroke: default implementation\n");
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean
|
|
pika_stroke_is_empty (PikaStroke *stroke)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->is_empty (stroke);
|
|
}
|
|
|
|
static gboolean
|
|
pika_stroke_real_is_empty (PikaStroke *stroke)
|
|
{
|
|
return g_queue_is_empty (stroke->anchors);
|
|
}
|
|
|
|
|
|
gboolean
|
|
pika_stroke_reverse (PikaStroke *stroke)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->reverse (stroke);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
pika_stroke_real_reverse (PikaStroke *stroke)
|
|
{
|
|
g_queue_reverse (stroke->anchors);
|
|
|
|
/* keep the first node the same for closed strokes */
|
|
if (stroke->closed && stroke->anchors->length > 0)
|
|
g_queue_push_head_link (stroke->anchors,
|
|
g_queue_pop_tail_link (stroke->anchors));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
gboolean
|
|
pika_stroke_shift_start (PikaStroke *stroke,
|
|
PikaAnchor *new_start)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE);
|
|
g_return_val_if_fail (new_start != NULL, FALSE);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->shift_start (stroke, new_start);
|
|
}
|
|
|
|
static gboolean
|
|
pika_stroke_real_shift_start (PikaStroke *stroke,
|
|
PikaAnchor *new_start)
|
|
{
|
|
GList *link;
|
|
|
|
link = g_queue_find (stroke->anchors, new_start);
|
|
if (!link)
|
|
return FALSE;
|
|
|
|
if (link == stroke->anchors->head)
|
|
return TRUE;
|
|
|
|
stroke->anchors->tail->next = stroke->anchors->head;
|
|
stroke->anchors->head->prev = stroke->anchors->tail;
|
|
stroke->anchors->tail = link->prev;
|
|
stroke->anchors->head = link;
|
|
stroke->anchors->tail->next = NULL;
|
|
stroke->anchors->head->prev = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
gdouble
|
|
pika_stroke_get_length (PikaStroke *stroke,
|
|
gdouble precision)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), 0.0);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->get_length (stroke, precision);
|
|
}
|
|
|
|
static gdouble
|
|
pika_stroke_real_get_length (PikaStroke *stroke,
|
|
gdouble precision)
|
|
{
|
|
GArray *points;
|
|
gint i;
|
|
gdouble length;
|
|
PikaCoords difference;
|
|
|
|
if (g_queue_is_empty (stroke->anchors))
|
|
return -1;
|
|
|
|
points = pika_stroke_interpolate (stroke, precision, NULL);
|
|
if (points == NULL)
|
|
return -1;
|
|
|
|
length = 0;
|
|
|
|
for (i = 0; i < points->len - 1; i++ )
|
|
{
|
|
pika_coords_difference (&(g_array_index (points, PikaCoords, i)),
|
|
&(g_array_index (points, PikaCoords, i+1)),
|
|
&difference);
|
|
length += pika_coords_length (&difference);
|
|
}
|
|
|
|
g_array_free(points, TRUE);
|
|
|
|
return length;
|
|
}
|
|
|
|
|
|
gdouble
|
|
pika_stroke_get_distance (PikaStroke *stroke,
|
|
const PikaCoords *coord)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), 0.0);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->get_distance (stroke, coord);
|
|
}
|
|
|
|
static gdouble
|
|
pika_stroke_real_get_distance (PikaStroke *stroke,
|
|
const PikaCoords *coord)
|
|
{
|
|
g_printerr ("pika_stroke_get_distance: default implementation\n");
|
|
|
|
return 0.0;
|
|
}
|
|
|
|
|
|
GArray *
|
|
pika_stroke_interpolate (PikaStroke *stroke,
|
|
gdouble precision,
|
|
gboolean *ret_closed)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->interpolate (stroke, precision,
|
|
ret_closed);
|
|
}
|
|
|
|
static GArray *
|
|
pika_stroke_real_interpolate (PikaStroke *stroke,
|
|
gdouble precision,
|
|
gboolean *ret_closed)
|
|
{
|
|
g_printerr ("pika_stroke_interpolate: default implementation\n");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
PikaStroke *
|
|
pika_stroke_duplicate (PikaStroke *stroke)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->duplicate (stroke);
|
|
}
|
|
|
|
static PikaStroke *
|
|
pika_stroke_real_duplicate (PikaStroke *stroke)
|
|
{
|
|
PikaStroke *new_stroke;
|
|
GList *list;
|
|
|
|
new_stroke = g_object_new (G_TYPE_FROM_INSTANCE (stroke),
|
|
"name", pika_object_get_name (stroke),
|
|
NULL);
|
|
|
|
g_queue_free_full (new_stroke->anchors, (GDestroyNotify) pika_anchor_free);
|
|
new_stroke->anchors = g_queue_copy (stroke->anchors);
|
|
|
|
for (list = new_stroke->anchors->head; list; list = g_list_next (list))
|
|
{
|
|
list->data = pika_anchor_copy (PIKA_ANCHOR (list->data));
|
|
}
|
|
|
|
new_stroke->closed = stroke->closed;
|
|
/* we do *not* copy the ID! */
|
|
|
|
return new_stroke;
|
|
}
|
|
|
|
|
|
PikaBezierDesc *
|
|
pika_stroke_make_bezier (PikaStroke *stroke)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->make_bezier (stroke);
|
|
}
|
|
|
|
static PikaBezierDesc *
|
|
pika_stroke_real_make_bezier (PikaStroke *stroke)
|
|
{
|
|
g_printerr ("pika_stroke_make_bezier: default implementation\n");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
pika_stroke_translate (PikaStroke *stroke,
|
|
gdouble offset_x,
|
|
gdouble offset_y)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
|
|
PIKA_STROKE_GET_CLASS (stroke)->translate (stroke, offset_x, offset_y);
|
|
}
|
|
|
|
static void
|
|
pika_stroke_real_translate (PikaStroke *stroke,
|
|
gdouble offset_x,
|
|
gdouble offset_y)
|
|
{
|
|
GList *list;
|
|
|
|
for (list = stroke->anchors->head; list; list = g_list_next (list))
|
|
{
|
|
PikaAnchor *anchor = list->data;
|
|
|
|
anchor->position.x += offset_x;
|
|
anchor->position.y += offset_y;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
pika_stroke_scale (PikaStroke *stroke,
|
|
gdouble scale_x,
|
|
gdouble scale_y)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
|
|
PIKA_STROKE_GET_CLASS (stroke)->scale (stroke, scale_x, scale_y);
|
|
}
|
|
|
|
static void
|
|
pika_stroke_real_scale (PikaStroke *stroke,
|
|
gdouble scale_x,
|
|
gdouble scale_y)
|
|
{
|
|
GList *list;
|
|
|
|
for (list = stroke->anchors->head; list; list = g_list_next (list))
|
|
{
|
|
PikaAnchor *anchor = list->data;
|
|
|
|
anchor->position.x *= scale_x;
|
|
anchor->position.y *= scale_y;
|
|
}
|
|
}
|
|
|
|
void
|
|
pika_stroke_rotate (PikaStroke *stroke,
|
|
gdouble center_x,
|
|
gdouble center_y,
|
|
gdouble angle)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
|
|
PIKA_STROKE_GET_CLASS (stroke)->rotate (stroke, center_x, center_y, angle);
|
|
}
|
|
|
|
static void
|
|
pika_stroke_real_rotate (PikaStroke *stroke,
|
|
gdouble center_x,
|
|
gdouble center_y,
|
|
gdouble angle)
|
|
{
|
|
PikaMatrix3 matrix;
|
|
|
|
angle = angle / 180.0 * G_PI;
|
|
pika_matrix3_identity (&matrix);
|
|
pika_transform_matrix_rotate_center (&matrix, center_x, center_y, angle);
|
|
|
|
pika_stroke_transform (stroke, &matrix, NULL);
|
|
}
|
|
|
|
void
|
|
pika_stroke_flip (PikaStroke *stroke,
|
|
PikaOrientationType flip_type,
|
|
gdouble axis)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
|
|
PIKA_STROKE_GET_CLASS (stroke)->flip (stroke, flip_type, axis);
|
|
}
|
|
|
|
static void
|
|
pika_stroke_real_flip (PikaStroke *stroke,
|
|
PikaOrientationType flip_type,
|
|
gdouble axis)
|
|
{
|
|
PikaMatrix3 matrix;
|
|
|
|
pika_matrix3_identity (&matrix);
|
|
pika_transform_matrix_flip (&matrix, flip_type, axis);
|
|
pika_stroke_transform (stroke, &matrix, NULL);
|
|
}
|
|
|
|
void
|
|
pika_stroke_flip_free (PikaStroke *stroke,
|
|
gdouble x1,
|
|
gdouble y1,
|
|
gdouble x2,
|
|
gdouble y2)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
|
|
PIKA_STROKE_GET_CLASS (stroke)->flip_free (stroke, x1, y1, x2, y2);
|
|
}
|
|
|
|
static void
|
|
pika_stroke_real_flip_free (PikaStroke *stroke,
|
|
gdouble x1,
|
|
gdouble y1,
|
|
gdouble x2,
|
|
gdouble y2)
|
|
{
|
|
/* x, y, width and height parameter in pika_transform_matrix_flip_free are unused */
|
|
PikaMatrix3 matrix;
|
|
|
|
pika_matrix3_identity (&matrix);
|
|
pika_transform_matrix_flip_free (&matrix, x1, y1, x2, y2);
|
|
|
|
pika_stroke_transform (stroke, &matrix, NULL);
|
|
}
|
|
|
|
/* transforms 'stroke' by 'matrix'. due to clipping, the transformation may
|
|
* result in multiple strokes.
|
|
*
|
|
* if 'ret_strokes' is not NULL, the transformed strokes are appended to the
|
|
* queue, and 'stroke' is left in an unspecified state. one of the resulting
|
|
* strokes may alias 'stroke'.
|
|
*
|
|
* if 'ret_strokes' is NULL, the transformation is performed in-place. if the
|
|
* transformation results in multiple strokes (which, atm, can only happen for
|
|
* non-affine transformation), the result is undefined.
|
|
*/
|
|
void
|
|
pika_stroke_transform (PikaStroke *stroke,
|
|
const PikaMatrix3 *matrix,
|
|
GQueue *ret_strokes)
|
|
{
|
|
g_return_if_fail (PIKA_IS_STROKE (stroke));
|
|
|
|
PIKA_STROKE_GET_CLASS (stroke)->transform (stroke, matrix, ret_strokes);
|
|
}
|
|
|
|
static void
|
|
pika_stroke_real_transform (PikaStroke *stroke,
|
|
const PikaMatrix3 *matrix,
|
|
GQueue *ret_strokes)
|
|
{
|
|
GList *list;
|
|
|
|
for (list = stroke->anchors->head; list; list = g_list_next (list))
|
|
{
|
|
PikaAnchor *anchor = list->data;
|
|
|
|
pika_matrix3_transform_point (matrix,
|
|
anchor->position.x,
|
|
anchor->position.y,
|
|
&anchor->position.x,
|
|
&anchor->position.y);
|
|
}
|
|
|
|
if (ret_strokes)
|
|
{
|
|
stroke->id = 0;
|
|
|
|
g_queue_push_tail (ret_strokes, g_object_ref (stroke));
|
|
}
|
|
}
|
|
|
|
|
|
GList *
|
|
pika_stroke_get_draw_anchors (PikaStroke *stroke)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->get_draw_anchors (stroke);
|
|
}
|
|
|
|
static GList *
|
|
pika_stroke_real_get_draw_anchors (PikaStroke *stroke)
|
|
{
|
|
GList *list;
|
|
GList *ret_list = NULL;
|
|
|
|
for (list = stroke->anchors->head; list; list = g_list_next (list))
|
|
{
|
|
if (PIKA_ANCHOR (list->data)->type == PIKA_ANCHOR_ANCHOR)
|
|
ret_list = g_list_prepend (ret_list, list->data);
|
|
}
|
|
|
|
return g_list_reverse (ret_list);
|
|
}
|
|
|
|
|
|
GList *
|
|
pika_stroke_get_draw_controls (PikaStroke *stroke)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->get_draw_controls (stroke);
|
|
}
|
|
|
|
static GList *
|
|
pika_stroke_real_get_draw_controls (PikaStroke *stroke)
|
|
{
|
|
GList *list;
|
|
GList *ret_list = NULL;
|
|
|
|
for (list = stroke->anchors->head; list; list = g_list_next (list))
|
|
{
|
|
PikaAnchor *anchor = list->data;
|
|
|
|
if (anchor->type == PIKA_ANCHOR_CONTROL)
|
|
{
|
|
PikaAnchor *next = list->next ? list->next->data : NULL;
|
|
PikaAnchor *prev = list->prev ? list->prev->data : NULL;
|
|
|
|
if (next && next->type == PIKA_ANCHOR_ANCHOR && next->selected)
|
|
{
|
|
/* Ok, this is a hack.
|
|
* The idea is to give control points at the end of a
|
|
* stroke a higher priority for the interactive tool.
|
|
*/
|
|
if (prev)
|
|
ret_list = g_list_prepend (ret_list, anchor);
|
|
else
|
|
ret_list = g_list_append (ret_list, anchor);
|
|
}
|
|
else if (prev && prev->type == PIKA_ANCHOR_ANCHOR && prev->selected)
|
|
{
|
|
/* same here... */
|
|
if (next)
|
|
ret_list = g_list_prepend (ret_list, anchor);
|
|
else
|
|
ret_list = g_list_append (ret_list, anchor);
|
|
}
|
|
}
|
|
}
|
|
|
|
return g_list_reverse (ret_list);
|
|
}
|
|
|
|
|
|
GArray *
|
|
pika_stroke_get_draw_lines (PikaStroke *stroke)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->get_draw_lines (stroke);
|
|
}
|
|
|
|
static GArray *
|
|
pika_stroke_real_get_draw_lines (PikaStroke *stroke)
|
|
{
|
|
GList *list;
|
|
GArray *ret_lines = NULL;
|
|
gint count = 0;
|
|
|
|
for (list = stroke->anchors->head; list; list = g_list_next (list))
|
|
{
|
|
PikaAnchor *anchor = list->data;
|
|
|
|
if (anchor->type == PIKA_ANCHOR_ANCHOR && anchor->selected)
|
|
{
|
|
if (list->next)
|
|
{
|
|
PikaAnchor *next = list->next->data;
|
|
|
|
if (count == 0)
|
|
ret_lines = g_array_new (FALSE, FALSE, sizeof (PikaCoords));
|
|
|
|
ret_lines = g_array_append_val (ret_lines, anchor->position);
|
|
ret_lines = g_array_append_val (ret_lines, next->position);
|
|
count += 1;
|
|
}
|
|
|
|
if (list->prev)
|
|
{
|
|
PikaAnchor *prev = list->prev->data;
|
|
|
|
if (count == 0)
|
|
ret_lines = g_array_new (FALSE, FALSE, sizeof (PikaCoords));
|
|
|
|
ret_lines = g_array_append_val (ret_lines, anchor->position);
|
|
ret_lines = g_array_append_val (ret_lines, prev->position);
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret_lines;
|
|
}
|
|
|
|
GArray *
|
|
pika_stroke_control_points_get (PikaStroke *stroke,
|
|
gboolean *ret_closed)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->control_points_get (stroke,
|
|
ret_closed);
|
|
}
|
|
|
|
static GArray *
|
|
pika_stroke_real_control_points_get (PikaStroke *stroke,
|
|
gboolean *ret_closed)
|
|
{
|
|
guint num_anchors;
|
|
GArray *ret_array;
|
|
GList *list;
|
|
|
|
num_anchors = g_queue_get_length (stroke->anchors);
|
|
ret_array = g_array_sized_new (FALSE, FALSE,
|
|
sizeof (PikaAnchor), num_anchors);
|
|
|
|
for (list = stroke->anchors->head; list; list = g_list_next (list))
|
|
{
|
|
g_array_append_vals (ret_array, list->data, 1);
|
|
}
|
|
|
|
if (ret_closed)
|
|
*ret_closed = stroke->closed;
|
|
|
|
return ret_array;
|
|
}
|
|
|
|
gboolean
|
|
pika_stroke_get_point_at_dist (PikaStroke *stroke,
|
|
gdouble dist,
|
|
gdouble precision,
|
|
PikaCoords *position,
|
|
gdouble *slope)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE);
|
|
|
|
return PIKA_STROKE_GET_CLASS (stroke)->get_point_at_dist (stroke,
|
|
dist,
|
|
precision,
|
|
position,
|
|
slope);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
pika_stroke_real_get_point_at_dist (PikaStroke *stroke,
|
|
gdouble dist,
|
|
gdouble precision,
|
|
PikaCoords *position,
|
|
gdouble *slope)
|
|
{
|
|
GArray *points;
|
|
gint i;
|
|
gdouble length;
|
|
gdouble segment_length;
|
|
gboolean ret = FALSE;
|
|
PikaCoords difference;
|
|
|
|
points = pika_stroke_interpolate (stroke, precision, NULL);
|
|
if (points == NULL)
|
|
return ret;
|
|
|
|
length = 0;
|
|
for (i=0; i < points->len - 1; i++)
|
|
{
|
|
pika_coords_difference (&(g_array_index (points, PikaCoords , i)),
|
|
&(g_array_index (points, PikaCoords , i+1)),
|
|
&difference);
|
|
segment_length = pika_coords_length (&difference);
|
|
|
|
if (segment_length == 0 || length + segment_length < dist )
|
|
{
|
|
length += segment_length;
|
|
}
|
|
else
|
|
{
|
|
/* x = x1 + (x2 - x1 ) u */
|
|
/* x = x1 (1-u) + u x2 */
|
|
|
|
gdouble u = (dist - length) / segment_length;
|
|
|
|
pika_coords_mix (1 - u, &(g_array_index (points, PikaCoords , i)),
|
|
u, &(g_array_index (points, PikaCoords , i+1)),
|
|
position);
|
|
|
|
if (difference.x == 0)
|
|
*slope = G_MAXDOUBLE;
|
|
else
|
|
*slope = difference.y / difference.x;
|
|
|
|
ret = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
g_array_free (points, TRUE);
|
|
|
|
return ret;
|
|
}
|