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 January 26, 2004, 5:21 PM 035 */ 036 037package com.kitfox.svg; 038 039import com.kitfox.svg.Marker.MarkerLayout; 040import com.kitfox.svg.Marker.MarkerPos; 041import com.kitfox.svg.xml.StyleAttribute; 042import java.awt.AlphaComposite; 043import java.awt.BasicStroke; 044import java.awt.Color; 045import java.awt.Composite; 046import java.awt.Graphics2D; 047import java.awt.Paint; 048import java.awt.Shape; 049import java.awt.geom.AffineTransform; 050import java.awt.geom.Point2D; 051import java.awt.geom.Rectangle2D; 052import java.net.URI; 053import java.util.ArrayList; 054import java.util.List; 055 056 057 058/** 059 * Parent of shape objects 060 * 061 * @author Mark McKay 062 * @author <a href="mailto:mark@kitfox.com">Mark McKay</a> 063 */ 064abstract public class ShapeElement extends RenderableElement 065{ 066 067 /** 068 * This is necessary to get text elements to render the stroke the correct 069 * width. It is an alternative to producing new font glyph sets at different 070 * sizes. 071 */ 072 protected float strokeWidthScalar = 1f; 073 074 /** Creates a new instance of ShapeElement */ 075 public ShapeElement() { 076 } 077 078 abstract public void render(java.awt.Graphics2D g) throws SVGException; 079 080 /* 081 protected void setStrokeWidthScalar(float strokeWidthScalar) 082 { 083 this.strokeWidthScalar = strokeWidthScalar; 084 } 085 */ 086 087 void pick(Point2D point, boolean boundingBox, List retVec) throws SVGException 088 { 089// StyleAttribute styleAttrib = new StyleAttribute(); 090// if (getStyle(styleAttrib.setName("fill")) && getShape().contains(point)) 091 if ((boundingBox ? getBoundingBox() : getShape()).contains(point)) 092 { 093 retVec.add(getPath(null)); 094 } 095 } 096 097 void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List retVec) throws SVGException 098 { 099 StyleAttribute styleAttrib = new StyleAttribute(); 100// if (getStyle(styleAttrib.setName("fill")) && getShape().contains(point)) 101 if (ltw.createTransformedShape((boundingBox ? getBoundingBox() : getShape())).intersects(pickArea)) 102 { 103 retVec.add(getPath(null)); 104 } 105 } 106 107 private Paint handleCurrentColor(StyleAttribute styleAttrib) throws SVGException 108 { 109 if (styleAttrib.getStringValue().equals("currentColor")) 110 { 111 StyleAttribute currentColorAttrib = new StyleAttribute(); 112 if (getStyle(currentColorAttrib.setName("color"))) 113 { 114 if (!currentColorAttrib.getStringValue().equals("none")) 115 { 116 return currentColorAttrib.getColorValue(); 117 } 118 } 119 return null; 120 } 121 else 122 { 123 return styleAttrib.getColorValue(); 124 } 125 } 126 127 protected void renderShape(Graphics2D g, Shape shape) throws SVGException 128 { 129//g.setColor(Color.green); 130 131 StyleAttribute styleAttrib = new StyleAttribute(); 132 133 //Don't process if not visible 134 if (getStyle(styleAttrib.setName("visibility"))) 135 { 136 if (!styleAttrib.getStringValue().equals("visible")) return; 137 } 138 139 if (getStyle(styleAttrib.setName("display"))) 140 { 141 if (styleAttrib.getStringValue().equals("none")) return; 142 } 143 144 //None, solid color, gradient, pattern 145 Paint fillPaint = Color.black; //Default to black. Must be explicitly set to none for no fill. 146 if (getStyle(styleAttrib.setName("fill"))) 147 { 148 if (styleAttrib.getStringValue().equals("none")) fillPaint = null; 149 else 150 { 151 fillPaint = handleCurrentColor(styleAttrib); 152 if (fillPaint == null) 153 { 154 URI uri = styleAttrib.getURIValue(getXMLBase()); 155 if (uri != null) 156 { 157 Rectangle2D bounds = shape.getBounds2D(); 158 AffineTransform xform = g.getTransform(); 159 160 SVGElement ele = diagram.getUniverse().getElement(uri); 161 if (ele != null) 162 { 163 try { 164 fillPaint = ((FillElement)ele).getPaint(bounds, xform); 165 } catch (IllegalArgumentException e) { 166 throw new SVGException(e); 167 } 168 } 169 } 170 } 171 } 172 } 173 174 //Default opacity 175 float opacity = 1f; 176 if (getStyle(styleAttrib.setName("opacity"))) 177 { 178 opacity = styleAttrib.getRatioValue(); 179 } 180 181 float fillOpacity = opacity; 182 if (getStyle(styleAttrib.setName("fill-opacity"))) 183 { 184 fillOpacity *= styleAttrib.getRatioValue(); 185 } 186 187 188 Paint strokePaint = null; //Default is to stroke with none 189 if (getStyle(styleAttrib.setName("stroke"))) 190 { 191 if (styleAttrib.getStringValue().equals("none")) strokePaint = null; 192 else 193 { 194 strokePaint = handleCurrentColor(styleAttrib); 195 if (strokePaint == null) 196 { 197 URI uri = styleAttrib.getURIValue(getXMLBase()); 198 if (uri != null) 199 { 200 Rectangle2D bounds = shape.getBounds2D(); 201 AffineTransform xform = g.getTransform(); 202 203 SVGElement ele = diagram.getUniverse().getElement(uri); 204 if (ele != null) 205 { 206 strokePaint = ((FillElement)ele).getPaint(bounds, xform); 207 } 208 } 209 } 210 } 211 } 212 213 float[] strokeDashArray = null; 214 if (getStyle(styleAttrib.setName("stroke-dasharray"))) 215 { 216 strokeDashArray = styleAttrib.getFloatList(); 217 if (strokeDashArray.length == 0) strokeDashArray = null; 218 } 219 220 float strokeDashOffset = 0f; 221 if (getStyle(styleAttrib.setName("stroke-dashoffset"))) 222 { 223 strokeDashOffset = styleAttrib.getFloatValueWithUnits(); 224 } 225 226 int strokeLinecap = BasicStroke.CAP_BUTT; 227 if (getStyle(styleAttrib.setName("stroke-linecap"))) 228 { 229 String val = styleAttrib.getStringValue(); 230 if (val.equals("round")) 231 { 232 strokeLinecap = BasicStroke.CAP_ROUND; 233 } 234 else if (val.equals("square")) 235 { 236 strokeLinecap = BasicStroke.CAP_SQUARE; 237 } 238 } 239 240 int strokeLinejoin = BasicStroke.JOIN_MITER; 241 if (getStyle(styleAttrib.setName("stroke-linejoin"))) 242 { 243 String val = styleAttrib.getStringValue(); 244 if (val.equals("round")) 245 { 246 strokeLinejoin = BasicStroke.JOIN_ROUND; 247 } 248 else if (val.equals("bevel")) 249 { 250 strokeLinejoin = BasicStroke.JOIN_BEVEL; 251 } 252 } 253 254 float strokeMiterLimit = 4f; 255 if (getStyle(styleAttrib.setName("stroke-miterlimit"))) 256 { 257 strokeMiterLimit = Math.max(styleAttrib.getFloatValueWithUnits(), 1); 258 } 259 260 float strokeOpacity = opacity; 261 if (getStyle(styleAttrib.setName("stroke-opacity"))) 262 { 263 strokeOpacity *= styleAttrib.getRatioValue(); 264 } 265 266 float strokeWidth = 1f; 267 if (getStyle(styleAttrib.setName("stroke-width"))) 268 { 269 strokeWidth = styleAttrib.getFloatValueWithUnits(); 270 } 271// if (strokeWidthScalar != 1f) 272// { 273 strokeWidth *= strokeWidthScalar; 274// } 275 276 Marker markerStart = null; 277 if (getStyle(styleAttrib.setName("marker-start"))) 278 { 279 if (!styleAttrib.getStringValue().equals("none")) 280 { 281 URI uri = styleAttrib.getURIValue(getXMLBase()); 282 markerStart = (Marker)diagram.getUniverse().getElement(uri); 283 } 284 } 285 286 Marker markerMid = null; 287 if (getStyle(styleAttrib.setName("marker-mid"))) 288 { 289 if (!styleAttrib.getStringValue().equals("none")) 290 { 291 URI uri = styleAttrib.getURIValue(getXMLBase()); 292 markerMid = (Marker)diagram.getUniverse().getElement(uri); 293 } 294 } 295 296 Marker markerEnd = null; 297 if (getStyle(styleAttrib.setName("marker-end"))) 298 { 299 if (!styleAttrib.getStringValue().equals("none")) 300 { 301 URI uri = styleAttrib.getURIValue(getXMLBase()); 302 markerEnd = (Marker)diagram.getUniverse().getElement(uri); 303 } 304 } 305 306 307 //Draw the shape 308 if (fillPaint != null && fillOpacity != 0f) 309 { 310 if (fillOpacity <= 0) 311 { 312 //Do nothing 313 } 314 else if (fillOpacity < 1f) 315 { 316 Composite cachedComposite = g.getComposite(); 317 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, fillOpacity)); 318 319 g.setPaint(fillPaint); 320 g.fill(shape); 321 322 g.setComposite(cachedComposite); 323 } 324 else 325 { 326 g.setPaint(fillPaint); 327 g.fill(shape); 328 } 329 } 330 331 332 if (strokePaint != null && strokeOpacity != 0f) 333 { 334 BasicStroke stroke; 335 if (strokeDashArray == null) 336 { 337 stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit); 338 } 339 else 340 { 341 stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit, strokeDashArray, strokeDashOffset); 342 } 343 344 Shape strokeShape; 345 AffineTransform cacheXform = g.getTransform(); 346 if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE) 347 { 348 strokeShape = cacheXform.createTransformedShape(shape); 349 strokeShape = stroke.createStrokedShape(strokeShape); 350 } 351 else 352 { 353 strokeShape = stroke.createStrokedShape(shape); 354 } 355 356 if (strokeOpacity <= 0) 357 { 358 //Do nothing 359 } 360 else 361 { 362 Composite cachedComposite = g.getComposite(); 363 364 if (strokeOpacity < 1f) 365 { 366 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, strokeOpacity)); 367 } 368 369 if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE) 370 { 371 //Set to identity 372 g.setTransform(new AffineTransform()); 373 } 374 375 g.setPaint(strokePaint); 376 g.fill(strokeShape); 377 378 if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE) 379 { 380 //Set to identity 381 g.setTransform(cacheXform); 382 } 383 384 if (strokeOpacity < 1f) 385 { 386 g.setComposite(cachedComposite); 387 } 388 } 389 } 390 391 if (markerStart != null || markerMid != null || markerEnd != null) 392 { 393 MarkerLayout layout = new MarkerLayout(); 394 layout.layout(shape); 395 396 ArrayList list = layout.getMarkerList(); 397 for (int i = 0; i < list.size(); ++i) 398 { 399 MarkerPos pos = (MarkerPos)list.get(i); 400 401 switch (pos.type) 402 { 403 case Marker.MARKER_START: 404 if (markerStart != null) 405 { 406 markerStart.render(g, pos, strokeWidth); 407 } 408 break; 409 case Marker.MARKER_MID: 410 if (markerMid != null) 411 { 412 markerMid.render(g, pos, strokeWidth); 413 } 414 break; 415 case Marker.MARKER_END: 416 if (markerEnd != null) 417 { 418 markerEnd.render(g, pos, strokeWidth); 419 } 420 break; 421 } 422 } 423 } 424 } 425 426 abstract public Shape getShape(); 427 428 protected Rectangle2D includeStrokeInBounds(Rectangle2D rect) throws SVGException 429 { 430 StyleAttribute styleAttrib = new StyleAttribute(); 431 if (!getStyle(styleAttrib.setName("stroke"))) return rect; 432 433 double strokeWidth = 1; 434 if (getStyle(styleAttrib.setName("stroke-width"))) strokeWidth = styleAttrib.getDoubleValue(); 435 436 rect.setRect( 437 rect.getX() - strokeWidth / 2, 438 rect.getY() - strokeWidth / 2, 439 rect.getWidth() + strokeWidth, 440 rect.getHeight() + strokeWidth); 441 442 return rect; 443 } 444 445}