aboutsummaryrefslogtreecommitdiffstats
path: root/libgnomecanvas/gnome-canvas-line.c
diff options
context:
space:
mode:
Diffstat (limited to 'libgnomecanvas/gnome-canvas-line.c')
-rw-r--r--libgnomecanvas/gnome-canvas-line.c1423
1 files changed, 1423 insertions, 0 deletions
diff --git a/libgnomecanvas/gnome-canvas-line.c b/libgnomecanvas/gnome-canvas-line.c
new file mode 100644
index 0000000000..48cafb71c7
--- /dev/null
+++ b/libgnomecanvas/gnome-canvas-line.c
@@ -0,0 +1,1423 @@
+/*
+ * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
+ * All rights reserved.
+ *
+ * This file is part of the Gnome Library.
+ *
+ * The Gnome Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * The Gnome 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with the Gnome Library; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/*
+ @NOTATION@
+ */
+
+/* Line/curve item type for GnomeCanvas widget
+ *
+ * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget. Tk is
+ * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties.
+ *
+ *
+ * Author: Federico Mena <federico@nuclecu.unam.mx>
+ */
+
+#include <config.h>
+#include <math.h>
+#include <string.h>
+#include <libart_lgpl/art_vpath.h>
+#include <libart_lgpl/art_svp.h>
+#include <libart_lgpl/art_svp_vpath.h>
+#include <libart_lgpl/art_svp_vpath_stroke.h>
+#include "libgnomecanvas.h"
+
+#define noVERBOSE
+
+#define DEFAULT_SPLINE_STEPS 12 /* this is what Tk uses */
+#define NUM_ARROW_POINTS 6 /* number of points in an arrowhead */
+#define NUM_STATIC_POINTS 256 /* number of static points to use to avoid allocating arrays */
+
+
+#define GROW_BOUNDS(bx1, by1, bx2, by2, x, y) { \
+ if (x < bx1) \
+ bx1 = x; \
+ \
+ if (x > bx2) \
+ bx2 = x; \
+ \
+ if (y < by1) \
+ by1 = y; \
+ \
+ if (y > by2) \
+ by2 = y; \
+}
+
+
+enum {
+ PROP_0,
+ PROP_POINTS,
+ PROP_FILL_COLOR,
+ PROP_FILL_COLOR_GDK,
+ PROP_FILL_COLOR_RGBA,
+ PROP_FILL_STIPPLE,
+ PROP_WIDTH_PIXELS,
+ PROP_WIDTH_UNITS,
+ PROP_CAP_STYLE,
+ PROP_JOIN_STYLE,
+ PROP_LINE_STYLE,
+ PROP_FIRST_ARROWHEAD,
+ PROP_LAST_ARROWHEAD,
+ PROP_SMOOTH,
+ PROP_SPLINE_STEPS,
+ PROP_ARROW_SHAPE_A,
+ PROP_ARROW_SHAPE_B,
+ PROP_ARROW_SHAPE_C
+};
+
+
+static void gnome_canvas_line_class_init (GnomeCanvasLineClass *class);
+static void gnome_canvas_line_init (GnomeCanvasLine *line);
+static void gnome_canvas_line_destroy (GtkObject *object);
+static void gnome_canvas_line_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gnome_canvas_line_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gnome_canvas_line_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags);
+static void gnome_canvas_line_realize (GnomeCanvasItem *item);
+static void gnome_canvas_line_unrealize (GnomeCanvasItem *item);
+static void gnome_canvas_line_draw (GnomeCanvasItem *item, GdkDrawable *drawable,
+ int x, int y, int width, int height);
+static double gnome_canvas_line_point (GnomeCanvasItem *item, double x, double y,
+ int cx, int cy, GnomeCanvasItem **actual_item);
+static void gnome_canvas_line_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2);
+static void gnome_canvas_line_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf);
+
+
+static GnomeCanvasItemClass *parent_class;
+
+
+GType
+gnome_canvas_line_get_type (void)
+{
+ static GType line_type;
+
+ if (!line_type) {
+ const GTypeInfo object_info = {
+ sizeof (GnomeCanvasLineClass),
+ (GBaseInitFunc) NULL,
+ (GBaseFinalizeFunc) NULL,
+ (GClassInitFunc) gnome_canvas_line_class_init,
+ (GClassFinalizeFunc) NULL,
+ NULL, /* class_data */
+ sizeof (GnomeCanvasLine),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) gnome_canvas_line_init,
+ NULL /* value_table */
+ };
+
+ line_type = g_type_register_static (GNOME_TYPE_CANVAS_ITEM, "GnomeCanvasLine",
+ &object_info, 0);
+ }
+
+ return line_type;
+}
+
+static void
+gnome_canvas_line_class_init (GnomeCanvasLineClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkObjectClass *object_class;
+ GnomeCanvasItemClass *item_class;
+
+ gobject_class = (GObjectClass *) class;
+ object_class = (GtkObjectClass *) class;
+ item_class = (GnomeCanvasItemClass *) class;
+
+ parent_class = g_type_class_peek_parent (class);
+
+ gobject_class->set_property = gnome_canvas_line_set_property;
+ gobject_class->get_property = gnome_canvas_line_get_property;
+
+ g_object_class_install_property
+ (gobject_class,
+ PROP_POINTS,
+ g_param_spec_boxed ("points", NULL, NULL,
+ GNOME_TYPE_CANVAS_POINTS,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_FILL_COLOR,
+ g_param_spec_string ("fill_color", NULL, NULL,
+ NULL,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_FILL_COLOR_GDK,
+ g_param_spec_boxed ("fill_color_gdk", NULL, NULL,
+ GDK_TYPE_COLOR,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_FILL_COLOR_RGBA,
+ g_param_spec_uint ("fill_color_rgba", NULL, NULL,
+ 0, G_MAXUINT, 0,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_FILL_STIPPLE,
+ g_param_spec_object ("fill_stipple", NULL, NULL,
+ GDK_TYPE_DRAWABLE,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_WIDTH_PIXELS,
+ g_param_spec_uint ("width_pixels", NULL, NULL,
+ 0, G_MAXUINT, 0,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_WIDTH_UNITS,
+ g_param_spec_double ("width_units", NULL, NULL,
+ 0.0, G_MAXDOUBLE, 0.0,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_CAP_STYLE,
+ g_param_spec_enum ("cap_style", NULL, NULL,
+ GDK_TYPE_CAP_STYLE,
+ GDK_CAP_BUTT,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_JOIN_STYLE,
+ g_param_spec_enum ("join_style", NULL, NULL,
+ GDK_TYPE_JOIN_STYLE,
+ GDK_JOIN_MITER,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_LINE_STYLE,
+ g_param_spec_enum ("line_style", NULL, NULL,
+ GDK_TYPE_LINE_STYLE,
+ GDK_LINE_SOLID,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_FIRST_ARROWHEAD,
+ g_param_spec_boolean ("first_arrowhead", NULL, NULL,
+ FALSE,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_LAST_ARROWHEAD,
+ g_param_spec_boolean ("last_arrowhead", NULL, NULL,
+ FALSE,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_SMOOTH,
+ g_param_spec_boolean ("smooth", NULL, NULL,
+ FALSE,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_SPLINE_STEPS,
+ g_param_spec_uint ("spline_steps", NULL, NULL,
+ 0, G_MAXUINT, DEFAULT_SPLINE_STEPS,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_ARROW_SHAPE_A,
+ g_param_spec_double ("arrow_shape_a", NULL, NULL,
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_ARROW_SHAPE_B,
+ g_param_spec_double ("arrow_shape_b", NULL, NULL,
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+ g_object_class_install_property
+ (gobject_class,
+ PROP_ARROW_SHAPE_C,
+ g_param_spec_double ("arrow_shape_c", NULL, NULL,
+ -G_MAXDOUBLE, G_MAXDOUBLE, 0,
+ (G_PARAM_READABLE | G_PARAM_WRITABLE)));
+
+ object_class->destroy = gnome_canvas_line_destroy;
+
+ item_class->update = gnome_canvas_line_update;
+ item_class->realize = gnome_canvas_line_realize;
+ item_class->unrealize = gnome_canvas_line_unrealize;
+ item_class->draw = gnome_canvas_line_draw;
+ item_class->point = gnome_canvas_line_point;
+ item_class->bounds = gnome_canvas_line_bounds;
+
+ item_class->render = gnome_canvas_line_render;
+}
+
+static void
+gnome_canvas_line_init (GnomeCanvasLine *line)
+{
+ line->width = 0.0;
+ line->cap = GDK_CAP_BUTT;
+ line->join = GDK_JOIN_MITER;
+ line->line_style = GDK_LINE_SOLID;
+ line->shape_a = 0.0;
+ line->shape_b = 0.0;
+ line->shape_c = 0.0;
+ line->spline_steps = DEFAULT_SPLINE_STEPS;
+}
+
+static void
+gnome_canvas_line_destroy (GtkObject *object)
+{
+ GnomeCanvasLine *line;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GNOME_IS_CANVAS_LINE (object));
+
+ line = GNOME_CANVAS_LINE (object);
+
+ /* remember, destroy can be run multiple times! */
+
+ if (line->coords)
+ g_free (line->coords);
+ line->coords = NULL;
+
+ if (line->first_coords)
+ g_free (line->first_coords);
+ line->first_coords = NULL;
+
+ if (line->last_coords)
+ g_free (line->last_coords);
+ line->last_coords = NULL;
+
+ if (line->stipple)
+ g_object_unref (line->stipple);
+ line->stipple = NULL;
+
+ if (line->fill_svp)
+ art_svp_free (line->fill_svp);
+ line->fill_svp = NULL;
+
+ if (line->first_svp)
+ art_svp_free (line->first_svp);
+ line->first_svp = NULL;
+
+ if (line->last_svp)
+ art_svp_free (line->last_svp);
+ line->last_svp = NULL;
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+/* Computes the bounding box of the line, including its arrow points. Assumes that the number of
+ * points in the line is not zero.
+ */
+static void
+get_bounds (GnomeCanvasLine *line, double *bx1, double *by1, double *bx2, double *by2)
+{
+ double *coords;
+ double x1, y1, x2, y2;
+ double width;
+ int i;
+
+ if (!line->coords) {
+ *bx1 = *by1 = *bx2 = *by2 = 0.0;
+ return;
+ }
+
+ /* Find bounding box of line's points */
+
+ x1 = x2 = line->coords[0];
+ y1 = y2 = line->coords[1];
+
+ for (i = 1, coords = line->coords + 2; i < line->num_points; i++, coords += 2)
+ GROW_BOUNDS (x1, y1, x2, y2, coords[0], coords[1]);
+
+ /* Add possible over-estimate for wide lines */
+
+ if (line->width_pixels)
+ width = line->width / line->item.canvas->pixels_per_unit;
+ else
+ width = line->width;
+
+ x1 -= width;
+ y1 -= width;
+ x2 += width;
+ y2 += width;
+
+ /* For mitered lines, make a second pass through all the points. Compute the location of
+ * the two miter vertex points and add them to the bounding box.
+ */
+
+ if (line->join == GDK_JOIN_MITER)
+ for (i = line->num_points, coords = line->coords; i >= 3; i--, coords += 2) {
+ double mx1, my1, mx2, my2;
+
+ if (gnome_canvas_get_miter_points (coords[0], coords[1],
+ coords[2], coords[3],
+ coords[4], coords[5],
+ width,
+ &mx1, &my1, &mx2, &my2)) {
+ GROW_BOUNDS (x1, y1, x2, y2, mx1, my1);
+ GROW_BOUNDS (x1, y1, x2, y2, mx2, my2);
+ }
+ }
+
+ /* Add the arrow points, if any */
+
+ if (line->first_arrow && line->first_coords)
+ for (i = 0, coords = line->first_coords; i < NUM_ARROW_POINTS; i++, coords += 2)
+ GROW_BOUNDS (x1, y1, x2, y2, coords[0], coords[1]);
+
+ if (line->last_arrow && line->last_coords)
+ for (i = 0, coords = line->last_coords; i < NUM_ARROW_POINTS; i++, coords += 2)
+ GROW_BOUNDS (x1, y1, x2, y2, coords[0], coords[1]);
+
+ /* Done */
+
+ *bx1 = x1;
+ *by1 = y1;
+ *bx2 = x2;
+ *by2 = y2;
+}
+
+/* Computes the bounding box of the line, in canvas coordinates. Assumes that the number of points in the polygon is
+ * not zero. Affine is the i2c transformation.
+ */
+static void
+get_bounds_canvas (GnomeCanvasLine *line, double *bx1, double *by1, double *bx2, double *by2, double affine[6])
+{
+ /* It would be possible to tighten the bounds somewhat by transforming the individual points before
+ aggregating them into the bbox. But it hardly seems worth it. */
+ ArtDRect bbox_world;
+ ArtDRect bbox_canvas;
+
+ get_bounds (line, &bbox_world.x0, &bbox_world.y0, &bbox_world.x1, &bbox_world.y1);
+
+ art_drect_affine_transform (&bbox_canvas, &bbox_world, affine);
+ /* include 1 pixel of fudge */
+ *bx1 = bbox_canvas.x0 - 1;
+ *by1 = bbox_canvas.y0 - 1;
+ *bx2 = bbox_canvas.x1 + 1;
+ *by2 = bbox_canvas.y1 + 1;
+}
+
+/* Recalculates the arrow polygons for the line */
+static void
+reconfigure_arrows (GnomeCanvasLine *line)
+{
+ double *poly, *coords;
+ double dx, dy, length;
+ double sin_theta, cos_theta, tmp;
+ double frac_height; /* Line width as fraction of arrowhead width */
+ double backup; /* Distance to backup end points so the line ends in the middle of the arrowhead */
+ double vx, vy; /* Position of arrowhead vertex */
+ double shape_a, shape_b, shape_c;
+ double width;
+ int i;
+
+ if (line->num_points == 0)
+ return;
+
+ /* Set up things */
+
+ if (line->first_arrow) {
+ if (line->first_coords) {
+ line->coords[0] = line->first_coords[0];
+ line->coords[1] = line->first_coords[1];
+ } else
+ line->first_coords = g_new (double, 2 * NUM_ARROW_POINTS);
+ } else if (line->first_coords) {
+ line->coords[0] = line->first_coords[0];
+ line->coords[1] = line->first_coords[1];
+
+ g_free (line->first_coords);
+ line->first_coords = NULL;
+ }
+
+ i = 2 * (line->num_points - 1);
+
+ if (line->last_arrow) {
+ if (line->last_coords) {
+ line->coords[i] = line->last_coords[0];
+ line->coords[i + 1] = line->last_coords[1];
+ } else
+ line->last_coords = g_new (double, 2 * NUM_ARROW_POINTS);
+ } else if (line->last_coords) {
+ line->coords[i] = line->last_coords[0];
+ line->coords[i + 1] = line->last_coords[1];
+
+ g_free (line->last_coords);
+ line->last_coords = NULL;
+ }
+
+ if (!line->first_arrow && !line->last_arrow)
+ return;
+
+ if (line->width_pixels)
+ width = line->width / line->item.canvas->pixels_per_unit;
+ else
+ width = line->width;
+
+ /* Add fudge value for better-looking results */
+
+ shape_a = line->shape_a;
+ shape_b = line->shape_b;
+ shape_c = line->shape_c + width / 2.0;
+
+ if (line->width_pixels) {
+ shape_a /= line->item.canvas->pixels_per_unit;
+ shape_b /= line->item.canvas->pixels_per_unit;
+ shape_c /= line->item.canvas->pixels_per_unit;
+ }
+
+ shape_a += 0.001;
+ shape_b += 0.001;
+ shape_c += 0.001;
+
+ /* Compute the polygon for the first arrowhead and adjust the first point in the line so
+ * that the line does not stick out past the leading edge of the arrowhead.
+ */
+
+ frac_height = (line->width / 2.0) / shape_c;
+ backup = frac_height * shape_b + shape_a * (1.0 - frac_height) / 2.0;
+
+ if (line->first_arrow) {
+ poly = line->first_coords;
+ poly[0] = poly[10] = line->coords[0];
+ poly[1] = poly[11] = line->coords[1];
+
+ dx = poly[0] - line->coords[2];
+ dy = poly[1] - line->coords[3];
+ length = sqrt (dx * dx + dy * dy);
+ if (length < GNOME_CANVAS_EPSILON)
+ sin_theta = cos_theta = 0.0;
+ else {
+ sin_theta = dy / length;
+ cos_theta = dx / length;
+ }
+
+ vx = poly[0] - shape_a * cos_theta;
+ vy = poly[1] - shape_a * sin_theta;
+
+ tmp = shape_c * sin_theta;
+
+ poly[2] = poly[0] - shape_b * cos_theta + tmp;
+ poly[8] = poly[2] - 2.0 * tmp;
+
+ tmp = shape_c * cos_theta;
+
+ poly[3] = poly[1] - shape_b * sin_theta - tmp;
+ poly[9] = poly[3] + 2.0 * tmp;
+
+ poly[4] = poly[2] * frac_height + vx * (1.0 - frac_height);
+ poly[5] = poly[3] * frac_height + vy * (1.0 - frac_height);
+ poly[6] = poly[8] * frac_height + vx * (1.0 - frac_height);
+ poly[7] = poly[9] * frac_height + vy * (1.0 - frac_height);
+
+ /* Move the first point towards the second so that the corners at the end of the
+ * line are inside the arrowhead.
+ */
+
+ line->coords[0] = poly[0] - backup * cos_theta;
+ line->coords[1] = poly[1] - backup * sin_theta;
+ }
+
+ /* Same process for last arrowhead */
+
+ if (line->last_arrow) {
+ coords = line->coords + 2 * (line->num_points - 2);
+ poly = line->last_coords;
+ poly[0] = poly[10] = coords[2];
+ poly[1] = poly[11] = coords[3];
+
+ dx = poly[0] - coords[0];
+ dy = poly[1] - coords[1];
+ length = sqrt (dx * dx + dy * dy);
+ if (length < GNOME_CANVAS_EPSILON)
+ sin_theta = cos_theta = 0.0;
+ else {
+ sin_theta = dy / length;
+ cos_theta = dx / length;
+ }
+
+ vx = poly[0] - shape_a * cos_theta;
+ vy = poly[1] - shape_a * sin_theta;
+
+ tmp = shape_c * sin_theta;
+
+ poly[2] = poly[0] - shape_b * cos_theta + tmp;
+ poly[8] = poly[2] - 2.0 * tmp;
+
+ tmp = shape_c * cos_theta;
+
+ poly[3] = poly[1] - shape_b * sin_theta - tmp;
+ poly[9] = poly[3] + 2.0 * tmp;
+
+ poly[4] = poly[2] * frac_height + vx * (1.0 - frac_height);
+ poly[5] = poly[3] * frac_height + vy * (1.0 - frac_height);
+ poly[6] = poly[8] * frac_height + vx * (1.0 - frac_height);
+ poly[7] = poly[9] * frac_height + vy * (1.0 - frac_height);
+
+ coords[2] = poly[0] - backup * cos_theta;
+ coords[3] = poly[1] - backup * sin_theta;
+ }
+}
+
+/* Convenience function to set the line's GC's foreground color */
+static void
+set_line_gc_foreground (GnomeCanvasLine *line)
+{
+ GdkColor c;
+
+ if (!line->gc)
+ return;
+
+ c.pixel = line->fill_pixel;
+ gdk_gc_set_foreground (line->gc, &c);
+}
+
+/* Recalculate the line's width and set it in its GC */
+static void
+set_line_gc_width (GnomeCanvasLine *line)
+{
+ int width;
+
+ if (!line->gc)
+ return;
+
+ if (line->width_pixels)
+ width = (int) line->width;
+ else
+ width = (int) (line->width * line->item.canvas->pixels_per_unit + 0.5);
+
+ gdk_gc_set_line_attributes (line->gc,
+ width,
+ line->line_style,
+ (line->first_arrow || line->last_arrow) ? GDK_CAP_BUTT : line->cap,
+ line->join);
+}
+
+/* Sets the stipple pattern for the line */
+static void
+set_stipple (GnomeCanvasLine *line, GdkBitmap *stipple, int reconfigure)
+{
+ if (line->stipple && !reconfigure)
+ g_object_unref (line->stipple);
+
+ line->stipple = stipple;
+ if (stipple && !reconfigure)
+ g_object_ref (stipple);
+
+ if (line->gc) {
+ if (stipple) {
+ gdk_gc_set_stipple (line->gc, stipple);
+ gdk_gc_set_fill (line->gc, GDK_STIPPLED);
+ } else
+ gdk_gc_set_fill (line->gc, GDK_SOLID);
+ }
+}
+
+static void
+gnome_canvas_line_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GnomeCanvasItem *item;
+ GnomeCanvasLine *line;
+ GnomeCanvasPoints *points;
+ GdkColor color = { 0, 0, 0, 0, };
+ GdkColor *pcolor;
+ gboolean color_changed;
+ int have_pixel;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GNOME_IS_CANVAS_LINE (object));
+
+ item = GNOME_CANVAS_ITEM (object);
+ line = GNOME_CANVAS_LINE (object);
+
+ color_changed = FALSE;
+ have_pixel = FALSE;
+
+ switch (param_id) {
+ case PROP_POINTS:
+ points = g_value_get_boxed (value);
+
+ if (line->coords) {
+ g_free (line->coords);
+ line->coords = NULL;
+ }
+
+ if (!points)
+ line->num_points = 0;
+ else {
+ line->num_points = points->num_points;
+ line->coords = g_new (double, 2 * line->num_points);
+ memcpy (line->coords, points->coords, 2 * line->num_points * sizeof (double));
+ }
+
+ /* Drop the arrowhead polygons if they exist -- they will be regenerated */
+
+ if (line->first_coords) {
+ g_free (line->first_coords);
+ line->first_coords = NULL;
+ }
+
+ if (line->last_coords) {
+ g_free (line->last_coords);
+ line->last_coords = NULL;
+ }
+
+ /* Since the line's points have changed, we need to re-generate arrowheads in
+ * addition to recalculating the bounds.
+ */
+ gnome_canvas_item_request_update (item);
+ break;
+
+ case PROP_FILL_COLOR:
+ if (g_value_get_string (value))
+ gdk_color_parse (g_value_get_string (value), &color);
+ line->fill_rgba = ((color.red & 0xff00) << 16 |
+ (color.green & 0xff00) << 8 |
+ (color.blue & 0xff00) |
+ 0xff);
+ color_changed = TRUE;
+ break;
+
+ case PROP_FILL_COLOR_GDK:
+ pcolor = g_value_get_boxed (value);
+ if (pcolor) {
+ GdkColormap *colormap;
+ color = *pcolor;
+
+ colormap = gtk_widget_get_colormap (GTK_WIDGET (item->canvas));
+ gdk_rgb_find_color (colormap, &color);
+
+ have_pixel = TRUE;
+ }
+
+ line->fill_rgba = ((color.red & 0xff00) << 16 |
+ (color.green & 0xff00) << 8 |
+ (color.blue & 0xff00) |
+ 0xff);
+ color_changed = TRUE;
+ break;
+
+ case PROP_FILL_COLOR_RGBA:
+ line->fill_rgba = g_value_get_uint (value);
+ color_changed = TRUE;
+ break;
+
+ case PROP_FILL_STIPPLE:
+ set_stipple (line, (GdkBitmap *) g_value_get_object (value), FALSE);
+ gnome_canvas_item_request_redraw_svp (item, line->fill_svp);
+ break;
+
+ case PROP_WIDTH_PIXELS:
+ line->width = g_value_get_uint (value);
+ line->width_pixels = TRUE;
+ set_line_gc_width (line);
+ gnome_canvas_item_request_update (item);
+ break;
+
+ case PROP_WIDTH_UNITS:
+ line->width = fabs (g_value_get_double (value));
+ line->width_pixels = FALSE;
+ set_line_gc_width (line);
+ gnome_canvas_item_request_update (item);
+ break;
+
+ case PROP_CAP_STYLE:
+ line->cap = g_value_get_enum (value);
+ gnome_canvas_item_request_update (item);
+ break;
+
+ case PROP_JOIN_STYLE:
+ line->join = g_value_get_enum (value);
+ gnome_canvas_item_request_update (item);
+ break;
+
+ case PROP_LINE_STYLE:
+ line->line_style = g_value_get_enum (value);
+ set_line_gc_width (line);
+ gnome_canvas_item_request_update (item);
+ break;
+
+ case PROP_FIRST_ARROWHEAD:
+ line->first_arrow = g_value_get_boolean (value);
+ gnome_canvas_item_request_update (item);
+ break;
+
+ case PROP_LAST_ARROWHEAD:
+ line->last_arrow = g_value_get_boolean (value);
+ gnome_canvas_item_request_update (item);
+ break;
+
+ case PROP_SMOOTH:
+ /* FIXME */
+ break;
+
+ case PROP_SPLINE_STEPS:
+ /* FIXME */
+ break;
+
+ case PROP_ARROW_SHAPE_A:
+ line->shape_a = fabs (g_value_get_double (value));
+ gnome_canvas_item_request_update (item);
+ break;
+
+ case PROP_ARROW_SHAPE_B:
+ line->shape_b = fabs (g_value_get_double (value));
+ gnome_canvas_item_request_update (item);
+ break;
+
+ case PROP_ARROW_SHAPE_C:
+ line->shape_c = fabs (g_value_get_double (value));
+ gnome_canvas_item_request_update (item);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+
+ if (color_changed) {
+ if (have_pixel)
+ line->fill_pixel = color.pixel;
+ else
+ line->fill_pixel = gnome_canvas_get_color_pixel (item->canvas,
+ line->fill_rgba);
+
+ if (!item->canvas->aa)
+ set_line_gc_foreground (line);
+
+ gnome_canvas_item_request_redraw_svp (item, line->fill_svp);
+
+ if (line->first_svp)
+ gnome_canvas_item_request_redraw_svp (item, line->first_svp);
+
+ if (line->last_svp)
+ gnome_canvas_item_request_redraw_svp (item, line->last_svp);
+
+ }
+}
+
+/* Returns a copy of the line's points without the endpoint adjustments for
+ * arrowheads.
+ */
+static GnomeCanvasPoints *
+get_points (GnomeCanvasLine *line)
+{
+ GnomeCanvasPoints *points;
+ int start_ofs, end_ofs;
+
+ if (line->num_points == 0)
+ return NULL;
+
+ start_ofs = end_ofs = 0;
+
+ points = gnome_canvas_points_new (line->num_points);
+
+ /* Invariant: if first_coords or last_coords exist, then the line's
+ * endpoints have been adjusted.
+ */
+
+ if (line->first_coords) {
+ start_ofs = 1;
+
+ points->coords[0] = line->first_coords[0];
+ points->coords[1] = line->first_coords[1];
+ }
+
+ if (line->last_coords) {
+ end_ofs = 1;
+
+ points->coords[2 * (line->num_points - 1)] = line->last_coords[0];
+ points->coords[2 * (line->num_points - 1) + 1] = line->last_coords[1];
+ }
+
+ memcpy (points->coords + 2 * start_ofs,
+ line->coords + 2 * start_ofs,
+ 2 * (line->num_points - (start_ofs + end_ofs)) * sizeof (double));
+
+ return points;
+}
+
+static void
+gnome_canvas_line_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GnomeCanvasLine *line;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (GNOME_IS_CANVAS_LINE (object));
+
+ line = GNOME_CANVAS_LINE (object);
+
+ switch (param_id) {
+ case PROP_POINTS:
+ /* get_points returns a copy */
+ g_value_take_boxed (value, get_points (line));
+ break;
+
+ case PROP_FILL_COLOR:
+ g_value_take_string (value,
+ g_strdup_printf ("#%02x%02x%02x",
+ line->fill_rgba >> 24,
+ (line->fill_rgba >> 16) & 0xff,
+ (line->fill_rgba >> 8) & 0xff));
+ break;
+
+ case PROP_FILL_COLOR_GDK: {
+ GnomeCanvas *canvas = GNOME_CANVAS_ITEM (line)->canvas;
+ GdkColormap *colormap = gtk_widget_get_colormap (GTK_WIDGET (canvas));
+ GdkColor color;
+
+ gdk_colormap_query_color (colormap, line->fill_pixel, &color);
+ g_value_set_boxed (value, &color);
+ break;
+ }
+
+ case PROP_FILL_COLOR_RGBA:
+ g_value_set_uint (value, line->fill_rgba);
+ break;
+
+ case PROP_FILL_STIPPLE:
+ g_value_set_object (value, line->stipple);
+ break;
+
+ case PROP_WIDTH_PIXELS:
+ g_value_set_uint (value, line->width);
+ break;
+
+ case PROP_WIDTH_UNITS:
+ g_value_set_double (value, line->width);
+ break;
+
+ case PROP_CAP_STYLE:
+ g_value_set_enum (value, line->cap);
+ break;
+
+ case PROP_JOIN_STYLE:
+ g_value_set_enum (value, line->join);
+ break;
+
+ case PROP_LINE_STYLE:
+ g_value_set_enum (value, line->line_style);
+ break;
+
+ case PROP_FIRST_ARROWHEAD:
+ g_value_set_boolean (value, line->first_arrow);
+ break;
+
+ case PROP_LAST_ARROWHEAD:
+ g_value_set_boolean (value, line->last_arrow);
+ break;
+
+ case PROP_SMOOTH:
+ g_value_set_boolean (value, line->smooth);
+ break;
+
+ case PROP_SPLINE_STEPS:
+ g_value_set_uint (value, line->spline_steps);
+ break;
+
+ case PROP_ARROW_SHAPE_A:
+ g_value_set_double (value, line->shape_a);
+ break;
+
+ case PROP_ARROW_SHAPE_B:
+ g_value_set_double (value, line->shape_b);
+ break;
+
+ case PROP_ARROW_SHAPE_C:
+ g_value_set_double (value, line->shape_c);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+gnome_canvas_line_render (GnomeCanvasItem *item,
+ GnomeCanvasBuf *buf)
+{
+ GnomeCanvasLine *line;
+
+ line = GNOME_CANVAS_LINE (item);
+
+ if (line->fill_svp != NULL)
+ gnome_canvas_render_svp (buf, line->fill_svp, line->fill_rgba);
+
+ if (line->first_svp != NULL)
+ gnome_canvas_render_svp (buf, line->first_svp, line->fill_rgba);
+
+ if (line->last_svp != NULL)
+ gnome_canvas_render_svp (buf, line->last_svp, line->fill_rgba);
+}
+
+
+static ArtSVP *
+svp_from_points (const double *item_coords, int num_points, const double affine[6])
+{
+ ArtVpath *vpath;
+ ArtSVP *svp;
+ double x, y;
+ int i;
+
+ vpath = art_new (ArtVpath, num_points + 2);
+
+ for (i = 0; i < num_points; i++) {
+ vpath[i].code = i == 0 ? ART_MOVETO : ART_LINETO;
+ x = item_coords[i * 2];
+ y = item_coords[i * 2 + 1];
+ vpath[i].x = x * affine[0] + y * affine[2] + affine[4];
+ vpath[i].y = x * affine[1] + y * affine[3] + affine[5];
+ }
+#if 0
+ vpath[i].code = ART_LINETO;
+ vpath[i].x = vpath[0].x;
+ vpath[i].y = vpath[0].y;
+ i++;
+#endif
+ vpath[i].code = ART_END;
+ vpath[i].x = 0;
+ vpath[i].y = 0;
+
+ svp = art_svp_from_vpath (vpath);
+
+ art_free (vpath);
+
+ return svp;
+}
+
+static void
+gnome_canvas_line_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags)
+{
+ GnomeCanvasLine *line;
+ int i;
+ ArtVpath *vpath;
+ ArtPoint pi, pc;
+ double width;
+ ArtSVP *svp;
+ double x1, y1, x2, y2;
+
+ line = GNOME_CANVAS_LINE (item);
+
+ if (parent_class->update)
+ (* parent_class->update) (item, affine, clip_path, flags);
+
+ reconfigure_arrows (line);
+
+ if (item->canvas->aa) {
+ gnome_canvas_item_reset_bounds (item);
+
+ vpath = art_new (ArtVpath, line->num_points + 2);
+
+ for (i = 0; i < line->num_points; i++) {
+ pi.x = line->coords[i * 2];
+ pi.y = line->coords[i * 2 + 1];
+ art_affine_point (&pc, &pi, affine);
+ vpath[i].code = i == 0 ? ART_MOVETO : ART_LINETO;
+ vpath[i].x = pc.x;
+ vpath[i].y = pc.y;
+ }
+ vpath[i].code = ART_END;
+ vpath[i].x = 0;
+ vpath[i].y = 0;
+
+ if (line->width_pixels)
+ width = line->width;
+ else
+ width = line->width * art_affine_expansion (affine);
+
+ if (width < 0.5)
+ width = 0.5;
+
+ svp = art_svp_vpath_stroke (vpath,
+ gnome_canvas_join_gdk_to_art (line->join),
+ gnome_canvas_cap_gdk_to_art (line->cap),
+ width,
+ 4,
+ 0.25);
+ art_free (vpath);
+
+ gnome_canvas_item_update_svp_clip (item, &line->fill_svp, svp, clip_path);
+
+ if (line->first_arrow && line->first_coords) {
+ svp = svp_from_points (line->first_coords, NUM_ARROW_POINTS, affine);
+ gnome_canvas_item_update_svp_clip (item,
+ &line->first_svp, svp, clip_path);
+ }
+
+
+ if (line->last_arrow && line->last_coords) {
+ svp = svp_from_points (line->last_coords, NUM_ARROW_POINTS, affine);
+ gnome_canvas_item_update_svp_clip (item,
+ &line->last_svp, svp, clip_path);
+ }
+
+
+ } else {
+ set_line_gc_foreground (line);
+ set_line_gc_width (line);
+ set_stipple (line, line->stipple, TRUE);
+
+ get_bounds_canvas (line, &x1, &y1, &x2, &y2, affine);
+ gnome_canvas_update_bbox (item, x1, y1, x2, y2);
+ }
+}
+
+static void
+gnome_canvas_line_realize (GnomeCanvasItem *item)
+{
+ GnomeCanvasLine *line;
+
+ line = GNOME_CANVAS_LINE (item);
+
+ if (parent_class->realize)
+ (* parent_class->realize) (item);
+
+ line->gc = gdk_gc_new (item->canvas->layout.bin_window);
+
+#if 0
+ (* GNOME_CANVAS_ITEM_CLASS (item->object.klass)->update) (item, NULL, NULL, 0);
+#endif
+}
+
+static void
+gnome_canvas_line_unrealize (GnomeCanvasItem *item)
+{
+ GnomeCanvasLine *line;
+
+ line = GNOME_CANVAS_LINE (item);
+
+ g_object_unref (line->gc);
+ line->gc = NULL;
+
+ if (parent_class->unrealize)
+ (* parent_class->unrealize) (item);
+}
+
+static void
+item_to_canvas (GnomeCanvas *canvas, double *item_coords, GdkPoint *canvas_coords, int num_points,
+ int *num_drawn_points, double i2c[6], int x, int y)
+{
+ int i;
+ int old_cx, old_cy;
+ int cx, cy;
+ ArtPoint pi, pc;
+
+#ifdef VERBOSE
+ {
+ char str[128];
+ art_affine_to_string (str, i2c);
+ g_print ("line item_to_canvas %s\n", str);
+ }
+#endif
+
+ /* the first point is always drawn */
+
+ pi.x = item_coords[0];
+ pi.y = item_coords[1];
+ art_affine_point (&pc, &pi, i2c);
+ cx = floor (pc.x + 0.5);
+ cy = floor (pc.y + 0.5);
+ canvas_coords->x = cx - x;
+ canvas_coords->y = cy - y;
+ canvas_coords++;
+ old_cx = cx;
+ old_cy = cy;
+ *num_drawn_points = 1;
+
+ for (i = 1; i < num_points; i++) {
+ pi.x = item_coords[i * 2];
+ pi.y = item_coords[i * 2 + 1];
+ art_affine_point (&pc, &pi, i2c);
+ cx = floor (pc.x + 0.5);
+ cy = floor (pc.y + 0.5);
+ if (old_cx != cx || old_cy != cy) {
+ canvas_coords->x = cx - x;
+ canvas_coords->y = cy - y;
+ old_cx = cx;
+ old_cy = cy;
+ canvas_coords++;
+ (*num_drawn_points)++;
+ }
+ }
+}
+
+static void
+gnome_canvas_line_draw (GnomeCanvasItem *item, GdkDrawable *drawable,
+ int x, int y, int width, int height)
+{
+ GnomeCanvasLine *line;
+ GdkPoint static_points[NUM_STATIC_POINTS];
+ GdkPoint *points;
+ int actual_num_points_drawn;
+ double i2c[6];
+
+ line = GNOME_CANVAS_LINE (item);
+
+ if (line->num_points == 0)
+ return;
+
+ /* Build array of canvas pixel coordinates */
+
+ if (line->num_points <= NUM_STATIC_POINTS)
+ points = static_points;
+ else
+ points = g_new (GdkPoint, line->num_points);
+
+
+ gnome_canvas_item_i2c_affine (item, i2c);
+
+ item_to_canvas (item->canvas, line->coords, points, line->num_points,
+ &actual_num_points_drawn, i2c, x, y);
+
+ if (line->stipple)
+ gnome_canvas_set_stipple_origin (item->canvas, line->gc);
+
+ gdk_draw_lines (drawable, line->gc, points, actual_num_points_drawn);
+
+ if (points != static_points)
+ g_free (points);
+
+ /* Draw arrowheads */
+
+ points = static_points;
+
+ if (line->first_arrow) {
+ item_to_canvas (item->canvas, line->first_coords, points, NUM_ARROW_POINTS,
+ &actual_num_points_drawn, i2c, x, y);
+ gdk_draw_polygon (drawable, line->gc, TRUE, points, actual_num_points_drawn );
+ }
+
+ if (line->last_arrow) {
+ item_to_canvas (item->canvas, line->last_coords, points, NUM_ARROW_POINTS,
+ &actual_num_points_drawn, i2c, x, y);
+ gdk_draw_polygon (drawable, line->gc, TRUE, points, actual_num_points_drawn );
+ }
+}
+
+static double
+gnome_canvas_line_point (GnomeCanvasItem *item, double x, double y,
+ int cx, int cy, GnomeCanvasItem **actual_item)
+{
+ GnomeCanvasLine *line;
+ double *line_points = NULL, *coords;
+ double static_points[2 * NUM_STATIC_POINTS];
+ double poly[10];
+ double best, dist;
+ double dx, dy;
+ double width;
+ int num_points = 0, i;
+ int changed_miter_to_bevel;
+
+#ifdef VERBOSE
+ g_print ("gnome_canvas_line_point x, y = (%g, %g); cx, cy = (%d, %d)\n", x, y, cx, cy);
+#endif
+
+ line = GNOME_CANVAS_LINE (item);
+
+ *actual_item = item;
+
+ best = 1.0e36;
+
+ /* Handle smoothed lines by generating an expanded set ot points */
+
+ if (line->smooth && (line->num_points > 2)) {
+ /* FIXME */
+ } else {
+ num_points = line->num_points;
+ line_points = line->coords;
+ }
+
+ /* Compute a polygon for each edge of the line and test the point against it. The effective
+ * width of the line is adjusted so that it will be at least one pixel thick (so that zero
+ * pixel-wide lines can be pickedup as well).
+ */
+
+ if (line->width_pixels)
+ width = line->width / item->canvas->pixels_per_unit;
+ else
+ width = line->width;
+
+ if (width < (1.0 / item->canvas->pixels_per_unit))
+ width = 1.0 / item->canvas->pixels_per_unit;
+
+ changed_miter_to_bevel = 0;
+
+ for (i = num_points, coords = line_points; i >= 2; i--, coords += 2) {
+ /* If rounding is done around the first point, then compute distance between the
+ * point and the first point.
+ */
+
+ if (((line->cap == GDK_CAP_ROUND) && (i == num_points))
+ || ((line->join == GDK_JOIN_ROUND) && (i != num_points))) {
+ dx = coords[0] - x;
+ dy = coords[1] - y;
+ dist = sqrt (dx * dx + dy * dy) - width / 2.0;
+ if (dist < GNOME_CANVAS_EPSILON) {
+ best = 0.0;
+ goto done;
+ } else if (dist < best)
+ best = dist;
+ }
+
+ /* Compute the polygonal shape corresponding to this edge, with two points for the
+ * first point of the edge and two points for the last point of the edge.
+ */
+
+ if (i == num_points)
+ gnome_canvas_get_butt_points (coords[2], coords[3], coords[0], coords[1],
+ width, (line->cap == GDK_CAP_PROJECTING),
+ poly, poly + 1, poly + 2, poly + 3);
+ else if ((line->join == GDK_JOIN_MITER) && !changed_miter_to_bevel) {
+ poly[0] = poly[6];
+ poly[1] = poly[7];
+ poly[2] = poly[4];
+ poly[3] = poly[5];
+ } else {
+ gnome_canvas_get_butt_points (coords[2], coords[3], coords[0], coords[1],
+ width, FALSE,
+ poly, poly + 1, poly + 2, poly + 3);
+
+ /* If this line uses beveled joints, then check the distance to a polygon
+ * comprising the last two points of the previous polygon and the first two
+ * from this polygon; this checks the wedges that fill the mitered point.
+ */
+
+ if ((line->join == GDK_JOIN_BEVEL) || changed_miter_to_bevel) {
+ poly[8] = poly[0];
+ poly[9] = poly[1];
+
+ dist = gnome_canvas_polygon_to_point (poly, 5, x, y);
+ if (dist < GNOME_CANVAS_EPSILON) {
+ best = 0.0;
+ goto done;
+ } else if (dist < best)
+ best = dist;
+
+ changed_miter_to_bevel = FALSE;
+ }
+ }
+
+ if (i == 2)
+ gnome_canvas_get_butt_points (coords[0], coords[1], coords[2], coords[3],
+ width, (line->cap == GDK_CAP_PROJECTING),
+ poly + 4, poly + 5, poly + 6, poly + 7);
+ else if (line->join == GDK_JOIN_MITER) {
+ if (!gnome_canvas_get_miter_points (coords[0], coords[1],
+ coords[2], coords[3],
+ coords[4], coords[5],
+ width,
+ poly + 4, poly + 5, poly + 6, poly + 7)) {
+ changed_miter_to_bevel = TRUE;
+ gnome_canvas_get_butt_points (coords[0], coords[1], coords[2], coords[3],
+ width, FALSE,
+ poly + 4, poly + 5, poly + 6, poly + 7);
+ }
+ } else
+ gnome_canvas_get_butt_points (coords[0], coords[1], coords[2], coords[3],
+ width, FALSE,
+ poly + 4, poly + 5, poly + 6, poly + 7);
+
+ poly[8] = poly[0];
+ poly[9] = poly[1];
+
+ dist = gnome_canvas_polygon_to_point (poly, 5, x, y);
+ if (dist < GNOME_CANVAS_EPSILON) {
+ best = 0.0;
+ goto done;
+ } else if (dist < best)
+ best = dist;
+ }
+
+ /* If caps are rounded, check the distance to the cap around the final end point of the line */
+
+ if (line->cap == GDK_CAP_ROUND) {
+ dx = coords[0] - x;
+ dy = coords[1] - y;
+ dist = sqrt (dx * dx + dy * dy) - width / 2.0;
+ if (dist < GNOME_CANVAS_EPSILON) {
+ best = 0.0;
+ goto done;
+ } else
+ best = dist;
+ }
+
+ /* sometimes the GnomeCanvasItem::update signal will not have
+ been processed between deleting the arrow points and a call
+ to this routine -- this can cause a segfault here */
+ if ((line->first_arrow && !line->first_coords) ||
+ (line->last_arrow && !line->last_coords))
+ reconfigure_arrows(line);
+
+ /* If there are arrowheads, check the distance to them */
+
+ if (line->first_arrow) {
+ dist = gnome_canvas_polygon_to_point (line->first_coords, NUM_ARROW_POINTS, x, y);
+ if (dist < GNOME_CANVAS_EPSILON) {
+ best = 0.0;
+ goto done;
+ } else
+ best = dist;
+ }
+
+ if (line->last_arrow) {
+ dist = gnome_canvas_polygon_to_point (line->last_coords, NUM_ARROW_POINTS, x, y);
+ if (dist < GNOME_CANVAS_EPSILON) {
+ best = 0.0;
+ goto done;
+ } else
+ best = dist;
+ }
+
+done:
+
+ if ((line_points != static_points) && (line_points != line->coords))
+ g_free (line_points);
+
+ return best;
+}
+
+static void
+gnome_canvas_line_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2)
+{
+ GnomeCanvasLine *line;
+
+ line = GNOME_CANVAS_LINE (item);
+
+ if (line->num_points == 0) {
+ *x1 = *y1 = *x2 = *y2 = 0.0;
+ return;
+ }
+
+ get_bounds (line, x1, y1, x2, y2);
+}