/*
 * Copyright 2007-2012 Hidekatsu Izuno, Shunsuke Mori
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package net.arnx.wmf2svg.gdi.svg;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.logging.Logger;

import javax.xml.parsers.*;
import org.w3c.dom.*;

import net.arnx.wmf2svg.gdi.*;
import net.arnx.wmf2svg.util.Base64;
import net.arnx.wmf2svg.util.ImageUtil;

/**
 * @author Hidekatsu Izuno
 * @author Shunsuke Mori
 */
public class SvgGdi implements Gdi {
	private static Logger log = Logger.getLogger(SvgGdi.class.getName());
	
	private boolean compatible;
	
	private boolean replaceSymbolFont = false;
	
	private Properties props = new Properties();

	private SvgDc dc;

	private LinkedList saveDC = new LinkedList();

	private Document doc = null;
	
	private Element parentNode = null;
	
	private Element styleNode = null;
	
	private Element defsNode = null;

	private int brushNo = 0;

	private int fontNo = 0;

	private int penNo = 0;

	private int patternNo = 0;
	
	private int rgnNo = 0;
	
	private int clipPathNo = 0;
	
	private int maskNo = 0;
	
	private Map nameMap = new HashMap();

	private StringBuffer buffer = new StringBuffer();

	private SvgBrush defaultBrush;

	private SvgPen defaultPen;

	private SvgFont defaultFont;
	
	public SvgGdi() throws SvgGdiException {
		this(false);
	}
	
	public SvgGdi(boolean compatible) throws SvgGdiException {
		this.compatible = compatible;
		
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = null;
		try {
			builder = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			throw new SvgGdiException(e);
		}

		DOMImplementation dom = builder.getDOMImplementation();
		doc = dom.createDocument("http://www.w3.org/2000/svg", "svg", null);
		
		InputStream in = null;
		try {
			in = getClass().getResourceAsStream("SvgGdi.properties");
			props.load(in);
		} catch (Exception e) {
			throw new SvgGdiException("properties format error: SvgGDI.properties");
		} finally {
			try {
				if (in != null) in.close();
			} catch (IOException e) {
				// no handle
			}
		}
	}
	
	public void setCompatible(boolean flag) {
		compatible = flag;
	}
	
	public boolean isCompatible() {
		return compatible;
	}
	
	public void setReplaceSymbolFont(boolean flag) {
		replaceSymbolFont = flag;
	}
	
	public boolean isReplaceSymbolFont() {
		return replaceSymbolFont;
	}

	public SvgDc getDC() {
		return dc;
	}

	public String getProperty(String key) {
		return props.getProperty(key);
	}

	public Document getDocument() {
		return doc;
	}
	
	public Element getDefsElement() {
		return defsNode;
	}
	
	public Element getStyleElement() {
		return styleNode;
	}

	public void placeableHeader(int wsx, int wsy, int wex, int wey, int dpi) {
		if (parentNode == null) {
			init();
		}
		
		dc.setWindowExtEx(Math.abs(wex - wsx), Math.abs(wey - wsy), null);
		dc.setDpi(dpi);

		Element root = doc.getDocumentElement();
		root.setAttribute("width", ""
				+ (Math.abs(wex - wsx) / (double) dc.getDpi()) + "in");
		root.setAttribute("height", ""
				+ (Math.abs(wey - wsy) / (double) dc.getDpi()) + "in");
	}

	public void header() {
		if (parentNode == null) {
			init();
		}
	}

	private void init() {
		dc = new SvgDc(this);

		Element root = doc.getDocumentElement();
		root.setAttribute("xmlns", "http://www.w3.org/2000/svg");
		root.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
		
		defsNode = doc.createElement("defs");
		root.appendChild(defsNode);

		styleNode = doc.createElement("style");
		styleNode.setAttribute("type", "text/css");
		root.appendChild(styleNode);
		
		parentNode = doc.createElement("g");
		doc.getDocumentElement().appendChild(parentNode);

		defaultBrush = (SvgBrush) createBrushIndirect(GdiBrush.BS_SOLID,
				0x00FFFFFF, 0);
		defaultPen = (SvgPen) createPenIndirect(GdiPen.PS_SOLID, 1,
				0x00000000);
		defaultFont = null;

		dc.setBrush(defaultBrush);
		dc.setPen(defaultPen);
		dc.setFont(defaultFont);
	}

	public void animatePalette(GdiPalette palette, int startIndex, int[] entries) {
		// TODO
		log.fine("not implemented: animatePalette");
	}

	public void arc(int sxr, int syr, int exr, int eyr, int sxa, int sya,
			int exa, int eya) {
		
		double rx = (Math.abs(exr - sxr) - 1) / 2.0;
		double ry = (Math.abs(eyr - syr) - 1) / 2.0;
		if (rx <= 0 || ry <= 0) return;
		
		double cx = Math.min(sxr, exr) + rx;
		double cy = Math.min(syr, eyr) + ry;
		
		Element elem = null;
		if (sxa == exa && sya == eya) {
			if (rx == ry) {
				elem = doc.createElement("circle");
				elem.setAttribute("cx", "" + dc.toAbsoluteX(cx));
				elem.setAttribute("cy", "" + dc.toAbsoluteY(cy));
				elem.setAttribute("r", "" + dc.toRelativeX(rx));
			} else {
				elem = doc.createElement("ellipse");
				elem.setAttribute("cx", "" + dc.toAbsoluteX(cx));
				elem.setAttribute("cy", "" + dc.toAbsoluteY(cy));
				elem.setAttribute("rx", "" + dc.toRelativeX(rx));
				elem.setAttribute("ry", "" + dc.toRelativeY(ry));
			}
		} else {
			double sa = Math.atan2((sya - cy) * rx, (sxa - cx) * ry);
			double sx = rx * Math.cos(sa);
			double sy = ry * Math.sin(sa);
			
			double ea = Math.atan2((eya - cy) * rx, (exa - cx) * ry);
			double ex = rx * Math.cos(ea);
			double ey = ry * Math.sin(ea);
			
			double a = Math.atan2((ex-sx) * (-sy) - (ey-sy) * (-sx), (ex-sx) * (-sx) + (ey-sy) * (-sy));
			
			elem = doc.createElement("path");
			elem.setAttribute("d", "M " + dc.toAbsoluteX(sx + cx) + "," + dc.toAbsoluteY(sy + cy)
					+ " A " + dc.toRelativeX(rx)  + "," + dc.toRelativeY(ry)
					+ " 0 " + (a > 0 ? "1" : "0") + " 0"
					+ " " + dc.toAbsoluteX(ex + cx) + "," + dc.toAbsoluteY(ey + cy));
		}
		
		if (dc.getPen() != null) {
			elem.setAttribute("class", getClassString(dc.getPen()));
		}
		elem.setAttribute("fill", "none");
		parentNode.appendChild(elem);
	}

	public void bitBlt(byte[] image, int dx, int dy, int dw, int dh, 
			int sx, int sy, long rop) {
		bmpToSvg(image, dx, dy, dw, dh, sx, sy, dw, dh, Gdi.DIB_RGB_COLORS, rop);
	}

