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