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