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