xref: /plan9/sys/src/cmd/gs/src/gspath.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 1989, 1995, 1996, 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: gspath.c,v 1.10 2004/08/31 13:23:16 igor Exp $ */
18 /* Basic path routines for Ghostscript library */
19 #include "gx.h"
20 #include "math_.h"
21 #include "gserrors.h"
22 #include "gxfixed.h"
23 #include "gxmatrix.h"
24 #include "gscoord.h"		/* requires gsmatrix.h */
25 #include "gspath.h"		/* for checking prototypes */
26 #include "gzstate.h"
27 #include "gzpath.h"
28 #include "gxdevice.h"		/* for gxcpath.h */
29 #include "gxdevmem.h"		/* for gs_device_is_memory */
30 #include "gzcpath.h"
31 
32 /* ------ Miscellaneous ------ */
33 
34 int
gs_newpath(gs_state * pgs)35 gs_newpath(gs_state * pgs)
36 {
37     pgs->current_point_valid = false;
38     return gx_path_new(pgs->path);
39 }
40 
41 int
gs_closepath(gs_state * pgs)42 gs_closepath(gs_state * pgs)
43 {
44     gx_path *ppath = pgs->path;
45     int code = gx_path_close_subpath(ppath);
46 
47     if (code < 0)
48 	return code;
49     pgs->current_point = pgs->subpath_start;
50     return code;
51 }
52 
53 int
gs_upmergepath(gs_state * pgs)54 gs_upmergepath(gs_state * pgs)
55 {
56     return gx_path_add_path(pgs->saved->path, pgs->path);
57 }
58 
59 /* Get the current path (for internal use only). */
60 gx_path *
gx_current_path(const gs_state * pgs)61 gx_current_path(const gs_state * pgs)
62 {
63     return pgs->path;
64 }
65 
66 /* ------ Points and lines ------ */
67 
68 /*
69  * Define clamped values for out-of-range coordinates.
70  * Currently the path drawing routines can't handle values
71  * close to the edge of the representable space.
72  */
73 #define max_coord_fixed (max_fixed - int2fixed(1000))	/* arbitrary */
74 #define min_coord_fixed (-max_coord_fixed)
75 private inline void
clamp_point(gs_fixed_point * ppt,floatp x,floatp y)76 clamp_point(gs_fixed_point * ppt, floatp x, floatp y)
77 {
78 #define clamp_coord(xy)\
79   ppt->xy = (xy > fixed2float(max_coord_fixed) ? max_coord_fixed :\
80 	     xy < fixed2float(min_coord_fixed) ? min_coord_fixed :\
81 	     float2fixed(xy))
82     clamp_coord(x);
83     clamp_coord(y);
84 #undef clamp_coord
85 }
86 
87 int
gs_currentpoint(gs_state * pgs,gs_point * ppt)88 gs_currentpoint(gs_state * pgs, gs_point * ppt)
89 {
90     if (!pgs->current_point_valid)
91 	return_error(gs_error_nocurrentpoint);
92     return gs_itransform(pgs, pgs->current_point.x,
93 			      pgs->current_point.y, ppt);
94 }
95 
96 private inline int
gs_point_transform_compat(floatp x,floatp y,const gs_matrix_fixed * m,gs_point * pt)97 gs_point_transform_compat(floatp x, floatp y, const gs_matrix_fixed *m, gs_point *pt)
98 {
99 #if !PRECISE_CURRENTPOINT
100     gs_fixed_point p;
101     int code = gs_point_transform2fixed(m, x, y, &p);
102 
103     if (code < 0)
104 	return code;
105     pt->x = fixed2float(p.x);
106     pt->y = fixed2float(p.y);
107     return 0;
108 #else
109     return gs_point_transform(x, y, (const gs_matrix *)m, pt);
110 #endif
111 }
112 
113 private inline int
gs_distance_transform_compat(floatp x,floatp y,const gs_matrix_fixed * m,gs_point * pt)114 gs_distance_transform_compat(floatp x, floatp y, const gs_matrix_fixed *m, gs_point *pt)
115 {
116 #if !PRECISE_CURRENTPOINT
117     gs_fixed_point p;
118     int code = gs_distance_transform2fixed(m, x, y, &p);
119 
120     if (code < 0)
121 	return code;
122     pt->x = fixed2float(p.x);
123     pt->y = fixed2float(p.y);
124     return 0;
125 #else
126     return gs_distance_transform(x, y, (const gs_matrix *)m, pt);
127 #endif
128 }
129 
130 private inline int
clamp_point_aux(bool clamp_coordinates,gs_fixed_point * ppt,floatp x,floatp y)131 clamp_point_aux(bool clamp_coordinates, gs_fixed_point *ppt, floatp x, floatp y)
132 {
133     if (!f_fits_in_bits(x, fixed_int_bits) || !f_fits_in_bits(y, fixed_int_bits)) {
134 	if (!clamp_coordinates)
135 	    return_error(gs_error_limitcheck);
136 	clamp_point(ppt, x, y);
137     } else {
138 	/* 181-01.ps" fails with no rounding in
139 	   "Verify as last element of a userpath and effect on setbbox." */
140 	ppt->x = float2fixed_rounded(x);
141 	ppt->y = float2fixed_rounded(y);
142     }
143     return 0;
144 }
145 
146 int
gs_moveto_aux(gs_imager_state * pis,gx_path * ppath,floatp x,floatp y)147 gs_moveto_aux(gs_imager_state *pis, gx_path *ppath, floatp x, floatp y)
148 {
149     gs_fixed_point pt;
150     int code;
151 
152     code = clamp_point_aux(pis->clamp_coordinates, &pt, x, y);
153     if (code < 0)
154 	return code;
155     code = gx_path_add_point(ppath, pt.x, pt.y);
156     if (code < 0)
157 	return code;
158     ppath->start_flags = ppath->state_flags;
159     gx_setcurrentpoint(pis, x, y);
160     pis->subpath_start = pis->current_point;
161     pis->current_point_valid = true;
162     return 0;
163 }
164 
165 int
gs_moveto(gs_state * pgs,floatp x,floatp y)166 gs_moveto(gs_state * pgs, floatp x, floatp y)
167 {
168     gs_point pt;
169     int code = gs_point_transform_compat(x, y, &pgs->ctm, &pt);
170 
171     if (code < 0)
172 	return code;
173     return gs_moveto_aux((gs_imager_state *)pgs, pgs->path, pt.x, pt.y);
174 }
175 
176 int
gs_rmoveto(gs_state * pgs,floatp x,floatp y)177 gs_rmoveto(gs_state * pgs, floatp x, floatp y)
178 {
179     gs_point dd;
180     int code;
181 
182     if (!pgs->current_point_valid)
183 	return_error(gs_error_nocurrentpoint);
184     code = gs_distance_transform_compat(x, y, &pgs->ctm, &dd);
185     if (code < 0)
186 	return code;
187     /* fixme : check in range. */
188     return gs_moveto_aux((gs_imager_state *)pgs, pgs->path,
189 		dd.x + pgs->current_point.x, dd.y + pgs->current_point.y);
190 }
191 
192 private inline int
gs_lineto_aux(gs_state * pgs,floatp x,floatp y)193 gs_lineto_aux(gs_state * pgs, floatp x, floatp y)
194 {
195     gx_path *ppath = pgs->path;
196     gs_fixed_point pt;
197     int code;
198 
199     code = clamp_point_aux(pgs->clamp_coordinates, &pt, x, y);
200     if (code < 0)
201 	return code;
202     code = gx_path_add_line(ppath, pt.x, pt.y);
203     if (code < 0)
204 	return code;
205     gx_setcurrentpoint(pgs, x, y);
206     return 0;
207 }
208 
209 int
gs_lineto(gs_state * pgs,floatp x,floatp y)210 gs_lineto(gs_state * pgs, floatp x, floatp y)
211 {
212     gs_point pt;
213     int code = gs_point_transform_compat(x, y, &pgs->ctm, &pt);
214 
215     if (code < 0)
216 	return code;
217     return gs_lineto_aux(pgs, pt.x, pt.y);
218 }
219 
220 int
gs_rlineto(gs_state * pgs,floatp x,floatp y)221 gs_rlineto(gs_state * pgs, floatp x, floatp y)
222 {
223     gs_point dd;
224     int code;
225 
226     if (!pgs->current_point_valid)
227 	return_error(gs_error_nocurrentpoint);
228     code = gs_distance_transform_compat(x, y, &pgs->ctm, &dd);
229     if (code < 0)
230 	return code;
231     /* fixme : check in range. */
232     return gs_lineto_aux(pgs, dd.x + pgs->current_point.x,
233                               dd.y + pgs->current_point.y);
234 }
235 
236 /* ------ Curves ------ */
237 
238 private inline int
gs_curveto_aux(gs_state * pgs,floatp x1,floatp y1,floatp x2,floatp y2,floatp x3,floatp y3)239 gs_curveto_aux(gs_state * pgs,
240 	   floatp x1, floatp y1, floatp x2, floatp y2, floatp x3, floatp y3)
241 {
242     gs_fixed_point p1, p2, p3;
243     int code;
244     gx_path *ppath = pgs->path;
245 
246     code = clamp_point_aux(pgs->clamp_coordinates, &p1, x1, y1);
247     if (code < 0)
248 	return code;
249     code = clamp_point_aux(pgs->clamp_coordinates, &p2, x2, y2);
250     if (code < 0)
251 	return code;
252     code = clamp_point_aux(pgs->clamp_coordinates, &p3, x3, y3);
253     if (code < 0)
254 	return code;
255     code = gx_path_add_curve(ppath, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
256     if (code < 0)
257 	return code;
258     gx_setcurrentpoint(pgs, x3, y3);
259     return 0;
260 }
261 
262 int
gs_curveto(gs_state * pgs,floatp x1,floatp y1,floatp x2,floatp y2,floatp x3,floatp y3)263 gs_curveto(gs_state * pgs,
264 	   floatp x1, floatp y1, floatp x2, floatp y2, floatp x3, floatp y3)
265 {
266     gs_point pt1, pt2, pt3;
267     int code;
268 
269     code = gs_point_transform_compat(x1, y1, &pgs->ctm, &pt1);
270     if (code < 0)
271 	return code;
272     code = gs_point_transform_compat(x2, y2, &pgs->ctm, &pt2);
273     if (code < 0)
274 	return code;
275     code = gs_point_transform_compat(x3, y3, &pgs->ctm, &pt3);
276     if (code < 0)
277 	return code;
278     return gs_curveto_aux(pgs,   pt1.x, pt1.y,   pt2.x, pt2.y,   pt3.x, pt3.y);
279 }
280 
281 int
gs_rcurveto(gs_state * pgs,floatp dx1,floatp dy1,floatp dx2,floatp dy2,floatp dx3,floatp dy3)282 gs_rcurveto(gs_state * pgs,
283      floatp dx1, floatp dy1, floatp dx2, floatp dy2, floatp dx3, floatp dy3)
284 {
285     gs_point dd1, dd2, dd3;
286     int code;
287 
288     if (!pgs->current_point_valid)
289 	return_error(gs_error_nocurrentpoint);
290     code = gs_distance_transform_compat(dx1, dy1, &pgs->ctm, &dd1);
291     if (code < 0)
292 	return code;
293     code = gs_distance_transform_compat(dx2, dy2, &pgs->ctm, &dd2);
294     if (code < 0)
295 	return code;
296     code = gs_distance_transform_compat(dx3, dy3, &pgs->ctm, &dd3);
297     if (code < 0)
298 	return code;
299     /* fixme : check in range. */
300     return gs_curveto_aux(pgs, dd1.x + pgs->current_point.x, dd1.y + pgs->current_point.y,
301 			       dd2.x + pgs->current_point.x, dd2.y + pgs->current_point.y,
302 			       dd3.x + pgs->current_point.x, dd3.y + pgs->current_point.y);
303 }
304 
305 /* ------ Clipping ------ */
306 
307 /* Forward references */
308 private int common_clip(gs_state *, int);
309 
310 /*
311  * Return the effective clipping path of a graphics state.  Sometimes this
312  * is the intersection of the clip path and the view clip path; sometimes it
313  * is just the clip path.  We aren't sure what the correct algorithm is for
314  * this: for now, we use view clipping unless the current device is a memory
315  * device.  This takes care of the most important case, where the current
316  * device is a cache device.
317  */
318 int
gx_effective_clip_path(gs_state * pgs,gx_clip_path ** ppcpath)319 gx_effective_clip_path(gs_state * pgs, gx_clip_path ** ppcpath)
320 {
321     gs_id view_clip_id =
322 	(pgs->view_clip == 0 || pgs->view_clip->rule == 0 ? gs_no_id :
323 	 pgs->view_clip->id);
324 
325     if (gs_device_is_memory(pgs->device)) {
326 	*ppcpath = pgs->clip_path;
327 	return 0;
328     }
329     if (pgs->effective_clip_id == pgs->clip_path->id &&
330 	pgs->effective_view_clip_id == view_clip_id
331 	) {
332 	*ppcpath = pgs->effective_clip_path;
333 	return 0;
334     }
335     /* Update the cache. */
336     if (view_clip_id == gs_no_id) {
337 	if (!pgs->effective_clip_shared)
338 	    gx_cpath_free(pgs->effective_clip_path, "gx_effective_clip_path");
339 	pgs->effective_clip_path = pgs->clip_path;
340 	pgs->effective_clip_shared = true;
341     } else {
342 	gs_fixed_rect cbox, vcbox;
343 
344 	gx_cpath_inner_box(pgs->clip_path, &cbox);
345 	gx_cpath_outer_box(pgs->view_clip, &vcbox);
346 	if (rect_within(vcbox, cbox)) {
347 	    if (!pgs->effective_clip_shared)
348 		gx_cpath_free(pgs->effective_clip_path,
349 			      "gx_effective_clip_path");
350 	    pgs->effective_clip_path = pgs->view_clip;
351 	    pgs->effective_clip_shared = true;
352 	} else {
353 	    /* Construct the intersection of the two clip paths. */
354 	    int code;
355 	    gx_clip_path ipath;
356 	    gx_path vpath;
357 	    gx_clip_path *npath = pgs->effective_clip_path;
358 
359 	    if (pgs->effective_clip_shared) {
360 		npath = gx_cpath_alloc(pgs->memory, "gx_effective_clip_path");
361 		if (npath == 0)
362 		    return_error(gs_error_VMerror);
363 	    }
364 	    gx_cpath_init_local(&ipath, pgs->memory);
365 	    code = gx_cpath_assign_preserve(&ipath, pgs->clip_path);
366 	    if (code < 0)
367 		return code;
368 	    gx_path_init_local(&vpath, pgs->memory);
369 	    code = gx_cpath_to_path(pgs->view_clip, &vpath);
370 	    if (code < 0 ||
371 		(code = gx_cpath_clip(pgs, &ipath, &vpath,
372 				      gx_rule_winding_number)) < 0 ||
373 		(code = gx_cpath_assign_free(npath, &ipath)) < 0
374 		)
375 		DO_NOTHING;
376 	    gx_path_free(&vpath, "gx_effective_clip_path");
377 	    gx_cpath_free(&ipath, "gx_effective_clip_path");
378 	    if (code < 0)
379 		return code;
380 	    pgs->effective_clip_path = npath;
381 	    pgs->effective_clip_shared = false;
382 	}
383     }
384     pgs->effective_clip_id = pgs->effective_clip_path->id;
385     pgs->effective_view_clip_id = view_clip_id;
386     *ppcpath = pgs->effective_clip_path;
387     return 0;
388 }
389 
390 #ifdef DEBUG
391 /* Note that we just set the clipping path (internal). */
392 private void
note_set_clip_path(const gs_state * pgs)393 note_set_clip_path(const gs_state * pgs)
394 {
395     if (gs_debug_c('P')) {
396 	dlprintf("[P]Clipping path:\n");
397 	gx_cpath_print(pgs->clip_path);
398     }
399 }
400 #else
401 #  define note_set_clip_path(pgs) DO_NOTHING
402 #endif
403 
404 int
gs_clippath(gs_state * pgs)405 gs_clippath(gs_state * pgs)
406 {
407     gx_path cpath;
408     int code;
409 
410     gx_path_init_local(&cpath, pgs->path->memory);
411     code = gx_cpath_to_path(pgs->clip_path, &cpath);
412     if (code >= 0)
413 	code = gx_path_assign_free(pgs->path, &cpath);
414     if (code < 0)
415 	gx_path_free(&cpath, "gs_clippath");
416     return code;
417 }
418 
419 int
gs_initclip(gs_state * pgs)420 gs_initclip(gs_state * pgs)
421 {
422     gs_fixed_rect box;
423     int code = gx_default_clip_box(pgs, &box);
424 
425     if (code < 0)
426 	return code;
427     return gx_clip_to_rectangle(pgs, &box);
428 }
429 
430 int
gs_clip(gs_state * pgs)431 gs_clip(gs_state * pgs)
432 {
433     return common_clip(pgs, gx_rule_winding_number);
434 }
435 
436 int
gs_eoclip(gs_state * pgs)437 gs_eoclip(gs_state * pgs)
438 {
439     return common_clip(pgs, gx_rule_even_odd);
440 }
441 
442 private int
common_clip(gs_state * pgs,int rule)443 common_clip(gs_state * pgs, int rule)
444 {
445     int code = gx_cpath_clip(pgs, pgs->clip_path, pgs->path, rule);
446     if (code < 0)
447 	return code;
448     pgs->clip_path->rule = rule;
449     note_set_clip_path(pgs);
450     return 0;
451 }
452 
453 /* Establish a rectangle as the clipping path. */
454 /* Used by initclip and by the character and Pattern cache logic. */
455 int
gx_clip_to_rectangle(gs_state * pgs,gs_fixed_rect * pbox)456 gx_clip_to_rectangle(gs_state * pgs, gs_fixed_rect * pbox)
457 {
458     int code = gx_cpath_from_rectangle(pgs->clip_path, pbox);
459 
460     if (code < 0)
461 	return code;
462     pgs->clip_path->rule = gx_rule_winding_number;
463     note_set_clip_path(pgs);
464     return 0;
465 }
466 
467 /* Set the clipping path to the current path, without intersecting. */
468 /* This is very inefficient right now. */
469 int
gx_clip_to_path(gs_state * pgs)470 gx_clip_to_path(gs_state * pgs)
471 {
472     gs_fixed_rect bbox;
473     int code;
474 
475     if ((code = gx_path_bbox(pgs->path, &bbox)) < 0 ||
476 	(code = gx_clip_to_rectangle(pgs, &bbox)) < 0 ||
477 	(code = gs_clip(pgs)) < 0
478 	)
479 	return code;
480     note_set_clip_path(pgs);
481     return 0;
482 }
483 
484 /* Get the default clipping box. */
485 int
gx_default_clip_box(const gs_state * pgs,gs_fixed_rect * pbox)486 gx_default_clip_box(const gs_state * pgs, gs_fixed_rect * pbox)
487 {
488     register gx_device *dev = gs_currentdevice(pgs);
489     gs_rect bbox;
490     gs_matrix imat;
491     int code;
492 
493     if (dev->ImagingBBox_set) {	/* Use the ImagingBBox, relative to default user space. */
494 	gs_defaultmatrix(pgs, &imat);
495 	bbox.p.x = dev->ImagingBBox[0];
496 	bbox.p.y = dev->ImagingBBox[1];
497 	bbox.q.x = dev->ImagingBBox[2];
498 	bbox.q.y = dev->ImagingBBox[3];
499     } else {			/* Use the MediaSize indented by the HWMargins, */
500 	/* relative to unrotated user space adjusted by */
501 	/* the Margins.  (We suspect this isn't quite right, */
502 	/* but the whole issue of "margins" is such a mess that */
503 	/* we don't think we can do any better.) */
504 	(*dev_proc(dev, get_initial_matrix)) (dev, &imat);
505 	/* Adjust for the Margins. */
506 	imat.tx += dev->Margins[0] * dev->HWResolution[0] /
507 	    dev->MarginsHWResolution[0];
508 	imat.ty += dev->Margins[1] * dev->HWResolution[1] /
509 	    dev->MarginsHWResolution[1];
510 	bbox.p.x = dev->HWMargins[0];
511 	bbox.p.y = dev->HWMargins[1];
512 	bbox.q.x = dev->MediaSize[0] - dev->HWMargins[2];
513 	bbox.q.y = dev->MediaSize[1] - dev->HWMargins[3];
514     }
515     code = gs_bbox_transform(&bbox, &imat, &bbox);
516     if (code < 0)
517 	return code;
518     /* Round the clipping box so that it doesn't get ceilinged. */
519     pbox->p.x = fixed_rounded(float2fixed(bbox.p.x));
520     pbox->p.y = fixed_rounded(float2fixed(bbox.p.y));
521     pbox->q.x = fixed_rounded(float2fixed(bbox.q.x));
522     pbox->q.y = fixed_rounded(float2fixed(bbox.q.y));
523     return 0;
524 }
525