	public void chord(int sxr, int syr, int exr, int eyr, int sxa, int sya,
			int exa, int eya) {
		double rx = (Math.abs(exr - sxr) - 1) / 2.0;
		double ry = (Math.abs(eyr - syr) - 1) / 2.0;
		if (rx <= 0 || ry <= 0) return;
		
		double cx = Math.min(sxr, exr) + rx;
		double cy = Math.min(syr, eyr) + ry;
		
		Element elem = null;
		if (sxa == exa && sya == eya) {
			if (rx == ry) {
				elem = doc.createElement("circle");
				elem.setAttribute("cx", "" + dc.toAbsoluteX(cx));
				elem.setAttribute("cy", "" + dc.toAbsoluteY(cy));
				elem.setAttribute("r", "" + dc.toRelativeX(rx));
			} else {
				elem = doc.createElement("ellipse");
				elem.setAttribute("cx", "" + dc.toAbsoluteX(cx));
				elem.setAttribute("cy", "" + dc.toAbsoluteY(cy));
				elem.setAttribute("rx", "" + dc.toRelativeX(rx));
				elem.setAttribute("ry", "" + dc.toRelativeY(ry));
			}
		} else {
			double sa = Math.atan2((sya - cy) * rx, (sxa - cx) * ry);
			double sx = rx * Math.cos(sa);
			double sy = ry * Math.sin(sa);
			
			double ea = Math.atan2((eya - cy) * rx, (exa - cx) * ry);
			double ex = rx * Math.cos(ea);
			double ey = ry * Math.sin(ea);
			
			double a = Math.atan2((ex-sx) * (-sy) - (ey-sy) * (-sx), (ex-sx) * (-sx) + (ey-sy) * (-sy));
			
			elem = doc.createElement("path");
			elem.setAttribute("d", "M " + dc.toAbsoluteX(sx + cx) + "," + dc.toAbsoluteY(sy + cy)
					+ " A " + dc.toRelativeX(rx)  + "," + dc.toRelativeY(ry)
					+ " 0 " + (a > 0 ? "1" : "0") + " 0"
					+ " " + dc.toAbsoluteX(ex + cx) + "," + dc.toAbsoluteY(ey + cy) + " z");
		}

		if (dc.getPen() != null || dc.getBrush() != null) {
			elem.setAttribute("class", getClassString(dc.getPen(), dc.getBrush()));
			if (dc.getBrush() != null
					&& dc.getBrush().getStyle() == GdiBrush.BS_HATCHED) {
				String id = "pattern" + (patternNo++);
				elem.setAttribute("fill", "url(#" + id + ")");
				defsNode.appendChild(dc.getBrush().createFillPattern(id));
			}
		}

		parentNode.appendChild(elem);
	}

	public GdiBrush createBrushIndirect(int style, int color, int hatch) {
		SvgBrush brush = new SvgBrush(this, style, color, hatch);
		if (!nameMap.containsKey(brush)) {
			String name = "brush" + (brushNo++);
			nameMap.put(brush, name);
			styleNode.appendChild(brush.createTextNode(name));
		}
		return brush;
	}

	public GdiFont createFontIndirect(int height, int width, int escapement,
			int orientation, int weight, boolean italic, boolean underline,
			boolean strikeout, int charset, int outPrecision,
			int clipPrecision, int quality, int pitchAndFamily, byte[] faceName) {
		SvgFont font = new SvgFont(this, height, width, escapement,
				orientation, weight, italic, underline, strikeout, charset,
				outPrecision, clipPrecision, quality, pitchAndFamily, faceName);
		if (!nameMap.containsKey(font)) {
			String name = "font" + (fontNo++);
			nameMap.put(font, name);
			styleNode.appendChild(font.createTextNode(name));
		}
		return font;
	}
	
	public GdiPalette createPalette(int version, int[] entries) {
		return new SvgPalette(this, version, entries);
	}
	
	public GdiPatternBrush createPatternBrush(byte[] image) {
		return new SvgPatternBrush(this, image);
	}

	public GdiPen createPenIndirect(int style, int width, int color) {
		SvgPen pen = new SvgPen(this, style, width, color);
		if (!nameMap.containsKey(pen)) {
			String name = "pen" + (penNo++);
			nameMap.put(pen, name);
			styleNode.appendChild(pen.createTextNode(name));
		}
		return pen;
	}
	
	public GdiRegion createRectRgn(int left, int top, int right, int bottom) {
		SvgRectRegion rgn = new SvgRectRegion(this, left, top, right, bottom);
		if (!nameMap.containsKey(rgn)) {
			nameMap.put(rgn, "rgn" + (rgnNo++));
			defsNode.appendChild(rgn.createElement());
		}
		return rgn;
	}

	public void deleteObject(GdiObject obj) {
		if (dc.getBrush() == obj) {
			dc.setBrush(defaultBrush);
		} else if (dc.getFont() == obj) {
			dc.setFont(defaultFont);
		} else if (dc.getPen() == obj) {
			dc.setPen(defaultPen);
		}
	}
	
	public void dibBitBlt(byte[] image, int dx, int dy, int dw, int dh,
			int sx, int sy, long rop) {
		bitBlt(image, dx, dy, dw, dh, sx, sy, rop);
	}

	public GdiPatternBrush dibCreatePatternBrush(byte[] image, int usage) {
		// TODO usage
		return new SvgPatternBrush(this, image);
	}
	
    public void dibStretchBlt(byte[] image, int dx, int dy, int dw, int dh,
			int sx, int sy, int sw, int sh, long rop) {
		
    	this.stretchDIBits(dx, dy, dw, dh, sx, sy, sw, sh, image, Gdi.DIB_RGB_COLORS, rop);
    }

	public void ellipse(int sx, int sy, int ex, int ey) {
		Element elem = doc.createElement("ellipse");

		if (dc.getPen() != null || dc.getBrush() != null) {
			elem.setAttribute("class", getClassString(dc.getPen(), dc
					.getBrush()));
			if (dc.getBrush() != null
					&& dc.getBrush().getStyle() == GdiBrush.BS_HATCHED) {
				String id = "pattern" + (patternNo++);
				elem.setAttribute("fill", "url(#" + id + ")");
				defsNode.appendChild(dc.getBrush().createFillPattern(id));
			}
		}

		elem.setAttribute("cx", "" + (int)dc.toAbsoluteX((sx + ex) / 2));
		elem.setAttribute("cy", "" + (int)dc.toAbsoluteY((sy + ey) / 2));
		elem.setAttribute("rx", "" + (int)dc.toRelativeX((ex - sx) / 2));
		elem.setAttribute("ry", "" + (int)dc.toRelativeY((ey - sy) / 2));
		parentNode.appendChild(elem);
	}

	public void escape(byte[] data) {
	}

	public int excludeClipRect(int left, int top, int right, int bottom) {
		Element mask = dc.getMask();
		if (mask != null) {
			mask = (Element)mask.cloneNode(true);
			String name = "mask" + (maskNo++);
			mask.setAttribute("id", name);
			defsNode.appendChild(mask);
			
			Element unclip = doc.createElement("rect");
			unclip.setAttribute("x", "" + (int)dc.toAbsoluteX(left));
			unclip.setAttribute("y", "" + (int)dc.toAbsoluteY(top));
			unclip.setAttribute("width", "" + (int)dc.toRelativeX(right - left));
			unclip.setAttribute("height", "" + (int)dc.toRelativeY(bottom - top));
			unclip.setAttribute("fill", "black");
			mask.appendChild(unclip);
			dc.setMask(mask);
			
			// TODO
			return GdiRegion.COMPLEXREGION;
		} else {
			return GdiRegion.NULLREGION;
		}
	}

	public void extFloodFill(int x, int y, int color, int type) {
		// TODO
		log.fine("not implemented: extFloodFill");
	}

