001: /*
002:
003: Licensed to the Apache Software Foundation (ASF) under one or more
004: contributor license agreements. See the NOTICE file distributed with
005: this work for additional information regarding copyright ownership.
006: The ASF licenses this file to You under the Apache License, Version 2.0
007: (the "License"); you may not use this file except in compliance with
008: the License. You may obtain a copy of the License at
009:
010: http://www.apache.org/licenses/LICENSE-2.0
011:
012: Unless required by applicable law or agreed to in writing, software
013: distributed under the License is distributed on an "AS IS" BASIS,
014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: See the License for the specific language governing permissions and
016: limitations under the License.
017:
018: */
019: package org.apache.batik.bridge;
020:
021: import java.awt.Graphics2D;
022: import java.awt.Paint;
023: import java.awt.Shape;
024: import java.awt.geom.AffineTransform;
025: import java.awt.geom.Rectangle2D;
026: import java.util.Iterator;
027: import java.util.LinkedList;
028: import java.util.List;
029:
030: import org.apache.batik.dom.svg.SVGOMDocument;
031: import org.apache.batik.dom.util.XLinkSupport;
032: import org.apache.batik.ext.awt.image.ConcreteComponentTransferFunction;
033: import org.apache.batik.ext.awt.image.renderable.ComponentTransferRable8Bit;
034: import org.apache.batik.ext.awt.image.renderable.Filter;
035: import org.apache.batik.gvt.AbstractGraphicsNode;
036: import org.apache.batik.gvt.RootGraphicsNode;
037: import org.apache.batik.gvt.GraphicsNode;
038: import org.apache.batik.gvt.PatternPaint;
039: import org.apache.batik.util.ParsedURL;
040: import org.w3c.dom.Element;
041: import org.w3c.dom.Node;
042:
043: /**
044: * Bridge class for the <pattern> element.
045: *
046: * @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a>
047: * @version $Id: SVGPatternElementBridge.java 475477 2006-11-15 22:44:28Z cam $
048: */
049: public class SVGPatternElementBridge extends AnimatableGenericSVGBridge
050: implements PaintBridge, ErrorConstants {
051:
052: /**
053: * Constructs a new SVGPatternElementBridge.
054: */
055: public SVGPatternElementBridge() {
056: }
057:
058: /**
059: * Returns 'pattern'.
060: */
061: public String getLocalName() {
062: return SVG_PATTERN_TAG;
063: }
064:
065: /**
066: * Creates a <tt>Paint</tt> according to the specified parameters.
067: *
068: * @param ctx the bridge context to use
069: * @param patternElement the pattern element that defines a Paint
070: * @param paintedElement the element referencing the paint
071: * @param paintedNode the graphics node on which the Paint will be applied
072: * @param opacity the opacity of the Paint to create
073: */
074: public Paint createPaint(BridgeContext ctx, Element patternElement,
075: Element paintedElement, GraphicsNode paintedNode,
076: float opacity) {
077:
078: // extract pattern content
079: RootGraphicsNode patternContentNode;
080: patternContentNode = (RootGraphicsNode) ctx
081: .getElementData(patternElement);
082:
083: if (patternContentNode == null) {
084: patternContentNode = extractPatternContent(patternElement,
085: ctx);
086: ctx.setElementData(patternElement, patternContentNode);
087: }
088: if (patternContentNode == null) {
089: return null; // no content means no paint
090: }
091:
092: // get pattern region using 'patternUnits'. Pattern region is
093: // in tile pace.
094: Rectangle2D patternRegion = SVGUtilities.convertPatternRegion(
095: patternElement, paintedElement, paintedNode, ctx);
096:
097: String s;
098:
099: // 'patternTransform' attribute - default is an Identity matrix
100: AffineTransform patternTransform;
101: s = SVGUtilities.getChainableAttributeNS(patternElement, null,
102: SVG_PATTERN_TRANSFORM_ATTRIBUTE, ctx);
103: if (s.length() != 0) {
104: patternTransform = SVGUtilities.convertTransform(
105: patternElement, SVG_PATTERN_TRANSFORM_ATTRIBUTE, s,
106: ctx);
107: } else {
108: patternTransform = new AffineTransform();
109: }
110:
111: // 'overflow' on the pattern element
112: boolean overflowIsHidden = CSSUtilities
113: .convertOverflow(patternElement);
114:
115: // 'patternContentUnits' - default is userSpaceOnUse
116: short contentCoordSystem;
117: s = SVGUtilities.getChainableAttributeNS(patternElement, null,
118: SVG_PATTERN_CONTENT_UNITS_ATTRIBUTE, ctx);
119: if (s.length() == 0) {
120: contentCoordSystem = SVGUtilities.USER_SPACE_ON_USE;
121: } else {
122: contentCoordSystem = SVGUtilities.parseCoordinateSystem(
123: patternElement,
124: SVG_PATTERN_CONTENT_UNITS_ATTRIBUTE, s, ctx);
125: }
126:
127: // Compute a transform according to viewBox, preserveAspectRatio
128: // and patternContentUnits and the pattern transform attribute.
129: //
130: // The stack of transforms is:
131: //
132: // +-------------------------------+
133: // | viewPortTranslation |
134: // +-------------------------------+
135: // | preserveAspectRatioTransform |
136: // +-------------------------------+
137: // + patternContentUnitsTransform |
138: // +-------------------------------+
139: //
140: // where:
141: // - viewPortTranslation is the transform that translate to
142: // the viewPort's origin.
143: // - preserveAspectRatioTransform is the transformed implied by the
144: // preserveAspectRatio attribute.
145: // - patternContentUnitsTransform is the transform implied by the
146: // patternContentUnits attribute.
147: //
148: // Note that there is an additional transform from the tiling
149: // space to the user space (patternTransform) that is passed
150: // separately to the PatternPaintContext.
151: //
152: AffineTransform patternContentTransform = new AffineTransform();
153:
154: //
155: // Process viewPortTranslation
156: //
157: patternContentTransform.translate(patternRegion.getX(),
158: patternRegion.getY());
159:
160: //
161: // Process preserveAspectRatioTransform
162: //
163:
164: // 'viewBox' attribute
165: String viewBoxStr = SVGUtilities.getChainableAttributeNS(
166: patternElement, null, SVG_VIEW_BOX_ATTRIBUTE, ctx);
167:
168: if (viewBoxStr.length() > 0) {
169: // There is a viewBox attribute. Then, take
170: // preserveAspectRatio into account.
171: String aspectRatioStr = SVGUtilities
172: .getChainableAttributeNS(patternElement, null,
173: SVG_PRESERVE_ASPECT_RATIO_ATTRIBUTE, ctx);
174: float w = (float) patternRegion.getWidth();
175: float h = (float) patternRegion.getHeight();
176: AffineTransform preserveAspectRatioTransform = ViewBox
177: .getPreserveAspectRatioTransform(patternElement,
178: viewBoxStr, aspectRatioStr, w, h, ctx);
179:
180: patternContentTransform
181: .concatenate(preserveAspectRatioTransform);
182: } else {
183: //
184: // Process patternContentUnitsTransform
185: //
186: if (contentCoordSystem == SVGUtilities.OBJECT_BOUNDING_BOX) {
187: AffineTransform patternContentUnitsTransform = new AffineTransform();
188: Rectangle2D objectBoundingBox = paintedNode
189: .getGeometryBounds();
190: patternContentUnitsTransform.translate(
191: objectBoundingBox.getX(), objectBoundingBox
192: .getY());
193:
194: patternContentUnitsTransform.scale(objectBoundingBox
195: .getWidth(), objectBoundingBox.getHeight());
196:
197: patternContentTransform
198: .concatenate(patternContentUnitsTransform);
199: }
200: }
201:
202: //
203: // Apply transform
204: //
205: // RootGraphicsNode gn = new RootGraphicsNode();
206: // gn.getChildren().add(patternContentNode);
207: GraphicsNode gn = new PatternGraphicsNode(patternContentNode);
208:
209: gn.setTransform(patternContentTransform);
210:
211: // take the opacity into account. opacity is implemented by a Filter
212: if (opacity != 1) {
213: Filter filter = gn.getGraphicsNodeRable(true);
214: filter = new ComponentTransferRable8Bit(filter,
215: ConcreteComponentTransferFunction
216: .getLinearTransfer(opacity, 0), //alpha
217: ConcreteComponentTransferFunction
218: .getIdentityTransfer(), //Red
219: ConcreteComponentTransferFunction
220: .getIdentityTransfer(), //Grn
221: ConcreteComponentTransferFunction
222: .getIdentityTransfer());//Blu
223: gn.setFilter(filter);
224: }
225:
226: return new PatternPaint(gn, patternRegion, !overflowIsHidden,
227: patternTransform);
228:
229: }
230:
231: /**
232: * Returns the content of the specified pattern element. The
233: * content of the pattern can be specified as children of the
234: * patternElement or children of one of its 'ancestor' (linked with
235: * the xlink:href attribute).
236: *
237: * @param patternElement the gradient element
238: * @param ctx the bridge context to use
239: */
240: protected static RootGraphicsNode extractPatternContent(
241: Element patternElement, BridgeContext ctx) {
242:
243: List refs = new LinkedList();
244: for (;;) {
245: RootGraphicsNode content = extractLocalPatternContent(
246: patternElement, ctx);
247: if (content != null) {
248: return content; // pattern content found, exit
249: }
250: String uri = XLinkSupport.getXLinkHref(patternElement);
251: if (uri.length() == 0) {
252: return null; // no xlink:href found, exit
253: }
254: // check if there is circular dependencies
255: SVGOMDocument doc = (SVGOMDocument) patternElement
256: .getOwnerDocument();
257: ParsedURL purl = new ParsedURL(doc.getURL(), uri);
258: if (!purl.complete())
259: throw new BridgeException(ctx, patternElement,
260: ERR_URI_MALFORMED, new Object[] { uri });
261:
262: if (contains(refs, purl)) {
263: throw new BridgeException(ctx, patternElement,
264: ERR_XLINK_HREF_CIRCULAR_DEPENDENCIES,
265: new Object[] { uri });
266: }
267: refs.add(purl);
268: patternElement = ctx.getReferencedElement(patternElement,
269: uri);
270: }
271: }
272:
273: /**
274: * Returns the content of the specified pattern element or null if any.
275: *
276: * @param e the pattern element
277: * @param ctx the bridge context
278: */
279: protected static RootGraphicsNode extractLocalPatternContent(
280: Element e, BridgeContext ctx) {
281:
282: GVTBuilder builder = ctx.getGVTBuilder();
283: RootGraphicsNode content = null;
284: for (Node n = e.getFirstChild(); n != null; n = n
285: .getNextSibling()) {
286: // check if the Node is valid
287: if (n.getNodeType() != Node.ELEMENT_NODE) {
288: continue;
289: }
290:
291: GraphicsNode gn = builder.build(ctx, (Element) n);
292: // check if a GraphicsNode has been created
293: if (gn != null) {
294: // lazy instantation of the grouping element.
295: if (content == null) {
296: content = new RootGraphicsNode();
297: }
298: content.getChildren().add(gn);
299: }
300: }
301: return content;
302: }
303:
304: /**
305: * Returns true if the specified list of ParsedURLs contains the
306: * specified url.
307: *
308: * @param urls the list of ParsedURLs
309: * @param key the url to search for */
310: private static boolean contains(List urls, ParsedURL key) {
311: Iterator iter = urls.iterator();
312: while (iter.hasNext()) {
313: if (key.equals(iter.next()))
314: return true;
315: }
316: return false;
317: }
318:
319: public static class PatternGraphicsNode extends
320: AbstractGraphicsNode {
321: GraphicsNode pcn;
322: Rectangle2D pBounds;
323: Rectangle2D gBounds;
324: Rectangle2D sBounds;
325: Shape oShape;
326:
327: public PatternGraphicsNode(GraphicsNode gn) {
328: this .pcn = gn;
329: }
330:
331: public void primitivePaint(Graphics2D g2d) {
332: pcn.paint(g2d);
333: }
334:
335: public Rectangle2D getPrimitiveBounds() {
336: if (pBounds != null)
337: return pBounds;
338: pBounds = pcn.getTransformedBounds(IDENTITY);
339: return pBounds;
340: }
341:
342: public Rectangle2D getGeometryBounds() {
343: if (gBounds != null)
344: return gBounds;
345: gBounds = pcn.getTransformedGeometryBounds(IDENTITY);
346: return gBounds;
347: }
348:
349: public Rectangle2D getSensitiveBounds() {
350: if (sBounds != null)
351: return sBounds;
352: sBounds = pcn.getTransformedSensitiveBounds(IDENTITY);
353: return sBounds;
354: }
355:
356: public Shape getOutline() {
357: if (oShape != null)
358: return oShape;
359: oShape = pcn.getOutline();
360: AffineTransform tr = pcn.getTransform();
361: if (tr != null)
362: oShape = tr.createTransformedShape(oShape);
363: return oShape;
364: }
365:
366: protected void invalidateGeometryCache() {
367: pBounds = null;
368: gBounds = null;
369: sBounds = null;
370: oShape = null;
371: super.invalidateGeometryCache();
372: }
373:
374: }
375: }
|