View Javadoc

1   /*
2    * VectorGraphics2D: Vector export for Java(R) Graphics2D
3    *
4    * (C) Copyright 2010 Erich Seifert <dev[at]erichseifert.de>
5    *
6    * This file is part of VectorGraphics2D.
7    *
8    * VectorGraphics2D is free software: you can redistribute it and/or modify
9    * it under the terms of the GNU Lesser General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or
11   * (at your option) any later version.
12   *
13   * VectorGraphics2D is distributed in the hope that it will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   * GNU Lesser General Public License for more details.
17   *
18   * You should have received a copy of the GNU Lesser General Public License
19   * along with VectorGraphics2D.  If not, see <http://www.gnu.org/licenses/>.
20   */
21  
22  package de.erichseifert.vectorgraphics2d;
23  
24  import java.awt.BasicStroke;
25  import java.awt.Color;
26  import java.awt.Image;
27  import java.awt.Shape;
28  import java.awt.geom.AffineTransform;
29  import java.awt.geom.Ellipse2D;
30  import java.awt.geom.Line2D;
31  import java.awt.geom.PathIterator;
32  import java.awt.geom.Rectangle2D;
33  import java.awt.geom.RoundRectangle2D;
34  import java.awt.image.BufferedImage;
35  import java.io.ByteArrayOutputStream;
36  import java.io.IOException;
37  import java.util.Map;
38  
39  import javax.imageio.ImageIO;
40  import javax.xml.bind.DatatypeConverter;
41  
42  /**
43   * <code>Graphics2D</code> implementation that saves all operations to a string
44   * in the <i>Enhanced Meta File</i> (EMF) format.
45   */
46  public class EMFGraphics2D extends VectorGraphics2D {
47  	/** Mapping of stroke endcap values from Java to EMF. */
48  	private static final Map<Integer, String> STROKE_ENDCAPS = DataUtils.map(
49  		new Integer[] { BasicStroke.CAP_BUTT, BasicStroke.CAP_ROUND, BasicStroke.CAP_SQUARE },
50  		new String[] { "butt", "round", "square" }
51  	);
52  
53  	/** Mapping of line join values for path drawing from Java to EMF. */
54  	private static final Map<Integer, String> STROKE_LINEJOIN = DataUtils.map(
55  		new Integer[] { BasicStroke.JOIN_MITER, BasicStroke.JOIN_ROUND, BasicStroke.JOIN_BEVEL },
56  		new String[] { "miter", "round", "bevel" }
57  	);
58  
59  	/** Prefix string for ids of clipping paths. */
60  	private static final String CLIP_PATH_ID = "clip";
61  	/** Number of the current clipping path. */
62  	private long clipCounter;
63  
64  	/**
65  	 * Constructor that initializes a new <code>EMFGraphics2D</code> instance.
66  	 * The document dimension must be specified as parameters.
67  	 */
68  	public EMFGraphics2D(double x, double y, double width, double height) {
69  		super(x, y, width, height);
70  		writeHeader();
71  	}
72  
73  	@Override
74  	protected void writeString(String str, double x, double y) {
75  		// Escape string
76  		str = str.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
77  		// Output
78  		writeln("<text x=\"", x, "\" y=\"", y, "\">", str, "</text>");
79  	}
80  
81  	@Override
82  	protected void writeImage(Image img, int imgWidth, int imgHeight, double x,
83  			double y, double width, double height) {
84  		BufferedImage bufferedImg = GraphicsUtils.toBufferedImage(img);
85  		String imgData = getSvg(bufferedImg);
86  		write("<image x=\"" , x, "\" y=\"" , y, "\" ",
87  				"width=\"" , width, "\" height=\"" , height, "\" ",
88  				"xlink:href=\"", imgData, "\" ",
89  				"/>");
90  	}
91  
92  	@Override
93  	public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
94  		write("<polygon points=\"");
95  		for (int i = 0; i < nPoints; i++) {
96  			if (i > 0) {
97  				write(" ");
98  			}
99  			write(xPoints[i], ",", yPoints[i]);
100 		}
101 		write("\" ");
102 		writeClosingDraw();
103 	}
104 
105 	@Override
106 	public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {
107 		write("<polyline points=\"");
108 		for (int i = 0; i < nPoints; i++) {
109 			if (i > 0) {
110 				write(" ");
111 			}
112 			write(xPoints[i], ",", yPoints[i]);
113 		}
114 		write("\" ");
115 		writeClosingDraw();
116 	}
117 
118 	@Override
119 	public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
120 		write("<polygon points=\"");
121 		for (int i = 0; i < nPoints; i++) {
122 			if (i > 0) {
123 				write(" ");
124 			}
125 			write(xPoints[i], ",", yPoints[i]);
126 		}
127 		write("\" ");
128 		writeClosingFill();
129 	}
130 
131 	@Override
132 	public void setClip(Shape clip) {
133 		super.setClip(clip);
134 		if (getClip() != null) {
135 			writeln("<clipPath id=\"", CLIP_PATH_ID, ++clipCounter, "\">");
136 			writeShape(getClip());
137 			writeln("/>");
138 			writeln("</clipPath>");
139 		}
140 	}
141 
142 	@Override
143 	protected void setAffineTransform(AffineTransform tx) {
144 		// Close previous transformation group
145 		if (isTransformed()) {
146 			writeln("</g>");
147 		}
148 		// Set transformation matrix
149 		super.setAffineTransform(tx);
150 		// Begin new transformation group
151 		if (isTransformed()) {
152 			double[] matrix = new double[6];
153 			getTransform().getMatrix(matrix);
154 			write("<g transform=\"matrix(", DataUtils.join(" ", matrix), ") \">");
155 		}
156 	}
157 
158 	@Override
159 	protected void writeHeader() {
160 		Rectangle2D bounds = getBounds();
161 		double x = bounds.getX();
162 		double y = bounds.getY();
163 		double w = bounds.getWidth();
164 		double h = bounds.getHeight();
165 		writeln("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
166 		writeln("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" ",
167 			"\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">");
168 		writeln("<svg version=\"1.2\" xmlns=\"http://www.w3.org/2000/svg\" ",
169 			"xmlns:xlink=\"http://www.w3.org/1999/xlink\" ",
170 			"x=\"", x, "mm\" y=\"", y, "mm\" ",
171 			"width=\"", w, "mm\" height=\"", h, "mm\" " +
172 			"viewBox=\"", x, " ", y, " ", w, " ", h, "\"",
173 			">");
174 		writeln("<style type=\"text/css\"><![CDATA[");
175 		writeln("text { font-family:", getFont().getFamily(), ";font-size:", getFont().getSize2D(), "px; }");
176 		writeln("]]></style>");
177 	}
178 
179 	/**
180 	 * Utility method for writing a tag closing fragment for drawing operations.
181 	 */
182 	@Override
183 	protected void writeClosingDraw() {
184 		write("style=\"fill:none;stroke:", getSvg(getColor()));
185 		if (getStroke() instanceof BasicStroke) {
186 			BasicStroke s = (BasicStroke) getStroke();
187 			if (s.getLineWidth() != 1f) {
188 				write(";stroke-width:", s.getLineWidth());
189 			}
190 			if (s.getEndCap() != BasicStroke.CAP_BUTT) {
191 				write(";stroke-linecap:", STROKE_ENDCAPS.get(s.getEndCap()));
192 			}
193 			if (s.getLineJoin() != BasicStroke.JOIN_MITER) {
194 				write(";stroke-linejoin:", STROKE_LINEJOIN.get(s.getLineJoin()));
195 			}
196 			//write(";stroke-miterlimit:", s.getMiterLimit());
197 			if (s.getDashArray() != null && s.getDashArray().length>0) {
198 				write(";stroke-dasharray:", DataUtils.join(",", s.getDashArray()));
199 				write(";stroke-dashoffset:", s.getDashPhase());
200 			}
201 		}
202 		if (getClip() != null) {
203 			write("\" clip-path=\"url(#", CLIP_PATH_ID, clipCounter, ")");
204 		}
205 		writeln("\" />");
206 	}
207 
208 	/**
209 	 * Utility method for writing a tag closing fragment for filling operations.
210 	 */
211 	@Override
212 	protected void writeClosingFill() {
213 		write("style=\"fill:", getSvg(getColor()), ";stroke:none");
214 		if (getClip() != null) {
215 			write("\" clip-path=\"url(#", CLIP_PATH_ID, clipCounter, ")");
216 		}
217 		writeln("\" />");
218 	}
219 
220 	/**
221 	 * Utility method for writing an arbitrary shape to.
222 	 * It tries to translate Java2D shapes to the corresponding EMF shape
223 	 * commands.
224 	 */
225 	@Override
226 	protected void writeShape(Shape s) {
227 		if (s instanceof Line2D) {
228 			Line2D l = (Line2D) s;
229 			double x1 = l.getX1();
230 			double y1 = l.getY1();
231 			double x2 = l.getX2();
232 			double y2 = l.getY2();
233 			write("<line x1=\"", x1, "\" y1=\"", y1, "\" x2=\"", x2, "\" y2=\"", y2, "\" ");
234 		} else if (s instanceof Rectangle2D) {
235 			Rectangle2D r = (Rectangle2D) s;
236 			double x = r.getX();
237 			double y = r.getY();
238 			double width = r.getWidth();
239 			double height = r.getHeight();
240 			write("<rect x=\"", x, "\" y=\"", y, "\" width=\"", width, "\" height=\"", height, "\" ");
241 		} else if (s instanceof RoundRectangle2D) {
242 			RoundRectangle2D r = (RoundRectangle2D) s;
243 			double x = r.getX();
244 			double y = r.getY();
245 			double width = r.getWidth();
246 			double height = r.getHeight();
247 			double arcWidth = r.getArcWidth();
248 			double arcHeight = r.getArcHeight();
249 			write("<rect x=\"", x, "\" y=\"", y, "\" width=\"", width, "\" height=\"", height, "\" rx=\"", arcWidth, "\" ry=\"", arcHeight, "\" ");
250 		} else if (s instanceof Ellipse2D) {
251 			Ellipse2D e = (Ellipse2D) s;
252 			double x = e.getX();
253 			double y = e.getY();
254 			double rx = e.getWidth()/2.0;
255 			double ry = e.getHeight()/2.0;
256 			write("<ellipse cx=\"", x+rx, "\" cy=\"", y+ry, "\" rx=\"", rx, "\" ry=\"", ry, "\" ");
257 		} else {
258 			write("<path d=\"");
259 			PathIterator segments = s.getPathIterator(null);
260 			double[] coords = new double[6];
261 			for (int i = 0; !segments.isDone(); i++, segments.next()) {
262 				if (i > 0) {
263 					write(" ");
264 				}
265 				int segmentType = segments.currentSegment(coords);
266 				switch (segmentType) {
267 				case PathIterator.SEG_MOVETO:
268 					write("M", coords[0], ",", coords[1]);
269 					break;
270 				case PathIterator.SEG_LINETO:
271 					write("L", coords[0], ",", coords[1]);
272 					break;
273 				case PathIterator.SEG_CUBICTO:
274 					write("C", coords[0], ",", coords[1], " ", coords[2], ",", coords[3], " ", coords[4], ",", coords[5]);
275 					break;
276 				case PathIterator.SEG_QUADTO:
277 					write("Q", coords[0], ",", coords[1], " ", coords[2], ",", coords[3]);
278 					break;
279 				case PathIterator.SEG_CLOSE:
280 					write("Z");
281 					break;
282 				}
283 			}
284 			write("\" ");
285 		}
286 	}
287 
288 	private static String getSvg(Color c) {
289 		String color = "rgb(" + c.getRed() + "," + c.getGreen() + "," + c.getBlue() + ")";
290 		if (c.getAlpha() < 255) {
291 			double opacity = c.getAlpha()/255.0;
292 			color += ";opacity:" + opacity;
293 		}
294 		return color;
295 	}
296 
297 	private static String getSvg(BufferedImage bufferedImg) {
298 		ByteArrayOutputStream data = new ByteArrayOutputStream();
299 		try {
300 			ImageIO.write(bufferedImg, "png", data);
301 		} catch (IOException e) {
302 			return "";
303 		}
304 		String dataBase64 = DatatypeConverter.printBase64Binary(data.toByteArray());
305 		return "data:image/png;base64," + dataBase64;
306 	}
307 
308 	@Override
309 	protected String getFooter() {
310 		String footer = "";
311 		// Close any previous transformation groups
312 		if (isTransformed()) {
313 			footer += "</g>\n";
314 		}
315 		footer += "</svg>\n";
316 		return footer;
317 	}
318 
319 	@Override
320 	public String toString() {
321 		String doc = super.toString();
322 		doc = doc.replaceAll("<g transform=\"[^\"]*\"></g>\n", "");
323 		return doc;
324 	}
325 }