	public void extTextOut(int x, int y, int options, int[] rect, byte[] text, int[] dx) {
		Element elem = doc.createElement("text");

		int escapement = 0;
		boolean vertical = false;
		if (dc.getFont() != null) {
			elem.setAttribute("class", getClassString(dc.getFont()));
			if (dc.getFont().getFaceName().startsWith("@")) {
				vertical = true;
				escapement = dc.getFont().getEscapement()-2700;
			} else {
				escapement = dc.getFont().getEscapement();
			}
		}
		elem.setAttribute("fill", SvgObject.toColor(dc.getTextColor()));

		// style
		buffer.setLength(0);
		int align = dc.getTextAlign();

		if ((align & (TA_LEFT|TA_CENTER|TA_RIGHT)) == TA_RIGHT) {
			buffer.append("text-anchor: end; ");
		} else if ((align & (TA_LEFT|TA_CENTER|TA_RIGHT)) == TA_CENTER) {
			buffer.append("text-anchor: middle; ");
		}
		
		if (compatible) {
			buffer.append("dominant-baseline: alphabetic; ");
		} else {
			if (vertical) {
				elem.setAttribute("writing-mode", "tb");
			} else {
				if ((align & (TA_BOTTOM|TA_TOP|TA_BASELINE)) == TA_BASELINE) {
					buffer.append("dominant-baseline: alphabetic; ");
				} else {
					buffer.append("dominant-baseline: text-before-edge; ");				
				}
			}
		}

		if ((align & TA_RTLREADING) == TA_RTLREADING  || (options & ETO_RTLREADING) > 0) {
			buffer.append("unicode-bidi: bidi-override; direction: rtl; ");
		}

		if (dc.getTextSpace() > 0) {
			buffer.append("word-spacing: ").append(dc.getTextSpace()).append("; ");
		}
		
		if (buffer.length() > 0) {
			buffer.setLength(buffer.length()-1);
			elem.setAttribute("style", buffer.toString());
		}

		elem.setAttribute("stroke", "none");

		if ((align & (TA_NOUPDATECP|TA_UPDATECP)) == TA_UPDATECP) {
			x = dc.getCurrentX();
			y = dc.getCurrentY();
		}

		// x
		int ax = (int)dc.toAbsoluteX(x);
		int width = 0;
		if (vertical) {
			elem.setAttribute("x", Integer.toString(ax));
			if (dc.getFont() != null) width = Math.abs(dc.getFont().getFontSize());
		} else {
			if (dc.getFont() != null) {
				dx = dc.getFont().validateDx(text, dx);
			}
			
			if (dx != null && dx.length > 0) {
				for (int i = 0; i < dx.length; i++) {
					width += dx[i];
				}

				int tx = x;

				if ((align & (TA_LEFT|TA_CENTER|TA_RIGHT)) == TA_RIGHT) {
					tx -= (width-dx[dx.length-1]);
				} else if ((align & (TA_LEFT|TA_CENTER|TA_RIGHT)) == TA_CENTER) {
					tx -= (width-dx[dx.length-1]) / 2;
				}
				
				buffer.setLength(0);
				for (int i = 0; i < dx.length; i++) {
					if (i > 0) buffer.append(" ");
					buffer.append((int)dc.toAbsoluteX(tx));
					tx += dx[i];
				}
				if ((align & (TA_NOUPDATECP|TA_UPDATECP)) == TA_UPDATECP) {
					dc.moveToEx(tx, y, null);
				}
				elem.setAttribute("x", buffer.toString());
			} else {
				if (dc.getFont() != null) width = Math.abs(dc.getFont().getFontSize() * text.length)/2;
				elem.setAttribute("x", Integer.toString(ax));				
			}
		}
		
		// y
		int ay = (int)dc.toAbsoluteY(y);
		int height = 0;
		if (vertical) {
			if (dc.getFont() != null) {
				dx = dc.getFont().validateDx(text, dx);
			}
			
			buffer.setLength(0);
			if(align == 0) {
				buffer.append(ay + (int)dc.toRelativeY(Math.abs(dc.getFont().getHeight())));
			} else {
				buffer.append(ay);
			}
			
			if (dx != null && dx.length > 0) {
				for (int i = 0; i < dx.length - 1; i++) {
					height += dx[i];
				}
				
				int ty = y;

				if ((align & (TA_LEFT|TA_CENTER|TA_RIGHT)) == TA_RIGHT) {
					ty -= (height-dx[dx.length-1]);
				} else if ((align & (TA_LEFT|TA_CENTER|TA_RIGHT)) == TA_CENTER) {
					ty -= (height-dx[dx.length-1]) / 2;
				}
				
				for (int i = 0; i < dx.length; i++) {
					buffer.append(" ");
					buffer.append((int)dc.toAbsoluteY(ty));
					ty += dx[i];
				}
	
				if ((align & (TA_NOUPDATECP|TA_UPDATECP)) == TA_UPDATECP) {
					dc.moveToEx(x, ty, null);
				}
			} else {
				if (dc.getFont() != null) height = Math.abs(dc.getFont().getFontSize() * text.length)/2;
			}
			elem.setAttribute("y", buffer.toString());
		} else {
			if (dc.getFont() != null) height = Math.abs(dc.getFont().getFontSize());
			if (compatible) {
				if ((align & (TA_BOTTOM|TA_TOP|TA_BASELINE)) == TA_TOP) {
					elem.setAttribute("y", Integer.toString(ay + (int)dc.toRelativeY(height*0.88)));	
				} else if ((align & (TA_BOTTOM|TA_TOP|TA_BASELINE)) == TA_BOTTOM) {
					elem.setAttribute("y", Integer.toString(ay + rect[3] - rect[1] + (int)dc.toRelativeY(height*0.88)));					
				} else {
					elem.setAttribute("y", Integer.toString(ay));
				}
			} else {
				if((align & (TA_BOTTOM|TA_TOP|TA_BASELINE)) == TA_BOTTOM && rect != null) {
					elem.setAttribute("y", Integer.toString(ay + rect[3] - rect[1] - (int)dc.toRelativeY(height)));
				} else {
					elem.setAttribute("y", Integer.toString(ay));
				}
			}
		}
		
		Element bk = null;
		if (dc.getBkMode() == OPAQUE || (options & ETO_OPAQUE) > 0) {
			if (rect == null && dc.getFont() != null) {
				rect = new int[4];
				if (vertical) {
					if ((align & (TA_BOTTOM|TA_TOP|TA_BASELINE)) == TA_BOTTOM) {
						rect[0] = x - width;
					} else if ((align & (TA_BOTTOM|TA_TOP|TA_BASELINE)) == TA_BASELINE) {
						rect[0] = x - (int)(width * 0.85);
					} else {
						rect[0] = x;
					}
					if ((align & (TA_LEFT|TA_RIGHT|TA_CENTER)) == TA_RIGHT) {
						rect[1] = y - height;
					} else if ((align & (TA_LEFT|TA_RIGHT|TA_CENTER)) == TA_CENTER) {
						rect[1] = y - height/2;
					} else {
						rect[1] = y;
					}
				} else {
					if ((align & (TA_LEFT|TA_RIGHT|TA_CENTER)) == TA_RIGHT) {
						rect[0] = x-width;
					} else if ((align & (TA_LEFT|TA_RIGHT|TA_CENTER)) == TA_CENTER) {
						rect[0] = x-width/2;
					} else {
						rect[0] = x;
					}
					if ((align & (TA_BOTTOM|TA_TOP|TA_BASELINE)) == TA_BOTTOM) {
						rect[1] = y - height;
					} else if ((align & (TA_BOTTOM|TA_TOP|TA_BASELINE)) == TA_BASELINE) {
						rect[1] = y - (int)(height * 0.85);
					} else {
						rect[1] = y;
					}
				}
				rect[2] = rect[0] + width;
				rect[3] = rect[1] + height;
			}
			bk = doc.createElement("rect");
			bk.setAttribute("x", Integer.toString((int)dc.toAbsoluteX(rect[0])));
			bk.setAttribute("y", Integer.toString((int)dc.toAbsoluteY(rect[1])));
			bk.setAttribute("width", Integer.toString((int)dc.toRelativeX(rect[2] - rect[0])));
			bk.setAttribute("height", Integer.toString((int)dc.toRelativeY(rect[3] - rect[1])));
			bk.setAttribute("fill", SvgObject.toColor(dc.getBkColor()));
		}
		
		Element clip = null;
		if ((options & ETO_CLIPPED) > 0) {
			String name = "clipPath" + (clipPathNo++);
			clip = doc.createElement("clipPath");
			clip.setAttribute("id", name);
			clip.setIdAttribute("id", true);
			
			Element clipRect = doc.createElement("rect");
			clipRect.setAttribute("x", Integer.toString((int)dc.toAbsoluteX(rect[0])));
			clipRect.setAttribute("y", Integer.toString((int)dc.toAbsoluteY(rect[1])));
			clipRect.setAttribute("width", Integer.toString((int)dc.toRelativeX(rect[2] - rect[0])));
			clipRect.setAttribute("height", Integer.toString((int)dc.toRelativeY(rect[3] - rect[1])));
			
			clip.appendChild(clipRect);
			elem.setAttribute("clip-path", "url(#" + name + ")");
		}
		
		String str = null;
		if (dc.getFont() != null) {
			str = GdiUtils.convertString(text, dc.getFont().getCharset());
		} else {
			str = GdiUtils.convertString(text, GdiFont.DEFAULT_CHARSET);
		}

		if (dc.getFont() != null && dc.getFont().getLang() != null) {
			elem.setAttribute("xml:lang", dc.getFont().getLang());
		}
		
		elem.setAttribute("xml:space", "preserve");
		appendText(elem, str);
		
		if (bk != null || clip != null) {
			Element g = doc.createElement("g");
			if (bk != null) g.appendChild(bk);
			if (clip != null) g.appendChild(clip);
			g.appendChild(elem);
			elem = g;
		}
		
		if (escapement != 0)  {
			elem.setAttribute("transform", "rotate(" + (-escapement/10.0) + ", " + ax + ", " + ay + ")");
		}
		parentNode.appendChild(elem);
	}

