/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* librvngabw
 * Version: MPL 2.0 / LGPLv2.1+
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Major Contributor(s):
 * Copyright (C) 2006 Ariya Hidayat (ariya@kde.org)
 * Copyright (C) 2005 Fridrich Strba (fridrich.strba@bluewin.ch)
 *
 * For minor contributions see the git repository.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU Lesser General Public License Version 2.1 or later
 * (LGPLv2.1+), in which case the provisions of the LGPLv2.1+ are
 * applicable instead of those above.
 */

#include <cmath>
#include <map>
#include <string>
#include <sstream>
#include <vector>

#include "librvngabw/librvngabw.hxx"

#include "ABWSVGDrawingGenerator.hxx"
#include "GraphicFunctions.hxx"

namespace librvngabw
{
/**************************************************************/
/**************************************************************/
static unsigned stringToColour(const librevenge::RVNGString &s)
{
	std::string str(s.cstr());
	if (str[0] == '#')
	{
		if (str.length() != 7)
			return 0;
		else
			str.erase(str.begin());
	}
	else
		return 0;

	std::istringstream istr(str);
	unsigned val = 0;
	istr >> std::hex >> val;
	return val;
}

/**************************************************************/
/**************************************************************/
//! small structure used to store a transformation
struct Transformation
{
	//! constructor
	Transformation() : m_tX(0), m_tY(0), m_angle(0), m_flipX(false), m_flipY(false),
		m_cX(0), m_cY(0),
		m_hasTranslation(false), m_hasRotation(false), m_hasSymetry(false),
		m_hasCenter(false), m_tCX(0), m_tCY(0)
	{
	}
	//! returns a translation transformation
	void translate(double const &tx, double const &ty, bool resetCenterTranslation=true)
	{
		m_tX+=tx;
		m_tY+=ty;
		m_hasTranslation=(m_tX<0||m_tX>0||m_tY<0||m_tY>0);
		if (!resetCenterTranslation) return;
		update();
	}
	//! set the rotation and the symetry invariant
	void setCenter(double const &cx, double const &cy)
	{
		m_cX=cx;
		m_cY=cy;
		m_hasCenter=true;
		update();
	}
	//! returns a rotation transformation (angle is given in degree )
	void rotate(double const &angle)
	{
		m_angle+=angle;
		m_hasRotation=(m_angle<0 || m_angle>0);
		update();
	}
	//! returns a symetrie transformation
	void symetry(bool flipX, bool flipY)
	{
		if (flipX && flipY)
		{
			m_angle+=180.;
			m_hasRotation=(m_angle<0 || m_angle>0);
		}
		else
		{
			if (flipX)
				m_flipX=!m_flipX;
			if (flipY)
				m_flipY=!m_flipY;
		}
		update();
	}
	//! transforms a point
	void transform(double &x, double &y) const;
	//! update the internal data
	void update();

	double m_tX /** the x translation */, m_tY/** the y translation */;
	double m_angle /** the angle of rotation in degree */;
	bool m_flipX /** true if we need to do an flip around x*/,
	     m_flipY /** true if we need to do an flip around x*/;
	double m_cX /** x invariant of flip and rotation */,
	       m_cY/** y invariant of flip and rotation */;
	//! true if the translation is defined
	bool m_hasTranslation;
	//! true if the rotation is defined
	bool m_hasRotation;
	//! true if the symetry is defined
	bool m_hasSymetry;
	//! a value used to know if the center is defined
	bool m_hasCenter;
	double m_tCX /** x translation to use to reset the center in center position */,
	       m_tCY /** y translation to use to reset the center in center position */;
};

void Transformation::transform(double &x, double &y) const
{
	if (m_hasRotation)
	{
		double tmpX=x;
		double tmpY=y;
		x=std::cos(m_angle)*tmpX-std::sin(m_angle)*tmpY;
		y=std::sin(m_angle)*tmpX+std::cos(m_angle)*tmpY;
	}
	if (m_flipX) x*=-1;
	if (m_flipY) y*=-1;
	if (m_hasTranslation)
	{
		x+=m_tX;
		y+=m_tY;
	}
	if (m_hasCenter)
	{
		x+=m_tCX;
		y+=m_tCY;
	}
}

void Transformation::update()
{
	if (!m_hasCenter) return;
	if (m_angle<0) m_angle+=360;
	if (m_angle>360) m_angle-=360;
	m_hasCenter=false;
	double x=m_cX, y=m_cY;
	transform(x,y);
	m_tCX=m_cX-x;
	m_tCY=m_cY-y;
	m_hasCenter=true;
}

/**************************************************************/
/**************************************************************/
class ABWSVGDrawingGeneratorInternal
{
public:
	ABWSVGDrawingGeneratorInternal();
	~ABWSVGDrawingGeneratorInternal()
	{
	}

	librevenge::RVNGString getPicture();
	void setStyle(const librevenge::RVNGPropertyList &propList);
	void setTransformation(const librevenge::RVNGPropertyList &propList);
	void setPictureOriginToZero();

	void writeStyle(bool isClosed=true);
	void drawPolySomething(const librevenge::RVNGPropertyListVector &vertices, bool isClosed);
	void drawPath(const librevenge::RVNGPropertyListVector &path);

	std::map<int, librevenge::RVNGPropertyList> m_idSpanMap;

