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 February 18, 2004, 5:33 PM 035 */ 036 037package com.kitfox.svg; 038 039import com.kitfox.svg.xml.NumberWithUnits; 040import com.kitfox.svg.xml.StyleAttribute; 041import com.kitfox.svg.xml.StyleSheet; 042import java.awt.Graphics2D; 043import java.awt.Rectangle; 044import java.awt.Shape; 045import java.awt.geom.AffineTransform; 046import java.awt.geom.NoninvertibleTransformException; 047import java.awt.geom.Point2D; 048import java.awt.geom.Rectangle2D; 049import java.util.List; 050 051/** 052 * The root element of an SVG tree. 053 * 054 * @author Mark McKay 055 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a> 056 */ 057public class SVGRoot extends Group 058{ 059 public static final String TAG_NAME = "svg"; 060 061 NumberWithUnits x; 062 NumberWithUnits y; 063 NumberWithUnits width; 064 NumberWithUnits height; 065 066 Rectangle2D.Float viewBox = null; 067 068 public static final int PA_X_NONE = 0; 069 public static final int PA_X_MIN = 1; 070 public static final int PA_X_MID = 2; 071 public static final int PA_X_MAX = 3; 072 073 public static final int PA_Y_NONE = 0; 074 public static final int PA_Y_MIN = 1; 075 public static final int PA_Y_MID = 2; 076 public static final int PA_Y_MAX = 3; 077 078 public static final int PS_MEET = 0; 079 public static final int PS_SLICE = 1; 080 081 int parSpecifier = PS_MEET; 082 int parAlignX = PA_X_MID; 083 int parAlignY = PA_Y_MID; 084 085 final AffineTransform viewXform = new AffineTransform(); 086 final Rectangle2D.Float clipRect = new Rectangle2D.Float(); 087 088 private StyleSheet styleSheet; 089 090 /** Creates a new instance of SVGRoot */ 091 public SVGRoot() 092 { 093 } 094 095 public String getTagName() 096 { 097 return TAG_NAME; 098 } 099 100 public void build() throws SVGException 101 { 102 super.build(); 103 104 StyleAttribute sty = new StyleAttribute(); 105 106 if (getPres(sty.setName("x"))) 107 { 108 x = sty.getNumberWithUnits(); 109 } 110 111 if (getPres(sty.setName("y"))) 112 { 113 y = sty.getNumberWithUnits(); 114 } 115 116 if (getPres(sty.setName("width"))) 117 { 118 width = sty.getNumberWithUnits(); 119 } 120 121 if (getPres(sty.setName("height"))) 122 { 123 height = sty.getNumberWithUnits(); 124 } 125 126 if (getPres(sty.setName("viewBox"))) 127 { 128 float[] coords = sty.getFloatList(); 129 viewBox = new Rectangle2D.Float(coords[0], coords[1], coords[2], coords[3]); 130 } 131 132 if (getPres(sty.setName("preserveAspectRatio"))) 133 { 134 String preserve = sty.getStringValue(); 135 136 if (contains(preserve, "none")) { parAlignX = PA_X_NONE; parAlignY = PA_Y_NONE; } 137 else if (contains(preserve, "xMinYMin")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MIN; } 138 else if (contains(preserve, "xMidYMin")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MIN; } 139 else if (contains(preserve, "xMaxYMin")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MIN; } 140 else if (contains(preserve, "xMinYMid")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MID; } 141 else if (contains(preserve, "xMidYMid")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MID; } 142 else if (contains(preserve, "xMaxYMid")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MID; } 143 else if (contains(preserve, "xMinYMax")) { parAlignX = PA_X_MIN; parAlignY = PA_Y_MAX; } 144 else if (contains(preserve, "xMidYMax")) { parAlignX = PA_X_MID; parAlignY = PA_Y_MAX; } 145 else if (contains(preserve, "xMaxYMax")) { parAlignX = PA_X_MAX; parAlignY = PA_Y_MAX; } 146 147 if (contains(preserve, "meet")) 148 { 149 parSpecifier = PS_MEET; 150 } 151 else if (contains(preserve, "slice")) 152 { 153 parSpecifier = PS_SLICE; 154 } 155 } 156 157 prepareViewport(); 158 } 159 160 private boolean contains(String text, String find) 161 { 162 return (text.indexOf(find) != -1); 163 } 164 165 public SVGRoot getRoot() 166 { 167 return this; 168 } 169 170 protected void prepareViewport() 171 { 172 Rectangle deviceViewport = diagram.getDeviceViewport(); 173 174 Rectangle2D defaultBounds; 175 try 176 { 177 defaultBounds = getBoundingBox(); 178 } 179 catch (SVGException ex) 180 { 181 defaultBounds= new Rectangle2D.Float(); 182 } 183 184 //Determine destination rectangle 185 float xx, yy, ww, hh; 186 if (width != null) 187 { 188 xx = (x == null) ? 0 : StyleAttribute.convertUnitsToPixels(x.getUnits(), x.getValue()); 189 if (width.getUnits() == NumberWithUnits.UT_PERCENT) 190 { 191 ww = width.getValue() * deviceViewport.width; 192 } 193 else 194 { 195 ww = StyleAttribute.convertUnitsToPixels(width.getUnits(), width.getValue()); 196 } 197 } 198 else if (viewBox != null) 199 { 200 xx = (float)viewBox.x; 201 ww = (float)viewBox.width; 202 width = new NumberWithUnits(ww, NumberWithUnits.UT_PX); 203 x = new NumberWithUnits(xx, NumberWithUnits.UT_PX); 204 } 205 else 206 { 207 //Estimate size from scene bounding box 208 xx = (float)defaultBounds.getX(); 209 ww = (float)defaultBounds.getWidth(); 210 width = new NumberWithUnits(ww, NumberWithUnits.UT_PX); 211 x = new NumberWithUnits(xx, NumberWithUnits.UT_PX); 212 } 213 214 if (height != null) 215 { 216 yy = (y == null) ? 0 : StyleAttribute.convertUnitsToPixels(y.getUnits(), y.getValue()); 217 if (height.getUnits() == NumberWithUnits.UT_PERCENT) 218 { 219 hh = height.getValue() * deviceViewport.height; 220 } 221 else 222 { 223 hh = StyleAttribute.convertUnitsToPixels(height.getUnits(), height.getValue()); 224 } 225 } 226 else if (viewBox != null) 227 { 228 yy = (float)viewBox.y; 229 hh = (float)viewBox.height; 230 height = new NumberWithUnits(hh, NumberWithUnits.UT_PX); 231 y = new NumberWithUnits(yy, NumberWithUnits.UT_PX); 232 } 233 else 234 { 235 //Estimate size from scene bounding box 236 yy = (float)defaultBounds.getY(); 237 hh = (float)defaultBounds.getHeight(); 238 height = new NumberWithUnits(hh, NumberWithUnits.UT_PX); 239 y = new NumberWithUnits(yy, NumberWithUnits.UT_PX); 240 } 241 242 clipRect.setRect(xx, yy, ww, hh); 243 244// if (viewBox == null) 245// { 246// viewXform.setToIdentity(); 247// } 248// else 249// { 250// //If viewport window is set, we are drawing to entire viewport 251// clipRect.setRect(deviceViewport); 252// 253// viewXform.setToIdentity(); 254// viewXform.setToTranslation(deviceViewport.x, deviceViewport.y); 255// viewXform.scale(deviceViewport.width, deviceViewport.height); 256// viewXform.scale(1 / viewBox.width, 1 / viewBox.height); 257// viewXform.translate(-viewBox.x, -viewBox.y); 258// } 259 } 260 261 public void renderToViewport(Graphics2D g) throws SVGException 262 { 263 prepareViewport(); 264 265 if (viewBox == null) 266 { 267 viewXform.setToIdentity(); 268 } 269 else 270 { 271 Rectangle deviceViewport = g.getClipBounds(); 272 //If viewport window is set, we are drawing to entire viewport 273 clipRect.setRect(deviceViewport); 274 275 viewXform.setToIdentity(); 276 viewXform.setToTranslation(deviceViewport.x, deviceViewport.y); 277 viewXform.scale(deviceViewport.width, deviceViewport.height); 278 viewXform.scale(1 / viewBox.width, 1 / viewBox.height); 279 viewXform.translate(-viewBox.x, -viewBox.y); 280 } 281 282 AffineTransform cachedXform = g.getTransform(); 283 g.transform(viewXform); 284 285 super.render(g); 286 287 g.setTransform(cachedXform); 288 } 289 290 public void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List retVec) throws SVGException 291 { 292 if (viewXform != null) 293 { 294 ltw = new AffineTransform(ltw); 295 ltw.concatenate(viewXform); 296 } 297 298 super.pick(pickArea, ltw, boundingBox, retVec); 299 } 300 301 public void pick(Point2D point, boolean boundingBox, List retVec) throws SVGException 302 { 303 Point2D xPoint = new Point2D.Double(point.getX(), point.getY()); 304 if (viewXform != null) 305 { 306 try 307 { 308 viewXform.inverseTransform(point, xPoint); 309 } catch (NoninvertibleTransformException ex) 310 { 311 throw new SVGException(ex); 312 } 313 } 314 315 super.pick(xPoint, boundingBox, retVec); 316 } 317 318 public Shape getShape() 319 { 320 Shape shape = super.getShape(); 321 return viewXform.createTransformedShape(shape); 322 } 323 324 public Rectangle2D getBoundingBox() throws SVGException 325 { 326 Rectangle2D bbox = super.getBoundingBox(); 327 return viewXform.createTransformedShape(bbox).getBounds2D(); 328 } 329 330 public float getDeviceWidth() 331 { 332 return clipRect.width; 333 } 334 335 public float getDeviceHeight() 336 { 337 return clipRect.height; 338 } 339 340 public Rectangle2D getDeviceRect(Rectangle2D rect) 341 { 342 rect.setRect(clipRect); 343 return rect; 344 } 345 346 /** 347 * Updates all attributes in this diagram associated with a time event. 348 * Ie, all attributes with track information. 349 * @return - true if this node has changed state as a result of the time 350 * update 351 */ 352 public boolean updateTime(double curTime) throws SVGException 353 { 354 boolean changeState = super.updateTime(curTime); 355 356 StyleAttribute sty = new StyleAttribute(); 357 boolean shapeChange = false; 358 359 if (getPres(sty.setName("x"))) 360 { 361 NumberWithUnits newVal = sty.getNumberWithUnits(); 362 if (!newVal.equals(x)) 363 { 364 x = newVal; 365 shapeChange = true; 366 } 367 } 368 369 if (getPres(sty.setName("y"))) 370 { 371 NumberWithUnits newVal = sty.getNumberWithUnits(); 372 if (!newVal.equals(y)) 373 { 374 y = newVal; 375 shapeChange = true; 376 } 377 } 378 379 if (getPres(sty.setName("width"))) 380 { 381 NumberWithUnits newVal = sty.getNumberWithUnits(); 382 if (!newVal.equals(width)) 383 { 384 width = newVal; 385 shapeChange = true; 386 } 387 } 388 389 if (getPres(sty.setName("height"))) 390 { 391 NumberWithUnits newVal = sty.getNumberWithUnits(); 392 if (!newVal.equals(height)) 393 { 394 height = newVal; 395 shapeChange = true; 396 } 397 } 398 399 if (getPres(sty.setName("viewBox"))) 400 { 401 float[] coords = sty.getFloatList(); 402 Rectangle2D.Float newViewBox = new Rectangle2D.Float(coords[0], coords[1], coords[2], coords[3]); 403 if (!newViewBox.equals(viewBox)) 404 { 405 viewBox = newViewBox; 406 shapeChange = true; 407 } 408 } 409 410 if (shapeChange) 411 { 412 build(); 413 } 414 415 return changeState || shapeChange; 416 } 417 418 /** 419 * @return the styleSheet 420 */ 421 public StyleSheet getStyleSheet() 422 { 423 if (styleSheet == null) 424 { 425 for (int i = 0; i < getNumChildren(); ++i) 426 { 427 SVGElement ele = getChild(i); 428 if (ele instanceof Style) 429 { 430 return ((Style)ele).getStyleSheet(); 431 } 432 } 433 } 434 435 return styleSheet; 436 } 437 438 /** 439 * @param styleSheet the styleSheet to set 440 */ 441 public void setStyleSheet(StyleSheet styleSheet) 442 { 443 this.styleSheet = styleSheet; 444 } 445 446}