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 Rectangle targetViewport = g.getClipBounds(); 268// 269// if (targetViewport == null) 270// { 271// Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); 272// targetViewport = new Rectangle(0, 0, size.width, size.height); 273// } 274// clipRect.setRect(targetViewport); 275 276 277 Rectangle deviceViewport = diagram.getDeviceViewport(); 278 if (width != null && height != null) 279 { 280 float xx, yy, ww, hh; 281 282 xx = (x == null) ? 0 : StyleAttribute.convertUnitsToPixels(x.getUnits(), x.getValue()); 283 if (width.getUnits() == NumberWithUnits.UT_PERCENT) 284 { 285 ww = width.getValue() * deviceViewport.width; 286 } 287 else 288 { 289 ww = StyleAttribute.convertUnitsToPixels(width.getUnits(), width.getValue()); 290 } 291 292 yy = (y == null) ? 0 : StyleAttribute.convertUnitsToPixels(y.getUnits(), y.getValue()); 293 if (height.getUnits() == NumberWithUnits.UT_PERCENT) 294 { 295 hh = height.getValue() * deviceViewport.height; 296 } 297 else 298 { 299 hh = StyleAttribute.convertUnitsToPixels(height.getUnits(), height.getValue()); 300 } 301 302 targetViewport = new Rectangle((int)xx, (int)yy, (int)ww, (int)hh); 303 } 304 else 305 { 306// Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); 307// targetViewport = new Rectangle(0, 0, size.width, size.height); 308 targetViewport = new Rectangle(deviceViewport); 309 } 310 clipRect.setRect(targetViewport); 311 312 if (viewBox == null) 313 { 314 viewXform.setToIdentity(); 315 } 316 else 317 { 318 viewXform.setToIdentity(); 319 viewXform.setToTranslation(targetViewport.x, targetViewport.y); 320 viewXform.scale(targetViewport.width, targetViewport.height); 321 viewXform.scale(1 / viewBox.width, 1 / viewBox.height); 322 viewXform.translate(-viewBox.x, -viewBox.y); 323 } 324 325 AffineTransform cachedXform = g.getTransform(); 326 g.transform(viewXform); 327 328 super.render(g); 329 330 g.setTransform(cachedXform); 331 } 332 333 public void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List retVec) throws SVGException 334 { 335 if (viewXform != null) 336 { 337 ltw = new AffineTransform(ltw); 338 ltw.concatenate(viewXform); 339 } 340 341 super.pick(pickArea, ltw, boundingBox, retVec); 342 } 343 344 public void pick(Point2D point, boolean boundingBox, List retVec) throws SVGException 345 { 346 Point2D xPoint = new Point2D.Double(point.getX(), point.getY()); 347 if (viewXform != null) 348 { 349 try 350 { 351 viewXform.inverseTransform(point, xPoint); 352 } catch (NoninvertibleTransformException ex) 353 { 354 throw new SVGException(ex); 355 } 356 } 357 358 super.pick(xPoint, boundingBox, retVec); 359 } 360 361 public Shape getShape() 362 { 363 Shape shape = super.getShape(); 364 return viewXform.createTransformedShape(shape); 365 } 366 367 public Rectangle2D getBoundingBox() throws SVGException 368 { 369 Rectangle2D bbox = super.getBoundingBox(); 370 return viewXform.createTransformedShape(bbox).getBounds2D(); 371 } 372 373 public float getDeviceWidth() 374 { 375 return clipRect.width; 376 } 377 378 public float getDeviceHeight() 379 { 380 return clipRect.height; 381 } 382 383 public Rectangle2D getDeviceRect(Rectangle2D rect) 384 { 385 rect.setRect(clipRect); 386 return rect; 387 } 388 389 /** 390 * Updates all attributes in this diagram associated with a time event. 391 * Ie, all attributes with track information. 392 * @return - true if this node has changed state as a result of the time 393 * update 394 */ 395 public boolean updateTime(double curTime) throws SVGException 396 { 397 boolean changeState = super.updateTime(curTime); 398 399 StyleAttribute sty = new StyleAttribute(); 400 boolean shapeChange = false; 401 402 if (getPres(sty.setName("x"))) 403 { 404 NumberWithUnits newVal = sty.getNumberWithUnits(); 405 if (!newVal.equals(x)) 406 { 407 x = newVal; 408 shapeChange = true; 409 } 410 } 411 412 if (getPres(sty.setName("y"))) 413 { 414 NumberWithUnits newVal = sty.getNumberWithUnits(); 415 if (!newVal.equals(y)) 416 { 417 y = newVal; 418 shapeChange = true; 419 } 420 } 421 422 if (getPres(sty.setName("width"))) 423 { 424 NumberWithUnits newVal = sty.getNumberWithUnits(); 425 if (!newVal.equals(width)) 426 { 427 width = newVal; 428 shapeChange = true; 429 } 430 } 431 432 if (getPres(sty.setName("height"))) 433 { 434 NumberWithUnits newVal = sty.getNumberWithUnits(); 435 if (!newVal.equals(height)) 436 { 437 height = newVal; 438 shapeChange = true; 439 } 440 } 441 442 if (getPres(sty.setName("viewBox"))) 443 { 444 float[] coords = sty.getFloatList(); 445 Rectangle2D.Float newViewBox = new Rectangle2D.Float(coords[0], coords[1], coords[2], coords[3]); 446 if (!newViewBox.equals(viewBox)) 447 { 448 viewBox = newViewBox; 449 shapeChange = true; 450 } 451 } 452 453 if (shapeChange) 454 { 455 build(); 456 } 457 458 return changeState || shapeChange; 459 } 460 461 /** 462 * @return the styleSheet 463 */ 464 public StyleSheet getStyleSheet() 465 { 466 if (styleSheet == null) 467 { 468 for (int i = 0; i < getNumChildren(); ++i) 469 { 470 SVGElement ele = getChild(i); 471 if (ele instanceof Style) 472 { 473 return ((Style)ele).getStyleSheet(); 474 } 475 } 476 } 477 478 return styleSheet; 479 } 480 481 /** 482 * @param styleSheet the styleSheet to set 483 */ 484 public void setStyleSheet(StyleSheet styleSheet) 485 { 486 this.styleSheet = styleSheet; 487 } 488 489}