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