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