/* * Bezier.c -- Implementation of bezier item. * * Authors : Patrick Lecoanet. * Creation date : Fri May 5 12:33:32 2000 * * $Id$ */ /* * Copyright (c) 1993 - 2000 CENA, Patrick Lecoanet -- * * This code 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. * * This code 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 this code; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include "Draw.h" #include "Item.h" #include "Geo.h" #include "Types.h" #include "WidgetInfo.h" #include "Image.h" #include "Color.h" #include #include static const char rcsid[] = "$Id$"; static const char compile_id[]="$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $"; /* * Bit offset of flags. */ #define FILLED_BIT 1<<0 /* If the item is filled with color/pattern */ #define CCW 1<<2 /* Tell if the controls are described in * clockwise or ccw order. */ #define FIRST_END_OK 1<<3 #define LAST_END_OK 1<<4 #define FILLED_OK 1<<5 #define RELIEF_OK 1<<6 /* ********************************************************************************** * * Specific Bezier item record * ********************************************************************************** */ typedef struct _BezierItemStruct { ItemStruct header; /* Public data */ ZnList points; unsigned int flags; ZnLineEnd first_end; /* These two are considered only if relief is flat */ ZnLineEnd last_end; LineStyle line_style; /* This is considered only if relief is flat */ int cap_style; ReliefStyle relief; int line_width; /* If 0 the path is not drawn, if <2 relief is flat */ Pixmap fill_pattern; ZnColorGradient fill_color; ZnGradientGeom grad_geom; Pixmap line_pattern; ZnColor line_color; char *tile_name; /* Private data */ ZnImage tile; ZnList dev_points; ZnColorGradient gradient; } BezierItemStruct, *BezierItem; static ZnAttrConfig bz_attrs[] = { { ZN_CONFIG_CAP_STYLE, "-capstyle", NULL, Tk_Offset(BezierItemStruct, cap_style), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_BOOL, "-composerotation", NULL, Tk_Offset(BezierItemStruct, header.flags), COMPOSE_ROTATION_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_BOOL, "-composescale", NULL, Tk_Offset(BezierItemStruct, header.flags), COMPOSE_SCALE_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_GRADIENT_COLOR, "-fillcolor", NULL, Tk_Offset(BezierItemStruct, fill_color), 0, ZN_DRAW_FLAG|ZN_BORDER_FLAG, False }, { ZN_CONFIG_PATTERN, "-fillpattern", NULL, Tk_Offset(BezierItemStruct, fill_pattern), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_BOOL, "-filled", NULL, Tk_Offset(BezierItemStruct, flags), FILLED_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_LINE_END, "-firstend", NULL, Tk_Offset(BezierItemStruct, first_end), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_GRADIENT_GEOM, "-gradient", NULL, Tk_Offset(BezierItemStruct, grad_geom), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_LINE_END, "-lastend", NULL, Tk_Offset(BezierItemStruct, last_end), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_COLOR, "-linecolor", NULL, Tk_Offset(BezierItemStruct, line_color), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_PATTERN, "-linepattern", NULL, Tk_Offset(BezierItemStruct, line_pattern), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_LINE_STYLE, "-linestyle", NULL, Tk_Offset(BezierItemStruct, line_style), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_DIM, "-linewidth", NULL, Tk_Offset(BezierItemStruct, line_width), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_PRI, "-priority", NULL, Tk_Offset(BezierItemStruct, header.priority), 0, ZN_DRAW_FLAG|ZN_REPICK_FLAG, False }, { ZN_CONFIG_RELIEF, "-relief", NULL, Tk_Offset(BezierItemStruct, relief), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_BOOL, "-sensitive", NULL, Tk_Offset(BezierItemStruct, header.flags), SENSITIVE_BIT, ZN_REPICK_FLAG, False }, { ZN_CONFIG_TAGS, "-tags", NULL, Tk_Offset(BezierItemStruct, header.tags), 0, 0, False }, { ZN_CONFIG_IMAGE, "-tile", NULL, Tk_Offset(BezierItemStruct, tile_name), 0, ZN_DRAW_FLAG|ZN_TILE_FLAG, False }, { ZN_CONFIG_BOOL, "-visible", NULL, Tk_Offset(BezierItemStruct, header.flags), VISIBLE_BIT, ZN_DRAW_FLAG|ZN_REPICK_FLAG|ZN_VIS_FLAG, False }, { ZN_CONFIG_END, NULL, NULL, 0, 0, 0 } }; /* ********************************************************************************** * * BzTileChange -- * ********************************************************************************** */ static void BzTileChange(ClientData client_data, int x, int y, int width, int height, int image_width, int image_height) { BezierItem bz = (BezierItem) client_data; InvalidateImage(bz->tile_name); ITEM.Invalidate((Item) bz, ZN_COORDS_FLAG); } /* ********************************************************************************** * * Init -- * ********************************************************************************** */ static int Init(Item item, int *argc, Tcl_Obj *CONST *args[]) { WidgetInfo *wi = item->wi; BezierItem bz = (BezierItem) item; Tcl_Obj **elems; int i, num_elems; ZnPoint p; bz->dev_points = NULL; bz->gradient = NULL; /* Init attributes */ SET(item->flags, VISIBLE_BIT); SET(item->flags, SENSITIVE_BIT); SET(item->flags, COMPOSE_ROTATION_BIT); SET(item->flags, COMPOSE_SCALE_BIT); item->priority = DEFAULT_BEZIER_PRIORITY; if (*argc < 1) { Tcl_AppendResult(wi->interp, " bezier coords expected", NULL); return ZN_ERROR; } if ((Tcl_ListObjGetElements(wi->interp, (*args)[0], &num_elems, &elems) == ZN_ERROR) || ((num_elems % 2) != 0)) { bz_error: Tcl_AppendResult(wi->interp, " malformed bezier coords", NULL); return ZN_ERROR; } bz->points = ZnListNew(num_elems/2, sizeof(ZnPoint)); for (i = 0; i < num_elems; i += 2) { if (Tcl_GetDoubleFromObj(wi->interp, elems[i], &p.x) == ZN_ERROR) { bz_error2: ZnListFree(bz->points); bz->points = NULL; goto bz_error; } if (Tcl_GetDoubleFromObj(wi->interp, elems[i+1], &p.y) == ZN_ERROR) { goto bz_error2; } ZnListAdd(bz->points, &p, ZnListTail); } (*args)++; (*argc)--; CLEAR(bz->flags, FILLED_BIT); bz->first_end = NULL; bz->last_end = NULL; bz->line_style = LINE_SIMPLE; bz->relief = RELIEF_FLAT; bz->line_width = 1; bz->tile_name = ""; bz->tile = ZnUnspecifiedImage; bz->fill_pattern = ZnUnspecifiedPattern; bz->line_pattern = ZnUnspecifiedPattern; bz->cap_style = CapRound; bz->fill_color = ZnGetColorGradient(wi->interp, wi->win, ZnNameOfColor(wi->fore_color)); bz->line_color = ZnGetColorByValue(wi->win, wi->fore_color); bz->grad_geom = NULL; return ZN_OK; } /* ********************************************************************************** * * Clone -- * ********************************************************************************** */ static void Clone(Item item) { BezierItem bz = (BezierItem) item; WidgetInfo *wi = item->wi; char *text; bz->dev_points = NULL; if (bz->gradient) { bz->gradient = ZnGetColorGradientByValue(bz->gradient); } if (bz->points) { bz->points = ZnListDuplicate(bz->points); } if (bz->line_pattern != ZnUnspecifiedPattern) { bz->line_pattern = Tk_GetBitmap(wi->interp, wi->win, Tk_NameOfBitmap(wi->dpy, bz->line_pattern)); } if (bz->first_end) { LineEndDuplicate(bz->first_end); } if (bz->last_end) { LineEndDuplicate(bz->last_end); } if (strlen(bz->tile_name) != 0) { text = ZnMalloc((strlen(bz->tile_name) + 1) * sizeof(char)); strcpy(text, bz->tile_name); bz->tile_name = text; bz->tile = Tk_GetImage(wi->interp, wi->win, bz->tile_name, BzTileChange, (ClientData) bz); } if (bz->fill_pattern != ZnUnspecifiedPattern) { bz->fill_pattern = Tk_GetBitmap(wi->interp, wi->win, Tk_NameOfBitmap(wi->dpy, bz->fill_pattern)); } bz->line_color = ZnGetColorByValue(wi->win, bz->line_color); bz->fill_color = ZnGetColorGradientByValue(bz->fill_color); if (bz->grad_geom) { bz->grad_geom = GradientGeomDuplicate(bz->grad_geom); } } /* ********************************************************************************** * * Destroy -- * ********************************************************************************** */ static void Destroy(Item item) { WidgetInfo *wi = item->wi; BezierItem bz = (BezierItem) item; if (bz->points) { ZnListFree(bz->points); } if (bz->dev_points) { ZnListFree(bz->dev_points); } if (bz->first_end) { LineEndDelete(bz->first_end); } if (bz->last_end) { LineEndDelete(bz->last_end); } if (bz->gradient) { ZnFreeColorGradient(bz->gradient); } if (bz->tile != ZnUnspecifiedImage) { Tk_FreeImage(bz->tile); bz->tile = ZnUnspecifiedImage; } if (strlen(bz->tile_name) != 0) { ZnFree(bz->tile_name); } if (bz->line_pattern != ZnUnspecifiedPattern) { Tk_FreeBitmap(wi->dpy, bz->line_pattern); } if (bz->fill_pattern != ZnUnspecifiedPattern) { Tk_FreeBitmap(wi->dpy, bz->fill_pattern); } ZnFreeColorGradient(bz->fill_color); ZnFreeColor(bz->line_color); if (bz->grad_geom) { GradientGeomDelete(bz->grad_geom); } } /* ********************************************************************************** * * Setup flags to control the precedence between the * graphical attributes. * ********************************************************************************** */ static void SetRenderFlags(BezierItem bz) { ASSIGN(bz->flags, FILLED_OK, ISSET(bz->flags, FILLED_BIT) && (ZnListSize(bz->points) > 2)); ASSIGN(bz->flags, RELIEF_OK, (bz->relief != RELIEF_FLAT) && (ZnListSize(bz->points) > 1) && (bz->line_width > 1)); ASSIGN(bz->flags, FIRST_END_OK, (bz->first_end != NULL) && (ZnListSize(bz->points) > 1) && ISCLEAR(bz->flags, FILLED_BIT) && bz->line_width && ISCLEAR(bz->flags, RELIEF_OK)); ASSIGN(bz->flags, LAST_END_OK, (bz->last_end != NULL) && (ZnListSize(bz->points) > 1) && ISCLEAR(bz->flags, FILLED_BIT) && bz->line_width && ISCLEAR(bz->flags, RELIEF_OK)); } /* ********************************************************************************** * * Configure -- * ********************************************************************************** */ static int Configure(Item item, int argc, Tcl_Obj *CONST argv[], int *flags) { WidgetInfo *wi = item->wi; BezierItem bz = (BezierItem) item; int status = ZN_OK; status = ITEM_P.ConfigureAttributes((char *) item, -1, argc, argv, flags); if (bz->gradient && (ISSET(*flags, ZN_BORDER_FLAG) || (bz->relief == RELIEF_FLAT))) { ZnFreeColorGradient(bz->gradient); bz->gradient = NULL; } if ((bz->relief != RELIEF_FLAT) && !bz->gradient) { bz->gradient = ZnGetReliefGradient(wi->interp, wi->win, ZnNameOfColor(ZnColorGradientMidColor(wi->win, bz->fill_color))); } if (ISSET(*flags, ZN_TILE_FLAG)) { Tk_Image tile; if (strcmp(bz->tile_name, "") != 0) { tile = Tk_GetImage(wi->interp, wi->win, bz->tile_name, BzTileChange, (ClientData) bz); if (tile == NULL) { /* * The name will not be in sync with the image in * this case. */ status = ZN_ERROR; } } else { tile = ZnUnspecifiedImage; } if (bz->tile != ZnUnspecifiedImage) { Tk_FreeImage(bz->tile); } bz->tile = tile; } return status; } /* ********************************************************************************** * * Query -- * ********************************************************************************** */ static int Query(Item item, int argc, Tcl_Obj *CONST argv[]) { if (ITEM_P.QueryAttribute((char *) item, -1, argv[0]) == ZN_ERROR) { return ZN_ERROR; } return ZN_OK; } static ZnBool TestCCW(ZnPoint *points, int num_points) { ZnPoint *p, *p_p, *p_n, min; ZnReal xprod; int i, min_index; if (num_points < 3) { return True; } /* * First find the lowest rightmost vertex. In X11 this is the * topmost one. */ p = points; min = *p; min_index = 0; for (i = 1, p++; i < num_points; i++, p++) { if ((p->y < min.y) || ((p->y == min.y) && (p->x > min.x))) { min_index = i; min = *p; } } /* * Then find the indices of the previous and next * vertices. */ p = &points[min_index]; p_p = &points[(min_index+(num_points-1))%num_points]; /* min_index-1 */ p_n = &points[(min_index+1)%num_points]; xprod = ((p_p->x*p->y - p_p->y*p->x) + (p_p->y*p_n->x - p_p->x*p_n->y) + (p->x*p_n->y - p->y*p_n->x)); return (xprod <= 0.0); /* Should be >= 0 but X11 has Y axis reverted. */ } /* ********************************************************************************** * * ComputeCoordinates -- * ********************************************************************************** */ static void ComputeCoordinates(Item item, ZnBool force) { WidgetInfo *wi = item->wi; BezierItem bz = (BezierItem) item; ZnPoint *points; ZnPoint end_points[LINE_END_POINTS]; ZnPoint *dev_points; int num_points; ZnBBox bbox; int lw; ResetBBox(&item->item_bounding_box); if (bz->points == NULL) { return; } SetRenderFlags(bz); points = (ZnPoint *) ZnListArray(bz->points); num_points = ZnListSize(bz->points); /* * Allocate space for devices coordinates */ if (bz->dev_points == NULL) { bz->dev_points = ZnListNew(num_points, sizeof(ZnPoint)); } ZnListAssertSize(bz->dev_points, num_points); dev_points = (ZnPoint *) ZnListArray(bz->dev_points); /* * Compute device coordinates. */ ZnTransformPoints(wi->current_transfo, points, dev_points, num_points); lw = bz->line_width; if (ISSET(bz->flags, RELIEF_OK)) { ASSIGN(bz->flags, CCW, TestCCW(dev_points, num_points)); /* * Compute the bounding box. */ GetPolygonReliefBBox(dev_points, num_points, ISCLEAR(bz->flags, CCW)?-lw:lw, &bbox); AddBBoxToBBox(&item->item_bounding_box, &bbox); return; } /* * Compute the bounding box. */ AddPointsToBBox(&item->item_bounding_box, dev_points, num_points); /* * Add the line width in all directions. * This overestimates the space needed to draw the polyline * but is simple. This is even more true for smoothed polygons but is * even faster. */ item->item_bounding_box.orig.x -= lw; item->item_bounding_box.orig.y -= lw; item->item_bounding_box.corner.x += lw; item->item_bounding_box.corner.y += lw; /* * Process arrows. */ if (ISSET(bz->flags, FIRST_END_OK)) { GetLineEnd(&dev_points[0], &dev_points[1], lw, bz->cap_style, bz->first_end, end_points); AddPointsToBBox(&item->item_bounding_box, end_points, LINE_END_POINTS); } if (ISSET(bz->flags, LAST_END_OK)) { GetLineEnd(&dev_points[num_points-1], &dev_points[num_points-2], lw, bz->cap_style, bz->last_end, end_points); AddPointsToBBox(&item->item_bounding_box, end_points, LINE_END_POINTS); } /* * Expand again the bounding box by one pixel in all * directions to take care of rounding errors. */ item->item_bounding_box.orig.x -= 1; item->item_bounding_box.orig.y -= 1; item->item_bounding_box.corner.x += 1; item->item_bounding_box.corner.y += 1; } /* ********************************************************************************** * * ToArea -- * Tell if the object is entirely outside (-1), * entirely inside (1) or in between (0). * ********************************************************************************** */ static int ToArea(Item item, ZnBBox *area, Tk_Uid tag_uid, int enclosed, ZnBool report) { BezierItem bz = (BezierItem) item; WidgetInfo *wi = item->wi; ZnPoint *points; ZnPoint end_points[LINE_END_POINTS]; int num_points, result=-1, result2; int lw = bz->line_width; if (bz->dev_points == NULL) { return -1; } /*printf("============== bezier %d ==============\n", item->id);*/ GetBezierPath(bz->dev_points, wi->work_pts); points = (ZnPoint *) ZnListArray(wi->work_pts); num_points = ZnListSize(wi->work_pts); if (ISSET(bz->flags, FILLED_OK)) { result = PolygonInBBox(points, num_points, area, NULL); if (result == 0) { return 0; } } if (lw > 0) { if (ISCLEAR(bz->flags, RELIEF_OK)) { result2 = PolylineInBBox(points, num_points, lw, bz->cap_style, JoinRound, area); } else { result2 = PolygonReliefInBBox(points, num_points, ISCLEAR(bz->flags, CCW)?-lw:lw, area); } if (ISCLEAR(bz->flags, FILLED_OK)) { if (result2 == 0) { return 0; } result = result2; } else if (result2 != result) { return 0; } /* * Check line ends. */ if (ISSET(bz->flags, FIRST_END_OK)) { GetLineEnd(&points[0], &points[1], lw, bz->cap_style, bz->first_end, end_points); if (PolygonInBBox(end_points, LINE_END_POINTS, area, NULL) != result) { return 0; } } if (ISSET(bz->flags, LAST_END_OK)) { GetLineEnd(&points[num_points-1], &points[num_points-2], lw, bz->cap_style, bz->last_end, end_points); if (PolygonInBBox(end_points, LINE_END_POINTS, area, NULL) != result) { return 0; } } } return result; } /* ********************************************************************************** * * Draw -- * ********************************************************************************** */ static void Draw(Item item) { WidgetInfo *wi = item->wi; BezierItem bz = (BezierItem) item; XGCValues values; int i, num_points; unsigned int gc_mask; ZnPoint *points; XPoint *xpoints = NULL; int lw = bz->line_width; if (bz->dev_points == NULL) { return; } GetBezierPath(bz->dev_points, wi->work_pts); points = (ZnPoint *) ZnListArray(wi->work_pts); num_points = ZnListSize(wi->work_pts); /* * Fill if requested. */ if (ISSET(bz->flags, FILLED_OK)) { if (bz->grad_geom) { ZnPoly poly; POLY_CONTOUR1(&poly, points, num_points); DrawPolygonGradient(wi, bz->grad_geom, bz->fill_color, &poly, &item->item_bounding_box); } else { values.foreground = ZnPixel(ZnColorGradientMidColor(wi->win, bz->fill_color)); gc_mask = GCFillStyle; if (bz->tile != ZnUnspecifiedImage) { /* Fill tiled */ Pixmap pmap = GetImagePixmap(wi->win, bz->tile_name, bz->tile, NULL); values.fill_style = FillTiled; values.tile = pmap; values.ts_x_origin = REAL_TO_INT(item->item_bounding_box.orig.x); values.ts_y_origin = REAL_TO_INT(item->item_bounding_box.orig.y); gc_mask |= GCTileStipXOrigin|GCTileStipYOrigin|GCTile; } else if (bz->fill_pattern != ZnUnspecifiedPattern) { /* Fill stippled */ values.fill_style = FillStippled; values.stipple = bz->fill_pattern; values.ts_x_origin = REAL_TO_INT(item->item_bounding_box.orig.x); values.ts_y_origin = REAL_TO_INT(item->item_bounding_box.orig.y); gc_mask |= GCTileStipXOrigin|GCTileStipYOrigin|GCStipple|GCForeground; } else { /* Fill solid */ values.fill_style = FillSolid; gc_mask |= GCForeground; } XChangeGC(wi->dpy, wi->gc, gc_mask, &values); if (!xpoints) { ZnListAssertSize(wi->work_xpts, num_points); xpoints = (XPoint *) ZnListArray(wi->work_xpts); for (i = 0; i < num_points; i++) { xpoints[i].x = REAL_TO_INT(points[i].x); xpoints[i].y = REAL_TO_INT(points[i].y); } } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xpoints, num_points, Complex, CoordModeOrigin); } } /* * Draw the lines between points */ if (lw) { ZnPoint end_points[LINE_END_POINTS]; XPoint xp[LINE_END_POINTS]; /* * Drawing with relief disables: ends, line style and line pattern. */ if (ISSET(bz->flags, RELIEF_OK)) { DrawPolygonRelief(wi, bz->relief, bz->gradient, points, num_points, ISCLEAR(bz->flags, CCW)?-lw:lw); } else { SetLineStyle(wi->dpy, wi->gc, bz->line_style); values.foreground = ZnPixel(bz->line_color); values.line_width = (lw == 1) ? 0 : lw; values.join_style = JoinRound; values.cap_style = bz->cap_style; if (bz->line_pattern == ZnUnspecifiedPattern) { values.fill_style = FillSolid; XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCLineWidth|GCJoinStyle|GCCapStyle|GCForeground, &values); } else { values.fill_style = FillStippled; values.stipple = bz->line_pattern; XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCStipple|GCLineWidth|GCJoinStyle|GCCapStyle|GCForeground, &values); } if (!xpoints) { ZnListAssertSize(wi->work_xpts, num_points); xpoints = (XPoint *) ZnListArray(wi->work_xpts); for (i = 0; i < num_points; i++) { xpoints[i].x = REAL_TO_INT(points[i].x); xpoints[i].y = REAL_TO_INT(points[i].y); } } XDrawLines(wi->dpy, wi->draw_buffer, wi->gc, xpoints, num_points, CoordModeOrigin); if (ISSET(bz->flags, FIRST_END_OK)) { GetLineEnd(&points[0], &points[1], lw, bz->cap_style, bz->first_end, end_points); for (i = 0; i < LINE_END_POINTS; i++) { xp[i].x = end_points[i].x; xp[i].y = end_points[i].y; } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, LINE_END_POINTS, Nonconvex, CoordModeOrigin); } if (ISSET(bz->flags, LAST_END_OK)) { GetLineEnd(&points[num_points-1], &points[num_points-2], lw, bz->cap_style, bz->last_end, end_points); for (i = 0; i < LINE_END_POINTS; i++) { xp[i].x = end_points[i].x; xp[i].y = end_points[i].y; } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, LINE_END_POINTS, Nonconvex, CoordModeOrigin); } } } } /* ********************************************************************************** * * Render -- * ********************************************************************************** */ static void Render(Item item) { /*WidgetInfo *wi = item->wi; BezierItem bz = (BezierItem) item;*/ } /* ********************************************************************************** * * IsSensitive -- * ********************************************************************************** */ static ZnBool IsSensitive(Item item, int item_part) { return (ISSET(item->flags, SENSITIVE_BIT) && item->parent->class->IsSensitive(item->parent, ZN_NO_PART)); } /* ********************************************************************************** * * Pick -- * ********************************************************************************** */ static double Pick(Item item, ZnPoint *p, Item start_item, int aperture, Item *a_item, int *part) { BezierItem bz = (BezierItem) item; WidgetInfo *wi = item->wi; double dist=1.0e40, new_dist; ZnPoint *points; ZnPoint end_points[LINE_END_POINTS]; int num_points; int lw = bz->line_width; if (bz->dev_points == NULL) { return dist; } /*printf("Pick in curve\n");*/ GetBezierPath(bz->dev_points, wi->work_pts); points = (ZnPoint *) ZnListArray(wi->work_pts); num_points = ZnListSize(wi->work_pts); if (ISSET(bz->flags, FILLED_OK)) { dist = PolygonToPointDist(points, num_points, p); if (dist <= 0.0) { return 0.0; } } if (lw > 0) { if (ISCLEAR(bz->flags, RELIEF_OK)) { new_dist = PolylineToPointDist(points, num_points, lw, bz->cap_style, JoinRound, p); if (new_dist < dist) { dist = new_dist; } if (dist <= 0.0) { return 0.0; } } else { new_dist = PolygonReliefToPointDist(points, num_points, ISCLEAR(bz->flags, CCW)?-lw:lw, p); if (new_dist < dist) { dist = new_dist; } if (dist <= 0.0) { return 0.0; } } } /* * Check line ends. */ if (ISSET(bz->flags, FIRST_END_OK)) { GetLineEnd(&points[0], &points[1], lw, bz->cap_style, bz->first_end, end_points); new_dist = PolygonToPointDist(end_points, LINE_END_POINTS, p); if (new_dist < dist) { dist = new_dist; } if (dist <= 0.0) { return 0.0; } } if (ISSET(bz->flags, LAST_END_OK)) { GetLineEnd(&points[num_points-1], &points[num_points-2], lw, bz->cap_style, bz->last_end, end_points); new_dist = PolygonToPointDist(end_points, LINE_END_POINTS, p); if (new_dist < dist) { dist = new_dist; } if (dist <= 0.0) { return 0.0; } } return dist; } /* ********************************************************************************** * * PostScript -- * ********************************************************************************** */ static void PostScript(Item item, PostScriptInfo ps_info) { } /* ********************************************************************************** * * GetClipVertices -- * Get the clipping shape. * ********************************************************************************** */ static ZnBool GetClipVertices(Item item, ZnPoly *poly) { BezierItem bz = (BezierItem) item; WidgetInfo *wi = item->wi; if (bz->dev_points) { GetBezierPath(bz->dev_points, wi->work_pts); POLY_CONTOUR1(poly, (ZnPoint *) ZnListArray(wi->work_pts), ZnListSize(wi->work_pts)); } return False; } /* ********************************************************************************** * * Coords -- * Return or edit the item vertices. * ********************************************************************************** */ static int Coords(Item item, int contour, int index, int cmd, ZnPoint **pts, int *num_pts) { BezierItem bz = (BezierItem) item; int num_points, i; ZnPoint *points; if ((cmd == COORDS_REPLACE) || (cmd == COORDS_REPLACE_ALL)) { if (*num_pts == 0) { Tcl_AppendResult(item->wi->interp, " coords command need at least 1 point on beziers", NULL); return ZN_ERROR; } if (cmd == COORDS_REPLACE_ALL) { ZnList tmp; replace_all: tmp = ZnListFromArray(*pts, *num_pts, sizeof(ZnPoint)); if (!bz->points) { ZnListEmpty(bz->points); } else { bz->points = ZnListNew(*num_pts, sizeof(ZnPoint)); } ZnListAppend(bz->points, tmp); ZnListFree(tmp); } else { if (!bz->points) { edit_err: Tcl_AppendResult(item->wi->interp, " coords command cannot edit empty beziers", NULL); return ZN_ERROR; } points = ZnListArray(bz->points); num_points = ZnListSize(bz->points); if (index < 0) { index += num_points; } if ((index < 0) || (index >= num_points)) { range_err: Tcl_AppendResult(item->wi->interp, " coord index out of range", NULL); return ZN_ERROR; } points[index] = (*pts)[0]; } ITEM.Invalidate(item, ZN_COORDS_FLAG); } else if ((cmd == COORDS_READ) || (cmd == COORDS_READ_ALL)) { if (!bz->points) { *num_pts = 0; *pts = NULL; return ZN_OK; } points = ZnListArray(bz->points); num_points = ZnListSize(bz->points); if (cmd == COORDS_READ_ALL) { *num_pts = num_points; *pts = points; } else { if (index < 0) { index += num_points; } if ((index < 0) || (index >= num_points)) { goto range_err; } *num_pts = 1; *pts = &points[index]; } } else if ((cmd == COORDS_ADD) || (cmd == COORDS_ADD_LAST)) { if (!bz->points) { goto replace_all; } else if (cmd == COORDS_ADD) { num_points = ZnListSize(bz->points); if (index < 0) { index += num_points; } if ((index < 0) || (index >= num_points)) { goto range_err; } for (i = 0; i < *num_pts; i++, index++) { ZnListAdd(bz->points, &(*pts)[i], index); } } else { ZnList tmp; tmp = ZnListFromArray(*pts, *num_pts, sizeof(ZnPoint)); ZnListAppend(bz->points, tmp); ZnListFree(tmp); } ITEM.Invalidate(item, ZN_COORDS_FLAG); } else if (cmd == COORDS_REMOVE) { if (!bz->points) { goto edit_err; } points = ZnListArray(bz->points); num_points = ZnListSize(bz->points); if (index < 0) { index += num_points; } if ((index < 0) || (index >= num_points)) { goto range_err; } ZnListDelete(bz->points, index); ITEM.Invalidate(item, ZN_COORDS_FLAG); } return ZN_OK; } /* ********************************************************************************** * * PickVertex -- * Return in 'vertex' the vertex closest to p and in 'o_vertex' the * opposite vertex on the closest edge, if such an edge exists or -1 * in the other case. * ********************************************************************************** */ static void PickVertex(Item item, ZnPoint *p, int *contour, int *vertex, int *o_vertex) { BezierItem bz = (BezierItem) item; int i, k, num_points; ZnPoint *points; ZnReal dist=1.0e40, new_dist, dist2; *contour = *vertex = *o_vertex = -1; if ((bz->line_width > 0) || ISSET(bz->flags, FILLED_OK)) { points = (ZnPoint *) ZnListArray(bz->dev_points); num_points = ZnListSize(bz->dev_points); for (i = 0; i < num_points; i++) { new_dist = hypot(points[i].x - p->x, points[i].y - p->y); if (new_dist < dist) { dist = new_dist; *contour = 0; *vertex = i; } } /* * Update the opposite vertex. */ i = (*vertex+1) % num_points; new_dist = LineToPointDist(&points[*vertex], &points[i], p); k = ((unsigned)(*vertex-1)) % num_points; dist2 = LineToPointDist(&points[*vertex], &points[k], p); if (dist2 < new_dist) { *o_vertex = k; } else { *o_vertex = i; } } } /* ********************************************************************************** * * Exported functions struct -- * ********************************************************************************** */ static ItemClassStruct BEZIER_ITEM_CLASS = { sizeof(BezierItemStruct), False, /* has_fields */ 0, /* num_parts */ False, /* has_anchors */ "bezier", bz_attrs, Init, Clone, Destroy, Configure, Query, NULL, NULL, GetClipVertices, Coords, NULL, /* InsertChars */ NULL, /* DeleteChars */ NULL, /* Cursor */ NULL, /* Index */ NULL, /* Part */ NULL, /* Selection */ NULL, /* Contour */ ComputeCoordinates, ToArea, Draw, Render, IsSensitive, Pick, PickVertex, /* PickVertex */ PostScript }; ZnItemClassId ZnBezier = (ZnItemClassId) &BEZIER_ITEM_CLASS;