| /* |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| package org.apache.batik.bridge; |
| |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Rectangle2D; |
| |
| import java.util.Calendar; |
| |
| import org.apache.batik.anim.AbstractAnimation; |
| import org.apache.batik.anim.AnimationEngine; |
| import org.apache.batik.anim.timing.TimedElement; |
| import org.apache.batik.anim.values.AnimatableValue; |
| import org.apache.batik.css.engine.CSSEngineEvent; |
| import org.apache.batik.dom.AbstractNode; |
| import org.apache.batik.dom.anim.AnimatableElement; |
| import org.apache.batik.dom.anim.AnimationTarget; |
| import org.apache.batik.dom.anim.AnimationTargetListener; |
| import org.apache.batik.dom.svg.AnimatedLiveAttributeValue; |
| import org.apache.batik.dom.svg.SVGAnimationContext; |
| import org.apache.batik.dom.svg.SVGOMElement; |
| import org.apache.batik.dom.util.XLinkSupport; |
| import org.apache.batik.util.SVGTypes; |
| |
| import org.w3c.dom.DOMException; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.events.EventTarget; |
| import org.w3c.dom.events.MutationEvent; |
| import org.w3c.dom.svg.SVGElement; |
| |
| /** |
| * An abstract base class for the SVG animation element bridges. |
| * |
| * @author <a href="mailto:cam%40mcc%2eid%2eau">Cameron McCormack</a> |
| * @version $Id$ |
| */ |
| public abstract class SVGAnimationElementBridge extends AbstractSVGBridge |
| implements GenericBridge, |
| BridgeUpdateHandler, |
| SVGAnimationContext, |
| AnimatableElement { |
| |
| /** |
| * The animation element. |
| */ |
| protected SVGOMElement element; |
| |
| /** |
| * The BridgeContext to be used. |
| */ |
| protected BridgeContext ctx; |
| |
| /** |
| * The AnimationEngine that manages all of the animations in the document. |
| */ |
| protected SVGAnimationEngine eng; |
| |
| /** |
| * The TimedElement object that provides the timing for the animation. |
| */ |
| protected TimedElement timedElement; |
| |
| /** |
| * The animation object that provides the values for the animation. |
| */ |
| protected AbstractAnimation animation; |
| |
| /** |
| * The namespace URI of the attribute being animated. |
| */ |
| protected String attributeNamespaceURI; |
| |
| /** |
| * The local name of the attribute or the name of the property being |
| * animated. |
| */ |
| protected String attributeLocalName; |
| |
| /** |
| * The animation type. Must be one of the <code>ANIM_TYPE_*</code> |
| * constants defined in {@link AnimationEngine}. |
| */ |
| protected short animationType; |
| |
| /** |
| * The target element of the animation. |
| */ |
| protected SVGOMElement targetElement; |
| |
| /** |
| * The AnimationTarget the provides a context to the animation engine. |
| */ |
| protected AnimationTarget animationTarget; |
| |
| /** |
| * Returns the TimedElement for the animation. |
| */ |
| public TimedElement getTimedElement() { |
| return timedElement; |
| } |
| |
| // AnimatableElement ///////////////////////////////////////////////////// |
| |
| /** |
| * Returns the underlying value of the animated attribute. Used for |
| * composition of additive animations. This should be overridden in |
| * descendant classes that are for 'other' animations. |
| */ |
| public AnimatableValue getUnderlyingValue() { |
| if (animationType == AnimationEngine.ANIM_TYPE_XML) { |
| return animationTarget.getUnderlyingValue(attributeNamespaceURI, |
| attributeLocalName); |
| } else { |
| return eng.getUnderlyingCSSValue(element, |
| animationTarget, |
| attributeLocalName); |
| } |
| } |
| |
| // GenericBridge ///////////////////////////////////////////////////////// |
| |
| /** |
| * Handles this animation element. |
| * |
| * @param ctx the bridge context to use |
| * @param e the element being handled |
| */ |
| public void handleElement(BridgeContext ctx, Element e) { |
| if (ctx.isDynamic() && BridgeContext.getSVGContext(e) == null) { |
| SVGAnimationElementBridge b = |
| (SVGAnimationElementBridge) getInstance(); |
| b.element = (SVGOMElement) e; |
| b.ctx = ctx; |
| b.eng = ctx.getAnimationEngine(); |
| b.element.setSVGContext(b); |
| if (b.eng.hasStarted()) { |
| b.initializeAnimation(); |
| b.initializeTimedElement(); |
| } else { |
| b.eng.addInitialBridge(b); |
| } |
| } |
| } |
| |
| /** |
| * Parses the animation element's target attributes and adds it to the |
| * document's AnimationEngine. |
| */ |
| protected void initializeAnimation() { |
| // Determine the target element. |
| String uri = XLinkSupport.getXLinkHref(element); |
| Node t; |
| if (uri.length() == 0) { |
| t = element.getParentNode(); |
| } else { |
| t = ctx.getReferencedElement(element, uri); |
| if (t.getOwnerDocument() != element.getOwnerDocument()) { |
| throw new BridgeException |
| (ctx, element, ErrorConstants.ERR_URI_BAD_TARGET, |
| new Object[] { uri }); |
| } |
| } |
| animationTarget = null; |
| if (t instanceof SVGOMElement) { |
| targetElement = (SVGOMElement) t; |
| animationTarget = targetElement; |
| } |
| if (animationTarget == null) { |
| throw new BridgeException |
| (ctx, element, ErrorConstants.ERR_URI_BAD_TARGET, |
| new Object[] { uri }); |
| } |
| |
| // Get the attribute/property name. |
| String an = element.getAttributeNS(null, SVG_ATTRIBUTE_NAME_ATTRIBUTE); |
| int ci = an.indexOf(':'); |
| if (ci == -1) { |
| if (element.hasProperty(an)) { |
| animationType = AnimationEngine.ANIM_TYPE_CSS; |
| attributeLocalName = an; |
| } else { |
| animationType = AnimationEngine.ANIM_TYPE_XML; |
| attributeLocalName = an; |
| } |
| } else { |
| animationType = AnimationEngine.ANIM_TYPE_XML; |
| String prefix = an.substring(0, ci); |
| attributeNamespaceURI = element.lookupNamespaceURI(prefix); |
| attributeLocalName = an.substring(ci + 1); |
| } |
| if (animationType == AnimationEngine.ANIM_TYPE_CSS |
| && !targetElement.isPropertyAnimatable(attributeLocalName) |
| || animationType == AnimationEngine.ANIM_TYPE_XML |
| && !targetElement.isAttributeAnimatable(attributeNamespaceURI, |
| attributeLocalName)) { |
| throw new BridgeException |
| (ctx, element, "attribute.not.animatable", |
| new Object[] { targetElement.getNodeName(), an }); |
| } |
| |
| // Check that the attribute/property is animatable with this |
| // animation element. |
| int type; |
| if (animationType == AnimationEngine.ANIM_TYPE_CSS) { |
| type = targetElement.getPropertyType(attributeLocalName); |
| } else { |
| type = targetElement.getAttributeType(attributeNamespaceURI, |
| attributeLocalName); |
| } |
| if (!canAnimateType(type)) { |
| throw new BridgeException |
| (ctx, element, "type.not.animatable", |
| new Object[] { targetElement.getNodeName(), an, |
| element.getNodeName() }); |
| } |
| |
| // Add the animation. |
| timedElement = createTimedElement(); |
| animation = createAnimation(animationTarget); |
| eng.addAnimation(animationTarget, animationType, attributeNamespaceURI, |
| attributeLocalName, animation); |
| } |
| |
| /** |
| * Returns whether the animation element being handled by this bridge can |
| * animate attributes of the specified type. |
| * @param type one of the TYPE_ constants defined in {@link SVGTypes}. |
| */ |
| protected abstract boolean canAnimateType(int type); |
| |
| /** |
| * Returns whether the specified {@link AnimatableValue} is of a type allowed |
| * by this animation. |
| */ |
| protected boolean checkValueType(AnimatableValue v) { |
| return true; |
| } |
| |
| /** |
| * Parses the animation element's timing attributes and initializes the |
| * {@link TimedElement} object. |
| */ |
| protected void initializeTimedElement() { |
| initializeTimedElement(timedElement); |
| timedElement.initialize(); |
| } |
| |
| /** |
| * Creates a TimedElement for the animation element. |
| */ |
| protected TimedElement createTimedElement() { |
| return new SVGTimedElement(); |
| } |
| |
| /** |
| * Creates the animation object for the animation element. |
| */ |
| protected abstract AbstractAnimation createAnimation(AnimationTarget t); |
| |
| /** |
| * Parses an attribute as an AnimatableValue. |
| */ |
| protected AnimatableValue parseAnimatableValue(String an) { |
| if (!element.hasAttributeNS(null, an)) { |
| return null; |
| } |
| String s = element.getAttributeNS(null, an); |
| AnimatableValue val = eng.parseAnimatableValue |
| (element, animationTarget, attributeNamespaceURI, |
| attributeLocalName, animationType == AnimationEngine.ANIM_TYPE_CSS, |
| s); |
| if (!checkValueType(val)) { |
| throw new BridgeException |
| (ctx, element, ErrorConstants.ERR_ATTRIBUTE_VALUE_MALFORMED, |
| new Object[] { an, s }); |
| } |
| return val; |
| } |
| |
| /** |
| * Initializes the timing attributes of the timed element. |
| */ |
| protected void initializeTimedElement(TimedElement timedElement) { |
| timedElement.parseAttributes |
| (element.getAttributeNS(null, "begin"), |
| element.getAttributeNS(null, "dur"), |
| element.getAttributeNS(null, "end"), |
| element.getAttributeNS(null, "min"), |
| element.getAttributeNS(null, "max"), |
| element.getAttributeNS(null, "repeatCount"), |
| element.getAttributeNS(null, "repeatDur"), |
| element.getAttributeNS(null, "fill"), |
| element.getAttributeNS(null, "restart")); |
| } |
| |
| // BridgeUpdateHandler /////////////////////////////////////////////////// |
| |
| /** |
| * Invoked when an MutationEvent of type 'DOMAttrModified' is fired. |
| */ |
| public void handleDOMAttrModifiedEvent(MutationEvent evt) { |
| } |
| |
| /** |
| * Invoked when an MutationEvent of type 'DOMNodeInserted' is fired. |
| */ |
| public void handleDOMNodeInsertedEvent(MutationEvent evt) { |
| } |
| |
| /** |
| * Invoked when an MutationEvent of type 'DOMNodeRemoved' is fired. |
| */ |
| public void handleDOMNodeRemovedEvent(MutationEvent evt) { |
| element.setSVGContext(null); |
| dispose(); |
| } |
| |
| /** |
| * Invoked when an MutationEvent of type 'DOMCharacterDataModified' |
| * is fired. |
| */ |
| public void handleDOMCharacterDataModified(MutationEvent evt) { |
| } |
| |
| /** |
| * Invoked when an CSSEngineEvent is fired. |
| */ |
| public void handleCSSEngineEvent(CSSEngineEvent evt) { |
| } |
| |
| /** |
| * Invoked when the animated value of an animatable attribute has changed. |
| */ |
| public void handleAnimatedAttributeChanged |
| (AnimatedLiveAttributeValue alav) { |
| } |
| |
| /** |
| * Invoked when an 'other' animation value has changed. |
| */ |
| public void handleOtherAnimationChanged(String type) { |
| } |
| |
| /** |
| * Disposes this BridgeUpdateHandler and releases all resources. |
| */ |
| public void dispose() { |
| if (element.getSVGContext() == null) { |
| // Only remove the animation if this is not part of a rebuild. |
| eng.removeAnimation(animation); |
| timedElement.deinitialize(); |
| timedElement = null; |
| element = null; |
| } |
| } |
| |
| // SVGContext /////////////////////////////////////////////////////////// |
| |
| /** |
| * Returns the size of a px CSS unit in millimeters. |
| */ |
| public float getPixelUnitToMillimeter() { |
| return ctx.getUserAgent().getPixelUnitToMillimeter(); |
| } |
| |
| /** |
| * Returns the size of a px CSS unit in millimeters. |
| * This will be removed after next release. |
| * @see #getPixelUnitToMillimeter() |
| */ |
| public float getPixelToMM() { |
| return getPixelUnitToMillimeter(); |
| |
| } |
| |
| public Rectangle2D getBBox() { return null; } |
| public AffineTransform getScreenTransform() { |
| return ctx.getUserAgent().getTransform(); |
| } |
| public void setScreenTransform(AffineTransform at) { |
| ctx.getUserAgent().setTransform(at); |
| } |
| public AffineTransform getCTM() { return null; } |
| public AffineTransform getGlobalTransform() { return null; } |
| public float getViewportWidth() { |
| return ctx.getBlockWidth(element); |
| } |
| public float getViewportHeight() { |
| return ctx.getBlockHeight(element); |
| } |
| public float getFontSize() { return 0; } |
| public float svgToUserSpace(float v, int type, int pcInterp) { |
| return 0; |
| } |
| |
| /** |
| * Adds a listener for changes to the given attribute value. |
| */ |
| public void addTargetListener(String pn, AnimationTargetListener l) { |
| } |
| |
| /** |
| * Removes a listener for changes to the given attribute value. |
| */ |
| public void removeTargetListener(String pn, AnimationTargetListener l) { |
| } |
| |
| // SVGAnimationContext /////////////////////////////////////////////////// |
| |
| /** |
| * <b>DOM</b>: Implements {@link |
| * org.w3c.dom.svg.SVGAnimationElement#getTargetElement()}. |
| */ |
| public SVGElement getTargetElement() { |
| return targetElement; |
| } |
| |
| /** |
| * <b>DOM</b>: Implements {@link |
| * org.w3c.dom.svg.SVGAnimationElement#getStartTime()}. |
| */ |
| public float getStartTime() { |
| return timedElement.getCurrentBeginTime(); |
| } |
| |
| /** |
| * <b>DOM</b>: Implements {@link |
| * org.w3c.dom.svg.SVGAnimationElement#getCurrentTime()}. |
| */ |
| public float getCurrentTime() { |
| return timedElement.getLastSampleTime(); |
| } |
| |
| /** |
| * <b>DOM</b>: Implements {@link |
| * org.w3c.dom.svg.SVGAnimationElement#getSimpleDuration()}. With the |
| * difference that an indefinite simple duration is returned as |
| * {@link TimedElement#INDEFINITE}, rather than throwing an exception. |
| */ |
| public float getSimpleDuration() { |
| return timedElement.getSimpleDur(); |
| } |
| |
| /** |
| * Returns the time that the document would seek to if this animation |
| * element were hyperlinked to, or <code>NaN</code> if there is no |
| * such begin time. |
| */ |
| public float getHyperlinkBeginTime() { |
| return timedElement.getHyperlinkBeginTime(); |
| } |
| |
| // ElementTimeControl //////////////////////////////////////////////////// |
| |
| /** |
| * <b>DOM</b>: Implements {@link |
| * org.w3c.dom.smil.ElementTimeControl#beginElement()}. |
| */ |
| public boolean beginElement() throws DOMException { |
| timedElement.beginElement(); |
| return timedElement.canBegin(); |
| } |
| |
| /** |
| * <b>DOM</b>: Implements {@link |
| * org.w3c.dom.smil.ElementTimeControl#beginElementAt(float)}. |
| */ |
| public boolean beginElementAt(float offset) throws DOMException { |
| timedElement.beginElement(offset); |
| // XXX Not right, but who knows if it is possible to begin |
| // at some arbitrary point in the future. |
| return true; |
| } |
| |
| /** |
| * <b>DOM</b>: Implements {@link |
| * org.w3c.dom.smil.ElementTimeControl#endElement()}. |
| */ |
| public boolean endElement() throws DOMException { |
| timedElement.endElement(); |
| return timedElement.canEnd(); |
| } |
| |
| /** |
| * <b>DOM</b>: Implements {@link |
| * org.w3c.dom.smil.ElementTimeControl#endElementAt(float)}. |
| */ |
| public boolean endElementAt(float offset) throws DOMException { |
| timedElement.endElement(offset); |
| // XXX Not right, but who knows if it is possible to begin |
| // at some arbitrary point in the future. |
| return true; |
| } |
| |
| /** |
| * Returns whether this is a constant animation (i.e., a 'set' animation). |
| */ |
| protected boolean isConstantAnimation() { |
| return false; |
| } |
| |
| /** |
| * A TimedElement class for SVG animation elements. |
| */ |
| protected class SVGTimedElement extends TimedElement { |
| |
| /** |
| * Returns the DOM element this timed element is for. |
| */ |
| public Element getElement() { |
| return element; |
| } |
| |
| /** |
| * Fires a TimeEvent of the given type on this element. |
| * @param eventType the type of TimeEvent ("beginEvent", "endEvent" |
| * or "repeatEvent"). |
| * @param time the timestamp of the event object |
| */ |
| protected void fireTimeEvent(String eventType, Calendar time, |
| int detail) { |
| AnimationSupport.fireTimeEvent(element, eventType, time, detail); |
| } |
| |
| /** |
| * Invoked to indicate this timed element became active at the |
| * specified time. |
| * @param begin the time the element became active, in document |
| * simple time |
| */ |
| protected void toActive(float begin) { |
| eng.toActive(animation, begin); |
| } |
| |
| /** |
| * Invoked to indicate that this timed element became inactive. |
| * @param stillActive if true, indicates that the element is still |
| * actually active, but between the end of the |
| * computed repeat duration and the end of the |
| * interval |
| * @param isFrozen whether the element is frozen or not |
| */ |
| protected void toInactive(boolean stillActive, boolean isFrozen) { |
| eng.toInactive(animation, isFrozen); |
| } |
| |
| /** |
| * Invoked to indicate that this timed element has had its fill removed. |
| */ |
| protected void removeFill() { |
| eng.removeFill(animation); |
| } |
| |
| /** |
| * Invoked to indicate that this timed element has been sampled at the |
| * given time. |
| * @param simpleTime the sample time in local simple time |
| * @param simpleDur the simple duration of the element |
| * @param repeatIteration the repeat iteration during which the element |
| * was sampled |
| */ |
| protected void sampledAt(float simpleTime, float simpleDur, |
| int repeatIteration) { |
| eng.sampledAt(animation, simpleTime, simpleDur, repeatIteration); |
| } |
| |
| /** |
| * Invoked to indicate that this timed element has been sampled |
| * at the end of its active time, at an integer multiple of the |
| * simple duration. This is the "last" value that will be used |
| * for filling, which cannot be sampled normally. |
| */ |
| protected void sampledLastValue(int repeatIteration) { |
| eng.sampledLastValue(animation, repeatIteration); |
| } |
| |
| /** |
| * Returns the timed element with the given ID. |
| */ |
| protected TimedElement getTimedElementById(String id) { |
| return AnimationSupport.getTimedElementById(id, element); |
| } |
| |
| /** |
| * Returns the event target with the given ID. |
| */ |
| protected EventTarget getEventTargetById(String id) { |
| return AnimationSupport.getEventTargetById(id, element); |
| } |
| |
| /** |
| * Returns the event target that should be listened to for |
| * access key events. |
| */ |
| protected EventTarget getRootEventTarget() { |
| return (EventTarget) element.getOwnerDocument(); |
| } |
| |
| /** |
| * Returns the target of this animation as an {@link EventTarget}. Used |
| * for eventbase timing specifiers where the element ID is omitted. |
| */ |
| protected EventTarget getAnimationEventTarget() { |
| return targetElement; |
| } |
| |
| /** |
| * Returns whether this timed element comes before the given timed |
| * element in document order. |
| */ |
| public boolean isBefore(TimedElement other) { |
| Element e = ((SVGTimedElement) other).getElement(); |
| int pos = ((AbstractNode) element).compareDocumentPosition(e); |
| return (pos & AbstractNode.DOCUMENT_POSITION_PRECEDING) != 0; |
| } |
| |
| /** |
| * Returns a string representation of this animation. |
| */ |
| public String toString() { |
| if (element != null) { |
| String id = element.getAttributeNS(null, "id"); |
| if (id.length() != 0) { |
| return id; |
| } |
| } |
| return super.toString(); |
| } |
| |
| /** |
| * Returns whether this timed element is for a constant animation (i.e., |
| * a 'set' animation. |
| */ |
| protected boolean isConstantAnimation() { |
| return SVGAnimationElementBridge.this.isConstantAnimation(); |
| } |
| } |
| } |