	public void fillRgn(GdiRegion rgn, GdiBrush brush) {
		if (rgn == null) return;
		
		Element elem = doc.createElement("use");
		elem.setAttribute("xlink:href", "url(#" + nameMap.get(rgn) + ")");
		elem.setAttribute("class", getClassString(brush));
		SvgBrush sbrush = (SvgBrush)brush;
		if(sbrush.getStyle() == GdiBrush.BS_HATCHED) {
			String id = "pattern" + (patternNo++);
			elem.setAttribute("fill", "url(#" + id + ")");
			defsNode.appendChild(sbrush.createFillPattern(id));
		}
		parentNode.appendChild(elem);
	}

	public void floodFill(int x, int y, int color) {
		// TODO
		log.fine("not implemented: floodFill");
	}

	public void frameRgn(GdiRegion rgn, GdiBrush brush, int width, int height) {
		// TODO
		log.fine("not implemented: frameRgn");
	}

	public void intersectClipRect(int left, int top, int right, int bottom) {
		// TODO
		log.fine("not implemented: intersectClipRect");
	}

	public void invertRgn(GdiRegion rgn) {
		if (rgn == null) return;
		
		Element elem = doc.createElement("use");
		elem.setAttribute("xlink:href", "url(#" + nameMap.get(rgn) + ")");
		String ropFilter = dc.getRopFilter(DSTINVERT);
		if (ropFilter != null) {
			elem.setAttribute("filter", ropFilter);
		}
		parentNode.appendChild(elem);
	}

	public void lineTo(int ex, int ey) {
		Element elem = doc.createElement("line");
		if (dc.getPen() != null) {
			elem.setAttribute("class", getClassString(dc.getPen()));
		}

		elem.setAttribute("fill", "none");

		elem.setAttribute("x1", "" + (int)dc.toAbsoluteX(dc.getCurrentX()));
		elem.setAttribute("y1", "" + (int)dc.toAbsoluteY(dc.getCurrentY()));
		elem.setAttribute("x2", "" + (int)dc.toAbsoluteX(ex));
		elem.setAttribute("y2", "" + (int)dc.toAbsoluteY(ey));
		parentNode.appendChild(elem);

		dc.moveToEx(ex, ey, null);
	}

	public void moveToEx(int x, int y, Point old) {
		dc.moveToEx(x, y, old);
	}

	public void offsetClipRgn(int x, int y) {
		dc.offsetClipRgn(x, y);
		Element mask = dc.getMask();
		if (mask != null) {
			mask = (Element)mask.cloneNode(true);
			String name = "mask" + (maskNo++);
			mask.setAttribute("id", name);
			if (dc.getOffsetClipX() != 0 || dc.getOffsetClipY() != 0) {
				mask.setAttribute("transform", "translate(" + dc.getOffsetClipX() + "," + dc.getOffsetClipY() + ")");
			}
			defsNode.appendChild(mask);
			
			if (!parentNode.hasChildNodes()) {
				doc.getDocumentElement().removeChild(parentNode);
			}
			parentNode = doc.createElement("g");
			parentNode.setAttribute("mask", name);
			doc.getDocumentElement().appendChild(parentNode);
			
			dc.setMask(mask);
		}
	}

	public void offsetViewportOrgEx(int x, int y, Point point) {
		dc.offsetViewportOrgEx(x, y, point);
	}

	public void offsetWindowOrgEx(int x, int y, Point point) {
		dc.offsetWindowOrgEx(x, y, point);
	}

	public void paintRgn(GdiRegion rgn) {
		fillRgn(rgn, dc.getBrush());
	}

	public void patBlt(int x, int y, int width, int height, long rop) {
		// TODO
		log.fine("not implemented: patBlt");
	}

	public void pie(int sxr, int syr, int exr, int eyr, int sxa, int sya,
			int exa, int eya) {
		double rx = (Math.abs(exr - sxr) - 1) / 2.0;
		double ry = (Math.abs(eyr - syr) - 1) / 2.0;
		if (rx <= 0 || ry <= 0) return;
		
		double cx = Math.min(sxr, exr) + rx;
		double cy = Math.min(syr, eyr) + ry;
		
		Element elem = null;
		if (sxa == exa && sya == eya) {
			if (rx == ry) {
				elem = doc.createElement("circle");
				elem.setAttribute("cx", "" + dc.toAbsoluteX(cx));
				elem.setAttribute("cy", "" + dc.toAbsoluteY(cy));
				elem.setAttribute("r", "" + dc.toRelativeX(rx));
			} else {
				elem = doc.createElement("ellipse");
				elem.setAttribute("cx", "" + dc.toAbsoluteX(cx));
				elem.setAttribute("cy", "" + dc.toAbsoluteY(cy));
				elem.setAttribute("rx", "" + dc.toRelativeX(rx));
				elem.setAttribute("ry", "" + dc.toRelativeY(ry));
			}
		} else {
			double sa = Math.atan2((sya - cy) * rx, (sxa - cx) * ry);
			double sx = rx * Math.cos(sa);
			double sy = ry * Math.sin(sa);
			
			double ea = Math.atan2((eya - cy) * rx, (exa - cx) * ry);
			double ex = rx * Math.cos(ea);
			double ey = ry * Math.sin(ea);
			
			double a = Math.atan2((ex-sx) * (-sy) - (ey-sy) * (-sx), (ex-sx) * (-sx) + (ey-sy) * (-sy));
			
			elem = doc.createElement("path");
			elem.setAttribute("d", "M " + dc.toAbsoluteX(sx + cx) + "," + dc.toAbsoluteY(sy + cy)
					+ " L " + dc.toAbsoluteX(sx + cx) + "," + dc.toAbsoluteY(sy + cy)
					+ " A " + dc.toRelativeX(rx)  + "," + dc.toRelativeY(ry)
					+ " 0 " + (a > 0 ? "1" : "0") + " 0"
					+ " " + dc.toAbsoluteX(ex + cx) + "," + dc.toAbsoluteY(ey + cy) + " z");
		}

		if (dc.getPen() != null || dc.getBrush() != null) {
			elem.setAttribute("class", getClassString(dc.getPen(), dc
					.getBrush()));
			if (dc.getBrush() != null
					&& dc.getBrush().getStyle() == GdiBrush.BS_HATCHED) {
				String id = "pattern" + (patternNo++);
				elem.setAttribute("fill", "url(#" + id + ")");
				defsNode.appendChild(dc.getBrush().createFillPattern(id));
			}
		}
		parentNode.appendChild(elem);
	}

	public void polygon(Point[] points) {
		Element elem = doc.createElement("polygon");

		if (dc.getPen() != null || dc.getBrush() != null) {
			elem.setAttribute("class", getClassString(dc.getPen(), dc
					.getBrush()));
			if (dc.getBrush() != null
					&& dc.getBrush().getStyle() == GdiBrush.BS_HATCHED) {
				String id = "pattern" + (patternNo++);
				elem.setAttribute("fill", "url(#" + id + ")");
				defsNode.appendChild(dc.getBrush().createFillPattern(id));
			}
			if (dc.getPolyFillMode() == WINDING) {
				elem.setAttribute("fill-rule", "nonzero");
			}
		}

		buffer.setLength(0);
		for (int i = 0; i < points.length; i++) {
			if (i != 0) {
				buffer.append(" ");
			}
			buffer.append((int)dc.toAbsoluteX(points[i].x)).append(",");
			buffer.append((int)dc.toAbsoluteY(points[i].y));
		}
		elem.setAttribute("points", buffer.toString());
		parentNode.appendChild(elem);
	}

