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.Shape;
022: import java.awt.geom.AffineTransform;
023: import java.awt.geom.Rectangle2D;
024: import java.lang.ref.SoftReference;
025:
026: import org.apache.batik.css.engine.CSSEngineEvent;
027: import org.apache.batik.css.engine.SVGCSSEngine;
028: import org.apache.batik.dom.events.AbstractEvent;
029: import org.apache.batik.dom.svg.AbstractSVGTransformList;
030: import org.apache.batik.dom.svg.AnimatedLiveAttributeValue;
031: import org.apache.batik.dom.svg.LiveAttributeException;
032: import org.apache.batik.dom.svg.SVGContext;
033: import org.apache.batik.dom.svg.SVGMotionAnimatableElement;
034: import org.apache.batik.dom.svg.SVGOMElement;
035: import org.apache.batik.dom.svg.SVGOMAnimatedTransformList;
036: import org.apache.batik.ext.awt.geom.SegmentList;
037: import org.apache.batik.gvt.CanvasGraphicsNode;
038: import org.apache.batik.gvt.CompositeGraphicsNode;
039: import org.apache.batik.gvt.GraphicsNode;
040:
041: import org.w3c.dom.Element;
042: import org.w3c.dom.Node;
043: import org.w3c.dom.events.DocumentEvent;
044: import org.w3c.dom.events.EventTarget;
045: import org.w3c.dom.events.MutationEvent;
046: import org.w3c.dom.svg.SVGFitToViewBox;
047: import org.w3c.dom.svg.SVGTransformable;
048:
049: /**
050: * The base bridge class for SVG graphics node. By default, the namespace URI is
051: * the SVG namespace. Override the <tt>getNamespaceURI</tt> if you want to add
052: * custom <tt>GraphicsNode</tt> with a custom namespace.
053: *
054: * <p>This class handles various attributes that are defined on most
055: * of the SVG graphic elements as described in the SVG
056: * specification.</p>
057: *
058: * <ul>
059: * <li>clip-path</li>
060: * <li>filter</li>
061: * <li>mask</li>
062: * <li>opacity</li>
063: * <li>transform</li>
064: * <li>visibility</li>
065: * </ul>
066: *
067: * @author <a href="mailto:tkormann@apache.org">Thierry Kormann</a>
068: * @version $Id: AbstractGraphicsNodeBridge.java 504819 2007-02-08 08:23:19Z dvholten $
069: */
070: public abstract class AbstractGraphicsNodeBridge extends
071: AnimatableSVGBridge implements SVGContext, BridgeUpdateHandler,
072: GraphicsNodeBridge, ErrorConstants {
073:
074: /**
075: * The graphics node constructed by this bridge.
076: */
077: protected GraphicsNode node;
078:
079: /**
080: * Whether the document is an SVG 1.2 document.
081: */
082: protected boolean isSVG12;
083:
084: /**
085: * The unit context for length conversions.
086: */
087: protected UnitProcessor.Context unitContext;
088:
089: /**
090: * Constructs a new abstract bridge.
091: */
092: protected AbstractGraphicsNodeBridge() {
093: }
094:
095: /**
096: * Creates a <tt>GraphicsNode</tt> according to the specified parameters.
097: *
098: * @param ctx the bridge context to use
099: * @param e the element that describes the graphics node to build
100: * @return a graphics node that represents the specified element
101: */
102: public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) {
103: // 'requiredFeatures', 'requiredExtensions' and 'systemLanguage'
104: if (!SVGUtilities.matchUserAgent(e, ctx.getUserAgent())) {
105: return null;
106: }
107:
108: GraphicsNode node = instantiateGraphicsNode();
109:
110: // 'transform'
111: setTransform(node, e, ctx);
112:
113: // 'visibility'
114: node.setVisible(CSSUtilities.convertVisibility(e));
115:
116: associateSVGContext(ctx, e, node);
117:
118: return node;
119: }
120:
121: /**
122: * Creates the GraphicsNode depending on the GraphicsNodeBridge
123: * implementation.
124: */
125: protected abstract GraphicsNode instantiateGraphicsNode();
126:
127: /**
128: * Builds using the specified BridgeContext and element, the
129: * specified graphics node.
130: *
131: * @param ctx the bridge context to use
132: * @param e the element that describes the graphics node to build
133: * @param node the graphics node to build
134: */
135: public void buildGraphicsNode(BridgeContext ctx, Element e,
136: GraphicsNode node) {
137: // 'opacity'
138: node.setComposite(CSSUtilities.convertOpacity(e));
139: // 'filter'
140: node.setFilter(CSSUtilities.convertFilter(e, node, ctx));
141: // 'mask'
142: node.setMask(CSSUtilities.convertMask(e, node, ctx));
143: // 'clip-path'
144: node.setClip(CSSUtilities.convertClipPath(e, node, ctx));
145: // 'pointer-events'
146: node.setPointerEventType(CSSUtilities.convertPointerEvents(e));
147:
148: initializeDynamicSupport(ctx, e, node);
149: }
150:
151: /**
152: * Returns true if the graphics node has to be displayed, false
153: * otherwise.
154: */
155: public boolean getDisplay(Element e) {
156: return CSSUtilities.convertDisplay(e);
157: }
158:
159: /**
160: * Returns an {@link AffineTransform} that is the transformation to
161: * be applied to the node.
162: */
163: protected AffineTransform computeTransform(SVGTransformable te,
164: BridgeContext ctx) {
165: try {
166: // motion animation
167: AffineTransform at = new AffineTransform();
168: if (e instanceof SVGMotionAnimatableElement) {
169: SVGMotionAnimatableElement mae = (SVGMotionAnimatableElement) e;
170: AffineTransform mat = mae.getMotionTransform();
171: if (mat != null) {
172: at.concatenate(mat);
173: }
174: }
175:
176: // 'transform'
177: SVGOMAnimatedTransformList atl = (SVGOMAnimatedTransformList) te
178: .getTransform();
179: if (atl.isSpecified()) {
180: AbstractSVGTransformList tl = (AbstractSVGTransformList) te
181: .getTransform().getAnimVal();
182: at.concatenate(tl.getAffineTransform());
183: }
184:
185: return at;
186: } catch (LiveAttributeException ex) {
187: throw new BridgeException(ctx, ex);
188: }
189: }
190:
191: /**
192: * Sets the graphics node's transform to the current animated transform
193: * value.
194: */
195: protected void setTransform(GraphicsNode n, Element e,
196: BridgeContext ctx) {
197: n.setTransform(computeTransform((SVGTransformable) e, ctx));
198: }
199:
200: /**
201: * Associates the {@link SVGContext} with the element. This method should
202: * be called even for static documents, since some bridges will need to
203: * access animated attribute values even during the first build.
204: */
205: protected void associateSVGContext(BridgeContext ctx, Element e,
206: GraphicsNode node) {
207: this .e = e;
208: this .node = node;
209: this .ctx = ctx;
210: this .unitContext = UnitProcessor.createContext(ctx, e);
211: this .isSVG12 = ctx.isSVG12();
212: ((SVGOMElement) e).setSVGContext(this );
213: }
214:
215: /**
216: * This method is invoked during the build phase if the document
217: * is dynamic. The responsibility of this method is to ensure that
218: * any dynamic modifications of the element this bridge is
219: * dedicated to, happen on its associated GVT product.
220: */
221: protected void initializeDynamicSupport(BridgeContext ctx,
222: Element e, GraphicsNode node) {
223: if (ctx.isInteractive()) {
224: // Bind the nodes for interactive and dynamic.
225: ctx.bind(e, node);
226: }
227: }
228:
229: // BridgeUpdateHandler implementation //////////////////////////////////
230:
231: /**
232: * Invoked when an MutationEvent of type 'DOMAttrModified' is fired.
233: */
234: public void handleDOMAttrModifiedEvent(MutationEvent evt) {
235: }
236:
237: /**
238: * Invoked when the geometry of a graphical element has changed.
239: */
240: protected void handleGeometryChanged() {
241: node.setFilter(CSSUtilities.convertFilter(e, node, ctx));
242: node.setMask(CSSUtilities.convertMask(e, node, ctx));
243: node.setClip(CSSUtilities.convertClipPath(e, node, ctx));
244: if (isSVG12) {
245: if (!SVG_USE_TAG.equals(e.getLocalName())) {
246: // ShapeChange events get fired only for basic shapes and paths.
247: fireShapeChangeEvent();
248: }
249: fireBBoxChangeEvent();
250: }
251: }
252:
253: /**
254: * Fires a ShapeChange event on the element this bridge is managing.
255: */
256: protected void fireShapeChangeEvent() {
257: DocumentEvent d = (DocumentEvent) e.getOwnerDocument();
258: AbstractEvent evt = (AbstractEvent) d.createEvent("SVGEvents");
259: evt.initEventNS(SVG_NAMESPACE_URI, "shapechange", true, false);
260: try {
261: ((EventTarget) e).dispatchEvent(evt);
262: } catch (RuntimeException ex) {
263: ctx.getUserAgent().displayError(ex);
264: }
265: }
266:
267: /**
268: * Invoked when an MutationEvent of type 'DOMNodeInserted' is fired.
269: */
270: public void handleDOMNodeInsertedEvent(MutationEvent evt) {
271: if (evt.getTarget() instanceof Element) {
272: // Handle "generic" bridges.
273: Element e2 = (Element) evt.getTarget();
274: Bridge b = ctx.getBridge(e2);
275: if (b instanceof GenericBridge) {
276: ((GenericBridge) b).handleElement(ctx, e2);
277: }
278: }
279: }
280:
281: /**
282: * Invoked when an MutationEvent of type 'DOMNodeRemoved' is fired.
283: */
284: public void handleDOMNodeRemovedEvent(MutationEvent evt) {
285: Node parent = e.getParentNode();
286: if (parent instanceof SVGOMElement) {
287: SVGContext bridge = ((SVGOMElement) parent).getSVGContext();
288: if (bridge instanceof SVGSwitchElementBridge) {
289: ((SVGSwitchElementBridge) bridge)
290: .handleChildElementRemoved(e);
291: return;
292: }
293: }
294: CompositeGraphicsNode gn = node.getParent();
295: gn.remove(node);
296: disposeTree(e);
297: }
298:
299: /**
300: * Invoked when an MutationEvent of type 'DOMCharacterDataModified'
301: * is fired.
302: */
303: public void handleDOMCharacterDataModified(MutationEvent evt) {
304: }
305:
306: /**
307: * Disposes this BridgeUpdateHandler and releases all resources.
308: */
309: public void dispose() {
310: SVGOMElement elt = (SVGOMElement) e;
311: elt.setSVGContext(null);
312: ctx.unbind(e);
313:
314: bboxShape = null;
315: }
316:
317: /**
318: * Disposes all resources related to the specified node and its subtree.
319: */
320: protected void disposeTree(Node node) {
321: disposeTree(node, true);
322: }
323:
324: /**
325: * Disposes all resources related to the specified node and its subtree,
326: * and optionally removes the nodes' {@link SVGContext}.
327: */
328: protected void disposeTree(Node node, boolean removeContext) {
329: if (node instanceof SVGOMElement) {
330: SVGOMElement elt = (SVGOMElement) node;
331: SVGContext ctx = elt.getSVGContext();
332: if (ctx instanceof BridgeUpdateHandler) {
333: BridgeUpdateHandler h = (BridgeUpdateHandler) ctx;
334: if (removeContext) {
335: elt.setSVGContext(null);
336: }
337: h.dispose();
338: }
339: }
340: for (Node n = node.getFirstChild(); n != null; n = n
341: .getNextSibling()) {
342: disposeTree(n, removeContext);
343: }
344: }
345:
346: /**
347: * Invoked when an CSSEngineEvent is fired.
348: */
349: public void handleCSSEngineEvent(CSSEngineEvent evt) {
350: try {
351: SVGCSSEngine eng = (SVGCSSEngine) evt.getSource();
352: int[] properties = evt.getProperties();
353: for (int i = 0; i < properties.length; i++) {
354: int idx = properties[i];
355: handleCSSPropertyChanged(idx);
356: String pn = eng.getPropertyName(idx);
357: fireBaseAttributeListeners(pn);
358: }
359: } catch (Exception ex) {
360: ctx.getUserAgent().displayError(ex);
361: }
362: }
363:
364: /**
365: * Invoked for each CSS property that has changed.
366: */
367: protected void handleCSSPropertyChanged(int property) {
368: switch (property) {
369: case SVGCSSEngine.VISIBILITY_INDEX:
370: node.setVisible(CSSUtilities.convertVisibility(e));
371: break;
372: case SVGCSSEngine.OPACITY_INDEX:
373: node.setComposite(CSSUtilities.convertOpacity(e));
374: break;
375: case SVGCSSEngine.FILTER_INDEX:
376: node.setFilter(CSSUtilities.convertFilter(e, node, ctx));
377: break;
378: case SVGCSSEngine.MASK_INDEX:
379: node.setMask(CSSUtilities.convertMask(e, node, ctx));
380: break;
381: case SVGCSSEngine.CLIP_PATH_INDEX:
382: node.setClip(CSSUtilities.convertClipPath(e, node, ctx));
383: break;
384: case SVGCSSEngine.POINTER_EVENTS_INDEX:
385: node.setPointerEventType(CSSUtilities
386: .convertPointerEvents(e));
387: break;
388: case SVGCSSEngine.DISPLAY_INDEX:
389: if (!getDisplay(e)) {
390: // Remove the subtree.
391: CompositeGraphicsNode parent = node.getParent();
392: parent.remove(node);
393: disposeTree(e, false);
394: }
395: break;
396: }
397: }
398:
399: /**
400: * Invoked when the animated value of an animatable attribute has changed.
401: */
402: public void handleAnimatedAttributeChanged(
403: AnimatedLiveAttributeValue alav) {
404: if (alav.getNamespaceURI() == null
405: && alav.getLocalName().equals(SVG_TRANSFORM_ATTRIBUTE)) {
406: setTransform(node, e, ctx);
407: handleGeometryChanged();
408: }
409: }
410:
411: /**
412: * Invoked when an 'other' animation value has changed.
413: */
414: public void handleOtherAnimationChanged(String type) {
415: if (type.equals("motion")) {
416: setTransform(node, e, ctx);
417: handleGeometryChanged();
418: }
419: }
420:
421: /**
422: * Checks if the bounding box of the node has changed, and if so,
423: * fires a bboxchange event on the element.
424: */
425: protected void checkBBoxChange() {
426: if (e != null) {
427: /*Rectangle2D oldBBox = bbox;
428: Rectangle2D newBBox = getBBox();
429: if (oldBBox != newBBox && newBBox != null) {
430: if (oldBBox == null ||
431: oldBBox.getX() != bbox.getX()
432: || oldBBox.getY() != bbox.getY()
433: || oldBBox.getWidth() != bbox.getWidth()
434: || oldBBox.getHeight() != bbox.getHeight()) {*/
435: fireBBoxChangeEvent();
436: /*}
437: }*/
438: }
439: }
440:
441: /**
442: * Fires an svg:bboxchange event on the element.
443: */
444: protected void fireBBoxChangeEvent() {
445: DocumentEvent d = (DocumentEvent) e.getOwnerDocument();
446: AbstractEvent evt = (AbstractEvent) d.createEvent("SVGEvents");
447: evt.initEventNS(SVG_NAMESPACE_URI, "RenderedBBoxChange", true,
448: false);
449: try {
450: ((EventTarget) e).dispatchEvent(evt);
451: } catch (RuntimeException ex) {
452: ctx.getUserAgent().displayError(ex);
453: }
454: }
455:
456: // SVGContext implementation ///////////////////////////////////////////
457:
458: /**
459: * Returns the size of a px CSS unit in millimeters.
460: */
461: public float getPixelUnitToMillimeter() {
462: return ctx.getUserAgent().getPixelUnitToMillimeter();
463: }
464:
465: /**
466: * Returns the size of a px CSS unit in millimeters.
467: * This will be removed after next release.
468: * @see #getPixelUnitToMillimeter()
469: */
470: public float getPixelToMM() {
471: return getPixelUnitToMillimeter();
472: }
473:
474: protected SoftReference bboxShape = null;
475: protected Rectangle2D bbox = null;
476:
477: /**
478: * Returns the tight bounding box in current user space (i.e.,
479: * after application of the transform attribute, if any) on the
480: * geometry of all contained graphics elements, exclusive of
481: * stroke-width and filter effects).
482: */
483: public Rectangle2D getBBox() {
484: if (node == null) {
485: return null;
486: }
487: Shape s = node.getOutline();
488:
489: if ((bboxShape != null) && (s == bboxShape.get()))
490: return bbox;
491: bboxShape = new SoftReference(s); // don't keep this live.
492: bbox = null;
493: if (s == null)
494: return bbox;
495:
496: // SegmentList.getBounds2D gives tight BBox.
497: SegmentList sl = new SegmentList(s);
498: bbox = sl.getBounds2D();
499: return bbox;
500: }
501:
502: /**
503: * Returns the transformation matrix from current user units
504: * (i.e., after application of the transform attribute, if any) to
505: * the viewport coordinate system for the nearestViewportElement.
506: */
507: public AffineTransform getCTM() {
508: GraphicsNode gn = node;
509: AffineTransform ctm = new AffineTransform();
510: Element elt = e;
511: while (elt != null) {
512: if (elt instanceof SVGFitToViewBox) {
513: AffineTransform at;
514: if (gn instanceof CanvasGraphicsNode) {
515: at = ((CanvasGraphicsNode) gn)
516: .getViewingTransform();
517: } else {
518: at = gn.getTransform();
519: }
520: if (at != null) {
521: ctm.preConcatenate(at);
522: }
523: break;
524: }
525:
526: AffineTransform at = gn.getTransform();
527: if (at != null)
528: ctm.preConcatenate(at);
529:
530: elt = SVGCSSEngine.getParentCSSStylableElement(elt);
531: gn = gn.getParent();
532: }
533: return ctm;
534: }
535:
536: /**
537: * Returns the display transform.
538: */
539: public AffineTransform getScreenTransform() {
540: return ctx.getUserAgent().getTransform();
541: }
542:
543: /**
544: * Sets the display transform.
545: */
546: public void setScreenTransform(AffineTransform at) {
547: ctx.getUserAgent().setTransform(at);
548: }
549:
550: /**
551: * Returns the global transformation matrix from the current
552: * element to the root.
553: */
554: public AffineTransform getGlobalTransform() {
555: return node.getGlobalTransform();
556: }
557:
558: /**
559: * Returns the width of the viewport which directly contains the
560: * given element.
561: */
562: public float getViewportWidth() {
563: return ctx.getBlockWidth(e);
564: }
565:
566: /**
567: * Returns the height of the viewport which directly contains the
568: * given element.
569: */
570: public float getViewportHeight() {
571: return ctx.getBlockHeight(e);
572: }
573:
574: /**
575: * Returns the font-size on the associated element.
576: */
577: public float getFontSize() {
578: return CSSUtilities.getComputedStyle(e,
579: SVGCSSEngine.FONT_SIZE_INDEX).getFloatValue();
580: }
581: }
|