/*
* SVG Salamander
* Copyright (c) 2004, Mark McKay
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Mark McKay can be contacted at mark@kitfox.com. Salamander and other
* projects can be found at http://www.kitfox.com
*
* Created on January 26, 2004, 5:21 PM
*/
package com.kitfox.svg;
import com.kitfox.svg.Marker.MarkerLayout;
import com.kitfox.svg.Marker.MarkerPos;
import com.kitfox.svg.xml.StyleAttribute;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
/**
* Parent of shape objects
*
* @author Mark McKay
* @author Mark McKay
*/
abstract public class ShapeElement extends RenderableElement
{
/**
* This is necessary to get text elements to render the stroke the correct
* width. It is an alternative to producing new font glyph sets at different
* sizes.
*/
protected float strokeWidthScalar = 1f;
/** Creates a new instance of ShapeElement */
public ShapeElement() {
}
@Override
abstract public void render(java.awt.Graphics2D g) throws SVGException;
/*
protected void setStrokeWidthScalar(float strokeWidthScalar)
{
this.strokeWidthScalar = strokeWidthScalar;
}
*/
@Override
void pick(Point2D point, boolean boundingBox, List> retVec) throws SVGException
{
// StyleAttribute styleAttrib = new StyleAttribute();
// if (getStyle(styleAttrib.setName("fill")) && getShape().contains(point))
if ((boundingBox ? getBoundingBox() : getShape()).contains(point))
{
retVec.add(getPath(null));
}
}
@Override
void pick(Rectangle2D pickArea, AffineTransform ltw, boolean boundingBox, List> retVec) throws SVGException
{
// StyleAttribute styleAttrib = new StyleAttribute();
// if (getStyle(styleAttrib.setName("fill")) && getShape().contains(point))
if (ltw.createTransformedShape((boundingBox ? getBoundingBox() : getShape())).intersects(pickArea))
{
retVec.add(getPath(null));
}
}
private Paint handleCurrentColor(StyleAttribute styleAttrib) throws SVGException
{
if (styleAttrib.getStringValue().equals("currentColor"))
{
StyleAttribute currentColorAttrib = new StyleAttribute();
if (getStyle(currentColorAttrib.setName("color")))
{
if (!currentColorAttrib.getStringValue().equals("none"))
{
return currentColorAttrib.getColorValue();
}
}
return null;
}
else
{
return styleAttrib.getColorValue();
}
}
protected void renderShape(Graphics2D g, Shape shape) throws SVGException
{
//g.setColor(Color.green);
StyleAttribute styleAttrib = new StyleAttribute();
//Don't process if not visible
if (getStyle(styleAttrib.setName("visibility")))
{
if (!styleAttrib.getStringValue().equals("visible")) return;
}
if (getStyle(styleAttrib.setName("display")))
{
if (styleAttrib.getStringValue().equals("none")) return;
}
//None, solid color, gradient, pattern
Paint fillPaint = Color.black; //Default to black. Must be explicitly set to none for no fill.
if (getStyle(styleAttrib.setName("fill")))
{
if (styleAttrib.getStringValue().equals("none")) fillPaint = null;
else
{
fillPaint = handleCurrentColor(styleAttrib);
if (fillPaint == null)
{
URI uri = styleAttrib.getURIValue(getXMLBase());
if (uri != null)
{
Rectangle2D bounds = shape.getBounds2D();
AffineTransform xform = g.getTransform();
SVGElement ele = diagram.getUniverse().getElement(uri);
if (ele != null)
{
try {
fillPaint = ((FillElement)ele).getPaint(bounds, xform);
} catch (IllegalArgumentException e) {
throw new SVGException(e);
}
}
}
}
}
}
//Default opacity
float opacity = 1f;
if (getStyle(styleAttrib.setName("opacity")))
{
opacity = styleAttrib.getRatioValue();
}
float fillOpacity = opacity;
if (getStyle(styleAttrib.setName("fill-opacity")))
{
fillOpacity *= styleAttrib.getRatioValue();
}
Paint strokePaint = null; //Default is to stroke with none
if (getStyle(styleAttrib.setName("stroke")))
{
if (styleAttrib.getStringValue().equals("none")) strokePaint = null;
else
{
strokePaint = handleCurrentColor(styleAttrib);
if (strokePaint == null)
{
URI uri = styleAttrib.getURIValue(getXMLBase());
if (uri != null)
{
Rectangle2D bounds = shape.getBounds2D();
AffineTransform xform = g.getTransform();
SVGElement ele = diagram.getUniverse().getElement(uri);
if (ele != null)
{
strokePaint = ((FillElement)ele).getPaint(bounds, xform);
}
}
}
}
}
float[] strokeDashArray = null;
if (getStyle(styleAttrib.setName("stroke-dasharray")))
{
strokeDashArray = styleAttrib.getFloatList();
if (strokeDashArray.length == 0) strokeDashArray = null;
}
float strokeDashOffset = 0f;
if (getStyle(styleAttrib.setName("stroke-dashoffset")))
{
strokeDashOffset = styleAttrib.getFloatValueWithUnits();
}
int strokeLinecap = BasicStroke.CAP_BUTT;
if (getStyle(styleAttrib.setName("stroke-linecap")))
{
String val = styleAttrib.getStringValue();
if (val.equals("round"))
{
strokeLinecap = BasicStroke.CAP_ROUND;
}
else if (val.equals("square"))
{
strokeLinecap = BasicStroke.CAP_SQUARE;
}
}
int strokeLinejoin = BasicStroke.JOIN_MITER;
if (getStyle(styleAttrib.setName("stroke-linejoin")))
{
String val = styleAttrib.getStringValue();
if (val.equals("round"))
{
strokeLinejoin = BasicStroke.JOIN_ROUND;
}
else if (val.equals("bevel"))
{
strokeLinejoin = BasicStroke.JOIN_BEVEL;
}
}
float strokeMiterLimit = 4f;
if (getStyle(styleAttrib.setName("stroke-miterlimit")))
{
strokeMiterLimit = Math.max(styleAttrib.getFloatValueWithUnits(), 1);
}
float strokeOpacity = opacity;
if (getStyle(styleAttrib.setName("stroke-opacity")))
{
strokeOpacity *= styleAttrib.getRatioValue();
}
float strokeWidth = 1f;
if (getStyle(styleAttrib.setName("stroke-width")))
{
strokeWidth = styleAttrib.getFloatValueWithUnits();
}
// if (strokeWidthScalar != 1f)
// {
strokeWidth *= strokeWidthScalar;
// }
Marker markerStart = null;
if (getStyle(styleAttrib.setName("marker-start")))
{
if (!styleAttrib.getStringValue().equals("none"))
{
URI uri = styleAttrib.getURIValue(getXMLBase());
markerStart = (Marker)diagram.getUniverse().getElement(uri);
}
}
Marker markerMid = null;
if (getStyle(styleAttrib.setName("marker-mid")))
{
if (!styleAttrib.getStringValue().equals("none"))
{
URI uri = styleAttrib.getURIValue(getXMLBase());
markerMid = (Marker)diagram.getUniverse().getElement(uri);
}
}
Marker markerEnd = null;
if (getStyle(styleAttrib.setName("marker-end")))
{
if (!styleAttrib.getStringValue().equals("none"))
{
URI uri = styleAttrib.getURIValue(getXMLBase());
markerEnd = (Marker)diagram.getUniverse().getElement(uri);
}
}
//Draw the shape
if (fillPaint != null && fillOpacity != 0f)
{
if (fillOpacity <= 0)
{
//Do nothing
}
else if (fillOpacity < 1f)
{
Composite cachedComposite = g.getComposite();
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, fillOpacity));
g.setPaint(fillPaint);
g.fill(shape);
g.setComposite(cachedComposite);
}
else
{
g.setPaint(fillPaint);
g.fill(shape);
}
}
if (strokePaint != null && strokeOpacity != 0f)
{
BasicStroke stroke;
if (strokeDashArray == null)
{
stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit);
}
else
{
stroke = new BasicStroke(strokeWidth, strokeLinecap, strokeLinejoin, strokeMiterLimit, strokeDashArray, strokeDashOffset);
}
Shape strokeShape;
AffineTransform cacheXform = g.getTransform();
if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE)
{
strokeShape = cacheXform.createTransformedShape(shape);
strokeShape = stroke.createStrokedShape(strokeShape);
}
else
{
strokeShape = stroke.createStrokedShape(shape);
}
if (strokeOpacity <= 0)
{
//Do nothing
}
else
{
Composite cachedComposite = g.getComposite();
if (strokeOpacity < 1f)
{
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, strokeOpacity));
}
if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE)
{
//Set to identity
g.setTransform(new AffineTransform());
}
g.setPaint(strokePaint);
g.fill(strokeShape);
if (vectorEffect == VECTOR_EFFECT_NON_SCALING_STROKE)
{
//Set to identity
g.setTransform(cacheXform);
}
if (strokeOpacity < 1f)
{
g.setComposite(cachedComposite);
}
}
}
if (markerStart != null || markerMid != null || markerEnd != null)
{
MarkerLayout layout = new MarkerLayout();
layout.layout(shape);
ArrayList list = layout.getMarkerList();
for (int i = 0; i < list.size(); ++i)
{
MarkerPos pos = list.get(i);
switch (pos.type)
{
case Marker.MARKER_START:
if (markerStart != null)
{
markerStart.render(g, pos, strokeWidth);
}
break;
case Marker.MARKER_MID:
if (markerMid != null)
{
markerMid.render(g, pos, strokeWidth);
}
break;
case Marker.MARKER_END:
if (markerEnd != null)
{
markerEnd.render(g, pos, strokeWidth);
}
break;
}
}
}
}
abstract public Shape getShape();
protected Rectangle2D includeStrokeInBounds(Rectangle2D rect) throws SVGException
{
StyleAttribute styleAttrib = new StyleAttribute();
if (!getStyle(styleAttrib.setName("stroke"))) return rect;
double strokeWidth = 1;
if (getStyle(styleAttrib.setName("stroke-width"))) strokeWidth = styleAttrib.getDoubleValue();
rect.setRect(
rect.getX() - strokeWidth / 2,
rect.getY() - strokeWidth / 2,
rect.getWidth() + strokeWidth,
rect.getHeight() + strokeWidth);
return rect;
}
}