1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
3 *
4 * render_svg.c - an SVG renderer for dia, based on render_eps.c
5 * Copyright (C) 1999, 2000 James Henstridge
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 */
21 #include <config.h>
22
23 #include <stdlib.h>
24 #include <string.h>
25 #include <time.h>
26 #include <math.h>
27 #include <errno.h>
28 #ifdef HAVE_UNISTD_H
29 #include <unistd.h>
30 #endif
31
32 #include <glib.h>
33 #include <glib/gstdio.h>
34
35 #include <libxml/entities.h>
36 #include <libxml/tree.h>
37 #include <libxml/xmlmemory.h>
38
39 #include "geometry.h"
40 #include "diasvgrenderer.h"
41 #include "filter.h"
42 #include "intl.h"
43 #include "message.h"
44 #include "diagramdata.h"
45 #include "dia_xml_libxml.h"
46 #include "object.h"
47 #include "textline.h"
48
49 G_BEGIN_DECLS
50
51 #define SVG_TYPE_RENDERER (svg_renderer_get_type ())
52 #define SVG_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SVG_TYPE_RENDERER, SvgRenderer))
53 #define SVG_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SVG_TYPE_RENDERER, SvgRendererClass))
54 #define SVG_IS_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SVG_TYPE_RENDERER))
55 #define SVG_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SVG_TYPE_RENDERER, SvgRendererClass))
56
57 GType svg_renderer_get_type (void) G_GNUC_CONST;
58
59 typedef struct _SvgRenderer SvgRenderer;
60 typedef struct _SvgRendererClass SvgRendererClass;
61
62 struct _SvgRenderer
63 {
64 DiaSvgRenderer parent_instance;
65
66 /* track the parents while grouping in draw_object() */
67 GQueue *parents;
68 };
69
70 struct _SvgRendererClass
71 {
72 DiaSvgRendererClass parent_class;
73 };
74
75 G_END_DECLS
76
77 static DiaSvgRenderer *new_svg_renderer(DiagramData *data, const char *filename);
78
79 static void draw_object (DiaRenderer *renderer,
80 DiaObject *object);
81 static void draw_rounded_rect (DiaRenderer *renderer,
82 Point *ul_corner, Point *lr_corner,
83 Color *colour, real rounding);
84 static void fill_rounded_rect (DiaRenderer *renderer,
85 Point *ul_corner, Point *lr_corner,
86 Color *colour, real rounding);
87 static void draw_string (DiaRenderer *self,
88 const char *text,
89 Point *pos, Alignment alignment,
90 Color *colour);
91 static void draw_text_line (DiaRenderer *self, TextLine *text_line,
92 Point *pos, Alignment alignment, Color *colour);
93 static void draw_text (DiaRenderer *self, Text *text);
94
95 static void svg_renderer_class_init (SvgRendererClass *klass);
96
97 static gpointer parent_class = NULL;
98
99 /* constructor */
100 static void
svg_renderer_init(GTypeInstance * self,gpointer g_class)101 svg_renderer_init (GTypeInstance *self, gpointer g_class)
102 {
103 SvgRenderer *renderer = SVG_RENDERER (self);
104
105 renderer->parents = g_queue_new ();
106 }
107
108 GType
svg_renderer_get_type(void)109 svg_renderer_get_type (void)
110 {
111 static GType object_type = 0;
112
113 if (!object_type)
114 {
115 static const GTypeInfo object_info =
116 {
117 sizeof (SvgRendererClass),
118 (GBaseInitFunc) NULL,
119 (GBaseFinalizeFunc) NULL,
120 (GClassInitFunc) svg_renderer_class_init,
121 NULL, /* class_finalize */
122 NULL, /* class_data */
123 sizeof (SvgRenderer),
124 0, /* n_preallocs */
125 svg_renderer_init /* init */
126 };
127
128 object_type = g_type_register_static (DIA_TYPE_SVG_RENDERER,
129 "SvgRenderer",
130 &object_info, 0);
131 }
132
133 return object_type;
134 }
135
136 static void
begin_render(DiaRenderer * self)137 begin_render (DiaRenderer *self)
138 {
139 SvgRenderer *renderer = SVG_RENDERER (self);
140 g_assert (g_queue_is_empty (renderer->parents));
141 DIA_RENDERER_CLASS (parent_class)->begin_render (DIA_RENDERER (self));
142 }
143
144 static void
end_render(DiaRenderer * self)145 end_render (DiaRenderer *self)
146 {
147 SvgRenderer *renderer = SVG_RENDERER (self);
148 g_assert (g_queue_is_empty (renderer->parents));
149 DIA_RENDERER_CLASS (parent_class)->end_render (DIA_RENDERER (self));
150 }
151
152 /* destructor */
153 static void
svg_renderer_finalize(GObject * object)154 svg_renderer_finalize (GObject *object)
155 {
156 SvgRenderer *svg_renderer = SVG_RENDERER (object);
157
158 g_queue_free (svg_renderer->parents);
159
160 G_OBJECT_CLASS (parent_class)->finalize (object);
161 }
162
163 static void
svg_renderer_class_init(SvgRendererClass * klass)164 svg_renderer_class_init (SvgRendererClass *klass)
165 {
166 GObjectClass *object_class = G_OBJECT_CLASS (klass);
167 DiaRendererClass *renderer_class = DIA_RENDERER_CLASS (klass);
168
169 parent_class = g_type_class_peek_parent (klass);
170
171 object_class->finalize = svg_renderer_finalize;
172
173 renderer_class->begin_render = begin_render;
174 renderer_class->end_render = end_render;
175 renderer_class->draw_object = draw_object;
176 renderer_class->draw_rounded_rect = draw_rounded_rect;
177 renderer_class->fill_rounded_rect = fill_rounded_rect;
178 renderer_class->draw_string = draw_string;
179 renderer_class->draw_text = draw_text;
180 renderer_class->draw_text_line = draw_text_line;
181 }
182
183
184 static DiaSvgRenderer *
new_svg_renderer(DiagramData * data,const char * filename)185 new_svg_renderer(DiagramData *data, const char *filename)
186 {
187 DiaSvgRenderer *renderer;
188 SvgRenderer *svg_renderer;
189 FILE *file;
190 gchar buf[512];
191 time_t time_now;
192 Rectangle *extent;
193 const char *name;
194 xmlDtdPtr dtd;
195
196 file = g_fopen(filename, "w");
197
198 if (file==NULL) {
199 message_error(_("Can't open output file %s: %s\n"),
200 dia_message_filename(filename), strerror(errno));
201 return NULL;
202 }
203 fclose(file);
204
205 /* we need access to our base object */
206 renderer = DIA_SVG_RENDERER (g_object_new(SVG_TYPE_RENDERER, NULL));
207
208 renderer->filename = g_strdup(filename);
209
210 renderer->dash_length = 1.0;
211 renderer->dot_length = 0.2;
212 renderer->saved_line_style = LINESTYLE_SOLID;
213 /* apparently most svg readers don't like small values, especially not in the viewBox attribute */
214 renderer->scale = 20.0;
215
216 /* set up the root node */
217 renderer->doc = xmlNewDoc((const xmlChar *)"1.0");
218 renderer->doc->encoding = xmlStrdup((const xmlChar *)"UTF-8");
219 renderer->doc->standalone = FALSE;
220 dtd = xmlCreateIntSubset(renderer->doc, (const xmlChar *)"svg",
221 (const xmlChar *)"-//W3C//DTD SVG 1.0//EN",
222 (const xmlChar *)"http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd");
223 xmlAddChild((xmlNodePtr) renderer->doc, (xmlNodePtr) dtd);
224 renderer->root = xmlNewDocNode(renderer->doc, NULL, (const xmlChar *)"svg", NULL);
225 xmlAddSibling(renderer->doc->children, (xmlNodePtr) renderer->root);
226
227 /* add namespaces to make strict parsers happy, e.g. Firefox */
228 svg_renderer = SVG_RENDERER (renderer);
229
230 /* set the extents of the SVG document */
231 extent = &data->extents;
232 g_snprintf(buf, sizeof(buf), "%dcm",
233 (int)ceil((extent->right - extent->left)));
234 xmlSetProp(renderer->root, (const xmlChar *)"width", (xmlChar *) buf);
235 g_snprintf(buf, sizeof(buf), "%dcm",
236 (int)ceil((extent->bottom - extent->top)));
237 xmlSetProp(renderer->root, (const xmlChar *)"height", (xmlChar *) buf);
238 g_snprintf(buf, sizeof(buf), "%d %d %d %d",
239 (int)floor(extent->left * renderer->scale), (int)floor(extent->top * renderer->scale),
240 (int)ceil((extent->right - extent->left) * renderer->scale),
241 (int)ceil((extent->bottom - extent->top) * renderer->scale));
242 xmlSetProp(renderer->root, (const xmlChar *)"viewBox", (xmlChar *) buf);
243 xmlSetProp(renderer->root,(const xmlChar *)"xmlns", (const xmlChar *)"http://www.w3.org/2000/svg");
244 xmlSetProp(renderer->root,(const xmlChar *)"xmlns", (const xmlChar *)"http://www.w3.org/2000/svg");
245 xmlSetProp(renderer->root,(const xmlChar *)"xmlns:xlink", (const xmlChar *)"http://www.w3.org/1999/xlink");
246
247 time_now = time(NULL);
248 name = g_get_user_name();
249
250 #if 0
251 /* some comments at the top of the file ... */
252 xmlAddChild(renderer->root, xmlNewText("\n"));
253 xmlAddChild(renderer->root, xmlNewComment("Dia-Version: "VERSION));
254 xmlAddChild(renderer->root, xmlNewText("\n"));
255 g_snprintf(buf, sizeof(buf), "File: %s", dia->filename);
256 xmlAddChild(renderer->root, xmlNewComment(buf));
257 xmlAddChild(renderer->root, xmlNewText("\n"));
258 g_snprintf(buf, sizeof(buf), "Date: %s", ctime(&time_now));
259 buf[strlen(buf)-1] = '\0'; /* remove the trailing new line */
260 xmlAddChild(renderer->root, xmlNewComment(buf));
261 xmlAddChild(renderer->root, xmlNewText("\n"));
262 g_snprintf(buf, sizeof(buf), "For: %s", name);
263 xmlAddChild(renderer->root, xmlNewComment(buf));
264 xmlAddChild(renderer->root, xmlNewText("\n\n"));
265
266 xmlNewChild(renderer->root, NULL, "title", dia->filename);
267 #endif
268
269 return renderer;
270 }
271
272 static void
draw_object(DiaRenderer * self,DiaObject * object)273 draw_object(DiaRenderer *self,
274 DiaObject *object)
275 {
276 /* wrap in <g></g>
277 * We could try to be smart and count the objects we using for the object.
278 * If it is only one the grouping is superfluous and should be removed.
279 */
280 DiaSvgRenderer *renderer = DIA_SVG_RENDERER (self);
281 SvgRenderer *svg_renderer = SVG_RENDERER (self);
282 int n_children = 0;
283 xmlNodePtr child, group;
284
285 g_queue_push_tail (svg_renderer->parents, renderer->root);
286 /* modifying the root pointer so everything below us gets into the new node */
287 renderer->root = group = xmlNewNode (renderer->svg_name_space, (const xmlChar *)"g");
288
289 object->ops->draw(object, DIA_RENDERER (renderer));
290
291 /* no easy way to count? */
292 child = renderer->root->children;
293 while (child != NULL) {
294 child = child->next;
295 ++n_children;
296 }
297 renderer->root = g_queue_pop_tail (svg_renderer->parents);
298 /* if there is only one element added to the group node unpack it again */
299 if (1 == n_children) {
300 xmlAddChild (renderer->root, group->children);
301 xmlUnlinkNode (group); /* dont free the children */
302 xmlFree (group);
303 } else {
304 xmlAddChild (renderer->root, group);
305 }
306 }
307
308 static void
draw_rounded_rect(DiaRenderer * self,Point * ul_corner,Point * lr_corner,Color * colour,real rounding)309 draw_rounded_rect(DiaRenderer *self,
310 Point *ul_corner, Point *lr_corner,
311 Color *colour, real rounding)
312 {
313 DiaSvgRenderer *renderer = DIA_SVG_RENDERER (self);
314 xmlNodePtr node;
315 gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
316
317 node = xmlNewChild(renderer->root, NULL, (const xmlChar *)"rect", NULL);
318
319 xmlSetProp(node, (const xmlChar *)"style",
320 (const xmlChar *) DIA_SVG_RENDERER_GET_CLASS(self)->get_draw_style(renderer, colour));
321
322 g_ascii_formatd(buf, sizeof(buf), "%g", ul_corner->x * renderer->scale);
323 xmlSetProp(node, (const xmlChar *)"x", (xmlChar *) buf);
324 g_ascii_formatd(buf, sizeof(buf), "%g", ul_corner->y * renderer->scale);
325 xmlSetProp(node, (const xmlChar *)"y", (xmlChar *) buf);
326 g_ascii_formatd(buf, sizeof(buf), "%g", (lr_corner->x - ul_corner->x) * renderer->scale);
327 xmlSetProp(node, (const xmlChar *)"width", (xmlChar *) buf);
328 g_ascii_formatd(buf, sizeof(buf), "%g", (lr_corner->y - ul_corner->y) * renderer->scale);
329 xmlSetProp(node, (const xmlChar *)"height", (xmlChar *) buf);
330 g_ascii_formatd(buf, sizeof(buf),"%g", (rounding * renderer->scale));
331 xmlSetProp(node, (const xmlChar *)"rx", (xmlChar *) buf);
332 xmlSetProp(node, (const xmlChar *)"ry", (xmlChar *) buf);
333 }
334
335 static void
fill_rounded_rect(DiaRenderer * self,Point * ul_corner,Point * lr_corner,Color * colour,real rounding)336 fill_rounded_rect(DiaRenderer *self,
337 Point *ul_corner, Point *lr_corner,
338 Color *colour, real rounding)
339 {
340 DiaSvgRenderer *renderer = DIA_SVG_RENDERER (self);
341 xmlNodePtr node;
342 gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
343
344 node = xmlNewChild(renderer->root, NULL, (const xmlChar *)"rect", NULL);
345
346 xmlSetProp(node, (const xmlChar *)"style",
347 (const xmlChar *) DIA_SVG_RENDERER_GET_CLASS(self)->get_fill_style(renderer, colour));
348
349 g_ascii_formatd(buf, sizeof(buf), "%g", (ul_corner->x * renderer->scale));
350 xmlSetProp(node, (const xmlChar *)"x", (xmlChar *) buf);
351 g_ascii_formatd(buf, sizeof(buf), "%g", (ul_corner->y * renderer->scale));
352 xmlSetProp(node, (const xmlChar *)"y", (xmlChar *) buf);
353 g_ascii_formatd(buf, sizeof(buf), "%g", (lr_corner->x - ul_corner->x) * renderer->scale);
354 xmlSetProp(node, (const xmlChar *)"width", (xmlChar *) buf);
355 g_ascii_formatd(buf, sizeof(buf), "%g", (lr_corner->y - ul_corner->y) * renderer->scale);
356 xmlSetProp(node, (const xmlChar *)"height", (xmlChar *) buf);
357 g_ascii_formatd(buf, sizeof(buf),"%g", (rounding * renderer->scale));
358 xmlSetProp(node, (const xmlChar *)"rx", (xmlChar *) buf);
359 xmlSetProp(node, (const xmlChar *)"ry", (xmlChar *) buf);
360 }
361
362 #define dia_svg_dtostr(buf,d) \
363 g_ascii_formatd(buf,sizeof(buf),"%g",(d)*renderer->scale)
364
365 static void
node_set_text_style(xmlNodePtr node,DiaSvgRenderer * renderer,const DiaFont * font,real font_height,Alignment alignment,Color * colour)366 node_set_text_style (xmlNodePtr node,
367 DiaSvgRenderer *renderer,
368 const DiaFont *font,
369 real font_height,
370 Alignment alignment,
371 Color *colour)
372 {
373 char *style, *tmp;
374 real saved_width;
375 gchar d_buf[G_ASCII_DTOSTR_BUF_SIZE];
376 DiaSvgRendererClass *svg_renderer_class = DIA_SVG_RENDERER_GET_CLASS (renderer);
377 /* SVG font-size is the (line-) height, from SVG Spec:
378 * ... property refers to the size of the font from baseline to baseline when multiple lines of text are set ...
379 so we should be able to use font_height directly instead of:
380 */
381 real font_size = dia_font_get_size (font) * (font_height / dia_font_get_height (font));
382 /* ... but at least Inkscape and Firefox would produce the wrong font-size */
383 const gchar *family = dia_font_get_family(font);
384
385 saved_width = renderer->linewidth;
386 renderer->linewidth = 0.001;
387 style = (char*)svg_renderer_class->get_fill_style(renderer, colour);
388 /* return value must not be freed */
389 renderer->linewidth = saved_width;
390 /* This is going to break for non-LTR texts, as SVG thinks 'start' is
391 * 'right' for those.
392 */
393 switch (alignment) {
394 case ALIGN_LEFT:
395 style = g_strconcat(style, ";text-anchor:start", NULL);
396 break;
397 case ALIGN_CENTER:
398 style = g_strconcat(style, ";text-anchor:middle", NULL);
399 break;
400 case ALIGN_RIGHT:
401 style = g_strconcat(style, ";text-anchor:end", NULL);
402 break;
403 }
404 #if 0 /* would need a unit according to https://bugzilla.mozilla.org/show_bug.cgi?id=707071#c4 */
405 tmp = g_strdup_printf("%s;font-size:%s", style,
406 dia_svg_dtostr(d_buf, font_size) );
407 g_free (style);
408 style = tmp;
409 #else
410 /* font-size as attribute can work like the other length w/o unit */
411 dia_svg_dtostr(d_buf, font_size);
412 xmlSetProp(node, (const xmlChar *)"font-size", (xmlChar *) d_buf);
413 #endif
414
415 if (font) {
416 tmp = g_strdup_printf("%s;font-family:%s;font-style:%s;"
417 "font-weight:%s",style,
418 strcmp(family, "sans") == 0 ? "sans-serif" : family,
419 dia_font_get_slant_string(font),
420 dia_font_get_weight_string(font));
421 g_free(style);
422 style = tmp;
423 }
424
425 /* have to do something about fonts here ... */
426
427 xmlSetProp(node, (xmlChar *)"style", (xmlChar *)style);
428 g_free(style);
429 }
430
431 static void
draw_string(DiaRenderer * self,const char * text,Point * pos,Alignment alignment,Color * colour)432 draw_string(DiaRenderer *self,
433 const char *text,
434 Point *pos, Alignment alignment,
435 Color *colour)
436 {
437 DiaSvgRenderer *renderer = DIA_SVG_RENDERER (self);
438 xmlNodePtr node;
439 gchar d_buf[G_ASCII_DTOSTR_BUF_SIZE];
440
441 node = xmlNewChild(renderer->root, renderer->svg_name_space, (xmlChar *)"text", (xmlChar *)text);
442
443 node_set_text_style(node, renderer, self->font, self->font_height, alignment, colour);
444
445 dia_svg_dtostr(d_buf, pos->x);
446 xmlSetProp(node, (xmlChar *)"x", (xmlChar *)d_buf);
447 dia_svg_dtostr(d_buf, pos->y);
448 xmlSetProp(node, (xmlChar *)"y", (xmlChar *)d_buf);
449 }
450
451 static void
draw_text_line(DiaRenderer * self,TextLine * text_line,Point * pos,Alignment alignment,Color * colour)452 draw_text_line(DiaRenderer *self, TextLine *text_line,
453 Point *pos, Alignment alignment, Color *colour)
454 {
455 DiaSvgRenderer *renderer = DIA_SVG_RENDERER (self);
456 xmlNodePtr node;
457 DiaFont *font = text_line_get_font(text_line); /* no reference? */
458 real font_height = text_line_get_height(text_line);
459 gchar d_buf[G_ASCII_DTOSTR_BUF_SIZE];
460
461 node = xmlNewChild(renderer->root, renderer->svg_name_space, (const xmlChar *)"text",
462 (xmlChar *) text_line_get_string(text_line));
463
464 /* not using the renderers font but the textlines */
465 node_set_text_style(node, renderer, font, font_height, alignment, colour);
466
467 dia_svg_dtostr(d_buf, pos->x);
468 xmlSetProp(node, (const xmlChar *)"x", (xmlChar *) d_buf);
469 dia_svg_dtostr(d_buf, pos->y);
470 xmlSetProp(node, (const xmlChar *)"y", (xmlChar *) d_buf);
471 dia_svg_dtostr(d_buf, text_line_get_width(text_line));
472 xmlSetProp(node, (const xmlChar*)"textLength", (xmlChar *) d_buf);
473 }
474
475 static void
draw_text(DiaRenderer * self,Text * text)476 draw_text (DiaRenderer *self, Text *text)
477 {
478 DiaSvgRenderer *renderer = DIA_SVG_RENDERER (self);
479 Point pos = text->position;
480 int i;
481 xmlNodePtr node_text, node_tspan;
482 gchar d_buf[G_ASCII_DTOSTR_BUF_SIZE];
483
484 node_text = xmlNewChild(renderer->root, renderer->svg_name_space, (const xmlChar *)"text", NULL);
485 /* text 'global' properties */
486 node_set_text_style(node_text, renderer, text->font, text->height, text->alignment, &text->color);
487 dia_svg_dtostr(d_buf, pos.x);
488 xmlSetProp(node_text, (const xmlChar *)"x", (xmlChar *) d_buf);
489 dia_svg_dtostr(d_buf, pos.y);
490 xmlSetProp(node_text, (const xmlChar *)"y", (xmlChar *) d_buf);
491
492 pos = text->position;
493 for (i=0;i<text->numlines;i++) {
494 TextLine *text_line = text->lines[i];
495
496 node_tspan = xmlNewTextChild(node_text, renderer->svg_name_space, (const xmlChar *)"tspan",
497 (const xmlChar *)text_line_get_string(text_line));
498 dia_svg_dtostr(d_buf, pos.x);
499 xmlSetProp(node_tspan, (const xmlChar *)"x", (xmlChar *) d_buf);
500 dia_svg_dtostr(d_buf, pos.y);
501 xmlSetProp(node_tspan, (const xmlChar *)"y", (xmlChar *) d_buf);
502
503 pos.y += text->height;
504 }
505 }
506
507 static void
export_svg(DiagramData * data,const gchar * filename,const gchar * diafilename,void * user_data)508 export_svg(DiagramData *data, const gchar *filename,
509 const gchar *diafilename, void* user_data)
510 {
511 DiaSvgRenderer *renderer;
512
513 if ((renderer = new_svg_renderer(data, filename))) {
514 data_render(data, DIA_RENDERER(renderer), NULL, NULL, NULL);
515 g_object_unref(renderer);
516 }
517 }
518
519 static const gchar *extensions[] = { "svg", NULL };
520 DiaExportFilter svg_export_filter = {
521 N_("Scalable Vector Graphics"),
522 extensions,
523 export_svg
524 };
525