View Javadoc

1   /**********************************************
2    * Copyright (C) 2009 Lukas Laag
3    * This file is part of Vectomatic.
4    * 
5    * Vectomatic is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    * 
10   * Vectomatic is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   * 
15   * You should have received a copy of the GNU General Public License
16   * along with Vectomatic.  If not, see http://www.gnu.org/licenses/
17   **********************************************/
18  package org.vectomatic.common.format;
19  
20  import java.util.HashMap;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.vectomatic.common.model.Attribute;
26  import org.vectomatic.common.model.CloneShapeVisitor;
27  import org.vectomatic.common.model.FloatAttributeValue;
28  import org.vectomatic.common.model.IShapeVisitor;
29  import org.vectomatic.common.model.IStyleVisitor;
30  import org.vectomatic.common.model.Shape;
31  import org.vectomatic.common.model.geometry.BezierSegment;
32  import org.vectomatic.common.model.geometry.BoundingBox;
33  import org.vectomatic.common.model.geometry.Ellipse;
34  import org.vectomatic.common.model.geometry.LineSegment;
35  import org.vectomatic.common.model.geometry.Path;
36  import org.vectomatic.common.model.geometry.Point;
37  import org.vectomatic.common.model.geometry.Polyline;
38  import org.vectomatic.common.model.geometry.Rect;
39  import org.vectomatic.common.model.geometry.Segment;
40  import org.vectomatic.common.model.geometry.ShapeGroup;
41  import org.vectomatic.common.model.geometry.TransformMatrix;
42  import org.vectomatic.common.model.style.Color;
43  import org.vectomatic.common.model.style.IStyle;
44  import org.vectomatic.common.model.style.NoneStyle;
45  import org.vectomatic.common.model.style.Palette;
46  import org.vectomatic.common.model.style.PaletteList;
47  
48  /**
49   * Class to export models to the SVG 1.1 format
50   */
51  public class SVG11Visitor implements IShapeVisitor, ISVGExporter {
52  	private class Context {
53  		TransformMatrix m;
54  		Shape shape;
55  		public Context() {
56  			m = new TransformMatrix();
57  		}
58  	}
59  	private IStyleVisitor _defStyleVisitor = new IStyleVisitor() {
60  		public void visitColor(Color color) {
61  			String style = color.toString();
62  			_attributes.clear();
63  			_attributes.put(ATT_SOLIDCOLOR, style);
64  			writeStartElement(ELT_SOLIDCOLOR, _attributes, true);
65  		}
66  
67  		public void visitNoneStyle(NoneStyle none) {
68  			_attributes.clear();
69  			_attributes.put(ATT_SOLIDCOLOR, "rgb(0,0,0)");
70  			_attributes.put(ATT_SOLIDOPACITY, "0");
71  			_attributes.put(ATT_ID, "none");
72  			writeStartElement(ELT_SOLIDCOLOR, _attributes, true);
73  		}		
74  	};
75  	private IStyleVisitor _strokeStyleVisitor = new IStyleVisitor() {
76  		public void visitColor(Color color) {
77  			_attributes.put(ATT_STROKE, color.toString());
78  		}
79  
80  		public void visitNoneStyle(NoneStyle none) {
81  			_attributes.put(ATT_STROKE, "url(#none)");
82  		}		
83  	};	
84  
85  	private IStyleVisitor _fillStyleVisitor = new IStyleVisitor() {
86  		public void visitColor(Color color) {
87  			_attributes.put(ATT_FILL, color.toString());
88  		}
89  
90  		public void visitNoneStyle(NoneStyle none) {
91  			_attributes.put(ATT_FILL, "url(#none)");
92  		}		
93  	};	
94  
95  	private Context[] _stack;
96  	private int _index;
97  	private IOutputStream _stream;
98  	private Map<String, String> _attributes;
99  	private CloneShapeVisitor _cloner;
100 	private Point _p0, _p1;
101 
102 	public SVG11Visitor() {
103 		_stack = new Context[8];
104 		_attributes = new HashMap<String, String>();
105 		_cloner = new CloneShapeVisitor();
106 		_p0 = new Point();
107 		_p1 = new Point();
108 	}
109 	
110 
111 
112 	public TransformMatrix pushShape(Shape shape, boolean isTailShape) {
113 		// Grow the context stack if too small
114 		if (_index >= _stack.length) {
115 			Context[] stack = new Context[2 * _stack.length];
116 			for (int i = 0; i < _stack.length; i++) {
117 				stack[i] = _stack[i];
118 			}
119 			_stack = stack;
120 		}
121 		
122 		// Allocate a stack slot if not allocated yet. Otherwise reuse it
123 		if (_stack[_index] == null) {
124 			_stack[_index] = new Context();
125 		}
126 
127 		// Compute the shape transform matrix
128 		TransformMatrix m;
129 		if (_index == 0) {
130 			m = _stack[_index].m = shape.getTransform();
131 		} else {
132 			m = shape.getTransform().preMultiply(_stack[_index - 1].m, _stack[_index].m);
133 		}
134 		_stack[_index].shape = shape;
135 		_index++;
136 
137 		if (isTailShape) {
138 			_attributes.clear();
139 			IStyle strokeStyle = ((IStyle)getAttribute(Attribute.LINE_STYLE));
140 			if (strokeStyle != null) {
141 				strokeStyle.acceptVisitor(_strokeStyleVisitor);
142 				float opacity = ((FloatAttributeValue)getAttribute(Attribute.LINE_OPACITY)).getValue();
143 				if (opacity < 1.0f) {
144 					_attributes.put(ATT_STROKEOPACITY, Float.toString(opacity));
145 				}
146 				_attributes.put(ATT_STROKEWIDTH, getAttribute(Attribute.LINE_WIDTH).toString());
147 			}
148 			IStyle fillStyle = ((IStyle)getAttribute(Attribute.FILL_STYLE));
149 			if (fillStyle != null) {
150 				fillStyle.acceptVisitor(_fillStyleVisitor);
151 				float opacity = ((FloatAttributeValue)getAttribute(Attribute.FILL_OPACITY)).getValue();
152 				if (opacity < 1.0f) {
153 					_attributes.put(ATT_FILLOPACITY, Float.toString(opacity));
154 				}
155 			}
156 
157 			
158 			// The transformation of the shape must be decomposed
159 			// in two parts to isolate the scaling which affects the line
160 			// stroke in SVG1.1
161 			// M0...Mn = [T(_t) . R(_r)] . [S(_s) . T(- _bbox.center)]
162 			shape.acceptVisitor(_cloner);
163 			Shape tmp = _cloner.getClone();
164 			tmp.setTransform(m);
165 			TransformMatrix r = new TransformMatrix().rotation(tmp.getRotation());
166 			TransformMatrix T = new TransformMatrix().translation(tmp.getTranslation(new Point()));
167 			TransformMatrix m1 = r.preMultiply(T, new TransformMatrix());
168 			TransformMatrix s = new TransformMatrix().scaling(tmp.getScaling(new Point()));
169 			TransformMatrix t = new TransformMatrix().translation(tmp.getBoundingBox().getPoint(BoundingBox.PT_C, new Point()).negate());
170 			TransformMatrix m2 = t.preMultiply(s, new TransformMatrix());
171 
172 			StringBuffer buffer = new StringBuffer();
173 			buffer.append("matrix(");
174 			buffer.append(m1.m11);
175 			buffer.append(" ");
176 			buffer.append(m1.m21);
177 			buffer.append(" ");
178 			buffer.append(m1.m12);
179 			buffer.append(" ");
180 			buffer.append(m1.m22);
181 			buffer.append(" ");
182 			buffer.append(m1.m13);
183 			buffer.append(" ");
184 			buffer.append(m1.m23);
185 			buffer.append(")");
186 			_attributes.put(ATT_TRANSFORM, buffer.toString());
187 			return m2;
188 		}
189 		return m;
190 	}
191 	
192 	
193 	private void popShape() {
194 		_index--;
195 	}
196 	
197 	private Object getAttribute(Attribute attr) {
198 		Object attrValue = null;
199 		for (int i = 0; i < _index; i++) {
200 			if (_stack[i].shape.definesAttribute(attr)) {
201 				attrValue = _stack[i].shape.getAttribute(attr);
202 				break;
203 			}
204 		}
205 		return attrValue;
206 	}
207 	
208 
209 
210 
211 	public void writeSVG(IOutputStream stream, Shape[] shapes, PaletteList paletteList, int width, int height) {
212 		_stream = stream;
213 		_stream.write("<?xml version=\"1.0\"?>\n");
214 
215 		_attributes.clear();
216 		_attributes.put(ATT_WIDTH, width + "px");
217 		_attributes.put(ATT_HEIGHT, height + "px");
218 		_attributes.put("xmlns", NS);
219 		writeStartElement(ELT_SVG, _attributes, false);
220 		
221 		// Serialize the representation metadata
222 		writeStartElement(ELT_DESC, new HashMap<String,String>(), false);
223 		writeCharacters("SVG 1.1 export generated by vectomatic.org");
224 		writeEndElement(ELT_DESC);
225 		
226 		// Serialize the palettes
227 		writeStartElement(ELT_DEFS, new HashMap<String,String>(), false);
228 		if (paletteList != null) {
229 			for (int i = 0, isize = paletteList.size(); i < isize; i++) {
230 				writeStartElement(ELT_G, new HashMap<String,String>(), false);
231 				Palette palette = paletteList.getPalette(i);
232 				writeStartElement(ELT_DESC, new HashMap<String,String>(), false);
233 				writeCharacters(palette.getName());
234 				writeEndElement(ELT_DESC);
235 				for (int j = 0, jsize = palette.getSize(); j < jsize; j++) {
236 					palette.getColor(j).acceptVisitor(_defStyleVisitor);
237 				}
238 				writeEndElement(ELT_G);
239 			}			
240 		}
241 		
242 		// Serialize the special color 'none'
243 		NoneStyle.NONE.acceptVisitor(_defStyleVisitor);
244 		writeEndElement(ELT_DEFS);
245 		
246 		// Serialize the shapes
247 		if (shapes != null) {
248 			for (int i = 0; i < shapes.length; i++) {
249 				shapes[i].acceptVisitor(this);
250 			}
251 		}
252 		writeEndElement(ELT_SVG);
253 	}
254 	
255 	public void visitEllipse(Ellipse ellipse) {
256 		TransformMatrix m = pushShape(ellipse, true);
257 		ellipse.getBoundingBox().getPoint(BoundingBox.PT_C, _p0).transform(m);
258 		ellipse.getBoundingBox().getPoint(BoundingBox.PT_SE, _p1).transform(m);
259 		_attributes.put(ATT_CX, Float.toString(_p0.x));
260 		_attributes.put(ATT_CY, Float.toString(_p0.y));
261 		_attributes.put(ATT_RX, Float.toString(Math.abs(_p1.x - _p0.x)));
262 		_attributes.put(ATT_RY, Float.toString(Math.abs(_p1.y - _p0.y)));
263 		writeStartElement(ELT_ELLIPSE, _attributes, true);
264 		popShape();
265 	}
266 
267 	public void visitPolyline(Polyline polyline) {
268 		TransformMatrix m = pushShape(polyline, true);
269 		Point[] pts = polyline.getVertices();
270 		StringBuffer vertexBuffer = new StringBuffer();
271 		for (int i = 0, size = pts.length - (polyline.isClosed() ? 1 : 0); i < size; i++) {
272 			if (i > 0) {
273 				vertexBuffer.append(" ");
274 			}
275 			pts[i].transform(m, _p0);
276 			vertexBuffer.append(_p0.x);
277 			vertexBuffer.append(",");
278 			vertexBuffer.append(_p0.y);
279 		}
280 		_attributes.put(ATT_POINTS, vertexBuffer.toString());
281 		if (polyline.isClosed()) {
282 			writeStartElement(ELT_POLYGON, _attributes, true);
283 		} else {
284 			writeStartElement(ELT_POLYLINE, _attributes, true);
285 		}
286 		popShape();
287 	}
288 
289 	public void visitRect(Rect rect) {
290 		TransformMatrix m = pushShape(rect, true);
291 		rect.getBoundingBox().getPoint(BoundingBox.PT_NW, _p0).transform(m);
292 		rect.getBoundingBox().getPoint(BoundingBox.PT_SE, _p1).transform(m);
293 		_attributes.put(ATT_X, Float.toString(Math.min(_p0.x, _p1.x)));
294 		_attributes.put(ATT_Y, Float.toString(Math.min(_p0.y, _p1.y)));
295 		_attributes.put(ATT_WIDTH, Float.toString(Math.abs(_p1.x - _p0.x)));
296 		_attributes.put(ATT_HEIGHT, Float.toString(Math.abs(_p1.y - _p0.y)));
297 		writeStartElement(ELT_RECT, _attributes, true);
298 		popShape();
299 	}
300 	
301 	public void visitPath(Path path) {
302 		TransformMatrix m = pushShape(path, true);
303 
304 		StringBuffer dValue = new StringBuffer();
305 		List<Segment> segments = path.getSegments();
306 		for (int i = 0, size = segments.size(); i < size; i++) {
307 			Segment segment = segments.get(i);
308 			if (i == 0) {
309 				segment.getStartPoint().transform(m, _p0);
310 				dValue.append(VAL_MOVE_TO);
311 				dValue.append(" ");
312 				dValue.append(_p0.x);
313 				dValue.append(" ");
314 				dValue.append(_p0.y);
315 			}
316 			dValue.append(" ");
317 			if (segment instanceof LineSegment) {
318 				segment.getEndPoint().transform(m, _p0);
319 				dValue.append(VAL_LINE_TO);
320 				dValue.append(" ");
321 				dValue.append(_p0.x);
322 				dValue.append(" ");
323 				dValue.append(_p0.y);
324 			} else {
325 				BezierSegment bezierSegment = (BezierSegment)segment;
326 				dValue.append(VAL_CURVE_TO);
327 				dValue.append(" ");
328 				bezierSegment.getStartControlPoint().transform(m, _p0);
329 				dValue.append(_p0.x);
330 				dValue.append(" ");
331 				dValue.append(_p0.y);
332 				dValue.append(" ");
333 				bezierSegment.getEndControlPoint().transform(m, _p0);
334 				dValue.append(_p0.x);
335 				dValue.append(" ");
336 				dValue.append(_p0.y);
337 				dValue.append(" ");
338 				bezierSegment.getEndPoint().transform(m, _p0);
339 				dValue.append(_p0.x);
340 				dValue.append(" ");
341 				dValue.append(_p0.y);
342 			}
343 		}
344 		_attributes.put(ATT_D, dValue.toString());
345 		
346 		if (!path.isClosed()) {
347 			_attributes.put(ATT_FILL, VAL_NONE);
348 			_attributes.remove(ATT_FILLOPACITY);
349 		}
350 
351 		writeStartElement(ELT_PATH, _attributes, true);
352 		popShape();
353 
354 	}
355 
356 	public void visitShapeGroup(ShapeGroup group) {
357 		pushShape(group, false);
358 		List<Shape> shapes = group.getShapes();
359 		for (int i = 0, size = shapes.size(); i < size; i++) {
360 			Shape shape = shapes.get(i);
361 			shape.acceptVisitor(this);
362 		}
363 		popShape();
364 	}
365 	
366 
367 	private void writeStartElement(String name, Map<String, String> attributes, boolean empty) {
368 		_stream.write("<");
369 		_stream.write(name);
370 		Iterator<Map.Entry<String, String>> iterator = attributes.entrySet().iterator();
371 		while(iterator.hasNext()) {
372 			Map.Entry<String, String> entry = iterator.next();
373 			String attName = entry.getKey();
374 			String attValue = entry.getValue();
375 			_stream.write(" ");
376 			_stream.write(attName);
377 			_stream.write("=\"");
378 			_stream.write(attValue);
379 			_stream.write("\"");
380 		}
381 		if (empty) {
382 			_stream.write("/");
383 		}
384 		_stream.write(">");
385 		if (empty) {
386 			_stream.write("\n");
387 		}
388 	}
389 	
390 	private void writeEndElement(String name) {
391 		_stream.write("</");
392 		_stream.write(name);
393 		_stream.write(">\n");
394 	}
395 	
396 	private void writeCharacters(String str) {
397 		_stream.write(str.replaceAll("&", "&amp;").replaceAll("<", "&lt;"));
398 	}
399 }