1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
44
45
46 public class SVGGraphics2D extends VectorGraphics2D {
47
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
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
60 private static final String CLIP_PATH_ID = "clip";
61
62 private long clipCounter;
63
64
65
66
67
68 public SVGGraphics2D(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
76 str = str.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
77
78 float fontSize = getFont().getSize2D();
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94 str = str.replaceAll("[\r\n]", "");
95 writeln("<text x=\"", x, "\" y=\"", y, "\" style=\"font:",
96 fontSize, "px ", getFont().getFamily(), "\">", str, "</text>");
97 }
98
99 @Override
100 protected void writeImage(Image img, int imgWidth, int imgHeight, double x,
101 double y, double width, double height) {
102 BufferedImage bufferedImg = GraphicsUtils.toBufferedImage(img);
103 String imgData = getSvg(bufferedImg);
104 write("<image x=\"" , x, "\" y=\"" , y, "\" ",
105 "width=\"" , width, "\" height=\"" , height, "\" ",
106 "xlink:href=\"", imgData, "\" ",
107 "/>");
108 }
109
110 @Override
111 public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
112 write("<polygon points=\"");
113 for (int i = 0; i < nPoints; i++) {
114 if (i > 0) {
115 write(" ");
116 }
117 write(xPoints[i], ",", yPoints[i]);
118 }
119 write("\" ");
120 writeClosingDraw();
121 }
122
123 @Override
124 public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {
125 write("<polyline points=\"");
126 for (int i = 0; i < nPoints; i++) {
127 if (i > 0) {
128 write(" ");
129 }
130 write(xPoints[i], ",", yPoints[i]);
131 }
132 write("\" ");
133 writeClosingDraw();
134 }
135
136 @Override
137 public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
138 write("<polygon points=\"");
139 for (int i = 0; i < nPoints; i++) {
140 if (i > 0) {
141 write(" ");
142 }
143 write(xPoints[i], ",", yPoints[i]);
144 }
145 write("\" ");
146 writeClosingFill();
147 }
148
149 @Override
150 public void setClip(Shape clip) {
151 super.setClip(clip);
152 if (getClip() != null) {
153 writeln("<clipPath id=\"", CLIP_PATH_ID, ++clipCounter, "\">");
154 writeShape(getClip());
155 writeln("/>");
156 writeln("</clipPath>");
157 }
158 }
159
160 @Override
161 protected void setAffineTransform(AffineTransform tx) {
162
163 if (isTransformed()) {
164 writeln("</g>");
165 }
166
167 super.setAffineTransform(tx);
168
169 if (isTransformed()) {
170 double[] matrix = new double[6];
171 getTransform().getMatrix(matrix);
172 write("<g transform=\"matrix(", DataUtils.join(" ", matrix), ") \">");
173 }
174 }
175
176 @Override
177 protected void writeHeader() {
178 Rectangle2D bounds = getBounds();
179 double x = bounds.getX();
180 double y = bounds.getY();
181 double w = bounds.getWidth();
182 double h = bounds.getHeight();
183 writeln("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
184 writeln("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" ",
185 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">");
186 writeln("<svg version=\"1.2\" xmlns=\"http://www.w3.org/2000/svg\" ",
187 "xmlns:xlink=\"http://www.w3.org/1999/xlink\" ",
188 "x=\"", x, "mm\" y=\"", y, "mm\" ",
189 "width=\"", w, "mm\" height=\"", h, "mm\" " +
190 "viewBox=\"", x, " ", y, " ", w, " ", h, "\"",
191 ">");
192 writeln("<style type=\"text/css\"><![CDATA[");
193 writeln("text { font:", getFont().getSize2D(), "px ", getFont().getFamily(), "; }");
194 writeln("]]></style>");
195 }
196
197
198
199
200 @Override
201 protected void writeClosingDraw() {
202 write("style=\"fill:none;stroke:", getSvg(getColor()));
203 if (getStroke() instanceof BasicStroke) {
204 BasicStroke s = (BasicStroke) getStroke();
205 if (s.getLineWidth() != 1f) {
206 write(";stroke-width:", s.getLineWidth());
207 }
208 if (s.getEndCap() != BasicStroke.CAP_BUTT) {
209 write(";stroke-linecap:", STROKE_ENDCAPS.get(s.getEndCap()));
210 }
211 if (s.getLineJoin() != BasicStroke.JOIN_MITER) {
212 write(";stroke-linejoin:", STROKE_LINEJOIN.get(s.getLineJoin()));
213 }
214
215 if (s.getDashArray() != null && s.getDashArray().length>0) {
216 write(";stroke-dasharray:", DataUtils.join(",", s.getDashArray()));
217 write(";stroke-dashoffset:", s.getDashPhase());
218 }
219 }
220 if (getClip() != null) {
221 write("\" clip-path=\"url(#", CLIP_PATH_ID, clipCounter, ")");
222 }
223 writeln("\" />");
224 }
225
226
227
228
229 @Override
230 protected void writeClosingFill() {
231 write("style=\"fill:", getSvg(getColor()), ";stroke:none");
232 if (getClip() != null) {
233 write("\" clip-path=\"url(#", CLIP_PATH_ID, clipCounter, ")");
234 }
235 writeln("\" />");
236 }
237
238
239
240
241
242 @Override
243 protected void writeShape(Shape s) {
244 if (s instanceof Line2D) {
245 Line2D l = (Line2D) s;
246 double x1 = l.getX1();
247 double y1 = l.getY1();
248 double x2 = l.getX2();
249 double y2 = l.getY2();
250 write("<line x1=\"", x1, "\" y1=\"", y1, "\" x2=\"", x2, "\" y2=\"", y2, "\" ");
251 } else if (s instanceof Rectangle2D) {
252 Rectangle2D r = (Rectangle2D) s;
253 double x = r.getX();
254 double y = r.getY();
255 double width = r.getWidth();
256 double height = r.getHeight();
257 write("<rect x=\"", x, "\" y=\"", y, "\" width=\"", width, "\" height=\"", height, "\" ");
258 } else if (s instanceof RoundRectangle2D) {
259 RoundRectangle2D r = (RoundRectangle2D) s;
260 double x = r.getX();
261 double y = r.getY();
262 double width = r.getWidth();
263 double height = r.getHeight();
264 double arcWidth = r.getArcWidth();
265 double arcHeight = r.getArcHeight();
266 write("<rect x=\"", x, "\" y=\"", y, "\" width=\"", width, "\" height=\"", height, "\" rx=\"", arcWidth, "\" ry=\"", arcHeight, "\" ");
267 } else if (s instanceof Ellipse2D) {
268 Ellipse2D e = (Ellipse2D) s;
269 double x = e.getX();
270 double y = e.getY();
271 double rx = e.getWidth()/2.0;
272 double ry = e.getHeight()/2.0;
273 write("<ellipse cx=\"", x+rx, "\" cy=\"", y+ry, "\" rx=\"", rx, "\" ry=\"", ry, "\" ");
274 } else {
275 write("<path d=\"");
276 PathIterator segments = s.getPathIterator(null);
277 double[] coords = new double[6];
278 for (int i = 0; !segments.isDone(); i++, segments.next()) {
279 if (i > 0) {
280 write(" ");
281 }
282 int segmentType = segments.currentSegment(coords);
283 switch (segmentType) {
284 case PathIterator.SEG_MOVETO:
285 write("M", coords[0], ",", coords[1]);
286 break;
287 case PathIterator.SEG_LINETO:
288 write("L", coords[0], ",", coords[1]);
289 break;
290 case PathIterator.SEG_CUBICTO:
291 write("C", coords[0], ",", coords[1], " ", coords[2], ",", coords[3], " ", coords[4], ",", coords[5]);
292 break;
293 case PathIterator.SEG_QUADTO:
294 write("Q", coords[0], ",", coords[1], " ", coords[2], ",", coords[3]);
295 break;
296 case PathIterator.SEG_CLOSE:
297 write("Z");
298 break;
299 }
300 }
301 write("\" ");
302 }
303 }
304
305 private static String getSvg(Color c) {
306 String color = "rgb(" + c.getRed() + "," + c.getGreen() + "," + c.getBlue() + ")";
307 if (c.getAlpha() < 255) {
308 double opacity = c.getAlpha()/255.0;
309 color += ";opacity:" + opacity;
310 }
311 return color;
312 }
313
314 private static String getSvg(BufferedImage bufferedImg) {
315 ByteArrayOutputStream data = new ByteArrayOutputStream();
316 try {
317 ImageIO.write(bufferedImg, "png", data);
318 } catch (IOException e) {
319 return "";
320 }
321 String dataBase64 = DatatypeConverter.printBase64Binary(data.toByteArray());
322 return "data:image/png;base64," + dataBase64;
323 }
324
325 @Override
326 protected String getFooter() {
327 String footer = "";
328
329 if (isTransformed()) {
330 footer += "</g>\n";
331 }
332 footer += "</svg>\n";
333 return footer;
334 }
335
336 @Override
337 public String toString() {
338 String doc = super.toString();
339 doc = doc.replaceAll("<g transform=\"[^\"]*\"></g>\n", "");
340 return doc;
341 }
342 }