From cafd483e81aaa7f672e4b9643861531b1231e92e Mon Sep 17 00:00:00 2001 From: lecoanet Date: Thu, 11 May 2000 13:50:30 +0000 Subject: R�alisation des polygones multi-contours et des op�rations g�om�triques sur les polygones. S�paration de la partie {smooth, Bezier} dans l'item Bezier. --- generic/Curve.c | 997 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 581 insertions(+), 416 deletions(-) (limited to 'generic') diff --git a/generic/Curve.c b/generic/Curve.c index 0c6704d..38faabb 100644 --- a/generic/Curve.c +++ b/generic/Curve.c @@ -34,6 +34,7 @@ #include "WidgetInfo.h" #include "Image.h" #include "Color.h" +#include "gpc/gpc.h" #include #include @@ -48,17 +49,13 @@ static const char compile_id[]="$Compile: " __FILE__ " " __DATE__ " " __TIME__ " */ #define FILLED_BIT 1<<0 /* If the item is filled with color/pattern */ #define MARKED_BIT 1<<1 /* If the vertices are marked by a symbol */ -#define SMOOTHED_BIT 1<<2 /* If we `smooth' the path with Bezier curves */ -#define BEZIER_BIT 1<<3 /* If we interpret the vertices as Bezier controls. */ - -#define CCW 1<<5 /* Tell if the vertices are described in - * clockwise or ccw order. */ +#define CLOSED_BIT 1<<2 /* If the outline should be closed automatically */ +#define REDUCED_BIT 1<<5 /* Tell if the contours are in the most reduced + * form. */ #define FIRST_END_OK 1<<6 #define LAST_END_OK 1<<7 #define FILLED_OK 1<<8 -#define SMOOTHED_OK 1<<9 #define RELIEF_OK 1<<10 -#define BEZIER_OK 1<<11 #define MARKER_OK 1<<12 @@ -73,7 +70,7 @@ typedef struct _CurveItemStruct { ItemStruct header; /* Public data */ - ZnList points; + ZnPoly shape; unsigned int flags; Pixmap marker; ZnLineEnd first_end; /* These two are considered only if relief is flat */ @@ -93,22 +90,17 @@ typedef struct _CurveItemStruct { /* Private data */ ZnImage tile; - ZnList dev_points; + ZnPoly dev_shape; ZnColorGradient gradient; + gpc_tristrip tristrip; } CurveItemStruct, *CurveItem; -/* - * Need ZN_COORDS_FLAG in -smoothed and -bezier because - * -relief may need a reversing of vertices and this is done - * lazily. - */ static ZnAttrConfig cv_attrs[] = { - { ZN_CONFIG_BOOL, "-bezier", NULL, - Tk_Offset(CurveItemStruct, flags), BEZIER_BIT, - ZN_COORDS_FLAG, False }, { ZN_CONFIG_CAP_STYLE, "-capstyle", NULL, Tk_Offset(CurveItemStruct, cap_style), 0, ZN_COORDS_FLAG, False }, + { ZN_CONFIG_BOOL, "-closed", NULL, + Tk_Offset(CurveItemStruct, flags), CLOSED_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_BOOL, "-composerotation", NULL, Tk_Offset(CurveItemStruct, header.flags), COMPOSE_ROTATION_BIT, ZN_COORDS_FLAG, False }, @@ -152,9 +144,6 @@ static ZnAttrConfig cv_attrs[] = { { ZN_CONFIG_BOOL, "-sensitive", NULL, Tk_Offset(CurveItemStruct, header.flags), SENSITIVE_BIT, ZN_REPICK_FLAG, False }, - { ZN_CONFIG_BOOL, "-smoothed", NULL, - Tk_Offset(CurveItemStruct, flags), SMOOTHED_BIT, - ZN_COORDS_FLAG, False }, { ZN_CONFIG_TAGS, "-tags", NULL, Tk_Offset(CurveItemStruct, header.tags), 0, 0, False }, { ZN_CONFIG_IMAGE, "-tile", NULL, @@ -207,12 +196,14 @@ Init(Item item, CurveItem cv = (CurveItem) item; Arg *elems; int i, result, num_elems; - ZnPoint p; + ZnPoint *p, *points; + double dbl; #ifdef PTK LangFreeProc *freeProc = NULL; #endif - cv->dev_points = NULL; + POLY_INIT(&cv->dev_shape); + cv->tristrip.num_strips = 0; cv->gradient = NULL; /* Init attributes */ @@ -220,7 +211,10 @@ Init(Item item, SET(item->flags, SENSITIVE_BIT); SET(item->flags, COMPOSE_ROTATION_BIT); SET(item->flags, COMPOSE_SCALE_BIT); - item->priority = DEFAULT_MULTI_POINT_PRIORITY; + SET(item->flags, CLOSED_BIT); + SET(item->flags, REDUCED_BIT); + + item->priority = DEFAULT_CURVE_PRIORITY; if (*argc < 1) { Tcl_AppendResult(wi->interp, " curve coords expected", NULL); @@ -238,21 +232,27 @@ Init(Item item, return ZN_ERROR; } - cv->points = ZnListNew(num_elems/2, sizeof(ZnPoint)); - for (i = 0; i < num_elems; i += 2) { - if (Tcl_GetDouble(wi->interp, elems[i], &p.x) == ZN_ERROR) { - cv_error2: + if (num_elems == 0) { + POLY_INIT(&cv->shape); + } + else { + p = points = (ZnPoint *) ZnMalloc(num_elems/2 * sizeof(ZnPoint)); + POLY_CONTOUR1(&cv->shape, points, num_elems/2); + for (i = 0; i < num_elems; i += 2, p++) { + if (Tcl_GetDouble(wi->interp, elems[i], &dbl) == ZN_ERROR) { + cv_error2: #ifndef PTK - Tcl_Free((char *) elems); + Tcl_Free((char *) elems); #endif - ZnListFree(cv->points); - cv->points = NULL; - goto cv_error; - } - if (Tcl_GetDouble(wi->interp, elems[i+1], &p.y) == ZN_ERROR) { - goto cv_error2; + POLY_FREE(&cv->shape); + goto cv_error; + } + p->x = dbl; + if (Tcl_GetDouble(wi->interp, elems[i+1], &dbl) == ZN_ERROR) { + goto cv_error2; + } + p->y = dbl; } - ZnListAdd(cv->points, &p, ZnListTail); } (*args)++; (*argc)--; @@ -265,8 +265,6 @@ Init(Item item, #endif CLEAR(cv->flags, FILLED_BIT); - CLEAR(cv->flags, SMOOTHED_BIT); - CLEAR(cv->flags, BEZIER_BIT); cv->first_end = NULL; cv->last_end = NULL; cv->line_style = LINE_SIMPLE; @@ -308,15 +306,35 @@ Clone(Item item) CurveItem cv = (CurveItem) item; WidgetInfo *wi = item->wi; char *text; + int i; + ZnContour *conts; + ZnBool *holes; + + POLY_INIT(&cv->dev_shape); + + if (cv->shape.num_contours) { + conts = cv->shape.contours; + holes = cv->shape.holes; + if (cv->shape.contours != &cv->shape.contour1) { + cv->shape.contours = (ZnContour *) ZnMalloc(cv->shape.num_contours*sizeof(ZnContour)); + if (cv->shape.holes) { + cv->shape.holes = (ZnBool *) ZnMalloc(cv->shape.num_contours*sizeof(ZnBool)); + } + } + for (i = 0; i < cv->shape.num_contours; i++) { + if (cv->shape.holes) { + cv->shape.holes[i] = holes[i]; + } + cv->shape.contours[i].num_points = conts[i].num_points; + cv->shape.contours[i].points = (ZnPoint *) ZnMalloc(conts[i].num_points*sizeof(ZnPoint)); + memcpy(cv->shape.contours[i].points, conts[i].points, + conts[i].num_points*sizeof(ZnPoint)); + } + } - cv->dev_points = NULL; - if (cv->gradient) { cv->gradient = ZnGetColorGradientByValue(cv->gradient); } - if (cv->points) { - cv->points = ZnListDuplicate(cv->points); - } if (cv->line_pattern != ZnUnspecifiedPattern) { cv->line_pattern = Tk_GetBitmap(wi->interp, wi->win, Tk_NameOfBitmap(wi->dpy, cv->line_pattern)); @@ -363,13 +381,10 @@ Destroy(Item item) { WidgetInfo *wi = item->wi; CurveItem cv = (CurveItem) item; + + POLY_FREE(&cv->shape); + POLY_FREE(&cv->dev_shape); - if (cv->points) { - ZnListFree(cv->points); - } - if (cv->dev_points) { - ZnListFree(cv->dev_points); - } if (cv->first_end) { LineEndDelete(cv->first_end); } @@ -416,35 +431,30 @@ static void SetRenderFlags(CurveItem cv) { ASSIGN(cv->flags, FILLED_OK, - ISSET(cv->flags, FILLED_BIT) && (ZnListSize(cv->points) > 2)); - - ASSIGN(cv->flags, BEZIER_OK, - ISSET(cv->flags, BEZIER_BIT) && (ZnListSize(cv->points) > 2) && - (ISSET(cv->flags, FILLED_BIT) || cv->line_width)); - - ASSIGN(cv->flags, SMOOTHED_OK, - ISSET(cv->flags, SMOOTHED_BIT) && (ZnListSize(cv->points) > 2) && - ISCLEAR(cv->flags, BEZIER_OK) && - (ISSET(cv->flags, FILLED_BIT) || cv->line_width)); + ISSET(cv->flags, FILLED_BIT) && (cv->shape.num_contours >= 1)); ASSIGN(cv->flags, RELIEF_OK, - (cv->relief != RELIEF_FLAT) && (ZnListSize(cv->points) > 1) && - (cv->line_width > 1)/* && ISCLEAR(cv->flags, SMOOTHED_OK) && - ISCLEAR(cv->flags, BEZIER_OK)*/); + (cv->relief != RELIEF_FLAT) && + (cv->shape.num_contours >= 1) && + (cv->line_width > 1)); ASSIGN(cv->flags, MARKER_OK, - (cv->marker != ZnUnspecifiedPattern) && ISCLEAR(cv->flags, BEZIER_OK) && - ISCLEAR(cv->flags, SMOOTHED_OK) && ISCLEAR(cv->flags, RELIEF_OK)); + (cv->marker != ZnUnspecifiedPattern) && + ISCLEAR(cv->flags, RELIEF_OK)); ASSIGN(cv->flags, FIRST_END_OK, - (cv->first_end != NULL) && (ZnListSize(cv->points) > 1) && + (cv->first_end != NULL) && + (cv->shape.num_contours == 1) && (cv->shape.contours[0].num_points > 1) && ISCLEAR(cv->flags, FILLED_BIT) && cv->line_width && - ISCLEAR(cv->flags, RELIEF_OK)); + ISCLEAR(cv->flags, RELIEF_OK) && + ISCLEAR(cv->flags, CLOSED_BIT)); ASSIGN(cv->flags, LAST_END_OK, - (cv->last_end != NULL) && (ZnListSize(cv->points) > 1) && + (cv->last_end != NULL) && + (cv->shape.num_contours == 1) && (cv->shape.contours[0].num_points > 1) && ISCLEAR(cv->flags, FILLED_BIT) && cv->line_width && - ISCLEAR(cv->flags, RELIEF_OK)); + ISCLEAR(cv->flags, RELIEF_OK) && + ISCLEAR(cv->flags, CLOSED_BIT)); } @@ -499,8 +509,6 @@ Configure(Item item, cv->tile = tile; } - SetRenderFlags(cv); - return status; } @@ -556,6 +564,8 @@ TestCCW(ZnPoint *points, * vertices. */ p = &points[min_index]; + /*printf("min index %d, prev %d, next %d\n", min_index, + (min_index+(num_points-1))%num_points, (min_index+1)%num_points);*/ 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) + @@ -566,6 +576,30 @@ TestCCW(ZnPoint *points, /* + * Create a reduced polygon from an unknown one by + * adding/clipping all the shapes/holes in turn. + */ +static void +ReduceContours(ZnPoly *poly_in, + ZnPoly *poly_out) +{ + int i; + ZnContour *c; + ZnBool *hole; + ZnPoly cpoly, rpoly; + + POLY_INIT(poly_out); + for (i = 0, hole = poly_in->holes, c = poly_in->contours; + i < poly_in->num_contours; i++, c++, hole++) { + POLY_CONTOUR1(&cpoly, c->points, c->num_points); + gpc_polygon_clip(*hole?GPC_DIFF:GPC_UNION, (gpc_polygon *) &poly_out, + (gpc_polygon *) &cpoly, (gpc_polygon *) &rpoly); + POLY_SET(poly_out, &rpoly); + } +} + + +/* ********************************************************************************** * * ComputeCoordinates -- @@ -579,90 +613,124 @@ ComputeCoordinates(Item item, WidgetInfo *wi = item->wi; CurveItem cv = (CurveItem) item; int i, j; - ZnPoint *points; ZnPoint end_points[LINE_END_POINTS]; - ZnPoint *dev_points, *p; - int num_points; + ZnPoint *points; + int num_points, num_contours; ZnBBox bbox; int lw; + ZnContour *c1, *c2; + ZnBool *holes, *cw; ResetBBox(&item->item_bounding_box); - if (cv->points == NULL) { - return; - } - points = (ZnPoint *) ZnListArray(cv->points); - num_points = ZnListSize(cv->points); + SetRenderFlags(cv); /* - * Allocate space for devices coordinates + * Try to reduce the contours if needed. This can be + * switched off and replaced by an explicit reduction. */ - if (cv->dev_points == NULL) { - cv->dev_points = ZnListNew(num_points, sizeof(ZnPoint)); + if (cv->shape.num_contours == 1) { + SET(cv->flags, REDUCED_BIT); + } + else if (ISCLEAR(cv->flags, REDUCED_BIT)) { + ZnPoly poly; + printf("Contour reduction\n"); + ReduceContours(&cv->shape, &poly); + POLY_SET(&cv->shape, &poly); + SET(cv->flags, REDUCED_BIT); + } + + num_contours = cv->shape.num_contours; + if (num_contours == 0) { + return; } - ZnListAssertSize(cv->dev_points, num_points); - dev_points = (ZnPoint *) ZnListArray(cv->dev_points); + if (cv->tristrip.num_strips) { + gpc_free_tristrip(&cv->tristrip); + } + /* - * Process all points, skipping adjacent points that transform - * to the same device location. + * Allocate space for devices coordinates, the holes array is _NOT_ + * duplicated. */ - for (i = 0, j = 0; i < num_points; i++, j++) { - /* Compute device coordinates. - * Identical vertices are no longer skipped to allow a better control - * on Bezier curves and smoothing. - */ - ZnTransformPoint(wi->current_transfo, &points[i], &dev_points[j]); + POLY_FREE(&cv->dev_shape); + if (cv->shape.contours != &cv->shape.contour1) { + cv->dev_shape.contours = (ZnContour *) ZnMalloc(num_contours*sizeof(ZnContour)); + } + else { + cv->dev_shape.contours = &cv->dev_shape.contour1; + cv->dev_shape.cw = &cv->dev_shape.cw1; + } + cv->dev_shape.num_contours = num_contours; + c1 = cv->shape.contours; + c2 = cv->dev_shape.contours; + for (i = 0; i < cv->shape.num_contours; i++, c1++, c2++) { /* - if ((j == 0) || (dev_points[j].x != dev_points[j-1].x) || - (dev_points[j].y != dev_points[j-1].y)) { - j++; + * Drop the last point of a contour if it is the same as + * the first. + */ + if ((c1->points[0].x == c1->points[c1->num_points-1].x) && + (c1->points[0].y == c1->points[c1->num_points-1].y)) { + c1->num_points--; } - */ + c2->num_points = c1->num_points; + /* + * The device array is always one point larger to accomodate + * the outline end point when CLOSED is specified. + */ + c2->points = (ZnPoint *) ZnMalloc((c1->num_points+1)*sizeof(ZnPoint)); + /*printf("CC: \"%d\" num_points %d\n", item->id, c1->num_points);*/ } /* - * Adjust the device coords list to the actual size. + * Process all points, transforming coordinates. */ - ZnListTruncate(cv->dev_points, j); - /*printf("==========num_points %d=============\n", ZnListSize(cv->dev_points));*/ + c1 = cv->shape.contours; + c2 = cv->dev_shape.contours; + for (j = 0; j < num_contours; j++, c1++, c2++) { + ZnTransformPoints(wi->current_transfo, c1->points, c2->points, c1->num_points); + if (ISSET(cv->flags, CLOSED_BIT)) { + c2->points[c2->num_points] = c2->points[0]; + } + } lw = cv->line_width; - ASSIGN(cv->flags, CCW, TestCCW(dev_points, num_points)); - if (ISCLEAR(cv->flags, CCW)) { - if (ISSET(cv->flags, RELIEF_OK)) { - ZnPoint tmp; - int mid = num_points/2; + if (ISSET(cv->flags, RELIEF_OK)) { + holes = cv->shape.holes; + c2 = cv->dev_shape.contours; + if (!cv->dev_shape.cw) { + cv->dev_shape.cw = (ZnBool *) ZnMalloc(num_contours*sizeof(ZnBool)); + } + cw = cv->dev_shape.cw; + for (j = 0; j < num_contours; j++, c2++, holes++, cw++) { + num_points = c2->num_points; + points = c2->points; + *cw = !TestCCW(points, num_points); + if (*holes) { + continue; + } /* - * Revert the points to draw the relief inside. + * Add to bounding box. */ - for (i = 0; i < mid; i++) { - tmp = dev_points[i]; - dev_points[i] = dev_points[num_points-i-1]; - dev_points[num_points-i-1] = tmp; - } + GetPolygonReliefBBox(points, num_points, (*cw ^ *holes)?-lw:lw, &bbox); + AddBBoxToBBox(&item->item_bounding_box, &bbox); } - } - - /* - * Add to bounding box. - */ - if (ISSET(cv->flags, RELIEF_OK)) { - GetPolygonReliefBBox(cv->dev_points, lw, &bbox); - AddBBoxToBBox(&item->item_bounding_box, &bbox); return; } - for (i = 0; i < j; i++) { - AddPointToBBox(&item->item_bounding_box, dev_points[i].x, dev_points[i].y); + holes = cv->shape.holes; + c2 = cv->dev_shape.contours; + for (j = 0; j < num_contours; j++, c2++, holes++) { + if (!*holes) { + AddPointsToBBox(&item->item_bounding_box, c2->points, c2->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. + * but is simple. */ item->item_bounding_box.orig.x -= lw; item->item_bounding_box.orig.y -= lw; @@ -670,48 +738,55 @@ ComputeCoordinates(Item item, item->item_bounding_box.corner.y += lw; /* - * Take care of miters. + * Take care of miters, markers and arrows. */ - if (cv->join_style == JoinMiter) { - ZnPoint miter_i, miter_o; - for (i = j, p = dev_points; i >= 3; i--, p++) { - GetMiterPoints(p, p+1, p+2, lw, &miter_i, &miter_o); - AddPointToBBox(&item->item_bounding_box, miter_i.x, miter_i.y); - AddPointToBBox(&item->item_bounding_box, miter_o.x, miter_o.y); + holes = cv->shape.holes; + c2 = cv->dev_shape.contours; + for (j = 0; j < num_contours; j++, c2++, holes++) { + if (*holes) { + continue; } - } - - /* - * Add the markers. - */ - if (ISSET(cv->flags, MARKER_OK)) { - int w, h; - ZnBBox bbox; - - Tk_SizeOfBitmap(wi->dpy, cv->marker, &w, &h); - w = w/2 + 2; - h = w/2 + 2; - for (i = 0; i < j; i++) { - bbox.orig.x = dev_points[i].x - w; - bbox.orig.y = dev_points[i].y - h; - bbox.corner.x = dev_points[i].x + w; - bbox.corner.y = dev_points[i].y + h; - AddBBoxToBBox(&item->item_bounding_box, &bbox); + if (cv->join_style == JoinMiter) { + ZnPoint miter_i, miter_o; + for (i = c2->num_points-1, points = c2->points; i >= 3; i--, points++) { + GetMiterPoints(points, points+1, points+2, lw, &miter_i, &miter_o); + AddPointToBBox(&item->item_bounding_box, miter_i.x, miter_i.y); + AddPointToBBox(&item->item_bounding_box, miter_o.x, miter_o.y); + } + } + /* + * Add the markers. + */ + if (ISSET(cv->flags, MARKER_OK)) { + int w, h; + ZnBBox bbox; + Tk_SizeOfBitmap(wi->dpy, cv->marker, &w, &h); + w = w/2 + 2; + h = w/2 + 2; + num_points = c2->num_points; + for (i = 0, points = c2->points; i < num_points; i++, points++) { + bbox.orig.x = points->x - w; + bbox.orig.y = points->y - h; + bbox.corner.x = points->x + w; + bbox.corner.y = points->y + h; + AddBBoxToBBox(&item->item_bounding_box, &bbox); + } + } + /* + * Process arrows. + */ + num_points = c2->num_points; + points = c2->points; + if (ISSET(cv->flags, FIRST_END_OK)) { + GetLineEnd(&points[0], &points[1], lw, cv->cap_style, + cv->first_end, end_points); + AddPointsToBBox(&item->item_bounding_box, end_points, LINE_END_POINTS); + } + if (ISSET(cv->flags, LAST_END_OK)) { + GetLineEnd(&points[num_points-1], &points[num_points-2], + lw, cv->cap_style, cv->last_end, end_points); + AddPointsToBBox(&item->item_bounding_box, end_points, LINE_END_POINTS); } - } - - /* - * Process arrows. - */ - if (ISSET(cv->flags, FIRST_END_OK)) { - GetLineEnd(&dev_points[0], &dev_points[1], lw, cv->cap_style, - cv->first_end, end_points); - AddPointsToBBox(&item->item_bounding_box, end_points, LINE_END_POINTS); - } - if (ISSET(cv->flags, LAST_END_OK)) { - GetLineEnd(&dev_points[j-1], &dev_points[j-2], lw, cv->cap_style, - cv->last_end, end_points); - AddPointsToBBox(&item->item_bounding_box, end_points, LINE_END_POINTS); } /* @@ -747,88 +822,115 @@ ToArea(Item item, ZnBBox bbox; ZnPoint *points; ZnPoint end_points[LINE_END_POINTS]; - int num_points, result, result2; - ZnList actual_points; + int i, num_points, result, result2; + int width, height; + ZnBool first_done = False; - if (cv->dev_points == NULL) { + if (cv->dev_shape.num_contours == 0) { return -1; } /*printf("============== poly %d ==============\n", item->id);*/ - if (ISSET(cv->flags, SMOOTHED_OK)) { - SmoothPathWithBezier(cv->dev_points, wi->work_pts); - actual_points = wi->work_pts; - } - else if (ISSET(cv->flags, BEZIER_OK)) { - GetBezierPath(cv->dev_points, wi->work_pts); - actual_points = wi->work_pts; - } - else { - actual_points = cv->dev_points; - } - - points = (ZnPoint *) ZnListArray(actual_points); - num_points = ZnListSize(actual_points); if (ISSET(cv->flags, FILLED_OK)) { - result = PolygonInBBox(points, num_points, area); - if (result == 0) { - return 0; + for (i = 0; i < cv->dev_shape.num_contours; i++) { + ZnBool area_enclosed; + + num_points = cv->dev_shape.contours[i].num_points; + points = cv->dev_shape.contours[i].points; + if (!first_done) { + first_done = True; + result = PolygonInBBox(points, num_points, area, &area_enclosed); + printf("contour %d, result %d, area_enclosed %d\n", + i, result, area_enclosed); + if (cv->shape.holes[i] && area_enclosed) { + return -1; + } + } + else { + result2 = PolygonInBBox(points, num_points, area, &area_enclosed); + printf("contour %d, result %d, area_enclosed %d\n", + i, result2, area_enclosed); + if (cv->shape.holes[i] && area_enclosed) { + return -1; + } + if (result2 != result) { + return 0; + } + } } } if (cv->line_width > 0) { - if (ISCLEAR(cv->flags, RELIEF_OK)) { - result2 = PolylineInBBox(points, num_points, cv->line_width, - cv->cap_style, cv->join_style, area); - } - else { - result2 = PolygonReliefInBBox(actual_points, cv->line_width, area); - } - if (ISCLEAR(cv->flags, FILLED_OK)) { - if (result2 == 0) { - return 0; + for (i = 0; i < cv->dev_shape.num_contours; i++) { + num_points = cv->dev_shape.contours[i].num_points; + points = cv->dev_shape.contours[i].points; + if (!first_done) { + first_done = True; + if (ISCLEAR(cv->flags, RELIEF_OK)) { + result = PolylineInBBox(points, + ISSET(cv->flags, CLOSED_BIT)?num_points+1:num_points, + cv->line_width, cv->cap_style, cv->join_style, area); + } + else { + result = PolygonReliefInBBox(points, + ISSET(cv->flags, CLOSED_BIT)?num_points+1:num_points, + (cv->dev_shape.cw[0]^cv->shape.holes[0])?-cv->line_width:cv->line_width, area); + } + if (result == 0) { + return 0; + } + } + else { + if (ISCLEAR(cv->flags, RELIEF_OK)) { + result2 = PolylineInBBox(points, + ISSET(cv->flags, CLOSED_BIT)?num_points+1:num_points, + cv->line_width, cv->cap_style, cv->join_style, area); + } + else { + result2 = PolygonReliefInBBox(points, + ISSET(cv->flags, CLOSED_BIT)?num_points+1:num_points, + (cv->dev_shape.cw[0]^cv->shape.holes[0])?-cv->line_width:cv->line_width, area); + } + if (result2 != result) { + return 0; + } } - result = result2; - } - else if (result2 != result) { - return 0; } - + /* - * Check line ends. + * Check line ends (only on first contour). */ + points = cv->dev_shape.contours[0].points; + num_points = cv->dev_shape.contours[0].num_points; if (ISSET(cv->flags, FIRST_END_OK)) { GetLineEnd(&points[0], &points[1], cv->line_width, cv->cap_style, cv->first_end, end_points); - if (PolygonInBBox(end_points, LINE_END_POINTS, area) != result) { + if (PolygonInBBox(end_points, LINE_END_POINTS, area, NULL) != result) { return 0; } } if (ISSET(cv->flags, LAST_END_OK)) { GetLineEnd(&points[num_points-1], &points[num_points-2], cv->line_width, cv->cap_style, cv->last_end, end_points); - if (PolygonInBBox(end_points, LINE_END_POINTS, area) != result) { + if (PolygonInBBox(end_points, LINE_END_POINTS, area, NULL) != result) { return 0; } - } - - /* - * Last, check markers - */ - if (ISSET(cv->flags, MARKER_OK)) { - int num_points, width, height; - ZnPoint *points; - - points = (ZnPoint *) ZnListArray(actual_points); - num_points = ZnListSize(actual_points); + } + } + + /* + * Last, check markers + */ + if (ISSET(cv->flags, MARKER_OK)) { + for (i = 0; i < cv->dev_shape.num_contours; i++) { + points = cv->dev_shape.contours[i].points; + num_points = cv->dev_shape.contours[i].num_points; if (ISSET(cv->flags, FIRST_END_OK)) { num_points--; points++; } - if (ISSET(cv->flags, LAST_END_OK) || - ((points[0].x == points[num_points-1].x) && - (points[0].y == points[num_points-1].y))) { + if (ISSET(cv->flags, LAST_END_OK)) { num_points--; } @@ -862,36 +964,24 @@ Draw(Item item) WidgetInfo *wi = item->wi; CurveItem cv = (CurveItem) item; XGCValues values; - int i, num_points; + int i, j, num_points, num2; unsigned int gc_mask; ZnPoint *points; XPoint *xpoints = NULL; - if (cv->dev_points == NULL) { + if ((cv->dev_shape.num_contours == 0) || + (ISCLEAR(cv->flags, FILLED_OK) && + !cv->line_width && + ISCLEAR(cv->flags, MARKER_OK))) { return; } - if (ISSET(cv->flags, SMOOTHED_OK)) { - SmoothPathWithBezier(cv->dev_points, wi->work_pts); - points = (ZnPoint *) ZnListArray(wi->work_pts); - num_points = ZnListSize(wi->work_pts); - } - else if (ISSET(cv->flags, BEZIER_OK)) { - GetBezierPath(cv->dev_points, wi->work_pts); - points = (ZnPoint *) ZnListArray(wi->work_pts); - num_points = ZnListSize(wi->work_pts); - } - else { - points = (ZnPoint *) ZnListArray(cv->dev_points); - num_points = ZnListSize(cv->dev_points); - } - /* * Fill if requested. */ if (ISSET(cv->flags, FILLED_OK)) { if (cv->grad_geom) { - DrawPolygonGradient(wi, cv->grad_geom, cv->fill_color, points, num_points, + DrawPolygonGradient(wi, cv->grad_geom, cv->fill_color, &cv->dev_shape, &item->item_bounding_box); } else { @@ -918,16 +1008,23 @@ Draw(Item item) } XChangeGC(wi->dpy, wi->gc, gc_mask, &values); - if (!xpoints) { + if (cv->tristrip.num_strips == 0) { + gpc_polygon_to_tristrip((gpc_polygon *) &cv->dev_shape, &cv->tristrip); + } + for (i = 0; i < cv->tristrip.num_strips; i++) { + num_points = cv->tristrip.strip[i].num_vertices; + points = (ZnPoint *) cv->tristrip.strip[i].vertex; 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); + for (j = 0; j < num_points; j++) { + xpoints[j].x = REAL_TO_INT(points[j].x); + xpoints[j].y = REAL_TO_INT(points[j].y); + } + for (j = 0; j < num_points-2; j++) { + XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, + &xpoints[j], 3, Convex, CoordModeOrigin); } } - XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, - xpoints, num_points, Complex, CoordModeOrigin); } } @@ -935,15 +1032,26 @@ Draw(Item item) * Draw the lines between points */ if (cv->line_width) { - ZnPoint end_points[LINE_END_POINTS]; - XPoint xp[LINE_END_POINTS]; + ZnPoint end_points[LINE_END_POINTS]; + XPoint xp[LINE_END_POINTS]; /* * Drawing with relief disables: ends, line style and line pattern. */ if (ISSET(cv->flags, RELIEF_OK)) { - DrawPolygonRelief(wi, cv->relief, cv->gradient, - points, num_points, cv->line_width); + for (j = 0; j < cv->dev_shape.num_contours; j++) { + num_points = cv->dev_shape.contours[j].num_points; + points = cv->dev_shape.contours[j].points; + if (ISSET(cv->flags, CLOSED_BIT)) { + num_points++; + } + /*printf("Draw: num_points %d %g@%g %g@%g, cw %d\n", + num_points, points[0].x, points[0].y, + points[num_points-1].x, points[num_points-1].y, + cv->dev_shape.cw[j]);*/ + DrawPolygonRelief(wi, cv->relief, cv->gradient, points, num_points, + (cv->dev_shape.cw[j]^cv->shape.holes[j])?-cv->line_width:cv->line_width); + } } else { SetLineStyle(wi->dpy, wi->gc, cv->line_style); @@ -963,17 +1071,24 @@ Draw(Item item) GCFillStyle|GCStipple|GCLineWidth|GCJoinStyle|GCCapStyle|GCForeground, &values); } - if (!xpoints) { + for (j = 0; j < cv->dev_shape.num_contours; j++) { + num2 = num_points = cv->dev_shape.contours[j].num_points; + points = cv->dev_shape.contours[j].points; + if (ISSET(cv->flags, CLOSED_BIT)) { + num_points++; + } ZnListAssertSize(wi->work_xpts, num_points); xpoints = (XPoint *) ZnListArray(wi->work_xpts); - for (i = 0; i < num_points; i++) { + for (i = 0; i < num2; i++) { xpoints[i].x = REAL_TO_INT(points[i].x); xpoints[i].y = REAL_TO_INT(points[i].y); } + if (ISSET(cv->flags, CLOSED_BIT)) { + xpoints[num2] = xpoints[0]; + } + XDrawLines(wi->dpy, wi->draw_buffer, wi->gc, + xpoints, num_points, CoordModeOrigin); } - XDrawLines(wi->dpy, wi->draw_buffer, wi->gc, - xpoints, num_points, CoordModeOrigin); - if (ISSET(cv->flags, FIRST_END_OK)) { GetLineEnd(&points[0], &points[1], cv->line_width, cv->cap_style, cv->first_end, end_points); @@ -998,66 +1113,47 @@ Draw(Item item) } /* - * Draw the marks at each point. Smoothed line or relief disable this. - * If ends are specified or if last point join first point suppress - * markers as needed. + * Draw the marks at each point. If arrows are specified or + * if last point join first point suppress markers at end points. */ if (ISSET(cv->flags, MARKER_OK)) { - int width, h_width, height, h_height; - ZnPoint ptmp, *p; - int i; - - i = num_points; - p = points; - if (ISSET(cv->flags, FIRST_END_OK)) { - i--; - p++; - } - if (ISSET(cv->flags, LAST_END_OK) || - ((p[0].x == p[num_points-1].x) && - (p[0].y == p[num_points-1].y))) { - i--; - } - - values.foreground = ZnPixel(cv->marker_color); + int width, h_width, height, h_height; + ZnPoint ptmp; + Tk_SizeOfBitmap(wi->dpy, cv->marker, &width, &height); h_width = (width+1)/2; h_height = (height+1)/2; values.fill_style = FillStippled; values.stipple = cv->marker; - for (; i > 0; i--, p++) { - ptmp.x = p->x - h_width; - ptmp.y = p->y - h_height; - values.ts_x_origin = ptmp.x; - values.ts_y_origin = ptmp.y; - XChangeGC(wi->dpy, wi->gc, - GCFillStyle|GCStipple|GCTileStipXOrigin|GCTileStipYOrigin|GCForeground, - &values); - XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc, ptmp.x, ptmp.y, - width, height); - } - } - - /* - * If nothing useful is requested then why have you - * created this item ? Try to do something useful, we - * guess that drawing points is useful. - */ - if (ISCLEAR(cv->flags, FILLED_OK) && !cv->line_width && - (cv->marker == ZnUnspecifiedPattern)) { values.foreground = ZnPixel(cv->marker_color); - values.fill_style = FillSolid; - XChangeGC(wi->dpy, wi->gc, GCForeground | GCFillStyle, &values); - - if (!xpoints) { + XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCStipple|GCForeground, &values); + for (j = 0; j < cv->dev_shape.num_contours; j++) { + num_points = cv->dev_shape.contours[j].num_points; + points = cv->dev_shape.contours[j].points; ZnListAssertSize(wi->work_xpts, num_points); xpoints = (XPoint *) ZnListArray(wi->work_xpts); for (i = 0; i < num_points; i++) { - xpoints[i].x = points[i].x; - xpoints[i].y = points[i].y; + xpoints[i].x = REAL_TO_INT(points[i].x); + xpoints[i].y = REAL_TO_INT(points[i].y); + } + if (ISSET(cv->flags, FIRST_END_OK)) { + num_points--; + points++; + } + if (ISSET(cv->flags, LAST_END_OK)) { + num_points--; + } + for (; num_points > 0; num_points--, points++) { + ptmp.x = points->x - h_width; + ptmp.y = points->y - h_height; + values.ts_x_origin = ptmp.x; + values.ts_y_origin = ptmp.y; + XChangeGC(wi->dpy, wi->gc, + GCTileStipXOrigin|GCTileStipYOrigin|GCForeground, &values); + XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc, ptmp.x, ptmp.y, + width, height); } } - XDrawPoints(wi->dpy, wi->draw_buffer, wi->gc, xpoints, num_points, CoordModeOrigin); } } @@ -1100,58 +1196,88 @@ Pick(Item item, ZnPoint *points; ZnPoint end_points[LINE_END_POINTS]; int num_points; - ZnList actual_points; - - if (cv->dev_points == NULL) { + int width, height; + int i; + + if (cv->dev_shape.num_contours == 0) { return dist; } /*printf("Pick in curve\n");*/ - if (ISSET(cv->flags, SMOOTHED_OK)) { - SmoothPathWithBezier(cv->dev_points, wi->work_pts); - actual_points = wi->work_pts; - } - else if (ISSET(cv->flags, BEZIER_OK)) { - GetBezierPath(cv->dev_points, wi->work_pts); - actual_points = wi->work_pts; - } - else { - actual_points = cv->dev_points; - } - - points = (ZnPoint *) ZnListArray(actual_points); - num_points = ZnListSize(actual_points); - if (ISSET(cv->flags, FILLED_OK)) { - dist = PolygonToPointDist(points, num_points, p); - if (dist <= 0.0) { + double hole_dist=1.0e40, new_hole_dist; + /* + * Check all contours. Compute distance to holes + * in the same pass. + */ + for (i = 0; i < cv->dev_shape.num_contours; i++) { + if (cv->shape.holes[i]) { + new_hole_dist = PolygonToPointDist(cv->dev_shape.contours[i].points, + cv->dev_shape.contours[i].num_points, p); + if (new_hole_dist < hole_dist) { + hole_dist = new_hole_dist; + } + } + else { + new_dist = PolygonToPointDist(cv->dev_shape.contours[i].points, + cv->dev_shape.contours[i].num_points, p); + if (new_dist < dist) { + dist = new_dist; + } + } + } + if ((dist <= 0.0) && (hole_dist >= 0.0)) { return 0.0; } + if (hole_dist < 0.0) { + hole_dist = -hole_dist; + } + if (dist <= 0.0) { + dist = hole_dist; + } + else if (hole_dist < dist) { + dist = hole_dist; + } } if (cv->line_width > 0) { - if (ISCLEAR(cv->flags, RELIEF_OK)) { - new_dist = PolylineToPointDist(points, num_points, cv->line_width, - cv->cap_style, cv->join_style, p); - if (new_dist < dist) { - dist = new_dist; - } - if (dist <= 0.0) { - return 0.0; - } - } - else { - new_dist = PolygonReliefToPointDist(actual_points, cv->line_width, p); - if (new_dist < dist) { - dist = new_dist; + /* + * Check all contours. + */ + for (i = 0; i < cv->dev_shape.num_contours; i++) { + points = cv->dev_shape.contours[i].points; + num_points = cv->dev_shape.contours[i].num_points; + if (ISCLEAR(cv->flags, RELIEF_OK)) { + new_dist = PolylineToPointDist(points, + ISSET(cv->flags, CLOSED_BIT)?num_points+1:num_points, + cv->line_width, cv->cap_style, cv->join_style, p); + if (new_dist < dist) { + dist = new_dist; + } + if (dist <= 0.0) { + return 0.0; + } } - if (dist <= 0.0) { - return 0.0; + else { + new_dist = PolygonReliefToPointDist(points, + ISSET(cv->flags, CLOSED_BIT)?num_points+1:num_points, + (cv->dev_shape.cw[0]^cv->shape.holes[0])?-cv->line_width:cv->line_width, p); + if (new_dist < dist) { + dist = new_dist; + } + if (dist <= 0.0) { + return 0.0; + } } } } /* + * Line ends are checked only on the first contour. + */ + points = cv->dev_shape.contours[0].points; + num_points = cv->dev_shape.contours[0].num_points; + /* * Check line ends. */ if (ISSET(cv->flags, FIRST_END_OK)) { @@ -1178,37 +1304,34 @@ Pick(Item item, } /* - * Last, check markers + * Last, check markers on all contours. */ if (ISSET(cv->flags, MARKER_OK)) { - int num_points, width, height; - ZnPoint *points; - - points = (ZnPoint *) ZnListArray(actual_points); - num_points = ZnListSize(actual_points); - - if (ISSET(cv->flags, FIRST_END_OK)) { - num_points--; - points++; - } - if (ISSET(cv->flags, LAST_END_OK) || - ((points[0].x == points[num_points-1].x) && - (points[0].y == points[num_points-1].y))) { - num_points--; - } - - Tk_SizeOfBitmap(wi->dpy, cv->marker, &width, &height); - for (; num_points > 0; num_points--, points++) { - bbox.orig.x = points->x - (width+1)/2; - bbox.orig.y = points->y - (height+1)/2; - bbox.corner.x = bbox.orig.x + width; - bbox.corner.y = bbox.orig.y + height; - new_dist = RectangleToPointDist(&bbox, p); - if (new_dist < dist) { - dist = new_dist; + for (i = 0; i < cv->dev_shape.num_contours; i++) { + points = cv->dev_shape.contours[i].points; + num_points = cv->dev_shape.contours[i].num_points; + + if (ISSET(cv->flags, FIRST_END_OK)) { + num_points--; + points++; } - if (dist <= 0.0) { - return 0.0; + if (ISSET(cv->flags, LAST_END_OK)) { + num_points--; + } + + Tk_SizeOfBitmap(wi->dpy, cv->marker, &width, &height); + for (; num_points > 0; num_points--, points++) { + bbox.orig.x = points->x - (width+1)/2; + bbox.orig.y = points->y - (height+1)/2; + bbox.corner.x = bbox.orig.x + width; + bbox.corner.y = bbox.orig.y + height; + new_dist = RectangleToPointDist(&bbox, p); + if (new_dist < dist) { + dist = new_dist; + } + if (dist <= 0.0) { + return 0.0; + } } } } @@ -1241,17 +1364,19 @@ PostScript(Item item, */ static ZnBool GetClipVertices(Item item, - ZnPoint **points, - int *num_points) + ZnPoly *poly) { CurveItem cv = (CurveItem) item; - *points = NULL; - *num_points = 0; + POLY_INIT(poly); - if (cv->dev_points) { - *points = (ZnPoint *) ZnListArray(cv->dev_points); - *num_points = ZnListSize(cv->dev_points); + if (cv->dev_shape.num_contours == 1) { + POLY_CONTOUR1(poly, cv->dev_shape.contours[0].points, + cv->dev_shape.contours[0].num_points); + } + else if (cv->dev_shape.num_contours > 1) { + poly->num_contours = cv->dev_shape.num_contours; + poly->contours = cv->dev_shape.contours; } return False; @@ -1268,15 +1393,26 @@ GetClipVertices(Item item, */ static int Coords(Item item, + int contour, int index, int cmd, ZnPoint **pts, int *num_pts) { CurveItem cv = (CurveItem) item; - int num_points, i; - ZnPoint *points; + int i; + ZnContour *c; + if (contour < 0) { + contour += cv->shape.num_contours; + } + if ((contour < 0) || (contour >= cv->shape.num_contours)) { + Tcl_AppendResult(item->wi->interp, + " curve contour index out of range", NULL); + return ZN_ERROR; + } + c = &cv->shape.contours[contour]; + if ((cmd == COORDS_REPLACE) || (cmd == COORDS_REPLACE_ALL)) { if (*num_pts == 0) { Tcl_AppendResult(item->wi->interp, @@ -1284,99 +1420,98 @@ Coords(Item item, return ZN_ERROR; } if (cmd == COORDS_REPLACE_ALL) { - ZnList tmp; replace_all: - tmp = ZnListFromArray(*pts, *num_pts, sizeof(ZnPoint)); - if (!cv->points) { - ZnListEmpty(cv->points); + if (c->points) { + ZnFree(c->points); } - else { - cv->points = ZnListNew(*num_pts, sizeof(ZnPoint)); - } - ZnListAppend(cv->points, tmp); - ZnListFree(tmp); + c->points = (ZnPoint *) ZnMalloc(*num_pts*sizeof(ZnPoint)); + memcpy(c->points, *pts, *num_pts*sizeof(ZnPoint)); } else { - if (!cv->points) { + if (c->num_points == 0) { edit_err: Tcl_AppendResult(item->wi->interp, - " coords command cannot edit empty curves", NULL); + " coords command cannot edit empty curve contour", NULL); return ZN_ERROR; } - points = ZnListArray(cv->points); - num_points = ZnListSize(cv->points); if (index < 0) { - index += num_points; + index += c->num_points; } - if ((index < 0) || (index >= num_points)) { + if ((index < 0) || (index >= c->num_points)) { range_err: Tcl_AppendResult(item->wi->interp, " coord index out of range", NULL); return ZN_ERROR; } - points[index] = (*pts)[0]; + c->points[index] = (*pts)[0]; } + CLEAR(cv->flags, REDUCED_BIT); ITEM.Invalidate(item, ZN_COORDS_FLAG); } else if ((cmd == COORDS_READ) || (cmd == COORDS_READ_ALL)) { - if (!cv->points) { + if (c->num_points == 0) { *num_pts = 0; *pts = NULL; return ZN_OK; } - points = ZnListArray(cv->points); - num_points = ZnListSize(cv->points); if (cmd == COORDS_READ_ALL) { - *num_pts = num_points; - *pts = points; + *num_pts = c->num_points; + *pts = c->points; } else { if (index < 0) { - index += num_points; + index += c->num_points; } - if ((index < 0) || (index >= num_points)) { + if ((index < 0) || (index >= c->num_points)) { goto range_err; } *num_pts = 1; - *pts = &points[index]; + *pts = &c->points[index]; } } else if ((cmd == COORDS_ADD) || (cmd == COORDS_ADD_LAST)) { - if (!cv->points) { + if (c->num_points == 0) { goto replace_all; } - else if (cmd == COORDS_ADD) { - num_points = ZnListSize(cv->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(cv->points, &(*pts)[i], index); - } + if (cmd == COORDS_ADD_LAST) { + index = c->num_points; } - else { - ZnList tmp; - tmp = ZnListFromArray(*pts, *num_pts, sizeof(ZnPoint)); - ZnListAppend(cv->points, tmp); - ZnListFree(tmp); + if (index < 0) { + index += c->num_points; + } + if ((index < 0) || (index > c->num_points)) { + goto range_err; + } + c->points = (ZnPoint *) ZnRealloc(c->points, + (c->num_points+*num_pts)*sizeof(ZnPoint)); + /* + * Make a hole if needed. + */ + for (i = c->num_points-1; i >= index; i--) { + c->points[i+*num_pts] = c->points[i]; + } + for (i = 0; i < *num_pts; i++, index++) { + c->points[index] = (*pts)[i]; } + CLEAR(cv->flags, REDUCED_BIT); ITEM.Invalidate(item, ZN_COORDS_FLAG); } else if (cmd == COORDS_REMOVE) { - if (!cv->points) { + if (c->num_points == 0) { goto edit_err; } - points = ZnListArray(cv->points); - num_points = ZnListSize(cv->points); if (index < 0) { - index += num_points; + index += c->num_points; } - if ((index < 0) || (index >= num_points)) { + if ((index < 0) || (index >= c->num_points)) { goto range_err; } - ZnListDelete(cv->points, index); + c->num_points--; + if (index != c->num_points) { + for (i = index; i < c->num_points; i++) { + c->points[i] = c->points[i+1]; + } + } + CLEAR(cv->flags, REDUCED_BIT); ITEM.Invalidate(item, ZN_COORDS_FLAG); } @@ -1387,15 +1522,43 @@ Coords(Item item, /* ********************************************************************************** * + * Contour -- + * Perform geometric operations on curve contours. + * + ********************************************************************************** + */ +static void +Contour(Item item, + int cmd, + ZnPoly *poly) +{ + CurveItem cv = (CurveItem) item; + ZnPoly rpoly; + + POLY_INIT(&rpoly); + gpc_polygon_clip(cmd, (gpc_polygon *) &cv->shape, (gpc_polygon *) poly, + (gpc_polygon *) &rpoly); + POLY_SET(&cv->shape, &rpoly); + /*printf("cmd %d, num contours %d, num vertices %d\n", + cmd, + cv->shape.num_contours, + cv->shape.contours[0].num_points);*/ + ITEM.Invalidate(item, ZN_COORDS_FLAG); +} + + +/* + ********************************************************************************** + * * Exported functions struct -- * ********************************************************************************** */ static ItemClassStruct CURVE_ITEM_CLASS = { sizeof(CurveItemStruct), - False, - False, - False, + False, /* has_fields */ + False, /* has_parts */ + False, /* has_anchors */ "curve", cv_attrs, Init, @@ -1403,15 +1566,17 @@ static ItemClassStruct CURVE_ITEM_CLASS = { Destroy, Configure, Query, - NULL, - NULL, + NULL, /* GetFieldSet */ + NULL, /* GetAnchor */ GetClipVertices, Coords, + Contour, ComputeCoordinates, ToArea, Draw, IsSensitive, Pick, + NULL, /* PickVertex */ PostScript }; -- cgit v1.1