xref: /plan9/sys/src/cmd/gs/src/zupath.c (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 1990, 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: zupath.c,v 1.10 2004/08/04 19:36:13 stefan Exp $ */
18 /* Operators related to user paths */
19 #include "ghost.h"
20 #include "oper.h"
21 #include "oparc.h"
22 #include "idict.h"
23 #include "dstack.h"
24 #include "igstate.h"
25 #include "iname.h"
26 #include "iutil.h"
27 #include "store.h"
28 #include "stream.h"
29 #include "ibnum.h"
30 #include "gsmatrix.h"
31 #include "gsstate.h"
32 #include "gscoord.h"
33 #include "gspaint.h"
34 #include "gxfixed.h"
35 #include "gxdevice.h"
36 #include "gspath.h"
37 #include "gzpath.h"		/* for saving path */
38 #include "gzstate.h"		/* for accessing path */
39 
40 /* Imported data */
41 extern const gx_device gs_hit_device;
42 extern const int gs_hit_detected;
43 
44 /* Forward references */
45 private int upath_append(os_ptr, i_ctx_t *);
46 private int upath_stroke(i_ctx_t *, gs_matrix *);
47 
48 /* ---------------- Insideness testing ---------------- */
49 
50 /* Forward references */
51 private int in_test(i_ctx_t *, int (*)(gs_state *));
52 private int in_path(os_ptr, i_ctx_t *, gx_device *);
53 private int in_path_result(i_ctx_t *, int, int);
54 private int in_utest(i_ctx_t *, int (*)(gs_state *));
55 private int in_upath(i_ctx_t *, gx_device *);
56 private int in_upath_result(i_ctx_t *, int, int);
57 
58 /* <x> <y> ineofill <bool> */
59 /* <userpath> ineofill <bool> */
60 private int
zineofill(i_ctx_t * i_ctx_p)61 zineofill(i_ctx_t *i_ctx_p)
62 {
63     return in_test(i_ctx_p, gs_eofill);
64 }
65 
66 /* <x> <y> infill <bool> */
67 /* <userpath> infill <bool> */
68 private int
zinfill(i_ctx_t * i_ctx_p)69 zinfill(i_ctx_t *i_ctx_p)
70 {
71     return in_test(i_ctx_p, gs_fill);
72 }
73 
74 /* <x> <y> instroke <bool> */
75 /* <userpath> instroke <bool> */
76 private int
zinstroke(i_ctx_t * i_ctx_p)77 zinstroke(i_ctx_t *i_ctx_p)
78 {
79     return in_test(i_ctx_p, gs_stroke);
80 }
81 
82 /* <x> <y> <userpath> inueofill <bool> */
83 /* <userpath1> <userpath2> inueofill <bool> */
84 private int
zinueofill(i_ctx_t * i_ctx_p)85 zinueofill(i_ctx_t *i_ctx_p)
86 {
87     return in_utest(i_ctx_p, gs_eofill);
88 }
89 
90 /* <x> <y> <userpath> inufill <bool> */
91 /* <userpath1> <userpath2> inufill <bool> */
92 private int
zinufill(i_ctx_t * i_ctx_p)93 zinufill(i_ctx_t *i_ctx_p)
94 {
95     return in_utest(i_ctx_p, gs_fill);
96 }
97 
98 /* <x> <y> <userpath> inustroke <bool> */
99 /* <x> <y> <userpath> <matrix> inustroke <bool> */
100 /* <userpath1> <userpath2> inustroke <bool> */
101 /* <userpath1> <userpath2> <matrix> inustroke <bool> */
102 private int
zinustroke(i_ctx_t * i_ctx_p)103 zinustroke(i_ctx_t *i_ctx_p)
104 {	/* This is different because of the optional matrix operand. */
105     os_ptr op = osp;
106     int code = gs_gsave(igs);
107     int spop, npop;
108     gs_matrix mat;
109     gx_device hdev;
110 
111     if (code < 0)
112 	return code;
113     if ((spop = upath_stroke(i_ctx_p, &mat)) < 0) {
114 	gs_grestore(igs);
115 	return spop;
116     }
117     if ((npop = in_path(op - spop, i_ctx_p, &hdev)) < 0) {
118 	gs_grestore(igs);
119 	return npop;
120     }
121     if (npop > 1)		/* matrix was supplied */
122 	code = gs_concat(igs, &mat);
123     if (code >= 0)
124 	code = gs_stroke(igs);
125     return in_upath_result(i_ctx_p, npop + spop, code);
126 }
127 
128 /* ------ Internal routines ------ */
129 
130 /* Do the work of the non-user-path insideness operators. */
131 private int
in_test(i_ctx_t * i_ctx_p,int (* paintproc)(gs_state *))132 in_test(i_ctx_t *i_ctx_p, int (*paintproc)(gs_state *))
133 {
134     os_ptr op = osp;
135     gx_device hdev;
136     int npop = in_path(op, i_ctx_p, &hdev);
137     int code;
138 
139     if (npop < 0)
140 	return npop;
141     code = (*paintproc)(igs);
142     return in_path_result(i_ctx_p, npop, code);
143 }
144 
145 /* Set up a clipping path and device for insideness testing. */
146 private int
in_path(os_ptr oppath,i_ctx_t * i_ctx_p,gx_device * phdev)147 in_path(os_ptr oppath, i_ctx_t *i_ctx_p, gx_device * phdev)
148 {
149     int code = gs_gsave(igs);
150     int npop;
151     double uxy[2];
152 
153     if (code < 0)
154 	return code;
155     code = num_params(oppath, 2, uxy);
156     if (code >= 0) {		/* Aperture is a single pixel. */
157 	gs_point dxy;
158 	gs_fixed_rect fr;
159 
160 	gs_transform(igs, uxy[0], uxy[1], &dxy);
161 	fr.p.x = fixed_floor(float2fixed(dxy.x));
162 	fr.p.y = fixed_floor(float2fixed(dxy.y));
163 	fr.q.x = fr.p.x + fixed_1;
164 	fr.q.y = fr.p.y + fixed_1;
165 	code = gx_clip_to_rectangle(igs, &fr);
166 	npop = 2;
167     } else {			/* Aperture is a user path. */
168 	/* We have to set the clipping path without disturbing */
169 	/* the current path. */
170 	gx_path *ipath = igs->path;
171 	gx_path save;
172 
173 	gx_path_init_local(&save, imemory);
174 	gx_path_assign_preserve(&save, ipath);
175 	gs_newpath(igs);
176 	code = upath_append(oppath, i_ctx_p);
177 	if (code >= 0)
178 	    code = gx_clip_to_path(igs);
179 	gx_path_assign_free(igs->path, &save);
180 	npop = 1;
181     }
182     if (code < 0) {
183 	gs_grestore(igs);
184 	return code;
185     }
186     /* Install the hit detection device. */
187     gx_set_device_color_1(igs);
188     gx_device_init((gx_device *) phdev, (const gx_device *)&gs_hit_device,
189 		   NULL, true);
190     phdev->width = phdev->height = max_int;
191     gx_device_fill_in_procs(phdev);
192     gx_set_device_only(igs, phdev);
193     return npop;
194 }
195 
196 /* Finish an insideness test. */
197 private int
in_path_result(i_ctx_t * i_ctx_p,int npop,int code)198 in_path_result(i_ctx_t *i_ctx_p, int npop, int code)
199 {
200     os_ptr op = osp;
201     bool result;
202 
203     gs_grestore(igs);		/* matches gsave in in_path */
204     if (code == gs_hit_detected)
205 	result = true;
206     else if (code == 0)		/* completed painting without a hit */
207 	result = false;
208     else			/* error */
209 	return code;
210     npop--;
211     pop(npop);
212     op -= npop;
213     make_bool(op, result);
214     return 0;
215 
216 }
217 
218 /* Do the work of the user-path insideness operators. */
219 private int
in_utest(i_ctx_t * i_ctx_p,int (* paintproc)(gs_state *))220 in_utest(i_ctx_t *i_ctx_p, int (*paintproc)(gs_state *))
221 {
222     gx_device hdev;
223     int npop = in_upath(i_ctx_p, &hdev);
224     int code;
225 
226     if (npop < 0)
227 	return npop;
228     code = (*paintproc)(igs);
229     return in_upath_result(i_ctx_p, npop, code);
230 }
231 
232 /* Set up a clipping path and device for insideness testing */
233 /* with a user path. */
234 private int
in_upath(i_ctx_t * i_ctx_p,gx_device * phdev)235 in_upath(i_ctx_t *i_ctx_p, gx_device * phdev)
236 {
237     os_ptr op = osp;
238     int code = gs_gsave(igs);
239     int npop;
240 
241     if (code < 0)
242 	return code;
243     if ((code = upath_append(op, i_ctx_p)) < 0 ||
244 	(npop = in_path(op - 1, i_ctx_p, phdev)) < 0
245 	) {
246 	gs_grestore(igs);
247 	return code;
248     }
249     return npop + 1;
250 }
251 
252 /* Finish an insideness test with a user path. */
253 private int
in_upath_result(i_ctx_t * i_ctx_p,int npop,int code)254 in_upath_result(i_ctx_t *i_ctx_p, int npop, int code)
255 {
256     gs_grestore(igs);		/* matches gsave in in_upath */
257     return in_path_result(i_ctx_p, npop, code);
258 }
259 
260 /* ---------------- User paths ---------------- */
261 
262 /* User path operator codes */
263 typedef enum {
264     upath_op_setbbox = 0,
265     upath_op_moveto = 1,
266     upath_op_rmoveto = 2,
267     upath_op_lineto = 3,
268     upath_op_rlineto = 4,
269     upath_op_curveto = 5,
270     upath_op_rcurveto = 6,
271     upath_op_arc = 7,
272     upath_op_arcn = 8,
273     upath_op_arct = 9,
274     upath_op_closepath = 10,
275     upath_op_ucache = 11
276 } upath_op;
277 
278 #define UPATH_MAX_OP 11
279 #define UPATH_REPEAT 32
280 static const byte up_nargs[UPATH_MAX_OP + 1] = {
281     4, 2, 2, 2, 2, 6, 6, 5, 5, 5, 0, 0
282 };
283 
284 /* Declare operator procedures not declared in opextern.h. */
285 int zsetbbox(i_ctx_t *);
286 private int zucache(i_ctx_t *);
287 
288 #undef zp
289 static const op_proc_t up_ops[UPATH_MAX_OP + 1] = {
290     zsetbbox, zmoveto, zrmoveto, zlineto, zrlineto,
291     zcurveto, zrcurveto, zarc, zarcn, zarct,
292     zclosepath, zucache
293 };
294 
295 /* - ucache - */
296 private int
zucache(i_ctx_t * i_ctx_p)297 zucache(i_ctx_t *i_ctx_p)
298 {
299     /* A no-op for now. */
300     return 0;
301 }
302 
303 /* <userpath> uappend - */
304 private int
zuappend(i_ctx_t * i_ctx_p)305 zuappend(i_ctx_t *i_ctx_p)
306 {
307     os_ptr op = osp;
308     int code = gs_gsave(igs);
309 
310     if (code < 0)
311 	return code;
312     if ((code = upath_append(op, i_ctx_p)) >= 0)
313 	code = gs_upmergepath(igs);
314     gs_grestore(igs);
315     if (code < 0)
316 	return code;
317     pop(1);
318     return 0;
319 }
320 
321 /* <userpath> ueofill - */
322 private int
zueofill(i_ctx_t * i_ctx_p)323 zueofill(i_ctx_t *i_ctx_p)
324 {
325     os_ptr op = osp;
326     int code = gs_gsave(igs);
327 
328     if (code < 0)
329 	return code;
330     if ((code = upath_append(op, i_ctx_p)) >= 0)
331 	code = gs_eofill(igs);
332     gs_grestore(igs);
333     if (code < 0)
334 	return code;
335     pop(1);
336     return 0;
337 }
338 
339 /* <userpath> ufill - */
340 private int
zufill(i_ctx_t * i_ctx_p)341 zufill(i_ctx_t *i_ctx_p)
342 {
343     os_ptr op = osp;
344     int code = gs_gsave(igs);
345 
346     if (code < 0)
347 	return code;
348     if ((code = upath_append(op, i_ctx_p)) >= 0)
349 	code = gs_fill(igs);
350     gs_grestore(igs);
351     if (code < 0)
352 	return code;
353     pop(1);
354     return 0;
355 }
356 
357 /* <userpath> ustroke - */
358 /* <userpath> <matrix> ustroke - */
359 private int
zustroke(i_ctx_t * i_ctx_p)360 zustroke(i_ctx_t *i_ctx_p)
361 {
362     int code = gs_gsave(igs);
363     int npop;
364 
365     if (code < 0)
366 	return code;
367     if ((code = npop = upath_stroke(i_ctx_p, NULL)) >= 0)
368 	code = gs_stroke(igs);
369     gs_grestore(igs);
370     if (code < 0)
371 	return code;
372     pop(npop);
373     return 0;
374 }
375 
376 /* <userpath> ustrokepath - */
377 /* <userpath> <matrix> ustrokepath - */
378 private int
zustrokepath(i_ctx_t * i_ctx_p)379 zustrokepath(i_ctx_t *i_ctx_p)
380 {
381     gx_path save;
382     int code, npop;
383 
384     /* Save and reset the path. */
385     gx_path_init_local(&save, imemory);
386     gx_path_assign_preserve(&save, igs->path);
387     if ((code = npop = upath_stroke(i_ctx_p, NULL)) < 0 ||
388 	(code = gs_strokepath(igs)) < 0
389 	) {
390 	gx_path_assign_free(igs->path, &save);
391 	return code;
392     }
393     gx_path_free(&save, "ustrokepath");
394     pop(npop);
395     return 0;
396 }
397 
398 /* <with_ucache> upath <userpath> */
399 /* We do all the work in a procedure that is also used to construct */
400 /* the UnpaintedPath user path for ImageType 2 images. */
401 int make_upath(i_ctx_t *i_ctx_p, ref *rupath, gs_state *pgs, gx_path *ppath,
402 	       bool with_ucache);
403 private int
zupath(i_ctx_t * i_ctx_p)404 zupath(i_ctx_t *i_ctx_p)
405 {
406     os_ptr op = osp;
407 
408     check_type(*op, t_boolean);
409     return make_upath(i_ctx_p, op, igs, igs->path, op->value.boolval);
410 }
411 int
make_upath(i_ctx_t * i_ctx_p,ref * rupath,gs_state * pgs,gx_path * ppath,bool with_ucache)412 make_upath(i_ctx_t *i_ctx_p, ref *rupath, gs_state *pgs, gx_path *ppath,
413 	   bool with_ucache)
414 {
415     int size = (with_ucache ? 6 : 5);
416     gs_path_enum penum;
417     int op;
418     ref *next;
419     int code;
420 
421     /* Compute the size of the user path array. */
422     {
423 	gs_fixed_point pts[3];
424 
425 	gx_path_enum_init(&penum, ppath);
426 	while ((op = gx_path_enum_next(&penum, pts)) != 0) {
427 	    switch (op) {
428 		case gs_pe_moveto:
429 		case gs_pe_lineto:
430 		    size += 3;
431 		    continue;
432 		case gs_pe_curveto:
433 		    size += 7;
434 		    continue;
435 		case gs_pe_closepath:
436 		    size += 1;
437 		    continue;
438 		default:
439 		    return_error(e_unregistered);
440 	    }
441 	}
442     }
443     code = ialloc_ref_array(rupath, a_all | a_executable, size,
444 			    "make_upath");
445     if (code < 0)
446 	return code;
447     /* Construct the path. */
448     next = rupath->value.refs;
449     if (with_ucache) {
450         if ((code = name_enter_string(pgs->memory, "ucache", next)) < 0)
451 	    return code;
452 	r_set_attrs(next, a_executable | l_new);
453 	++next;
454     } {
455 	gs_rect bbox;
456 
457 	if ((code = gs_upathbbox(pgs, &bbox, true)) < 0) {
458 	    /*
459 	     * Note: Adobe throws 'nocurrentpoint' error, but the PLRM
460 	     * not list this as a possible error from 'upath', so we
461 	     * set a reasonable default bbox instead.
462 	     */
463 	    if (code != e_nocurrentpoint)
464 		return code;
465 	    bbox.p.x = bbox.p.y = bbox.q.x = bbox.q.y = 0;
466 	}
467 	make_real_new(next, bbox.p.x);
468 	make_real_new(next + 1, bbox.p.y);
469 	make_real_new(next + 2, bbox.q.x);
470 	make_real_new(next + 3, bbox.q.y);
471 	next += 4;
472 	if ((code = name_enter_string(pgs->memory, "setbbox", next)) < 0)
473 	    return code;
474 	r_set_attrs(next, a_executable | l_new);
475 	++next;
476     }
477     {
478 	gs_point pts[3];
479 
480 	/* Patch the path in the gstate to set up the enumerator. */
481 	gx_path *save_path = pgs->path;
482 
483 	pgs->path = ppath;
484 	gs_path_enum_copy_init(&penum, pgs, false);
485 	pgs->path = save_path;
486 	while ((op = gs_path_enum_next(&penum, pts)) != 0) {
487 	    const char *opstr;
488 
489 	    switch (op) {
490 		case gs_pe_moveto:
491 		    opstr = "moveto";
492 		    goto ml;
493 		case gs_pe_lineto:
494 		    opstr = "lineto";
495 		  ml:make_real_new(next, pts[0].x);
496 		    make_real_new(next + 1, pts[0].y);
497 		    next += 2;
498 		    break;
499 		case gs_pe_curveto:
500 		    opstr = "curveto";
501 		    make_real_new(next, pts[0].x);
502 		    make_real_new(next + 1, pts[0].y);
503 		    make_real_new(next + 2, pts[1].x);
504 		    make_real_new(next + 3, pts[1].y);
505 		    make_real_new(next + 4, pts[2].x);
506 		    make_real_new(next + 5, pts[2].y);
507 		    next += 6;
508 		    break;
509 		case gs_pe_closepath:
510 		    opstr = "closepath";
511 		    break;
512 		default:
513 		    return_error(e_unregistered);
514 	    }
515 	    if ((code = name_enter_string(pgs->memory, opstr, next)) < 0)
516 		return code;
517 	    r_set_attrs(next, a_executable);
518 	    ++next;
519 	}
520     }
521     return 0;
522 }
523 
524 /* ------ Internal routines ------ */
525 
526 /* Append a user path to the current path. */
527 private inline int
upath_append_aux(os_ptr oppath,i_ctx_t * i_ctx_p)528 upath_append_aux(os_ptr oppath, i_ctx_t *i_ctx_p)
529 {
530     ref opcodes;
531     check_read(*oppath);
532     gs_newpath(igs);
533 /****** ROUND tx AND ty ******/
534     if (!r_is_array(oppath))
535 	return_error(e_typecheck);
536 
537     if ( r_size(oppath) == 2 &&
538 	 array_get(imemory, oppath, 1, &opcodes) >= 0 &&
539          r_has_type(&opcodes, t_string)
540 	) {			/* 1st element is operands, 2nd is operators */
541 	ref operands;
542 	int code, format;
543 	int repcount = 1;
544 	const byte *opp;
545 	uint ocount, i = 0;
546 
547         array_get(imemory, oppath, 0, &operands);
548         code = num_array_format(&operands);
549 	if (code < 0)
550 	    return code;
551 	format = code;
552 	opp = opcodes.value.bytes;
553 	ocount = r_size(&opcodes);
554 	while (ocount--) {
555 	    byte opx = *opp++;
556 
557 	    if (opx > UPATH_REPEAT)
558 		repcount = opx - UPATH_REPEAT;
559 	    else if (opx > UPATH_MAX_OP)
560 		return_error(e_rangecheck);
561 	    else {		/* operator */
562 		do {
563 		    os_ptr op = osp;
564 		    byte opargs = up_nargs[opx];
565 
566 		    while (opargs--) {
567 			push(1);
568 			code = num_array_get(imemory, &operands, format, i++, op);
569 			switch (code) {
570 			    case t_integer:
571 				r_set_type_attrs(op, t_integer, 0);
572 				break;
573 			    case t_real:
574 				r_set_type_attrs(op, t_real, 0);
575 				break;
576 			    default:
577 				return_error(e_typecheck);
578 			}
579 		    }
580 		    code = (*up_ops[opx])(i_ctx_p);
581 		    if (code < 0)
582 			return code;
583 		}
584 		while (--repcount);
585 		repcount = 1;
586 	    }
587 	}
588     } else {	/* Ordinary executable array. */
589 	const ref *arp = oppath;
590 	uint ocount = r_size(oppath);
591 	long index = 0;
592 	int argcount = 0;
593 	op_proc_t oproc;
594 	int opx, code;
595 
596 	for (; index < ocount; index++) {
597 	    ref rup;
598 	    ref *defp;
599 	    os_ptr op = osp;
600 
601 	    array_get(imemory, arp, index, &rup);
602 	    switch (r_type(&rup)) {
603 		case t_integer:
604 		case t_real:
605 		    argcount++;
606 		    push(1);
607 		    *op = rup;
608 		    break;
609 		case t_name:
610 		    if (!r_has_attr(&rup, a_executable))
611 			return_error(e_typecheck);
612 		    if (dict_find(systemdict, &rup, &defp) <= 0)
613 			return_error(e_undefined);
614 		    if (r_btype(defp) != t_operator)
615 			return_error(e_typecheck);
616 		    goto xop;
617 		case t_operator:
618 		    defp = &rup;
619 		  xop:if (!r_has_attr(defp, a_executable))
620 			return_error(e_typecheck);
621 		    oproc = real_opproc(defp);
622 		    for (opx = 0; opx <= UPATH_MAX_OP; opx++)
623 			if (oproc == up_ops[opx])
624 			    break;
625 		    if (opx > UPATH_MAX_OP || argcount != up_nargs[opx])
626 			return_error(e_typecheck);
627 		    code = (*oproc)(i_ctx_p);
628 		    if (code < 0)
629 			return code;
630 		    argcount = 0;
631 		    break;
632 		default:
633 		    return_error(e_typecheck);
634 	    }
635 	}
636 	if (argcount)
637 	    return_error(e_typecheck);	/* leftover args */
638     }
639     return 0;
640 }
641 private int
upath_append(os_ptr oppath,i_ctx_t * i_ctx_p)642 upath_append(os_ptr oppath, i_ctx_t *i_ctx_p)
643 {
644     int code = upath_append_aux(oppath, i_ctx_p);
645 
646     if (code < 0)
647 	return code;
648     igs->current_point.x = fixed2float(igs->path->position.x);
649     igs->current_point.y = fixed2float(igs->path->position.y);
650     return 0;
651 }
652 
653 /* Append a user path to the current path, and then apply or return */
654 /* a transformation if one is supplied. */
655 private int
upath_stroke(i_ctx_t * i_ctx_p,gs_matrix * pmat)656 upath_stroke(i_ctx_t *i_ctx_p, gs_matrix *pmat)
657 {
658     os_ptr op = osp;
659     int code, npop;
660     gs_matrix mat;
661 
662     if ((code = read_matrix(imemory, op, &mat)) >= 0) {
663 	if ((code = upath_append(op - 1, i_ctx_p)) >= 0) {
664 	    if (pmat)
665 		*pmat = mat;
666 	    else
667 		code = gs_concat(igs, &mat);
668 	}
669 	npop = 2;
670     } else {
671 	if ((code = upath_append(op, i_ctx_p)) >= 0)
672 	    if (pmat)
673 		gs_make_identity(pmat);
674 	npop = 1;
675     }
676     return (code < 0 ? code : npop);
677 }
678 
679 /* ---------------- Initialization procedure ---------------- */
680 
681 const op_def zupath_l2_op_defs[] =
682 {
683     op_def_begin_level2(),
684 		/* Insideness testing */
685     {"1ineofill", zineofill},
686     {"1infill", zinfill},
687     {"1instroke", zinstroke},
688     {"2inueofill", zinueofill},
689     {"2inufill", zinufill},
690     {"2inustroke", zinustroke},
691 		/* User paths */
692     {"1uappend", zuappend},
693     {"0ucache", zucache},
694     {"1ueofill", zueofill},
695     {"1ufill", zufill},
696     {"1upath", zupath},
697     {"1ustroke", zustroke},
698     {"1ustrokepath", zustrokepath},
699     op_def_end(0)
700 };
701