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.svg12;
020:
021: import java.awt.Dimension;
022: import java.awt.geom.AffineTransform;
023: import java.awt.geom.Rectangle2D;
024: import java.util.Collection;
025: import java.util.Iterator;
026: import java.util.LinkedList;
027: import java.util.List;
028:
029: import org.apache.batik.ext.awt.image.renderable.ClipRable8Bit;
030: import org.apache.batik.ext.awt.image.renderable.Filter;
031:
032: import org.apache.batik.bridge.Bridge;
033: import org.apache.batik.bridge.BridgeContext;
034: import org.apache.batik.bridge.BridgeException;
035: import org.apache.batik.bridge.CSSUtilities;
036: import org.apache.batik.bridge.SVGImageElementBridge;
037: import org.apache.batik.bridge.SVGUtilities;
038: import org.apache.batik.bridge.UnitProcessor;
039: import org.apache.batik.bridge.Viewport;
040: import org.apache.batik.dom.AbstractNode;
041: import org.apache.batik.dom.util.XLinkSupport;
042: import org.apache.batik.gvt.GraphicsNode;
043: import org.apache.batik.gvt.ImageNode;
044: import org.apache.batik.gvt.svg12.MultiResGraphicsNode;
045:
046: import org.apache.batik.util.ParsedURL;
047: import org.apache.batik.util.SVG12Constants;
048: import org.w3c.dom.Attr;
049: import org.w3c.dom.Document;
050: import org.w3c.dom.Element;
051: import org.w3c.dom.NamedNodeMap;
052: import org.w3c.dom.Node;
053:
054: /**
055: * Bridge class for the <multiImage> element.
056: *
057: * The 'multiImage' element is similar to the 'image' element (supports
058: * all the same attributes and properties) except.
059: * <ol>
060: * <li>It can only be used to reference raster content (this is an
061: * implementation thing really)</li>
062: * <li>It has two addtional attributes: 'pixel-width' and
063: * 'pixel-height' which are the maximum width and height of the
064: * image referenced by the xlink:href attribute.</li>
065: * <li>It can contain a child element 'subImage' which has only
066: * three attributes, pixel-width, pixel-height and xlink:href.
067: * The image displayed is the smallest image such that
068: * pixel-width and pixel-height are greater than or equal to the
069: * required image size for display.</li>
070: * </ol>
071: *
072: *
073: * @author <a href="mailto:deweese@apache.org">Thomas DeWeese</a>
074: * @version $Id: SVGMultiImageElementBridge.java 475477 2006-11-15 22:44:28Z cam $
075: */
076: public class SVGMultiImageElementBridge extends SVGImageElementBridge {
077:
078: public SVGMultiImageElementBridge() {
079: }
080:
081: /**
082: * Returns the Batik Extension namespace URI.
083: */
084: public String getNamespaceURI() {
085: return SVG12Constants.SVG_NAMESPACE_URI;
086: }
087:
088: /**
089: * Returns 'multiImage'.
090: */
091: public String getLocalName() {
092: return SVG12Constants.SVG_MULTI_IMAGE_TAG;
093: }
094:
095: /**
096: * Returns a new instance of this bridge.
097: */
098: public Bridge getInstance() {
099: return new SVGMultiImageElementBridge();
100: }
101:
102: /**
103: * Creates a graphics node using the specified BridgeContext and for the
104: * specified element.
105: *
106: * @param ctx the bridge context to use
107: * @param e the element that describes the graphics node to build
108: * @return a graphics node that represents the specified element
109: */
110: public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) {
111: // 'requiredFeatures', 'requiredExtensions' and 'systemLanguage'
112: if (!SVGUtilities.matchUserAgent(e, ctx.getUserAgent())) {
113: return null;
114: }
115:
116: ImageNode imgNode = (ImageNode) instantiateGraphicsNode();
117: if (imgNode == null) {
118: return null;
119: }
120:
121: associateSVGContext(ctx, e, imgNode);
122:
123: Rectangle2D b = getImageBounds(ctx, e);
124:
125: // 'transform'
126: AffineTransform at = null;
127: String s = e.getAttribute(SVG_TRANSFORM_ATTRIBUTE);
128: if (s.length() != 0) {
129: at = SVGUtilities.convertTransform(e,
130: SVG_TRANSFORM_ATTRIBUTE, s, ctx);
131: } else {
132: at = new AffineTransform();
133: }
134:
135: at.translate(b.getX(), b.getY());
136: imgNode.setTransform(at);
137:
138: // 'visibility'
139: imgNode.setVisible(CSSUtilities.convertVisibility(e));
140:
141: Rectangle2D clip;
142: clip = new Rectangle2D.Double(0, 0, b.getWidth(), b.getHeight());
143: Filter filter = imgNode.getGraphicsNodeRable(true);
144: imgNode.setClip(new ClipRable8Bit(filter, clip));
145:
146: // 'enable-background'
147: Rectangle2D r = CSSUtilities.convertEnableBackground(e);
148: if (r != null) {
149: imgNode.setBackgroundEnable(r);
150: }
151: ctx.openViewport(e, new MultiImageElementViewport((float) b
152: .getWidth(), (float) b.getHeight()));
153:
154: List elems = new LinkedList();
155: List minDim = new LinkedList();
156: List maxDim = new LinkedList();
157:
158: for (Node n = e.getFirstChild(); n != null; n = n
159: .getNextSibling()) {
160: if (n.getNodeType() != Node.ELEMENT_NODE)
161: continue;
162:
163: Element se = (Element) n;
164: if (!getNamespaceURI().equals(se.getNamespaceURI()))
165: continue;
166: if (se.getLocalName().equals(
167: SVG12Constants.SVG_SUB_IMAGE_TAG)) {
168: addInfo(se, elems, minDim, maxDim, b);
169: }
170: if (se.getLocalName().equals(
171: SVG12Constants.SVG_SUB_IMAGE_REF_TAG)) {
172: addRefInfo(se, elems, minDim, maxDim, b);
173: }
174: }
175:
176: Dimension[] mindary = new Dimension[elems.size()];
177: Dimension[] maxdary = new Dimension[elems.size()];
178: Element[] elemary = new Element[elems.size()];
179: Iterator mindi = minDim.iterator();
180: Iterator maxdi = maxDim.iterator();
181: Iterator ei = elems.iterator();
182: int n = 0;
183: while (mindi.hasNext()) {
184: Dimension minD = (Dimension) mindi.next();
185: Dimension maxD = (Dimension) maxdi.next();
186: int i = 0;
187: if (minD != null) {
188: for (; i < n; i++) {
189: if ((mindary[i] != null)
190: && (minD.width < mindary[i].width)) {
191: break;
192: }
193: }
194: }
195: for (int j = n; j > i; j--) {
196: elemary[j] = elemary[j - 1];
197: mindary[j] = mindary[j - 1];
198: maxdary[j] = maxdary[j - 1];
199: }
200:
201: elemary[i] = (Element) ei.next();
202: mindary[i] = minD;
203: maxdary[i] = maxD;
204: n++;
205: }
206:
207: GraphicsNode node = new MultiResGraphicsNode(e, clip, elemary,
208: mindary, maxdary, ctx);
209: imgNode.setImage(node);
210:
211: return imgNode;
212: }
213:
214: /**
215: * Returns false as shapes are not a container.
216: */
217: public boolean isComposite() {
218: return false;
219: }
220:
221: public void buildGraphicsNode(BridgeContext ctx, Element e,
222: GraphicsNode node) {
223: initializeDynamicSupport(ctx, e, node);
224:
225: // Handle children elements such as <title>
226: //SVGUtilities.bridgeChildren(ctx, e);
227: //super.buildGraphicsNode(ctx, e, node);
228: ctx.closeViewport(e);
229: }
230:
231: /**
232: * This method is invoked during the build phase if the document
233: * is dynamic. The responsability of this method is to ensure that
234: * any dynamic modifications of the element this bridge is
235: * dedicated to, happen on its associated GVT product.
236: */
237: protected void initializeDynamicSupport(BridgeContext ctx,
238: Element e, GraphicsNode node) {
239: if (ctx.isInteractive()) {
240: // HACK due to the way images are represented in GVT
241: ImageNode imgNode = (ImageNode) node;
242: ctx.bind(e, imgNode.getImage());
243: }
244: }
245:
246: /**
247: * Disposes this BridgeUpdateHandler and releases all resources.
248: */
249: public void dispose() {
250: ctx.removeViewport(e);
251: super .dispose();
252: }
253:
254: /**
255: * Returns the bounds of the specified image element.
256: *
257: * @param ctx the bridge context
258: * @param element the image element
259: */
260: protected static Rectangle2D getImageBounds(BridgeContext ctx,
261: Element element) {
262:
263: UnitProcessor.Context uctx = UnitProcessor.createContext(ctx,
264: element);
265:
266: // 'x' attribute - default is 0
267: String s = element.getAttributeNS(null, SVG_X_ATTRIBUTE);
268: float x = 0;
269: if (s.length() != 0) {
270: x = UnitProcessor.svgHorizontalCoordinateToUserSpace(s,
271: SVG_X_ATTRIBUTE, uctx);
272: }
273:
274: // 'y' attribute - default is 0
275: s = element.getAttributeNS(null, SVG_Y_ATTRIBUTE);
276: float y = 0;
277: if (s.length() != 0) {
278: y = UnitProcessor.svgVerticalCoordinateToUserSpace(s,
279: SVG_Y_ATTRIBUTE, uctx);
280: }
281:
282: // 'width' attribute - required
283: s = element.getAttributeNS(null, SVG_WIDTH_ATTRIBUTE);
284: float w;
285: if (s.length() == 0) {
286: throw new BridgeException(ctx, element,
287: ERR_ATTRIBUTE_MISSING,
288: new Object[] { SVG_WIDTH_ATTRIBUTE });
289: } else {
290: w = UnitProcessor.svgHorizontalLengthToUserSpace(s,
291: SVG_WIDTH_ATTRIBUTE, uctx);
292: }
293:
294: // 'height' attribute - required
295: s = element.getAttributeNS(null, SVG_HEIGHT_ATTRIBUTE);
296: float h;
297: if (s.length() == 0) {
298: throw new BridgeException(ctx, element,
299: ERR_ATTRIBUTE_MISSING,
300: new Object[] { SVG_HEIGHT_ATTRIBUTE });
301: } else {
302: h = UnitProcessor.svgVerticalLengthToUserSpace(s,
303: SVG_HEIGHT_ATTRIBUTE, uctx);
304: }
305:
306: return new Rectangle2D.Float(x, y, w, h);
307: }
308:
309: protected void addInfo(Element e, Collection elems,
310: Collection minDim, Collection maxDim, Rectangle2D bounds) {
311: Document doc = e.getOwnerDocument();
312: Element gElem = doc.createElementNS(SVG_NAMESPACE_URI,
313: SVG_G_TAG);
314: NamedNodeMap attrs = e.getAttributes();
315: int len = attrs.getLength();
316: for (int i = 0; i < len; i++) {
317: Attr attr = (Attr) attrs.item(i);
318: gElem.setAttributeNS(attr.getNamespaceURI(),
319: attr.getName(), attr.getValue());
320: }
321: // move the children from <subImage> to the <g> element
322: for (Node n = e.getFirstChild(); n != null; n = e
323: .getFirstChild()) {
324: gElem.appendChild(n);
325: }
326: e.appendChild(gElem);
327: elems.add(gElem);
328: minDim.add(getElementMinPixel(e, bounds));
329: maxDim.add(getElementMaxPixel(e, bounds));
330: }
331:
332: protected void addRefInfo(Element e, Collection elems,
333: Collection minDim, Collection maxDim, Rectangle2D bounds) {
334: String uriStr = XLinkSupport.getXLinkHref(e);
335: if (uriStr.length() == 0) {
336: throw new BridgeException(ctx, e, ERR_ATTRIBUTE_MISSING,
337: new Object[] { "xlink:href" });
338: }
339: String baseURI = AbstractNode.getBaseURI(e);
340: ParsedURL purl;
341: if (baseURI == null)
342: purl = new ParsedURL(uriStr);
343: else
344: purl = new ParsedURL(baseURI, uriStr);
345: Document doc = e.getOwnerDocument();
346: Element imgElem = doc.createElementNS(SVG_NAMESPACE_URI,
347: SVG_IMAGE_TAG);
348: imgElem.setAttributeNS(XLINK_NAMESPACE_URI,
349: XLINK_HREF_ATTRIBUTE, purl.toString());
350: // move the attributes from <subImageRef> to the <image> element
351: NamedNodeMap attrs = e.getAttributes();
352: int len = attrs.getLength();
353: for (int i = 0; i < len; i++) {
354: Attr attr = (Attr) attrs.item(i);
355: imgElem.setAttributeNS(attr.getNamespaceURI(), attr
356: .getName(), attr.getValue());
357: }
358: String s;
359: s = e.getAttribute("x");
360: if (s.length() == 0)
361: imgElem.setAttribute("x", "0");
362: s = e.getAttribute("y");
363: if (s.length() == 0)
364: imgElem.setAttribute("y", "0");
365: s = e.getAttribute("width");
366: if (s.length() == 0)
367: imgElem.setAttribute("width", "100%");
368: s = e.getAttribute("height");
369: if (s.length() == 0)
370: imgElem.setAttribute("height", "100%");
371: e.appendChild(imgElem);
372: elems.add(imgElem);
373:
374: minDim.add(getElementMinPixel(e, bounds));
375: maxDim.add(getElementMaxPixel(e, bounds));
376: }
377:
378: protected Dimension getElementMinPixel(Element e, Rectangle2D bounds) {
379: return getElementPixelSize(e,
380: SVG12Constants.SVG_MAX_PIXEL_SIZE_ATTRIBUTE, bounds);
381: }
382:
383: protected Dimension getElementMaxPixel(Element e, Rectangle2D bounds) {
384: return getElementPixelSize(e,
385: SVG12Constants.SVG_MIN_PIXEL_SIZE_ATTRIBUTE, bounds);
386: }
387:
388: protected Dimension getElementPixelSize(Element e, String attr,
389: Rectangle2D bounds) {
390: String s;
391: s = e.getAttribute(attr);
392: if (s.length() == 0)
393: return null;
394:
395: Float[] vals = SVGUtilities.convertSVGNumberOptionalNumber(e,
396: attr, s, ctx);
397:
398: if (vals[0] == null)
399: return null;
400:
401: float xPixSz = vals[0].floatValue();
402: float yPixSz = xPixSz;
403: if (vals[1] != null)
404: yPixSz = vals[1].floatValue();
405:
406: return new Dimension((int) (bounds.getWidth() / xPixSz + 0.5),
407: (int) (bounds.getHeight() / yPixSz + 0.5));
408: }
409:
410: /**
411: * A viewport defined an <svg> element.
412: */
413: public static class MultiImageElementViewport implements Viewport {
414: private float width;
415: private float height;
416:
417: /**
418: * Constructs a new viewport with the specified <tt>SVGSVGElement</tt>.
419: * @param w the width of the viewport
420: * @param h the height of the viewport
421: */
422: public MultiImageElementViewport(float w, float h) {
423: this .width = w;
424: this .height = h;
425: }
426:
427: /**
428: * Returns the width of this viewport.
429: */
430: public float getWidth() {
431: return width;
432: }
433:
434: /**
435: * Returns the height of this viewport.
436: */
437: public float getHeight() {
438: return height;
439: }
440: }
441: }
|