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 fillPaint = ((FillElement)ele).getPaint(bounds, xform); 164 } 165 } 166 } 167 } 168 } 169 170 //Default opacity 171 float opacity = 1f; 172 if (getStyle(styleAttrib.setName("opacity"))) 173 { 174 opacity = styleAttrib.getRatioValue(); 175 } 176 177 float fillOpacity = opacity; 178 if (getStyle(styleAttrib.setName("fill-opacity"))) 179 { 180 fillOpacity *= styleAttrib.getRatioValue(); 181 } 182 183 184 Paint strokePaint = null; //Default is to stroke with none 185 if (getStyle(styleAttrib.setName("stroke"))) 186 { 187 if (styleAttrib.getStringValue().equals("none")) strokePaint = null; 188 else 189 { 190 strokePaint = handleCurrentColor(styleAttrib); 191 if (strokePaint == null) 192 { 193 URI uri = styleAttrib.getURIValue(getXMLBase()); 194 if (uri != null) 195 { 196 Rectangle2D bounds = shape.getBounds2D(); 197 AffineTransform xform = g.getTransform(); 198 199 SVGElement ele = diagram.getUniverse().getElement(uri); 200 if (ele != null) 201 { 202 strokePaint = ((FillElement)ele).getPaint(bounds, xform); 203 } 204 } 205 } 206 } 207 } 208 209 float[] strokeDashArray = null; 210 if (getStyle(styleAttrib.setName("stroke-dasharray"))) 211 { 212 strokeDashArray = styleAttrib.getFloatList(); 213 if (strokeDashArray.length == 0) strokeDashArray = null; 214 } 215 216 float strokeDashOffset = 0f; 217 if (getStyle(styleAttrib.setName("stroke-dashoffset"))) 218 { 219 strokeDashOffset = styleAttrib.getFloatValueWithUnits(); 220 } 221 222 int strokeLinecap = BasicStroke.CAP_BUTT; 223 if (getStyle(styleAttrib.setName("stroke-linecap"))) 224 { 225 String val = styleAttrib.getStringValue(); 226 if (val.equals("round")) 227 { 228 strokeLinecap = BasicStroke.CAP_ROUND; 229 } 230 else if (val.equals("square")) 231 { 232 strokeLinecap = BasicStroke.CAP_SQUARE; 233 } 234 } 235 236 int strokeLinejoin = BasicStroke.JOIN_MITER; 237 if (getStyle(styleAttrib.setName("stroke-linejoin"))) 238 { 239 String val = styleAttrib.getStringValue(); 240 if (val.equals("round")) 241 { 242 strokeLinejoin = BasicStroke.JOIN_ROUND; 243 } 244 else if (val.equals("bevel")) 245 { 246 strokeLinejoin = BasicStroke.JOIN_BEVEL; 247 } 248 } 249 250 float strokeMiterLimit = 4f; 251 if (getStyle(styleAttrib.setName("stroke-miterlimit"))) 252 { 253 strokeMiterLimit = Math.max(styleAttrib.getFloatValueWithUnits(), 1); 254 } 255 256 float strokeOpacity = opacity; 257 if (getStyle(styleAttrib.setName("stroke-opacity"))) 258 { 259 strokeOpacity *= styleAttrib.getRatioValue(); 260 } 261 262 float strokeWidth = 1f; 263 if (getStyle(styleAttrib.setName("stroke-width"))) 264 { 265 strokeWidth = styleAttrib.getFloatValueWithUnits(); 266 } 267// if (strokeWidthScalar != 1f) 268// { 269 strokeWidth *= strokeWidthScalar; 270// } 271 272 Marker markerStart = null; 273 if (getStyle(styleAttrib.setName("marker-start"))) 274 { 275 if (!styleAttrib.getStringValue().equals("none")) 276 { 277 URI uri = styleAttrib.getURIValue(getXMLBase()); 278 markerStart = (Marker)diagram.getUniverse().getElement(uri); 279 } 280 } 281 282 Marker markerMid = null; 283 if (getStyle(styleAttrib.setName("marker-mid"))) 284 { 285 if (!styleAttrib.getStringValue().equals("none")) 286 { 287 URI uri = styleAttrib.getURIValue(getXMLBase()); 288 markerMid = (Marker)diagram.getUniverse().getElement(uri); 289 } 290 } 291 292 Marker markerEnd = null; 293 if (getStyle(styleAttrib.setName("marker-end"))) 294 { 295 if (!styleAttrib.getStringValue().equals("none")) 296 { 297 URI uri = styleAttrib.getURIValue(getXMLBase()); 298 markerEnd = (Marker)diagram.getUniverse().getElement(uri); 299 } 300 } 301 302 303 //Draw the shape 304 if (fillPaint != null && fillOpacity != 0f) 305 { 306 if (fillOpacity <= 0) 307 { 308 //Do nothing 309 } 310 else if (fillOpacity < 1f) 311 { 312 Composite cachedComposite = g.getComposite(); 313 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, fillOpacity)); 314 315 g.setPaint(fillPaint); 316 g.fill(shape); 317 318 g.setComposite(cachedComposite); 319 } 320 else 321 { 322 g.setPaint(fillPaint); 323 g.fill(shape); 324 } 325 } 326 327 328 if (strokePaint != null && strokeOpacity != 0f) 329 { 330 BasicStroke stroke; 331 if (strokeDashArray == null) 332 { 333 stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit); 334 } 335 else 336 { 337 stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit, strokeDashArray, strokeDashOffset); 338 } 339 340 Shape strokeShape; 341 AffineTransform cacheXform = g.getTransform(); 342 if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE) 343 { 344 strokeShape = cacheXform.createTransformedShape(shape); 345 strokeShape = stroke.createStrokedShape(strokeShape); 346 } 347 else 348 { 349 strokeShape = stroke.createStrokedShape(shape); 350 } 351 352 if (strokeOpacity <= 0) 353 { 354 //Do nothing 355 } 356 else 357 { 358 Composite cachedComposite = g.getComposite(); 359 360 if (strokeOpacity < 1f) 361 { 362 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, strokeOpacity)); 363 } 364 365 if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE) 366 { 367 //Set to identity 368 g.setTransform(new AffineTransform()); 369 } 370 371 g.setPaint(strokePaint); 372 g.fill(strokeShape); 373 374 if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE) 375 { 376 //Set to identity 377 g.setTransform(cacheXform); 378 } 379 380 if (strokeOpacity < 1f) 381 { 382 g.setComposite(cachedComposite); 383 } 384 } 385 } 386 387 if (markerStart != null || markerMid != null || markerEnd != null) 388 { 389 MarkerLayout layout = new MarkerLayout(); 390 layout.layout(shape); 391 392 ArrayList list = layout.getMarkerList(); 393 for (int i = 0; i < list.size(); ++i) 394 { 395 MarkerPos pos = (MarkerPos)list.get(i); 396 397 switch (pos.type) 398 { 399 case Marker.MARKER_START: 400 if (markerStart != null) 401 { 402 markerStart.render(g, pos, strokeWidth); 403 } 404 break; 405 case Marker.MARKER_MID: 406 if (markerMid != null) 407 { 408 markerMid.render(g, pos, strokeWidth); 409 } 410 break; 411 case Marker.MARKER_END: 412 if (markerEnd != null) 413 { 414 markerEnd.render(g, pos, strokeWidth); 415 } 416 break; 417 } 418 } 419 } 420 } 421 422 abstract public Shape getShape(); 423 424 protected Rectangle2D includeStrokeInBounds(Rectangle2D rect) throws SVGException 425 { 426 StyleAttribute styleAttrib = new StyleAttribute(); 427 if (!getStyle(styleAttrib.setName("stroke"))) return rect; 428 429 double strokeWidth = 1; 430 if (getStyle(styleAttrib.setName("stroke-width"))) strokeWidth = styleAttrib.getDoubleValue(); 431 432 rect.setRect( 433 rect.getX() - strokeWidth / 2, 434 rect.getY() - strokeWidth / 2, 435 rect.getWidth() + strokeWidth, 436 rect.getHeight() + strokeWidth); 437 438 return rect; 439 } 440 441}