1 /* Copyright (C) 2001-2019 Artifex Software, Inc.
2 All Rights Reserved.
3
4 This software is provided AS-IS with no warranty, either express or
5 implied.
6
7 This software is distributed under license and may not be copied,
8 modified or distributed except as expressly authorized under the terms
9 of the license contained in the file LICENSE in this distribution.
10
11 Refer to licensing information at http://www.artifex.com or contact
12 Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato,
13 CA 94945, U.S.A., +1(415)492-9861, for further information.
14 */
15
16
17 /* Path tracing procedures for Ghostscript library */
18 #include "math_.h"
19 #include "gx.h"
20 #include "gserrors.h"
21 #include "gspath.h" /* for gs_path_enum_alloc prototype */
22 #include "gsstruct.h"
23 #include "gxfixed.h"
24 #include "gxarith.h"
25 #include "gzpath.h"
26
27 /* Define the enumeration structure. */
28 public_st_path_enum();
29
30 /* Read the current point of a path. */
31 int
gx_path_current_point(const gx_path * ppath,gs_fixed_point * ppt)32 gx_path_current_point(const gx_path * ppath, gs_fixed_point * ppt)
33 {
34 if (!path_position_valid(ppath))
35 return_error(gs_error_nocurrentpoint);
36 /* Copying the coordinates individually */
37 /* is much faster on a PC, and almost as fast on other machines.... */
38 ppt->x = ppath->position.x, ppt->y = ppath->position.y;
39 return 0;
40 }
41
42 /* Read the start point of the current subpath. */
43 int
gx_path_subpath_start_point(const gx_path * ppath,gs_fixed_point * ppt)44 gx_path_subpath_start_point(const gx_path * ppath, gs_fixed_point * ppt)
45 {
46 const subpath *psub = ppath->current_subpath;
47
48 if (!psub)
49 return_error(gs_error_nocurrentpoint);
50 *ppt = psub->pt;
51 return 0;
52 }
53
54 /* Read the bounding box of a path. */
55 /* Note that if the last element of the path is a moveto, */
56 /* the bounding box does not include this point, */
57 /* unless this is the only element of the path. */
58 int
gx_path_bbox(gx_path * ppath,gs_fixed_rect * pbox)59 gx_path_bbox(gx_path * ppath, gs_fixed_rect * pbox)
60 {
61 if (ppath == NULL) {
62 return_error(gs_error_unknownerror) ;
63 }
64 if (ppath->bbox_accurate) {
65 /* The bounding box was set by setbbox. */
66 *pbox = ppath->bbox;
67 return 0;
68 }
69 if (ppath->first_subpath == 0) {
70 /* The path is empty, use the current point if any. */
71 int code = gx_path_current_point(ppath, &pbox->p);
72
73 if (code < 0) {
74 /*
75 * Don't return garbage, in case the caller doesn't
76 * check the return code.
77 */
78 pbox->p.x = pbox->p.y = 0;
79 }
80 pbox->q = pbox->p;
81 return code;
82 }
83 /* The stored bounding box may not be up to date. */
84 /* Correct it now if necessary. */
85 if (ppath->box_last == ppath->current_subpath->last) {
86 /* Box is up to date */
87 *pbox = ppath->bbox;
88 } else {
89 fixed px, py, qx, qy;
90 const segment *pseg = ppath->box_last;
91
92 if (pseg == 0) { /* box is uninitialized */
93 pseg = (const segment *)ppath->first_subpath;
94 px = qx = pseg->pt.x;
95 py = qy = pseg->pt.y;
96 } else {
97 px = ppath->bbox.p.x, py = ppath->bbox.p.y;
98 qx = ppath->bbox.q.x, qy = ppath->bbox.q.y;
99 }
100
101 /* Macro for adjusting the bounding box when adding a point */
102 #define ADJUST_BBOX(pt)\
103 if ((pt).x < px) px = (pt).x;\
104 else if ((pt).x > qx) qx = (pt).x;\
105 if ((pt).y < py) py = (pt).y;\
106 else if ((pt).y > qy) qy = (pt).y
107
108 while ((pseg = pseg->next) != 0) {
109 switch (pseg->type) {
110 case s_curve:
111 ADJUST_BBOX(((const curve_segment *)pseg)->p1);
112 ADJUST_BBOX(((const curve_segment *)pseg)->p2);
113 /* falls through */
114 default:
115 ADJUST_BBOX(pseg->pt);
116 }
117 }
118 #undef ADJUST_BBOX
119
120 #define STORE_BBOX(b)\
121 (b).p.x = px, (b).p.y = py, (b).q.x = qx, (b).q.y = qy;
122 STORE_BBOX(*pbox);
123 STORE_BBOX(ppath->bbox);
124 #undef STORE_BBOX
125
126 ppath->box_last = ppath->current_subpath->last;
127 }
128 return 0;
129 }
130
131 /* A variation of gs_path_bbox, to be used by the patbbox operator */
132 int
gx_path_bbox_set(gx_path * ppath,gs_fixed_rect * pbox)133 gx_path_bbox_set(gx_path * ppath, gs_fixed_rect * pbox)
134 {
135 if (ppath->bbox_set) {
136 /* The bounding box was set by setbbox. */
137 *pbox = ppath->bbox;
138 return 0;
139 } else
140 return gx_path_bbox(ppath, pbox);
141 }
142
143 /* Test if a path has any curves. */
144 #undef gx_path_has_curves
145 bool
gx_path_has_curves(const gx_path * ppath)146 gx_path_has_curves(const gx_path * ppath)
147 {
148 return gx_path_has_curves_inline(ppath);
149 }
150 #define gx_path_has_curves(ppath)\
151 gx_path_has_curves_inline(ppath)
152
153 /* Test if a path has no segments. */
154 #undef gx_path_is_void
155 bool
gx_path_is_void(const gx_path * ppath)156 gx_path_is_void(const gx_path * ppath)
157 {
158 return gx_path_is_void_inline(ppath);
159 }
160 #define gx_path_is_void(ppath)\
161 gx_path_is_void_inline(ppath)
162
163 /* Test if a path has no elements at all. */
164 bool
gx_path_is_null(const gx_path * ppath)165 gx_path_is_null(const gx_path * ppath)
166 {
167 return gx_path_is_null_inline(ppath);
168 }
169
170 /*
171 * Test if a subpath is a rectangle; if so, return its bounding box
172 * and the start of the next subpath.
173 * Note that this must recognize:
174 * ordinary closed rectangles (M, L, L, L, C);
175 * open rectangles (M, L, L, L);
176 * rectangles closed with lineto (Mo, L, L, L, Lo);
177 * rectangles closed with *both* lineto and closepath (bad PostScript,
178 * but unfortunately not rare) (Mo, L, L, L, Lo, C).
179 */
180 gx_path_rectangular_type
gx_subpath_is_rectangular(const subpath * pseg0,gs_fixed_rect * pbox,const subpath ** ppnext)181 gx_subpath_is_rectangular(const subpath * pseg0, gs_fixed_rect * pbox,
182 const subpath ** ppnext)
183 {
184 const segment *pseg1, *pseg2, *pseg3, *pseg4;
185 gx_path_rectangular_type type = prt_none;
186 fixed x0 = pseg0->pt.x, y0 = pseg0->pt.y;
187 fixed x1, y1, x2, y2, x3, y3;
188
189 pseg1 = (const segment *)pseg0;
190 do {
191 pseg1 = pseg1->next;
192 if (pseg1 == NULL)
193 return prt_none;
194 x1 = pseg1->pt.x;
195 y1 = pseg1->pt.y;
196 if (pseg1->type == s_curve) {
197 if (gx_curve_is_really_point(x0, y0, pseg1))
198 continue; /* Ignore this one and try again */
199 if (gx_curve_is_really_line(x0, y0, pseg1))
200 break; /* That'll do! */
201 return prt_none;
202 } else if (pseg1->type != s_line && pseg1->type != s_gap)
203 return prt_none;
204 } while (x1 == x0 && y1 == y0);
205
206 pseg2 = pseg1;
207 do {
208 pseg2 = pseg2->next;
209 if (pseg2 == NULL)
210 return prt_none;
211 x2 = pseg2->pt.x;
212 y2 = pseg2->pt.y;
213 if (pseg2->type == s_curve) {
214 if (gx_curve_is_really_point(x1, y1, pseg2))
215 continue; /* Ignore this one and try again */
216 if (gx_curve_is_really_line(x1, y1, pseg2))
217 break; /* That'll do! */
218 return prt_none;
219 } else if (pseg2->type != s_line && pseg2->type != s_gap)
220 return prt_none;
221 } while (x2 == x1 && y2 == y1);
222
223 pseg3 = pseg2;
224 do {
225 pseg3 = pseg3->next;
226 if (pseg3 == NULL)
227 return prt_none;
228 x3 = pseg3->pt.x;
229 y3 = pseg3->pt.y;
230 if (pseg3->type == s_curve) {
231 if (gx_curve_is_really_point(x2, y2, pseg3))
232 continue; /* Ignore this one and try again */
233 if (gx_curve_is_really_line(x2, y2, pseg3))
234 break; /* That'll do! */
235 return prt_none;
236 } else if (pseg3->type != s_line && pseg3->type != s_gap)
237 return prt_none;
238 } while (x3 == x2 && y3 == y2);
239
240 pseg4 = pseg3;
241 do {
242 pseg4 = pseg4->next;
243 if (pseg4 == NULL || pseg4->type == s_start) {
244 type = prt_open; /* M, L, L, L */
245 goto type_known;
246 }
247 if (pseg4->type == s_curve) {
248 if (gx_curve_is_really_point(x3, y3, pseg4))
249 continue; /* Ignore this one and try again */
250 if (gx_curve_is_really_line(x3, y3, pseg4))
251 break; /* That'll do! */
252 return prt_none;
253 } else if (pseg4->type == s_line_close) {
254 type = prt_closed; /* M, L, L, L, C */
255 goto type_known;
256 }
257 } while (pseg4->pt.x == x3 && pseg4->pt.y == y3);
258
259 if (pseg4->pt.x != pseg0->pt.x || pseg4->pt.y != pseg0->pt.y)
260 return prt_none;
261 else if (pseg4->next == NULL || pseg4->next->type == s_start)
262 type = prt_fake_closed; /* Mo, L, L, L, L, Mo */
263 else
264 return prt_none;
265
266 type_known:
267
268 if ((x0 == x1 && y1 == y2 && x2 == x3 && y3 == y0) ||
269 (x0 == x3 && y3 == y2 && x2 == x1 && y1 == y0)) {
270 /* Path is a rectangle. Return the bounding box. */
271 if (x0 < x2)
272 pbox->p.x = x0, pbox->q.x = x2;
273 else
274 pbox->p.x = x2, pbox->q.x = x0;
275 if (y0 < y2)
276 pbox->p.y = y0, pbox->q.y = y2;
277 else
278 pbox->p.y = y2, pbox->q.y = y0;
279 while (pseg4 != 0 && pseg4->type != s_start)
280 pseg4 = pseg4->next;
281 *ppnext = (const subpath *)pseg4;
282 return type;
283 }
284 return prt_none;
285 }
286 /* Test if an entire path to be filled is a rectangle. */
287 gx_path_rectangular_type
gx_path_is_rectangular(const gx_path * ppath,gs_fixed_rect * pbox)288 gx_path_is_rectangular(const gx_path * ppath, gs_fixed_rect * pbox)
289 {
290 const subpath *pnext;
291
292 return
293 (gx_path_subpath_count(ppath) == 1 ?
294 gx_subpath_is_rectangular(ppath->first_subpath, pbox, &pnext) :
295 prt_none);
296 }
297
298 /* Translate an already-constructed path (in device space). */
299 /* Don't bother to update the cbox. */
300 int
gx_path_translate(gx_path * ppath,fixed dx,fixed dy)301 gx_path_translate(gx_path * ppath, fixed dx, fixed dy)
302 {
303 segment *pseg;
304
305 #define update_xy(pt)\
306 pt.x += dx, pt.y += dy
307 if (ppath->box_last != 0) {
308 update_xy(ppath->bbox.p);
309 update_xy(ppath->bbox.q);
310 }
311 if (path_position_valid(ppath))
312 update_xy(ppath->position);
313 for (pseg = (segment *) (ppath->first_subpath); pseg != 0;
314 pseg = pseg->next
315 )
316 switch (pseg->type) {
317 case s_curve:
318 #define pcseg ((curve_segment *)pseg)
319 update_xy(pcseg->p1);
320 update_xy(pcseg->p2);
321 #undef pcseg
322 /* fall through */
323 default:
324 update_xy(pseg->pt);
325 }
326 #undef update_xy
327 return 0;
328 }
329
330 /* Scale an existing path by a power of 2 (positive or negative).
331 * Currently the path drawing routines can't handle values
332 * close to the edge of the representable space.
333 * Also see clamp_point() in gspath.c .
334 */
335 void
gx_point_scale_exp2(gs_fixed_point * pt,int sx,int sy)336 gx_point_scale_exp2(gs_fixed_point * pt, int sx, int sy)
337 {
338 int v;
339
340 if (sx > 0) {
341 v = (max_int - int2fixed(1000)) >> sx; /* arbitrary */
342 if (pt->x > v)
343 pt->x = v;
344 else if (pt->x < -v)
345 pt->x = -v;
346 pt->x <<= sx;
347 } else
348 pt->x >>= -sx;
349
350 if (sy > 0) {
351 v = (max_int - int2fixed(1000)) >> sy;
352 if (pt->y > v)
353 pt->y = v;
354 else if (pt->y < -v)
355 pt->y = -v;
356 pt->y <<= sy;
357 } else
358 pt->y >>= -sy;
359 }
360 void
gx_rect_scale_exp2(gs_fixed_rect * pr,int sx,int sy)361 gx_rect_scale_exp2(gs_fixed_rect * pr, int sx, int sy)
362 {
363 gx_point_scale_exp2(&pr->p, sx, sy);
364 gx_point_scale_exp2(&pr->q, sx, sy);
365 }
366 int
gx_path_scale_exp2_shared(gx_path * ppath,int log2_scale_x,int log2_scale_y,bool segments_shared)367 gx_path_scale_exp2_shared(gx_path * ppath, int log2_scale_x, int log2_scale_y,
368 bool segments_shared)
369 {
370 segment *pseg;
371
372 gx_rect_scale_exp2(&ppath->bbox, log2_scale_x, log2_scale_y);
373 #define SCALE_XY(pt) gx_point_scale_exp2(&pt, log2_scale_x, log2_scale_y)
374 SCALE_XY(ppath->position);
375 if (!segments_shared) {
376 for (pseg = (segment *) (ppath->first_subpath); pseg != 0;
377 pseg = pseg->next
378 )
379 switch (pseg->type) {
380 case s_curve:
381 SCALE_XY(((curve_segment *)pseg)->p1);
382 SCALE_XY(((curve_segment *)pseg)->p2);
383 /* fall through */
384 default:
385 SCALE_XY(pseg->pt);
386 }
387 }
388 #undef SCALE_XY
389 return 0;
390 }
391
392 /*
393 * Reverse a path. We know ppath != ppath_old.
394 * NOTE: in releases 5.01 and earlier, the implicit line added by closepath
395 * became the first segment of the reversed path. Starting in release
396 * 5.02, the code follows the Adobe implementation (and LanguageLevel 3
397 * specification), in which this line becomes the *last* segment of the
398 * reversed path. This can produce some quite unintuitive results.
399 *
400 * The order of the subpaths is unspecified in the PLRM, but the CPSI
401 * reverses the subpaths, and the CET (11-05 p6, test 3) tests for it.
402 */
403 int
gx_path_copy_reversed(const gx_path * ppath_old,gx_path * ppath)404 gx_path_copy_reversed(const gx_path * ppath_old, gx_path * ppath)
405 {
406 const subpath *psub = ppath_old->current_subpath;
407
408 #ifdef DEBUG
409 if (gs_debug_c('P'))
410 gx_dump_path(ppath_old, "before reversepath");
411 #endif
412 nsp:
413 if (psub) {
414 const segment *prev = psub->last;
415 const segment *pseg;
416 segment_notes notes =
417 (prev == (const segment *)psub ? sn_none :
418 psub->next->notes);
419 segment_notes prev_notes;
420 int code;
421
422 if (!psub->is_closed) {
423 code = gx_path_add_point(ppath, prev->pt.x, prev->pt.y);
424 if (code < 0)
425 return code;
426 }
427 /*
428 * The do ... while structure of this loop is artificial,
429 * designed solely to keep compilers from complaining about
430 * 'statement not reached' or 'end-of-loop code not reached'.
431 * The normal exit from this loop is the goto statement in
432 * the s_start arm of the switch.
433 */
434 do {
435 pseg = prev;
436 prev_notes = notes;
437 prev = pseg->prev;
438 notes = pseg->notes;
439 prev_notes = (prev_notes & sn_not_first) |
440 (notes & ~sn_not_first);
441 switch (pseg->type) {
442 case s_start:
443 /* Finished subpath */
444 if (psub->is_closed) {
445 code =
446 gx_path_close_subpath_notes(ppath, prev_notes);
447 if (code < 0)
448 return code;
449 }
450 do {
451 psub = (const subpath *)psub->prev;
452 } while (psub && psub->type != s_start);
453 goto nsp;
454 case s_curve:
455 {
456 const curve_segment *pc =
457 (const curve_segment *)pseg;
458
459 code = gx_path_add_curve_notes(ppath,
460 pc->p2.x, pc->p2.y,
461 pc->p1.x, pc->p1.y,
462 prev->pt.x, prev->pt.y, prev_notes);
463 break;
464 }
465 case s_line:
466 code = gx_path_add_line_notes(ppath,
467 prev->pt.x, prev->pt.y, prev_notes);
468 break;
469 case s_gap:
470 code = gx_path_add_gap_notes(ppath,
471 prev->pt.x, prev->pt.y, prev_notes);
472 break;
473 case s_line_close:
474 /* Skip the closing line. */
475 code = gx_path_add_point(ppath, prev->pt.x,
476 prev->pt.y);
477 break;
478 default: /* not possible */
479 return_error(gs_error_Fatal);
480 }
481 } while (code >= 0);
482 return code; /* only reached if code < 0 */
483 }
484 #undef sn_not_end
485 /*
486 * In the Adobe implementations, reversepath discards a trailing
487 * moveto unless the path consists only of a moveto. We reproduce
488 * this behavior here, even though we consider it a bug.
489 */
490 if (ppath_old->first_subpath == 0 &&
491 path_last_is_moveto(ppath_old)
492 ) {
493 int code = gx_path_add_point(ppath, ppath_old->position.x,
494 ppath_old->position.y);
495
496 if (code < 0)
497 return code;
498 }
499 #ifdef DEBUG
500 if (gs_debug_c('P'))
501 gx_dump_path(ppath, "after reversepath");
502 #endif
503 return 0;
504 }
505
506 int
gx_path_append_reversed(const gx_path * ppath_old,gx_path * ppath)507 gx_path_append_reversed(const gx_path * ppath_old, gx_path * ppath)
508 {
509 const subpath *psub = ppath_old->current_subpath;
510
511 #ifdef DEBUG
512 if (gs_debug_c('P'))
513 gx_dump_path(ppath_old, "before reversepath");
514 #endif
515 nsp:
516 if (psub) {
517 const segment *prev = psub->last;
518 const segment *pseg;
519 segment_notes notes =
520 (prev == (const segment *)psub ? sn_none :
521 psub->next->notes);
522 segment_notes prev_notes;
523 int code;
524
525 if (!psub->is_closed) {
526 code = gx_path_add_line(ppath, prev->pt.x, prev->pt.y);
527 if (code < 0)
528 return code;
529 }
530 /*
531 * The do ... while structure of this loop is artificial,
532 * designed solely to keep compilers from complaining about
533 * 'statement not reached' or 'end-of-loop code not reached'.
534 * The normal exit from this loop is the goto statement in
535 * the s_start arm of the switch.
536 */
537 do {
538 pseg = prev;
539 prev_notes = notes;
540 prev = pseg->prev;
541 notes = pseg->notes;
542 prev_notes = (prev_notes & sn_not_first) |
543 (notes & ~sn_not_first);
544 switch (pseg->type) {
545 case s_start:
546 /* Finished subpath */
547 if (psub->is_closed) {
548 code =
549 gx_path_close_subpath_notes(ppath, prev_notes);
550 if (code < 0)
551 return code;
552 }
553 do {
554 psub = (const subpath *)psub->prev;
555 } while (psub && psub->type != s_start);
556 goto nsp;
557 case s_curve:
558 {
559 const curve_segment *pc =
560 (const curve_segment *)pseg;
561
562 code = gx_path_add_curve_notes(ppath,
563 pc->p2.x, pc->p2.y,
564 pc->p1.x, pc->p1.y,
565 prev->pt.x, prev->pt.y, prev_notes);
566 break;
567 }
568 case s_line:
569 code = gx_path_add_line_notes(ppath,
570 prev->pt.x, prev->pt.y, prev_notes);
571 break;
572 case s_gap:
573 code = gx_path_add_gap_notes(ppath,
574 prev->pt.x, prev->pt.y, prev_notes);
575 break;
576 case s_line_close:
577 /* Skip the closing line. */
578 code = gx_path_add_point(ppath, prev->pt.x,
579 prev->pt.y);
580 break;
581 default: /* not possible */
582 return_error(gs_error_Fatal);
583 }
584 } while (code >= 0);
585 return code; /* only reached if code < 0 */
586 }
587 #undef sn_not_end
588 /*
589 * In the Adobe implementations, reversepath discards a trailing
590 * moveto unless the path consists only of a moveto. We reproduce
591 * this behavior here, even though we consider it a bug.
592 */
593 if (ppath_old->first_subpath == 0 &&
594 path_last_is_moveto(ppath_old)
595 ) {
596 int code = gx_path_add_point(ppath, ppath_old->position.x,
597 ppath_old->position.y);
598
599 if (code < 0)
600 return code;
601 }
602 #ifdef DEBUG
603 if (gs_debug_c('P'))
604 gx_dump_path(ppath, "after reversepath");
605 #endif
606 return 0;
607 }
608
609 /* ------ Path enumeration ------ */
610
611 /* Allocate a path enumerator. */
612 gs_path_enum *
gs_path_enum_alloc(gs_memory_t * mem,client_name_t cname)613 gs_path_enum_alloc(gs_memory_t * mem, client_name_t cname)
614 {
615 return gs_alloc_struct(mem, gs_path_enum, &st_path_enum, cname);
616 }
617
618 /* Start enumerating a path. */
619 int
gx_path_enum_init(gs_path_enum * penum,const gx_path * ppath)620 gx_path_enum_init(gs_path_enum * penum, const gx_path * ppath)
621 {
622 penum->memory = 0; /* path not copied */
623 penum->path = ppath;
624 penum->copied_path = 0; /* not copied */
625 penum->pseg = (const segment *)ppath->first_subpath;
626 penum->moveto_done = false;
627 penum->notes = sn_none;
628 return 0;
629 }
630
631 /* Enumerate the next element of a path. */
632 /* If the path is finished, return 0; */
633 /* otherwise, return the element type. */
634 int
gx_path_enum_next(gs_path_enum * penum,gs_fixed_point ppts[3])635 gx_path_enum_next(gs_path_enum * penum, gs_fixed_point ppts[3])
636 {
637 const segment *pseg = penum->pseg;
638
639 if (pseg == 0) { /* We've enumerated all the segments, but there might be */
640 /* a trailing moveto. */
641 const gx_path *ppath = penum->path;
642
643 if (path_last_is_moveto(ppath) && !penum->moveto_done) { /* Handle a trailing moveto */
644 penum->moveto_done = true;
645 penum->notes = sn_none;
646 ppts[0] = ppath->position;
647 return gs_pe_moveto;
648 }
649 return 0;
650 }
651 penum->pseg = pseg->next;
652 penum->notes = pseg->notes;
653 switch (pseg->type) {
654 case s_start:
655 ppts[0] = pseg->pt;
656 return gs_pe_moveto;
657 case s_line:
658 ppts[0] = pseg->pt;
659 return gs_pe_lineto;
660 case s_gap:
661 ppts[0] = pseg->pt;
662 return gs_pe_gapto;
663 case s_line_close:
664 ppts[0] = pseg->pt;
665 return gs_pe_closepath;
666 case s_curve:
667 #define pcseg ((const curve_segment *)pseg)
668 ppts[0] = pcseg->p1;
669 ppts[1] = pcseg->p2;
670 ppts[2] = pseg->pt;
671 return gs_pe_curveto;
672 #undef pcseg
673 default:
674 lprintf1("bad type %x in gx_path_enum_next!\n", pseg->type);
675 return_error(gs_error_Fatal);
676 }
677 }
678
679 /* Return the notes from the last-enumerated segment. */
680 segment_notes
gx_path_enum_notes(const gs_path_enum * penum)681 gx_path_enum_notes(const gs_path_enum * penum)
682 {
683 return penum->notes;
684 }
685
686 /* Back up 1 element in the path being enumerated. */
687 /* Return true if successful, false if we are at the beginning of the path. */
688 /* This implementation allows backing up multiple times, */
689 /* but no client currently relies on this. */
690 bool
gx_path_enum_backup(gs_path_enum * penum)691 gx_path_enum_backup(gs_path_enum * penum)
692 {
693 const segment *pseg = penum->pseg;
694
695 if (pseg != 0) {
696 if ((pseg = pseg->prev) == 0)
697 return false;
698 penum->pseg = pseg;
699 return true;
700 }
701 /* We're at the end of the path. Check to see whether */
702 /* we need to back up over a trailing moveto. */
703 {
704 const gx_path *ppath = penum->path;
705
706 if (path_last_is_moveto(ppath) && penum->moveto_done) { /* Back up over the trailing moveto. */
707 penum->moveto_done = false;
708 return true;
709 } {
710 const subpath *psub = ppath->current_subpath;
711
712 if (psub == 0) /* empty path */
713 return false;
714 /* Back up to the last segment of the last subpath. */
715 penum->pseg = psub->last;
716 return true;
717 }
718 }
719 }
720