/*****************************************************************************
* Copyright (C) The Apache Software Foundation. All rights reserved. *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in *
* the LICENSE file. *
*****************************************************************************/
package org.apache.batik.bridge;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import org.apache.batik.css.engine.CSSEngine;
import org.apache.batik.dom.svg.SVGOMDocument;
import org.apache.batik.dom.util.XLinkSupport;
import org.apache.batik.dom.util.XMLSupport;
import org.apache.batik.extension.svg.BatikExtConstants;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.parser.AWTTransformProducer;
import org.apache.batik.parser.ParseException;
import org.apache.batik.util.ParsedURL;
import org.apache.batik.util.SVGConstants;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGElement;
import org.w3c.dom.svg.SVGLangSpace;
import org.w3c.dom.svg.SVGNumberList;
/**
* A collection of utility methods for SVG.
*
* @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a>
* @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
* @version $Id: SVGUtilities.java,v 1.25 2003/06/17 01:37:57 deweese Exp $
*/
public abstract class SVGUtilities implements SVGConstants, ErrorConstants {
/**
* No instance of this class is required.
*/
protected SVGUtilities() {}
////////////////////////////////////////////////////////////////////////
// common methods
////////////////////////////////////////////////////////////////////////
/**
* Returns the node imported by the given node, or null.
*/
public static Node getImportedChild(Node n) {
return CSSEngine.getImportedChild(n);
}
/**
* Returns the logical parent element of the given element.
* The parent element of a used element is the <use> element
* which reference it.
*/
public static Element getParentElement(Element elt) {
return CSSEngine.getParentElement(elt);
}
/**
* Converts an SVGNumberList into a float array.
* @param l the list to convert
*/
public static float[] convertSVGNumberList(SVGNumberList l) {
int n = l.getNumberOfItems();
if (n == 0) {
return null;
}
float fl[] = new float[n];
for (int i=0; i < n; i++) {
fl[i] = l.getItem(i).getValue();
}
return fl;
}
/**
* Converts a string into a float.
* @param s the float representation to convert
*/
public static float convertSVGNumber(String s) {
return Float.parseFloat(s);
}
/**
* Converts a string into an integer.
* @param s the integer representation to convert
*/
public static int convertSVGInteger(String s) {
return Integer.parseInt(s);
}
/**
* Converts the specified ratio to float number.
* @param v the ratio value to convert
* @exception NumberFormatException if the ratio is not a valid
* number or percentage
*/
public static float convertRatio(String v) {
float d = 1;
if (v.endsWith("%")) {
v = v.substring(0, v.length() - 1);
d = 100;
}
float r = Float.parseFloat(v)/d;
if (r < 0) {
r = 0;
} else if (r > 1) {
r = 1;
}
return r;
}
/**
* Returns the content of the 'desc' child of the given element.
*/
public static String getDescription(SVGElement elt) {
String result = "";
boolean preserve = false;
Node n = elt.getFirstChild();
if (n != null && n.getNodeType() == Node.ELEMENT_NODE) {
String name =
(n.getPrefix() == null) ? n.getNodeName() : n.getLocalName();
if (name.equals(SVG_DESC_TAG)) {
preserve = ((SVGLangSpace)n).getXMLspace().equals
(SVG_PRESERVE_VALUE);
for (n = n.getFirstChild();
n != null;
n = n.getNextSibling()) {
if (n.getNodeType() == Node.TEXT_NODE) {
result += n.getNodeValue();
}
}
}
}
return (preserve)
? XMLSupport.preserveXMLSpace(result)
: XMLSupport.defaultXMLSpace(result);
}
/**
* Tests whether or not the given element match a specified user agent.
*
* @param elt the element to check
* @param ua the user agent
*/
public static boolean matchUserAgent(Element elt, UserAgent ua) {
test: if (elt.hasAttributeNS(null, SVG_SYSTEM_LANGUAGE_ATTRIBUTE)) {
// Tests the system languages.
String sl = elt.getAttributeNS(null,
SVG_SYSTEM_LANGUAGE_ATTRIBUTE);
StringTokenizer st = new StringTokenizer(sl, ", ");
while (st.hasMoreTokens()) {
String s = st.nextToken();
if (matchUserLanguage(s, ua.getLanguages())) {
break test;
}
}
return false;
}
if (elt.hasAttributeNS(null, SVG_REQUIRED_FEATURES_ATTRIBUTE)) {
// Tests the system features.
String sf = elt.getAttributeNS(null,
SVG_REQUIRED_FEATURES_ATTRIBUTE);
StringTokenizer st = new StringTokenizer(sf, " ");
while (st.hasMoreTokens()) {
String s = st.nextToken();
if (!ua.hasFeature(s)) {
return false;
}
}
}
if (elt.hasAttributeNS(null, SVG_REQUIRED_EXTENSIONS_ATTRIBUTE)) {
// Tests the system features.
String sf = elt.getAttributeNS(null,
SVG_REQUIRED_EXTENSIONS_ATTRIBUTE);
StringTokenizer st = new StringTokenizer(sf, " ");
while (st.hasMoreTokens()) {
String s = st.nextToken();
if (!ua.supportExtension(s)) {
return false;
}
}
}
return true;
}
/**
* Tests whether or not the specified language specification matches
* the user preferences.
*
* @param s the langage to check
* @param userLanguages the user langages
*/
protected static boolean matchUserLanguage(String s,
String userLanguages) {
StringTokenizer st = new StringTokenizer(userLanguages, ", ");
while (st.hasMoreTokens()) {
String t = st.nextToken();
if (s.startsWith(t)) {
if (s.length() > t.length()) {
return (s.charAt(t.length()) == '-');
}
return true;
}
}
return false;
}
/**
* Returns the value of the specified attribute specified on the
* specified element or one of its ancestor. Ancestors are found
* using the xlink:href attribute.
*
* @param element the element to start with
* @param namespaceURI the namespace URI of the attribute to return
* @param attrName the name of the attribute to search
* @param ctx the bridge context
* @return the value of the attribute or an empty string if not defined
*/
public static String getChainableAttributeNS(Element element,
String namespaceURI,
String attrName,
BridgeContext ctx) {
DocumentLoader loader = ctx.getDocumentLoader();
Element e = element;
List refs = new LinkedList();
for (;;) {
String v = e.getAttributeNS(namespaceURI, attrName);
if (v.length() > 0) { // exit if attribute defined
return v;
}
String uriStr = XLinkSupport.getXLinkHref(e);
if (uriStr.length() == 0) { // exit if no more xlink:href
return "";
}
SVGDocument svgDoc = (SVGDocument)e.getOwnerDocument();
String baseURI = ((SVGOMDocument)svgDoc).getURL();
ParsedURL purl = new ParsedURL(baseURI, uriStr);
if (!purl.complete())
throw new BridgeException(e, ERR_URI_MALFORMED,
new Object[] {uriStr});
Iterator iter = refs.iterator();
while (iter.hasNext()) {
if (purl.equals(iter.next()))
throw new BridgeException
(e, ERR_XLINK_HREF_CIRCULAR_DEPENDENCIES,
new Object[] {uriStr});
}
try {
URIResolver resolver = new URIResolver(svgDoc, loader);
e = resolver.getElement(purl.toString(), e);
refs.add(purl);
} catch(IOException ex) {
throw new BridgeException(e, ERR_URI_IO,
new Object[] {uriStr});
} catch(SecurityException ex) {
throw new BridgeException(e, ERR_URI_UNSECURE,
new Object[] {uriStr});
}
}
}
/////////////////////////////////////////////////////////////////////////
// <linearGradient> and <radialGradient>
/////////////////////////////////////////////////////////////////////////
/**
* Returns a Point2D in user units according to the specified parameters.
*
* @param xStr the x coordinate
* @param xAttr the name of the attribute that represents the x coordinate
* @param yStr the y coordinate
* @param yAttr the name of the attribute that represents the y coordinate
* @param unitsType the coordinate system (OBJECT_BOUNDING_BOX |
* USER_SPACE_ON_USE)
* @param uctx the unit processor context
*/
public static Point2D convertPoint(String xStr,
String xAttr,
String yStr,
String yAttr,
short unitsType,
UnitProcessor.Context uctx) {
float x, y;
switch (unitsType) {
case OBJECT_BOUNDING_BOX:
x = UnitProcessor.svgHorizontalCoordinateToObjectBoundingBox
(xStr, xAttr, uctx);
y = UnitProcessor.svgVerticalCoordinateToObjectBoundingBox
(yStr, yAttr, uctx);
break;
case USER_SPACE_ON_USE:
x = UnitProcessor.svgHorizontalCoordinateToUserSpace
(xStr, xAttr, uctx);
y = UnitProcessor.svgVerticalCoordinateToUserSpace
(yStr, yAttr, uctx);
break;
default:
throw new Error(); // can't be reached
}
return new Point2D.Float(x, y);
}
/**
* Returns a float in user units according to the specified parameters.
*
* @param length the length
* @param attr the name of the attribute that represents the length
* @param unitsType the coordinate system (OBJECT_BOUNDING_BOX |
* USER_SPACE_ON_USE)
* @param uctx the unit processor context
*/
public static float convertLength(String length,
String attr,
short unitsType,
UnitProcessor.Context uctx) {
switch (unitsType) {
case OBJECT_BOUNDING_BOX:
return UnitProcessor.svgOtherLengthToObjectBoundingBox
(length, attr, uctx);
case USER_SPACE_ON_USE:
return UnitProcessor.svgOtherLengthToUserSpace(length, attr, uctx);
default:
throw new Error(); // can't be reached
}
}
/////////////////////////////////////////////////////////////////////////
// <mask> region
/////////////////////////////////////////////////////////////////////////
/**
* Returns the mask region according to the x, y, width, height,
* and maskUnits attributes.
*
* @param maskElement the mask element that defines the various attributes
* @param maskedElement the element referencing the mask
* @param maskedNode the graphics node to mask (objectBoundingBox)
* @param ctx the bridge context
*/
public static Rectangle2D convertMaskRegion(Element maskElement,
Element maskedElement,
GraphicsNode maskedNode,
BridgeContext ctx) {
// 'x' attribute - default is -10%
String xStr = maskElement.getAttributeNS(null, SVG_X_ATTRIBUTE);
if (xStr.length() == 0) {
xStr = SVG_MASK_X_DEFAULT_VALUE;
}
// 'y' attribute - default is -10%
String yStr = maskElement.getAttributeNS(null, SVG_Y_ATTRIBUTE);
if (yStr.length() == 0) {
yStr = SVG_MASK_Y_DEFAULT_VALUE;
}
// 'width' attribute - default is 120%
String wStr = maskElement.getAttributeNS(null, SVG_WIDTH_ATTRIBUTE);
if (wStr.length() == 0) {
wStr = SVG_MASK_WIDTH_DEFAULT_VALUE;
}
// 'height' attribute - default is 120%
String hStr = maskElement.getAttributeNS(null, SVG_HEIGHT_ATTRIBUTE);
if (hStr.length() == 0) {
hStr = SVG_MASK_HEIGHT_DEFAULT_VALUE;
}
// 'maskUnits' attribute - default is 'objectBoundingBox'
short unitsType;
String units =
maskElement.getAttributeNS(null, SVG_MASK_UNITS_ATTRIBUTE);
if (units.length() == 0) {
unitsType = OBJECT_BOUNDING_BOX;
} else {
unitsType = parseCoordinateSystem
(maskElement, SVG_MASK_UNITS_ATTRIBUTE, units);
}
// resolve units in the (referenced) maskedElement's coordinate system
UnitProcessor.Context uctx
= UnitProcessor.createContext(ctx, maskedElement);
return convertRegion(xStr,
yStr,
wStr,
hStr,
unitsType,
maskedNode,
uctx);
}
/////////////////////////////////////////////////////////////////////////
// <pattern> region
/////////////////////////////////////////////////////////////////////////
/**
* Returns the pattern region according to the x, y, width, height,
* and patternUnits attributes.
*
* @param patternElement the pattern element that defines the attributes
* @param paintedElement the element referencing the pattern
* @param paintedNode the graphics node to paint (objectBoundingBox)
* @param ctx the bridge context
*/
public static Rectangle2D convertPatternRegion(Element patternElement,
Element paintedElement,
GraphicsNode paintedNode,
BridgeContext ctx) {
// 'x' attribute - default is 0%
String xStr = getChainableAttributeNS
(patternElement, null, SVG_X_ATTRIBUTE, ctx);
if (xStr.length() == 0) {
xStr = SVG_PATTERN_X_DEFAULT_VALUE;
}
// 'y' attribute - default is 0%
String yStr = getChainableAttributeNS
(patternElement, null, SVG_Y_ATTRIBUTE, ctx);
if (yStr.length() == 0) {
yStr = SVG_PATTERN_Y_DEFAULT_VALUE;
}
// 'width' attribute - required
String wStr = getChainableAttributeNS
(patternElement, null, SVG_WIDTH_ATTRIBUTE, ctx);
if (wStr.length() == 0) {
throw new BridgeException(patternElement, ERR_ATTRIBUTE_MISSING,
new Object[] {SVG_WIDTH_ATTRIBUTE});
}
// 'height' attribute - required
String hStr = getChainableAttributeNS
(patternElement, null, SVG_HEIGHT_ATTRIBUTE, ctx);
if (hStr.length() == 0) {
throw new BridgeException(patternElement, ERR_ATTRIBUTE_MISSING,
new Object[] {SVG_HEIGHT_ATTRIBUTE});
}
// 'patternUnits' attribute - default is 'objectBoundingBox'
short unitsType;
String units = getChainableAttributeNS
(patternElement, null, SVG_PATTERN_UNITS_ATTRIBUTE, ctx);
if (units.length() == 0) {
unitsType = OBJECT_BOUNDING_BOX;
} else {
unitsType = parseCoordinateSystem
(patternElement, SVG_PATTERN_UNITS_ATTRIBUTE, units);
}
// resolve units in the (referenced) paintedElement's coordinate system
UnitProcessor.Context uctx
= UnitProcessor.createContext(ctx, paintedElement);
return convertRegion(xStr,
yStr,
wStr,
hStr,
unitsType,
paintedNode,
uctx);
}
/////////////////////////////////////////////////////////////////////////
// <filter> and filter primitive
/////////////////////////////////////////////////////////////////////////
/**
* Returns an array of 2 float numbers that describes the filter
* resolution of the specified filter element.
*
* @param filterElement the filter element
* @param ctx the bridge context
*/
public static
float [] convertFilterRes(Element filterElement, BridgeContext ctx) {
float [] filterRes = new float[2];
String s = getChainableAttributeNS
(filterElement, null, SVG_FILTER_RES_ATTRIBUTE, ctx);
Float [] vals = convertSVGNumberOptionalNumber
(filterElement, SVG_FILTER_RES_ATTRIBUTE, s);
if (filterRes[0] < 0 || filterRes[1] < 0) {
throw new BridgeException
(filterElement, ERR_ATTRIBUTE_VALUE_MALFORMED,
new Object[] {SVG_FILTER_RES_ATTRIBUTE, s});
}
if (vals[0] == null)
filterRes[0] = -1;
else {
filterRes[0] = vals[0].floatValue();
if (filterRes[0] < 0)
throw new BridgeException
(filterElement, ERR_ATTRIBUTE_VALUE_MALFORMED,
new Object[] {SVG_FILTER_RES_ATTRIBUTE, s});
}
if (vals[1] == null)
filterRes[1] = filterRes[0];
else {
filterRes[1] = vals[1].floatValue();
if (filterRes[1] < 0)
throw new BridgeException
(filterElement, ERR_ATTRIBUTE_VALUE_MALFORMED,
new Object[] {SVG_FILTER_RES_ATTRIBUTE, s});
}
return filterRes;
}
/**
* This function parses attrValue for a number followed by an optional
* second Number. It always returns an array of two Floats. If either
* or both values are not provided the entries are set to null
*/
public static Float []
convertSVGNumberOptionalNumber(Element elem,
String attrName,
String attrValue) {
Float [] ret = new Float[2];
if (attrValue.length() == 0)
return ret;
try {
StringTokenizer tokens = new StringTokenizer(attrValue, " ");
ret[0] = new Float(Float.parseFloat(tokens.nextToken()));
if (tokens.hasMoreTokens()) {
ret[1] = new Float(Float.parseFloat(tokens.nextToken()));
}
if (tokens.hasMoreTokens()) {
throw new BridgeException
(elem, ERR_ATTRIBUTE_VALUE_MALFORMED,
new Object[] {attrName, attrValue});
}
} catch (NumberFormatException ex) {
throw new BridgeException
(elem, ERR_ATTRIBUTE_VALUE_MALFORMED,
new Object[] {attrName, attrValue, ex});
}
return ret;
}
/**
* Returns the filter region according to the x, y, width, height,
* dx, dy, dw, dh and filterUnits attributes.
*
* @param filterElement the filter element that defines the attributes
* @param filteredElement the element referencing the filter
* @param filteredNode the graphics node to filter (objectBoundingBox)
* @param uctx the unit processor context (userSpaceOnUse)
* @param ctx the bridge context
*/
public static
Rectangle2D convertFilterChainRegion(Element filterElement,
Element filteredElement,
GraphicsNode filteredNode,
BridgeContext ctx) {
// 'x' attribute - default is -10%
String xStr = getChainableAttributeNS
(filterElement, null, SVG_X_ATTRIBUTE, ctx);
if (xStr.length() == 0) {
xStr = SVG_FILTER_X_DEFAULT_VALUE;
}
// 'y' attribute - default is -10%
String yStr = getChainableAttributeNS
(filterElement, null, SVG_Y_ATTRIBUTE, ctx);
if (yStr.length() == 0) {
yStr = SVG_FILTER_Y_DEFAULT_VALUE;
}
// 'width' attribute - default is 120%
String wStr = getChainableAttributeNS
(filterElement, null, SVG_WIDTH_ATTRIBUTE, ctx);
if (wStr.length() == 0) {
wStr = SVG_FILTER_WIDTH_DEFAULT_VALUE;
}
// 'height' attribute - default is 120%
String hStr = getChainableAttributeNS
(filterElement, null, SVG_HEIGHT_ATTRIBUTE, ctx);
if (hStr.length() == 0) {
hStr = SVG_FILTER_HEIGHT_DEFAULT_VALUE;
}
// 'filterUnits' attribute - default is 'objectBoundingBox'
short unitsType;
String units = getChainableAttributeNS
(filterElement, null, SVG_FILTER_UNITS_ATTRIBUTE, ctx);
if (units.length() == 0) {
unitsType = OBJECT_BOUNDING_BOX;
} else {
unitsType = parseCoordinateSystem
(filterElement, SVG_FILTER_UNITS_ATTRIBUTE, units);
}
// resolve units in the (referenced) filteredElement's
// coordinate system
UnitProcessor.Context uctx
= UnitProcessor.createContext(ctx, filteredElement);
Rectangle2D region = convertRegion(xStr,
yStr,
wStr,
hStr,
unitsType,
filteredNode,
uctx);
//
// Account for region padding
//
units = getChainableAttributeNS
(filterElement, null,
BatikExtConstants.BATIK_EXT_FILTER_MARGINS_UNITS_ATTRIBUTE, ctx);
if (units.length() == 0) {
// Default to user space on use for margins, not objectBoundingBox
unitsType = USER_SPACE_ON_USE;
} else {
unitsType = parseCoordinateSystem
(filterElement,
BatikExtConstants.BATIK_EXT_FILTER_MARGINS_UNITS_ATTRIBUTE, units);
}
// 'batik:dx' attribute - default is 0
String dxStr = filterElement.getAttributeNS(BatikExtConstants.BATIK_EXT_NAMESPACE_URI,
BatikExtConstants.BATIK_EXT_DX_ATRIBUTE);
if (dxStr.length() == 0) {
dxStr = BatikExtConstants.SVG_FILTER_DX_DEFAULT_VALUE;
}
// 'batik:dy' attribute - default is 0
String dyStr = filterElement.getAttributeNS(BatikExtConstants.BATIK_EXT_NAMESPACE_URI,
BatikExtConstants.BATIK_EXT_DY_ATRIBUTE);
if (dyStr.length() == 0) {
dyStr = BatikExtConstants.SVG_FILTER_DY_DEFAULT_VALUE;
}
// 'batik:dw' attribute - default is 0
String dwStr = filterElement.getAttributeNS(BatikExtConstants.BATIK_EXT_NAMESPACE_URI,
BatikExtConstants.BATIK_EXT_DW_ATRIBUTE);
if (dwStr.length() == 0) {
dwStr = BatikExtConstants.SVG_FILTER_DW_DEFAULT_VALUE;
}
// 'batik:dh' attribute - default is 0
String dhStr = filterElement.getAttributeNS(BatikExtConstants.BATIK_EXT_NAMESPACE_URI,
BatikExtConstants.BATIK_EXT_DH_ATRIBUTE);
if (dhStr.length() == 0) {
dhStr = BatikExtConstants.SVG_FILTER_DH_DEFAULT_VALUE;
}
return extendRegion(dxStr,
dyStr,
dwStr,
dhStr,
unitsType,
filteredNode,
region,
uctx);
}
/**
* Returns a rectangle that represents the region extended by the
* specified differential coordinates.
*
* @param dxStr the differential x coordinate of the region
* @param dyStr the differential y coordinate of the region
* @param dwStr the differential width of the region
* @param dhStr the differential height of the region
* @param unitsType specifies whether the values are in userSpaceOnUse
* or objectBoundingBox space
* @param region the region to extend
* @param uctx the unit processor context (needed for userSpaceOnUse)
*/
protected static Rectangle2D extendRegion(String dxStr,
String dyStr,
String dwStr,
String dhStr,
short unitsType,
GraphicsNode filteredNode,
Rectangle2D region,
UnitProcessor.Context uctx) {
float dx,dy,dw,dh;
switch (unitsType) {
case USER_SPACE_ON_USE:
dx = UnitProcessor.svgHorizontalCoordinateToUserSpace
(dxStr, BatikExtConstants.BATIK_EXT_DX_ATRIBUTE, uctx);
dy = UnitProcessor.svgVerticalCoordinateToUserSpace
(dyStr, BatikExtConstants.BATIK_EXT_DY_ATRIBUTE, uctx);
dw = UnitProcessor.svgHorizontalCoordinateToUserSpace
(dwStr, BatikExtConstants.BATIK_EXT_DW_ATRIBUTE, uctx);
dh = UnitProcessor.svgVerticalCoordinateToUserSpace
(dhStr, BatikExtConstants.BATIK_EXT_DH_ATRIBUTE, uctx);
break;
case OBJECT_BOUNDING_BOX:
Rectangle2D bounds = filteredNode.getGeometryBounds();
if (bounds == null) {
dx = dy = dw = dh = 0;
} else {
dx = UnitProcessor.svgHorizontalCoordinateToObjectBoundingBox
(dxStr, BatikExtConstants.BATIK_EXT_DX_ATRIBUTE, uctx);
dx *= bounds.getWidth();
dy = UnitProcessor.svgVerticalCoordinateToObjectBoundingBox
(dyStr, BatikExtConstants.BATIK_EXT_DY_ATRIBUTE, uctx);
dy *= bounds.getHeight();
dw = UnitProcessor.svgHorizontalCoordinateToObjectBoundingBox
(dwStr, BatikExtConstants.BATIK_EXT_DW_ATRIBUTE, uctx);
dw *= bounds.getWidth();
dh = UnitProcessor.svgVerticalCoordinateToObjectBoundingBox
(dhStr, BatikExtConstants.BATIK_EXT_DH_ATRIBUTE, uctx);
dh *= bounds.getHeight();
}
break;
default:
throw new Error(); // can't be reached
}
region.setRect(region.getX() + dx,
region.getY() + dy,
region.getWidth() + dw,
region.getHeight() + dh);
return region;
}
/**
* Returns the filter primitive region according to the x, y,
* width, height, and filterUnits attributes. Processing the
* element as the top one in the filter chain.
*
* @param filterPrimitiveElement the filter primitive element
* @param filteredElement the element referencing the filter
* @param filteredNode the graphics node to use (objectBoundingBox)
* @param defaultRegion the default region to filter
* @param filterRegion the filter chain region
* @param ctx the bridge context
*/
public static Rectangle2D
convertFilterPrimitiveRegion(Element filterPrimitiveElement,
Element filteredElement,
GraphicsNode filteredNode,
Rectangle2D defaultRegion,
Rectangle2D filterRegion,
BridgeContext ctx) {
// 'primitiveUnits' - default is userSpaceOnUse
Node parentNode = filterPrimitiveElement.getParentNode();
String units = "";
if ((parentNode != null) &&
(parentNode.getNodeType() == parentNode.ELEMENT_NODE)) {
Element parent = (Element)parentNode;
units = getChainableAttributeNS(parent,
null,
SVG_PRIMITIVE_UNITS_ATTRIBUTE,
ctx);
}
short unitsType;
if (units.length() == 0) {
unitsType = USER_SPACE_ON_USE;
} else {
unitsType = parseCoordinateSystem
(filterPrimitiveElement, SVG_FILTER_UNITS_ATTRIBUTE, units);
}
// 'x' attribute - default is defaultRegion.getX()
String xStr =
filterPrimitiveElement.getAttributeNS(null, SVG_X_ATTRIBUTE);
// 'y' attribute - default is defaultRegion.getY()
String yStr =
filterPrimitiveElement.getAttributeNS(null, SVG_Y_ATTRIBUTE);
// 'width' attribute - default is defaultRegion.getWidth()
String wStr =
filterPrimitiveElement.getAttributeNS(null, SVG_WIDTH_ATTRIBUTE);
// 'height' attribute - default is defaultRegion.getHeight()
String hStr =
filterPrimitiveElement.getAttributeNS(null, SVG_HEIGHT_ATTRIBUTE);
double x = defaultRegion.getX();
double y = defaultRegion.getY();
double w = defaultRegion.getWidth();
double h = defaultRegion.getHeight();
// resolve units in the (referenced) filteredElement's coordinate system
UnitProcessor.Context uctx
= UnitProcessor.createContext(ctx, filteredElement);
switch (unitsType) {
case OBJECT_BOUNDING_BOX:
Rectangle2D bounds = filteredNode.getGeometryBounds();
if (bounds != null) {
if (xStr.length() != 0) {
x = UnitProcessor.svgHorizontalCoordinateToObjectBoundingBox
(xStr, SVG_X_ATTRIBUTE, uctx);
x = bounds.getX() + x*bounds.getWidth();
}
if (yStr.length() != 0) {
y = UnitProcessor.svgVerticalCoordinateToObjectBoundingBox
(yStr, SVG_Y_ATTRIBUTE, uctx);
y = bounds.getY() + y*bounds.getHeight();
}
if (wStr.length() != 0) {
w = UnitProcessor.svgHorizontalLengthToObjectBoundingBox
(wStr, SVG_WIDTH_ATTRIBUTE, uctx);
w *= bounds.getWidth();
}
if (hStr.length() != 0) {
h = UnitProcessor.svgVerticalLengthToObjectBoundingBox
(hStr, SVG_HEIGHT_ATTRIBUTE, uctx);
h *= bounds.getHeight();
}
}
break;
case USER_SPACE_ON_USE:
if (xStr.length() != 0) {
x = UnitProcessor.svgHorizontalCoordinateToUserSpace
(xStr, SVG_X_ATTRIBUTE, uctx);
}
if (yStr.length() != 0) {
y = UnitProcessor.svgVerticalCoordinateToUserSpace
(yStr, SVG_Y_ATTRIBUTE, uctx);
}
if (wStr.length() != 0) {
w = UnitProcessor.svgHorizontalLengthToUserSpace
(wStr, SVG_WIDTH_ATTRIBUTE, uctx);
}
if (hStr.length() != 0) {
h = UnitProcessor.svgVerticalLengthToUserSpace
(hStr, SVG_HEIGHT_ATTRIBUTE, uctx);
}
break;
default:
throw new Error(); // can't be reached
}
Rectangle2D region = new Rectangle2D.Double(x, y, w, h);
// Now, extend filter primitive region with dx/dy/dw/dh
// settings (Batik extension). The dx/dy/dw/dh padding is
// *always* in userSpaceOnUse space.
units = "";
if ((parentNode != null) &&
(parentNode.getNodeType() == parentNode.ELEMENT_NODE)) {
Element parent = (Element)parentNode;
units = getChainableAttributeNS
(parent,
BatikExtConstants.BATIK_EXT_NAMESPACE_URI,
BatikExtConstants.BATIK_EXT_FILTER_PRIMITIVE_MARGINS_UNITS_ATTRIBUTE,
ctx);
}
if (units.length() == 0) {
unitsType = USER_SPACE_ON_USE;
} else {
unitsType = parseCoordinateSystem
(filterPrimitiveElement,
BatikExtConstants.BATIK_EXT_FILTER_PRIMITIVE_MARGINS_UNITS_ATTRIBUTE, units);
}
// 'batik:dx' attribute - default is 0
String dxStr = filterPrimitiveElement.getAttributeNS(BatikExtConstants.BATIK_EXT_NAMESPACE_URI,
BatikExtConstants.BATIK_EXT_DX_ATRIBUTE);
if (dxStr.length() == 0) {
dxStr = BatikExtConstants.SVG_FILTER_DX_DEFAULT_VALUE;
}
// 'batik:dy' attribute - default is 0
String dyStr = filterPrimitiveElement.getAttributeNS(BatikExtConstants.BATIK_EXT_NAMESPACE_URI,
BatikExtConstants.BATIK_EXT_DY_ATRIBUTE);
if (dyStr.length() == 0) {
dyStr = BatikExtConstants.SVG_FILTER_DY_DEFAULT_VALUE;
}
// 'batik:dw' attribute - default is 0
String dwStr = filterPrimitiveElement.getAttributeNS(BatikExtConstants.BATIK_EXT_NAMESPACE_URI,
BatikExtConstants.BATIK_EXT_DW_ATRIBUTE);
if (dwStr.length() == 0) {
dwStr = BatikExtConstants.SVG_FILTER_DW_DEFAULT_VALUE;
}
// 'batik:dh' attribute - default is 0
String dhStr = filterPrimitiveElement.getAttributeNS(BatikExtConstants.BATIK_EXT_NAMESPACE_URI,
BatikExtConstants.BATIK_EXT_DH_ATRIBUTE);
if (dhStr.length() == 0) {
dhStr = BatikExtConstants.SVG_FILTER_DH_DEFAULT_VALUE;
}
region = extendRegion(dxStr,
dyStr,
dwStr,
dhStr,
unitsType,
filteredNode,
region,
uctx);
region.intersect(region, filterRegion, region);
return region;
}
/////////////////////////////////////////////////////////////////////////
// region convenient methods
/////////////////////////////////////////////////////////////////////////
/** The userSpaceOnUse coordinate system constants. */
public static final short USER_SPACE_ON_USE = 1;
/** The objectBoundingBox coordinate system constants. */
public static final short OBJECT_BOUNDING_BOX = 2;
/** The strokeWidth coordinate system constants. */
public static final short STROKE_WIDTH = 3;
/**
* Parses the specified coordinate system defined by the specified element.
*
* @param e the element that defines the coordinate system
* @param attr the attribute which contains the coordinate system
* @param coordinateSystem the coordinate system to parse
* @return OBJECT_BOUNDING_BOX | USER_SPACE_ON_USE
*/
public static short parseCoordinateSystem(Element e,
String attr,
String coordinateSystem) {
if (SVG_USER_SPACE_ON_USE_VALUE.equals(coordinateSystem)) {
return USER_SPACE_ON_USE;
} else if (SVG_OBJECT_BOUNDING_BOX_VALUE.equals(coordinateSystem)) {
return OBJECT_BOUNDING_BOX;
} else {
throw new BridgeException(e, ERR_ATTRIBUTE_VALUE_MALFORMED,
new Object[] {attr, coordinateSystem});
}
}
/**
* Parses the specified coordinate system defined by the specified
* marker element.
*
* @param e the element that defines the coordinate system
* @param attr the attribute which contains the coordinate system
* @param coordinateSystem the coordinate system to parse
* @return STROKE_WIDTH | USER_SPACE_ON_USE
*/
public static short parseMarkerCoordinateSystem(Element e,
String attr,
String coordinateSystem) {
if (SVG_USER_SPACE_ON_USE_VALUE.equals(coordinateSystem)) {
return USER_SPACE_ON_USE;
} else if (SVG_STROKE_WIDTH_VALUE.equals(coordinateSystem)) {
return STROKE_WIDTH;
} else {
throw new BridgeException(e, ERR_ATTRIBUTE_VALUE_MALFORMED,
new Object[] {attr, coordinateSystem});
}
}
/**
* Returns a rectangle that represents the region defined by the
* specified coordinates.
*
* @param xStr the x coordinate of the region
* @param yStr the y coordinate of the region
* @param wStr the width of the region
* @param hStr the height of the region
* @param targetNode the graphics node (needed for objectBoundingBox)
* @param uctx the unit processor context (needed for userSpaceOnUse)
* @param rc the graphics node render context
*/
protected static Rectangle2D convertRegion(String xStr,
String yStr,
String wStr,
String hStr,
short unitsType,
GraphicsNode targetNode,
UnitProcessor.Context uctx) {
// construct the mask region in the appropriate coordinate system
double x, y, w, h;
switch (unitsType) {
case OBJECT_BOUNDING_BOX:
x = UnitProcessor.svgHorizontalCoordinateToObjectBoundingBox
(xStr, SVG_X_ATTRIBUTE, uctx);
y = UnitProcessor.svgVerticalCoordinateToObjectBoundingBox
(yStr, SVG_Y_ATTRIBUTE, uctx);
w = UnitProcessor.svgHorizontalLengthToObjectBoundingBox
(wStr, SVG_WIDTH_ATTRIBUTE, uctx);
h = UnitProcessor.svgVerticalLengthToObjectBoundingBox
(hStr, SVG_HEIGHT_ATTRIBUTE, uctx);
Rectangle2D bounds = targetNode.getGeometryBounds();
if (bounds != null ) {
x = bounds.getX() + x*bounds.getWidth();
y = bounds.getY() + y*bounds.getHeight();
w *= bounds.getWidth();
h *= bounds.getHeight();
} else {
x = y = w = h = 0;
}
break;
case USER_SPACE_ON_USE:
x = UnitProcessor.svgHorizontalCoordinateToUserSpace
(xStr, SVG_X_ATTRIBUTE, uctx);
y = UnitProcessor.svgVerticalCoordinateToUserSpace
(yStr, SVG_Y_ATTRIBUTE, uctx);
w = UnitProcessor.svgHorizontalLengthToUserSpace
(wStr, SVG_WIDTH_ATTRIBUTE, uctx);
h = UnitProcessor.svgVerticalLengthToUserSpace
(hStr, SVG_HEIGHT_ATTRIBUTE, uctx);
break;
default:
throw new Error(); // can't be reached
}
return new Rectangle2D.Double(x, y, w, h);
}
/////////////////////////////////////////////////////////////////////////
// coordinate system and transformation support methods
/////////////////////////////////////////////////////////////////////////
/**
* Returns an AffineTransform according to the specified parameters.
*
* @param e the element that defines the transform
* @param attr the name of the attribute that represents the transform
* @param transform the transform to parse
*
*/
public static AffineTransform convertTransform(Element e,
String attr,
String transform) {
try {
return AWTTransformProducer.createAffineTransform(transform);
} catch (ParseException ex) {
throw new BridgeException(e, ERR_ATTRIBUTE_VALUE_MALFORMED,
new Object[] {attr, transform, ex});
}
}
/**
* Returns an AffineTransform to move to the objectBoundingBox
* coordinate system.
*
* @param Tx the original transformation
* @param node the graphics node that defines the coordinate
* system to move into
* @param rc the graphics node render context
*/
public static AffineTransform toObjectBBox(AffineTransform Tx,
GraphicsNode node) {
AffineTransform Mx = new AffineTransform();
Rectangle2D bounds = node.getGeometryBounds();
if (bounds != null) {
Mx.translate(bounds.getX(), bounds.getY());
Mx.scale(bounds.getWidth(), bounds.getHeight());
}
Mx.concatenate(Tx);
return Mx;
}
/**
* Returns the specified a Rectangle2D move to the objectBoundingBox
* coordinate system of the specified graphics node.
*
* @param r the original Rectangle2D
* @param node the graphics node that defines the coordinate
* system to move into
* @param rc the graphics node render context
*/
public static Rectangle2D toObjectBBox(Rectangle2D r,
GraphicsNode node) {
Rectangle2D bounds = node.getGeometryBounds();
if(bounds != null){
return new Rectangle2D.Double
(bounds.getX() + r.getX()*bounds.getWidth(),
bounds.getY() + r.getY()*bounds.getHeight(),
r.getWidth() * bounds.getWidth(),
r.getHeight() * bounds.getHeight());
} else {
return new Rectangle2D.Double();
}
}
/**
* Scans the children of the input <tt>e</tt> element and
* invokes any registered bridge found for the children.
*
* @param ctx active BridgeContext
* @param e element to be scanned
*/
public static void bridgeChildren(BridgeContext ctx,
Element elt){
for (Node n = elt.getFirstChild();
n != null;
n = n.getNextSibling()) {
if ((n.getNodeType() != Node.ELEMENT_NODE)) {
continue;
}
Element e = (Element)n;
Bridge bridge = ctx.getBridge(e);
if (bridge == null || !(bridge instanceof GenericBridge)) {
continue;
}
((GenericBridge)bridge).handleElement(ctx, e);
}
}
}