xref: /plan9/sys/src/cmd/gs/src/gxpath2.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 1989, 1995, 1997, 1998, 1999 Aladdin Enterprises.  All rights reserved.
2 
3   This software is provided AS-IS with no warranty, either express or
4   implied.
5 
6   This software is distributed under license and may not be copied,
7   modified or distributed except as expressly authorized under the terms
8   of the license contained in the file LICENSE in this distribution.
9 
10   For more information about licensing, please refer to
11   http://www.ghostscript.com/licensing/. For information on
12   commercial licensing, go to http://www.artifex.com/licensing/ or
13   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14   San Rafael, CA  94903, U.S.A., +1(415)492-9861.
15 */
16 
17 /*$Id: gxpath2.c,v 1.6 2002/08/30 02:38:24 dan Exp $ */
18 /* Path tracing procedures for Ghostscript library */
19 #include "math_.h"
20 #include "gx.h"
21 #include "gserrors.h"
22 #include "gspath.h"		/* for gs_path_enum_alloc prototype */
23 #include "gsstruct.h"
24 #include "gxfixed.h"
25 #include "gxarith.h"
26 #include "gzpath.h"
27 
28 /* Define the enumeration structure. */
29 public_st_path_enum();
30 
31 /* Read the current point of a path. */
32 int
gx_path_current_point(const gx_path * ppath,gs_fixed_point * ppt)33 gx_path_current_point(const gx_path * ppath, gs_fixed_point * ppt)
34 {
35     if (!path_position_valid(ppath))
36 	return_error(gs_error_nocurrentpoint);
37     /* Copying the coordinates individually */
38     /* is much faster on a PC, and almost as fast on other machines.... */
39     ppt->x = ppath->position.x, ppt->y = ppath->position.y;
40     return 0;
41 }
42 
43 /* Read the start point of the current subpath. */
44 int
gx_path_subpath_start_point(const gx_path * ppath,gs_fixed_point * ppt)45 gx_path_subpath_start_point(const gx_path * ppath, gs_fixed_point * ppt)
46 {
47     const subpath *psub = ppath->current_subpath;
48 
49     if (!psub)
50 	return_error(gs_error_nocurrentpoint);
51     *ppt = psub->pt;
52     return 0;
53 }
54 
55 /* Read the bounding box of a path. */
56 /* Note that if the last element of the path is a moveto, */
57 /* the bounding box does not include this point, */
58 /* unless this is the only element of the path. */
59 int
gx_path_bbox(gx_path * ppath,gs_fixed_rect * pbox)60 gx_path_bbox(gx_path * ppath, gs_fixed_rect * pbox)
61 {
62     if (ppath->bbox_accurate) {
63 	/* The bounding box was set by setbbox. */
64 	*pbox = ppath->bbox;
65 	return 0;
66     }
67     if (ppath->first_subpath == 0) {
68 	/* The path is empty, use the current point if any. */
69 	int code = gx_path_current_point(ppath, &pbox->p);
70 
71 	if (code < 0) {
72 	    /*
73 	     * Don't return garbage, in case the caller doesn't
74 	     * check the return code.
75 	     */
76 	    pbox->p.x = pbox->p.y = 0;
77 	}
78 	pbox->q = pbox->p;
79 	return code;
80     }
81     /* The stored bounding box may not be up to date. */
82     /* Correct it now if necessary. */
83     if (ppath->box_last == ppath->current_subpath->last) {
84 	/* Box is up to date */
85 	*pbox = ppath->bbox;
86     } else {
87 	fixed px, py, qx, qy;
88 	const segment *pseg = ppath->box_last;
89 
90 	if (pseg == 0) {	/* box is uninitialized */
91 	    pseg = (const segment *)ppath->first_subpath;
92 	    px = qx = pseg->pt.x;
93 	    py = qy = pseg->pt.y;
94 	} else {
95 	    px = ppath->bbox.p.x, py = ppath->bbox.p.y;
96 	    qx = ppath->bbox.q.x, qy = ppath->bbox.q.y;
97 	}
98 
99 /* Macro for adjusting the bounding box when adding a point */
100 #define ADJUST_BBOX(pt)\
101   if ((pt).x < px) px = (pt).x;\
102   else if ((pt).x > qx) qx = (pt).x;\
103   if ((pt).y < py) py = (pt).y;\
104   else if ((pt).y > qy) qy = (pt).y
105 
106 	while ((pseg = pseg->next) != 0) {
107 	    switch (pseg->type) {
108 		case s_curve:
109 		    ADJUST_BBOX(((const curve_segment *)pseg)->p1);
110 		    ADJUST_BBOX(((const curve_segment *)pseg)->p2);
111 		    /* falls through */
112 		default:
113 		    ADJUST_BBOX(pseg->pt);
114 	    }
115 	}
116 #undef ADJUST_BBOX
117 
118 #define STORE_BBOX(b)\
119   (b).p.x = px, (b).p.y = py, (b).q.x = qx, (b).q.y = qy;
120 	STORE_BBOX(*pbox);
121 	STORE_BBOX(ppath->bbox);
122 #undef STORE_BBOX
123 
124 	ppath->box_last = ppath->current_subpath->last;
125     }
126     return 0;
127 }
128 
129 /* A variation of gs_path_bbox, to be used by the patbbox operator */
130 int
gx_path_bbox_set(gx_path * ppath,gs_fixed_rect * pbox)131 gx_path_bbox_set(gx_path * ppath, gs_fixed_rect * pbox)
132 {
133     if (ppath->bbox_set) {
134 	/* The bounding box was set by setbbox. */
135 	*pbox = ppath->bbox;
136 	return 0;
137     } else
138 	return gx_path_bbox(ppath, pbox);
139 }
140 
141 
142 /* Test if a path has any curves. */
143 #undef gx_path_has_curves
144 bool
gx_path_has_curves(const gx_path * ppath)145 gx_path_has_curves(const gx_path * ppath)
146 {
147     return gx_path_has_curves_inline(ppath);
148 }
149 #define gx_path_has_curves(ppath)\
150   gx_path_has_curves_inline(ppath)
151 
152 /* Test if a path has no segments. */
153 #undef gx_path_is_void
154 bool
gx_path_is_void(const gx_path * ppath)155 gx_path_is_void(const gx_path * ppath)
156 {
157     return gx_path_is_void_inline(ppath);
158 }
159 #define gx_path_is_void(ppath)\
160   gx_path_is_void_inline(ppath)
161 
162 /* Test if a path has no elements at all. */
163 bool
gx_path_is_null(const gx_path * ppath)164 gx_path_is_null(const gx_path * ppath)
165 {
166     return gx_path_is_null_inline(ppath);
167 }
168 
169 /*
170  * Test if a subpath is a rectangle; if so, return its bounding box
171  * and the start of the next subpath.
172  * Note that this must recognize:
173  *      ordinary closed rectangles (M, L, L, L, C);
174  *      open rectangles (M, L, L, L);
175  *      rectangles closed with lineto (Mo, L, L, L, Lo);
176  *      rectangles closed with *both* lineto and closepath (bad PostScript,
177  *        but unfortunately not rare) (Mo, L, L, L, Lo, C).
178  */
179 gx_path_rectangular_type
gx_subpath_is_rectangular(const subpath * pseg0,gs_fixed_rect * pbox,const subpath ** ppnext)180 gx_subpath_is_rectangular(const subpath * pseg0, gs_fixed_rect * pbox,
181 			  const subpath ** ppnext)
182 {
183     const segment *pseg1, *pseg2, *pseg3, *pseg4;
184     gx_path_rectangular_type type;
185 
186     if (pseg0->curve_count == 0 &&
187 	(pseg1 = pseg0->next) != 0 &&
188 	(pseg2 = pseg1->next) != 0 &&
189 	(pseg3 = pseg2->next) != 0
190 	) {
191 	if ((pseg4 = pseg3->next) == 0 || pseg4->type == s_start)
192 	    type = prt_open;	/* M, L, L, L */
193 	else if (pseg4->type != s_line)		/* must be s_line_close */
194 	    type = prt_closed;	/* M, L, L, L, C */
195 	else if (pseg4->pt.x != pseg0->pt.x ||
196 		 pseg4->pt.y != pseg0->pt.y
197 	    )
198 	    return prt_none;
199 	else if (pseg4->next == 0 || pseg4->next->type == s_start)
200 	    type = prt_fake_closed;	/* Mo, L, L, L, Lo */
201 	else if (pseg4->next->type != s_line)	/* must be s_line_close */
202 	    type = prt_closed;	/* Mo, L, L, L, Lo, C */
203 	else
204 	    return prt_none;
205 	{
206 	    fixed x0 = pseg0->pt.x, y0 = pseg0->pt.y;
207 	    fixed x2 = pseg2->pt.x, y2 = pseg2->pt.y;
208 
209 	    if ((x0 == pseg1->pt.x && pseg1->pt.y == y2 &&
210 		 x2 == pseg3->pt.x && pseg3->pt.y == y0) ||
211 		(x0 == pseg3->pt.x && pseg3->pt.y == y2 &&
212 		 x2 == pseg1->pt.x && pseg1->pt.y == y0)
213 		) {		/* Path is a rectangle.  Return the bounding box. */
214 		if (x0 < x2)
215 		    pbox->p.x = x0, pbox->q.x = x2;
216 		else
217 		    pbox->p.x = x2, pbox->q.x = x0;
218 		if (y0 < y2)
219 		    pbox->p.y = y0, pbox->q.y = y2;
220 		else
221 		    pbox->p.y = y2, pbox->q.y = y0;
222 		while (pseg4 != 0 && pseg4->type != s_start)
223 		    pseg4 = pseg4->next;
224 		*ppnext = (const subpath *)pseg4;
225 		return type;
226 	    }
227 	}
228     }
229     return prt_none;
230 }
231 /* Test if an entire path to be filled is a rectangle. */
232 gx_path_rectangular_type
gx_path_is_rectangular(const gx_path * ppath,gs_fixed_rect * pbox)233 gx_path_is_rectangular(const gx_path * ppath, gs_fixed_rect * pbox)
234 {
235     const subpath *pnext;
236 
237     return
238 	(gx_path_subpath_count(ppath) == 1 ?
239 	 gx_subpath_is_rectangular(ppath->first_subpath, pbox, &pnext) :
240 	 prt_none);
241 }
242 
243 /* Translate an already-constructed path (in device space). */
244 /* Don't bother to update the cbox. */
245 int
gx_path_translate(gx_path * ppath,fixed dx,fixed dy)246 gx_path_translate(gx_path * ppath, fixed dx, fixed dy)
247 {
248     segment *pseg;
249 
250 #define update_xy(pt)\
251   pt.x += dx, pt.y += dy
252     if (ppath->box_last != 0) {
253 	update_xy(ppath->bbox.p);
254 	update_xy(ppath->bbox.q);
255     }
256     if (path_position_valid(ppath))
257 	update_xy(ppath->position);
258     for (pseg = (segment *) (ppath->first_subpath); pseg != 0;
259 	 pseg = pseg->next
260 	)
261 	switch (pseg->type) {
262 	    case s_curve:
263 #define pcseg ((curve_segment *)pseg)
264 		update_xy(pcseg->p1);
265 		update_xy(pcseg->p2);
266 #undef pcseg
267 	    default:
268 		update_xy(pseg->pt);
269 	}
270 #undef update_xy
271     return 0;
272 }
273 
274 /* Scale an existing path by a power of 2 (positive or negative). */
275 void
gx_point_scale_exp2(gs_fixed_point * pt,int sx,int sy)276 gx_point_scale_exp2(gs_fixed_point * pt, int sx, int sy)
277 {
278     if (sx >= 0)
279 	pt->x <<= sx;
280     else
281 	pt->x >>= -sx;
282     if (sy >= 0)
283 	pt->y <<= sy;
284     else
285 	pt->y >>= -sy;
286 }
287 void
gx_rect_scale_exp2(gs_fixed_rect * pr,int sx,int sy)288 gx_rect_scale_exp2(gs_fixed_rect * pr, int sx, int sy)
289 {
290     gx_point_scale_exp2(&pr->p, sx, sy);
291     gx_point_scale_exp2(&pr->q, sx, sy);
292 }
293 int
gx_path_scale_exp2_shared(gx_path * ppath,int log2_scale_x,int log2_scale_y,bool segments_shared)294 gx_path_scale_exp2_shared(gx_path * ppath, int log2_scale_x, int log2_scale_y,
295 			  bool segments_shared)
296 {
297     segment *pseg;
298 
299     gx_rect_scale_exp2(&ppath->bbox, log2_scale_x, log2_scale_y);
300 #define SCALE_XY(pt) gx_point_scale_exp2(&pt, log2_scale_x, log2_scale_y)
301     SCALE_XY(ppath->position);
302     if (!segments_shared) {
303 	for (pseg = (segment *) (ppath->first_subpath); pseg != 0;
304 	     pseg = pseg->next
305 	     )
306 	    switch (pseg->type) {
307 	    case s_curve:
308 		SCALE_XY(((curve_segment *)pseg)->p1);
309 		SCALE_XY(((curve_segment *)pseg)->p2);
310 	    default:
311 		SCALE_XY(pseg->pt);
312 	    }
313     }
314 #undef SCALE_XY
315     return 0;
316 }
317 
318 /*
319  * Reverse a path.  We know ppath != ppath_old.
320  * NOTE: in releases 5.01 and earlier, the implicit line added by closepath
321  * became the first segment of the reversed path.  Starting in release
322  * 5.02, the code follows the Adobe implementation (and LanguageLevel 3
323  * specification), in which this line becomes the *last* segment of the
324  * reversed path.  This can produce some quite unintuitive results.
325  */
326 int
gx_path_copy_reversed(const gx_path * ppath_old,gx_path * ppath)327 gx_path_copy_reversed(const gx_path * ppath_old, gx_path * ppath)
328 {
329     const subpath *psub = ppath_old->first_subpath;
330 
331 #ifdef DEBUG
332     if (gs_debug_c('P'))
333 	gx_dump_path(ppath_old, "before reversepath");
334 #endif
335  nsp:
336     if (psub) {
337 	const segment *prev = psub->last;
338 	const segment *pseg;
339 	segment_notes notes =
340 	    (prev == (const segment *)psub ? sn_none :
341 	     psub->next->notes);
342 	segment_notes prev_notes;
343 	int code;
344 
345 	if (!psub->is_closed) {
346 	    code = gx_path_add_point(ppath, prev->pt.x, prev->pt.y);
347 	    if (code < 0)
348 		return code;
349 	}
350 	/*
351 	 * The do ... while structure of this loop is artificial,
352 	 * designed solely to keep compilers from complaining about
353 	 * 'statement not reached' or 'end-of-loop code not reached'.
354 	 * The normal exit from this loop is the goto statement in
355 	 * the s_start arm of the switch.
356 	 */
357 	do {
358 	    pseg = prev;
359 	    prev_notes = notes;
360 	    prev = pseg->prev;
361 	    notes = pseg->notes;
362 	    prev_notes = (prev_notes & sn_not_first) |
363 		(notes & ~sn_not_first);
364 	    switch (pseg->type) {
365 		case s_start:
366 		    /* Finished subpath */
367 		    if (psub->is_closed) {
368 			code =
369 			    gx_path_close_subpath_notes(ppath, prev_notes);
370 			if (code < 0)
371 			    return code;
372 		    }
373 		    psub = (const subpath *)psub->last->next;
374 		    goto nsp;
375 		case s_curve:
376 		    {
377 			const curve_segment *pc =
378 			(const curve_segment *)pseg;
379 
380 			code = gx_path_add_curve_notes(ppath,
381 						       pc->p2.x, pc->p2.y,
382 						       pc->p1.x, pc->p1.y,
383 					prev->pt.x, prev->pt.y, prev_notes);
384 			break;
385 		    }
386 		case s_line:
387 		    code = gx_path_add_line_notes(ppath,
388 					prev->pt.x, prev->pt.y, prev_notes);
389 		    break;
390 		case s_line_close:
391 		    /* Skip the closing line. */
392 		    code = gx_path_add_point(ppath, prev->pt.x,
393 					     prev->pt.y);
394 		    break;
395 		default:	/* not possible */
396 		    return_error(gs_error_Fatal);
397 	    }
398 	} while (code >= 0);
399 	return code;		/* only reached if code < 0 */
400     }
401 #undef sn_not_end
402     /*
403      * In the Adobe implementations, reversepath discards a trailing
404      * moveto unless the path consists only of a moveto.  We reproduce
405      * this behavior here, even though we consider it a bug.
406      */
407     if (ppath_old->first_subpath == 0 &&
408 	path_last_is_moveto(ppath_old)
409 	) {
410 	int code = gx_path_add_point(ppath, ppath_old->position.x,
411 				     ppath_old->position.y);
412 
413 	if (code < 0)
414 	    return code;
415     }
416 #ifdef DEBUG
417     if (gs_debug_c('P'))
418 	gx_dump_path(ppath, "after reversepath");
419 #endif
420     return 0;
421 }
422 
423 /* ------ Path enumeration ------ */
424 
425 /* Allocate a path enumerator. */
426 gs_path_enum *
gs_path_enum_alloc(gs_memory_t * mem,client_name_t cname)427 gs_path_enum_alloc(gs_memory_t * mem, client_name_t cname)
428 {
429     return gs_alloc_struct(mem, gs_path_enum, &st_path_enum, cname);
430 }
431 
432 /* Start enumerating a path. */
433 int
gx_path_enum_init(gs_path_enum * penum,const gx_path * ppath)434 gx_path_enum_init(gs_path_enum * penum, const gx_path * ppath)
435 {
436     penum->memory = 0;		/* path not copied */
437     penum->path = ppath;
438     penum->copied_path = 0;	/* not copied */
439     penum->pseg = (const segment *)ppath->first_subpath;
440     penum->moveto_done = false;
441     penum->notes = sn_none;
442     return 0;
443 }
444 
445 /* Enumerate the next element of a path. */
446 /* If the path is finished, return 0; */
447 /* otherwise, return the element type. */
448 int
gx_path_enum_next(gs_path_enum * penum,gs_fixed_point ppts[3])449 gx_path_enum_next(gs_path_enum * penum, gs_fixed_point ppts[3])
450 {
451     const segment *pseg = penum->pseg;
452 
453     if (pseg == 0) {		/* We've enumerated all the segments, but there might be */
454 	/* a trailing moveto. */
455 	const gx_path *ppath = penum->path;
456 
457 	if (path_last_is_moveto(ppath) && !penum->moveto_done) {	/* Handle a trailing moveto */
458 	    penum->moveto_done = true;
459 	    penum->notes = sn_none;
460 	    ppts[0] = ppath->position;
461 	    return gs_pe_moveto;
462 	}
463 	return 0;
464     }
465     penum->pseg = pseg->next;
466     penum->notes = pseg->notes;
467     switch (pseg->type) {
468 	case s_start:
469 	    ppts[0] = pseg->pt;
470 	    return gs_pe_moveto;
471 	case s_line:
472 	    ppts[0] = pseg->pt;
473 	    return gs_pe_lineto;
474 	case s_line_close:
475 	    ppts[0] = pseg->pt;
476 	    return gs_pe_closepath;
477 	case s_curve:
478 #define pcseg ((const curve_segment *)pseg)
479 	    ppts[0] = pcseg->p1;
480 	    ppts[1] = pcseg->p2;
481 	    ppts[2] = pseg->pt;
482 	    return gs_pe_curveto;
483 #undef pcseg
484 	default:
485 	    lprintf1("bad type %x in gx_path_enum_next!\n", pseg->type);
486 	    return_error(gs_error_Fatal);
487     }
488 }
489 
490 /* Return the notes from the last-enumerated segment. */
491 segment_notes
gx_path_enum_notes(const gs_path_enum * penum)492 gx_path_enum_notes(const gs_path_enum * penum)
493 {
494     return penum->notes;
495 }
496 
497 /* Back up 1 element in the path being enumerated. */
498 /* Return true if successful, false if we are at the beginning of the path. */
499 /* This implementation allows backing up multiple times, */
500 /* but no client currently relies on this. */
501 bool
gx_path_enum_backup(gs_path_enum * penum)502 gx_path_enum_backup(gs_path_enum * penum)
503 {
504     const segment *pseg = penum->pseg;
505 
506     if (pseg != 0) {
507 	if ((pseg = pseg->prev) == 0)
508 	    return false;
509 	penum->pseg = pseg;
510 	return true;
511     }
512     /* We're at the end of the path.  Check to see whether */
513     /* we need to back up over a trailing moveto. */
514     {
515 	const gx_path *ppath = penum->path;
516 
517 	if (path_last_is_moveto(ppath) && penum->moveto_done) {		/* Back up over the trailing moveto. */
518 	    penum->moveto_done = false;
519 	    return true;
520 	} {
521 	    const subpath *psub = ppath->current_subpath;
522 
523 	    if (psub == 0)	/* empty path */
524 		return false;
525 	    /* Back up to the last segment of the last subpath. */
526 	    penum->pseg = psub->last;
527 	    return true;
528 	}
529     }
530 }
531