xref: /plan9/sys/src/cmd/gs/src/gxacpath.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
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 			     &params, &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