	public void polyline(Point[] points) {
		Element elem = doc.createElement("polyline");
		if (dc.getPen() != null) {
			elem.setAttribute("class", getClassString(dc.getPen()));
		}
		elem.setAttribute("fill", "none");

		buffer.setLength(0);
		for (int i = 0; i < points.length; i++) {
			if (i != 0)
				buffer.append(" ");
			buffer.append((int)dc.toAbsoluteX(points[i].x)).append(",");
			buffer.append((int)dc.toAbsoluteY(points[i].y));
		}
		elem.setAttribute("points", buffer.toString());
		parentNode.appendChild(elem);
	}

	public void polyPolygon(Point[][] points) {
		Element elem = doc.createElement("path");

		if (dc.getPen() != null || dc.getBrush() != null) {
			elem.setAttribute("class", getClassString(dc.getPen(), dc
					.getBrush()));
			if (dc.getBrush() != null
					&& dc.getBrush().getStyle() == GdiBrush.BS_HATCHED) {
				String id = "pattern" + (patternNo++);
				elem.setAttribute("fill", "url(#" + id + ")");
				defsNode.appendChild(dc.getBrush().createFillPattern(id));
			}
			if (dc.getPolyFillMode() == WINDING) {
				elem.setAttribute("fill-rule", "nonzero");
			}
		}

		buffer.setLength(0);
		for (int i = 0; i < points.length; i++) {
			if (i != 0) {
				buffer.append(" ");
			}
			for (int j = 0; j < points[i].length; j++) {
				if (j == 0) {
					buffer.append("M ");
				} else if (j == 1) {
					buffer.append(" L ");
				}
				buffer.append((int)dc.toAbsoluteX(points[i][j].x)).append(",");
				buffer.append((int)dc.toAbsoluteY(points[i][j].y)).append(" ");
				if (j == points[i].length - 1) {
					buffer.append("z");
				}
			}
		}
		elem.setAttribute("d", buffer.toString());
		parentNode.appendChild(elem);
	}

	public void realizePalette() {
		// TODO
		log.fine("not implemented: realizePalette");
	}

	public void restoreDC(int savedDC) {
		int limit = (savedDC < 0) ? -savedDC : saveDC.size()-savedDC;
		for (int i = 0; i < limit; i++) {
			dc = (SvgDc)saveDC.removeLast();
		}
		
		if (!parentNode.hasChildNodes()) {
			doc.getDocumentElement().removeChild(parentNode);
		}
		parentNode = doc.createElement("g");
		Element mask = dc.getMask();
		if (mask != null) {
			parentNode.setAttribute("mask", "url(#" + mask.getAttribute("id") + ")");
		}
		doc.getDocumentElement().appendChild(parentNode);
	}

	public void rectangle(int sx, int sy, int ex, int ey) {
		Element elem = doc.createElement("rect");

		if (dc.getPen() != null || dc.getBrush() != null) {
			elem.setAttribute("class", getClassString(dc.getPen(), dc
					.getBrush()));
			if (dc.getBrush() != null
					&& dc.getBrush().getStyle() == GdiBrush.BS_HATCHED) {
				String id = "pattern" + (patternNo++);
				elem.setAttribute("fill", "url(#" + id + ")");
				defsNode.appendChild(dc.getBrush().createFillPattern(id));
			}
		}

		elem.setAttribute("x", "" + (int)dc.toAbsoluteX(sx));
		elem.setAttribute("y", "" + (int)dc.toAbsoluteY(sy));
		elem.setAttribute("width", "" + (int)dc.toRelativeX(ex - sx));
		elem.setAttribute("height", "" + (int)dc.toRelativeY(ey - sy));
		parentNode.appendChild(elem);
	}

	public void resizePalette(GdiPalette palette) {
		// TODO
		log.fine("not implemented: ResizePalette");
	}

	public void roundRect(int sx, int sy, int ex, int ey, int rw, int rh) {
		Element elem = doc.createElement("rect");

		if (dc.getPen() != null || dc.getBrush() != null) {
			elem.setAttribute("class", getClassString(dc.getPen(), dc
					.getBrush()));
			if (dc.getBrush() != null
					&& dc.getBrush().getStyle() == GdiBrush.BS_HATCHED) {
				String id = "pattern" + (patternNo++);
				elem.setAttribute("fill", "url(#" + id + ")");
				defsNode.appendChild(dc.getBrush().createFillPattern(id));
			}
		}

		elem.setAttribute("x", "" + (int)dc.toAbsoluteX(sx));
		elem.setAttribute("y", "" + (int)dc.toAbsoluteY(sy));
		elem.setAttribute("width", "" + (int)dc.toRelativeX(ex - sx));
		elem.setAttribute("height", "" + (int)dc.toRelativeY(ey - sy));
		elem.setAttribute("rx", "" + (int)dc.toRelativeX(rw));
		elem.setAttribute("ry", "" + (int)dc.toRelativeY(rh));
		parentNode.appendChild(elem);
	}

	public void seveDC() {
		saveDC.add(dc.clone());
	}

	public void scaleViewportExtEx(int x, int xd, int y, int yd, Size old) {
		dc.scaleViewportExtEx(x, xd, y, yd, old);
	}

	public void scaleWindowExtEx(int x, int xd, int y, int yd, Size old) {
		dc.scaleWindowExtEx(x, xd, y, yd, old);
	}

	public void selectClipRgn(GdiRegion rgn) {
		if (!parentNode.hasChildNodes()) {
			doc.getDocumentElement().removeChild(parentNode);
		}
		parentNode = doc.createElement("g");
		
		if (rgn != null) {
			Element mask = doc.createElement("mask");
			mask.setAttribute("id", "mask" + (maskNo++));
			mask.setIdAttribute("id", true);
			
			if (dc.getOffsetClipX() != 0 || dc.getOffsetClipY() != 0) {
				mask.setAttribute("transform", "translate(" + dc.getOffsetClipX() + "," + dc.getOffsetClipY() + ")");
			}
			defsNode.appendChild(mask);
			
			Element clip = doc.createElement("use");
			clip.setAttribute("xlink:href", "url(#" + nameMap.get(rgn) + ")");
			clip.setAttribute("fill", "white");
			
			mask.appendChild(clip);
			
			parentNode.setAttribute("mask", "url(#" + mask.getAttribute("id") + ")");
		}
		
		doc.getDocumentElement().appendChild(parentNode);
	}

	public void selectObject(GdiObject obj) {
		if (obj instanceof SvgBrush) {
			dc.setBrush((SvgBrush) obj);
		} else if (obj instanceof SvgFont) {
			dc.setFont((SvgFont) obj);
		} else if (obj instanceof SvgPen) {
			dc.setPen((SvgPen) obj);
		}
	}

	public void selectPalette(GdiPalette palette, boolean mode) {
		// TODO
		log.fine("not implemented: selectPalette");
	}

	public void setBkColor(int color) {
		dc.setBkColor(color);
	}

	public void setBkMode(int mode) {
		dc.setBkMode(mode);
	}

	public void setDIBitsToDevice(int dx, int dy, int dw, int dh, int sx,
			int sy, int startscan, int scanlines, byte[] image, int colorUse) {
		stretchDIBits(dx, dy, dw, dh, sx, sy, dw, dh, image, colorUse, SRCCOPY);
	}
	
	public void setLayout(long layout) {
		dc.setLayout(layout);
	}
	
	public void setMapMode(int mode) {
		dc.setMapMode(mode);
	}

	public void setMapperFlags(long flags) {
		dc.setMapperFlags(flags);
	}

	public void setPaletteEntries(GdiPalette palette, int startIndex, int[] entries) {
		// TODO
		log.fine("not implemented: setPaletteEntries");
	}

	public void setPixel(int x, int y, int color) {
		Element elem = doc.createElement("rect");
		elem.setAttribute("stroke", "none");
		elem.setAttribute("fill", SvgPen.toColor(color));
		elem.setAttribute("x", "" + (int)dc.toAbsoluteX(x));
		elem.setAttribute("y", "" + (int)dc.toAbsoluteY(y));
		elem.setAttribute("width", "" + (int)dc.toRelativeX(1));
		elem.setAttribute("height", "" + (int)dc.toRelativeY(1));
		parentNode.appendChild(elem);
	}

	public void setPolyFillMode(int mode) {
		dc.setPolyFillMode(mode);
	}
	