	librevenge::RVNGPropertyListVector m_gradient;
	librevenge::RVNGPropertyList m_style;
	int m_gradientIndex, m_shadowIndex;
	//! index uses when fill=bitmap
	int m_patternIndex;
	int m_arrowStartIndex /** start arrow index*/, m_arrowEndIndex /** end arrow index */;
	std::ostringstream m_outputSink;
	/** the actual transformation */
	Transformation m_transformation;
	double m_bBox[4]/** the xmin, ymin, xmax, ymax bdbox */;
	double m_finalBBox[4]/** the xmin, ymin, xmax, ymax final bdbox */;

protected:
	ABWSVGDrawingGeneratorInternal(ABWSVGDrawingGeneratorInternal const &);
	ABWSVGDrawingGeneratorInternal &operator=(ABWSVGDrawingGeneratorInternal const &);
};

ABWSVGDrawingGeneratorInternal::ABWSVGDrawingGeneratorInternal() :
	m_idSpanMap(),
	m_gradient(),
	m_style(),
	m_gradientIndex(1),	m_shadowIndex(1), m_patternIndex(1), m_arrowStartIndex(1), m_arrowEndIndex(1),
	m_outputSink(), m_transformation()
{
	for (int i=0; i<4; ++i)
		m_bBox[i]=m_finalBBox[i]=0;
}

librevenge::RVNGString ABWSVGDrawingGeneratorInternal::getPicture()
{
	if (m_outputSink.str().empty()) return "";
	std::stringstream s;
	s << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n";
	s << "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n";
	s << "<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" ";
	s << "xmlns:xlink=\"http://www.w3.org/1999/xlink\" ";
	if (m_bBox[2]-m_bBox[0]>0)
		s << "width=\"" << doubleToString(72*(m_bBox[2]-m_bBox[0])).cstr() << "\" ";
	if (m_bBox[3]-m_bBox[1]>0)
		s << "height=\"" << doubleToString(72*(m_bBox[3]-m_bBox[1])).cstr()  << "\"";
	s << " >\n";
	s << m_outputSink.str();
	s << "</svg>\n";
	return s.str().c_str();
}

void ABWSVGDrawingGeneratorInternal::setPictureOriginToZero()
{
	bool valueSet=false;
	for (int i=0; i<2; ++i)
	{
		for (int j=0; j<2; ++j)
		{
			double x=m_bBox[2*i], y=m_bBox[2*j+1];
			m_transformation.transform(x,y);
			if (!valueSet)
			{
				m_finalBBox[0]=m_finalBBox[2]=x;
				m_finalBBox[1]=m_finalBBox[3]=y;
				valueSet=true;
				continue;
			}
			if (x<m_finalBBox[0]) m_finalBBox[0]=x;
			else if (x>m_finalBBox[2]) m_finalBBox[2]=x;
			if (y<m_finalBBox[1]) m_finalBBox[1]=y;
			else if (y>m_finalBBox[3]) m_finalBBox[3]=y;
		}
	}
	m_transformation.translate(-m_finalBBox[0],-m_finalBBox[1],false);
}

void ABWSVGDrawingGeneratorInternal::setTransformation(const librevenge::RVNGPropertyList &propList)
{
	m_transformation=Transformation();
	bool setCenter=false;
	if (propList["draw:rotate"])
	{
		double angle=propList["draw:rotate"]->getDouble();
		if (angle < 0) angle += 360;
		else if (angle > 360) angle -= 360;
		m_transformation.rotate(angle);
		setCenter=true;
	}
	bool flipX(propList["draw:mirror-horizontal"] && propList["draw:mirror-horizontal"]->getInt());
	bool flipY(propList["draw:mirror-vertical"] && propList["draw:mirror-vertical"]->getInt());
	if (flipX || flipY)
	{
		m_transformation.symetry(flipX, flipY);
		setCenter=true;
	}
	if (setCenter)
	{
		double cx=propList["svg:cx"] ? getInchValue(*propList["svg:cx"]) : (m_bBox[0]+m_bBox[2])/2;
		double cy=propList["svg:cy"] ? getInchValue(*propList["svg:cy"]) : (m_bBox[1]+m_bBox[3])/2;
		m_transformation.setCenter(cx, cy);
	}
}

void ABWSVGDrawingGeneratorInternal::setStyle(const librevenge::RVNGPropertyList &propList)
{
	m_style.clear();
	m_style = propList;

	auto const *gradient = propList.child("svg:linearGradient");
	if (!gradient)
		gradient = propList.child("svg:radialGradient");
	m_gradient = gradient ? *gradient : librevenge::RVNGPropertyListVector();
	if (m_style["draw:shadow"] && m_style["draw:shadow"]->getStr() == "visible" && m_style["draw:shadow-opacity"])
	{
		double shadowRed = 0.0;
		double shadowGreen = 0.0;
		double shadowBlue = 0.0;
		if (m_style["draw:shadow-color"])
		{
			unsigned shadowColour = stringToColour(m_style["draw:shadow-color"]->getStr());
			shadowRed = (double)((shadowColour & 0x00ff0000) >> 16)/255.0;
			shadowGreen = (double)((shadowColour & 0x0000ff00) >> 8)/255.0;
			shadowBlue = (double)(shadowColour & 0x000000ff)/255.0;
		}
		m_outputSink << "<defs>\n";
		m_outputSink << "<filter filterUnits=\"userSpaceOnUse\" id=\"shadow" << m_shadowIndex++ << "\">";
		m_outputSink << "<feOffset in=\"SourceGraphic\" result=\"offset\" ";
		if (m_style["draw:shadow-offset-x"])
			m_outputSink << "dx=\"" << doubleToString(72*getInchValue(*m_style["draw:shadow-offset-x"])).cstr() << "\" ";
		if (m_style["draw:shadow-offset-y"])
			m_outputSink << "dy=\"" << doubleToString(72*getInchValue(*m_style["draw:shadow-offset-y"])).cstr() << "\" ";
		m_outputSink << "/>";
		m_outputSink << "<feColorMatrix in=\"offset\" result=\"offset-color\" type=\"matrix\" values=\"";
		m_outputSink << "0 0 0 0 " << doubleToString(shadowRed).cstr();
		m_outputSink << " 0 0 0 0 " << doubleToString(shadowGreen).cstr();
		m_outputSink << " 0 0 0 0 " << doubleToString(shadowBlue).cstr();
		if (m_style["draw:opacity"] && m_style["draw:opacity"]->getDouble() < 1)
			m_outputSink << " 0 0 0 "   << doubleToString(m_style["draw:shadow-opacity"]->getDouble()/m_style["draw:opacity"]->getDouble()).cstr() << " 0\"/>";
		else
			m_outputSink << " 0 0 0 "   << doubleToString(m_style["draw:shadow-opacity"]->getDouble()).cstr() << " 0\"/>";

		m_outputSink << "<feMerge>";
		m_outputSink << "<feMergeNode in=\"offset-color\" />";
		m_outputSink << "<feMergeNode in=\"SourceGraphic\" />";
		m_outputSink << "</feMerge>";
		m_outputSink << "</filter>";
		m_outputSink << "</defs>";
	}

	if (m_style["draw:fill"] && m_style["draw:fill"]->getStr() == "gradient")
	{
		if (m_style["draw:style"] && (m_style["draw:style"]->getStr() == "radial" || m_style["draw:style"]->getStr() == "rectangular" ||
		                              m_style["draw:style"]->getStr() == "square" || m_style["draw:style"]->getStr() == "ellipsoid"))
		{
			m_outputSink << "<defs>\n";
			m_outputSink << "  <radialGradient id=\"grad" << m_gradientIndex++ << "\"";

			if (m_style["svg:cx"])
				m_outputSink << " cx=\"" << m_style["svg:cx"]->getStr().cstr() << "\"";
			else if (m_style["draw:cx"])
				m_outputSink << " cx=\"" << m_style["draw:cx"]->getStr().cstr() << "\"";

			if (m_style["svg:cy"])
				m_outputSink << " cy=\"" << m_style["svg:cy"]->getStr().cstr() << "\"";
			else if (m_style["draw:cy"])
				m_outputSink << " cy=\"" << m_style["draw:cy"]->getStr().cstr() << "\"";
			if (m_style["svg:r"])
				m_outputSink << " r=\"" << m_style["svg:r"]->getStr().cstr() << "\"";
			else if (m_style["draw:border"])
				m_outputSink << " r=\"" << doubleToString((1 - m_style["draw:border"]->getDouble())*100.0).cstr() << "%\"";
			else
				m_outputSink << " r=\"100%\"";
			m_outputSink << " >\n";
			if (m_gradient.count())
			{
				for (unsigned c = 0; c < m_gradient.count(); c++)
				{
					auto const &grad=m_gradient[c];
					m_outputSink << "    <stop";
					if (grad["svg:offset"])
						m_outputSink << " offset=\"" << grad["svg:offset"]->getStr().cstr() << "\"";
					if (grad["svg:stop-color"])
						m_outputSink << " stop-color=\"" << grad["svg:stop-color"]->getStr().cstr() << "\"";
					if (grad["svg:stop-opacity"])
						m_outputSink << " stop-opacity=\"" << doubleToString(grad["svg:stop-opacity"]->getDouble()).cstr() << "\"";
					m_outputSink << "/>" << std::endl;
				}
			}
			else if (m_style["draw:start-color"] && m_style["draw:end-color"])
			{
				m_outputSink << "    <stop offset=\"0%\"";
				m_outputSink << " stop-color=\"" << m_style["draw:end-color"]->getStr().cstr() << "\"";
				m_outputSink << " stop-opacity=\"" << doubleToString(m_style["librevenge:end-opacity"] ? m_style["librevenge:end-opacity"]->getDouble() : 1).cstr() << "\" />" << std::endl;

				m_outputSink << "    <stop offset=\"100%\"";
				m_outputSink << " stop-color=\"" << m_style["draw:start-color"]->getStr().cstr() << "\"";
				m_outputSink << " stop-opacity=\"" << doubleToString(m_style["librevenge:start-opacity"] ? m_style["librevenge:start-opacity"]->getDouble() : 1).cstr() << "\" />" << std::endl;
			}
			m_outputSink << "  </radialGradient>\n";
			m_outputSink << "</defs>\n";
		}
		else if (!m_style["draw:style"] || m_style["draw:style"]->getStr() == "linear" || m_style["draw:style"]->getStr() == "axial")
		{
			m_outputSink << "<defs>\n";
			m_outputSink << "  <linearGradient id=\"grad" << m_gradientIndex++ << "\" >\n";

			if (m_gradient.count())
			{
				bool canBuildAxial = false;
				if (m_style["draw:style"] && m_style["draw:style"]->getStr() == "axial")
				{
					// check if we can reconstruct the linear offset, ie. if each offset is a valid percent%
					canBuildAxial = true;
					for (unsigned c = 0; c < m_gradient.count(); ++c)
					{
						auto const &grad=m_gradient[c];
						if (!grad["svg:offset"] || grad["svg:offset"]->getDouble()<0 || grad["svg:offset"]->getDouble() > 1)
						{
							canBuildAxial=false;
							break;
						}
						auto str=grad["svg:offset"]->getStr();
						int len=str.len();
						if (len<1 || str.cstr()[len-1]!='%')
						{
							canBuildAxial=false;
							break;
						}
					}
				}
				if (canBuildAxial)
				{
					for (unsigned long c = m_gradient.count(); c>0 ;)
					{
						auto const &grad=m_gradient[--c];
						m_outputSink << "    <stop ";
						if (grad["svg:offset"])
							m_outputSink << "offset=\"" << doubleToString(50.-50.*grad["svg:offset"]->getDouble()).cstr() << "%\"";
						if (grad["svg:stop-color"])
							m_outputSink << " stop-color=\"" << grad["svg:stop-color"]->getStr().cstr() << "\"";
						if (grad["svg:stop-opacity"])
							m_outputSink << " stop-opacity=\"" << doubleToString(grad["svg:stop-opacity"]->getDouble()).cstr() << "\"";
						m_outputSink << "/>" << std::endl;
					}
					for (unsigned long c = 0; c < m_gradient.count(); ++c)
					{
						auto const &grad=m_gradient[c];
						if (c==0 && grad["svg:offset"] && grad["svg:offset"]->getDouble() <= 0)
							continue;
						m_outputSink << "    <stop ";
						if (grad["svg:offset"])
							m_outputSink << "offset=\"" << doubleToString(50.+50.*grad["svg:offset"]->getDouble()).cstr() << "%\"";
						if (grad["svg:stop-color"])
							m_outputSink << " stop-color=\"" << grad["svg:stop-color"]->getStr().cstr() << "\"";
						if (grad["svg:stop-opacity"])
							m_outputSink << " stop-opacity=\"" << doubleToString(grad["svg:stop-opacity"]->getDouble()).cstr() << "\"";
						m_outputSink << "/>" << std::endl;
					}
				}
				else
				{
					for (unsigned c = 0; c < m_gradient.count(); c++)
					{
						auto const &grad=m_gradient[c];
						m_outputSink << "    <stop";
						if (grad["svg:offset"])
							m_outputSink << " offset=\"" << grad["svg:offset"]->getStr().cstr() << "\"";
						if (grad["svg:stop-color"])
							m_outputSink << " stop-color=\"" << grad["svg:stop-color"]->getStr().cstr() << "\"";
						if (grad["svg:stop-opacity"])
							m_outputSink << " stop-opacity=\"" << doubleToString(grad["svg:stop-opacity"]->getDouble()).cstr() << "\"";
						m_outputSink << "/>" << std::endl;
					}
				}
			}
			else if (m_style["draw:start-color"] && m_style["draw:end-color"])
			{
				if (!m_style["draw:style"] || m_style["draw:style"]->getStr() == "linear")
				{
					m_outputSink << "    <stop offset=\"0%\"";
					m_outputSink << " stop-color=\"" << m_style["draw:start-color"]->getStr().cstr() << "\"";
					m_outputSink << " stop-opacity=\"" << doubleToString(m_style["librevenge:start-opacity"] ? m_style["librevenge:start-opacity"]->getDouble() : 1).cstr() << "\" />" << std::endl;

					m_outputSink << "    <stop offset=\"100%\"";
					m_outputSink << " stop-color=\"" << m_style["draw:end-color"]->getStr().cstr() << "\"";
					m_outputSink << " stop-opacity=\"" << doubleToString(m_style["librevenge:end-opacity"] ? m_style["librevenge:end-opacity"]->getDouble() : 1).cstr() << "\" />" << std::endl;
				}
				else
				{
					m_outputSink << "    <stop offset=\"0%\"";
					m_outputSink << " stop-color=\"" << m_style["draw:end-color"]->getStr().cstr() << "\"";
					m_outputSink << " stop-opacity=\"" << doubleToString(m_style["librevenge:end-opacity"] ? m_style["librevenge:end-opacity"]->getDouble() : 1).cstr() << "\" />" << std::endl;

					m_outputSink << "    <stop offset=\"50%\"";
					m_outputSink << " stop-color=\"" << m_style["draw:start-color"]->getStr().cstr() << "\"";
					m_outputSink << " stop-opacity=\"" << doubleToString(m_style["librevenge:start-opacity"] ? m_style["librevenge:start-opacity"]->getDouble() : 1).cstr() << "\" />" << std::endl;

					m_outputSink << "    <stop offset=\"100%\"";
					m_outputSink << " stop-color=\"" << m_style["draw:end-color"]->getStr().cstr() << "\"";
					m_outputSink << " stop-opacity=\"" << doubleToString(m_style["librevenge:end-opacity"] ? m_style["librevenge:end-opacity"]->getDouble() : 1).cstr() << "\" />" << std::endl;
				}
			}
			m_outputSink << "  </linearGradient>\n";

			// not a simple horizontal gradient
			double angle = m_style["draw:angle"] ? m_style["draw:angle"]->getDouble() : 0;
			if (angle < 0) angle += 360;
			else if (angle > 360) angle -= 360;

			if (angle<90 || angle>90)
			{
				m_outputSink << "  <linearGradient xlink:href=\"#grad" << m_gradientIndex-1 << "\"";
				m_outputSink << " id=\"grad" << m_gradientIndex++ << "\" ";
				m_outputSink << "x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\" ";
				m_outputSink << "gradientTransform=\"rotate(" << 360-angle << " .5 .5)\" ";
				m_outputSink << "gradientUnits=\"objectBoundingBox\" >\n";
				m_outputSink << "  </linearGradient>\n";
			}

			m_outputSink << "</defs>\n";
		}
	}
	else if (m_style["draw:fill"] && m_style["draw:fill"]->getStr() == "bitmap" && m_style["draw:fill-image"] && m_style["librevenge:mime-type"])
	{
		m_outputSink << "<defs>\n";
		m_outputSink << "  <pattern id=\"img" << m_patternIndex++ << "\" patternUnits=\"userSpaceOnUse\" ";
		if (m_style["svg:width"])
			m_outputSink << "width=\"" << doubleToString(72*getInchValue(*m_style["svg:width"])).cstr() << "\" ";
		else
			m_outputSink << "width=\"100\" ";

		if (m_style["svg:height"])
			m_outputSink << "height=\"" << doubleToString(72*getInchValue(*m_style["svg:height"])).cstr() << "\">" << std::endl;
		else
			m_outputSink << "height=\"100\">" << std::endl;
		m_outputSink << "<image ";

		if (m_style["svg:x"])
			m_outputSink << "x=\"" << doubleToString(72*getInchValue(*m_style["svg:x"])).cstr() << "\" ";
		else
			m_outputSink << "x=\"0\" ";

		if (m_style["svg:y"])
			m_outputSink << "y=\"" << doubleToString(72*getInchValue(*m_style["svg:y"])).cstr() << "\" ";
		else
			m_outputSink << "y=\"0\" ";

		if (m_style["svg:width"])
			m_outputSink << "width=\"" << doubleToString(72*getInchValue(*m_style["svg:width"])).cstr() << "\" ";
		else
			m_outputSink << "width=\"100\" ";

		if (m_style["svg:height"])
			m_outputSink << "height=\"" << doubleToString(72*getInchValue(*m_style["svg:height"])).cstr() << "\" ";
		else
			m_outputSink << "height=\"100\" ";

		m_outputSink << "xlink:href=\"data:" << m_style["librevenge:mime-type"]->getStr().cstr() << ";base64,";
		m_outputSink << m_style["draw:fill-image"]->getStr().cstr();
		m_outputSink << "\" />\n";
		m_outputSink << "  </pattern>\n";
		m_outputSink << "</defs>\n";
	}

	// check for arrow and if find some, define a basic arrow
	if (m_style["draw:marker-start-path"])
	{
		m_outputSink << "<defs>\n";
		m_outputSink << "<marker id=\"startMarker" << m_arrowStartIndex++ << "\" ";
		m_outputSink << " markerUnits=\"strokeWidth\" orient=\"auto\" markerWidth=\"8\" markerHeight=\"6\"\n";
		m_outputSink << " viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\">\n";
		m_outputSink << "<polyline points=\"10,0 0,5 10,10 9,5\" fill=\"solid\" />\n";
		m_outputSink << "</marker>\n";
		m_outputSink << "</defs>\n";
	}
	if (m_style["draw:marker-end-path"])
	{
		m_outputSink << "<defs>\n";
		m_outputSink << "<marker id=\"endMarker" << m_arrowEndIndex++ << "\" ";
		m_outputSink << " markerUnits=\"strokeWidth\" orient=\"auto\" markerWidth=\"8\" markerHeight=\"6\"\n";
		m_outputSink << " viewBox=\"0 0 10 10\" refX=\"1\" refY=\"5\">\n";
		m_outputSink << "<polyline points=\"0,0 10,5 0,10 1,5\" fill=\"solid\" />\n";
		m_outputSink << "</marker>\n";
		m_outputSink << "</defs>\n";
	}
}

void ABWSVGDrawingGeneratorInternal::drawPolySomething(const librevenge::RVNGPropertyListVector &vertices, bool isClosed)
{
	if (vertices.count() < 2)
		return;

	if (vertices.count() == 2)
	{
		if (!vertices[0]["svg:x"]||!vertices[0]["svg:y"]||!vertices[1]["svg:x"]||!vertices[1]["svg:y"])
			return;
		m_outputSink << "<line ";
		double x=getInchValue(*vertices[0]["svg:x"]);
		double y=getInchValue(*vertices[0]["svg:y"]);
		m_transformation.transform(x,y);
		m_outputSink << "x1=\"" << doubleToString(72*x).cstr() << "\"  y1=\"" << doubleToString(72*y).cstr() << "\" ";
		x=getInchValue(*vertices[1]["svg:x"]);
		y=getInchValue(*vertices[1]["svg:y"]);
		m_transformation.transform(x,y);
		m_outputSink << "x2=\"" << doubleToString(72*x).cstr() << "\"  y2=\"" << doubleToString(72*y).cstr() << "\"\n";
		writeStyle();
		m_outputSink << "/>\n";
	}
	else
	{
		if (isClosed)
			m_outputSink << "<polygon ";
		else
			m_outputSink << "<polyline ";

		m_outputSink << "points=\"";
		for (unsigned i = 0; i < vertices.count(); i++)
		{
			if (!vertices[i]["svg:x"]||!vertices[i]["svg:y"])
				continue;
			double x=getInchValue(*vertices[i]["svg:x"]);
			double y=getInchValue(*vertices[i]["svg:y"]);
			m_transformation.transform(x,y);
			m_outputSink << doubleToString(72*x).cstr() << " " << doubleToString(72*y).cstr();
			if (i < vertices.count()-1)
				m_outputSink << ", ";
		}
		m_outputSink << "\"\n";
		writeStyle(isClosed);
		m_outputSink << "/>\n";
	}
}

void ABWSVGDrawingGeneratorInternal::drawPath(const librevenge::RVNGPropertyListVector &path)
{
	m_outputSink << "<path d=\" ";
	bool isClosed = false;
	unsigned i=0;
	double lastX=0, lastY=0;
	for (i=0; i < path.count(); i++)
	{
		librevenge::RVNGPropertyList pList(path[i]);
		if (!pList["librevenge:path-action"]) continue;
		std::string action=pList["librevenge:path-action"]->getStr().cstr();
		if (action.length()!=1) continue;
		bool coordOk=pList["svg:x"]&&pList["svg:y"];
		bool coord1Ok=coordOk && pList["svg:x1"]&&pList["svg:y1"];
		bool coord2Ok=coord1Ok && pList["svg:x2"]&&pList["svg:y2"];
		double x=lastX, y=lastY;
		if (pList["svg:x"] && action[0] == 'H')
		{
			x=getInchValue(*pList["svg:x"]);
			m_transformation.transform(x,y);
			m_outputSink << "\nL" << doubleToString(72*x).cstr() << doubleToString(72*y).cstr() ;
		}
		else if (pList["svg:y"] && action[0] == 'V')
		{
			y=getInchValue(*pList["svg:y"]);
			m_transformation.transform(x,y);
			m_outputSink << "\nL" << doubleToString(72*x).cstr() << doubleToString(72*y).cstr() ;
		}
		else if (coordOk && (action[0] == 'M' || action[0] == 'L' || action[0] == 'T'))
		{
			x=getInchValue(*pList["svg:x"]);
			y=getInchValue(*pList["svg:y"]);
			m_transformation.transform(x,y);
			m_outputSink << "\n" << action;
			m_outputSink << doubleToString(72*x).cstr() << "," << doubleToString(72*y).cstr();
		}
		else if (coord1Ok && (action[0] == 'Q' || action[0] == 'S'))
		{
			m_outputSink << "\n" << action;
			x=getInchValue(*pList["svg:x1"]);
			y=getInchValue(*pList["svg:y1"]);
			m_transformation.transform(x,y);
			m_outputSink << doubleToString(72*x).cstr() << "," << doubleToString(72*y).cstr() << " ";
			x=getInchValue(*pList["svg:x"]);
			y=getInchValue(*pList["svg:y"]);
			m_transformation.transform(x,y);
			m_outputSink << doubleToString(72*x).cstr() << "," << doubleToString(72*y).cstr();
		}
		else if (coord2Ok && action[0] == 'C')
		{
			m_outputSink << "\nC";
			x=getInchValue(*pList["svg:x1"]);
			y=getInchValue(*pList["svg:y1"]);
			m_transformation.transform(x,y);
			m_outputSink << doubleToString(72*x).cstr() << "," << doubleToString(72*y).cstr() << " ";
			x=getInchValue(*pList["svg:x2"]);
			y=getInchValue(*pList["svg:y2"]);
			m_transformation.transform(x,y);
			m_outputSink << doubleToString(72*x).cstr() << "," << doubleToString(72*y).cstr() << " ";
			x=getInchValue(*pList["svg:x"]);
			y=getInchValue(*pList["svg:y"]);
			m_transformation.transform(x,y);
			m_outputSink << doubleToString(72*x).cstr() << "," << doubleToString(72*y).cstr();
		}
		else if (coordOk && pList["svg:rx"] && pList["svg:ry"] && action[0] == 'A')
		{
			m_outputSink << "\nA";
			m_outputSink << doubleToString(72*getInchValue(*pList["svg:rx"])).cstr() << "," << doubleToString(72*getInchValue(*pList["svg:ry"])).cstr() << " ";
			m_outputSink << doubleToString(pList["librevenge:rotate"] ? pList["librevenge:rotate"]->getDouble() : 0).cstr() << " ";
			m_outputSink << (pList["librevenge:large-arc"] ? pList["librevenge:large-arc"]->getInt() : 1) << ",";
			m_outputSink << (pList["librevenge:sweep"] ? pList["librevenge:sweep"]->getInt() : 1) << " ";
			x=getInchValue(*pList["svg:x"]);
			y=getInchValue(*pList["svg:y"]);
			m_transformation.transform(x,y);
			m_outputSink << doubleToString(72*x).cstr() << "," << doubleToString(72*y).cstr();
		}
		else if (action[0] == 'Z')
		{
			isClosed = true;
			m_outputSink << "\nZ";
		}
		lastX=x;
		lastY=y;
	}

	m_outputSink << "\" \n";
	writeStyle(isClosed);
	m_outputSink << "/>\n";
}

// create "style" attribute based on current pen and brush
void ABWSVGDrawingGeneratorInternal::writeStyle(bool /* isClosed */)
{
	m_outputSink << "style=\"";

	double width = 1.0 / 72.0;
	if (m_style["svg:stroke-width"])
	{
		width = getInchValue(*m_style["svg:stroke-width"]);
#if 0
		// add me in libmspub and libcdr
		if (width <= 0.0 && m_style["draw:stroke"] && m_style["draw:stroke"]->getStr() != "none")
			width = 0.2 / 72.0; // reasonable hairline
#endif
		m_outputSink << "stroke-width: " << doubleToString(72*width).cstr() << "; ";
	}

	if (m_style["draw:stroke"] && m_style["draw:stroke"]->getStr() != "none")
	{
		if (m_style["svg:stroke-color"])
			m_outputSink << "stroke: " << m_style["svg:stroke-color"]->getStr().cstr()  << "; ";
		if (m_style["svg:stroke-opacity"] &&  m_style["svg:stroke-opacity"]->getInt()!= 1)
			m_outputSink << "stroke-opacity: " << doubleToString(m_style["svg:stroke-opacity"]->getDouble()).cstr() << "; ";
	}

	if (m_style["draw:stroke"] && m_style["draw:stroke"]->getStr() == "solid")
		m_outputSink << "stroke-dasharray: none; ";
	else if (m_style["draw:stroke"] && m_style["draw:stroke"]->getStr() == "dash")
	{
		int dots1 = m_style["draw:dots1"] ? m_style["draw:dots1"]->getInt() : 0;
		int dots2 = m_style["draw:dots2"] ? m_style["draw:dots2"]->getInt() : 0;
		double dots1len = 72.*width, dots2len = 72.*width, gap = 72.*width;
		if (m_style["draw:dots1-length"])
		{
			if (m_style["draw:dots1-length"]->getUnit()==librevenge::RVNG_PERCENT)
				dots1len=72*m_style["draw:dots1-length"]->getDouble()*width;
			else
				dots1len=72*getInchValue(*m_style["draw:dots1-length"]);
		}
		if (m_style["draw:dots2-length"])
		{
			if (m_style["draw:dots2-length"]->getUnit()==librevenge::RVNG_PERCENT)
				dots2len=72*m_style["draw:dots2-length"]->getDouble()*width;
			else
				dots2len=72*getInchValue(*m_style["draw:dots2-length"]);
		}
		if (m_style["draw:distance"])
		{
			if (m_style["draw:distance"]->getUnit()==librevenge::RVNG_PERCENT)
				gap=72*m_style["draw:distance"]->getDouble()*width;
			else
				gap=72*getInchValue(*m_style["draw:distance"]);
		}
		m_outputSink << "stroke-dasharray: ";
		for (int i = 0; i < dots1; i++)
		{
			if (i)
				m_outputSink << ", ";
			m_outputSink << doubleToString(dots1len).cstr();
			m_outputSink << ", ";
			m_outputSink << doubleToString(gap).cstr();
		}
		for (int j = 0; j < dots2; j++)
		{
			m_outputSink << ", ";
			m_outputSink << doubleToString(dots2len).cstr();
			m_outputSink << ", ";
			m_outputSink << doubleToString(gap).cstr();
		}
		m_outputSink << "; ";
	}

	if (m_style["svg:stroke-linecap"])
		m_outputSink << "stroke-linecap: " << m_style["svg:stroke-linecap"]->getStr().cstr() << "; ";

	if (m_style["svg:stroke-linejoin"])
		m_outputSink << "stroke-linejoin: " << m_style["svg:stroke-linejoin"]->getStr().cstr() << "; ";

	if (m_style["draw:fill"] && m_style["draw:fill"]->getStr() == "none")
		m_outputSink << "fill: none; ";
	else if (m_style["svg:fill-rule"])
		m_outputSink << "fill-rule: " << m_style["svg:fill-rule"]->getStr().cstr() << "; ";

	if (m_style["draw:fill"] && m_style["draw:fill"]->getStr() == "gradient")
		m_outputSink << "fill: url(#grad" << m_gradientIndex-1 << "); ";
	else if (m_style["draw:fill"] && m_style["draw:fill"]->getStr() == "bitmap")
		m_outputSink << "fill: url(#img" << m_patternIndex-1 << "); ";

	if (m_style["draw:shadow"] && m_style["draw:shadow"]->getStr() == "visible")
		m_outputSink << "filter:url(#shadow" << m_shadowIndex-1 << "); ";

	if (m_style["draw:fill"] && m_style["draw:fill"]->getStr() == "solid")
		if (m_style["draw:fill-color"])
			m_outputSink << "fill: " << m_style["draw:fill-color"]->getStr().cstr() << "; ";
	if (m_style["draw:opacity"] && m_style["draw:opacity"]->getDouble() < 1)
		m_outputSink << "fill-opacity: " << doubleToString(m_style["draw:opacity"]->getDouble()).cstr() << "; ";

	if (m_style["draw:marker-start-path"])
		m_outputSink << "marker-start: url(#startMarker" << m_arrowStartIndex-1 << "); ";
	if (m_style["draw:marker-end-path"])
		m_outputSink << "marker-end: url(#endMarker" << m_arrowEndIndex-1 << "); ";

	m_outputSink << "\""; // style
}

/**************************************************************/
/**************************************************************/
ABWSVGDrawingGenerator::ABWSVGDrawingGenerator()
	: m_data(new ABWSVGDrawingGeneratorInternal())
{
}

ABWSVGDrawingGenerator::~ABWSVGDrawingGenerator()
{
}

void ABWSVGDrawingGenerator::setStyle(const librevenge::RVNGPropertyList &propList)
{
	m_data->setStyle(propList);
}

void ABWSVGDrawingGenerator::drawRectangle(const librevenge::RVNGPropertyList &propList)
{
	if (!propList["svg:x"] || !propList["svg:y"] || !propList["svg:width"] || !propList["svg:height"])
		return;

	double x0=getInchValue(*propList["svg:x"]);
	double y0=getInchValue(*propList["svg:y"]);
	double x1=x0+getInchValue(*propList["svg:width"]);
	double y1=x1+getInchValue(*propList["svg:height"]);
	m_data->m_bBox[0]=x0;
	m_data->m_bBox[1]=y0;
	m_data->m_bBox[2]=x1;
	m_data->m_bBox[3]=y1;
	m_data->setTransformation(propList);
	m_data->setPictureOriginToZero();

	if (m_data->m_transformation.m_hasRotation)
	{
		// we need to transform in a polygon
		librevenge::RVNGPropertyListVector listPoints;
		librevenge::RVNGPropertyList point;
		point.insert("svg:x", x0, librevenge::RVNG_INCH);
		point.insert("svg:y", y0, librevenge::RVNG_INCH);
		listPoints.append(point);
		point.insert("svg:x", x1, librevenge::RVNG_INCH);
		point.insert("svg:y", y0, librevenge::RVNG_INCH);
		listPoints.append(point);
		point.insert("svg:x", x1, librevenge::RVNG_INCH);
		point.insert("svg:y", y1, librevenge::RVNG_INCH);
		listPoints.append(point);
		point.insert("svg:x", x0, librevenge::RVNG_INCH);
		point.insert("svg:y", y1, librevenge::RVNG_INCH);
		listPoints.append(point);
		m_data->drawPolySomething(listPoints, true);
		return;
	}

	m_data->m_transformation.transform(x0,y0);
	m_data->m_transformation.transform(x1,y1);
	if (x0>x1)
	{
		double tmp=x0;
		x0=x1;
		x1=tmp;
	}
	if (y0>y1)
	{
		double tmp=y0;
		y0=y1;
		y1=tmp;
	}
	m_data->m_outputSink << "<rect ";
	m_data->m_outputSink << "x=\"" << doubleToString(72*x0).cstr() << "\" y=\"" << doubleToString(72*y0).cstr() << "\" ";
	m_data->m_outputSink << "width=\"" << doubleToString(72*(x1-x0)).cstr() << "\" height=\"" << doubleToString(72*(y1-y0)).cstr() << "\" ";
	if (propList["svg:rx"] && propList["svg:rx"]->getDouble() > 0 && propList["svg:ry"] && propList["svg:ry"]->getDouble()>0)
		m_data->m_outputSink << "rx=\"" << doubleToString(72*getInchValue(*propList["svg:rx"])).cstr() << "\" ry=\"" << doubleToString(72*getInchValue(*propList["svg:ry"])).cstr() << "\" ";
	m_data->writeStyle();
	m_data->m_outputSink << "/>\n";
}

void ABWSVGDrawingGenerator::drawEllipse(const librevenge::RVNGPropertyList &propList)
{
	if (!propList["svg:cx"] || !propList["svg:cy"] || !propList["svg:rx"] || !propList["svg:ry"])
		return;
	double cx=getInchValue(*propList["svg:cx"]);
	double cy=getInchValue(*propList["svg:cy"]);
	double rx=getInchValue(*propList["svg:rx"]);
	double ry=getInchValue(*propList["svg:ry"]);
	m_data->m_bBox[0]=cx-rx;
	m_data->m_bBox[1]=cy-ry;
	m_data->m_bBox[2]=cx+rx;
	m_data->m_bBox[3]=cy+ry;
	m_data->setTransformation(propList);
	m_data->m_transformation.transform(cx,cy);
	m_data->setPictureOriginToZero();

	m_data->m_outputSink << "<ellipse ";
	m_data->m_outputSink << "cx=\"" << doubleToString(72*cx).cstr() << "\" cy=\"" << doubleToString(72*cy).cstr() << "\" ";
	m_data->m_outputSink << "rx=\"" << doubleToString(72*rx).cstr() << "\" ry=\"" << doubleToString(72*ry).cstr() << "\" ";
	m_data->writeStyle();
	if (propList["librevenge:rotate"] && (propList["librevenge:rotate"]->getDouble()<0||propList["librevenge:rotate"]->getDouble()>0))
		m_data->m_outputSink << " transform=\" rotate(" << doubleToString(-propList["librevenge:rotate"]->getDouble()).cstr() << ", "
		                     << doubleToString(72*cx).cstr() << ", " << doubleToString(72*cy).cstr() << ")\" ";
	m_data->m_outputSink << "/>\n";
}

void ABWSVGDrawingGenerator::drawPolyline(const librevenge::RVNGPropertyList &propList)
{
	auto const *vertices = propList.child("svg:points");
	if (!vertices || !vertices->count() ||
	        !getListPointsBBox(*vertices, m_data->m_bBox[0], m_data->m_bBox[1], m_data->m_bBox[2], m_data->m_bBox[3]))
		return;
	m_data->setTransformation(propList);
	m_data->setPictureOriginToZero();
	m_data->drawPolySomething(*vertices, false);
}

void ABWSVGDrawingGenerator::drawPolygon(const librevenge::RVNGPropertyList &propList)
{
	auto const *vertices = propList.child("svg:points");
	if (!vertices || !vertices->count() ||
	        !getListPointsBBox(*vertices, m_data->m_bBox[0], m_data->m_bBox[1], m_data->m_bBox[2], m_data->m_bBox[3]))
		return;
	m_data->setTransformation(propList);
	m_data->setPictureOriginToZero();
	m_data->drawPolySomething(*vertices, true);
}

void ABWSVGDrawingGenerator::drawPath(const librevenge::RVNGPropertyList &propList)
{
	auto const *path = propList.child("svg:d");
	if (!path || !getPathBBox(*path, m_data->m_bBox[0], m_data->m_bBox[1], m_data->m_bBox[2], m_data->m_bBox[3]))
		return;
	m_data->setTransformation(propList);
	m_data->setPictureOriginToZero();
	m_data->drawPath(*path);
}

bool ABWSVGDrawingGenerator::getPicture(librevenge::RVNGString &pict,
                                        double &minx, double &miny, double &maxx, double &maxy)
{
	pict=m_data->getPicture();
	if (pict.empty()) return false;
	minx=m_data->m_finalBBox[0];
	miny=m_data->m_finalBBox[1];
	maxx=m_data->m_finalBBox[2];
	maxy=m_data->m_finalBBox[3];
	return true;
}

}

/* vim:set shiftwidth=4 softtabstop=4 noexpandtab: */
