/* Copyright (C) 1989, 1995, 1996, 1997, 1998, 1999 artofcode LLC. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA, 02111-1307. */ /*$Id: gxpath.c,v 1.2.6.1.2.1 2003/01/17 00:49:04 giles Exp $ */ /* Internal path management routines for Ghostscript library */ #include "gx.h" #include "gserrors.h" #include "gsstruct.h" #include "gxfixed.h" #include "gzpath.h" /* These routines all assume that all points are */ /* already in device coordinates, and in fixed representation. */ /* As usual, they return either 0 or a (negative) error code. */ /* Forward references */ private int path_alloc_copy(P1(gx_path *)); private int gx_path_new_subpath(P1(gx_path *)); #ifdef DEBUG private void gx_print_segment(P1(const segment *)); # define trace_segment(msg, pseg)\ if ( gs_debug_c('P') ) dlprintf(msg), gx_print_segment(pseg); #else # define trace_segment(msg, pseg) DO_NOTHING #endif /* Check a point against a preset bounding box. */ #define outside_bbox(ppath, px, py)\ (px < ppath->bbox.p.x || px > ppath->bbox.q.x ||\ py < ppath->bbox.p.y || py > ppath->bbox.q.y) #define check_in_bbox(ppath, px, py)\ if ( outside_bbox(ppath, px, py) )\ return_error(gs_error_rangecheck) /* Structure descriptors for paths and path segment types. */ public_st_path(); private_st_path_segments(); private_st_segment(); private_st_line(); private_st_line_close(); private_st_curve(); private_st_subpath(); /* ------ Initialize/free paths ------ */ private rc_free_proc(rc_free_path_segments); private rc_free_proc(rc_free_path_segments_local); private void gx_path_init_contents(gx_path * ppath) { ppath->box_last = 0; ppath->first_subpath = ppath->current_subpath = 0; ppath->subpath_count = 0; ppath->curve_count = 0; path_update_newpath(ppath); ppath->bbox_set = 0; } /* * Initialize a path contained in an already-heap-allocated object, * optionally allocating its segments. */ private int path_alloc_segments(gx_path_segments ** ppsegs, gs_memory_t * mem, client_name_t cname) { rc_alloc_struct_1(*ppsegs, gx_path_segments, &st_path_segments, mem, return_error(gs_error_VMerror), cname); (*ppsegs)->rc.free = rc_free_path_segments; return 0; } int gx_path_init_contained_shared(gx_path * ppath, const gx_path * shared, gs_memory_t * mem, client_name_t cname) { if (shared) { if (shared->segments == &shared->local_segments) { lprintf1("Attempt to share (local) segments of path 0x%lx!\n", (ulong) shared); return_error(gs_error_Fatal); } *ppath = *shared; rc_increment(ppath->segments); } else { int code = path_alloc_segments(&ppath->segments, mem, cname); if (code < 0) return code; gx_path_init_contents(ppath); } ppath->memory = mem; ppath->allocation = path_allocated_contained; return 0; } /* * Allocate a path on the heap, and initialize it. If shared is NULL, * allocate a segments object; if shared is an existing path, share its * segments. */ gx_path * gx_path_alloc_shared(const gx_path * shared, gs_memory_t * mem, client_name_t cname) { gx_path *ppath = gs_alloc_struct(mem, gx_path, &st_path, cname); if (ppath == 0) return 0; if (shared) { if (shared->segments == &shared->local_segments) { lprintf1("Attempt to share (local) segments of path 0x%lx!\n", (ulong) shared); gs_free_object(mem, ppath, cname); return 0; } *ppath = *shared; rc_increment(ppath->segments); } else { int code = path_alloc_segments(&ppath->segments, mem, cname); if (code < 0) { gs_free_object(mem, ppath, cname); return 0; } gx_path_init_contents(ppath); } ppath->memory = mem; ppath->allocation = path_allocated_on_heap; return ppath; } /* * Initialize a stack-allocated path. This doesn't allocate anything, * but may still share the segments. */ int gx_path_init_local_shared(gx_path * ppath, const gx_path * shared, gs_memory_t * mem) { if (shared) { if (shared->segments == &shared->local_segments) { lprintf1("Attempt to share (local) segments of path 0x%lx!\n", (ulong) shared); return_error(gs_error_Fatal); } *ppath = *shared; rc_increment(ppath->segments); } else { rc_init_free(&ppath->local_segments, mem, 1, rc_free_path_segments_local); ppath->segments = &ppath->local_segments; gx_path_init_contents(ppath); } ppath->memory = mem; ppath->allocation = path_allocated_on_stack; return 0; } /* * Ensure that a path owns its segments, by copying the segments if * they currently have multiple references. */ int gx_path_unshare(gx_path * ppath) { int code = 0; if (gx_path_is_shared(ppath)) code = path_alloc_copy(ppath); return code; } /* * Free a path by releasing its segments if they have no more references. * This also frees the path object iff it was allocated by gx_path_alloc. */ void gx_path_free(gx_path * ppath, client_name_t cname) { rc_decrement(ppath->segments, cname); /* Clean up pointers for GC. */ ppath->box_last = 0; ppath->segments = 0; /* Nota bene */ if (ppath->allocation == path_allocated_on_heap) gs_free_object(ppath->memory, ppath, cname); } /* * Assign one path to another, adjusting reference counts appropriately. * Note that this requires that segments of the two paths (but not the path * objects themselves) were allocated with the same allocator. Note also * that since it does the equivalent of a gx_path_new(ppto), it may allocate * a new segments object for ppto. */ int gx_path_assign_preserve(gx_path * ppto, gx_path * ppfrom) { gx_path_segments *fromsegs = ppfrom->segments; gx_path_segments *tosegs = ppto->segments; gs_memory_t *mem = ppto->memory; gx_path_allocation_t allocation = ppto->allocation; if (fromsegs == &ppfrom->local_segments) { /* We can't use ppfrom's segments object. */ if (tosegs == &ppto->local_segments || gx_path_is_shared(ppto)) { /* We can't use ppto's segments either. Allocate a new one. */ int code = path_alloc_segments(&tosegs, ppto->memory, "gx_path_assign"); if (code < 0) return code; rc_decrement(ppto->segments, "gx_path_assign"); } else { /* Use ppto's segments object. */ rc_free_path_segments_local(tosegs->rc.memory, tosegs, "gx_path_assign"); } tosegs->contents = fromsegs->contents; ppfrom->segments = tosegs; rc_increment(tosegs); /* for reference from ppfrom */ } else { /* We can use ppfrom's segments object. */ rc_increment(fromsegs); rc_decrement(tosegs, "gx_path_assign"); } *ppto = *ppfrom; ppto->memory = mem; ppto->allocation = allocation; return 0; } /* * Assign one path to another and free the first path at the same time. * (This may do less work than assign_preserve + free.) */ int gx_path_assign_free(gx_path * ppto, gx_path * ppfrom) { /* * Detect the special case where both paths have non-shared local * segments, since we can avoid allocating new segments in this case. */ if (ppto->segments == &ppto->local_segments && ppfrom->segments == &ppfrom->local_segments && !gx_path_is_shared(ppto) ) { #define fromsegs (&ppfrom->local_segments) #define tosegs (&ppto->local_segments) gs_memory_t *mem = ppto->memory; gx_path_allocation_t allocation = ppto->allocation; rc_free_path_segments_local(tosegs->rc.memory, tosegs, "gx_path_assign_free"); /* We record a bogus reference to fromsegs, which */ /* gx_path_free will undo. */ *ppto = *ppfrom; rc_increment(fromsegs); ppto->segments = tosegs; ppto->memory = mem; ppto->allocation = allocation; #undef fromsegs #undef tosegs } else { /* In all other cases, just do assign + free. */ int code = gx_path_assign_preserve(ppto, ppfrom); if (code < 0) return code; } gx_path_free(ppfrom, "gx_path_assign_free"); return 0; } /* * Free the segments of a path when their reference count goes to zero. * We do this in reverse order so as to maximize LIFO allocator behavior. * We don't have to worry about cleaning up pointers, because we're about * to free the segments object. */ private void rc_free_path_segments_local(gs_memory_t * mem, void *vpsegs, client_name_t cname) { gx_path_segments *psegs = (gx_path_segments *) vpsegs; segment *pseg; if (psegs->contents.subpath_first == 0) return; /* empty path */ pseg = (segment *) psegs->contents.subpath_current->last; while (pseg) { segment *prev = pseg->prev; trace_segment("[P]release", pseg); gs_free_object(mem, pseg, cname); pseg = prev; } } private void rc_free_path_segments(gs_memory_t * mem, void *vpsegs, client_name_t cname) { rc_free_path_segments_local(mem, vpsegs, cname); gs_free_object(mem, vpsegs, cname); } /* ------ Incremental path building ------ */ /* Guarantee that a path's segments are not shared with any other path. */ #define path_unshare(ppath)\ BEGIN\ if ( gx_path_is_shared(ppath) ) {\ int code_;\ if( (code_ = path_alloc_copy(ppath)) < 0 ) return code_;\ }\ END /* Macro for opening the current subpath. */ /* ppath points to the path; sets psub to ppath->current_subpath. */ #define path_open()\ BEGIN\ if ( !path_is_drawing(ppath) ) {\ int code_;\ if ( !path_position_valid(ppath) )\ return_error(gs_error_nocurrentpoint);\ code_ = gx_path_new_subpath(ppath);\ if ( code_ < 0 ) return code_;\ }\ END /* Macros for allocating path segments. */ /* Note that they assume that ppath points to the path. */ /* We have to split the macro into two because of limitations */ /* on the size of a single statement (sigh). */ #define path_alloc_segment(pseg,ctype,pstype,stype,snotes,cname)\ path_unshare(ppath);\ psub = ppath->current_subpath;\ if( !(pseg = gs_alloc_struct(ppath->memory, ctype, pstype, cname)) )\ return_error(gs_error_VMerror);\ pseg->type = stype, pseg->notes = snotes, pseg->next = 0 #define path_alloc_link(pseg)\ { segment *prev = psub->last;\ prev->next = (segment *)pseg;\ pseg->prev = prev;\ psub->last = (segment *)pseg;\ } /* Make a new path (newpath). */ int gx_path_new(gx_path * ppath) { gx_path_segments *psegs = ppath->segments; if (gx_path_is_shared(ppath)) { int code = path_alloc_segments(&ppath->segments, ppath->memory, "gx_path_new"); if (code < 0) return code; rc_decrement(psegs, "gx_path_new"); } else { rc_free_path_segments_local(psegs->rc.memory, psegs, "gx_path_new"); } gx_path_init_contents(ppath); return 0; } /* Open a new subpath. */ /* The client must invoke path_update_xxx. */ private int gx_path_new_subpath(gx_path * ppath) { subpath *psub; subpath *spp; path_alloc_segment(spp, subpath, &st_subpath, s_start, sn_none, "gx_path_new_subpath"); spp->last = (segment *) spp; spp->curve_count = 0; spp->is_closed = 0; spp->pt = ppath->position; if (!psub) { /* first subpath */ ppath->first_subpath = spp; spp->prev = 0; } else { segment *prev = psub->last; prev->next = (segment *) spp; spp->prev = prev; } ppath->current_subpath = spp; ppath->subpath_count++; trace_segment("[P]", (const segment *)spp); return 0; } /* Add a point to the current path (moveto). */ int gx_path_add_point(gx_path * ppath, fixed x, fixed y) { if (ppath->bbox_set) check_in_bbox(ppath, x, y); ppath->position.x = x; ppath->position.y = y; path_update_moveto(ppath); return 0; } /* Add a relative point to the current path (rmoveto). */ int gx_path_add_relative_point(gx_path * ppath, fixed dx, fixed dy) { if (!path_position_in_range(ppath)) return_error((path_position_valid(ppath) ? gs_error_limitcheck : gs_error_nocurrentpoint)); { fixed nx = ppath->position.x + dx, ny = ppath->position.y + dy; /* Check for overflow in addition. */ if (((nx ^ dx) < 0 && (ppath->position.x ^ dx) >= 0) || ((ny ^ dy) < 0 && (ppath->position.y ^ dy) >= 0) ) return_error(gs_error_limitcheck); if (ppath->bbox_set) check_in_bbox(ppath, nx, ny); ppath->position.x = nx; ppath->position.y = ny; } path_update_moveto(ppath); return 0; } /* Set the segment point and the current point in the path. */ /* Assumes ppath points to the path. */ #define path_set_point(pseg, fx, fy)\ (pseg)->pt.x = ppath->position.x = (fx),\ (pseg)->pt.y = ppath->position.y = (fy) /* Add a line to the current path (lineto). */ int gx_path_add_line_notes(gx_path * ppath, fixed x, fixed y, segment_notes notes) { subpath *psub; line_segment *lp; if (ppath->bbox_set) check_in_bbox(ppath, x, y); path_open(); path_alloc_segment(lp, line_segment, &st_line, s_line, notes, "gx_path_add_line"); path_alloc_link(lp); path_set_point(lp, x, y); path_update_draw(ppath); trace_segment("[P]", (segment *) lp); return 0; } /* Add multiple lines to the current path. */ /* Note that all lines have the same notes. */ int gx_path_add_lines_notes(gx_path *ppath, const gs_fixed_point *ppts, int count, segment_notes notes) { subpath *psub; segment *prev; line_segment *lp = 0; int i; int code = 0; if (count <= 0) return 0; path_unshare(ppath); path_open(); psub = ppath->current_subpath; prev = psub->last; /* * We could do better than the following, but this is a start. * Note that we don't make any attempt to undo partial additions * if we fail partway through; this is equivalent to what would * happen with multiple calls on gx_path_add_line. */ for (i = 0; i < count; i++) { fixed x = ppts[i].x; fixed y = ppts[i].y; line_segment *next; if (ppath->bbox_set && outside_bbox(ppath, x, y)) { code = gs_note_error(gs_error_rangecheck); break; } if (!(next = gs_alloc_struct(ppath->memory, line_segment, &st_line, "gx_path_add_lines")) ) { code = gs_note_error(gs_error_VMerror); break; } lp = next; lp->type = s_line; lp->notes = notes; prev->next = (segment *) lp; lp->prev = prev; lp->pt.x = x; lp->pt.y = y; prev = (segment *) lp; trace_segment("[P]", (segment *) lp); } if (lp != 0) ppath->position.x = lp->pt.x, ppath->position.y = lp->pt.y, psub->last = (segment *) lp, lp->next = 0, path_update_draw(ppath); return code; } /* Add a rectangle to the current path. */ /* This is a special case of adding a closed polygon. */ int gx_path_add_rectangle(gx_path * ppath, fixed x0, fixed y0, fixed x1, fixed y1) { gs_fixed_point pts[3]; int code; pts[0].x = x0; pts[1].x = pts[2].x = x1; pts[2].y = y0; pts[0].y = pts[1].y = y1; if ((code = gx_path_add_point(ppath, x0, y0)) < 0 || (code = gx_path_add_lines(ppath, pts, 3)) < 0 || (code = gx_path_close_subpath(ppath)) < 0 ) return code; return 0; } /* Add a curve to the current path (curveto). */ int gx_path_add_curve_notes(gx_path * ppath, fixed x1, fixed y1, fixed x2, fixed y2, fixed x3, fixed y3, segment_notes notes) { subpath *psub; curve_segment *lp; if (ppath->bbox_set) { check_in_bbox(ppath, x1, y1); check_in_bbox(ppath, x2, y2); check_in_bbox(ppath, x3, y3); } path_open(); path_alloc_segment(lp, curve_segment, &st_curve, s_curve, notes, "gx_path_add_curve"); path_alloc_link(lp); lp->p1.x = x1; lp->p1.y = y1; lp->p2.x = x2; lp->p2.y = y2; path_set_point(lp, x3, y3); psub->curve_count++; ppath->curve_count++; path_update_draw(ppath); trace_segment("[P]", (segment *) lp); return 0; } /* * Add an approximation of an arc to the current path. * The current point of the path is the initial point of the arc; * parameters are the final point of the arc * and the point at which the extended tangents meet. * We require that the arc be less than a semicircle. * The arc may go either clockwise or counterclockwise. * The approximation is a very simple one: a single curve * whose other two control points are a fraction F of the way * to the intersection of the tangents, where * F = (4/3)(1 / (1 + sqrt(1+(d/r)^2))) * where r is the radius and d is the distance from either tangent * point to the intersection of the tangents. This produces * a curve whose center point, as well as its ends, lies on * the desired arc. * * Because F has to be computed in user space, we let the client * compute it and pass it in as an argument. */ int gx_path_add_partial_arc_notes(gx_path * ppath, fixed x3, fixed y3, fixed xt, fixed yt, floatp fraction, segment_notes notes) { fixed x0 = ppath->position.x, y0 = ppath->position.y; return gx_path_add_curve_notes(ppath, x0 + (fixed) ((xt - x0) * fraction), y0 + (fixed) ((yt - y0) * fraction), x3 + (fixed) ((xt - x3) * fraction), y3 + (fixed) ((yt - y3) * fraction), x3, y3, notes | sn_from_arc); } /* Append a path to another path, and reset the first path. */ /* Currently this is only used to append a path to its parent */ /* (the path in the previous graphics context). */ int gx_path_add_path(gx_path * ppath, gx_path * ppfrom) { path_unshare(ppfrom); path_unshare(ppath); if (ppfrom->first_subpath) { /* i.e. ppfrom not empty */ if (ppath->first_subpath) { /* i.e. ppath not empty */ subpath *psub = ppath->current_subpath; segment *pseg = psub->last; subpath *pfsub = ppfrom->first_subpath; pseg->next = (segment *) pfsub; pfsub->prev = pseg; } else ppath->first_subpath = ppfrom->first_subpath; ppath->current_subpath = ppfrom->current_subpath; ppath->subpath_count += ppfrom->subpath_count; ppath->curve_count += ppfrom->curve_count; } /* Transfer the remaining state. */ ppath->position = ppfrom->position; ppath->outside_position = ppfrom->outside_position; ppath->state_flags = ppfrom->state_flags; /* Reset the source path. */ gx_path_init_contents(ppfrom); return 0; } /* Add a path or its bounding box to the enclosing path, */ /* and reset the first path. Only used for implementing charpath and its */ /* relatives. */ int gx_path_add_char_path(gx_path * to_path, gx_path * from_path, gs_char_path_mode mode) { int code; gs_fixed_rect bbox; switch (mode) { default: /* shouldn't happen! */ gx_path_new(from_path); return 0; case cpm_charwidth: { gs_fixed_point cpt; code = gx_path_current_point(from_path, &cpt); if (code < 0) break; return gx_path_add_point(to_path, cpt.x, cpt.y); } case cpm_true_charpath: case cpm_false_charpath: return gx_path_add_path(to_path, from_path); case cpm_true_charboxpath: gx_path_bbox(from_path, &bbox); code = gx_path_add_rectangle(to_path, bbox.p.x, bbox.p.y, bbox.q.x, bbox.q.y); break; case cpm_false_charboxpath: gx_path_bbox(from_path, &bbox); code = gx_path_add_point(to_path, bbox.p.x, bbox.p.y); if (code >= 0) code = gx_path_add_line(to_path, bbox.q.x, bbox.q.y); break; } if (code < 0) return code; gx_path_new(from_path); return 0; } /* Close the current subpath. */ int gx_path_close_subpath_notes(gx_path * ppath, segment_notes notes) { subpath *psub; line_close_segment *lp; int code; if (!path_subpath_open(ppath)) return 0; if (path_last_is_moveto(ppath)) { /* The last operation was a moveto: create a subpath. */ code = gx_path_new_subpath(ppath); if (code < 0) return code; } path_alloc_segment(lp, line_close_segment, &st_line_close, s_line_close, notes, "gx_path_close_subpath"); path_alloc_link(lp); path_set_point(lp, psub->pt.x, psub->pt.y); lp->sub = psub; psub->is_closed = 1; path_update_closepath(ppath); trace_segment("[P]", (segment *) lp); return 0; } /* Remove the last line from the current subpath, and then close it. */ /* The Type 1 font hinting routines use this if a path ends with */ /* a line to the start followed by a closepath. */ int gx_path_pop_close_notes(gx_path * ppath, segment_notes notes) { subpath *psub = ppath->current_subpath; segment *pseg; segment *prev; if (psub == 0 || (pseg = psub->last) == 0 || pseg->type != s_line ) return_error(gs_error_unknownerror); prev = pseg->prev; prev->next = 0; psub->last = prev; gs_free_object(ppath->memory, pseg, "gx_path_pop_close_subpath"); return gx_path_close_subpath_notes(ppath, notes); } /* ------ Internal routines ------ */ /* * Copy the current path, because it was shared. */ private int path_alloc_copy(gx_path * ppath) { gx_path path_new; int code; gx_path_init_local(&path_new, ppath->memory); code = gx_path_copy(ppath, &path_new); if (code < 0) { gx_path_free(&path_new, "path_alloc_copy error"); return code; } return gx_path_assign_free(ppath, &path_new); } /* ------ Debugging printout ------ */ #ifdef DEBUG /* Print out a path with a label */ void gx_dump_path(const gx_path * ppath, const char *tag) { dlprintf2("[P]Path 0x%lx %s:\n", (ulong) ppath, tag); gx_path_print(ppath); } /* Print a path */ void gx_path_print(const gx_path * ppath) { const segment *pseg = (const segment *)ppath->first_subpath; dlprintf5(" state_flags=%d subpaths=%d, curves=%d, point=(%f,%f)\n", ppath->state_flags, ppath->subpath_count, ppath->curve_count, fixed2float(ppath->position.x), fixed2float(ppath->position.y)); dlprintf5(" box=(%f,%f),(%f,%f) last=0x%lx\n", fixed2float(ppath->bbox.p.x), fixed2float(ppath->bbox.p.y), fixed2float(ppath->bbox.q.x), fixed2float(ppath->bbox.q.y), (ulong) ppath->box_last); dlprintf4(" segments=0x%lx (refct=%ld, first=0x%lx, current=0x%lx)\n", (ulong) ppath->segments, (long)ppath->segments->rc.ref_count, (ulong) ppath->segments->contents.subpath_first, (ulong) ppath->segments->contents.subpath_current); while (pseg) { dlputs(""); gx_print_segment(pseg); pseg = pseg->next; } } private void gx_print_segment(const segment * pseg) { double px = fixed2float(pseg->pt.x); double py = fixed2float(pseg->pt.y); char out[80]; sprintf(out, " 0x%lx<0x%lx,0x%lx>:%u", (ulong) pseg, (ulong) pseg->prev, (ulong) pseg->next, pseg->notes); switch (pseg->type) { case s_start:{ const subpath *const psub = (const subpath *)pseg; dprintf5("%s: %1.4f %1.4f moveto\t%% #curves=%d last=0x%lx\n", out, px, py, psub->curve_count, (ulong) psub->last); break; } case s_curve:{ const curve_segment *const pcur = (const curve_segment *)pseg; dprintf7("%s: %1.4f %1.4f %1.4f %1.4f %1.4f %1.4f curveto\n", out, fixed2float(pcur->p1.x), fixed2float(pcur->p1.y), fixed2float(pcur->p2.x), fixed2float(pcur->p2.y), px, py); break; } case s_line: dprintf3("%s: %1.4f %1.4f lineto\n", out, px, py); break; case s_line_close:{ const line_close_segment *const plc = (const line_close_segment *)pseg; dprintf4("%s: closepath\t%% %1.4f %1.4f 0x%lx\n", out, px, py, (ulong) (plc->sub)); break; } default: dprintf4("%s: %1.4f %1.4f \n", out, px, py, pseg->type); } } #endif /* DEBUG */