1 /* -*- Mode: C; c-basic-offset: 4 -*- */
2 /* Dia -- an diagram creation/manipulation program
3  * Copyright (C) 1998 Alexander Larsson
4  *
5  * shape-export.c: shape export filter for dia
6  * Copyright (C) 2000 Steffen Macke
7  *
8  * Major refactoring while porting to use DiaSvgRenderer
9  * Copyright (C) 2002 Hans Breuer
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24  */
25 /*
26  * TODO:
27  *   - While porting to use DiaSvgRenderer I removved all connection point
28        adding to fill_* methods with the assumption that they always have
29        a corresponding draw_* method call where the connection points are
30        already added. Correct me if I'm wrong ...                    --hb
31  */
32 #include <config.h>
33 
34 #include <stdlib.h>
35 #include <string.h>
36 #include <time.h>
37 #include <math.h>
38 #include <errno.h>
39 #ifdef HAVE_UNISTD_H
40 #include <unistd.h>
41 #endif
42 
43 #include <glib/gstdio.h>
44 
45 /* the dots per centimetre to render this diagram at */
46 /* this matches the setting `100%' setting in dia. */
47 #define DPCM 20
48 
49 #include <libxml/entities.h>
50 #include <libxml/tree.h>
51 #include <libxml/xmlmemory.h>
52 #include <libxml/parser.h> /* xmlStrdup */
53 #include "dia_xml.h"
54 #include "dia_xml_libxml.h"
55 #include "geometry.h"
56 #include "diasvgrenderer.h"
57 #include "filter.h"
58 #include "intl.h"
59 #include "message.h"
60 #include "diagramdata.h"
61 
62 G_BEGIN_DECLS
63 
64 #define SHAPE_TYPE_RENDERER           (shape_renderer_get_type ())
65 #define SHAPE_RENDERER(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHAPE_TYPE_RENDERER, ShapeRenderer))
66 #define SHAPE_RENDERER_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST ((klass), SHAPE_TYPE_RENDERER, ShapeRendererClass))
67 #define SHAPE_IS_RENDERER(obj)        (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHAPE_TYPE_RENDERER))
68 #define SHAPE_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHAPE_TYPE_RENDERER, ShapeRendererClass))
69 
70 GType shape_renderer_get_type (void) G_GNUC_CONST;
71 
72 typedef struct _ShapeRenderer ShapeRenderer;
73 typedef struct _ShapeRendererClass ShapeRendererClass;
74 
75 struct _ShapeRenderer
76 {
77   DiaSvgRenderer parent_instance;
78 
79   xmlNodePtr connection_root;
80 };
81 
82 struct _ShapeRendererClass
83 {
84   DiaSvgRendererClass parent_class;
85 };
86 
87 G_END_DECLS
88 
89 static DiaSvgRenderer *new_shape_renderer(DiagramData *data, const char *filename);
90 
91 /* DiaSvgRenderer members */
92 static void end_render(DiaRenderer *self);
93 
94 static void draw_line(DiaRenderer *self,
95 		      Point *start, Point *end,
96 		      Color *line_colour);
97 static void draw_polyline(DiaRenderer *self,
98 			  Point *points, int num_points,
99 			  Color *line_colour);
100 static void draw_polygon(DiaRenderer *self,
101 			 Point *points, int num_points,
102 			 Color *line_colour);
103 static void draw_rect(DiaRenderer *self,
104 		      Point *ul_corner, Point *lr_corner,
105 		      Color *colour);
106 static void draw_ellipse(DiaRenderer *self,
107 			 Point *center,
108 			 real width, real height,
109 			 Color *colour);
110 
111 /* helper functions */
112 static void add_connection_point(ShapeRenderer *renderer,
113                                  Point *point);
114 static void add_rectangle_connection_points(ShapeRenderer *renderer,
115                                             Point *ul_corner, Point *lr_corner);
116 static void add_ellipse_connection_points(ShapeRenderer *renderer,
117                                           Point *center,
118                                           real width, real height);
119 
120 static DiaSvgRenderer *
new_shape_renderer(DiagramData * data,const char * filename)121 new_shape_renderer(DiagramData *data, const char *filename)
122 {
123   ShapeRenderer *shape_renderer;
124   DiaSvgRenderer *renderer;
125   FILE *file;
126   char *point;
127   xmlNsPtr name_space;
128   xmlNodePtr xml_node_ptr;
129   gint i;
130   gchar *png_filename;
131   char *shapename, *dirname, *fullname;
132   char *sheetname;
133 
134   file = g_fopen(filename, "w");
135 
136   if (file==NULL) {
137       message_error(_("Can't open output file %s: %s\n"),
138 		    dia_message_filename(filename), strerror(errno));
139     return NULL;
140   }
141   fclose(file);
142 
143   shape_renderer = g_object_new(SHAPE_TYPE_RENDERER, NULL);
144   renderer = DIA_SVG_RENDERER (shape_renderer);
145 
146   renderer->filename = g_strdup(filename);
147 
148   renderer->dash_length = 1.0;
149   renderer->dot_length = 0.2;
150   renderer->saved_line_style = LINESTYLE_SOLID;
151   /* keep everything unscaled, i.e. in Dia's scale default */
152   renderer->scale = 1.0;
153 
154   /* set up the root node */
155   renderer->doc = xmlNewDoc((const xmlChar *)"1.0");
156   renderer->doc->encoding = xmlStrdup((const xmlChar *)"UTF-8");
157   renderer->root = xmlNewDocNode(renderer->doc, NULL, (const xmlChar *)"shape", NULL);
158   name_space = xmlNewNs(renderer->root,
159                         (const xmlChar *)"http://www.daa.com.au/~james/dia-shape-ns", NULL);
160 
161   renderer->svg_name_space = xmlNewNs(renderer->root,
162                                       (const xmlChar *)"http://www.w3.org/2000/svg", (const xmlChar *)"svg");
163   renderer->doc->xmlRootNode = renderer->root;
164 
165   dirname = g_path_get_dirname(filename);
166   sheetname = g_path_get_basename(dirname);
167   shapename = g_strndup(g_basename(filename), strlen(g_basename(filename))-6);
168   fullname = g_strdup_printf ("%s - %s", sheetname, shapename);
169   g_free(dirname);
170   g_free(sheetname);
171   g_free(shapename);
172 
173   xmlNewChild(renderer->root, NULL, (const xmlChar *)"name", (xmlChar *) fullname);
174   g_free(fullname);
175   point = strrchr(filename, '.');
176   i = (int)(point-filename);
177   point = g_strndup(filename, i);
178   png_filename = g_strdup_printf("%s.png",point);
179   g_free(point);
180   xmlNewChild(renderer->root, NULL, (const xmlChar *)"icon", (xmlChar *) g_basename(png_filename));
181   g_free(png_filename);
182   shape_renderer->connection_root = xmlNewChild(renderer->root, NULL, (const xmlChar *)"connections", NULL);
183   xml_node_ptr = xmlNewChild(renderer->root, NULL, (const xmlChar *)"aspectratio",NULL);
184   xmlSetProp(xml_node_ptr, (const xmlChar *)"type", (const xmlChar *)"fixed");
185   renderer->root = xmlNewChild(renderer->root, renderer->svg_name_space, (const xmlChar *)"svg", NULL);
186 
187   return renderer;
188 }
189 
190 /* GObject stuff */
191 static void shape_renderer_class_init (ShapeRendererClass *klass);
192 
193 static gpointer parent_class = NULL;
194 
195 GType
shape_renderer_get_type(void)196 shape_renderer_get_type (void)
197 {
198   static GType object_type = 0;
199 
200   if (!object_type)
201     {
202       static const GTypeInfo object_info =
203       {
204         sizeof (ShapeRendererClass),
205         (GBaseInitFunc) NULL,
206         (GBaseFinalizeFunc) NULL,
207         (GClassInitFunc) shape_renderer_class_init,
208         NULL,           /* class_finalize */
209         NULL,           /* class_data */
210         sizeof (ShapeRenderer),
211         0,              /* n_preallocs */
212 	NULL            /* init */
213       };
214 
215       object_type = g_type_register_static (DIA_TYPE_SVG_RENDERER,
216                                             "ShapeRenderer",
217                                             &object_info, 0);
218     }
219 
220   return object_type;
221 }
222 
223 static void
shape_renderer_finalize(GObject * object)224 shape_renderer_finalize (GObject *object)
225 {
226   G_OBJECT_CLASS (parent_class)->finalize (object);
227 }
228 
229 static void
shape_renderer_class_init(ShapeRendererClass * klass)230 shape_renderer_class_init (ShapeRendererClass *klass)
231 {
232   GObjectClass *object_class = G_OBJECT_CLASS (klass);
233   DiaRendererClass *renderer_class = DIA_RENDERER_CLASS (klass);
234 
235   parent_class = g_type_class_peek_parent (klass);
236 
237   object_class->finalize = shape_renderer_finalize;
238 
239   /* dia svg renderer overwrites */
240   renderer_class->end_render = end_render;
241   renderer_class->draw_line = draw_line;
242   renderer_class->draw_polyline = draw_polyline;
243   renderer_class->draw_polygon = draw_polygon;
244   renderer_class->draw_rect = draw_rect;
245   renderer_class->draw_ellipse = draw_ellipse;
246 }
247 
248 /* member implementations */
249 /* full overwrite */
250 static void
end_render(DiaRenderer * self)251 end_render(DiaRenderer *self)
252 {
253   DiaSvgRenderer *renderer = DIA_SVG_RENDERER (self);
254   int old_blanks_default = pretty_formated_xml;
255 
256   /* FIXME HACK: we always want nice readable shape files,
257    *  but toggling it by a global var is ugly   --hb
258    */
259   pretty_formated_xml = TRUE;
260 
261   g_free(renderer->linestyle);
262   renderer->linestyle = NULL;
263 
264   xmlSetDocCompressMode(renderer->doc, 0);
265   xmlDiaSaveFile(renderer->filename, renderer->doc);
266   g_free(renderer->filename);
267   renderer->filename = NULL;
268   xmlFreeDoc(renderer->doc);
269   pretty_formated_xml = old_blanks_default;
270 }
271 
272 static void
add_connection_point(ShapeRenderer * renderer,Point * point)273 add_connection_point (ShapeRenderer *renderer,
274                       Point *point)
275 {
276   xmlNodePtr node;
277   gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
278 
279   node = xmlNewChild(renderer->connection_root, NULL, (const xmlChar *)"point", NULL);
280   g_ascii_formatd(buf, sizeof(buf), "%g", point->x);
281   xmlSetProp(node, (const xmlChar *)"x", (xmlChar *) buf);
282   g_ascii_formatd(buf, sizeof(buf), "%g", point->y);
283   xmlSetProp(node, (const xmlChar *)"y", (xmlChar *) buf);
284 }
285 
286 static void
draw_line(DiaRenderer * self,Point * start,Point * end,Color * line_colour)287 draw_line(DiaRenderer *self,
288 	  Point *start, Point *end,
289 	  Color *line_colour)
290 {
291   Point center;
292   ShapeRenderer *renderer = SHAPE_RENDERER(self);
293 
294   /* use base class implementation */
295   DIA_RENDERER_CLASS(parent_class)->draw_line (self, start, end, line_colour);
296 
297   /* do our own stuff */
298   add_connection_point(renderer, start);
299   add_connection_point(renderer, end);
300   center.x = (start->x + end->x)/2;
301   center.y = (start->y + end->y)/2;
302   add_connection_point(renderer, &center);
303 
304 }
305 
306 /* complete overwrite, code duplication with base class */
307 static void
draw_polyline(DiaRenderer * self,Point * points,int num_points,Color * line_colour)308 draw_polyline(DiaRenderer *self,
309 	      Point *points, int num_points,
310 	      Color *line_colour)
311 {
312   DiaSvgRenderer *renderer = DIA_SVG_RENDERER (self);
313   int i;
314   xmlNodePtr node;
315   GString *str;
316   Point center;
317   gchar px_buf[G_ASCII_DTOSTR_BUF_SIZE];
318   gchar py_buf[G_ASCII_DTOSTR_BUF_SIZE];
319 
320   node = xmlNewChild(renderer->root, renderer->svg_name_space, (const xmlChar *)"polyline", NULL);
321 
322   xmlSetProp(node, (const xmlChar *)"style",
323              (xmlChar *) DIA_SVG_RENDERER_GET_CLASS(renderer)->get_draw_style(renderer, line_colour));
324 
325   str = g_string_new(NULL);
326   for (i = 0; i < num_points; i++) {
327     g_string_append_printf(str, "%s,%s ",
328 			   g_ascii_formatd(px_buf, sizeof(px_buf), "%g", points[i].x),
329 			   g_ascii_formatd(py_buf, sizeof(py_buf), "%g", points[i].y) );
330     add_connection_point(SHAPE_RENDERER(self), &points[i]);
331   }
332   xmlSetProp(node, (const xmlChar *)"points", (xmlChar *) str->str);
333   g_string_free(str, TRUE);
334   for(i = 1; i < num_points; i++) {
335     center.x = (points[i].x + points[i-1].x)/2;
336     center.y = (points[i].y + points[i-1].y)/2;
337     add_connection_point(SHAPE_RENDERER(renderer), &center);
338   }
339 }
340 
341 
342 /* complete overwrite, necessary code duplication */
343 static void
draw_polygon(DiaRenderer * self,Point * points,int num_points,Color * line_colour)344 draw_polygon(DiaRenderer *self,
345 	      Point *points, int num_points,
346 	      Color *line_colour)
347 {
348   DiaSvgRenderer *renderer = DIA_SVG_RENDERER (self);
349   int i;
350   xmlNodePtr node;
351   GString *str;
352   Point center;
353   gchar px_buf[G_ASCII_DTOSTR_BUF_SIZE];
354   gchar py_buf[G_ASCII_DTOSTR_BUF_SIZE];
355 
356   node = xmlNewChild(renderer->root, renderer->svg_name_space, (const xmlChar *)"polygon", NULL);
357 
358   xmlSetProp(node, (const xmlChar *)"style",
359              (xmlChar *) DIA_SVG_RENDERER_GET_CLASS(renderer)->get_draw_style(renderer, line_colour));
360 
361   str = g_string_new(NULL);
362   for (i = 0; i < num_points; i++) {
363     g_string_append_printf(str, "%s,%s ",
364 			   g_ascii_formatd(px_buf, sizeof(px_buf), "%g", points[i].x),
365 			   g_ascii_formatd(py_buf, sizeof(py_buf), "%g", points[i].y) );
366     add_connection_point(SHAPE_RENDERER(self), &points[i]);
367   }
368   for(i = 1; i < num_points; i++) {
369     center.x = (points[i].x + points[i-1].x)/2;
370     center.y = (points[i].y + points[i-1].y)/2;
371     add_connection_point(SHAPE_RENDERER(self), &center);
372   }
373   xmlSetProp(node, (const xmlChar *)"points", (xmlChar *) str->str);
374   g_string_free(str, TRUE);
375 }
376 
377 static void
add_rectangle_connection_points(ShapeRenderer * renderer,Point * ul_corner,Point * lr_corner)378 add_rectangle_connection_points (ShapeRenderer *renderer,
379                                  Point *ul_corner, Point *lr_corner)
380 {
381   Point connection;
382   coord center_x, center_y;
383 
384   center_x = (ul_corner->x + lr_corner->x)/2;
385   center_y = (ul_corner->y + lr_corner->y)/2;
386 
387   add_connection_point(renderer, ul_corner);
388   add_connection_point(renderer, lr_corner);
389   connection.x = ul_corner->x;
390   connection.y = lr_corner->y;
391   add_connection_point(renderer, &connection);
392   connection.y = center_y;
393   add_connection_point(renderer, &connection);
394 
395   connection.x = lr_corner->x;
396   connection.y = ul_corner->y;
397   add_connection_point(renderer, &connection);
398   connection.y = center_y;
399   add_connection_point(renderer, &connection);
400 
401   connection.x = center_x;
402   connection.y = lr_corner->y;
403   add_connection_point(renderer, &connection);
404   connection.y = ul_corner->y;
405   add_connection_point(renderer, &connection);
406 }
407 
408 
409 static void
draw_rect(DiaRenderer * self,Point * ul_corner,Point * lr_corner,Color * colour)410 draw_rect (DiaRenderer *self,
411            Point *ul_corner, Point *lr_corner,
412            Color *colour)
413 {
414   ShapeRenderer *renderer = SHAPE_RENDERER(self);
415 
416   /* use base class implementation */
417   DIA_RENDERER_CLASS(parent_class)->draw_rect (self, ul_corner, lr_corner, colour);
418   /* do our own stuff */
419   add_rectangle_connection_points(renderer, ul_corner, lr_corner);
420 }
421 
422 static void
add_ellipse_connection_points(ShapeRenderer * renderer,Point * center,real width,real height)423 add_ellipse_connection_points (ShapeRenderer *renderer,
424                                Point *center,
425                                real width, real height)
426 {
427   Point connection;
428 
429   connection.x = center->x;
430   connection.y = center->y + height/2;
431   add_connection_point(renderer, &connection);
432   connection.y = center->y - height/2;
433   add_connection_point(renderer, &connection);
434 
435   connection.y = center->y;
436   connection.x = center->x - width/2;
437   add_connection_point(renderer, &connection);
438   connection.x = center->x + width/2;
439   add_connection_point(renderer, &connection);
440 }
441 
442 static void
draw_ellipse(DiaRenderer * self,Point * center,real width,real height,Color * colour)443 draw_ellipse(DiaRenderer *self,
444              Point *center,
445              real width, real height,
446              Color *colour)
447 {
448   ShapeRenderer *renderer = SHAPE_RENDERER(self);
449 
450   /* use base class implementation */
451   DIA_RENDERER_CLASS(parent_class)->draw_ellipse (self, center, width, height, colour);
452 
453   /* do our own stuff */
454   add_ellipse_connection_points(renderer, center, width, height);
455 }
456 
457 static void
export_shape(DiagramData * data,const gchar * filename,const gchar * diafilename,void * user_data)458 export_shape(DiagramData *data, const gchar *filename,
459              const gchar *diafilename, void* user_data)
460 {
461     DiaSvgRenderer *renderer;
462     int i;
463     gchar *point;
464     gchar *png_filename = NULL;
465     DiaExportFilter *exportfilter;
466     gfloat old_scaling;
467     Rectangle *ext = &data->extents;
468     gfloat scaling_x, scaling_y;
469 
470     /* create the png preview shown in the toolbox */
471     point = strrchr(filename, '.');
472     if (point == NULL ||
473 	strcmp(point, ".shape")) {
474 	message_warning(_("Shape files must end in .shape, or they cannot be loaded by Dia"));
475 	return;
476     }
477     i = (int)(point-filename);
478     point = g_strndup(filename, i);
479     png_filename = g_strdup_printf("%s.png",point);
480     g_free(point);
481     /* we are especially requesting the libart/png cause it is the only one with the size-hack */
482     exportfilter = filter_get_by_name ("png-libart");
483     /* ... but the code below does not use the size-hack anymore ... */
484     if (!exportfilter)
485       exportfilter = filter_guess_export_filter(png_filename);
486 
487     if (!exportfilter) {
488       message_warning(_("Can't export png icon without export plug-in!"));
489     } else {
490       /* get the scaling right */
491       old_scaling = data->paper.scaling;
492       scaling_x = 22/((ext->right - ext->left) * 20);
493       scaling_y = 22/((ext->bottom - ext->top) * 20);
494       data->paper.scaling = MIN(scaling_x, scaling_y);
495       exportfilter->export_func(data, png_filename, diafilename, exportfilter->user_data);
496       data->paper.scaling = old_scaling;
497     }
498     /* create the shape */
499     if((renderer = new_shape_renderer(data, filename))) {
500       data_render(data, DIA_RENDERER(renderer), NULL, NULL, NULL);
501       g_object_unref (renderer);
502     }
503 
504     /* Create a sheet entry if applicable (../../sheets exists) */
505 
506 
507     g_free(png_filename);
508 }
509 
510 static const gchar *extensions[] = { "shape", NULL };
511 DiaExportFilter shape_export_filter = {
512     N_("Dia Shape File"),
513     extensions,
514     export_shape
515 };
516