0001: /*
0002:
0003: Licensed to the Apache Software Foundation (ASF) under one or more
0004: contributor license agreements. See the NOTICE file distributed with
0005: this work for additional information regarding copyright ownership.
0006: The ASF licenses this file to You under the Apache License, Version 2.0
0007: (the "License"); you may not use this file except in compliance with
0008: the License. You may obtain a copy of the License at
0009:
0010: http://www.apache.org/licenses/LICENSE-2.0
0011:
0012: Unless required by applicable law or agreed to in writing, software
0013: distributed under the License is distributed on an "AS IS" BASIS,
0014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0015: See the License for the specific language governing permissions and
0016: limitations under the License.
0017:
0018: */
0019: package org.apache.batik.bridge;
0020:
0021: import java.awt.geom.AffineTransform;
0022: import java.awt.geom.Point2D;
0023: import java.awt.geom.Rectangle2D;
0024: import java.io.IOException;
0025: import java.util.Iterator;
0026: import java.util.LinkedList;
0027: import java.util.List;
0028: import java.util.StringTokenizer;
0029:
0030: import org.apache.batik.css.engine.CSSEngine;
0031: import org.apache.batik.dom.AbstractNode;
0032: import org.apache.batik.dom.util.XLinkSupport;
0033: import org.apache.batik.dom.util.XMLSupport;
0034: import org.apache.batik.gvt.GraphicsNode;
0035: import org.apache.batik.parser.AWTTransformProducer;
0036: import org.apache.batik.parser.ClockHandler;
0037: import org.apache.batik.parser.ClockParser;
0038: import org.apache.batik.parser.ParseException;
0039: import org.apache.batik.util.ParsedURL;
0040: import org.apache.batik.util.SVG12Constants;
0041: import org.apache.batik.util.SVGConstants;
0042: import org.w3c.dom.Element;
0043: import org.w3c.dom.Node;
0044: import org.w3c.dom.svg.SVGDocument;
0045: import org.w3c.dom.svg.SVGElement;
0046: import org.w3c.dom.svg.SVGLangSpace;
0047: import org.w3c.dom.svg.SVGNumberList;
0048:
0049: /**
0050: * A collection of utility methods for SVG.
0051: *
0052: * @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a>
0053: * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
0054: * @version $Id: SVGUtilities.java 504084 2007-02-06 11:24:46Z dvholten $
0055: */
0056: public abstract class SVGUtilities implements SVGConstants,
0057: ErrorConstants {
0058:
0059: /**
0060: * No instance of this class is required.
0061: */
0062: protected SVGUtilities() {
0063: }
0064:
0065: ////////////////////////////////////////////////////////////////////////
0066: // common methods
0067: ////////////////////////////////////////////////////////////////////////
0068:
0069: /**
0070: * Returns the logical parent element of the given element.
0071: * The parent element of a used element is the <use> element
0072: * which reference it.
0073: */
0074: public static Element getParentElement(Element elt) {
0075: Node n = CSSEngine.getCSSParentNode(elt);
0076: while (n != null && n.getNodeType() != Node.ELEMENT_NODE) {
0077: n = CSSEngine.getCSSParentNode(n);
0078: }
0079: return (Element) n;
0080: }
0081:
0082: /**
0083: * Converts an SVGNumberList into a float array.
0084: * @param l the list to convert
0085: */
0086: public static float[] convertSVGNumberList(SVGNumberList l) {
0087: int n = l.getNumberOfItems();
0088: if (n == 0) {
0089: return null;
0090: }
0091: float[] fl = new float[n];
0092: for (int i = 0; i < n; i++) {
0093: fl[i] = l.getItem(i).getValue();
0094: }
0095: return fl;
0096: }
0097:
0098: /**
0099: * Converts a string into a float.
0100: * @param s the float representation to convert
0101: */
0102: public static float convertSVGNumber(String s) {
0103: return Float.parseFloat(s);
0104: }
0105:
0106: /**
0107: * Converts a string into an integer.
0108: * @param s the integer representation to convert
0109: */
0110: public static int convertSVGInteger(String s) {
0111: return Integer.parseInt(s);
0112: }
0113:
0114: /**
0115: * Converts the specified ratio to float number.
0116: * @param v the ratio value to convert
0117: * @exception NumberFormatException if the ratio is not a valid
0118: * number or percentage
0119: */
0120: public static float convertRatio(String v) {
0121: float d = 1;
0122: if (v.endsWith("%")) {
0123: v = v.substring(0, v.length() - 1);
0124: d = 100;
0125: }
0126: float r = Float.parseFloat(v) / d;
0127: if (r < 0) {
0128: r = 0;
0129: } else if (r > 1) {
0130: r = 1;
0131: }
0132: return r;
0133: }
0134:
0135: /**
0136: * Returns the content of the 'desc' child of the given element.
0137: */
0138: public static String getDescription(SVGElement elt) {
0139: String result = "";
0140: boolean preserve = false;
0141: Node n = elt.getFirstChild();
0142: if (n != null && n.getNodeType() == Node.ELEMENT_NODE) {
0143: String name = (n.getPrefix() == null) ? n.getNodeName() : n
0144: .getLocalName();
0145: if (name.equals(SVG_DESC_TAG)) {
0146: preserve = ((SVGLangSpace) n).getXMLspace().equals(
0147: SVG_PRESERVE_VALUE);
0148: for (n = n.getFirstChild(); n != null; n = n
0149: .getNextSibling()) {
0150: if (n.getNodeType() == Node.TEXT_NODE) {
0151: result += n.getNodeValue();
0152: }
0153: }
0154: }
0155: }
0156: return (preserve) ? XMLSupport.preserveXMLSpace(result)
0157: : XMLSupport.defaultXMLSpace(result);
0158: }
0159:
0160: /**
0161: * Tests whether or not the given element match a specified user agent.
0162: *
0163: * @param elt the element to check
0164: * @param ua the user agent
0165: */
0166: public static boolean matchUserAgent(Element elt, UserAgent ua) {
0167: test: if (elt.hasAttributeNS(null,
0168: SVG_SYSTEM_LANGUAGE_ATTRIBUTE)) {
0169: // Tests the system languages.
0170: String sl = elt.getAttributeNS(null,
0171: SVG_SYSTEM_LANGUAGE_ATTRIBUTE);
0172: if (sl.length() == 0) // SVG spec says empty returns false
0173: return false;
0174: StringTokenizer st = new StringTokenizer(sl, ", ");
0175: while (st.hasMoreTokens()) {
0176: String s = st.nextToken();
0177: if (matchUserLanguage(s, ua.getLanguages())) {
0178: break test;
0179: }
0180: }
0181: return false;
0182: }
0183: if (elt.hasAttributeNS(null, SVG_REQUIRED_FEATURES_ATTRIBUTE)) {
0184: // Tests the system features.
0185: String rf = elt.getAttributeNS(null,
0186: SVG_REQUIRED_FEATURES_ATTRIBUTE);
0187: if (rf.length() == 0) // SVG spec says empty returns false
0188: return false;
0189: StringTokenizer st = new StringTokenizer(rf, " ");
0190: while (st.hasMoreTokens()) {
0191: String s = st.nextToken();
0192: if (!ua.hasFeature(s)) {
0193: return false;
0194: }
0195: }
0196: }
0197: if (elt.hasAttributeNS(null, SVG_REQUIRED_EXTENSIONS_ATTRIBUTE)) {
0198: // Tests the system features.
0199: String re = elt.getAttributeNS(null,
0200: SVG_REQUIRED_EXTENSIONS_ATTRIBUTE);
0201: if (re.length() == 0) // SVG spec says empty returns false
0202: return false;
0203: StringTokenizer st = new StringTokenizer(re, " ");
0204: while (st.hasMoreTokens()) {
0205: String s = st.nextToken();
0206: if (!ua.supportExtension(s)) {
0207: return false;
0208: }
0209: }
0210: }
0211: return true;
0212: }
0213:
0214: /**
0215: * Tests whether or not the specified language specification matches
0216: * the user preferences.
0217: *
0218: * @param s the langage to check
0219: * @param userLanguages the user langages
0220: */
0221: protected static boolean matchUserLanguage(String s,
0222: String userLanguages) {
0223: StringTokenizer st = new StringTokenizer(userLanguages, ", ");
0224: while (st.hasMoreTokens()) {
0225: String t = st.nextToken();
0226: if (s.startsWith(t)) {
0227: if (s.length() > t.length()) {
0228: return (s.charAt(t.length()) == '-');
0229: }
0230: return true;
0231: }
0232: }
0233: return false;
0234: }
0235:
0236: /**
0237: * Returns the value of the specified attribute specified on the
0238: * specified element or one of its ancestor. Ancestors are found
0239: * using the xlink:href attribute.
0240: *
0241: * @param element the element to start with
0242: * @param namespaceURI the namespace URI of the attribute to return
0243: * @param attrName the name of the attribute to search
0244: * @param ctx the bridge context
0245: * @return the value of the attribute or an empty string if not defined
0246: */
0247: public static String getChainableAttributeNS(Element element,
0248: String namespaceURI, String attrName, BridgeContext ctx) {
0249:
0250: DocumentLoader loader = ctx.getDocumentLoader();
0251: Element e = element;
0252: List refs = new LinkedList();
0253: for (;;) {
0254: String v = e.getAttributeNS(namespaceURI, attrName);
0255: if (v.length() > 0) { // exit if attribute defined
0256: return v;
0257: }
0258: String uriStr = XLinkSupport.getXLinkHref(e);
0259: if (uriStr.length() == 0) { // exit if no more xlink:href
0260: return "";
0261: }
0262: String baseURI = ((AbstractNode) e).getBaseURI();
0263: ParsedURL purl = new ParsedURL(baseURI, uriStr);
0264:
0265: Iterator iter = refs.iterator();
0266: while (iter.hasNext()) {
0267: if (purl.equals(iter.next()))
0268: throw new BridgeException(ctx, e,
0269: ERR_XLINK_HREF_CIRCULAR_DEPENDENCIES,
0270: new Object[] { uriStr });
0271: }
0272:
0273: try {
0274: SVGDocument svgDoc = (SVGDocument) e.getOwnerDocument();
0275: URIResolver resolver = ctx.createURIResolver(svgDoc,
0276: loader);
0277: e = resolver.getElement(purl.toString(), e);
0278: refs.add(purl);
0279: } catch (IOException ioEx) {
0280: throw new BridgeException(ctx, e, ioEx, ERR_URI_IO,
0281: new Object[] { uriStr });
0282: } catch (SecurityException secEx) {
0283: throw new BridgeException(ctx, e, secEx,
0284: ERR_URI_UNSECURE, new Object[] { uriStr });
0285: }
0286: }
0287: }
0288:
0289: /////////////////////////////////////////////////////////////////////////
0290: // <linearGradient> and <radialGradient>
0291: /////////////////////////////////////////////////////////////////////////
0292:
0293: /**
0294: * Returns a Point2D in user units according to the specified parameters.
0295: *
0296: * @param xStr the x coordinate
0297: * @param xAttr the name of the attribute that represents the x coordinate
0298: * @param yStr the y coordinate
0299: * @param yAttr the name of the attribute that represents the y coordinate
0300: * @param unitsType the coordinate system (OBJECT_BOUNDING_BOX |
0301: * USER_SPACE_ON_USE)
0302: * @param uctx the unit processor context
0303: */
0304: public static Point2D convertPoint(String xStr, String xAttr,
0305: String yStr, String yAttr, short unitsType,
0306: UnitProcessor.Context uctx) {
0307: float x, y;
0308: switch (unitsType) {
0309: case OBJECT_BOUNDING_BOX:
0310: x = UnitProcessor
0311: .svgHorizontalCoordinateToObjectBoundingBox(xStr,
0312: xAttr, uctx);
0313: y = UnitProcessor.svgVerticalCoordinateToObjectBoundingBox(
0314: yStr, yAttr, uctx);
0315: break;
0316: case USER_SPACE_ON_USE:
0317: x = UnitProcessor.svgHorizontalCoordinateToUserSpace(xStr,
0318: xAttr, uctx);
0319: y = UnitProcessor.svgVerticalCoordinateToUserSpace(yStr,
0320: yAttr, uctx);
0321: break;
0322: default:
0323: throw new IllegalArgumentException("Invalid unit type");
0324: }
0325: return new Point2D.Float(x, y);
0326: }
0327:
0328: /**
0329: * Returns a float in user units according to the specified parameters.
0330: *
0331: * @param length the length
0332: * @param attr the name of the attribute that represents the length
0333: * @param unitsType the coordinate system (OBJECT_BOUNDING_BOX |
0334: * USER_SPACE_ON_USE)
0335: * @param uctx the unit processor context
0336: */
0337: public static float convertLength(String length, String attr,
0338: short unitsType, UnitProcessor.Context uctx) {
0339: switch (unitsType) {
0340: case OBJECT_BOUNDING_BOX:
0341: return UnitProcessor.svgOtherLengthToObjectBoundingBox(
0342: length, attr, uctx);
0343: case USER_SPACE_ON_USE:
0344: return UnitProcessor.svgOtherLengthToUserSpace(length,
0345: attr, uctx);
0346: default:
0347: throw new IllegalArgumentException("Invalid unit type");
0348: }
0349: }
0350:
0351: /////////////////////////////////////////////////////////////////////////
0352: // <mask> region
0353: /////////////////////////////////////////////////////////////////////////
0354:
0355: /**
0356: * Returns the mask region according to the x, y, width, height,
0357: * and maskUnits attributes.
0358: *
0359: * @param maskElement the mask element that defines the various attributes
0360: * @param maskedElement the element referencing the mask
0361: * @param maskedNode the graphics node to mask (objectBoundingBox)
0362: * @param ctx the bridge context
0363: */
0364: public static Rectangle2D convertMaskRegion(Element maskElement,
0365: Element maskedElement, GraphicsNode maskedNode,
0366: BridgeContext ctx) {
0367:
0368: // 'x' attribute - default is -10%
0369: String xStr = maskElement.getAttributeNS(null, SVG_X_ATTRIBUTE);
0370: if (xStr.length() == 0) {
0371: xStr = SVG_MASK_X_DEFAULT_VALUE;
0372: }
0373: // 'y' attribute - default is -10%
0374: String yStr = maskElement.getAttributeNS(null, SVG_Y_ATTRIBUTE);
0375: if (yStr.length() == 0) {
0376: yStr = SVG_MASK_Y_DEFAULT_VALUE;
0377: }
0378: // 'width' attribute - default is 120%
0379: String wStr = maskElement.getAttributeNS(null,
0380: SVG_WIDTH_ATTRIBUTE);
0381: if (wStr.length() == 0) {
0382: wStr = SVG_MASK_WIDTH_DEFAULT_VALUE;
0383: }
0384: // 'height' attribute - default is 120%
0385: String hStr = maskElement.getAttributeNS(null,
0386: SVG_HEIGHT_ATTRIBUTE);
0387: if (hStr.length() == 0) {
0388: hStr = SVG_MASK_HEIGHT_DEFAULT_VALUE;
0389: }
0390: // 'maskUnits' attribute - default is 'objectBoundingBox'
0391: short unitsType;
0392: String units = maskElement.getAttributeNS(null,
0393: SVG_MASK_UNITS_ATTRIBUTE);
0394: if (units.length() == 0) {
0395: unitsType = OBJECT_BOUNDING_BOX;
0396: } else {
0397: unitsType = parseCoordinateSystem(maskElement,
0398: SVG_MASK_UNITS_ATTRIBUTE, units, ctx);
0399: }
0400:
0401: // resolve units in the (referenced) maskedElement's coordinate system
0402: UnitProcessor.Context uctx = UnitProcessor.createContext(ctx,
0403: maskedElement);
0404:
0405: return convertRegion(xStr, yStr, wStr, hStr, unitsType,
0406: maskedNode, uctx);
0407: }
0408:
0409: /////////////////////////////////////////////////////////////////////////
0410: // <pattern> region
0411: /////////////////////////////////////////////////////////////////////////
0412:
0413: /**
0414: * Returns the pattern region according to the x, y, width, height,
0415: * and patternUnits attributes.
0416: *
0417: * @param patternElement the pattern element that defines the attributes
0418: * @param paintedElement the element referencing the pattern
0419: * @param paintedNode the graphics node to paint (objectBoundingBox)
0420: * @param ctx the bridge context
0421: */
0422: public static Rectangle2D convertPatternRegion(
0423: Element patternElement, Element paintedElement,
0424: GraphicsNode paintedNode, BridgeContext ctx) {
0425:
0426: // 'x' attribute - default is 0%
0427: String xStr = getChainableAttributeNS(patternElement, null,
0428: SVG_X_ATTRIBUTE, ctx);
0429: if (xStr.length() == 0) {
0430: xStr = SVG_PATTERN_X_DEFAULT_VALUE;
0431: }
0432: // 'y' attribute - default is 0%
0433: String yStr = getChainableAttributeNS(patternElement, null,
0434: SVG_Y_ATTRIBUTE, ctx);
0435: if (yStr.length() == 0) {
0436: yStr = SVG_PATTERN_Y_DEFAULT_VALUE;
0437: }
0438: // 'width' attribute - required
0439: String wStr = getChainableAttributeNS(patternElement, null,
0440: SVG_WIDTH_ATTRIBUTE, ctx);
0441: if (wStr.length() == 0) {
0442: throw new BridgeException(ctx, patternElement,
0443: ERR_ATTRIBUTE_MISSING,
0444: new Object[] { SVG_WIDTH_ATTRIBUTE });
0445: }
0446: // 'height' attribute - required
0447: String hStr = getChainableAttributeNS(patternElement, null,
0448: SVG_HEIGHT_ATTRIBUTE, ctx);
0449: if (hStr.length() == 0) {
0450: throw new BridgeException(ctx, patternElement,
0451: ERR_ATTRIBUTE_MISSING,
0452: new Object[] { SVG_HEIGHT_ATTRIBUTE });
0453: }
0454: // 'patternUnits' attribute - default is 'objectBoundingBox'
0455: short unitsType;
0456: String units = getChainableAttributeNS(patternElement, null,
0457: SVG_PATTERN_UNITS_ATTRIBUTE, ctx);
0458: if (units.length() == 0) {
0459: unitsType = OBJECT_BOUNDING_BOX;
0460: } else {
0461: unitsType = parseCoordinateSystem(patternElement,
0462: SVG_PATTERN_UNITS_ATTRIBUTE, units, ctx);
0463: }
0464:
0465: // resolve units in the (referenced) paintedElement's coordinate system
0466: UnitProcessor.Context uctx = UnitProcessor.createContext(ctx,
0467: paintedElement);
0468:
0469: return convertRegion(xStr, yStr, wStr, hStr, unitsType,
0470: paintedNode, uctx);
0471: }
0472:
0473: /////////////////////////////////////////////////////////////////////////
0474: // <filter> and filter primitive
0475: /////////////////////////////////////////////////////////////////////////
0476:
0477: /**
0478: * Returns an array of 2 float numbers that describes the filter
0479: * resolution of the specified filter element.
0480: *
0481: * @param filterElement the filter element
0482: * @param ctx the bridge context
0483: */
0484: public static float[] convertFilterRes(Element filterElement,
0485: BridgeContext ctx) {
0486:
0487: float[] filterRes = new float[2];
0488: String s = getChainableAttributeNS(filterElement, null,
0489: SVG_FILTER_RES_ATTRIBUTE, ctx);
0490: Float[] vals = convertSVGNumberOptionalNumber(filterElement,
0491: SVG_FILTER_RES_ATTRIBUTE, s, ctx);
0492:
0493: if (filterRes[0] < 0 || filterRes[1] < 0) {
0494: throw new BridgeException(ctx, filterElement,
0495: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
0496: SVG_FILTER_RES_ATTRIBUTE, s });
0497: }
0498:
0499: if (vals[0] == null)
0500: filterRes[0] = -1;
0501: else {
0502: filterRes[0] = vals[0].floatValue();
0503: if (filterRes[0] < 0)
0504: throw new BridgeException(ctx, filterElement,
0505: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
0506: SVG_FILTER_RES_ATTRIBUTE, s });
0507: }
0508:
0509: if (vals[1] == null)
0510: filterRes[1] = filterRes[0];
0511: else {
0512: filterRes[1] = vals[1].floatValue();
0513: if (filterRes[1] < 0)
0514: throw new BridgeException(ctx, filterElement,
0515: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
0516: SVG_FILTER_RES_ATTRIBUTE, s });
0517: }
0518: return filterRes;
0519: }
0520:
0521: /**
0522: * This function parses attrValue for a number followed by an optional
0523: * second Number. It always returns an array of two Floats. If either
0524: * or both values are not provided the entries are set to null
0525: */
0526: public static Float[] convertSVGNumberOptionalNumber(Element elem,
0527: String attrName, String attrValue, BridgeContext ctx) {
0528:
0529: Float[] ret = new Float[2];
0530: if (attrValue.length() == 0)
0531: return ret;
0532:
0533: try {
0534: StringTokenizer tokens = new StringTokenizer(attrValue, " ");
0535: ret[0] = new Float(Float.parseFloat(tokens.nextToken()));
0536: if (tokens.hasMoreTokens()) {
0537: ret[1] = new Float(Float.parseFloat(tokens.nextToken()));
0538: }
0539:
0540: if (tokens.hasMoreTokens()) {
0541: throw new BridgeException(ctx, elem,
0542: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
0543: attrName, attrValue });
0544: }
0545: } catch (NumberFormatException nfEx) {
0546: throw new BridgeException(ctx, elem, nfEx,
0547: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
0548: attrName, attrValue, nfEx });
0549: }
0550: return ret;
0551: }
0552:
0553: /**
0554: * Returns the filter region according to the x, y, width, height,
0555: * dx, dy, dw, dh and filterUnits attributes.
0556: *
0557: * @param filterElement the filter element that defines the attributes
0558: * @param filteredElement the element referencing the filter
0559: * @param filteredNode the graphics node to filter (objectBoundingBox)
0560: * @param ctx the bridge context
0561: */
0562: public static Rectangle2D convertFilterChainRegion(
0563: Element filterElement, Element filteredElement,
0564: GraphicsNode filteredNode, BridgeContext ctx) {
0565:
0566: // 'x' attribute - default is -10%
0567: String xStr = getChainableAttributeNS(filterElement, null,
0568: SVG_X_ATTRIBUTE, ctx);
0569: if (xStr.length() == 0) {
0570: xStr = SVG_FILTER_X_DEFAULT_VALUE;
0571: }
0572: // 'y' attribute - default is -10%
0573: String yStr = getChainableAttributeNS(filterElement, null,
0574: SVG_Y_ATTRIBUTE, ctx);
0575: if (yStr.length() == 0) {
0576: yStr = SVG_FILTER_Y_DEFAULT_VALUE;
0577: }
0578: // 'width' attribute - default is 120%
0579: String wStr = getChainableAttributeNS(filterElement, null,
0580: SVG_WIDTH_ATTRIBUTE, ctx);
0581: if (wStr.length() == 0) {
0582: wStr = SVG_FILTER_WIDTH_DEFAULT_VALUE;
0583: }
0584: // 'height' attribute - default is 120%
0585: String hStr = getChainableAttributeNS(filterElement, null,
0586: SVG_HEIGHT_ATTRIBUTE, ctx);
0587: if (hStr.length() == 0) {
0588: hStr = SVG_FILTER_HEIGHT_DEFAULT_VALUE;
0589: }
0590: // 'filterUnits' attribute - default is 'objectBoundingBox'
0591: short unitsType;
0592: String units = getChainableAttributeNS(filterElement, null,
0593: SVG_FILTER_UNITS_ATTRIBUTE, ctx);
0594: if (units.length() == 0) {
0595: unitsType = OBJECT_BOUNDING_BOX;
0596: } else {
0597: unitsType = parseCoordinateSystem(filterElement,
0598: SVG_FILTER_UNITS_ATTRIBUTE, units, ctx);
0599: }
0600:
0601: // resolve units in the (referenced) filteredElement's
0602: // coordinate system
0603: UnitProcessor.Context uctx = UnitProcessor.createContext(ctx,
0604: filteredElement);
0605:
0606: Rectangle2D region = convertRegion(xStr, yStr, wStr, hStr,
0607: unitsType, filteredNode, uctx);
0608: //
0609: // Account for region padding
0610: //
0611: units = getChainableAttributeNS(filterElement, null,
0612: SVG12Constants.SVG_FILTER_MARGINS_UNITS_ATTRIBUTE, ctx);
0613: if (units.length() == 0) {
0614: // Default to user space on use for margins, not objectBoundingBox
0615: unitsType = USER_SPACE_ON_USE;
0616: } else {
0617: unitsType = parseCoordinateSystem(filterElement,
0618: SVG12Constants.SVG_FILTER_MARGINS_UNITS_ATTRIBUTE,
0619: units, ctx);
0620: }
0621:
0622: // 'batik:dx' attribute - default is 0
0623: String dxStr = filterElement.getAttributeNS(null,
0624: SVG12Constants.SVG_MX_ATRIBUTE);
0625: if (dxStr.length() == 0) {
0626: dxStr = SVG12Constants.SVG_FILTER_MX_DEFAULT_VALUE;
0627: }
0628: // 'batik:dy' attribute - default is 0
0629: String dyStr = filterElement.getAttributeNS(null,
0630: SVG12Constants.SVG_MY_ATRIBUTE);
0631: if (dyStr.length() == 0) {
0632: dyStr = SVG12Constants.SVG_FILTER_MY_DEFAULT_VALUE;
0633: }
0634: // 'batik:dw' attribute - default is 0
0635: String dwStr = filterElement.getAttributeNS(null,
0636: SVG12Constants.SVG_MW_ATRIBUTE);
0637: if (dwStr.length() == 0) {
0638: dwStr = SVG12Constants.SVG_FILTER_MW_DEFAULT_VALUE;
0639: }
0640: // 'batik:dh' attribute - default is 0
0641: String dhStr = filterElement.getAttributeNS(null,
0642: SVG12Constants.SVG_MH_ATRIBUTE);
0643: if (dhStr.length() == 0) {
0644: dhStr = SVG12Constants.SVG_FILTER_MH_DEFAULT_VALUE;
0645: }
0646:
0647: return extendRegion(dxStr, dyStr, dwStr, dhStr, unitsType,
0648: filteredNode, region, uctx);
0649: }
0650:
0651: /**
0652: * Returns a rectangle that represents the region extended by the
0653: * specified differential coordinates.
0654: *
0655: * @param dxStr the differential x coordinate of the region
0656: * @param dyStr the differential y coordinate of the region
0657: * @param dwStr the differential width of the region
0658: * @param dhStr the differential height of the region
0659: * @param unitsType specifies whether the values are in userSpaceOnUse
0660: * or objectBoundingBox space
0661: * @param region the region to extend
0662: * @param uctx the unit processor context (needed for userSpaceOnUse)
0663: */
0664: protected static Rectangle2D extendRegion(String dxStr,
0665: String dyStr, String dwStr, String dhStr, short unitsType,
0666: GraphicsNode filteredNode, Rectangle2D region,
0667: UnitProcessor.Context uctx) {
0668:
0669: float dx, dy, dw, dh;
0670: switch (unitsType) {
0671: case USER_SPACE_ON_USE:
0672: dx = UnitProcessor.svgHorizontalCoordinateToUserSpace(
0673: dxStr, SVG12Constants.SVG_MX_ATRIBUTE, uctx);
0674: dy = UnitProcessor.svgVerticalCoordinateToUserSpace(dyStr,
0675: SVG12Constants.SVG_MY_ATRIBUTE, uctx);
0676: dw = UnitProcessor.svgHorizontalCoordinateToUserSpace(
0677: dwStr, SVG12Constants.SVG_MW_ATRIBUTE, uctx);
0678: dh = UnitProcessor.svgVerticalCoordinateToUserSpace(dhStr,
0679: SVG12Constants.SVG_MH_ATRIBUTE, uctx);
0680: break;
0681: case OBJECT_BOUNDING_BOX:
0682: Rectangle2D bounds = filteredNode.getGeometryBounds();
0683: if (bounds == null) {
0684: dx = dy = dw = dh = 0;
0685: } else {
0686: dx = UnitProcessor
0687: .svgHorizontalCoordinateToObjectBoundingBox(
0688: dxStr, SVG12Constants.SVG_MX_ATRIBUTE,
0689: uctx);
0690: dx *= bounds.getWidth();
0691:
0692: dy = UnitProcessor
0693: .svgVerticalCoordinateToObjectBoundingBox(
0694: dyStr, SVG12Constants.SVG_MY_ATRIBUTE,
0695: uctx);
0696: dy *= bounds.getHeight();
0697:
0698: dw = UnitProcessor
0699: .svgHorizontalCoordinateToObjectBoundingBox(
0700: dwStr, SVG12Constants.SVG_MW_ATRIBUTE,
0701: uctx);
0702: dw *= bounds.getWidth();
0703:
0704: dh = UnitProcessor
0705: .svgVerticalCoordinateToObjectBoundingBox(
0706: dhStr, SVG12Constants.SVG_MH_ATRIBUTE,
0707: uctx);
0708: dh *= bounds.getHeight();
0709: }
0710: break;
0711: default:
0712: throw new IllegalArgumentException("Invalid unit type");
0713: }
0714:
0715: region.setRect(region.getX() + dx, region.getY() + dy, region
0716: .getWidth()
0717: + dw, region.getHeight() + dh);
0718:
0719: return region;
0720: }
0721:
0722: public static Rectangle2D getBaseFilterPrimitiveRegion(
0723: Element filterPrimitiveElement, Element filteredElement,
0724: GraphicsNode filteredNode, Rectangle2D defaultRegion,
0725: BridgeContext ctx) {
0726: String s;
0727:
0728: // resolve units in the (referenced) filteredElement's
0729: // coordinate system
0730: UnitProcessor.Context uctx;
0731: uctx = UnitProcessor.createContext(ctx, filteredElement);
0732:
0733: // 'x' attribute - default is defaultRegion.getX()
0734: double x = defaultRegion.getX();
0735: s = filterPrimitiveElement
0736: .getAttributeNS(null, SVG_X_ATTRIBUTE);
0737: if (s.length() != 0) {
0738: x = UnitProcessor.svgHorizontalCoordinateToUserSpace(s,
0739: SVG_X_ATTRIBUTE, uctx);
0740: }
0741:
0742: // 'y' attribute - default is defaultRegion.getY()
0743: double y = defaultRegion.getY();
0744: s = filterPrimitiveElement
0745: .getAttributeNS(null, SVG_Y_ATTRIBUTE);
0746: if (s.length() != 0) {
0747: y = UnitProcessor.svgVerticalCoordinateToUserSpace(s,
0748: SVG_Y_ATTRIBUTE, uctx);
0749: }
0750:
0751: // 'width' attribute - default is defaultRegion.getWidth()
0752: double w = defaultRegion.getWidth();
0753: s = filterPrimitiveElement.getAttributeNS(null,
0754: SVG_WIDTH_ATTRIBUTE);
0755: if (s.length() != 0) {
0756: w = UnitProcessor.svgHorizontalLengthToUserSpace(s,
0757: SVG_WIDTH_ATTRIBUTE, uctx);
0758: }
0759:
0760: // 'height' attribute - default is defaultRegion.getHeight()
0761: double h = defaultRegion.getHeight();
0762: s = filterPrimitiveElement.getAttributeNS(null,
0763: SVG_HEIGHT_ATTRIBUTE);
0764: if (s.length() != 0) {
0765: h = UnitProcessor.svgVerticalLengthToUserSpace(s,
0766: SVG_HEIGHT_ATTRIBUTE, uctx);
0767: }
0768:
0769: // NOTE: it may be that dx/dy/dw/dh should be applied here
0770: // but since this is mostly aimed at feImage I am
0771: // unsure that it is really needed.
0772: return new Rectangle2D.Double(x, y, w, h);
0773: }
0774:
0775: /**
0776: * Returns the filter primitive region according to the x, y,
0777: * width, height, and filterUnits attributes. Processing the
0778: * element as the top one in the filter chain.
0779: *
0780: * @param filterPrimitiveElement the filter primitive element
0781: * @param filteredElement the element referencing the filter
0782: * @param filteredNode the graphics node to use (objectBoundingBox)
0783: * @param defaultRegion the default region to filter
0784: * @param filterRegion the filter chain region
0785: * @param ctx the bridge context
0786: */
0787: public static Rectangle2D convertFilterPrimitiveRegion(
0788: Element filterPrimitiveElement, Element filteredElement,
0789: GraphicsNode filteredNode, Rectangle2D defaultRegion,
0790: Rectangle2D filterRegion, BridgeContext ctx) {
0791:
0792: // 'primitiveUnits' - default is userSpaceOnUse
0793: Node parentNode = filterPrimitiveElement.getParentNode();
0794: String units = "";
0795: if ((parentNode != null)
0796: && (parentNode.getNodeType() == Node.ELEMENT_NODE)) {
0797: Element parent = (Element) parentNode;
0798: units = getChainableAttributeNS(parent, null,
0799: SVG_PRIMITIVE_UNITS_ATTRIBUTE, ctx);
0800: }
0801: short unitsType;
0802: if (units.length() == 0) {
0803: unitsType = USER_SPACE_ON_USE;
0804: } else {
0805: unitsType = parseCoordinateSystem(filterPrimitiveElement,
0806: SVG_FILTER_UNITS_ATTRIBUTE, units, ctx);
0807: }
0808:
0809: // 'x' attribute - default is defaultRegion.getX()
0810: String xStr = filterPrimitiveElement.getAttributeNS(null,
0811: SVG_X_ATTRIBUTE);
0812:
0813: // 'y' attribute - default is defaultRegion.getY()
0814: String yStr = filterPrimitiveElement.getAttributeNS(null,
0815: SVG_Y_ATTRIBUTE);
0816:
0817: // 'width' attribute - default is defaultRegion.getWidth()
0818: String wStr = filterPrimitiveElement.getAttributeNS(null,
0819: SVG_WIDTH_ATTRIBUTE);
0820:
0821: // 'height' attribute - default is defaultRegion.getHeight()
0822: String hStr = filterPrimitiveElement.getAttributeNS(null,
0823: SVG_HEIGHT_ATTRIBUTE);
0824:
0825: double x = defaultRegion.getX();
0826: double y = defaultRegion.getY();
0827: double w = defaultRegion.getWidth();
0828: double h = defaultRegion.getHeight();
0829:
0830: // resolve units in the (referenced) filteredElement's coordinate system
0831: UnitProcessor.Context uctx = UnitProcessor.createContext(ctx,
0832: filteredElement);
0833:
0834: switch (unitsType) {
0835: case OBJECT_BOUNDING_BOX:
0836: Rectangle2D bounds = filteredNode.getGeometryBounds();
0837: if (bounds != null) {
0838: if (xStr.length() != 0) {
0839: x = UnitProcessor
0840: .svgHorizontalCoordinateToObjectBoundingBox(
0841: xStr, SVG_X_ATTRIBUTE, uctx);
0842: x = bounds.getX() + x * bounds.getWidth();
0843: }
0844: if (yStr.length() != 0) {
0845: y = UnitProcessor
0846: .svgVerticalCoordinateToObjectBoundingBox(
0847: yStr, SVG_Y_ATTRIBUTE, uctx);
0848: y = bounds.getY() + y * bounds.getHeight();
0849: }
0850: if (wStr.length() != 0) {
0851: w = UnitProcessor
0852: .svgHorizontalLengthToObjectBoundingBox(
0853: wStr, SVG_WIDTH_ATTRIBUTE, uctx);
0854: w *= bounds.getWidth();
0855: }
0856: if (hStr.length() != 0) {
0857: h = UnitProcessor
0858: .svgVerticalLengthToObjectBoundingBox(hStr,
0859: SVG_HEIGHT_ATTRIBUTE, uctx);
0860: h *= bounds.getHeight();
0861: }
0862: }
0863: break;
0864: case USER_SPACE_ON_USE:
0865: if (xStr.length() != 0) {
0866: x = UnitProcessor.svgHorizontalCoordinateToUserSpace(
0867: xStr, SVG_X_ATTRIBUTE, uctx);
0868: }
0869: if (yStr.length() != 0) {
0870: y = UnitProcessor.svgVerticalCoordinateToUserSpace(
0871: yStr, SVG_Y_ATTRIBUTE, uctx);
0872: }
0873: if (wStr.length() != 0) {
0874: w = UnitProcessor.svgHorizontalLengthToUserSpace(wStr,
0875: SVG_WIDTH_ATTRIBUTE, uctx);
0876: }
0877: if (hStr.length() != 0) {
0878: h = UnitProcessor.svgVerticalLengthToUserSpace(hStr,
0879: SVG_HEIGHT_ATTRIBUTE, uctx);
0880: }
0881: break;
0882: default:
0883: throw new Error("invalid unitsType:" + unitsType); // can't be reached
0884: }
0885:
0886: Rectangle2D region = new Rectangle2D.Double(x, y, w, h);
0887:
0888: // Now, extend filter primitive region with dx/dy/dw/dh
0889: // settings (Batik extension). The dx/dy/dw/dh padding is
0890: // *always* in userSpaceOnUse space.
0891:
0892: units = "";
0893: if ((parentNode != null)
0894: && (parentNode.getNodeType() == Node.ELEMENT_NODE)) {
0895: Element parent = (Element) parentNode;
0896: units = getChainableAttributeNS(
0897: parent,
0898: null,
0899: SVG12Constants.SVG_FILTER_PRIMITIVE_MARGINS_UNITS_ATTRIBUTE,
0900: ctx);
0901: }
0902:
0903: if (units.length() == 0) {
0904: unitsType = USER_SPACE_ON_USE;
0905: } else {
0906: unitsType = parseCoordinateSystem(
0907: filterPrimitiveElement,
0908: SVG12Constants.SVG_FILTER_PRIMITIVE_MARGINS_UNITS_ATTRIBUTE,
0909: units, ctx);
0910: }
0911:
0912: // 'batik:dx' attribute - default is 0
0913: String dxStr = filterPrimitiveElement.getAttributeNS(null,
0914: SVG12Constants.SVG_MX_ATRIBUTE);
0915: if (dxStr.length() == 0) {
0916: dxStr = SVG12Constants.SVG_FILTER_MX_DEFAULT_VALUE;
0917: }
0918:
0919: // 'batik:dy' attribute - default is 0
0920: String dyStr = filterPrimitiveElement.getAttributeNS(null,
0921: SVG12Constants.SVG_MY_ATRIBUTE);
0922: if (dyStr.length() == 0) {
0923: dyStr = SVG12Constants.SVG_FILTER_MY_DEFAULT_VALUE;
0924: }
0925:
0926: // 'batik:dw' attribute - default is 0
0927: String dwStr = filterPrimitiveElement.getAttributeNS(null,
0928: SVG12Constants.SVG_MW_ATRIBUTE);
0929: if (dwStr.length() == 0) {
0930: dwStr = SVG12Constants.SVG_FILTER_MW_DEFAULT_VALUE;
0931: }
0932:
0933: // 'batik:dh' attribute - default is 0
0934: String dhStr = filterPrimitiveElement.getAttributeNS(null,
0935: SVG12Constants.SVG_MH_ATRIBUTE);
0936: if (dhStr.length() == 0) {
0937: dhStr = SVG12Constants.SVG_FILTER_MH_DEFAULT_VALUE;
0938: }
0939:
0940: region = extendRegion(dxStr, dyStr, dwStr, dhStr, unitsType,
0941: filteredNode, region, uctx);
0942:
0943: Rectangle2D.intersect(region, filterRegion, region);
0944:
0945: return region;
0946: }
0947:
0948: /////////////////////////////////////////////////////////////////////////
0949: // region convenient methods
0950: /////////////////////////////////////////////////////////////////////////
0951:
0952: /** The userSpaceOnUse coordinate system constants. */
0953: public static final short USER_SPACE_ON_USE = 1;
0954:
0955: /** The objectBoundingBox coordinate system constants. */
0956: public static final short OBJECT_BOUNDING_BOX = 2;
0957:
0958: /** The strokeWidth coordinate system constants. */
0959: public static final short STROKE_WIDTH = 3;
0960:
0961: /**
0962: * Parses the specified coordinate system defined by the specified element.
0963: *
0964: * @param e the element that defines the coordinate system
0965: * @param attr the attribute which contains the coordinate system
0966: * @param coordinateSystem the coordinate system to parse
0967: * @param ctx the BridgeContext to use for error information
0968: * @return OBJECT_BOUNDING_BOX | USER_SPACE_ON_USE
0969: */
0970: public static short parseCoordinateSystem(Element e, String attr,
0971: String coordinateSystem, BridgeContext ctx) {
0972: if (SVG_USER_SPACE_ON_USE_VALUE.equals(coordinateSystem)) {
0973: return USER_SPACE_ON_USE;
0974: } else if (SVG_OBJECT_BOUNDING_BOX_VALUE
0975: .equals(coordinateSystem)) {
0976: return OBJECT_BOUNDING_BOX;
0977: } else {
0978: throw new BridgeException(ctx, e,
0979: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] { attr,
0980: coordinateSystem });
0981: }
0982: }
0983:
0984: /**
0985: * Parses the specified coordinate system defined by the specified
0986: * marker element.
0987: *
0988: * @param e the element that defines the coordinate system
0989: * @param attr the attribute which contains the coordinate system
0990: * @param coordinateSystem the coordinate system to parse
0991: * @param ctx the BridgeContext to use for error information
0992: * @return STROKE_WIDTH | USER_SPACE_ON_USE
0993: */
0994: public static short parseMarkerCoordinateSystem(Element e,
0995: String attr, String coordinateSystem, BridgeContext ctx) {
0996: if (SVG_USER_SPACE_ON_USE_VALUE.equals(coordinateSystem)) {
0997: return USER_SPACE_ON_USE;
0998: } else if (SVG_STROKE_WIDTH_VALUE.equals(coordinateSystem)) {
0999: return STROKE_WIDTH;
1000: } else {
1001: throw new BridgeException(ctx, e,
1002: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] { attr,
1003: coordinateSystem });
1004: }
1005: }
1006:
1007: /**
1008: * Returns a rectangle that represents the region defined by the
1009: * specified coordinates.
1010: *
1011: * @param xStr the x coordinate of the region
1012: * @param yStr the y coordinate of the region
1013: * @param wStr the width of the region
1014: * @param hStr the height of the region
1015: * @param targetNode the graphics node (needed for objectBoundingBox)
1016: * @param uctx the unit processor context (needed for userSpaceOnUse)
1017: */
1018: protected static Rectangle2D convertRegion(String xStr,
1019: String yStr, String wStr, String hStr, short unitsType,
1020: GraphicsNode targetNode, UnitProcessor.Context uctx) {
1021:
1022: // construct the mask region in the appropriate coordinate system
1023: double x, y, w, h;
1024: switch (unitsType) {
1025: case OBJECT_BOUNDING_BOX:
1026: x = UnitProcessor
1027: .svgHorizontalCoordinateToObjectBoundingBox(xStr,
1028: SVG_X_ATTRIBUTE, uctx);
1029: y = UnitProcessor.svgVerticalCoordinateToObjectBoundingBox(
1030: yStr, SVG_Y_ATTRIBUTE, uctx);
1031: w = UnitProcessor.svgHorizontalLengthToObjectBoundingBox(
1032: wStr, SVG_WIDTH_ATTRIBUTE, uctx);
1033: h = UnitProcessor.svgVerticalLengthToObjectBoundingBox(
1034: hStr, SVG_HEIGHT_ATTRIBUTE, uctx);
1035:
1036: Rectangle2D bounds = targetNode.getGeometryBounds();
1037: if (bounds != null) {
1038: x = bounds.getX() + x * bounds.getWidth();
1039: y = bounds.getY() + y * bounds.getHeight();
1040: w *= bounds.getWidth();
1041: h *= bounds.getHeight();
1042: } else {
1043: x = y = w = h = 0;
1044: }
1045: break;
1046: case USER_SPACE_ON_USE:
1047: x = UnitProcessor.svgHorizontalCoordinateToUserSpace(xStr,
1048: SVG_X_ATTRIBUTE, uctx);
1049: y = UnitProcessor.svgVerticalCoordinateToUserSpace(yStr,
1050: SVG_Y_ATTRIBUTE, uctx);
1051: w = UnitProcessor.svgHorizontalLengthToUserSpace(wStr,
1052: SVG_WIDTH_ATTRIBUTE, uctx);
1053: h = UnitProcessor.svgVerticalLengthToUserSpace(hStr,
1054: SVG_HEIGHT_ATTRIBUTE, uctx);
1055: break;
1056: default:
1057: throw new Error("invalid unitsType:" + unitsType); // can't be reached
1058: }
1059: return new Rectangle2D.Double(x, y, w, h);
1060: }
1061:
1062: /////////////////////////////////////////////////////////////////////////
1063: // coordinate system and transformation support methods
1064: /////////////////////////////////////////////////////////////////////////
1065:
1066: /**
1067: * Returns an AffineTransform according to the specified parameters.
1068: *
1069: * @param e the element that defines the transform
1070: * @param attr the name of the attribute that represents the transform
1071: * @param transform the transform to parse
1072: * @param ctx the BridgeContext to use for error information
1073: */
1074: public static AffineTransform convertTransform(Element e,
1075: String attr, String transform, BridgeContext ctx) {
1076: try {
1077: return AWTTransformProducer
1078: .createAffineTransform(transform);
1079: } catch (ParseException pEx) {
1080: throw new BridgeException(ctx, e, pEx,
1081: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] { attr,
1082: transform, pEx });
1083: }
1084: }
1085:
1086: /**
1087: * Returns an AffineTransform to move to the objectBoundingBox
1088: * coordinate system.
1089: *
1090: * @param Tx the original transformation
1091: * @param node the graphics node that defines the coordinate
1092: * system to move into
1093: */
1094: public static AffineTransform toObjectBBox(AffineTransform Tx,
1095: GraphicsNode node) {
1096:
1097: AffineTransform Mx = new AffineTransform();
1098: Rectangle2D bounds = node.getGeometryBounds();
1099: if (bounds != null) {
1100: Mx.translate(bounds.getX(), bounds.getY());
1101: Mx.scale(bounds.getWidth(), bounds.getHeight());
1102: }
1103: Mx.concatenate(Tx);
1104: return Mx;
1105: }
1106:
1107: /**
1108: * Returns the specified a Rectangle2D move to the objectBoundingBox
1109: * coordinate system of the specified graphics node.
1110: *
1111: * @param r the original Rectangle2D
1112: * @param node the graphics node that defines the coordinate
1113: * system to move into
1114: */
1115: public static Rectangle2D toObjectBBox(Rectangle2D r,
1116: GraphicsNode node) {
1117:
1118: Rectangle2D bounds = node.getGeometryBounds();
1119: if (bounds != null) {
1120: return new Rectangle2D.Double(bounds.getX() + r.getX()
1121: * bounds.getWidth(), bounds.getY() + r.getY()
1122: * bounds.getHeight(), r.getWidth()
1123: * bounds.getWidth(), r.getHeight()
1124: * bounds.getHeight());
1125: } else {
1126: return new Rectangle2D.Double();
1127: }
1128: }
1129:
1130: /**
1131: * Returns the value of the 'snapshotTime' attribute on the specified
1132: * element as a float, or <code>0f</code> if the attribute is missing
1133: * or given as <code>"none"</code>.
1134: *
1135: * @param e the element from which to retrieve the 'snapshotTime' attribute
1136: * @param ctx the BridgeContext to use for error information
1137: */
1138: public static float convertSnapshotTime(Element e, BridgeContext ctx) {
1139: if (!e.hasAttributeNS(null, SVG_SNAPSHOT_TIME_ATTRIBUTE)) {
1140: return 0f;
1141: }
1142: String t = e.getAttributeNS(null, SVG_SNAPSHOT_TIME_ATTRIBUTE);
1143: if (t.equals(SVG_NONE_VALUE)) {
1144: return 0f;
1145: }
1146:
1147: class Handler implements ClockHandler {
1148: float time;
1149:
1150: public void clockValue(float t) {
1151: time = t;
1152: }
1153: }
1154: ClockParser p = new ClockParser(false);
1155: Handler h = new Handler();
1156: p.setClockHandler(h);
1157: try {
1158: p.parse(t);
1159: } catch (ParseException pEx) {
1160: throw new BridgeException(null, e, pEx,
1161: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
1162: SVG_SNAPSHOT_TIME_ATTRIBUTE, t, pEx });
1163: }
1164: return h.time;
1165: }
1166: }
|