/* * 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@ */ /* Miscellaneous utility functions for the 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 */ #include /* needed for M_PI_2 under 'gcc -ansi -predantic' on GNU/Linux */ #ifndef _BSD_SOURCE # define _BSD_SOURCE 1 #endif #include #include #include #include "gnome-canvas.h" #include "gnome-canvas-util.h" #include #include #include #include #include #include #include /** * gnome_canvas_get_miter_points: * @x1: X coordinate of the first point * @y1: Y coordinate of the first point * @x2: X coordinate of the second (angle) point * @y2: Y coordinate of the second (angle) point * @x3: X coordinate of the third point * @y3: Y coordinate of the third point * @width: Width of the line * @mx1: The X coordinate of the first miter point is returned here. * @my1: The Y coordinate of the first miter point is returned here. * @mx2: The X coordinate of the second miter point is returned here. * @my2: The Y coordinate of the second miter point is returned here. * * Given three points forming an angle, computes the coordinates of the inside * and outside points of the mitered corner formed by a line of a given width at * that angle. * * Return value: FALSE if the angle is less than 11 degrees (this is the same * threshold as X uses. If this occurs, the return points are not modified. * Otherwise, returns TRUE. **/ gint gnome_canvas_get_miter_points (gdouble x1, gdouble y1, gdouble x2, gdouble y2, gdouble x3, gdouble y3, gdouble width, gdouble *mx1, gdouble *my1, gdouble *mx2, gdouble *my2) { gdouble theta1; /* angle of segment p2-p1 */ gdouble theta2; /* angle of segment p2-p3 */ gdouble theta; /* angle between line segments */ gdouble theta3; /* angle that bisects theta1 and theta2 and points to p1 */ gdouble dist; /* distance of miter points from p2 */ gdouble dx, dy; /* x and y offsets corresponding to dist */ #define ELEVEN_DEGREES (11.0 * G_PI / 180.0) if (y2 == y1) theta1 = (x2 < x1) ? 0.0 : G_PI; else if (x2 == x1) theta1 = (y2 < y1) ? G_PI_2 : -G_PI_2; else theta1 = atan2 (y1 - y2, x1 - x2); if (y3 == y2) theta2 = (x3 > x2) ? 0 : G_PI; else if (x3 == x2) theta2 = (y3 > y2) ? G_PI_2 : -G_PI_2; else theta2 = atan2 (y3 - y2, x3 - x2); theta = theta1 - theta2; if (theta > G_PI) theta -= 2.0 * G_PI; else if (theta < -G_PI) theta += 2.0 * G_PI; if ((theta < ELEVEN_DEGREES) && (theta > -ELEVEN_DEGREES)) return FALSE; dist = 0.5 * width / sin (0.5 * theta); if (dist < 0.0) dist = -dist; theta3 = (theta1 + theta2) / 2.0; if (sin (theta3 - (theta1 + G_PI)) < 0.0) theta3 += G_PI; dx = dist * cos (theta3); dy = dist * sin (theta3); *mx1 = x2 + dx; *mx2 = x2 - dx; *my1 = y2 + dy; *my2 = y2 - dy; return TRUE; } /** * gnome_canvas_get_butt_points: * @x1: X coordinate of first point in the line * @y1: Y cooordinate of first point in the line * @x2: X coordinate of second point (endpoint) of the line * @y2: Y coordinate of second point (endpoint) of the line * @width: Width of the line * @project: Whether the butt points should project out by width/2 distance * @bx1: X coordinate of first butt point is returned here * @by1: Y coordinate of first butt point is returned here * @bx2: X coordinate of second butt point is returned here * @by2: Y coordinate of second butt point is returned here * * Computes the butt points of a line segment. **/ void gnome_canvas_get_butt_points (gdouble x1, gdouble y1, gdouble x2, gdouble y2, gdouble width, gint project, gdouble *bx1, gdouble *by1, gdouble *bx2, gdouble *by2) { gdouble length; gdouble dx, dy; width *= 0.5; dx = x2 - x1; dy = y2 - y1; length = sqrt (dx * dx + dy * dy); if (length < GNOME_CANVAS_EPSILON) { *bx1 = *bx2 = x2; *by1 = *by2 = y2; } else { dx = -width * (y2 - y1) / length; dy = width * (x2 - x1) / length; *bx1 = x2 + dx; *bx2 = x2 - dx; *by1 = y2 + dy; *by2 = y2 - dy; if (project) { *bx1 += dy; *bx2 += dy; *by1 -= dx; *by2 -= dx; } } } /** * gnome_canvas_polygon_to_point: * @poly: Vertices of the polygon. X coordinates are in the even indices, and Y * coordinates are in the odd indices * @num_points: Number of points in the polygon * @x: X coordinate of the point * @y: Y coordinate of the point * * Computes the distance between a point and a polygon. * * Return value: The distance from the point to the polygon, or zero if the * point is inside the polygon. **/ gdouble gnome_canvas_polygon_to_point (gdouble *poly, gint num_points, gdouble x, gdouble y) { gdouble best; gint intersections; gint i; gdouble *p; gdouble dx, dy; /* Iterate through all the edges in the polygon, updating best and * intersections. * * When computing intersections, include left X coordinate of line * within its range, but not Y coordinate. Otherwise if the point * lies exactly below a vertex we'll count it as two intersections. */ best = 1.0e36; intersections = 0; for (i = num_points, p = poly; i > 1; i--, p += 2) { gdouble px, py, dist; /* Compute the point on the current edge closest to the * point and update the intersection count. This must be * done separately for vertical edges, horizontal edges, * and others. */ if (p[2] == p[0]) { /* Vertical edge */ px = p[0]; if (p[1] >= p[3]) { py = MIN (p[1], y); py = MAX (py, p[3]); } else { py = MIN (p[3], y); py = MAX (py, p[1]); } } else if (p[3] == p[1]) { /* Horizontal edge */ py = p[1]; if (p[0] >= p[2]) { px = MIN (p[0], x); px = MAX (px, p[2]); if ((y < py) && (x < p[0]) && (x >= p[2])) intersections++; } else { px = MIN (p[2], x); px = MAX (px, p[0]); if ((y < py) && (x < p[2]) && (x >= p[0])) intersections++; } } else { gdouble m1, b1, m2, b2; gint lower; /* Diagonal edge. Convert the edge to a line equation (y = m1*x + b1), then * compute a line perpendicular to this edge but passing through the point, * (y = m2*x + b2). */ m1 = (p[3] - p[1]) / (p[2] - p[0]); b1 = p[1] - m1 * p[0]; m2 = -1.0 / m1; b2 = y - m2 * x; px = (b2 - b1) / (m1 - m2); py = m1 * px + b1; if (p[0] > p[2]) { if (px > p[0]) { px = p[0]; py = p[1]; } else if (px < p[2]) { px = p[2]; py = p[3]; } } else { if (px > p[2]) { px = p[2]; py = p[3]; } else if (px < p[0]) { px = p[0]; py = p[1]; } } lower = (m1 * x + b1) > y; if (lower && (x >= MIN (p[0], p[2])) && (x < MAX (p[0], p[2]))) intersections++; } /* Compute the distance to the closest point, and see if that is the best so far */ dx = x - px; dy = y - py; dist = sqrt (dx * dx + dy * dy); if (dist < best) best = dist; } /* We've processed all the points. If the number of * intersections is odd, the point is inside the polygon. */ if (intersections & 0x1) return 0.0; else return best; } /* Here are some helper functions for aa rendering: */ /** * gnome_canvas_update_svp: * @canvas: the canvas containing the svp that needs updating. * @p_svp: a pointer to the existing svp * @new_svp: the new svp * * Sets the svp to the new value, requesting repaint on what's changed. This * function takes responsibility for freeing new_svp. **/ void gnome_canvas_update_svp (GnomeCanvas *canvas, ArtSVP **p_svp, ArtSVP *new_svp) { ArtSVP *old_svp; ArtSVP *diff G_GNUC_UNUSED; ArtUta *repaint_uta; old_svp = *p_svp; if (old_svp != NULL) { ArtDRect bb; art_drect_svp (&bb, old_svp); if ((bb.x1 - bb.x0) * (bb.y1 - bb.y0) > (64 * 64)) { repaint_uta = art_uta_from_svp (old_svp); gnome_canvas_request_redraw_uta (canvas, repaint_uta); } else { ArtIRect ib; art_drect_to_irect (&ib, &bb); gnome_canvas_request_redraw (canvas, ib.x0, ib.y0, ib.x1, ib.y1); } art_svp_free (old_svp); } if (new_svp != NULL) { ArtDRect bb; art_drect_svp (&bb, new_svp); if ((bb.x1 - bb.x0) * (bb.y1 - bb.y0) > (64 * 64)) { repaint_uta = art_uta_from_svp (new_svp); gnome_canvas_request_redraw_uta (canvas, repaint_uta); } else { ArtIRect ib; art_drect_to_irect (&ib, &bb); gnome_canvas_request_redraw (canvas, ib.x0, ib.y0, ib.x1, ib.y1); } } *p_svp = new_svp; } /** * gnome_canvas_update_svp_clip: * @canvas: the canvas containing the svp that needs updating. * @p_svp: a pointer to the existing svp * @new_svp: the new svp * @clip_svp: a clip path, if non-null * * Sets the svp to the new value, clipping if necessary, and requesting repaint * on what's changed. This function takes responsibility for freeing new_svp. **/ void gnome_canvas_update_svp_clip (GnomeCanvas *canvas, ArtSVP **p_svp, ArtSVP *new_svp, ArtSVP *clip_svp) { ArtSVP *clipped_svp; if (clip_svp != NULL) { clipped_svp = art_svp_intersect (new_svp, clip_svp); art_svp_free (new_svp); } else { clipped_svp = new_svp; } gnome_canvas_update_svp (canvas, p_svp, clipped_svp); } /** * gnome_canvas_item_reset_bounds: * @item: A canvas item * * Resets the bounding box of a canvas item to an empty rectangle. **/ void gnome_canvas_item_reset_bounds (GnomeCanvasItem *item) { item->x1 = 0.0; item->y1 = 0.0; item->x2 = 0.0; item->y2 = 0.0; } /** * gnome_canvas_item_update_svp: * @item: the canvas item containing the svp that needs updating. * @p_svp: a pointer to the existing svp * @new_svp: the new svp * * Sets the svp to the new value, requesting repaint on what's changed. This * function takes responsibility for freeing new_svp. This routine also adds the * svp's bbox to the item's. **/ void gnome_canvas_item_update_svp (GnomeCanvasItem *item, ArtSVP **p_svp, ArtSVP *new_svp) { ArtDRect bbox; gnome_canvas_update_svp (item->canvas, p_svp, new_svp); if (new_svp) { bbox.x0 = item->x1; bbox.y0 = item->y1; bbox.x1 = item->x2; bbox.y1 = item->y2; art_drect_svp_union (&bbox, new_svp); item->x1 = bbox.x0; item->y1 = bbox.y0; item->x2 = bbox.x1; item->y2 = bbox.y1; } } /** * gnome_canvas_item_update_svp_clip: * @item: the canvas item containing the svp that needs updating. * @p_svp: a pointer to the existing svp * @new_svp: the new svp * @clip_svp: a clip path, if non-null * * Sets the svp to the new value, clipping if necessary, and requesting repaint * on what's changed. This function takes responsibility for freeing new_svp. **/ void gnome_canvas_item_update_svp_clip (GnomeCanvasItem *item, ArtSVP **p_svp, ArtSVP *new_svp, ArtSVP *clip_svp) { ArtSVP *clipped_svp; if (clip_svp != NULL) { clipped_svp = art_svp_intersect (new_svp, clip_svp); art_svp_free (new_svp); } else { clipped_svp = new_svp; } gnome_canvas_item_update_svp (item, p_svp, clipped_svp); } /** * gnome_canvas_item_request_redraw_svp * @item: the item containing the svp * @svp: the svp that needs to be redrawn * * Request redraw of the svp if in aa mode, or the entire item in in xlib mode. **/ void gnome_canvas_item_request_redraw_svp (GnomeCanvasItem *item, const ArtSVP *svp) { gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2); } /** * gnome_canvas_update_bbox: * @item: the canvas item needing update * @x1: Left coordinate of the new bounding box * @y1: Top coordinate of the new bounding box * @x2: Right coordinate of the new bounding box * @y2: Bottom coordinate of the new bounding box * * Sets the bbox to the new value, requesting full repaint. **/ void gnome_canvas_update_bbox (GnomeCanvasItem *item, gint x1, gint y1, gint x2, gint y2) { gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2); item->x1 = x1; item->y1 = y1; item->x2 = x2; item->y2 = y2; gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2); } /** * gnome_canvas_join_gdk_to_art * @gdk_join: a join type, represented in GDK format * * Convert from GDK line join specifier to libart. * * Return value: The line join specifier in libart format. **/ ArtPathStrokeJoinType gnome_canvas_join_gdk_to_art (GdkJoinStyle gdk_join) { switch (gdk_join) { case GDK_JOIN_MITER: return ART_PATH_STROKE_JOIN_MITER; case GDK_JOIN_ROUND: return ART_PATH_STROKE_JOIN_ROUND; case GDK_JOIN_BEVEL: return ART_PATH_STROKE_JOIN_BEVEL; default: g_assert_not_reached (); return ART_PATH_STROKE_JOIN_MITER; /* shut up the compiler */ } } /** * gnome_canvas_cap_gdk_to_art * @gdk_cap: a cap type, represented in GDK format * * Convert from GDK line cap specifier to libart. * * Return value: The line cap specifier in libart format. **/ ArtPathStrokeCapType gnome_canvas_cap_gdk_to_art (GdkCapStyle gdk_cap) { switch (gdk_cap) { case GDK_CAP_BUTT: case GDK_CAP_NOT_LAST: return ART_PATH_STROKE_CAP_BUTT; case GDK_CAP_ROUND: return ART_PATH_STROKE_CAP_ROUND; case GDK_CAP_PROJECTING: return ART_PATH_STROKE_CAP_SQUARE; default: g_assert_not_reached (); return ART_PATH_STROKE_CAP_BUTT; /* shut up the compiler */ } } /** * gnome_canvas_cairo_create_scratch: * * Create a scratch #cairo_t. This is useful for measuring purposes or * calling functions like cairo_in_fill(). * * Returns: A new cairo_t. Destroy with cairo_destroy() after use. **/ cairo_t * gnome_canvas_cairo_create_scratch (void) { cairo_surface_t *surface; cairo_t *cr; surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0); cr = cairo_create (surface); cairo_surface_destroy (surface); return cr; }