Source code: org/apache/batik/bridge/AbstractSVGGradientElementBridge.java
1 /*
2
3 Copyright 2001-2003 The Apache Software Foundation
4
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16
17 */
18 package org.apache.batik.bridge;
19
20 import java.awt.Color;
21 import java.awt.Paint;
22 import java.awt.geom.AffineTransform;
23 import java.util.Iterator;
24 import java.util.LinkedList;
25 import java.util.List;
26
27 import org.apache.batik.dom.svg.SVGOMDocument;
28 import org.apache.batik.dom.util.XLinkSupport;
29 import org.apache.batik.ext.awt.MultipleGradientPaint;
30 import org.apache.batik.gvt.GraphicsNode;
31 import org.apache.batik.util.ParsedURL;
32 import org.w3c.dom.Element;
33 import org.w3c.dom.Node;
34
35 /**
36 * Bridge class for vending gradients.
37 *
38 * @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a>
39 * @version $Id: AbstractSVGGradientElementBridge.java,v 1.12 2004/08/18 07:12:30 vhardy Exp $
40 */
41 public abstract class AbstractSVGGradientElementBridge extends AbstractSVGBridge
42 implements PaintBridge, ErrorConstants {
43
44 /**
45 * Constructs a new AbstractSVGGradientElementBridge.
46 */
47 protected AbstractSVGGradientElementBridge() {}
48
49 /**
50 * Creates a <tt>Paint</tt> according to the specified parameters.
51 *
52 * @param ctx the bridge context to use
53 * @param paintElement the element that defines a Paint
54 * @param paintedElement the element referencing the paint
55 * @param paintedNode the graphics node on which the Paint will be applied
56 * @param opacity the opacity of the Paint to create
57 */
58 public Paint createPaint(BridgeContext ctx,
59 Element paintElement,
60 Element paintedElement,
61 GraphicsNode paintedNode,
62 float opacity) {
63
64 String s;
65
66 // stop elements
67 List stops = extractStop(paintElement, opacity, ctx);
68 // if no stops are defined, painting is the same as 'none'
69 if (stops == null) {
70 return null;
71 }
72 int stopLength = stops.size();
73 // if one stops is defined, painting is the same as a single color
74 if (stopLength == 1) {
75 return ((Stop)stops.get(0)).color;
76 }
77 float [] offsets = new float[stopLength];
78 Color [] colors = new Color[stopLength];
79 Iterator iter = stops.iterator();
80 for (int i=0; iter.hasNext(); ++i) {
81 Stop stop = (Stop)iter.next();
82 offsets[i] = stop.offset;
83 colors[i] = stop.color;
84 }
85
86 // 'spreadMethod' attribute - default is pad
87 MultipleGradientPaint.CycleMethodEnum spreadMethod
88 = MultipleGradientPaint.NO_CYCLE;
89 s = SVGUtilities.getChainableAttributeNS
90 (paintElement, null, SVG_SPREAD_METHOD_ATTRIBUTE, ctx);
91 if (s.length() != 0) {
92 spreadMethod = convertSpreadMethod(paintElement, s);
93 }
94
95 // 'color-interpolation' CSS property
96 MultipleGradientPaint.ColorSpaceEnum colorSpace
97 = CSSUtilities.convertColorInterpolation(paintElement);
98
99 // 'gradientTransform' attribute - default is an Identity matrix
100 AffineTransform transform;
101 s = SVGUtilities.getChainableAttributeNS
102 (paintElement, null, SVG_GRADIENT_TRANSFORM_ATTRIBUTE, ctx);
103 if (s.length() != 0) {
104 transform = SVGUtilities.convertTransform
105 (paintElement, SVG_GRADIENT_TRANSFORM_ATTRIBUTE, s);
106 } else {
107 transform = new AffineTransform();
108 }
109
110 Paint paint = buildGradient(paintElement,
111 paintedElement,
112 paintedNode,
113 spreadMethod,
114 colorSpace,
115 transform,
116 colors,
117 offsets,
118 ctx);
119 return paint;
120 }
121
122 /**
123 * Builds a concrete gradient according to the specified parameters.
124 *
125 * @param paintElement the element that defines a Paint
126 * @param paintedElement the element referencing the paint
127 * @param paintedNode the graphics node on which the Paint will be applied
128 * @param spreadMethod the spread method
129 * @param colorSpace the color space (sRGB | LinearRGB)
130 * @param transform the gradient transform
131 * @param colors the colors of the gradient
132 * @param offsets the offsets
133 * @param ctx the bridge context to use
134 */
135 protected abstract
136 Paint buildGradient(Element paintElement,
137 Element paintedElement,
138 GraphicsNode paintedNode,
139 MultipleGradientPaint.CycleMethodEnum spreadMethod,
140 MultipleGradientPaint.ColorSpaceEnum colorSpace,
141 AffineTransform transform,
142 Color [] colors,
143 float [] offsets,
144 BridgeContext ctx);
145
146 // convenient methods
147
148 /**
149 * Converts the spreadMethod attribute.
150 *
151 * @param paintElement the paint Element with a spreadMethod
152 * @param s the spread method
153 */
154 protected static MultipleGradientPaint.CycleMethodEnum convertSpreadMethod
155 (Element paintElement, String s) {
156 if (SVG_REPEAT_VALUE.equals(s)) {
157 return MultipleGradientPaint.REPEAT;
158 }
159 if (SVG_REFLECT_VALUE.equals(s)) {
160 return MultipleGradientPaint.REFLECT;
161 }
162 if (SVG_PAD_VALUE.equals(s)) {
163 return MultipleGradientPaint.NO_CYCLE;
164 }
165 throw new BridgeException
166 (paintElement, ERR_ATTRIBUTE_VALUE_MALFORMED,
167 new Object[] {SVG_SPREAD_METHOD_ATTRIBUTE, s});
168 }
169
170 /**
171 * Returns the stops elements of the specified gradient
172 * element. Stops can be children of the gradients or defined on
173 * one of its 'ancestor' (linked with the xlink:href attribute).
174 *
175 * @param paintElement the gradient element
176 * @param opacity the opacity
177 * @param ctx the bridge context to use
178 */
179 protected static List extractStop(Element paintElement,
180 float opacity,
181 BridgeContext ctx) {
182
183 List refs = new LinkedList();
184 for (;;) {
185 List stops = extractLocalStop(paintElement, opacity, ctx);
186 if (stops != null) {
187 return stops; // stop elements found, exit
188 }
189 String uri = XLinkSupport.getXLinkHref(paintElement);
190 if (uri.length() == 0) {
191 return null; // no xlink:href found, exit
192 }
193 // check if there is circular dependencies
194 SVGOMDocument doc = (SVGOMDocument)paintElement.getOwnerDocument();
195 ParsedURL purl = new ParsedURL(doc.getURL(), uri);
196 if (!purl.complete())
197 throw new BridgeException(paintElement,
198 ERR_URI_MALFORMED,
199 new Object[] {uri});
200
201 if (contains(refs, purl)) {
202 throw new BridgeException(paintElement,
203 ERR_XLINK_HREF_CIRCULAR_DEPENDENCIES,
204 new Object[] {uri});
205 }
206 refs.add(purl);
207 paintElement = ctx.getReferencedElement(paintElement, uri);
208 }
209 }
210
211 /**
212 * Returns a list of <tt>Stop</tt> elements, children of the
213 * specified paintElement can have or null if any.
214 *
215 * @param gradientElement the paint element
216 * @param opacity the opacity
217 * @param ctx the bridge context
218 */
219 protected static List extractLocalStop(Element gradientElement,
220 float opacity,
221 BridgeContext ctx) {
222 LinkedList stops = null;
223 Stop previous = null;
224 for (Node n = gradientElement.getFirstChild();
225 n != null;
226 n = n.getNextSibling()) {
227
228 if ((n.getNodeType() != Node.ELEMENT_NODE)) {
229 continue;
230 }
231
232 Element e = (Element)n;
233 Bridge bridge = ctx.getBridge(e);
234 if (bridge == null || !(bridge instanceof SVGStopElementBridge)) {
235 continue;
236 }
237 Stop stop = ((SVGStopElementBridge)bridge).createStop
238 (ctx, gradientElement, e, opacity);
239 if (stops == null) {
240 stops = new LinkedList();
241 }
242 if (previous != null) {
243 if (stop.offset < previous.offset) {
244 stop.offset = previous.offset;
245 }
246 }
247 stops.add(stop);
248 previous = stop;
249 }
250 return stops;
251 }
252
253 /**
254 * Returns true if the specified list of URLs contains the specified url.
255 *
256 * @param urls the list of URLs
257 * @param key the url to search for
258 */
259 private static boolean contains(List urls, ParsedURL key) {
260 Iterator iter = urls.iterator();
261 while (iter.hasNext()) {
262 if (key.equals(iter.next()))
263 return true;
264 }
265 return false;
266 }
267
268 /**
269 * This class represents a gradient <stop> element.
270 */
271 public static class Stop {
272
273 /** The stop color. */
274 public Color color;
275 /** The stop offset. */
276 public float offset;
277
278 /**
279 * Constructs a new stop definition.
280 *
281 * @param color the stop color
282 * @param offset the stop offset
283 */
284 public Stop(Color color, float offset) {
285 this.color = color;
286 this.offset = offset;
287 }
288 }
289
290 /**
291 * Bridge class for the gradient <stop> element.
292 */
293 public static class SVGStopElementBridge extends AbstractSVGBridge
294 implements Bridge {
295
296 /**
297 * Returns 'stop'.
298 */
299 public String getLocalName() {
300 return SVG_STOP_TAG;
301 }
302
303 /**
304 * Creates a <tt>Stop</tt> according to the specified parameters.
305 *
306 * @param ctx the bridge context to use
307 * @param gradientElement the gradient element
308 * @param stopElement the stop element
309 * @param opacity an additional opacity of the stop color
310 */
311 public Stop createStop(BridgeContext ctx,
312 Element gradientElement,
313 Element stopElement,
314 float opacity) {
315
316 String s = stopElement.getAttributeNS(null, SVG_OFFSET_ATTRIBUTE);
317 if (s.length() == 0) {
318 throw new BridgeException(stopElement, ERR_ATTRIBUTE_MISSING,
319 new Object[] {SVG_OFFSET_ATTRIBUTE});
320 }
321 float offset;
322 try {
323 offset = SVGUtilities.convertRatio(s);
324 } catch (NumberFormatException ex) {
325 throw new BridgeException
326 (stopElement, ERR_ATTRIBUTE_VALUE_MALFORMED,
327 new Object[] {SVG_OFFSET_ATTRIBUTE, s, ex});
328 }
329 Color color
330 = CSSUtilities.convertStopColor(stopElement, opacity, ctx);
331
332 return new Stop(color, offset);
333 }
334 }
335 }