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.Cursor;
022: import java.awt.RenderingHints;
023: import java.awt.geom.AffineTransform;
024: import java.awt.geom.Rectangle2D;
025:
026: import org.apache.batik.dom.events.NodeEventTarget;
027: import org.apache.batik.dom.svg.AnimatedLiveAttributeValue;
028: import org.apache.batik.dom.svg.LiveAttributeException;
029: import org.apache.batik.dom.svg.SVGOMAnimatedLength;
030: import org.apache.batik.dom.svg.SVGOMDocument;
031: import org.apache.batik.dom.svg.SVGOMUseElement;
032: import org.apache.batik.dom.svg.SVGOMUseShadowRoot;
033: import org.apache.batik.gvt.CompositeGraphicsNode;
034: import org.apache.batik.gvt.GraphicsNode;
035: import org.apache.batik.util.XMLConstants;
036:
037: import org.w3c.dom.Attr;
038: import org.w3c.dom.Element;
039: import org.w3c.dom.NamedNodeMap;
040: import org.w3c.dom.Node;
041: import org.w3c.dom.events.Event;
042: import org.w3c.dom.events.EventListener;
043: import org.w3c.dom.svg.SVGTransformable;
044: import org.w3c.dom.svg.SVGUseElement;
045:
046: /**
047: * Bridge class for the <use> element.
048: *
049: * @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a>
050: * @version $Id: SVGUseElementBridge.java 491178 2006-12-30 06:18:34Z cam $
051: */
052: public class SVGUseElementBridge extends AbstractGraphicsNodeBridge {
053:
054: /**
055: * Used to handle mutation of the referenced content. This is
056: * only used in dynamic context and only for reference to local
057: * content.
058: */
059: protected ReferencedElementMutationListener l;
060:
061: /**
062: * The bridge context for the referenced document.
063: */
064: protected BridgeContext subCtx;
065:
066: /**
067: * Constructs a new bridge for the <use> element.
068: */
069: public SVGUseElementBridge() {
070: }
071:
072: /**
073: * Returns 'use'.
074: */
075: public String getLocalName() {
076: return SVG_USE_TAG;
077: }
078:
079: /**
080: * Returns a new instance of this bridge.
081: */
082: public Bridge getInstance() {
083: return new SVGUseElementBridge();
084: }
085:
086: /**
087: * Creates a <tt>GraphicsNode</tt> according to the specified parameters.
088: *
089: * @param ctx the bridge context to use
090: * @param e the element that describes the graphics node to build
091: * @return a graphics node that represents the specified element
092: */
093: public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) {
094: // 'requiredFeatures', 'requiredExtensions' and 'systemLanguage'
095: if (!SVGUtilities.matchUserAgent(e, ctx.getUserAgent()))
096: return null;
097:
098: CompositeGraphicsNode gn = buildCompositeGraphicsNode(ctx, e,
099: null);
100: associateSVGContext(ctx, e, gn);
101:
102: return gn;
103: }
104:
105: /**
106: * Creates a <tt>GraphicsNode</tt> from the input element and
107: * populates the input <tt>CompositeGraphicsNode</tt>
108: *
109: * @param ctx the bridge context to use
110: * @param e the element that describes the graphics node to build
111: * @param gn the CompositeGraphicsNode where the use graphical
112: * content will be appended. The composite node is emptied
113: * before appending new content.
114: */
115: public CompositeGraphicsNode buildCompositeGraphicsNode(
116: BridgeContext ctx, Element e, CompositeGraphicsNode gn) {
117: // get the referenced element
118: SVGOMUseElement ue = (SVGOMUseElement) e;
119: String uri = ue.getHref().getAnimVal();
120: if (uri.length() == 0) {
121: throw new BridgeException(ctx, e, ERR_ATTRIBUTE_MISSING,
122: new Object[] { "xlink:href" });
123: }
124:
125: Element refElement = ctx.getReferencedElement(e, uri);
126:
127: SVGOMDocument document, refDocument;
128: document = (SVGOMDocument) e.getOwnerDocument();
129: refDocument = (SVGOMDocument) refElement.getOwnerDocument();
130: boolean isLocal = (refDocument == document);
131:
132: BridgeContext theCtx = ctx;
133: subCtx = null;
134: if (!isLocal) {
135: subCtx = (BridgeContext) refDocument.getCSSEngine()
136: .getCSSContext();
137: theCtx = subCtx;
138: }
139:
140: // import or clone the referenced element in current document
141: Element localRefElement;
142: localRefElement = (Element) document.importNode(refElement,
143: true, true);
144:
145: if (SVG_SYMBOL_TAG.equals(localRefElement.getLocalName())) {
146: // The referenced 'symbol' and its contents are deep-cloned into
147: // the generated tree, with the exception that the 'symbol' is
148: // replaced by an 'svg'.
149: Element svgElement = document.createElementNS(
150: SVG_NAMESPACE_URI, SVG_SVG_TAG);
151:
152: // move the attributes from <symbol> to the <svg> element
153: NamedNodeMap attrs = localRefElement.getAttributes();
154: int len = attrs.getLength();
155: for (int i = 0; i < len; i++) {
156: Attr attr = (Attr) attrs.item(i);
157: svgElement.setAttributeNS(attr.getNamespaceURI(), attr
158: .getName(), attr.getValue());
159: }
160: // move the children from <symbol> to the <svg> element
161: for (Node n = localRefElement.getFirstChild(); n != null; n = localRefElement
162: .getFirstChild()) {
163: svgElement.appendChild(n);
164: }
165: localRefElement = svgElement;
166: }
167:
168: if (SVG_SVG_TAG.equals(localRefElement.getLocalName())) {
169: // The referenced 'svg' and its contents are deep-cloned into the
170: // generated tree. If attributes width and/or height are provided
171: // on the 'use' element, then these values will override the
172: // corresponding attributes on the 'svg' in the generated tree.
173: try {
174: SVGOMAnimatedLength al = (SVGOMAnimatedLength) ue
175: .getWidth();
176: if (al.isSpecified()) {
177: localRefElement.setAttributeNS(null,
178: SVG_WIDTH_ATTRIBUTE, al.getAnimVal()
179: .getValueAsString());
180: }
181: al = (SVGOMAnimatedLength) ue.getHeight();
182: if (al.isSpecified()) {
183: localRefElement.setAttributeNS(null,
184: SVG_HEIGHT_ATTRIBUTE, al.getAnimVal()
185: .getValueAsString());
186: }
187: } catch (LiveAttributeException ex) {
188: throw new BridgeException(ctx, ex);
189: }
190: }
191:
192: // attach the referenced element to the current document
193: SVGOMUseShadowRoot root;
194: root = new SVGOMUseShadowRoot(document, e, isLocal);
195: root.appendChild(localRefElement);
196:
197: if (gn == null) {
198: gn = new CompositeGraphicsNode();
199: associateSVGContext(ctx, e, node);
200: } else {
201: int s = gn.size();
202: for (int i = 0; i < s; i++)
203: gn.remove(0);
204: }
205:
206: Node oldRoot = ue.getCSSFirstChild();
207: if (oldRoot != null) {
208: disposeTree(oldRoot);
209: }
210: ue.setUseShadowTree(root);
211:
212: Element g = localRefElement;
213:
214: // compute URIs and style sheets for the used element
215: CSSUtilities.computeStyleAndURIs(refElement, localRefElement,
216: uri);
217:
218: GVTBuilder builder = ctx.getGVTBuilder();
219: GraphicsNode refNode = builder.build(ctx, g);
220:
221: ///////////////////////////////////////////////////////////////////////
222:
223: gn.getChildren().add(refNode);
224:
225: gn.setTransform(computeTransform((SVGTransformable) e, ctx));
226:
227: // set an affine transform to take into account the (x, y)
228: // coordinates of the <use> element
229:
230: // 'visibility'
231: gn.setVisible(CSSUtilities.convertVisibility(e));
232:
233: RenderingHints hints = null;
234: hints = CSSUtilities.convertColorRendering(e, hints);
235: if (hints != null)
236: gn.setRenderingHints(hints);
237:
238: // 'enable-background'
239: Rectangle2D r = CSSUtilities.convertEnableBackground(e);
240: if (r != null)
241: gn.setBackgroundEnable(r);
242:
243: if (l != null) {
244: // Remove event listeners
245: NodeEventTarget target = l.target;
246: target.removeEventListenerNS(
247: XMLConstants.XML_EVENTS_NAMESPACE_URI,
248: "DOMAttrModified", l, true);
249: target.removeEventListenerNS(
250: XMLConstants.XML_EVENTS_NAMESPACE_URI,
251: "DOMNodeInserted", l, true);
252: target.removeEventListenerNS(
253: XMLConstants.XML_EVENTS_NAMESPACE_URI,
254: "DOMNodeRemoved", l, true);
255: target.removeEventListenerNS(
256: XMLConstants.XML_EVENTS_NAMESPACE_URI,
257: "DOMCharacterDataModified", l, true);
258: l = null;
259: }
260:
261: ///////////////////////////////////////////////////////////////////////
262:
263: // Handle mutations on content referenced in the same file if
264: // we are in a dynamic context.
265: if (isLocal && ctx.isDynamic()) {
266: l = new ReferencedElementMutationListener();
267:
268: NodeEventTarget target = (NodeEventTarget) refElement;
269: l.target = target;
270:
271: target.addEventListenerNS(
272: XMLConstants.XML_EVENTS_NAMESPACE_URI,
273: "DOMAttrModified", l, true, null);
274: theCtx.storeEventListenerNS(target,
275: XMLConstants.XML_EVENTS_NAMESPACE_URI,
276: "DOMAttrModified", l, true);
277:
278: target.addEventListenerNS(
279: XMLConstants.XML_EVENTS_NAMESPACE_URI,
280: "DOMNodeInserted", l, true, null);
281: theCtx.storeEventListenerNS(target,
282: XMLConstants.XML_EVENTS_NAMESPACE_URI,
283: "DOMNodeInserted", l, true);
284:
285: target.addEventListenerNS(
286: XMLConstants.XML_EVENTS_NAMESPACE_URI,
287: "DOMNodeRemoved", l, true, null);
288: theCtx.storeEventListenerNS(target,
289: XMLConstants.XML_EVENTS_NAMESPACE_URI,
290: "DOMNodeRemoved", l, true);
291:
292: target.addEventListenerNS(
293: XMLConstants.XML_EVENTS_NAMESPACE_URI,
294: "DOMCharacterDataModified", l, true, null);
295: theCtx.storeEventListenerNS(target,
296: XMLConstants.XML_EVENTS_NAMESPACE_URI,
297: "DOMCharacterDataModified", l, true);
298: }
299:
300: return gn;
301: }
302:
303: public void dispose() {
304: if (l != null) {
305: // Remove event listeners
306: NodeEventTarget target = l.target;
307: target.removeEventListenerNS(
308: XMLConstants.XML_EVENTS_NAMESPACE_URI,
309: "DOMAttrModified", l, true);
310: target.removeEventListenerNS(
311: XMLConstants.XML_EVENTS_NAMESPACE_URI,
312: "DOMNodeInserted", l, true);
313: target.removeEventListenerNS(
314: XMLConstants.XML_EVENTS_NAMESPACE_URI,
315: "DOMNodeRemoved", l, true);
316: target.removeEventListenerNS(
317: XMLConstants.XML_EVENTS_NAMESPACE_URI,
318: "DOMCharacterDataModified", l, true);
319: l = null;
320: }
321:
322: SVGOMUseElement ue = (SVGOMUseElement) e;
323: if (ue != null && ue.getCSSFirstChild() != null) {
324: disposeTree(ue.getCSSFirstChild());
325: }
326:
327: super .dispose();
328:
329: subCtx = null;
330: }
331:
332: /**
333: * Returns an {@link AffineTransform} that is the transformation to
334: * be applied to the node.
335: */
336: protected AffineTransform computeTransform(SVGTransformable e,
337: BridgeContext ctx) {
338: AffineTransform at = super .computeTransform(e, ctx);
339: SVGUseElement ue = (SVGUseElement) e;
340: try {
341: // 'x' attribute - default is 0
342: float x = ue.getX().getAnimVal().getValue();
343:
344: // 'y' attribute - default is 0
345: float y = ue.getY().getAnimVal().getValue();
346:
347: AffineTransform xy = AffineTransform.getTranslateInstance(
348: x, y);
349: xy.preConcatenate(at);
350: return xy;
351: } catch (LiveAttributeException ex) {
352: throw new BridgeException(ctx, ex);
353: }
354: }
355:
356: /**
357: * Creates the GraphicsNode depending on the GraphicsNodeBridge
358: * implementation.
359: */
360: protected GraphicsNode instantiateGraphicsNode() {
361: return null; // nothing to do, createGraphicsNode is fully overriden
362: }
363:
364: /**
365: * Returns false as the <use> element is a not container.
366: */
367: public boolean isComposite() {
368: return false;
369: }
370:
371: /**
372: * Builds using the specified BridgeContext and element, the
373: * specified graphics node.
374: *
375: * @param ctx the bridge context to use
376: * @param e the element that describes the graphics node to build
377: * @param node the graphics node to build
378: */
379: public void buildGraphicsNode(BridgeContext ctx, Element e,
380: GraphicsNode node) {
381:
382: super .buildGraphicsNode(ctx, e, node);
383:
384: if (ctx.isInteractive()) {
385: NodeEventTarget target = (NodeEventTarget) e;
386: EventListener l = new CursorMouseOverListener(ctx);
387: target.addEventListenerNS(
388: XMLConstants.XML_EVENTS_NAMESPACE_URI,
389: SVG_EVENT_MOUSEOVER, l, false, null);
390: ctx.storeEventListenerNS(target,
391: XMLConstants.XML_EVENTS_NAMESPACE_URI,
392: SVG_EVENT_MOUSEOVER, l, false);
393: }
394: }
395:
396: /**
397: * To handle a mouseover on an anchor and set the cursor.
398: */
399: public static class CursorMouseOverListener implements
400: EventListener {
401:
402: protected BridgeContext ctx;
403:
404: public CursorMouseOverListener(BridgeContext ctx) {
405: this .ctx = ctx;
406: }
407:
408: public void handleEvent(Event evt) {
409: //
410: // Only modify the cursor if the current target's (i.e., the <use>) cursor
411: // property is *not* 'auto'.
412: //
413: Element currentTarget = (Element) evt.getCurrentTarget();
414:
415: if (!CSSUtilities.isAutoCursor(currentTarget)) {
416: Cursor cursor;
417: cursor = CSSUtilities.convertCursor(currentTarget, ctx);
418: if (cursor != null) {
419: ctx.getUserAgent().setSVGCursor(cursor);
420: }
421: }
422: }
423: }
424:
425: /**
426: * Used to handle modifications to the referenced content
427: */
428: protected class ReferencedElementMutationListener implements
429: EventListener {
430: protected NodeEventTarget target;
431:
432: public void handleEvent(Event evt) {
433: // We got a mutation in the referenced content. We need to
434: // build the content again, just in case.
435: // Note that this is way sub-optimal, because multiple changes
436: // to the referenced content will cause multiple updates to the
437: // referencing <use>. However, this provides the desired behavior
438: buildCompositeGraphicsNode(ctx, e,
439: (CompositeGraphicsNode) node);
440: }
441: }
442:
443: // BridgeUpdateHandler implementation //////////////////////////////////
444:
445: /**
446: * Invoked when the animated value of an animatable attribute has changed.
447: */
448: public void handleAnimatedAttributeChanged(
449: AnimatedLiveAttributeValue alav) {
450: try {
451: String ns = alav.getNamespaceURI();
452: String ln = alav.getLocalName();
453: if (ns == null
454: && (ln.equals(SVG_X_ATTRIBUTE)
455: || ln.equals(SVG_Y_ATTRIBUTE) || ln
456: .equals(SVG_TRANSFORM_ATTRIBUTE))) {
457: node.setTransform(computeTransform(
458: (SVGTransformable) e, ctx));
459: handleGeometryChanged();
460: } else if (ns == null
461: && (ln.equals(SVG_WIDTH_ATTRIBUTE) || ln
462: .equals(SVG_HEIGHT_ATTRIBUTE))
463: || ns.equals(XLINK_NAMESPACE_URI)
464: && (ln.equals(XLINK_HREF_ATTRIBUTE))) {
465: buildCompositeGraphicsNode(ctx, e,
466: (CompositeGraphicsNode) node);
467: }
468: } catch (LiveAttributeException ex) {
469: throw new BridgeException(ctx, ex);
470: }
471: super.handleAnimatedAttributeChanged(alav);
472: }
473: }
|