/**
 * $Id: mxSvgCanvas.java,v 1.55 2010/05/31 08:49:27 gaudenz Exp $
 * Copyright (c) 2007, Gaudenz Alder
 */
package com.mxgraph.canvas;

import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;

/**
 * An implementation of a canvas that uses SVG for painting. This canvas
 * ignores the STYLE_LABEL_BACKGROUNDCOLOR and
 * STYLE_LABEL_BORDERCOLOR styles due to limitations of SVG.
 */
public class mxSvgCanvas extends mxBasicCanvas
{

	/**
	 * Holds the HTML document that represents the canvas.
	 */
	protected Document document;

	/**
	 * Constructs a new SVG canvas for the specified dimension and scale.
	 */
	public mxSvgCanvas()
	{
		this(null);
	}

	/**
	 * Constructs a new SVG canvas for the specified bounds, scale and
	 * background color.
	 */
	public mxSvgCanvas(Document document)
	{
		setDocument(document);
	}

	/**
	 * 
	 */
	public void appendSvgElement(Element node)
	{
		if (document != null)
		{
			document.getDocumentElement().appendChild(node);
		}
	}

	/**
	 * 
	 */
	public void setDocument(Document document)
	{
		this.document = document;
	}

	/**
	 * Returns a reference to the document that represents the canvas.
	 * 
	 * @return Returns the document.
	 */
	public Document getDocument()
	{
		return document;
	}

	/*
	 * (non-Javadoc)
	 * @see com.mxgraph.canvas.mxICanvas#drawCell()
	 */
	public Object drawCell(mxCellState state)
	{
		Map<String, Object> style = state.getStyle();
		Element elem = null;

		if (state.getAbsolutePointCount() > 1)
		{
			List<mxPoint> pts = state.getAbsolutePoints();

			// Transpose all points by cloning into a new array
			pts = mxUtils.translatePoints(pts, translate.x, translate.y);

			// Draws the line
			elem = drawLine(pts, style);

			// Applies opacity
			float opacity = mxUtils.getFloat(style, mxConstants.STYLE_OPACITY,
					100);

			if (opacity != 100)
			{
				String value = String.valueOf(opacity / 100);
				elem.setAttribute("fill-opacity", value);
				elem.setAttribute("stroke-opacity", value);
			}
		}
		else
		{
			int x = (int) state.getX() + translate.x;
			int y = (int) state.getY() + translate.y;
			int w = (int) state.getWidth();
			int h = (int) state.getHeight();

			if (!mxUtils.getString(style, mxConstants.STYLE_SHAPE, "").equals(
					mxConstants.SHAPE_SWIMLANE))
			{
				elem = drawShape(x, y, w, h, style);
			}
			else
			{
				int start = (int) Math.round(mxUtils.getInt(style,
						mxConstants.STYLE_STARTSIZE,
						mxConstants.DEFAULT_STARTSIZE)
						* scale);

				// Removes some styles to draw the content area
				Map<String, Object> cloned = new Hashtable<String, Object>(
						style);
				cloned.remove(mxConstants.STYLE_FILLCOLOR);
				cloned.remove(mxConstants.STYLE_ROUNDED);

				if (mxUtils.isTrue(style, mxConstants.STYLE_HORIZONTAL, true))
				{
					elem = drawShape(x, y, w, start, style);
					drawShape(x, y + start, w, h - start, cloned);
				}
				else
				{
					elem = drawShape(x, y, start, h, style);
					drawShape(x + start, y, w - start, h, cloned);
				}
			}
		}

		return elem;
	}

	/*
	 * (non-Javadoc)
	 * @see com.mxgraph.canvas.mxICanvas#drawLabel()
	 */
	public Object drawLabel(String label, mxCellState state, boolean html)
	{
		mxRectangle bounds = state.getLabelBounds();

		if (drawLabels)
		{
			int x = (int) bounds.getX() + translate.x;
			int y = (int) bounds.getY() + translate.y;
			int w = (int) bounds.getWidth();
			int h = (int) bounds.getHeight();
			Map<String, Object> style = state.getStyle();

			return drawText(label, x, y, w, h, style);
		}

		return null;
	}

