| /* |
| |
| 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.Color; |
| import java.awt.Paint; |
| import java.lang.ref.WeakReference; |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.Arrays; |
| import java.util.Set; |
| |
| import org.apache.batik.anim.AnimationEngine; |
| import org.apache.batik.anim.AnimationException; |
| import org.apache.batik.anim.dom.AnimationTarget; |
| import org.apache.batik.anim.dom.SVGOMDocument; |
| import org.apache.batik.anim.dom.SVGOMElement; |
| import org.apache.batik.anim.dom.SVGStylableElement; |
| import org.apache.batik.anim.timing.TimedDocumentRoot; |
| import org.apache.batik.anim.timing.TimedElement; |
| import org.apache.batik.anim.values.AnimatableAngleValue; |
| import org.apache.batik.anim.values.AnimatableAngleOrIdentValue; |
| import org.apache.batik.anim.values.AnimatableBooleanValue; |
| import org.apache.batik.anim.values.AnimatableIntegerValue; |
| import org.apache.batik.anim.values.AnimatableLengthValue; |
| import org.apache.batik.anim.values.AnimatableLengthListValue; |
| import org.apache.batik.anim.values.AnimatableLengthOrIdentValue; |
| import org.apache.batik.anim.values.AnimatableNumberValue; |
| import org.apache.batik.anim.values.AnimatableNumberListValue; |
| import org.apache.batik.anim.values.AnimatableNumberOrPercentageValue; |
| import org.apache.batik.anim.values.AnimatablePathDataValue; |
| import org.apache.batik.anim.values.AnimatablePointListValue; |
| import org.apache.batik.anim.values.AnimatablePreserveAspectRatioValue; |
| import org.apache.batik.anim.values.AnimatableNumberOrIdentValue; |
| import org.apache.batik.anim.values.AnimatableRectValue; |
| import org.apache.batik.anim.values.AnimatableStringValue; |
| import org.apache.batik.anim.values.AnimatableValue; |
| import org.apache.batik.anim.values.AnimatableColorValue; |
| import org.apache.batik.anim.values.AnimatablePaintValue; |
| import org.apache.batik.css.engine.CSSEngine; |
| import org.apache.batik.css.engine.CSSStylableElement; |
| import org.apache.batik.css.engine.StyleMap; |
| import org.apache.batik.css.engine.value.FloatValue; |
| import org.apache.batik.css.engine.value.StringValue; |
| import org.apache.batik.css.engine.value.Value; |
| import org.apache.batik.css.engine.value.ValueManager; |
| import org.apache.batik.parser.DefaultPreserveAspectRatioHandler; |
| import org.apache.batik.parser.FloatArrayProducer; |
| import org.apache.batik.parser.DefaultLengthHandler; |
| import org.apache.batik.parser.LengthArrayProducer; |
| import org.apache.batik.parser.LengthHandler; |
| import org.apache.batik.parser.LengthListParser; |
| import org.apache.batik.parser.LengthParser; |
| import org.apache.batik.parser.NumberListParser; |
| import org.apache.batik.parser.PathArrayProducer; |
| import org.apache.batik.parser.PathParser; |
| import org.apache.batik.parser.PointsParser; |
| import org.apache.batik.parser.ParseException; |
| import org.apache.batik.parser.PreserveAspectRatioHandler; |
| import org.apache.batik.parser.PreserveAspectRatioParser; |
| import org.apache.batik.util.RunnableQueue; |
| import org.apache.batik.util.SMILConstants; |
| import org.apache.batik.util.XMLConstants; |
| |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.css.CSSPrimitiveValue; |
| import org.w3c.dom.css.CSSStyleDeclaration; |
| import org.w3c.dom.css.CSSValue; |
| import org.w3c.dom.events.EventTarget; |
| import org.w3c.dom.svg.SVGAngle; |
| import org.w3c.dom.svg.SVGLength; |
| import org.w3c.dom.svg.SVGPreserveAspectRatio; |
| |
| /** |
| * An AnimationEngine for SVG documents. |
| * |
| * @author <a href="mailto:cam%40mcc%2eid%2eau">Cameron McCormack</a> |
| * @version $Id$ |
| */ |
| public class SVGAnimationEngine extends AnimationEngine { |
| |
| /** |
| * The BridgeContext to use for value parsing. |
| */ |
| protected BridgeContext ctx; |
| |
| /** |
| * The CSSEngine used for CSS value parsing. |
| */ |
| protected CSSEngine cssEngine; |
| |
| /** |
| * Whether animation processing has started. This affects whether |
| * animation element bridges add their animation on to the initial |
| * bridge list, or process them immediately. |
| */ |
| protected boolean started; |
| |
| /** |
| * The Runnable that ticks the document. |
| */ |
| protected AnimationTickRunnable animationTickRunnable; |
| |
| /** |
| * The initial time that will be seeked to when the animation engine starts, |
| * as set by {@link #setCurrentTime}. |
| */ |
| protected float initialStartTime; |
| |
| /** |
| * The factory for unparsed string values. |
| */ |
| protected UncomputedAnimatableStringValueFactory |
| uncomputedAnimatableStringValueFactory = |
| new UncomputedAnimatableStringValueFactory(); |
| |
| /** |
| * The factory for length-or-ident values. |
| */ |
| protected AnimatableLengthOrIdentFactory |
| animatableLengthOrIdentFactory = new AnimatableLengthOrIdentFactory(); |
| |
| /** |
| * The factory for number-or-ident values. |
| */ |
| protected AnimatableNumberOrIdentFactory |
| animatableNumberOrIdentFactory = |
| new AnimatableNumberOrIdentFactory(false); |
| |
| /** |
| * Factories for {@link AnimatableValue} parsing. |
| */ |
| protected Factory[] factories = { |
| null, // TYPE_UNKNOWN |
| new AnimatableIntegerValueFactory(), // TYPE_INTEGER |
| new AnimatableNumberValueFactory(), // TYPE_NUMBER |
| new AnimatableLengthValueFactory(), // TYPE_LENGTH |
| null, // TYPE_NUMBER_OPTIONAL_NUMBER |
| new AnimatableAngleValueFactory(), // TYPE_ANGLE |
| new AnimatableColorValueFactory(), // TYPE_COLOR |
| new AnimatablePaintValueFactory(), // TYPE_PAINT |
| null, // TYPE_PERCENTAGE |
| null, // TYPE_TRANSFORM_LIST |
| uncomputedAnimatableStringValueFactory, // TYPE_URI |
| null, // TYPE_FREQUENCY |
| null, // TYPE_TIME |
| new AnimatableNumberListValueFactory(), // TYPE_NUMBER_LIST |
| new AnimatableLengthListValueFactory(), // TYPE_LENGTH_LIST |
| uncomputedAnimatableStringValueFactory, // TYPE_IDENT |
| uncomputedAnimatableStringValueFactory, // TYPE_CDATA |
| animatableLengthOrIdentFactory, // TYPE_LENGTH_OR_INHERIT |
| uncomputedAnimatableStringValueFactory, // TYPE_IDENT_LIST |
| uncomputedAnimatableStringValueFactory, // TYPE_CLIP_VALUE |
| uncomputedAnimatableStringValueFactory, // TYPE_URI_OR_IDENT |
| uncomputedAnimatableStringValueFactory, // TYPE_CURSOR_VALUE |
| new AnimatablePathDataFactory(), // TYPE_PATH_DATA |
| uncomputedAnimatableStringValueFactory, // TYPE_ENABLE_BACKGROUND_VALUE |
| null, // TYPE_TIME_VALUE_LIST |
| animatableNumberOrIdentFactory, // TYPE_NUMBER_OR_INHERIT |
| uncomputedAnimatableStringValueFactory, // TYPE_FONT_FAMILY_VALUE |
| null, // TYPE_FONT_FACE_FONT_SIZE_VALUE |
| new AnimatableNumberOrIdentFactory(true), // TYPE_FONT_WEIGHT_VALUE |
| new AnimatableAngleOrIdentFactory(), // TYPE_ANGLE_OR_IDENT |
| null, // TYPE_KEY_SPLINES_VALUE |
| new AnimatablePointListValueFactory(), // TYPE_POINTS_VALUE |
| new AnimatablePreserveAspectRatioValueFactory(), // TYPE_PRESERVE_ASPECT_RATIO_VALUE |
| null, // TYPE_URI_LIST |
| uncomputedAnimatableStringValueFactory, // TYPE_LENGTH_LIST_OR_IDENT |
| null, // TYPE_CHARACTER_OR_UNICODE_RANGE_LIST |
| null, // TYPE_UNICODE_RANGE_LIST |
| null, // TYPE_FONT_VALUE |
| null, // TYPE_FONT_DECSRIPTOR_SRC_VALUE |
| animatableLengthOrIdentFactory, // TYPE_FONT_SIZE_VALUE |
| animatableLengthOrIdentFactory, // TYPE_BASELINE_SHIFT_VALUE |
| animatableLengthOrIdentFactory, // TYPE_KERNING_VALUE |
| animatableLengthOrIdentFactory, // TYPE_SPACING_VALUE |
| animatableLengthOrIdentFactory, // TYPE_LINE_HEIGHT_VALUE |
| animatableNumberOrIdentFactory, // TYPE_FONT_SIZE_ADJUST_VALUE |
| null, // TYPE_LANG_VALUE |
| null, // TYPE_LANG_LIST_VALUE |
| new AnimatableNumberOrPercentageValueFactory(), // TYPE_NUMBER_OR_PERCENTAGE |
| null, // TYPE_TIMING_SPECIFIER_LIST |
| new AnimatableBooleanValueFactory(), // TYPE_BOOLEAN |
| new AnimatableRectValueFactory() // TYPE_RECT |
| }; |
| |
| /** |
| * Whether the document is an SVG 1.2 document. |
| */ |
| protected boolean isSVG12; |
| |
| /** |
| * List of bridges that will be initialized when the document is started. |
| */ |
| protected LinkedList initialBridges = new LinkedList(); |
| |
| /** |
| * A StyleMap used by the {@link Factory}s when computing CSS values. |
| */ |
| protected StyleMap dummyStyleMap; |
| |
| /** |
| * The thread that ticks the animation engine. |
| */ |
| protected AnimationThread animationThread; |
| |
| /** |
| * The animation limiting mode. |
| */ |
| protected int animationLimitingMode; |
| |
| /** |
| * The amount of animation limiting. |
| */ |
| protected float animationLimitingAmount; |
| |
| /** |
| * Set of SMIL animation event names for SVG 1.1. |
| */ |
| protected static final Set animationEventNames11 = new HashSet(); |
| |
| /** |
| * Set of SMIL animation event names for SVG 1.2. |
| */ |
| protected static final Set animationEventNames12 = new HashSet(); |
| |
| static { |
| String[] eventNamesCommon = { |
| "click", "mousedown", "mouseup", "mouseover", "mousemove", |
| "mouseout", "beginEvent", "endEvent" |
| }; |
| String[] eventNamesSVG11 = { |
| "DOMSubtreeModified", "DOMNodeInserted", "DOMNodeRemoved", |
| "DOMNodeRemovedFromDocument", "DOMNodeInsertedIntoDocument", |
| "DOMAttrModified", "DOMCharacterDataModified", "SVGLoad", |
| "SVGUnload", "SVGAbort", "SVGError", "SVGResize", "SVGScroll", |
| "repeatEvent" |
| }; |
| String[] eventNamesSVG12 = { |
| "load", "resize", "scroll", "zoom" |
| }; |
| for (String anEventNamesCommon : eventNamesCommon) { |
| animationEventNames11.add(anEventNamesCommon); |
| animationEventNames12.add(anEventNamesCommon); |
| } |
| for (String anEventNamesSVG11 : eventNamesSVG11) { |
| animationEventNames11.add(anEventNamesSVG11); |
| } |
| for (String anEventNamesSVG12 : eventNamesSVG12) { |
| animationEventNames12.add(anEventNamesSVG12); |
| } |
| } |
| |
| /** |
| * Creates a new SVGAnimationEngine. |
| */ |
| public SVGAnimationEngine(Document doc, BridgeContext ctx) { |
| super(doc); |
| this.ctx = ctx; |
| SVGOMDocument d = (SVGOMDocument) doc; |
| cssEngine = d.getCSSEngine(); |
| dummyStyleMap = new StyleMap(cssEngine.getNumberOfProperties()); |
| isSVG12 = d.isSVG12(); |
| } |
| |
| /** |
| * Disposes this animation engine. |
| */ |
| public void dispose() { |
| synchronized (this) { |
| pause(); |
| super.dispose(); |
| } |
| } |
| |
| /** |
| * Adds an animation element bridge to the list of bridges that |
| * require initializing when the document is started. |
| */ |
| public void addInitialBridge(SVGAnimationElementBridge b) { |
| if (initialBridges != null) { |
| initialBridges.add(b); |
| } |
| } |
| |
| /** |
| * Returns whether animation processing has begun. |
| */ |
| public boolean hasStarted() { |
| return started; |
| } |
| |
| /** |
| * Parses an AnimatableValue. |
| */ |
| public AnimatableValue parseAnimatableValue(Element animElt, |
| AnimationTarget target, |
| String ns, String ln, |
| boolean isCSS, |
| String s) { |
| SVGOMElement elt = (SVGOMElement) target.getElement(); |
| int type; |
| if (isCSS) { |
| type = elt.getPropertyType(ln); |
| } else { |
| type = elt.getAttributeType(ns, ln); |
| } |
| Factory factory = factories[type]; |
| if (factory == null) { |
| String an = ns == null ? ln : '{' + ns + '}' + ln; |
| throw new BridgeException |
| (ctx, animElt, "attribute.not.animatable", |
| new Object[] { target.getElement().getNodeName(), an }); |
| } |
| return factories[type].createValue(target, ns, ln, isCSS, s); |
| } |
| |
| /** |
| * Returns an AnimatableValue for the underlying value of a CSS property. |
| */ |
| public AnimatableValue getUnderlyingCSSValue(Element animElt, |
| AnimationTarget target, |
| String pn) { |
| ValueManager[] vms = cssEngine.getValueManagers(); |
| int idx = cssEngine.getPropertyIndex(pn); |
| if (idx != -1) { |
| int type = vms[idx].getPropertyType(); |
| Factory factory = factories[type]; |
| if (factory == null) { |
| throw new BridgeException |
| (ctx, animElt, "attribute.not.animatable", |
| new Object[] { target.getElement().getNodeName(), pn }); |
| } |
| SVGStylableElement e = (SVGStylableElement) target.getElement(); |
| CSSStyleDeclaration over = e.getOverrideStyle(); |
| String oldValue = over.getPropertyValue(pn); |
| if (oldValue != null) { |
| over.removeProperty(pn); |
| } |
| Value v = cssEngine.getComputedStyle(e, null, idx); |
| if (oldValue != null && !oldValue.equals("")) { |
| over.setProperty(pn, oldValue, null); |
| } |
| return factories[type].createValue(target, pn, v); |
| } |
| // XXX Doesn't handle shorthands. |
| return null; |
| } |
| |
| /** |
| * Pauses the animations. |
| */ |
| public void pause() { |
| super.pause(); |
| UpdateManager um = ctx.getUpdateManager(); |
| if (um != null) { |
| um.getUpdateRunnableQueue().setIdleRunnable(null); |
| } |
| } |
| |
| /** |
| * Pauses the animations. |
| */ |
| public void unpause() { |
| super.unpause(); |
| UpdateManager um = ctx.getUpdateManager(); |
| if (um != null) { |
| um.getUpdateRunnableQueue().setIdleRunnable(animationTickRunnable); |
| } |
| } |
| |
| /** |
| * Returns the current document time. |
| */ |
| public float getCurrentTime() { |
| boolean p = pauseTime != 0; |
| unpause(); |
| float t = timedDocumentRoot.getCurrentTime(); |
| if (p) { |
| pause(); |
| } |
| return Float.isNaN(t) ? 0 : t; |
| } |
| |
| /** |
| * Sets the current document time. |
| */ |
| public float setCurrentTime(float t) { |
| if (started) { |
| float ret = super.setCurrentTime(t); |
| if (animationTickRunnable != null) { |
| animationTickRunnable.resume(); |
| } |
| return ret; |
| } else { |
| initialStartTime = t; |
| return 0; |
| } |
| } |
| |
| /** |
| * Creates a new returns a new TimedDocumentRoot object for the document. |
| */ |
| protected TimedDocumentRoot createDocumentRoot() { |
| return new AnimationRoot(); |
| } |
| |
| /** |
| * Starts the animation engine. |
| */ |
| public void start(long documentStartTime) { |
| if (started) { |
| return; |
| } |
| started = true; |
| try { |
| try { |
| Calendar cal = Calendar.getInstance(); |
| cal.setTime(new Date(documentStartTime)); |
| timedDocumentRoot.resetDocument(cal); |
| Object[] bridges = initialBridges.toArray(); |
| initialBridges = null; |
| for (Object bridge2 : bridges) { |
| SVGAnimationElementBridge bridge = |
| (SVGAnimationElementBridge) bridge2; |
| bridge.initializeAnimation(); |
| } |
| for (Object bridge1 : bridges) { |
| SVGAnimationElementBridge bridge = |
| (SVGAnimationElementBridge) bridge1; |
| bridge.initializeTimedElement(); |
| } |
| |
| // tick(0, false); |
| // animationThread = new AnimationThread(); |
| // animationThread.start(); |
| UpdateManager um = ctx.getUpdateManager(); |
| if (um != null) { |
| RunnableQueue q = um.getUpdateRunnableQueue(); |
| animationTickRunnable = new AnimationTickRunnable(q, this); |
| q.setIdleRunnable(animationTickRunnable); |
| if (initialStartTime != 0) { |
| setCurrentTime(initialStartTime); |
| } |
| } |
| } catch (AnimationException ex) { |
| throw new BridgeException(ctx, ex.getElement().getElement(), |
| ex.getMessage()); |
| } |
| } catch (Exception ex) { |
| if (ctx.getUserAgent() == null) { |
| ex.printStackTrace(); |
| } else { |
| ctx.getUserAgent().displayError(ex); |
| } |
| } |
| } |
| |
| /** |
| * Sets the animation limiting mode to "none". |
| */ |
| public void setAnimationLimitingNone() { |
| animationLimitingMode = 0; |
| } |
| |
| /** |
| * Sets the animation limiting mode to a percentage of CPU. |
| * @param pc the maximum percentage of CPU to use (0 < pc ≤ 1) |
| */ |
| public void setAnimationLimitingCPU(float pc) { |
| animationLimitingMode = 1; |
| animationLimitingAmount = pc; |
| } |
| |
| /** |
| * Sets the animation limiting mode to a number of frames per second. |
| * @param fps the maximum number of frames per second (fps > 0) |
| */ |
| public void setAnimationLimitingFPS(float fps) { |
| animationLimitingMode = 2; |
| animationLimitingAmount = fps; |
| } |
| |
| /** |
| * A class for the root time container. |
| */ |
| protected class AnimationRoot extends TimedDocumentRoot { |
| |
| /** |
| * Creates a new AnimationRoot object. |
| */ |
| public AnimationRoot() { |
| super(!isSVG12, isSVG12); |
| } |
| |
| /** |
| * Returns the namespace URI of the event that corresponds to the given |
| * animation event name. |
| */ |
| protected String getEventNamespaceURI(String eventName) { |
| if (!isSVG12) { |
| return null; |
| } |
| if (eventName.equals("focusin") |
| || eventName.equals("focusout") |
| || eventName.equals("activate") |
| || animationEventNames12.contains(eventName)) { |
| return XMLConstants.XML_EVENTS_NAMESPACE_URI; |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the type of the event that corresponds to the given |
| * animation event name. |
| */ |
| protected String getEventType(String eventName) { |
| if (eventName.equals("focusin")) { |
| return "DOMFocusIn"; |
| } else if (eventName.equals("focusout")) { |
| return "DOMFocusOut"; |
| } else if (eventName.equals("activate")) { |
| return "DOMActivate"; |
| } |
| if (isSVG12) { |
| if (animationEventNames12.contains(eventName)) { |
| return eventName; |
| } |
| } else { |
| if (animationEventNames11.contains(eventName)) { |
| return eventName; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the name of the repeat event. |
| * @return "repeatEvent" for SVG |
| */ |
| protected String getRepeatEventName() { |
| return SMILConstants.SMIL_REPEAT_EVENT_NAME; |
| } |
| |
| /** |
| * Fires a TimeEvent of the given type on this element. |
| * @param eventType the type of TimeEvent ("beginEvent", "endEvent" |
| * or "repeatEvent"/"repeat"). |
| * @param time the timestamp of the event object |
| */ |
| protected void fireTimeEvent(String eventType, Calendar time, |
| int detail) { |
| AnimationSupport.fireTimeEvent |
| ((EventTarget) document, 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) { |
| } |
| |
| /** |
| * 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) { |
| } |
| |
| /** |
| * Invoked to indicate that this timed element has had its fill removed. |
| */ |
| protected void removeFill() { |
| } |
| |
| /** |
| * 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) { |
| } |
| |
| /** |
| * 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) { |
| } |
| |
| /** |
| * Returns the timed element with the given ID. |
| */ |
| protected TimedElement getTimedElementById(String id) { |
| return AnimationSupport.getTimedElementById(id, document); |
| } |
| |
| /** |
| * Returns the event target with the given ID. |
| */ |
| protected EventTarget getEventTargetById(String id) { |
| return AnimationSupport.getEventTargetById(id, document); |
| } |
| |
| /** |
| * 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 null; |
| } |
| |
| /** |
| * Returns the event target that should be listened to for |
| * access key events. |
| */ |
| protected EventTarget getRootEventTarget() { |
| return (EventTarget) document; |
| } |
| |
| /** |
| * Returns the DOM element that corresponds to this timed element, if |
| * such a DOM element exists. |
| */ |
| public Element getElement() { |
| return null; |
| } |
| |
| /** |
| * Returns whether this timed element comes before the given timed |
| * element in document order. |
| */ |
| public boolean isBefore(TimedElement other) { |
| return false; |
| } |
| |
| /** |
| * Invoked by timed elements in this document to indicate that the |
| * current interval will be re-evaluated at the next sample. |
| */ |
| protected void currentIntervalWillUpdate() { |
| if (animationTickRunnable != null) { |
| animationTickRunnable.resume(); |
| } |
| } |
| } |
| |
| /** |
| * Idle runnable to tick the animation, that reads times from System.in. |
| */ |
| protected static class DebugAnimationTickRunnable extends AnimationTickRunnable { |
| |
| float t = 0f; |
| |
| public DebugAnimationTickRunnable(RunnableQueue q, SVGAnimationEngine eng) { |
| super(q, eng); |
| waitTime = Long.MAX_VALUE; |
| new Thread() { |
| public void run() { |
| java.io.BufferedReader r = new java.io.BufferedReader(new java.io.InputStreamReader(System.in)); |
| System.out.println("Enter times."); |
| for (;;) { |
| String s; |
| try { |
| s = r.readLine(); |
| } catch (java.io.IOException e) { |
| s = null; |
| } |
| if (s == null) { |
| System.exit(0); |
| } |
| t = Float.parseFloat(s); |
| DebugAnimationTickRunnable.this.resume(); |
| } |
| } |
| }.start(); |
| } |
| |
| public void resume() { |
| waitTime = 0; |
| Object lock = q.getIteratorLock(); |
| synchronized (lock) { |
| lock.notify(); |
| } |
| } |
| |
| public long getWaitTime() { |
| long wt = waitTime; |
| waitTime = Long.MAX_VALUE; |
| return wt; |
| } |
| |
| public void run() { |
| SVGAnimationEngine eng = getAnimationEngine(); |
| synchronized (eng) { |
| try { |
| try { |
| eng.tick(t, false); |
| } catch (AnimationException ex) { |
| throw new BridgeException |
| (eng.ctx, ex.getElement().getElement(), |
| ex.getMessage()); |
| } |
| } catch (Exception ex) { |
| if (eng.ctx.getUserAgent() == null) { |
| ex.printStackTrace(); |
| } else { |
| eng.ctx.getUserAgent().displayError(ex); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Idle runnable to tick the animation. |
| */ |
| protected static class AnimationTickRunnable |
| implements RunnableQueue.IdleRunnable { |
| |
| /** |
| * Calendar instance used for passing current time values to the |
| * animation timing system. |
| */ |
| protected Calendar time = Calendar.getInstance(); |
| |
| // /** |
| // * The current document time in seconds, truncated. |
| // */ |
| // protected double second = -1.; |
| |
| // /** |
| // * The number of frames that have been ticked so far this second. |
| // */ |
| // protected int frames; |
| |
| /** |
| * The number of milliseconds to wait until the next animation tick. |
| * This is returned by {@link #getWaitTime()}. |
| */ |
| protected long waitTime; |
| |
| /** |
| * The RunnableQueue in which this is the |
| * {@link RunnableQueue.IdleRunnable}. |
| */ |
| protected RunnableQueue q; |
| |
| /** |
| * The number of past tick times to keep, for computing the average |
| * time per tick. |
| */ |
| private static final int NUM_TIMES = 8; |
| |
| /** |
| * The past tick times. |
| */ |
| protected long[] times = new long[NUM_TIMES]; |
| |
| /** |
| * The sum of the times in {@link #times}. |
| */ |
| protected long sumTime; |
| |
| /** |
| * The current index into {@link #times}. |
| */ |
| protected int timeIndex; |
| |
| /** |
| * A weak reference to the SVGAnimationEngine this AnimationTickRunnable |
| * is for. We make this a WeakReference so that a ticking animation |
| * engine does not prevent from being GCed. |
| */ |
| protected WeakReference engRef; |
| |
| /** |
| * The maximum number of consecutive exceptions to allow before |
| * stopping the report of them. |
| */ |
| protected static final int MAX_EXCEPTION_COUNT = 10; |
| |
| /** |
| * The number of consecutive exceptions that have been thrown. This is |
| * used to detect when exceptions are occurring every tick, and to stop |
| * reporting them when this happens. |
| */ |
| protected int exceptionCount; |
| |
| /** |
| * Creates a new AnimationTickRunnable. |
| */ |
| public AnimationTickRunnable(RunnableQueue q, SVGAnimationEngine eng) { |
| this.q = q; |
| this.engRef = new WeakReference(eng); |
| // Initialize the past times to 100ms. |
| Arrays.fill(times, 100); |
| sumTime = 100 * NUM_TIMES; |
| } |
| |
| /** |
| * Forces an animation update, if the {@link RunnableQueue} is |
| * currently waiting. |
| */ |
| public void resume() { |
| waitTime = 0; |
| Object lock = q.getIteratorLock(); |
| synchronized (lock) { |
| lock.notify(); |
| } |
| } |
| |
| /** |
| * Returns the system time that can be safely waited until before this |
| * {@link Runnable} is run again. |
| * |
| * @return time to wait until, <code>0</code> if no waiting can |
| * be done, or {@link Long#MAX_VALUE} if the {@link Runnable} |
| * should not be run again at this time |
| */ |
| public long getWaitTime() { |
| return waitTime; |
| } |
| |
| /** |
| * Performs one tick of the animation. |
| */ |
| public void run() { |
| SVGAnimationEngine eng = getAnimationEngine(); |
| synchronized (eng) { |
| int animationLimitingMode = eng.animationLimitingMode; |
| float animationLimitingAmount = eng.animationLimitingAmount; |
| try { |
| try { |
| long before = System.currentTimeMillis(); |
| time.setTime(new Date(before)); |
| float t = eng.timedDocumentRoot.convertWallclockTime(time); |
| // if (Math.floor(t) > second) { |
| // second = Math.floor(t); |
| // System.err.println("fps: " + frames); |
| // frames = 0; |
| // } |
| float t2 = eng.tick(t, false); |
| long after = System.currentTimeMillis(); |
| long dur = after - before; |
| if (dur == 0) { |
| dur = 1; |
| } |
| sumTime -= times[timeIndex]; |
| sumTime += dur; |
| times[timeIndex] = dur; |
| timeIndex = (timeIndex + 1) % NUM_TIMES; |
| |
| if (t2 == Float.POSITIVE_INFINITY) { |
| waitTime = Long.MAX_VALUE; |
| } else { |
| waitTime = before + (long) (t2 * 1000) - 1000; |
| if (waitTime < after) { |
| waitTime = after; |
| } |
| if (animationLimitingMode != 0) { |
| float ave = (float) sumTime / NUM_TIMES; |
| float delay; |
| if (animationLimitingMode == 1) { |
| // %cpu |
| delay = ave / animationLimitingAmount - ave; |
| } else { |
| // fps |
| delay = 1000f / animationLimitingAmount - ave; |
| } |
| long newWaitTime = after + (long) delay; |
| if (newWaitTime > waitTime) { |
| waitTime = newWaitTime; |
| } |
| } |
| } |
| // frames++; |
| } catch (AnimationException ex) { |
| throw new BridgeException |
| (eng.ctx, ex.getElement().getElement(), |
| ex.getMessage()); |
| } |
| exceptionCount = 0; |
| } catch (Exception ex) { |
| if (++exceptionCount < MAX_EXCEPTION_COUNT) { |
| if (eng.ctx.getUserAgent() == null) { |
| ex.printStackTrace(); |
| } else { |
| eng.ctx.getUserAgent().displayError(ex); |
| } |
| } |
| } |
| |
| if (animationLimitingMode == 0) { |
| // so we don't steal too much time from the Swing thread |
| try { |
| Thread.sleep(1); |
| } catch (InterruptedException ie) { |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the SVGAnimationEngine this AnimationTickRunnable is for. |
| */ |
| protected SVGAnimationEngine getAnimationEngine() { |
| return (SVGAnimationEngine) engRef.get(); |
| } |
| } |
| |
| /** |
| * The thread that ticks the animation. |
| */ |
| protected class AnimationThread extends Thread { |
| |
| /** |
| * The current time. |
| */ |
| protected Calendar time = Calendar.getInstance(); |
| |
| /** |
| * The RunnableQueue to perform the animation in. |
| */ |
| protected RunnableQueue runnableQueue = |
| ctx.getUpdateManager().getUpdateRunnableQueue(); |
| |
| /** |
| * The animation ticker Runnable. |
| */ |
| protected Ticker ticker = new Ticker(); |
| |
| /** |
| * Ticks the animation over as fast as possible. |
| */ |
| public void run() { |
| if (true) { |
| for (;;) { |
| time.setTime(new Date()); |
| ticker.t = timedDocumentRoot.convertWallclockTime(time); |
| try { |
| runnableQueue.invokeAndWait(ticker); |
| } catch (InterruptedException e) { |
| return; |
| } |
| } |
| } else { |
| ticker.t = 1; |
| while (ticker.t < 10) { |
| try { |
| Thread.sleep(1000); |
| } catch (InterruptedException ie) { |
| } |
| try { |
| runnableQueue.invokeAndWait(ticker); |
| } catch (InterruptedException e) { |
| return; |
| } |
| ticker.t++; |
| } |
| } |
| } |
| |
| /** |
| * A runnable that ticks the animation engine. |
| */ |
| protected class Ticker implements Runnable { |
| |
| /** |
| * The document time to tick at next. |
| */ |
| protected float t; |
| |
| /** |
| * Ticks the animation over. |
| */ |
| public void run() { |
| tick(t, false); |
| } |
| } |
| } |
| |
| // AnimatableValue factories |
| |
| /** |
| * Interface for AnimatableValue factories. |
| */ |
| protected interface Factory { |
| |
| /** |
| * Creates a new AnimatableValue from a string. |
| */ |
| AnimatableValue createValue(AnimationTarget target, String ns, |
| String ln, boolean isCSS, String s); |
| |
| /** |
| * Creates a new AnimatableValue from a CSS {@link Value}. |
| */ |
| AnimatableValue createValue(AnimationTarget target, String pn, Value v); |
| } |
| |
| /** |
| * Factory class for AnimatableValues for CSS properties. |
| * XXX Shorthand properties are not supported. |
| */ |
| protected abstract class CSSValueFactory implements Factory { |
| |
| public AnimatableValue createValue(AnimationTarget target, String ns, |
| String ln, boolean isCSS, String s) { |
| // XXX Always parsing as a CSS value. |
| return createValue(target, ln, createCSSValue(target, ln, s)); |
| } |
| |
| public AnimatableValue createValue(AnimationTarget target, String pn, |
| Value v) { |
| CSSStylableElement elt = (CSSStylableElement) target.getElement(); |
| v = computeValue(elt, pn, v); |
| return createAnimatableValue(target, pn, v); |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a CSS {@link Value}, after |
| * computation and inheritance. |
| */ |
| protected abstract AnimatableValue createAnimatableValue |
| (AnimationTarget target, String pn, Value v); |
| |
| /** |
| * Creates a new CSS {@link Value} from a string. |
| */ |
| protected Value createCSSValue(AnimationTarget t, String pn, String s) { |
| CSSStylableElement elt = (CSSStylableElement) t.getElement(); |
| Value v = cssEngine.parsePropertyValue(elt, pn, s); |
| return computeValue(elt, pn, v); |
| } |
| |
| /** |
| * Computes a CSS {@link Value} and performance inheritance if the |
| * specified value is 'inherit'. |
| */ |
| protected Value computeValue(CSSStylableElement elt, String pn, |
| Value v) { |
| ValueManager[] vms = cssEngine.getValueManagers(); |
| int idx = cssEngine.getPropertyIndex(pn); |
| if (idx != -1) { |
| if (v.getCssValueType() == CSSValue.CSS_INHERIT) { |
| elt = CSSEngine.getParentCSSStylableElement(elt); |
| if (elt != null) { |
| return cssEngine.getComputedStyle(elt, null, idx); |
| } |
| return vms[idx].getDefaultValue(); |
| } |
| v = vms[idx].computeValue(elt, null, cssEngine, idx, |
| dummyStyleMap, v); |
| } |
| return v; |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatableBooleanValue}s. |
| */ |
| protected class AnimatableBooleanValueFactory implements Factory { |
| |
| /** |
| * Creates a new AnimatableValue from a string. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String ns, |
| String ln, boolean isCSS, String s) { |
| return new AnimatableBooleanValue(target, "true".equals(s)); |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a CSS {@link Value}. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String pn, |
| Value v) { |
| return new AnimatableBooleanValue(target, |
| "true".equals(v.getCssText())); |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatableIntegerValue}s. |
| */ |
| protected class AnimatableIntegerValueFactory implements Factory { |
| |
| /** |
| * Creates a new AnimatableValue from a string. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String ns, |
| String ln, boolean isCSS, String s) { |
| return new AnimatableIntegerValue(target, Integer.parseInt(s)); |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a CSS {@link Value}. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String pn, |
| Value v) { |
| return new AnimatableIntegerValue(target, |
| Math.round(v.getFloatValue())); |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatableNumberValue}s. |
| */ |
| protected class AnimatableNumberValueFactory implements Factory { |
| |
| /** |
| * Creates a new AnimatableValue from a string. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String ns, |
| String ln, boolean isCSS, String s) { |
| return new AnimatableNumberValue(target, Float.parseFloat(s)); |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a CSS {@link Value}. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String pn, |
| Value v) { |
| return new AnimatableNumberValue(target, v.getFloatValue()); |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatableNumberOrPercentageValue}s. |
| */ |
| protected class AnimatableNumberOrPercentageValueFactory |
| implements Factory { |
| |
| /** |
| * Creates a new AnimatableValue from a string. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String ns, |
| String ln, boolean isCSS, String s) { |
| float v; |
| boolean pc; |
| if (s.charAt(s.length() - 1) == '%') { |
| v = Float.parseFloat(s.substring(0, s.length() - 1)); |
| pc = true; |
| } else { |
| v = Float.parseFloat(s); |
| pc = false; |
| } |
| return new AnimatableNumberOrPercentageValue(target, v, pc); |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a CSS {@link Value}. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String pn, |
| Value v) { |
| switch (v.getPrimitiveType()) { |
| case CSSPrimitiveValue.CSS_PERCENTAGE: |
| return new AnimatableNumberOrPercentageValue |
| (target, v.getFloatValue(), true); |
| case CSSPrimitiveValue.CSS_NUMBER: |
| return new AnimatableNumberOrPercentageValue |
| (target, v.getFloatValue()); |
| } |
| // XXX Do something better than returning null. |
| return null; |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatablePreserveAspectRatioValue}s. |
| */ |
| protected class AnimatablePreserveAspectRatioValueFactory implements Factory { |
| |
| /** |
| * The parsed 'align' value. |
| */ |
| protected short align; |
| |
| /** |
| * The parsed 'meetOrSlice' value. |
| */ |
| protected short meetOrSlice; |
| |
| /** |
| * Parser for preserveAspectRatio values. |
| */ |
| protected PreserveAspectRatioParser parser = |
| new PreserveAspectRatioParser(); |
| |
| /** |
| * Handler for the preserveAspectRatio parser. |
| */ |
| protected DefaultPreserveAspectRatioHandler handler = |
| new DefaultPreserveAspectRatioHandler() { |
| |
| /** |
| * Implements {@link |
| * PreserveAspectRatioHandler#startPreserveAspectRatio()}. |
| */ |
| public void startPreserveAspectRatio() throws ParseException { |
| align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_UNKNOWN; |
| meetOrSlice = SVGPreserveAspectRatio.SVG_MEETORSLICE_UNKNOWN; |
| } |
| |
| /** |
| * Implements {@link PreserveAspectRatioHandler#none()}. |
| */ |
| public void none() throws ParseException { |
| align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_NONE; |
| } |
| |
| /** |
| * Implements {@link PreserveAspectRatioHandler#xMaxYMax()}. |
| */ |
| public void xMaxYMax() throws ParseException { |
| align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMAX; |
| } |
| |
| /** |
| * Implements {@link PreserveAspectRatioHandler#xMaxYMid()}. |
| */ |
| public void xMaxYMid() throws ParseException { |
| align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMID; |
| } |
| |
| /** |
| * Implements {@link PreserveAspectRatioHandler#xMaxYMin()}. |
| */ |
| public void xMaxYMin() throws ParseException { |
| align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN; |
| } |
| |
| /** |
| * Implements {@link PreserveAspectRatioHandler#xMidYMax()}. |
| */ |
| public void xMidYMax() throws ParseException { |
| align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMAX; |
| } |
| |
| /** |
| * Implements {@link PreserveAspectRatioHandler#xMidYMid()}. |
| */ |
| public void xMidYMid() throws ParseException { |
| align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID; |
| } |
| |
| /** |
| * Implements {@link PreserveAspectRatioHandler#xMidYMin()}. |
| */ |
| public void xMidYMin() throws ParseException { |
| align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMIN; |
| } |
| |
| /** |
| * Implements {@link PreserveAspectRatioHandler#xMinYMax()}. |
| */ |
| public void xMinYMax() throws ParseException { |
| align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMAX; |
| } |
| |
| /** |
| * Implements {@link PreserveAspectRatioHandler#xMinYMid()}. |
| */ |
| public void xMinYMid() throws ParseException { |
| align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMID; |
| } |
| |
| /** |
| * Implements {@link PreserveAspectRatioHandler#xMinYMin()}. |
| */ |
| public void xMinYMin() throws ParseException { |
| align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMIN; |
| } |
| |
| /** |
| * Implements {@link PreserveAspectRatioHandler#meet()}. |
| */ |
| public void meet() throws ParseException { |
| meetOrSlice = SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET; |
| } |
| |
| /** |
| * Implements {@link PreserveAspectRatioHandler#slice()}. |
| */ |
| public void slice() throws ParseException { |
| meetOrSlice = SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE; |
| } |
| }; |
| |
| /** |
| * Creates a new AnimatablePreserveAspectRatioValueFactory. |
| */ |
| public AnimatablePreserveAspectRatioValueFactory() { |
| parser.setPreserveAspectRatioHandler(handler); |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a string. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String ns, |
| String ln, boolean isCSS, String s) { |
| try { |
| parser.parse(s); |
| return new AnimatablePreserveAspectRatioValue(target, align, |
| meetOrSlice); |
| } catch (ParseException e) { |
| // XXX Do something better than returning null. |
| return null; |
| } |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a CSS {@link Value}. Returns null |
| * since preserveAspectRatio values aren't used in CSS values. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String pn, |
| Value v) { |
| return null; |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatableLengthValue}s. |
| */ |
| protected class AnimatableLengthValueFactory implements Factory { |
| |
| /** |
| * The parsed length unit type. |
| */ |
| protected short type; |
| |
| /** |
| * The parsed length value. |
| */ |
| protected float value; |
| |
| /** |
| * Parser for lengths. |
| */ |
| protected LengthParser parser = new LengthParser(); |
| |
| /** |
| * Handler for the length parser. |
| */ |
| protected LengthHandler handler = new DefaultLengthHandler() { |
| public void startLength() throws ParseException { |
| type = SVGLength.SVG_LENGTHTYPE_NUMBER; |
| } |
| public void lengthValue(float v) throws ParseException { |
| value = v; |
| } |
| public void em() throws ParseException { |
| type = SVGLength.SVG_LENGTHTYPE_EMS; |
| } |
| public void ex() throws ParseException { |
| type = SVGLength.SVG_LENGTHTYPE_EXS; |
| } |
| public void in() throws ParseException { |
| type = SVGLength.SVG_LENGTHTYPE_IN; |
| } |
| public void cm() throws ParseException { |
| type = SVGLength.SVG_LENGTHTYPE_CM; |
| } |
| public void mm() throws ParseException { |
| type = SVGLength.SVG_LENGTHTYPE_MM; |
| } |
| public void pc() throws ParseException { |
| type = SVGLength.SVG_LENGTHTYPE_PC; |
| } |
| public void pt() throws ParseException { |
| type = SVGLength.SVG_LENGTHTYPE_PT; |
| } |
| public void px() throws ParseException { |
| type = SVGLength.SVG_LENGTHTYPE_PX; |
| } |
| public void percentage() throws ParseException { |
| type = SVGLength.SVG_LENGTHTYPE_PERCENTAGE; |
| } |
| public void endLength() throws ParseException { |
| } |
| }; |
| |
| /** |
| * Creates a new AnimatableLengthValueFactory. |
| */ |
| public AnimatableLengthValueFactory() { |
| parser.setLengthHandler(handler); |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a string. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String ns, |
| String ln, boolean isCSS, String s) { |
| short pcInterp = target.getPercentageInterpretation(ns, ln, isCSS); |
| try { |
| parser.parse(s); |
| return new AnimatableLengthValue |
| (target, type, value, pcInterp); |
| } catch (ParseException e) { |
| // XXX Do something better than returning null. |
| return null; |
| } |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a CSS {@link Value}. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String pn, |
| Value v) { |
| return new AnimatableIntegerValue(target, |
| Math.round(v.getFloatValue())); |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatableLengthListValue}s. |
| */ |
| protected class AnimatableLengthListValueFactory implements Factory { |
| |
| /** |
| * Parser for length lists. |
| */ |
| protected LengthListParser parser = new LengthListParser(); |
| |
| /** |
| * The producer class that accumulates the lengths. |
| */ |
| protected LengthArrayProducer producer = new LengthArrayProducer(); |
| |
| /** |
| * Creates a new AnimatableLengthListValueFactory. |
| */ |
| public AnimatableLengthListValueFactory() { |
| parser.setLengthListHandler(producer); |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a string. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String ns, |
| String ln, boolean isCSS, String s) { |
| try { |
| short pcInterp = target.getPercentageInterpretation |
| (ns, ln, isCSS); |
| parser.parse(s); |
| return new AnimatableLengthListValue |
| (target, producer.getLengthTypeArray(), |
| producer.getLengthValueArray(), |
| pcInterp); |
| } catch (ParseException e) { |
| // XXX Do something better than returning null. |
| return null; |
| } |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a CSS {@link Value}. Returns null |
| * since point lists aren't used in CSS values. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String pn, |
| Value v) { |
| return null; |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatableNumberListValue}s. |
| */ |
| protected class AnimatableNumberListValueFactory implements Factory { |
| |
| /** |
| * Parser for number lists. |
| */ |
| protected NumberListParser parser = new NumberListParser(); |
| |
| /** |
| * The producer class that accumulates the numbers. |
| */ |
| protected FloatArrayProducer producer = new FloatArrayProducer(); |
| |
| /** |
| * Creates a new AnimatableNumberListValueFactory. |
| */ |
| public AnimatableNumberListValueFactory() { |
| parser.setNumberListHandler(producer); |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a string. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String ns, |
| String ln, boolean isCSS, String s) { |
| try { |
| parser.parse(s); |
| return new AnimatableNumberListValue(target, |
| producer.getFloatArray()); |
| } catch (ParseException e) { |
| // XXX Do something better than returning null. |
| return null; |
| } |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a CSS {@link Value}. Returns null |
| * since number lists aren't used in CSS values. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String pn, |
| Value v) { |
| return null; |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatableNumberListValue}s. |
| */ |
| protected class AnimatableRectValueFactory implements Factory { |
| |
| /** |
| * Parser for number lists. |
| */ |
| protected NumberListParser parser = new NumberListParser(); |
| |
| /** |
| * The producer class that accumulates the numbers. |
| */ |
| protected FloatArrayProducer producer = new FloatArrayProducer(); |
| |
| /** |
| * Creates a new AnimatableNumberListValueFactory. |
| */ |
| public AnimatableRectValueFactory() { |
| parser.setNumberListHandler(producer); |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a string. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String ns, |
| String ln, boolean isCSS, String s) { |
| try { |
| parser.parse(s); |
| float[] r = producer.getFloatArray(); |
| if (r.length != 4) { |
| // XXX Do something better than returning null. |
| return null; |
| } |
| return new AnimatableRectValue(target, r[0], r[1], r[2], r[3]); |
| } catch (ParseException e) { |
| // XXX Do something better than returning null. |
| return null; |
| } |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a CSS {@link Value}. Returns null |
| * since rects aren't used in CSS values. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String pn, |
| Value v) { |
| return null; |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatablePointListValue}s. |
| */ |
| protected class AnimatablePointListValueFactory implements Factory { |
| |
| /** |
| * Parser for point lists. |
| */ |
| protected PointsParser parser = new PointsParser(); |
| |
| /** |
| * The producer class that accumulates the points. |
| */ |
| protected FloatArrayProducer producer = new FloatArrayProducer(); |
| |
| /** |
| * Creates a new AnimatablePointListValueFactory. |
| */ |
| public AnimatablePointListValueFactory() { |
| parser.setPointsHandler(producer); |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a string. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String ns, |
| String ln, boolean isCSS, String s) { |
| try { |
| parser.parse(s); |
| return new AnimatablePointListValue(target, |
| producer.getFloatArray()); |
| } catch (ParseException e) { |
| // XXX Do something better than returning null. |
| return null; |
| } |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a CSS {@link Value}. Returns null |
| * since point lists aren't used in CSS values. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String pn, |
| Value v) { |
| return null; |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatablePathDataValue}s. |
| */ |
| protected class AnimatablePathDataFactory implements Factory { |
| |
| /** |
| * Parser for path data. |
| */ |
| protected PathParser parser = new PathParser(); |
| |
| /** |
| * The producer class that accumulates the path segments. |
| */ |
| protected PathArrayProducer producer = new PathArrayProducer(); |
| |
| /** |
| * Creates a new AnimatablePathDataFactory. |
| */ |
| public AnimatablePathDataFactory() { |
| parser.setPathHandler(producer); |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a string. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String ns, |
| String ln, boolean isCSS, String s) { |
| try { |
| parser.parse(s); |
| return new AnimatablePathDataValue |
| (target, producer.getPathCommands(), |
| producer.getPathParameters()); |
| } catch (ParseException e) { |
| // XXX Do something better than returning null. |
| return null; |
| } |
| } |
| |
| /** |
| * Creates a new AnimatableValue from a CSS {@link Value}. Returns null |
| * since point lists aren't used in CSS values. |
| */ |
| public AnimatableValue createValue(AnimationTarget target, String pn, |
| Value v) { |
| return null; |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatableStringValue}s. |
| */ |
| protected class UncomputedAnimatableStringValueFactory implements Factory { |
| |
| public AnimatableValue createValue(AnimationTarget target, String ns, |
| String ln, boolean isCSS, String s) { |
| return new AnimatableStringValue(target, s); |
| } |
| |
| public AnimatableValue createValue(AnimationTarget target, String pn, |
| Value v) { |
| return new AnimatableStringValue(target, v.getCssText()); |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatableLengthOrIdentValue}s. |
| */ |
| protected class AnimatableLengthOrIdentFactory extends CSSValueFactory { |
| |
| protected AnimatableValue createAnimatableValue(AnimationTarget target, |
| String pn, Value v) { |
| if (v instanceof StringValue) { |
| return new AnimatableLengthOrIdentValue(target, |
| v.getStringValue()); |
| } |
| short pcInterp = target.getPercentageInterpretation(null, pn, true); |
| FloatValue fv = (FloatValue) v; |
| return new AnimatableLengthOrIdentValue |
| (target, fv.getPrimitiveType(), fv.getFloatValue(), pcInterp); |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatableNumberOrIdentValue}s. |
| */ |
| protected class AnimatableNumberOrIdentFactory extends CSSValueFactory { |
| |
| /** |
| * Whether numbers are actually numeric keywords, as with the |
| * font-weight property. |
| */ |
| protected boolean numericIdents; |
| |
| public AnimatableNumberOrIdentFactory(boolean numericIdents) { |
| this.numericIdents = numericIdents; |
| } |
| |
| protected AnimatableValue createAnimatableValue(AnimationTarget target, |
| String pn, Value v) { |
| if (v instanceof StringValue) { |
| return new AnimatableNumberOrIdentValue(target, |
| v.getStringValue()); |
| } |
| FloatValue fv = (FloatValue) v; |
| return new AnimatableNumberOrIdentValue(target, fv.getFloatValue(), |
| numericIdents); |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatableAngleValue}s. |
| */ |
| protected class AnimatableAngleValueFactory extends CSSValueFactory { |
| |
| protected AnimatableValue createAnimatableValue(AnimationTarget target, |
| String pn, Value v) { |
| FloatValue fv = (FloatValue) v; |
| short unit; |
| switch (fv.getPrimitiveType()) { |
| case CSSPrimitiveValue.CSS_NUMBER: |
| case CSSPrimitiveValue.CSS_DEG: |
| unit = SVGAngle.SVG_ANGLETYPE_DEG; |
| break; |
| case CSSPrimitiveValue.CSS_RAD: |
| unit = SVGAngle.SVG_ANGLETYPE_RAD; |
| break; |
| case CSSPrimitiveValue.CSS_GRAD: |
| unit = SVGAngle.SVG_ANGLETYPE_GRAD; |
| break; |
| default: |
| // XXX Do something better than returning null. |
| return null; |
| } |
| return new AnimatableAngleValue(target, fv.getFloatValue(), unit); |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatableAngleOrIdentValue}s. |
| */ |
| protected class AnimatableAngleOrIdentFactory extends CSSValueFactory { |
| |
| protected AnimatableValue createAnimatableValue(AnimationTarget target, |
| String pn, Value v) { |
| if (v instanceof StringValue) { |
| return new AnimatableAngleOrIdentValue(target, |
| v.getStringValue()); |
| } |
| FloatValue fv = (FloatValue) v; |
| short unit; |
| switch (fv.getPrimitiveType()) { |
| case CSSPrimitiveValue.CSS_NUMBER: |
| case CSSPrimitiveValue.CSS_DEG: |
| unit = SVGAngle.SVG_ANGLETYPE_DEG; |
| break; |
| case CSSPrimitiveValue.CSS_RAD: |
| unit = SVGAngle.SVG_ANGLETYPE_RAD; |
| break; |
| case CSSPrimitiveValue.CSS_GRAD: |
| unit = SVGAngle.SVG_ANGLETYPE_GRAD; |
| break; |
| default: |
| // XXX Do something better than returning null. |
| return null; |
| } |
| return new AnimatableAngleOrIdentValue(target, fv.getFloatValue(), |
| unit); |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatableColorValue}s. |
| */ |
| protected class AnimatableColorValueFactory extends CSSValueFactory { |
| |
| protected AnimatableValue createAnimatableValue(AnimationTarget target, |
| String pn, Value v) { |
| Paint p = PaintServer.convertPaint |
| (target.getElement(), null, v, 1.0f, ctx); |
| if (p instanceof Color) { |
| Color c = (Color) p; |
| return new AnimatableColorValue(target, |
| c.getRed() / 255f, |
| c.getGreen() / 255f, |
| c.getBlue() / 255f); |
| } |
| // XXX Indicate that the parsed value wasn't a Color? |
| return null; |
| } |
| } |
| |
| /** |
| * Factory class for {@link AnimatablePaintValue}s. |
| */ |
| protected class AnimatablePaintValueFactory extends CSSValueFactory { |
| |
| /** |
| * Creates a new {@link AnimatablePaintValue} from a {@link Color} |
| * object. |
| */ |
| protected AnimatablePaintValue createColorPaintValue(AnimationTarget t, |
| Color c) { |
| return AnimatablePaintValue.createColorPaintValue |
| (t, c.getRed() / 255f, c.getGreen() / 255f, c.getBlue() / 255f); |
| |
| } |
| |
| protected AnimatableValue createAnimatableValue(AnimationTarget target, |
| String pn, Value v) { |
| if (v.getCssValueType() == CSSValue.CSS_PRIMITIVE_VALUE) { |
| switch (v.getPrimitiveType()) { |
| case CSSPrimitiveValue.CSS_IDENT: |
| return AnimatablePaintValue.createNonePaintValue(target); |
| case CSSPrimitiveValue.CSS_RGBCOLOR: { |
| Paint p = PaintServer.convertPaint |
| (target.getElement(), null, v, 1.0f, ctx); |
| return createColorPaintValue(target, (Color) p); |
| } |
| case CSSPrimitiveValue.CSS_URI: |
| return AnimatablePaintValue.createURIPaintValue |
| (target, v.getStringValue()); |
| } |
| } else { |
| Value v1 = v.item(0); |
| switch (v1.getPrimitiveType()) { |
| case CSSPrimitiveValue.CSS_RGBCOLOR: { |
| Paint p = PaintServer.convertPaint |
| (target.getElement(), null, v, 1.0f, ctx); |
| return createColorPaintValue(target, (Color) p); |
| } |
| case CSSPrimitiveValue.CSS_URI: { |
| Value v2 = v.item(1); |
| switch (v2.getPrimitiveType()) { |
| case CSSPrimitiveValue.CSS_IDENT: |
| return AnimatablePaintValue.createURINonePaintValue |
| (target, v1.getStringValue()); |
| case CSSPrimitiveValue.CSS_RGBCOLOR: { |
| Paint p = PaintServer.convertPaint |
| (target.getElement(), null, v.item(1), 1.0f, ctx); |
| return createColorPaintValue(target, (Color) p); |
| } |
| } |
| } |
| } |
| } |
| // XXX Indicate that the specified Value wasn't a Color? |
| return null; |
| } |
| } |
| |
| /** |
| * Factory class for computed CSS {@link AnimatableStringValue}s. |
| */ |
| protected class AnimatableStringValueFactory extends CSSValueFactory { |
| |
| protected AnimatableValue createAnimatableValue(AnimationTarget target, |
| String pn, Value v) { |
| return new AnimatableStringValue(target, v.getCssText()); |
| } |
| } |
| } |