1 /* Copyright (C) 1993, 2000 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: gxacpath.c,v 1.10 2004/08/04 19:36:12 stefan Exp $ */
18 /* Accumulator for clipping paths */
19 #include "gx.h"
20 #include "gserrors.h"
21 #include "gsrop.h"
22 #include "gsstruct.h"
23 #include "gsutil.h"
24 #include "gsdcolor.h"
25 #include "gxdevice.h"
26 #include "gxfixed.h"
27 #include "gxistate.h"
28 #include "gzpath.h"
29 #include "gxpaint.h"
30 #include "gzcpath.h"
31 #include "gzacpath.h"
32
33 /* Device procedures */
34 private dev_proc_open_device(accum_open);
35 private dev_proc_close_device(accum_close);
36 private dev_proc_fill_rectangle(accum_fill_rectangle);
37
38 /* The device descriptor */
39 /* Many of these procedures won't be called; they are set to NULL. */
40 private const gx_device_cpath_accum gs_cpath_accum_device =
41 {std_device_std_body(gx_device_cpath_accum, 0, "clip list accumulator",
42 0, 0, 1, 1),
43 {accum_open,
44 NULL,
45 NULL,
46 NULL,
47 accum_close,
48 NULL,
49 NULL,
50 accum_fill_rectangle,
51 NULL,
52 NULL,
53 NULL,
54 NULL,
55 NULL,
56 NULL,
57 NULL,
58 NULL,
59 NULL,
60 NULL,
61 NULL,
62 NULL,
63 NULL,
64 NULL,
65 NULL,
66 NULL,
67 gx_default_fill_path,
68 gx_default_stroke_path,
69 NULL,
70 gx_default_fill_trapezoid,
71 gx_default_fill_parallelogram,
72 gx_default_fill_triangle,
73 gx_default_draw_thin_line,
74 gx_default_begin_image,
75 gx_default_image_data,
76 gx_default_end_image,
77 NULL,
78 NULL,
79 gx_get_largest_clipping_box,
80 gx_default_begin_typed_image,
81 NULL,
82 NULL,
83 NULL,
84 NULL,
85 gx_default_text_begin,
86 gx_default_finish_copydevice,
87 NULL,
88 NULL,
89 NULL,
90 NULL,
91 NULL,
92 NULL,
93 NULL,
94 NULL,
95 NULL
96 }
97 };
98
99 /* Start accumulating a clipping path. */
100 void
gx_cpath_accum_begin(gx_device_cpath_accum * padev,gs_memory_t * mem)101 gx_cpath_accum_begin(gx_device_cpath_accum * padev, gs_memory_t * mem)
102 {
103 gx_device_init((gx_device *) padev,
104 (const gx_device *) & gs_cpath_accum_device,
105 NULL /* allocated on stack */ , true);
106 padev->list_memory = mem;
107 (*dev_proc(padev, open_device)) ((gx_device *) padev);
108 }
109
110 void
gx_cpath_accum_set_cbox(gx_device_cpath_accum * padev,const gs_fixed_rect * pbox)111 gx_cpath_accum_set_cbox(gx_device_cpath_accum * padev,
112 const gs_fixed_rect * pbox)
113 {
114 padev->clip_box.p.x = fixed2int_var(pbox->p.x);
115 padev->clip_box.p.y = fixed2int_var(pbox->p.y);
116 padev->clip_box.q.x = fixed2int_var_ceiling(pbox->q.x);
117 padev->clip_box.q.y = fixed2int_var_ceiling(pbox->q.y);
118 }
119
120 /* Finish accumulating a clipping path. */
121 int
gx_cpath_accum_end(const gx_device_cpath_accum * padev,gx_clip_path * pcpath)122 gx_cpath_accum_end(const gx_device_cpath_accum * padev, gx_clip_path * pcpath)
123 {
124 int code = (*dev_proc(padev, close_device)) ((gx_device *) padev);
125 /* Make an entire clipping path so we can use cpath_assign. */
126 gx_clip_path apath;
127
128 if (code < 0)
129 return code;
130 gx_cpath_init_local(&apath, padev->list_memory);
131 apath.rect_list->list = padev->list;
132 if (padev->list.count == 0)
133 apath.path.bbox.p.x = apath.path.bbox.p.y =
134 apath.path.bbox.q.x = apath.path.bbox.q.y = 0;
135 else {
136 apath.path.bbox.p.x = int2fixed(padev->bbox.p.x);
137 apath.path.bbox.p.y = int2fixed(padev->bbox.p.y);
138 apath.path.bbox.q.x = int2fixed(padev->bbox.q.x);
139 apath.path.bbox.q.y = int2fixed(padev->bbox.q.y);
140 }
141 /* indicate that the bbox is accurate */
142 apath.path.bbox_accurate = 1;
143 /* Note that the result of the intersection might be */
144 /* a single rectangle. This will cause clip_path_is_rect.. */
145 /* to return true. This, in turn, requires that */
146 /* we set apath.inner_box correctly. */
147 if (clip_list_is_rectangle(&padev->list))
148 apath.inner_box = apath.path.bbox;
149 else {
150 /* The quick check must fail. */
151 apath.inner_box.p.x = apath.inner_box.p.y = 0;
152 apath.inner_box.q.x = apath.inner_box.q.y = 0;
153 }
154 gx_cpath_set_outer_box(&apath);
155 apath.path_valid = false;
156 apath.id = gs_next_ids(padev->list_memory, 1); /* path changed => change id */
157 gx_cpath_assign_free(pcpath, &apath);
158 return 0;
159 }
160
161 /* Discard an accumulator in case of error. */
162 void
gx_cpath_accum_discard(gx_device_cpath_accum * padev)163 gx_cpath_accum_discard(gx_device_cpath_accum * padev)
164 {
165 gx_clip_list_free(&padev->list, padev->list_memory);
166 }
167
168 /* Intersect two clipping paths using an accumulator. */
169 int
gx_cpath_intersect_path_slow(gx_clip_path * pcpath,gx_path * ppath,int rule,gs_imager_state * pis)170 gx_cpath_intersect_path_slow(gx_clip_path * pcpath, gx_path * ppath,
171 int rule, gs_imager_state *pis)
172 {
173 gs_logical_operation_t save_lop = gs_current_logical_op_inline(pis);
174 gx_device_cpath_accum adev;
175 gx_device_color devc;
176 gx_fill_params params;
177 int code;
178
179 gx_cpath_accum_begin(&adev, pcpath->path.memory);
180 set_nonclient_dev_color(&devc, 0); /* arbitrary, but not transparent */
181 gs_set_logical_op_inline(pis, lop_default);
182 params.rule = rule;
183 params.adjust.x = params.adjust.y = fixed_half;
184 params.flatness = gs_currentflat_inline(pis);
185 params.fill_zero_width = true;
186 code = gx_fill_path_only(ppath, (gx_device *)&adev, pis,
187 ¶ms, &devc, pcpath);
188 if (code < 0 || (code = gx_cpath_accum_end(&adev, pcpath)) < 0)
189 gx_cpath_accum_discard(&adev);
190 gs_set_logical_op_inline(pis, save_lop);
191 return code;
192 }
193
194 /* ------ Device implementation ------ */
195
196 #ifdef DEBUG
197 /* Validate a clipping path after accumulation. */
198 private bool
clip_list_validate(const gx_clip_list * clp)199 clip_list_validate(const gx_clip_list * clp)
200 {
201 if (clp->count <= 1)
202 return (clp->head == 0 && clp->tail == 0 &&
203 clp->single.next == 0 && clp->single.prev == 0);
204 else {
205 const gx_clip_rect *prev = clp->head;
206 const gx_clip_rect *ptr;
207 bool ok = true;
208
209 while ((ptr = prev->next) != 0) {
210 if (ptr->ymin > ptr->ymax || ptr->xmin > ptr->xmax ||
211 !(ptr->ymin >= prev->ymax ||
212 (ptr->ymin == prev->ymin &&
213 ptr->ymax == prev->ymax &&
214 ptr->xmin >= prev->xmax)) ||
215 ptr->prev != prev
216 ) {
217 clip_rect_print('q', "WRONG:", ptr);
218 ok = false;
219 }
220 prev = ptr;
221 }
222 return ok && prev == clp->tail;
223 }
224 }
225 #endif /* DEBUG */
226
227 /* Initialize the accumulation device. */
228 private int
accum_open(register gx_device * dev)229 accum_open(register gx_device * dev)
230 {
231 gx_device_cpath_accum * const adev = (gx_device_cpath_accum *)dev;
232
233 gx_clip_list_init(&adev->list);
234 adev->bbox.p.x = adev->bbox.p.y = max_int;
235 adev->bbox.q.x = adev->bbox.q.y = min_int;
236 adev->clip_box.p.x = adev->clip_box.p.y = min_int;
237 adev->clip_box.q.x = adev->clip_box.q.y = max_int;
238 return 0;
239 }
240
241 /* Close the accumulation device. */
242 private int
accum_close(gx_device * dev)243 accum_close(gx_device * dev)
244 {
245 gx_device_cpath_accum * const adev = (gx_device_cpath_accum *)dev;
246
247 adev->list.xmin = adev->bbox.p.x;
248 adev->list.xmax = adev->bbox.q.x;
249 #ifdef DEBUG
250 if (gs_debug_c('q')) {
251 gx_clip_rect *rp =
252 (adev->list.count <= 1 ? &adev->list.single : adev->list.head);
253
254 dlprintf6("[q]list at 0x%lx, count=%d, head=0x%lx, tail=0x%lx, xrange=(%d,%d):\n",
255 (ulong) & adev->list, adev->list.count,
256 (ulong) adev->list.head, (ulong) adev->list.tail,
257 adev->list.xmin, adev->list.xmax);
258 while (rp != 0) {
259 clip_rect_print('q', " ", rp);
260 rp = rp->next;
261 }
262 }
263 if (!clip_list_validate(&adev->list)) {
264 lprintf1("[q]Bad clip list 0x%lx!\n", (ulong) & adev->list);
265 return_error(gs_error_Fatal);
266 }
267 #endif
268 return 0;
269 }
270
271 /* Accumulate one rectangle. */
272 /* Allocate a rectangle to be added to the list. */
273 static const gx_clip_rect clip_head_rect = {
274 0, 0, min_int, min_int, min_int, min_int
275 };
276 static const gx_clip_rect clip_tail_rect = {
277 0, 0, max_int, max_int, max_int, max_int
278 };
279 private gx_clip_rect *
accum_alloc_rect(gx_device_cpath_accum * adev)280 accum_alloc_rect(gx_device_cpath_accum * adev)
281 {
282 gs_memory_t *mem = adev->list_memory;
283 gx_clip_rect *ar = gs_alloc_struct(mem, gx_clip_rect, &st_clip_rect,
284 "accum_alloc_rect");
285
286 if (ar == 0)
287 return 0;
288 if (adev->list.count == 2) {
289 /* We're switching from a single rectangle to a list. */
290 /* Allocate the head and tail entries. */
291 gx_clip_rect *head = ar;
292 gx_clip_rect *tail =
293 gs_alloc_struct(mem, gx_clip_rect, &st_clip_rect,
294 "accum_alloc_rect(tail)");
295 gx_clip_rect *single =
296 gs_alloc_struct(mem, gx_clip_rect, &st_clip_rect,
297 "accum_alloc_rect(single)");
298
299 ar = gs_alloc_struct(mem, gx_clip_rect, &st_clip_rect,
300 "accum_alloc_rect(head)");
301 if (tail == 0 || single == 0 || ar == 0) {
302 gs_free_object(mem, ar, "accum_alloc_rect");
303 gs_free_object(mem, single, "accum_alloc_rect(single)");
304 gs_free_object(mem, tail, "accum_alloc_rect(tail)");
305 gs_free_object(mem, head, "accum_alloc_rect(head)");
306 return 0;
307 }
308 *head = clip_head_rect;
309 head->next = single;
310 *single = adev->list.single;
311 single->prev = head;
312 single->next = tail;
313 *tail = clip_tail_rect;
314 tail->prev = single;
315 adev->list.head = head;
316 adev->list.tail = tail;
317 }
318 return ar;
319 }
320 #define ACCUM_ALLOC(s, ar, px, py, qx, qy)\
321 if (++(adev->list.count) == 1)\
322 ar = &adev->list.single;\
323 else if ((ar = accum_alloc_rect(adev)) == 0)\
324 return_error(gs_error_VMerror);\
325 ACCUM_SET(s, ar, px, py, qx, qy)
326 #define ACCUM_SET(s, ar, px, py, qx, qy)\
327 (ar)->xmin = px, (ar)->ymin = py, (ar)->xmax = qx, (ar)->ymax = qy;\
328 clip_rect_print('Q', s, ar)
329 /* Link or unlink a rectangle in the list. */
330 #define ACCUM_ADD_LAST(ar)\
331 ACCUM_ADD_BEFORE(ar, adev->list.tail)
332 #define ACCUM_ADD_AFTER(ar, rprev)\
333 ar->prev = (rprev), (ar->next = (rprev)->next)->prev = ar,\
334 (rprev)->next = ar
335 #define ACCUM_ADD_BEFORE(ar, rnext)\
336 (ar->prev = (rnext)->prev)->next = ar, ar->next = (rnext),\
337 (rnext)->prev = ar
338 #define ACCUM_REMOVE(ar)\
339 ar->next->prev = ar->prev, ar->prev->next = ar->next
340 /* Free a rectangle that was removed from the list. */
341 #define ACCUM_FREE(s, ar)\
342 if (--(adev->list.count)) {\
343 clip_rect_print('Q', s, ar);\
344 gs_free_object(adev->list_memory, ar, "accum_rect");\
345 }
346 /*
347 * Add a rectangle to the list. It would be wonderful if rectangles
348 * were always disjoint and always presented in the correct order,
349 * but they aren't: the fill loop works by trapezoids, not by scan lines,
350 * and may produce slightly overlapping rectangles because of "fattening".
351 * All we can count on is that they are approximately disjoint and
352 * approximately in order.
353 *
354 * Because of the way the fill loop handles a path that is just a single
355 * rectangle, we take special care to merge Y-adjacent rectangles when
356 * this is possible.
357 */
358 private int
accum_fill_rectangle(gx_device * dev,int x,int y,int w,int h,gx_color_index color)359 accum_fill_rectangle(gx_device * dev, int x, int y, int w, int h,
360 gx_color_index color)
361 {
362 gx_device_cpath_accum * const adev = (gx_device_cpath_accum *)dev;
363 int xe = x + w, ye = y + h;
364 gx_clip_rect *nr;
365 gx_clip_rect *ar;
366 register gx_clip_rect *rptr;
367 int ymin, ymax;
368
369 /* Clip the rectangle being added. */
370 if (y < adev->clip_box.p.y)
371 y = adev->clip_box.p.y;
372 if (ye > adev->clip_box.q.y)
373 ye = adev->clip_box.q.y;
374 if (y >= ye)
375 return 0;
376 if (x < adev->clip_box.p.x)
377 x = adev->clip_box.p.x;
378 if (xe > adev->clip_box.q.x)
379 xe = adev->clip_box.q.x;
380 if (x >= xe)
381 return 0;
382
383 /* Update the bounding box. */
384 if (x < adev->bbox.p.x)
385 adev->bbox.p.x = x;
386 if (y < adev->bbox.p.y)
387 adev->bbox.p.y = y;
388 if (xe > adev->bbox.q.x)
389 adev->bbox.q.x = xe;
390 if (ye > adev->bbox.q.y)
391 adev->bbox.q.y = ye;
392
393 top:
394 if (adev->list.count == 0) { /* very first rectangle */
395 adev->list.count = 1;
396 ACCUM_SET("single", &adev->list.single, x, y, xe, ye);
397 return 0;
398 }
399 if (adev->list.count == 1) { /* check for Y merging */
400 rptr = &adev->list.single;
401 if (x == rptr->xmin && xe == rptr->xmax &&
402 y <= rptr->ymax && ye >= rptr->ymin
403 ) {
404 if (y < rptr->ymin)
405 rptr->ymin = y;
406 if (ye > rptr->ymax)
407 rptr->ymax = ye;
408 return 0;
409 }
410 }
411 else
412 rptr = adev->list.tail->prev;
413 if (y >= rptr->ymax) {
414 if (y == rptr->ymax && x == rptr->xmin && xe == rptr->xmax &&
415 (rptr->prev == 0 || y != rptr->prev->ymax)
416 ) {
417 rptr->ymax = ye;
418 return 0;
419 }
420 ACCUM_ALLOC("app.y", nr, x, y, xe, ye);
421 ACCUM_ADD_LAST(nr);
422 return 0;
423 } else if (y == rptr->ymin && ye == rptr->ymax && x >= rptr->xmin) {
424 if (x <= rptr->xmax) {
425 if (xe > rptr->xmax)
426 rptr->xmax = xe;
427 return 0;
428 }
429 ACCUM_ALLOC("app.x", nr, x, y, xe, ye);
430 ACCUM_ADD_LAST(nr);
431 return 0;
432 }
433 ACCUM_ALLOC("accum", nr, x, y, xe, ye);
434 rptr = adev->list.tail->prev;
435 /* Work backwards till we find the insertion point. */
436 while (ye <= rptr->ymin)
437 rptr = rptr->prev;
438 ymin = rptr->ymin;
439 ymax = rptr->ymax;
440 if (ye > ymax) {
441 if (y >= ymax) { /* Insert between two bands. */
442 ACCUM_ADD_AFTER(nr, rptr);
443 return 0;
444 }
445 /* Split off the top part of the new rectangle. */
446 ACCUM_ALLOC("a.top", ar, x, ymax, xe, ye);
447 ACCUM_ADD_AFTER(ar, rptr);
448 ye = nr->ymax = ymax;
449 clip_rect_print('Q', " ymax", nr);
450 }
451 /* Here we know ymin < ye <= ymax; */
452 /* rptr points to the last node with this value of ymin/ymax. */
453 /* If necessary, split off the part of the existing band */
454 /* that is above the new band. */
455 if (ye < ymax) {
456 gx_clip_rect *rsplit = rptr;
457
458 while (rsplit->ymax == ymax) {
459 ACCUM_ALLOC("s.top", ar, rsplit->xmin, ye, rsplit->xmax, ymax);
460 ACCUM_ADD_AFTER(ar, rptr);
461 rsplit->ymax = ye;
462 rsplit = rsplit->prev;
463 }
464 ymax = ye;
465 }
466 /* Now ye = ymax. If necessary, split off the part of the */
467 /* existing band that is below the new band. */
468 if (y > ymin) {
469 gx_clip_rect *rbot = rptr, *rsplit;
470
471 while (rbot->prev->ymin == ymin)
472 rbot = rbot->prev;
473 for (rsplit = rbot;;) {
474 ACCUM_ALLOC("s.bot", ar, rsplit->xmin, ymin, rsplit->xmax, y);
475 ACCUM_ADD_BEFORE(ar, rbot);
476 rsplit->ymin = y;
477 if (rsplit == rptr)
478 break;
479 rsplit = rsplit->next;
480 }
481 ymin = y;
482 }
483 /* Now y <= ymin as well. (y < ymin is possible.) */
484 nr->ymin = ymin;
485 /* Search for the X insertion point. */
486 for (; rptr->ymin == ymin; rptr = rptr->prev) {
487 if (xe < rptr->xmin)
488 continue; /* still too far to right */
489 if (x > rptr->xmax)
490 break; /* disjoint */
491 /* The new rectangle overlaps an existing one. Merge them. */
492 if (xe > rptr->xmax) {
493 rptr->xmax = nr->xmax; /* might be > xe if */
494 /* we already did a merge */
495 clip_rect_print('Q', "widen", rptr);
496 }
497 ACCUM_FREE("free", nr);
498 if (x >= rptr->xmin)
499 goto out;
500 /* Might overlap other rectangles to the left. */
501 rptr->xmin = x;
502 nr = rptr;
503 ACCUM_REMOVE(rptr);
504 clip_rect_print('Q', "merge", nr);
505 }
506 ACCUM_ADD_AFTER(nr, rptr);
507 out:
508 /* Check whether there are only 0 or 1 rectangles left. */
509 if (adev->list.count <= 1) {
510 /* We're switching from a list to at most 1 rectangle. */
511 /* Free the head and tail entries. */
512 gs_memory_t *mem = adev->list_memory;
513 gx_clip_rect *single = adev->list.head->next;
514
515 if (single != adev->list.tail) {
516 adev->list.single = *single;
517 gs_free_object(mem, single, "accum_free_rect(single)");
518 adev->list.single.next = adev->list.single.prev = 0;
519 }
520 gs_free_object(mem, adev->list.tail, "accum_free_rect(tail)");
521 gs_free_object(mem, adev->list.head, "accum_free_rect(head)");
522 adev->list.head = 0;
523 adev->list.tail = 0;
524 }
525 /* Check whether there is still more of the new band to process. */
526 if (y < ymin) {
527 /* Continue with the bottom part of the new rectangle. */
528 clip_rect_print('Q', " ymin", nr);
529 ye = ymin;
530 goto top;
531 }
532 return 0;
533 }
534