001 /*
002 * ShapeElement.java
003 *
004 *
005 * The Salamander Project - 2D and 3D graphics libraries in Java
006 * Copyright (C) 2004 Mark McKay
007 *
008 * This library is free software; you can redistribute it and/or
009 * modify it under the terms of the GNU Lesser General Public
010 * License as published by the Free Software Foundation; either
011 * version 2.1 of the License, or (at your option) any later version.
012 *
013 * This library is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016 * Lesser General Public License for more details.
017 *
018 * You should have received a copy of the GNU Lesser General Public
019 * License along with this library; if not, write to the Free Software
020 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021 *
022 * Mark McKay can be contacted at mark@kitfox.com. Salamander and other
023 * projects can be found at http://www.kitfox.com
024 *
025 * Created on January 26, 2004, 5:21 PM
026 */
027
028 package com.kitfox.svg;
029
030 import com.kitfox.svg.Marker.MarkerLayout;
031 import com.kitfox.svg.Marker.MarkerPos;
032 import com.kitfox.svg.xml.StyleAttribute;
033 import java.awt.AlphaComposite;
034 import java.awt.BasicStroke;
035 import java.awt.Color;
036 import java.awt.Composite;
037 import java.awt.Graphics2D;
038 import java.awt.Paint;
039 import java.awt.Shape;
040 import java.awt.geom.AffineTransform;
041 import java.awt.geom.Point2D;
042 import java.awt.geom.Rectangle2D;
043 import java.net.URI;
044 import java.util.ArrayList;
045 import java.util.List;
046
047
048
049 /**
050 * Parent of shape objects
051 *
052 * @author Mark McKay
053 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a>
054 */
055 abstract public class ShapeElement extends RenderableElement
056 {
057
058 /**
059 * This is necessary to get text elements to render the stroke the correct
060 * width. It is an alternative to producing new font glyph sets at different
061 * sizes.
062 */
063 protected float strokeWidthScalar = 1f;
064
065 /** Creates a new instance of ShapeElement */
066 public ShapeElement() {
067 }
068
069 abstract public void render(java.awt.Graphics2D g) throws SVGException;
070
071 /*
072 protected void setStrokeWidthScalar(float strokeWidthScalar)
073 {
074 this.strokeWidthScalar = strokeWidthScalar;
075 }
076 */
077
078 void pick(Point2D point, boolean boundingBox, List retVec) throws SVGException
079 {
080 StyleAttribute styleAttrib = new StyleAttribute();
081 // if (getStyle(styleAttrib.setName("fill")) && getShape().contains(point))
082 if ((boundingBox ? getBoundingBox() : getShape()).contains(point))
083 {
084 retVec.add(getPath(null));
085 }
086 }
087
088 void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List retVec) throws SVGException
089 {
090 StyleAttribute styleAttrib = new StyleAttribute();
091 // if (getStyle(styleAttrib.setName("fill")) && getShape().contains(point))
092 if (ltw.createTransformedShape((boundingBox ? getBoundingBox() : getShape())).intersects(pickArea))
093 {
094 retVec.add(getPath(null));
095 }
096 }
097
098 private Paint handleCurrentColor(StyleAttribute styleAttrib) throws SVGException
099 {
100 if (styleAttrib.getStringValue().equals("currentColor"))
101 {
102 StyleAttribute currentColorAttrib = new StyleAttribute();
103 if (getStyle(currentColorAttrib.setName("color")))
104 {
105 if (!currentColorAttrib.getStringValue().equals("none"))
106 {
107 return currentColorAttrib.getColorValue();
108 }
109 }
110 return null;
111 }
112 else
113 {
114 return styleAttrib.getColorValue();
115 }
116 }
117
118 protected void renderShape(Graphics2D g, Shape shape) throws SVGException
119 {
120 //g.setColor(Color.green);
121
122 StyleAttribute styleAttrib = new StyleAttribute();
123
124 //Don't process if not visible
125 if (getStyle(styleAttrib.setName("visibility")))
126 {
127 if (!styleAttrib.getStringValue().equals("visible")) return;
128 }
129
130 if (getStyle(styleAttrib.setName("display")))
131 {
132 if (styleAttrib.getStringValue().equals("none")) return;
133 }
134
135 //None, solid color, gradient, pattern
136 Paint fillPaint = Color.black; //Default to black. Must be explicitly set to none for no fill.
137 if (getStyle(styleAttrib.setName("fill")))
138 {
139 if (styleAttrib.getStringValue().equals("none")) fillPaint = null;
140 else
141 {
142 fillPaint = handleCurrentColor(styleAttrib);
143 if (fillPaint == null)
144 {
145 URI uri = styleAttrib.getURIValue(getXMLBase());
146 if (uri != null)
147 {
148 Rectangle2D bounds = shape.getBounds2D();
149 AffineTransform xform = g.getTransform();
150
151 SVGElement ele = diagram.getUniverse().getElement(uri);
152 if (ele != null)
153 {
154 fillPaint = ((FillElement)ele).getPaint(bounds, xform);
155 }
156 }
157 }
158 }
159 }
160
161 //Default opacity
162 float opacity = 1f;
163 if (getStyle(styleAttrib.setName("opacity")))
164 {
165 opacity = styleAttrib.getRatioValue();
166 }
167
168 float fillOpacity = opacity;
169 if (getStyle(styleAttrib.setName("fill-opacity")))
170 {
171 fillOpacity *= styleAttrib.getRatioValue();
172 }
173
174
175 Paint strokePaint = null; //Default is to stroke with none
176 if (getStyle(styleAttrib.setName("stroke")))
177 {
178 if (styleAttrib.getStringValue().equals("none")) strokePaint = null;
179 else
180 {
181 strokePaint = handleCurrentColor(styleAttrib);
182 if (strokePaint == null)
183 {
184 URI uri = styleAttrib.getURIValue(getXMLBase());
185 if (uri != null)
186 {
187 Rectangle2D bounds = shape.getBounds2D();
188 AffineTransform xform = g.getTransform();
189
190 SVGElement ele = diagram.getUniverse().getElement(uri);
191 if (ele != null)
192 {
193 strokePaint = ((FillElement)ele).getPaint(bounds, xform);
194 }
195 }
196 }
197 }
198 }
199
200 float[] strokeDashArray = null;
201 if (getStyle(styleAttrib.setName("stroke-dasharray")))
202 {
203 strokeDashArray = styleAttrib.getFloatList();
204 if (strokeDashArray.length == 0) strokeDashArray = null;
205 }
206
207 float strokeDashOffset = 0f;
208 if (getStyle(styleAttrib.setName("stroke-dashoffset")))
209 {
210 strokeDashOffset = styleAttrib.getFloatValueWithUnits();
211 }
212
213 int strokeLinecap = BasicStroke.CAP_BUTT;
214 if (getStyle(styleAttrib.setName("stroke-linecap")))
215 {
216 String val = styleAttrib.getStringValue();
217 if (val.equals("round"))
218 {
219 strokeLinecap = BasicStroke.CAP_ROUND;
220 }
221 else if (val.equals("square"))
222 {
223 strokeLinecap = BasicStroke.CAP_SQUARE;
224 }
225 }
226
227 int strokeLinejoin = BasicStroke.JOIN_MITER;
228 if (getStyle(styleAttrib.setName("stroke-linejoin")))
229 {
230 String val = styleAttrib.getStringValue();
231 if (val.equals("round"))
232 {
233 strokeLinejoin = BasicStroke.JOIN_ROUND;
234 }
235 else if (val.equals("bevel"))
236 {
237 strokeLinejoin = BasicStroke.JOIN_BEVEL;
238 }
239 }
240
241 float strokeMiterLimit = 4f;
242 if (getStyle(styleAttrib.setName("stroke-miterlimit")))
243 {
244 strokeMiterLimit = Math.max(styleAttrib.getFloatValueWithUnits(), 1);
245 }
246
247 float strokeOpacity = opacity;
248 if (getStyle(styleAttrib.setName("stroke-opacity")))
249 {
250 strokeOpacity *= styleAttrib.getRatioValue();
251 }
252
253 float strokeWidth = 1f;
254 if (getStyle(styleAttrib.setName("stroke-width")))
255 {
256 strokeWidth = styleAttrib.getFloatValueWithUnits();
257 }
258 // if (strokeWidthScalar != 1f)
259 // {
260 strokeWidth *= strokeWidthScalar;
261 // }
262
263 Marker markerStart = null;
264 if (getStyle(styleAttrib.setName("marker-start")))
265 {
266 if (!styleAttrib.getStringValue().equals("none"))
267 {
268 URI uri = styleAttrib.getURIValue(getXMLBase());
269 markerStart = (Marker)diagram.getUniverse().getElement(uri);
270 }
271 }
272
273 Marker markerMid = null;
274 if (getStyle(styleAttrib.setName("marker-mid")))
275 {
276 if (!styleAttrib.getStringValue().equals("none"))
277 {
278 URI uri = styleAttrib.getURIValue(getXMLBase());
279 markerMid = (Marker)diagram.getUniverse().getElement(uri);
280 }
281 }
282
283 Marker markerEnd = null;
284 if (getStyle(styleAttrib.setName("marker-end")))
285 {
286 if (!styleAttrib.getStringValue().equals("none"))
287 {
288 URI uri = styleAttrib.getURIValue(getXMLBase());
289 markerEnd = (Marker)diagram.getUniverse().getElement(uri);
290 }
291 }
292
293
294 //Draw the shape
295 if (fillPaint != null && fillOpacity != 0f)
296 {
297 if (fillOpacity <= 0)
298 {
299 //Do nothing
300 }
301 else if (fillOpacity < 1f)
302 {
303 Composite cachedComposite = g.getComposite();
304 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, fillOpacity));
305
306 g.setPaint(fillPaint);
307 g.fill(shape);
308
309 g.setComposite(cachedComposite);
310 }
311 else
312 {
313 g.setPaint(fillPaint);
314 g.fill(shape);
315 }
316 }
317
318
319 if (strokePaint != null && strokeOpacity != 0f)
320 {
321 BasicStroke stroke;
322 if (strokeDashArray == null)
323 {
324 stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit);
325 }
326 else
327 {
328 stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit, strokeDashArray, strokeDashOffset);
329 }
330
331 Shape strokeShape = stroke.createStrokedShape(shape);
332
333 if (strokeOpacity <= 0)
334 {
335 //Do nothing
336 }
337 else if (strokeOpacity < 1f)
338 {
339 Composite cachedComposite = g.getComposite();
340 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, strokeOpacity));
341
342 g.setPaint(strokePaint);
343 g.fill(strokeShape);
344
345 g.setComposite(cachedComposite);
346 }
347 else
348 {
349 g.setPaint(strokePaint);
350 g.fill(strokeShape);
351 }
352 }
353
354 if (markerStart != null || markerMid != null || markerEnd != null)
355 {
356 MarkerLayout layout = new MarkerLayout();
357 layout.layout(shape);
358
359 ArrayList list = layout.getMarkerList();
360 for (int i = 0; i < list.size(); ++i)
361 {
362 MarkerPos pos = (MarkerPos)list.get(i);
363
364 switch (pos.type)
365 {
366 case Marker.MARKER_START:
367 if (markerStart != null)
368 {
369 markerStart.render(g, pos, strokeWidth);
370 }
371 break;
372 case Marker.MARKER_MID:
373 if (markerMid != null)
374 {
375 markerMid.render(g, pos, strokeWidth);
376 }
377 break;
378 case Marker.MARKER_END:
379 if (markerEnd != null)
380 {
381 markerEnd.render(g, pos, strokeWidth);
382 }
383 break;
384 }
385 }
386 }
387 }
388
389 abstract public Shape getShape();
390
391 protected Rectangle2D includeStrokeInBounds(Rectangle2D rect) throws SVGException
392 {
393 StyleAttribute styleAttrib = new StyleAttribute();
394 if (!getStyle(styleAttrib.setName("stroke"))) return rect;
395
396 double strokeWidth = 1;
397 if (getStyle(styleAttrib.setName("stroke-width"))) strokeWidth = styleAttrib.getDoubleValue();
398
399 rect.setRect(
400 rect.getX() - strokeWidth / 2,
401 rect.getY() - strokeWidth / 2,
402 rect.getWidth() + strokeWidth,
403 rect.getHeight() + strokeWidth);
404
405 return rect;
406 }
407
408 }