001/* 002 * SVG Salamander 003 * Copyright (c) 2004, Mark McKay 004 * All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or 007 * without modification, are permitted provided that the following 008 * conditions are met: 009 * 010 * - Redistributions of source code must retain the above 011 * copyright notice, this list of conditions and the following 012 * disclaimer. 013 * - Redistributions in binary form must reproduce the above 014 * copyright notice, this list of conditions and the following 015 * disclaimer in the documentation and/or other materials 016 * provided with the distribution. 017 * 018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 019 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 020 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 021 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 022 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 025 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 026 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 027 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 028 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 029 * OF THE POSSIBILITY OF SUCH DAMAGE. 030 * 031 * Mark McKay can be contacted at mark@kitfox.com. Salamander and other 032 * projects can be found at http://www.kitfox.com 033 * 034 * Created on January 26, 2004, 1:56 AM 035 */ 036package com.kitfox.svg; 037 038import com.kitfox.svg.xml.StyleAttribute; 039import java.awt.Graphics2D; 040import java.awt.Shape; 041import java.awt.geom.AffineTransform; 042import java.awt.geom.Area; 043import java.awt.geom.NoninvertibleTransformException; 044import java.awt.geom.Point2D; 045import java.awt.geom.Rectangle2D; 046import java.util.Iterator; 047import java.util.List; 048 049/** 050 * @author Mark McKay 051 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a> 052 */ 053public class Group extends ShapeElement 054{ 055 public static final String TAG_NAME = "group"; 056 057 //Cache bounding box for faster clip testing 058 Rectangle2D boundingBox; 059 Shape cachedShape; 060 061 /** 062 * Creates a new instance of Stop 063 */ 064 public Group() 065 { 066 } 067 068 @Override 069 public String getTagName() 070 { 071 return TAG_NAME; 072 } 073 074 /** 075 * Called after the start element but before the end element to indicate 076 * each child tag that has been processed 077 */ 078 @Override 079 public void loaderAddChild(SVGLoaderHelper helper, SVGElement child) throws SVGElementException 080 { 081 super.loaderAddChild(helper, child); 082 } 083 084 protected boolean outsideClip(Graphics2D g) throws SVGException 085 { 086 Shape clip = g.getClip(); 087 if (clip == null) 088 { 089 return false; 090 } 091 //g.getClipBounds(clipBounds); 092 Rectangle2D rect = getBoundingBox(); 093 094 if (clip.intersects(rect)) 095 { 096 return false; 097 } 098 099 return true; 100 } 101 102 @Override 103 void pick(Point2D point, boolean boundingBox, List<List<SVGElement>> retVec) throws SVGException 104 { 105 Point2D xPoint = new Point2D.Double(point.getX(), point.getY()); 106 if (xform != null) 107 { 108 try 109 { 110 xform.inverseTransform(point, xPoint); 111 } catch (NoninvertibleTransformException ex) 112 { 113 throw new SVGException(ex); 114 } 115 } 116 117 118 for (SVGElement ele : children) { 119 if (ele instanceof RenderableElement) 120 { 121 RenderableElement rendEle = (RenderableElement) ele; 122 123 rendEle.pick(xPoint, boundingBox, retVec); 124 } 125 } 126 } 127 128 @Override 129 void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List<List<SVGElement>> retVec) throws SVGException 130 { 131 if (xform != null) 132 { 133 ltw = new AffineTransform(ltw); 134 ltw.concatenate(xform); 135 } 136 137 138 for (SVGElement ele : children) { 139 if (ele instanceof RenderableElement) 140 { 141 RenderableElement rendEle = (RenderableElement) ele; 142 143 rendEle.pick(pickArea, ltw, boundingBox, retVec); 144 } 145 } 146 } 147 148 @Override 149 public void render(Graphics2D g) throws SVGException 150 { 151 //Don't process if not visible 152 StyleAttribute styleAttrib = new StyleAttribute(); 153 //Visibility can be overridden by children 154 155 if (getStyle(styleAttrib.setName("display"))) 156 { 157 if (styleAttrib.getStringValue().equals("none")) 158 { 159 return; 160 } 161 } 162 163 //Do not process offscreen groups 164 boolean ignoreClip = diagram.ignoringClipHeuristic(); 165// if (!ignoreClip && outsideClip(g)) 166// { 167// return; 168// } 169 170 beginLayer(g); 171 172 Iterator<SVGElement> it = children.iterator(); 173 174// try 175// { 176// g.getClipBounds(clipBounds); 177// } 178// catch (Exception e) 179// { 180// //For some reason, getClipBounds can throw a null pointer exception for 181// // some types of Graphics2D 182// ignoreClip = true; 183// } 184 185 Shape clip = g.getClip(); 186 while (it.hasNext()) 187 { 188 SVGElement ele = it.next(); 189 if (ele instanceof RenderableElement) 190 { 191 RenderableElement rendEle = (RenderableElement) ele; 192 193// if (shapeEle == null) continue; 194 195 if (!(ele instanceof Group)) 196 { 197 //Skip if clipping area is outside our bounds 198 if (!ignoreClip && clip != null 199 && !clip.intersects(rendEle.getBoundingBox())) 200 { 201 continue; 202 } 203 } 204 205 rendEle.render(g); 206 } 207 } 208 209 finishLayer(g); 210 } 211 212 /** 213 * Retrieves the cached bounding box of this group 214 */ 215 @Override 216 public Shape getShape() 217 { 218 if (cachedShape == null) 219 { 220 calcShape(); 221 } 222 return cachedShape; 223 } 224 225 public void calcShape() 226 { 227 Area retShape = new Area(); 228 229 for (SVGElement ele : children) { 230 if (ele instanceof ShapeElement) 231 { 232 ShapeElement shpEle = (ShapeElement) ele; 233 Shape shape = shpEle.getShape(); 234 if (shape != null) 235 { 236 retShape.add(new Area(shape)); 237 } 238 } 239 } 240 241 cachedShape = shapeToParent(retShape); 242 } 243 244 /** 245 * Retrieves the cached bounding box of this group 246 */ 247 @Override 248 public Rectangle2D getBoundingBox() throws SVGException 249 { 250 if (boundingBox == null) 251 { 252 calcBoundingBox(); 253 } 254// calcBoundingBox(); 255 return boundingBox; 256 } 257 258 /** 259 * Recalculates the bounding box by taking the union of the bounding boxes 260 * of all children. Caches the result. 261 * @throws com.kitfox.svg.SVGException 262 */ 263 public void calcBoundingBox() throws SVGException 264 { 265 Rectangle2D retRect = null; 266 267 for (SVGElement ele : children) { 268 if (ele instanceof RenderableElement) 269 { 270 RenderableElement rendEle = (RenderableElement) ele; 271 Rectangle2D bounds = rendEle.getBoundingBox(); 272 if (bounds != null && (bounds.getWidth() != 0 || bounds.getHeight() != 0)) 273 { 274 if (retRect == null) 275 { 276 retRect = bounds; 277 } 278 else 279 { 280 if (retRect.getWidth() != 0 || retRect.getHeight() != 0) 281 { 282 retRect = retRect.createUnion(bounds); 283 } 284 } 285 } 286 } 287 } 288 289// if (xform != null) 290// { 291// retRect = xform.createTransformedShape(retRect).getBounds2D(); 292// } 293 294 //If no contents, use degenerate rectangle 295 if (retRect == null) 296 { 297 retRect = new Rectangle2D.Float(); 298 } 299 300 boundingBox = boundsToParent(retRect); 301 } 302 303 @Override 304 public boolean updateTime(double curTime) throws SVGException 305 { 306 boolean changeState = super.updateTime(curTime); 307 Iterator<SVGElement> it = children.iterator(); 308 309 //Distribute message to all members of this group 310 while (it.hasNext()) 311 { 312 SVGElement ele = it.next(); 313 boolean updateVal = ele.updateTime(curTime); 314 315 changeState = changeState || updateVal; 316 317 //Update our shape if shape aware children change 318 if (ele instanceof ShapeElement) 319 { 320 cachedShape = null; 321 } 322 if (ele instanceof RenderableElement) 323 { 324 boundingBox = null; 325 } 326 } 327 328 return changeState; 329 } 330}