	/**
	 * Draws the shape specified with the STYLE_SHAPE key in the given style.
	 * 
	 * @param x X-coordinate of the shape.
	 * @param y Y-coordinate of the shape.
	 * @param w Width of the shape.
	 * @param h Height of the shape.
	 * @param style Style of the the shape.
	 */
	public Element drawShape(int x, int y, int w, int h,
			Map<String, Object> style)
	{
		String fillColor = mxUtils.getString(style,
				mxConstants.STYLE_FILLCOLOR, "none");
		String strokeColor = mxUtils.getString(style,
				mxConstants.STYLE_STROKECOLOR);
		float strokeWidth = (float) (mxUtils.getFloat(style,
				mxConstants.STYLE_STROKEWIDTH, 1) * scale);

		// Draws the shape
		String shape = mxUtils.getString(style, mxConstants.STYLE_SHAPE);
		Element elem = null;
		Element background = null;

		if (shape.equals(mxConstants.SHAPE_IMAGE))
		{
			String img = getImageForStyle(style);

			if (img != null)
			{
				elem = document.createElement("image");

				elem.setAttribute("x", String.valueOf(x));
				elem.setAttribute("y", String.valueOf(y));
				elem.setAttribute("width", String.valueOf(w));
				elem.setAttribute("height", String.valueOf(h));

				elem.setAttributeNS(mxConstants.NS_XLINK, "xlink:href", img);
			}
		}
		else if (shape.equals(mxConstants.SHAPE_LINE))
		{
			String direction = mxUtils.getString(style,
					mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
			String d = null;

			if (direction.equals(mxConstants.DIRECTION_EAST)
					|| direction.equals(mxConstants.DIRECTION_WEST))
			{
				int mid = (int) (y + h / 2);
				d = "M " + x + " " + mid + " L " + (x + w) + " " + mid;
			}
			else
			{
				int mid = (int) (x + w / 2);
				d = "M " + mid + " " + y + " L " + mid + " " + (y + h);
			}

			elem = document.createElement("path");
			elem.setAttribute("d", d + " Z");
		}
		else if (shape.equals(mxConstants.SHAPE_ELLIPSE))
		{
			elem = document.createElement("ellipse");

			elem.setAttribute("cx", String.valueOf(x + w / 2));
			elem.setAttribute("cy", String.valueOf(y + h / 2));
			elem.setAttribute("rx", String.valueOf(w / 2));
			elem.setAttribute("ry", String.valueOf(h / 2));
		}
		else if (shape.equals(mxConstants.SHAPE_DOUBLE_ELLIPSE))
		{
			elem = document.createElement("g");
			background = document.createElement("ellipse");
			background.setAttribute("cx", String.valueOf(x + w / 2));
			background.setAttribute("cy", String.valueOf(y + h / 2));
			background.setAttribute("rx", String.valueOf(w / 2));
			background.setAttribute("ry", String.valueOf(h / 2));
			elem.appendChild(background);

			int inset = (int) ((3 + strokeWidth) * scale);

			Element foreground = document.createElement("ellipse");
			foreground.setAttribute("fill", "none");
			foreground.setAttribute("stroke", strokeColor);
			foreground
					.setAttribute("stroke-width", String.valueOf(strokeWidth));

			foreground.setAttribute("cx", String.valueOf(x + w / 2));
			foreground.setAttribute("cy", String.valueOf(y + h / 2));
			foreground.setAttribute("rx", String.valueOf(w / 2 - inset));
			foreground.setAttribute("ry", String.valueOf(h / 2 - inset));
			elem.appendChild(foreground);
		}
		else if (shape.equals(mxConstants.SHAPE_RHOMBUS))
		{
			elem = document.createElement("path");

			String d = "M " + (x + w / 2) + " " + y + " L " + (x + w) + " "
					+ (y + h / 2) + " L " + (x + w / 2) + " " + (y + h) + " L "
					+ x + " " + (y + h / 2);

			elem.setAttribute("d", d + " Z");
		}
		else if (shape.equals(mxConstants.SHAPE_TRIANGLE))
		{
			elem = document.createElement("path");
			String direction = mxUtils.getString(style,
					mxConstants.STYLE_DIRECTION, "");
			String d = null;

			if (direction.equals(mxConstants.DIRECTION_NORTH))
			{
				d = "M " + x + " " + (y + h) + " L " + (x + w / 2) + " " + y
						+ " L " + (x + w) + " " + (y + h);
			}
			else if (direction.equals(mxConstants.DIRECTION_SOUTH))
			{
				d = "M " + x + " " + y + " L " + (x + w / 2) + " " + (y + h)
						+ " L " + (x + w) + " " + y;
			}
			else if (direction.equals(mxConstants.DIRECTION_WEST))
			{
				d = "M " + (x + w) + " " + y + " L " + x + " " + (y + h / 2)
						+ " L " + (x + w) + " " + (y + h);
			}
			else
			// east
			{
				d = "M " + x + " " + y + " L " + (x + w) + " " + (y + h / 2)
						+ " L " + x + " " + (y + h);
			}

			elem.setAttribute("d", d + " Z");
		}
		else if (shape.equals(mxConstants.SHAPE_HEXAGON))
		{
			elem = document.createElement("path");
			String direction = mxUtils.getString(style,
					mxConstants.STYLE_DIRECTION, "");
			String d = null;

			if (direction.equals(mxConstants.DIRECTION_NORTH)
					|| direction.equals(mxConstants.DIRECTION_SOUTH))
			{
				d = "M " + (x + 0.5 * w) + " " + y + " L " + (x + w) + " "
						+ (y + 0.25 * h) + " L " + (x + w) + " "
						+ (y + 0.75 * h) + " L " + (x + 0.5 * w) + " "
						+ (y + h) + " L " + x + " " + (y + 0.75 * h) + " L "
						+ x + " " + (y + 0.25 * h);
			}
			else
			{
				d = "M " + (x + 0.25 * w) + " " + y + " L " + (x + 0.75 * w)
						+ " " + y + " L " + (x + w) + " " + (y + 0.5 * h)
						+ " L " + (x + 0.75 * w) + " " + (y + h) + " L "
						+ (x + 0.25 * w) + " " + (y + h) + " L " + x + " "
						+ (y + 0.5 * h);
			}

			elem.setAttribute("d", d + " Z");
		}
		else if (shape.equals(mxConstants.SHAPE_CLOUD))
		{
			elem = document.createElement("path");

			String d = "M " + (x + 0.25 * w) + " " + (y + 0.25 * h) + " C "
					+ (x + 0.05 * w) + " " + (y + 0.25 * h) + " " + x + " "
					+ (y + 0.5 * h) + " " + (x + 0.16 * w) + " "
					+ (y + 0.55 * h) + " C " + x + " " + (y + 0.66 * h) + " "
					+ (x + 0.18 * w) + " " + (y + 0.9 * h) + " "
					+ (x + 0.31 * w) + " " + (y + 0.8 * h) + " C "
					+ (x + 0.4 * w) + " " + (y + h) + " " + (x + 0.7 * w) + " "
					+ (y + h) + " " + (x + 0.8 * w) + " " + (y + 0.8 * h)
					+ " C " + (x + w) + " " + (y + 0.8 * h) + " " + (x + w)
					+ " " + (y + 0.6 * h) + " " + (x + 0.875 * w) + " "
					+ (y + 0.5 * h) + " C " + (x + w) + " " + (y + 0.3 * h)
					+ " " + (x + 0.8 * w) + " " + (y + 0.1 * h) + " "
					+ (x + 0.625 * w) + " " + (y + 0.2 * h) + " C "
					+ (x + 0.5 * w) + " " + (y + 0.05 * h) + " "
					+ (x + 0.3 * w) + " " + (y + 0.05 * h) + " "
					+ (x + 0.25 * w) + " " + (y + 0.25 * h);

			elem.setAttribute("d", d + " Z");
		}
		else if (shape.equals(mxConstants.SHAPE_ACTOR))
		{
			elem = document.createElement("path");
			double width3 = w / 3;

			String d = " M " + x + " " + (y + h) + " C " + x + " "
					+ (y + 3 * h / 5) + " " + x + " " + (y + 2 * h / 5) + " "
					+ (x + w / 2) + " " + (y + 2 * h / 5) + " C "
					+ (x + w / 2 - width3) + " " + (y + 2 * h / 5) + " "
					+ (x + w / 2 - width3) + " " + y + " " + (x + w / 2) + " "
					+ y + " C " + (x + w / 2 + width3) + " " + y + " "
					+ (x + w / 2 + width3) + " " + (y + 2 * h / 5) + " "
					+ (x + w / 2) + " " + (y + 2 * h / 5) + " C " + (x + w)
					+ " " + (y + 2 * h / 5) + " " + (x + w) + " "
					+ (y + 3 * h / 5) + " " + (x + w) + " " + (y + h);

			elem.setAttribute("d", d + " Z");
		}
		else if (shape.equals(mxConstants.SHAPE_CYLINDER))
		{
			elem = document.createElement("g");
			background = document.createElement("path");

			double dy = Math.min(40, Math.floor(h / 5));
			String d = " M " + x + " " + (y + dy) + " C " + x + " "
					+ (y - dy / 3) + " " + (x + w) + " " + (y - dy / 3) + " "
					+ (x + w) + " " + (y + dy) + " L " + (x + w) + " "
					+ (y + h - dy) + " C " + (x + w) + " " + (y + h + dy / 3)
					+ " " + x + " " + (y + h + dy / 3) + " " + x + " "
					+ (y + h - dy);
			background.setAttribute("d", d + " Z");
			elem.appendChild(background);

			Element foreground = document.createElement("path");
			d = "M " + x + " " + (y + dy) + " C " + x + " " + (y + 2 * dy)
					+ " " + (x + w) + " " + (y + 2 * dy) + " " + (x + w) + " "
					+ (y + dy);

			foreground.setAttribute("d", d);
			foreground.setAttribute("fill", "none");
			foreground.setAttribute("stroke", strokeColor);
			foreground
					.setAttribute("stroke-width", String.valueOf(strokeWidth));

			elem.appendChild(foreground);
		}
		else
		{
			elem = document.createElement("rect");

			elem.setAttribute("x", String.valueOf(x));
			elem.setAttribute("y", String.valueOf(y));
			elem.setAttribute("width", String.valueOf(w));
			elem.setAttribute("height", String.valueOf(h));

			if (mxUtils.isTrue(style, mxConstants.STYLE_ROUNDED, false))
			{
				elem.setAttribute("rx", String.valueOf(w
						* mxConstants.RECTANGLE_ROUNDING_FACTOR));
				elem.setAttribute("ry", String.valueOf(h
						* mxConstants.RECTANGLE_ROUNDING_FACTOR));
			}
		}

		Element bg = background;

		if (bg == null)
		{
			bg = elem;
		}

		bg.setAttribute("fill", fillColor);
		bg.setAttribute("stroke", strokeColor);
		bg.setAttribute("stroke-width", String.valueOf(strokeWidth));

		// Adds the shadow element
		Element shadowElement = null;

		if (mxUtils.isTrue(style, mxConstants.STYLE_SHADOW, false)
				&& !fillColor.equals("none"))
		{
			shadowElement = (Element) bg.cloneNode(true);

			shadowElement.setAttribute("transform",
					mxConstants.SVG_SHADOWTRANSFORM);
			shadowElement.setAttribute("fill", mxConstants.W3C_SHADOWCOLOR);
			shadowElement.setAttribute("stroke", mxConstants.W3C_SHADOWCOLOR);
			shadowElement.setAttribute("stroke-width", String
					.valueOf(strokeWidth));

			appendSvgElement(shadowElement);
		}

		// Applies rotation
		double rotation = mxUtils.getDouble(style, mxConstants.STYLE_ROTATION);

		if (rotation != 0)
		{
			int cx = x + w / 2;
			int cy = y + h / 2;

			elem.setAttribute("transform", "rotate(" + rotation + "," + cx
					+ "," + cy + ")");

			if (shadowElement != null)
			{
				shadowElement.setAttribute("transform", "rotate(" + rotation
						+ "," + cx + "," + cy + ") "
						+ mxConstants.SVG_SHADOWTRANSFORM);
			}
		}

		// Applies opacity
		float opacity = mxUtils.getFloat(style, mxConstants.STYLE_OPACITY, 100);

		if (opacity != 100)
		{
			String value = String.valueOf(opacity / 100);
			elem.setAttribute("fill-opacity", value);
			elem.setAttribute("stroke-opacity", value);

			if (shadowElement != null)
			{
				shadowElement.setAttribute("fill-opacity", value);
				shadowElement.setAttribute("stroke-opacity", value);
			}
		}

		if (mxUtils.isTrue(style, mxConstants.STYLE_DASHED))
		{
			elem.setAttribute("stroke-dasharray", "3, 3");
		}

		appendSvgElement(elem);

		return elem;
	}

	/**
	 * Draws the given lines as segments between all points of the given list
	 * of mxPoints.
	 * 
	 * @param pts List of points that define the line.
	 * @param style Style to be used for painting the line.
	 */
	public Element drawLine(List<mxPoint> pts, Map<String, Object> style)
	{
		Element group = document.createElement("g");
		Element path = document.createElement("path");

		String strokeColor = mxUtils.getString(style,
				mxConstants.STYLE_STROKECOLOR);
		float tmpStroke = (float) (mxUtils.getFloat(style,
				mxConstants.STYLE_STROKEWIDTH, 1));
		float strokeWidth = (float) (tmpStroke * scale);

		if (strokeColor != null && strokeWidth > 0)
		{
			// Draws the start marker
			Object marker = style.get(mxConstants.STYLE_STARTARROW);

			mxPoint pt = pts.get(1);
			mxPoint p0 = pts.get(0);
			mxPoint offset = null;

			if (marker != null)
			{
				float size = (float) (mxUtils.getFloat(style,
						mxConstants.STYLE_STARTSIZE,
						mxConstants.DEFAULT_MARKERSIZE));
				offset = drawMarker(group, marker, pt, p0, size, tmpStroke,
						strokeColor);
			}
			else
			{
				double dx = pt.getX() - p0.getX();
				double dy = pt.getY() - p0.getY();

				double dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
				double nx = dx * strokeWidth / dist;
				double ny = dy * strokeWidth / dist;

				offset = new mxPoint(nx / 2, ny / 2);
			}

			// Applies offset to the point
			if (offset != null)
			{
				p0 = (mxPoint) p0.clone();
				p0.setX(p0.getX() + offset.getX());
				p0.setY(p0.getY() + offset.getY());

				offset = null;
			}

			// Draws the end marker
			marker = style.get(mxConstants.STYLE_ENDARROW);

			pt = pts.get(pts.size() - 2);
			mxPoint pe = pts.get(pts.size() - 1);

			if (marker != null)
			{
				float size = (float) (mxUtils.getFloat(style,
						mxConstants.STYLE_ENDSIZE,
						mxConstants.DEFAULT_MARKERSIZE));
				offset = drawMarker(group, marker, pt, pe, size, tmpStroke,
						strokeColor);
			}
			else
			{
				double dx = pt.getX() - p0.getX();
				double dy = pt.getY() - p0.getY();

				double dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
				double nx = dx * strokeWidth / dist;
				double ny = dy * strokeWidth / dist;

				offset = new mxPoint(nx / 2, ny / 2);
			}

			// Applies offset to the point
			if (offset != null)
			{
				pe = (mxPoint) pe.clone();
				pe.setX(pe.getX() + offset.getX());
				pe.setY(pe.getY() + offset.getY());

				offset = null;
			}

			// Draws the line segments
			pt = p0;
			String d = "M " + pt.getX() + " " + pt.getY();

			for (int i = 1; i < pts.size() - 1; i++)
			{
				pt = pts.get(i);
				d += " L " + pt.getX() + " " + pt.getY();
			}

			d += " L " + pe.getX() + " " + pe.getY();

			path.setAttribute("d", d);
			path.setAttribute("stroke", strokeColor);
			path.setAttribute("fill", "none");
			path.setAttribute("stroke-width", String.valueOf(strokeWidth));

			if (mxUtils.isTrue(style, mxConstants.STYLE_DASHED))
			{
				path.setAttribute("stroke-dasharray", "3, 3");
			}

			group.appendChild(path);
			appendSvgElement(group);
		}

		return group;
	}

	/**
	 * Draws the specified marker as a child path in the given parent.
	 */
	public mxPoint drawMarker(Element parent, Object type, mxPoint p0,
			mxPoint pe, float size, float strokeWidth, String color)
	{
		mxPoint offset = null;

		// Computes the norm and the inverse norm
		double dx = pe.getX() - p0.getX();
		double dy = pe.getY() - p0.getY();

		double dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
		double absSize = size * scale;
		double nx = dx * absSize / dist;
		double ny = dy * absSize / dist;

		pe = (mxPoint) pe.clone();
		pe.setX(pe.getX() - nx * strokeWidth / (2 * size));
		pe.setY(pe.getY() - ny * strokeWidth / (2 * size));

		nx *= 0.5 + strokeWidth / 2;
		ny *= 0.5 + strokeWidth / 2;

		Element path = document.createElement("path");
		path.setAttribute("stroke-width", String.valueOf(strokeWidth * scale));
		path.setAttribute("stroke", color);
		path.setAttribute("fill", color);

		String d = null;

		if (type.equals(mxConstants.ARROW_CLASSIC)
				|| type.equals(mxConstants.ARROW_BLOCK))
		{
			d = "M "
					+ pe.getX()
					+ " "
					+ pe.getY()
					+ " L "
					+ (pe.getX() - nx - ny / 2)
					+ " "
					+ (pe.getY() - ny + nx / 2)
					+ ((!type.equals(mxConstants.ARROW_CLASSIC)) ? "" : " L "
							+ (pe.getX() - nx * 3 / 4) + " "
							+ (pe.getY() - ny * 3 / 4)) + " L "
					+ (pe.getX() + ny / 2 - nx) + " "
					+ (pe.getY() - ny - nx / 2) + " z";
		}
		else if (type.equals(mxConstants.ARROW_OPEN))
		{
			nx *= 1.2;
			ny *= 1.2;

			d = "M " + (pe.getX() - nx - ny / 2) + " "
					+ (pe.getY() - ny + nx / 2) + " L " + (pe.getX() - nx / 6)
					+ " " + (pe.getY() - ny / 6) + " L "
					+ (pe.getX() + ny / 2 - nx) + " "
					+ (pe.getY() - ny - nx / 2) + " M " + pe.getX() + " "
					+ pe.getY();
			path.setAttribute("fill", "none");
		}
		else if (type.equals(mxConstants.ARROW_OVAL))
		{
			nx *= 1.2;
			ny *= 1.2;
			absSize *= 1.2;

			d = "M " + (pe.getX() - ny / 2) + " " + (pe.getY() + nx / 2)
					+ " a " + (absSize / 2) + " " + (absSize / 2) + " 0  1,1 "
					+ (nx / 8) + " " + (ny / 8) + " z";
		}
		else if (type.equals(mxConstants.ARROW_DIAMOND))
		{
			d = "M " + (pe.getX() + nx / 2) + " " + (pe.getY() + ny / 2)
					+ " L " + (pe.getX() - ny / 2) + " " + (pe.getY() + nx / 2)
					+ " L " + (pe.getX() - nx / 2) + " " + (pe.getY() - ny / 2)
					+ " L " + (pe.getX() + ny / 2) + " " + (pe.getY() - nx / 2)
					+ " z";
		}

		if (d != null)
		{
			path.setAttribute("d", d);
			parent.appendChild(path);
		}

		return offset;
	}

	/**
	 * Draws the specified text either using drawHtmlString or using drawString.
	 * 
	 * @param text Text to be painted.
	 * @param x X-coordinate of the text.
	 * @param y Y-coordinate of the text.
	 * @param w Width of the text.
	 * @param h Height of the text.
	 * @param style Style to be used for painting the text.
	 */
	public Object drawText(String text, int x, int y, int w, int h,
			Map<String, Object> style)
	{
		Element elem = null;
		String fontColor = mxUtils.getString(style,
				mxConstants.STYLE_FONTCOLOR, "black");
		String fontFamily = mxUtils.getString(style,
				mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILIES);
		int fontSize = (int) (mxUtils.getInt(style, mxConstants.STYLE_FONTSIZE,
				mxConstants.DEFAULT_FONTSIZE) * scale);

		if (text != null && text.length() > 0)
		{
			elem = document.createElement("text");

			// Applies the opacity
			float opacity = mxUtils.getFloat(style,
					mxConstants.STYLE_TEXT_OPACITY, 100);

			if (opacity != 100)
			{
				String value = String.valueOf(opacity / 100);
				elem.setAttribute("fill-opacity", value);
				elem.setAttribute("stroke-opacity", value);
			}

			elem.setAttribute("text-anchor", "middle");
			elem.setAttribute("font-weight", "normal");
			elem.setAttribute("font-decoration", "none");

			elem.setAttribute("font-size", String.valueOf(fontSize));
			elem.setAttribute("font-family", fontFamily);
			elem.setAttribute("fill", fontColor);

			String[] lines = text.split("\n");
			y += fontSize
					+ (h - lines.length * (fontSize + mxConstants.LINESPACING))
					/ 2 - 2;

			for (int i = 0; i < lines.length; i++)
			{
				Element tspan = document.createElement("tspan");

				tspan.setAttribute("x", String.valueOf(x + w / 2));
				tspan.setAttribute("y", String.valueOf(y));

				tspan.appendChild(document.createTextNode(lines[i]));
				elem.appendChild(tspan);

				y += fontSize + mxConstants.LINESPACING;
			}

			appendSvgElement(elem);
		}

		return elem;
	}

}
