/* 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 <stdio.h>
#include <libart_lgpl/art_affine.h>
#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 = 
	"<?xml version=\"1.0\" standalone=\"no\"?>\n"
	"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\"\n"
	"\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\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, "<path d=\"");
	while (vp && vp->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,
		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 cx=\"%f\" cy=\"%f\" rx=\"%f\" ry=\"%f\" "
		"style=\"stroke:#%06x;opacity:%f;stroke-width:%f;fill:#%06x;"
		"fill-opacity:%f;fill-rule:evenodd\"/>",
		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,
		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, "<svg width=\"%f\" height=\"%f\" style=\"overflow:hidden\">",
			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, "<g transform=\"matrix(%f %f %f %f %f %f)\""
		      " style=\"font-size:%dpx;font-family:%s;font-style:%s;"
		      "font-stretch:%s;font-weight:%d;font-variant:%s;"
		      "stroke:#%06x;opacity:%f:text-anchor:%s\">",
		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)],
		shape->color >> 8,
		((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,
				"<text x=\"%f\" y=\"%f\"><![CDATA[%s]]></text>",
				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, "</g>");

#ifdef USE_TEXT_CLIPPING
	if (has_clip)
		writer (data, "</svg>");
#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, "<image width=\"%d\" height=\"%d\" xlink:href=\"data:;base64,", gdk_pixbuf_get_width (image->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, "<g transform=\"matrix(%f %f %f %f %f %f)\">",
		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, "<svg x=\"%f\" y=\"%f\" width=\"%f\" height=\"%f\" style=\"overflow:hidden\">",
				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, "</svg>");
		clip_depth--;
	}
	*/
	writer (data, "</g>");
}

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,
		"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"%f\" height=\"%f\"><g transform=\"translate(%f %f)\">",
	       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, "</g></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);
}