	public void setRelAbs(int mode) {
		dc.setRelAbs(mode);
	}

	public void setROP2(int mode) {
		dc.setROP2(mode);
	}

	public void setStretchBltMode(int mode) {
		dc.setStretchBltMode(mode);
	}

	public void setTextAlign(int align) {
		dc.setTextAlign(align);
	}

	public void setTextCharacterExtra(int extra) {
		dc.setTextCharacterExtra(extra);
	}

	public void setTextColor(int color) {
		dc.setTextColor(color);
	}

	public void setTextJustification(int breakExtra, int breakCount) {
		if (breakCount > 0) {
			dc.setTextSpace(Math.abs((int)dc.toRelativeX(breakExtra)) / breakCount);
		}
	}
	
	public void setViewportExtEx(int x, int y, Size old) {
		dc.setViewportExtEx(x, y, old);
	}

	public void setViewportOrgEx(int x, int y, Point old) {
		dc.setViewportOrgEx(x, y, old);
	}

	public void setWindowExtEx(int width, int height, Size old) {
		dc.setWindowExtEx(width, height, old);
	}

	public void setWindowOrgEx(int x, int y, Point old) {
		dc.setWindowOrgEx(x, y, old);
	}

	public void stretchBlt(byte[] image, int dx, int dy, int dw, int dh, int sx, int sy,
			int sw, int sh, long rop) {
		dibStretchBlt(image, dx, dy, dw, dh, sx, sy, sw, sh, rop);
	}

	public void stretchDIBits(int dx, int dy, int dw, int dh, int sx, int sy,
			int sw, int sh, byte[] image, int usage, long rop) {
		bmpToSvg(image, dx, dy, dw, dh, sx, sy, sw, sh, usage, rop);
	}
	
	public void textOut(int x, int y, byte[] text) {
		Element elem = doc.createElement("text");
		
		int escapement = 0;
		boolean vertical = false;
		if (dc.getFont() != null) {
			elem.setAttribute("class", getClassString(dc.getFont()));
			if (dc.getFont().getFaceName().startsWith("@")) {
				vertical = true;
				escapement = dc.getFont().getEscapement()-2700;
			} else {
				escapement = dc.getFont().getEscapement();
			}
		}
		elem.setAttribute("fill", SvgObject.toColor(dc.getTextColor()));

		// style
		buffer.setLength(0);
		int align = dc.getTextAlign();

		if ((align & (TA_LEFT|TA_RIGHT|TA_CENTER)) == TA_RIGHT) {
			buffer.append("text-anchor: end; ");
		} else if ((align & (TA_LEFT|TA_RIGHT|TA_CENTER)) == TA_CENTER) {
			buffer.append("text-anchor: middle; ");
		}

		if (vertical) {
			elem.setAttribute("writing-mode", "tb");
			buffer.append("dominant-baseline: ideographic; ");
		} else {
			if ((align & (TA_BOTTOM|TA_TOP|TA_BASELINE)) == TA_BASELINE) {
				buffer.append("dominant-baseline: alphabetic; ");
			} else {
				buffer.append("dominant-baseline: text-before-edge; ");				
			}
		}

		if ((align & TA_RTLREADING) == TA_RTLREADING) {
			buffer.append("unicode-bidi: bidi-override; direction: rtl; ");
		}

		if (dc.getTextSpace() > 0) {
			buffer.append("word-spacing: " + dc.getTextSpace() + "; ");
		}
		
		if (buffer.length() > 0) {
			buffer.setLength(buffer.length()-1);
			elem.setAttribute("style", buffer.toString());
		}

		elem.setAttribute("stroke", "none");
		
		int ax = (int)dc.toAbsoluteX(x);
		int ay = (int)dc.toAbsoluteY(y);
		elem.setAttribute("x", Integer.toString(ax));
		elem.setAttribute("y", Integer.toString(ay));
				
		if (escapement != 0)  {
			elem.setAttribute("transform", "rotate(" + (-escapement/10.0) + ", " + ax + ", " + ay + ")");
		}

		String str = null;
		if (dc.getFont() != null) {
			str = GdiUtils.convertString(text, dc.getFont().getCharset());
		} else {
			str = GdiUtils.convertString(text, GdiFont.DEFAULT_CHARSET);
		}

		if (dc.getTextCharacterExtra() != 0) {
			buffer.setLength(0);

			for (int i = 0; i < str.length() - 1; i++) {
				if (i != 0) {
					buffer.append(" ");
				}
				buffer.append((int)dc.toRelativeX(dc.getTextCharacterExtra()));
			}

			elem.setAttribute("dx", buffer.toString());
		}

		if (dc.getFont() != null && dc.getFont().getLang() != null) {
			elem.setAttribute("xml:lang", dc.getFont().getLang());
		}
		elem.setAttribute("xml:space", "preserve");
		appendText(elem, str);
		parentNode.appendChild(elem);
	}

	public void footer() {
		Element root = doc.getDocumentElement();
		if (!root.hasAttribute("width") && dc.getWindowWidth() != 0) {
			root.setAttribute("width", "" + Math.abs(dc.getWindowWidth()));
		}
		if (!root.hasAttribute("height") && dc.getWindowHeight() != 0) {
			root.setAttribute("height", "" + Math.abs(dc.getWindowHeight()));
		}
		if (dc.getWindowWidth() != 0 && dc.getWindowHeight() != 0) {
			root.setAttribute("viewBox", "0 0 " + Math.abs(dc.getWindowWidth()) + " " + Math.abs(dc.getWindowHeight()));
			root.setAttribute("preserveAspectRatio", "none");
		}
		root.setAttribute("stroke-linecap", "round");
		root.setAttribute("fill-rule", "evenodd");
		
		if (!styleNode.hasChildNodes()) {
			root.removeChild(styleNode);
		} else {
			styleNode.insertBefore(doc.createTextNode("\n"), styleNode.getFirstChild());
		}

		if (!defsNode.hasChildNodes()) {
			root.removeChild(defsNode);
		}
	}

	private String getClassString(GdiObject obj1, GdiObject obj2) {
		String name1 = getClassString(obj1);
		String name2 = getClassString(obj2);
		if (name1 != null && name2 != null) {
			return name1 + " " + name2;
		}
		if (name1 != null) {
			return name1;
		}
		if (name2 != null) {
			return name2;
		}
		return "";
	}

	private String getClassString(GdiObject style) {
		if (style == null) {
			return "";
		}

		return (String) nameMap.get(style);
	}
	
