/* dia-export-svg.c * Copyright (C) 2002 Arjan Molenaar * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include #include "dia-export-svg.h" #include "dia-canvas.h" #include "dia-canvas-groupable.h" #include "dia-shape.h" #include "dia-canvas-i18n.h" /* Extra currently unused features for the text renderer. */ //#define USE_TEXT_CLIPPING //#define USE_TEXT_ANCHOR //#define D(s) g_message s; #define D(s) static gchar *svg_header = "\n" "\n"; static gchar *svg_linecap[] = { "butt", "round", "square" }; static gchar *svg_linejoin[] = { "miter", "round", "bevel" }; /* static gchar *svg_alignment[] = { "start", "middle", "end" }; */ /* * typedef enum { * PANGO_STRETCH_ULTRA_CONDENSED, * PANGO_STRETCH_EXTRA_CONDENSED, * PANGO_STRETCH_CONDENSED, * PANGO_STRETCH_SEMI_CONDENSED, * PANGO_STRETCH_NORMAL, * PANGO_STRETCH_SEMI_EXPANDED, * PANGO_STRETCH_EXPANDED, * PANGO_STRETCH_EXTRA_EXPANDED, * PANGO_STRETCH_ULTRA_EXPANDED * } PangoStretch; */ static gchar *svg_stretch[] = { "ultra-condensed", "extra-condensed", "condensed", "semi-condensed", "normal", "semi-expanded", "expanded", "extra-expanded", "ultra-expanded" }; static gchar *svg_style[] = { "normal", "oblique", "italic" }; static gchar *svg_variant[] = { "normal", "small-caps" }; typedef void (* DiaExportSVGWriter) (gpointer data, const gchar *format, ...); static void dia_export_svg_class_init (DiaExportSVGClass *klass); static void dia_export_svg_init (DiaExportSVG *svg); static void dia_export_svg_dispose (GObject *object); static void dia_export_svg_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void dia_export_svg_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static GObjectClass *parent_class = NULL; GType dia_export_svg_get_type (void) { static GType object_type = 0; if (!object_type) { static const GTypeInfo object_info = { sizeof (DiaExportSVGClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) dia_export_svg_class_init, (GClassFinalizeFunc) NULL, (gconstpointer) NULL, /* class_data */ sizeof (DiaExportSVG), (guint16) 0, /* n_preallocs */ (GInstanceInitFunc) dia_export_svg_init, }; object_type = g_type_register_static (G_TYPE_OBJECT, "DiaExportSVG", &object_info, 0); } return object_type; } static void dia_export_svg_class_init (DiaExportSVGClass *klass) { GObjectClass *object_class; object_class = (GObjectClass*) klass; parent_class = g_type_class_peek_parent (klass); object_class->dispose = dia_export_svg_dispose; //object_class->get_property = dia_export_svg_get_property; //object_class->set_property = dia_export_svg_set_property; } static void dia_export_svg_init (DiaExportSVG *svg) { svg->svg = g_string_sized_new (4096); } static void dia_export_svg_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void dia_export_svg_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void dia_export_svg_dispose (GObject *object) { DiaExportSVG *svg = (DiaExportSVG *) object; if (svg->svg) { g_string_free (svg->svg, TRUE); svg->svg = NULL; } G_OBJECT_CLASS (parent_class)->dispose (object); } DiaExportSVG* dia_export_svg_new (void) { return DIA_EXPORT_SVG (g_object_new (DIA_TYPE_EXPORT_SVG, NULL)); } static void render_path (DiaShape *shape, DiaExportSVGWriter writer, gpointer data) { DiaShapePath *path = (DiaShapePath*) shape; ArtVpath *vp = path->vpath; if (!vp || vp->code == ART_END) return; writer (data, "code != ART_END) { switch (vp->code) { case ART_MOVETO: writer (data, "M%f %f", vp->x, vp->y); break; case ART_LINETO: writer (data, "L%f %f", vp->x, vp->y); break; default: g_warning ("Unknown path code: %d", vp->code); } vp++; } if (path->cyclic) writer (data, "Z"); writer (data, "\" style=\"stroke:#%06x;opacity:%f;stroke-width:%f;" "stroke-linecap:%s;stroke-linejoin:%s;" "fill:#%06x;fill-opacity:%f;fill-rule:evenodd", shape->color >> 8, ((gdouble) DIA_COLOR_ALPHA (shape->color)) / 255.0, path->line_width / 2.0, svg_linecap[path->cap], svg_linejoin[path->join], path->fill_color >> 8, ((gdouble) DIA_COLOR_ALPHA (path->fill_color)) / 255.0); if (path->dash.n_dash > 0) { int i; writer (data, ";stroke-dashoffset:%f;stroke-dasharray:%f", path->dash.offset, path->dash.dash[0]); for (i = 1; i < path->dash.n_dash; i++) writer (data, ",%f", path->dash.dash[i]); } writer (data, "\"/>"); } static void render_bezier (DiaShape *shape, DiaExportSVGWriter writer, gpointer data) { /* TODO: see Path for an example. Should be pretty easy, but I * need a bezier CanvasItem for testing. */ } static void render_ellipse (DiaShape *shape, DiaExportSVGWriter writer, gpointer data) { DiaShapeEllipse *ellipse = (DiaShapeEllipse*) shape; writer (data, "", ellipse->center.x, ellipse->center.y, ellipse->width / 2.0, ellipse->height / 2.0, shape->color >> 8, ((gdouble) DIA_COLOR_ALPHA (shape->color)) / 255.0, ellipse->line_width / 2.0, ellipse->fill_color >> 8, ((gdouble) DIA_COLOR_ALPHA (ellipse->fill_color)) / 255.0); } static inline gchar* markup_text (const gchar *text, gint len, gboolean markup) { if (markup) { /* Skip <..> tags in markuped text... */ GString *str = g_string_new (""); const gchar *p = text; const gchar *end = text + len; gboolean skip = FALSE; while (p < end) { const gchar *next = g_utf8_next_char (p); if (*p == '<') skip = TRUE; if (!skip) g_string_append_len (str, p, next - p); else if (*p == '>') skip = FALSE; p = next; } return g_string_free (str, FALSE); } else return g_markup_escape_text (text, len); } static void render_text (DiaShape *shape, DiaExportSVGWriter writer, gpointer data) { /* TODO: Use tspan for text */ DiaShapeText *text = (DiaShapeText*) shape; PangoLayout *layout = dia_shape_text_to_pango_layout (shape, TRUE); PangoFontDescription *font_desc; PangoLayoutLine *line; PangoLayoutIter *layout_iter; PangoRectangle irect, lrect; const gchar *pango_text; gchar *line_text; gdouble x = 0.0; gchar *text_anchor = "start"; if (!text->text || text->text[0] == '\0') return; /* set context font */ if (text->font_desc) font_desc = text->font_desc; else font_desc = pango_context_get_font_description ( pango_layout_get_context (layout)); #ifdef USE_TEXT_CLIPPING if (text->max_width < G_MAXINT && text->max_height < G_MAXINT) { writer (data, "", text->max_width, text->max_height); has_clip = TRUE; } #endif /* USE_TEXT_CLIPPING */ #ifdef USE_TEXT_ANCHOR /* This makes no sense since sodipodi and rsvg both do not support the * text-anchor attribute... */ switch (text->alignment) { case PANGO_ALIGN_LEFT: break; case PANGO_ALIGN_CENTER: text_anchor = "middle"; x = (gdouble) text->max_width / 2.0; break; case PANGO_ALIGN_RIGHT: text_anchor = "end"; x = (gdouble) text->max_width; break; default: g_assert_not_reached(); } #endif /* USE_TEXT_ANCHOR */ writer (data, "", text->affine[0], text->affine[1], text->affine[2], text->affine[3], text->affine[4], text->affine[5], pango_font_description_get_size (font_desc) / PANGO_SCALE, pango_font_description_get_family (font_desc), svg_style[pango_font_description_get_style (font_desc)], svg_stretch[pango_font_description_get_stretch (font_desc)], pango_font_description_get_weight (font_desc), svg_variant[pango_font_description_get_variant (font_desc)], ((gdouble) DIA_COLOR_ALPHA (shape->color)) / 255.0, text_anchor); /* iterate on lines*/ layout_iter = pango_layout_get_iter (layout); pango_text = pango_layout_get_text (layout); if (layout_iter) do { line = pango_layout_iter_get_line (layout_iter); pango_layout_iter_get_line_extents (layout_iter, &irect, &lrect); #ifndef USE_TEXT_ANCHOR /* This is a simple hack since Sodipodi and RSVG both do not * support the text-anchor attribute, we have to align the * text in some way... */ switch (text->alignment) { case PANGO_ALIGN_LEFT: x = 0.0; break; case PANGO_ALIGN_CENTER: x = ((gdouble) text->max_width - irect.width/PANGO_SCALE) / 2.0; break; case PANGO_ALIGN_RIGHT: x = (gdouble) text->max_width - irect.width/PANGO_SCALE; break; default: g_assert_not_reached(); } #endif /* !USE_TEXT_ANCHOR */ line_text = markup_text (&(pango_text[line->start_index]), line->length, text->markup); if (line_text && line_text[0] != '\0') writer (data, "%s", x, //((gdouble)irect.x)/PANGO_SCALE, ((gdouble)irect.y + lrect.height/2)/PANGO_SCALE, line_text); g_free (line_text); } while (pango_layout_iter_next_line (layout_iter)); pango_layout_iter_free (layout_iter); writer (data, ""); #ifdef USE_TEXT_CLIPPING if (has_clip) writer (data, ""); #endif /* USE_TEXT_CLIPPING */ } static void render_image (DiaShape *shape, DiaExportSVGWriter writer, gpointer data) { #if 0 DiaShapeImage *image = (DiaShapeImage*) shape; if (!image->pixbuf) return; writer (data, "pixbuf), gdk_pixbuf_get_height (image->pixbuf)); /* TODO: How do I convert a GdkPixbuf to a UUEncoded PNG stream??? */ writer (data, "\"/>"); #endif } static void dia_export_svg_real_render (DiaCanvasItem *item, DiaExportSVGWriter writer, gpointer data) { DiaCanvasIter iter; DiaShape *shape; gint clip_depth = 0; if (!DIA_CANVAS_ITEM_VISIBLE(item)) return; writer (data, "", item->affine[0], item->affine[1], item->affine[2], item->affine[3], item->affine[4], item->affine[5]); /* Draw shapes */ if (dia_canvas_item_get_shape_iter (item, &iter)) do { shape = dia_canvas_item_shape_value (item, &iter); /* Only draw normal visible objects */ if (shape->visibility != DIA_SHAPE_VISIBLE) continue; switch (shape->type) { case DIA_SHAPE_PATH: render_path (shape, writer, data); break; case DIA_SHAPE_BEZIER: render_bezier (shape, writer, data); case DIA_SHAPE_ELLIPSE: render_ellipse (shape, writer, data); break; case DIA_SHAPE_TEXT: render_text (shape, writer, data); break; case DIA_SHAPE_IMAGE: render_image (shape, writer, data); break; /* case DIA_SHAPE_CLIP: { ArtDRect *clip = &((DiaShapeClip*) shape)->clip; writer (data, "", clip->x0, clip->y0, clip->x1 - clip->x0, clip->y1 - clip->y0); clip_depth++; break; } */ default: break; } } while (dia_canvas_item_shape_next (item, &iter)); if (DIA_IS_CANVAS_GROUPABLE (item) && dia_canvas_groupable_get_iter (DIA_CANVAS_GROUPABLE (item), &iter)) do { dia_export_svg_real_render (dia_canvas_groupable_value (DIA_CANVAS_GROUPABLE (item), &iter), writer, data); } while (dia_canvas_groupable_next (DIA_CANVAS_GROUPABLE (item), &iter)); /* while (clip_depth > 0) { writer (data, ""); clip_depth--; } */ writer (data, ""); } void dia_export_svg_render (DiaExportSVG *export_svg, DiaCanvas *canvas) { gboolean old_state_requests; g_return_if_fail (DIA_IS_EXPORT_SVG (export_svg)); g_return_if_fail (DIA_IS_CANVAS (canvas)); dia_canvas_update_now (canvas); g_string_append_printf (export_svg->svg, "", MAX (canvas->extents.right - canvas->extents.left, 1.0), MAX (canvas->extents.bottom - canvas->extents.top, 1.0), -canvas->extents.left, -canvas->extents.top); /* Disable state requests... */ old_state_requests = canvas->allow_state_requests; g_object_set (canvas, "allow_state_requests", FALSE, NULL); dia_export_svg_real_render (canvas->root, (DiaExportSVGWriter) g_string_append_printf, export_svg->svg); g_object_set (canvas, "allow_state_requests", old_state_requests ? TRUE : FALSE, NULL); g_string_append (export_svg->svg, ""); } void dia_export_svg_save (DiaExportSVG *export_svg, const gchar *filename, GError **error) { FILE *file; g_return_if_fail (DIA_IS_EXPORT_SVG (export_svg)); g_return_if_fail (filename != NULL); g_return_if_fail (export_svg->svg->len > 0); file = fopen (filename, "w"); if (file == NULL) { g_set_error (error, 0 /* quark */, G_FILE_ERROR_FAILED, "Could not open file %s for writing", filename); return; } fwrite (svg_header, sizeof (gchar), strlen (svg_header), file); fwrite (export_svg->svg->str, sizeof (gchar), export_svg->svg->len, file); fclose (file); }