1
/* Copyright (C) 2007-2008 Artifex Software, Inc.
4
This software is provided AS-IS with no warranty, either express or
7
This software is distributed under license and may not be copied, modified
8
or distributed except as expressly authorized under the terms of that
9
license. Refer to licensing information at http://www.artifex.com/
10
or contact Artifex Software, Inc., 7 Mt. Lassen Drive - Suite A-134,
11
San Rafael, CA 94903, U.S.A., +1(415)492-9861, for further information.
14
/* $Id: gdevsvg.c 8798 2008-06-22 06:43:28Z giles $ */
15
/* SVG (Scalable Vector Graphics) output device */
23
/* SVG data constants */
25
#define XML_DECL "<?xml version=\"1.0\" standalone=\"no\"?>"
26
#define SVG_DOCTYPE "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \n\
27
\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">"
28
#define SVG_XMLNS "http://www.w3.org/2000/svg"
29
#define SVG_VERSION "1.1"
31
/* default resolution. */
39
/* internal line buffer */
40
#define SVG_LINESIZE 100
42
/* default constants */
43
#define SVG_DEFAULT_LINEWIDTH 1.0
44
#define SVG_DEFAULT_LINECAP gs_cap_butt
45
#define SVG_DEFAULT_LINEJOIN gs_join_miter
46
#define SVG_DEFAULT_MITERLIMIT 4.0
48
/* ---------------- Device definition ---------------- */
50
typedef struct gx_device_svg_s {
51
/* superclass state */
52
gx_device_vector_common;
54
int header; /* whether we've written the file header */
55
int dirty; /* whether we need to rewrite the <g> element */
56
int mark; /* <g> nesting level */
57
int page_count; /* how many output_page calls we've seen */
58
char *strokecolor, *fillcolor;
61
gs_line_join linejoin;
65
#define svg_device_body(dname, depth)\
66
std_device_dci_type_body(gx_device_svg, 0, dname, &st_device_svg, \
67
DEFAULT_WIDTH_10THS * X_DPI / 10, \
68
DEFAULT_HEIGHT_10THS * Y_DPI / 10, \
70
(depth > 8 ? 3 : 1), depth, \
71
(depth > 1 ? 255 : 1), (depth > 8 ? 255 : 0), \
72
(depth > 1 ? 256 : 2), (depth > 8 ? 256 : 1))
74
static dev_proc_open_device(svg_open_device);
75
static dev_proc_output_page(svg_output_page);
76
static dev_proc_close_device(svg_close_device);
78
static dev_proc_get_params(svg_get_params);
79
static dev_proc_put_params(svg_put_params);
81
#define svg_device_procs \
84
NULL, /* get_initial_matrix */\
85
NULL, /* sync_output */\
88
gx_default_rgb_map_rgb_color,\
89
gx_default_rgb_map_color_rgb,\
90
gdev_vector_fill_rectangle,\
91
NULL, /* tile_rectangle */\
92
NULL, /* copy_mono */\
93
NULL, /* copy_color */\
94
NULL, /* draw_line */\
98
NULL, /* map_cmyk_color */\
99
NULL, /* get_xfont_procs */\
100
NULL, /* get_xfont_device */\
101
NULL, /* map_rgb_alpha_color */\
102
gx_page_device_get_page_device,\
103
NULL, /* get_alpha_bits */\
104
NULL, /* copy_alpha */\
105
NULL, /* get_band */\
106
NULL, /* copy_rop */\
107
gdev_vector_fill_path,\
108
gdev_vector_stroke_path,\
109
NULL, /* fill_mask */\
110
gdev_vector_fill_trapezoid,\
111
gdev_vector_fill_parallelogram,\
112
gdev_vector_fill_triangle,\
113
NULL, /* draw_thin_line */\
114
NULL, /* begin_image */\
115
NULL, /* image_data */\
116
NULL, /* end_image */\
117
NULL, /* strip_tile_rectangle */\
118
NULL /* strip_copy_rop */\
121
gs_public_st_suffix_add0_final(st_device_svg, gx_device_svg,
123
device_svg_enum_ptrs,
124
device_svg_reloc_ptrs,
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),
136
/* Vector device procedures */
139
svg_beginpage(gx_device_vector *vdev);
141
svg_setlinewidth(gx_device_vector *vdev, floatp width);
143
svg_setlinecap(gx_device_vector *vdev, gs_line_cap cap);
145
svg_setlinejoin(gx_device_vector *vdev, gs_line_join join);
147
svg_setmiterlimit(gx_device_vector *vdev, floatp limit);
149
svg_setdash(gx_device_vector *vdev, const float *pattern,
150
uint count, floatp offset);
152
svg_setlogop(gx_device_vector *vdev, gs_logical_operation_t lop,
153
gs_logical_operation_t diff);
156
svg_can_handle_hl_color(gx_device_vector *vdev, const gs_imager_state *pis,
157
const gx_drawing_color * pdc);
159
svg_setfillcolor(gx_device_vector *vdev, const gs_imager_state *pis,
160
const gx_drawing_color *pdc);
162
svg_setstrokecolor(gx_device_vector *vdev, const gs_imager_state *pis,
163
const gx_drawing_color *pdc);
166
svg_dorect(gx_device_vector *vdev, fixed x0, fixed y0,
167
fixed x1, fixed y1, gx_path_type_t type);
169
svg_beginpath(gx_device_vector *vdev, gx_path_type_t type);
172
svg_moveto(gx_device_vector *vdev, floatp x0, floatp y0,
173
floatp x, floatp y, gx_path_type_t type);
175
svg_lineto(gx_device_vector *vdev, floatp x0, floatp y0,
176
floatp x, floatp y, gx_path_type_t type);
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);
182
svg_closepath(gx_device_vector *vdev, floatp x, floatp y,
183
floatp x_start, floatp y_start, gx_path_type_t type);
185
svg_endpath(gx_device_vector *vdev, gx_path_type_t type);
187
/* Vector device function table */
189
static const gx_device_vector_procs svg_vector_procs = {
190
/* Page management */
201
svg_can_handle_hl_color,
215
/* local utility prototypes */
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);
221
static int svg_write_header(gx_device_svg *svg);
223
/* Driver procedure implementation */
225
/* Open the device */
227
svg_open_device(gx_device *dev)
229
gx_device_vector *const vdev = (gx_device_vector*)dev;
230
gx_device_svg *const svg = (gx_device_svg*)dev;
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) return code;
240
/* svg-specific initialization goes here */
245
svg->strokecolor = NULL;
246
svg->fillcolor = NULL;
247
/* these should be the graphics library defaults instead? */
248
svg->linewidth = SVG_DEFAULT_LINEWIDTH;
249
svg->linecap = SVG_DEFAULT_LINECAP;
250
svg->linejoin = SVG_DEFAULT_LINEJOIN;
251
svg->miterlimit = SVG_DEFAULT_MITERLIMIT;
255
/* Complete a page */
257
svg_output_page(gx_device *dev, int num_copies, int flush)
259
gx_device_svg *const svg = (gx_device_svg*)dev;
263
svg_write(svg, "\n<!-- svg_output_page -->\n");
264
if (ferror(svg->file)) return_error(gs_error_ioerror);
266
return gx_finish_output_page(dev, num_copies, flush);
269
/* Close the device */
271
svg_close_device(gx_device *dev)
273
gx_device_svg *const svg = (gx_device_svg*)dev;
275
svg_write(svg, "\n<!-- svg_close_device -->\n");
276
/* close any open group elements */
277
while (svg->mark > 0) {
278
svg_write(svg, "</g>\n");
282
svg_write(svg, "</svg>\n");
286
if (svg->fillcolor) gs_free_string(svg->memory, svg->fillcolor, 8,
288
if (svg->strokecolor) gs_free_string(svg->memory, svg->strokecolor, 8,
291
if (ferror(svg->file)) return_error(gs_error_ioerror);
293
return gdev_vector_close_file((gx_device_vector*)dev);
296
/* Respond to a device parameter query from the client */
298
svg_get_params(gx_device *dev, gs_param_list *plist)
302
dprintf("svg_get_params\n");
304
/* call our superclass to add its standard set */
305
code = gdev_vector_get_params(dev, plist);
306
if (code < 0) return code;
308
/* svg specific parameters are added to plist here */
313
/* Read the device parameters passed to us by the client */
315
svg_put_params(gx_device *dev, gs_param_list *plist)
319
dprintf("svg_put_params\n");
321
/* svg specific parameters are parsed here */
323
/* call our superclass to get its parameters, like OutputFile */
324
code = gdev_vector_put_params(dev, plist);
325
if (code < 0) return code;
330
/* write a length-limited char buffer */
332
svg_write_bytes(gx_device_svg *svg, const char *string, uint length)
334
/* calling the accessor ensures beginpage is called */
335
stream *s = gdev_vector_stream((gx_device_vector*)svg);
338
sputs(s, (const byte *)string, length, &used);
340
return !(length == used);
343
/* write a null terminated string */
345
svg_write(gx_device_svg *svg, const char *string)
347
return svg_write_bytes(svg, string, strlen(string));
351
svg_write_header(gx_device_svg *svg)
353
/* we're called from beginpage, so we can't use
354
svg_write() which calls gdev_vector_stream()
355
which calls beginpage! */
356
stream *s = svg->strm;
360
dprintf("svg_write_header\n");
362
/* only write the header once */
363
if (svg->header) return 1;
365
/* write the initial boilerplate */
366
sprintf(line, "%s\n", XML_DECL);
367
/* svg_write(svg, line); */
368
sputs(s, line, strlen(line), &used);
369
sprintf(line, "%s\n", SVG_DOCTYPE);
370
/* svg_write(svg, line); */
371
sputs(s, line, strlen(line), &used);
372
sprintf(line, "<svg xmlns='%s' version='%s'",
373
SVG_XMLNS, SVG_VERSION);
374
/* svg_write(svg, line); */
375
sputs(s, line, strlen(line), &used);
376
sprintf(line, "\n\twidth='%dpt' height='%dpt'>\n",
377
(int)svg->MediaSize[0], (int)svg->MediaSize[1]);
378
sputs(s, line, strlen(line), &used);
380
/* Scale drawing so our coordinates are in pixels */
381
sprintf(line, "<g transform='scale(%lf,%lf)'>\n",
382
72.0 / svg->HWResolution[0],
383
72.0 / svg->HWResolution[1]);
384
/* svg_write(svg, line); */
385
sputs(s, line, strlen(line), &used);
388
/* mark that we've been called */
395
svg_make_color(gx_device_svg *svg, gx_drawing_color *pdc)
397
char *paint = gs_alloc_string(svg->memory, 8, "svg_make_color");
400
gs_note_error(gs_error_VMerror);
404
if (gx_dc_is_pure(pdc)) {
405
gx_color_index color = gx_dc_pure_color(pdc);
406
sprintf(paint, "#%06x", color & 0xffffff);
407
} else if (gx_dc_is_null(pdc)) {
408
sprintf(paint, "None");
410
gs_free_string(svg->memory, paint, 8, "svg_make_color");
411
gs_note_error(gs_error_rangecheck);
419
svg_write_state(gx_device_svg *svg)
421
char line[SVG_LINESIZE];
423
/* has anything changed? */
424
if (!svg->dirty) return 0;
426
/* close the current graphics state element, if any */
428
svg_write(svg, "</g>\n");
431
/* write out the new current state */
432
svg_write(svg, "<g ");
433
if (svg->strokecolor) {
434
sprintf(line, " stroke='%s'", svg->strokecolor);
435
svg_write(svg, line);
437
svg_write(svg, " stroke='none'");
439
if (svg->fillcolor) {
440
sprintf(line, " fill='%s'", svg->fillcolor);
441
svg_write(svg, line);
443
svg_write(svg, " fill='none'");
445
if (svg->linewidth != 1.0) {
446
sprintf(line, " stroke-width='%lf'", svg->linewidth);
447
svg_write(svg, line);
449
if (svg->linecap != SVG_DEFAULT_LINECAP) {
450
switch (svg->linecap) {
452
svg_write(svg, " stroke-linecap='round'");
455
svg_write(svg, " stroke-linecap='square'");
459
/* treat all the other options as the default */
460
svg_write(svg, " stroke-linecap='butt'");
464
if (svg->linejoin != SVG_DEFAULT_LINEJOIN) {
465
switch (svg->linejoin) {
467
svg_write(svg, " stroke-linejoin='round'");
470
svg_write(svg, " stroke-linejoin='bevel'");
474
/* SVG doesn't support any other variants */
475
svg_write(svg, " stroke-linejoin='miter'");
479
if (svg->miterlimit != SVG_DEFAULT_MITERLIMIT) {
480
sprintf(line, " stroke-miterlimit='%lf'", svg->miterlimit);
481
svg_write(svg, line);
483
svg_write(svg, ">\n");
490
/* vector device implementation */
492
/* Page management */
494
svg_beginpage(gx_device_vector *vdev)
496
gx_device_svg *svg = (gx_device_svg *)vdev;
498
svg_write_header(svg);
500
dprintf1("svg_beginpage (page count %d)\n", svg->page_count);
506
svg_setlinewidth(gx_device_vector *vdev, floatp width)
508
gx_device_svg *svg = (gx_device_svg *)vdev;
510
dprintf1("svg_setlinewidth(%lf)\n", width);
512
svg->linewidth = width;
518
svg_setlinecap(gx_device_vector *vdev, gs_line_cap cap)
520
gx_device_svg *svg = (gx_device_svg *)vdev;
521
const char *linecap_names[] = {"butt", "round", "square",
522
"triangle", "unknown"};
524
if (cap < 0 || cap > gs_cap_unknown)
525
return_error(gs_error_rangecheck);
526
dprintf1("svg_setlinecap(%s)\n", linecap_names[cap]);
534
svg_setlinejoin(gx_device_vector *vdev, gs_line_join join)
536
gx_device_svg *svg = (gx_device_svg *)vdev;
537
const char *linejoin_names[] = {"miter", "round", "bevel",
538
"none", "triangle", "unknown"};
540
if (join < 0 || join > gs_join_unknown)
541
return_error(gs_error_rangecheck);
542
dprintf1("svg_setlinejoin(%s)\n", linejoin_names[join]);
544
svg->linejoin = join;
550
svg_setmiterlimit(gx_device_vector *vdev, floatp limit)
552
dprintf1("svg_setmiterlimit(%lf)\n", limit);
556
svg_setdash(gx_device_vector *vdev, const float *pattern,
557
uint count, floatp offset)
559
dprintf("svg_setdash\n");
563
svg_setlogop(gx_device_vector *vdev, gs_logical_operation_t lop,
564
gs_logical_operation_t diff)
566
dprintf2("svg_setlogop(%u,%u) set logical operation\n",
568
/* SVG can fake some simpler modes, but we ignore this for now. */
575
svg_can_handle_hl_color(gx_device_vector *vdev, const gs_imager_state *pis,
576
const gx_drawing_color * pdc)
578
dprintf("svg_can_handle_hl_color\n");
583
svg_setfillcolor(gx_device_vector *vdev, const gs_imager_state *pis,
584
const gx_drawing_color *pdc)
586
gx_device_svg *svg = (gx_device_svg*)vdev;
589
dprintf("svg_setfillcolor\n");
591
fill = svg_make_color(svg, pdc);
592
if (!fill) return gs_error_VMerror;
593
if (svg->fillcolor && !strcmp(fill, svg->fillcolor))
594
return 0; /* not a new color */
596
/* update our state with the new color */
597
if (svg->fillcolor) gs_free_string(svg->memory, svg->fillcolor, 8,
599
svg->fillcolor = fill;
600
/* request a new group element */
607
svg_setstrokecolor(gx_device_vector *vdev, const gs_imager_state *pis,
608
const gx_drawing_color *pdc)
610
gx_device_svg *svg = (gx_device_svg*)vdev;
613
dprintf("svg_setstrokecolor\n");
615
stroke = svg_make_color(svg, pdc);
616
if (!stroke) return gs_error_VMerror;
617
if (svg->strokecolor && !strcmp(stroke, svg->strokecolor))
618
return 0; /* not a new color */
620
/* update our state with the new color */
621
if (svg->strokecolor) gs_free_string(svg->memory, svg->strokecolor, 8,
622
"svg_setstrokecolor");
623
svg->strokecolor = stroke;
624
/* request a new group element */
631
/* gdev_vector_dopath */
633
static int svg_print_path_type(gx_device_svg *svg, gx_path_type_t type)
635
const char *path_type_names[] = {"winding number", "fill", "stroke",
636
"fill and stroke", "clip"};
639
dprintf2("type %d (%s)", type, path_type_names[type]);
641
dprintf1("type %d", type);
647
svg_dorect(gx_device_vector *vdev, fixed x0, fixed y0,
648
fixed x1, fixed y1, gx_path_type_t type)
650
gx_device_svg *svg = (gx_device_svg *)vdev;
653
if (svg->page_count) return 0; /* hack single-page output */
655
dprintf("svg_dorect ");
656
svg_print_path_type(svg, type);
659
svg_write_state(svg);
661
if (type & gx_path_type_clip) {
662
svg_write(svg, "<clipPath>\n");
665
sprintf(line, "<rect x='%lf' y='%lf' width='%lf' height='%lf'",
666
fixed2float(x0), fixed2float(y0),
667
fixed2float(x1 - x0), fixed2float(y1 - y0));
668
svg_write(svg, line);
669
/* override the inherited stroke attribute if we're not stroking */
670
if (!(type & gx_path_type_stroke) && svg->strokecolor)
671
svg_write(svg, " stroke='none'");
672
/* override the inherited fill attribute if we're not filling */
673
if (!(type & gx_path_type_fill) && svg->fillcolor)
674
svg_write(svg, " fill='none'");
675
svg_write(svg, "/>\n");
677
if (type & gx_path_type_clip) {
678
svg_write(svg, "</clipPath>\n");
685
svg_beginpath(gx_device_vector *vdev, gx_path_type_t type)
687
gx_device_svg *svg = (gx_device_svg *)vdev;
689
if (svg->page_count) return 0; /* hack single-page output */
690
if (!(type & gx_path_type_fill) && !(type & gx_path_type_stroke))
691
return 0; /* skip non-drawing paths for now */
693
dprintf("svg_beginpath ");
694
svg_print_path_type(svg, type);
697
svg_write_state(svg);
698
svg_write(svg, "<path d='");
704
svg_moveto(gx_device_vector *vdev, floatp x0, floatp y0,
705
floatp x, floatp y, gx_path_type_t type)
707
gx_device_svg *svg = (gx_device_svg *)vdev;
708
char line[SVG_LINESIZE];
710
if (svg->page_count) return 0; /* hack single-page output */
711
if (!(type & gx_path_type_fill) && !(type & gx_path_type_stroke))
712
return 0; /* skip non-drawing paths for now */
714
dprintf4("svg_moveto(%lf,%lf,%lf,%lf) ", x0, y0, x, y);
715
svg_print_path_type(svg, type);
718
sprintf(line, " M%lf,%lf", x, y);
719
svg_write(svg, line);
725
svg_lineto(gx_device_vector *vdev, floatp x0, floatp y0,
726
floatp x, floatp y, gx_path_type_t type)
728
gx_device_svg *svg = (gx_device_svg *)vdev;
729
char line[SVG_LINESIZE];
731
if (svg->page_count) return 0; /* hack single-page output */
732
if (!(type & gx_path_type_fill) && !(type & gx_path_type_stroke))
733
return 0; /* skip non-drawing paths for now */
735
dprintf4("svg_lineto(%lf,%lf,%lf,%lf) ", x0,y0, x,y);
736
svg_print_path_type(svg, type);
739
sprintf(line, " L%lf,%lf", x, y);
740
svg_write(svg, line);
746
svg_curveto(gx_device_vector *vdev, floatp x0, floatp y0,
747
floatp x1, floatp y1, floatp x2, floatp y2,
748
floatp x3, floatp y3, gx_path_type_t type)
750
gx_device_svg *svg = (gx_device_svg *)vdev;
751
char line[SVG_LINESIZE];
753
if (svg->page_count) return 0; /* hack single-page output */
754
if (!(type & gx_path_type_fill) && !(type & gx_path_type_stroke))
755
return 0; /* skip non-drawing paths for now */
757
dprintf8("svg_curveto(%lf,%lf, %lf,%lf, %lf,%lf, %lf,%lf) ",
758
x0,y0, x1,y1, x2,y2, x3,y3);
759
svg_print_path_type(svg, type);
762
sprintf(line, " C%lf,%lf %lf,%lf %lf,%lf", x1,y1, x2,y2, x3,y3);
763
svg_write(svg, line);
769
svg_closepath(gx_device_vector *vdev, floatp x, floatp y,
770
floatp x_start, floatp y_start, gx_path_type_t type)
772
gx_device_svg *svg = (gx_device_svg *)vdev;
774
if (svg->page_count) return 0; /* hack single-page output */
775
if (!(type & gx_path_type_fill) && !(type & gx_path_type_stroke))
776
return 0; /* skip non-drawing paths for now */
778
dprintf("svg_closepath ");
779
svg_print_path_type(svg, type);
782
svg_write(svg, " z");
788
svg_endpath(gx_device_vector *vdev, gx_path_type_t type)
790
gx_device_svg *svg = (gx_device_svg *)vdev;
791
char line[SVG_LINESIZE];
793
if (svg->page_count) return 0; /* hack single-page output */
794
if (!(type & gx_path_type_fill) && !(type & gx_path_type_stroke))
795
return 0; /* skip non-drawing paths for now */
797
dprintf("svg_endpath ");
798
svg_print_path_type(svg, type);
801
/* close the path data attribute */
804
/* override the inherited stroke attribute if we're not stroking */
805
if (!(type & gx_path_type_stroke) && svg->strokecolor) {
806
svg_write(svg, " stroke='none'");
808
/* override the inherited fill attribute if we're not filling */
809
if (!(type & gx_path_type_fill) && svg->fillcolor) {
810
svg_write(svg, " fill='none'");
813
svg_write(svg, "/>\n");