	private void appendText(Element elem, String str) {
		if (compatible) {
			str = str.replaceAll("\\r\\n|[\\t\\r\\n ]", "\u00A0");
		}
		SvgFont font = dc.getFont();
		if (replaceSymbolFont && font != null) {
			if ("Symbol".equals(font.getFaceName())) {
				int state = 0; // 0: default, 1: serif, 2: sans-serif
				int start = 0;
				char[] ca = str.toCharArray();
				for (int i = 0; i < ca.length; i++) {
					int nstate = state;
					switch (ca[i]) {
					case '"': ca[i] = '\u2200'; nstate = 1; break;
					case '$': ca[i] = '\u2203'; nstate = 1; break;
					case '\'': ca[i] = '\u220D'; nstate = 1; break;
					case '*': ca[i] = '\u2217'; nstate = 1; break;
					case '-': ca[i] = '\u2212'; nstate = 1; break;
					case '@': ca[i] = '\u2245'; nstate = 1; break;
					case 'A': ca[i] = '\u0391'; nstate = 1; break;
					case 'B': ca[i] = '\u0392'; nstate = 1; break;
					case 'C': ca[i] = '\u03A7'; nstate = 1; break;
					case 'D': ca[i] = '\u0394'; nstate = 1; break;
					case 'E': ca[i] = '\u0395'; nstate = 1; break;
					case 'F': ca[i] = '\u03A6'; nstate = 1; break;
					case 'G': ca[i] = '\u0393'; nstate = 1; break;
					case 'H': ca[i] = '\u0397'; nstate = 1; break;
					case 'I': ca[i] = '\u0399'; nstate = 1; break;
					case 'J': ca[i] = '\u03D1'; nstate = 1; break;
					case 'K': ca[i] = '\u039A'; nstate = 1; break;
					case 'L': ca[i] = '\u039B'; nstate = 1; break;
					case 'M': ca[i] = '\u039C'; nstate = 1; break;
					case 'N': ca[i] = '\u039D'; nstate = 1; break;
					case 'O': ca[i] = '\u039F'; nstate = 1; break;
					case 'P': ca[i] = '\u03A0'; nstate = 1; break;
					case 'Q': ca[i] = '\u0398'; nstate = 1; break;
					case 'R': ca[i] = '\u03A1'; nstate = 1; break;
					case 'S': ca[i] = '\u03A3'; nstate = 1; break;
					case 'T': ca[i] = '\u03A4'; nstate = 1; break;
					case 'U': ca[i] = '\u03A5'; nstate = 1; break;
					case 'V': ca[i] = '\u03C3'; nstate = 1; break;
					case 'W': ca[i] = '\u03A9'; nstate = 1; break;
					case 'X': ca[i] = '\u039E'; nstate = 1; break;
					case 'Y': ca[i] = '\u03A8'; nstate = 1; break;
					case 'Z': ca[i] = '\u0396'; nstate = 1; break;
					case '\\': ca[i] = '\u2234'; nstate = 1; break;
					case '^': ca[i] = '\u22A5'; nstate = 1; break;
					case '`': ca[i] = '\uF8E5'; nstate = 1; break;
					case 'a': ca[i] = '\u03B1'; nstate = 1; break;
					case 'b': ca[i] = '\u03B2'; nstate = 1; break;
					case 'c': ca[i] = '\u03C7'; nstate = 1; break;
					case 'd': ca[i] = '\u03B4'; nstate = 1; break;
					case 'e': ca[i] = '\u03B5'; nstate = 1; break;
					case 'f': ca[i] = '\u03C6'; nstate = 1; break;
					case 'g': ca[i] = '\u03B3'; nstate = 1; break;
					case 'h': ca[i] = '\u03B7'; nstate = 1; break;
					case 'i': ca[i] = '\u03B9'; nstate = 1; break;
					case 'j': ca[i] = '\u03D5'; nstate = 1; break;
					case 'k': ca[i] = '\u03BA'; nstate = 1; break;
					case 'l': ca[i] = '\u03BB'; nstate = 1; break;
					case 'm': ca[i] = '\u03BC'; nstate = 1; break;
					case 'n': ca[i] = '\u03BD'; nstate = 1; break;
					case 'o': ca[i] = '\u03BF'; nstate = 1; break;
					case 'p': ca[i] = '\u03C0'; nstate = 1; break;
					case 'q': ca[i] = '\u03B8'; nstate = 1; break;
					case 'r': ca[i] = '\u03C1'; nstate = 1; break;
					case 's': ca[i] = '\u03C3'; nstate = 1; break;
					case 't': ca[i] = '\u03C4'; nstate = 1; break;
					case 'u': ca[i] = '\u03C5'; nstate = 1; break;
					case 'v': ca[i] = '\u03D6'; nstate = 1; break;
					case 'w': ca[i] = '\u03C9'; nstate = 1; break;
					case 'x': ca[i] = '\u03BE'; nstate = 1; break;
					case 'y': ca[i] = '\u03C8'; nstate = 1; break;
					case 'z': ca[i] = '\u03B6'; nstate = 1; break;
					case '~': ca[i] = '\u223C'; nstate = 1; break;
					case '\u00A0': ca[i] = '\u20AC'; nstate = 1; break;
					case '\u00A1': ca[i] = '\u03D2'; nstate = 1; break;
					case '\u00A2': ca[i] = '\u2032'; nstate = 1; break;
					case '\u00A3': ca[i] = '\u2264'; nstate = 1; break;
					case '\u00A4': ca[i] = '\u2044'; nstate = 1; break;
					case '\u00A5': ca[i] = '\u221E'; nstate = 1; break;
					case '\u00A6': ca[i] = '\u0192'; nstate = 1; break;
					case '\u00A7': ca[i] = '\u2663'; nstate = 1; break;
					case '\u00A8': ca[i] = '\u2666'; nstate = 1; break;
					case '\u00A9': ca[i] = '\u2665'; nstate = 1; break;
					case '\u00AA': ca[i] = '\u2660'; nstate = 1; break;
					case '\u00AB': ca[i] = '\u2194'; nstate = 1; break;
					case '\u00AC': ca[i] = '\u2190'; nstate = 1; break;
					case '\u00AD': ca[i] = '\u2191'; nstate = 1; break;
					case '\u00AE': ca[i] = '\u2192'; nstate = 1; break;
					case '\u00AF': ca[i] = '\u2193'; nstate = 1; break;
					case '\u00B2': ca[i] = '\u2033'; nstate = 1; break;
					case '\u00B3': ca[i] = '\u2265'; nstate = 1; break;
					case '\u00B4': ca[i] = '\u00D7'; nstate = 1; break;
					case '\u00B5': ca[i] = '\u221D'; nstate = 1; break;
					case '\u00B6': ca[i] = '\u2202'; nstate = 1; break;
					case '\u00B7': ca[i] = '\u2022'; nstate = 1; break;
					case '\u00B8': ca[i] = '\u00F7'; nstate = 1; break;
					case '\u00B9': ca[i] = '\u2260'; nstate = 1; break;
					case '\u00BA': ca[i] = '\u2261'; nstate = 1; break;
					case '\u00BB': ca[i] = '\u2248'; nstate = 1; break;
					case '\u00BC': ca[i] = '\u2026'; nstate = 1; break;
					case '\u00BD': ca[i] = '\u23D0'; nstate = 1; break;
					case '\u00BE': ca[i] = '\u23AF'; nstate = 1; break;
					case '\u00BF': ca[i] = '\u21B5'; nstate = 1; break;
					case '\u00C0': ca[i] = '\u2135'; nstate = 1; break;
					case '\u00C1': ca[i] = '\u2111'; nstate = 1; break;
					case '\u00C2': ca[i] = '\u211C'; nstate = 1; break;
					case '\u00C3': ca[i] = '\u2118'; nstate = 1; break;
					case '\u00C4': ca[i] = '\u2297'; nstate = 1; break;
					case '\u00C5': ca[i] = '\u2295'; nstate = 1; break;
					case '\u00C6': ca[i] = '\u2205'; nstate = 1; break;
					case '\u00C7': ca[i] = '\u2229'; nstate = 1; break;
					case '\u00C8': ca[i] = '\u222A'; nstate = 1; break;
					case '\u00C9': ca[i] = '\u2283'; nstate = 1; break;
					case '\u00CA': ca[i] = '\u2287'; nstate = 1; break;
					case '\u00CB': ca[i] = '\u2284'; nstate = 1; break;
					case '\u00CC': ca[i] = '\u2282'; nstate = 1; break;
					case '\u00CD': ca[i] = '\u2286'; nstate = 1; break;
					case '\u00CE': ca[i] = '\u2208'; nstate = 1; break;
					case '\u00CF': ca[i] = '\u2209'; nstate = 1; break;
					case '\u00D0': ca[i] = '\u2220'; nstate = 1; break;
					case '\u00D1': ca[i] = '\u2207'; nstate = 1; break;
					case '\u00D2': ca[i] = '\u00AE'; nstate = 1; break;
					case '\u00D3': ca[i] = '\u00A9'; nstate = 1; break;
					case '\u00D4': ca[i] = '\u2122'; nstate = 1; break;
					case '\u00D5': ca[i] = '\u220F'; nstate = 1; break;
					case '\u00D6': ca[i] = '\u221A'; nstate = 1; break;
					case '\u00D7': ca[i] = '\u22C5'; nstate = 1; break;
					case '\u00D8': ca[i] = '\u00AC'; nstate = 1; break;
					case '\u00D9': ca[i] = '\u2227'; nstate = 1; break;
					case '\u00DA': ca[i] = '\u2228'; nstate = 1; break;
					case '\u00DB': ca[i] = '\u21D4'; nstate = 1; break;
					case '\u00DC': ca[i] = '\u21D0'; nstate = 1; break;
					case '\u00DD': ca[i] = '\u21D1'; nstate = 1; break;
					case '\u00DE': ca[i] = '\u21D2'; nstate = 1; break;
					case '\u00DF': ca[i] = '\u21D3'; nstate = 1; break;
					case '\u00E0': ca[i] = '\u25CA'; nstate = 1; break;
					case '\u00E1': ca[i] = '\u3008'; nstate = 1; break;
					case '\u00E2': ca[i] = '\u00AE'; nstate = 2; break;
					case '\u00E3': ca[i] = '\u00A9'; nstate = 2; break;
					case '\u00E4': ca[i] = '\u2122'; nstate = 2; break;
					case '\u00E5': ca[i] = '\u2211'; nstate = 1; break;
					case '\u00E6': ca[i] = '\u239B'; nstate = 1; break;
					case '\u00E7': ca[i] = '\u239C'; nstate = 1; break;
					case '\u00E8': ca[i] = '\u239D'; nstate = 1; break;
					case '\u00E9': ca[i] = '\u23A1'; nstate = 1; break;
					case '\u00EA': ca[i] = '\u23A2'; nstate = 1; break;
					case '\u00EB': ca[i] = '\u23A3'; nstate = 1; break;
					case '\u00EC': ca[i] = '\u23A7'; nstate = 1; break;
					case '\u00ED': ca[i] = '\u23A8'; nstate = 1; break;
					case '\u00EE': ca[i] = '\u23A9'; nstate = 1; break;
					case '\u00EF': ca[i] = '\u23AA'; nstate = 1; break;
					case '\u00F0': ca[i] = '\uF8FF'; nstate = 1; break;
					case '\u00F1': ca[i] = '\u3009'; nstate = 1; break;
					case '\u00F2': ca[i] = '\u222B'; nstate = 1; break;
					case '\u00F3': ca[i] = '\u2320'; nstate = 1; break;
					case '\u00F4': ca[i] = '\u23AE'; nstate = 1; break;
					case '\u00F5': ca[i] = '\u2321'; nstate = 1; break;
					case '\u00F6': ca[i] = '\u239E'; nstate = 1; break;
					case '\u00F7': ca[i] = '\u239F'; nstate = 1; break;
					case '\u00F8': ca[i] = '\u23A0'; nstate = 1; break;
					case '\u00F9': ca[i] = '\u23A4'; nstate = 1; break;
					case '\u00FA': ca[i] = '\u23A5'; nstate = 1; break;
					case '\u00FB': ca[i] = '\u23A6'; nstate = 1; break;
					case '\u00FC': ca[i] = '\u23AB'; nstate = 1; break;
					case '\u00FD': ca[i] = '\u23AC'; nstate = 1; break;
					case '\u00FE': ca[i] = '\u23AD'; nstate = 1; break;
					case '\u00FF': ca[i] = '\u2192'; nstate = 1; break;
					default: nstate = 0;
					}
					
					if (nstate != state) {
						if (start < i) {
							Node text = doc.createTextNode(String.valueOf(ca, start, i));
							if (state == 0) {
								elem.appendChild(text);
							} else if (state == 1) {
								Element span = doc.createElement("tspan");
								span.setAttribute("font-family", "serif");
								span.appendChild(text);
								elem.appendChild(span);
							} else if (state == 2) {
								Element span = doc.createElement("tspan");
								span.setAttribute("font-family", "sans-serif");
								span.appendChild(text);
								elem.appendChild(span);
							}
							start = i;
						}
						state = nstate;
					}
				}
				
				if (start < ca.length) {
					Node text = doc.createTextNode(String.valueOf(ca, start, ca.length));
					if (state == 0) {
						elem.appendChild(text);
					} else if (state == 1) {
						Element span = doc.createElement("tspan");
						span.setAttribute("font-family", "serif");
						span.appendChild(text);
						elem.appendChild(span);
					} else if (state == 2) {
						Element span = doc.createElement("tspan");
						span.setAttribute("font-family", "sans-serif");
						span.appendChild(text);
						elem.appendChild(span);
					}
				}
				return;
			}
		}
		
		elem.appendChild(doc.createTextNode(str));		
	}
	
