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.Font;
27  import java.awt.Image;
28  import java.awt.Shape;
29  import java.awt.Stroke;
30  import java.awt.geom.AffineTransform;
31  import java.awt.geom.Arc2D;
32  import java.awt.geom.Ellipse2D;
33  import java.awt.geom.Line2D;
34  import java.awt.geom.PathIterator;
35  import java.awt.geom.Rectangle2D;
36  import java.awt.image.BufferedImage;
37  import java.io.UnsupportedEncodingException;
38  import java.util.Arrays;
39  import java.util.Map;
40  
41  /**
42   * <code>Graphics2D</code> implementation that saves all operations to a string
43   * in the <i>Encapsulated PostScript®</i> (EPS) format.
44   */
45  public class EPSGraphics2D extends VectorGraphics2D {
46  	/** Constant to convert values from millimeters to PostScript® units (1/72th inch). */
47  	protected static final double MM_IN_UNITS = 72.0 / 25.4;
48  
49  	/** Mapping of stroke endcap values from Java to PostScript®. */
50  	private static final Map<Integer, Integer> STROKE_ENDCAPS = DataUtils.map(
51  		new Integer[] { BasicStroke.CAP_BUTT, BasicStroke.CAP_ROUND, BasicStroke.CAP_SQUARE },
52  		new Integer[] { 0, 1, 2 }
53  	);
54  
55  	/** Mapping of line join values for path drawing from Java to PostScript®. */
56  	private static final Map<Integer, Integer> STROKE_LINEJOIN = DataUtils.map(
57  		new Integer[] { BasicStroke.JOIN_MITER, BasicStroke.JOIN_ROUND, BasicStroke.JOIN_BEVEL },
58  		new Integer[] { 0, 1, 2 }
59  	);
60  
61  	/**
62  	 * Constructor that initializes a new <code>EPSGraphics2D</code> instance.
63  	 * The document dimension must be specified as parameters.
64  	 */
65  	public EPSGraphics2D(double x, double y, double width, double height) {
66  		super(x, y, width, height);
67  		writeHeader();
68  	}
69  
70  	@Override
71  	protected void writeString(String str, double x, double y) {
72  		// TODO Encode string
73  		//byte[] bytes = str.getBytes("ISO-8859-1");
74  
75  		// Escape string
76  		str = str.replaceAll("\\\\", "\\\\\\\\")
77  			.replaceAll("\t", "\\\\t").replaceAll("\b", "\\\\b").replaceAll("\f", "\\\\f")
78  			.replaceAll("\\(", "\\\\(").replaceAll("\\)", "\\\\)");
79  
80  		//float fontSize = getFont().getSize2D();
81  		//float leading = getFont().getLineMetrics("", getFontRenderContext()).getLeading();
82  
83  		write("gsave 1 -1 scale ");
84  
85  		/*
86  		// Extract lines
87  		String[] lines = str.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
88  		// Output lines
89  		for (int i = 0; i < lines.length; i++) {
90  			String line = lines[i];
91  			write(x, " -", y + i*fontSize + ((i>0) ? leading : 0f), " M (", line, ") show ");
92  		}
93  		*/
94  
95  		str = str.replaceAll("[\r\n]", "");
96  		write(x, " -", y, " M (", str, ") show ");
97  
98  		writeln("grestore");
99  	}
100 
101 	@Override
102 	public void setStroke(Stroke s) {
103 		BasicStroke bsPrev;
104 		if (getStroke() instanceof BasicStroke) {
105 			bsPrev = (BasicStroke) getStroke();
106 		} else {
107 			bsPrev = new BasicStroke();
108 		}
109 
110 		super.setStroke(s);
111 
112 		if (s instanceof BasicStroke) {
113 			BasicStroke bs = (BasicStroke) s;
114 			if (bs.getLineWidth() != bsPrev.getLineWidth()) {
115 				writeln(bs.getLineWidth(), " setlinewidth");
116 			}
117 			if (bs.getLineJoin() != bsPrev.getLineJoin()) {
118 				writeln(STROKE_LINEJOIN.get(bs.getLineJoin()), " setlinejoin");
119 			}
120 			if (bs.getEndCap() != bsPrev.getEndCap()) {
121 				writeln(STROKE_ENDCAPS.get(bs.getEndCap()), " setlinecap");
122 			}
123 			if ((!Arrays.equals(bs.getDashArray(), bsPrev.getDashArray())) ||
124 				(bs.getDashPhase() != bsPrev.getDashPhase())) {
125 				writeln("[", DataUtils.join(" ", bs.getDashArray()), "] ",
126 						bs.getDashPhase(), " setdash");
127 			}
128 		}
129 	}
130 
131 	@Override
132 	protected void writeImage(Image img, int imgWidth, int imgHeight, double x, double y, double width, double height) {
133 		BufferedImage bufferedImg = GraphicsUtils.toBufferedImage(img);
134 		String imgData = getEps(bufferedImg);
135 		int bands = bufferedImg.getSampleModel().getNumBands();
136 		int bitsPerSample = bufferedImg.getColorModel().getPixelSize() / bands;
137 		if (bands > 3) {
138 			bands = 3;
139 		}
140 		writeln("gsave");
141 		writeln(x, " ", y, " ", width, " ", height, " ",
142 			imgWidth, " ", imgHeight, " ", bitsPerSample, " img false ", bands, " colorimage"
143 		);
144 		writeln(imgData, ">");
145 		writeln("grestore");
146 	}
147 
148 	@Override
149 	public void setColor(Color c) {
150 		Color color = getColor();
151 		if (c != null) {
152 			super.setColor(c);
153 			// TODO Add transparency hints for PDF conversion
154 			/*if (color.getAlpha() != c.getAlpha()) {
155 				double a = c.getAlpha()/255.0;
156 				writeln("[ /ca ", a, " /SetTransparency pdfmark");
157 			}*/
158 			if (color.getRed() != c.getRed() || color.getGreen() != c.getGreen() || color.getBlue() != c.getBlue()) {
159 				double r = c.getRed()/255.0;
160 				double g = c.getGreen()/255.0;
161 				double b = c.getBlue()/255.0;
162 				writeln(r, " ", g, " ", b, " rgb");
163 			}
164 		}
165 	}
166 
167 	@Override
168 	public void setFont(Font font) {
169 		if (!getFont().equals(font)) {
170 			super.setFont(font);
171 			writeln("/", font.getPSName(), " ", font.getSize2D(), " selectfont");
172 
173 		}
174 	}
175 
176 	@Override
177 	public void setClip(Shape clip) {
178 		if (getClip() != null) {
179 			writeln("cliprestore");
180 		}
181 		super.setClip(clip);
182 		if (getClip() != null) {
183 			writeShape(getClip());
184 			writeln(" clip");
185 		}
186 	}
187 
188 	@Override
189 	public void setTransform(AffineTransform tx) {
190 		super.setTransform(tx);
191 		double[] matrix = new double[6];
192 		getTransform().getMatrix(matrix);
193 		writeln("basematrix setmatrix [", DataUtils.join(" ", matrix), "] concat");
194 	}
195 
196 	@Override
197 	public void translate(double tx, double ty) {
198 		super.translate(tx, ty);
199 		writeln(tx, " ", ty, " translate");
200 	}
201 
202 	@Override
203 	public void scale(double tx, double ty) {
204 		super.scale(tx, ty);
205 		writeln(tx, " ", ty, " scale");
206 	}
207 
208 	@Override
209 	public void rotate(double theta) {
210 		super.rotate(theta);
211 		writeln(theta/Math.PI*180.0, " rotate");
212 	}
213 
214 	@Override
215 	public void rotate(double theta, double x, double y) {
216 		super.rotate(theta, x, y);
217 		writeln(x, " ", y, " translate ", theta/Math.PI*180.0, " rotate ", -x, " ", -y, " translate");
218 	}
219 
220 	@Override
221 	public void shear(double sx, double sy) {
222 		super.shear(sx, sy);
223 		setTransform(getTransform());
224 	}
225 
226 	@Override
227 	protected void writeHeader() {
228 		Rectangle2D bounds = getBounds();
229 		double x = bounds.getX() * MM_IN_UNITS;
230 		double y = bounds.getY() * MM_IN_UNITS;
231 		double w = bounds.getWidth() * MM_IN_UNITS;
232 		double h = bounds.getHeight() * MM_IN_UNITS;
233 
234 		writeln("%!PS-Adobe-3.0 EPSF-3.0");
235 		writeln("%%BoundingBox: ",
236 				(int)Math.floor(x), " ", (int)Math.floor(y), " ",
237 				(int)Math.ceil(x + w), " ", (int)Math.ceil(y + h));
238 		writeln("%%HiResBoundingBox: ", x, " ", y, " ", x + w, " ", y + h);
239 		writeln("%%LanguageLevel: 3");
240 		writeln("%%Pages: 1");
241 		writeln("%%Page: 1 1");
242 
243 		// Utility functions
244 		writeln("/M /moveto load def");
245 		writeln("/L /lineto load def");
246 		writeln("/C /curveto load def");
247 		writeln("/Z /closepath load def");
248 		writeln("/RL /rlineto load def");
249 		writeln("/rgb /setrgbcolor load def");
250 		writeln("/rect { ",
251 			"/height exch def /width exch def /y exch def /x exch def ",
252 			"x y M width 0 RL 0 height RL width neg 0 RL ",
253 			"} bind def");
254 		writeln("/ellipse { ",
255 			"/endangle exch def /startangle exch def /ry exch def /rx exch def /y exch def /x exch def ",
256 			"/savematrix matrix currentmatrix def ",
257 			"x y translate rx ry scale 0 0 1 startangle endangle arcn ",
258 			"savematrix setmatrix ",
259 			"} bind def");
260 		writeln("/img { ",
261 			"/bits exch def /imgheight exch def /imgwidth exch def /height exch def /width exch def /y exch def /x exch def ",
262 			"x y translate width height scale ",
263 			"imgwidth imgheight bits [imgwidth 0 0 imgheight 0 0] currentfile /ASCIIHexDecode filter ",
264 			"} bind def");
265 		// Set default font
266 		writeln("/", getFont().getPSName(), " ", getFont().getSize2D(), " selectfont");
267 		//writeln("<< /AllowTransparency true >> setdistillerparams"); // TODO
268 		// Save state
269 		writeln("gsave");
270 		// Save state
271 		writeln("clipsave");
272 		// Settings
273 		writeln("/DeviceRGB setcolorspace");
274 		// Adjust page size and page origin
275 		writeln("0 ", h, " translate");
276 		writeln(MM_IN_UNITS, " -", MM_IN_UNITS, " scale");
277 		writeln("/basematrix matrix currentmatrix def");
278 	}
279 
280 	/**
281 	 * Utility method for writing a tag closing fragment for drawing operations.
282 	 */
283 	@Override
284 	protected void writeClosingDraw() {
285 		writeln(" stroke");
286 	}
287 
288 	/**
289 	 * Utility method for writing a tag closing fragment for filling operations.
290 	 */
291 	@Override
292 	protected void writeClosingFill() {
293 		writeln(" fill");
294 	}
295 
296 	/**
297 	 * Utility method for writing an arbitrary shape to.
298 	 * It tries to translate Java2D shapes to the corresponding EPS shape
299 	 * commands.
300 	 */
301 	@Override
302 	protected void writeShape(Shape s) {
303 		write("newpath ");
304 		if (s instanceof Line2D) {
305 			Line2D l = (Line2D) s;
306 			double x1 = l.getX1();
307 			double y1 = l.getY1();
308 			double x2 = l.getX2();
309 			double y2 = l.getY2();
310 			write(x1, " ", y1, " M ", x2, " ", y2, " L");
311 			return;
312 		} else if (s instanceof Rectangle2D) {
313 			Rectangle2D r = (Rectangle2D) s;
314 			double x = r.getX();
315 			double y = r.getY();
316 			double width = r.getWidth();
317 			double height = r.getHeight();
318 			write(x, " ", y, " ", width, " ", height, " rect Z");
319 			return;
320 		} else if (s instanceof Ellipse2D) {
321 			Ellipse2D e = (Ellipse2D) s;
322 			double x = e.getX() + e.getWidth()/2.0;
323 			double y = e.getY() + e.getHeight()/2.0;
324 			double rx = e.getWidth()/2.0;
325 			double ry = e.getHeight()/2.0;
326 			write(x, " ", y, " ", rx, " ", ry, " ", 360.0, " ", 0.0, " ellipse Z");
327 			return;
328 		} else if (s instanceof Arc2D) {
329 			Arc2D e = (Arc2D) s;
330 			double x = (e.getX() + e.getWidth()/2.0);
331 			double y = (e.getY() + e.getHeight()/2.0);
332 			double rx = e.getWidth()/2.0;
333 			double ry = e.getHeight()/2.0;
334 			double startAngle = -e.getAngleStart();
335 			double endAngle = -(e.getAngleStart() + e.getAngleExtent());
336 			write(x, " ", y, " ", rx, " ", ry, " ", startAngle, " ", endAngle, " ellipse");
337 			if (e.getArcType() == Arc2D.CHORD) {
338 				write(" Z");
339 			} else if (e.getArcType() == Arc2D.PIE) {
340 				write(" ", x, " ", y, " L Z");
341 			}
342 			return;
343 		} else {
344 			PathIterator segments = s.getPathIterator(null);
345 			double[] coordsCur = new double[6];
346 			double[] pointPrev = new double[2];
347 			for (int i = 0; !segments.isDone(); i++, segments.next()) {
348 				if (i > 0) {
349 					write(" ");
350 				}
351 				int segmentType = segments.currentSegment(coordsCur);
352 				switch (segmentType) {
353 				case PathIterator.SEG_MOVETO:
354 					write(coordsCur[0], " ", coordsCur[1], " M");
355 					pointPrev[0] = coordsCur[0];
356 					pointPrev[1] = coordsCur[1];
357 					break;
358 				case PathIterator.SEG_LINETO:
359 					write(coordsCur[0], " ", coordsCur[1], " L");
360 					pointPrev[0] = coordsCur[0];
361 					pointPrev[1] = coordsCur[1];
362 					break;
363 				case PathIterator.SEG_CUBICTO:
364 					write(coordsCur[0], " ", coordsCur[1], " ", coordsCur[2], " ", coordsCur[3], " ", coordsCur[4], " ", coordsCur[5], " C");
365 					pointPrev[0] = coordsCur[4];
366 					pointPrev[1] = coordsCur[5];
367 					break;
368 				case PathIterator.SEG_QUADTO:
369 					double x1 = pointPrev[0] + 2.0/3.0*(coordsCur[0] - pointPrev[0]);
370 					double y1 = pointPrev[1] + 2.0/3.0*(coordsCur[1] - pointPrev[1]);
371 					double x2 = coordsCur[0] + 1.0/3.0*(coordsCur[2] - coordsCur[0]);
372 					double y2 = coordsCur[1] + 1.0/3.0*(coordsCur[3] - coordsCur[1]);
373 					double x3 = coordsCur[2];
374 					double y3 = coordsCur[3];
375 					write(x1, " ", y1, " ", x2, " ", y2, " ", x3, " ", y3, " C");
376 					pointPrev[0] = x3;
377 					pointPrev[1] = y3;
378 					break;
379 				case PathIterator.SEG_CLOSE:
380 					write("Z");
381 					break;
382 				}
383 			}
384 		}
385 	}
386 
387 	public static String getEps(BufferedImage bufferedImg) {
388 		int width = bufferedImg.getWidth();
389 		int height = bufferedImg.getHeight();
390 		int bands = bufferedImg.getSampleModel().getNumBands();
391 		StringBuffer str = new StringBuffer(width*height*bands*2);
392 		for (int y = 0; y < height; y++) {
393 			for (int x = 0; x < width; x++) {
394 				int pixel = bufferedImg.getRGB(x, y) & 0xffffff;
395 				if (bands >= 3) {
396 					String hex = String.format("%06x", pixel);
397 					str.append(hex);
398 				} else if (bands == 1) {
399 					str.append(String.format("%02x", pixel));
400 				}
401 			}
402 			str.append("\n");
403 		}
404 		return str.toString();
405 	}
406 
407 	@Override
408 	protected String getFooter() {
409 		return "grestore  % Restore state\n%%EOF\n";
410 	}
411 
412 	@Override
413 	public byte[] getBytes() {
414 		try {
415 			return toString().getBytes("ISO-8859-1");
416 		} catch (UnsupportedEncodingException e) {
417 			return super.getBytes();
418 		}
419 	}
420 }