1 /* Copyright (C) 2001-2012 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.,  7 Mt. Lassen Drive - Suite A-134, San Rafael,
13    CA  94903, U.S.A., +1(415)492-9861, for further information.
14 */
15 
16 
17 /* SVG (Scalable Vector Graphics) output device */
18 
19 #include "string_.h"
20 #include "gx.h"
21 #include "gserrors.h"
22 #include "gdevvec.h"
23 #include "stream.h"
24 
25 /* SVG data constants */
26 
27 #define XML_DECL    "<?xml version=\"1.0\" standalone=\"no\"?>"
28 #define SVG_DOCTYPE "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \n\
29          \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">"
30 #define SVG_XMLNS   "http://www.w3.org/2000/svg"
31 #define SVG_VERSION "1.1"
32 
33 /* default resolution. */
34 #ifndef X_DPI
35 #  define X_DPI 300
36 #endif
37 #ifndef Y_DPI
38 #  define Y_DPI 300
39 #endif
40 
41 /* internal line buffer */
42 #define SVG_LINESIZE 100
43 
44 /* default constants */
45 #define SVG_DEFAULT_LINEWIDTH	1.0
46 #define SVG_DEFAULT_LINECAP	gs_cap_butt
47 #define SVG_DEFAULT_LINEJOIN	gs_join_miter
48 #define SVG_DEFAULT_MITERLIMIT	4.0
49 
50 /* ---------------- Device definition ---------------- */
51 
52 typedef struct gx_device_svg_s {
53     /* superclass state */
54     gx_device_vector_common;
55     /* local state */
56     int header;		/* whether we've written the file header */
57     int dirty;		/* whether we need to rewrite the <g> element */
58     int mark;		/* <g> nesting level */
59     int page_count;	/* how many output_page calls we've seen */
60     gx_color_index strokecolor, fillcolor;
61     double linewidth;
62     gs_line_cap linecap;
63     gs_line_join linejoin;
64     double miterlimit;
65 } gx_device_svg;
66 
67 #define svg_device_body(dname, depth)\
68   std_device_dci_type_body(gx_device_svg, 0, dname, &st_device_svg, \
69                            DEFAULT_WIDTH_10THS * X_DPI / 10, \
70                            DEFAULT_HEIGHT_10THS * Y_DPI / 10, \
71                            X_DPI, Y_DPI, \
72                            (depth > 8 ? 3 : 1), depth, \
73                            (depth > 1 ? 255 : 1), (depth > 8 ? 255 : 0), \
74                            (depth > 1 ? 256 : 2), (depth > 8 ? 256 : 1))
75 
76 static dev_proc_open_device(svg_open_device);
77 static dev_proc_output_page(svg_output_page);
78 static dev_proc_close_device(svg_close_device);
79 
80 static dev_proc_get_params(svg_get_params);
81 static dev_proc_put_params(svg_put_params);
82 
83 #define svg_device_procs \
84 { \
85         svg_open_device, \
86         NULL,                   /* get_initial_matrix */\
87         NULL,                   /* sync_output */\
88         svg_output_page,\
89         svg_close_device,\
90         gx_default_rgb_map_rgb_color,\
91         gx_default_rgb_map_color_rgb,\
92         gdev_vector_fill_rectangle,\
93         NULL,                   /* tile_rectangle */\
94         NULL,			/* copy_mono */\
95         NULL,			/* copy_color */\
96         NULL,                   /* draw_line */\
97         NULL,                   /* get_bits */\
98         svg_get_params,\
99         svg_put_params,\
100         NULL,                   /* map_cmyk_color */\
101         NULL,                   /* get_xfont_procs */\
102         NULL,                   /* get_xfont_device */\
103         NULL,                   /* map_rgb_alpha_color */\
104         gx_page_device_get_page_device,\
105         NULL,                   /* get_alpha_bits */\
106         NULL,                   /* copy_alpha */\
107         NULL,                   /* get_band */\
108         NULL,                   /* copy_rop */\
109         gdev_vector_fill_path,\
110         gdev_vector_stroke_path,\
111         NULL,			/* fill_mask */\
112         gdev_vector_fill_trapezoid,\
113         gdev_vector_fill_parallelogram,\
114         gdev_vector_fill_triangle,\
115         NULL,			/* draw_thin_line */\
116         NULL,			/* begin_image */\
117         NULL,                   /* image_data */\
118         NULL,                   /* end_image */\
119         NULL,                   /* strip_tile_rectangle */\
120         NULL			/* strip_copy_rop */\
121 }
122 
123 gs_public_st_suffix_add0_final(st_device_svg, gx_device_svg,
124                                "gx_device_svg",
125                                device_svg_enum_ptrs, device_svg_reloc_ptrs,
126                                gx_device_finalize, st_device_vector);
127 
128 /* The output device is named 'svg' but we're referred to as the
129    'svgwrite' device by the build system to avoid conflicts with
130    the svg interpreter */
131 const gx_device_svg gs_svgwrite_device = {
132     svg_device_body("svg", 24),
133     svg_device_procs
134 };
135 
136 /* Vector device procedures */
137 
138 static int
139 svg_beginpage(gx_device_vector *vdev);
140 static int
141 svg_setlinewidth(gx_device_vector *vdev, floatp width);
142 static int
143 svg_setlinecap(gx_device_vector *vdev, gs_line_cap cap);
144 static int
145 svg_setlinejoin(gx_device_vector *vdev, gs_line_join join);
146 static int
147 svg_setmiterlimit(gx_device_vector *vdev, floatp limit);
148 static int
149 svg_setdash(gx_device_vector *vdev, const float *pattern,
150             uint count, floatp offset);
151 static int
152 svg_setlogop(gx_device_vector *vdev, gs_logical_operation_t lop,
153              gs_logical_operation_t diff);
154 
155 static int
156 svg_can_handle_hl_color(gx_device_vector *vdev, const gs_imager_state *pis,
157                         const gx_drawing_color * pdc);
158 static int
159 svg_setfillcolor(gx_device_vector *vdev, const gs_imager_state *pis,
160                  const gx_drawing_color *pdc);
161 static int
162 svg_setstrokecolor(gx_device_vector *vdev, const gs_imager_state *pis,
163                    const gx_drawing_color *pdc);
164 
165 static int
166 svg_dorect(gx_device_vector *vdev, fixed x0, fixed y0,
167            fixed x1, fixed y1, gx_path_type_t type);
168 static int
169 svg_beginpath(gx_device_vector *vdev, gx_path_type_t type);
170 
171 static int
172 svg_moveto(gx_device_vector *vdev, floatp x0, floatp y0,
173            floatp x, floatp y, gx_path_type_t type);
174 static int
175 svg_lineto(gx_device_vector *vdev, floatp x0, floatp y0,
176            floatp x, floatp y, gx_path_type_t type);
177 static int
178 svg_curveto(gx_device_vector *vdev, floatp x0, floatp y0,
179             floatp x1, floatp y1, floatp x2, floatp y2,
180             floatp x3, floatp y3, gx_path_type_t type);
181 static int
182 svg_closepath(gx_device_vector *vdev, floatp x, floatp y,
183               floatp x_start, floatp y_start, gx_path_type_t type);
184 static int
185 svg_endpath(gx_device_vector *vdev, gx_path_type_t type);
186 
187 /* Vector device function table */
188 
189 static const gx_device_vector_procs svg_vector_procs = {
190         /* Page management */
191     svg_beginpage,
192         /* Imager state */
193     svg_setlinewidth,
194     svg_setlinecap,
195     svg_setlinejoin,
196     svg_setmiterlimit,
197     svg_setdash,
198     gdev_vector_setflat,
199     svg_setlogop,
200         /* Other state */
201     svg_can_handle_hl_color,
202     svg_setfillcolor,
203     svg_setstrokecolor,
204         /* Paths */
205     gdev_vector_dopath,
206     svg_dorect,
207     svg_beginpath,
208     svg_moveto,
209     svg_lineto,
210     svg_curveto,
211     svg_closepath,
212     svg_endpath
213 };
214 
215 /* local utility prototypes */
216 
217 static int svg_write_bytes(gx_device_svg *svg,
218                 const char *string, uint length);
219 static int svg_write(gx_device_svg *svg, const char *string);
220 
221 static int svg_write_header(gx_device_svg *svg);
222 
223 /* Driver procedure implementation */
224 
225 /* Open the device */
226 static int
svg_open_device(gx_device * dev)227 svg_open_device(gx_device *dev)
228 {
229     gx_device_vector *const vdev = (gx_device_vector*)dev;
230     gx_device_svg *const svg = (gx_device_svg*)dev;
231     int code = 0;
232 
233     vdev->v_memory = dev->memory;
234     vdev->vec_procs = &svg_vector_procs;
235     gdev_vector_init(vdev);
236     code = gdev_vector_open_file_options(vdev, 512,
237         VECTOR_OPEN_FILE_SEQUENTIAL);
238     if (code < 0)
239       return gs_rethrow_code(code);
240 
241     /* svg-specific initialization goes here */
242     svg->header = 0;
243     svg->dirty = 0;
244     svg->mark = 0;
245     svg->page_count = 0;
246     svg->strokecolor = gx_no_color_index;
247     svg->fillcolor = gx_no_color_index;
248     /* these should be the graphics library defaults instead? */
249     svg->linewidth = SVG_DEFAULT_LINEWIDTH;
250     svg->linecap = SVG_DEFAULT_LINECAP;
251     svg->linejoin = SVG_DEFAULT_LINEJOIN;
252     svg->miterlimit = SVG_DEFAULT_MITERLIMIT;
253 
254     return code;
255 }
256 
257 /* Complete a page */
258 static int
svg_output_page(gx_device * dev,int num_copies,int flush)259 svg_output_page(gx_device *dev, int num_copies, int flush)
260 {
261     gx_device_svg *const svg = (gx_device_svg*)dev;
262     int code;
263 
264     svg->page_count++;
265 
266     svg_write(svg, "\n<!-- svg_output_page -->\n");
267     if (ferror(svg->file))
268       return gs_throw_code(gs_error_ioerror);
269 
270     if ((code=gx_finish_output_page(dev, num_copies, flush)) < 0)
271         return code;
272     /* Check if we need to change the output file for separate pages */
273     if (gx_outputfile_is_separate_pages(((gx_device_vector *)dev)->fname, dev->memory)) {
274         if ((code = svg_close_device(dev)) < 0)
275             return code;
276         code = svg_open_device(dev);
277     }
278     return code;
279 }
280 
281 /* Close the device */
282 static int
svg_close_device(gx_device * dev)283 svg_close_device(gx_device *dev)
284 {
285     gx_device_svg *const svg = (gx_device_svg*)dev;
286 
287     svg_write(svg, "\n<!-- svg_close_device -->\n");
288     /* close any open group elements */
289     while (svg->mark > 0) {
290       svg_write(svg, "</g>\n");
291       svg->mark--;
292     }
293     if (svg->header) {
294       svg_write(svg, "</svg>\n");
295       svg->header = 0;
296     }
297 
298     if (ferror(svg->file))
299       return gs_throw_code(gs_error_ioerror);
300 
301     return gdev_vector_close_file((gx_device_vector*)dev);
302 }
303 
304 /* Respond to a device parameter query from the client */
305 static int
svg_get_params(gx_device * dev,gs_param_list * plist)306 svg_get_params(gx_device *dev, gs_param_list *plist)
307 {
308     int code = 0;
309 
310     if_debug0('_', "svg_get_params\n");
311 
312     /* call our superclass to add its standard set */
313     code = gdev_vector_get_params(dev, plist);
314     if (code < 0)
315       return gs_rethrow_code(code);
316 
317     /* svg specific parameters are added to plist here */
318 
319     return code;
320 }
321 
322 /* Read the device parameters passed to us by the client */
323 static int
svg_put_params(gx_device * dev,gs_param_list * plist)324 svg_put_params(gx_device *dev, gs_param_list *plist)
325 {
326     int code = 0;
327 
328     if_debug0('_', "svg_put_params\n");
329 
330     /* svg specific parameters are parsed here */
331 
332     /* call our superclass to get its parameters, like OutputFile */
333     code = gdev_vector_put_params(dev, plist);
334     if (code < 0)
335       return gs_rethrow_code(code);
336 
337     return code;
338 }
339 
340 /* write a length-limited char buffer */
341 static int
svg_write_bytes(gx_device_svg * svg,const char * string,uint length)342 svg_write_bytes(gx_device_svg *svg, const char *string, uint length)
343 {
344     /* calling the accessor ensures beginpage is called */
345     stream *s = gdev_vector_stream((gx_device_vector*)svg);
346     uint used;
347 
348     sputs(s, (const byte *)string, length, &used);
349 
350     return !(length == used);
351 }
352 
353 /* write a null terminated string */
354 static int
svg_write(gx_device_svg * svg,const char * string)355 svg_write(gx_device_svg *svg, const char *string)
356 {
357     return svg_write_bytes(svg, string, strlen(string));
358 }
359 
360 static int
svg_write_header(gx_device_svg * svg)361 svg_write_header(gx_device_svg *svg)
362 {
363     /* we're called from beginpage, so we can't use
364        svg_write() which calls gdev_vector_stream()
365        which calls beginpage! */
366     stream *s = svg->strm;
367     uint used;
368     char line[300];
369 
370     if_debug0('_', "svg_write_header\n");
371 
372     /* only write the header once */
373     if (svg->header)
374       return 1;
375 
376     /* write the initial boilerplate */
377     sprintf(line, "%s\n", XML_DECL);
378     /* svg_write(svg, line); */
379     sputs(s, (byte *)line, strlen(line), &used);
380     sprintf(line, "%s\n", SVG_DOCTYPE);
381     /* svg_write(svg, line); */
382     sputs(s, (byte *)line, strlen(line), &used);
383     sprintf(line, "<svg xmlns='%s' version='%s'",
384         SVG_XMLNS, SVG_VERSION);
385     /* svg_write(svg, line); */
386     sputs(s, (byte *)line, strlen(line), &used);
387     sprintf(line, "\n\twidth='%dpt' height='%dpt'>\n",
388         (int)svg->MediaSize[0], (int)svg->MediaSize[1]);
389     sputs(s, (byte *)line, strlen(line), &used);
390 
391     /* Scale drawing so our coordinates are in pixels */
392     sprintf(line, "<g transform='scale(%lf,%lf)'>\n",
393         72.0 / svg->HWResolution[0],
394         72.0 / svg->HWResolution[1]);
395     /* svg_write(svg, line); */
396     sputs(s, (byte *)line, strlen(line), &used);
397     svg->mark++;
398 
399     /* mark that we've been called */
400     svg->header = 1;
401 
402     return 0;
403 }
404 
405 static gx_color_index
svg_get_color(gx_device_svg * svg,const gx_drawing_color * pdc)406 svg_get_color(gx_device_svg *svg, const gx_drawing_color *pdc)
407 {
408 
409     gx_color_index color = gx_no_color_index;
410 
411     if (gx_dc_is_pure(pdc))
412 	color = gx_dc_pure_color(pdc);
413     return color;
414 }
415 
416 static int
svg_write_state(gx_device_svg * svg)417 svg_write_state(gx_device_svg *svg)
418 {
419     char line[SVG_LINESIZE];
420 
421     /* has anything changed? */
422     if (!svg->dirty)
423       return 0;
424 
425     /* close the current graphics state element, if any */
426     if (svg->mark > 1) {
427       svg_write(svg, "</g>\n");
428       svg->mark--;
429     }
430     /* write out the new current state */
431     svg_write(svg, "<g ");
432     if (svg->strokecolor != gx_no_color_index) {
433 	sprintf(line, " stroke='#%06x'", svg->strokecolor & 0xffffffL);
434         svg_write(svg, line);
435     } else {
436         svg_write(svg, " stroke='none'");
437     }
438     if (svg->fillcolor != gx_no_color_index) {
439         sprintf(line, "#%06x", svg->fillcolor & 0xffffffL);
440         svg_write(svg, line);
441     } else {
442       svg_write(svg, " fill='none'");
443     }
444     if (svg->linewidth != 1.0) {
445       sprintf(line, " stroke-width='%lf'", svg->linewidth);
446       svg_write(svg, line);
447     }
448     if (svg->linecap != SVG_DEFAULT_LINECAP) {
449         switch (svg->linecap) {
450           case gs_cap_round:
451             svg_write(svg, " stroke-linecap='round'");
452             break;
453           case gs_cap_square:
454             svg_write(svg, " stroke-linecap='square'");
455             break;
456           case gs_cap_butt:
457           default:
458             /* treat all the other options as the default */
459             svg_write(svg, " stroke-linecap='butt'");
460             break;
461         }
462     }
463     if (svg->linejoin != SVG_DEFAULT_LINEJOIN) {
464         switch (svg->linejoin) {
465           case gs_join_round:
466             svg_write(svg, " stroke-linejoin='round'");
467             break;
468           case gs_join_bevel:
469             svg_write(svg, " stroke-linejoin='bevel'");
470             break;
471           case gs_join_miter:
472           default:
473             /* SVG doesn't support any other variants */
474             svg_write(svg, " stroke-linejoin='miter'");
475             break;
476         }
477     }
478     if (svg->miterlimit != SVG_DEFAULT_MITERLIMIT) {
479         sprintf(line, " stroke-miterlimit='%lf'", svg->miterlimit);
480         svg_write(svg, line);
481     }
482     svg_write(svg, ">\n");
483     svg->mark++;
484 
485     svg->dirty = 0;
486     return 0;
487 }
488 
489 /* vector device implementation */
490 
491         /* Page management */
492 static int
svg_beginpage(gx_device_vector * vdev)493 svg_beginpage(gx_device_vector *vdev)
494 {
495     gx_device_svg *svg = (gx_device_svg *)vdev;
496 
497     svg_write_header(svg);
498 
499     if_debug1('_', "svg_beginpage (page count %d)\n", svg->page_count);
500     return 0;
501 }
502 
503         /* Imager state */
504 static int
svg_setlinewidth(gx_device_vector * vdev,floatp width)505 svg_setlinewidth(gx_device_vector *vdev, floatp width)
506 {
507     gx_device_svg *svg = (gx_device_svg *)vdev;
508 
509     if_debug1('_', "svg_setlinewidth(%lf)\n", width);
510 
511     svg->linewidth = width;
512     svg->dirty++;
513 
514     return 0;
515 }
516 static int
svg_setlinecap(gx_device_vector * vdev,gs_line_cap cap)517 svg_setlinecap(gx_device_vector *vdev, gs_line_cap cap)
518 {
519     gx_device_svg *svg = (gx_device_svg *)vdev;
520     const char *linecap_names[] = {"butt", "round", "square",
521         "triangle", "unknown"};
522 
523     if (cap < 0 || cap > gs_cap_unknown)
524         return gs_throw_code(gs_error_rangecheck);
525     if_debug1('_', "svg_setlinecap(%s)\n", linecap_names[cap]);
526 
527     svg->linecap = cap;
528     svg->dirty++;
529 
530     return 0;
531 }
532 static int
svg_setlinejoin(gx_device_vector * vdev,gs_line_join join)533 svg_setlinejoin(gx_device_vector *vdev, gs_line_join join)
534 {
535     gx_device_svg *svg = (gx_device_svg *)vdev;
536     const char *linejoin_names[] = {"miter", "round", "bevel",
537         "none", "triangle", "unknown"};
538 
539     if (join < 0 || join > gs_join_unknown)
540         return gs_throw_code(gs_error_rangecheck);
541     if_debug1('_', "svg_setlinejoin(%s)\n", linejoin_names[join]);
542 
543     svg->linejoin = join;
544     svg->dirty++;
545 
546     return 0;
547 }
548 static int
svg_setmiterlimit(gx_device_vector * vdev,floatp limit)549 svg_setmiterlimit(gx_device_vector *vdev, floatp limit)
550 {
551     if_debug1('_', "svg_setmiterlimit(%lf)\n", limit);
552     return 0;
553 }
554 static int
svg_setdash(gx_device_vector * vdev,const float * pattern,uint count,floatp offset)555 svg_setdash(gx_device_vector *vdev, const float *pattern,
556             uint count, floatp offset)
557 {
558     if_debug0('_', "svg_setdash\n");
559     return 0;
560 }
561 static int
svg_setlogop(gx_device_vector * vdev,gs_logical_operation_t lop,gs_logical_operation_t diff)562 svg_setlogop(gx_device_vector *vdev, gs_logical_operation_t lop,
563              gs_logical_operation_t diff)
564 {
565     if_debug2('_', "svg_setlogop(%u,%u) set logical operation\n",
566         lop, diff);
567     /* SVG can fake some simpler modes, but we ignore this for now. */
568     return 0;
569 }
570 
571         /* Other state */
572 
573 static int
svg_can_handle_hl_color(gx_device_vector * vdev,const gs_imager_state * pis,const gx_drawing_color * pdc)574 svg_can_handle_hl_color(gx_device_vector *vdev, const gs_imager_state *pis,
575                           const gx_drawing_color * pdc)
576 {
577     if_debug0('_', "svg_can_handle_hl_color\n");
578     return 0;
579 }
580 
581 static int
svg_setfillcolor(gx_device_vector * vdev,const gs_imager_state * pis,const gx_drawing_color * pdc)582 svg_setfillcolor(gx_device_vector *vdev, const gs_imager_state *pis,
583                  const gx_drawing_color *pdc)
584 {
585     gx_device_svg *svg = (gx_device_svg*)vdev;
586     gx_color_index fill = svg_get_color(svg, pdc);
587 
588     if_debug0('_', "svg_setfillcolor\n");
589 
590     if (svg->fillcolor == fill)
591       return 0; /* not a new color */
592     /* update our state with the new color */
593     svg->fillcolor = fill;
594     /* request a new group element */
595     svg->dirty++;
596     return 0;
597 }
598 
599 static int
svg_setstrokecolor(gx_device_vector * vdev,const gs_imager_state * pis,const gx_drawing_color * pdc)600 svg_setstrokecolor(gx_device_vector *vdev, const gs_imager_state *pis,
601                    const gx_drawing_color *pdc)
602 {
603     gx_device_svg *svg = (gx_device_svg*)vdev;
604     gx_color_index stroke = svg_get_color(svg, pdc);
605 
606     if_debug0('_', "svg_setstrokecolor\n");
607 
608     if (svg->strokecolor == stroke)
609       return 0; /* not a new color */
610 
611     /* update our state with the new color */
612     svg->strokecolor = stroke;
613     /* request a new group element */
614     svg->dirty++;
615 
616     return 0;
617 }
618 
619         /* Paths */
620 /*    gdev_vector_dopath */
621 
svg_print_path_type(gx_device_svg * svg,gx_path_type_t type)622 static int svg_print_path_type(gx_device_svg *svg, gx_path_type_t type)
623 {
624     const char *path_type_names[] = {"winding number", "fill", "stroke",
625         "fill and stroke", "clip"};
626 
627     if (type <= 4)
628         if_debug2('_', "type %d (%s)", type, path_type_names[type]);
629     else
630         if_debug1('_', "type %d", type);
631 
632     return 0;
633 }
634 
635 static int
svg_dorect(gx_device_vector * vdev,fixed x0,fixed y0,fixed x1,fixed y1,gx_path_type_t type)636 svg_dorect(gx_device_vector *vdev, fixed x0, fixed y0,
637            fixed x1, fixed y1, gx_path_type_t type)
638 {
639     gx_device_svg *svg = (gx_device_svg *)vdev;
640     char line[300];
641 
642     /* hack single-page output */
643     if (svg->page_count)
644       return 0;
645 
646     if_debug0('_', "svg_dorect");
647     svg_print_path_type(svg, type);
648     if_debug0('_', "\n");
649 
650     svg_write_state(svg);
651 
652     if (type & gx_path_type_clip) {
653         svg_write(svg, "<clipPath>\n");
654     }
655 
656     sprintf(line, "<rect x='%lf' y='%lf' width='%lf' height='%lf'",
657         fixed2float(x0), fixed2float(y0),
658         fixed2float(x1 - x0), fixed2float(y1 - y0));
659     svg_write(svg, line);
660     /* override the inherited stroke attribute if we're not stroking */
661     if (!(type & gx_path_type_stroke) && (svg->strokecolor != gx_no_color_index))
662         svg_write(svg, " stroke='none'");
663     /* override the inherited fill attribute if we're not filling */
664     if (!(type & gx_path_type_fill) && (svg->fillcolor != gx_no_color_index))
665         svg_write(svg, " fill='none'");
666     svg_write(svg, "/>\n");
667 
668     if (type & gx_path_type_clip) {
669         svg_write(svg, "</clipPath>\n");
670     }
671 
672     return 0;
673 }
674 
675 static int
svg_beginpath(gx_device_vector * vdev,gx_path_type_t type)676 svg_beginpath(gx_device_vector *vdev, gx_path_type_t type)
677 {
678     gx_device_svg *svg = (gx_device_svg *)vdev;
679 
680     /* hack single-page output */
681     if (svg->page_count)
682       return 0;
683 
684     /* skip non-drawing paths for now */
685     if (!(type & gx_path_type_fill) && !(type & gx_path_type_stroke))
686       return 0;
687 
688     if_debug0('_', "svg_beginpath ");
689     svg_print_path_type(svg, type);
690     if_debug0('_', "\n");
691 
692     svg_write_state(svg);
693     svg_write(svg, "<path d='");
694 
695     return 0;
696 }
697 
698 static int
svg_moveto(gx_device_vector * vdev,floatp x0,floatp y0,floatp x,floatp y,gx_path_type_t type)699 svg_moveto(gx_device_vector *vdev, floatp x0, floatp y0,
700            floatp x, floatp y, gx_path_type_t type)
701 {
702     gx_device_svg *svg = (gx_device_svg *)vdev;
703     char line[SVG_LINESIZE];
704 
705     /* hack single-page output */
706     if (svg->page_count)
707       return 0;
708 
709     /* skip non-drawing paths for now */
710     if (!(type & gx_path_type_fill) && !(type & gx_path_type_stroke))
711       return 0;
712 
713     if_debug4('_', "svg_moveto(%lf,%lf,%lf,%lf) ", x0, y0, x, y);
714     svg_print_path_type(svg, type);
715     if_debug0('_', "\n");
716 
717     sprintf(line, " M%lf,%lf", x, y);
718     svg_write(svg, line);
719 
720     return 0;
721 }
722 
723 static int
svg_lineto(gx_device_vector * vdev,floatp x0,floatp y0,floatp x,floatp y,gx_path_type_t type)724 svg_lineto(gx_device_vector *vdev, floatp x0, floatp y0,
725            floatp x, floatp y, gx_path_type_t type)
726 {
727     gx_device_svg *svg = (gx_device_svg *)vdev;
728     char line[SVG_LINESIZE];
729 
730     /* hack single-page output */
731     if (svg->page_count)
732       return 0;
733 
734     /* skip non-drawing paths for now */
735     if (!(type & gx_path_type_fill) && !(type & gx_path_type_stroke))
736       return 0;
737 
738     if_debug4('_', "svg_lineto(%lf,%lf,%lf,%lf) ", x0,y0, x,y);
739     svg_print_path_type(svg, type);
740     if_debug0('_', "\n");
741 
742     sprintf(line, " L%lf,%lf", x, y);
743     svg_write(svg, line);
744 
745     return 0;
746 }
747 
748 static int
svg_curveto(gx_device_vector * vdev,floatp x0,floatp y0,floatp x1,floatp y1,floatp x2,floatp y2,floatp x3,floatp y3,gx_path_type_t type)749 svg_curveto(gx_device_vector *vdev, floatp x0, floatp y0,
750             floatp x1, floatp y1, floatp x2, floatp y2,
751             floatp x3, floatp y3, gx_path_type_t type)
752 {
753     gx_device_svg *svg = (gx_device_svg *)vdev;
754     char line[SVG_LINESIZE];
755 
756     /* hack single-page output */
757     if (svg->page_count)
758       return 0;
759 
760     /* skip non-drawing paths for now */
761     if (!(type & gx_path_type_fill) && !(type & gx_path_type_stroke))
762       return 0;
763 
764     if_debug8('_', "svg_curveto(%lf,%lf, %lf,%lf, %lf,%lf, %lf,%lf) ",
765         x0,y0, x1,y1, x2,y2, x3,y3);
766     svg_print_path_type(svg, type);
767     if_debug0('_', "\n");
768 
769     sprintf(line, " C%lf,%lf %lf,%lf %lf,%lf", x1,y1, x2,y2, x3,y3);
770     svg_write(svg, line);
771 
772     return 0;
773 }
774 
775 static int
svg_closepath(gx_device_vector * vdev,floatp x,floatp y,floatp x_start,floatp y_start,gx_path_type_t type)776 svg_closepath(gx_device_vector *vdev, floatp x, floatp y,
777               floatp x_start, floatp y_start, gx_path_type_t type)
778 {
779     gx_device_svg *svg = (gx_device_svg *)vdev;
780 
781     /* hack single-page output */
782     if (svg->page_count)
783       return 0;
784 
785     /* skip non-drawing paths for now */
786     if (!(type & gx_path_type_fill) && !(type & gx_path_type_stroke))
787       return 0;
788 
789     if_debug0('_', "svg_closepath ");
790     svg_print_path_type(svg, type);
791     if_debug0('_', "\n");
792 
793     svg_write(svg, " z");
794 
795     return 0;
796 }
797 
798 static int
svg_endpath(gx_device_vector * vdev,gx_path_type_t type)799 svg_endpath(gx_device_vector *vdev, gx_path_type_t type)
800 {
801     gx_device_svg *svg = (gx_device_svg *)vdev;
802 
803     /* hack single-page output */
804     if (svg->page_count)
805       return 0;
806 
807     /* skip non-drawing paths for now */
808     if (!(type & gx_path_type_fill) && !(type & gx_path_type_stroke))
809       return 0;
810 
811     if_debug0('_', "svg_endpath ");
812     svg_print_path_type(svg, type);
813     if_debug0('_', "\n");
814 
815     /* close the path data attribute */
816     svg_write(svg, "'");
817 
818     /* override the inherited stroke attribute if we're not stroking */
819     if (!(type & gx_path_type_stroke) && (svg->strokecolor != gx_no_color_index))
820       svg_write(svg, " stroke='none'");
821 
822     /* override the inherited fill attribute if we're not filling */
823     if (!(type & gx_path_type_fill) && (svg->fillcolor != gx_no_color_index))
824       svg_write(svg, " fill='none'");
825 
826     svg_write(svg, "/>\n");
827 
828     return 0;
829 }
830