// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SvgWriter.cs" company="OxyPlot">
//   Copyright (c) 2014 OxyPlot contributors
// </copyright>
// <summary>
//   Represents a writer that provides easy generation of Scalable Vector Graphics files.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace OxyPlot
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Text;

	using System.Drawing;
	using System.Drawing.Drawing2D;
	using fonts.freetype;

	class OxyFontHolder : IDisposable
	{
	    private FreetypeFontSupplier fsup = null;
	    private Matrix defaultMatrix = null; 
	    private List<char> chars = null;

    	private readonly float _emSize;
		private readonly String _pathToFont;
		private readonly float _weight;
	    
        private const float EXTRA_LIGHT = 0.5f;
        private const float LIGHT = 0.75f;
        private const float DEMILIGHT = 0.875f;
        private const float REGULAR = 1.0f;
        private const float SEMIBOLD = 1.25f;
        private const float MEDIUM = 1.5f;
        private const float DEMIBOLD = 1.75f;
        private const float BOLD = 2.0f;
        private const float HEAVY = 2.25f;
        private const float EXTRABOLD = 2.5f;
        private const float ULTRABOLD = 2.75f;

        private const float POSTURE_REGULAR = 0.0f;
        private const float POSTURE_OBLIQUE = 0.20f;
        
        private const float WIDTH_CONDENSED = 0.75f;
        private const float WIDTH_SEMI_CONDENSED = 0.875f;
        private const float WIDTH_REGULAR = 1.0f;
        private const float WIDTH_SEMI_EXTENDED = 1.25f;
        private const float WIDTH_EXTENDED = 1.5f;
        
        private static readonly float[] fontWeights = { 
        	EXTRA_LIGHT + (LIGHT - EXTRA_LIGHT)/2f,
            LIGHT + (DEMILIGHT - LIGHT)/2f,
            DEMILIGHT + (REGULAR - DEMILIGHT)/2f,
            REGULAR + (SEMIBOLD - REGULAR)/2f,
            SEMIBOLD + (MEDIUM - SEMIBOLD)/2f,
            MEDIUM + (DEMIBOLD - MEDIUM)/2f,
            DEMIBOLD + (BOLD - DEMIBOLD)/2f,
            BOLD + (HEAVY - BOLD)/2f,
            HEAVY + (EXTRABOLD - HEAVY)/2f,
            EXTRABOLD + (ULTRABOLD - EXTRABOLD)
        };

        private static readonly String[] svgWeights = {
            /*EXTRA_LIGHT*/ "100",
            /*LIGHT*/       "200",
            /*DEMILIGHT*/   "300",
            /*REGULAR*/     "normal",
            /*SEMIBOLD*/    "500",
            /*MEDIUM*/      "500",
            /*DEMIBOLD*/    "600",
            /*BOLD*/        "bold",
            /*HEAVY*/       "800",
            /*EXTRABOLD*/   "800",
            /*ULTRABOLD*/   "900"
        };

        private static readonly float[] fontStyles = {
            POSTURE_REGULAR + (POSTURE_OBLIQUE - POSTURE_REGULAR)/2
        };

        private static readonly String[] svgStyles = {
            /*POSTURE_REGULAR*/   "normal",
            /*POSTURE_OBLIQUE*/   "italic"
        };

        private const int COMMON_FONT_SIZE = 100;

	    protected FreetypeFontSupplier FontSup 
	    {
	    	get 
	    	{
	    		lock(GetType()) 
	    		{
	    			if(fsup == null) 
	    			{
	    				defaultMatrix = new Matrix(1f/COMMON_FONT_SIZE, 0, 0, 1f/COMMON_FONT_SIZE, 0, 0);
		    			fsup = new FreetypeFontSupplier(File.ReadAllBytes(PathToFont), defaultMatrix);
		    		}
	    		}
	    		return fsup;
	    	}
	    }

	    public void register(String s) {
	    	for(int k = 0; k < s.Length; ++k) {
	    		char cc = s[k];
	    		if(!chars.Contains(cc)) chars.Add(cc);
	    	}
	    }

		public OxyFontHolder(String pathToFont, float emSize)
		{
			this._pathToFont = pathToFont;
			this._emSize = emSize;
			chars = new List<char>();
		}

		public OxyFontHolder(String pathToFont, float emSize, float weight)
			: this(pathToFont, emSize)
		{
			this._weight = weight;
		}

		internal OxyFontHolder(OxyFontHolder fh, float emSize, float weight)
		{
			this._pathToFont = fh.PathToFont;
			this._emSize = emSize;
			this._weight = weight;

			// fdsup and chars are shared
			this.fsup = fh.FontSup;
			this.chars = fh.chars;
			this.defaultMatrix = fh.defaultMatrix;
		}

		private GlyphInfo getGlyph(char c) {
			return FontSup.getGlyph(c);
		}

		public String PathToFont
		{
			get { return _pathToFont; }
		}

		public String SvgFontName 
		{
			get { return "Font_" + PathToFont.GetHashCode(); }
		}
		
        private float EmSize // in canvas
        {
            get { return _emSize * 96.0f / 72.0f; }
        }

        private float EmSizePt // in SVG
        {
            get { return _emSize * 72.0f / 96.0f; }
        }

		public float Weight
		{
			get { return _weight; }
		}

		public GraphicsUnit Unit
		{
			get { return GraphicsUnit.Point; }
		}

		public float Height 
		{
			get { return FontSup.fontHeight * EmSize; }
		}
        
		public float Ascent 
		{
			get { return FontSup.fontAscent * EmSize; }
		}

		public List<char> KnownChars 
		{ 
			get { return chars; } 
		}

		public void Dispose()
		{
			if(fsup != null) fsup = null;
		}

		private void computeW(float[] w, float[] maxW, char cc) 
		{
			int code  = (int)cc;
			GlyphInfo gi = FontSup.getGlyph(code);
			if(cc == '\n' || cc == '\r') {
				maxW[0] = Math.Max(maxW[0], w[0]);
				w[0] = 0;
			}
			else if(gi != null) 
				w[0] += gi.advance.X;
		}

		public float MeasureTextWidth(String text)
		{
		    float[] width = new float[1];
		    float[] maxW  = new float[1];
		    width[0] = maxW[0] = 0;

		    register(text);
		    
			for(int k = 0; k < text.Length; ++k) {
				char cc = text[k];
				computeW(width, maxW, cc);
			}
		    width[0] = Math.Max(maxW[0], width[0]);
			return width[0] * EmSize;
		}

		private int countCRs(String s ) {
			int c = 0;
			for(int k = 0; k < s.Length; ++k)
				if(s[k] == '\n') ++c;
			return c;
		}

	    private String SvgWeight
	    {
		    get
			{
            	float weight = Weight;
		        int i = 0;
    	    	for (i=0; i < fontWeights.Length; i++) {
        	    	if (weight<=fontWeights[i])
            	    	break;
	        	}
	       		return svgWeights[i];
    		}
    	}

    	private String SvgStyle
    	{
        	get 
        	{
            	float style = POSTURE_REGULAR;
    	        int i = 0;
        	    for (i=0; i< fontStyles.Length; i++) {
            	    if (style <= fontStyles[i])
                	    break;
    	        }

        	    return svgStyles[i];
    	    }
    	}
	    
    	private String toSVGPathData(GraphicsPath s) 
    	{
			StringBuilder d = new StringBuilder();
		
			float lastX = 0;
			float lastY = 0;
			
			int strIdx;
			int pathType;
			float x1, y1, x2, y2, x3, y3;
			int iptr;

			for (iptr = 0; iptr < s.PathData.Types.Length;)
			{
				pathType = (int)s.PathData.Types[iptr];
				strIdx = iptr;
				bool close = (pathType & (int)PathPointType.CloseSubpath) == (int)PathPointType.CloseSubpath;
				bool dashed= (pathType & (int)PathPointType.DashMode) == (int)PathPointType.DashMode;

				pathType &= (int)PathPointType.PathTypeMask;
				switch((PathPointType)pathType) 
				{
					/* PathPointType.Bezier3 == PathPointType.Bezier */
					case PathPointType.Bezier3:
						x1 = s.PathData.Points[strIdx].X;
						y1 = s.PathData.Points[strIdx].Y;
						x2 = s.PathData.Points[strIdx+1].X;
						y2 = s.PathData.Points[strIdx+1].Y;
						x3 = s.PathData.Points[strIdx+2].X;
						y3 = s.PathData.Points[strIdx+2].Y;
						lastX = x3;
						lastY = y3;
                		d.Append("C ");
               			appendPoint(d, x1, y1);
                		appendPoint(d, x2, y2);
                		appendPoint(d, x3, y3);
						iptr += 3;
						break;
					case PathPointType.Line:
						lastX = s.PathData.Points[strIdx].X;
						lastY = s.PathData.Points[strIdx].Y;
                		d.Append("L ");
                		appendPoint(d, lastX, lastY);
						++iptr;
						break;
					case PathPointType.Start: 
						lastX = s.PathData.Points[strIdx].X;
						lastY = s.PathData.Points[strIdx].Y;
                		d.Append("M ");
                		appendPoint(d, lastX, lastY);
						++iptr;
						break;
				}

				if(close)
					d.Append("Z ");
			}

        	if (d.Length > 0)
            	return d.ToString().Trim();
        	else // emty path
            	return "";
    	}

    	private void appendPoint(StringBuilder d, float x, float y) {
        	d.Append(x);
        	d.Append(' ');
        	d.Append(y);
        	d.Append(' ');
    	}

    	private void appendAttr(StringBuilder sb, String k, String v) {
    		sb.Append(' ');
    		sb.Append(k);
    		sb.Append('=');
    		sb.Append('\"');
    		sb.Append(v);
    		sb.Append('\"');
    	}

	    public void dumpGlyphBody(SvgWriter w, char c) 
		{
        	if('\uFF00' == c) 
	            w.WriteStartElementP("missing-glyph");
        	else {
	            w.WriteStartElementP("glyph");
        		w.WriteAttributeStringP("unicode", "" + c);
        	}

	    	GlyphInfo gi = getGlyph(c);
	    	GraphicsPath gp;
	    	if(gi == null) {
	    		w.WriteAttributeStringP("horiz-adv-x", "" + (10 * FontSup.unitsPerEm / COMMON_FONT_SIZE));
	    	    gp = new GraphicsPath();
	    	    gp.AddRectangle(new RectangleF(0.0f, 0.0f, COMMON_FONT_SIZE, COMMON_FONT_SIZE));
	    	    gp.AddLine(0, 0, COMMON_FONT_SIZE, COMMON_FONT_SIZE);
	    	    gp.AddLine(COMMON_FONT_SIZE, 0, COMMON_FONT_SIZE, 0);
	    	}
	    	else {
	    		gp = (GraphicsPath)gi.gp.Clone();
		        Matrix at = new Matrix();
    		    at.Scale(1 * COMMON_FONT_SIZE, 1 * COMMON_FONT_SIZE);
        		gp.Transform(at);
	    		w.WriteAttributeStringP("horiz-adv-x", gi.advance.X * FontSup.unitsPerEm);
            }
            w.WriteAttributeStringP("d", toSVGPathData(gp) );
            w.WriteEndElementP();
    	}

	    // now font glyphs definition
    	public void dumpFontGlyphs(SvgWriter w)
    	{
        	w.WriteStartElementP("font");
        	w.WriteAttributeStringP("id", SvgFontName);
        	w.WriteStartElementP("font-face");
        	w.WriteAttributeStringP("ascent",  FontSup.fontAscent * COMMON_FONT_SIZE);
        	w.WriteAttributeStringP("descent", FontSup.fontDescent * COMMON_FONT_SIZE);
        	w.WriteAttributeStringP("units-per-em", "" + FontSup.unitsPerEm /*  * COMMON_FONT_SIZE / 1000.0f */);
        	w.WriteAttributeStringP("height", FontSup.fontHeight * COMMON_FONT_SIZE);
        	w.WriteAttributeStringP("font-style", SvgStyle);
        	w.WriteAttributeStringP("font-family", SvgFontName);
        	w.WriteAttributeStringP("font-weight", "normal");
            w.WriteEndElementP();

        	dumpGlyphBody(w, '\uFF00');
        	for(int k = 0; k < KnownChars.Count; ++k) 
            	dumpGlyphBody(w, KnownChars[k]) ;
            w.WriteEndElementP();
        }
	}

    /// <summary>
    /// Represents a writer that provides easy generation of Scalable Vector Graphics files.
    /// </summary>
    public class SvgWriter : XmlWriterBase
    {
        /// <summary>
        /// The end is written.
        /// </summary>
        private bool endIsWritten;

        /// <summary>
        /// The clip path
        /// </summary>
        private string clipPath;

        /// <summary>
        /// The clip path number
        /// </summary>
        private int clipPathNumber = 1;

        /// <summary>
        /// Initializes a new instance of the <see cref="SvgWriter" /> class.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="width">The width (in user units).</param>
        /// <param name="height">The height (in user units).</param>
        /// <param name="isDocument">if set to <c>true</c>, the writer will write the xml headers (?xml and !DOCTYPE).</param>
        public SvgWriter(Stream stream, double width, double height, bool isDocument = true)
            : base(stream)
        {
            this.IsDocument = isDocument;
            this.NumberFormat = "f4";
            this.WriteHeader(width, height);
        }

        /// <summary>
        /// Gets or sets a value indicating whether this writer should produce a stand-alone document.
        /// </summary>
        public bool IsDocument { get; set; }

        /// <summary>
        /// Gets or sets the number format.
        /// </summary>
        /// <value>The number format.</value>
        public string NumberFormat { get; set; }

        /// <summary>
        /// Closes the svg document.
        /// </summary>
        public override void Close()
        {
            if (!this.endIsWritten)
            {
                this.Complete();
            }

            base.Close();
        }

        /// <summary>
        /// Writes the end of the document.
        /// </summary>
        public void Complete()
        {
            this.WriteEndElement();
            if (this.IsDocument)
            {
                this.WriteEndDocument();
            }

            this.endIsWritten = true;
        }

        /// <summary>
        /// Creates a style.
        /// </summary>
        /// <param name="fill">The fill color.</param>
        /// <param name="stroke">The stroke color.</param>
        /// <param name="thickness">The stroke thickness (in user units).</param>
        /// <param name="dashArray">The line dash array.</param>
        /// <param name="lineJoin">The line join type.</param>
        /// <returns>A style string.</returns>
        public string CreateStyle(
            OxyColor fill,
            OxyColor stroke,
            double thickness,
            double[] dashArray = null,
            LineJoin lineJoin = LineJoin.Miter)
        {
            // http://oreilly.com/catalog/svgess/chapter/ch03.html
            var style = new StringBuilder();
            if (fill.IsInvisible())
            {
                style.AppendFormat("fill:none;");
            }
            else
            {
                style.AppendFormat("fill:{0};", this.ColorToString(fill));
                if (fill.A != 0xFF)
                {
                    style.AppendFormat(CultureInfo.InvariantCulture, "fill-opacity:{0};", fill.A / 255.0);
                }
            }

            if (stroke.IsInvisible())
            {
                style.AppendFormat("stroke:none;");
            }
            else
            {
                string formatString = "stroke:{0};stroke-width:{1:" + this.NumberFormat + "}";
                style.AppendFormat(CultureInfo.InvariantCulture, formatString, this.ColorToString(stroke), thickness);
                switch (lineJoin)
                {
                    case LineJoin.Round:
                        style.AppendFormat(";stroke-linejoin:round");
                        break;
                    case LineJoin.Bevel:
                        style.AppendFormat(";stroke-linejoin:bevel");
                        break;
                }

                if (stroke.A != 0xFF)
                {
                    style.AppendFormat(CultureInfo.InvariantCulture, ";stroke-opacity:{0}", stroke.A / 255.0);
                }

                if (dashArray != null && dashArray.Length > 0)
                {
                    style.Append(";stroke-dasharray:");
                    for (int i = 0; i < dashArray.Length; i++)
                    {
                        style.AppendFormat(
                            CultureInfo.InvariantCulture, "{0}{1}", i > 0 ? "," : string.Empty, dashArray[i]);
                    }
                }
            }

            return style.ToString();
        }

        /// <summary>
        /// Writes an ellipse.
        /// </summary>
        /// <param name="x">The x-coordinate of the center.</param>
        /// <param name="y">The y-coordinate of the center.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="style">The style.</param>
        public void WriteEllipse(double x, double y, double width, double height, string style)
        {
            // http://www.w3.org/TR/SVG/shapes.html#EllipseElement
            this.WriteStartElement("ellipse");
            this.WriteAttributeString("cx", x + (width / 2));
            this.WriteAttributeString("cy", y + (height / 2));
            this.WriteAttributeString("rx", width / 2);
            this.WriteAttributeString("ry", height / 2);
            this.WriteAttributeString("style", style);
            this.WriteClipPathAttribute();
            this.WriteEndElement();
        }

        /// <summary>
        /// Sets a clipping rectangle.
        /// </summary>
        /// <param name="x">The x coordinate of the clipping rectangle.</param>
        /// <param name="y">The y coordinate of the clipping rectangle.</param>
        /// <param name="width">The width of the clipping rectangle.</param>
        /// <param name="height">The height of the clipping rectangle.</param>
        public void BeginClip(double x, double y, double width, double height)
        {
            // http://www.w3.org/TR/SVG/masking.html
            // https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath
            // http://www.svgbasics.com/clipping.html
            this.clipPath = "clipPath" + (this.clipPathNumber++);
            this.WriteStartElement("g");
            this.WriteAttributeString("clip-rule", "nonzero");
            this.WriteStartElement("clipPath");
            this.WriteAttributeString("id", this.clipPath);
            this.WriteStartElement("rect");
            this.WriteAttributeString("x", x);
            this.WriteAttributeString("y", y);
            this.WriteAttributeString("width", width);
            this.WriteAttributeString("height", height);
            this.WriteEndElement(); // rect
            this.WriteEndElement(); // clipPath
        }

        /// <summary>
        /// Resets the clipping rectangle.
        /// </summary>
        public void EndClip()
        {
            this.WriteEndElement(); // g
            this.clipPath = null;
        }

        /// <summary>
        /// Writes a portion of the specified image.
        /// </summary>
        /// <param name="srcX">The x-coordinate of the upper-left corner of the portion of the source image to draw.</param>
        /// <param name="srcY">The y-coordinate of the upper-left corner of the portion of the source image to draw.</param>
        /// <param name="srcWidth">Width of the portion of the source image to draw.</param>
        /// <param name="srcHeight">Height of the portion of the source image to draw.</param>
        /// <param name="destX">The destination x-coordinate.</param>
        /// <param name="destY">The destination y-coordinate.</param>
        /// <param name="destWidth">Width of the destination rectangle.</param>
        /// <param name="destHeight">Height of the destination rectangle.</param>
        /// <param name="image">The image.</param>
        public void WriteImage(
            double srcX,
            double srcY,
            double srcWidth,
            double srcHeight,
            double destX,
            double destY,
            double destWidth,
            double destHeight,
            OxyImage image)
        {
            double x = destX - (srcX / srcWidth * destWidth);
            double width = image.Width / srcWidth * destWidth;
            double y = destY - (srcY / srcHeight * destHeight);
            double height = image.Height / srcHeight * destHeight;
            this.BeginClip(destX, destY, destWidth, destHeight);
            this.WriteImage(x, y, width, height, image);
            this.EndClip();
        }

        /// <summary>
        /// Writes the specified image.
        /// </summary>
        /// <param name="x">The x-coordinate.</param>
        /// <param name="y">The y-coordinate.</param>
        /// <param name="width">The width of the image.</param>
        /// <param name="height">The height of the image.</param>
        /// <param name="image">The image.</param>
        public void WriteImage(double x, double y, double width, double height, OxyImage image)
        {
            // http://www.w3.org/TR/SVG/shapes.html#ImageElement
            this.WriteStartElement("image");
            this.WriteAttributeString("x", x);
            this.WriteAttributeString("y", y);
            this.WriteAttributeString("width", width);
            this.WriteAttributeString("height", height);
            this.WriteAttributeString("preserveAspectRatio", "none");
            var imageData = image.GetData();
            var encodedImage = new StringBuilder();
            encodedImage.Append("data:");
            encodedImage.Append("image/png");
            encodedImage.Append(";base64,");
            encodedImage.Append(Convert.ToBase64String(imageData));
            this.WriteAttributeString("xlink", "href", null, encodedImage.ToString());
            this.WriteClipPathAttribute();
            this.WriteEndElement();
        }

        /// <summary>
        /// Writes a line.
        /// </summary>
        /// <param name="p1">The first point.</param>
        /// <param name="p2">The second point.</param>
        /// <param name="style">The style.</param>
        public void WriteLine(ScreenPoint p1, ScreenPoint p2, string style)
        {
            // http://www.w3.org/TR/SVG/shapes.html#LineElement
            // http://www.w3schools.com/svg/svg_line.asp
            this.WriteStartElement("line");
            this.WriteAttributeString("x1", p1.X);
            this.WriteAttributeString("y1", p1.Y);
            this.WriteAttributeString("x2", p2.X);
            this.WriteAttributeString("y2", p2.Y);
            this.WriteAttributeString("style", style);
            this.WriteClipPathAttribute();
            this.WriteEndElement();
        }

        /// <summary>
        /// Writes a polygon.
        /// </summary>
        /// <param name="points">The points.</param>
        /// <param name="style">The style.</param>
        public void WritePolygon(IEnumerable<ScreenPoint> points, string style)
        {
            // http://www.w3.org/TR/SVG/shapes.html#PolygonElement
            this.WriteStartElement("polygon");
            this.WriteAttributeString("points", this.PointsToString(points));
            this.WriteAttributeString("style", style);
            this.WriteClipPathAttribute();
            this.WriteEndElement();
        }

        /// <summary>
        /// Writes a polyline.
        /// </summary>
        /// <param name="pts">The points.</param>
        /// <param name="style">The style.</param>
        public void WritePolyline(IEnumerable<ScreenPoint> pts, string style)
        {
            // http://www.w3.org/TR/SVG/shapes.html#PolylineElement
            this.WriteStartElement("polyline");
            this.WriteAttributeString("points", this.PointsToString(pts));
            this.WriteAttributeString("style", style);
            this.WriteClipPathAttribute();
            this.WriteEndElement();
        }

        /// <summary>
        /// Writes a rectangle.
        /// </summary>
        /// <param name="x">The x coordinate.</param>
        /// <param name="y">The y coordinate.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="style">The style.</param>
        public void WriteRectangle(double x, double y, double width, double height, string style)
        {
            // http://www.w3.org/TR/SVG/shapes.html#RectangleElement
            this.WriteStartElement("rect");
            this.WriteAttributeString("x", x);
            this.WriteAttributeString("y", y);
            this.WriteAttributeString("width", width);
            this.WriteAttributeString("height", height);
            this.WriteAttributeString("style", style);
            this.WriteClipPathAttribute();
            this.WriteEndElement();
        }

        /// <summary>
        /// Writes text.
        /// </summary>
        /// <param name="position">The position.</param>
        /// <param name="text">The text.</param>
        /// <param name="fill">The text color.</param>
        /// <param name="fontFamily">The font family.</param>
        /// <param name="fontSize">The font size (in user units).</param>
        /// <param name="fontWeight">The font weight.</param>
        /// <param name="rotate">The rotation angle.</param>
        /// <param name="halign">The horizontal alignment.</param>
        /// <param name="valign">The vertical alignment.</param>
        public void WriteText(
            ScreenPoint position,
            string text,
            OxyColor fill,
            string fontFamily = null,
            double fontSize = 10,
            double fontWeight = FontWeights.Normal,
            double rotate = 0,
            HorizontalAlignment halign = HorizontalAlignment.Left,
            VerticalAlignment valign = VerticalAlignment.Top)
        {
            // http://www.w3.org/TR/SVG/text.html
            this.WriteStartElement("text");

            // WriteAttributeString("x", position.X);
            // WriteAttributeString("y", position.Y);
            string baselineAlignment = "hanging";
            if (valign == VerticalAlignment.Middle)
            {
                baselineAlignment = "middle";
            }

            if (valign == VerticalAlignment.Bottom)
            {
                baselineAlignment = "baseline";
            }

            this.WriteAttributeString("dominant-baseline", baselineAlignment);

            string textAnchor = "start";
            if (halign == HorizontalAlignment.Center)
            {
                textAnchor = "middle";
            }

            if (halign == HorizontalAlignment.Right)
            {
                textAnchor = "end";
            }

            this.WriteAttributeString("text-anchor", textAnchor);

            string fmt = "translate({0:" + this.NumberFormat + "},{1:" + this.NumberFormat + "})";
            string transform = string.Format(CultureInfo.InvariantCulture, fmt, position.X, position.Y);
            if (Math.Abs(rotate) > 0)
            {
                transform += string.Format(CultureInfo.InvariantCulture, " rotate({0})", rotate);
            }

            this.WriteAttributeString("transform", transform);

            if (fontFamily != null)
            {
                this.WriteAttributeString("font-family", fontFamily);
            }

            if (fontSize > 0)
            {
                this.WriteAttributeString("font-size", fontSize);
            }

            if (fontWeight > 0)
            {
                this.WriteAttributeString("font-weight", fontWeight);
            }

            this.WriteAttributeString("fill", this.ColorToString(fill));
            this.WriteClipPathAttribute();

            // WriteAttributeString("style", style);
            this.WriteString(text);
            this.WriteEndElement();
        }

        /// <summary>
        /// Converts a color to a svg color string.
        /// </summary>
        /// <param name="color">The color.</param>
        /// <returns>The color string.</returns>
        protected string ColorToString(OxyColor color)
        {
            if (color.Equals(OxyColors.Black))
            {
                return "black";
            }

            return color.ToSvgString();
        }

        /// <summary>
        /// Writes an double attribute.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        protected void WriteAttributeString(string name, double value)
        {
            this.WriteAttributeString(name, value.ToString(this.NumberFormat, CultureInfo.InvariantCulture));
        }

        /// <summary>
        /// Writes the clip path attribute.
        /// </summary>
        private void WriteClipPathAttribute()
        {
            if (this.clipPath == null)
            {
                return;
            }

            this.WriteAttributeString("clip-path", string.Format("url(#{0})", this.clipPath));
        }

        /// <summary>
        /// Converts a value to a string or to the specified "auto" string if the value is NaN.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="auto">The string to return if value is NaN.</param>
        /// <returns>A string.</returns>
        private string GetAutoValue(double value, string auto)
        {
            if (double.IsNaN(value))
            {
                return auto;
            }

            return value.ToString(this.NumberFormat, CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Converts a list of points to a string.
        /// </summary>
        /// <param name="points">The points.</param>
        /// <returns>A string.</returns>
        private string PointsToString(IEnumerable<ScreenPoint> points)
        {
            var sb = new StringBuilder();
            string fmt = "{0:" + this.NumberFormat + "},{1:" + this.NumberFormat + "} ";
            foreach (var p in points)
            {
                sb.AppendFormat(CultureInfo.InvariantCulture, fmt, p.X, p.Y);
            }

            return sb.ToString().Trim();
        }

        /// <summary>
        /// Writes the header.
        /// </summary>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        private void WriteHeader(double width, double height)
        {
            // http://www.w3.org/TR/SVG/struct.html#SVGElement
            if (this.IsDocument)
            {
                this.WriteStartDocument(false);
                this.WriteDocType(
                    "svg", "-//W3C//DTD SVG 1.1//EN", "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd", null);
            }

            this.WriteStartElement("svg", "http://www.w3.org/2000/svg");
            this.WriteAttributeString("width", this.GetAutoValue(width, "100%"));
            this.WriteAttributeString("height", this.GetAutoValue(height, "100%"));
            this.WriteAttributeString("version", "1.1");
            this.WriteAttributeString("xmlns", "xlink", null, "http://www.w3.org/1999/xlink");
        }

        // Wrappers for "so close" isolation levels.
        public void WriteAttributeStringP(string name, string value)
        {
            WriteAttributeString(name, value);
        }

        public void WriteAttributeStringP(string name, float value)
        {
            WriteAttributeString(name, "" + value);
        }

        public void WriteEndElementP()
        {
            WriteEndElement();
        }

        public void WriteStartElementP(string name)
        {
            WriteStartElement(name);
        }
    }
}