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.Color;
022: import java.awt.Paint;
023: import java.awt.geom.AffineTransform;
024: import java.util.Iterator;
025: import java.util.LinkedList;
026: import java.util.List;
027:
028: import org.apache.batik.dom.AbstractNode;
029: import org.apache.batik.dom.util.XLinkSupport;
030: import org.apache.batik.ext.awt.MultipleGradientPaint;
031: import org.apache.batik.gvt.GraphicsNode;
032: import org.apache.batik.util.ParsedURL;
033:
034: import org.w3c.dom.Element;
035: import org.w3c.dom.Node;
036:
037: /**
038: * Bridge class for vending gradients.
039: *
040: * @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a>
041: * @version $Id: AbstractSVGGradientElementBridge.java 501922 2007-01-31 17:47:47Z dvholten $
042: */
043: public abstract class AbstractSVGGradientElementBridge extends
044: AnimatableGenericSVGBridge implements PaintBridge,
045: ErrorConstants {
046:
047: /**
048: * Constructs a new AbstractSVGGradientElementBridge.
049: */
050: protected AbstractSVGGradientElementBridge() {
051: }
052:
053: /**
054: * Creates a <tt>Paint</tt> according to the specified parameters.
055: *
056: * @param ctx the bridge context to use
057: * @param paintElement the element that defines a Paint
058: * @param paintedElement the element referencing the paint
059: * @param paintedNode the graphics node on which the Paint will be applied
060: * @param opacity the opacity of the Paint to create
061: */
062: public Paint createPaint(BridgeContext ctx, Element paintElement,
063: Element paintedElement, GraphicsNode paintedNode,
064: float opacity) {
065:
066: String s;
067:
068: // stop elements
069: List stops = extractStop(paintElement, opacity, ctx);
070: // if no stops are defined, painting is the same as 'none'
071: if (stops == null) {
072: return null;
073: }
074: int stopLength = stops.size();
075: // if one stops is defined, painting is the same as a single color
076: if (stopLength == 1) {
077: return ((Stop) stops.get(0)).color;
078: }
079: float[] offsets = new float[stopLength];
080: Color[] colors = new Color[stopLength];
081: Iterator iter = stops.iterator();
082: for (int i = 0; iter.hasNext(); ++i) {
083: Stop stop = (Stop) iter.next();
084: offsets[i] = stop.offset;
085: colors[i] = stop.color;
086: }
087:
088: // 'spreadMethod' attribute - default is pad
089: MultipleGradientPaint.CycleMethodEnum spreadMethod = MultipleGradientPaint.NO_CYCLE;
090: s = SVGUtilities.getChainableAttributeNS(paintElement, null,
091: SVG_SPREAD_METHOD_ATTRIBUTE, ctx);
092: if (s.length() != 0) {
093: spreadMethod = convertSpreadMethod(paintElement, s, ctx);
094: }
095:
096: // 'color-interpolation' CSS property
097: MultipleGradientPaint.ColorSpaceEnum colorSpace = CSSUtilities
098: .convertColorInterpolation(paintElement);
099:
100: // 'gradientTransform' attribute - default is an Identity matrix
101: AffineTransform transform;
102: s = SVGUtilities.getChainableAttributeNS(paintElement, null,
103: SVG_GRADIENT_TRANSFORM_ATTRIBUTE, ctx);
104: if (s.length() != 0) {
105: transform = SVGUtilities.convertTransform(paintElement,
106: SVG_GRADIENT_TRANSFORM_ATTRIBUTE, s, ctx);
107: } else {
108: transform = new AffineTransform();
109: }
110:
111: Paint paint = buildGradient(paintElement, paintedElement,
112: paintedNode, spreadMethod, colorSpace, transform,
113: colors, offsets, ctx);
114: return paint;
115: }
116:
117: /**
118: * Builds a concrete gradient according to the specified parameters.
119: *
120: * @param paintElement the element that defines a Paint
121: * @param paintedElement the element referencing the paint
122: * @param paintedNode the graphics node on which the Paint will be applied
123: * @param spreadMethod the spread method
124: * @param colorSpace the color space (sRGB | LinearRGB)
125: * @param transform the gradient transform
126: * @param colors the colors of the gradient
127: * @param offsets the offsets
128: * @param ctx the bridge context to use
129: */
130: protected abstract Paint buildGradient(Element paintElement,
131: Element paintedElement, GraphicsNode paintedNode,
132: MultipleGradientPaint.CycleMethodEnum spreadMethod,
133: MultipleGradientPaint.ColorSpaceEnum colorSpace,
134: AffineTransform transform, Color[] colors, float[] offsets,
135: BridgeContext ctx);
136:
137: // convenient methods
138:
139: /**
140: * Converts the spreadMethod attribute.
141: *
142: * @param paintElement the paint Element with a spreadMethod
143: * @param s the spread method
144: * @param ctx the BridgeContext to use for error information
145: */
146: protected static MultipleGradientPaint.CycleMethodEnum convertSpreadMethod(
147: Element paintElement, String s, BridgeContext ctx) {
148: if (SVG_REPEAT_VALUE.equals(s)) {
149: return MultipleGradientPaint.REPEAT;
150: }
151: if (SVG_REFLECT_VALUE.equals(s)) {
152: return MultipleGradientPaint.REFLECT;
153: }
154: if (SVG_PAD_VALUE.equals(s)) {
155: return MultipleGradientPaint.NO_CYCLE;
156: }
157: throw new BridgeException(ctx, paintElement,
158: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
159: SVG_SPREAD_METHOD_ATTRIBUTE, s });
160: }
161:
162: /**
163: * Returns the stops elements of the specified gradient
164: * element. Stops can be children of the gradients or defined on
165: * one of its 'ancestor' (linked with the xlink:href attribute).
166: *
167: * @param paintElement the gradient element
168: * @param opacity the opacity
169: * @param ctx the bridge context to use
170: */
171: protected static List extractStop(Element paintElement,
172: float opacity, BridgeContext ctx) {
173:
174: List refs = new LinkedList();
175: for (;;) {
176: List stops = extractLocalStop(paintElement, opacity, ctx);
177: if (stops != null) {
178: return stops; // stop elements found, exit
179: }
180: String uri = XLinkSupport.getXLinkHref(paintElement);
181: if (uri.length() == 0) {
182: return null; // no xlink:href found, exit
183: }
184: // check if there is circular dependencies
185: String baseURI = ((AbstractNode) paintElement).getBaseURI();
186: ParsedURL purl = new ParsedURL(baseURI, uri);
187:
188: if (contains(refs, purl)) {
189: throw new BridgeException(ctx, paintElement,
190: ERR_XLINK_HREF_CIRCULAR_DEPENDENCIES,
191: new Object[] { uri });
192: }
193: refs.add(purl);
194: paintElement = ctx.getReferencedElement(paintElement, uri);
195: }
196: }
197:
198: /**
199: * Returns a list of <tt>Stop</tt> elements, children of the
200: * specified paintElement can have or null if any.
201: *
202: * @param gradientElement the paint element
203: * @param opacity the opacity
204: * @param ctx the bridge context
205: */
206: protected static List extractLocalStop(Element gradientElement,
207: float opacity, BridgeContext ctx) {
208: LinkedList stops = null;
209: Stop previous = null;
210: for (Node n = gradientElement.getFirstChild(); n != null; n = n
211: .getNextSibling()) {
212:
213: if ((n.getNodeType() != Node.ELEMENT_NODE)) {
214: continue;
215: }
216:
217: Element e = (Element) n;
218: Bridge bridge = ctx.getBridge(e);
219: if (bridge == null
220: || !(bridge instanceof SVGStopElementBridge)) {
221: continue;
222: }
223: Stop stop = ((SVGStopElementBridge) bridge).createStop(ctx,
224: gradientElement, e, opacity);
225: if (stops == null) {
226: stops = new LinkedList();
227: }
228: if (previous != null) {
229: if (stop.offset < previous.offset) {
230: stop.offset = previous.offset;
231: }
232: }
233: stops.add(stop);
234: previous = stop;
235: }
236: return stops;
237: }
238:
239: /**
240: * Returns true if the specified list of URLs contains the specified url.
241: *
242: * @param urls the list of URLs
243: * @param key the url to search for
244: */
245: private static boolean contains(List urls, ParsedURL key) {
246: Iterator iter = urls.iterator();
247: while (iter.hasNext()) {
248: if (key.equals(iter.next()))
249: return true;
250: }
251: return false;
252: }
253:
254: /**
255: * This class represents a gradient <stop> element.
256: */
257: public static class Stop {
258:
259: /** The stop color. */
260: public Color color;
261: /** The stop offset. */
262: public float offset;
263:
264: /**
265: * Constructs a new stop definition.
266: *
267: * @param color the stop color
268: * @param offset the stop offset
269: */
270: public Stop(Color color, float offset) {
271: this .color = color;
272: this .offset = offset;
273: }
274: }
275:
276: /**
277: * Bridge class for the gradient <stop> element.
278: */
279: public static class SVGStopElementBridge extends
280: AnimatableGenericSVGBridge implements Bridge {
281:
282: /**
283: * Returns 'stop'.
284: */
285: public String getLocalName() {
286: return SVG_STOP_TAG;
287: }
288:
289: /**
290: * Creates a <tt>Stop</tt> according to the specified parameters.
291: *
292: * @param ctx the bridge context to use
293: * @param gradientElement the gradient element
294: * @param stopElement the stop element
295: * @param opacity an additional opacity of the stop color
296: */
297: public Stop createStop(BridgeContext ctx,
298: Element gradientElement, Element stopElement,
299: float opacity) {
300:
301: String s = stopElement.getAttributeNS(null,
302: SVG_OFFSET_ATTRIBUTE);
303: if (s.length() == 0) {
304: throw new BridgeException(ctx, stopElement,
305: ERR_ATTRIBUTE_MISSING,
306: new Object[] { SVG_OFFSET_ATTRIBUTE });
307: }
308: float offset;
309: try {
310: offset = SVGUtilities.convertRatio(s);
311: } catch (NumberFormatException nfEx) {
312: throw new BridgeException(ctx, stopElement, nfEx,
313: ERR_ATTRIBUTE_VALUE_MALFORMED, new Object[] {
314: SVG_OFFSET_ATTRIBUTE, s, nfEx });
315: }
316: Color color = CSSUtilities.convertStopColor(stopElement,
317: opacity, ctx);
318:
319: return new Stop(color, offset);
320: }
321: }
322: }
|