	private void bmpToSvg(byte[] image, int dx, int dy, int dw, int dh, int sx, int sy,
			int sw, int sh, int usage, long rop) {
		image = ImageUtil.convert(dibToBmp(image), "png", dh < 0);
	
		StringBuffer buffer = new StringBuffer("data:image/png;base64,");
		buffer.append(Base64.encode(image));
		String data = buffer.toString();
		if (data == null || data.equals("")) {
			return;
		}

		Element elem = doc.createElement("image");
		int x = (int)dc.toAbsoluteX(dx);
		int y = (int)dc.toAbsoluteY(dy);
		int width = (int)dc.toRelativeX(dw);
		int height = (int)dc.toRelativeY(dh);
		
		if (width < 0 && height < 0) {
			elem.setAttribute("transform", "scale(-1, -1) translate(" + -x + ", " + -y + ")");
		} else if (width < 0) {
			elem.setAttribute("transform", "scale(-1, 1) translate(" + -x + ", " + y + ")");
		} else if (height < 0) {
			elem.setAttribute("transform", "scale(1, -1) translate(" + x + ", " + -y + ")");
		} else {
			elem.setAttribute("x", "" + x);
			elem.setAttribute("y", "" + y);			
		}
		
		elem.setAttribute("width", "" + Math.abs(width));
		elem.setAttribute("height", "" + Math.abs(height));

		if (sx != 0 || sy != 0 || sw != dw || sh != dh) {
			elem.setAttribute("viewBox", "" + sx + " " + sy + " " + sw + " "+ sh);
			elem.setAttribute("preserveAspectRatio", "none");
		}
		
		String ropFilter = dc.getRopFilter(rop);
		if (ropFilter != null) {
			elem.setAttribute("filter", ropFilter);
		}
		
		elem.setAttribute("xlink:href", data);
		parentNode.appendChild(elem);
	}

	private byte[] dibToBmp(byte[] dib) {
		byte[] data = new byte[14 + dib.length];

		/* BitmapFileHeader */
		data[0] = 0x42; // 'B'
		data[1] = 0x4d; // 'M'

		long bfSize = data.length;
		data[2] = (byte) (bfSize & 0xff);
		data[3] = (byte) ((bfSize >> 8) & 0xff);
		data[4] = (byte) ((bfSize >> 16) & 0xff);
		data[5] = (byte) ((bfSize >> 24) & 0xff);

		// reserved 1
		data[6] = 0x00;
		data[7] = 0x00;

		// reserved 2
		data[8] = 0x00;
		data[9] = 0x00;

		// offset
		long bfOffBits = 14;

		/* BitmapInfoHeader */
		long biSize = (dib[0] & 0xff) + ((dib[1] & 0xff) << 8)
				+ ((dib[2] & 0xff) << 16) + ((dib[3] & 0xff) << 24);
		bfOffBits += biSize;

		int biBitCount = (dib[14] & 0xff) + ((dib[15] & 0xff) << 8);

		long clrUsed = (dib[32] & 0xff) + ((dib[33] & 0xff) << 8)
				+ ((dib[34] & 0xff) << 16) + ((dib[35] & 0xff) << 24);

		switch (biBitCount) {
		case 1:
			bfOffBits += (0x1L + 1) * 4;
			break;
		case 4:
			bfOffBits += (0xFL + 1) * 4;
			break;
		case 8:
			bfOffBits += (0xFFL + 1) * 4;
			break;
		case 16:
			bfOffBits += (clrUsed == 0L) ? 0 : (0xFFFFL + 1) * 4;
			break;
		case 24:
			bfOffBits += (clrUsed == 0L) ? 0 : (0xFFFFFFL + 1) * 4;
			break;
		case 32:
			bfOffBits += (clrUsed == 0L) ? 0 : (0xFFFFFFFFL + 1) * 4;
			break;
		}
		
		data[10] = (byte) (bfOffBits & 0xff);
		data[11] = (byte) ((bfOffBits >> 8) & 0xff);
		data[12] = (byte) ((bfOffBits >> 16) & 0xff);
		data[13] = (byte) ((bfOffBits >> 24) & 0xff);
		
		System.arraycopy(dib, 0, data, 14, dib.length);

		return data;
	}
}
