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 @Override 096 public String getTagName() 097 { 098 return TAG_NAME; 099 } 100 101 @Override 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 @Override 168 public SVGRoot getRoot() 169 { 170 return this; 171 } 172 173 protected void prepareViewport() 174 { 175 Rectangle deviceViewport = diagram.getDeviceViewport(); 176 177 Rectangle2D defaultBounds; 178 try 179 { 180 defaultBounds = getBoundingBox(); 181 } 182 catch (SVGException ex) 183 { 184 defaultBounds= new Rectangle2D.Float(); 185 } 186 187 //Determine destination rectangle 188 float xx, yy, ww, hh; 189 if (width != null) 190 { 191 xx = (x == null) ? 0 : StyleAttribute.convertUnitsToPixels(x.getUnits(), x.getValue()); 192 if (width.getUnits() == NumberWithUnits.UT_PERCENT) 193 { 194 ww = width.getValue() * deviceViewport.width; 195 } 196 else 197 { 198 ww = StyleAttribute.convertUnitsToPixels(width.getUnits(), width.getValue()); 199 } 200 } 201 else if (viewBox != null) 202 { 203 xx = viewBox.x; 204 ww = viewBox.width; 205 width = new NumberWithUnits(ww, NumberWithUnits.UT_PX); 206 x = new NumberWithUnits(xx, NumberWithUnits.UT_PX); 207 } 208 else 209 { 210 //Estimate size from scene bounding box 211 xx = (float)defaultBounds.getX(); 212 ww = (float)defaultBounds.getWidth(); 213 width = new NumberWithUnits(ww, NumberWithUnits.UT_PX); 214 x = new NumberWithUnits(xx, NumberWithUnits.UT_PX); 215 } 216 217 if (height != null) 218 { 219 yy = (y == null) ? 0 : StyleAttribute.convertUnitsToPixels(y.getUnits(), y.getValue()); 220 if (height.getUnits() == NumberWithUnits.UT_PERCENT) 221 { 222 hh = height.getValue() * deviceViewport.height; 223 } 224 else 225 { 226 hh = StyleAttribute.convertUnitsToPixels(height.getUnits(), height.getValue()); 227 } 228 } 229 else if (viewBox != null) 230 { 231 yy = viewBox.y; 232 hh = viewBox.height; 233 height = new NumberWithUnits(hh, NumberWithUnits.UT_PX); 234 y = new NumberWithUnits(yy, NumberWithUnits.UT_PX); 235 } 236 else 237 { 238 //Estimate size from scene bounding box 239 yy = (float)defaultBounds.getY(); 240 hh = (float)defaultBounds.getHeight(); 241 height = new NumberWithUnits(hh, NumberWithUnits.UT_PX); 242 y = new NumberWithUnits(yy, NumberWithUnits.UT_PX); 243 } 244 245 clipRect.setRect(xx, yy, ww, hh); 246 247// if (viewBox == null) 248// { 249// viewXform.setToIdentity(); 250// } 251// else 252// { 253// //If viewport window is set, we are drawing to entire viewport 254// clipRect.setRect(deviceViewport); 255// 256// viewXform.setToIdentity(); 257// viewXform.setToTranslation(deviceViewport.x, deviceViewport.y); 258// viewXform.scale(deviceViewport.width, deviceViewport.height); 259// viewXform.scale(1 / viewBox.width, 1 / viewBox.height); 260// viewXform.translate(-viewBox.x, -viewBox.y); 261// } 262 } 263 264 public void renderToViewport(Graphics2D g) throws SVGException 265 { 266 render(g); 267 } 268 269 @Override 270 public void render(Graphics2D g) throws SVGException 271 { 272 prepareViewport(); 273 274 Rectangle targetViewport = g.getClipBounds(); 275// 276// if (targetViewport == null) 277// { 278// Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); 279// targetViewport = new Rectangle(0, 0, size.width, size.height); 280// } 281// clipRect.setRect(targetViewport); 282 283 284 Rectangle deviceViewport = diagram.getDeviceViewport(); 285 if (width != null && height != null) 286 { 287 float xx, yy, ww, hh; 288 289 xx = (x == null) ? 0 : StyleAttribute.convertUnitsToPixels(x.getUnits(), x.getValue()); 290 if (width.getUnits() == NumberWithUnits.UT_PERCENT) 291 { 292 ww = width.getValue() * deviceViewport.width; 293 } 294 else 295 { 296 ww = StyleAttribute.convertUnitsToPixels(width.getUnits(), width.getValue()); 297 } 298 299 yy = (y == null) ? 0 : StyleAttribute.convertUnitsToPixels(y.getUnits(), y.getValue()); 300 if (height.getUnits() == NumberWithUnits.UT_PERCENT) 301 { 302 hh = height.getValue() * deviceViewport.height; 303 } 304 else 305 { 306 hh = StyleAttribute.convertUnitsToPixels(height.getUnits(), height.getValue()); 307 } 308 309 targetViewport = new Rectangle((int)xx, (int)yy, (int)ww, (int)hh); 310 } 311 else 312 { 313// Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); 314// targetViewport = new Rectangle(0, 0, size.width, size.height); 315 targetViewport = new Rectangle(deviceViewport); 316 } 317 clipRect.setRect(targetViewport); 318 319 if (viewBox == null) 320 { 321 viewXform.setToIdentity(); 322 } 323 else 324 { 325 viewXform.setToIdentity(); 326 viewXform.setToTranslation(targetViewport.x, targetViewport.y); 327 viewXform.scale(targetViewport.width, targetViewport.height); 328 viewXform.scale(1 / viewBox.width, 1 / viewBox.height); 329 viewXform.translate(-viewBox.x, -viewBox.y); 330 } 331 332 AffineTransform cachedXform = g.getTransform(); 333 g.transform(viewXform); 334 335 super.render(g); 336 337 g.setTransform(cachedXform); 338 } 339 340 @Override 341 public void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List<List<SVGElement>> retVec) throws SVGException 342 { 343 if (viewXform != null) 344 { 345 ltw = new AffineTransform(ltw); 346 ltw.concatenate(viewXform); 347 } 348 349 super.pick(pickArea, ltw, boundingBox, retVec); 350 } 351 352 @Override 353 public void pick(Point2D point, boolean boundingBox, List<List<SVGElement>> retVec) throws SVGException 354 { 355 Point2D xPoint = new Point2D.Double(point.getX(), point.getY()); 356 if (viewXform != null) 357 { 358 try 359 { 360 viewXform.inverseTransform(point, xPoint); 361 } catch (NoninvertibleTransformException ex) 362 { 363 throw new SVGException(ex); 364 } 365 } 366 367 super.pick(xPoint, boundingBox, retVec); 368 } 369 370 @Override 371 public Shape getShape() 372 { 373 Shape shape = super.getShape(); 374 return viewXform.createTransformedShape(shape); 375 } 376 377 @Override 378 public Rectangle2D getBoundingBox() throws SVGException 379 { 380 Rectangle2D bbox = super.getBoundingBox(); 381 return viewXform.createTransformedShape(bbox).getBounds2D(); 382 } 383 384 public float getDeviceWidth() 385 { 386 return clipRect.width; 387 } 388 389 public float getDeviceHeight() 390 { 391 return clipRect.height; 392 } 393 394 public Rectangle2D getDeviceRect(Rectangle2D rect) 395 { 396 rect.setRect(clipRect); 397 return rect; 398 } 399 400 /** 401 * Updates all attributes in this diagram associated with a time event. 402 * Ie, all attributes with track information. 403 * @return - true if this node has changed state as a result of the time 404 * update 405 */ 406 @Override 407 public boolean updateTime(double curTime) throws SVGException 408 { 409 boolean changeState = super.updateTime(curTime); 410 411 StyleAttribute sty = new StyleAttribute(); 412 boolean shapeChange = false; 413 414 if (getPres(sty.setName("x"))) 415 { 416 NumberWithUnits newVal = sty.getNumberWithUnits(); 417 if (!newVal.equals(x)) 418 { 419 x = newVal; 420 shapeChange = true; 421 } 422 } 423 424 if (getPres(sty.setName("y"))) 425 { 426 NumberWithUnits newVal = sty.getNumberWithUnits(); 427 if (!newVal.equals(y)) 428 { 429 y = newVal; 430 shapeChange = true; 431 } 432 } 433 434 if (getPres(sty.setName("width"))) 435 { 436 NumberWithUnits newVal = sty.getNumberWithUnits(); 437 if (!newVal.equals(width)) 438 { 439 width = newVal; 440 shapeChange = true; 441 } 442 } 443 444 if (getPres(sty.setName("height"))) 445 { 446 NumberWithUnits newVal = sty.getNumberWithUnits(); 447 if (!newVal.equals(height)) 448 { 449 height = newVal; 450 shapeChange = true; 451 } 452 } 453 454 if (getPres(sty.setName("viewBox"))) 455 { 456 float[] coords = sty.getFloatList(); 457 Rectangle2D.Float newViewBox = new Rectangle2D.Float(coords[0], coords[1], coords[2], coords[3]); 458 if (!newViewBox.equals(viewBox)) 459 { 460 viewBox = newViewBox; 461 shapeChange = true; 462 } 463 } 464 465 if (shapeChange) 466 { 467 build(); 468 } 469 470 return changeState || shapeChange; 471 } 472 473 /** 474 * @return the styleSheet 475 */ 476 public StyleSheet getStyleSheet() 477 { 478 if (styleSheet == null) 479 { 480 for (int i = 0; i < getNumChildren(); ++i) 481 { 482 SVGElement ele = getChild(i); 483 if (ele instanceof Style) 484 { 485 return ((Style)ele).getStyleSheet(); 486 } 487 } 488 } 489 490 return styleSheet; 491 } 492 493 /** 494 * @param styleSheet the styleSheet to set 495 */ 496 public void setStyleSheet(StyleSheet styleSheet) 497 { 498 this.styleSheet = styleSheet; 499 } 500 501}