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  import java.util.Stack;
25  
26  import org.vectomatic.common.model.Attribute;
27  import org.vectomatic.common.model.FloatAttributeValue;
28  import org.vectomatic.common.model.IAttributeValue;
29  import org.vectomatic.common.model.IShapeVisitor;
30  import org.vectomatic.common.model.IStyleVisitor;
31  import org.vectomatic.common.model.Shape;
32  import org.vectomatic.common.model.geometry.BezierSegment;
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.2 format
50   */
51  public class SVG12Visitor implements IShapeVisitor, ISVGExporter {
52  	private Stack<Shape> _stack;
53  	private IOutputStream _stream;
54  	private Map<String, String> _attributes;
55  	private IStyleVisitor _defStyleVisitor = new IStyleVisitor() {
56  		public void visitColor(Color color) {
57  			String style = color.toString();
58  			_attributes.clear();
59  			_attributes.put(ATT_SOLIDCOLOR, style);
60  			writeStartElement(ELT_SOLIDCOLOR, _attributes, true);
61  		}
62  
63  		public void visitNoneStyle(NoneStyle none) {
64  			_attributes.clear();
65  			_attributes.put(ATT_SOLIDCOLOR, "rgb(0,0,0)");
66  			_attributes.put(ATT_SOLIDOPACITY, "0");
67  			_attributes.put(ATT_ID, "none");
68  			writeStartElement(ELT_SOLIDCOLOR, _attributes, true);
69  		}		
70  	};
71  	
72  	private IStyleVisitor _strokeStyleVisitor = new IStyleVisitor() {
73  		public void visitColor(Color color) {
74  			_attributes.put(ATT_STROKE, color.toString());
75  		}
76  
77  		public void visitNoneStyle(NoneStyle none) {
78  			_attributes.put(ATT_STROKE, "url(#none)");
79  		}		
80  	};	
81  
82  	private IStyleVisitor _fillStyleVisitor = new IStyleVisitor() {
83  		public void visitColor(Color color) {
84  			_attributes.put(ATT_FILL, color.toString());
85  		}
86  
87  		public void visitNoneStyle(NoneStyle none) {
88  			_attributes.put(ATT_FILL, "url(#none)");
89  		}		
90  	};	
91  
92  	public SVG12Visitor() {
93  		_stack = new Stack<Shape>();
94  		_attributes = new HashMap<String,String>();
95  	}
96  	
97  	public void writeSVG(IOutputStream stream, Shape[] shapes, PaletteList paletteList, int width, int height) {
98  		_stream = stream;
99  		_stream.write("<?xml version=\"1.0\"?>\n");
100 
101 		_attributes.clear();
102 		_attributes.put(ATT_WIDTH, width + "px");
103 		_attributes.put(ATT_HEIGHT, height + "px");
104 		_attributes.put(ATT_VERSION, "1.2");
105 		_attributes.put(ATT_BASEPROFILE, "tiny");
106 		_attributes.put("xmlns", NS);
107 		writeStartElement(ELT_SVG, _attributes, false);
108 		
109 		// Serialize the representation metadata
110 		writeStartElement(ELT_DESC, new HashMap<String,String>(), false);
111 		writeCharacters("SVG 1.2 export generated by vectomatic.org");
112 		writeEndElement(ELT_DESC);
113 		
114 		// Serialize the palettes
115 		writeStartElement(ELT_DEFS, new HashMap<String,String>(), false);
116 		if (paletteList != null) {
117 			for (int i = 0, isize = paletteList.size(); i < isize; i++) {
118 				writeStartElement(ELT_G, new HashMap<String,String>(), false);
119 				Palette palette = paletteList.getPalette(i);
120 				writeStartElement(ELT_DESC, new HashMap<String,String>(), false);
121 				writeCharacters(palette.getName());
122 				writeEndElement(ELT_DESC);
123 				for (int j = 0, jsize = palette.getSize(); j < jsize; j++) {
124 					palette.getColor(j).acceptVisitor(_defStyleVisitor);
125 				}
126 				writeEndElement(ELT_G);
127 			}			
128 		}
129 		
130 		// Serialize the special color 'none'
131 		NoneStyle.NONE.acceptVisitor(_defStyleVisitor);
132 		writeEndElement(ELT_DEFS);
133 		
134 		// Serialize the shapes
135 		if (shapes != null) {
136 			for (int i = 0; i < shapes.length; i++) {
137 				shapes[i].acceptVisitor(this);
138 			}
139 		}
140 		writeEndElement(ELT_SVG);
141 	}
142 	
143 	public void visitEllipse(Ellipse ellipse) {
144 		pushShape(ellipse);
145 		_attributes.put(ATT_CX, "0");
146 		_attributes.put(ATT_CY, "0");
147 		_attributes.put(ATT_RX, "1");
148 		_attributes.put(ATT_RY, "1");
149 		writeStartElement(ELT_ELLIPSE, _attributes, true);
150 		popShape();
151 	}
152 
153 	public void visitPolyline(Polyline polyline) {
154 		pushShape(polyline);
155 		Point[] pts = polyline.getVertices();
156 		StringBuffer vertexBuffer = new StringBuffer();
157 		for (int i = 0, size = pts.length - (polyline.isClosed() ? 1 : 0); i < size; i++) {
158 			if (i > 0) {
159 				vertexBuffer.append(" ");
160 			}
161 			vertexBuffer.append(pts[i].x);
162 			vertexBuffer.append(",");
163 			vertexBuffer.append(pts[i].y);
164 		}
165 		_attributes.put(ATT_POINTS, vertexBuffer.toString());
166 		if (polyline.isClosed()) {
167 			writeStartElement(ELT_POLYGON, _attributes, true);
168 		} else {
169 			writeStartElement(ELT_POLYLINE, _attributes, true);
170 		}
171 		popShape();
172 	}
173 
174 	public void visitRect(Rect rect) {
175 		pushShape(rect);
176 		_attributes.put(ATT_X, "-1");
177 		_attributes.put(ATT_Y, "-1");
178 		_attributes.put(ATT_WIDTH, "2");
179 		_attributes.put(ATT_HEIGHT, "2");
180 		writeStartElement(ELT_RECT, _attributes, true);
181 		popShape();
182 	}
183 
184 	public void visitPath(Path path) {
185 		pushShape(path);
186 
187 		StringBuffer dValue = new StringBuffer();
188 		List<Segment> segments = path.getSegments();
189 		for (int i = 0, size = segments.size(); i < size; i++) {
190 			Segment segment = segments.get(i);
191 			if (i == 0) {
192 				dValue.append(VAL_MOVE_TO);
193 				dValue.append(" ");
194 				dValue.append(segment.getStartPoint().x);
195 				dValue.append(" ");
196 				dValue.append(segment.getStartPoint().y);
197 			}
198 			dValue.append(" ");
199 			if (segment instanceof LineSegment) {
200 				dValue.append(VAL_LINE_TO);
201 				dValue.append(" ");
202 				dValue.append(segment.getEndPoint().x);
203 				dValue.append(" ");
204 				dValue.append(segment.getEndPoint().y);
205 			} else {
206 				BezierSegment bezierSegment = (BezierSegment)segment;
207 				dValue.append(VAL_CURVE_TO);
208 				dValue.append(" ");
209 				dValue.append(bezierSegment.getStartControlPoint().x);
210 				dValue.append(" ");
211 				dValue.append(bezierSegment.getStartControlPoint().y);
212 				dValue.append(" ");
213 				dValue.append(bezierSegment.getEndControlPoint().x);
214 				dValue.append(" ");
215 				dValue.append(bezierSegment.getEndControlPoint().y);
216 				dValue.append(" ");
217 				dValue.append(bezierSegment.getEndPoint().x);
218 				dValue.append(" ");
219 				dValue.append(bezierSegment.getEndPoint().y);
220 			}
221 		}
222 		_attributes.put(ATT_D, dValue.toString());
223 		
224 		if (!path.isClosed()) {
225 			_attributes.put(ATT_FILL, VAL_NONE);
226 			_attributes.remove(ATT_FILLOPACITY);
227 		}
228 
229 		writeStartElement(ELT_PATH, _attributes, true);
230 		popShape();
231 
232 	}
233 
234 	public void visitShapeGroup(ShapeGroup group) {
235 		pushShape(group);
236 		writeStartElement(ELT_G, _attributes, false);
237 		List<Shape> shapes = group.getShapes();
238 		for (int i = 0, size = shapes.size(); i < size; i++) {
239 			Shape shape = shapes.get(i);
240 			shape.acceptVisitor(this);
241 		}
242 		writeEndElement(ELT_G);
243 		popShape();
244 	}
245 	
246 	private String getTransform(Shape shape) {
247 		StringBuffer buffer = new StringBuffer();
248 		TransformMatrix m = shape.getTransform();
249 		buffer.append("matrix(");
250 		buffer.append(m.m11);
251 		buffer.append(" ");
252 		buffer.append(m.m21);
253 		buffer.append(" ");
254 		buffer.append(m.m12);
255 		buffer.append(" ");
256 		buffer.append(m.m22);
257 		buffer.append(" ");
258 		buffer.append(m.m13);
259 		buffer.append(" ");
260 		buffer.append(m.m23);
261 		buffer.append(")");
262 		return buffer.toString();
263 	}
264 	
265 	private void pushShape(Shape shape) {
266 		_attributes.clear();
267 		_attributes.put(ATT_TRANSFORM, getTransform(shape));
268 		_attributes.put(ATT_VECTOREFFECT, VAL_NON_SCALING_STROKE);
269 		_stack.push(shape);
270 		IStyle strokeStyle = ((IStyle)getAttribute(Attribute.LINE_STYLE));
271 		if (strokeStyle != null) {
272 			strokeStyle.acceptVisitor(_strokeStyleVisitor);
273 			float opacity = ((FloatAttributeValue)getAttribute(Attribute.LINE_OPACITY)).getValue();
274 			if (opacity < 1.0f) {
275 				_attributes.put(ATT_STROKEOPACITY, Float.toString(opacity));
276 			}
277 			_attributes.put(ATT_STROKEWIDTH, getAttribute(Attribute.LINE_WIDTH).toString());
278 		}
279 		IStyle fillStyle = ((IStyle)getAttribute(Attribute.FILL_STYLE));
280 		if (fillStyle != null) {
281 			fillStyle.acceptVisitor(_fillStyleVisitor);
282 			float opacity = ((FloatAttributeValue)getAttribute(Attribute.FILL_OPACITY)).getValue();
283 			if (opacity < 1.0f) {
284 				_attributes.put(ATT_FILLOPACITY, Float.toString(opacity));
285 			}
286 		}
287 	}
288 	
289 	private void popShape() {
290 		_stack.pop();
291 	}
292 	
293 	private IAttributeValue getAttribute(Attribute attr) {
294 		IAttributeValue attrValue = null;
295 		for (int i = 0; i < _stack.size(); i++) {
296 			if (_stack.get(i).definesAttribute(attr)) {
297 				attrValue = _stack.get(i).getAttribute(attr);
298 				break;
299 			}
300 		}
301 		return attrValue;
302 	}
303 	
304 	private void writeStartElement(String name, Map<String,String> attributes, boolean empty) {
305 		_stream.write("<");
306 		_stream.write(name);
307 		Iterator<Map.Entry<String,String>> iterator = attributes.entrySet().iterator();
308 		while(iterator.hasNext()) {
309 			Map.Entry<String,String> entry = iterator.next();
310 			String attName = entry.getKey();
311 			String attValue = entry.getValue();
312 			_stream.write(" ");
313 			_stream.write(attName);
314 			_stream.write("=\"");
315 			_stream.write(attValue);
316 			_stream.write("\"");
317 		}
318 		if (empty) {
319 			_stream.write("/");
320 		}
321 		_stream.write(">");
322 		if (empty) {
323 			_stream.write("\n");
324 		}
325 	}
326 	
327 	private void writeEndElement(String name) {
328 		_stream.write("</");
329 		_stream.write(name);
330 		_stream.write(">\n");
331 	}
332 	
333 	private void writeCharacters(String str) {
334 		_stream.write(str.replaceAll("&", "&amp;").replaceAll("<", "&lt;"));
335 	}
336 }