1 /* Copyright (C) 1997, 2000 artofcode LLC. All rights reserved.
2
3 This program is free software; you can redistribute it and/or modify it
4 under the terms of the GNU General Public License as published by the
5 Free Software Foundation; either version 2 of the License, or (at your
6 option) any later version.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program; if not, write to the Free Software Foundation, Inc.,
15 59 Temple Place, Suite 330, Boston, MA, 02111-1307.
16
17 */
18
19 /*$Id: gdevvec.c,v 1.12.2.2.2.1 2003/01/17 00:49:01 giles Exp $ */
20 /* Utilities for "vector" devices */
21 #include "math_.h"
22 #include "memory_.h"
23 #include "string_.h"
24 #include "gx.h"
25 #include "gp.h"
26 #include "gserrors.h"
27 #include "gsparam.h"
28 #include "gsutil.h"
29 #include "gxfixed.h"
30 #include "gdevvec.h"
31 #include "gscspace.h"
32 #include "gxdcolor.h"
33 #include "gxpaint.h" /* requires gx_path, ... */
34 #include "gzpath.h"
35 #include "gzcpath.h"
36
37 /* Structure descriptors */
38 public_st_device_vector();
39 public_st_vector_image_enum();
40
41 /* ================ Default implementations of vector procs ================ */
42
43 int
gdev_vector_setflat(gx_device_vector * vdev,floatp flatness)44 gdev_vector_setflat(gx_device_vector * vdev, floatp flatness)
45 {
46 return 0;
47 }
48
49 /* Put a path on the output file. */
50 private bool
coord_between(fixed start,fixed mid,fixed end)51 coord_between(fixed start, fixed mid, fixed end)
52 {
53 return (start <= end ? start <= mid && mid <= end :
54 start >= mid && mid >= end);
55 }
56 int
gdev_vector_dopath(gx_device_vector * vdev,const gx_path * ppath,gx_path_type_t type,const gs_matrix * pmat)57 gdev_vector_dopath(gx_device_vector *vdev, const gx_path * ppath,
58 gx_path_type_t type, const gs_matrix *pmat)
59 {
60 bool do_close =
61 (type & (gx_path_type_stroke | gx_path_type_always_close)) != 0;
62 gs_fixed_rect rbox;
63 gx_path_rectangular_type rtype = gx_path_is_rectangular(ppath, &rbox);
64 gs_path_enum cenum;
65 gdev_vector_dopath_state_t state;
66 gs_fixed_point line_start, line_end;
67 bool incomplete_line = false;
68 bool need_moveto = false;
69 int code;
70
71 gdev_vector_dopath_init(&state, vdev, type, pmat);
72 /*
73 * if the path type is stroke, we only recognize closed
74 * rectangles; otherwise, we recognize all rectangles.
75 * Note that for stroking with a transformation, we can't use dorect,
76 * which requires (untransformed) device coordinates.
77 */
78 if (rtype != prt_none &&
79 !((type & gx_path_type_stroke) && rtype == prt_open) &&
80 (pmat == 0 || is_xxyy(pmat) || is_xyyx(pmat)) &&
81 (state.scale_mat.xx == 1.0 && state.scale_mat.yy == 1.0 &&
82 is_xxyy(&state.scale_mat) &&
83 is_fzero2(state.scale_mat.tx, state.scale_mat.ty))
84 ) {
85 gs_point p, q;
86
87 gs_point_transform_inverse((floatp)rbox.p.x, (floatp)rbox.p.y,
88 &state.scale_mat, &p);
89 gs_point_transform_inverse((floatp)rbox.q.x, (floatp)rbox.q.y,
90 &state.scale_mat, &q);
91 code = vdev_proc(vdev, dorect)(vdev, (fixed)p.x, (fixed)p.y,
92 (fixed)q.x, (fixed)q.y, type);
93 if (code >= 0)
94 return code;
95 /* If the dorect proc failed, use a general path. */
96 }
97 code = vdev_proc(vdev, beginpath)(vdev, type);
98 if (code < 0)
99 return code;
100 gx_path_enum_init(&cenum, ppath);
101 for (;;) {
102 gs_fixed_point vs[3];
103 int pe_op = gx_path_enum_next(&cenum, vs);
104
105 sw:
106 if (type & gx_path_type_optimize) {
107 opt:
108 if (pe_op == gs_pe_lineto) {
109 if (!incomplete_line) {
110 line_end = vs[0];
111 incomplete_line = true;
112 continue;
113 }
114 /*
115 * Merge collinear horizontal or vertical line segments
116 * going in the same direction.
117 */
118 if (vs[0].x == line_end.x) {
119 if (vs[0].x == line_start.x &&
120 coord_between(line_start.y, line_end.y, vs[0].y)
121 ) {
122 line_end.y = vs[0].y;
123 continue;
124 }
125 } else if (vs[0].y == line_end.y) {
126 if (vs[0].y == line_start.y &&
127 coord_between(line_start.x, line_end.x, vs[0].x)
128 ) {
129 line_end.x = vs[0].x;
130 continue;
131 }
132 }
133 }
134 if (incomplete_line) {
135 if (need_moveto) { /* see gs_pe_moveto case */
136 code = gdev_vector_dopath_segment(&state, gs_pe_moveto,
137 &line_start);
138 if (code < 0)
139 return code;
140 need_moveto = false;
141 }
142 code = gdev_vector_dopath_segment(&state, gs_pe_lineto,
143 &line_end);
144 if (code < 0)
145 return code;
146 line_start = line_end;
147 incomplete_line = false;
148 goto opt;
149 }
150 }
151 switch (pe_op) {
152 case 0: /* done */
153 done:
154 code = vdev_proc(vdev, endpath)(vdev, type);
155 return (code < 0 ? code : 0);
156 case gs_pe_curveto:
157 if (need_moveto) { /* see gs_pe_moveto case */
158 code = gdev_vector_dopath_segment(&state, gs_pe_moveto,
159 &line_start);
160 if (code < 0)
161 return code;
162 need_moveto = false;
163 }
164 line_start = vs[2];
165 goto draw;
166 case gs_pe_moveto:
167 /*
168 * A bug in Acrobat Reader 4 causes it to draw a single pixel
169 * for a fill with an isolated moveto. If we're doing a fill
170 * without a stroke, defer emitting a moveto until we know that
171 * the subpath has more elements.
172 */
173 line_start = vs[0];
174 if (!(type & gx_path_type_stroke) && (type & gx_path_type_fill)) {
175 need_moveto = true;
176 continue;
177 }
178 goto draw;
179 case gs_pe_lineto:
180 if (need_moveto) { /* see gs_pe_moveto case */
181 code = gdev_vector_dopath_segment(&state, gs_pe_moveto,
182 &line_start);
183 if (code < 0)
184 return code;
185 need_moveto = false;
186 }
187 line_start = vs[0];
188 goto draw;
189 case gs_pe_closepath:
190 if (need_moveto) { /* see gs_pe_moveto case */
191 need_moveto = false;
192 continue;
193 }
194 if (!do_close) {
195 pe_op = gx_path_enum_next(&cenum, vs);
196 if (pe_op == 0)
197 goto done;
198 code = gdev_vector_dopath_segment(&state, gs_pe_closepath, vs);
199 if (code < 0)
200 return code;
201 goto sw;
202 }
203 /* falls through */
204 draw:
205 code = gdev_vector_dopath_segment(&state, pe_op, vs);
206 if (code < 0)
207 return code;
208 }
209 incomplete_line = false; /* only needed if optimizing */
210 }
211 }
212
213 int
gdev_vector_dorect(gx_device_vector * vdev,fixed x0,fixed y0,fixed x1,fixed y1,gx_path_type_t type)214 gdev_vector_dorect(gx_device_vector * vdev, fixed x0, fixed y0, fixed x1,
215 fixed y1, gx_path_type_t type)
216 {
217 int code = (*vdev_proc(vdev, beginpath)) (vdev, type);
218
219 if (code < 0)
220 return code;
221 code = gdev_vector_write_rectangle(vdev, x0, y0, x1, y1,
222 (type & gx_path_type_stroke) != 0,
223 gx_rect_x_first);
224 if (code < 0)
225 return code;
226 return (*vdev_proc(vdev, endpath)) (vdev, type);
227 }
228
229 /* ================ Utility procedures ================ */
230
231 /* Recompute the cached color values. */
232 private void
gdev_vector_load_cache(gx_device_vector * vdev)233 gdev_vector_load_cache(gx_device_vector * vdev)
234 {
235 vdev->black = gx_device_black((gx_device *)vdev);
236 vdev->white = gx_device_white((gx_device *)vdev);
237 }
238
239 /* Initialize the state. */
240 void
gdev_vector_init(gx_device_vector * vdev)241 gdev_vector_init(gx_device_vector * vdev)
242 {
243 gdev_vector_reset(vdev);
244 vdev->scale.x = vdev->scale.y = 1.0;
245 vdev->in_page = false;
246 gdev_vector_load_cache(vdev);
247 }
248
249 /* Reset the remembered graphics state. */
250 void
gdev_vector_reset(gx_device_vector * vdev)251 gdev_vector_reset(gx_device_vector * vdev)
252 {
253 static const gs_imager_state state_initial =
254 {gs_imager_state_initial(1)};
255
256 vdev->state = state_initial;
257 color_unset(&vdev->fill_color);
258 color_unset(&vdev->stroke_color);
259 vdev->clip_path_id =
260 vdev->no_clip_path_id = gs_next_ids(1);
261 }
262
263 /* Open the output file and stream. */
264 int
gdev_vector_open_file_options(gx_device_vector * vdev,uint strmbuf_size,int open_options)265 gdev_vector_open_file_options(gx_device_vector * vdev, uint strmbuf_size,
266 int open_options)
267 {
268 bool binary = !(open_options & VECTOR_OPEN_FILE_ASCII);
269 int code = -1; /* (only for testing, never returned) */
270
271 /* Open the file as seekable or sequential, as requested. */
272 if (!(open_options & VECTOR_OPEN_FILE_SEQUENTIAL)) {
273 /* Try to open as seekable. */
274 code =
275 gx_device_open_output_file((gx_device *)vdev, vdev->fname,
276 binary, true, &vdev->file);
277 }
278 if (code < 0 && (open_options & (VECTOR_OPEN_FILE_SEQUENTIAL |
279 VECTOR_OPEN_FILE_SEQUENTIAL_OK))) {
280 /* Try to open as sequential. */
281 code = gx_device_open_output_file((gx_device *)vdev, vdev->fname,
282 binary, false, &vdev->file);
283 }
284 if (code < 0)
285 return code;
286 if ((vdev->strmbuf = gs_alloc_bytes(vdev->v_memory, strmbuf_size,
287 "vector_open(strmbuf)")) == 0 ||
288 (vdev->strm = s_alloc(vdev->v_memory,
289 "vector_open(strm)")) == 0 ||
290 ((open_options & VECTOR_OPEN_FILE_BBOX) &&
291 (vdev->bbox_device =
292 gs_alloc_struct_immovable(vdev->v_memory,
293 gx_device_bbox, &st_device_bbox,
294 "vector_open(bbox_device)")) == 0)
295 ) {
296 if (vdev->bbox_device)
297 gs_free_object(vdev->v_memory, vdev->bbox_device,
298 "vector_open(bbox_device)");
299 vdev->bbox_device = 0;
300 if (vdev->strm)
301 gs_free_object(vdev->v_memory, vdev->strm,
302 "vector_open(strm)");
303 vdev->strm = 0;
304 if (vdev->strmbuf)
305 gs_free_object(vdev->v_memory, vdev->strmbuf,
306 "vector_open(strmbuf)");
307 vdev->strmbuf = 0;
308 fclose(vdev->file);
309 vdev->file = 0;
310 return_error(gs_error_VMerror);
311 }
312 vdev->strmbuf_size = strmbuf_size;
313 swrite_file(vdev->strm, vdev->file, vdev->strmbuf, strmbuf_size);
314 vdev->open_options = open_options;
315 /*
316 * We don't want finalization to close the file, but we do want it
317 * to flush the stream buffer.
318 */
319 vdev->strm->procs.close = vdev->strm->procs.flush;
320 if (vdev->bbox_device) {
321 gx_device_bbox_init(vdev->bbox_device, NULL);
322 gx_device_set_resolution((gx_device *) vdev->bbox_device,
323 vdev->HWResolution[0],
324 vdev->HWResolution[1]);
325 /* Do the right thing about upright vs. inverted. */
326 /* (This is dangerous in general, since the procedure */
327 /* might reference non-standard elements.) */
328 set_dev_proc(vdev->bbox_device, get_initial_matrix,
329 dev_proc(vdev, get_initial_matrix));
330 (*dev_proc(vdev->bbox_device, open_device))
331 ((gx_device *) vdev->bbox_device);
332 }
333 return 0;
334 }
335
336 /* Get the current stream, calling beginpage if in_page is false. */
337 stream *
gdev_vector_stream(gx_device_vector * vdev)338 gdev_vector_stream(gx_device_vector * vdev)
339 {
340 if (!vdev->in_page) {
341 (*vdev_proc(vdev, beginpage)) (vdev);
342 vdev->in_page = true;
343 }
344 return vdev->strm;
345 }
346
347 /* Compare two drawing colors. */
348 /* Right now we don't attempt to handle non-pure colors. */
349 private bool
drawing_color_eq(const gx_drawing_color * pdc1,const gx_drawing_color * pdc2)350 drawing_color_eq(const gx_drawing_color * pdc1, const gx_drawing_color * pdc2)
351 {
352 return (gx_dc_is_pure(pdc1) ?
353 gx_dc_is_pure(pdc2) &&
354 gx_dc_pure_color(pdc1) == gx_dc_pure_color(pdc2) :
355 gx_dc_is_null(pdc1) ?
356 gx_dc_is_null(pdc2) :
357 false);
358 }
359
360 /* Update the logical operation. */
361 int
gdev_vector_update_log_op(gx_device_vector * vdev,gs_logical_operation_t lop)362 gdev_vector_update_log_op(gx_device_vector * vdev, gs_logical_operation_t lop)
363 {
364 gs_logical_operation_t diff = lop ^ vdev->state.log_op;
365
366 if (diff != 0) {
367 int code = (*vdev_proc(vdev, setlogop)) (vdev, lop, diff);
368
369 if (code < 0)
370 return code;
371 vdev->state.log_op = lop;
372 }
373 return 0;
374 }
375
376 /* Update the fill color. */
377 int
gdev_vector_update_fill_color(gx_device_vector * vdev,const gx_drawing_color * pdcolor)378 gdev_vector_update_fill_color(gx_device_vector * vdev,
379 const gx_drawing_color * pdcolor)
380 {
381 if (!drawing_color_eq(pdcolor, &vdev->fill_color)) {
382 int code = (*vdev_proc(vdev, setfillcolor)) (vdev, pdcolor);
383
384 if (code < 0)
385 return code;
386 vdev->fill_color = *pdcolor;
387 }
388 return 0;
389 }
390
391 /* Update the state for filling a region. */
392 private int
update_fill(gx_device_vector * vdev,const gx_drawing_color * pdcolor,gs_logical_operation_t lop)393 update_fill(gx_device_vector * vdev, const gx_drawing_color * pdcolor,
394 gs_logical_operation_t lop)
395 {
396 int code = gdev_vector_update_fill_color(vdev, pdcolor);
397
398 if (code < 0)
399 return code;
400 return gdev_vector_update_log_op(vdev, lop);
401 }
402
403 /* Bring state up to date for filling. */
404 int
gdev_vector_prepare_fill(gx_device_vector * vdev,const gs_imager_state * pis,const gx_fill_params * params,const gx_drawing_color * pdcolor)405 gdev_vector_prepare_fill(gx_device_vector * vdev, const gs_imager_state * pis,
406 const gx_fill_params * params, const gx_drawing_color * pdcolor)
407 {
408 if (params->flatness != vdev->state.flatness) {
409 int code = (*vdev_proc(vdev, setflat)) (vdev, params->flatness);
410
411 if (code < 0)
412 return code;
413 vdev->state.flatness = params->flatness;
414 }
415 return update_fill(vdev, pdcolor, pis->log_op);
416 }
417
418 /* Compare two dash patterns. */
419 private bool
dash_pattern_eq(const float * stored,const gx_dash_params * set,floatp scale)420 dash_pattern_eq(const float *stored, const gx_dash_params * set, floatp scale)
421 {
422 int i;
423
424 for (i = 0; i < set->pattern_size; ++i)
425 if (stored[i] != (float)(set->pattern[i] * scale))
426 return false;
427 return true;
428 }
429
430 /* Bring state up to date for stroking. */
431 int
gdev_vector_prepare_stroke(gx_device_vector * vdev,const gs_imager_state * pis,const gx_stroke_params * params,const gx_drawing_color * pdcolor,floatp scale)432 gdev_vector_prepare_stroke(gx_device_vector * vdev,
433 const gs_imager_state * pis, /* may be NULL */
434 const gx_stroke_params * params, /* may be NULL */
435 const gx_drawing_color * pdcolor, /* may be NULL */
436 floatp scale)
437 {
438 if (pis) {
439 int pattern_size = pis->line_params.dash.pattern_size;
440 float dash_offset = pis->line_params.dash.offset * scale;
441 float half_width = pis->line_params.half_width * scale;
442
443 if (pattern_size > max_dash)
444 return_error(gs_error_limitcheck);
445 if (dash_offset != vdev->state.line_params.dash.offset ||
446 pattern_size != vdev->state.line_params.dash.pattern_size ||
447 (pattern_size != 0 &&
448 !dash_pattern_eq(vdev->dash_pattern, &pis->line_params.dash,
449 scale))
450 ) {
451 float pattern[max_dash];
452 int i, code;
453
454 for (i = 0; i < pattern_size; ++i)
455 pattern[i] = pis->line_params.dash.pattern[i] * scale;
456 code = (*vdev_proc(vdev, setdash))
457 (vdev, pattern, pattern_size, dash_offset);
458 if (code < 0)
459 return code;
460 memcpy(vdev->dash_pattern, pattern, pattern_size * sizeof(float));
461
462 vdev->state.line_params.dash.pattern_size = pattern_size;
463 vdev->state.line_params.dash.offset = dash_offset;
464 }
465 if (half_width != vdev->state.line_params.half_width) {
466 int code = (*vdev_proc(vdev, setlinewidth))
467 (vdev, half_width * 2);
468
469 if (code < 0)
470 return code;
471 vdev->state.line_params.half_width = half_width;
472 }
473 if (pis->line_params.miter_limit != vdev->state.line_params.miter_limit) {
474 int code = (*vdev_proc(vdev, setmiterlimit))
475 (vdev, pis->line_params.miter_limit);
476
477 if (code < 0)
478 return code;
479 gx_set_miter_limit(&vdev->state.line_params,
480 pis->line_params.miter_limit);
481 }
482 if (pis->line_params.cap != vdev->state.line_params.cap) {
483 int code = (*vdev_proc(vdev, setlinecap))
484 (vdev, pis->line_params.cap);
485
486 if (code < 0)
487 return code;
488 vdev->state.line_params.cap = pis->line_params.cap;
489 }
490 if (pis->line_params.join != vdev->state.line_params.join) {
491 int code = (*vdev_proc(vdev, setlinejoin))
492 (vdev, pis->line_params.join);
493
494 if (code < 0)
495 return code;
496 vdev->state.line_params.join = pis->line_params.join;
497 } {
498 int code = gdev_vector_update_log_op(vdev, pis->log_op);
499
500 if (code < 0)
501 return code;
502 }
503 }
504 if (params) {
505 if (params->flatness != vdev->state.flatness) {
506 int code = (*vdev_proc(vdev, setflat)) (vdev, params->flatness);
507
508 if (code < 0)
509 return code;
510 vdev->state.flatness = params->flatness;
511 }
512 }
513 if (pdcolor) {
514 if (!drawing_color_eq(pdcolor, &vdev->stroke_color)) {
515 int code = (*vdev_proc(vdev, setstrokecolor)) (vdev, pdcolor);
516
517 if (code < 0)
518 return code;
519 vdev->stroke_color = *pdcolor;
520 }
521 }
522 return 0;
523 }
524
525 /*
526 * Compute the scale for transforming the line width and dash pattern for a
527 * stroke operation, and, if necessary to handle anisotropic scaling, a full
528 * transformation matrix to be inverse-applied to the path elements as well.
529 * Return 0 if only scaling, 1 if a full matrix is needed.
530 */
531 int
gdev_vector_stroke_scaling(const gx_device_vector * vdev,const gs_imager_state * pis,double * pscale,gs_matrix * pmat)532 gdev_vector_stroke_scaling(const gx_device_vector *vdev,
533 const gs_imager_state *pis,
534 double *pscale, gs_matrix *pmat)
535 {
536 bool set_ctm = true;
537 double scale = 1;
538
539 /*
540 * If the CTM is not uniform, stroke width depends on angle.
541 * We'd like to avoid resetting the CTM, so we check for uniform
542 * CTMs explicitly. Note that in PDF, unlike PostScript, it is
543 * the CTM at the time of the stroke operation, not the CTM at
544 * the time the path was constructed, that is used for transforming
545 * the points of the path; so if we have to reset the CTM, we must
546 * do it before constructing the path, and inverse-transform all
547 * the coordinates.
548 */
549 if (is_xxyy(&pis->ctm)) {
550 scale = fabs(pis->ctm.xx);
551 set_ctm = fabs(pis->ctm.yy) != scale;
552 } else if (is_xyyx(&pis->ctm)) {
553 scale = fabs(pis->ctm.xy);
554 set_ctm = fabs(pis->ctm.yx) != scale;
555 } else if ((pis->ctm.xx == pis->ctm.yy && pis->ctm.xy == -pis->ctm.yx) ||
556 (pis->ctm.xx == -pis->ctm.yy && pis->ctm.xy == pis->ctm.yx)
557 ) {
558 scale = hypot(pis->ctm.xx, pis->ctm.xy);
559 set_ctm = false;
560 }
561 if (set_ctm) {
562 /*
563 * Adobe Acrobat Reader has limitations on the maximum user
564 * coordinate value. If we scale the matrix down too far, the
565 * coordinates will get too big: limit the scale factor to prevent
566 * this from happening. (This does no harm for other output
567 * formats.)
568 */
569 double
570 mxx = pis->ctm.xx / vdev->scale.x,
571 mxy = pis->ctm.xy / vdev->scale.y,
572 myx = pis->ctm.yx / vdev->scale.x,
573 myy = pis->ctm.yy / vdev->scale.y;
574
575 scale = 0.5 * (fabs(mxx) + fabs(mxy) + fabs(myx) + fabs(myy));
576 pmat->xx = mxx / scale, pmat->xy = mxy / scale;
577 pmat->yx = myx / scale, pmat->yy = myy / scale;
578 pmat->tx = pmat->ty = 0;
579 }
580 *pscale = scale;
581 return (int)set_ctm;
582 }
583
584 /* Initialize for writing a path using the default implementation. */
585 void
gdev_vector_dopath_init(gdev_vector_dopath_state_t * state,gx_device_vector * vdev,gx_path_type_t type,const gs_matrix * pmat)586 gdev_vector_dopath_init(gdev_vector_dopath_state_t *state,
587 gx_device_vector *vdev, gx_path_type_t type,
588 const gs_matrix *pmat)
589 {
590 state->vdev = vdev;
591 state->type = type;
592 if (pmat) {
593 state->scale_mat = *pmat;
594 /*
595 * The path element writing procedures all divide the coordinates
596 * by the scale, so we must compensate for that here.
597 */
598 gs_matrix_scale(&state->scale_mat, 1.0 / vdev->scale.x,
599 1.0 / vdev->scale.y, &state->scale_mat);
600 } else {
601 gs_make_scaling(vdev->scale.x, vdev->scale.y, &state->scale_mat);
602 }
603 state->first = true;
604 }
605
606 /*
607 * Put a segment of an enumerated path on the output file.
608 * pe_op is assumed to be valid and non-zero.
609 */
610 int
gdev_vector_dopath_segment(gdev_vector_dopath_state_t * state,int pe_op,gs_fixed_point vs[3])611 gdev_vector_dopath_segment(gdev_vector_dopath_state_t *state, int pe_op,
612 gs_fixed_point vs[3])
613 {
614 gx_device_vector *vdev = state->vdev;
615 const gs_matrix *const pmat = &state->scale_mat;
616 gs_point vp[3];
617 int code;
618
619 switch (pe_op) {
620 case gs_pe_moveto:
621 gs_point_transform_inverse(fixed2float(vs[0].x),
622 fixed2float(vs[0].y), pmat, &vp[0]);
623 if (state->first)
624 state->start = vp[0], state->first = false;
625 code = vdev_proc(vdev, moveto)
626 (vdev, state->prev.x, state->prev.y, vp[0].x, vp[0].y,
627 state->type);
628 state->prev = vp[0];
629 break;
630 case gs_pe_lineto:
631 gs_point_transform_inverse(fixed2float(vs[0].x),
632 fixed2float(vs[0].y), pmat, &vp[0]);
633 code = vdev_proc(vdev, lineto)
634 (vdev, state->prev.x, state->prev.y, vp[0].x, vp[0].y,
635 state->type);
636 state->prev = vp[0];
637 break;
638 case gs_pe_curveto:
639 gs_point_transform_inverse(fixed2float(vs[0].x),
640 fixed2float(vs[0].y), pmat, &vp[0]);
641 gs_point_transform_inverse(fixed2float(vs[1].x),
642 fixed2float(vs[1].y), pmat, &vp[1]);
643 gs_point_transform_inverse(fixed2float(vs[2].x),
644 fixed2float(vs[2].y), pmat, &vp[2]);
645 code = vdev_proc(vdev, curveto)
646 (vdev, state->prev.x, state->prev.y, vp[0].x, vp[0].y,
647 vp[1].x, vp[1].y, vp[2].x, vp[2].y, state->type);
648 state->prev = vp[2];
649 break;
650 case gs_pe_closepath:
651 code = vdev_proc(vdev, closepath)
652 (vdev, state->prev.x, state->prev.y, state->start.x,
653 state->start.y, state->type);
654 state->prev = state->start;
655 break;
656 default: /* can't happen */
657 return -1;
658 }
659 return code;
660 }
661
662 /* Write a polygon as part of a path. */
663 /* May call beginpath, moveto, lineto, closepath, endpath. */
664 int
gdev_vector_write_polygon(gx_device_vector * vdev,const gs_fixed_point * points,uint count,bool close,gx_path_type_t type)665 gdev_vector_write_polygon(gx_device_vector * vdev, const gs_fixed_point * points,
666 uint count, bool close, gx_path_type_t type)
667 {
668 int code = 0;
669
670 if (type != gx_path_type_none &&
671 (code = (*vdev_proc(vdev, beginpath)) (vdev, type)) < 0
672 )
673 return code;
674 if (count > 0) {
675 double x = fixed2float(points[0].x) / vdev->scale.x, y = fixed2float(points[0].y) / vdev->scale.y;
676 double x_start = x, y_start = y, x_prev, y_prev;
677 uint i;
678
679 code = (*vdev_proc(vdev, moveto))
680 (vdev, 0.0, 0.0, x, y, type);
681 if (code >= 0)
682 for (i = 1; i < count && code >= 0; ++i) {
683 x_prev = x, y_prev = y;
684 code = (*vdev_proc(vdev, lineto))
685 (vdev, x_prev, y_prev,
686 (x = fixed2float(points[i].x) / vdev->scale.x),
687 (y = fixed2float(points[i].y) / vdev->scale.y),
688 type);
689 }
690 if (code >= 0 && close)
691 code = (*vdev_proc(vdev, closepath))
692 (vdev, x, y, x_start, y_start, type);
693 }
694 return (code >= 0 && type != gx_path_type_none ?
695 (*vdev_proc(vdev, endpath)) (vdev, type) : code);
696 }
697
698 /* Write a rectangle as part of a path. */
699 /* May call moveto, lineto, closepath. */
700 int
gdev_vector_write_rectangle(gx_device_vector * vdev,fixed x0,fixed y0,fixed x1,fixed y1,bool close,gx_rect_direction_t direction)701 gdev_vector_write_rectangle(gx_device_vector * vdev, fixed x0, fixed y0,
702 fixed x1, fixed y1, bool close, gx_rect_direction_t direction)
703 {
704 gs_fixed_point points[4];
705
706 points[0].x = x0, points[0].y = y0;
707 points[2].x = x1, points[2].y = y1;
708 if (direction == gx_rect_x_first)
709 points[1].x = x1, points[1].y = y0,
710 points[3].x = x0, points[3].y = y1;
711 else
712 points[1].x = x0, points[1].y = y1,
713 points[3].x = x1, points[3].y = y0;
714 return gdev_vector_write_polygon(vdev, points, 4, close,
715 gx_path_type_none);
716 }
717
718 /* Write a clipping path by calling the path procedures. */
719 int
gdev_vector_write_clip_path(gx_device_vector * vdev,const gx_clip_path * pcpath)720 gdev_vector_write_clip_path(gx_device_vector * vdev,
721 const gx_clip_path * pcpath)
722 {
723 const gx_clip_rect *prect;
724 gx_clip_rect page_rect;
725 int code;
726
727 if (pcpath == 0) {
728 /* There's no special provision for initclip. */
729 /* Write a rectangle that covers the entire page. */
730 page_rect.xmin = page_rect.ymin = 0;
731 page_rect.xmax = vdev->width;
732 page_rect.ymax = vdev->height;
733 page_rect.next = 0;
734 prect = &page_rect;
735 } else if (pcpath->path_valid) {
736 return (*vdev_proc(vdev, dopath))
737 (vdev, &pcpath->path,
738 (pcpath->rule <= 0 ?
739 gx_path_type_clip | gx_path_type_winding_number :
740 gx_path_type_clip | gx_path_type_even_odd),
741 NULL);
742 } else {
743 const gx_clip_list *list = gx_cpath_list(pcpath);
744
745 prect = list->head;
746 if (prect == 0)
747 prect = &list->single;
748 }
749 /* Write out the rectangles. */
750 code = (*vdev_proc(vdev, beginpath)) (vdev, gx_path_type_clip);
751 for (; code >= 0 && prect != 0; prect = prect->next)
752 if (prect->xmax > prect->xmin && prect->ymax > prect->ymin)
753 code = gdev_vector_write_rectangle
754 (vdev, int2fixed(prect->xmin), int2fixed(prect->ymin),
755 int2fixed(prect->xmax), int2fixed(prect->ymax),
756 false, gx_rect_x_first);
757 if (code >= 0)
758 code = (*vdev_proc(vdev, endpath)) (vdev, gx_path_type_clip);
759 return code;
760 }
761
762 /* Update the clipping path if needed. */
763 int
gdev_vector_update_clip_path(gx_device_vector * vdev,const gx_clip_path * pcpath)764 gdev_vector_update_clip_path(gx_device_vector * vdev,
765 const gx_clip_path * pcpath)
766 {
767 if (pcpath) {
768 if (pcpath->id != vdev->clip_path_id) {
769 int code = gdev_vector_write_clip_path(vdev, pcpath);
770
771 if (code < 0)
772 return code;
773 vdev->clip_path_id = pcpath->id;
774 }
775 } else {
776 if (vdev->clip_path_id != vdev->no_clip_path_id) {
777 int code = gdev_vector_write_clip_path(vdev, NULL);
778
779 if (code < 0)
780 return code;
781 vdev->clip_path_id = vdev->no_clip_path_id;
782 }
783 }
784 return 0;
785 }
786
787 /* Close the output file and stream. */
788 int
gdev_vector_close_file(gx_device_vector * vdev)789 gdev_vector_close_file(gx_device_vector * vdev)
790 {
791 FILE *f = vdev->file;
792 int err;
793
794 gs_free_object(vdev->v_memory, vdev->bbox_device,
795 "vector_close(bbox_device)");
796 vdev->bbox_device = 0;
797 sclose(vdev->strm);
798 gs_free_object(vdev->v_memory, vdev->strm, "vector_close(strm)");
799 vdev->strm = 0;
800 gs_free_object(vdev->v_memory, vdev->strmbuf, "vector_close(strmbuf)");
801 vdev->strmbuf = 0;
802 vdev->file = 0;
803 err = ferror(f);
804 /* We prevented sclose from closing the file. */
805 if (fclose(f) != 0 || err != 0)
806 return_error(gs_error_ioerror);
807 return 0;
808 }
809
810 /* ---------------- Image enumeration ---------------- */
811
812 /* Initialize for enumerating an image. */
813 int
gdev_vector_begin_image(gx_device_vector * vdev,const gs_imager_state * pis,const gs_image_t * pim,gs_image_format_t format,const gs_int_rect * prect,const gx_drawing_color * pdcolor,const gx_clip_path * pcpath,gs_memory_t * mem,const gx_image_enum_procs_t * pprocs,gdev_vector_image_enum_t * pie)814 gdev_vector_begin_image(gx_device_vector * vdev,
815 const gs_imager_state * pis, const gs_image_t * pim,
816 gs_image_format_t format, const gs_int_rect * prect,
817 const gx_drawing_color * pdcolor, const gx_clip_path * pcpath,
818 gs_memory_t * mem, const gx_image_enum_procs_t * pprocs,
819 gdev_vector_image_enum_t * pie)
820 {
821 const gs_color_space *pcs = pim->ColorSpace;
822 int num_components;
823 int bits_per_pixel;
824 int code;
825
826 if (pim->ImageMask)
827 bits_per_pixel = num_components = 1;
828 else
829 num_components = gs_color_space_num_components(pcs),
830 bits_per_pixel = pim->BitsPerComponent;
831 code = gx_image_enum_common_init((gx_image_enum_common_t *) pie,
832 (const gs_data_image_t *)pim,
833 pprocs, (gx_device *) vdev,
834 num_components, format);
835 if (code < 0)
836 return code;
837 pie->bits_per_pixel = bits_per_pixel * num_components /
838 pie->num_planes;
839 pie->default_info = 0;
840 pie->bbox_info = 0;
841 if ((code = gdev_vector_update_log_op(vdev, pis->log_op)) < 0 ||
842 (code = gdev_vector_update_clip_path(vdev, pcpath)) < 0 ||
843 ((pim->ImageMask ||
844 (pim->CombineWithColor && rop3_uses_T(pis->log_op))) &&
845 (code = gdev_vector_update_fill_color(vdev, pdcolor)) < 0) ||
846 (vdev->bbox_device &&
847 (code = (*dev_proc(vdev->bbox_device, begin_image))
848 ((gx_device *) vdev->bbox_device, pis, pim, format, prect,
849 pdcolor, pcpath, mem, &pie->bbox_info)) < 0)
850 )
851 return code;
852 pie->memory = mem;
853 if (prect)
854 pie->width = prect->q.x - prect->p.x,
855 pie->height = prect->q.y - prect->p.y;
856 else
857 pie->width = pim->Width, pie->height = pim->Height;
858 pie->bits_per_row = pie->width * pie->bits_per_pixel;
859 pie->y = 0;
860 return 0;
861 }
862
863 /* End an image, optionally supplying any necessary blank padding rows. */
864 /* Return 0 if we used the default implementation, 1 if not. */
865 int
gdev_vector_end_image(gx_device_vector * vdev,gdev_vector_image_enum_t * pie,bool draw_last,gx_color_index pad)866 gdev_vector_end_image(gx_device_vector * vdev,
867 gdev_vector_image_enum_t * pie, bool draw_last, gx_color_index pad)
868 {
869 int code;
870
871 if (pie->default_info) {
872 code = gx_default_end_image((gx_device *) vdev, pie->default_info,
873 draw_last);
874 if (code >= 0)
875 code = 0;
876 } else { /* Fill out to the full image height. */
877 if (pie->y < pie->height && pad != gx_no_color_index) {
878 uint bytes_per_row = (pie->bits_per_row + 7) >> 3;
879 byte *row = gs_alloc_bytes(pie->memory, bytes_per_row,
880 "gdev_vector_end_image(fill)");
881
882 if (row == 0)
883 return_error(gs_error_VMerror);
884 /****** FILL VALUE IS WRONG ******/
885 memset(row, (byte) pad, bytes_per_row);
886 for (; pie->y < pie->height; pie->y++)
887 gx_image_data((gx_image_enum_common_t *) pie,
888 (const byte **)&row, 0,
889 bytes_per_row, 1);
890 gs_free_object(pie->memory, row,
891 "gdev_vector_end_image(fill)");
892 }
893 code = 1;
894 }
895 if (vdev->bbox_device) {
896 int bcode = gx_image_end(pie->bbox_info, draw_last);
897
898 if (bcode < 0)
899 code = bcode;
900 }
901 gs_free_object(pie->memory, pie, "gdev_vector_end_image");
902 return code;
903 }
904
905 /* ================ Device procedures ================ */
906
907 #define vdev ((gx_device_vector *)dev)
908
909 /* Get parameters. */
910 int
gdev_vector_get_params(gx_device * dev,gs_param_list * plist)911 gdev_vector_get_params(gx_device * dev, gs_param_list * plist)
912 {
913 int code = gx_default_get_params(dev, plist);
914 int ecode;
915 gs_param_string ofns;
916
917 if (code < 0)
918 return code;
919 ofns.data = (const byte *)vdev->fname,
920 ofns.size = strlen(vdev->fname),
921 ofns.persistent = false;
922 if ((ecode = param_write_string(plist, "OutputFile", &ofns)) < 0)
923 return ecode;
924 return code;
925 }
926
927 /* Put parameters. */
928 int
gdev_vector_put_params(gx_device * dev,gs_param_list * plist)929 gdev_vector_put_params(gx_device * dev, gs_param_list * plist)
930 {
931 int ecode = 0;
932 int code;
933 gs_param_name param_name;
934 gs_param_string ofns;
935
936 switch (code = param_read_string(plist, (param_name = "OutputFile"), &ofns)) {
937 case 0:
938 /*
939 * Vector devices typically write header information at the
940 * beginning of the file: changing the file name after writing
941 * any pages should be an error.
942 */
943 if (ofns.size > fname_size)
944 ecode = gs_error_limitcheck;
945 else if (!bytes_compare(ofns.data, ofns.size,
946 (const byte *)vdev->fname,
947 strlen(vdev->fname))
948 ) {
949 /* The new name is the same as the old name. Do nothing. */
950 ofns.data = 0;
951 break;
952 } else if (dev->LockSafetyParams ||
953 (dev->is_open && vdev->strm != 0 &&
954 stell(vdev->strm) != 0)
955 )
956 ecode = (dev->LockSafetyParams) ? gs_error_invalidaccess :
957 gs_error_rangecheck;
958 else
959 break;
960 goto ofe;
961 default:
962 ecode = code;
963 ofe:param_signal_error(plist, param_name, ecode);
964 case 1:
965 ofns.data = 0;
966 break;
967 }
968
969 if (ecode < 0)
970 return ecode;
971 {
972 bool open = dev->is_open;
973
974 /* Don't let gx_default_put_params close the device. */
975 dev->is_open = false;
976 code = gx_default_put_params(dev, plist);
977 dev->is_open = open;
978 }
979 if (code < 0)
980 return code;
981
982 if (ofns.data != 0) {
983 memcpy(vdev->fname, ofns.data, ofns.size);
984 vdev->fname[ofns.size] = 0;
985 if (vdev->file != 0) {
986 gx_device_bbox *bbdev = vdev->bbox_device;
987
988 vdev->bbox_device = 0; /* don't let it be freed */
989 code = gdev_vector_close_file(vdev);
990 vdev->bbox_device = bbdev;
991 if (code < 0)
992 return code;
993 return gdev_vector_open_file_options(vdev, vdev->strmbuf_size,
994 vdev->open_options);
995 }
996 }
997 gdev_vector_load_cache(vdev); /* in case color mapping changed */
998 return 0;
999 }
1000
1001 /* ---------------- Defaults ---------------- */
1002
1003 int
gdev_vector_fill_rectangle(gx_device * dev,int x,int y,int w,int h,gx_color_index color)1004 gdev_vector_fill_rectangle(gx_device * dev, int x, int y, int w, int h,
1005 gx_color_index color)
1006 {
1007 gx_drawing_color dcolor;
1008
1009 /* Ignore the initial fill with white. */
1010 if (!vdev->in_page && color == vdev->white)
1011 return 0;
1012 color_set_pure(&dcolor, color);
1013 {
1014 int code = update_fill(vdev, &dcolor, rop3_T);
1015
1016 if (code < 0)
1017 return code;
1018 /* Make sure we aren't being clipped. */
1019 code = gdev_vector_update_clip_path(vdev, NULL);
1020 if (code < 0)
1021 return code;
1022 }
1023 if (vdev->bbox_device) {
1024 int code = (*dev_proc(vdev->bbox_device, fill_rectangle))
1025 ((gx_device *) vdev->bbox_device, x, y, w, h, color);
1026
1027 if (code < 0)
1028 return code;
1029 }
1030 return (*vdev_proc(vdev, dorect)) (vdev, int2fixed(x), int2fixed(y),
1031 int2fixed(x + w), int2fixed(y + h),
1032 gx_path_type_fill);
1033 }
1034
1035 int
gdev_vector_fill_path(gx_device * dev,const gs_imager_state * pis,gx_path * ppath,const gx_fill_params * params,const gx_device_color * pdevc,const gx_clip_path * pcpath)1036 gdev_vector_fill_path(gx_device * dev, const gs_imager_state * pis,
1037 gx_path * ppath, const gx_fill_params * params,
1038 const gx_device_color * pdevc, const gx_clip_path * pcpath)
1039 {
1040 int code;
1041
1042 if ((code = gdev_vector_prepare_fill(vdev, pis, params, pdevc)) < 0 ||
1043 (code = gdev_vector_update_clip_path(vdev, pcpath)) < 0 ||
1044 (vdev->bbox_device &&
1045 (code = (*dev_proc(vdev->bbox_device, fill_path))
1046 ((gx_device *) vdev->bbox_device, pis, ppath, params,
1047 pdevc, pcpath)) < 0) ||
1048 (code = (*vdev_proc(vdev, dopath))
1049 (vdev, ppath,
1050 (params->rule > 0 ? gx_path_type_even_odd :
1051 gx_path_type_winding_number) | gx_path_type_fill |
1052 vdev->fill_options,
1053 NULL)) < 0
1054 )
1055 return gx_default_fill_path(dev, pis, ppath, params, pdevc, pcpath);
1056 return code;
1057 }
1058
1059 int
gdev_vector_stroke_path(gx_device * dev,const gs_imager_state * pis,gx_path * ppath,const gx_stroke_params * params,const gx_drawing_color * pdcolor,const gx_clip_path * pcpath)1060 gdev_vector_stroke_path(gx_device * dev, const gs_imager_state * pis,
1061 gx_path * ppath, const gx_stroke_params * params,
1062 const gx_drawing_color * pdcolor, const gx_clip_path * pcpath)
1063 {
1064 int code;
1065 double scale;
1066 int set_ctm;
1067 gs_matrix mat;
1068
1069 if ((set_ctm = gdev_vector_stroke_scaling(vdev, pis, &scale, &mat)) != 0 ||
1070 (code = gdev_vector_prepare_stroke(vdev, pis, params, pdcolor, scale)) < 0 ||
1071 (code = gdev_vector_update_clip_path(vdev, pcpath)) < 0 ||
1072 (vdev->bbox_device &&
1073 (code = (*dev_proc(vdev->bbox_device, stroke_path))
1074 ((gx_device *) vdev->bbox_device, pis, ppath, params,
1075 pdcolor, pcpath)) < 0) ||
1076 (code = (*vdev_proc(vdev, dopath))
1077 (vdev, ppath, gx_path_type_stroke | vdev->stroke_options, NULL)) < 0
1078 )
1079 return gx_default_stroke_path(dev, pis, ppath, params, pdcolor, pcpath);
1080 return code;
1081 }
1082
1083 int
gdev_vector_fill_trapezoid(gx_device * dev,const gs_fixed_edge * left,const gs_fixed_edge * right,fixed ybot,fixed ytop,bool swap_axes,const gx_device_color * pdevc,gs_logical_operation_t lop)1084 gdev_vector_fill_trapezoid(gx_device * dev, const gs_fixed_edge * left,
1085 const gs_fixed_edge * right, fixed ybot, fixed ytop, bool swap_axes,
1086 const gx_device_color * pdevc, gs_logical_operation_t lop)
1087 {
1088 fixed xl = left->start.x;
1089 fixed wl = left->end.x - xl;
1090 fixed yl = left->start.y;
1091 fixed hl = left->end.y - yl;
1092 fixed xr = right->start.x;
1093 fixed wr = right->end.x - xr;
1094 fixed yr = right->start.y;
1095 fixed hr = right->end.y - yr;
1096 fixed x0l = xl + fixed_mult_quo(wl, ybot - yl, hl);
1097 fixed x1l = xl + fixed_mult_quo(wl, ytop - yl, hl);
1098 fixed x0r = xr + fixed_mult_quo(wr, ybot - yr, hr);
1099 fixed x1r = xr + fixed_mult_quo(wr, ytop - yr, hr);
1100
1101 #define y0 ybot
1102 #define y1 ytop
1103 int code = update_fill(vdev, pdevc, lop);
1104 gs_fixed_point points[4];
1105
1106 if (code < 0)
1107 return gx_default_fill_trapezoid(dev, left, right, ybot, ytop,
1108 swap_axes, pdevc, lop);
1109 /* Make sure we aren't being clipped. */
1110 code = gdev_vector_update_clip_path(vdev, NULL);
1111 if (code < 0)
1112 return code;
1113 if (swap_axes)
1114 points[0].y = x0l, points[1].y = x0r,
1115 points[0].x = points[1].x = y0,
1116 points[2].y = x1r, points[3].y = x1l,
1117 points[2].x = points[3].x = y1;
1118 else
1119 points[0].x = x0l, points[1].x = x0r,
1120 points[0].y = points[1].y = y0,
1121 points[2].x = x1r, points[3].x = x1l,
1122 points[2].y = points[3].y = y1;
1123 #undef y0
1124 #undef y1
1125 if (vdev->bbox_device) {
1126 int code = (*dev_proc(vdev->bbox_device, fill_trapezoid))
1127 ((gx_device *) vdev->bbox_device, left, right, ybot, ytop,
1128 swap_axes, pdevc, lop);
1129
1130 if (code < 0)
1131 return code;
1132 }
1133 return gdev_vector_write_polygon(vdev, points, 4, true,
1134 gx_path_type_fill);
1135 }
1136
1137 int
gdev_vector_fill_parallelogram(gx_device * dev,fixed px,fixed py,fixed ax,fixed ay,fixed bx,fixed by,const gx_device_color * pdevc,gs_logical_operation_t lop)1138 gdev_vector_fill_parallelogram(gx_device * dev,
1139 fixed px, fixed py, fixed ax, fixed ay, fixed bx, fixed by,
1140 const gx_device_color * pdevc, gs_logical_operation_t lop)
1141 {
1142 fixed pax = px + ax, pay = py + ay;
1143 int code = update_fill(vdev, pdevc, lop);
1144 gs_fixed_point points[4];
1145
1146 if (code < 0)
1147 return gx_default_fill_parallelogram(dev, px, py, ax, ay, bx, by,
1148 pdevc, lop);
1149 /* Make sure we aren't being clipped. */
1150 code = gdev_vector_update_clip_path(vdev, NULL);
1151 if (code < 0)
1152 return code;
1153 if (vdev->bbox_device) {
1154 code = (*dev_proc(vdev->bbox_device, fill_parallelogram))
1155 ((gx_device *) vdev->bbox_device, px, py, ax, ay, bx, by,
1156 pdevc, lop);
1157 if (code < 0)
1158 return code;
1159 }
1160 points[0].x = px, points[0].y = py;
1161 points[1].x = pax, points[1].y = pay;
1162 points[2].x = pax + bx, points[2].y = pay + by;
1163 points[3].x = px + bx, points[3].y = py + by;
1164 return gdev_vector_write_polygon(vdev, points, 4, true,
1165 gx_path_type_fill);
1166 }
1167
1168 int
gdev_vector_fill_triangle(gx_device * dev,fixed px,fixed py,fixed ax,fixed ay,fixed bx,fixed by,const gx_device_color * pdevc,gs_logical_operation_t lop)1169 gdev_vector_fill_triangle(gx_device * dev,
1170 fixed px, fixed py, fixed ax, fixed ay, fixed bx, fixed by,
1171 const gx_device_color * pdevc, gs_logical_operation_t lop)
1172 {
1173 int code = update_fill(vdev, pdevc, lop);
1174 gs_fixed_point points[3];
1175
1176 if (code < 0)
1177 return gx_default_fill_triangle(dev, px, py, ax, ay, bx, by,
1178 pdevc, lop);
1179 /* Make sure we aren't being clipped. */
1180 code = gdev_vector_update_clip_path(vdev, NULL);
1181 if (code < 0)
1182 return code;
1183 if (vdev->bbox_device) {
1184 code = (*dev_proc(vdev->bbox_device, fill_triangle))
1185 ((gx_device *) vdev->bbox_device, px, py, ax, ay, bx, by,
1186 pdevc, lop);
1187 if (code < 0)
1188 return code;
1189 }
1190 points[0].x = px, points[0].y = py;
1191 points[1].x = px + ax, points[1].y = py + ay;
1192 points[2].x = px + bx, points[2].y = py + by;
1193 return gdev_vector_write_polygon(vdev, points, 3, true,
1194 gx_path_type_fill);
1195 }
1196
1197 #undef vdev
1198