1 /* -*- Mode: JS; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
4 * - Presentation Engine - *
5 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
8 * WARNING: any comment that should not be striped out by the script
9 * generating the C++ header file must start with a '/' and exactly 5 '*'
10 * not striped examples: '/*****', '/***** *'
11 * striped examples: '/** ***' (not contiguous), '/******' (more than 5)
13 * NOTE: This file combines several works, under different
14 * licenses. See the @licstart / @licend sections below.
17 /*! Hammer.JS - v2.0.7 - 2016-04-22
18 * http://hammerjs.github.io/
20 * Copyright (c) 2016 Jorik Tangelder;
21 * Licensed under the MIT license */
22 (function(window, document, exportName, undefined) {
25 var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
26 var TEST_ELEMENT = document.createElement('div');
28 var TYPE_FUNCTION = 'function';
30 var round = Math.round;
38 Math.trunc = function (v) {
39 return v < 0 ? Math.ceil(v) : Math.floor(v);
44 * set a timeout with a given scope
45 * @param {Function} fn
46 * @param {Number} timeout
47 * @param {Object} context
50 function setTimeoutContext(fn, timeout, context) {
51 return setTimeout(bindFn(fn, context), timeout);
55 * if the argument is an array, we want to execute the fn on each entry
56 * if it aint an array we don't want to do a thing.
57 * this is used by all the methods that accept a single and array argument.
58 * @param {*|Array} arg
60 * @param {Object} [context]
63 function invokeArrayArg(arg, fn, context) {
64 if (Array.isArray(arg)) {
65 each(arg, context[fn], context);
72 * walk objects and arrays
74 * @param {Function} iterator
75 * @param {Object} context
77 function each(obj, iterator, context) {
85 obj.forEach(iterator, context);
86 } else if (obj.length !== undefined) {
88 while (i < obj.length) {
89 iterator.call(context, obj[i], i, obj);
94 obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
100 * wrap a method with a deprecation warning and stack trace
101 * @param {Function} method
102 * @param {String} name
103 * @param {String} message
104 * @returns {Function} A new function wrapping the supplied method.
106 function deprecate(method, name, message) {
107 var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
109 var e = new Error('get-stack-trace');
110 var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
111 .replace(/^\s+at\s+/gm, '')
112 .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
114 var log = window.console && (window.console.warn || window.console.log);
116 log.call(window.console, deprecationMessage, stack);
118 return method.apply(this, arguments);
124 * means that properties in dest will be overwritten by the ones in src.
125 * @param {Object} target
126 * @param {...Object} objects_to_assign
127 * @returns {Object} target
130 if (typeof Object.assign !== 'function') {
131 assign = function assign(target) {
132 if (target === undefined || target === null) {
133 throw new TypeError('Cannot convert undefined or null to object');
136 var output = Object(target);
137 for (var index = 1; index < arguments.length; index++) {
138 var source = arguments[index];
139 if (source !== undefined && source !== null) {
140 for (var nextKey in source) {
141 if (source.hasOwnProperty(nextKey)) {
142 output[nextKey] = source[nextKey];
150 assign = Object.assign;
155 * means that properties in dest will be overwritten by the ones in src.
156 * @param {Object} dest
157 * @param {Object} src
158 * @param {Boolean} [merge=false]
159 * @returns {Object} dest
161 var extend = deprecate(function extend(dest, src, merge) {
162 var keys = Object.keys(src);
164 while (i < keys.length) {
165 if (!merge || (merge && dest[keys[i]] === undefined)) {
166 dest[keys[i]] = src[keys[i]];
171 }, 'extend', 'Use `assign`.');
174 * merge the values from src in the dest.
175 * means that properties that exist in dest will not be overwritten by src
176 * @param {Object} dest
177 * @param {Object} src
178 * @returns {Object} dest
180 var merge = deprecate(function merge(dest, src) {
181 return extend(dest, src, true);
182 }, 'merge', 'Use `assign`.');
185 * simple class inheritance
186 * @param {Function} child
187 * @param {Function} base
188 * @param {Object} [properties]
190 function inherit(child, base, properties) {
191 var baseP = base.prototype,
194 childP = child.prototype = Object.create(baseP);
195 childP.constructor = child;
196 childP._super = baseP;
199 assign(childP, properties);
204 * simple function bind
205 * @param {Function} fn
206 * @param {Object} context
207 * @returns {Function}
209 function bindFn(fn, context) {
210 return function boundFn() {
211 return fn.apply(context, arguments);
216 * let a boolean value also be a function that must return a boolean
217 * this first item in args will be used as the context
218 * @param {Boolean|Function} val
219 * @param {Array} [args]
222 function boolOrFn(val, args) {
223 if (typeof val == TYPE_FUNCTION) {
224 return val.apply(args ? args[0] || undefined : undefined, args);
230 * use the val2 when val1 is undefined
235 function ifUndefined(val1, val2) {
236 return (val1 === undefined) ? val2 : val1;
240 * addEventListener with multiple events at once
241 * @param {EventTarget} target
242 * @param {String} types
243 * @param {Function} handler
245 function addEventListeners(target, types, handler) {
246 each(splitStr(types), function(type) {
247 target.addEventListener(type, handler, false);
252 * removeEventListener with multiple events at once
253 * @param {EventTarget} target
254 * @param {String} types
255 * @param {Function} handler
257 function removeEventListeners(target, types, handler) {
258 each(splitStr(types), function(type) {
259 target.removeEventListener(type, handler, false);
264 * find if a node is in the given parent
266 * @param {HTMLElement} node
267 * @param {HTMLElement} parent
268 * @return {Boolean} found
270 function hasParent(node, parent) {
272 if (node == parent) {
275 node = node.parentNode;
281 * small indexOf wrapper
282 * @param {String} str
283 * @param {String} find
284 * @returns {Boolean} found
286 function inStr(str, find) {
287 return str.indexOf(find) > -1;
291 * split string on whitespace
292 * @param {String} str
293 * @returns {Array} words
295 function splitStr(str) {
296 return str.trim().split(/\s+/g);
300 * find if an array contains the object using indexOf or a simple polyFill
302 * @param {String} find
303 * @param {String} [findByKey]
304 * @return {Boolean|Number} false when not found, or the index
306 function inArray(src, find, findByKey) {
307 if (src.indexOf && !findByKey) {
308 return src.indexOf(find);
311 while (i < src.length) {
312 if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
322 * convert array-like objects to real arrays
323 * @param {Object} obj
326 function toArray(obj) {
327 return Array.prototype.slice.call(obj, 0);
331 * unique array with objects based on a key (like 'id') or just by the array's value
332 * @param {Array} src [{id:1},{id:2},{id:1}]
333 * @param {String} [key]
334 * @param {Boolean} [sort=False]
335 * @returns {Array} [{id:1},{id:2}]
337 function uniqueArray(src, key, sort) {
342 while (i < src.length) {
343 var val = key ? src[i][key] : src[i];
344 if (inArray(values, val) < 0) {
345 results.push(src[i]);
353 results = results.sort();
355 results = results.sort(function sortUniqueArray(a, b) {
356 return a[key] > b[key];
365 * get the prefixed property
366 * @param {Object} obj
367 * @param {String} property
368 * @returns {String|Undefined} prefixed
370 function prefixed(obj, property) {
371 // tml: Have to check for obj being undefined
372 if (obj === undefined) {
377 var camelProp = property[0].toUpperCase() + property.slice(1);
380 while (i < VENDOR_PREFIXES.length) {
381 prefix = VENDOR_PREFIXES[i];
382 prop = (prefix) ? prefix + camelProp : property;
394 * @returns {number} uniqueId
397 function uniqueId() {
402 * get the window object of an element
403 * @param {HTMLElement} element
404 * @returns {DocumentView|Window}
406 function getWindowForElement(element) {
407 var doc = element.ownerDocument || element;
408 return (doc.defaultView || doc.parentWindow || window);
411 var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
413 var SUPPORT_TOUCH = ('ontouchstart' in window);
414 var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
415 var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
417 var INPUT_TYPE_TOUCH = 'touch';
418 var INPUT_TYPE_PEN = 'pen';
419 var INPUT_TYPE_MOUSE = 'mouse';
420 var INPUT_TYPE_KINECT = 'kinect';
422 var COMPUTE_INTERVAL = 25;
427 var INPUT_CANCEL = 8;
429 var DIRECTION_NONE = 1;
430 var DIRECTION_LEFT = 2;
431 var DIRECTION_RIGHT = 4;
432 var DIRECTION_UP = 8;
433 var DIRECTION_DOWN = 16;
435 var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
436 var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
437 var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
439 var PROPS_XY = ['x', 'y'];
440 var PROPS_CLIENT_XY = ['clientX', 'clientY'];
443 * create new input type manager
444 * @param {Manager} manager
445 * @param {Function} callback
449 function Input(manager, callback) {
451 this.manager = manager;
452 this.callback = callback;
453 this.element = manager.element;
454 this.target = manager.options.inputTarget;
456 // smaller wrapper around the handler, for the scope and the enabled state of the manager,
457 // so when disabled the input events are completely bypassed.
458 this.domHandler = function(ev) {
459 if (boolOrFn(manager.options.enable, [manager])) {
470 * should handle the inputEvent data and trigger the callback
473 handler: function() { },
479 this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
480 this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
481 this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
487 destroy: function() {
488 this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
489 this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
490 this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
495 * create new input type manager
496 * called by the Manager constructor
497 * @param {Hammer} manager
500 function createInputInstance(manager) {
502 var inputClass = manager.options.inputClass;
506 } else if (!SUPPORT_TOUCH && SUPPORT_POINTER_EVENTS) {
507 Type = PointerEventInput;
508 } else if (SUPPORT_ONLY_TOUCH) {
510 } else if (!SUPPORT_TOUCH) {
513 Type = TouchMouseInput;
515 return new (Type)(manager, inputHandler);
519 * handle input events
520 * @param {Manager} manager
521 * @param {String} eventType
522 * @param {Object} input
524 function inputHandler(manager, eventType, input) {
525 var pointersLen = input.pointers.length;
526 var changedPointersLen = input.changedPointers.length;
527 var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
528 var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
530 input.isFirst = !!isFirst;
531 input.isFinal = !!isFinal;
534 manager.session = {};
537 // source event is the normalized value of the domEvents
538 // like 'touchstart, mouseup, pointerdown'
539 input.eventType = eventType;
541 // compute scale, rotation etc
542 computeInputData(manager, input);
545 manager.emit('hammer.input', input);
547 manager.recognize(input);
548 manager.session.prevInput = input;
552 * extend the data with some usable properties like scale, rotate, velocity etc
553 * @param {Object} manager
554 * @param {Object} input
556 function computeInputData(manager, input) {
557 var session = manager.session;
558 var pointers = input.pointers;
559 var pointersLength = pointers.length;
561 // store the first input to calculate the distance and direction
562 if (!session.firstInput) {
563 session.firstInput = simpleCloneInputData(input);
566 // to compute scale and rotation we need to store the multiple touches
567 if (pointersLength > 1 && !session.firstMultiple) {
568 session.firstMultiple = simpleCloneInputData(input);
569 } else if (pointersLength === 1) {
570 session.firstMultiple = false;
573 var firstInput = session.firstInput;
574 var firstMultiple = session.firstMultiple;
575 var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
577 var center = input.center = getCenter(pointers);
578 input.timeStamp = now();
579 input.deltaTime = input.timeStamp - firstInput.timeStamp;
581 input.angle = getAngle(offsetCenter, center);
582 input.distance = getDistance(offsetCenter, center);
584 computeDeltaXY(session, input);
585 input.offsetDirection = getDirection(input.deltaX, input.deltaY);
587 var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
588 input.overallVelocityX = overallVelocity.x;
589 input.overallVelocityY = overallVelocity.y;
590 input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
592 input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
593 input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
595 input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
596 session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
598 computeIntervalInputData(session, input);
600 // find the correct target
601 var target = manager.element;
602 if (hasParent(input.srcEvent.target, target)) {
603 target = input.srcEvent.target;
605 input.target = target;
608 function computeDeltaXY(session, input) {
609 var center = input.center;
610 var offset = session.offsetDelta || {};
611 var prevDelta = session.prevDelta || {};
612 var prevInput = session.prevInput || {};
614 if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
615 prevDelta = session.prevDelta = {
616 x: prevInput.deltaX || 0,
617 y: prevInput.deltaY || 0
620 offset = session.offsetDelta = {
626 input.deltaX = prevDelta.x + (center.x - offset.x);
627 input.deltaY = prevDelta.y + (center.y - offset.y);
631 * velocity is calculated every x ms
632 * @param {Object} session
633 * @param {Object} input
635 function computeIntervalInputData(session, input) {
636 var last = session.lastInterval || input,
637 deltaTime = input.timeStamp - last.timeStamp,
638 velocity, velocityX, velocityY, direction;
640 if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
641 var deltaX = input.deltaX - last.deltaX;
642 var deltaY = input.deltaY - last.deltaY;
644 var v = getVelocity(deltaTime, deltaX, deltaY);
647 velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
648 direction = getDirection(deltaX, deltaY);
650 session.lastInterval = input;
652 // use latest velocity info if it doesn't overtake a minimum period
653 velocity = last.velocity;
654 velocityX = last.velocityX;
655 velocityY = last.velocityY;
656 direction = last.direction;
659 input.velocity = velocity;
660 input.velocityX = velocityX;
661 input.velocityY = velocityY;
662 input.direction = direction;
666 * create a simple clone from the input used for storage of firstInput and firstMultiple
667 * @param {Object} input
668 * @returns {Object} clonedInputData
670 function simpleCloneInputData(input) {
671 // make a simple copy of the pointers because we will get a reference if we don't
672 // we only need clientXY for the calculations
675 while (i < input.pointers.length) {
677 clientX: round(input.pointers[i].clientX),
678 clientY: round(input.pointers[i].clientY)
686 center: getCenter(pointers),
687 deltaX: input.deltaX,
693 * get the center of all the pointers
694 * @param {Array} pointers
695 * @return {Object} center contains `x` and `y` properties
697 function getCenter(pointers) {
698 var pointersLength = pointers.length;
700 // no need to loop when only one touch
701 if (pointersLength === 1) {
703 x: round(pointers[0].clientX),
704 y: round(pointers[0].clientY)
708 var x = 0, y = 0, i = 0;
709 while (i < pointersLength) {
710 x += pointers[i].clientX;
711 y += pointers[i].clientY;
716 x: round(x / pointersLength),
717 y: round(y / pointersLength)
722 * calculate the velocity between two points. unit is in px per ms.
723 * @param {Number} deltaTime
726 * @return {Object} velocity `x` and `y`
728 function getVelocity(deltaTime, x, y) {
730 x: x / deltaTime || 0,
731 y: y / deltaTime || 0
736 * get the direction between two points
739 * @return {Number} direction
741 function getDirection(x, y) {
743 return DIRECTION_NONE;
746 if (abs(x) >= abs(y)) {
747 return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
749 return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
753 * calculate the absolute distance between two points
754 * @param {Object} p1 {x, y}
755 * @param {Object} p2 {x, y}
756 * @param {Array} [props] containing x and y keys
757 * @return {Number} distance
759 function getDistance(p1, p2, props) {
763 var x = p2[props[0]] - p1[props[0]],
764 y = p2[props[1]] - p1[props[1]];
766 return Math.sqrt((x * x) + (y * y));
770 * calculate the angle between two coordinates
773 * @param {Array} [props] containing x and y keys
774 * @return {Number} angle
776 function getAngle(p1, p2, props) {
780 var x = p2[props[0]] - p1[props[0]],
781 y = p2[props[1]] - p1[props[1]];
782 return Math.atan2(y, x) * 180 / Math.PI;
786 * calculate the rotation degrees between two pointersets
787 * @param {Array} start array of pointers
788 * @param {Array} end array of pointers
789 * @return {Number} rotation
791 function getRotation(start, end) {
792 return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
796 * calculate the scale factor between two pointersets
797 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
798 * @param {Array} start array of pointers
799 * @param {Array} end array of pointers
800 * @return {Number} scale
802 function getScale(start, end) {
803 return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
806 var MOUSE_INPUT_MAP = {
807 mousedown: INPUT_START,
808 mousemove: INPUT_MOVE,
812 var MOUSE_ELEMENT_EVENTS = 'mousedown';
813 var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
820 function MouseInput() {
821 this.evEl = MOUSE_ELEMENT_EVENTS;
822 this.evWin = MOUSE_WINDOW_EVENTS;
824 this.pressed = false; // mousedown state
826 Input.apply(this, arguments);
829 inherit(MouseInput, Input, {
831 * handle mouse events
834 handler: function MEhandler(ev) {
835 // console.log('==> MouseInput handler');
836 var eventType = MOUSE_INPUT_MAP[ev.type];
838 // on start we want to have the left mouse button down
839 if (eventType & INPUT_START && ev.button === 0) {
843 if (eventType & INPUT_MOVE && ev.which !== 1) {
844 eventType = INPUT_END;
847 // mouse must be down
852 if (eventType & INPUT_END) {
853 this.pressed = false;
856 this.callback(this.manager, eventType, {
858 changedPointers: [ev],
859 pointerType: INPUT_TYPE_MOUSE,
865 var POINTER_INPUT_MAP = {
866 pointerdown: INPUT_START,
867 pointermove: INPUT_MOVE,
868 pointerup: INPUT_END,
869 pointercancel: INPUT_CANCEL,
870 pointerout: INPUT_CANCEL
873 // in IE10 the pointer types is defined as an enum
874 var IE10_POINTER_TYPE_ENUM = {
878 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
881 var POINTER_ELEMENT_EVENTS = 'pointerdown';
882 var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
884 // IE10 has prefixed support, and case-sensitive
885 if (window.MSPointerEvent && !window.PointerEvent) {
886 POINTER_ELEMENT_EVENTS = 'MSPointerDown';
887 POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
891 * Pointer events input
895 function PointerEventInput() {
896 this.evEl = POINTER_ELEMENT_EVENTS;
897 this.evWin = POINTER_WINDOW_EVENTS;
899 Input.apply(this, arguments);
901 this.store = (this.manager.session.pointerEvents = []);
904 inherit(PointerEventInput, Input, {
906 * handle mouse events
909 handler: function PEhandler(ev) {
910 // console.log('==> PointerEventInput handler');
911 var store = this.store;
912 var removePointer = false;
914 var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
915 var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
916 var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
918 var isTouch = (pointerType == INPUT_TYPE_TOUCH);
920 // get index of the event in the store
921 var storeIndex = inArray(store, ev.pointerId, 'pointerId');
923 // start and mouse must be down
924 if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
925 if (storeIndex < 0) {
927 storeIndex = store.length - 1;
929 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
930 removePointer = true;
933 // it not found, so the pointer hasn't been down (so it's probably a hover)
934 if (storeIndex < 0) {
938 // update the event in the store
939 store[storeIndex] = ev;
941 this.callback(this.manager, eventType, {
943 changedPointers: [ev],
944 pointerType: pointerType,
949 // remove from the store
950 store.splice(storeIndex, 1);
955 var SINGLE_TOUCH_INPUT_MAP = {
956 touchstart: INPUT_START,
957 touchmove: INPUT_MOVE,
959 touchcancel: INPUT_CANCEL
962 var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
963 var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
970 function SingleTouchInput() {
971 this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
972 this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
973 this.started = false;
975 Input.apply(this, arguments);
978 inherit(SingleTouchInput, Input, {
979 handler: function TEhandler(ev) {
980 // console.log('==> SingleTouchInput handler');
981 var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
983 // should we handle the touch events?
984 if (type === INPUT_START) {
992 var touches = normalizeSingleTouches.call(this, ev, type);
994 // when done, reset the started state
995 if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
996 this.started = false;
999 this.callback(this.manager, type, {
1000 pointers: touches[0],
1001 changedPointers: touches[1],
1002 pointerType: INPUT_TYPE_TOUCH,
1009 * @this {TouchInput}
1010 * @param {Object} ev
1011 * @param {Number} type flag
1012 * @returns {undefined|Array} [all, changed]
1014 function normalizeSingleTouches(ev, type) {
1015 var all = toArray(ev.touches);
1016 var changed = toArray(ev.changedTouches);
1018 if (type & (INPUT_END | INPUT_CANCEL)) {
1019 all = uniqueArray(all.concat(changed), 'identifier', true);
1022 return [all, changed];
1025 var TOUCH_INPUT_MAP = {
1026 touchstart: INPUT_START,
1027 touchmove: INPUT_MOVE,
1028 touchend: INPUT_END,
1029 touchcancel: INPUT_CANCEL
1032 var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
1035 * Multi-user touch events input
1039 function TouchInput() {
1040 this.evTarget = TOUCH_TARGET_EVENTS;
1041 this.targetIds = {};
1043 Input.apply(this, arguments);
1046 inherit(TouchInput, Input, {
1047 handler: function MTEhandler(ev) {
1048 // console.log('==> TouchInput handler');
1049 var type = TOUCH_INPUT_MAP[ev.type];
1050 var touches = getTouches.call(this, ev, type);
1055 this.callback(this.manager, type, {
1056 pointers: touches[0],
1057 changedPointers: touches[1],
1058 pointerType: INPUT_TYPE_TOUCH,
1065 * @this {TouchInput}
1066 * @param {Object} ev
1067 * @param {Number} type flag
1068 * @returns {undefined|Array} [all, changed]
1070 function getTouches(ev, type) {
1071 var allTouches = toArray(ev.touches);
1072 var targetIds = this.targetIds;
1074 // when there is only one touch, the process can be simplified
1075 if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
1076 targetIds[allTouches[0].identifier] = true;
1077 return [allTouches, allTouches];
1082 changedTouches = toArray(ev.changedTouches),
1083 changedTargetTouches = [],
1084 target = this.target;
1086 // get target touches from touches
1087 targetTouches = allTouches.filter(function(touch) {
1088 return hasParent(touch.target, target);
1092 if (type === INPUT_START) {
1094 while (i < targetTouches.length) {
1095 targetIds[targetTouches[i].identifier] = true;
1100 // filter changed touches to only contain touches that exist in the collected target ids
1102 while (i < changedTouches.length) {
1103 if (targetIds[changedTouches[i].identifier]) {
1104 changedTargetTouches.push(changedTouches[i]);
1107 // cleanup removed touches
1108 if (type & (INPUT_END | INPUT_CANCEL)) {
1109 delete targetIds[changedTouches[i].identifier];
1114 if (!changedTargetTouches.length) {
1119 // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
1120 uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
1121 changedTargetTouches
1126 * Combined touch and mouse input
1128 * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
1129 * This because touch devices also emit mouse events while doing a touch.
1135 var DEDUP_TIMEOUT = 2500;
1136 var DEDUP_DISTANCE = 25;
1138 function TouchMouseInput() {
1139 Input.apply(this, arguments);
1141 var handler = bindFn(this.handler, this);
1142 this.touch = new TouchInput(this.manager, handler);
1143 this.mouse = new MouseInput(this.manager, handler);
1145 this.primaryTouch = null;
1146 this.lastTouches = [];
1149 inherit(TouchMouseInput, Input, {
1151 * handle mouse and touch events
1152 * @param {Hammer} manager
1153 * @param {String} inputEvent
1154 * @param {Object} inputData
1156 handler: function TMEhandler(manager, inputEvent, inputData) {
1157 // console.log('==> TouchMouseInput handler');
1158 var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
1159 isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
1161 if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
1165 // when we're in a touch event, record touches to de-dupe synthetic mouse event
1167 recordTouches.call(this, inputEvent, inputData);
1168 } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
1172 this.callback(manager, inputEvent, inputData);
1176 * remove the event listeners
1178 destroy: function destroy() {
1179 this.touch.destroy();
1180 this.mouse.destroy();
1184 function recordTouches(eventType, eventData) {
1185 if (eventType & INPUT_START) {
1186 this.primaryTouch = eventData.changedPointers[0].identifier;
1187 setLastTouch.call(this, eventData);
1188 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
1189 setLastTouch.call(this, eventData);
1193 function setLastTouch(eventData) {
1194 var touch = eventData.changedPointers[0];
1196 if (touch.identifier === this.primaryTouch) {
1197 var lastTouch = {x: touch.clientX, y: touch.clientY};
1198 this.lastTouches.push(lastTouch);
1199 var lts = this.lastTouches;
1200 var removeLastTouch = function() {
1201 var i = lts.indexOf(lastTouch);
1206 setTimeout(removeLastTouch, DEDUP_TIMEOUT);
1210 function isSyntheticEvent(eventData) {
1211 var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
1212 for (var i = 0; i < this.lastTouches.length; i++) {
1213 var t = this.lastTouches[i];
1214 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
1215 if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
1222 var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
1223 var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
1225 // magical touchAction value
1226 var TOUCH_ACTION_COMPUTE = 'compute';
1227 var TOUCH_ACTION_AUTO = 'auto';
1228 var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
1229 var TOUCH_ACTION_NONE = 'none';
1230 var TOUCH_ACTION_PAN_X = 'pan-x';
1231 var TOUCH_ACTION_PAN_Y = 'pan-y';
1232 var TOUCH_ACTION_MAP = getTouchActionProps();
1236 * sets the touchAction property or uses the js alternative
1237 * @param {Manager} manager
1238 * @param {String} value
1241 function TouchAction(manager, value) {
1242 this.manager = manager;
1246 TouchAction.prototype = {
1248 * set the touchAction value on the element or enable the polyfill
1249 * @param {String} value
1251 set: function(value) {
1252 // find out the touch-action by the event handlers
1253 if (value == TOUCH_ACTION_COMPUTE) {
1254 value = this.compute();
1257 if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
1258 this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
1260 this.actions = value.toLowerCase().trim();
1264 * just re-set the touchAction value
1266 update: function() {
1267 this.set(this.manager.options.touchAction);
1271 * compute the value for the touchAction property based on the recognizer's settings
1272 * @returns {String} value
1274 compute: function() {
1276 each(this.manager.recognizers, function(recognizer) {
1277 if (boolOrFn(recognizer.options.enable, [recognizer])) {
1278 actions = actions.concat(recognizer.getTouchAction());
1281 return cleanTouchActions(actions.join(' '));
1285 * this method is called on each input cycle and provides the preventing of the browser behavior
1286 * @param {Object} input
1288 preventDefaults: function(input) {
1289 var srcEvent = input.srcEvent;
1290 var direction = input.offsetDirection;
1292 // if the touch action did prevented once this session
1293 if (this.manager.session.prevented) {
1294 srcEvent.preventDefault();
1298 var actions = this.actions;
1299 var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
1300 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
1301 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
1304 //do not prevent defaults if this is a tap gesture
1306 var isTapPointer = input.pointers.length === 1;
1307 var isTapMovement = input.distance < 2;
1308 var isTapTouchTime = input.deltaTime < 250;
1310 if (isTapPointer && isTapMovement && isTapTouchTime) {
1315 if (hasPanX && hasPanY) {
1316 // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
1321 (hasPanY && direction & DIRECTION_HORIZONTAL) ||
1322 (hasPanX && direction & DIRECTION_VERTICAL)) {
1323 return this.preventSrc(srcEvent);
1328 * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
1329 * @param {Object} srcEvent
1331 preventSrc: function(srcEvent) {
1332 this.manager.session.prevented = true;
1333 srcEvent.preventDefault();
1338 * when the touchActions are collected they are not a valid value, so we need to clean things up. *
1339 * @param {String} actions
1342 function cleanTouchActions(actions) {
1344 if (inStr(actions, TOUCH_ACTION_NONE)) {
1345 return TOUCH_ACTION_NONE;
1348 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
1349 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
1351 // if both pan-x and pan-y are set (different recognizers
1352 // for different directions, e.g. horizontal pan but vertical swipe?)
1353 // we need none (as otherwise with pan-x pan-y combined none of these
1354 // recognizers will work, since the browser would handle all panning
1355 if (hasPanX && hasPanY) {
1356 return TOUCH_ACTION_NONE;
1360 if (hasPanX || hasPanY) {
1361 return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
1365 if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
1366 return TOUCH_ACTION_MANIPULATION;
1369 return TOUCH_ACTION_AUTO;
1372 function getTouchActionProps() {
1373 if (!NATIVE_TOUCH_ACTION) {
1377 var cssSupports = window.CSS && window.CSS.supports;
1378 ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
1380 // If css.supports is not supported but there is native touch-action assume it supports
1381 // all values. This is the case for IE 10 and 11.
1382 touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
1388 * Recognizer flow explained; *
1389 * All recognizers have the initial state of POSSIBLE when an input session starts.
1390 * The definition of an input session is from the first input until the last input, with all it's movement in it. *
1391 * Example session for mouse-input: mousedown -> mousemove -> mouseup
1393 * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
1394 * which determines with state it should be.
1396 * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
1397 * POSSIBLE to give it another change on the next cycle.
1401 * +-----+---------------+
1405 * Failed Cancelled |
1414 var STATE_POSSIBLE = 1;
1415 var STATE_BEGAN = 2;
1416 var STATE_CHANGED = 4;
1417 var STATE_ENDED = 8;
1418 var STATE_RECOGNIZED = STATE_ENDED;
1419 var STATE_CANCELLED = 16;
1420 var STATE_FAILED = 32;
1424 * Every recognizer needs to extend from this class.
1426 * @param {Object} options
1428 function Recognizer(options) {
1429 this.options = assign({}, this.defaults, options || {});
1431 this.id = uniqueId();
1433 this.manager = null;
1435 // default is enable true
1436 this.options.enable = ifUndefined(this.options.enable, true);
1438 this.state = STATE_POSSIBLE;
1440 this.simultaneous = {};
1441 this.requireFail = [];
1444 Recognizer.prototype = {
1453 * @param {Object} options
1454 * @return {Recognizer}
1456 set: function(options) {
1457 assign(this.options, options);
1459 // also update the touchAction, in case something changed about the directions/enabled state
1460 this.manager && this.manager.touchAction.update();
1465 * recognize simultaneous with another recognizer.
1466 * @param {Recognizer} otherRecognizer
1467 * @returns {Recognizer} this
1469 recognizeWith: function(otherRecognizer) {
1470 if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
1474 var simultaneous = this.simultaneous;
1475 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1476 if (!simultaneous[otherRecognizer.id]) {
1477 simultaneous[otherRecognizer.id] = otherRecognizer;
1478 otherRecognizer.recognizeWith(this);
1484 * drop the simultaneous link. It doesn't remove the link on the other recognizer.
1485 * @param {Recognizer} otherRecognizer
1486 * @returns {Recognizer} this
1488 dropRecognizeWith: function(otherRecognizer) {
1489 if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
1493 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1494 delete this.simultaneous[otherRecognizer.id];
1499 * recognizer can only run when another is failing
1500 * @param {Recognizer} otherRecognizer
1501 * @returns {Recognizer} this
1503 requireFailure: function(otherRecognizer) {
1504 if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
1508 var requireFail = this.requireFail;
1509 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1510 if (inArray(requireFail, otherRecognizer) === -1) {
1511 requireFail.push(otherRecognizer);
1512 otherRecognizer.requireFailure(this);
1518 * drop the requireFailure link. It does not remove the link on the other recognizer.
1519 * @param {Recognizer} otherRecognizer
1520 * @returns {Recognizer} this
1522 dropRequireFailure: function(otherRecognizer) {
1523 if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
1527 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1528 var index = inArray(this.requireFail, otherRecognizer);
1530 this.requireFail.splice(index, 1);
1536 * has require failures boolean
1537 * @returns {boolean}
1539 hasRequireFailures: function() {
1540 return this.requireFail.length > 0;
1544 * if the recognizer can recognize simultaneous with another recognizer
1545 * @param {Recognizer} otherRecognizer
1546 * @returns {Boolean}
1548 canRecognizeWith: function(otherRecognizer) {
1549 return !!this.simultaneous[otherRecognizer.id];
1553 * You should use `tryEmit` instead of `emit` directly to check
1554 * that all the needed recognizers has failed before emitting.
1555 * @param {Object} input
1557 emit: function(input) {
1559 var state = this.state;
1561 function emit(event) {
1562 self.manager.emit(event, input);
1565 // 'panstart' and 'panmove'
1566 if (state < STATE_ENDED) {
1567 emit(self.options.event + stateStr(state));
1570 emit(self.options.event); // simple 'eventName' events
1572 if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
1573 emit(input.additionalEvent);
1576 // panend and pancancel
1577 if (state >= STATE_ENDED) {
1578 emit(self.options.event + stateStr(state));
1583 * Check that all the require failure recognizers has failed,
1584 * if true, it emits a gesture event,
1585 * otherwise, setup the state to FAILED.
1586 * @param {Object} input
1588 tryEmit: function(input) {
1589 if (this.canEmit()) {
1590 return this.emit(input);
1592 // it's failing anyway
1593 this.state = STATE_FAILED;
1598 * @returns {boolean}
1600 canEmit: function() {
1602 while (i < this.requireFail.length) {
1603 if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
1612 * update the recognizer
1613 * @param {Object} inputData
1615 recognize: function(inputData) {
1616 // make a new copy of the inputData
1617 // so we can change the inputData without messing up the other recognizers
1618 var inputDataClone = assign({}, inputData);
1620 // is it enabled and allow recognizing?
1621 if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
1623 this.state = STATE_FAILED;
1627 // reset when we've reached the end
1628 if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
1629 this.state = STATE_POSSIBLE;
1632 this.state = this.process(inputDataClone);
1634 // the recognizer has recognized a gesture
1635 // so trigger an event
1636 if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
1637 this.tryEmit(inputDataClone);
1642 * return the state of the recognizer
1643 * the actual recognizing happens in this method
1645 * @param {Object} inputData
1646 * @returns {Const} STATE
1648 process: function(inputData) { }, // jshint ignore:line
1651 * return the preferred touch-action
1655 getTouchAction: function() { },
1658 * called when the gesture isn't allowed to recognize
1659 * like when another is being recognized or it is disabled
1662 reset: function() { }
1666 * get a usable string, used as event postfix
1667 * @param {Const} state
1668 * @returns {String} state
1670 function stateStr(state) {
1671 if (state & STATE_CANCELLED) {
1673 } else if (state & STATE_ENDED) {
1675 } else if (state & STATE_CHANGED) {
1677 } else if (state & STATE_BEGAN) {
1684 * direction cons to string
1685 * @param {Const} direction
1688 function directionStr(direction) {
1689 if (direction == DIRECTION_DOWN) {
1691 } else if (direction == DIRECTION_UP) {
1693 } else if (direction == DIRECTION_LEFT) {
1695 } else if (direction == DIRECTION_RIGHT) {
1702 * get a recognizer by name if it is bound to a manager
1703 * @param {Recognizer|String} otherRecognizer
1704 * @param {Recognizer} recognizer
1705 * @returns {Recognizer}
1707 function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
1708 var manager = recognizer.manager;
1710 return manager.get(otherRecognizer);
1712 return otherRecognizer;
1716 * This recognizer is just used as a base for the simple attribute recognizers.
1718 * @extends Recognizer
1720 function AttrRecognizer() {
1721 Recognizer.apply(this, arguments);
1724 inherit(AttrRecognizer, Recognizer, {
1727 * @memberof AttrRecognizer
1738 * Used to check if it the recognizer receives valid input, like input.distance > 10.
1739 * @memberof AttrRecognizer
1740 * @param {Object} input
1741 * @returns {Boolean} recognized
1743 attrTest: function(input) {
1744 var optionPointers = this.options.pointers;
1745 return optionPointers === 0 || input.pointers.length === optionPointers;
1749 * Process the input and return the state for the recognizer
1750 * @memberof AttrRecognizer
1751 * @param {Object} input
1752 * @returns {*} State
1754 process: function(input) {
1755 var state = this.state;
1756 var eventType = input.eventType;
1758 var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
1759 var isValid = this.attrTest(input);
1761 // on cancel input and we've recognized before, return STATE_CANCELLED
1762 if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
1763 return state | STATE_CANCELLED;
1764 } else if (isRecognized || isValid) {
1765 if (eventType & INPUT_END) {
1766 return state | STATE_ENDED;
1767 } else if (!(state & STATE_BEGAN)) {
1770 return state | STATE_CHANGED;
1772 return STATE_FAILED;
1778 * Recognized when the pointer is down and moved in the allowed direction.
1780 * @extends AttrRecognizer
1782 function PanRecognizer() {
1783 AttrRecognizer.apply(this, arguments);
1789 inherit(PanRecognizer, AttrRecognizer, {
1792 * @memberof PanRecognizer
1798 direction: DIRECTION_ALL
1801 getTouchAction: function() {
1802 var direction = this.options.direction;
1804 if (direction & DIRECTION_HORIZONTAL) {
1805 actions.push(TOUCH_ACTION_PAN_Y);
1807 if (direction & DIRECTION_VERTICAL) {
1808 actions.push(TOUCH_ACTION_PAN_X);
1813 directionTest: function(input) {
1814 var options = this.options;
1815 var hasMoved = true;
1816 var distance = input.distance;
1817 var direction = input.direction;
1818 var x = input.deltaX;
1819 var y = input.deltaY;
1822 if (!(direction & options.direction)) {
1823 if (options.direction & DIRECTION_HORIZONTAL) {
1824 direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
1825 hasMoved = x != this.pX;
1826 distance = Math.abs(input.deltaX);
1828 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
1829 hasMoved = y != this.pY;
1830 distance = Math.abs(input.deltaY);
1833 input.direction = direction;
1834 return hasMoved && distance > options.threshold && direction & options.direction;
1837 attrTest: function(input) {
1838 return AttrRecognizer.prototype.attrTest.call(this, input) &&
1839 (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
1842 emit: function(input) {
1844 this.pX = input.deltaX;
1845 this.pY = input.deltaY;
1847 var direction = directionStr(input.direction);
1850 input.additionalEvent = this.options.event + direction;
1852 this._super.emit.call(this, input);
1858 * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
1860 * @extends AttrRecognizer
1862 function PinchRecognizer() {
1863 AttrRecognizer.apply(this, arguments);
1866 inherit(PinchRecognizer, AttrRecognizer, {
1869 * @memberof PinchRecognizer
1877 getTouchAction: function() {
1878 return [TOUCH_ACTION_NONE];
1881 attrTest: function(input) {
1882 return this._super.attrTest.call(this, input) &&
1883 (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
1886 emit: function(input) {
1887 if (input.scale !== 1) {
1888 var inOut = input.scale < 1 ? 'in' : 'out';
1889 input.additionalEvent = this.options.event + inOut;
1891 this._super.emit.call(this, input);
1897 * Recognized when the pointer is down for x ms without any movement.
1899 * @extends Recognizer
1901 function PressRecognizer() {
1902 Recognizer.apply(this, arguments);
1908 inherit(PressRecognizer, Recognizer, {
1911 * @memberof PressRecognizer
1916 time: 251, // minimal time of the pointer to be pressed
1917 threshold: 9 // a minimal movement is ok, but keep it low
1920 getTouchAction: function() {
1921 return [TOUCH_ACTION_AUTO];
1924 process: function(input) {
1925 var options = this.options;
1926 var validPointers = input.pointers.length === options.pointers;
1927 var validMovement = input.distance < options.threshold;
1928 var validTime = input.deltaTime > options.time;
1930 this._input = input;
1932 // we only allow little movement
1933 // and we've reached an end event, so a tap is possible
1934 if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
1936 } else if (input.eventType & INPUT_START) {
1938 this._timer = setTimeoutContext(function() {
1939 this.state = STATE_RECOGNIZED;
1941 }, options.time, this);
1942 } else if (input.eventType & INPUT_END) {
1943 return STATE_RECOGNIZED;
1945 return STATE_FAILED;
1949 clearTimeout(this._timer);
1952 emit: function(input) {
1953 if (this.state !== STATE_RECOGNIZED) {
1957 if (input && (input.eventType & INPUT_END)) {
1958 this.manager.emit(this.options.event + 'up', input);
1960 this._input.timeStamp = now();
1961 this.manager.emit(this.options.event, this._input);
1968 * Recognized when two or more pointer are moving in a circular motion.
1970 * @extends AttrRecognizer
1972 function RotateRecognizer() {
1973 AttrRecognizer.apply(this, arguments);
1976 inherit(RotateRecognizer, AttrRecognizer, {
1979 * @memberof RotateRecognizer
1987 getTouchAction: function() {
1988 return [TOUCH_ACTION_NONE];
1991 attrTest: function(input) {
1992 return this._super.attrTest.call(this, input) &&
1993 (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
1999 * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
2001 * @extends AttrRecognizer
2003 function SwipeRecognizer() {
2004 AttrRecognizer.apply(this, arguments);
2007 inherit(SwipeRecognizer, AttrRecognizer, {
2010 * @memberof SwipeRecognizer
2016 direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
2020 getTouchAction: function() {
2021 return PanRecognizer.prototype.getTouchAction.call(this);
2024 attrTest: function(input) {
2025 var direction = this.options.direction;
2028 if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
2029 velocity = input.overallVelocity;
2030 } else if (direction & DIRECTION_HORIZONTAL) {
2031 velocity = input.overallVelocityX;
2032 } else if (direction & DIRECTION_VERTICAL) {
2033 velocity = input.overallVelocityY;
2036 return this._super.attrTest.call(this, input) &&
2037 direction & input.offsetDirection &&
2038 input.distance > this.options.threshold &&
2039 input.maxPointers == this.options.pointers &&
2040 abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
2043 emit: function(input) {
2044 var direction = directionStr(input.offsetDirection);
2046 this.manager.emit(this.options.event + direction, input);
2049 this.manager.emit(this.options.event, input);
2054 * A tap is recognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
2055 * between the given interval and position. The delay option can be used to recognize multi-taps without firing
2058 * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
2059 * multi-taps being recognized.
2061 * @extends Recognizer
2063 function TapRecognizer() {
2064 Recognizer.apply(this, arguments);
2066 // previous time and center,
2067 // used for tap counting
2069 this.pCenter = false;
2076 inherit(TapRecognizer, Recognizer, {
2079 * @memberof PinchRecognizer
2085 interval: 300, // max time between the multi-tap taps
2086 time: 250, // max time of the pointer to be down (like finger on the screen)
2087 threshold: 9, // a minimal movement is ok, but keep it low
2088 posThreshold: 10 // a multi-tap can be a bit off the initial position
2091 getTouchAction: function() {
2092 return [TOUCH_ACTION_MANIPULATION];
2095 process: function(input) {
2096 var options = this.options;
2098 var validPointers = input.pointers.length === options.pointers;
2099 var validMovement = input.distance < options.threshold;
2100 var validTouchTime = input.deltaTime < options.time;
2104 if ((input.eventType & INPUT_START) && (this.count === 0)) {
2105 return this.failTimeout();
2108 // we only allow little movement
2109 // and we've reached an end event, so a tap is possible
2110 if (validMovement && validTouchTime && validPointers) {
2111 if (input.eventType != INPUT_END) {
2112 return this.failTimeout();
2115 var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
2116 var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
2118 this.pTime = input.timeStamp;
2119 this.pCenter = input.center;
2121 if (!validMultiTap || !validInterval) {
2127 this._input = input;
2129 // if tap count matches we have recognized it,
2130 // else it has begun recognizing...
2131 var tapCount = this.count % options.taps;
2132 if (tapCount === 0) {
2133 // no failing requirements, immediately trigger the tap event
2134 // or wait as long as the multitap interval to trigger
2135 if (!this.hasRequireFailures()) {
2136 return STATE_RECOGNIZED;
2138 this._timer = setTimeoutContext(function() {
2139 this.state = STATE_RECOGNIZED;
2141 }, options.interval, this);
2146 return STATE_FAILED;
2149 failTimeout: function() {
2150 this._timer = setTimeoutContext(function() {
2151 this.state = STATE_FAILED;
2152 }, this.options.interval, this);
2153 return STATE_FAILED;
2157 clearTimeout(this._timer);
2161 if (this.state == STATE_RECOGNIZED) {
2162 this._input.tapCount = this.count;
2163 this.manager.emit(this.options.event, this._input);
2169 * Simple way to create a manager with a default set of recognizers.
2170 * @param {HTMLElement} element
2171 * @param {Object} [options]
2174 function Hammer(element, options) {
2175 options = options || {};
2176 options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);
2177 return new Manager(element, options);
2183 Hammer.VERSION = '2.0.7';
2191 * set if DOM events are being triggered.
2192 * But this is slower and unused by simple implementations, so disabled by default.
2199 * The value for the touchAction property/fallback.
2200 * When set to `compute` it will magically set the correct value based on the added recognizers.
2204 touchAction: TOUCH_ACTION_COMPUTE,
2213 * EXPERIMENTAL FEATURE -- can be removed/changed
2214 * Change the parent input target element.
2215 * If Null, then it is being set the to main element.
2216 * @type {Null|EventTarget}
2222 * force an input class
2223 * @type {Null|Function}
2229 * Default recognizer setup when calling `Hammer()`
2230 * When creating a new Manager these will be skipped.
2234 // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
2235 [RotateRecognizer, {enable: false}],
2236 [PinchRecognizer, {enable: false}, ['rotate']],
2237 [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
2238 [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
2240 [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
2245 * Some CSS properties can be used to improve the working of Hammer.
2246 * Add them to this method and they will be set when creating a new Manager.
2251 * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
2258 * Disable the Windows Phone grippers when pressing an element.
2262 touchSelect: 'none',
2265 * Disables the default callout shown when you touch and hold a touch target.
2266 * On iOS, when you touch and hold a touch target such as a link, Safari displays
2267 * a callout containing information about the link. This property allows you to disable that callout.
2271 touchCallout: 'none',
2274 * Specifies whether zooming is enabled. Used by IE10>
2278 contentZooming: 'none',
2281 * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
2288 * Overrides the highlight color shown when the user taps a link or a JavaScript
2289 * clickable element in iOS. This property obeys the alpha value, if specified.
2291 * @default 'rgba(0,0,0,0)'
2293 tapHighlightColor: 'rgba(0,0,0,0)'
2298 var FORCED_STOP = 2;
2302 * @param {HTMLElement} element
2303 * @param {Object} [options]
2306 function Manager(element, options) {
2307 this.options = assign({}, Hammer.defaults, options || {});
2309 this.options.inputTarget = this.options.inputTarget || element;
2313 this.recognizers = [];
2314 this.oldCssProps = {};
2316 this.element = element;
2317 this.input = createInputInstance(this);
2318 this.touchAction = new TouchAction(this, this.options.touchAction);
2320 toggleCssProps(this, true);
2322 each(this.options.recognizers, function(item) {
2323 var recognizer = this.add(new (item[0])(item[1]));
2324 item[2] && recognizer.recognizeWith(item[2]);
2325 item[3] && recognizer.requireFailure(item[3]);
2329 Manager.prototype = {
2332 * @param {Object} options
2333 * @returns {Manager}
2335 set: function(options) {
2336 assign(this.options, options);
2338 // Options that need a little more setup
2339 if (options.touchAction) {
2340 this.touchAction.update();
2342 if (options.inputTarget) {
2343 // Clean up existing event listeners and reinitialize
2344 this.input.destroy();
2345 this.input.target = options.inputTarget;
2352 * stop recognizing for this session.
2353 * This session will be discarded, when a new [input]start event is fired.
2354 * When forced, the recognizer cycle is stopped immediately.
2355 * @param {Boolean} [force]
2357 stop: function(force) {
2358 this.session.stopped = force ? FORCED_STOP : STOP;
2362 * run the recognizers!
2363 * called by the inputHandler function on every movement of the pointers (touches)
2364 * it walks through all the recognizers and tries to detect the gesture that is being made
2365 * @param {Object} inputData
2367 recognize: function(inputData) {
2368 var session = this.session;
2369 if (session.stopped) {
2373 // run the touch-action polyfill
2374 this.touchAction.preventDefaults(inputData);
2377 var recognizers = this.recognizers;
2379 // this holds the recognizer that is being recognized.
2380 // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
2381 // if no recognizer is detecting a thing, it is set to `null`
2382 var curRecognizer = session.curRecognizer;
2384 // reset when the last recognizer is recognized
2385 // or when we're in a new session
2386 if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
2387 curRecognizer = session.curRecognizer = null;
2391 while (i < recognizers.length) {
2392 recognizer = recognizers[i];
2394 // find out if we are allowed try to recognize the input for this one.
2395 // 1. allow if the session is NOT forced stopped (see the .stop() method)
2396 // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
2397 // that is being recognized.
2398 // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
2399 // this can be setup with the `recognizeWith()` method on the recognizer.
2400 if (session.stopped !== FORCED_STOP && ( // 1
2401 !curRecognizer || recognizer == curRecognizer || // 2
2402 recognizer.canRecognizeWith(curRecognizer))) { // 3
2403 recognizer.recognize(inputData);
2408 // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
2409 // current active recognizer. but only if we don't already have an active recognizer
2410 if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
2411 curRecognizer = session.curRecognizer = recognizer;
2418 * get a recognizer by its event name.
2419 * @param {Recognizer|String} recognizer
2420 * @returns {Recognizer|Null}
2422 get: function(recognizer) {
2423 if (recognizer instanceof Recognizer) {
2427 var recognizers = this.recognizers;
2428 for (var i = 0; i < recognizers.length; i++) {
2429 if (recognizers[i].options.event == recognizer) {
2430 return recognizers[i];
2437 * add a recognizer to the manager
2438 * existing recognizers with the same event name will be removed
2439 * @param {Recognizer} recognizer
2440 * @returns {Recognizer|Manager}
2442 add: function(recognizer) {
2443 if (invokeArrayArg(recognizer, 'add', this)) {
2448 var existing = this.get(recognizer.options.event);
2450 this.remove(existing);
2453 this.recognizers.push(recognizer);
2454 recognizer.manager = this;
2456 this.touchAction.update();
2461 * remove a recognizer by name or instance
2462 * @param {Recognizer|String} recognizer
2463 * @returns {Manager}
2465 remove: function(recognizer) {
2466 if (invokeArrayArg(recognizer, 'remove', this)) {
2470 recognizer = this.get(recognizer);
2472 // let's make sure this recognizer exists
2474 var recognizers = this.recognizers;
2475 var index = inArray(recognizers, recognizer);
2478 recognizers.splice(index, 1);
2479 this.touchAction.update();
2488 * @param {String} events
2489 * @param {Function} handler
2490 * @returns {EventEmitter} this
2492 on: function(events, handler) {
2493 if (events === undefined) {
2496 if (handler === undefined) {
2500 var handlers = this.handlers;
2501 each(splitStr(events), function(event) {
2502 handlers[event] = handlers[event] || [];
2503 handlers[event].push(handler);
2509 * unbind event, leave emit blank to remove all handlers
2510 * @param {String} events
2511 * @param {Function} [handler]
2512 * @returns {EventEmitter} this
2514 off: function(events, handler) {
2515 if (events === undefined) {
2519 var handlers = this.handlers;
2520 each(splitStr(events), function(event) {
2522 delete handlers[event];
2524 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
2531 * emit event to the listeners
2532 * @param {String} event
2533 * @param {Object} data
2535 emit: function(event, data) {
2536 // we also want to trigger dom events
2537 if (this.options.domEvents) {
2538 triggerDomEvent(event, data);
2541 // no handlers, so skip it all
2542 var handlers = this.handlers[event] && this.handlers[event].slice();
2543 if (!handlers || !handlers.length) {
2548 data.preventDefault = function() {
2549 data.srcEvent.preventDefault();
2553 while (i < handlers.length) {
2560 * destroy the manager and unbinds all events
2561 * it doesn't unbind dom events, that is the user own responsibility
2563 destroy: function() {
2564 this.element && toggleCssProps(this, false);
2568 this.input.destroy();
2569 this.element = null;
2574 * add/remove the css properties as defined in manager.options.cssProps
2575 * @param {Manager} manager
2576 * @param {Boolean} add
2578 function toggleCssProps(manager, add) {
2579 var element = manager.element;
2580 if (!element.style) {
2584 each(manager.options.cssProps, function(value, name) {
2585 prop = prefixed(element.style, name);
2587 manager.oldCssProps[prop] = element.style[prop];
2588 element.style[prop] = value;
2590 element.style[prop] = manager.oldCssProps[prop] || '';
2594 manager.oldCssProps = {};
2600 * @param {String} event
2601 * @param {Object} data
2603 function triggerDomEvent(event, data) {
2604 var gestureEvent = document.createEvent('Event');
2605 gestureEvent.initEvent(event, true, true);
2606 gestureEvent.gesture = data;
2607 data.target.dispatchEvent(gestureEvent);
2611 INPUT_START: INPUT_START,
2612 INPUT_MOVE: INPUT_MOVE,
2613 INPUT_END: INPUT_END,
2614 INPUT_CANCEL: INPUT_CANCEL,
2616 STATE_POSSIBLE: STATE_POSSIBLE,
2617 STATE_BEGAN: STATE_BEGAN,
2618 STATE_CHANGED: STATE_CHANGED,
2619 STATE_ENDED: STATE_ENDED,
2620 STATE_RECOGNIZED: STATE_RECOGNIZED,
2621 STATE_CANCELLED: STATE_CANCELLED,
2622 STATE_FAILED: STATE_FAILED,
2624 DIRECTION_NONE: DIRECTION_NONE,
2625 DIRECTION_LEFT: DIRECTION_LEFT,
2626 DIRECTION_RIGHT: DIRECTION_RIGHT,
2627 DIRECTION_UP: DIRECTION_UP,
2628 DIRECTION_DOWN: DIRECTION_DOWN,
2629 DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
2630 DIRECTION_VERTICAL: DIRECTION_VERTICAL,
2631 DIRECTION_ALL: DIRECTION_ALL,
2635 TouchAction: TouchAction,
2637 TouchInput: TouchInput,
2638 MouseInput: MouseInput,
2639 PointerEventInput: PointerEventInput,
2640 TouchMouseInput: TouchMouseInput,
2641 SingleTouchInput: SingleTouchInput,
2643 Recognizer: Recognizer,
2644 AttrRecognizer: AttrRecognizer,
2647 Swipe: SwipeRecognizer,
2648 Pinch: PinchRecognizer,
2649 Rotate: RotateRecognizer,
2650 Press: PressRecognizer,
2652 on: addEventListeners,
2653 off: removeEventListeners,
2663 // this prevents errors when Hammer is loaded in the presence of an AMD
2664 // style loader but by script tag, not by the loader.
2665 var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
2666 freeGlobal.Hammer = Hammer;
2668 if (typeof define === 'function' && define.amd) {
2672 } else if (typeof module != 'undefined' && module.exports) {
2673 module.exports = Hammer;
2675 window[exportName] = Hammer;
2678 })(window, document, 'Hammer');
2683 * The following is the license notice for the part of JavaScript code of this
2684 * page included between the '@jessyinkstart' and the '@jessyinkend' notes.
2687 /***** ******************************************************************
2689 * Copyright 2008-2013 Hannes Hochreiner
2691 * The JavaScript code included between the start note '@jessyinkstart'
2692 * and the end note '@jessyinkend' is subject to the terms of the Mozilla
2693 * Public License, v. 2.0. If a copy of the MPL was not distributed with
2694 * this file, You can obtain one at http://mozilla.org/MPL/2.0/.
2696 * Alternatively, you can redistribute and/or that part of this file
2697 * under the terms of the GNU General Public License as published by
2698 * the Free Software Foundation, either version 3 of the License, or
2699 * (at your option) any later version.
2701 * This program is distributed in the hope that it will be useful,
2702 * but WITHOUT ANY WARRANTY; without even the implied warranty of
2703 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2704 * GNU General Public License for more details.
2706 * You should have received a copy of the GNU General Public License
2707 * along with this program. If not, see http://www.gnu.org/licenses/.
2711 * You can find the complete source code of the JessyInk project at:
2712 * @source http://code.google.com/p/jessyink/
2718 * The above is the license notice for the part of JavaScript code of this
2719 * page included between the '@jessyinkstart' and the '@jessyinkend' notes.
2727 * The following code is a derivative work of some parts of the JessyInk
2729 * @source http://code.google.com/p/jessyink/
2732 /** Convenience function to get an element depending on whether it has a
2733 * property with a particular name.
2735 * @param node element of the document
2736 * @param name attribute name
2738 * @returns Array array containing all the elements of the tree with root
2739 * 'node' that own the property 'name'
2741 function getElementsByProperty( node, name )
2745 if( node.getAttribute( name ) )
2746 elements.push( node );
2748 for( var counter = 0; counter < node.childNodes.length; ++counter )
2750 if( node.childNodes[counter].nodeType == 1 )
2752 var subElements = getElementsByProperty( node.childNodes[counter], name );
2753 elements = elements.concat( subElements );
2759 /** Event handler for key press.
2761 * @param aEvt the event
2763 function onKeyDown( aEvt )
2766 aEvt = window.event;
2768 var code = aEvt.keyCode || aEvt.charCode;
2770 // console.log('===> onKeyDown: ' + code);
2772 // Handle arrow keys in iOS WebKit (including Mobile Safari)
2773 if (code == 0 && aEvt.key != undefined) {
2775 case 'UIKeyInputLeftArrow':
2778 case 'UIKeyInputUpArrow':
2781 case 'UIKeyInputRightArrow':
2784 case 'UIKeyInputDownArrow':
2789 // console.log(' now: ' + code);
2792 if( !processingEffect && keyCodeDictionary[currentMode] && keyCodeDictionary[currentMode][code] )
2794 return keyCodeDictionary[currentMode][code]();
2798 document.onkeypress = onKeyPress;
2802 //Set event handler for key down.
2803 document.onkeydown = onKeyDown;
2805 /** Event handler for key press.
2807 * @param aEvt the event
2809 function onKeyPress( aEvt )
2811 document.onkeypress = null;
2814 aEvt = window.event;
2816 var str = String.fromCharCode( aEvt.keyCode || aEvt.charCode );
2818 if ( !processingEffect && charCodeDictionary[currentMode] && charCodeDictionary[currentMode][str] )
2819 return charCodeDictionary[currentMode][str]();
2824 /** Function to supply the default key code dictionary.
2826 * @returns Object default key code dictionary
2828 function getDefaultKeyCodeDictionary()
2830 var keyCodeDict = {};
2832 keyCodeDict[SLIDE_MODE] = {};
2833 keyCodeDict[INDEX_MODE] = {};
2836 keyCodeDict[SLIDE_MODE][LEFT_KEY]
2837 = function() { return aSlideShow.rewindEffect(); };
2838 keyCodeDict[SLIDE_MODE][RIGHT_KEY]
2839 = function() { return dispatchEffects(1); };
2840 keyCodeDict[SLIDE_MODE][UP_KEY]
2841 = function() { return aSlideShow.rewindEffect(); };
2842 keyCodeDict[SLIDE_MODE][DOWN_KEY]
2843 = function() { return skipEffects(1); };
2844 keyCodeDict[SLIDE_MODE][PAGE_UP_KEY]
2845 = function() { return aSlideShow.rewindAllEffects(); };
2846 keyCodeDict[SLIDE_MODE][PAGE_DOWN_KEY]
2847 = function() { return skipAllEffects(); };
2848 keyCodeDict[SLIDE_MODE][HOME_KEY]
2849 = function() { return aSlideShow.displaySlide( 0, true ); };
2850 keyCodeDict[SLIDE_MODE][END_KEY]
2851 = function() { return aSlideShow.displaySlide( theMetaDoc.nNumberOfSlides - 1, true ); };
2852 keyCodeDict[SLIDE_MODE][SPACE_KEY]
2853 = function() { return dispatchEffects(1); };
2854 // The ESC key can't actually be handled on iOS, it seems to be hardcoded to work like the home button? But try anyway.
2855 keyCodeDict[SLIDE_MODE][ESCAPE_KEY]
2856 = function() { return aSlideShow.exitSlideShowInApp(); };
2857 keyCodeDict[SLIDE_MODE][Q_KEY]
2858 = function() { return aSlideShow.exitSlideShowInApp(); };
2861 keyCodeDict[INDEX_MODE][LEFT_KEY]
2862 = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex - 1 ); };
2863 keyCodeDict[INDEX_MODE][RIGHT_KEY]
2864 = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex + 1 ); };
2865 keyCodeDict[INDEX_MODE][UP_KEY]
2866 = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex - theSlideIndexPage.indexColumns ); };
2867 keyCodeDict[INDEX_MODE][DOWN_KEY]
2868 = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex + theSlideIndexPage.indexColumns ); };
2869 keyCodeDict[INDEX_MODE][PAGE_UP_KEY]
2870 = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex - theSlideIndexPage.getTotalThumbnails() ); };
2871 keyCodeDict[INDEX_MODE][PAGE_DOWN_KEY]
2872 = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex + theSlideIndexPage.getTotalThumbnails() ); };
2873 keyCodeDict[INDEX_MODE][HOME_KEY]
2874 = function() { return indexSetPageSlide( 0 ); };
2875 keyCodeDict[INDEX_MODE][END_KEY]
2876 = function() { return indexSetPageSlide( theMetaDoc.nNumberOfSlides - 1 ); };
2877 keyCodeDict[INDEX_MODE][ENTER_KEY]
2878 = function() { return toggleSlideIndex(); };
2879 keyCodeDict[INDEX_MODE][SPACE_KEY]
2880 = function() { return toggleSlideIndex(); };
2881 keyCodeDict[INDEX_MODE][ESCAPE_KEY]
2882 = function() { return abandonIndexMode(); };
2887 /** Function to supply the default char code dictionary.
2889 * @returns Object char code dictionary
2891 function getDefaultCharCodeDictionary()
2893 var charCodeDict = {};
2895 charCodeDict[SLIDE_MODE] = {};
2896 charCodeDict[INDEX_MODE] = {};
2899 charCodeDict[SLIDE_MODE]['i']
2900 = function () { return toggleSlideIndex(); };
2903 charCodeDict[INDEX_MODE]['i']
2904 = function () { return toggleSlideIndex(); };
2905 charCodeDict[INDEX_MODE]['-']
2906 = function () { return theSlideIndexPage.decreaseNumberOfColumns(); };
2907 charCodeDict[INDEX_MODE]['=']
2908 = function () { return theSlideIndexPage.increaseNumberOfColumns(); };
2909 charCodeDict[INDEX_MODE]['+']
2910 = function () { return theSlideIndexPage.increaseNumberOfColumns(); };
2911 charCodeDict[INDEX_MODE]['0']
2912 = function () { return theSlideIndexPage.resetNumberOfColumns(); };
2914 return charCodeDict;
2918 function slideOnMouseUp( aEvt )
2921 aEvt = window.event;
2925 if( aEvt.button == 0 )
2927 else if( aEvt.button == 2 )
2931 dispatchEffects( nOffset );
2932 return true; // the click has been handled
2935 document.handleClick = slideOnMouseUp;
2938 /** Event handler for mouse wheel events in slide mode.
2939 * based on http://adomas.org/javascript-mouse-wheel/
2941 * @param aEvt the event
2943 function slideOnMouseWheel(aEvt)
2948 aEvt = window.event;
2950 if (aEvt.wheelDelta)
2952 delta = aEvt.wheelDelta/120;
2954 else if (aEvt.detail)
2956 delta = -aEvt.detail/3;
2964 if (aEvt.preventDefault)
2965 aEvt.preventDefault();
2967 aEvt.returnValue = false;
2971 if( window.addEventListener )
2973 window.addEventListener( 'DOMMouseScroll', function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_WHEEL ); }, false );
2976 //Opera Safari OK - may not work in IE
2978 = function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_WHEEL ); };
2980 /** Function to handle all mouse events.
2983 * @param anAction type of event (e.g. mouse up, mouse wheel)
2985 function mouseHandlerDispatch( aEvt, anAction )
2988 aEvt = window.event;
2992 if ( mouseHandlerDictionary[currentMode] && mouseHandlerDictionary[currentMode][anAction] )
2994 var subRetVal = mouseHandlerDictionary[currentMode][anAction]( aEvt );
2996 if( subRetVal != null && subRetVal != undefined )
3000 if( aEvt.preventDefault && !retVal )
3001 aEvt.preventDefault();
3003 aEvt.returnValue = retVal;
3008 //Set mouse event handler.
3009 document.onmouseup = function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_UP ); };
3012 /** mouseClickHelper
3015 * a mouse click handler
3017 function mouseClickHelper( aEvt )
3019 // In case text is selected we stay on the current slide.
3020 // Anyway if we are dealing with Firefox there is an issue:
3021 // Firefox supports a naive way of selecting svg text, if you click
3022 // on text the current selection is set to the whole text fragment
3023 // wrapped by the related <tspan> element.
3024 // That means until you click on text you never move to the next slide.
3025 // In order to avoid this case we do not test the status of current
3026 // selection, when the presentation is running on a mozilla browser.
3027 if( !Detect.isMozilla )
3029 var aWindowObject = document.defaultView;
3032 var aTextSelection = aWindowObject.getSelection();
3033 var sSelectedText = aTextSelection.toString();
3036 DBGLOG( 'text selection: ' + sSelectedText );
3037 if( sLastSelectedText !== sSelectedText )
3039 bTextHasBeenSelected = true;
3040 sLastSelectedText = sSelectedText;
3044 bTextHasBeenSelected = false;
3048 else if( bTextHasBeenSelected )
3050 bTextHasBeenSelected = false;
3051 sLastSelectedText = '';
3057 log( 'error: HyperlinkElement.handleClick: invalid window object.' );
3061 var aSlideAnimationsHandler = theMetaDoc.aMetaSlideSet[nCurSlide].aSlideAnimationsHandler;
3062 if( aSlideAnimationsHandler )
3064 var aCurrentEventMultiplexer = aSlideAnimationsHandler.aEventMultiplexer;
3065 if( aCurrentEventMultiplexer )
3067 if( aCurrentEventMultiplexer.hasRegisteredMouseClickHandlers() )
3069 return aCurrentEventMultiplexer.notifyMouseClick( aEvt );
3073 return slideOnMouseUp( aEvt );
3077 /** Function to supply the default mouse handler dictionary.
3079 * @returns Object default mouse handler dictionary
3081 function getDefaultMouseHandlerDictionary()
3083 var mouseHandlerDict = {};
3085 mouseHandlerDict[SLIDE_MODE] = {};
3086 mouseHandlerDict[INDEX_MODE] = {};
3089 mouseHandlerDict[SLIDE_MODE][MOUSE_UP]
3092 mouseHandlerDict[SLIDE_MODE][MOUSE_WHEEL]
3093 = function( aEvt ) { return slideOnMouseWheel( aEvt ); };
3096 mouseHandlerDict[INDEX_MODE][MOUSE_UP]
3097 = function( ) { return toggleSlideIndex(); };
3099 return mouseHandlerDict;
3102 /** Function to set the page and active slide in index view.
3104 * @param nIndex index of the active slide
3106 * NOTE: To force a redraw,
3107 * set INDEX_OFFSET to -1 before calling indexSetPageSlide().
3109 * This is necessary for zooming (otherwise the index might not
3110 * get redrawn) and when switching to index mode.
3113 * indexSetPageSlide(activeSlide);
3115 function indexSetPageSlide( nIndex )
3117 var aMetaSlideSet = theMetaDoc.aMetaSlideSet;
3118 nIndex = getSafeIndex( nIndex, 0, aMetaSlideSet.length - 1 );
3120 //calculate the offset
3121 var nSelectedThumbnailIndex = nIndex % theSlideIndexPage.getTotalThumbnails();
3122 var offset = nIndex - nSelectedThumbnailIndex;
3127 //if different from kept offset, then record and change the page
3128 if( offset != INDEX_OFFSET )
3130 INDEX_OFFSET = offset;
3131 displayIndex( INDEX_OFFSET );
3134 //set the selected thumbnail and the current slide
3135 theSlideIndexPage.setSelection( nSelectedThumbnailIndex );
3142 * The above code is a derivative work of some parts of the JessyInk project.
3143 * @source http://code.google.com/p/jessyink/
3153 * The following is the license notice for the part of JavaScript code of this
3154 * page included between the '@dojostart' and the '@dojoend' notes.
3157 /***** **********************************************************************
3159 * The 'New' BSD License:
3160 * **********************
3161 * Copyright (c) 2005-2012, The Dojo Foundation
3162 * All rights reserved.
3164 * Redistribution and use in source and binary forms, with or without
3165 * modification, are permitted provided that the following conditions are met:
3167 * * Redistributions of source code must retain the above copyright notice,
3168 * this list of conditions and the following disclaimer.
3169 * * Redistributions in binary form must reproduce the above copyright notice,
3170 * this list of conditions and the following disclaimer in the documentation
3171 * and/or other materials provided with the distribution.
3172 * * Neither the name of the Dojo Foundation nor the names of its contributors
3173 * may be used to endorse or promote products derived from this software
3174 * without specific prior written permission.
3176 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND
3177 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3178 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3179 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
3180 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3181 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3182 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
3183 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3184 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3185 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3187 ****************************************************************************/
3193 * The above is the license notice for the part of JavaScript code of this
3194 * page included between the '@dojostart' and the '@dojoend' notes.
3202 * The following code is a derivative work of some part of the dojox.gfx library.
3203 * @source http://svn.dojotoolkit.org/src/dojox/trunk/_base/sniff.js
3206 function has( name )
3208 return has.cache[name];
3213 has.add = function( name, test )
3215 has.cache[name] = test;
3218 function configureDetectionTools()
3222 log( 'error: configureDetectionTools: configuration failed' );
3229 tv = parseFloat(dav);
3231 has.add('air', dua.indexOf('AdobeAIR') >= 0);
3232 has.add('khtml', dav.indexOf('Konqueror') >= 0 ? tv : undefined);
3233 has.add('webkit', parseFloat(dua.split('WebKit/')[1]) || undefined);
3234 has.add('chrome', parseFloat(dua.split('Chrome/')[1]) || undefined);
3235 has.add('safari', dav.indexOf('Safari')>=0 && !has('chrome') ? parseFloat(dav.split('Version/')[1]) : undefined);
3236 has.add('mac', dav.indexOf('Macintosh') >= 0);
3237 has.add('quirks', document.compatMode == 'BackCompat');
3238 has.add('ios', /iPhone|iPod|iPad/.test(dua));
3239 has.add('android', parseFloat(dua.split('Android ')[1]) || undefined);
3243 if(dua.indexOf('Opera') >= 0){
3244 // see http://dev.opera.com/articles/view/opera-ua-string-changes and http://www.useragentstring.com/pages/Opera/
3245 // 9.8 has both styles; <9.8, 9.9 only old style
3246 has.add('opera', tv >= 9.8 ? parseFloat(dua.split('Version/')[1]) || tv : tv);
3249 // Mozilla and firefox
3250 if(dua.indexOf('Gecko') >= 0 && !has('khtml') && !has('webkit')){
3251 has.add('mozilla', tv);
3254 //We really need to get away from this. Consider a sane isGecko approach for the future.
3255 has.add('ff', parseFloat(dua.split('Firefox/')[1] || dua.split('Minefield/')[1]) || undefined);
3259 if(document.all && !has('opera')){
3260 var isIE = parseFloat(dav.split('MSIE ')[1]) || undefined;
3262 //In cases where the page has an HTTP header or META tag with
3263 //X-UA-Compatible, then it is in emulation mode.
3264 //Make sure isIE reflects the desired version.
3265 //document.documentMode of 5 means quirks mode.
3266 //Only switch the value if documentMode's major version
3267 //is different from isIE major version.
3268 var mode = document.documentMode;
3269 if(mode && mode != 5 && Math.floor(isIE) != mode){
3273 has.add('ie', isIE);
3277 has.add('wii', typeof opera != 'undefined' && opera.wiiremote);
3282 // isFF: Number|undefined
3283 // Version as a Number if client is FireFox. undefined otherwise. Corresponds to
3284 // major detected FireFox version (1.5, 2, 3, etc.)
3287 // isIE: Number|undefined
3288 // Version as a Number if client is MSIE(PC). undefined otherwise. Corresponds to
3289 // major detected IE version (6, 7, 8, etc.)
3292 // isKhtml: Number|undefined
3293 // Version as a Number if client is a KHTML browser. undefined otherwise. Corresponds to major
3294 // detected version.
3295 isKhtml: has('khtml'),
3297 // isWebKit: Number|undefined
3298 // Version as a Number if client is a WebKit-derived browser (Konqueror,
3299 // Safari, Chrome, etc.). undefined otherwise.
3300 isWebKit: has('webkit'),
3302 // isMozilla: Number|undefined
3303 // Version as a Number if client is a Mozilla-based browser (Firefox,
3304 // SeaMonkey). undefined otherwise. Corresponds to major detected version.
3305 isMozilla: has('mozilla'),
3306 // isMoz: Number|undefined
3307 // Version as a Number if client is a Mozilla-based browser (Firefox,
3308 // SeaMonkey). undefined otherwise. Corresponds to major detected version.
3309 isMoz: has('mozilla'),
3311 // isOpera: Number|undefined
3312 // Version as a Number if client is Opera. undefined otherwise. Corresponds to
3313 // major detected version.
3314 isOpera: has('opera'),
3316 // isSafari: Number|undefined
3317 // Version as a Number if client is Safari or iPhone. undefined otherwise.
3318 isSafari: has('safari'),
3320 // isChrome: Number|undefined
3321 // Version as a Number if client is Chrome browser. undefined otherwise.
3322 isChrome: has('chrome'),
3325 // True if the client runs on Mac
3329 // True if client is iPhone, iPod, or iPad
3332 // isAndroid: Number|undefined
3333 // Version as a Number if client is android browser. undefined otherwise.
3334 isAndroid: has('android'),
3337 // True if client is Wii
3340 // isQuirks: Boolean
3341 // Page is in quirks mode.
3342 isQuirks: has('quirks'),
3345 // True if client is Adobe Air
3354 * The above code is a derivative work of some part of the dojox.gfx library.
3355 * @source http://svn.dojotoolkit.org/src/dojox/trunk/_base/sniff.js
3361 * The following is the license notice for the part of JavaScript code of this
3362 * file included between the '@svgpathstart' and the '@svgpathend' notes.
3365 /***** **********************************************************************
3367 * Copyright 2015 The Chromium Authors. All rights reserved.
3369 * The Chromium Authors can be found at
3370 * http://src.chromium.org/svn/trunk/src/AUTHORS
3372 * Redistribution and use in source and binary forms, with or without
3373 * modification, are permitted provided that the following conditions are
3376 * * Redistributions of source code must retain the above copyright
3377 * notice, this list of conditions and the following disclaimer.
3378 * * Redistributions in binary form must reproduce the above
3379 * copyright notice, this list of conditions and the following disclaimer
3380 * in the documentation and/or other materials provided with the
3382 * * Neither the name of Google Inc. nor the names of its
3383 * contributors may be used to endorse or promote products derived from
3384 * this software without specific prior written permission.
3386 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
3387 * 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
3388 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
3389 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
3390 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
3391 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
3392 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
3393 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3394 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3395 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3396 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3398 ****************************************************************************/
3403 * The above is the license notice for the part of JavaScript code of this
3404 * file included between the '@svgpathstart' and the '@svgpathend' notes.
3411 * The following code is a derivative work of some part of the SVGPathSeg API.
3413 * This API is a drop-in replacement for the SVGPathSeg and SVGPathSegList APIs that were removed from
3414 * SVG2 (https://lists.w3.org/Archives/Public/www-svg/2015Jun/0044.html), including the latest spec
3415 * changes which were implemented in Firefox 43 and Chrome 46.
3417 * @source https://github.com/progers/pathseg
3420 (function() { 'use strict';
3421 if (!('SVGPathSeg' in window)) {
3422 // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSeg
3423 window.SVGPathSeg = function(type, typeAsLetter, owningPathSegList) {
3424 this.pathSegType = type;
3425 this.pathSegTypeAsLetter = typeAsLetter;
3426 this._owningPathSegList = owningPathSegList;
3429 window.SVGPathSeg.prototype.classname = 'SVGPathSeg';
3431 window.SVGPathSeg.PATHSEG_UNKNOWN = 0;
3432 window.SVGPathSeg.PATHSEG_CLOSEPATH = 1;
3433 window.SVGPathSeg.PATHSEG_MOVETO_ABS = 2;
3434 window.SVGPathSeg.PATHSEG_MOVETO_REL = 3;
3435 window.SVGPathSeg.PATHSEG_LINETO_ABS = 4;
3436 window.SVGPathSeg.PATHSEG_LINETO_REL = 5;
3437 window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS = 6;
3438 window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL = 7;
3439 window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS = 8;
3440 window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL = 9;
3441 window.SVGPathSeg.PATHSEG_ARC_ABS = 10;
3442 window.SVGPathSeg.PATHSEG_ARC_REL = 11;
3443 window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS = 12;
3444 window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL = 13;
3445 window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS = 14;
3446 window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL = 15;
3447 window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS = 16;
3448 window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL = 17;
3449 window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS = 18;
3450 window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL = 19;
3452 // Notify owning PathSegList on any changes so they can be synchronized back to the path element.
3453 window.SVGPathSeg.prototype._segmentChanged = function() {
3454 if (this._owningPathSegList)
3455 this._owningPathSegList.segmentChanged(this);
3458 window.SVGPathSegClosePath = function(owningPathSegList) {
3459 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CLOSEPATH, 'z', owningPathSegList);
3461 window.SVGPathSegClosePath.prototype = Object.create(window.SVGPathSeg.prototype);
3462 window.SVGPathSegClosePath.prototype.toString = function() { return '[object SVGPathSegClosePath]'; }
3463 window.SVGPathSegClosePath.prototype._asPathString = function() { return this.pathSegTypeAsLetter; }
3464 window.SVGPathSegClosePath.prototype.clone = function() { return new window.SVGPathSegClosePath(undefined); }
3466 window.SVGPathSegMovetoAbs = function(owningPathSegList, x, y) {
3467 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_MOVETO_ABS, 'M', owningPathSegList);
3471 window.SVGPathSegMovetoAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3472 window.SVGPathSegMovetoAbs.prototype.toString = function() { return '[object SVGPathSegMovetoAbs]'; }
3473 window.SVGPathSegMovetoAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
3474 window.SVGPathSegMovetoAbs.prototype.clone = function() { return new window.SVGPathSegMovetoAbs(undefined, this._x, this._y); }
3475 Object.defineProperty(window.SVGPathSegMovetoAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3476 Object.defineProperty(window.SVGPathSegMovetoAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3478 window.SVGPathSegMovetoRel = function(owningPathSegList, x, y) {
3479 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_MOVETO_REL, 'm', owningPathSegList);
3483 window.SVGPathSegMovetoRel.prototype = Object.create(window.SVGPathSeg.prototype);
3484 window.SVGPathSegMovetoRel.prototype.toString = function() { return '[object SVGPathSegMovetoRel]'; }
3485 window.SVGPathSegMovetoRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
3486 window.SVGPathSegMovetoRel.prototype.clone = function() { return new window.SVGPathSegMovetoRel(undefined, this._x, this._y); }
3487 Object.defineProperty(window.SVGPathSegMovetoRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3488 Object.defineProperty(window.SVGPathSegMovetoRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3490 window.SVGPathSegLinetoAbs = function(owningPathSegList, x, y) {
3491 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_ABS, 'L', owningPathSegList);
3495 window.SVGPathSegLinetoAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3496 window.SVGPathSegLinetoAbs.prototype.toString = function() { return '[object SVGPathSegLinetoAbs]'; }
3497 window.SVGPathSegLinetoAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
3498 window.SVGPathSegLinetoAbs.prototype.clone = function() { return new window.SVGPathSegLinetoAbs(undefined, this._x, this._y); }
3499 Object.defineProperty(window.SVGPathSegLinetoAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3500 Object.defineProperty(window.SVGPathSegLinetoAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3502 window.SVGPathSegLinetoRel = function(owningPathSegList, x, y) {
3503 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_REL, 'l', owningPathSegList);
3507 window.SVGPathSegLinetoRel.prototype = Object.create(window.SVGPathSeg.prototype);
3508 window.SVGPathSegLinetoRel.prototype.toString = function() { return '[object SVGPathSegLinetoRel]'; }
3509 window.SVGPathSegLinetoRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
3510 window.SVGPathSegLinetoRel.prototype.clone = function() { return new window.SVGPathSegLinetoRel(undefined, this._x, this._y); }
3511 Object.defineProperty(window.SVGPathSegLinetoRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3512 Object.defineProperty(window.SVGPathSegLinetoRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3514 window.SVGPathSegCurvetoCubicAbs = function(owningPathSegList, x, y, x1, y1, x2, y2) {
3515 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS, 'C', owningPathSegList);
3523 window.SVGPathSegCurvetoCubicAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3524 window.SVGPathSegCurvetoCubicAbs.prototype.toString = function() { return '[object SVGPathSegCurvetoCubicAbs]'; }
3525 window.SVGPathSegCurvetoCubicAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
3526 window.SVGPathSegCurvetoCubicAbs.prototype.clone = function() { return new window.SVGPathSegCurvetoCubicAbs(undefined, this._x, this._y, this._x1, this._y1, this._x2, this._y2); }
3527 Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3528 Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3529 Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'x1', { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
3530 Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'y1', { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
3531 Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'x2', { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
3532 Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'y2', { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
3534 window.SVGPathSegCurvetoCubicRel = function(owningPathSegList, x, y, x1, y1, x2, y2) {
3535 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL, 'c', owningPathSegList);
3543 window.SVGPathSegCurvetoCubicRel.prototype = Object.create(window.SVGPathSeg.prototype);
3544 window.SVGPathSegCurvetoCubicRel.prototype.toString = function() { return '[object SVGPathSegCurvetoCubicRel]'; }
3545 window.SVGPathSegCurvetoCubicRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
3546 window.SVGPathSegCurvetoCubicRel.prototype.clone = function() { return new window.SVGPathSegCurvetoCubicRel(undefined, this._x, this._y, this._x1, this._y1, this._x2, this._y2); }
3547 Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3548 Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3549 Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'x1', { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
3550 Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'y1', { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
3551 Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'x2', { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
3552 Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'y2', { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
3554 window.SVGPathSegCurvetoQuadraticAbs = function(owningPathSegList, x, y, x1, y1) {
3555 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS, 'Q', owningPathSegList);
3561 window.SVGPathSegCurvetoQuadraticAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3562 window.SVGPathSegCurvetoQuadraticAbs.prototype.toString = function() { return '[object SVGPathSegCurvetoQuadraticAbs]'; }
3563 window.SVGPathSegCurvetoQuadraticAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x + ' ' + this._y; }
3564 window.SVGPathSegCurvetoQuadraticAbs.prototype.clone = function() { return new window.SVGPathSegCurvetoQuadraticAbs(undefined, this._x, this._y, this._x1, this._y1); }
3565 Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3566 Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3567 Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype, 'x1', { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
3568 Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype, 'y1', { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
3570 window.SVGPathSegCurvetoQuadraticRel = function(owningPathSegList, x, y, x1, y1) {
3571 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL, 'q', owningPathSegList);
3577 window.SVGPathSegCurvetoQuadraticRel.prototype = Object.create(window.SVGPathSeg.prototype);
3578 window.SVGPathSegCurvetoQuadraticRel.prototype.toString = function() { return '[object SVGPathSegCurvetoQuadraticRel]'; }
3579 window.SVGPathSegCurvetoQuadraticRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x + ' ' + this._y; }
3580 window.SVGPathSegCurvetoQuadraticRel.prototype.clone = function() { return new window.SVGPathSegCurvetoQuadraticRel(undefined, this._x, this._y, this._x1, this._y1); }
3581 Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3582 Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3583 Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype, 'x1', { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
3584 Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype, 'y1', { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
3586 window.SVGPathSegArcAbs = function(owningPathSegList, x, y, r1, r2, angle, largeArcFlag, sweepFlag) {
3587 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_ARC_ABS, 'A', owningPathSegList);
3592 this._angle = angle;
3593 this._largeArcFlag = largeArcFlag;
3594 this._sweepFlag = sweepFlag;
3596 window.SVGPathSegArcAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3597 window.SVGPathSegArcAbs.prototype.toString = function() { return '[object SVGPathSegArcAbs]'; }
3598 window.SVGPathSegArcAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._r1 + ' ' + this._r2 + ' ' + this._angle + ' ' + (this._largeArcFlag ? '1' : '0') + ' ' + (this._sweepFlag ? '1' : '0') + ' ' + this._x + ' ' + this._y; }
3599 window.SVGPathSegArcAbs.prototype.clone = function() { return new window.SVGPathSegArcAbs(undefined, this._x, this._y, this._r1, this._r2, this._angle, this._largeArcFlag, this._sweepFlag); }
3600 Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3601 Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3602 Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'r1', { get: function() { return this._r1; }, set: function(r1) { this._r1 = r1; this._segmentChanged(); }, enumerable: true });
3603 Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'r2', { get: function() { return this._r2; }, set: function(r2) { this._r2 = r2; this._segmentChanged(); }, enumerable: true });
3604 Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'angle', { get: function() { return this._angle; }, set: function(angle) { this._angle = angle; this._segmentChanged(); }, enumerable: true });
3605 Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'largeArcFlag', { get: function() { return this._largeArcFlag; }, set: function(largeArcFlag) { this._largeArcFlag = largeArcFlag; this._segmentChanged(); }, enumerable: true });
3606 Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'sweepFlag', { get: function() { return this._sweepFlag; }, set: function(sweepFlag) { this._sweepFlag = sweepFlag; this._segmentChanged(); }, enumerable: true });
3608 window.SVGPathSegArcRel = function(owningPathSegList, x, y, r1, r2, angle, largeArcFlag, sweepFlag) {
3609 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_ARC_REL, 'a', owningPathSegList);
3614 this._angle = angle;
3615 this._largeArcFlag = largeArcFlag;
3616 this._sweepFlag = sweepFlag;
3618 window.SVGPathSegArcRel.prototype = Object.create(window.SVGPathSeg.prototype);
3619 window.SVGPathSegArcRel.prototype.toString = function() { return '[object SVGPathSegArcRel]'; }
3620 window.SVGPathSegArcRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._r1 + ' ' + this._r2 + ' ' + this._angle + ' ' + (this._largeArcFlag ? '1' : '0') + ' ' + (this._sweepFlag ? '1' : '0') + ' ' + this._x + ' ' + this._y; }
3621 window.SVGPathSegArcRel.prototype.clone = function() { return new window.SVGPathSegArcRel(undefined, this._x, this._y, this._r1, this._r2, this._angle, this._largeArcFlag, this._sweepFlag); }
3622 Object.defineProperty(window.SVGPathSegArcRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3623 Object.defineProperty(window.SVGPathSegArcRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3624 Object.defineProperty(window.SVGPathSegArcRel.prototype, 'r1', { get: function() { return this._r1; }, set: function(r1) { this._r1 = r1; this._segmentChanged(); }, enumerable: true });
3625 Object.defineProperty(window.SVGPathSegArcRel.prototype, 'r2', { get: function() { return this._r2; }, set: function(r2) { this._r2 = r2; this._segmentChanged(); }, enumerable: true });
3626 Object.defineProperty(window.SVGPathSegArcRel.prototype, 'angle', { get: function() { return this._angle; }, set: function(angle) { this._angle = angle; this._segmentChanged(); }, enumerable: true });
3627 Object.defineProperty(window.SVGPathSegArcRel.prototype, 'largeArcFlag', { get: function() { return this._largeArcFlag; }, set: function(largeArcFlag) { this._largeArcFlag = largeArcFlag; this._segmentChanged(); }, enumerable: true });
3628 Object.defineProperty(window.SVGPathSegArcRel.prototype, 'sweepFlag', { get: function() { return this._sweepFlag; }, set: function(sweepFlag) { this._sweepFlag = sweepFlag; this._segmentChanged(); }, enumerable: true });
3630 window.SVGPathSegLinetoHorizontalAbs = function(owningPathSegList, x) {
3631 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS, 'H', owningPathSegList);
3634 window.SVGPathSegLinetoHorizontalAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3635 window.SVGPathSegLinetoHorizontalAbs.prototype.toString = function() { return '[object SVGPathSegLinetoHorizontalAbs]'; }
3636 window.SVGPathSegLinetoHorizontalAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x; }
3637 window.SVGPathSegLinetoHorizontalAbs.prototype.clone = function() { return new window.SVGPathSegLinetoHorizontalAbs(undefined, this._x); }
3638 Object.defineProperty(window.SVGPathSegLinetoHorizontalAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3640 window.SVGPathSegLinetoHorizontalRel = function(owningPathSegList, x) {
3641 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL, 'h', owningPathSegList);
3644 window.SVGPathSegLinetoHorizontalRel.prototype = Object.create(window.SVGPathSeg.prototype);
3645 window.SVGPathSegLinetoHorizontalRel.prototype.toString = function() { return '[object SVGPathSegLinetoHorizontalRel]'; }
3646 window.SVGPathSegLinetoHorizontalRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x; }
3647 window.SVGPathSegLinetoHorizontalRel.prototype.clone = function() { return new window.SVGPathSegLinetoHorizontalRel(undefined, this._x); }
3648 Object.defineProperty(window.SVGPathSegLinetoHorizontalRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3650 window.SVGPathSegLinetoVerticalAbs = function(owningPathSegList, y) {
3651 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS, 'V', owningPathSegList);
3654 window.SVGPathSegLinetoVerticalAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3655 window.SVGPathSegLinetoVerticalAbs.prototype.toString = function() { return '[object SVGPathSegLinetoVerticalAbs]'; }
3656 window.SVGPathSegLinetoVerticalAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._y; }
3657 window.SVGPathSegLinetoVerticalAbs.prototype.clone = function() { return new window.SVGPathSegLinetoVerticalAbs(undefined, this._y); }
3658 Object.defineProperty(window.SVGPathSegLinetoVerticalAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3660 window.SVGPathSegLinetoVerticalRel = function(owningPathSegList, y) {
3661 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL, 'v', owningPathSegList);
3664 window.SVGPathSegLinetoVerticalRel.prototype = Object.create(window.SVGPathSeg.prototype);
3665 window.SVGPathSegLinetoVerticalRel.prototype.toString = function() { return '[object SVGPathSegLinetoVerticalRel]'; }
3666 window.SVGPathSegLinetoVerticalRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._y; }
3667 window.SVGPathSegLinetoVerticalRel.prototype.clone = function() { return new window.SVGPathSegLinetoVerticalRel(undefined, this._y); }
3668 Object.defineProperty(window.SVGPathSegLinetoVerticalRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3670 window.SVGPathSegCurvetoCubicSmoothAbs = function(owningPathSegList, x, y, x2, y2) {
3671 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS, 'S', owningPathSegList);
3677 window.SVGPathSegCurvetoCubicSmoothAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3678 window.SVGPathSegCurvetoCubicSmoothAbs.prototype.toString = function() { return '[object SVGPathSegCurvetoCubicSmoothAbs]'; }
3679 window.SVGPathSegCurvetoCubicSmoothAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
3680 window.SVGPathSegCurvetoCubicSmoothAbs.prototype.clone = function() { return new window.SVGPathSegCurvetoCubicSmoothAbs(undefined, this._x, this._y, this._x2, this._y2); }
3681 Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3682 Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3683 Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype, 'x2', { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
3684 Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype, 'y2', { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
3686 window.SVGPathSegCurvetoCubicSmoothRel = function(owningPathSegList, x, y, x2, y2) {
3687 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL, 's', owningPathSegList);
3693 window.SVGPathSegCurvetoCubicSmoothRel.prototype = Object.create(window.SVGPathSeg.prototype);
3694 window.SVGPathSegCurvetoCubicSmoothRel.prototype.toString = function() { return '[object SVGPathSegCurvetoCubicSmoothRel]'; }
3695 window.SVGPathSegCurvetoCubicSmoothRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
3696 window.SVGPathSegCurvetoCubicSmoothRel.prototype.clone = function() { return new window.SVGPathSegCurvetoCubicSmoothRel(undefined, this._x, this._y, this._x2, this._y2); }
3697 Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3698 Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3699 Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype, 'x2', { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
3700 Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype, 'y2', { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
3702 window.SVGPathSegCurvetoQuadraticSmoothAbs = function(owningPathSegList, x, y) {
3703 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS, 'T', owningPathSegList);
3707 window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3708 window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype.toString = function() { return '[object SVGPathSegCurvetoQuadraticSmoothAbs]'; }
3709 window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
3710 window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype.clone = function() { return new window.SVGPathSegCurvetoQuadraticSmoothAbs(undefined, this._x, this._y); }
3711 Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3712 Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3714 window.SVGPathSegCurvetoQuadraticSmoothRel = function(owningPathSegList, x, y) {
3715 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, 't', owningPathSegList);
3719 window.SVGPathSegCurvetoQuadraticSmoothRel.prototype = Object.create(window.SVGPathSeg.prototype);
3720 window.SVGPathSegCurvetoQuadraticSmoothRel.prototype.toString = function() { return '[object SVGPathSegCurvetoQuadraticSmoothRel]'; }
3721 window.SVGPathSegCurvetoQuadraticSmoothRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
3722 window.SVGPathSegCurvetoQuadraticSmoothRel.prototype.clone = function() { return new window.SVGPathSegCurvetoQuadraticSmoothRel(undefined, this._x, this._y); }
3723 Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3724 Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3726 // Add createSVGPathSeg* functions to window.SVGPathElement.
3727 // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-Interfacewindow.SVGPathElement.
3728 window.SVGPathElement.prototype.createSVGPathSegClosePath = function() { return new window.SVGPathSegClosePath(undefined); }
3729 window.SVGPathElement.prototype.createSVGPathSegMovetoAbs = function(x, y) { return new window.SVGPathSegMovetoAbs(undefined, x, y); }
3730 window.SVGPathElement.prototype.createSVGPathSegMovetoRel = function(x, y) { return new window.SVGPathSegMovetoRel(undefined, x, y); }
3731 window.SVGPathElement.prototype.createSVGPathSegLinetoAbs = function(x, y) { return new window.SVGPathSegLinetoAbs(undefined, x, y); }
3732 window.SVGPathElement.prototype.createSVGPathSegLinetoRel = function(x, y) { return new window.SVGPathSegLinetoRel(undefined, x, y); }
3733 window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicAbs = function(x, y, x1, y1, x2, y2) { return new window.SVGPathSegCurvetoCubicAbs(undefined, x, y, x1, y1, x2, y2); }
3734 window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicRel = function(x, y, x1, y1, x2, y2) { return new window.SVGPathSegCurvetoCubicRel(undefined, x, y, x1, y1, x2, y2); }
3735 window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticAbs = function(x, y, x1, y1) { return new window.SVGPathSegCurvetoQuadraticAbs(undefined, x, y, x1, y1); }
3736 window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticRel = function(x, y, x1, y1) { return new window.SVGPathSegCurvetoQuadraticRel(undefined, x, y, x1, y1); }
3737 window.SVGPathElement.prototype.createSVGPathSegArcAbs = function(x, y, r1, r2, angle, largeArcFlag, sweepFlag) { return new window.SVGPathSegArcAbs(undefined, x, y, r1, r2, angle, largeArcFlag, sweepFlag); }
3738 window.SVGPathElement.prototype.createSVGPathSegArcRel = function(x, y, r1, r2, angle, largeArcFlag, sweepFlag) { return new window.SVGPathSegArcRel(undefined, x, y, r1, r2, angle, largeArcFlag, sweepFlag); }
3739 window.SVGPathElement.prototype.createSVGPathSegLinetoHorizontalAbs = function(x) { return new window.SVGPathSegLinetoHorizontalAbs(undefined, x); }
3740 window.SVGPathElement.prototype.createSVGPathSegLinetoHorizontalRel = function(x) { return new window.SVGPathSegLinetoHorizontalRel(undefined, x); }
3741 window.SVGPathElement.prototype.createSVGPathSegLinetoVerticalAbs = function(y) { return new window.SVGPathSegLinetoVerticalAbs(undefined, y); }
3742 window.SVGPathElement.prototype.createSVGPathSegLinetoVerticalRel = function(y) { return new window.SVGPathSegLinetoVerticalRel(undefined, y); }
3743 window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothAbs = function(x, y, x2, y2) { return new window.SVGPathSegCurvetoCubicSmoothAbs(undefined, x, y, x2, y2); }
3744 window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothRel = function(x, y, x2, y2) { return new window.SVGPathSegCurvetoCubicSmoothRel(undefined, x, y, x2, y2); }
3745 window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothAbs = function(x, y) { return new window.SVGPathSegCurvetoQuadraticSmoothAbs(undefined, x, y); }
3746 window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothRel = function(x, y) { return new window.SVGPathSegCurvetoQuadraticSmoothRel(undefined, x, y); }
3748 if (!('getPathSegAtLength' in window.SVGPathElement.prototype)) {
3749 // Add getPathSegAtLength to SVGPathElement.
3750 // Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-__svg__SVGPathElement__getPathSegAtLength
3751 // This polyfill requires SVGPathElement.getTotalLength to implement the distance-along-a-path algorithm.
3752 window.SVGPathElement.prototype.getPathSegAtLength = function(distance) {
3753 if (distance === undefined || !isFinite(distance))
3754 throw 'Invalid arguments.';
3756 var measurementElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
3757 measurementElement.setAttribute('d', this.getAttribute('d'));
3758 var lastPathSegment = measurementElement.pathSegList.numberOfItems - 1;
3760 // If the path is empty, return 0.
3761 if (lastPathSegment <= 0)
3765 measurementElement.pathSegList.removeItem(lastPathSegment);
3766 if (distance > measurementElement.getTotalLength())
3769 } while (lastPathSegment > 0);
3770 return lastPathSegment;
3775 // Checking for SVGPathSegList in window checks for the case of an implementation without the
3776 // SVGPathSegList API.
3777 // The second check for appendItem is specific to Firefox 59+ which removed only parts of the
3778 // SVGPathSegList API (e.g., appendItem). In this case we need to re-implement the entire API
3779 // so the polyfill data (i.e., _list) is used throughout.
3780 if (!('SVGPathSegList' in window) || !('appendItem' in window.SVGPathSegList.prototype)) {
3781 // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSegList
3782 window.SVGPathSegList = function(pathElement) {
3783 this._pathElement = pathElement;
3784 this._list = this._parsePath(this._pathElement.getAttribute('d'));
3786 // Use a MutationObserver to catch changes to the path's 'd' attribute.
3787 this._mutationObserverConfig = { 'attributes': true, 'attributeFilter': ['d'] };
3788 this._pathElementMutationObserver = new MutationObserver(this._updateListFromPathMutations.bind(this));
3789 this._pathElementMutationObserver.observe(this._pathElement, this._mutationObserverConfig);
3792 window.SVGPathSegList.prototype.classname = 'SVGPathSegList';
3794 Object.defineProperty(window.SVGPathSegList.prototype, 'numberOfItems', {
3796 this._checkPathSynchronizedToList();
3797 return this._list.length;
3802 // The length property was not specified but was in Firefox 58.
3803 Object.defineProperty(window.SVGPathSegList.prototype, 'length', {
3805 this._checkPathSynchronizedToList();
3806 return this._list.length;
3811 // Add the pathSegList accessors to window.SVGPathElement.
3812 // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGAnimatedPathData
3813 Object.defineProperty(window.SVGPathElement.prototype, 'pathSegList', {
3815 if (!this._pathSegList)
3816 this._pathSegList = new window.SVGPathSegList(this);
3817 return this._pathSegList;
3821 // FIXME: The following are not implemented and simply return window.SVGPathElement.pathSegList.
3822 Object.defineProperty(window.SVGPathElement.prototype, 'normalizedPathSegList', { get: function() { return this.pathSegList; }, enumerable: true });
3823 Object.defineProperty(window.SVGPathElement.prototype, 'animatedPathSegList', { get: function() { return this.pathSegList; }, enumerable: true });
3824 Object.defineProperty(window.SVGPathElement.prototype, 'animatedNormalizedPathSegList', { get: function() { return this.pathSegList; }, enumerable: true });
3826 // Process any pending mutations to the path element and update the list as needed.
3827 // This should be the first call of all public functions and is needed because
3828 // MutationObservers are not synchronous so we can have pending asynchronous mutations.
3829 window.SVGPathSegList.prototype._checkPathSynchronizedToList = function() {
3830 this._updateListFromPathMutations(this._pathElementMutationObserver.takeRecords());
3833 window.SVGPathSegList.prototype._updateListFromPathMutations = function(mutationRecords) {
3834 if (!this._pathElement)
3836 var hasPathMutations = false;
3837 mutationRecords.forEach(function(record) {
3838 if (record.attributeName == 'd')
3839 hasPathMutations = true;
3841 if (hasPathMutations)
3842 this._list = this._parsePath(this._pathElement.getAttribute('d'));
3845 // Serialize the list and update the path's 'd' attribute.
3846 window.SVGPathSegList.prototype._writeListToPath = function() {
3847 this._pathElementMutationObserver.disconnect();
3848 this._pathElement.setAttribute('d', window.SVGPathSegList._pathSegArrayAsString(this._list));
3849 this._pathElementMutationObserver.observe(this._pathElement, this._mutationObserverConfig);
3852 // When a path segment changes the list needs to be synchronized back to the path element.
3853 window.SVGPathSegList.prototype.segmentChanged = function(pathSeg) {
3854 this._writeListToPath();
3857 window.SVGPathSegList.prototype.clear = function() {
3858 this._checkPathSynchronizedToList();
3860 this._list.forEach(function(pathSeg) {
3861 pathSeg._owningPathSegList = null;
3864 this._writeListToPath();
3867 window.SVGPathSegList.prototype.initialize = function(newItem) {
3868 this._checkPathSynchronizedToList();
3870 this._list = [newItem];
3871 newItem._owningPathSegList = this;
3872 this._writeListToPath();
3876 window.SVGPathSegList.prototype._checkValidIndex = function(index) {
3877 if (isNaN(index) || index < 0 || index >= this.numberOfItems)
3878 throw 'INDEX_SIZE_ERR';
3881 window.SVGPathSegList.prototype.getItem = function(index) {
3882 this._checkPathSynchronizedToList();
3884 this._checkValidIndex(index);
3885 return this._list[index];
3888 window.SVGPathSegList.prototype.insertItemBefore = function(newItem, index) {
3889 this._checkPathSynchronizedToList();
3891 // Spec: If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list.
3892 if (index > this.numberOfItems)
3893 index = this.numberOfItems;
3894 if (newItem._owningPathSegList) {
3895 // SVG2 spec says to make a copy.
3896 newItem = newItem.clone();
3898 this._list.splice(index, 0, newItem);
3899 newItem._owningPathSegList = this;
3900 this._writeListToPath();
3904 window.SVGPathSegList.prototype.replaceItem = function(newItem, index) {
3905 this._checkPathSynchronizedToList();
3907 if (newItem._owningPathSegList) {
3908 // SVG2 spec says to make a copy.
3909 newItem = newItem.clone();
3911 this._checkValidIndex(index);
3912 this._list[index] = newItem;
3913 newItem._owningPathSegList = this;
3914 this._writeListToPath();
3918 window.SVGPathSegList.prototype.removeItem = function(index) {
3919 this._checkPathSynchronizedToList();
3921 this._checkValidIndex(index);
3922 var item = this._list[index];
3923 this._list.splice(index, 1);
3924 this._writeListToPath();
3928 window.SVGPathSegList.prototype.appendItem = function(newItem) {
3929 this._checkPathSynchronizedToList();
3931 if (newItem._owningPathSegList) {
3932 // SVG2 spec says to make a copy.
3933 newItem = newItem.clone();
3935 this._list.push(newItem);
3936 newItem._owningPathSegList = this;
3937 // TODO: Optimize this to just append to the existing attribute.
3938 this._writeListToPath();
3942 window.SVGPathSegList.prototype.matrixTransform = function(aSVGMatrix) {
3943 this._checkPathSynchronizedToList();
3945 var nLength = this._list.length;
3946 for( var i = 0; i < nLength; ++i )
3949 var aPathSeg = this._list[i];
3950 switch( aPathSeg.pathSegTypeAsLetter )
3954 aPathSeg._x2 = aSVGMatrix.a * nX + aSVGMatrix.c * aPathSeg._y2 + aSVGMatrix.e;
3955 aPathSeg._y2 = aSVGMatrix.b * nX + aSVGMatrix.d * aPathSeg._y2 + aSVGMatrix.f;
3956 // fall through intended
3959 aPathSeg._x1 = aSVGMatrix.a * nX + aSVGMatrix.c * aPathSeg._y1 + aSVGMatrix.e;
3960 aPathSeg._y1 = aSVGMatrix.b * nX + aSVGMatrix.d * aPathSeg._y1 + aSVGMatrix.f;
3961 // fall through intended
3965 aPathSeg._x = aSVGMatrix.a * nX + aSVGMatrix.c * aPathSeg._y + aSVGMatrix.e;
3966 aPathSeg._y = aSVGMatrix.b * nX + aSVGMatrix.d * aPathSeg._y + aSVGMatrix.f;
3969 log( 'SVGPathSeg.matrixTransform: unexpected path segment type: '
3970 + aPathSeg.pathSegTypeAsLetter );
3974 this._writeListToPath();
3977 window.SVGPathSegList.prototype.changeOrientation = function() {
3978 this._checkPathSynchronizedToList();
3980 var aPathSegList = this._list;
3981 var nLength = aPathSegList.length;
3982 if( nLength == 0 ) return;
3987 var aPathSeg = aPathSegList[0];
3988 if( aPathSeg.pathSegTypeAsLetter == 'M' )
3990 nCurrentX = aPathSeg.x;
3991 nCurrentY = aPathSeg.y;
3992 aPathSegList.shift();
3997 for( i = 0; i < nLength; ++i )
3999 aPathSeg = aPathSegList[i];
4000 switch( aPathSeg.pathSegTypeAsLetter )
4003 var nX = aPathSeg._x1;
4004 aPathSeg._x1 = aPathSeg._x2;
4006 var nY = aPathSeg._y1;
4007 aPathSeg._y1 = aPathSeg._y2;
4009 // fall through intended
4013 var aPoint = { x: aPathSeg._x, y: aPathSeg._y };
4014 aPathSeg._x = nCurrentX;
4015 aPathSeg._y = nCurrentY;
4016 nCurrentX = aPoint.x;
4017 nCurrentY = aPoint.y;
4020 log( 'SVGPathSegList.changeOrientation: unexpected path segment type: '
4021 + aPathSeg.pathSegTypeAsLetter );
4026 aPathSegList.reverse();
4028 var aMovePathSeg = new window.SVGPathSegMovetoAbs( this, nCurrentX, nCurrentY );
4029 aPathSegList.unshift( aMovePathSeg );
4031 this._writeListToPath();
4034 window.SVGPathSegList._pathSegArrayAsString = function(pathSegArray) {
4037 pathSegArray.forEach(function(pathSeg) {
4040 string += pathSeg._asPathString();
4042 string += ' ' + pathSeg._asPathString();
4048 // This closely follows SVGPathParser::parsePath from Source/core/svg/SVGPathParser.cpp.
4049 window.SVGPathSegList.prototype._parsePath = function(string) {
4050 if (!string || string.length == 0)
4053 var owningPathSegList = this;
4055 var Builder = function() {
4056 this.pathSegList = [];
4059 Builder.prototype.appendSegment = function(pathSeg) {
4060 this.pathSegList.push(pathSeg);
4063 var Source = function(string) {
4064 this._string = string;
4065 this._currentIndex = 0;
4066 this._endIndex = this._string.length;
4067 this._previousCommand = window.SVGPathSeg.PATHSEG_UNKNOWN;
4069 this._skipOptionalSpaces();
4072 Source.prototype._isCurrentSpace = function() {
4073 var character = this._string[this._currentIndex];
4074 return character <= ' ' && (character == ' ' || character == '\n' || character == '\t' || character == '\r' || character == '\f');
4077 Source.prototype._skipOptionalSpaces = function() {
4078 while (this._currentIndex < this._endIndex && this._isCurrentSpace())
4079 this._currentIndex++;
4080 return this._currentIndex < this._endIndex;
4083 Source.prototype._skipOptionalSpacesOrDelimiter = function() {
4084 if (this._currentIndex < this._endIndex && !this._isCurrentSpace() && this._string.charAt(this._currentIndex) != ',')
4086 if (this._skipOptionalSpaces()) {
4087 if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == ',') {
4088 this._currentIndex++;
4089 this._skipOptionalSpaces();
4092 return this._currentIndex < this._endIndex;
4095 Source.prototype.hasMoreData = function() {
4096 return this._currentIndex < this._endIndex;
4099 Source.prototype.peekSegmentType = function() {
4100 var lookahead = this._string[this._currentIndex];
4101 return this._pathSegTypeFromChar(lookahead);
4104 Source.prototype._pathSegTypeFromChar = function(lookahead) {
4105 switch (lookahead) {
4108 return window.SVGPathSeg.PATHSEG_CLOSEPATH;
4110 return window.SVGPathSeg.PATHSEG_MOVETO_ABS;
4112 return window.SVGPathSeg.PATHSEG_MOVETO_REL;
4114 return window.SVGPathSeg.PATHSEG_LINETO_ABS;
4116 return window.SVGPathSeg.PATHSEG_LINETO_REL;
4118 return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS;
4120 return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL;
4122 return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS;
4124 return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL;
4126 return window.SVGPathSeg.PATHSEG_ARC_ABS;
4128 return window.SVGPathSeg.PATHSEG_ARC_REL;
4130 return window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS;
4132 return window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL;
4134 return window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS;
4136 return window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL;
4138 return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS;
4140 return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL;
4142 return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS;
4144 return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL;
4146 return window.SVGPathSeg.PATHSEG_UNKNOWN;
4150 Source.prototype._nextCommandHelper = function(lookahead, previousCommand) {
4151 // Check for remaining coordinates in the current command.
4152 if ((lookahead == '+' || lookahead == '-' || lookahead == '.' || (lookahead >= '0' && lookahead <= '9')) && previousCommand != window.SVGPathSeg.PATHSEG_CLOSEPATH) {
4153 if (previousCommand == window.SVGPathSeg.PATHSEG_MOVETO_ABS)
4154 return window.SVGPathSeg.PATHSEG_LINETO_ABS;
4155 if (previousCommand == window.SVGPathSeg.PATHSEG_MOVETO_REL)
4156 return window.SVGPathSeg.PATHSEG_LINETO_REL;
4157 return previousCommand;
4159 return window.SVGPathSeg.PATHSEG_UNKNOWN;
4162 Source.prototype.initialCommandIsMoveTo = function() {
4163 // If the path is empty it is still valid, so return true.
4164 if (!this.hasMoreData())
4166 var command = this.peekSegmentType();
4167 // Path must start with moveTo.
4168 return command == window.SVGPathSeg.PATHSEG_MOVETO_ABS || command == window.SVGPathSeg.PATHSEG_MOVETO_REL;
4171 // Parse a number from an SVG path. This very closely follows genericParseNumber(...) from Source/core/svg/SVGParserUtilities.cpp.
4172 // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-PathDataBNF
4173 Source.prototype._parseNumber = function() {
4181 var startIndex = this._currentIndex;
4183 this._skipOptionalSpaces();
4186 if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == '+')
4187 this._currentIndex++;
4188 else if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == '-') {
4189 this._currentIndex++;
4193 if (this._currentIndex == this._endIndex || ((this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9') && this._string.charAt(this._currentIndex) != '.'))
4194 // The first character of a number must be one of [0-9+-.].
4197 // Read the integer part, build right-to-left.
4198 var startIntPartIndex = this._currentIndex;
4199 while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9')
4200 this._currentIndex++; // Advance to first non-digit.
4202 if (this._currentIndex != startIntPartIndex) {
4203 var scanIntPartIndex = this._currentIndex - 1;
4205 while (scanIntPartIndex >= startIntPartIndex) {
4206 integer += multiplier * (this._string.charAt(scanIntPartIndex--) - '0');
4211 // Read the decimals.
4212 if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == '.') {
4213 this._currentIndex++;
4215 // There must be a least one digit following the .
4216 if (this._currentIndex >= this._endIndex || this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9')
4218 while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
4220 decimal += (this._string.charAt(this._currentIndex) - '0') / frac;
4221 this._currentIndex += 1;
4225 // Read the exponent part.
4226 if (this._currentIndex != startIndex && this._currentIndex + 1 < this._endIndex && (this._string.charAt(this._currentIndex) == 'e' || this._string.charAt(this._currentIndex) == 'E') && (this._string.charAt(this._currentIndex + 1) != 'x' && this._string.charAt(this._currentIndex + 1) != 'm')) {
4227 this._currentIndex++;
4229 // Read the sign of the exponent.
4230 if (this._string.charAt(this._currentIndex) == '+') {
4231 this._currentIndex++;
4232 } else if (this._string.charAt(this._currentIndex) == '-') {
4233 this._currentIndex++;
4237 // There must be an exponent.
4238 if (this._currentIndex >= this._endIndex || this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9')
4241 while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
4243 exponent += (this._string.charAt(this._currentIndex) - '0');
4244 this._currentIndex++;
4248 var number = integer + decimal;
4252 number *= Math.pow(10, expsign * exponent);
4254 if (startIndex == this._currentIndex)
4257 this._skipOptionalSpacesOrDelimiter();
4262 Source.prototype._parseArcFlag = function() {
4263 if (this._currentIndex >= this._endIndex)
4266 var flagChar = this._string.charAt(this._currentIndex++);
4267 if (flagChar == '0')
4269 else if (flagChar == '1')
4274 this._skipOptionalSpacesOrDelimiter();
4278 Source.prototype.parseSegment = function() {
4279 var lookahead = this._string[this._currentIndex];
4280 var command = this._pathSegTypeFromChar(lookahead);
4281 if (command == window.SVGPathSeg.PATHSEG_UNKNOWN) {
4282 // Possibly an implicit command. Not allowed if this is the first command.
4283 if (this._previousCommand == window.SVGPathSeg.PATHSEG_UNKNOWN)
4285 command = this._nextCommandHelper(lookahead, this._previousCommand);
4286 if (command == window.SVGPathSeg.PATHSEG_UNKNOWN)
4289 this._currentIndex++;
4292 this._previousCommand = command;
4295 case window.SVGPathSeg.PATHSEG_MOVETO_REL:
4296 return new window.SVGPathSegMovetoRel(owningPathSegList, this._parseNumber(), this._parseNumber());
4297 case window.SVGPathSeg.PATHSEG_MOVETO_ABS:
4298 return new window.SVGPathSegMovetoAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
4299 case window.SVGPathSeg.PATHSEG_LINETO_REL:
4300 return new window.SVGPathSegLinetoRel(owningPathSegList, this._parseNumber(), this._parseNumber());
4301 case window.SVGPathSeg.PATHSEG_LINETO_ABS:
4302 return new window.SVGPathSegLinetoAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
4303 case window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL:
4304 return new window.SVGPathSegLinetoHorizontalRel(owningPathSegList, this._parseNumber());
4305 case window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS:
4306 return new window.SVGPathSegLinetoHorizontalAbs(owningPathSegList, this._parseNumber());
4307 case window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL:
4308 return new window.SVGPathSegLinetoVerticalRel(owningPathSegList, this._parseNumber());
4309 case window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS:
4310 return new window.SVGPathSegLinetoVerticalAbs(owningPathSegList, this._parseNumber());
4311 case window.SVGPathSeg.PATHSEG_CLOSEPATH:
4312 this._skipOptionalSpaces();
4313 return new window.SVGPathSegClosePath(owningPathSegList);
4314 case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL:
4315 var points = {x1: this._parseNumber(), y1: this._parseNumber(), x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
4316 return new window.SVGPathSegCurvetoCubicRel(owningPathSegList, points.x, points.y, points.x1, points.y1, points.x2, points.y2);
4317 case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS:
4318 var points = {x1: this._parseNumber(), y1: this._parseNumber(), x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
4319 return new window.SVGPathSegCurvetoCubicAbs(owningPathSegList, points.x, points.y, points.x1, points.y1, points.x2, points.y2);
4320 case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
4321 var points = {x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
4322 return new window.SVGPathSegCurvetoCubicSmoothRel(owningPathSegList, points.x, points.y, points.x2, points.y2);
4323 case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
4324 var points = {x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
4325 return new window.SVGPathSegCurvetoCubicSmoothAbs(owningPathSegList, points.x, points.y, points.x2, points.y2);
4326 case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL:
4327 var points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
4328 return new window.SVGPathSegCurvetoQuadraticRel(owningPathSegList, points.x, points.y, points.x1, points.y1);
4329 case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS:
4330 var points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
4331 return new window.SVGPathSegCurvetoQuadraticAbs(owningPathSegList, points.x, points.y, points.x1, points.y1);
4332 case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
4333 return new window.SVGPathSegCurvetoQuadraticSmoothRel(owningPathSegList, this._parseNumber(), this._parseNumber());
4334 case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
4335 return new window.SVGPathSegCurvetoQuadraticSmoothAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
4336 case window.SVGPathSeg.PATHSEG_ARC_REL:
4337 var points = {x1: this._parseNumber(), y1: this._parseNumber(), arcAngle: this._parseNumber(), arcLarge: this._parseArcFlag(), arcSweep: this._parseArcFlag(), x: this._parseNumber(), y: this._parseNumber()};
4338 return new window.SVGPathSegArcRel(owningPathSegList, points.x, points.y, points.x1, points.y1, points.arcAngle, points.arcLarge, points.arcSweep);
4339 case window.SVGPathSeg.PATHSEG_ARC_ABS:
4340 var points = {x1: this._parseNumber(), y1: this._parseNumber(), arcAngle: this._parseNumber(), arcLarge: this._parseArcFlag(), arcSweep: this._parseArcFlag(), x: this._parseNumber(), y: this._parseNumber()};
4341 return new window.SVGPathSegArcAbs(owningPathSegList, points.x, points.y, points.x1, points.y1, points.arcAngle, points.arcLarge, points.arcSweep);
4343 throw 'Unknown path seg type.'
4347 var builder = new Builder();
4348 var source = new Source(string);
4350 if (!source.initialCommandIsMoveTo())
4352 while (source.hasMoreData()) {
4353 var pathSeg = source.parseSegment();
4356 builder.appendSegment(pathSeg);
4359 return builder.pathSegList;
4367 * The above code is a derivative work of some part of the SVGPathSeg API.
4369 * This API is a drop-in replacement for the SVGPathSeg and SVGPathSegList APIs that were removed from
4370 * SVG2 (https://lists.w3.org/Archives/Public/www-svg/2015Jun/0044.html), including the latest spec
4371 * changes which were implemented in Firefox 43 and Chrome 46.
4373 * @source https://github.com/progers/pathseg
4380 * The following is the license notice for the part of JavaScript code of
4381 * this page included between the '@libreofficestart' and the '@libreofficeend'
4385 /***** ******************************************************************
4387 * This file is part of the LibreOffice project.
4389 * This Source Code Form is subject to the terms of the Mozilla Public
4390 * License, v. 2.0. If a copy of the MPL was not distributed with this
4391 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
4393 * This file incorporates work covered by the following license notice:
4395 * Licensed to the Apache Software Foundation (ASF) under one or more
4396 * contributor license agreements. See the NOTICE file distributed
4397 * with this work for additional information regarding copyright
4398 * ownership. The ASF licenses this file to you under the Apache
4399 * License, Version 2.0 (the 'License'); you may not use this file
4400 * except in compliance with the License. You may obtain a copy of
4401 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
4403 ************************************************************************/
4408 * The above is the license notice for the part of JavaScript code of
4409 * this page included between the '@libreofficestart' and the '@libreofficeend'
4417 * Several parts of the following code are the result of the porting,
4418 * started on August 2011, of the C++ code included in the source
4419 * files placed under the folder '/slideshow/source' and
4420 * sub-folders. This got later rebased onto the AL2-licensed versions
4421 * of those files in early 2013.
4422 * @source https://cgit.freedesktop.org/libreoffice/core/tree/slideshow/source
4427 window.onload = init;
4431 var aOOOElemMetaSlides = 'ooo:meta_slides';
4432 var aOOOElemMetaSlide = 'ooo:meta_slide';
4433 var aOOOElemTextField = 'ooo:text_field';
4434 var aPresentationClipPathId = 'presentation_clip_path';
4435 var aPresentationClipPathShrinkId = 'presentation_clip_path_shrink';
4438 var aOOOAttrNumberOfSlides = 'number-of-slides';
4439 var aOOOAttrStartSlideNumber= 'start-slide-number';
4440 var aOOOAttrNumberingType = 'page-numbering-type';
4441 var aOOOAttrListItemNumberingType= 'numbering-type';
4442 var aOOOAttrUsePositionedChars = 'use-positioned-chars';
4444 var aOOOAttrSlide = 'slide';
4445 var aOOOAttrMaster = 'master';
4446 var aOOOAttrDisplayName = 'display-name';
4447 var aOOOAttrSlideDuration = 'slide-duration';
4448 var aOOOAttrHasTransition = 'has-transition';
4449 var aOOOAttrHasCustomBackground = 'has-custom-background';
4450 var aOOOAttrBackgroundVisibility = 'background-visibility';
4451 var aOOOAttrMasterObjectsVisibility = 'master-objects-visibility';
4452 var aOOOAttrPageNumberVisibility = 'page-number-visibility';
4453 var aOOOAttrDateTimeVisibility = 'date-time-visibility';
4454 var aOOOAttrFooterVisibility = 'footer-visibility';
4455 var aOOOAttrHeaderVisibility = 'header-visibility';
4456 var aOOOAttrDateTimeField = 'date-time-field';
4457 var aOOOAttrFooterField = 'footer-field';
4458 var aOOOAttrHeaderField = 'header-field';
4460 var aOOOAttrDateTimeFormat = 'date-time-format';
4462 var aOOOAttrTextAdjust = 'text-adjust';
4464 // element class names
4465 var aClipPathGroupClassName = 'ClipPathGroup';
4466 var aPageClassName = 'Page';
4467 var aSlideNumberClassName = 'Slide_Number';
4468 var aDateTimeClassName = 'Date/Time';
4469 var aFooterClassName = 'Footer';
4470 var aHeaderClassName = 'Header';
4471 var aDateClassName = 'Date';
4472 var aTimeClassName = 'Time';
4473 var aSlideNameClassName='SlideName';
4475 // Creating a namespace dictionary.
4477 NSS['svg']='http://www.w3.org/2000/svg';
4478 NSS['rdf']='http://www.w3.org/1999/02/22-rdf-syntax-ns#';
4479 NSS['xlink']='http://www.w3.org/1999/xlink';
4480 NSS['xml']='http://www.w3.org/XML/1998/namespace';
4481 NSS['ooo'] = 'http://xml.openoffice.org/svg/export';
4482 NSS['presentation'] = 'http://sun.com/xmlns/staroffice/presentation';
4483 NSS['smil'] = 'http://www.w3.org/2001/SMIL20/';
4484 NSS['anim'] = 'urn:oasis:names:tc:opendocument:xmlns:animation:1.0';
4486 // Presentation modes.
4490 // Mouse handler actions.
4492 var MOUSE_DOWN = 2; // eslint-disable-line no-unused-vars
4493 var MOUSE_MOVE = 3; // eslint-disable-line no-unused-vars
4494 var MOUSE_WHEEL = 4;
4497 var LEFT_KEY = 37; // cursor left keycode
4498 var UP_KEY = 38; // cursor up keycode
4499 var RIGHT_KEY = 39; // cursor right keycode
4500 var DOWN_KEY = 40; // cursor down keycode
4501 var PAGE_UP_KEY = 33; // page up keycode
4502 var PAGE_DOWN_KEY = 34; // page down keycode
4503 var HOME_KEY = 36; // home keycode
4504 var END_KEY = 35; // end keycode
4507 var ESCAPE_KEY = 27;
4510 // Visibility Values
4514 var aVisibilityAttributeValue = [ 'hidden', 'visible', 'inherit' ]; // eslint-disable-line no-unused-vars
4515 var aVisibilityValue = { 'hidden' : HIDDEN, 'visible' : VISIBLE, 'inherit' : INHERIT };
4518 var ROOT_NODE = document.getElementsByTagNameNS( NSS['svg'], 'svg' )[0];
4521 var INDEX_COLUMNS_DEFAULT = 3;
4522 var INDEX_OFFSET = 0;
4525 var Detect = configureDetectionTools();
4527 var theSlideIndexPage;
4528 var currentMode = SLIDE_MODE;
4529 var processingEffect = false;
4530 var nCurSlide = undefined;
4531 var bTextHasBeenSelected = false;
4532 var sLastSelectedText = '';
4535 // Initialize char and key code dictionaries.
4536 var charCodeDictionary = getDefaultCharCodeDictionary();
4537 var keyCodeDictionary = getDefaultKeyCodeDictionary();
4539 // Initialize mouse handler dictionary.
4540 var mouseHandlerDictionary = getDefaultMouseHandlerDictionary();
4542 /***************************
4543 ** OOP support functions **
4544 ***************************/
4546 function object( aObject )
4548 var F = function() {};
4549 F.prototype = aObject;
4554 function extend( aSubType, aSuperType )
4556 if (!aSuperType || !aSubType)
4558 alert('extend failed, verify dependencies');
4560 var OP = Object.prototype;
4561 var sp = aSuperType.prototype;
4562 var rp = object( sp );
4563 aSubType.prototype = rp;
4565 rp.constructor = aSubType;
4566 aSubType.superclass = sp;
4568 // assign constructor property
4569 if (aSuperType != Object && sp.constructor == OP.constructor)
4571 sp.constructor = aSuperType;
4578 function instantiate( TemplateClass, BaseType )
4580 if( !TemplateClass.instanceSet )
4581 TemplateClass.instanceSet = [];
4583 var nSize = TemplateClass.instanceSet.length;
4585 for( var i = 0; i < nSize; ++i )
4587 if( TemplateClass.instanceSet[i].base === BaseType )
4588 return TemplateClass.instanceSet[i].instance;
4591 TemplateClass.instanceSet[ nSize ] = {};
4592 TemplateClass.instanceSet[ nSize ].base = BaseType;
4593 TemplateClass.instanceSet[ nSize ].instance = TemplateClass( BaseType );
4595 return TemplateClass.instanceSet[ nSize ].instance;
4600 /**********************************
4601 ** Helper functions and classes **
4602 **********************************/
4604 function Rectangle( aSVGRectElem )
4606 var x = parseInt( aSVGRectElem.getAttribute( 'x' ) );
4607 var y = parseInt( aSVGRectElem.getAttribute( 'y' ) );
4608 var width = parseInt( aSVGRectElem.getAttribute( 'width' ) );
4609 var height = parseInt( aSVGRectElem.getAttribute( 'height' ) );
4612 this.right = x + width;
4614 this.bottom = y + height;
4618 * Returns key corresponding to a value in object, null otherwise.
4623 function getKeyByValue(aObj, value) {
4624 for(var key in aObj) {
4625 if(aObj[key] == value)
4631 function log( message )
4633 if( typeof console == 'object' )
4635 // eslint-disable-next-line no-console
4636 console.log( message );
4638 else if( typeof opera == 'object' )
4640 opera.postError( message );
4642 // eslint-disable-next-line no-undef
4643 else if( typeof java == 'object' && typeof java.lang == 'object' )
4645 // eslint-disable-next-line no-undef
4646 java.lang.System.out.println( message );
4650 function getNSAttribute( sNSPrefix, aElem, sAttrName )
4652 if( !aElem ) return null;
4653 if( 'getAttributeNS' in aElem )
4655 return aElem.getAttributeNS( NSS[sNSPrefix], sAttrName );
4659 return aElem.getAttribute( sNSPrefix + ':' + sAttrName );
4663 function getOOOAttribute( aElem, sAttrName )
4665 return getNSAttribute( 'ooo', aElem, sAttrName );
4668 function setNSAttribute( sNSPrefix, aElem, sAttrName, aValue )
4670 if( !aElem ) return false;
4671 if( 'setAttributeNS' in aElem )
4673 aElem.setAttributeNS( NSS[sNSPrefix], sAttrName, aValue );
4678 aElem.setAttribute(sNSPrefix + ':' + sAttrName, aValue );
4683 function getElementsByClassName( aElem, sClassName )
4686 var aElementSet = [];
4687 // not all browsers support the 'getElementsByClassName' method
4688 if( 'getElementsByClassName' in aElem )
4690 aElementSet = aElem.getElementsByClassName( sClassName );
4694 var aElementSetByClassProperty = getElementsByProperty( aElem, 'class' );
4695 for( var i = 0; i < aElementSetByClassProperty.length; ++i )
4697 var sAttrClassName = aElementSetByClassProperty[i].getAttribute( 'class' );
4698 if( sAttrClassName == sClassName )
4700 aElementSet.push( aElementSetByClassProperty[i] );
4707 function getElementByClassName( aElem, sClassName /*, sTagName */)
4709 var aElementSet = getElementsByClassName( aElem, sClassName );
4710 if ( aElementSet.length == 1 )
4711 return aElementSet[0];
4716 function getClassAttribute( aElem )
4719 return aElem.getAttribute( 'class' );
4723 function createElementGroup( aParentElement, aElementList, nFrom, nCount, sGroupClass, sGroupId )
4725 var nTo = nFrom + nCount;
4726 if( nCount < 1 || aElementList.length < nTo )
4728 log( 'createElementGroup: not enough elements available.' );
4731 var firstElement = aElementList[nFrom];
4734 log( 'createElementGroup: element not found.' );
4737 var aGroupElement = document.createElementNS( NSS['svg'], 'g' );
4739 aGroupElement.setAttribute( 'id', sGroupId );
4741 aGroupElement.setAttribute( 'class', sGroupClass );
4742 aParentElement.insertBefore( aGroupElement, firstElement );
4744 for( ; i < nTo; ++i )
4746 aParentElement.removeChild( aElementList[i] );
4747 aGroupElement.appendChild( aElementList[i] );
4751 function initVisibilityProperty( aElement )
4753 var nVisibility = VISIBLE;
4754 var sVisibility = aElement.getAttribute( 'visibility' );
4755 if( sVisibility ) nVisibility = aVisibilityValue[ sVisibility ];
4759 function getSafeIndex( nIndex, nMin, nMax )
4763 else if( nIndex > nMax )
4769 function getUrlParameter(name)
4771 name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
4772 var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
4773 var results = regex.exec(window.location.search);
4774 return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
4781 * an integer in [0,nMax[
4783 function getRandomInt( nMax )
4785 return Math.floor( Math.random() * nMax );
4788 function isTextFieldElement( aElement ) // eslint-disable-line no-unused-vars
4790 var sClassName = aElement.getAttribute( 'class' );
4791 return ( sClassName === aSlideNumberClassName ) ||
4792 ( sClassName === aFooterClassName ) ||
4793 ( sClassName === aHeaderClassName ) ||
4794 ( sClassName === aDateTimeClassName );
4798 /*********************
4799 ** Debug Utilities **
4800 *********************/
4802 function DebugPrinter()
4804 this.bEnabled = false;
4808 DebugPrinter.prototype.on = function()
4810 this.bEnabled = true;
4813 DebugPrinter.prototype.off = function()
4815 this.bEnabled = false;
4818 DebugPrinter.prototype.isEnabled = function()
4820 return this.bEnabled;
4823 DebugPrinter.prototype.print = function( sMessage, nTime )
4825 if( this.isEnabled() )
4827 var sInfo = 'DBG: ' + sMessage;
4829 sInfo += ' (at: ' + String( nTime / 1000 ) + 's)';
4835 // - Debug Printers -
4836 var aGenericDebugPrinter = new DebugPrinter();
4837 aGenericDebugPrinter.on();
4838 var DBGLOG = bind2( DebugPrinter.prototype.print, aGenericDebugPrinter );
4840 var NAVDBG = new DebugPrinter();
4843 var ANIMDBG = new DebugPrinter();
4846 var aRegisterEventDebugPrinter = new DebugPrinter();
4847 aRegisterEventDebugPrinter.off();
4849 var aTimerEventQueueDebugPrinter = new DebugPrinter();
4850 aTimerEventQueueDebugPrinter.off();
4852 var aEventMultiplexerDebugPrinter = new DebugPrinter();
4853 aEventMultiplexerDebugPrinter.off();
4855 var aNextEffectEventArrayDebugPrinter = new DebugPrinter();
4856 aNextEffectEventArrayDebugPrinter.off();
4858 var aActivityQueueDebugPrinter = new DebugPrinter();
4859 aActivityQueueDebugPrinter.off();
4861 var aAnimatedElementDebugPrinter = new DebugPrinter();
4862 aAnimatedElementDebugPrinter.off();
4867 /************************
4868 *** Core Classes ***
4869 ************************/
4871 /** Class MetaDocument
4872 * This class provides a pool of properties related to the whole presentation.
4873 * Moreover it is responsible for:
4874 * - initializing the set of MetaSlide objects that handle the meta information
4876 * - creating a map with key an id and value the svg element containing
4877 * the animations performed on the slide with such an id.
4880 function MetaDocument()
4882 // We look for the svg element that provides the following presentation
4884 // - the number of slides in the presentation;
4885 // - the type of numbering used in the presentation.
4886 // Moreover it wraps svg elements providing meta information on each slide
4887 // and svg elements providing content and properties of each text field.
4888 var aMetaDocElem = document.getElementById( aOOOElemMetaSlides );
4889 assert( aMetaDocElem, 'MetaDocument: the svg element with id:' + aOOOElemMetaSlides + 'is not valid.');
4891 // We initialize general presentation properties:
4892 // - the number of slides in the presentation;
4893 this.nNumberOfSlides = parseInt( aMetaDocElem.getAttributeNS( NSS['ooo'], aOOOAttrNumberOfSlides ) );
4894 assert( typeof this.nNumberOfSlides == 'number' && this.nNumberOfSlides > 0,
4895 'MetaDocument: number of slides is zero or undefined.' );
4896 // - the index of the slide to show when the presentation starts;
4897 this.nStartSlideNumber = parseInt( aMetaDocElem.getAttributeNS( NSS['ooo'], aOOOAttrStartSlideNumber ) ) || 0;
4898 // - get the parameter StartSlideNumber in the URL for online presentation
4899 var aParmStartSlideNumber = getUrlParameter('StartSlideNumber');
4900 if (aParmStartSlideNumber !== '')
4902 this.nStartSlideNumber = parseInt(aParmStartSlideNumber);
4904 // - the numbering type used in the presentation, default type is arabic.
4905 this.sPageNumberingType = aMetaDocElem.getAttributeNS( NSS['ooo'], aOOOAttrNumberingType ) || 'arabic';
4906 // - the way text is exported
4907 this.bIsUsePositionedChars = ( aMetaDocElem.getAttributeNS( NSS['ooo'], aOOOAttrUsePositionedChars ) === 'true' );
4909 // The <defs> element used for wrapping <clipPath>.
4910 this.aClipPathGroup = getElementByClassName( ROOT_NODE, aClipPathGroupClassName );
4911 assert( this.aClipPathGroup, 'MetaDocument: the clip path group element is not valid.');
4913 // The <clipPath> element used to clip all slides.
4914 this.aPresentationClipPath = document.getElementById( aPresentationClipPathId );
4915 assert( this.aPresentationClipPath,
4916 'MetaDocument: the presentation clip path element element is not valid.');
4918 // The collections for handling properties of each slide, svg elements
4919 // related to master pages and content and properties of text fields.
4920 this.aMetaSlideSet = [];
4921 this.aMasterPageSet = {};
4922 this.aTextFieldHandlerSet = {};
4923 this.aTextFieldContentProviderSet = [];
4924 this.aSlideNumberProvider = new SlideNumberProvider( this.nStartSlideNumber + 1, this.sPageNumberingType );
4925 this.aCurrentDateProvider = new CurrentDateTimeProvider( null, '<date>' );
4926 this.aCurrentTimeProvider = new CurrentDateTimeProvider( null, '<time>' );
4928 // We create a map with key an id and value the svg element containing
4929 // the animations performed on the slide with such an id.
4930 this.bIsAnimated = false;
4931 this.aSlideAnimationsMap = {};
4932 this.initSlideAnimationsMap();
4934 // We initialize dummy slide - used as leaving slide for transition on the first slide
4935 this.theMetaDummySlide = new MetaSlide( 'ooo:meta_dummy_slide', this );
4937 // We initialize the set of MetaSlide objects that handle the meta
4938 // information for each slide.
4939 for( var i = 0; i < this.nNumberOfSlides; ++i )
4941 var sMetaSlideId = aOOOElemMetaSlide + '_' + i;
4942 this.aMetaSlideSet.push( new MetaSlide( sMetaSlideId, this ) );
4944 assert( this.aMetaSlideSet.length == this.nNumberOfSlides,
4945 'MetaDocument: aMetaSlideSet.length != nNumberOfSlides.' );
4948 MetaDocument.prototype =
4950 /*** public methods ***/
4955 * The MetaSlide object handling the current slide.
4957 getCurrentSlide : function()
4959 return this.aMetaSlideSet[nCurSlide];
4964 * @param nSlideIndex
4965 * The index of the slide to show.
4967 setCurrentSlide : function( nSlideIndex )
4969 if( nSlideIndex >= 0 && nSlideIndex < this.nNumberOfSlides )
4971 if( nCurSlide !== undefined )
4972 this.aMetaSlideSet[nCurSlide].hide();
4973 this.aMetaSlideSet[nSlideIndex].show();
4974 nCurSlide = nSlideIndex;
4978 log('MetaDocument.setCurrentSlide: slide index out of range: ' + nSlideIndex );
4982 /*** private methods ***/
4984 initSlideAnimationsMap : function()
4986 var aAnimationsSection = document.getElementById( 'presentation-animations' );
4987 if( aAnimationsSection )
4989 var aAnimationsDefSet = aAnimationsSection.getElementsByTagName( 'defs' );
4991 // we have at least one slide with animations ?
4992 this.bIsAnimated = ( typeof aAnimationsDefSet.length =='number' &&
4993 aAnimationsDefSet.length > 0 );
4995 for( var i = 0; i < aAnimationsDefSet.length; ++i )
4997 var sSlideId = aAnimationsDefSet[i].getAttributeNS( NSS['ooo'], aOOOAttrSlide );
4998 var aChildSet = getElementChildren( aAnimationsDefSet[i] );
4999 if( sSlideId && ( aChildSet.length === 1 ) )
5001 this.aSlideAnimationsMap[ sSlideId ] = aChildSet[0];
5007 }; // end MetaDocument prototype
5010 * This class is responsible for:
5011 * - parsing and initializing slide properties;
5012 * - creating a MasterSlide object that provides direct access to the target
5013 * master slide and its sub-elements;
5014 * - initializing text field content providers;
5015 * - initializing the slide animation handler.
5017 * @param sMetaSlideId
5018 * The string representing the id attribute of the meta-slide element.
5020 * The MetaDocument global object.
5022 function MetaSlide( sMetaSlideId, aMetaDoc )
5024 this.theDocument = document;
5025 this.id = sMetaSlideId;
5026 this.theMetaDoc = aMetaDoc;
5028 // We get a reference to the meta-slide element.
5029 this.element = this.theDocument.getElementById( this.id );
5030 assert( this.element,
5031 'MetaSlide: meta_slide element <' + this.id + '> not found.' );
5033 // We get a reference to the slide element.
5034 this.slideId = this.element.getAttributeNS( NSS['ooo'], aOOOAttrSlide );
5035 this.slideElement = this.theDocument.getElementById( this.slideId );
5036 assert( this.slideElement,
5037 'MetaSlide: slide element <' + this.slideId + '> not found.' );
5039 if( this.slideId !== 'dummy_slide' )
5040 this.nSlideNumber = parseInt( this.slideId.substr(2) );
5042 this.nSlideNumber= -1;
5044 this.slideName = this.element.getAttributeNS( NSS['ooo'], aOOOAttrDisplayName );
5046 // Each slide element is double wrapped by <g> elements.
5047 // The outer <g> element is responsible for
5048 // the slide element visibility. In fact the visibility attribute has
5049 // to be set on the parent of the slide element and not directly on
5050 // the slide element. The reason is that in index mode each slide
5051 // rendered in a thumbnail view is targeted by a <use> element, however
5052 // when the visibility attribute is set directly on the referred slide
5053 // element its visibility is not overridden by the visibility attribute
5054 // defined by the targeting <use> element. The previous solution was,
5055 // when the user switched to index mode, to set up the visibility attribute
5056 // of all slides rendered in a thumbnail to 'visible'.
5057 // Obviously the slides were not really visible because the grid of
5058 // thumbnails was above them, anyway Firefox performance was really bad.
5059 // The workaround of setting up the visibility attribute on the slide
5060 // parent element let us to make visible a slide in a <use> element
5061 // even if the slide parent element visibility is set to 'hidden'.
5062 // The inner <g> element is used in order to append some element
5063 // before or after the slide, operation that can be needed for some
5064 // slide transition (e.g. fade through black). In this way we can
5065 // create a view of both the slide and the appended elements that turns out
5066 // to be useful for handling transition from the last to the first slide.
5067 this.aContainerElement = this.slideElement.parentNode;
5068 this.slideContainerId = this.aContainerElement.getAttribute( 'id' );
5069 this.aVisibilityStatusElement = this.aContainerElement.parentNode;
5071 // We get a reference to the draw page element, where all shapes specific
5072 // of this slide live.
5073 this.pageElement = getElementByClassName( this.slideElement, aPageClassName );
5074 assert( this.pageElement,
5075 'MetaSlide: page element <' + this.slideId + '> not found.' );
5077 // The slide custom background element and its id attribute.
5078 this.backgroundElement = getElementByClassName( this.pageElement, 'Background' );
5079 if( this.backgroundElement )
5081 this.backgroundId = this.backgroundElement.getAttribute( 'id' );
5084 // We initialize text fields
5085 this.initPlaceholderElements();
5087 // We initialize the MasterPage object that provides direct access to
5088 // the target master page element.
5089 this.masterPage = this.initMasterPage();
5091 // We check if the slide has a custom background which overrides the one of the targeted master page
5092 this.bHasCustomBackground = this.initHasCustomBackground();
5094 // We initialize visibility properties of the target master page elements.
5095 this.nAreMasterObjectsVisible = this.initVisibilityProperty( aOOOAttrMasterObjectsVisibility, VISIBLE );
5096 this.nIsBackgroundVisible = this.initVisibilityProperty( aOOOAttrBackgroundVisibility, VISIBLE );
5097 this.nIsPageNumberVisible = this.initVisibilityProperty( aOOOAttrPageNumberVisibility, HIDDEN );
5098 this.nIsDateTimeVisible = this.initVisibilityProperty( aOOOAttrDateTimeVisibility, VISIBLE );
5099 this.nIsFooterVisible = this.initVisibilityProperty( aOOOAttrFooterVisibility, VISIBLE );
5100 this.nIsHeaderVisible = this.initVisibilityProperty( aOOOAttrHeaderVisibility, VISIBLE );
5102 // This property tell us if the date/time field need to be updated
5103 // each time the slide is shown. It is initialized in
5104 // the initDateTimeFieldContentProvider method.
5105 this.bIsDateTimeVariable = undefined;
5107 // We initialize the objects responsible to provide the content to text field.
5108 this.aTextFieldContentProviderSet = {};
5109 this.aTextFieldContentProviderSet[aSlideNumberClassName] = this.initSlideNumberFieldContentProvider();
5110 this.aTextFieldContentProviderSet[aDateTimeClassName] = this.initDateTimeFieldContentProvider( aOOOAttrDateTimeField );
5111 this.aTextFieldContentProviderSet[aFooterClassName] = this.initFixedTextFieldContentProvider( aOOOAttrFooterField );
5112 this.aTextFieldContentProviderSet[aHeaderClassName] = this.initFixedTextFieldContentProvider( aOOOAttrHeaderField );
5113 this.aTextFieldContentProviderSet[aDateClassName] = this.theMetaDoc.aCurrentDateProvider;
5114 this.aTextFieldContentProviderSet[aTimeClassName] = this.theMetaDoc.aCurrentTimeProvider;
5115 this.aTextFieldContentProviderSet[aSlideNameClassName] = new FixedTextProvider( this.slideName );
5117 // We init the slide duration when automatic slide transition is enabled
5118 this.fDuration = this.initSlideDuration();
5120 // We look for slide transition.
5121 this.aTransitionHandler = null;
5122 this.bHasTransition = this.initHasTransition() || true;
5123 if( this.bHasTransition )
5125 this.aTransitionHandler = new SlideTransition( this.getSlideAnimationsRoot(), this.slideId );
5128 // We initialize the SlideAnimationsHandler object
5129 this.aSlideAnimationsHandler = new SlideAnimations( aSlideShow.getContext() );
5130 this.aSlideAnimationsHandler.importAnimations( this.getSlideAnimationsRoot() );
5131 this.aSlideAnimationsHandler.parseElements();
5133 // this statement is used only for debugging
5134 // eslint-disable-next-line no-constant-condition
5135 if( false && this.aSlideAnimationsHandler.aRootNode )
5136 log( this.aSlideAnimationsHandler.aRootNode.info( true ) );
5138 // We collect text shapes included in this slide .
5139 this.aTextShapeSet = this.collectTextShapes();
5141 // We initialize hyperlinks
5142 this.aHyperlinkSet = this.initHyperlinks();
5146 MetaSlide.prototype =
5148 /*** public methods ***/
5151 * Set the visibility property of the slide to 'inherit'
5152 * and update the master page view.
5156 this.updateMasterPageView();
5157 this.aVisibilityStatusElement.setAttribute( 'visibility', 'inherit' );
5161 * Set the visibility property of the slide to 'hidden'.
5165 this.aVisibilityStatusElement.setAttribute( 'visibility', 'hidden' );
5168 /** updateMasterPageView
5169 * On first call it creates a master page view element and insert it at
5170 * the begin of the slide element. Moreover it updates the text fields
5171 * included in the master page view.
5173 updateMasterPageView : function()
5175 // The master page view element is generated and attached on first time
5176 // the slide is shown.
5177 if( !this.aMasterPageView )
5179 this.aMasterPageView = new MasterPageView( this );
5180 this.aMasterPageView.attachToSlide();
5182 this.aMasterPageView.update();
5185 /*** private methods ***/
5187 // It handles a text field inserted on a slide, not on a master page.
5188 initPlaceholderElements : function()
5190 var aPlaceholderList = getElementsByClassName(this.pageElement , 'PlaceholderText' );
5192 for( ; i < aPlaceholderList.length; ++i )
5194 var aPlaceholderElem = aPlaceholderList[i];
5195 var sContent = aPlaceholderElem.textContent;
5196 if( sContent === '<date>' )
5197 aPlaceholderElem.textContent = new Date().toLocaleDateString();
5198 else if( sContent === '<time>' )
5199 aPlaceholderElem.textContent = new Date().toLocaleTimeString();
5203 initMasterPage : function()
5205 var sMasterPageId = this.element.getAttributeNS( NSS['ooo'], aOOOAttrMaster );
5207 // Check that the master page handler object has not already been
5208 // created by another slide that target the same master page.
5209 if( !this.theMetaDoc.aMasterPageSet.hasOwnProperty( sMasterPageId ) )
5211 this.theMetaDoc.aMasterPageSet[ sMasterPageId ] = new MasterPage( sMasterPageId, this );
5213 // We initialize aTextFieldHandlerSet[ sMasterPageId ] to an empty
5215 this.theMetaDoc.aTextFieldHandlerSet[ sMasterPageId ] = {};
5217 return this.theMetaDoc.aMasterPageSet[ sMasterPageId ];
5220 initSlideDuration : function()
5222 var sSlideDuration = this.element.getAttributeNS( NSS['ooo'], aOOOAttrSlideDuration );
5223 if( sSlideDuration && sSlideDuration.length > 0 )
5224 return parseFloat( sSlideDuration );
5229 initHasTransition : function()
5231 var sHasTransition = this.element.getAttributeNS( NSS['ooo'], aOOOAttrHasTransition );
5232 return ( sHasTransition === 'true' );
5235 initHasCustomBackground : function()
5237 var sHasCustomBackground = this.element.getAttributeNS( NSS['ooo'], aOOOAttrHasCustomBackground );
5238 return ( sHasCustomBackground === 'true' );
5241 initVisibilityProperty : function( aVisibilityAttribute, nDefaultValue )
5243 var nVisibility = nDefaultValue;
5244 var sVisibility = getOOOAttribute( this.element, aVisibilityAttribute );
5246 nVisibility = aVisibilityValue[ sVisibility ];
5250 initSlideNumberFieldContentProvider : function()
5252 return this.theMetaDoc.aSlideNumberProvider;
5255 initDateTimeFieldContentProvider : function( aOOOAttrDateTimeField )
5257 var sTextFieldId = getOOOAttribute( this.element, aOOOAttrDateTimeField );
5258 if( !sTextFieldId ) return null;
5260 var nLength = aOOOElemTextField.length + 1;
5261 var nIndex = parseInt(sTextFieldId.substring( nLength ) );
5262 if( typeof nIndex != 'number') return null;
5264 if( !this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ] )
5267 var aTextFieldElem = document.getElementById( sTextFieldId );
5268 var sClassName = getClassAttribute( aTextFieldElem );
5269 if( sClassName == 'FixedDateTimeField' )
5271 aTextField = new FixedTextByElementProvider( aTextFieldElem );
5272 this.bIsDateTimeVariable = false;
5274 else if( sClassName == 'VariableDateTimeField' )
5276 aTextField = new CurrentDateTimeProvider( aTextFieldElem );
5277 this.bIsDateTimeVariable = true;
5283 this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ] = aTextField;
5285 return this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ];
5288 initFixedTextFieldContentProvider : function( aOOOAttribute )
5290 var sTextFieldId = getOOOAttribute( this.element, aOOOAttribute );
5291 if( !sTextFieldId ) return null;
5293 var nLength = aOOOElemTextField.length + 1;
5294 var nIndex = parseInt( sTextFieldId.substring( nLength ) );
5295 if( typeof nIndex != 'number') return null;
5297 if( !this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ] )
5299 var aTextFieldElem = document.getElementById( sTextFieldId );
5300 this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ]
5301 = new FixedTextByElementProvider( aTextFieldElem );
5303 return this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ];
5306 collectTextShapes : function()
5308 var aTextShapeSet = [];
5309 var aTextShapeIndexElem = getElementByClassName( document, 'TextShapeIndex' );
5310 if( aTextShapeIndexElem )
5312 var aIndexEntryList = getElementChildren( aTextShapeIndexElem );
5314 for( i = 0; i < aIndexEntryList.length; ++i )
5316 var sSlideId = getOOOAttribute( aIndexEntryList[i], 'slide' );
5317 if( sSlideId === this.slideId )
5319 var sTextShapeIds = getOOOAttribute( aIndexEntryList[i], 'id-list' );
5322 var aTextShapeIdSet = sTextShapeIds.split( ' ' );
5324 for( j = 0; j < aTextShapeIdSet.length; ++j )
5326 var aTextShapeElem = document.getElementById( aTextShapeIdSet[j] );
5327 if( aTextShapeElem )
5329 aTextShapeSet.push( aTextShapeElem );
5333 log( 'warning: MetaSlide.collectTextShapes: text shape with id <' + aTextShapeIdSet[j] + '> is not valid.' );
5341 return aTextShapeSet;
5344 initHyperlinks : function()
5346 var aHyperlinkSet = {};
5348 for( i = 0; i < this.aTextShapeSet.length; ++i )
5350 if( this.aTextShapeSet[i] )
5352 var aHyperlinkIdList = getElementByClassName( this.aTextShapeSet[i], 'HyperlinkIdList' );
5353 if( aHyperlinkIdList )
5355 var sHyperlinkIds = aHyperlinkIdList.textContent;
5358 var aHyperlinkIdSet = sHyperlinkIds.trim().split( ' ' );
5360 for( j = 0; j < aHyperlinkIdSet.length; ++j )
5362 var sId = aHyperlinkIdSet[j];
5363 aHyperlinkSet[ sId ] = new HyperlinkElement( sId, this.aSlideAnimationsHandler.aEventMultiplexer );
5369 return aHyperlinkSet;
5372 getSlideAnimationsRoot : function()
5374 return this.theMetaDoc.aSlideAnimationsMap[ this.slideId ];
5377 }; // end MetaSlide prototype
5379 function getTextFieldType ( elem )
5381 var sFieldType = null;
5382 var sClass = elem.getAttribute('class');
5383 if( sClass == 'TextShape' )
5385 var aPlaceholderElement = getElementByClassName( elem, 'PlaceholderText' );
5386 if (aPlaceholderElement)
5388 var sContent = aPlaceholderElement.textContent
5389 if (sContent === '<number>')
5390 sFieldType = aSlideNumberClassName;
5391 else if (sContent === '<date>')
5392 sFieldType = aDateClassName;
5393 else if (sContent === '<time>')
5394 sFieldType = aTimeClassName;
5395 else if (sContent === '<slide-name>')
5396 sFieldType = aSlideNameClassName;
5402 function isTextFieldByClassName ( sClassName )
5404 return sClassName === aDateTimeClassName || sClassName === aFooterClassName
5405 || sClassName === aHeaderClassName || sClassName.indexOf( aSlideNumberClassName ) == 0
5406 || sClassName.indexOf( aDateClassName ) == 0 || sClassName.indexOf( aTimeClassName ) == 0
5407 || sClassName.indexOf( aSlideNameClassName ) == 0;
5410 /** Class MasterPage
5411 * This class gives direct access to a master page element and to the following
5412 * elements included in the master page:
5413 * - the background element,
5414 * - the background objects group element,
5415 * Moreover for each text field element a Placeholder object is created which
5416 * manages the text field element itself.
5418 * The master page element structure is the following:
5419 * <g class='Master_Slide'>
5420 * <g class='Background'>
5423 * <g class='BackgroundObjects'>
5424 * <g class='Date/Time'>
5425 * date/time placeholder
5427 * <g class='Header'>
5428 * header placeholder
5430 * <g class='Footer'>
5431 * footer placeholder
5433 * <g class='Slide_Number'>
5434 * slide number placeholder
5440 * @param sMasterPageId
5441 * A string representing the value of the id attribute of the master page
5442 * element to be handled.
5444 * A meta slide having as master page the one with the passed id.
5446 function MasterPage( sMasterPageId, aMetaSlide )
5448 this.id = sMasterPageId;
5449 this.metaSlide = aMetaSlide;
5451 // The master page element to be handled.
5452 this.element = document.getElementById( this.id );
5453 assert( this.element,
5454 'MasterPage: master page element <' + this.id + '> not found.' );
5456 // The master page background element and its id attribute.
5457 this.background = getElementByClassName( this.element, 'Background' );
5458 if( this.background )
5460 this.backgroundId = this.background.getAttribute( 'id' );
5461 this.backgroundVisibility = initVisibilityProperty( this.background );
5465 this.backgroundId = '';
5466 log( 'MasterPage: the background element is not valid.' );
5469 // The background objects group element that contains every element presents
5470 // on the master page except the background element.
5471 this.backgroundObjects = getElementByClassName( this.element, 'BackgroundObjects' );
5472 this.aBackgroundObjectSubGroupIdList = [];
5473 if( this.backgroundObjects )
5475 this.backgroundObjectsId = this.backgroundObjects.getAttribute( 'id' );
5476 this.backgroundObjectsVisibility = initVisibilityProperty( this.backgroundObjects );
5478 if( this.backgroundObjectsVisibility != HIDDEN )
5480 var aBackgroundObjectList = getElementChildren( this.backgroundObjects );
5483 var nSubGroupId = 1;
5487 for( ; i < aBackgroundObjectList.length; ++i )
5489 var aObject = aBackgroundObjectList[i];
5491 var sFieldType = getTextFieldType( aObject );
5492 if( sFieldType && aObject.firstElementChild )
5494 var sObjId = aObject.firstElementChild.getAttribute( 'id' );
5497 sClass = sFieldType + '.' + sObjId;
5498 aObject.setAttribute('class', sClass);
5503 sClass = aBackgroundObjectList[i].getAttribute('class');
5505 if( !sClass || !isTextFieldByClassName( sClass ) )
5510 sId = this.backgroundObjectsId + '.' + nSubGroupId;
5512 this.aBackgroundObjectSubGroupIdList.push( sId );
5518 this.aBackgroundObjectSubGroupIdList.push( sClass );
5521 createElementGroup( this.backgroundObjects, aBackgroundObjectList, nFrom, nCount, 'BackgroundObjectSubgroup', sId );
5528 createElementGroup( this.backgroundObjects, aBackgroundObjectList, nFrom, nCount, 'BackgroundObjectSubgroup', sId );
5534 this.backgroundObjectsId = '';
5535 log( 'MasterPage: the background objects element is not valid.' );
5538 // We populate the collection of placeholders.
5539 this.aPlaceholderShapeSet = {};
5540 this.initPlaceholderShapes();
5543 MasterPage.prototype =
5545 /*** private methods ***/
5547 initPlaceholderShapes : function()
5551 for( ; i < this.aBackgroundObjectSubGroupIdList.length; ++i )
5553 sClassName = this.aBackgroundObjectSubGroupIdList[i];
5554 if( isTextFieldByClassName( sClassName ) )
5555 this.aPlaceholderShapeSet[ sClassName ] = new PlaceholderShape( this, sClassName );
5559 }; // end MasterPage prototype
5561 /** Class PlaceholderShape
5562 * This class provides direct access to a text field element and
5563 * to the embedded placeholder element.
5564 * Moreover it set up the text adjustment and position for the placeholder
5566 * Note: the text field element included in a master page is used only as
5567 * a template element, it is cloned for each specific text content
5568 * (see the TextFieldContentProvider class and its derived classes).
5570 * @param aMasterPage
5571 * The master page object to which the text field to be handled belongs.
5573 * A string representing the value of the class attribute of the text
5574 * field element to be handled.
5576 function PlaceholderShape( aMasterPage, sClassName )
5578 this.masterPage = aMasterPage;
5579 this.className = sClassName;
5581 this.element = null;
5582 this.textElement = null;
5586 /* public methods */
5587 PlaceholderShape.prototype.isValid = function()
5589 return ( this.element && this.textElement );
5592 /* private methods */
5595 * In case a text field element of class type 'className' exists and such
5596 * an element embeds a placeholder element, the text adjustment and position
5597 * of the placeholder element is set up.
5599 PlaceholderShape.prototype.init = function()
5601 var aTextFieldElement = getElementByClassName( this.masterPage.backgroundObjects, this.className );
5602 if( aTextFieldElement )
5604 var aTextElem = getElementByClassName( aTextFieldElement, 'SVGTextShape' );
5607 var aPlaceholderElement = getElementByClassName(aTextElem, 'PlaceholderText');
5608 if( aPlaceholderElement )
5610 // SVG 1.1 does not support text wrapping wrt a rectangle.
5611 // When a text shape contains a placeholder, setting up the position
5612 // of each text line doesn't work since the position is computed
5613 // before replacing the placeholder text.
5614 // Anyway each text shape has an invisible rectangle that can be
5615 // regarded as the text shape bounding box.
5616 // We exploit such a feature and the exported text adjust attribute
5617 // value in order to set up correctly the position and text
5618 // adjustment for the text shape content.
5619 // We assume that once the real value has been substituted to
5620 // the placeholder the resulting content is no more than a single line.
5621 // So we remove from <tspan> elements used for setting up the
5622 // position of text lines (class TextPosition) the 'x' and 'y' attribute.
5623 // In the general case we would need to implement a function
5624 // which is able to compute at which words the text shape content has
5626 var aSVGRectElem = getElementByClassName( aTextFieldElement, 'BoundingBox' );
5629 var aRect = new Rectangle( aSVGRectElem );
5630 var sTextAdjust = getOOOAttribute( aTextFieldElement, aOOOAttrTextAdjust );
5631 // the bbox of the text shape is indeed a bit larger, there is a bit of internal padding
5632 var nMargin = 250; // 1000th mm
5633 var sTextAnchor, sX;
5634 if( sTextAdjust == 'left' )
5636 sTextAnchor = 'start';
5637 sX = String( Math.trunc( aRect.left + nMargin ) );
5639 else if( sTextAdjust == 'right' )
5641 sTextAnchor = 'end';
5642 sX = String( Math.trunc( aRect.right - nMargin ) );
5644 else if( sTextAdjust == 'center' )
5646 sTextAnchor = 'middle';
5647 var nMiddle = ( aRect.left + aRect.right ) / 2;
5648 sX = String( parseInt( String( nMiddle ) ) );
5652 aTextElem.setAttribute( 'text-anchor', sTextAnchor );
5654 aTextElem.setAttribute( 'x', sX );
5656 var aTSpanElements = getElementsByClassName( aTextElem, 'TextPosition' );
5657 if( aTSpanElements )
5660 for( ; i < aTSpanElements.length; ++i )
5662 var aTSpanElem = aTSpanElements[i];
5663 aTSpanElem.removeAttribute( 'x' );
5665 aTSpanElem.removeAttribute( 'y' );
5671 // date/time fields were not exported correctly when positioned chars are used
5672 if( this.masterPage.metaSlide.theMetaDoc.bIsUsePositionedChars )
5674 // We remove all text lines but the first one used as placeholder.
5675 var aTextLineGroupElem = aPlaceholderElement.parentNode.parentNode;
5676 if( aTextLineGroupElem )
5678 // Just to be sure it is the element we are looking for.
5679 var sFontFamilyAttr = aTextLineGroupElem.getAttribute( 'font-family' );
5680 if( sFontFamilyAttr )
5682 var aChildSet = getElementChildren( aTextLineGroupElem );
5683 if( aChildSet.length > 1 )
5685 for( ; i < aChildSet.length; ++i )
5687 aTextLineGroupElem.removeChild( aChildSet[i] );
5692 this.textElement = aPlaceholderElement;
5695 this.element = aTextFieldElement;
5699 /** Class MasterPageView
5700 * This class is used to creates a svg element of class MasterPageView and its
5702 * It is also responsible for updating the content of the included text fields.
5704 * MasterPageView element structure:
5706 * <g class='MasterPageView'>
5707 * <use class='Background'> // reference to master page background element
5708 * <g class='BackgroundObjects'>
5709 * <use class='BackgroundObjectSubGroup'> // reference to the group of shapes on the master page that are below text fields
5710 * <g class='Slide_Number'> // a cloned element
5713 * <use class='Date/Time'> // reference to a clone
5714 * <use class='Footer'>
5715 * <use class='Header'>
5716 * <use class='BackgroundObjectSubGroup'> // reference to the group of shapes on the master page that are above text fields
5720 * Sub-elements are present only if they are visible.
5723 * The MetaSlide object managing the slide element that targets
5724 * the master page view element created by an instance of MasterPageView.
5726 function MasterPageView( aMetaSlide )
5728 this.aMetaSlide = aMetaSlide;
5729 this.aSlideElement = aMetaSlide.slideElement;
5730 this.aPageElement = aMetaSlide.pageElement;
5731 this.aMasterPage = aMetaSlide.masterPage;
5732 this.aMPVElement = this.createElement();
5733 this.bIsAttached = false;
5736 /*** public methods ***/
5739 * Prepend the master slide view element to the slide element.
5741 MasterPageView.prototype.attachToSlide = function()
5743 if( !this.bIsAttached )
5745 var aInsertedElement = this.aSlideElement.insertBefore( this.aMPVElement, this.aPageElement );
5746 assert( aInsertedElement === this.aMPVElement,
5747 'MasterPageView.attachToSlide: aInsertedElement != this.aMPVElement' );
5749 this.bIsAttached = true;
5754 * Remove the master slide view element from the slide element.
5756 MasterPageView.prototype.detachFromSlide = function()
5758 if( this.bIsAttached )
5760 this.aSlideElement.removeChild( this.aMPVElement );
5761 this.bIsAttached = false;
5766 * Update the content of text fields placed on the master page.
5768 MasterPageView.prototype.update = function()
5770 if( this.aDateTimeFieldHandler && this.aMetaSlide.bIsDateTimeVariable )
5771 this.aDateTimeFieldHandler.update();
5774 /*** private methods ***/
5776 MasterPageView.prototype.createElement = function()
5778 var theDocument = document;
5779 var aMasterPageViewElement = theDocument.createElementNS( NSS['svg'], 'g' );
5780 assert( aMasterPageViewElement,
5781 'MasterPageView.createElement: failed to create a master page view element.' );
5782 aMasterPageViewElement.setAttribute( 'class', 'MasterPageView' );
5784 // we place a white rect below any else element
5785 // that is also a workaround for some kind of slide transition
5786 // when the master page is empty
5787 var aWhiteRect = theDocument.createElementNS( NSS['svg'], 'rect' );
5788 var nWidthExt = WIDTH / 1000;
5789 var nHeightExt = HEIGHT / 1000;
5790 aWhiteRect.setAttribute( 'x', String( -nWidthExt / 2 ) );
5791 aWhiteRect.setAttribute( 'y', String( -nHeightExt / 2 ) );
5792 aWhiteRect.setAttribute( 'width', String( WIDTH + nWidthExt ) );
5793 aWhiteRect.setAttribute( 'height', String( HEIGHT + nHeightExt ) );
5794 aWhiteRect.setAttribute( 'fill', '#FFFFFF' );
5795 aMasterPageViewElement.appendChild( aWhiteRect );
5797 // init the Background element
5798 if( this.aMetaSlide.nIsBackgroundVisible )
5800 var nBackgroundId = this.aMetaSlide.bHasCustomBackground ? this.aMetaSlide.backgroundId : this.aMasterPage.backgroundId;
5801 this.aBackgroundElement = theDocument.createElementNS( NSS['svg'], 'use' );
5802 this.aBackgroundElement.setAttribute( 'class', 'Background' );
5803 setNSAttribute( 'xlink', this.aBackgroundElement,
5804 'href', '#' + nBackgroundId );
5807 aMasterPageViewElement.appendChild( this.aBackgroundElement );
5810 // init the BackgroundObjects element
5811 if( this.aMetaSlide.nAreMasterObjectsVisible )
5813 this.aBackgroundObjectsElement = theDocument.createElementNS( NSS['svg'], 'g' );
5814 this.aBackgroundObjectsElement.setAttribute( 'class', 'BackgroundObjects' );
5816 // clone and initialize text field elements
5817 var aBackgroundObjectSubGroupIdList = this.aMasterPage.aBackgroundObjectSubGroupIdList;
5818 this.aBackgroundSubGroupElementSet = [];
5819 var aPlaceholderShapeSet = this.aMasterPage.aPlaceholderShapeSet;
5820 var aTextFieldContentProviderSet = this.aMetaSlide.aTextFieldContentProviderSet;
5821 // where cloned elements are appended
5822 var aDefsElement = this.aMetaSlide.element.parentNode;
5823 var aTextFieldHandlerSet = this.aMetaSlide.theMetaDoc.aTextFieldHandlerSet;
5824 var sMasterSlideId = this.aMasterPage.id;
5828 for( ; i < aBackgroundObjectSubGroupIdList.length; ++i )
5830 sId = aBackgroundObjectSubGroupIdList[i];
5831 if( sId.indexOf( aSlideNumberClassName ) == 0 )
5833 // Slide Number Field
5834 // The cloned element is appended directly to the field group element
5835 // since there is no slide number field content shared between two slide
5836 // (because the slide number of two slide is always different).
5837 var nIsPageNumberVisible = sId === aSlideNumberClassName ? this.aMetaSlide.nIsPageNumberVisible : true;
5838 if( aPlaceholderShapeSet[sId] &&
5839 aPlaceholderShapeSet[sId].isValid() &&
5840 nIsPageNumberVisible &&
5841 aTextFieldContentProviderSet[aSlideNumberClassName] )
5843 var aSlideNumberFieldHandler =
5844 new SlideNumberFieldHandler( aPlaceholderShapeSet[sId],
5845 aTextFieldContentProviderSet[aSlideNumberClassName] );
5846 aSlideNumberFieldHandler.update( this.aMetaSlide.nSlideNumber );
5847 aSlideNumberFieldHandler.appendTo( this.aBackgroundObjectsElement );
5848 if ( sId === aSlideNumberClassName )
5849 this.aSlideNumberFieldHandler = aSlideNumberFieldHandler;
5852 else if( sId === aDateTimeClassName )
5855 if( this.aMetaSlide.nIsDateTimeVisible )
5857 this.aDateTimeFieldHandler =
5858 this.initTextFieldHandler( aDateTimeClassName, aPlaceholderShapeSet,
5859 aTextFieldContentProviderSet, aDefsElement,
5860 aTextFieldHandlerSet, sMasterSlideId );
5863 else if( sId === aFooterClassName )
5866 if( this.aMetaSlide.nIsFooterVisible )
5868 this.aFooterFieldHandler =
5869 this.initTextFieldHandler( aFooterClassName, aPlaceholderShapeSet,
5870 aTextFieldContentProviderSet, aDefsElement,
5871 aTextFieldHandlerSet, sMasterSlideId );
5874 else if( sId === aHeaderClassName )
5877 if( this.aMetaSlide.nIsHeaderVisible )
5879 this.aHeaderFieldHandler =
5880 this.initTextFieldHandler( aHeaderClassName, aPlaceholderShapeSet,
5881 aTextFieldContentProviderSet, aDefsElement,
5882 aTextFieldHandlerSet, sMasterSlideId );
5885 else if( sId.indexOf( aDateClassName ) == 0
5886 || sId.indexOf( aTimeClassName ) == 0
5887 || sId.indexOf( aSlideNameClassName ) == 0 )
5889 this.initTextFieldHandler( sId, aPlaceholderShapeSet,
5890 aTextFieldContentProviderSet, aDefsElement,
5891 aTextFieldHandlerSet, sMasterSlideId );
5895 // init BackgroundObjectSubGroup elements
5896 var aBackgroundSubGroupElement = theDocument.createElementNS( NSS['svg'], 'use' );
5897 aBackgroundSubGroupElement.setAttribute( 'class', 'BackgroundObjectSubGroup' );
5898 setNSAttribute( 'xlink', aBackgroundSubGroupElement,
5899 'href', '#' + sId );
5900 this.aBackgroundSubGroupElementSet.push( aBackgroundSubGroupElement );
5902 this.aBackgroundObjectsElement.appendChild( aBackgroundSubGroupElement );
5907 aMasterPageViewElement.appendChild( this.aBackgroundObjectsElement );
5910 return aMasterPageViewElement;
5913 MasterPageView.prototype.initTextFieldHandler =
5914 function( sId, aPlaceholderShapeSet, aTextFieldContentProviderSet,
5915 aDefsElement, aTextFieldHandlerSet, sMasterSlideId )
5918 var aTextFieldHandler = null;
5919 var sClassName = sId.split('.')[0];
5920 var aPlaceholderShape = aPlaceholderShapeSet[sId];
5921 var aTextFieldContentProvider = aTextFieldContentProviderSet[sClassName];
5922 if( aPlaceholderShape && aPlaceholderShape.isValid()
5923 && aTextFieldContentProvider )
5925 var sTextFieldContentProviderId = aTextFieldContentProvider.sId;
5926 // We create only one single TextFieldHandler object (and so one only
5927 // text field clone) per master slide and text content.
5928 if ( !aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ] )
5930 aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ] =
5931 new TextFieldHandler( aPlaceholderShape,
5932 aTextFieldContentProvider );
5933 aTextFieldHandler = aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ];
5934 aTextFieldHandler.update();
5935 aTextFieldHandler.appendTo( aDefsElement );
5939 aTextFieldHandler = aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ];
5941 sRefId = aTextFieldHandler.sId;
5943 else if( aPlaceholderShape && aPlaceholderShape.element && aPlaceholderShape.element.firstElementChild
5944 && !aPlaceholderShape.textElement && !aTextFieldContentProvider )
5946 sRefId = aPlaceholderShape.element.firstElementChild.getAttribute('id');
5951 // We create a <use> element referring to the cloned text field and
5952 // append it to the field group element.
5953 var aTextFieldElement = document.createElementNS(NSS['svg'], 'use');
5954 aTextFieldElement.setAttribute('class', sClassName);
5955 setNSAttribute('xlink', aTextFieldElement,
5956 'href', '#' + sRefId);
5958 this.aBackgroundObjectsElement.appendChild( aTextFieldElement );
5960 return aTextFieldHandler;
5963 /** Class TextFieldHandler
5964 * This class clone a text field field of a master page and set up
5965 * the content of the cloned element on demand.
5967 * @param aPlaceholderShape
5968 * A PlaceholderShape object that provides the text field to be cloned.
5969 * @param aTextContentProvider
5970 * A TextContentProvider object to which the actual content updating is
5973 function TextFieldHandler( aPlaceholderShape, aTextContentProvider )
5975 this.aPlaceHolderShape = aPlaceholderShape;
5976 this.aTextContentProvider = aTextContentProvider;
5977 assert( this.aTextContentProvider,
5978 'TextFieldHandler: text content provider not defined.' );
5979 this.sId = 'tf' + String( TextFieldHandler.getUniqueId() );
5980 // The cloned text field element to be handled.
5981 this.aTextFieldElement = null;
5982 // The actual <text> element where the field content has to be placed.
5983 this.aTextPlaceholderElement = null;
5984 this.cloneElement();
5987 /*** private methods ***/
5989 TextFieldHandler.CURR_UNIQUE_ID = 0;
5991 TextFieldHandler.getUniqueId = function()
5993 ++TextFieldHandler.CURR_UNIQUE_ID;
5994 return TextFieldHandler.CURR_UNIQUE_ID;
5997 TextFieldHandler.prototype.cloneElement = function()
5999 assert( this.aPlaceHolderShape && this.aPlaceHolderShape.isValid(),
6000 'TextFieldHandler.cloneElement: placeholder shape is not valid.' );
6001 // The cloned text field element.
6002 this.aTextFieldElement = this.aPlaceHolderShape.element.cloneNode( true /* deep clone */ );
6003 assert( this.aTextFieldElement,
6004 'TextFieldHandler.cloneElement: aTextFieldElement is not defined' );
6005 this.aTextFieldElement.setAttribute( 'id', this.sId );
6006 // Text field placeholder visibility is always set to 'hidden'.
6007 this.aTextFieldElement.removeAttribute( 'visibility' );
6008 // The actual <text> element where the field content has to be placed.
6009 this.aTextPlaceholderElement = getElementByClassName( this.aTextFieldElement, 'PlaceholderText' );
6010 assert( this.aTextPlaceholderElement,
6011 'TextFieldHandler.cloneElement: aTextPlaceholderElement is not defined' );
6014 /*** public methods ***/
6017 * Append the cloned text field element to a svg element.
6019 * @param aParentNode
6020 * The svg element to which the cloned text field has to be appended.
6022 TextFieldHandler.prototype.appendTo = function( aParentNode )
6024 if( !this.aTextFieldElement )
6026 log( 'TextFieldHandler.appendTo: aTextFieldElement is not defined' );
6031 log( 'TextFieldHandler.appendTo: parent node is not defined' );
6035 aParentNode.appendChild( this.aTextFieldElement );
6039 * Modify the content of the cloned text field.
6042 * A string representing the new content of the cloned text field.
6044 TextFieldHandler.prototype.setTextContent = function( sText )
6046 if( !this.aTextPlaceholderElement )
6048 log( 'PlaceholderShape.setTextContent: text element is not valid in placeholder of type '
6049 + this.className + ' that belongs to master slide ' + this.masterPage.id );
6052 this.aTextPlaceholderElement.textContent = sText;
6056 * Update the content of the handled text field. The new content is provided
6057 * directly from the TextContentProvider data member.
6059 TextFieldHandler.prototype.update = function()
6061 if( !this.aTextContentProvider )
6062 log('TextFieldHandler.update: text content provider not defined.');
6064 this.aTextContentProvider.update( this );
6067 /** SlideNumberFieldHandler
6068 * This class clone the slide number field of a master page and set up
6069 * the content of the cloned element on demand.
6071 * @param aPlaceholderShape
6072 * A PlaceholderShape object that provides the slide number field
6074 * @param aTextContentProvider
6075 * A SlideNumberProvider object to which the actual content updating is
6078 function SlideNumberFieldHandler( aPlaceholderShape, aTextContentProvider )
6080 SlideNumberFieldHandler.superclass.constructor.call( this, aPlaceholderShape, aTextContentProvider );
6082 extend( SlideNumberFieldHandler, TextFieldHandler );
6084 /*** public methods ***/
6087 * Update the content of the handled slide number field with the passed number.
6089 * @param nPageNumber
6090 * The number representing the new content of the slide number field.
6092 SlideNumberFieldHandler.prototype.update = function( nPageNumber )
6094 // The actual content updating is demanded to the related
6095 // SlideNumberProvider instance that have the needed info on
6096 // the numbering type.
6097 if( !this.aTextContentProvider )
6098 log('TextFieldHandler.update: text content provider not defined.');
6100 this.aTextContentProvider.update( this, nPageNumber );
6104 /******************************************************************************
6105 * Text Field Content Provider Class Hierarchy
6107 * The following classes are responsible to format and set the text content
6110 ******************************************************************************/
6112 /** Class TextFieldContentProvider
6113 * This class is the root abstract class of the hierarchy.
6115 * @param aTextFieldContentElement
6116 * The svg element that contains the text content for one or more
6117 * master slide text field.
6119 function TextFieldContentProvider()
6121 this.sId = TextFieldContentProvider.getUniqueId();
6124 /*** private methods ***/
6126 TextFieldContentProvider.CURR_UNIQUE_ID = 0;
6128 TextFieldContentProvider.getUniqueId = function()
6130 ++TextFieldContentProvider.CURR_UNIQUE_ID;
6131 return TextFieldContentProvider.CURR_UNIQUE_ID;
6134 /** Class FixedTextProvider
6135 * This class handles text field with a fixed text.
6136 * The text content is provided by the 'text' property.
6139 * a string containing the text to be substituted.
6141 function FixedTextProvider( aText )
6143 FixedTextProvider.superclass.constructor.call( this );
6146 extend( FixedTextProvider, TextFieldContentProvider );
6148 /*** public methods ***/
6151 * Set up the content of a fixed text field.
6153 * @param aFixedTextField
6154 * An object that implement a setTextContent( String ) method in order
6155 * to set the content of a given text field.
6157 FixedTextProvider.prototype.update = function( aFixedTextField )
6159 aFixedTextField.setTextContent( this.text );
6162 /** Class FixedTextByElementProvider
6163 * This class handles text field with a fixed text.
6164 * The text content is provided by the 'text' property.
6166 * @param aTextFieldContentElement
6167 * The svg element that contains the text content for one or more
6168 * master slide text field.
6170 function FixedTextByElementProvider( aTextFieldContentElement )
6172 FixedTextByElementProvider.superclass.constructor.call( this, aTextFieldContentElement.textContent );
6174 extend( FixedTextByElementProvider, FixedTextProvider );
6176 /** Class CurrentDateTimeProvider
6177 * Provide the text content to a date/time field by generating the current
6178 * date/time in the format specified by the 'dateTimeFormat' property.
6180 * @param aTextFieldContentElement
6181 * The svg element that contains the date/time format for one or more
6182 * master slide date/time field.
6184 function CurrentDateTimeProvider( aTextFieldContentElement, sDateTimeFormat )
6186 CurrentDateTimeProvider.superclass.constructor.call( this, aTextFieldContentElement );
6187 if( aTextFieldContentElement )
6188 this.dateTimeFormat = getOOOAttribute( aTextFieldContentElement, aOOOAttrDateTimeFormat );
6191 this.dateTimeFormat = sDateTimeFormat;
6194 extend( CurrentDateTimeProvider, TextFieldContentProvider );
6196 /*** public methods ***/
6199 * Set up the content of a variable date/time field.
6201 * @param aDateTimeField
6202 * An object that implement a setTextContent( String ) method in order
6203 * to set the content of a given text field.
6205 CurrentDateTimeProvider.prototype.update = function( aDateTimeField )
6207 var sText = this.createDateTimeText();
6208 aDateTimeField.setTextContent( sText );
6211 /*** private methods ***/
6213 CurrentDateTimeProvider.prototype.createDateTimeText = function()
6215 // TODO handle date/time format
6217 if( this.dateTimeFormat === '<date>' )
6218 sDate = new Date().toLocaleDateString();
6219 else if( this.dateTimeFormat === '<time>' )
6220 sDate = new Date().toLocaleTimeString();
6222 sDate = new Date().toLocaleDateString();
6226 /** Class SlideNumberProvider
6227 * Provides the text content to the related text field by generating
6228 * the current page number in the given page numbering type.
6230 function SlideNumberProvider( nInitialSlideNumber, sPageNumberingType )
6232 SlideNumberProvider.superclass.constructor.call( this );
6233 this.nInitialSlideNumber = nInitialSlideNumber;
6234 this.pageNumberingType = sPageNumberingType;
6237 extend( SlideNumberProvider, TextFieldContentProvider );
6239 /*** public methods ***/
6241 /** getNumberingType
6244 * The page numbering type.
6246 SlideNumberProvider.prototype.getNumberingType = function()
6248 return this.pageNumberingType;
6252 * Set up the content of a slide number field.
6254 * @param aSlideNumberField
6255 * An object that implement a setTextContent( String ) method in order
6256 * to set the content of a given text field.
6257 * @param nSlideNumber
6258 * An integer representing the slide number.
6261 SlideNumberProvider.prototype.update = function( aSlideNumberField, nSlideNumber )
6263 if( nSlideNumber === undefined )
6265 if( nCurSlide === undefined )
6266 nSlideNumber = this.nInitialSlideNumber;
6268 nSlideNumber = nCurSlide + 1;
6270 var sText = this.createSlideNumberText( nSlideNumber, this.getNumberingType() );
6271 aSlideNumberField.setTextContent( sText );
6274 /*** private methods ***/
6276 SlideNumberProvider.prototype.createSlideNumberText = function( nSlideNumber /*, sNumberingType*/ )
6278 // TODO handle page numbering type
6279 return String( nSlideNumber );
6285 /********************************
6286 ** Slide Index Classes **
6287 ********************************/
6289 /** Class SlideIndexPage **
6290 * This class is responsible for handling the slide index page
6292 function SlideIndexPage()
6294 this.pageElementId = 'slide_index';
6295 this.pageBgColor = 'rgb(252,252,252)';
6296 this.pageElement = this.createPageElement();
6297 assert( this.pageElement, 'SlideIndexPage: pageElement is not valid' );
6298 this.indexColumns = INDEX_COLUMNS_DEFAULT;
6299 this.totalThumbnails = this.indexColumns * this.indexColumns;
6300 this.selectedSlideIndex = undefined;
6302 // set up layout parameters
6303 this.xSpacingFactor = 600/28000;
6304 this.ySpacingFactor = 450/21000;
6305 this.xSpacing = WIDTH * this.xSpacingFactor;
6306 this.ySpacing = HEIGHT * this.ySpacingFactor;
6307 this.halfBorderWidthFactor = ( 300/28000 ) * ( this.indexColumns / 3 );
6308 this.halfBorderWidth = WIDTH * this.halfBorderWidthFactor;
6309 this.borderWidth = 2 * this.halfBorderWidth;
6310 // the following formula is used to compute the slide shrinking factor:
6311 // scaleFactor = ( WIDTH - ( columns + 1 ) * xSpacing ) / ( columns * ( WIDTH + borderWidth ) )
6312 // indeed we can divide everything by WIDTH:
6313 this.scaleFactor = ( 1 - ( this.indexColumns + 1 ) * this.xSpacingFactor ) /
6314 ( this.indexColumns * ( 1 + 2 * this.halfBorderWidthFactor ) );
6316 // We create a Thumbnail Border and Thumbnail MouseArea rectangle template that will be
6317 // used by every Thumbnail. The Mouse Area rectangle is used in order to trigger the
6318 // mouseover event properly even when the slide background is hidden.
6319 this.thumbnailMouseAreaTemplateId = 'thumbnail_mouse_area';
6320 this.thumbnailMouseAreaTemplateElement = null;
6321 this.thumbnailBorderTemplateId = 'thumbnail_border';
6322 this.thumbnailBorderTemplateElement = null;
6323 this.createTemplateElements();
6325 // Now we create the grid of thumbnails
6326 this.aThumbnailSet = new Array( this.totalThumbnails );
6327 for( var i = 0; i < this.totalThumbnails; ++i )
6329 this.aThumbnailSet[i] = new Thumbnail( this, i );
6330 this.aThumbnailSet[i].updateView();
6333 this.curThumbnailIndex = 0;
6337 /* public methods */
6338 SlideIndexPage.prototype.getTotalThumbnails = function()
6340 return this.totalThumbnails;
6343 SlideIndexPage.prototype.show = function()
6345 this.pageElement.setAttribute( 'display', 'inherit' );
6348 SlideIndexPage.prototype.hide = function()
6350 this.pageElement.setAttribute( 'display', 'none' );
6355 * Change the selected thumbnail from the current one to the thumbnail with index nIndex.
6357 * @param nIndex - the thumbnail index
6359 SlideIndexPage.prototype.setSelection = function( nIndex )
6361 nIndex = getSafeIndex( nIndex, 0, this.getTotalThumbnails() - 1 );
6362 if( this.curThumbnailIndex != nIndex )
6364 this.aThumbnailSet[ this.curThumbnailIndex ].unselect();
6365 this.aThumbnailSet[ nIndex ].select();
6366 this.curThumbnailIndex = nIndex;
6368 this.selectedSlideIndex = this.aThumbnailSet[ nIndex ].slideIndex;
6371 SlideIndexPage.prototype.createPageElement = function()
6373 var aPageElement = document.createElementNS( NSS['svg'], 'g' );
6374 aPageElement.setAttribute( 'id', this.pageElementId );
6375 aPageElement.setAttribute( 'display', 'none' );
6376 aPageElement.setAttribute( 'visibility', 'visible' );
6378 // the slide index page background
6379 var sPageBgColor = this.pageBgColor + ';';
6380 var aRectElement = document.createElementNS( NSS['svg'], 'rect' );
6381 aRectElement.setAttribute( 'x', 0 );
6382 aRectElement.setAttribute( 'y', 0 );
6383 aRectElement.setAttribute( 'width', WIDTH );
6384 aRectElement.setAttribute( 'height', HEIGHT );
6385 aRectElement.setAttribute( 'style', 'stroke:none;fill:' + sPageBgColor );
6387 aPageElement.appendChild( aRectElement );
6388 // The index page is appended after all slide elements
6389 // so when it is displayed it covers them all
6390 ROOT_NODE.appendChild( aPageElement );
6391 return( document.getElementById( this.pageElementId ) );
6394 SlideIndexPage.prototype.createTemplateElements = function()
6396 // We define a Rect element as a template of thumbnail border for all slide-thumbnails.
6397 // The stroke color is defined individually by each thumbnail according to
6398 // its selection status.
6399 var aDefsElement = document.createElementNS( NSS['svg'], 'defs' );
6400 var aRectElement = document.createElementNS( NSS['svg'], 'rect' );
6401 aRectElement.setAttribute( 'id', this.thumbnailBorderTemplateId );
6402 aRectElement.setAttribute( 'x', -this.halfBorderWidth );
6403 aRectElement.setAttribute( 'y', -this.halfBorderWidth );
6404 aRectElement.setAttribute( 'rx', this.halfBorderWidth );
6405 aRectElement.setAttribute( 'ry', this.halfBorderWidth );
6406 aRectElement.setAttribute( 'width', WIDTH + this.halfBorderWidth );
6407 aRectElement.setAttribute( 'height', HEIGHT + this.halfBorderWidth );
6408 aRectElement.setAttribute( 'stroke-width', this.borderWidth );
6409 aRectElement.setAttribute( 'fill', 'none' );
6410 aDefsElement.appendChild( aRectElement );
6412 // We define a Rect element as a template of mouse area for triggering the mouseover event.
6413 // A copy is used by each thumbnail element.
6414 aRectElement = document.createElementNS( NSS['svg'], 'rect' );
6415 aRectElement.setAttribute( 'id', this.thumbnailMouseAreaTemplateId );
6416 aRectElement.setAttribute( 'x', 0 );
6417 aRectElement.setAttribute( 'y', 0 );
6418 aRectElement.setAttribute( 'width', WIDTH );
6419 aRectElement.setAttribute( 'height', HEIGHT );
6420 aRectElement.setAttribute( 'fill', this.pageBgColor );
6421 aDefsElement.appendChild( aRectElement );
6423 this.pageElement.appendChild( aDefsElement );
6425 this.thumbnailMouseAreaTemplateElement = document.getElementById( this.thumbnailMouseAreaTemplateId );
6426 this.thumbnailBorderTemplateElement = document.getElementById( this.thumbnailBorderTemplateId );
6429 SlideIndexPage.prototype.decreaseNumberOfColumns = function()
6431 this.setNumberOfColumns( this.indexColumns - 1 );
6434 SlideIndexPage.prototype.increaseNumberOfColumns = function()
6436 this.setNumberOfColumns( this.indexColumns + 1 );
6439 SlideIndexPage.prototype.resetNumberOfColumns = function()
6441 this.setNumberOfColumns( INDEX_COLUMNS_DEFAULT );
6444 /** setNumberOfColumns
6446 * Change the size of the thumbnail grid.
6448 * @param nNumberOfColumns - the new number of columns/rows of the thumbnail grid
6450 SlideIndexPage.prototype.setNumberOfColumns = function( nNumberOfColumns )
6452 if( this.indexColumns == nNumberOfColumns ) return;
6453 if( nNumberOfColumns < 2 || nNumberOfColumns > 6 ) return;
6455 var suspendHandle = ROOT_NODE.suspendRedraw(500);
6457 var nOldTotalThumbnails = this.totalThumbnails;
6458 this.indexColumns = nNumberOfColumns;
6459 this.totalThumbnails = nNumberOfColumns * nNumberOfColumns;
6461 this.aThumbnailSet[this.curThumbnailIndex].unselect();
6463 // if we decreased the number of used columns we remove the exceeding thumbnail elements
6465 for( i = this.totalThumbnails; i < nOldTotalThumbnails; ++i )
6467 this.aThumbnailSet[i].removeElement();
6470 // if we increased the number of used columns we create the needed thumbnail objects
6471 for( i = nOldTotalThumbnails; i < this.totalThumbnails; ++i )
6473 this.aThumbnailSet[i] = new Thumbnail( this, i );
6476 // we set up layout parameters that depend on the number of columns
6477 this.halfBorderWidthFactor = ( 300/28000 ) * ( this.indexColumns / 3 );
6478 this.halfBorderWidth = WIDTH * this.halfBorderWidthFactor;
6479 this.borderWidth = 2 * this.halfBorderWidth;
6480 // scaleFactor = ( WIDTH - ( columns + 1 ) * xSpacing ) / ( columns * ( WIDTH + borderWidth ) )
6481 this.scaleFactor = ( 1 - ( this.indexColumns + 1 ) * this.xSpacingFactor ) /
6482 ( this.indexColumns * ( 1 + 2 * this.halfBorderWidthFactor ) );
6484 // update the thumbnail border size
6485 var aRectElement = this.thumbnailBorderTemplateElement;
6486 aRectElement.setAttribute( 'x', -this.halfBorderWidth );
6487 aRectElement.setAttribute( 'y', -this.halfBorderWidth );
6488 aRectElement.setAttribute( 'rx', this.halfBorderWidth );
6489 aRectElement.setAttribute( 'ry', this.halfBorderWidth );
6490 aRectElement.setAttribute( 'width', WIDTH + this.halfBorderWidth );
6491 aRectElement.setAttribute( 'height', HEIGHT + this.halfBorderWidth );
6492 aRectElement.setAttribute( 'stroke-width', this.borderWidth );
6494 // now we update the displacement on the index page of each thumbnail (old and new)
6495 for( i = 0; i < this.totalThumbnails; ++i )
6497 this.aThumbnailSet[i].updateView();
6500 this.curThumbnailIndex = this.selectedSlideIndex % this.totalThumbnails;
6501 this.aThumbnailSet[this.curThumbnailIndex].select();
6503 // needed for forcing the indexSetPageSlide routine to update the INDEX_OFFSET
6505 indexSetPageSlide( this.selectedSlideIndex );
6507 ROOT_NODE.unsuspendRedraw( suspendHandle );
6508 ROOT_NODE.forceRedraw();
6512 /** Class Thumbnail **
6513 * This class handles a slide thumbnail.
6515 function Thumbnail( aSlideIndexPage, nIndex )
6517 this.container = aSlideIndexPage;
6518 this.index = nIndex;//= getSafeIndex( nIndex, 0, this.container.getTotalThumbnails() );
6519 this.pageElement = this.container.pageElement;
6520 this.thumbnailId = 'thumbnail' + this.index;
6521 this.thumbnailElement = this.createThumbnailElement();
6522 this.slideElement = getElementByClassName( this.thumbnailElement, 'Slide' );
6523 this.borderElement = getElementByClassName( this.thumbnailElement, 'Border' );
6524 this.mouseAreaElement = getElementByClassName( this.thumbnailElement, 'MouseArea' );
6525 this.aTransformSet = new Array( 3 );
6526 this.visibility = VISIBLE;
6527 this.isSelected = false;
6530 /* static const class member */
6531 Thumbnail.prototype.sNormalBorderColor = 'rgb(216,216,216)';
6532 Thumbnail.prototype.sSelectionBorderColor = 'rgb(92,92,255)';
6534 /* public methods */
6535 Thumbnail.prototype.removeElement = function()
6537 if( this.thumbnailElement )
6538 this.container.pageElement.removeChild( this.thumbnailElement );
6541 Thumbnail.prototype.show = function()
6543 if( this.visibility == HIDDEN )
6545 this.thumbnailElement.setAttribute( 'display', 'inherit' );
6546 this.visibility = VISIBLE;
6550 Thumbnail.prototype.hide = function()
6552 if( this.visibility == VISIBLE )
6554 this.thumbnailElement.setAttribute( 'display', 'none' );
6555 this.visibility = HIDDEN;
6559 Thumbnail.prototype.select = function()
6561 if( !this.isSelected )
6563 this.borderElement.setAttribute( 'stroke', this.sSelectionBorderColor );
6564 this.isSelected = true;
6568 Thumbnail.prototype.unselect = function()
6570 if( this.isSelected )
6572 this.borderElement.setAttribute( 'stroke', this.sNormalBorderColor );
6573 this.isSelected = false;
6579 * This method updates the displacement of the thumbnail on the slide index page,
6580 * the value of the row, column coordinates of the thumbnail in the grid, and
6581 * the onmouseover property of the thumbnail element.
6584 Thumbnail.prototype.updateView = function()
6586 this.column = this.index % this.container.indexColumns;
6587 this.row = ( this.index - this.column ) / this.container.indexColumns;
6588 this.halfBorderWidth = this.container.halfBorderWidth;
6589 this.borderWidth = this.container.borderWidth;
6590 this.width = ( WIDTH + this.borderWidth ) * this.container.scaleFactor;
6591 this.height = ( HEIGHT + this.borderWidth ) * this.container.scaleFactor;
6592 this.aTransformSet[2] = 'translate(' + this.halfBorderWidth + ' ' + this.halfBorderWidth + ')';
6593 this.aTransformSet[1] = 'scale(' + this.container.scaleFactor + ')';
6594 var sTransformAttrValue = this.computeTransform();
6595 this.thumbnailElement.setAttribute( 'transform', sTransformAttrValue );
6596 this.mouseAreaElement.setAttribute( 'onmouseover', 'theSlideIndexPage.aThumbnailSet[' + this.index + '].onMouseOver()' );
6601 * This method update the content of the thumbnail view
6603 * @param nIndex - the index of the slide to be shown in the thumbnail
6605 Thumbnail.prototype.update = function( nIndex )
6607 if( this.slideIndex == nIndex ) return;
6609 var aMetaSlide = theMetaDoc.aMetaSlideSet[nIndex];
6610 aMetaSlide.updateMasterPageView();
6611 setNSAttribute( 'xlink', this.slideElement, 'href', '#' + aMetaSlide.slideId );
6612 this.slideIndex = nIndex;
6615 Thumbnail.prototype.clear = function( )
6617 setNSAttribute( 'xlink', this.slideElement, 'href', '' );
6620 /* private methods */
6621 Thumbnail.prototype.createThumbnailElement = function()
6623 var aThumbnailElement = document.createElementNS( NSS['svg'], 'g' );
6624 aThumbnailElement.setAttribute( 'id', this.thumbnailId );
6625 aThumbnailElement.setAttribute( 'display', 'inherit' );
6627 var aSlideElement = document.createElementNS( NSS['svg'], 'use' );
6628 setNSAttribute( 'xlink', aSlideElement, 'href', '' );
6629 aSlideElement.setAttribute( 'class', 'Slide' );
6630 aThumbnailElement.appendChild( aSlideElement );
6632 var aMouseAreaElement = document.createElementNS( NSS['svg'], 'use' );
6633 setNSAttribute( 'xlink', aMouseAreaElement, 'href', '#' + this.container.thumbnailMouseAreaTemplateId );
6634 aMouseAreaElement.setAttribute( 'class', 'MouseArea' );
6635 aMouseAreaElement.setAttribute( 'opacity', 0.0 );
6636 aThumbnailElement.appendChild( aMouseAreaElement );
6638 var aBorderElement = document.createElementNS( NSS['svg'], 'use' );
6639 setNSAttribute( 'xlink', aBorderElement, 'href', '#' + this.container.thumbnailBorderTemplateId );
6640 aBorderElement.setAttribute( 'stroke', this.sNormalBorderColor );
6641 aBorderElement.setAttribute( 'class', 'Border' );
6642 aThumbnailElement.appendChild( aBorderElement );
6644 this.container.pageElement.appendChild( aThumbnailElement );
6645 return( document.getElementById( this.thumbnailId ) );
6648 Thumbnail.prototype.computeTransform = function()
6650 var nXSpacing = this.container.xSpacing;
6651 var nYSpacing = this.container.ySpacing;
6653 var nXOffset = nXSpacing + ( this.width + nXSpacing ) * this.column;
6654 var nYOffset = nYSpacing + ( this.height + nYSpacing ) * this.row;
6656 this.aTransformSet[0] = 'translate(' + nXOffset + ' ' + nYOffset + ')';
6658 var sTransform = this.aTransformSet.join( ' ' );
6663 Thumbnail.prototype.onMouseOver = function()
6665 if( ( currentMode == INDEX_MODE ) && ( this.container.curThumbnailIndex != this.index ) )
6667 this.container.setSelection( this.index );
6675 /** Initialization function.
6676 * The whole presentation is set-up in this function.
6680 var VIEWBOX = ROOT_NODE.getAttribute('viewBox');
6684 WIDTH = ROOT_NODE.viewBox.animVal.width;
6685 HEIGHT = ROOT_NODE.viewBox.animVal.height;
6688 aSlideShow = new SlideShow();
6689 theMetaDoc = new MetaDocument();
6690 aSlideShow.bIsEnabled = theMetaDoc.bIsAnimated;
6691 theSlideIndexPage = new SlideIndexPage();
6692 aSlideShow.displaySlide( theMetaDoc.nStartSlideNumber, false );
6694 // Allow slide switching with swipe gestures left
6695 // and right. Swiping up or down will exit the slideshow.
6696 var hammer = new Hammer(ROOT_NODE);
6697 hammer.on('swipeleft', function() {
6698 switchSlide(1, false);
6700 hammer.on('swiperight', function() {
6701 switchSlide(-1, false);
6703 hammer.get('swipe').set({ direction: Hammer.DIRECTION_ALL });
6704 hammer.on('swipeup', function() {
6705 aSlideShow.exitSlideShowInApp();
6707 hammer.on('swipedown', function() {
6708 aSlideShow.exitSlideShowInApp();
6712 function presentationEngineStop(message)
6714 alert( message + '\nThe presentation engine will be stopped' );
6715 document.onkeydown = null;
6716 document.onkeypress = null;
6717 document.onclick = null;
6718 window.onmousewheel = null;
6721 function assert( condition, message )
6725 presentationEngineStop( message );
6726 if (typeof console == 'object')
6727 // eslint-disable-next-line no-console
6729 throw new Error( message );
6733 function dispatchEffects(dir)
6735 // TODO to be implemented
6739 var bRet = aSlideShow.nextEffect();
6743 switchSlide( 1, false );
6748 switchSlide( dir, false );
6752 function skipAllEffects()
6754 var bRet = aSlideShow.skipAllEffects();
6757 switchSlide( 1, true );
6761 function skipEffects(dir)
6765 var bRet = aSlideShow.skipPlayingOrNextEffect();
6769 switchSlide( 1, true );
6774 switchSlide( dir, true );
6778 function switchSlide( nOffset, bSkipTransition )
6780 var nNextSlide = nCurSlide + nOffset;
6781 aSlideShow.displaySlide( nNextSlide, bSkipTransition );
6784 /** Function to display the index sheet.
6786 * @param offsetNumber offset number
6788 function displayIndex( offsetNumber )
6790 var aMetaSlideSet = theMetaDoc.aMetaSlideSet;
6791 offsetNumber = getSafeIndex( offsetNumber, 0, aMetaSlideSet.length - 1 );
6793 var nTotalThumbnails = theSlideIndexPage.getTotalThumbnails();
6794 var nEnd = Math.min( offsetNumber + nTotalThumbnails, aMetaSlideSet.length);
6796 var aThumbnailSet = theSlideIndexPage.aThumbnailSet;
6798 for( var i = offsetNumber; i < nEnd; ++i, ++j )
6800 aThumbnailSet[j].update( i );
6801 aThumbnailSet[j].show();
6803 for( ; j < nTotalThumbnails; ++j )
6805 aThumbnailSet[j].hide();
6808 //do we need to save the current offset?
6809 if (INDEX_OFFSET != offsetNumber)
6810 INDEX_OFFSET = offsetNumber;
6813 /** Function to toggle between index and slide mode.
6815 function toggleSlideIndex()
6817 if( currentMode == SLIDE_MODE )
6820 theMetaDoc.getCurrentSlide().hide();
6822 indexSetPageSlide( nCurSlide );
6823 theSlideIndexPage.show();
6824 currentMode = INDEX_MODE;
6826 else if( currentMode == INDEX_MODE )
6828 theSlideIndexPage.hide();
6829 var nNewSlide = theSlideIndexPage.selectedSlideIndex;
6831 aSlideShow.displaySlide( nNewSlide, true );
6832 currentMode = SLIDE_MODE;
6836 /** Function that exit from the index mode without changing the shown slide
6839 function abandonIndexMode()
6841 theSlideIndexPage.selectedSlideIndex = nCurSlide;
6849 /*********************************************************************************************
6850 *********************************************************************************************
6851 *********************************************************************************************
6853 ***** ANIMATION ENGINE *****
6855 *********************************************************************************************
6856 *********************************************************************************************
6857 *********************************************************************************************/
6867 var CURR_UNIQUE_ID = 0;
6869 function getUniqueId()
6872 return CURR_UNIQUE_ID;
6875 function mem_fn( sMethodName )
6877 return function( aObject )
6879 var aMethod = aObject[ sMethodName ];
6881 aMethod.call( aObject );
6883 log( 'method sMethodName not found' );
6887 function bind( aObject, aMethod )
6891 return aMethod.call( aObject, arguments[0] );
6895 function bind2( aFunction )
6898 log( 'bind2: passed function is not valid.' );
6900 var aBoundArgList = arguments;
6902 var aResultFunction = null;
6904 switch( aBoundArgList.length )
6906 case 1: aResultFunction = function()
6908 return aFunction.call( arguments[0], arguments[1],
6909 arguments[2], arguments[3],
6913 case 2: aResultFunction = function()
6915 return aFunction.call( aBoundArgList[1], arguments[0],
6916 arguments[1], arguments[2],
6920 case 3: aResultFunction = function()
6922 return aFunction.call( aBoundArgList[1], aBoundArgList[2],
6923 arguments[0], arguments[1],
6927 case 4: aResultFunction = function()
6929 return aFunction.call( aBoundArgList[1], aBoundArgList[2],
6930 aBoundArgList[3], arguments[0],
6934 case 5: aResultFunction = function()
6936 return aFunction.call( aBoundArgList[1], aBoundArgList[2],
6937 aBoundArgList[3], aBoundArgList[4],
6942 log( 'bind2: arity not handled.' );
6945 return aResultFunction;
6948 function getCurrentSystemTime()
6950 return ( new Date() ).getTime();
6953 // eslint-disable-next-line no-unused-vars
6954 function getSlideAnimationsRoot( sSlideId )
6956 return theMetaDoc.aSlideAnimationsMap[ sSlideId ];
6959 /** This function return an array populated with all children nodes of the
6960 * passed element that are elements
6962 * @param aElement any XML element
6965 * an array that contains all children elements
6967 function getElementChildren( aElement )
6969 var aChildrenArray = [];
6971 var nSize = aElement.childNodes.length;
6973 for( var i = 0; i < nSize; ++i )
6975 if( aElement.childNodes[i].nodeType == 1 )
6976 aChildrenArray.push( aElement.childNodes[i] );
6979 return aChildrenArray;
6982 function removeWhiteSpaces( str )
6988 var aSplitString = str.split( re );
6989 return aSplitString.join('');
6992 function clamp( nValue, nMinimum, nMaximum )
6994 if( nValue < nMinimum )
6998 else if( nValue > nMaximum )
7008 function makeMatrixString( a, b, c, d, e, f )
7021 // eslint-disable-next-line no-unused-vars
7022 function matrixToString( aSVGMatrix )
7024 return makeMatrixString( aSVGMatrix.a, aSVGMatrix.b, aSVGMatrix.c,
7025 aSVGMatrix.d, aSVGMatrix.e, aSVGMatrix.f );
7031 // Attribute Parsers
7033 // eslint-disable-next-line no-unused-vars
7034 function numberParser( sValue )
7036 if( typeof sValue !== 'string' )
7038 if( sValue === '.' )
7040 var reFloatNumber = /^[+-]?[0-9]*[.]?[0-9]*$/;
7042 if( reFloatNumber.test( sValue ) )
7043 return parseFloat( sValue );
7048 function booleanParser( sValue )
7050 if( typeof sValue !== 'string' )
7053 sValue = sValue.toLowerCase();
7054 if( sValue === 'true' )
7056 else if( sValue === 'false' )
7062 function colorParser( sValue )
7064 if( typeof sValue !== 'string' )
7067 // The following 3 color functions are used in evaluating sValue string
7068 // so don't remove them.
7070 // eslint-disable-next-line no-unused-vars
7071 function hsl( nHue, nSaturation, nLuminance )
7073 return new HSLColor( nHue, nSaturation / 100, nLuminance / 100 );
7076 // eslint-disable-next-line no-unused-vars
7077 function rgb( nRed, nGreen, nBlue )
7079 return new RGBColor( nRed / 255, nGreen / 255, nBlue / 255 );
7082 // eslint-disable-next-line no-unused-vars
7083 function prgb( nRed, nGreen, nBlue )
7085 return new RGBColor( nRed / 100, nGreen / 100, nBlue / 100 );
7088 var sCommaPattern = ' *[,] *';
7089 var sIntegerPattern = '[+-]?[0-9]+';
7090 var sHexDigitPattern = '[0-9A-Fa-f]';
7092 var sHexColorPattern = '#(' + sHexDigitPattern + '{2})('
7093 + sHexDigitPattern + '{2})('
7094 + sHexDigitPattern + '{2})';
7096 var sRGBIntegerPattern = 'rgb[(] *' + sIntegerPattern + sCommaPattern
7097 + sIntegerPattern + sCommaPattern
7098 + sIntegerPattern + ' *[)]';
7100 var sRGBPercentPattern = 'rgb[(] *' + sIntegerPattern + '%' + sCommaPattern
7101 + sIntegerPattern + '%' + sCommaPattern
7102 + sIntegerPattern + '%' + ' *[)]';
7104 var sHSLPercentPattern = 'hsl[(] *' + sIntegerPattern + sCommaPattern
7105 + sIntegerPattern + '%' + sCommaPattern
7106 + sIntegerPattern + '%' + ' *[)]';
7108 var reHexColor = new RegExp( sHexColorPattern );
7109 var reRGBInteger = new RegExp( sRGBIntegerPattern );
7110 var reRGBPercent = new RegExp( sRGBPercentPattern );
7111 var reHSLPercent = new RegExp( sHSLPercentPattern );
7113 if( reHexColor.test( sValue ) )
7115 var aRGBTriple = reHexColor.exec( sValue );
7117 var nRed = parseInt( aRGBTriple[1], 16 ) / 255;
7118 var nGreen = parseInt( aRGBTriple[2], 16 ) / 255;
7119 var nBlue = parseInt( aRGBTriple[3], 16 ) / 255;
7121 return new RGBColor( nRed, nGreen, nBlue );
7123 else if( reHSLPercent.test( sValue ) )
7125 sValue = sValue.replace( '%', '' ).replace( '%', '' );
7126 return eval( sValue );
7128 else if( reRGBInteger.test( sValue ) )
7130 return eval( sValue );
7132 else if( reRGBPercent.test( sValue ) )
7134 sValue = 'p' + sValue.replace( '%', '' ).replace( '%', '' ).replace( '%', '' );
7135 return eval( sValue );
7145 /**********************************************************************************************
7146 * RGB and HSL color classes
7147 **********************************************************************************************/
7150 function RGBColor( nRed, nGreen, nBlue )
7152 this.eColorSpace = COLOR_SPACE_RGB;
7153 // values in the [0,1] range
7155 this.nGreen = nGreen;
7160 RGBColor.prototype.clone = function()
7162 return new RGBColor( this.nRed, this.nGreen, this.nBlue );
7165 RGBColor.prototype.equal = function( aRGBColor )
7167 return ( this.nRed == aRGBColor.nRed ) &&
7168 ( this.nGreen == aRGBColor.nGreen ) &&
7169 ( this.nBlue == aRGBColor.nBlue );
7172 RGBColor.prototype.add = function( aRGBColor )
7174 this.nRed += aRGBColor.nRed;
7175 this.nGreen += aRGBColor.nGreen;
7176 this.nBlue += aRGBColor.nBlue;
7180 RGBColor.prototype.scale = function( aT )
7188 RGBColor.clamp = function( aRGBColor )
7190 var aClampedRGBColor = new RGBColor( 0, 0, 0 );
7192 aClampedRGBColor.nRed = clamp( aRGBColor.nRed, 0.0, 1.0 );
7193 aClampedRGBColor.nGreen = clamp( aRGBColor.nGreen, 0.0, 1.0 );
7194 aClampedRGBColor.nBlue = clamp( aRGBColor.nBlue, 0.0, 1.0 );
7196 return aClampedRGBColor;
7199 RGBColor.prototype.convertToHSL = function()
7201 var nRed = clamp( this.nRed, 0.0, 1.0 );
7202 var nGreen = clamp( this.nGreen, 0.0, 1.0 );
7203 var nBlue = clamp( this.nBlue, 0.0, 1.0 );
7205 var nMax = Math.max( nRed, nGreen, nBlue );
7206 var nMin = Math.min( nRed, nGreen, nBlue );
7207 var nDelta = nMax - nMin;
7209 var nLuminance = ( nMax + nMin ) / 2.0;
7210 var nSaturation = 0.0;
7214 nSaturation = ( nLuminance > 0.5 ) ?
7215 ( nDelta / ( 2.0 - nMax - nMin) ) :
7216 ( nDelta / ( nMax + nMin ) );
7219 nHue = ( nGreen - nBlue ) / nDelta;
7220 else if( nGreen == nMax )
7221 nHue = 2.0 + ( nBlue - nRed ) / nDelta;
7222 else if( nBlue == nMax )
7223 nHue = 4.0 + ( nRed - nGreen ) / nDelta;
7231 return new HSLColor( nHue, nSaturation, nLuminance );
7235 RGBColor.prototype.toString = function( bClamped )
7240 aRGBColor = RGBColor.clamp( this );
7247 var nRed = Math.round( aRGBColor.nRed * 255 );
7248 var nGreen = Math.round( aRGBColor.nGreen * 255 );
7249 var nBlue = Math.round( aRGBColor.nBlue * 255 );
7251 return ( 'rgb(' + nRed + ',' + nGreen + ',' + nBlue + ')' );
7254 RGBColor.interpolate = function( aStartRGB , aEndRGB, nT )
7256 var aResult = aStartRGB.clone();
7257 var aTEndRGB = aEndRGB.clone();
7258 aResult.scale( 1.0 - nT );
7259 aTEndRGB.scale( nT );
7260 aResult.add( aTEndRGB );
7268 function HSLColor( nHue, nSaturation, nLuminance )
7270 this.eColorSpace = COLOR_SPACE_HSL;
7271 // Hue is in the [0,360[ range, Saturation and Luminance are in the [0,1] range
7273 this.nSaturation = nSaturation;
7274 this.nLuminance = nLuminance;
7276 this.normalizeHue();
7280 HSLColor.prototype.clone = function()
7282 return new HSLColor( this.nHue, this.nSaturation, this.nLuminance );
7285 HSLColor.prototype.equal = function( aHSLColor )
7287 return ( this.nHue == aHSLColor.nHue ) &&
7288 ( this.nSaturation += aHSLColor.nSaturation ) &&
7289 ( this.nLuminance += aHSLColor.nLuminance );
7292 HSLColor.prototype.add = function( aHSLColor )
7294 this.nHue += aHSLColor.nHue;
7295 this.nSaturation += aHSLColor.nSaturation;
7296 this.nLuminance += aHSLColor.nLuminance;
7297 this.normalizeHue();
7301 HSLColor.prototype.scale = function( aT )
7304 this.nSaturation *= aT;
7305 this.nLuminance *= aT;
7306 this.normalizeHue();
7310 HSLColor.clamp = function( aHSLColor )
7312 var aClampedHSLColor = new HSLColor( 0, 0, 0 );
7314 aClampedHSLColor.nHue = aHSLColor.nHue % 360;
7315 if( aClampedHSLColor.nHue < 0 )
7316 aClampedHSLColor.nHue += 360;
7317 aClampedHSLColor.nSaturation = clamp( aHSLColor.nSaturation, 0.0, 1.0 );
7318 aClampedHSLColor.nLuminance = clamp( aHSLColor.nLuminance, 0.0, 1.0 );
7321 HSLColor.prototype.normalizeHue = function()
7323 this.nHue = this.nHue % 360;
7324 if( this.nHue < 0 ) this.nHue += 360;
7327 HSLColor.prototype.toString = function()
7329 return 'hsl(' + this.nHue.toFixed( 3 ) + ','
7330 + this.nSaturation.toFixed( 3 ) + ','
7331 + this.nLuminance.toFixed( 3 ) + ')';
7334 HSLColor.prototype.convertToRGB = function()
7337 var nHue = this.nHue % 360;
7338 if( nHue < 0 ) nHue += 360;
7339 var nSaturation = clamp( this.nSaturation, 0.0, 1.0 );
7340 var nLuminance = clamp( this.nLuminance, 0.0, 1.0 );
7343 if( nSaturation === 0 )
7345 return new RGBColor( nLuminance, nLuminance, nLuminance );
7348 var nVal1 = ( nLuminance <= 0.5 ) ?
7349 ( nLuminance * (1.0 + nSaturation) ) :
7350 ( nLuminance + nSaturation - nLuminance * nSaturation );
7352 var nVal2 = 2.0 * nLuminance - nVal1;
7354 var nRed = HSLColor.hsl2rgbHelper( nVal2, nVal1, nHue + 120 );
7355 var nGreen = HSLColor.hsl2rgbHelper( nVal2, nVal1, nHue );
7356 var nBlue = HSLColor.hsl2rgbHelper( nVal2, nVal1, nHue - 120 );
7358 return new RGBColor( nRed, nGreen, nBlue );
7361 HSLColor.hsl2rgbHelper = function( nValue1, nValue2, nHue )
7368 return nValue1 + ( nValue2 - nValue1 ) * nHue / 60.0;
7369 else if( nHue < 180.0 )
7371 else if( nHue < 240.0 )
7372 return ( nValue1 + ( nValue2 - nValue1 ) * ( 240.0 - nHue ) / 60.0 );
7377 HSLColor.interpolate = function( aFrom, aTo, nT, bCCW )
7382 if( aFrom.nHue <= aTo.nHue && !bCCW )
7384 // interpolate hue clockwise. That is, hue starts at
7385 // high values and ends at low ones. Therefore, we
7386 // must 'cross' the 360 degrees and start at low
7387 // values again (imagine the hues to lie on the
7388 // circle, where values above 360 degrees are mapped
7389 // back to [0,360)).
7390 nHue = nS * (aFrom.nHue + 360.0) + nT * aTo.nHue;
7392 else if( aFrom.nHue > aTo.nHue && bCCW )
7394 // interpolate hue counter-clockwise. That is, hue
7395 // starts at high values and ends at low
7396 // ones. Therefore, we must 'cross' the 360 degrees
7397 // and start at low values again (imagine the hues to
7398 // lie on the circle, where values above 360 degrees
7399 // are mapped back to [0,360)).
7400 nHue = nS * aFrom.nHue + nT * (aTo.nHue + 360.0);
7404 // interpolate hue counter-clockwise. That is, hue
7405 // starts at low values and ends at high ones (imagine
7406 // the hue value as degrees on a circle, with
7407 // increasing values going counter-clockwise)
7408 nHue = nS * aFrom.nHue + nT * aTo.nHue;
7411 var nSaturation = nS * aFrom.nSaturation + nT * aTo.nSaturation;
7412 var nLuminance = nS * aFrom.nLuminance + nT * aTo.nLuminance;
7414 return new HSLColor( nHue, nSaturation, nLuminance );
7419 /**********************************************************************************************
7420 * SVGMatrix extensions
7421 **********************************************************************************************/
7423 var SVGIdentityMatrix = document.documentElement.createSVGMatrix();
7425 SVGMatrix.prototype.setToIdentity = function()
7427 this.a = this.d = 1;
7428 this.b = this.c = this.d = this.e = 0;
7431 SVGMatrix.prototype.setToRotationAroundPoint = function( nX, nY, nAngle )
7433 // convert to radians
7434 nAngle = Math.PI * nAngle / 180;
7435 var nSin = Math.sin( nAngle );
7436 var nCos = Math.cos( nAngle );
7438 this.a = nCos; this.c = -nSin; this.e = nX * (1 - nCos) + nY * nSin;
7439 this.b = nSin; this.d = nCos; this.f = nY * (1 - nCos) - nX * nSin;
7444 /**********************************************************************************************
7445 * SVGPath extensions
7446 **********************************************************************************************/
7448 /** SVGPathElement.prependPath
7449 * Merge the two path together by inserting the passed path before this one.
7452 * An object of type SVGPathElement to be prepended.
7454 SVGPathElement.prototype.prependPath = function( aPath )
7456 var sPathData = aPath.getAttribute( 'd' );
7457 sPathData += ( ' ' + this.getAttribute( 'd' ) );
7458 this.setAttribute( 'd', sPathData );
7461 /** SVGPathElement.appendPath
7462 * Merge the two path together by inserting the passed path after this one.
7465 * An object of type SVGPathElement to be appended.
7467 SVGPathElement.prototype.appendPath = function( aPath )
7469 var sPathData = this.getAttribute( 'd' );
7470 sPathData += ( ' ' + aPath.getAttribute( 'd' ) );
7471 this.setAttribute( 'd', sPathData );
7475 * Flips the SVG Path element along y-axis.
7478 * An object of type SVGPathElement to be flipped.
7480 function flipOnYAxis( aPath )
7482 var aPolyPath = aPath.cloneNode(true);
7483 var aTransform = document.documentElement.createSVGMatrix();
7486 aPolyPath.matrixTransform(aTransform);
7491 * Flips the SVG Path element along x-axis
7494 * An object of type SVGPathElement to be flipped
7496 function flipOnXAxis( aPath )
7498 var aPolyPath = aPath.cloneNode(true);
7499 var aTransform = document.documentElement.createSVGMatrix();
7502 aPolyPath.matrixTransform(aTransform);
7506 /** SVGPathElement.matrixTransform
7507 * Apply the transformation defined by the passed matrix to the referenced
7508 * svg <path> element.
7509 * After the transformation 'this' element is modified in order to reference
7510 * the transformed path.
7513 * An SVGMatrix instance.
7515 SVGPathElement.prototype.matrixTransform = function( aSVGMatrix )
7517 if( SVGPathSegList.prototype.matrixTransform )
7519 this.pathSegList.matrixTransform( aSVGMatrix );
7523 var aPathSegList = this.pathSegList;
7524 var nLength = aPathSegList.numberOfItems;
7526 for( i = 0; i < nLength; ++i )
7528 aPathSegList.getItem( i ).matrixTransform( aSVGMatrix );
7532 /** SVGPathElement.changeOrientation
7533 * Invert the path orientation by inverting the path command list.
7536 SVGPathElement.prototype.changeOrientation = function()
7538 var aPathSegList = this.pathSegList;
7539 var nLength = aPathSegList.numberOfItems;
7540 if( nLength == 0 ) return;
7542 if( SVGPathSegList.prototype.changeOrientation )
7544 aPathSegList.changeOrientation();
7551 var aPathSeg = aPathSegList.getItem( 0 );
7552 if( aPathSeg.pathSegTypeAsLetter == 'M' )
7554 nCurrentX = aPathSeg.x;
7555 nCurrentY = aPathSeg.y;
7556 aPathSegList.removeItem( 0 );
7561 for( i = 0; i < nLength; ++i )
7563 aPathSeg = aPathSegList.getItem( i );
7564 var aPoint = aPathSeg.changeOrientation( nCurrentX, nCurrentY );
7565 nCurrentX = aPoint.x;
7566 nCurrentY = aPoint.y;
7570 for( i = nLength - 2; i >= 0; --i )
7572 aPathSeg = aPathSegList.removeItem( i );
7573 aPathSegList.appendItem( aPathSeg );
7576 var aMovePathSeg = this.createSVGPathSegMovetoAbs( nCurrentX, nCurrentY );
7577 aPathSegList.insertItemBefore( aMovePathSeg, 0 );
7581 /** matrixTransform and changeOrientation
7582 * We implement these methods for each path segment type still present
7583 * after the path normalization (M, L, Q, C).
7585 * Note: Opera doesn't have any SVGPathSeg* class and rises an error.
7586 * We exploit this fact for providing a different implementation.
7591 { // Firefox, Google Chrome, Internet Explorer, Safari.
7593 SVGPathSegMovetoAbs.prototype.matrixTransform = function( aSVGMatrix )
7595 SVGPathMatrixTransform( this, aSVGMatrix );
7598 SVGPathSegLinetoAbs.prototype.matrixTransform = function( aSVGMatrix )
7600 SVGPathMatrixTransform( this, aSVGMatrix );
7603 SVGPathSegCurvetoQuadraticAbs.prototype.matrixTransform = function( aSVGMatrix )
7605 SVGPathMatrixTransform( this, aSVGMatrix );
7607 this.x1 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y1 + aSVGMatrix.e;
7608 this.y1 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y1 + aSVGMatrix.f;
7611 SVGPathSegCurvetoCubicAbs.prototype.matrixTransform = function( aSVGMatrix )
7613 SVGPathMatrixTransform( this, aSVGMatrix );
7615 this.x1 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y1 + aSVGMatrix.e;
7616 this.y1 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y1 + aSVGMatrix.f;
7618 this.x2 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y2 + aSVGMatrix.e;
7619 this.y2 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y2 + aSVGMatrix.f;
7623 SVGPathSegMovetoAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7625 var aPoint = { x: this.x, y: this.y };
7631 SVGPathSegLinetoAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7633 var aPoint = { x: this.x, y: this.y };
7639 SVGPathSegCurvetoQuadraticAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7641 var aPoint = { x: this.x, y: this.y };
7647 SVGPathSegCurvetoCubicAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7649 var aPoint = { x: this.x, y: this.y };
7665 if( e.name == 'ReferenceError' )
7667 SVGPathSeg.prototype.matrixTransform = function( aSVGMatrix )
7670 switch( this.pathSegTypeAsLetter )
7674 this.x2 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y2 + aSVGMatrix.e;
7675 this.y2 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y2 + aSVGMatrix.f;
7676 // fall through intended
7679 this.x1 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y1 + aSVGMatrix.e;
7680 this.y1 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y1 + aSVGMatrix.f;
7681 // fall through intended
7684 SVGPathMatrixTransform( this, aSVGMatrix );
7687 log( 'SVGPathSeg.matrixTransform: unexpected path segment type: '
7688 + this.pathSegTypeAsLetter );
7692 SVGPathSeg.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7694 switch( this.pathSegTypeAsLetter )
7703 // fall through intended
7707 var aPoint = { x: this.x, y: this.y };
7712 log( 'SVGPathSeg.changeOrientation: unexpected path segment type: '
7713 + this.pathSegTypeAsLetter );
7721 function SVGPathMatrixTransform( aPath, aSVGMatrix )
7724 aPath.x = aSVGMatrix.a * nX + aSVGMatrix.c * aPath.y + aSVGMatrix.e;
7725 aPath.y = aSVGMatrix.b * nX + aSVGMatrix.d * aPath.y + aSVGMatrix.f;
7729 /**********************************************************************************************
7730 * simple PriorityQueue
7731 **********************************************************************************************/
7733 function PriorityQueue( aCompareFunc )
7735 this.aSequence = [];
7736 this.aCompareFunc = aCompareFunc;
7739 PriorityQueue.prototype.clone = function()
7741 var aCopy = new PriorityQueue( this.aCompareFunc );
7742 var src = this.aSequence;
7745 for( i = 0, l = src.length; i < l; ++i )
7749 dest.push( src[i] );
7752 aCopy.aSequence = dest;
7756 PriorityQueue.prototype.top = function()
7758 return this.aSequence[this.aSequence.length - 1];
7761 PriorityQueue.prototype.isEmpty = function()
7763 return ( this.aSequence.length === 0 );
7766 PriorityQueue.prototype.push = function( aValue )
7768 this.aSequence.unshift( aValue );
7769 this.aSequence.sort(this.aCompareFunc);
7772 PriorityQueue.prototype.clear = function()
7774 this.aSequence = [];
7777 PriorityQueue.prototype.pop = function()
7779 return this.aSequence.pop();
7785 /**********************************************************************************************
7786 * AnimationNode Class Hierarchy
7787 **********************************************************************************************/
7792 var ANIMATION_NODE_CUSTOM = 0;
7793 var ANIMATION_NODE_PAR = 1;
7794 var ANIMATION_NODE_SEQ = 2;
7795 var ANIMATION_NODE_ITERATE = 3;
7796 var ANIMATION_NODE_ANIMATE = 4;
7797 var ANIMATION_NODE_SET = 5;
7798 var ANIMATION_NODE_ANIMATEMOTION = 6;
7799 var ANIMATION_NODE_ANIMATECOLOR = 7;
7800 var ANIMATION_NODE_ANIMATETRANSFORM = 8;
7801 var ANIMATION_NODE_TRANSITIONFILTER = 9;
7802 var ANIMATION_NODE_AUDIO = 10;
7803 var ANIMATION_NODE_COMMAND = 11;
7805 var aAnimationNodeTypeInMap = {
7806 'par' : ANIMATION_NODE_PAR,
7807 'seq' : ANIMATION_NODE_SEQ,
7808 'iterate' : ANIMATION_NODE_ITERATE,
7809 'animate' : ANIMATION_NODE_ANIMATE,
7810 'set' : ANIMATION_NODE_SET,
7811 'animatemotion' : ANIMATION_NODE_ANIMATEMOTION,
7812 'animatecolor' : ANIMATION_NODE_ANIMATECOLOR,
7813 'animatetransform' : ANIMATION_NODE_ANIMATETRANSFORM,
7814 'transitionfilter' : ANIMATION_NODE_TRANSITIONFILTER,
7815 'audio' : ANIMATION_NODE_AUDIO,
7816 'command' : ANIMATION_NODE_COMMAND
7822 function getAnimationElementType( aElement )
7824 var sName = aElement.localName.toLowerCase();
7826 if( sName && aAnimationNodeTypeInMap[ sName ] )
7827 return aAnimationNodeTypeInMap[ sName ];
7829 return ANIMATION_NODE_CUSTOM;
7837 var INVALID_NODE = 0;
7838 var UNRESOLVED_NODE = 1;
7839 var RESOLVED_NODE = 2;
7840 var ACTIVE_NODE = 4;
7841 var FROZEN_NODE = 8;
7842 var ENDED_NODE = 16;
7844 function getNodeStateName( eNodeState )
7846 switch( eNodeState )
7850 case UNRESOLVED_NODE:
7851 return 'UNRESOLVED';
7866 // Impress Node Types
7867 var IMPRESS_DEFAULT_NODE = 0;
7868 var IMPRESS_ON_CLICK_NODE = 1;
7869 var IMPRESS_WITH_PREVIOUS_NODE = 2;
7870 var IMPRESS_AFTER_PREVIOUS_NODE = 3;
7871 var IMPRESS_MAIN_SEQUENCE_NODE = 4;
7872 var IMPRESS_TIMING_ROOT_NODE = 5;
7873 var IMPRESS_INTERACTIVE_SEQUENCE_NODE = 6;
7875 var aImpressNodeTypeInMap = {
7876 'on-click' : IMPRESS_ON_CLICK_NODE,
7877 'with-previous' : IMPRESS_WITH_PREVIOUS_NODE,
7878 'after-previous' : IMPRESS_AFTER_PREVIOUS_NODE,
7879 'main-sequence' : IMPRESS_MAIN_SEQUENCE_NODE,
7880 'timing-root' : IMPRESS_TIMING_ROOT_NODE,
7881 'interactive-sequence' : IMPRESS_INTERACTIVE_SEQUENCE_NODE
7884 var aImpressNodeTypeOutMap = [ 'default', 'on-click', 'with-previous', 'after-previous',
7885 'main-sequence', 'timing-root', 'interactive-sequence' ];
7889 var aPresetClassInMap = {};
7893 var aPresetIdInMap = {};
7897 var RESTART_MODE_DEFAULT = 0;
7898 var RESTART_MODE_INHERIT = 0; // eslint-disable-line no-unused-vars
7899 var RESTART_MODE_ALWAYS = 1;
7900 var RESTART_MODE_WHEN_NOT_ACTIVE = 2;
7901 var RESTART_MODE_NEVER = 3;
7903 var aRestartModeInMap = {
7904 'inherit' : RESTART_MODE_DEFAULT,
7905 'always' : RESTART_MODE_ALWAYS,
7906 'whenNotActive' : RESTART_MODE_WHEN_NOT_ACTIVE,
7907 'never' : RESTART_MODE_NEVER
7910 var aRestartModeOutMap = [ 'inherit','always', 'whenNotActive', 'never' ];
7914 var FILL_MODE_DEFAULT = 0;
7915 var FILL_MODE_INHERIT = 0; // eslint-disable-line no-unused-vars
7916 var FILL_MODE_REMOVE = 1;
7917 var FILL_MODE_FREEZE = 2;
7918 var FILL_MODE_HOLD = 3;
7919 var FILL_MODE_TRANSITION = 4;
7920 var FILL_MODE_AUTO = 5;
7922 var aFillModeInMap = {
7923 'inherit' : FILL_MODE_DEFAULT,
7924 'remove' : FILL_MODE_REMOVE,
7925 'freeze' : FILL_MODE_FREEZE,
7926 'hold' : FILL_MODE_HOLD,
7927 'transition' : FILL_MODE_TRANSITION,
7928 'auto' : FILL_MODE_AUTO
7931 var aFillModeOutMap = [ 'inherit', 'remove', 'freeze', 'hold', 'transition', 'auto' ];
7935 var ADDITIVE_MODE_UNKNOWN = 0; // eslint-disable-line no-unused-vars
7936 var ADDITIVE_MODE_BASE = 1;
7937 var ADDITIVE_MODE_SUM = 2;
7938 var ADDITIVE_MODE_REPLACE = 3;
7939 var ADDITIVE_MODE_MULTIPLY = 4;
7940 var ADDITIVE_MODE_NONE = 5;
7942 var aAddittiveModeInMap = {
7943 'base' : ADDITIVE_MODE_BASE,
7944 'sum' : ADDITIVE_MODE_SUM,
7945 'replace' : ADDITIVE_MODE_REPLACE,
7946 'multiply' : ADDITIVE_MODE_MULTIPLY,
7947 'none' : ADDITIVE_MODE_NONE
7950 var aAddittiveModeOutMap = [ 'unknown', 'base', 'sum', 'replace', 'multiply', 'none' ];
7954 var ACCUMULATE_MODE_NONE = 0;
7955 var ACCUMULATE_MODE_SUM = 1;
7957 var aAccumulateModeOutMap = [ 'none', 'sum' ];
7959 // Calculation Modes
7960 var CALC_MODE_DISCRETE = 1;
7961 var CALC_MODE_LINEAR = 2;
7962 var CALC_MODE_PACED = 3;
7963 var CALC_MODE_SPLINE = 4;
7965 var aCalcModeInMap = {
7966 'discrete' : CALC_MODE_DISCRETE,
7967 'linear' : CALC_MODE_LINEAR,
7968 'paced' : CALC_MODE_PACED,
7969 'spline' : CALC_MODE_SPLINE
7972 var aCalcModeOutMap = [ 'unknown', 'discrete', 'linear', 'paced', 'spline' ];
7976 var COLOR_SPACE_RGB = 0;
7977 var COLOR_SPACE_HSL = 1;
7979 var aColorSpaceInMap = { 'rgb': COLOR_SPACE_RGB, 'hsl': COLOR_SPACE_HSL };
7981 var aColorSpaceOutMap = [ 'rgb', 'hsl' ];
7986 var COUNTERCLOCKWISE = 1;
7988 var aClockDirectionInMap = { 'clockwise': CLOCKWISE, 'counter-clockwise': COUNTERCLOCKWISE };
7990 var aClockDirectionOutMap = [ 'clockwise', 'counter-clockwise' ];
7993 // Attribute Value Types
7994 var UNKNOWN_PROPERTY = 0; // eslint-disable-line no-unused-vars
7995 var NUMBER_PROPERTY = 1;
7996 var ENUM_PROPERTY = 2;
7997 var COLOR_PROPERTY = 3;
7998 var STRING_PROPERTY = 4;
7999 var BOOL_PROPERTY = 5;
8000 var TUPLE_NUMBER_PROPERTY = 6;
8002 var aValueTypeOutMap = [ 'unknown', 'number', 'enum', 'color', 'string', 'boolean' ];
8008 'height': { 'type': NUMBER_PROPERTY,
8011 'getmod': 'makeScaler( 1/nHeight )',
8012 'setmod': 'makeScaler( nHeight)' },
8014 'opacity': { 'type': NUMBER_PROPERTY,
8015 'get': 'getOpacity',
8016 'set': 'setOpacity' },
8018 'scale': { 'type': TUPLE_NUMBER_PROPERTY,
8022 'translate': { 'type': TUPLE_NUMBER_PROPERTY,
8026 'rotate': { 'type': NUMBER_PROPERTY,
8027 'get': 'getRotationAngle',
8028 'set': 'setRotationAngle' },
8030 'width': { 'type': NUMBER_PROPERTY,
8033 'getmod': 'makeScaler( 1/nWidth )',
8034 'setmod': 'makeScaler( nWidth)' },
8036 'x': { 'type': NUMBER_PROPERTY,
8039 'getmod': 'makeScaler( 1/nWidth )',
8040 'setmod': 'makeScaler( nWidth)' },
8042 'y': { 'type': NUMBER_PROPERTY,
8045 'getmod': 'makeScaler( 1/nHeight )',
8046 'setmod': 'makeScaler( nHeight)' },
8048 'fill': { 'type': ENUM_PROPERTY,
8049 'get': 'getFillStyle',
8050 'set': 'setFillStyle' },
8052 'stroke': { 'type': ENUM_PROPERTY,
8053 'get': 'getStrokeStyle',
8054 'set': 'setStrokeStyle' },
8056 'visibility': { 'type': ENUM_PROPERTY,
8057 'get': 'getVisibility',
8058 'set': 'setVisibility' },
8060 'fill-color': { 'type': COLOR_PROPERTY,
8061 'get': 'getFillColor',
8062 'set': 'setFillColor' },
8064 'stroke-color': { 'type': COLOR_PROPERTY,
8065 'get': 'getStrokeColor',
8066 'set': 'setStrokeColor' },
8068 'color': { 'type': COLOR_PROPERTY,
8069 'get': 'getFontColor',
8070 'set': 'setFontColor' }
8075 // Transition Classes
8076 var TRANSITION_INVALID = 0; // Invalid type
8077 var TRANSITION_CLIP_POLYPOLYGON = 1; // Transition expressed by parametric clip polygon
8078 var TRANSITION_SPECIAL = 2; // Transition expressed by hand-crafted function
8081 * All Transition types should be in sync with aTransitionTypeInMap:
8082 * Comments '//' followed by integers represent the transition values in their
8083 * C++ implementations.
8087 var BARWIPE_TRANSITION = 1;
8088 var BOXWIPE_TRANSITION = 2;
8089 var FOURBOXWIPE_TRANSITION = 3;
8090 var ELLIPSEWIPE_TRANSITION = 4; // 17
8091 var CLOCKWIPE_TRANSITION = 5; // 22
8092 var PINWHEELWIPE_TRANSITION = 6; // 23
8093 var PUSHWIPE_TRANSITION = 7; // 35
8094 var SLIDEWIPE_TRANSITION = 8; // 36
8095 var FADE_TRANSITION = 9; // 37
8096 var RANDOMBARWIPE_TRANSITION = 10; // 38
8097 var CHECKERBOARDWIPE_TRANSITION = 11; // 39
8098 var DISSOLVE_TRANSITION = 12; // 40
8099 var SNAKEWIPE_TRANSITION = 13; // 30
8100 var PARALLELSNAKESWIPE_TRANSITION = 14; // 32
8101 var IRISWIPE_TRANSITION = 15; // 12
8102 var BARNDOORWIPE_TRANSITION = 16; // 4
8103 var VEEWIPE_TRANSITION = 17; // 8
8104 var ZIGZAGWIPE_TRANSITION = 18; // 10
8105 var BARNZIGZAGWIPE_TRANSITION = 19; // 11
8106 var FANWIPE_TRANSITION = 20; // 25
8107 var SINGLESWEEPWIPE_TRANSITION = 21; // 24
8108 var WATERFALLWIPE_TRANSITION = 22; // 34
8109 var SPIRALWIPE_TRANSITION = 23; // 31
8110 var MISCDIAGONALWIPE_TRANSITION = 24; // 7
8111 var BOXSNAKESWIPE_TRANSITION = 25; // 33
8113 var aTransitionTypeInMap = {
8114 'barWipe' : BARWIPE_TRANSITION,
8115 'boxWipe' : BOXWIPE_TRANSITION,
8116 'barnDoorWipe' : BARNDOORWIPE_TRANSITION,
8117 'fourBoxWipe' : FOURBOXWIPE_TRANSITION,
8118 'ellipseWipe' : ELLIPSEWIPE_TRANSITION,
8119 'clockWipe' : CLOCKWIPE_TRANSITION,
8120 'pinWheelWipe' : PINWHEELWIPE_TRANSITION,
8121 'miscDiagonalWipe' : MISCDIAGONALWIPE_TRANSITION,
8122 'pushWipe' : PUSHWIPE_TRANSITION,
8123 'slideWipe' : SLIDEWIPE_TRANSITION,
8124 'fade' : FADE_TRANSITION,
8125 'fanWipe' : FANWIPE_TRANSITION,
8126 'randomBarWipe' : RANDOMBARWIPE_TRANSITION,
8127 'checkerBoardWipe' : CHECKERBOARDWIPE_TRANSITION,
8128 'dissolve' : DISSOLVE_TRANSITION,
8129 'singleSweepWipe' : SINGLESWEEPWIPE_TRANSITION,
8130 'snakeWipe' : SNAKEWIPE_TRANSITION,
8131 'parallelSnakesWipe': PARALLELSNAKESWIPE_TRANSITION,
8132 'spiralWipe' : SPIRALWIPE_TRANSITION,
8133 'boxSnakesWipe' : BOXSNAKESWIPE_TRANSITION,
8134 'irisWipe' : IRISWIPE_TRANSITION,
8135 'veeWipe' : VEEWIPE_TRANSITION,
8136 'zigZagWipe' : ZIGZAGWIPE_TRANSITION,
8137 'barnZigZagWipe' : BARNZIGZAGWIPE_TRANSITION,
8138 'waterfallWipe' : WATERFALLWIPE_TRANSITION
8142 * All Transition subtypes should be in sync with aTransitionSubtypeInMap:
8143 * Comments '//' followed by integers represent the transition values in their
8144 * C++ implementations.
8146 // Transition Subtypes
8147 var DEFAULT_TRANS_SUBTYPE = 0;
8148 var LEFTTORIGHT_TRANS_SUBTYPE = 1;
8149 var TOPTOBOTTOM_TRANS_SUBTYPE = 2;
8150 var CORNERSIN_TRANS_SUBTYPE = 3; // 11
8151 var CORNERSOUT_TRANS_SUBTYPE = 4;
8152 var VERTICAL_TRANS_SUBTYPE = 5;
8153 var HORIZONTAL_TRANS_SUBTYPE = 6; // 14
8154 var DOWN_TRANS_SUBTYPE = 7; // 19
8155 var CIRCLE_TRANS_SUBTYPE = 8; // 27
8156 var CLOCKWISETWELVE_TRANS_SUBTYPE = 9; // 33
8157 var CLOCKWISETHREE_TRANS_SUBTYPE = 10;
8158 var CLOCKWISESIX_TRANS_SUBTYPE = 11;
8159 var CLOCKWISENINE_TRANS_SUBTYPE = 12;
8160 var TWOBLADEVERTICAL_TRANS_SUBTYPE = 13;
8161 var TWOBLADEHORIZONTAL_TRANS_SUBTYPE = 14;
8162 var FOURBLADE_TRANS_SUBTYPE = 15; // 39
8163 var FROMLEFT_TRANS_SUBTYPE = 16; // 97
8164 var FROMTOP_TRANS_SUBTYPE = 17;
8165 var FROMRIGHT_TRANS_SUBTYPE = 18;
8166 var FROMBOTTOM_TRANS_SUBTYPE = 19;
8167 var CROSSFADE_TRANS_SUBTYPE = 20;
8168 var FADETOCOLOR_TRANS_SUBTYPE = 21;
8169 var FADEFROMCOLOR_TRANS_SUBTYPE = 22;
8170 var FADEOVERCOLOR_TRANS_SUBTYPE = 23;
8171 var THREEBLADE_TRANS_SUBTYPE = 24;
8172 var EIGHTBLADE_TRANS_SUBTYPE = 25;
8173 var ONEBLADE_TRANS_SUBTYPE = 26; // 107
8174 var ACROSS_TRANS_SUBTYPE = 27;
8175 var TOPLEFTVERTICAL_TRANS_SUBTYPE = 28; // 109
8176 var TOPLEFTHORIZONTAL_TRANS_SUBTYPE = 29; // 64
8177 var TOPLEFTDIAGONAL_TRANS_SUBTYPE = 30; // 65
8178 var TOPRIGHTDIAGONAL_TRANS_SUBTYPE = 31; // 66
8179 var BOTTOMRIGHTDIAGONAL_TRANS_SUBTYPE = 32; // 67
8180 var BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE = 33; // 68
8181 var RECTANGLE_TRANS_SUBTYPE = 34; // 101
8182 var DIAMOND_TRANS_SUBTYPE = 35; // 102
8183 var TOPLEFT_TRANS_SUBTYPE = 36; // 3
8184 var TOPRIGHT_TRANS_SUBTYPE = 37; // 4
8185 var BOTTOMRIGHT_TRANS_SUBTYPE = 38; // 5
8186 var BOTTOMLEFT_TRANS_SUBTYPE = 39; // 6
8187 var TOPCENTER_TRANS_SUBTYPE = 40; // 7
8188 var RIGHTCENTER_TRANS_SUBTYPE = 41; // 8
8189 var BOTTOMCENTER_TRANS_SUBTYPE = 42; // 9
8190 var LEFTCENTER_TRANS_SUBTYPE = 43; // 10
8191 var LEFT_TRANS_SUBTYPE = 44; // 20
8192 var UP_TRANS_SUBTYPE = 45; // 21
8193 var RIGHT_TRANS_SUBTYPE = 46; // 22
8194 var DIAGONALBOTTOMLEFT_TRANS_SUBTYPE = 47; // 15
8195 var DIAGONALTOPLEFT_TRANS_SUBTYPE = 48; // 16
8196 var CENTERTOP_TRANS_SUBTYPE = 49; // 48
8197 var CENTERRIGHT_TRANS_SUBTYPE = 50; // 49
8198 var TOP_TRANS_SUBTYPE = 51; // 50
8199 var BOTTOM_TRANS_SUBTYPE = 52; // 52
8200 var CLOCKWISETOP_TRANS_SUBTYPE = 53; // 40
8201 var CLOCKWISERIGHT_TRANS_SUBTYPE = 54; // 41
8202 var CLOCKWISEBOTTOM_TRANS_SUBTYPE = 55; // 42
8203 var CLOCKWISELEFT_TRANS_SUBTYPE = 56; // 43
8204 var CLOCKWISETOPLEFT_TRANS_SUBTYPE = 57; // 44
8205 var COUNTERCLOCKWISEBOTTOMLEFT_TRANS_SUBTYPE = 58; // 45
8206 var CLOCKWISEBOTTOMRIGHT_TRANS_SUBTYPE = 59; // 46
8207 var COUNTERCLOCKWISETOPRIGHT_TRANS_SUBTYPE = 60; // 47
8208 var VERTICALLEFT_TRANS_SUBTYPE = 61; // 93
8209 var VERTICALRIGHT_TRANS_SUBTYPE = 62; // 94
8210 var HORIZONTALLEFT_TRANS_SUBTYPE = 63; // 95
8211 var HORIZONTALRIGHT_TRANS_SUBTYPE = 64; // 96
8212 var TOPLEFTCLOCKWISE_TRANS_SUBTYPE = 65; // 69
8213 var TOPRIGHTCLOCKWISE_TRANS_SUBTYPE = 66; // 70
8214 var BOTTOMRIGHTCLOCKWISE_TRANS_SUBTYPE = 67; // 71
8215 var BOTTOMLEFTCLOCKWISE_TRANS_SUBTYPE = 68; // 72
8216 var TOPLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE = 69; // 73
8217 var TOPRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE = 70; // 74
8218 var BOTTOMRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE = 71; // 75
8219 var BOTTOMLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE = 72; // 76
8220 var DOUBLEBARNDOOR_TRANS_SUBTYPE = 73; // 17
8221 var DOUBLEDIAMOND_TRANS_SUBTYPE = 74; // 18
8222 var VERTICALTOPSAME_TRANS_SUBTYPE = 75; // 77
8223 var VERTICALBOTTOMSAME_TRANS_SUBTYPE = 76; // 78
8224 var VERTICALTOPLEFTOPPOSITE_TRANS_SUBTYPE = 77; // 79
8225 var VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE = 78; // 80
8226 var HORIZONTALLEFTSAME_TRANS_SUBTYPE = 79; // 81
8227 var HORIZONTALRIGHTSAME_TRANS_SUBTYPE = 80; // 82
8228 var HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE = 81; // 83
8229 var HORIZONTALTOPRIGHTOPPOSITE_TRANS_SUBTYPE = 82; // 84
8230 var DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE = 83; // 85
8231 var DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE = 84; // 86
8232 var TWOBOXTOP_TRANS_SUBTYPE = 85; // 87
8233 var TWOBOXBOTTOM_TRANS_SUBTYPE = 86; // 88
8234 var TWOBOXLEFT_TRANS_SUBTYPE = 87; // 89
8235 var TWOBOXRIGHT_TRANS_SUBTYPE = 88; // 90
8236 var FOURBOXVERTICAL_TRANS_SUBTYPE = 89; // 91
8237 var FOURBOXHORIZONTAL_TRANS_SUBTYPE = 90; // 92
8239 var aTransitionSubtypeInMap = {
8240 'default' : DEFAULT_TRANS_SUBTYPE,
8241 'leftToRight' : LEFTTORIGHT_TRANS_SUBTYPE,
8242 'topToBottom' : TOPTOBOTTOM_TRANS_SUBTYPE,
8243 'cornersIn' : CORNERSIN_TRANS_SUBTYPE,
8244 'cornersOut' : CORNERSOUT_TRANS_SUBTYPE,
8245 'vertical' : VERTICAL_TRANS_SUBTYPE,
8246 'centerTop' : CENTERTOP_TRANS_SUBTYPE,
8247 'centerRight' : CENTERRIGHT_TRANS_SUBTYPE,
8248 'top' : TOP_TRANS_SUBTYPE,
8249 'right' : RIGHT_TRANS_SUBTYPE,
8250 'bottom' : BOTTOM_TRANS_SUBTYPE,
8251 'left' : LEFT_TRANS_SUBTYPE,
8252 'horizontal' : HORIZONTAL_TRANS_SUBTYPE,
8253 'down' : DOWN_TRANS_SUBTYPE,
8254 'circle' : CIRCLE_TRANS_SUBTYPE,
8255 'clockwiseTwelve' : CLOCKWISETWELVE_TRANS_SUBTYPE,
8256 'clockwiseThree' : CLOCKWISETHREE_TRANS_SUBTYPE,
8257 'clockwiseSix' : CLOCKWISESIX_TRANS_SUBTYPE,
8258 'clockwiseNine' : CLOCKWISENINE_TRANS_SUBTYPE,
8259 'clockwiseRight' : CLOCKWISERIGHT_TRANS_SUBTYPE,
8260 'clockwiseTop' : CLOCKWISETOP_TRANS_SUBTYPE,
8261 'clockwiseBottom' : CLOCKWISEBOTTOM_TRANS_SUBTYPE,
8262 'clockwiseLeft' : CLOCKWISELEFT_TRANS_SUBTYPE,
8263 'clockwiseTopLeft' : CLOCKWISETOPLEFT_TRANS_SUBTYPE,
8264 'counterClockwiseBottomLeft' : COUNTERCLOCKWISEBOTTOMLEFT_TRANS_SUBTYPE,
8265 'clockwiseBottomRight' : CLOCKWISEBOTTOMRIGHT_TRANS_SUBTYPE,
8266 'counterClockwiseTopRight' : COUNTERCLOCKWISETOPRIGHT_TRANS_SUBTYPE,
8267 'twoBladeVertical' : TWOBLADEVERTICAL_TRANS_SUBTYPE,
8268 'twoBladeHorizontal' : TWOBLADEHORIZONTAL_TRANS_SUBTYPE,
8269 'fourBlade' : FOURBLADE_TRANS_SUBTYPE,
8270 'fromLeft' : FROMLEFT_TRANS_SUBTYPE,
8271 'fromTop' : FROMTOP_TRANS_SUBTYPE,
8272 'fromRight' : FROMRIGHT_TRANS_SUBTYPE,
8273 'fromBottom' : FROMBOTTOM_TRANS_SUBTYPE,
8274 'crossfade' : CROSSFADE_TRANS_SUBTYPE,
8275 'fadeToColor' : FADETOCOLOR_TRANS_SUBTYPE,
8276 'fadeFromColor' : FADEFROMCOLOR_TRANS_SUBTYPE,
8277 'fadeOverColor' : FADEOVERCOLOR_TRANS_SUBTYPE,
8278 'threeBlade' : THREEBLADE_TRANS_SUBTYPE,
8279 'eightBlade' : EIGHTBLADE_TRANS_SUBTYPE,
8280 'oneBlade' : ONEBLADE_TRANS_SUBTYPE,
8281 'across' : ACROSS_TRANS_SUBTYPE,
8282 'topLeftVertical' : TOPLEFTVERTICAL_TRANS_SUBTYPE,
8283 'topLeftHorizontal' : TOPLEFTHORIZONTAL_TRANS_SUBTYPE,
8284 'topLeftDiagonal' : TOPLEFTDIAGONAL_TRANS_SUBTYPE,
8285 'topRightDiagonal' : TOPRIGHTDIAGONAL_TRANS_SUBTYPE,
8286 'bottomRightDiagonal' : BOTTOMRIGHTDIAGONAL_TRANS_SUBTYPE,
8287 'topLeftClockwise' : TOPLEFTCLOCKWISE_TRANS_SUBTYPE,
8288 'topRightClockwise' : TOPRIGHTCLOCKWISE_TRANS_SUBTYPE,
8289 'bottomRightClockwise' : BOTTOMRIGHTCLOCKWISE_TRANS_SUBTYPE,
8290 'bottomLeftClockwise' : BOTTOMLEFTCLOCKWISE_TRANS_SUBTYPE,
8291 'topLeftCounterClockwise' : TOPLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE,
8292 'topRightCounterClockwise' : TOPRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE,
8293 'bottomRightCounterClockwise' : BOTTOMRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE,
8294 'bottomLeftCounterClockwise' : BOTTOMLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE,
8295 'bottomLeftDiagonal' : BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE,
8296 'rectangle' : RECTANGLE_TRANS_SUBTYPE,
8297 'diamond' : DIAMOND_TRANS_SUBTYPE,
8298 'topLeft' : TOPLEFT_TRANS_SUBTYPE,
8299 'topRight' : TOPRIGHT_TRANS_SUBTYPE,
8300 'bottomRight' : BOTTOMRIGHT_TRANS_SUBTYPE,
8301 'bottomLeft' : BOTTOMLEFT_TRANS_SUBTYPE,
8302 'topCenter' : TOPCENTER_TRANS_SUBTYPE,
8303 'rightCenter' : RIGHTCENTER_TRANS_SUBTYPE,
8304 'bottomCenter' : BOTTOMCENTER_TRANS_SUBTYPE,
8305 'leftCenter' : LEFTCENTER_TRANS_SUBTYPE,
8306 'up' : UP_TRANS_SUBTYPE,
8307 'diagonalBottomLeft' : DIAGONALBOTTOMLEFT_TRANS_SUBTYPE,
8308 'diagonalTopLeft' : DIAGONALTOPLEFT_TRANS_SUBTYPE,
8309 'verticalLeft' : VERTICALLEFT_TRANS_SUBTYPE,
8310 'verticalRight' : VERTICALRIGHT_TRANS_SUBTYPE,
8311 'horizontalLeft' : HORIZONTALLEFT_TRANS_SUBTYPE,
8312 'horizontalRight' : HORIZONTALRIGHT_TRANS_SUBTYPE,
8313 'doubleBarnDoor' : DOUBLEBARNDOOR_TRANS_SUBTYPE,
8314 'doubleDiamond' : DOUBLEDIAMOND_TRANS_SUBTYPE,
8315 'verticalTopSame' : VERTICALTOPSAME_TRANS_SUBTYPE,
8316 'verticalBottomSame' : VERTICALBOTTOMSAME_TRANS_SUBTYPE,
8317 'verticalTopLeftOpposite' : VERTICALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
8318 'verticalBottomLeftOpposite' : VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE,
8319 'horizontalLeftSame' : HORIZONTALLEFTSAME_TRANS_SUBTYPE,
8320 'horizontalRightSame' : HORIZONTALRIGHTSAME_TRANS_SUBTYPE,
8321 'horizontalTopLeftOpposite' : HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
8322 'horizontalTopRightOpposite' : HORIZONTALTOPRIGHTOPPOSITE_TRANS_SUBTYPE,
8323 'diagonalBottomLeftOpposite' : DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE,
8324 'diagonalTopLeftOpposite' : DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
8325 'twoBoxTop' : TWOBOXTOP_TRANS_SUBTYPE,
8326 'twoBoxBottom' : TWOBOXBOTTOM_TRANS_SUBTYPE,
8327 'twoBoxLeft' : TWOBOXLEFT_TRANS_SUBTYPE,
8328 'twoBoxRight' : TWOBOXRIGHT_TRANS_SUBTYPE,
8329 'fourBoxVertical' : FOURBOXVERTICAL_TRANS_SUBTYPE,
8330 'fourBoxHorizontal' : FOURBOXHORIZONTAL_TRANS_SUBTYPE
8334 var TRANSITION_MODE_IN = 1;
8335 var TRANSITION_MODE_OUT = 0;
8337 var aTransitionModeOutMap = [ 'out', 'in' ];
8340 // Transition Reverse Methods
8342 // Ignore direction attribute altogether.
8343 // (If it has no sensible meaning for this transition.)
8344 var REVERSEMETHOD_IGNORE = 0;
8345 // Revert by changing the direction of the parameter sweep.
8346 // (From 1->0 instead of 0->1)
8347 var REVERSEMETHOD_INVERT_SWEEP = 1;
8348 // Revert by subtracting the generated polygon from the target bound rect.
8349 var REVERSEMETHOD_SUBTRACT_POLYGON = 2;
8350 // Combination of REVERSEMETHOD_INVERT_SWEEP and REVERSEMETHOD_SUBTRACT_POLYGON.
8351 var REVERSEMETHOD_SUBTRACT_AND_INVERT = 3;
8352 // Reverse by rotating polygon 180 degrees.
8353 var REVERSEMETHOD_ROTATE_180 = 4;
8354 // Reverse by flipping polygon at the y axis.
8355 var REVERSEMETHOD_FLIP_X = 5;
8356 // Reverse by flipping polygon at the x axis.
8357 var REVERSEMETHOD_FLIP_Y = 6;
8359 // eslint-disable-next-line no-unused-vars
8360 var aReverseMethodOutMap = ['ignore', 'invert sweep', 'subtract polygon',
8361 'subtract and invert', 'rotate 180', 'flip x', 'flip y'];
8364 // Transition filter info table
8366 var aTransitionInfoTable = {};
8368 // type: fake transition
8369 aTransitionInfoTable[0] = {};
8371 aTransitionInfoTable[0][0] =
8373 'class' : TRANSITION_INVALID,
8374 'rotationAngle' : 0.0,
8377 'reverseMethod' : REVERSEMETHOD_IGNORE,
8378 'outInvertsSweep' : false,
8379 'scaleIsotropically' : false
8382 aTransitionInfoTable[SNAKEWIPE_TRANSITION] = {};
8383 aTransitionInfoTable[SNAKEWIPE_TRANSITION][TOPLEFTVERTICAL_TRANS_SUBTYPE] =
8385 'class' : TRANSITION_CLIP_POLYPOLYGON,
8386 'rotationAngle' : -90.0,
8389 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8390 'outInvertsSweep' : true,
8391 'scaleIsotropically' : false
8393 aTransitionInfoTable[SNAKEWIPE_TRANSITION][TOPLEFTHORIZONTAL_TRANS_SUBTYPE] =
8395 'class' : TRANSITION_CLIP_POLYPOLYGON,
8396 'rotationAngle' : 0.0,
8399 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8400 'outInvertSweep' : true,
8401 'scaleIsotropically' : false
8403 aTransitionInfoTable[SNAKEWIPE_TRANSITION][TOPLEFTDIAGONAL_TRANS_SUBTYPE] =
8405 'class' : TRANSITION_CLIP_POLYPOLYGON,
8406 'rotationAngle' : 0.0,
8409 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8410 'outInvertSweep' : true,
8411 'scaleIsotropically' : false
8413 aTransitionInfoTable[SNAKEWIPE_TRANSITION][TOPRIGHTDIAGONAL_TRANS_SUBTYPE] =
8415 'class' : TRANSITION_CLIP_POLYPOLYGON,
8416 'rotationAngle' : 0.0,
8419 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8420 'outInvertSweep' : true,
8421 'scaleIsotropically' : false
8423 aTransitionInfoTable[SNAKEWIPE_TRANSITION][BOTTOMRIGHTDIAGONAL_TRANS_SUBTYPE] =
8425 'class' : TRANSITION_CLIP_POLYPOLYGON,
8426 'rotationAngle' : 180.0,
8429 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8430 'outInvertSweep' : true,
8431 'scaleIsotropically' : false
8433 aTransitionInfoTable[SNAKEWIPE_TRANSITION][BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE] =
8435 'class' : TRANSITION_CLIP_POLYPOLYGON,
8436 'rotationAngle' : 180.0,
8439 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8440 'outInvertSweep' : true,
8441 'scaleIsotropically' : false
8444 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION] = {};
8445 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][VERTICALTOPSAME_TRANS_SUBTYPE] =
8447 'class' : TRANSITION_CLIP_POLYPOLYGON,
8448 'rotationAngle' : 0.0,
8451 'reverseMethod' : REVERSEMETHOD_IGNORE,
8452 'outInvertSweep' : true,
8453 'scaleIsotropically' : false
8455 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][VERTICALBOTTOMSAME_TRANS_SUBTYPE] =
8457 'class' : TRANSITION_CLIP_POLYPOLYGON,
8458 'rotationAngle' : 180.0,
8461 'reverseMethod' : REVERSEMETHOD_IGNORE,
8462 'outInvertSweep' : true,
8463 'scaleIsotropically' : false
8465 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][VERTICALTOPLEFTOPPOSITE_TRANS_SUBTYPE] =
8467 'class' : TRANSITION_CLIP_POLYPOLYGON,
8468 'rotationAngle' : 0.0,
8471 'reverseMethod' : REVERSEMETHOD_IGNORE,
8472 'outInvertSweep' : true,
8473 'scaleIsotropically' : false
8475 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE] =
8477 'class' : TRANSITION_CLIP_POLYPOLYGON,
8478 'rotationAngle' : 0.0,
8481 'reverseMethod' : REVERSEMETHOD_IGNORE,
8482 'outInvertSweep' : true,
8483 'scaleIsotropically' : false
8485 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][HORIZONTALLEFTSAME_TRANS_SUBTYPE] =
8487 'class' : TRANSITION_CLIP_POLYPOLYGON,
8488 'rotationAngle' : -90.0,
8491 'reverseMethod' : REVERSEMETHOD_IGNORE,
8492 'outInvertSweep' : true,
8493 'scaleIsotropically' : false
8495 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][HORIZONTALRIGHTSAME_TRANS_SUBTYPE] =
8497 'class' : TRANSITION_CLIP_POLYPOLYGON,
8498 'rotationAngle' : 90.0,
8501 'reverseMethod' : REVERSEMETHOD_IGNORE,
8502 'outInvertSweep' : true,
8503 'scaleIsotropically' : false
8505 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE] =
8507 'class' : TRANSITION_CLIP_POLYPOLYGON,
8508 'rotationAngle' : -90.0,
8511 'reverseMethod' : REVERSEMETHOD_IGNORE,
8512 'outInvertSweep' : true,
8513 'scaleIsotropically' : false
8515 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][HORIZONTALTOPRIGHTOPPOSITE_TRANS_SUBTYPE] =
8517 'class' : TRANSITION_CLIP_POLYPOLYGON,
8518 'rotationAngle' : -90.0,
8521 'reverseMethod' : REVERSEMETHOD_IGNORE,
8522 'outInvertSweep' : true,
8523 'scaleIsotropically' : false
8525 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE] =
8527 'class' : TRANSITION_CLIP_POLYPOLYGON,
8528 'rotationAngle' : 0.0,
8531 'reverseMethod' : REVERSEMETHOD_IGNORE,
8532 'outInvertSweep' : true,
8533 'scaleIsotropically' : false
8535 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE] =
8537 'class' : TRANSITION_CLIP_POLYPOLYGON,
8538 'rotationAngle' : 0.0,
8541 'reverseMethod' : REVERSEMETHOD_IGNORE,
8542 'outInvertSweep' : true,
8543 'scaleIsotropically' : false
8546 aTransitionInfoTable[SPIRALWIPE_TRANSITION] = {};
8547 aTransitionInfoTable[SPIRALWIPE_TRANSITION][TOPLEFTCLOCKWISE_TRANS_SUBTYPE] =
8549 'class' : TRANSITION_CLIP_POLYPOLYGON,
8550 'rotationAngle' : 0.0,
8553 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8554 'outInvertSweep' : true,
8555 'scaleIsotropically' : false
8557 aTransitionInfoTable[SPIRALWIPE_TRANSITION][TOPRIGHTCLOCKWISE_TRANS_SUBTYPE] =
8559 'class' : TRANSITION_CLIP_POLYPOLYGON,
8560 'rotationAngle' : 90.0,
8563 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8564 'outInvertSweep' : true,
8565 'scaleIsotropically' : false
8567 aTransitionInfoTable[SPIRALWIPE_TRANSITION][BOTTOMRIGHTCLOCKWISE_TRANS_SUBTYPE] =
8569 'class' : TRANSITION_CLIP_POLYPOLYGON,
8570 'rotationAngle' : 180.0,
8573 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8574 'outInvertSweep' : true,
8575 'scaleIsotropically' : false
8577 aTransitionInfoTable[SPIRALWIPE_TRANSITION][BOTTOMLEFTCLOCKWISE_TRANS_SUBTYPE] =
8579 'class' : TRANSITION_CLIP_POLYPOLYGON,
8580 'rotationAngle' : 270.0,
8583 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8584 'outInvertSweep' : true,
8585 'scaleIsotropically' : false
8587 aTransitionInfoTable[SPIRALWIPE_TRANSITION][TOPLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE] =
8589 'class' : TRANSITION_CLIP_POLYPOLYGON,
8590 'rotationAngle' : 90.0,
8593 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8594 'outInvertSweep' : true,
8595 'scaleIsotropically' : false
8597 aTransitionInfoTable[SPIRALWIPE_TRANSITION][TOPRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE] =
8599 'class' : TRANSITION_CLIP_POLYPOLYGON,
8600 'rotationAngle' : 180.0,
8603 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8604 'outInvertSweep' : true,
8605 'scaleIsotropically' : false
8607 aTransitionInfoTable[SPIRALWIPE_TRANSITION][BOTTOMRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE] =
8609 'class' : TRANSITION_CLIP_POLYPOLYGON,
8610 'rotationAngle' : 270.0,
8613 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8614 'outInvertSweep' : true,
8615 'scaleIsotropically' : false
8617 aTransitionInfoTable[SPIRALWIPE_TRANSITION][BOTTOMLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE] =
8619 'class' : TRANSITION_CLIP_POLYPOLYGON,
8620 'rotationAngle' : 0.0,
8623 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8624 'outInvertSweep' : true,
8625 'scaleIsotropically' : false
8628 aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION] = {};
8629 aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][TWOBOXTOP_TRANS_SUBTYPE] =
8631 'class' : TRANSITION_CLIP_POLYPOLYGON,
8632 'rotationAngle' : 90.0,
8635 'reverseMethod' : REVERSEMETHOD_IGNORE,
8636 'outInvertSweep' : true,
8637 'scaleIsotropically' : false
8639 aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][TWOBOXBOTTOM_TRANS_SUBTYPE] =
8641 'class' : TRANSITION_CLIP_POLYPOLYGON,
8642 'rotationAngle' : -90.0,
8645 'reverseMethod' : REVERSEMETHOD_IGNORE,
8646 'outInvertSweep' : true,
8647 'scaleIsotropically' : false
8649 aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][TWOBOXLEFT_TRANS_SUBTYPE] =
8651 'class' : TRANSITION_CLIP_POLYPOLYGON,
8652 'rotationAngle' : 0.0,
8655 'reverseMethod' : REVERSEMETHOD_IGNORE,
8656 'outInvertSweep' : true,
8657 'scaleIsotropically' : false
8659 aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][TWOBOXRIGHT_TRANS_SUBTYPE] =
8661 'class' : TRANSITION_CLIP_POLYPOLYGON,
8662 'rotationAngle' : 180.0,
8665 'reverseMethod' : REVERSEMETHOD_IGNORE,
8666 'outInvertSweep' : true,
8667 'scaleIsotropically' : false
8669 aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][FOURBOXVERTICAL_TRANS_SUBTYPE] =
8671 'class' : TRANSITION_CLIP_POLYPOLYGON,
8672 'rotationAngle' : 90.0,
8675 'reverseMethod' : REVERSEMETHOD_IGNORE,
8676 'outInvertSweep' : true,
8677 'scaleIsotropically' : false
8679 aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][FOURBOXHORIZONTAL_TRANS_SUBTYPE] =
8681 'class' : TRANSITION_CLIP_POLYPOLYGON,
8682 'rotationAngle' : 0.0,
8685 'reverseMethod' : REVERSEMETHOD_IGNORE,
8686 'outInvertSweep' : true,
8687 'scaleIsotropically' : false
8690 aTransitionInfoTable[BARNDOORWIPE_TRANSITION] = {};
8691 aTransitionInfoTable[BARNDOORWIPE_TRANSITION][VERTICAL_TRANS_SUBTYPE] =
8693 'class' : TRANSITION_CLIP_POLYPOLYGON,
8694 'rotationAngle': 0.0,
8697 'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
8698 'outInvertsSweep': true,
8699 'scaleIsotropically': false
8701 aTransitionInfoTable[BARNDOORWIPE_TRANSITION][HORIZONTAL_TRANS_SUBTYPE] =
8703 'class' : TRANSITION_CLIP_POLYPOLYGON,
8704 'rotationAngle': 90.0,
8707 'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
8708 'outInvertsSweep': true,
8709 'scaleIsotropically': false
8711 aTransitionInfoTable[BARNDOORWIPE_TRANSITION][DIAGONALBOTTOMLEFT_TRANS_SUBTYPE] =
8713 'class' : TRANSITION_CLIP_POLYPOLYGON,
8714 'rotationAngle': 45.0,
8715 'scaleX': Math.SQRT2,
8716 'scaleY': Math.SQRT2,
8717 'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
8718 'outInvertsSweep': true,
8719 'scaleIsotropically': false
8721 aTransitionInfoTable[BARNDOORWIPE_TRANSITION][DIAGONALTOPLEFT_TRANS_SUBTYPE] =
8723 'class' : TRANSITION_CLIP_POLYPOLYGON,
8724 'rotationAngle': -45.0,
8725 'scaleX': Math.SQRT2,
8726 'scaleY': Math.SQRT2,
8727 'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
8728 'outInvertsSweep': true,
8729 'scaleIsotropically': false
8732 aTransitionInfoTable[MISCDIAGONALWIPE_TRANSITION] = {};
8733 aTransitionInfoTable[MISCDIAGONALWIPE_TRANSITION][DOUBLEBARNDOOR_TRANS_SUBTYPE] =
8735 'class' : TRANSITION_CLIP_POLYPOLYGON,
8736 'rotationAngle': 45.0,
8737 'scaleX': Math.SQRT2,
8738 'scaleY': Math.SQRT2,
8739 'reverseMethod': REVERSEMETHOD_IGNORE,
8740 'outInvertsSweep': true,
8741 'scaleIsotropically': false
8743 aTransitionInfoTable[MISCDIAGONALWIPE_TRANSITION][DOUBLEDIAMOND_TRANS_SUBTYPE] =
8745 'class' : TRANSITION_CLIP_POLYPOLYGON,
8746 'rotationAngle': 0.0,
8749 'reverseMethod': REVERSEMETHOD_IGNORE,
8750 'outInvertsSweep': true,
8751 'scaleIsotropically': false
8754 aTransitionInfoTable[IRISWIPE_TRANSITION] = {};
8755 aTransitionInfoTable[IRISWIPE_TRANSITION][RECTANGLE_TRANS_SUBTYPE] =
8757 'class' : TRANSITION_CLIP_POLYPOLYGON,
8758 'rotationAngle': 0.0,
8761 'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
8762 'outInvertsSweep': true,
8763 'scaleIsotropically': false
8766 aTransitionInfoTable[IRISWIPE_TRANSITION][DIAMOND_TRANS_SUBTYPE] =
8768 'class' : TRANSITION_CLIP_POLYPOLYGON,
8769 'rotationAngle': 45.0,
8770 'scaleX': Math.SQRT2,
8771 'scaleY': Math.SQRT2,
8772 'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
8773 'outInvertsSweep': true,
8774 'scaleIsotropically': false
8777 aTransitionInfoTable[ZIGZAGWIPE_TRANSITION] = {};
8778 aTransitionInfoTable[ZIGZAGWIPE_TRANSITION][LEFTTORIGHT_TRANS_SUBTYPE] =
8780 'class' : TRANSITION_CLIP_POLYPOLYGON,
8781 'rotationAngle' : 0.0,
8784 'reverseMethod' : REVERSEMETHOD_FLIP_X,
8785 'outInvertsSweep' : true,
8786 'scaleIsotropically' : false
8788 aTransitionInfoTable[ZIGZAGWIPE_TRANSITION][TOPTOBOTTOM_TRANS_SUBTYPE] =
8790 'class' : TRANSITION_CLIP_POLYPOLYGON,
8791 'rotationAngle' : 90.0,
8794 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8795 'outInvertsSweep' : true,
8796 'scaleIsotropically' : false
8799 aTransitionInfoTable[BARNZIGZAGWIPE_TRANSITION] = {};
8800 aTransitionInfoTable[BARNZIGZAGWIPE_TRANSITION][VERTICAL_TRANS_SUBTYPE] =
8802 'class' : TRANSITION_CLIP_POLYPOLYGON,
8803 'rotationAngle' : 0.0,
8806 'reverseMethod' : REVERSEMETHOD_IGNORE,
8807 'outInvertsSweep' : true,
8808 'scaleIsotropically' : false
8810 aTransitionInfoTable[BARNZIGZAGWIPE_TRANSITION][HORIZONTAL_TRANS_SUBTYPE] =
8812 'class' : TRANSITION_CLIP_POLYPOLYGON,
8813 'rotationAngle' : 90.0,
8816 'reverseMethod' : REVERSEMETHOD_IGNORE,
8817 'outInvertsSweep' : true,
8818 'scaleIsotropically' : false
8821 aTransitionInfoTable[BARWIPE_TRANSITION] = {};
8822 aTransitionInfoTable[BARWIPE_TRANSITION][LEFTTORIGHT_TRANS_SUBTYPE] =
8824 'class' : TRANSITION_CLIP_POLYPOLYGON,
8825 'rotationAngle' : 0.0,
8828 'reverseMethod' : REVERSEMETHOD_FLIP_X,
8829 'outInvertsSweep' : false,
8830 'scaleIsotropically' : false
8832 aTransitionInfoTable[BARWIPE_TRANSITION][TOPTOBOTTOM_TRANS_SUBTYPE] =
8834 'class' : TRANSITION_CLIP_POLYPOLYGON,
8835 'rotationAngle' : 90.0,
8838 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8839 'outInvertsSweep' : false,
8840 'scaleIsotropically' : false
8843 aTransitionInfoTable[WATERFALLWIPE_TRANSITION] = {};
8844 aTransitionInfoTable[WATERFALLWIPE_TRANSITION][VERTICALLEFT_TRANS_SUBTYPE] =
8846 'class' : TRANSITION_CLIP_POLYPOLYGON,
8847 'rotationAngle' : 0.0,
8850 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8851 'outInvertsSweep' : true,
8852 'scaleIsotropically' : false
8854 aTransitionInfoTable[WATERFALLWIPE_TRANSITION][VERTICALRIGHT_TRANS_SUBTYPE] =
8856 'class' : TRANSITION_CLIP_POLYPOLYGON,
8857 'rotationAngle' : 0.0,
8860 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8861 'outInvertsSweep' : true,
8862 'scaleIsotropically' : false
8864 aTransitionInfoTable[WATERFALLWIPE_TRANSITION][HORIZONTALLEFT_TRANS_SUBTYPE] =
8866 'class' : TRANSITION_CLIP_POLYPOLYGON,
8867 'rotationAngle' : -90.0,
8870 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8871 'outInvertsSweep' : true,
8872 'scaleIsotropically' : false
8874 aTransitionInfoTable[WATERFALLWIPE_TRANSITION][HORIZONTALRIGHT_TRANS_SUBTYPE] =
8876 'class' : TRANSITION_CLIP_POLYPOLYGON,
8877 'rotationAngle' : 90.0,
8880 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8881 'outInvertsSweep' : true,
8882 'scaleIsotropically' : false
8885 aTransitionInfoTable[BOXWIPE_TRANSITION] = {};
8886 aTransitionInfoTable[BOXWIPE_TRANSITION][TOPLEFT_TRANS_SUBTYPE] =
8888 'class' : TRANSITION_CLIP_POLYPOLYGON,
8889 'rotationAngle' : 0.0,
8892 'reverseMethod' : REVERSEMETHOD_IGNORE,
8893 'outInvertsSweep' : true,
8894 'scaleIsotropically' : false
8896 aTransitionInfoTable[BOXWIPE_TRANSITION][TOPRIGHT_TRANS_SUBTYPE] =
8898 'class' : TRANSITION_CLIP_POLYPOLYGON,
8899 'rotationAngle' : 90.0,
8902 'reverseMethod' : REVERSEMETHOD_IGNORE,
8903 'outInvertsSweep' : true,
8904 'scaleIsotropically' : false
8906 aTransitionInfoTable[BOXWIPE_TRANSITION][BOTTOMRIGHT_TRANS_SUBTYPE] =
8908 'class' : TRANSITION_CLIP_POLYPOLYGON,
8909 'rotationAngle' : 180.0,
8912 'reverseMethod' : REVERSEMETHOD_IGNORE,
8913 'outInvertsSweep' : true,
8914 'scaleIsotropically' : false
8916 aTransitionInfoTable[BOXWIPE_TRANSITION][BOTTOMLEFT_TRANS_SUBTYPE] =
8918 'class' : TRANSITION_CLIP_POLYPOLYGON,
8919 'rotationAngle' : -90.0,
8922 'reverseMethod' : REVERSEMETHOD_IGNORE,
8923 'outInvertsSweep' : true,
8924 'scaleIsotropically' : false
8926 aTransitionInfoTable[BOXWIPE_TRANSITION][TOPCENTER_TRANS_SUBTYPE] =
8928 'class' : TRANSITION_CLIP_POLYPOLYGON,
8929 'rotationAngle' : 0.0,
8932 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8933 'outInvertsSweep' : true,
8934 'scaleIsotropically' : false
8936 aTransitionInfoTable[BOXWIPE_TRANSITION][RIGHTCENTER_TRANS_SUBTYPE] =
8938 'class' : TRANSITION_CLIP_POLYPOLYGON,
8939 'rotationAngle' : 90.0,
8942 'reverseMethod' : REVERSEMETHOD_FLIP_X,
8943 'outInvertsSweep' : true,
8944 'scaleIsotropically' : false
8946 aTransitionInfoTable[BOXWIPE_TRANSITION][BOTTOMCENTER_TRANS_SUBTYPE] =
8948 'class' : TRANSITION_CLIP_POLYPOLYGON,
8949 'rotationAngle' : 180.0,
8952 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8953 'outInvertsSweep' : true,
8954 'scaleIsotropically' : false
8956 aTransitionInfoTable[BOXWIPE_TRANSITION][LEFTCENTER_TRANS_SUBTYPE] =
8958 'class' : TRANSITION_CLIP_POLYPOLYGON,
8959 'rotationAngle' : -90.0,
8962 'reverseMethod' : REVERSEMETHOD_FLIP_X,
8963 'outInvertsSweep' : true,
8964 'scaleIsotropically' : false
8967 aTransitionInfoTable[FOURBOXWIPE_TRANSITION] = {};
8968 aTransitionInfoTable[FOURBOXWIPE_TRANSITION][CORNERSIN_TRANS_SUBTYPE] =
8969 aTransitionInfoTable[FOURBOXWIPE_TRANSITION][CORNERSOUT_TRANS_SUBTYPE] =
8971 'class' : TRANSITION_CLIP_POLYPOLYGON,
8972 'rotationAngle' : 0.0,
8975 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8976 'outInvertsSweep' : true,
8977 'scaleIsotropically' : false
8980 aTransitionInfoTable[ELLIPSEWIPE_TRANSITION] = {};
8981 aTransitionInfoTable[ELLIPSEWIPE_TRANSITION][CIRCLE_TRANS_SUBTYPE] =
8983 'class' : TRANSITION_CLIP_POLYPOLYGON,
8984 'rotationAngle' : 0.0,
8987 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8988 'outInvertsSweep' : true,
8989 'scaleIsotropically' : true
8991 aTransitionInfoTable[ELLIPSEWIPE_TRANSITION][HORIZONTAL_TRANS_SUBTYPE] =
8993 'class' : TRANSITION_CLIP_POLYPOLYGON,
8994 'rotationAngle' : 0.0,
8997 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8998 'outInvertsSweep' : true,
8999 'scaleIsotropically' : false
9001 aTransitionInfoTable[ELLIPSEWIPE_TRANSITION][VERTICAL_TRANS_SUBTYPE] =
9003 'class' : TRANSITION_CLIP_POLYPOLYGON,
9004 'rotationAngle' : 90.0,
9007 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9008 'outInvertsSweep' : true,
9009 'scaleIsotropically' : false
9012 aTransitionInfoTable[CLOCKWIPE_TRANSITION] = {};
9013 aTransitionInfoTable[CLOCKWIPE_TRANSITION][CLOCKWISETWELVE_TRANS_SUBTYPE] =
9015 'class' : TRANSITION_CLIP_POLYPOLYGON,
9016 'rotationAngle' : 0.0,
9019 'reverseMethod' : REVERSEMETHOD_FLIP_X,
9020 'outInvertsSweep' : true,
9021 'scaleIsotropically' : false
9023 aTransitionInfoTable[CLOCKWIPE_TRANSITION][CLOCKWISETHREE_TRANS_SUBTYPE] =
9025 'class' : TRANSITION_CLIP_POLYPOLYGON,
9026 'rotationAngle' : 90.0,
9029 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
9030 'outInvertsSweep' : true,
9031 'scaleIsotropically' : false
9033 aTransitionInfoTable[CLOCKWIPE_TRANSITION][CLOCKWISESIX_TRANS_SUBTYPE] =
9035 'class' : TRANSITION_CLIP_POLYPOLYGON,
9036 'rotationAngle' : 180.0,
9039 'reverseMethod' : REVERSEMETHOD_FLIP_X,
9040 'outInvertsSweep' : true,
9041 'scaleIsotropically' : false
9043 aTransitionInfoTable[CLOCKWIPE_TRANSITION][CLOCKWISENINE_TRANS_SUBTYPE] =
9045 'class' : TRANSITION_CLIP_POLYPOLYGON,
9046 'rotationAngle' : 270.0,
9049 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
9050 'outInvertsSweep' : true,
9051 'scaleIsotropically' : false
9054 aTransitionInfoTable[VEEWIPE_TRANSITION] = {};
9055 aTransitionInfoTable[VEEWIPE_TRANSITION][DOWN_TRANS_SUBTYPE] =
9057 'class' : TRANSITION_CLIP_POLYPOLYGON,
9058 'rotationAngle' : 0.0,
9061 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
9062 'outInvertsSweep' : true,
9063 'scaleIsotropically' : false
9065 aTransitionInfoTable[VEEWIPE_TRANSITION][LEFT_TRANS_SUBTYPE] =
9067 'class' : TRANSITION_CLIP_POLYPOLYGON,
9068 'rotationAngle' : 90.0,
9071 'reverseMethod' : REVERSEMETHOD_FLIP_X,
9072 'outInvertsSweep' : true,
9073 'scaleIsotropically' : false
9075 aTransitionInfoTable[VEEWIPE_TRANSITION][UP_TRANS_SUBTYPE] =
9077 'class' : TRANSITION_CLIP_POLYPOLYGON,
9078 'rotationAngle' : 180.0,
9081 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
9082 'outInvertsSweep' : true,
9083 'scaleIsotropically' : false
9085 aTransitionInfoTable[VEEWIPE_TRANSITION][RIGHT_TRANS_SUBTYPE] =
9087 'class' : TRANSITION_CLIP_POLYPOLYGON,
9088 'rotationAngle' : -90.0,
9091 'reverseMethod' : REVERSEMETHOD_FLIP_X,
9092 'outInvertsSweep' : true,
9093 'scaleIsotropically' : false
9096 aTransitionInfoTable[FANWIPE_TRANSITION] = {};
9097 aTransitionInfoTable[FANWIPE_TRANSITION][CENTERTOP_TRANS_SUBTYPE] =
9099 'class' : TRANSITION_CLIP_POLYPOLYGON,
9100 'rotationAngle' : 0.0,
9103 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
9104 'outInvertsSweep' : true,
9105 'scaleIsotropically' : false
9107 aTransitionInfoTable[FANWIPE_TRANSITION][CENTERRIGHT_TRANS_SUBTYPE] =
9109 'class' : TRANSITION_CLIP_POLYPOLYGON,
9110 'rotationAngle' : 90.0,
9113 'reverseMethod' : REVERSEMETHOD_FLIP_X,
9114 'outInvertsSweep' : true,
9115 'scaleIsotropically' : false
9117 aTransitionInfoTable[FANWIPE_TRANSITION][TOP_TRANS_SUBTYPE] =
9119 'class' : TRANSITION_CLIP_POLYPOLYGON,
9120 'rotationAngle' : 180.0,
9123 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
9124 'outInvertsSweep' : true,
9125 'scaleIsotropically' : false
9127 aTransitionInfoTable[FANWIPE_TRANSITION][RIGHT_TRANS_SUBTYPE] =
9129 'class' : TRANSITION_CLIP_POLYPOLYGON,
9130 'rotationAngle' : -90.0,
9133 'reverseMethod' : REVERSEMETHOD_FLIP_X,
9134 'outInvertsSweep' : true,
9135 'scaleIsotropically' : false
9137 aTransitionInfoTable[FANWIPE_TRANSITION][BOTTOM_TRANS_SUBTYPE] =
9139 'class' : TRANSITION_CLIP_POLYPOLYGON,
9140 'rotationAngle' : 180.0,
9143 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
9144 'outInvertsSweep' : true,
9145 'scaleIsotropically' : false
9147 aTransitionInfoTable[FANWIPE_TRANSITION][LEFT_TRANS_SUBTYPE] =
9149 'class' : TRANSITION_CLIP_POLYPOLYGON,
9150 'rotationAngle' : 90.0,
9153 'reverseMethod' : REVERSEMETHOD_FLIP_X,
9154 'outInvertsSweep' : true,
9155 'scaleIsotropically' : false
9159 aTransitionInfoTable[PINWHEELWIPE_TRANSITION] = {};
9160 aTransitionInfoTable[PINWHEELWIPE_TRANSITION][ONEBLADE_TRANS_SUBTYPE] =
9161 aTransitionInfoTable[PINWHEELWIPE_TRANSITION][TWOBLADEVERTICAL_TRANS_SUBTYPE] =
9162 aTransitionInfoTable[PINWHEELWIPE_TRANSITION][THREEBLADE_TRANS_SUBTYPE] =
9163 aTransitionInfoTable[PINWHEELWIPE_TRANSITION][FOURBLADE_TRANS_SUBTYPE] =
9164 aTransitionInfoTable[PINWHEELWIPE_TRANSITION][EIGHTBLADE_TRANS_SUBTYPE] =
9166 'class' : TRANSITION_CLIP_POLYPOLYGON,
9167 'rotationAngle' : 0.0,
9170 'reverseMethod' : REVERSEMETHOD_FLIP_X,
9171 'outInvertsSweep' : true,
9172 'scaleIsotropically' : true
9174 aTransitionInfoTable[PINWHEELWIPE_TRANSITION][TWOBLADEHORIZONTAL_TRANS_SUBTYPE] =
9176 'class' : TRANSITION_CLIP_POLYPOLYGON,
9177 'rotationAngle' : -90.0,
9180 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
9181 'outInvertsSweep' : true,
9182 'scaleIsotropically' : true
9185 aTransitionInfoTable[PUSHWIPE_TRANSITION] = {};
9186 aTransitionInfoTable[PUSHWIPE_TRANSITION][FROMLEFT_TRANS_SUBTYPE] =
9187 aTransitionInfoTable[PUSHWIPE_TRANSITION][FROMTOP_TRANS_SUBTYPE] =
9188 aTransitionInfoTable[PUSHWIPE_TRANSITION][FROMRIGHT_TRANS_SUBTYPE] =
9189 aTransitionInfoTable[PUSHWIPE_TRANSITION][FROMBOTTOM_TRANS_SUBTYPE] =
9191 'class' : TRANSITION_SPECIAL,
9192 'rotationAngle' : 0.0,
9195 'reverseMethod' : REVERSEMETHOD_IGNORE,
9196 'outInvertsSweep' : true,
9197 'scaleIsotropically' : false
9201 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION] = {};
9202 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISETOP_TRANS_SUBTYPE] =
9204 'class' : TRANSITION_CLIP_POLYPOLYGON,
9205 'rotationAngle' : 0.0,
9208 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9209 'outInvertsSweep' : true,
9210 'scaleIsotropically' : false
9212 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISERIGHT_TRANS_SUBTYPE] =
9214 'class' : TRANSITION_CLIP_POLYPOLYGON,
9215 'rotationAngle' : 90.0,
9218 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9219 'outInvertsSweep' : true,
9220 'scaleIsotropically' : false
9222 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISEBOTTOM_TRANS_SUBTYPE] =
9224 'class' : TRANSITION_CLIP_POLYPOLYGON,
9225 'rotationAngle' : 180.0,
9228 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9229 'outInvertsSweep' : true,
9230 'scaleIsotropically' : false
9232 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISELEFT_TRANS_SUBTYPE] =
9234 'class' : TRANSITION_CLIP_POLYPOLYGON,
9235 'rotationAngle' : 270.0,
9238 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9239 'outInvertsSweep' : true,
9240 'scaleIsotropically' : false
9242 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISETOPLEFT_TRANS_SUBTYPE] =
9244 'class' : TRANSITION_CLIP_POLYPOLYGON,
9245 'rotationAngle' : 0.0,
9248 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9249 'outInvertsSweep' : true,
9250 'scaleIsotropically' : false
9252 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][COUNTERCLOCKWISEBOTTOMLEFT_TRANS_SUBTYPE] =
9254 'class' : TRANSITION_CLIP_POLYPOLYGON,
9255 'rotationAngle' : 180.0,
9258 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9259 'outInvertsSweep' : true,
9260 'scaleIsotropically' : false
9262 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISEBOTTOMRIGHT_TRANS_SUBTYPE] =
9264 'class' : TRANSITION_CLIP_POLYPOLYGON,
9265 'rotationAngle' : 180.0,
9268 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9269 'outInvertsSweep' : true,
9270 'scaleIsotropically' : false
9272 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][COUNTERCLOCKWISETOPRIGHT_TRANS_SUBTYPE] =
9274 'class' : TRANSITION_CLIP_POLYPOLYGON,
9275 'rotationAngle' : 0.0,
9278 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9279 'outInvertsSweep' : true,
9280 'scaleIsotropically' : false
9283 aTransitionInfoTable[SLIDEWIPE_TRANSITION] = {};
9284 aTransitionInfoTable[SLIDEWIPE_TRANSITION][FROMLEFT_TRANS_SUBTYPE] =
9285 aTransitionInfoTable[SLIDEWIPE_TRANSITION][FROMTOP_TRANS_SUBTYPE] =
9286 aTransitionInfoTable[SLIDEWIPE_TRANSITION][FROMRIGHT_TRANS_SUBTYPE] =
9287 aTransitionInfoTable[SLIDEWIPE_TRANSITION][FROMBOTTOM_TRANS_SUBTYPE] =
9289 'class' : TRANSITION_SPECIAL,
9290 'rotationAngle' : 0.0,
9293 'reverseMethod' : REVERSEMETHOD_IGNORE,
9294 'outInvertsSweep' : true,
9295 'scaleIsotropically' : false
9298 aTransitionInfoTable[FADE_TRANSITION] = {};
9299 aTransitionInfoTable[FADE_TRANSITION][CROSSFADE_TRANS_SUBTYPE] =
9300 aTransitionInfoTable[FADE_TRANSITION][FADETOCOLOR_TRANS_SUBTYPE] =
9301 aTransitionInfoTable[FADE_TRANSITION][FADEFROMCOLOR_TRANS_SUBTYPE] =
9302 aTransitionInfoTable[FADE_TRANSITION][FADEOVERCOLOR_TRANS_SUBTYPE] =
9304 'class' : TRANSITION_SPECIAL,
9305 'rotationAngle' : 0.0,
9308 'reverseMethod' : REVERSEMETHOD_IGNORE,
9309 'outInvertsSweep' : true,
9310 'scaleIsotropically' : false
9314 aTransitionInfoTable[RANDOMBARWIPE_TRANSITION] = {};
9315 aTransitionInfoTable[RANDOMBARWIPE_TRANSITION][VERTICAL_TRANS_SUBTYPE] =
9317 'class' : TRANSITION_CLIP_POLYPOLYGON,
9318 'rotationAngle' : 90.0,
9321 'reverseMethod' : REVERSEMETHOD_IGNORE,
9322 'outInvertsSweep' : true,
9323 'scaleIsotropically' : false
9325 aTransitionInfoTable[RANDOMBARWIPE_TRANSITION][HORIZONTAL_TRANS_SUBTYPE] =
9327 'class' : TRANSITION_CLIP_POLYPOLYGON,
9328 'rotationAngle' : 0.0,
9331 'reverseMethod' : REVERSEMETHOD_IGNORE,
9332 'outInvertsSweep' : true,
9333 'scaleIsotropically' : false
9336 aTransitionInfoTable[CHECKERBOARDWIPE_TRANSITION] = {};
9337 aTransitionInfoTable[CHECKERBOARDWIPE_TRANSITION][DOWN_TRANS_SUBTYPE] =
9339 'class' : TRANSITION_CLIP_POLYPOLYGON,
9340 'rotationAngle' : 90.0,
9343 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
9344 'outInvertsSweep' : true,
9345 'scaleIsotropically' : false
9347 aTransitionInfoTable[CHECKERBOARDWIPE_TRANSITION][ACROSS_TRANS_SUBTYPE] =
9349 'class' : TRANSITION_CLIP_POLYPOLYGON,
9350 'rotationAngle' : 0.0,
9353 'reverseMethod' : REVERSEMETHOD_FLIP_X,
9354 'outInvertsSweep' : true,
9355 'scaleIsotropically' : false
9358 aTransitionInfoTable[DISSOLVE_TRANSITION] = {};
9359 aTransitionInfoTable[DISSOLVE_TRANSITION][DEFAULT_TRANS_SUBTYPE] =
9361 'class' : TRANSITION_CLIP_POLYPOLYGON,
9362 'rotationAngle' : 0.0,
9365 'reverseMethod' : REVERSEMETHOD_IGNORE,
9366 'outInvertsSweep' : true,
9367 'scaleIsotropically' : true
9371 // Transition tables
9373 function createStateTransitionTable()
9377 aSTT[RESTART_MODE_NEVER] = {};
9378 aSTT[RESTART_MODE_WHEN_NOT_ACTIVE] = {};
9379 aSTT[RESTART_MODE_ALWAYS] = {};
9381 // transition table for restart=NEVER, fill=REMOVE
9383 aSTT[RESTART_MODE_NEVER][FILL_MODE_REMOVE] = {};
9384 aTable[INVALID_NODE] = INVALID_NODE;
9385 aTable[UNRESOLVED_NODE] = RESOLVED_NODE | ENDED_NODE;
9386 aTable[RESOLVED_NODE] = ACTIVE_NODE | ENDED_NODE;
9387 aTable[ACTIVE_NODE] = ENDED_NODE;
9388 aTable[FROZEN_NODE] = INVALID_NODE; // this state is unreachable here
9389 aTable[ENDED_NODE] = ENDED_NODE; // this state is a sink here (cannot restart)
9391 // transition table for restart=NEVER, fill=FREEZE
9393 aSTT[RESTART_MODE_NEVER][FILL_MODE_FREEZE] =
9394 aSTT[RESTART_MODE_NEVER][FILL_MODE_HOLD] =
9395 aSTT[RESTART_MODE_NEVER][FILL_MODE_TRANSITION] = {};
9396 aTable[INVALID_NODE] = INVALID_NODE;
9397 aTable[UNRESOLVED_NODE] = RESOLVED_NODE | ENDED_NODE;
9398 aTable[RESOLVED_NODE] = ACTIVE_NODE | ENDED_NODE;
9399 aTable[ACTIVE_NODE] = FROZEN_NODE | ENDED_NODE;
9400 aTable[FROZEN_NODE] = ENDED_NODE;
9401 aTable[ENDED_NODE] = ENDED_NODE; // this state is a sink here (cannot restart)
9403 // transition table for restart=WHEN_NOT_ACTIVE, fill=REMOVE
9405 aSTT[RESTART_MODE_WHEN_NOT_ACTIVE][FILL_MODE_REMOVE] = {};
9406 aTable[INVALID_NODE] = INVALID_NODE;
9407 aTable[UNRESOLVED_NODE] = RESOLVED_NODE | ENDED_NODE;
9408 aTable[RESOLVED_NODE] = ACTIVE_NODE | ENDED_NODE;
9409 aTable[ACTIVE_NODE] = ENDED_NODE;
9410 aTable[FROZEN_NODE] = INVALID_NODE; // this state is unreachable here
9411 aTable[ENDED_NODE] = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE; // restart is possible
9413 // transition table for restart=WHEN_NOT_ACTIVE, fill=FREEZE
9415 aSTT[RESTART_MODE_WHEN_NOT_ACTIVE][FILL_MODE_FREEZE] =
9416 aSTT[RESTART_MODE_WHEN_NOT_ACTIVE][FILL_MODE_HOLD] =
9417 aSTT[RESTART_MODE_WHEN_NOT_ACTIVE][FILL_MODE_TRANSITION] = {};
9418 aTable[INVALID_NODE] = INVALID_NODE;
9419 aTable[UNRESOLVED_NODE] = RESOLVED_NODE | ENDED_NODE;
9420 aTable[RESOLVED_NODE] = ACTIVE_NODE | ENDED_NODE;
9421 aTable[ACTIVE_NODE] = FROZEN_NODE | ENDED_NODE;
9422 aTable[FROZEN_NODE] = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE; // restart is possible
9423 aTable[ENDED_NODE] = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE; // restart is possible
9425 // transition table for restart=ALWAYS, fill=REMOVE
9427 aSTT[RESTART_MODE_ALWAYS][FILL_MODE_REMOVE] = {};
9428 aTable[INVALID_NODE] = INVALID_NODE;
9429 aTable[UNRESOLVED_NODE] = RESOLVED_NODE | ENDED_NODE;
9430 aTable[RESOLVED_NODE] = ACTIVE_NODE | ENDED_NODE;
9431 aTable[ACTIVE_NODE] = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE; // restart is possible
9432 aTable[FROZEN_NODE] = INVALID_NODE; // this state is unreachable here
9433 aTable[ENDED_NODE] = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE; // restart is possible
9435 // transition table for restart=ALWAYS, fill=FREEZE
9437 aSTT[RESTART_MODE_ALWAYS][FILL_MODE_FREEZE] =
9438 aSTT[RESTART_MODE_ALWAYS][FILL_MODE_HOLD] =
9439 aSTT[RESTART_MODE_ALWAYS][FILL_MODE_TRANSITION] = {};
9440 aTable[INVALID_NODE] = INVALID_NODE;
9441 aTable[UNRESOLVED_NODE] = RESOLVED_NODE | ENDED_NODE;
9442 aTable[RESOLVED_NODE] = ACTIVE_NODE | ENDED_NODE;
9443 aTable[ACTIVE_NODE] = RESOLVED_NODE | ACTIVE_NODE | FROZEN_NODE | ENDED_NODE;
9444 aTable[FROZEN_NODE] = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE; // restart is possible
9445 aTable[ENDED_NODE] = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE; // restart is possible
9450 var aStateTransitionTable = createStateTransitionTable();
9454 function getTransitionTable( eRestartMode, eFillMode )
9456 // If restart mode has not been resolved we use 'never'.
9457 // Note: RESTART_MODE_DEFAULT == RESTART_MODE_INHERIT.
9458 if( eRestartMode == RESTART_MODE_DEFAULT )
9460 log( 'getTransitionTable: unexpected restart mode: ' + eRestartMode
9461 + '. Used NEVER instead.');
9462 eRestartMode = RESTART_MODE_NEVER;
9465 // If fill mode has not been resolved we use 'remove'.
9466 // Note: FILL_MODE_DEFAULT == FILL_MODE_INHERIT
9467 if( eFillMode == FILL_MODE_DEFAULT ||
9468 eFillMode == FILL_MODE_AUTO )
9470 eFillMode = FILL_MODE_REMOVE;
9473 return aStateTransitionTable[eRestartMode][eFillMode];
9481 var EVENT_TRIGGER_UNKNOWN = 0;
9482 var EVENT_TRIGGER_ON_SLIDE_BEGIN = 1; // eslint-disable-line no-unused-vars
9483 var EVENT_TRIGGER_ON_SLIDE_END = 2; // eslint-disable-line no-unused-vars
9484 var EVENT_TRIGGER_BEGIN_EVENT = 3;
9485 var EVENT_TRIGGER_END_EVENT = 4;
9486 var EVENT_TRIGGER_ON_CLICK = 5;
9487 var EVENT_TRIGGER_ON_DBL_CLICK = 6; // eslint-disable-line no-unused-vars
9488 var EVENT_TRIGGER_ON_MOUSE_ENTER = 7; // eslint-disable-line no-unused-vars
9489 var EVENT_TRIGGER_ON_MOUSE_LEAVE = 8; // eslint-disable-line no-unused-vars
9490 var EVENT_TRIGGER_ON_NEXT_EFFECT = 9;
9491 var EVENT_TRIGGER_ON_PREV_EFFECT = 10;
9492 var EVENT_TRIGGER_REPEAT = 11; // eslint-disable-line no-unused-vars
9494 var aEventTriggerOutMap = [ 'unknown', 'slideBegin', 'slideEnd', 'begin', 'end', 'click',
9495 'doubleClick', 'mouseEnter', 'mouseLeave', 'next', 'previous', 'repeat' ];
9498 function getEventTriggerType( sEventTrigger )
9500 if( sEventTrigger == 'begin' )
9501 return EVENT_TRIGGER_BEGIN_EVENT;
9502 else if( sEventTrigger == 'end' )
9503 return EVENT_TRIGGER_END_EVENT;
9504 else if( sEventTrigger == 'next' )
9505 return EVENT_TRIGGER_ON_NEXT_EFFECT;
9506 else if( sEventTrigger == 'prev' )
9507 return EVENT_TRIGGER_ON_PREV_EFFECT;
9508 else if( sEventTrigger == 'click' )
9509 return EVENT_TRIGGER_ON_CLICK;
9511 return EVENT_TRIGGER_UNKNOWN;
9519 var UNKNOWN_TIMING = 0;
9520 var OFFSET_TIMING = 1;
9521 var WALLCLOCK_TIMING = 2; // eslint-disable-line no-unused-vars
9522 var INDEFINITE_TIMING = 3;
9523 var EVENT_TIMING = 4;
9524 var SYNCBASE_TIMING = 5;
9525 var MEDIA_TIMING = 6; // eslint-disable-line no-unused-vars
9527 var aTimingTypeOutMap = [ 'unknown', 'offset', 'wallclock', 'indefinite', 'event', 'syncbase', 'media' ];
9531 var CHARCODE_PLUS = '+'.charCodeAt(0);
9532 var CHARCODE_MINUS = '-'.charCodeAt(0);
9533 var CHARCODE_0 = '0'.charCodeAt(0);
9534 var CHARCODE_9 = '9'.charCodeAt(0);
9538 function Timing( aAnimationNode, sTimingAttribute )
9540 this.aAnimationNode = aAnimationNode; // the node, the timing attribute belongs to
9541 this.sTimingDescription = removeWhiteSpaces( sTimingAttribute );
9542 this.eTimingType = UNKNOWN_TIMING;
9543 this.nOffset = 0.0; // in seconds
9544 this.sEventBaseElementId = ''; // the element id for event based timing
9545 this.eEventType = EVENT_TRIGGER_UNKNOWN; // the event type
9548 Timing.prototype.getAnimationNode = function()
9550 return this.aAnimationNode;
9553 Timing.prototype.getType = function()
9555 return this.eTimingType;
9558 Timing.prototype.getOffset = function()
9560 return this.nOffset;
9563 Timing.prototype.getEventBaseElementId = function()
9565 return this.sEventBaseElementId;
9568 Timing.prototype.getEventType = function()
9570 return this.eEventType;
9573 Timing.prototype.parse = function()
9575 if( !this.sTimingDescription )
9577 this.eTimingType = OFFSET_TIMING;
9581 if( this.sTimingDescription == 'indefinite' )
9582 this.eTimingType = INDEFINITE_TIMING;
9585 var nFirstCharCode = this.sTimingDescription.charCodeAt(0);
9586 var bPositiveOffset = !( nFirstCharCode == CHARCODE_MINUS );
9587 if ( ( nFirstCharCode == CHARCODE_PLUS ) ||
9588 ( nFirstCharCode == CHARCODE_MINUS ) ||
9589 ( ( nFirstCharCode >= CHARCODE_0 ) && ( nFirstCharCode <= CHARCODE_9 ) ) )
9592 = ( ( nFirstCharCode == CHARCODE_PLUS ) || ( nFirstCharCode == CHARCODE_MINUS ) )
9593 ? this.sTimingDescription.substr( 1 )
9594 : this.sTimingDescription;
9596 var TimeInSec = Timing.parseClockValue( sClockValue );
9597 if( TimeInSec != undefined )
9599 this.eTimingType = OFFSET_TIMING;
9600 this.nOffset = bPositiveOffset ? TimeInSec : -TimeInSec;
9605 var aTimingSplit = [];
9606 bPositiveOffset = true;
9607 if( this.sTimingDescription.indexOf( '+' ) != -1 )
9609 aTimingSplit = this.sTimingDescription.split( '+' );
9611 else if( this.sTimingDescription.indexOf( '-' ) != -1 )
9613 aTimingSplit = this.sTimingDescription.split( '-' );
9614 bPositiveOffset = false;
9618 aTimingSplit[0] = this.sTimingDescription;
9619 aTimingSplit[1] = '';
9622 if( aTimingSplit[0].indexOf( '.' ) != -1 )
9624 var aEventSplit = aTimingSplit[0].split( '.' );
9625 this.sEventBaseElementId = aEventSplit[0];
9626 this.eEventType = getEventTriggerType( aEventSplit[1] );
9630 this.eEventType = getEventTriggerType( aTimingSplit[0] );
9633 if( this.eEventType == EVENT_TRIGGER_UNKNOWN )
9636 if( ( this.eEventType == EVENT_TRIGGER_BEGIN_EVENT ) ||
9637 ( this.eEventType == EVENT_TRIGGER_END_EVENT ) )
9639 this.eTimingType = SYNCBASE_TIMING;
9643 this.eTimingType = EVENT_TIMING;
9646 if( aTimingSplit[1] )
9648 sClockValue = aTimingSplit[1];
9649 TimeInSec = Timing.parseClockValue( sClockValue );
9650 if( TimeInSec != undefined )
9652 this.nOffset = ( bPositiveOffset ) ? TimeInSec : -TimeInSec;
9656 this.eTimingType = UNKNOWN_TIMING;
9665 Timing.parseClockValue = function( sClockValue )
9670 var nTimeInSec = undefined;
9672 var reFullClockValue = /^([0-9]+):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?$/;
9673 var rePartialClockValue = /^([0-5][0-9]):([0-5][0-9])(.[0-9]+)?$/;
9674 var reTimeCountValue = /^([0-9]+)(.[0-9]+)?(h|min|s|ms)?$/;
9676 if( reFullClockValue.test( sClockValue ) )
9678 var aClockTimeParts = reFullClockValue.exec( sClockValue );
9680 var nHours = parseInt( aClockTimeParts[1] );
9681 var nMinutes = parseInt( aClockTimeParts[2] );
9682 var nSeconds = parseInt( aClockTimeParts[3] );
9683 if( aClockTimeParts[4] )
9684 nSeconds += parseFloat( aClockTimeParts[4] );
9686 nTimeInSec = ( ( nHours * 60 ) + nMinutes ) * 60 + nSeconds;
9689 else if( rePartialClockValue.test( sClockValue ) )
9691 aClockTimeParts = rePartialClockValue.exec( sClockValue );
9693 nMinutes = parseInt( aClockTimeParts[1] );
9694 nSeconds = parseInt( aClockTimeParts[2] );
9695 if( aClockTimeParts[3] )
9696 nSeconds += parseFloat( aClockTimeParts[3] );
9698 nTimeInSec = nMinutes * 60 + nSeconds;
9700 else if( reTimeCountValue.test( sClockValue ) )
9702 aClockTimeParts = reTimeCountValue.exec( sClockValue );
9704 var nTimeCount = parseInt( aClockTimeParts[1] );
9705 if( aClockTimeParts[2] )
9706 nTimeCount += parseFloat( aClockTimeParts[2] );
9708 if( aClockTimeParts[3] )
9710 if( aClockTimeParts[3] == 'h' )
9712 nTimeInSec = nTimeCount * 3600;
9714 else if( aClockTimeParts[3] == 'min' )
9716 nTimeInSec = nTimeCount * 60;
9718 else if( aClockTimeParts[3] == 's' )
9720 nTimeInSec = nTimeCount;
9722 else if( aClockTimeParts[3] == 'ms' )
9724 nTimeInSec = nTimeCount / 1000;
9729 nTimeInSec = nTimeCount;
9735 nTimeInSec = parseFloat( nTimeInSec.toFixed( 3 ) );
9739 Timing.prototype.info = function( bVerbose )
9746 sInfo = 'description: ' + this.sTimingDescription + ', ';
9748 sInfo += ', type: ' + aTimingTypeOutMap[ this.getType() ];
9749 sInfo += ', offset: ' + this.getOffset();
9750 sInfo += ', event base element id: ' + this.getEventBaseElementId();
9751 sInfo += ', timing event type: ' + aEventTriggerOutMap[ this.getEventType() ];
9755 switch( this.getType() )
9757 case INDEFINITE_TIMING:
9758 sInfo += 'indefinite';
9761 sInfo += this.getOffset();
9764 case SYNCBASE_TIMING:
9765 if( this.getEventBaseElementId() )
9766 sInfo += this.getEventBaseElementId() + '.';
9767 sInfo += aEventTriggerOutMap[ this.getEventType() ];
9768 if( this.getOffset() )
9770 if( this.getOffset() > 0 )
9772 sInfo += this.getOffset();
9783 function Duration( sDurationAttribute )
9785 this.bIndefinite = false;
9786 this.bMedia = false;
9787 this.nValue = undefined;
9788 this.bDefined = false;
9790 if( !sDurationAttribute )
9793 if( sDurationAttribute == 'indefinite' )
9794 this.bIndefinite = true;
9795 else if( sDurationAttribute == 'media' )
9799 this.nValue = Timing.parseClockValue( sDurationAttribute );
9800 if( this.nValue <= 0.0 )
9801 this.nValue = 0.001; // duration must be always greater than 0
9803 this.bDefined = true;
9807 Duration.prototype.isSet = function()
9809 return this.bDefined;
9812 Duration.prototype.isIndefinite = function()
9814 return this.bIndefinite;
9817 Duration.prototype.isMedia = function()
9822 Duration.prototype.isValue = function()
9824 return this.nValue != undefined;
9827 Duration.prototype.getValue= function()
9832 Duration.prototype.info= function()
9836 if( this.isIndefinite() )
9837 sInfo = 'indefinite';
9838 else if( this.isMedia() )
9840 else if( this.getValue() )
9841 sInfo = this.getValue();
9849 function AnimationNode()
9853 AnimationNode.prototype.init = function() {};
9854 AnimationNode.prototype.resolve = function() {};
9855 AnimationNode.prototype.activate = function() {};
9856 AnimationNode.prototype.deactivate = function() {};
9857 AnimationNode.prototype.end = function() {};
9858 AnimationNode.prototype.getState = function() {};
9859 AnimationNode.prototype.registerDeactivatingListener = function() {};
9860 AnimationNode.prototype.notifyDeactivating = function() {};
9865 function NodeContext( aSlideShowContext )
9867 this.aContext = aSlideShowContext;
9868 this.aAnimationNodeMap = null;
9869 this.aAnimatedElementMap = null;
9870 this.aSourceEventElementMap = null;
9871 this.nStartDelay = 0.0;
9872 this.bFirstRun = undefined;
9873 this.bIsInvalid = false;
9874 this.aSlideHeight = HEIGHT;
9875 this.aSlideWidth = WIDTH;
9879 NodeContext.prototype.makeSourceEventElement = function( sId, aEventBaseElem )
9881 if( !aEventBaseElem )
9883 log( 'NodeContext.makeSourceEventElement: event base element is not valid' );
9887 if( !this.aContext.aEventMultiplexer )
9889 log( 'NodeContext.makeSourceEventElement: event multiplexer not initialized' );
9893 if( !this.aSourceEventElementMap[ sId ] )
9895 this.aSourceEventElementMap[ sId ] = new SourceEventElement( sId, aEventBaseElem, this.aContext.aEventMultiplexer );
9897 return this.aSourceEventElementMap[ sId ];
9903 function StateTransition( aBaseNode )
9905 this.aNode = aBaseNode;
9906 this.eToState = INVALID_NODE;
9909 StateTransition.prototype.enter = function( eNodeState, bForce )
9911 if( !bForce ) bForce = false;
9913 if( this.eToState != INVALID_NODE )
9915 log( 'StateTransition.enter: commit() before enter()ing again!' );
9918 if( !bForce && !this.aNode.isTransition( this.aNode.getState(), eNodeState ) )
9921 // recursion detection:
9922 if( ( this.aNode.nCurrentStateTransition & eNodeState ) != 0 )
9923 return false; // already in wanted transition
9926 this.aNode.nCurrentStateTransition |= eNodeState;
9927 this.eToState = eNodeState;
9931 StateTransition.prototype.commit = function()
9933 if( this.eToState != INVALID_NODE )
9935 this.aNode.eCurrentState = this.eToState;
9940 StateTransition.prototype.clear = function()
9942 if( this.eToState != INVALID_NODE )
9944 this.aNode.nCurrentStateTransition &= ~this.eToState;
9945 this.eToState = INVALID_NODE;
9952 function BaseNode( aAnimElem, aParentNode, aNodeContext )
9954 this.nId = getUniqueId();
9955 this.sClassName = 'BaseNode';
9958 log( 'BaseNode(id:' + this.nId + ') constructor: aAnimElem is not valid' );
9961 log( 'BaseNode(id:' + this.nId + ') constructor: aNodeContext is not valid' );
9963 if( !aNodeContext.aContext )
9964 log( 'BaseNode(id:' + this.nId + ') constructor: aNodeContext.aContext is not valid' );
9967 this.bIsContainer = false;
9968 this.aElement = aAnimElem;
9969 this.aParentNode = aParentNode;
9970 this.aNodeContext = aNodeContext;
9971 this.aContext = aNodeContext.aContext;
9972 this.nStartDelay = aNodeContext.nStartDelay;
9973 this.eCurrentState = UNRESOLVED_NODE;
9974 this.nCurrentStateTransition = 0;
9975 this.aDeactivatingListenerArray = [];
9976 this.aActivationEvent = null;
9977 this.aDeactivationEvent = null;
9980 this.aDuration = null;
9982 this.bMainSequenceRootNode = false;
9983 this.bInteractiveSequenceRootNode = false;
9984 this.eFillMode = FILL_MODE_FREEZE;
9985 this.eRestartMode = RESTART_MODE_NEVER;
9986 this.nRepeatCount = undefined;
9987 this.nAccelerate = 0.0;
9988 this.nDecelerate = 0.0;
9989 this.bAutoReverse = false;
9992 extend( BaseNode, AnimationNode );
9995 BaseNode.prototype.getId = function()
10000 BaseNode.prototype.parseElement = function()
10002 var aAnimElem = this.aElement;
10005 var sIdAttr = aAnimElem.getAttributeNS( NSS['xml'], 'id' );
10006 // we append the animation node to the Animation Node Map so that it can be retrieved
10007 // by the registerEvent routine for resolving begin values of type 'id.begin', 'id.end'
10009 this.aNodeContext.aAnimationNodeMap[ sIdAttr ] = this;
10012 this.aBegin = null;
10013 var sBeginAttr = aAnimElem.getAttributeNS( NSS['smil'], 'begin' );
10014 this.aBegin = new Timing( this, sBeginAttr );
10015 this.aBegin.parse();
10019 var sEndAttr = aAnimElem.getAttributeNS( NSS['smil'], 'end' );
10022 this.aEnd = new Timing( this, sEndAttr );
10027 this.aDuration = null;
10028 var sDurAttr = aAnimElem.getAttributeNS( NSS['smil'], 'dur' );
10029 this.aDuration = new Duration( sDurAttr );
10030 if( !this.aDuration.isSet() )
10032 if( this.isContainer() )
10033 this.aDuration = null;
10035 this.aDuration = new Duration( 'indefinite' );
10039 var sFillAttr = aAnimElem.getAttributeNS( NSS['smil'], 'fill' );
10040 if( sFillAttr && aFillModeInMap[ sFillAttr ])
10041 this.eFillMode = aFillModeInMap[ sFillAttr ];
10043 this.eFillMode = FILL_MODE_DEFAULT;
10045 // restart attribute
10046 var sRestartAttr = aAnimElem.getAttributeNS( NSS['smil'], 'restart' );
10047 if( sRestartAttr && aRestartModeInMap[ sRestartAttr ] )
10048 this.eRestartMode = aRestartModeInMap[ sRestartAttr ];
10050 this.eRestartMode = RESTART_MODE_DEFAULT;
10052 // repeatCount attribute
10053 var sRepeatCount = aAnimElem.getAttributeNS( NSS['smil'], 'repeatCount' );
10054 if( !sRepeatCount )
10055 this.nRepeatCount = 1;
10057 this.nRepeatCount = parseFloat( sRepeatCount );
10058 if( ( isNaN(this.nRepeatCount) ) && ( sRepeatCount != 'indefinite' ) )
10059 this.nRepeatCount = 1;
10061 // accelerate attribute
10062 this.nAccelerate = 0.0;
10063 var sAccelerateAttr = aAnimElem.getAttributeNS( NSS['smil'], 'accelerate' );
10064 if( sAccelerateAttr )
10065 this.nAccelerate = parseFloat( sAccelerateAttr );
10066 if( isNaN(this.nAccelerate) )
10067 this.nAccelerate = 0.0;
10069 // decelerate attribute
10070 this.nDecelerate = 0.0;
10071 var sDecelerateAttr = aAnimElem.getAttributeNS( NSS['smil'], 'decelerate' );
10072 if( sDecelerateAttr )
10073 this.nDecelerate = parseFloat( sDecelerateAttr );
10074 if( isNaN(this.nDecelerate) )
10075 this.nDecelerate = 0.0;
10077 // autoReverse attribute
10078 this.bAutoreverse = false;
10079 var sAutoReverseAttr = aAnimElem.getAttributeNS( NSS['smil'], 'autoReverse' );
10080 if( sAutoReverseAttr == 'true' )
10081 this.bAutoreverse = true;
10084 // resolve fill value
10085 if( this.eFillMode == FILL_MODE_DEFAULT )
10086 if( this.getParentNode() )
10087 this.eFillMode = this.getParentNode().getFillMode();
10089 this.eFillMode = FILL_MODE_AUTO;
10091 if( this.eFillMode == FILL_MODE_AUTO ) // see SMIL recommendation document
10093 this.eFillMode = ( this.aEnd ||
10094 ( this.nRepeatCount != 1) ||
10095 ( this.aDuration && !this.aDuration.isIndefinite() ) )
10097 : FILL_MODE_FREEZE;
10100 // resolve restart value
10101 if( this.eRestartMode == RESTART_MODE_DEFAULT )
10102 if( this.getParentNode() )
10103 this.eRestartMode = this.getParentNode().getRestartMode();
10105 // SMIL recommendation document says to set it to 'always'
10106 this.eRestartMode = RESTART_MODE_ALWAYS;
10108 // resolve accelerate and decelerate attributes
10109 // from the SMIL recommendation document: if the individual values of the accelerate
10110 // and decelerate attributes are between 0 and 1 and the sum is greater than 1,
10111 // then both the accelerate and decelerate attributes will be ignored and the timed
10112 // element will behave as if neither attribute was specified.
10113 if( ( this.nAccelerate + this.nDecelerate ) > 1.0 )
10115 this.nAccelerate = 0.0;
10116 this.nDecelerate = 0.0;
10119 this.aStateTransTable = getTransitionTable( this.getRestartMode(), this.getFillMode() );
10124 BaseNode.prototype.getParentNode = function()
10126 return this.aParentNode;
10129 BaseNode.prototype.init = function()
10131 this.DBG( this.callInfo( 'init' ) );
10132 if( ! this.checkValidNode() )
10134 if( this.aActivationEvent )
10135 this.aActivationEvent.dispose();
10136 if( this.aDeactivationEvent )
10137 this.aDeactivationEvent.dispose();
10139 this.eCurrentState = UNRESOLVED_NODE;
10141 return this.init_st();
10144 BaseNode.prototype.resolve = function()
10146 if( this.aNodeContext.bIsInvalid || ! this.checkValidNode() )
10149 this.DBG( this.callInfo( 'resolve' ) );
10151 if( this.eCurrentState == RESOLVED_NODE )
10152 log( 'BaseNode.resolve: already in RESOLVED state' );
10154 var aStateTrans = new StateTransition( this );
10156 if( aStateTrans.enter( RESOLVED_NODE ) &&
10157 this.isTransition( RESOLVED_NODE, ACTIVE_NODE ) &&
10158 this.resolve_st() )
10160 aStateTrans.commit();
10162 if( this.aActivationEvent )
10164 this.aActivationEvent.charge();
10168 this.aActivationEvent = makeDelay( bind( this, this.activate ), this.getBegin().getOffset() + this.nStartDelay );
10170 registerEvent( this.getId(), this.getBegin(), this.aActivationEvent, this.aNodeContext );
10178 BaseNode.prototype.activate = function()
10180 if( ! this.checkValidNode() )
10183 if( this.eCurrentState == ACTIVE_NODE )
10184 log( 'BaseNode.activate: already in ACTIVE state' );
10186 this.DBG( this.callInfo( 'activate' ), getCurrentSystemTime() );
10188 var aStateTrans = new StateTransition( this );
10190 if( aStateTrans.enter( ACTIVE_NODE ) )
10192 this.activate_st();
10193 aStateTrans.commit();
10194 if( !this.aContext.aEventMultiplexer )
10195 log( 'BaseNode.activate: this.aContext.aEventMultiplexer is not valid' );
10196 this.aContext.aEventMultiplexer.notifyEvent( EVENT_TRIGGER_BEGIN_EVENT, this.getId() );
10202 BaseNode.prototype.deactivate = function()
10204 if( this.inStateOrTransition( ENDED_NODE | FROZEN_NODE ) || !this.checkValidNode() )
10207 if( this.isTransition( this.eCurrentState, FROZEN_NODE ) )
10209 this.DBG( this.callInfo( 'deactivate' ), getCurrentSystemTime() );
10211 var aStateTrans = new StateTransition( this );
10212 if( aStateTrans.enter( FROZEN_NODE, true /* FORCE */ ) )
10214 this.deactivate_st( FROZEN_NODE );
10215 aStateTrans.commit();
10217 this.notifyEndListeners();
10219 if( this.aActivationEvent )
10220 this.aActivationEvent.dispose();
10221 if( this.aDeactivationEvent )
10222 this.aDeactivationEvent.dispose();
10229 // state has changed either to FROZEN or ENDED
10232 BaseNode.prototype.end = function()
10234 var bIsFrozenOrInTransitionToFrozen = this.inStateOrTransition( FROZEN_NODE );
10235 if( this.inStateOrTransition( ENDED_NODE ) || !this.checkValidNode() )
10238 if( !(this.isTransition( this.eCurrentState, ENDED_NODE ) ) )
10239 log( 'BaseNode.end: end state not reachable in transition table' );
10241 this.DBG( this.callInfo( 'end' ), getCurrentSystemTime() );
10243 var aStateTrans = new StateTransition( this );
10244 if( aStateTrans.enter( ENDED_NODE, true /* FORCE */ ) )
10246 this.deactivate_st( ENDED_NODE );
10247 aStateTrans.commit();
10249 // if is FROZEN or is to be FROZEN, then
10250 // will/already notified deactivating listeners
10251 if( !bIsFrozenOrInTransitionToFrozen )
10252 this.notifyEndListeners();
10254 if( this.aActivationEvent )
10255 this.aActivationEvent.dispose();
10256 if( this.aDeactivationEvent )
10257 this.aDeactivationEvent.dispose();
10261 BaseNode.prototype.dispose = function()
10263 if( this.aActivationEvent )
10264 this.aActivationEvent.dispose();
10265 if( this.aDeactivationEvent )
10266 this.aDeactivationEvent.dispose();
10267 this.aDeactivatingListenerArray = [];
10270 BaseNode.prototype.getState = function()
10272 return this.eCurrentState;
10275 BaseNode.prototype.registerDeactivatingListener = function( aNotifiee )
10277 if (! this.checkValidNode())
10282 log( 'BaseNode.registerDeactivatingListener(): invalid notifiee' );
10285 this.aDeactivatingListenerArray.push( aNotifiee );
10290 BaseNode.prototype.notifyDeactivating = function( aNotifier )
10292 assert( ( aNotifier.getState() == FROZEN_NODE ) || ( aNotifier.getState() == ENDED_NODE ),
10293 'BaseNode.notifyDeactivating: Notifier node is neither in FROZEN nor in ENDED state' );
10296 BaseNode.prototype.isMainSequenceRootNode = function()
10298 return this.bMainSequenceRootNode;
10301 BaseNode.prototype.isInteractiveSequenceRootNode = function()
10303 return this.bInteractiveSequenceRootNode;
10306 BaseNode.prototype.makeDeactivationEvent = function( nDelay )
10308 if( this.aDeactivationEvent )
10310 this.aDeactivationEvent.charge();
10314 if( typeof( nDelay ) == typeof(0) )
10315 this.aDeactivationEvent = makeDelay( bind( this, this.deactivate ), nDelay );
10317 this.aDeactivationEvent = null;
10319 return this.aDeactivationEvent;
10322 BaseNode.prototype.scheduleDeactivationEvent = function( aEvent )
10324 this.DBG( this.callInfo( 'scheduleDeactivationEvent' ) );
10328 if( this.getDuration() && this.getDuration().isValue() )
10329 aEvent = this.makeDeactivationEvent( this.getDuration().getValue() );
10333 this.aContext.aTimerEventQueue.addEvent( aEvent );
10337 BaseNode.prototype.checkValidNode = function()
10339 return ( this.eCurrentState != INVALID_NODE );
10342 BaseNode.prototype.init_st = function()
10347 BaseNode.prototype.resolve_st = function()
10352 BaseNode.prototype.activate_st = function()
10354 this.scheduleDeactivationEvent();
10357 BaseNode.prototype.deactivate_st = function( /*aNodeState*/ )
10362 BaseNode.prototype.notifyEndListeners = function()
10364 var nDeactivatingListenerCount = this.aDeactivatingListenerArray.length;
10366 for( var i = 0; i < nDeactivatingListenerCount; ++i )
10368 this.aDeactivatingListenerArray[i].notifyDeactivating( this );
10371 this.aContext.aEventMultiplexer.notifyEvent( EVENT_TRIGGER_END_EVENT, this.getId() );
10372 if( this.getParentNode() && this.getParentNode().isMainSequenceRootNode() )
10373 this.aContext.aEventMultiplexer.notifyNextEffectEndEvent();
10375 if( this.isMainSequenceRootNode() )
10376 this.aContext.aEventMultiplexer.notifyAnimationsEndEvent();
10379 BaseNode.prototype.getContext = function()
10381 return this.aContext;
10384 BaseNode.prototype.isTransition = function( eFromState, eToState )
10386 return ( ( this.aStateTransTable[ eFromState ] & eToState ) != 0 );
10389 BaseNode.prototype.inStateOrTransition = function( nMask )
10391 return ( ( ( this.eCurrentState & nMask ) != 0 ) || ( ( this.nCurrentStateTransition & nMask ) != 0 ) );
10394 BaseNode.prototype.isContainer = function()
10396 return this.bIsContainer;
10399 BaseNode.prototype.getBegin = function()
10401 return this.aBegin;
10404 BaseNode.prototype.getDuration = function()
10406 return this.aDuration;
10409 BaseNode.prototype.getEnd = function()
10414 BaseNode.prototype.getFillMode = function()
10416 return this.eFillMode;
10419 BaseNode.prototype.getRestartMode = function()
10421 return this.eRestartMode;
10424 BaseNode.prototype.getRepeatCount = function()
10426 return this.nRepeatCount;
10429 BaseNode.prototype.getAccelerateValue = function()
10431 return this.nAccelerate;
10434 BaseNode.prototype.getDecelerateValue = function()
10436 return this.nDecelerate;
10439 BaseNode.prototype.isAutoReverseEnabled = function()
10441 return this.bAutoreverse;
10444 BaseNode.prototype.info = function( bVerbose )
10446 var sInfo = 'class name: ' + this.sClassName;
10447 sInfo += '; element name: ' + this.aElement.localName;
10448 sInfo += '; id: ' + this.getId();
10449 sInfo += '; state: ' + getNodeStateName( this.getState() );
10454 sInfo += '; is container: ' + this.isContainer();
10457 if( this.getBegin() )
10458 sInfo += '; begin: ' + this.getBegin().info();
10461 if( this.getDuration() )
10462 sInfo += '; dur: ' + this.getDuration().info();
10465 if( this.getEnd() )
10466 sInfo += '; end: ' + this.getEnd().info();
10469 if( this.getFillMode() )
10470 sInfo += '; fill: ' + aFillModeOutMap[ this.getFillMode() ];
10473 if( this.getRestartMode() )
10474 sInfo += '; restart: ' + aRestartModeOutMap[ this.getRestartMode() ];
10477 if( this.getRepeatCount() && ( this.getRepeatCount() != 1.0 ) )
10478 sInfo += '; repeatCount: ' + this.getRepeatCount();
10481 if( this.getAccelerateValue() )
10482 sInfo += '; accelerate: ' + this.getAccelerateValue();
10485 if( this.getDecelerateValue() )
10486 sInfo += '; decelerate: ' + this.getDecelerateValue();
10489 if( this.isAutoReverseEnabled() )
10490 sInfo += '; autoReverse: true';
10497 BaseNode.prototype.callInfo = function( sMethodName )
10499 var sInfo = this.sClassName +
10500 '( ' + this.getId() +
10501 ', ' + getNodeStateName( this.getState() ) +
10502 ' ).' + sMethodName;
10506 BaseNode.prototype.DBG = function( sMessage, nTime )
10508 ANIMDBG.print( sMessage, nTime );
10514 function AnimationBaseNode( aAnimElem, aParentNode, aNodeContext )
10516 AnimationBaseNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
10518 this.sClassName = 'AnimationBaseNode';
10519 this.bIsContainer = false;
10520 this.aTargetElement = null;
10521 this.bIsTargetTextElement = false;
10522 this.aAnimatedElement = null;
10523 this.aActivity = null;
10525 this.nMinFrameCount = undefined;
10526 this.eAdditiveMode = undefined;
10529 extend( AnimationBaseNode, BaseNode );
10532 AnimationBaseNode.prototype.parseElement = function()
10534 var bRet = AnimationBaseNode.superclass.parseElement.call( this );
10536 var aAnimElem = this.aElement;
10538 // targetElement attribute
10539 this.aTargetElement = null;
10540 var sTargetElementAttr = aAnimElem.getAttributeNS( NSS['smil'], 'targetElement' );
10541 if( sTargetElementAttr )
10542 this.aTargetElement = document.getElementById( sTargetElementAttr );
10544 if( !this.aTargetElement )
10546 this.eCurrentState = INVALID_NODE;
10547 log( 'AnimationBaseNode.parseElement: target element not found: ' + sTargetElementAttr );
10550 // sub-item attribute for text animated element
10551 var sSubItemAttr = aAnimElem.getAttributeNS( NSS['anim'], 'sub-item' );
10552 this.bIsTargetTextElement = ( sSubItemAttr && ( sSubItemAttr === 'text' ) );
10554 // additive attribute
10555 var sAdditiveAttr = aAnimElem.getAttributeNS( NSS['smil'], 'additive' );
10556 if( sAdditiveAttr && aAddittiveModeInMap[sAdditiveAttr] )
10557 this.eAdditiveMode = aAddittiveModeInMap[sAdditiveAttr];
10559 this.eAdditiveMode = ADDITIVE_MODE_REPLACE;
10561 // set up min frame count value;
10562 this.nMinFrameCount = ( this.getDuration().isValue() )
10563 ? ( this.getDuration().getValue() * MINIMUM_FRAMES_PER_SECONDS )
10564 : MINIMUM_FRAMES_PER_SECONDS;
10565 if( this.nMinFrameCount < 1.0 )
10566 this.nMinFrameCount = 1;
10567 else if( this.nMinFrameCount > MINIMUM_FRAMES_PER_SECONDS )
10568 this.nMinFrameCount = MINIMUM_FRAMES_PER_SECONDS;
10571 if( this.aTargetElement )
10573 // set up target element initial visibility
10574 if( aAnimElem.getAttributeNS( NSS['smil'], 'attributeName' ) === 'visibility' )
10576 if( aAnimElem.getAttributeNS( NSS['smil'], 'to' ) === 'visible' )
10577 this.aTargetElement.setAttribute( 'visibility', 'hidden' );
10580 // create animated element
10581 if( !this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ] )
10583 if( this.bIsTargetTextElement )
10585 this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ]
10586 = new AnimatedTextElement( this.aTargetElement );
10590 this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ]
10591 = new AnimatedElement( this.aTargetElement );
10594 this.aAnimatedElement = this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ];
10596 // set additive mode
10597 this.aAnimatedElement.setAdditiveMode( this.eAdditiveMode );
10604 AnimationBaseNode.prototype.init_st = function()
10606 if( this.aActivity )
10607 this.aActivity.activate( makeEvent( bind( this, this.deactivate ) ) );
10609 this.aActivity = this.createActivity();
10613 AnimationBaseNode.prototype.resolve_st = function()
10618 AnimationBaseNode.prototype.activate_st = function()
10620 if( this.aActivity )
10622 this.saveStateOfAnimatedElement();
10623 this.aActivity.setTargets( this.getAnimatedElement() );
10624 if( this.getContext().bIsSkipping )
10626 this.aActivity.end();
10630 this.getContext().aActivityQueue.addActivity( this.aActivity );
10635 AnimationBaseNode.superclass.scheduleDeactivationEvent.call( this );
10639 AnimationBaseNode.prototype.deactivate_st = function( eDestState )
10641 if( eDestState == FROZEN_NODE )
10643 if( this.aActivity )
10644 this.aActivity.end();
10646 if( eDestState == ENDED_NODE )
10648 if( this.aActivity )
10649 this.aActivity.dispose();
10650 if( ( this.getFillMode() == FILL_MODE_REMOVE ) && this.getAnimatedElement() )
10651 this.removeEffect();
10655 AnimationBaseNode.prototype.createActivity = function()
10657 log( 'AnimationBaseNode.createActivity: abstract method called' );
10660 AnimationBaseNode.prototype.fillActivityParams = function()
10663 // compute duration
10664 var nDuration = 0.001;
10665 if( this.getDuration().isValue() )
10667 nDuration = this.getDuration().getValue();
10671 log( 'AnimationBaseNode.fillActivityParams: duration is not a number' );
10674 // create and set up activity params
10675 var aActivityParamSet = new ActivityParamSet();
10677 aActivityParamSet.aEndEvent = makeEvent( bind( this, this.deactivate ) );
10678 aActivityParamSet.aTimerEventQueue = this.aContext.aTimerEventQueue;
10679 aActivityParamSet.aActivityQueue = this.aContext.aActivityQueue;
10680 aActivityParamSet.nMinDuration = nDuration;
10681 aActivityParamSet.nMinNumberOfFrames = this.getMinFrameCount();
10682 aActivityParamSet.bAutoReverse = this.isAutoReverseEnabled();
10683 aActivityParamSet.nRepeatCount = this.getRepeatCount();
10684 aActivityParamSet.nAccelerationFraction = this.getAccelerateValue();
10685 aActivityParamSet.nDecelerationFraction = this.getDecelerateValue();
10686 aActivityParamSet.nSlideWidth = this.aNodeContext.aSlideWidth;
10687 aActivityParamSet.nSlideHeight = this.aNodeContext.aSlideHeight;
10689 return aActivityParamSet;
10692 AnimationBaseNode.prototype.hasPendingAnimation = function()
10697 AnimationBaseNode.prototype.saveStateOfAnimatedElement = function()
10699 this.getAnimatedElement().saveState( this.getId() );
10702 AnimationBaseNode.prototype.removeEffect = function()
10704 this.getAnimatedElement().restoreState( this.getId() );
10707 AnimationBaseNode.prototype.getTargetElement = function()
10709 return this.aTargetElement;
10712 AnimationBaseNode.prototype.getAnimatedElement = function()
10714 return this.aAnimatedElement;
10717 AnimationBaseNode.prototype.dispose= function()
10719 if( this.aActivity )
10720 this.aActivity.dispose();
10722 AnimationBaseNode.superclass.dispose.call( this );
10725 AnimationBaseNode.prototype.getMinFrameCount = function()
10727 return this.nMinFrameCount;
10730 AnimationBaseNode.prototype.getAdditiveMode = function()
10732 return this.eAdditiveMode;
10735 AnimationBaseNode.prototype.info = function( bVerbose )
10737 var sInfo = AnimationBaseNode.superclass.info.call( this, bVerbose );
10742 if( this.getMinFrameCount() )
10743 sInfo += '; min frame count: ' + this.getMinFrameCount();
10746 sInfo += '; additive: ' + aAddittiveModeOutMap[ this.getAdditiveMode() ];
10749 if( this.getTargetElement() )
10751 var sElemId = this.getTargetElement().getAttribute( 'id' );
10752 sInfo += '; targetElement: ' + sElemId;
10761 function AnimationBaseNode2( aAnimElem, aParentNode, aNodeContext )
10763 AnimationBaseNode2.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
10765 this.sAttributeName = '';
10766 this.aToValue = null;
10769 extend( AnimationBaseNode2, AnimationBaseNode );
10772 AnimationBaseNode2.prototype.parseElement = function()
10774 var bRet = AnimationBaseNode2.superclass.parseElement.call( this );
10776 var aAnimElem = this.aElement;
10778 // attributeName attribute
10779 this.sAttributeName = aAnimElem.getAttributeNS( NSS['smil'], 'attributeName' );
10780 if( !this.sAttributeName )
10782 this.eCurrentState = INVALID_NODE;
10783 log( 'AnimationBaseNode2.parseElement: target attribute name not found: ' + this.sAttributeName );
10787 this.aToValue = aAnimElem.getAttributeNS( NSS['smil'], 'to' );
10792 AnimationBaseNode2.prototype.getAttributeName = function()
10794 return this.sAttributeName;
10797 AnimationBaseNode2.prototype.getToValue = function()
10799 return this.aToValue;
10802 AnimationBaseNode2.prototype.info = function( bVerbose )
10804 var sInfo = AnimationBaseNode2.superclass.info.call( this, bVerbose );
10809 if( this.getAttributeName() )
10810 sInfo += '; attributeName: ' + this.getAttributeName();
10813 if( this.getToValue() )
10814 sInfo += '; to: ' + this.getToValue();
10823 function AnimationBaseNode3( aAnimElem, aParentNode, aNodeContext )
10825 AnimationBaseNode3.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
10827 this.eAccumulate = undefined;
10828 this.eCalcMode = undefined;
10829 this.aFromValue = null;
10830 this.aByValue = null;
10831 this.aKeyTimes = null;
10832 this.aValues = null;
10833 this.aFormula= null;
10835 extend( AnimationBaseNode3, AnimationBaseNode2 );
10838 AnimationBaseNode3.prototype.parseElement = function()
10840 var bRet = AnimationBaseNode3.superclass.parseElement.call( this );
10842 var aAnimElem = this.aElement;
10844 // accumulate attribute
10845 this.eAccumulate = ACCUMULATE_MODE_NONE;
10846 var sAccumulateAttr = aAnimElem.getAttributeNS( NSS['smil'], 'accumulate' );
10847 if( sAccumulateAttr == 'sum' )
10848 this.eAccumulate = ACCUMULATE_MODE_SUM;
10850 // calcMode attribute
10851 this.eCalcMode = CALC_MODE_LINEAR;
10852 var sCalcModeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'calcMode' );
10853 if( sCalcModeAttr && aCalcModeInMap[ sCalcModeAttr ] )
10854 this.eCalcMode = aCalcModeInMap[ sCalcModeAttr ];
10857 this.aFromValue = aAnimElem.getAttributeNS( NSS['smil'], 'from' );
10860 this.aByValue = aAnimElem.getAttributeNS( NSS['smil'], 'by' );
10862 // keyTimes attribute
10863 this.aKeyTimes = [];
10864 var sKeyTimesAttr = aAnimElem.getAttributeNS( NSS['smil'], 'keyTimes' );
10865 sKeyTimesAttr = removeWhiteSpaces( sKeyTimesAttr );
10866 if( sKeyTimesAttr )
10868 var aKeyTimes = sKeyTimesAttr.split( ';' );
10869 for( var i = 0; i < aKeyTimes.length; ++i )
10870 this.aKeyTimes.push( parseFloat( aKeyTimes[i] ) );
10873 // values attribute
10874 var sValuesAttr = aAnimElem.getAttributeNS( NSS['smil'], 'values' );
10877 this.aValues = sValuesAttr.split( ';' );
10884 // formula attribute
10885 this.aFormula = aAnimElem.getAttributeNS( NSS['anim'], 'formula' );
10890 AnimationBaseNode3.prototype.getAccumulate = function()
10892 return this.eAccumulate;
10895 AnimationBaseNode3.prototype.getCalcMode = function()
10897 return this.eCalcMode;
10900 AnimationBaseNode3.prototype.getFromValue = function()
10902 return this.aFromValue;
10905 AnimationBaseNode3.prototype.getByValue = function()
10907 return this.aByValue;
10910 AnimationBaseNode3.prototype.getKeyTimes = function()
10912 return this.aKeyTimes;
10915 AnimationBaseNode3.prototype.getValues = function()
10917 return this.aValues;
10920 AnimationBaseNode3.prototype.getFormula = function()
10922 return this.aFormula;
10925 AnimationBaseNode3.prototype.info = function( bVerbose )
10927 var sInfo = AnimationBaseNode3.superclass.info.call( this, bVerbose );
10932 if( this.getAccumulate() )
10933 sInfo += '; accumulate: ' + aAccumulateModeOutMap[ this.getAccumulate() ];
10936 sInfo += '; calcMode: ' + aCalcModeOutMap[ this.getCalcMode() ];
10939 if( this.getFromValue() )
10940 sInfo += '; from: ' + this.getFromValue();
10943 if( this.getByValue() )
10944 sInfo += '; by: ' + this.getByValue();
10947 if( this.getKeyTimes().length )
10948 sInfo += '; keyTimes: ' + this.getKeyTimes().join( ',' );
10951 if( this.getValues().length )
10952 sInfo += '; values: ' + this.getValues().join( ',' );
10955 if( this.getFormula() )
10956 sInfo += '; formula: ' + this.getFormula();
10965 function BaseContainerNode( aAnimElem, aParentNode, aNodeContext )
10967 BaseContainerNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
10969 this.sClassName = 'BaseContainerNode';
10970 this.bIsContainer = true;
10971 this.aChildrenArray = [];
10972 this.nFinishedChildren = 0;
10973 this.bDurationIndefinite = false;
10974 this.nLeftIterations = 1;
10976 this.eImpressNodeType = undefined;
10977 this.ePresetClass = undefined;
10978 this.ePresetId = undefined;
10980 extend( BaseContainerNode, BaseNode );
10983 BaseContainerNode.prototype.parseElement= function()
10985 var bRet = BaseContainerNode.superclass.parseElement.call( this );
10987 var aAnimElem = this.aElement;
10989 // node-type attribute
10990 this.eImpressNodeType = IMPRESS_DEFAULT_NODE;
10991 var sNodeTypeAttr = aAnimElem.getAttributeNS( NSS['presentation'], 'node-type' );
10992 if( sNodeTypeAttr && aImpressNodeTypeInMap[ sNodeTypeAttr ] )
10993 this.eImpressNodeType = aImpressNodeTypeInMap[ sNodeTypeAttr ];
10994 this.bMainSequenceRootNode = ( this.eImpressNodeType == IMPRESS_MAIN_SEQUENCE_NODE );
10995 this.bInteractiveSequenceRootNode = ( this.eImpressNodeType == IMPRESS_INTERACTIVE_SEQUENCE_NODE );
10997 // preset-class attribute
10998 this.ePresetClass = undefined;
10999 var sPresetClassAttr = aAnimElem.getAttributeNS( NSS['presentation'], 'preset-class' );
11000 if( sPresetClassAttr && aPresetClassInMap[ sPresetClassAttr ] )
11001 this.ePresetClass = aPresetClassInMap[ sPresetClassAttr ];
11003 // preset-id attribute
11004 this.ePresetId = undefined;
11005 var sPresetIdAttr = aAnimElem.getAttributeNS( NSS['presentation'], 'preset-id' );
11006 if( sPresetIdAttr && aPresetIdInMap[ sPresetIdAttr ] )
11007 this.ePresetId = aPresetIdInMap[ sPresetIdAttr ];
11010 // parse children elements
11011 var nChildrenCount = this.aChildrenArray.length;
11012 for( var i = 0; i < nChildrenCount; ++i )
11014 this.aChildrenArray[i].parseElement();
11018 // resolve duration
11019 this.bDurationIndefinite
11020 = ( !this.getDuration() || this.getDuration().isIndefinite() ) &&
11021 ( !this.getEnd() || ( this.getEnd().getType() != OFFSET_TIMING ) );
11026 BaseContainerNode.prototype.appendChildNode = function( aAnimationNode )
11028 if( ! this.checkValidNode() )
11031 if( aAnimationNode.registerDeactivatingListener( this ) )
11032 this.aChildrenArray.push( aAnimationNode );
11035 BaseContainerNode.prototype.removeAllChildrenNodes = function()
11037 this.aChildrenArray = [];
11040 BaseContainerNode.prototype.init_st = function()
11042 this.nLeftIterations = this.getRepeatCount();
11044 return this.init_children();
11047 BaseContainerNode.prototype.init_children = function()
11049 this.nFinishedChildren = 0;
11050 var nChildrenCount = this.aChildrenArray.length;
11051 var nInitChildren = 0;
11052 for( var i = 0; i < nChildrenCount; ++i )
11054 if( this.aChildrenArray[i].init() )
11059 return ( nChildrenCount == nInitChildren );
11063 BaseContainerNode.prototype.deactivate_st = function( eDestState )
11065 this.nLeftIterations = 0;
11066 if( eDestState == FROZEN_NODE )
11068 // deactivate all children that are not FROZEN or ENDED:
11069 this.forEachChildNode( mem_fn( 'deactivate' ), ~( FROZEN_NODE | ENDED_NODE ) );
11073 // end all children that are not ENDED:
11074 this.forEachChildNode( mem_fn( 'end' ), ~ENDED_NODE );
11075 if( this.getFillMode() == FILL_MODE_REMOVE )
11076 this.removeEffect();
11080 BaseContainerNode.prototype.hasPendingAnimation = function()
11082 var nChildrenCount = this.aChildrenArray.length;
11083 for( var i = 0; i < nChildrenCount; ++i )
11085 if( this.aChildrenArray[i].hasPendingAnimation() )
11091 BaseContainerNode.prototype.activate_st = function()
11093 log( 'BaseContainerNode.activate_st: abstract method called' );
11096 BaseContainerNode.prototype.notifyDeactivating = function( /*aAnimationNode*/ )
11098 log( 'BaseContainerNode.notifyDeactivating: abstract method called' );
11101 BaseContainerNode.prototype.isDurationIndefinite = function()
11103 return this.bDurationIndefinite;
11106 BaseContainerNode.prototype.isChildNode = function( aAnimationNode )
11108 var nChildrenCount = this.aChildrenArray.length;
11109 for( var i = 0; i < nChildrenCount; ++i )
11111 if( this.aChildrenArray[i].getId() == aAnimationNode.getId() )
11117 BaseContainerNode.prototype.notifyDeactivatedChild = function( aChildNode )
11119 assert( ( aChildNode.getState() == FROZEN_NODE ) || ( aChildNode.getState() == ENDED_NODE ),
11120 'BaseContainerNode.notifyDeactivatedChild: passed child node is neither in FROZEN nor in ENDED state' );
11122 assert( this.getState() != INVALID_NODE,
11123 'BaseContainerNode.notifyDeactivatedChild: this node is invalid' );
11125 if( !this.isChildNode( aChildNode ) )
11127 log( 'BaseContainerNode.notifyDeactivatedChild: unknown child notifier!' );
11131 var nChildrenCount = this.aChildrenArray.length;
11133 assert( ( this.nFinishedChildren < nChildrenCount ),
11134 'BaseContainerNode.notifyDeactivatedChild: assert(this.nFinishedChildren < nChildrenCount) failed' );
11136 ++this.nFinishedChildren;
11137 var bFinished = ( this.nFinishedChildren >= nChildrenCount );
11139 if( bFinished && this.isDurationIndefinite() )
11141 if( this.nLeftIterations >= 1.0 )
11143 this.nLeftIterations -= 1.0;
11145 if( this.nLeftIterations >= 1.0 )
11148 var aRepetitionEvent = makeDelay( bind( this, this.repeat ), 0.0 );
11149 this.aContext.aTimerEventQueue.addEvent( aRepetitionEvent );
11160 BaseContainerNode.prototype.repeat = function()
11162 // end all children that are not ENDED:
11163 this.forEachChildNode( mem_fn( 'end' ), ~ENDED_NODE );
11164 this.removeEffect();
11165 var bInitialized = this.init_children();
11167 this.activate_st();
11168 return bInitialized;
11171 BaseContainerNode.prototype.removeEffect = function()
11173 var nChildrenCount = this.aChildrenArray.length;
11174 if( nChildrenCount == 0 )
11176 // We remove effect in reverse order.
11177 for( var i = nChildrenCount - 1; i >= 0; --i )
11179 if( ( this.aChildrenArray[i].getState() & ( FROZEN_NODE | ENDED_NODE ) ) == 0 )
11181 log( 'BaseContainerNode.removeEffect: child(id:'
11182 + this.aChildrenArray[i].getId() + ') is neither frozen nor ended;'
11184 + aTransitionModeOutMap[ this.aChildrenArray[i].getState() ] );
11187 this.aChildrenArray[i].removeEffect();
11191 BaseContainerNode.prototype.saveStateOfAnimatedElement = function()
11193 var nChildrenCount = this.aChildrenArray.length;
11194 for( var i = 0; i < nChildrenCount; ++i )
11196 this.aChildrenArray[i].saveStateOfAnimatedElement();
11200 BaseContainerNode.prototype.forEachChildNode = function( aFunction, eNodeStateMask )
11202 if( !eNodeStateMask )
11203 eNodeStateMask = -1;
11205 var nChildrenCount = this.aChildrenArray.length;
11206 for( var i = 0; i < nChildrenCount; ++i )
11208 if( ( eNodeStateMask != -1 ) && ( ( this.aChildrenArray[i].getState() & eNodeStateMask ) == 0 ) )
11210 aFunction( this.aChildrenArray[i] );
11214 BaseContainerNode.prototype.dispose = function()
11216 var nChildrenCount = this.aChildrenArray.length;
11217 for( var i = 0; i < nChildrenCount; ++i )
11219 this.aChildrenArray[i].dispose();
11222 BaseContainerNode.superclass.dispose.call( this );
11225 BaseContainerNode.prototype.getImpressNodeType = function()
11227 return this.eImpressNodeType;
11230 BaseContainerNode.prototype.info = function( bVerbose )
11232 var sInfo = BaseContainerNode.superclass.info.call( this, bVerbose );
11236 // impress node type
11237 if( this.getImpressNodeType() )
11238 sInfo += '; node-type: ' + aImpressNodeTypeOutMap[ this.getImpressNodeType() ];
11241 var nChildrenCount = this.aChildrenArray.length;
11242 for( var i = 0; i < nChildrenCount; ++i )
11245 sInfo += this.aChildrenArray[i].info( bVerbose );
11252 function ParallelTimeContainer( aAnimElem, aParentNode, aNodeContext )
11254 ParallelTimeContainer.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
11256 this.sClassName = 'ParallelTimeContainer';
11258 extend( ParallelTimeContainer, BaseContainerNode );
11261 ParallelTimeContainer.prototype.activate_st = function()
11263 var nChildrenCount = this.aChildrenArray.length;
11264 var nResolvedChildren = 0;
11265 for( var i = 0; i < nChildrenCount; ++i )
11267 if( this.aChildrenArray[i].resolve() )
11269 ++nResolvedChildren;
11273 if( nChildrenCount != nResolvedChildren )
11275 log( 'ParallelTimeContainer.activate_st: resolving all children failed' );
11280 if( this.isDurationIndefinite() && ( nChildrenCount == 0 ) )
11282 this.scheduleDeactivationEvent( this.makeDeactivationEvent( 0.0 ) );
11286 this.scheduleDeactivationEvent();
11290 ParallelTimeContainer.prototype.notifyDeactivating = function( aAnimationNode )
11292 this.notifyDeactivatedChild( aAnimationNode );
11298 function SequentialTimeContainer( aAnimElem, aParentNode, aNodeContext )
11300 SequentialTimeContainer.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
11302 this.sClassName = 'SequentialTimeContainer';
11303 this.bIsRewinding = false;
11304 this.aCurrentSkipEvent = null;
11305 this.aRewindCurrentEffectEvent = null;
11306 this.aRewindLastEffectEvent = null;
11308 extend( SequentialTimeContainer, BaseContainerNode );
11311 SequentialTimeContainer.prototype.activate_st = function()
11313 var nChildrenCount = this.aChildrenArray.length;
11314 for( ; this.nFinishedChildren < nChildrenCount; ++this.nFinishedChildren )
11316 if( this.resolveChild( this.aChildrenArray[ this.nFinishedChildren ] ) )
11319 log( 'SequentialTimeContainer.activate_st: resolving child failed!' );
11322 if( this.isDurationIndefinite() && ( ( nChildrenCount == 0 ) || ( this.nFinishedChildren >= nChildrenCount ) ) )
11324 // deactivate ASAP:
11325 this.scheduleDeactivationEvent( this.makeDeactivationEvent( 0.0 ) );
11329 this.scheduleDeactivationEvent();
11333 SequentialTimeContainer.prototype.notifyDeactivating = function( aNotifier )
11335 // If we are rewinding we have not to resolve the next child.
11336 if( this.bIsRewinding )
11339 if( this.notifyDeactivatedChild( aNotifier ) )
11342 assert( this.nFinishedChildren < this.aChildrenArray.length,
11343 'SequentialTimeContainer.notifyDeactivating: assertion (this.nFinishedChildren < this.aChildrenArray.length) failed' );
11345 var aNextChild = this.aChildrenArray[ this.nFinishedChildren ];
11347 assert( aNextChild.getState() == UNRESOLVED_NODE,
11348 'SequentialTimeContainer.notifyDeactivating: assertion (aNextChild.getState == UNRESOLVED_NODE) failed' );
11350 if( !this.resolveChild( aNextChild ) )
11352 // could not resolve child - since we risk to
11353 // stall the chain of events here, play it safe
11354 // and deactivate this node (only if we have
11355 // indefinite duration - otherwise, we'll get a
11356 // deactivation event, anyways).
11362 * Skip the current playing shape effect.
11363 * Requires: the current node is the main sequence root node.
11365 * @param aChildNode
11366 * An animation node representing the root node of the shape effect being
11369 SequentialTimeContainer.prototype.skipEffect = function( aChildNode )
11371 if( this.isChildNode( aChildNode ) )
11373 // First off we end all queued activities.
11374 this.getContext().aActivityQueue.endAll();
11375 // We signal that we are going to skip all subsequent animations by
11376 // setting the bIsSkipping flag to 'true', then all queued events are
11377 // fired immediately. In such a way the correct order of the various
11378 // events that belong to the animation time-line is preserved.
11379 this.getContext().bIsSkipping = true;
11380 this.getContext().aTimerEventQueue.forceEmpty();
11381 this.getContext().bIsSkipping = false;
11382 var aEvent = makeEvent( bind2( aChildNode.deactivate, aChildNode ) );
11383 this.getContext().aTimerEventQueue.addEvent( aEvent );
11387 log( 'SequentialTimeContainer.skipEffect: unknown child: '
11388 + aChildNode.getId() );
11392 /** rewindCurrentEffect
11393 * Rewind a playing shape effect.
11394 * Requires: the current node is the main sequence root node.
11396 * @param aChildNode
11397 * An animation node representing the root node of the shape effect being
11400 SequentialTimeContainer.prototype.rewindCurrentEffect = function( aChildNode )
11402 if( this.isChildNode( aChildNode ) )
11404 assert( !this.bIsRewinding,
11405 'SequentialTimeContainer.rewindCurrentEffect: is already rewinding.' );
11407 // We signal we are rewinding so the notifyDeactivating method returns
11408 // immediately without increment the finished children counter and
11409 // resolve the next child.
11410 this.bIsRewinding = true;
11411 // First off we end all queued activities.
11412 this.getContext().aActivityQueue.endAll();
11413 // We signal that we are going to skip all subsequent animations by
11414 // setting the bIsSkipping flag to 'true', then all queued events are
11415 // fired immediately. In such a way the correct order of the various
11416 // events that belong to the animation time-line is preserved.
11417 this.getContext().bIsSkipping = true;
11418 this.getContext().aTimerEventQueue.forceEmpty();
11419 this.getContext().bIsSkipping = false;
11420 // We end all new activities appended to the activity queue by
11421 // the fired events.
11422 this.getContext().aActivityQueue.endAll();
11424 // Now we perform a final 'end' and restore the animated shape to
11425 // the state it was before the current effect was applied.
11427 aChildNode.removeEffect();
11428 // Finally we place the child node to the 'unresolved' state and
11429 // resolve it again.
11431 this.resolveChild( aChildNode );
11432 this.notifyRewindedEvent( aChildNode );
11433 this.bIsRewinding = false;
11437 log( 'SequentialTimeContainer.rewindCurrentEffect: unknown child: '
11438 + aChildNode.getId() );
11442 /** rewindLastEffect
11443 * Rewind the last ended effect.
11444 * Requires: the current node is the main sequence root node.
11446 * @param aChildNode
11447 * An animation node representing the root node of the next shape effect
11450 SequentialTimeContainer.prototype.rewindLastEffect = function( aChildNode )
11452 if( this.isChildNode( aChildNode ) )
11454 assert( !this.bIsRewinding,
11455 'SequentialTimeContainer.rewindLastEffect: is already rewinding.' );
11457 // We signal we are rewinding so the notifyDeactivating method returns
11458 // immediately without increment the finished children counter and
11459 // resolve the next child.
11460 this.bIsRewinding = true;
11461 // We end the current effect.
11462 this.getContext().aTimerEventQueue.forceEmpty();
11463 this.getContext().aActivityQueue.clear();
11465 // Invoking the end method on the current child node that has not yet
11466 // been activated should not lead to any change on the animated shape.
11467 // However for safety we used to call the removeEffect method but
11468 // lately we noticed that when interactive animation sequences are
11469 // involved into the shape effect invoking such a method causes
11471 //aChildNode.removeEffect();
11473 // As we rewind the previous effect we need to decrease the finished
11474 // children counter.
11475 --this.nFinishedChildren;
11476 var aPreviousChildNode = this.aChildrenArray[ this.nFinishedChildren ];
11477 // No need to invoke the end method for the previous child as it is
11478 // already in the ENDED state.
11480 aPreviousChildNode.removeEffect();
11481 // We place the child node to the 'unresolved' state.
11482 aPreviousChildNode.init();
11483 // We need to re-initialize the old current child too, because it is
11484 // in ENDED state now, On the contrary it cannot be resolved again later.
11486 this.resolveChild( aPreviousChildNode );
11487 this.notifyRewindedEvent( aChildNode );
11488 this.bIsRewinding = false;
11492 log( 'SequentialTimeContainer.rewindLastEffect: unknown child: '
11493 + aChildNode.getId() );
11498 * Resolve the passed child.
11499 * In case this node is a main sequence root node events for skipping and
11500 * rewinding the effect related to the passed child node are created and
11503 * @param aChildNode
11504 * An animation node representing the root node of the next shape effect
11507 * It returns true if the passed child has been resolved successfully,
11510 SequentialTimeContainer.prototype.resolveChild = function( aChildNode )
11512 var bResolved = aChildNode.resolve();
11514 if( bResolved && ( this.isMainSequenceRootNode() || this.isInteractiveSequenceRootNode() ) )
11516 if( this.aCurrentSkipEvent )
11517 this.aCurrentSkipEvent.dispose();
11518 this.aCurrentSkipEvent = makeEvent( bind2( SequentialTimeContainer.prototype.skipEffect, this, aChildNode ) );
11520 if( this.aRewindCurrentEffectEvent )
11521 this.aRewindCurrentEffectEvent.dispose();
11522 this.aRewindCurrentEffectEvent = makeEvent( bind2( SequentialTimeContainer.prototype.rewindCurrentEffect, this, aChildNode ) );
11524 if( this.aRewindLastEffectEvent )
11525 this.aRewindLastEffectEvent.dispose();
11526 this.aRewindLastEffectEvent = makeEvent( bind2( SequentialTimeContainer.prototype.rewindLastEffect, this, aChildNode ) );
11528 if( this.isMainSequenceRootNode() )
11530 this.aContext.aEventMultiplexer.registerSkipEffectEvent( this.aCurrentSkipEvent );
11531 this.aContext.aEventMultiplexer.registerRewindCurrentEffectEvent( this.aRewindCurrentEffectEvent );
11532 this.aContext.aEventMultiplexer.registerRewindLastEffectEvent( this.aRewindLastEffectEvent );
11534 else if( this.isInteractiveSequenceRootNode() )
11536 this.aContext.aEventMultiplexer.registerSkipInteractiveEffectEvent( aChildNode.getId(), this.aCurrentSkipEvent );
11537 this.aContext.aEventMultiplexer.registerRewindRunningInteractiveEffectEvent( aChildNode.getId(), this.aRewindCurrentEffectEvent );
11538 this.aContext.aEventMultiplexer.registerRewindEndedInteractiveEffectEvent( aChildNode.getId(), this.aRewindLastEffectEvent );
11544 SequentialTimeContainer.prototype.notifyRewindedEvent = function( aChildNode )
11546 if( this.isInteractiveSequenceRootNode() )
11548 this.aContext.aEventMultiplexer.notifyRewindedEffectEvent( aChildNode.getId() );
11550 var sId = aChildNode.getBegin().getEventBaseElementId();
11553 this.aContext.aEventMultiplexer.notifyRewindedEffectEvent( sId );
11558 SequentialTimeContainer.prototype.dispose = function()
11560 if( this.aCurrentSkipEvent )
11561 this.aCurrentSkipEvent.dispose();
11563 SequentialTimeContainer.superclass.dispose.call( this );
11569 function PropertyAnimationNode( aAnimElem, aParentNode, aNodeContext )
11571 PropertyAnimationNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
11573 this.sClassName = 'PropertyAnimationNode';
11575 extend( PropertyAnimationNode, AnimationBaseNode3 );
11578 PropertyAnimationNode.prototype.createActivity = function()
11580 var aActivityParamSet = this.fillActivityParams();
11582 var aAnimation = createPropertyAnimation( this.getAttributeName(),
11583 this.getAnimatedElement(),
11584 this.aNodeContext.aSlideWidth,
11585 this.aNodeContext.aSlideHeight );
11587 var aInterpolator = null; // createActivity will compute it;
11588 return createActivity( aActivityParamSet, this, aAnimation, aInterpolator );
11594 function isValidTransformation( sType )
11596 return ( sType === 'translate' || sType === 'scale' || sType === 'rotate'
11597 || sType === 'skewX' || sType === 'skewY' );
11600 function AnimationTransformNode( aAnimElem, aParentNode, aNodeContext )
11602 AnimationTransformNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
11604 this.sClassName = 'AnimationTransformNode';
11606 extend( AnimationTransformNode, AnimationBaseNode3 );
11609 AnimationTransformNode.prototype.parseElement = function()
11611 var bRet = AnimationTransformNode.superclass.parseElement.call(this);
11613 var aAnimElem = this.aElement;
11615 // transformation type
11616 var sTransformType = aAnimElem.getAttribute( 'svg:type' );
11617 if( !isValidTransformation( sTransformType ) )
11619 this.eCurrentState = INVALID_NODE;
11620 log( 'AnimationTransformNode.parseElement: transformation type not found: ' + sTransformType );
11624 this.sAttributeName = sTransformType;
11630 AnimationTransformNode.prototype.createActivity = function()
11632 var aActivityParamSet = this.fillActivityParams();
11635 if( this.getAttributeName() === 'scale' || this.getAttributeName() === 'translate' )
11637 aAnimation = createPairPropertyAnimation( this.getAttributeName(),
11638 this.getAnimatedElement(),
11639 this.aNodeContext.aSlideWidth,
11640 this.aNodeContext.aSlideHeight );
11645 aAnimation = createPropertyAnimation( this.getAttributeName(),
11646 this.getAnimatedElement(),
11647 this.aNodeContext.aSlideWidth,
11648 this.aNodeContext.aSlideHeight );
11651 var aInterpolator = null; // createActivity will compute it;
11652 return createActivity( aActivityParamSet, this, aAnimation, aInterpolator );
11658 function AnimationSetNode( aAnimElem, aParentNode, aNodeContext )
11660 AnimationSetNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
11662 this.sClassName = 'AnimationSetNode';
11664 extend( AnimationSetNode, AnimationBaseNode2 );
11667 AnimationSetNode.prototype.createActivity = function()
11669 var aAnimation = createPropertyAnimation( this.getAttributeName(),
11670 this.getAnimatedElement(),
11671 this.aNodeContext.aSlideWidth,
11672 this.aNodeContext.aSlideHeight );
11674 var aActivityParamSet = this.fillActivityParams();
11676 return new SetActivity( aActivityParamSet, aAnimation, this.getToValue() );
11682 function AnimationColorNode( aAnimElem, aParentNode, aNodeContext )
11684 AnimationColorNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
11686 this.sClassName = 'AnimationColorNode';
11688 this.eColorInterpolation = undefined;
11689 this.eColorInterpolationDirection = undefined;
11691 extend( AnimationColorNode, AnimationBaseNode3 );
11694 AnimationColorNode.prototype.parseElement = function()
11696 var bRet = AnimationColorNode.superclass.parseElement.call( this );
11698 var aAnimElem = this.aElement;
11700 // color-interpolation attribute
11701 this.eColorInterpolation = COLOR_SPACE_RGB;
11702 var sColorInterpolationAttr = aAnimElem.getAttributeNS( NSS['anim'], 'color-interpolation' );
11703 if( sColorInterpolationAttr && aColorSpaceInMap[ sColorInterpolationAttr ] )
11704 this.eColorInterpolation = aColorSpaceInMap[ sColorInterpolationAttr ];
11706 // color-interpolation-direction attribute
11707 this.eColorInterpolationDirection = CLOCKWISE;
11708 var sColorInterpolationDirectionAttr = aAnimElem.getAttributeNS( NSS['anim'], 'color-interpolation-direction' );
11709 if( sColorInterpolationDirectionAttr && aClockDirectionInMap[ sColorInterpolationDirectionAttr ] )
11710 this.eColorInterpolationDirection = aClockDirectionInMap[ sColorInterpolationDirectionAttr ];
11715 AnimationColorNode.prototype.createActivity = function()
11717 var aActivityParamSet = this.fillActivityParams();
11719 var aAnimation = createPropertyAnimation( this.getAttributeName(),
11720 this.getAnimatedElement(),
11721 this.aNodeContext.aSlideWidth,
11722 this.aNodeContext.aSlideHeight );
11724 var aColorAnimation;
11726 if( this.getColorInterpolation() === COLOR_SPACE_HSL )
11728 ANIMDBG.print( 'AnimationColorNode.createActivity: color space hsl' );
11729 aColorAnimation = new HSLAnimationWrapper( aAnimation );
11730 var aInterpolatorMaker = aInterpolatorHandler.getInterpolator( this.getCalcMode(),
11733 aInterpolator = aInterpolatorMaker( this.getColorInterpolationDirection() );
11737 ANIMDBG.print( 'AnimationColorNode.createActivity: color space rgb' );
11738 aColorAnimation = aAnimation;
11739 aInterpolator = aInterpolatorHandler.getInterpolator( this.getCalcMode(),
11744 return createActivity( aActivityParamSet, this, aColorAnimation, aInterpolator );
11747 AnimationColorNode.prototype.getColorInterpolation = function()
11749 return this.eColorInterpolation;
11752 AnimationColorNode.prototype.getColorInterpolationDirection = function()
11754 return this.eColorInterpolationDirection;
11757 AnimationColorNode.prototype.info = function( bVerbose )
11759 var sInfo = AnimationColorNode.superclass.info.call( this, bVerbose );
11763 // color interpolation
11764 sInfo += '; color-interpolation: ' + aColorSpaceOutMap[ this.getColorInterpolation() ];
11766 // color interpolation direction
11767 sInfo += '; color-interpolation-direction: ' + aClockDirectionOutMap[ this.getColorInterpolationDirection() ];
11775 function AnimationTransitionFilterNode( aAnimElem, aParentNode, aNodeContext )
11777 AnimationTransitionFilterNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
11779 this.sClassName = 'AnimationTransitionFilterNode';
11781 this.eTransitionType = undefined;
11782 this.eTransitionSubType = undefined;
11783 this.bReverseDirection = undefined;
11784 this.eTransitionMode = undefined;
11786 extend( AnimationTransitionFilterNode, AnimationBaseNode );
11789 AnimationTransitionFilterNode.prototype.createActivity = function()
11791 var aActivityParamSet = this.fillActivityParams();
11793 return createShapeTransition( aActivityParamSet,
11794 this.getAnimatedElement(),
11795 this.aNodeContext.aSlideWidth,
11796 this.aNodeContext.aSlideHeight,
11800 AnimationTransitionFilterNode.prototype.parseElement = function()
11802 var bRet = AnimationTransitionFilterNode.superclass.parseElement.call( this );
11803 var bIsValidTransition = true;
11805 var aAnimElem = this.aElement;
11808 this.eTransitionType = undefined;
11809 var sTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'type' );
11810 if( sTypeAttr && aTransitionTypeInMap[ sTypeAttr ] )
11812 this.eTransitionType = aTransitionTypeInMap[ sTypeAttr ];
11816 bIsValidTransition = false;
11817 log( 'AnimationTransitionFilterNode.parseElement: transition type not valid: ' + sTypeAttr );
11820 // subtype attribute
11821 this.eTransitionSubType = undefined;
11822 var sSubTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'subtype' );
11823 if( sSubTypeAttr === null )
11824 sSubTypeAttr = 'default';
11825 if( sSubTypeAttr && ( aTransitionSubtypeInMap[ sSubTypeAttr ] !== undefined ) )
11827 this.eTransitionSubType = aTransitionSubtypeInMap[ sSubTypeAttr ];
11831 bIsValidTransition = false;
11832 log( 'AnimationTransitionFilterNode.parseElement: transition subtype not valid: ' + sSubTypeAttr );
11835 // if we do not support the requested transition type we fall back to crossfade transition;
11836 // note: if we do not provide an alternative transition and we set the state of the animation node to 'invalid'
11837 // the animation engine stops itself;
11838 if( !bIsValidTransition )
11840 this.eTransitionType = FADE_TRANSITION;
11841 this.eTransitionSubType = CROSSFADE_TRANS_SUBTYPE;
11842 log( 'AnimationTransitionFilterNode.parseElement: in place of the invalid transition a crossfade transition is used' );
11845 // direction attribute
11846 this.bReverseDirection = false;
11847 var sDirectionAttr = aAnimElem.getAttributeNS( NSS['smil'], 'direction' );
11848 if( sDirectionAttr == 'reverse' )
11849 this.bReverseDirection = true;
11852 this.eTransitionMode = TRANSITION_MODE_IN;
11853 var sModeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'mode' );
11854 if( sModeAttr === 'out' )
11855 this.eTransitionMode = TRANSITION_MODE_OUT;
11860 AnimationTransitionFilterNode.prototype.getTransitionType = function()
11862 return this.eTransitionType;
11865 AnimationTransitionFilterNode.prototype.getTransitionSubType = function()
11867 return this.eTransitionSubType;
11870 AnimationTransitionFilterNode.prototype.getTransitionMode = function()
11872 return this.eTransitionMode;
11875 AnimationTransitionFilterNode.prototype.getReverseDirection = function()
11877 return this.bReverseDirection;
11880 AnimationTransitionFilterNode.prototype.info = function( bVerbose )
11882 var sInfo = AnimationTransitionFilterNode.superclass.info.call( this, bVerbose );
11887 sInfo += '; type: ' + getKeyByValue(aTransitionTypeInMap, this.getTransitionType());
11889 // transition subtype
11890 sInfo += '; subtype: ' + getKeyByValue(aTransitionSubtypeInMap, this.getTransitionSubType());
11892 // transition direction
11893 if( this.getReverseDirection() )
11894 sInfo += '; direction: reverse';
11902 /**********************************************************************************************
11903 * Animation Node Factory
11904 **********************************************************************************************/
11907 function createAnimationTree( aRootElement, aNodeContext )
11909 return createAnimationNode( aRootElement, null, aNodeContext );
11915 function createAnimationNode( aElement, aParentNode, aNodeContext )
11917 assert( aElement, 'createAnimationNode: invalid animation element' );
11919 var eAnimationNodeType = getAnimationElementType( aElement );
11921 var aCreatedNode = null;
11922 var aCreatedContainer = null;
11924 switch( eAnimationNodeType )
11926 case ANIMATION_NODE_PAR:
11927 aCreatedNode = aCreatedContainer =
11928 new ParallelTimeContainer( aElement, aParentNode, aNodeContext );
11930 case ANIMATION_NODE_ITERATE:
11931 // map iterate container to ParallelTimeContainer.
11932 // the iterating functionality is to be found
11933 // below, (see method implCreateIteratedNodes)
11934 aCreatedNode = aCreatedContainer =
11935 new ParallelTimeContainer( aElement, aParentNode, aNodeContext );
11937 case ANIMATION_NODE_SEQ:
11938 aCreatedNode = aCreatedContainer =
11939 new SequentialTimeContainer( aElement, aParentNode, aNodeContext );
11941 case ANIMATION_NODE_ANIMATE:
11942 aCreatedNode = new PropertyAnimationNode( aElement, aParentNode, aNodeContext );
11944 case ANIMATION_NODE_SET:
11945 aCreatedNode = new AnimationSetNode( aElement, aParentNode, aNodeContext );
11947 case ANIMATION_NODE_ANIMATEMOTION:
11948 //aCreatedNode = new AnimationPathMotionNode( aElement, aParentNode, aNodeContext );
11950 log( 'createAnimationNode: ANIMATEMOTION not implemented' );
11952 case ANIMATION_NODE_ANIMATECOLOR:
11953 aCreatedNode = new AnimationColorNode( aElement, aParentNode, aNodeContext );
11955 case ANIMATION_NODE_ANIMATETRANSFORM:
11956 aCreatedNode = new AnimationTransformNode( aElement, aParentNode, aNodeContext );
11958 case ANIMATION_NODE_TRANSITIONFILTER:
11959 aCreatedNode = new AnimationTransitionFilterNode( aElement, aParentNode, aNodeContext );
11961 case ANIMATION_NODE_AUDIO:
11962 log( 'createAnimationNode: AUDIO not implemented' );
11964 case ANIMATION_NODE_COMMAND:
11965 log( 'createAnimationNode: COMMAND not implemented' );
11968 log( 'createAnimationNode: invalid Animation Node Type: ' + eAnimationNodeType );
11972 if( aCreatedContainer )
11974 if( eAnimationNodeType == ANIMATION_NODE_ITERATE )
11976 createIteratedNodes( aElement, aCreatedContainer, aNodeContext );
11980 var aChildrenArray = getElementChildren( aElement );
11981 for( var i = 0; i < aChildrenArray.length; ++i )
11983 if( !createChildNode( aChildrenArray[i], aCreatedContainer, aNodeContext ) )
11985 aCreatedContainer.removeAllChildrenNodes();
11992 return aCreatedNode;
11998 function createChildNode( aElement, aParentNode, aNodeContext )
12000 var aChildNode = createAnimationNode( aElement, aParentNode, aNodeContext );
12004 log( 'createChildNode: child node creation failed' );
12009 aParentNode.appendChildNode( aChildNode );
12017 function createIteratedNodes( /*aElement, aContainerNode, aNodeContext*/ )
12024 /**********************************************************************************************
12025 * Animation Factory
12026 **********************************************************************************************/
12028 // makeScaler is used in aAttributeMap:
12029 // eslint-disable-next-line no-unused-vars
12030 function makeScaler( nScale )
12032 if( ( typeof( nScale ) !== typeof( 0 ) ) || !isFinite( nScale ) )
12034 log( 'makeScaler: not valid param passed: ' + nScale );
12038 return function( nValue )
12040 return ( nScale * nValue );
12046 // eslint-disable-next-line no-unused-vars
12047 function createPropertyAnimation( sAttrName, aAnimatedElement, nWidth, nHeight )
12049 if( !aAttributeMap[ sAttrName ] )
12051 log( 'createPropertyAnimation: attribute is unknown' );
12056 var aFunctorSet = aAttributeMap[ sAttrName ];
12058 var sGetValueMethod = aFunctorSet.get;
12059 var sSetValueMethod = aFunctorSet.set;
12061 if( !sGetValueMethod || !sSetValueMethod )
12063 log( 'createPropertyAnimation: attribute is not handled' );
12067 var aGetModifier = eval( aFunctorSet.getmod );
12068 var aSetModifier = eval( aFunctorSet.setmod );
12071 return new GenericAnimation( bind( aAnimatedElement, aAnimatedElement[ sGetValueMethod ] ),
12072 bind( aAnimatedElement, aAnimatedElement[ sSetValueMethod ] ),
12080 function createPairPropertyAnimation( sTransformType, aAnimatedElement, nWidth, nHeight )
12082 var aFunctorSet = aAttributeMap[ sTransformType ];
12083 var sGetValueMethod = aFunctorSet.get;
12084 var sSetValueMethod = aFunctorSet.set;
12086 var aDefaultValue = [];
12087 var aSizeReference = [];
12088 if( sTransformType === 'scale' )
12090 aDefaultValue[0] = aSizeReference[0] = aAnimatedElement.getBaseBBox().width;
12091 aDefaultValue[1] = aSizeReference[1] = aAnimatedElement.getBaseBBox().height;
12093 else if( sTransformType === 'translate' )
12095 aDefaultValue[0] = aAnimatedElement.getBaseCenterX();
12096 aDefaultValue[1] = aAnimatedElement.getBaseCenterY();
12097 aSizeReference[0] = nWidth;
12098 aSizeReference[1] = nHeight;
12102 log( 'createPairPropertyAnimation: transform type is not handled' );
12106 return new TupleAnimation( bind( aAnimatedElement, aAnimatedElement[ sGetValueMethod ] ),
12107 bind( aAnimatedElement, aAnimatedElement[ sSetValueMethod ] ),
12115 /** createShapeTransition
12117 * @param aActivityParamSet
12118 * The set of property for the activity to be created.
12119 * @param aAnimatedElement
12120 * The element to be animated.
12121 * @param nSlideWidth
12122 * The width of a slide.
12123 * @param nSlideHeight
12124 * The height of a slide.
12125 * @param aAnimatedTransitionFilterNode
12126 * An instance of the AnimationFilterNode that invoked this function.
12127 * @return {SimpleActivity}
12128 * A simple activity handling a shape transition.
12130 function createShapeTransition( aActivityParamSet, aAnimatedElement,
12131 nSlideWidth, nSlideHeight,
12132 aAnimatedTransitionFilterNode )
12134 if( !aAnimatedTransitionFilterNode )
12136 log( 'createShapeTransition: the animated transition filter node is not valid.' );
12139 var eTransitionType = aAnimatedTransitionFilterNode.getTransitionType();
12140 var eTransitionSubType = aAnimatedTransitionFilterNode.getTransitionSubType();
12141 var bDirectionForward = ! aAnimatedTransitionFilterNode.getReverseDirection();
12142 var bModeIn = ( aAnimatedTransitionFilterNode.getTransitionMode() == FORWARD );
12144 var aTransitionInfo = aTransitionInfoTable[eTransitionType][eTransitionSubType];
12145 var eTransitionClass = aTransitionInfo['class'];
12147 switch( eTransitionClass )
12150 case TRANSITION_INVALID:
12151 log( 'createShapeTransition: transition class: TRANSITION_INVALID' );
12154 case TRANSITION_CLIP_POLYPOLYGON:
12155 var aParametricPolyPolygon
12156 = createClipPolyPolygon( eTransitionType, eTransitionSubType );
12157 var aClippingAnimation
12158 = new ClippingAnimation( aParametricPolyPolygon, aTransitionInfo,
12159 bDirectionForward, bModeIn );
12160 return new SimpleActivity( aActivityParamSet, aClippingAnimation, true );
12162 case TRANSITION_SPECIAL:
12163 switch( eTransitionType )
12165 // no special transition filter provided
12166 // we map everything to crossfade
12169 = createPropertyAnimation( 'opacity',
12173 return new SimpleActivity( aActivityParamSet, aAnimation, bModeIn );
12182 /** Class ClippingAnimation
12183 * This class performs a shape transition where the effect is achieved by
12184 * clipping the shape to be animated with a parametric path.
12186 * @param aParametricPolyPolygon
12187 * An object handling a <path> element that depends on a parameter.
12188 * @param aTransitionInfo
12189 * The set of parameters defining the shape transition to be performed.
12190 * @param bDirectionForward
12191 * The direction the shape transition has to be performed.
12193 * If true the element to be animated becomes more visible as the transition
12194 * progress else it becomes less visible.
12196 function ClippingAnimation( aParametricPolyPolygon, aTransitionInfo,
12197 bDirectionForward, bModeIn )
12199 this.aClippingFunctor = new ClippingFunctor( aParametricPolyPolygon,
12201 bDirectionForward, bModeIn );
12202 this.bAnimationStarted = false;
12206 * This method notifies to the element involved in the transition that
12207 * the animation is starting and creates the <clipPath> element used for
12210 * @param aAnimatableElement
12211 * The element to be animated.
12213 ClippingAnimation.prototype.start = function( aAnimatableElement )
12215 assert( aAnimatableElement,
12216 'ClippingAnimation.start: animatable element is not valid' );
12217 this.aAnimatableElement = aAnimatableElement;
12218 this.aAnimatableElement.initClipPath();
12219 this.aAnimatableElement.notifyAnimationStart();
12221 if( !this.bAnimationStarted )
12222 this.bAnimationStarted = true;
12227 * The transition clean up is performed here.
12229 ClippingAnimation.prototype.end = function()
12231 if( this.bAnimationStarted )
12233 this.aAnimatableElement.cleanClipPath();
12234 this.bAnimationStarted = false;
12235 this.aAnimatableElement.notifyAnimationEnd();
12240 * This method set the position of the element to be animated according to
12241 * the passed time value.
12244 * The time parameter.
12246 ClippingAnimation.prototype.perform = function( nValue )
12248 var nWidth = this.aAnimatableElement.aClippingBBox.width;
12249 var nHeight = this.aAnimatableElement.aClippingBBox.height;
12250 var aPolyPolygonElement = this.aClippingFunctor.perform( nValue, nWidth, nHeight );
12251 this.aAnimatableElement.setClipPath( aPolyPolygonElement );
12254 ClippingAnimation.prototype.getUnderlyingValue = function()
12262 function GenericAnimation( aGetValueFunc, aSetValueFunc, aGetModifier, aSetModifier )
12264 assert( aGetValueFunc && aSetValueFunc,
12265 'GenericAnimation constructor: get value functor and/or set value functor are not valid' );
12267 this.aGetValueFunc = aGetValueFunc;
12268 this.aSetValueFunc = aSetValueFunc;
12269 this.aGetModifier = aGetModifier;
12270 this.aSetModifier = aSetModifier;
12271 this.aAnimatableElement = null;
12272 this.bAnimationStarted = false;
12276 GenericAnimation.prototype.start = function( aAnimatableElement )
12278 assert( aAnimatableElement, 'GenericAnimation.start: animatable element is not valid' );
12280 this.aAnimatableElement = aAnimatableElement;
12281 this.aAnimatableElement.notifyAnimationStart();
12283 if( !this.bAnimationStarted )
12284 this.bAnimationStarted = true;
12287 GenericAnimation.prototype.end = function()
12289 if( this.bAnimationStarted )
12291 this.bAnimationStarted = false;
12292 this.aAnimatableElement.notifyAnimationEnd();
12296 GenericAnimation.prototype.perform = function( aValue )
12298 if( this.aSetModifier )
12299 aValue = this.aSetModifier( aValue );
12301 this.aSetValueFunc( aValue );
12304 GenericAnimation.prototype.getUnderlyingValue = function()
12306 var aValue = this.aGetValueFunc();
12307 if( this.aGetModifier )
12308 aValue = this.aGetModifier( aValue );
12314 function TupleAnimation( aGetValueFunc, aSetValueFunc, aDefaultValue, aReferenceSize )
12316 TupleAnimation.superclass.constructor.call( this, aGetValueFunc, aSetValueFunc );
12317 assert( aDefaultValue && aReferenceSize,
12318 'TupleAnimation constructor: default value functor and/or reference size are not valid' );
12320 this.aDefaultValue = aDefaultValue;
12321 this.aReferenceSize = aReferenceSize;
12323 extend( TupleAnimation, GenericAnimation );
12325 TupleAnimation.prototype.perform = function( aNormValue )
12327 assert(aNormValue.length === this.aReferenceSize.length);
12330 for( var i = 0; i < aNormValue.length; ++i )
12332 aValue.push( aNormValue[i] * this.aReferenceSize[i] );
12335 this.aSetValueFunc( aValue );
12338 TupleAnimation.prototype.getUnderlyingValue = function()
12340 var aValue = this.aGetValueFunc();
12341 assert(aValue.length === this.aReferenceSize.length);
12343 var aNormValue = [];
12344 for( var i = 0; i < aValue.length; ++i )
12346 aNormValue.push( aValue[i] / this.aReferenceSize[i] );
12354 function HSLAnimationWrapper( aColorAnimation )
12356 assert( aColorAnimation,
12357 'HSLAnimationWrapper constructor: invalid color animation delegate' );
12359 this.aAnimation = aColorAnimation;
12363 HSLAnimationWrapper.prototype.start = function( aAnimatableElement )
12365 this.aAnimation.start( aAnimatableElement );
12368 HSLAnimationWrapper.prototype.end = function()
12370 this.aAnimation.end();
12372 HSLAnimationWrapper.prototype.perform = function( aHSLValue )
12374 this.aAnimation.perform( aHSLValue.convertToRGB() );
12377 HSLAnimationWrapper.prototype.getUnderlyingValue = function()
12379 return this.aAnimation.getUnderlyingValue().convertToHSL();
12385 /** Class SlideChangeBase
12386 * The base abstract class of classes performing slide transitions.
12388 * @param aLeavingSlide
12389 * An object of type AnimatedSlide handling the leaving slide.
12390 * @param aEnteringSlide
12391 * An object of type AnimatedSlide handling the entering slide.
12393 function SlideChangeBase(aLeavingSlide, aEnteringSlide)
12395 this.aLeavingSlide = aLeavingSlide;
12396 this.aEnteringSlide = aEnteringSlide;
12397 this.bIsFinished = false;
12401 * The transition initialization is performed here.
12403 SlideChangeBase.prototype.start = function()
12408 * The transition clean up is performed here.
12410 SlideChangeBase.prototype.end = function()
12412 if( this.bIsFinished )
12415 this.aLeavingSlide.hide();
12416 this.aEnteringSlide.reset();
12417 this.aLeavingSlide.reset();
12419 this.bIsFinished = true;
12423 * This method is responsible for performing the slide transition.
12426 * The time parameter.
12427 * @return {Boolean}
12428 * If the transition is performed returns tue else returns false.
12430 SlideChangeBase.prototype.perform = function( nValue )
12432 if( this.bIsFinished ) return false;
12434 if( this.aLeavingSlide )
12435 this.performOut( nValue );
12437 if( this.aEnteringSlide )
12438 this.performIn( nValue );
12443 SlideChangeBase.prototype.getUnderlyingValue = function()
12448 SlideChangeBase.prototype.performIn = function( )
12450 log( 'SlideChangeBase.performIn: abstract method called' );
12453 SlideChangeBase.prototype.performOut = function( )
12455 log( 'SlideChangeBase.performOut: abstract method called' );
12461 /** Class FadingSlideChange
12462 * This class performs a slide transition by fading out the leaving slide and
12463 * fading in the entering slide.
12465 * @param aLeavingSlide
12466 * An object of type AnimatedSlide handling the leaving slide.
12467 * @param aEnteringSlide
12468 * An object of type AnimatedSlide handling the entering slide.
12470 function FadingSlideChange( aLeavingSlide, aEnteringSlide )
12472 FadingSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
12473 this.bFirstRun = true;
12475 extend( FadingSlideChange, SlideChangeBase );
12478 * This method notifies to the slides involved in the transition the attributes
12479 * appended to the slide elements for performing the animation.
12480 * Moreover it sets the entering slide in the initial state and makes the slide
12483 FadingSlideChange.prototype.start = function()
12485 FadingSlideChange.superclass.start.call( this );
12486 this.aEnteringSlide.notifyUsedAttribute( 'opacity' );
12487 this.aLeavingSlide.notifyUsedAttribute( 'opacity' );
12488 this.aEnteringSlide.setOpacity( 0.0 );
12489 this.aEnteringSlide.show();
12493 * This method set the opacity of the entering slide according to the passed
12497 * The time parameter.
12499 FadingSlideChange.prototype.performIn = function( nT )
12501 this.aEnteringSlide.setOpacity( nT );
12505 * This method set the opacity of the leaving slide according to the passed
12509 * The time parameter.
12511 FadingSlideChange.prototype.performOut = function( nT )
12514 this.aLeavingSlide.setOpacity( 1 - nT );
12520 /** Class FadingOverColorSlideChange
12521 * This class performs a slide transition by fading out the leaving slide to
12522 * a given color and fading in the entering slide from the same color.
12524 * @param aLeavingSlide
12525 * An object of type AnimatedSlide handling the leaving slide.
12526 * @param aEnteringSlide
12527 * An object of type AnimatedSlide handling the entering slide.
12528 * @param sFadeColor
12529 * A string representing the color the leaving slide fades out to and
12530 * the entering slide fade in from.
12532 function FadingOverColorSlideChange( aLeavingSlide, aEnteringSlide, sFadeColor )
12534 FadingSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
12535 this.sFadeColor = sFadeColor;
12536 if( !this.sFadeColor )
12538 log( 'FadingOverColorSlideChange: sFadeColor not valid.' );
12539 this.sFadeColor = '#000000';
12541 this.aColorPlaneElement = this.createColorPlaneElement();
12543 extend( FadingOverColorSlideChange, SlideChangeBase );
12546 * This method notifies to the slides involved in the transition the attributes
12547 * appended to the slide elements for performing the animation.
12548 * Moreover it inserts the color plane element below the leaving slide.
12549 * Finally it sets the entering slide in the initial state and makes
12550 * the slide visible.
12552 FadingOverColorSlideChange.prototype.start = function()
12554 FadingOverColorSlideChange.superclass.start.call( this );
12555 this.aEnteringSlide.notifyUsedAttribute( 'opacity' );
12556 this.aLeavingSlide.notifyUsedAttribute( 'opacity' );
12557 this.aLeavingSlide.insertBefore( this.aColorPlaneElement );
12558 this.aEnteringSlide.setOpacity( 0.0 );
12559 this.aEnteringSlide.show();
12563 * This method removes the color plane element.
12565 FadingOverColorSlideChange.prototype.end = function()
12567 FadingOverColorSlideChange.superclass.end.call( this );
12568 this.aLeavingSlide.removeElement( this.aColorPlaneElement );
12572 * This method set the opacity of the entering slide according to the passed
12576 * The time parameter.
12578 FadingOverColorSlideChange.prototype.performIn = function( nT )
12580 this.aEnteringSlide.setOpacity( (nT > 0.55) ? 2.0*(nT-0.55) : 0.0 );
12584 * This method set the opacity of the leaving slide according to the passed
12588 * The time parameter.
12590 FadingOverColorSlideChange.prototype.performOut = function( nT )
12592 this.aLeavingSlide.setOpacity( (nT > 0.45) ? 0.0 : 2.0*(0.45-nT) );
12595 FadingOverColorSlideChange.prototype.createColorPlaneElement = function()
12597 var aColorPlaneElement = document.createElementNS( NSS['svg'], 'rect' );
12598 aColorPlaneElement.setAttribute( 'width', String( this.aLeavingSlide.getWidth() ) );
12599 aColorPlaneElement.setAttribute( 'height', String( this.aLeavingSlide.getHeight() ) );
12600 aColorPlaneElement.setAttribute( 'fill', this.sFadeColor );
12601 return aColorPlaneElement;
12607 /** Class MovingSlideChange
12608 * This class performs a slide transition that involves translating the leaving
12609 * slide and/or the entering one in a given direction.
12611 * @param aLeavingSlide
12612 * An object of type AnimatedSlide handling the leaving slide.
12613 * @param aEnteringSlide
12614 * An object of type AnimatedSlide handling the entering slide.
12615 * @param aLeavingDirection
12616 * A 2D vector object {x, y}.
12617 * @param aEnteringDirection
12618 * A 2D vector object {x, y}.
12620 function MovingSlideChange( aLeavingSlide, aEnteringSlide,
12621 aLeavingDirection, aEnteringDirection )
12623 MovingSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
12624 this.aLeavingDirection = aLeavingDirection;
12625 this.aEnteringDirection = aEnteringDirection;
12627 extend( MovingSlideChange, SlideChangeBase );
12630 * This method notifies to the slides involved in the transition the attributes
12631 * appended to the slide elements for performing the animation.
12632 * Moreover it sets the entering slide in the initial state and makes the slide
12635 MovingSlideChange.prototype.start = function()
12637 MovingSlideChange.superclass.start.call( this );
12638 this.aEnteringSlide.notifyUsedAttribute( 'transform' );
12639 this.aLeavingSlide.notifyUsedAttribute( 'transform' );
12640 // Before setting the 'visibility' attribute of the entering slide to 'visible'
12641 // we translate it to the initial position so that it is not really visible
12642 // because it is clipped out.
12643 this.performIn( 0 );
12644 this.aEnteringSlide.show();
12648 * This method set the position of the entering slide according to the passed
12652 * The time parameter.
12654 MovingSlideChange.prototype.performIn = function( nT )
12657 var dx = nS * this.aEnteringDirection.x * this.aEnteringSlide.getWidth();
12658 var dy = nS * this.aEnteringDirection.y * this.aEnteringSlide.getHeight();
12659 this.aEnteringSlide.translate( dx, dy );
12663 * This method set the position of the leaving slide according to the passed
12667 * The time parameter.
12669 MovingSlideChange.prototype.performOut = function( nT )
12671 var dx = nT * this.aLeavingDirection.x * this.aLeavingSlide.getWidth();
12672 var dy = nT * this.aLeavingDirection.y * this.aLeavingSlide.getHeight();
12673 this.aLeavingSlide.translate( dx, dy );
12679 /** Class ClippedSlideChange
12680 * This class performs a slide transition where the entering slide wipes
12681 * the leaving one out. The wipe effect is achieved by clipping the entering
12682 * slide with a parametric path.
12684 * @param aLeavingSlide
12685 * An object of type AnimatedSlide handling the leaving slide.
12686 * @param aEnteringSlide
12687 * An object of type AnimatedSlide handling the entering slide.
12688 * @param aParametricPolyPolygon
12689 * An object handling a <path> element that depends on a parameter.
12690 * @param aTransitionInfo
12691 * The set of parameters defining the slide transition to be performed.
12692 * @param bIsDirectionForward
12693 * The direction the slide transition has to be performed.
12695 function ClippedSlideChange( aLeavingSlide, aEnteringSlide, aParametricPolyPolygon,
12696 aTransitionInfo, bIsDirectionForward )
12698 ClippedSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
12700 var bIsModeIn = true;
12701 this.aClippingFunctor= new ClippingFunctor( aParametricPolyPolygon, aTransitionInfo,
12702 bIsDirectionForward, bIsModeIn );
12704 extend( ClippedSlideChange, SlideChangeBase );
12707 * This method notifies to the slides involved in the transition the attributes
12708 * appended to the slide elements for performing the animation.
12709 * Moreover it sets the entering slide in the initial state and makes the slide
12712 ClippedSlideChange.prototype.start = function()
12714 ClippedSlideChange.superclass.start.call( this );
12715 this.aEnteringSlide.notifyUsedAttribute( 'clip-path' );
12716 this.performIn( 0 );
12717 this.aEnteringSlide.show();
12721 * This method set the position of the entering slide according to the passed
12725 * The time parameter.
12727 ClippedSlideChange.prototype.performIn = function( nT )
12729 var nWidth = this.aEnteringSlide.getWidth();
12730 var nHeight = this.aEnteringSlide.getHeight();
12731 var aPolyPolygonElement = this.aClippingFunctor.perform( nT, nWidth, nHeight );
12732 this.aEnteringSlide.setClipPath( aPolyPolygonElement );
12735 ClippedSlideChange.prototype.performOut = function( )
12743 /** Class ClippingFunctor
12744 * This class is responsible for computing the <path> used for clipping
12745 * the entering slide in a polypolygon clipping slide transition or the
12746 * animated shape in a transition filter effect.
12748 * @param aParametricPolyPolygon
12749 * An object that handle a <path> element defined in the [0,1]x[0,1]
12750 * unit square and that depends on a parameter.
12751 * @param aTransitionInfo
12752 * The set of parameters defining the slide transition to be performed.
12753 * @param bIsDirectionForward
12754 * The direction the slide transition has to be performed.
12756 * The direction the filter effect has to be performed
12758 function ClippingFunctor( aParametricPolyPolygon, aTransitionInfo,
12759 bIsDirectionForward, bIsModeIn)
12761 this.aParametricPolyPolygon = aParametricPolyPolygon;
12762 this.aStaticTransformation = null;
12763 this.bForwardParameterSweep = true;
12764 this.bSubtractPolygon = false;
12765 this.bScaleIsotropically = aTransitionInfo.scaleIsotropically;
12766 this.bFlip = false;
12768 assert( this.aParametricPolyPolygon,
12769 'ClippingFunctor: parametric polygon is not valid' );
12771 if( aTransitionInfo.rotationAngle != 0.0 ||
12772 aTransitionInfo.scaleX != 1.0 || aTransitionInfo.scaleY != 1.0 )
12774 // note: operations must be defined in reverse order.
12775 this.aStaticTransformation = SVGIdentityMatrix.translate( 0.5, 0.5 );
12776 if( aTransitionInfo.scaleX != 1.0 || aTransitionInfo.scaleY != 1.0 )
12777 this.aStaticTransformation
12778 = this.aStaticTransformation.scaleNonUniform( aTransitionInfo.scaleX,
12779 aTransitionInfo.scaleY );
12780 if( aTransitionInfo.rotationAngle != 0.0 )
12781 this.aStaticTransformation
12782 = this.aStaticTransformation.rotate( aTransitionInfo.rotationAngle );
12783 this.aStaticTransformation = this.aStaticTransformation.translate( -0.5, -0.5 );
12787 this.aStaticTransformation = document.documentElement.createSVGMatrix();
12790 if( !bIsDirectionForward )
12792 var aMatrix = null;
12793 switch( aTransitionInfo.reverseMethod )
12796 log( 'ClippingFunctor: unexpected reverse method.' );
12798 case REVERSEMETHOD_IGNORE:
12800 case REVERSEMETHOD_INVERT_SWEEP:
12801 this.bForwardParameterSweep = !this.bForwardParameterSweep;
12803 case REVERSEMETHOD_SUBTRACT_POLYGON:
12804 this.bSubtractPolygon = !this.bSubtractPolygon;
12806 case REVERSEMETHOD_SUBTRACT_AND_INVERT:
12807 this.bForwardParameterSweep = !this.bForwardParameterSweep;
12808 this.bSubtractPolygon = !this.bSubtractPolygon;
12810 case REVERSEMETHOD_ROTATE_180:
12811 aMatrix = document.documentElement.createSVGMatrix();
12812 aMatrix.setToRotationAroundPoint( 0.5, 0.5, 180 );
12813 this.aStaticTransformation = aMatrix.multiply( this.aStaticTransformation );
12815 case REVERSEMETHOD_FLIP_X:
12816 aMatrix = document.documentElement.createSVGMatrix();
12819 aMatrix.a = -1; aMatrix.e = 1.0;
12820 this.aStaticTransformation = aMatrix.multiply( this.aStaticTransformation );
12823 case REVERSEMETHOD_FLIP_Y:
12824 aMatrix = document.documentElement.createSVGMatrix();
12827 aMatrix.d = -1; aMatrix.f = 1.0;
12828 this.aStaticTransformation = aMatrix.multiply( this.aStaticTransformation );
12836 if( aTransitionInfo.outInvertsSweep )
12838 this.bForwardParameterSweep = !this.bForwardParameterSweep;
12842 this.bSubtractPolygon = !this.bSubtractPolygon;
12847 // This path is used when the direction is the reverse one and
12848 // the reverse method type is the subtraction type.
12849 ClippingFunctor.aBoundingPath = document.createElementNS( NSS['svg'], 'path' );
12850 ClippingFunctor.aBoundingPath.setAttribute( 'd', 'M -1 -1 L 2 -1 L 2 2 L -1 2 L -1 -1' );
12855 * A parameter in [0,1] representing normalized time.
12857 * The width of the bounding box of the slide/shape to be clipped.
12859 * The height of the bounding box of the slide/shape to be clipped.
12860 * @return SVGPathElement
12861 * A svg <path> element representing the path to be used for the clipping
12864 ClippingFunctor.prototype.perform = function( nT, nWidth, nHeight )
12866 var aClipPoly = this.aParametricPolyPolygon.perform( this.bForwardParameterSweep ? nT : (1 - nT) );
12868 // Note: even if the reverse method involves flipping we don't need to
12869 // change the clip-poly orientation because we utilize the 'nonzero'
12871 // See: http://www.w3.org/TR/SVG11/painting.html#FillRuleProperty
12873 if( this.bSubtractPolygon )
12875 aClipPoly.changeOrientation();
12876 aClipPoly.prependPath( ClippingFunctor.aBoundingPath );
12880 if( this.bScaleIsotropically )
12882 var nScaleFactor = Math.max( nWidth, nHeight );
12883 // note: operations must be defined in reverse order.
12884 aMatrix = SVGIdentityMatrix.translate( -( nScaleFactor - nWidth ) / 2.0,
12885 -( nScaleFactor - nHeight ) / 2.0 );
12886 aMatrix = aMatrix.scale( nScaleFactor );
12887 aMatrix = aMatrix.multiply( this.aStaticTransformation );
12891 aMatrix = SVGIdentityMatrix.scaleNonUniform( nWidth, nHeight );
12892 aMatrix = aMatrix.multiply( this.aStaticTransformation );
12895 aClipPoly.matrixTransform( aMatrix );
12903 /** createClipPolyPolygon
12906 * An enumerator representing the transition type.
12908 * An enumerator representing the transition subtype.
12910 * An object that handles a parametric <path> element.
12912 function createClipPolyPolygon( nType, nSubtype )
12917 log( 'createClipPolyPolygon: unknown transition type: ' + nType );
12919 case BARWIPE_TRANSITION:
12920 return new BarWipePath( 1 );
12921 case FOURBOXWIPE_TRANSITION:
12922 return new FourBoxWipePath( nSubtype === CORNERSOUT_TRANS_SUBTYPE );
12923 case BOXWIPE_TRANSITION:
12924 return new BoxWipePath( nSubtype == LEFTCENTER_TRANS_SUBTYPE ||
12925 nSubtype == TOPCENTER_TRANS_SUBTYPE ||
12926 nSubtype == RIGHTCENTER_TRANS_SUBTYPE ||
12927 nSubtype == BOTTOMCENTER_TRANS_SUBTYPE );
12928 case ELLIPSEWIPE_TRANSITION:
12929 return new EllipseWipePath( nSubtype );
12930 case FANWIPE_TRANSITION:
12931 return new FanWipePath(nSubtype == CENTERTOP_TRANS_SUBTYPE ||
12932 nSubtype == CENTERRIGHT_TRANS_SUBTYPE, true, false);
12933 case PINWHEELWIPE_TRANSITION:
12937 case ONEBLADE_TRANS_SUBTYPE:
12940 case DEFAULT_TRANS_SUBTYPE:
12941 case TWOBLADEVERTICAL_TRANS_SUBTYPE:
12944 case TWOBLADEHORIZONTAL_TRANS_SUBTYPE:
12947 case THREEBLADE_TRANS_SUBTYPE:
12950 case FOURBLADE_TRANS_SUBTYPE:
12953 case EIGHTBLADE_TRANS_SUBTYPE:
12957 log( 'createClipPolyPolygon: unknown subtype: ' + nSubtype );
12960 return new PinWheelWipePath( nBlades );
12961 case CLOCKWIPE_TRANSITION:
12962 return new ClockWipePath();
12963 case RANDOMBARWIPE_TRANSITION:
12964 return new RandomWipePath( 128, true /* bars */ );
12965 case CHECKERBOARDWIPE_TRANSITION:
12966 return new CheckerBoardWipePath( 10 );
12967 case ZIGZAGWIPE_TRANSITION:
12968 return new ZigZagWipePath( 5 );
12969 case BARNZIGZAGWIPE_TRANSITION:
12970 return new BarnZigZagWipePath( 5 );
12971 case IRISWIPE_TRANSITION:
12974 case RECTANGLE_TRANS_SUBTYPE:
12975 return new IrisWipePath(0);
12976 case DIAMOND_TRANS_SUBTYPE:
12977 return new IrisWipePath(1);
12979 log( 'createClipPolyPolygon: unknown subtype: ' + nSubtype );
12982 case BARNDOORWIPE_TRANSITION:
12983 return new BarnDoorWipePath(false);
12984 case SINGLESWEEPWIPE_TRANSITION:
12985 return new SweepWipePath(
12987 nSubtype == CLOCKWISETOP_TRANS_SUBTYPE ||
12988 nSubtype == CLOCKWISERIGHT_TRANS_SUBTYPE ||
12989 nSubtype == CLOCKWISEBOTTOM_TRANS_SUBTYPE ||
12990 nSubtype == CLOCKWISELEFT_TRANS_SUBTYPE,
12993 // oppositeVertical
12996 nSubtype == COUNTERCLOCKWISEBOTTOMLEFT_TRANS_SUBTYPE ||
12997 nSubtype == COUNTERCLOCKWISETOPRIGHT_TRANS_SUBTYPE );
12998 case WATERFALLWIPE_TRANSITION:
12999 return new WaterfallWipePath(128, // flipOnYAxis
13000 nSubtype == VERTICALRIGHT_TRANS_SUBTYPE ||
13001 nSubtype == HORIZONTALLEFT_TRANS_SUBTYPE);
13002 case MISCDIAGONALWIPE_TRANSITION:
13004 case DOUBLEBARNDOOR_TRANS_SUBTYPE:
13005 return new BarnDoorWipePath(true /* Doubled */);
13006 case DOUBLEDIAMOND_TRANS_SUBTYPE:
13007 return new DoubleDiamondWipePath();
13009 log( 'createClipPolyPolygon: unhandled subtype: ' + nSubtype );
13012 case DISSOLVE_TRANSITION:
13013 return new RandomWipePath( 16 * 16, false /* dissolve */ );
13014 case VEEWIPE_TRANSITION:
13015 return new VeeWipePath();
13016 case SNAKEWIPE_TRANSITION:
13017 return new SnakeWipePath( 8 * 8, // diagonal
13018 nSubtype == TOPLEFTDIAGONAL_TRANS_SUBTYPE ||
13019 nSubtype == TOPRIGHTDIAGONAL_TRANS_SUBTYPE ||
13020 nSubtype == BOTTOMRIGHTDIAGONAL_TRANS_SUBTYPE ||
13021 nSubtype == BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE ,
13023 nSubtype == TOPLEFTVERTICAL_TRANS_SUBTYPE ||
13024 nSubtype == TOPRIGHTDIAGONAL_TRANS_SUBTYPE ||
13025 nSubtype == BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE
13027 case PARALLELSNAKESWIPE_TRANSITION:
13028 return new ParallelSnakesWipePath(
13031 nSubtype == DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
13032 nSubtype == DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
13034 nSubtype == VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
13035 nSubtype == HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE ||
13036 nSubtype == DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
13038 nSubtype == VERTICALTOPLEFTOPPOSITE_TRANS_SUBTYPE ||
13039 nSubtype == VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
13040 nSubtype == HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE ||
13041 nSubtype == HORIZONTALTOPRIGHTOPPOSITE_TRANS_SUBTYPE ||
13042 nSubtype == DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
13043 nSubtype == DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE
13046 case SPIRALWIPE_TRANSITION:
13047 return new SpiralWipePath(
13049 nSubtype == TOPLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE ||
13050 nSubtype == TOPRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE ||
13051 nSubtype == BOTTOMRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE ||
13052 nSubtype == BOTTOMLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE );
13054 case BOXSNAKESWIPE_TRANSITION:
13055 return new BoxSnakesWipePath(
13059 nSubtype == FOURBOXVERTICAL_TRANS_SUBTYPE ||
13060 nSubtype == FOURBOXHORIZONTAL_TRANS_SUBTYPE );
13067 function createUnitSquarePath()
13069 var aPath = document.createElementNS( NSS['svg'], 'path' );
13070 var sD = 'M 0 0 L 1 0 L 1 1 L 0 1 L 0 0';
13071 aPath.setAttribute( 'd', sD );
13075 function createEmptyPath()
13077 var aPath = document.createElementNS( NSS['svg'], 'path' );
13078 var sD = 'M 0 0 L 0 0';
13079 aPath.setAttribute( 'd', sD );
13083 function pruneScaleValue( nVal )
13086 return (nVal < -0.00001 ? nVal : -0.00001);
13088 return (nVal > 0.00001 ? nVal : 0.00001);
13092 /** Class BarWipePath
13093 * This class handles a <path> element that defines a unit square and
13094 * transforms it accordingly to a parameter in the [0,1] range for performing
13095 * a left to right barWipe transition.
13098 * The number of bars to be generated.
13100 function BarWipePath( nBars /* nBars > 1: blinds effect */ )
13102 this.nBars = nBars;
13103 if( this.nBars === undefined || this.nBars < 1 )
13105 this.aBasePath = createUnitSquarePath();
13111 * A parameter in [0,1] representing the width of the generated bars.
13112 * @return SVGPathElement
13113 * A svg <path> element representing a multi-bars.
13115 BarWipePath.prototype.perform = function( nT )
13118 var aMatrix = SVGIdentityMatrix.scaleNonUniform( pruneScaleValue( nT / this.nBars ), 1.0 );
13120 var aPolyPath = this.aBasePath.cloneNode( true );
13121 aPolyPath.matrixTransform( aMatrix );
13123 if( this.nBars > 1 )
13128 for( i = this.nBars - 1; i > 0; --i )
13130 aTransform = SVGIdentityMatrix.translate( i / this.nBars, 0.0 );
13131 aTransform = aTransform.multiply( aMatrix );
13132 aPath = this.aBasePath.cloneNode( true );
13133 aPath.matrixTransform( aTransform );
13134 aPolyPath.appendPath( aPath );
13141 /** Class BoxWipePath
13142 * This class handles a path made up by one square and is utilized for
13143 * performing BoxWipe transitions.
13145 * @param bIsTopCentered
13146 * if true the transition subtype is top centered else not.
13148 function BoxWipePath(bIsTopCentered) {
13149 this.bIsTopCentered = bIsTopCentered;
13150 this.aBasePath = createUnitSquarePath();
13153 BoxWipePath.prototype.perform = function( nT ) {
13154 var d = pruneScaleValue(nT);
13155 var aTransform = SVGIdentityMatrix;
13156 if(this.bIsTopCentered) {
13157 aTransform = aTransform.translate(-0.5, 0.0).scale(d, d).translate(0.5, 0.0);
13160 aTransform = aTransform.scale(d, d);
13162 var aPath = this.aBasePath.cloneNode(true);
13163 aPath.matrixTransform(aTransform);
13167 /* Class SweepWipePath
13171 function SweepWipePath(bCenter, bSingle, bOppositeVertical, bFlipOnYAxis) {
13172 this.bCenter = bCenter;
13173 this.bSingle = bSingle;
13174 this.bOppositeVertical = bOppositeVertical;
13175 this.bFlipOnYAxis = bFlipOnYAxis;
13176 this.aBasePath = createUnitSquarePath();
13179 SweepWipePath.prototype.perform = function( nT ) {
13183 if(!this.bSingle && !this.bOppositeVertical)
13186 var poly = PinWheelWipePath.calcCenteredClock( nT + 0.25, 1.0 );
13190 aTransform = SVGIdentityMatrix.translate(0.5, 0.0);
13191 poly.matrixTransform(aTransform);
13195 if(!this.bSingle) {
13196 if(this.bOppositeVertical) {
13197 aTransform = SVGIdentityMatrix.scale(1.0, -1.0);
13198 aTransform.translate(0.0, 1.0);
13199 poly.matrixTransform(aTransform);
13200 poly.changeOrientation();
13203 aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
13204 aTransform.rotate(Math.PI);
13205 aTransform.translate(0.5, 0.5);
13206 poly.matrixTransform(aTransform);
13208 res.appendPath(poly);
13210 return this.bFlipOnYAxis ? flipOnYAxis(res) : res;
13213 /** Class FourBoxWipePath
13214 * This class handles a path made up by four squares and is utilized for
13215 * performing fourBoxWipe transitions.
13217 * @param bCornersOut
13218 * If true the transition subtype is cornersOut else is cornersIn.
13220 function FourBoxWipePath( bCornersOut )
13222 this.bCornersOut = bCornersOut;
13223 this.aBasePath = createUnitSquarePath();
13226 FourBoxWipePath.prototype.perform = function( nT )
13229 var d = pruneScaleValue( nT / 2.0 );
13231 if( this.bCornersOut )
13233 aMatrix = SVGIdentityMatrix.translate( -0.25, -0.25 ).scale( d ).translate( -0.5, -0.5 );
13237 aMatrix = SVGIdentityMatrix.translate( -0.5, -0.5 ).scale( d );
13241 var aTransform = aMatrix;
13243 var aSquare = this.aBasePath.cloneNode( true );
13244 aSquare.matrixTransform( aTransform );
13245 var aPolyPath = aSquare;
13246 // bottom left, flip on x-axis:
13247 aMatrix = SVGIdentityMatrix.flipY();
13248 aTransform = aMatrix.multiply( aTransform );
13249 aSquare = this.aBasePath.cloneNode( true );
13250 aSquare.matrixTransform( aTransform );
13251 aSquare.changeOrientation();
13252 aPolyPath.appendPath( aSquare );
13253 // bottom right, flip on y-axis:
13254 aMatrix = SVGIdentityMatrix.flipX();
13255 aTransform = aMatrix.multiply( aTransform );
13256 aSquare = this.aBasePath.cloneNode( true );
13257 aSquare.matrixTransform( aTransform );
13258 aPolyPath.appendPath( aSquare );
13259 // top right, flip on x-axis:
13260 aMatrix = SVGIdentityMatrix.flipY();
13261 aTransform = aMatrix.multiply( aTransform );
13262 aSquare = this.aBasePath.cloneNode( true );
13263 aSquare.matrixTransform( aTransform );
13264 aSquare.changeOrientation();
13265 aPolyPath.appendPath( aSquare );
13267 // Remind: operations are applied in inverse order
13268 aMatrix = SVGIdentityMatrix.translate( 0.5, 0.5 );
13269 // We enlarge a bit the clip path so we avoid that in reverse direction
13270 // some thin line of the border stroke is visible.
13271 aMatrix = aMatrix.scale( 1.1 );
13272 aPolyPath.matrixTransform( aMatrix );
13280 /** Class EllipseWipePath
13281 * This class handles a parametric ellipse represented by a path made up of
13282 * cubic Bezier curve segments that helps in performing the ellipseWipe
13286 * The transition subtype.
13288 function EllipseWipePath( eSubtype )
13290 this.eSubtype = eSubtype;
13292 // precomputed circle( 0.5, 0.5, SQRT2 / 2 )
13293 var sPathData = 'M 0.5 -0.207107 ' +
13294 'C 0.687536 -0.207107 0.867392 -0.132608 1 0 ' +
13295 'C 1.13261 0.132608 1.20711 0.312464 1.20711 0.5 ' +
13296 'C 1.20711 0.687536 1.13261 0.867392 1 1 ' +
13297 'C 0.867392 1.13261 0.687536 1.20711 0.5 1.20711 ' +
13298 'C 0.312464 1.20711 0.132608 1.13261 0 1 ' +
13299 'C -0.132608 0.867392 -0.207107 0.687536 -0.207107 0.5 ' +
13300 'C -0.207107 0.312464 -0.132608 0.132608 0 0 ' +
13301 'C 0.132608 -0.132608 0.312464 -0.207107 0.5 -0.207107';
13303 this.aBasePath = document.createElementNS( NSS['svg'], 'path' );
13304 this.aBasePath.setAttribute( 'd', sPathData );
13307 EllipseWipePath.prototype.perform = function( nT )
13310 var aTransform = SVGIdentityMatrix.translate( 0.5, 0.5 ).scale( nT ).translate( -0.5, -0.5 );
13311 var aEllipse = this.aBasePath.cloneNode( true );
13312 aEllipse.matrixTransform( aTransform );
13318 * Class FanWipePath
13321 function FanWipePath(bIsCenter, bIsSingle, bIsFanIn) {
13322 this.bCenter = bIsCenter;
13323 this.bSingle = bIsSingle;
13324 this.bFanIn = bIsFanIn;
13325 this.aBasePath = createUnitSquarePath();
13328 FanWipePath.prototype.perform = function( nT ) {
13329 var res = this.aBasePath.cloneNode(true);
13330 var poly = PinWheelWipePath.calcCenteredClock(
13331 nT / ((this.bCenter && this.bSingle) ? 2.0 : 4.0), 1.0);
13332 res.appendPath(poly);
13334 var aTransform = SVGIdentityMatrix.flipY();
13335 aTransform = aTransform.scaleNonUniform(-1.0, 1.0);
13336 poly.matrixTransform(aTransform);
13337 res.appendPath(poly);
13340 aTransform = SVGIdentityMatrix.scaleNonUniform(0.5, 0.5).translate(0.5, 0.5);
13341 res.matrixTransform(aTransform);
13344 res.appendPath(flipOnXAxis(res));
13347 aTransform = SVGIdentityMatrix.scaleNonUniform(0.5, 1.0).translate(0.5, 1.0);
13348 res.matrixTransform(aTransform);
13354 * Class ClockWipePath
13357 function ClockWipePath() { }
13359 ClockWipePath.prototype.perform = function( nT ) {
13360 const aTransform = SVGIdentityMatrix.scaleNonUniform(0.5, 0.5).translate(0.5, 0.5);
13361 var aPolyPath = PinWheelWipePath.calcCenteredClock(nT, 1.0);
13362 aPolyPath.matrixTransform( aTransform );
13367 /** Class PinWheelWipePath
13368 * This class handles a parametric poly-path that is used for performing
13369 * a spinWheelWipe transition.
13372 * Number of blades generated by the transition.
13374 function PinWheelWipePath( nBlades )
13376 this.nBlades = nBlades;
13377 if( !this.nBlades || this.nBlades < 1 )
13381 PinWheelWipePath.calcCenteredClock = function( nT, nE )
13385 var aTransform = SVGIdentityMatrix.rotate( nT * 360 );
13387 var aPoint = document.documentElement.createSVGPoint();
13388 aPoint.y = -nMAX_EDGE;
13389 aPoint = aPoint.matrixTransform( aTransform );
13391 var sPathData = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
13394 sPathData += 'L ' + '-' + nE + ' -' + nE + ' ';
13397 sPathData += 'L ' + '-' + nE + ' ' + nE + ' ';
13400 sPathData += 'L ' + nE + ' ' + nE + ' ';
13403 sPathData += 'L ' + nE + ' -' + nE + ' ';
13406 sPathData += 'L 0 -' + nE + ' ';
13407 sPathData += 'L 0 0 ';
13409 sPathData += 'L ' + aPoint.x + ' ' + aPoint.y;
13411 var aPath = document.createElementNS( NSS['svg'], 'path' );
13412 aPath.setAttribute( 'd', sPathData );
13416 PinWheelWipePath.prototype.perform = function( nT )
13418 var aBasePath = PinWheelWipePath.calcCenteredClock( nT / this.nBlades,
13419 2.0 /* max edge when rotating */ );
13421 var aPolyPath = aBasePath.cloneNode( true );
13425 for( i = this.nBlades - 1; i > 0; --i )
13427 aRotation = SVGIdentityMatrix.rotate( (i * 360) / this.nBlades );
13428 aPath = aBasePath.cloneNode( true );
13429 aPath.matrixTransform( aRotation );
13430 aPolyPath.appendPath( aPath );
13433 var aTransform = SVGIdentityMatrix.translate( 0.5, 0.5 ).scale( 0.5 );
13434 aPolyPath.matrixTransform( aTransform );
13439 /** Class BarnDoorWipe
13443 function BarnDoorWipePath(doubled) {
13444 this.aBasePath = createUnitSquarePath();
13445 this.doubled = doubled;
13448 BarnDoorWipePath.prototype.perform = function( nT ) {
13451 var aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
13452 aTransform = aTransform.scaleNonUniform(pruneScaleValue(nT), 1.0).translate(0.5, 0.5);
13453 var aPath = this.aBasePath.cloneNode(true);
13454 aPath.matrixTransform(aTransform);
13458 aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
13459 aTransform = aTransform.rotate(Math.PI / 2).translate(0.5, 0.5);
13460 aPath.matrixTransform(aTransform);
13461 res.appendPath(aPath);
13466 /** Class WaterfallWipe
13469 * Number of cells to be used
13470 * @param bFlipOnYAxis
13471 * Whether to flip on y-axis or not.
13473 function WaterfallWipePath(nElements, bFlipOnYAxis) {
13474 this.bFlipOnYAxis = bFlipOnYAxis;
13476 var sqrtElements = Math.floor(Math.sqrt(nElements));
13477 var elementEdge = 1.0/sqrtElements;
13479 var aPath = 'M '+ 0.0 + ' ' + -1.0 + ' ';
13480 for(var pos = sqrtElements; pos--; ) {
13481 var xPos = sqrtElements - pos - 1;
13482 var yPos = pruneScaleValue( ((pos+1) * elementEdge) - 1.0);
13484 aPath += 'L ' + pruneScaleValue(xPos * elementEdge) + ' ' + yPos + ' ';
13485 aPath += 'L ' + pruneScaleValue((xPos+1)*elementEdge) + ' ' + yPos + ' ';
13487 aPath += 'L ' + 1.0 + ' ' + -1.0 + ' ';
13488 aPath += 'L ' + 0.0 + ' ' + -1.0 + ' ';
13489 this.aBasePath = document.createElementNS( NSS['svg'], 'path');
13490 this.aBasePath.setAttribute('d', aPath);
13493 WaterfallWipePath.prototype.perform = function( nT ) {
13494 var poly = this.aBasePath.cloneNode(true);
13495 var aTransform = SVGIdentityMatrix.translate(0.0, pruneScaleValue(2.0 * nT));
13496 poly.matrixTransform(aTransform);
13497 var aHead = 'M ' + 0.0 + ' ' + -1.0 + ' ';
13498 var aHeadPath= document.createElementNS( NSS['svg'], 'path');
13499 aHeadPath.setAttribute('d', aHead);
13501 var aTail = 'M ' + 1.0 + ' ' + -1.0 + ' ';
13502 var aTailPath = document.createElementNS( NSS['svg'], 'path');
13503 aTailPath.setAttribute('d', aTail);
13505 poly.prependPath(aHeadPath);
13506 poly.appendPath(aTailPath);
13508 return this.bFlipOnYAxis ? flipOnYAxis(poly) : poly;
13511 /** Class DoubleDiamondWipePath
13514 function DoubleDiamondWipePath() { }
13516 DoubleDiamondWipePath.prototype.perform = function( nT ) {
13517 var a = pruneScaleValue(0.25 + (nT * 0.75));
13518 var aPath = 'M ' + (0.5 + a) + ' ' + 0.5 + ' ';
13519 aPath += 'L ' + 0.5 + ' ' + (0.5 - a) + ' ';
13520 aPath += 'L ' + (0.5 - a) + ' ' + 0.5 + ' ';
13521 aPath += 'L ' + 0.5 + ' ' + (0.5 + a) + ' ';
13522 aPath += 'L ' + (0.5 + a) + ' ' + 0.5 + ' ';
13523 var poly = document.createElementNS( NSS['svg'], 'path');
13524 poly.setAttribute('d', aPath);
13525 var res = poly.cloneNode(true);
13527 var b = pruneScaleValue( (1.0 - nT) * 0.25);
13528 aPath = 'M ' + (0.5 + b) + ' ' + 0.5 + ' ';
13529 aPath += 'L ' + 0.5 + ' ' + (0.5 + b) + ' ';
13530 aPath += 'L ' + (0.5 - b) + ' ' + 0.5 + ' ';
13531 aPath += 'L ' + 0.5 + ' ' + (0.5 - b) + ' ';
13532 aPath += 'L ' + (0.5 + b) + ' ' + 0.5 + ' ';
13533 poly = document.createElementNS( NSS['svg'], 'path');
13534 poly.setAttribute('d', aPath);
13535 res.appendPath(poly);
13545 function IrisWipePath(unitRect) {
13546 this.unitRect = unitRect;
13547 this.aBasePath = createUnitSquarePath();
13554 * A parameter in [0,1] representing the diamond or rectangle.
13555 * @return SVGPathElement
13556 * A svg <path> element representing a transition.
13558 IrisWipePath.prototype.perform = function( nT ) {
13559 var d = pruneScaleValue(nT);
13560 var aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
13561 aTransform = aTransform.multiply(SVGIdentityMatrix.scaleNonUniform(d, d).translate(0.5, 0.5));
13562 var aPath = this.aBasePath.cloneNode(true);
13563 aPath.matrixTransform(aTransform);
13568 * Class ZigZagWipePath
13573 function ZigZagWipePath(nZigs) {
13574 this.zigEdge = 1.0/nZigs;
13575 const d = this.zigEdge;
13576 const d2 = (d / 2.0);
13577 this.aBasePath = 'M ' + (-1.0 - d) + ' ' + -d + ' ';
13578 this.aBasePath += 'L ' + (-1.0 - d) + ' ' + (1.0 + d) + ' ';
13579 this.aBasePath += 'L ' + -d + ' ' + (1.0 + d) + ' ';
13581 for(var pos = (nZigs + 2); pos--; ) {
13582 this.aBasePath += 'L ' + 0.0 + ' ' + ((pos - 1) * d + d2) + ' ';
13583 this.aBasePath += 'L ' + -d + ' ' + (pos - 1) * d + ' ';
13585 this.aBasePath += 'L ' + (-1.0 - d) + ' ' + -d + ' ';
13588 ZigZagWipePath.prototype.perform = function( nT ) {
13589 var res = document.createElementNS( NSS['svg'], 'path');
13590 res.setAttribute('d', this.aBasePath);
13591 res.matrixTransform(SVGIdentityMatrix.translate((1.0 + this.zigEdge) * nT, 0.0));
13596 * Class BarnZigZagWipePath
13601 function BarnZigZagWipePath( nZigs ) { ZigZagWipePath.call(this, nZigs); }
13603 BarnZigZagWipePath.prototype = Object.create(ZigZagWipePath);
13605 BarnZigZagWipePath.prototype.perform = function( nT ) {
13606 var res = createEmptyPath();
13607 var poly = document.createElementNS( NSS['svg'], 'path');
13608 var aTransform = SVGIdentityMatrix.translate(
13609 ((1.0 + this.zigEdge) * (1.0 - nT)) / 2.0, 0.0);
13610 poly.setAttribute('d', this.aBasePath);
13611 poly.changeOrientation();
13612 poly.matrixTransform(aTransform);
13613 res.appendPath(poly);
13615 aTransform = SVGIdentityMatrix.scale(-1.0, 1.0);
13616 aTransform.translate(1.0, this.zigEdge / 2.0);
13617 poly = document.createElementNS( NSS['svg'], 'path');
13618 poly.setAttribute('d', this.aBasePath);
13619 poly.matrixTransform(aTransform);
13620 res.appendPath(poly);
13625 /** Class CheckerBoardWipePath
13627 * @param unitsPerEdge
13628 * The number of cells (per line and column) in the checker board.
13630 function CheckerBoardWipePath( unitsPerEdge )
13632 this.unitsPerEdge = unitsPerEdge;
13633 if( this.unitsPerEdge === undefined || this.unitsPerEdge < 1 )
13634 this.unitsPerEdge = 10;
13635 this.aBasePath = createUnitSquarePath();
13641 * A parameter in [0,1] representing the width of the generated bars.
13642 * @return SVGPathElement
13643 * A svg <path> element representing a multi-bars.
13645 CheckerBoardWipePath.prototype.perform = function( nT )
13647 var d = pruneScaleValue(1.0 / this.unitsPerEdge);
13648 var aMatrix = SVGIdentityMatrix.scaleNonUniform(pruneScaleValue( d*2.0*nT ),
13649 pruneScaleValue( d ) );
13651 var aPolyPath = null;
13655 for ( i = this.unitsPerEdge; i--; )
13657 aTransform = SVGIdentityMatrix;
13659 if ((i % 2) == 1) // odd line
13660 aTransform = aTransform.translate( -d, 0.0 );
13662 aTransform = aTransform.multiply( aMatrix );
13664 for ( j = (this.unitsPerEdge / 2) + 1; j--;)
13666 aPath = this.aBasePath.cloneNode( true );
13667 aPath.matrixTransform( aTransform );
13668 if (aPolyPath == null) aPolyPath = aPath;
13669 else aPolyPath.appendPath( aPath );
13670 aTransform = SVGIdentityMatrix.translate( d*2.0, 0.0 ).multiply( aTransform );
13673 aMatrix = SVGIdentityMatrix.translate( 0.0, d ).multiply( aMatrix ); // next line
13681 /** Class RandomWipePath
13684 * The number of bars or cells to be used.
13685 * @param bRandomBars
13686 * true: generates a horizontal random bar wipe
13687 * false: generates a dissolve wipe
13689 function RandomWipePath( nElements, bRandomBars )
13691 this.nElements = nElements;
13692 this.aBasePath = createUnitSquarePath();
13693 this.aPositionArray = new Array( nElements );
13694 this.aClipPath = createEmptyPath();
13695 this.nAlreadyAppendedElements = 0;
13697 var fEdgeLength, nPos, aTransform;
13699 if( bRandomBars ) // random bar wipe
13701 fEdgeLength = 1.0 / nElements;
13702 for( nPos = 0; nPos < nElements; ++nPos )
13704 this.aPositionArray[nPos] = { x: 0.0, y: pruneScaleValue( nPos * fEdgeLength ) }
13706 aTransform = SVGIdentityMatrix.scaleNonUniform( 1.0, pruneScaleValue( fEdgeLength ) );
13708 else // dissolve wipe
13710 var nSqrtElements = Math.round( Math.sqrt( nElements ) );
13711 fEdgeLength = 1.0 / nSqrtElements;
13712 for( nPos = 0; nPos < nElements; ++nPos )
13714 this.aPositionArray[nPos] = {
13715 x: pruneScaleValue( ( nPos % nSqrtElements ) * fEdgeLength ),
13716 y: pruneScaleValue( ( nPos / nSqrtElements ) * fEdgeLength ) }
13718 aTransform = SVGIdentityMatrix.scale( pruneScaleValue( fEdgeLength ) );
13720 this.aBasePath.matrixTransform( aTransform );
13724 for( nPos1 = nElements - 1; nPos1 > 0; --nPos1 )
13726 nPos2 = getRandomInt( nPos1 + 1 );
13727 tmp = this.aPositionArray[nPos1];
13728 this.aPositionArray[nPos1] = this.aPositionArray[nPos2];
13729 this.aPositionArray[nPos2] = tmp;
13736 * A parameter in [0,1] representing the width of the generated bars or squares.
13737 * @return SVGPathElement
13738 * A svg <path> element representing a multi bars or a multi squared cells.
13740 RandomWipePath.prototype.perform = function( nT )
13742 var aPolyPath = createEmptyPath();
13746 var nElements = Math.round( nT * this.nElements );
13747 if( nElements === 0 )
13751 // check if we need to reset the clip path
13752 if( this.nAlreadyAppendedElements >= nElements )
13754 this.nAlreadyAppendedElements = 0;
13755 this.aClipPath = createEmptyPath();
13758 for( nPos = this.nAlreadyAppendedElements; nPos < nElements; ++nPos )
13760 aPoint = this.aPositionArray[nPos];
13761 aPath = this.aBasePath.cloneNode( true );
13762 aTransform = SVGIdentityMatrix.translate( aPoint.x, aPoint.y );
13763 aPath.matrixTransform( aTransform );
13764 aPolyPath.appendPath( aPath );
13767 this.nAlreadyAppendedElements = nElements;
13768 this.aClipPath.appendPath( aPolyPath );
13770 return this.aClipPath.cloneNode( true );
13773 /** Class SnakeWipeSlide
13777 * @param bFlipOnYaxis
13779 function SnakeWipePath(nElements, bDiagonal, bflipOnYAxis)
13781 this.sqrtElements = Math.floor(Math.sqrt(nElements));
13782 this.elementEdge = (1.0 / this.sqrtElements);
13783 this.diagonal = bDiagonal;
13784 this.flipOnYAxis = bflipOnYAxis;
13785 this.aBasePath = createUnitSquarePath();
13788 SnakeWipePath.prototype.calcSnake = function(t)
13790 var aPolyPath = createEmptyPath();
13791 const area = (t * this.sqrtElements * this.sqrtElements);
13792 const line_ = Math.floor(area) / this.sqrtElements;
13793 const line = pruneScaleValue(line_ / this.sqrtElements);
13794 const col = pruneScaleValue((area - (line_ * this.sqrtElements)) / this.sqrtElements);
13797 let aPath = 'M '+ 0.0 + ' ' + 0.0 + ' ';
13798 aPath += 'L ' + 0.0 + ' ' + line + ' ';
13799 aPath += 'L ' + 1.0 + ' ' + line + ' ';
13800 aPath += 'L ' + 1.0 + ' ' + 0.0 + ' ';
13802 let poly = document.createElementNS( NSS['svg'], 'path');
13803 poly.setAttribute('d', aPath);
13804 aPolyPath.appendPath(poly);
13808 if((line_ & 1) == 1) {
13809 // odd line: => right to left
13810 offset = (1.0 - col);
13812 let aPath = 'M ' + offset + ' ' + line + ' ';
13813 aPath += 'L '+ offset + ' ' + (line + this.elementEdge) + ' ';
13814 aPath += 'L ' + (offset+col) + ' ' + (line + this.elementEdge) + ' ';
13815 aPath += 'L ' + (offset+col) + ' ' + line + ' ';
13816 aPath += 'L ' + offset + ' ' + line + ' ';
13817 let poly = document.createElementNS( NSS['svg'], 'path');
13818 poly.setAttribute('d', aPath);
13819 aPolyPath.appendPath(poly);
13825 SnakeWipePath.prototype.calcHalfDiagonalSnake = function(nT, bIn) {
13826 var res = createEmptyPath();
13829 const sqrtArea2 = Math.sqrt(nT * this.sqrtElements * this.sqrtElements);
13830 const edge = pruneScaleValue(sqrtArea2 / this.sqrtElements);
13832 var aPath, aPoint = document.documentElement.createSVGPoint();
13834 aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
13836 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13839 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13841 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13842 const poly = document.createElementNS( NSS['svg'], 'path');
13843 poly.setAttribute('d', aPath);
13844 res.appendPath(poly);
13846 const a = (Math.SQRT1_2 / this.sqrtElements);
13847 const d = (sqrtArea2 - Math.floor(sqrtArea2));
13848 const len = (nT * Math.SQRT1_2 * d);
13849 const height = pruneScaleValue(Math.SQRT1_2 / this.sqrtElements);
13850 aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
13852 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13853 aPoint.x = len + a;
13854 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13856 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13858 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13859 const poly = document.createElementNS( NSS['svg'], 'path');
13860 poly.setAttribute('d', aPath);
13863 if((Math.floor(sqrtArea2) & 1) == 1) {
13865 aTransform = SVGIdentityMatrix.rotate((Math.PI)/2 + (Math.PI)/4);
13866 aTransform.translate(edge + this.elementEdge, 0.0);
13869 aTransform = SVGIdentityMatrix.translate(-a, 0.0);
13870 aTransform.rotate(-(Math.PI/4));
13871 aTransform.translate(0.0, edge);
13874 poly.matrixTransform(aTransform);
13875 res.appendPath(poly);
13878 const sqrtArea2 = Math.sqrt(nT * this.sqrtElements * this.sqrtElements);
13879 const edge = pruneScaleValue(Math.floor(sqrtArea2)/this.sqrtElements);
13881 let aPath, aPoint = document.documentElement.createSVGPoint();
13884 aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
13886 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13889 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13891 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13893 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13894 const poly = document.createElementNS( NSS['svg'], 'path');
13895 poly.setAttribute('d', aPath);
13896 res.appendPath(poly);
13898 const a = (Math.SQRT1_2 / this.sqrtElements);
13899 const d = (sqrtArea2 - Math.floor(sqrtArea2));
13900 const len = ((1.0 - nT) * Math.SQRT2 * d);
13901 const height = pruneScaleValue(Math.SQRT1_2 / this.sqrtElements);
13902 aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
13904 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13905 aPoint.x = len + a;
13906 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13908 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13910 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13911 const poly = document.createElementNS( NSS['svg'], 'path');
13912 poly.setAttribute('d', aPath);
13915 if((Math.floor(sqrtArea2) & 1) == 1) {
13917 aTransform = SVGIdentityMatrix.translate(0.0, -height);
13918 aTransform.rotate(Math.PI/2 + Math.PI/4);
13919 aTransform.translate(1.0, edge);
13922 aTransform = SVGIdentityMatrix.rotate(-(Math.PI/4));
13923 aTransform = aTransform.translate(edge, 1.0);
13925 poly.matrixTransform(aTransform);
13926 res.appendPath(poly);
13931 SnakeWipePath.prototype.perform = function(nT) {
13932 var res = createEmptyPath();
13933 if(this.diagonal) {
13935 res.appendPath(this.calcHalfDiagonalSnake(1.0, true));
13936 res.appendPath(this.calcHalfDiagonalSnake(2.0*(nT-0.5), false));
13939 res.appendPath(this.calcHalfDiagonalSnake(2.0*nT, true));
13942 res = this.calcSnake(nT);
13944 return this.flipOnYAxis ? flipOnYAxis(res) : res;
13947 /** Class ParallelSnakesWipePath
13948 * Generates a parallel snakes wipe:
13952 * @param bFlipOnYAxis
13955 function ParallelSnakesWipePath(nElements, bDiagonal, bFlipOnYAxis, bOpposite) {
13956 SnakeWipePath.call(this, nElements, bDiagonal, bFlipOnYAxis);
13957 this.bOpposite = bOpposite;
13960 ParallelSnakesWipePath.prototype = Object.create(SnakeWipePath);
13962 ParallelSnakesWipePath.prototype.perform = function( nT ) {
13963 var res = createEmptyPath(), half, aTransform;
13964 if(this.diagonal) {
13965 assert(this.bOpposite);
13966 half = SnakeWipePath.prototype.calcHalfDiagonalSnake.call(this, nT, false);
13967 // flip on x axis and rotate 90 degrees:
13968 aTransform = SVGIdentityMatrix.scale(1, -1);
13969 aTransform.translate(-0.5, 0.5);
13970 aTransform.rotate(Math.PI/2);
13971 aTransform.translate(0.5, 0.5);
13972 half.matrixTransform(aTransform);
13973 half.changeOrientation();
13974 res.appendPath(half);
13976 // rotate 180 degrees:
13977 aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
13978 aTransform.rotate(Math.PI);
13979 aTransform.translate(0.5, 0.5);
13980 half.matrixTransform(aTransform);
13981 res.appendPath(half);
13984 half = SnakeWipePath.prototype.calcSnake.call(this, nT / 2.0 );
13985 // rotate 90 degrees
13986 aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
13987 aTransform = aTransform.rotate(Math.PI/2);
13988 aTransform = aTransform.translate(0.5, 0.5);
13989 half.matrixTransform(aTransform);
13990 res.appendPath(flipOnYAxis(half));
13991 res.appendPath(this.bOpposite ? flipOnXAxis(half) : half);
13994 return this.flipOnYAxis ? flipOnYAxis(res) : res;
14000 * number of elements in the spiral animation
14001 * @param bFlipOnYAxis
14002 * boolean value indicating whether to flip on y-axis or not.
14004 function SpiralWipePath(nElements, bFlipOnYAxis) {
14005 this.nElements = nElements;
14006 this.sqrtElements = Math.floor(Math.sqrt(nElements));
14007 this.bFlipOnYAxis = bFlipOnYAxis;
14010 SpiralWipePath.prototype.calcNegSpiral = function( nT ) {
14011 var area = nT * this.nElements;
14012 var e = (Math.sqrt(area) / 2.0);
14013 var edge = Math.floor(e) * 2;
14015 var aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
14016 var edge_ = pruneScaleValue(edge / this.sqrtElements);
14018 aTransform = aTransform.scale(edge_, edge_);
14019 aTransform = aTransform.translate(0.5, 0.5);
14020 var poly = createUnitSquarePath();
14021 poly.matrixTransform(aTransform);
14022 var res = poly.cloneNode(true);
14024 if(1.0 - nT != 0) {
14025 var edge1 = edge + 1;
14026 var len = Math.floor( (e - edge/2) * edge1 * 4);
14027 var w = Math.PI / 2;
14030 var alen = Math.min(len, edge1);
14032 poly = createUnitSquarePath();
14033 aTransform = SVGIdentityMatrix.scale(
14034 pruneScaleValue( alen / this.sqrtElements ),
14035 pruneScaleValue( 1.0 / this.sqrtElements ));
14036 aTransform = aTransform.translate(
14037 - pruneScaleValue( (edge / 2) / this.sqrtElements ),
14038 pruneScaleValue( (edge / 2) / this.sqrtElements ));
14039 aTransform = aTransform.rotate( w );
14041 aTransform = aTransform.translate(0.5, 0.5);
14042 poly.matrixTransform(aTransform);
14043 res.appendPath(poly);
14050 SpiralWipePath.prototype.perform = function( nT ) {
14051 var res = createUnitSquarePath();
14052 var innerSpiral = this.calcNegSpiral( 1.0 - nT );
14053 innerSpiral.changeOrientation();
14054 res.appendPath(innerSpiral);
14056 return this.bFlipOnYAxis ? flipOnYAxis(res) : res;
14059 /** Class BoxSnakesWipePath
14060 * Generates a twoBoxLeft or fourBoxHorizontal wipe:
14063 function BoxSnakesWipePath(nElements, bFourBox) {
14064 SpiralWipePath.call(this, nElements);
14065 this.bFourBox = bFourBox;
14068 BoxSnakesWipePath.prototype = Object.create(SpiralWipePath);
14070 BoxSnakesWipePath.prototype.perform = function( nT ) {
14071 var res = createUnitSquarePath(), aTransform;
14072 var innerSpiral = SpiralWipePath.prototype.calcNegSpiral.call(this, 1.0 - nT);
14073 innerSpiral.changeOrientation();
14075 if(this.bFourBox) {
14076 aTransform = SVGIdentityMatrix.scale(0.5, 0.5);
14077 innerSpiral.matrixTransform(aTransform);
14078 res.appendPath(innerSpiral);
14079 res.appendPath(flipOnXAxis(innerSpiral));
14080 innerSpiral = flipOnYAxis(innerSpiral);
14081 res.appendPath(innerSpiral);
14082 res.appendPath(flipOnXAxis(innerSpiral));
14085 aTransform = SVGIdentityMatrix.scale(1.0, 0.5);
14086 innerSpiral.matrixTransform(aTransform);
14087 res.appendPath(innerSpiral);
14088 res.appendPath(flipOnXAxis(innerSpiral));
14090 return this.bFlipOnYAxis ? flipOnYAxis(res) : res;
14093 /** Class VeeWipePath
14096 function VeeWipePath() { }
14098 VeeWipePath.prototype.perform = function( nT ) {
14099 const d = pruneScaleValue(2.0 * nT);
14100 var polyPath = 'M ' + 0.0 + ' ' + -1.0 + ' ';
14101 polyPath += 'L ' + 0.0 + ' ' + (d - 1.0) + ' ';
14102 polyPath += 'L ' + 0.5 + ' ' + d + ' ';
14103 polyPath += 'L ' + 1.0 + ' ' + (d - 1.0) + ' ';
14104 polyPath += 'L ' + 1.0 + ' ' + -1.0 + ' ';
14105 polyPath += 'L ' + 0.0 + ' ' + -1.0 + ' ';
14107 var aPolyPolyPath = document.createElementNS( NSS['svg'], 'path');
14108 aPolyPolyPath.setAttribute('d', polyPath);
14109 return aPolyPolyPath;
14113 /** Class AnimatedSlide
14114 * This class handle a slide element during a slide transition.
14116 * @param aMetaSlide
14117 * The MetaSlide object related to the slide element to be handled.
14119 function AnimatedSlide( aMetaSlide )
14123 log( 'AnimatedSlide constructor: meta slide is not valid' );
14126 this.aMetaSlide = aMetaSlide;
14127 this.aSlideElement = this.aMetaSlide.slideElement;
14128 this.sSlideId = this.aMetaSlide.slideId;
14130 this.aUsedAttributeSet = [];
14132 this.aClipPathElement = null;
14133 this.aClipPathContent = null;
14134 this.bIsClipped = false;
14138 * Set the visibility property of the slide to 'inherit'
14139 * and update the master page view.
14141 AnimatedSlide.prototype.show = function()
14143 this.aMetaSlide.show();
14147 * Set the visibility property of the slide to 'hidden'.
14149 AnimatedSlide.prototype.hide = function()
14151 this.aMetaSlide.hide();
14154 /** notifyUsedAttribute
14155 * Populate the set of attribute used for the transition.
14158 * A string representing an attribute name.
14160 AnimatedSlide.prototype.notifyUsedAttribute = function( sName )
14162 if( sName == 'clip-path' )
14164 this.initClipPath();
14165 this.bIsClipped = true;
14169 this.aUsedAttributeSet.push( sName );
14174 * Remove from the handled slide element any attribute that was appended for
14175 * performing the transition.
14177 AnimatedSlide.prototype.reset = function()
14179 if( this.bIsClipped )
14181 this.cleanClipPath();
14182 this.bIsClipped = false;
14186 for( i = 0; i < this.aUsedAttributeSet.length; ++i )
14188 var sAttrName = this.aUsedAttributeSet[i];
14189 this.aSlideElement.removeAttribute( sAttrName );
14191 this.aUsedAttributeSet = [];
14195 * Create a new clip path element and append it to the clip path def group.
14196 * Moreover the created <clipPath> element is referenced by the handled slide
14199 AnimatedSlide.prototype.initClipPath = function()
14201 // We create the clip path element.
14202 this.aClipPathElement = document.createElementNS( NSS['svg'], 'clipPath' );
14204 var sId = 'clip-path-' + this.sSlideId;
14205 this.aClipPathElement.setAttribute( 'id', sId );
14206 this.aClipPathElement.setAttribute( 'clipPathUnits', 'userSpaceOnUse' );
14208 // We create and append a placeholder content.
14209 this.aClipPathContent = document.createElementNS( NSS['svg'], 'path' );
14210 var sPathData = 'M 0 0 h ' + WIDTH + ' v ' + HEIGHT + ' h -' + WIDTH + ' z';
14211 this.aClipPathContent.setAttribute( 'd', sPathData );
14212 this.aClipPathElement.appendChild( this.aClipPathContent );
14214 // We insert it into the svg document.
14215 var aClipPathGroup = theMetaDoc.aClipPathGroup;
14216 aClipPathGroup.appendChild( this.aClipPathElement );
14218 // Finally we set the reference to the created clip path.
14219 // We set it on the parent element because a slide element already
14220 // owns a clip path attribute.
14221 var sRef = 'url(#' + sId + ')';
14222 this.aSlideElement.parentNode.setAttribute( 'clip-path', sRef );
14226 * Removes the related <clipPath> element from the <defs> group,
14227 * and remove the 'clip-path' attribute from the slide element.
14230 AnimatedSlide.prototype.cleanClipPath = function()
14232 this.aSlideElement.parentNode.removeAttribute( 'clip-path' );
14234 if( this.aClipPathElement )
14236 var aClipPathGroup = theMetaDoc.aClipPathGroup;
14237 aClipPathGroup.removeChild( this.aClipPathElement );
14238 this.aClipPathElement = null;
14239 this.aClipPathContent = null;
14244 * Insert an svg element before the handled slide element.
14249 AnimatedSlide.prototype.insertBefore = function( aElement )
14253 this.aSlideElement.parentNode.insertBefore( aElement, this.aSlideElement );
14258 * Insert an svg element after the handled slide element.
14263 AnimatedSlide.prototype.appendElement = function( aElement )
14267 this.aSlideElement.parentNode.appendChild( aElement );
14272 * Remove an svg element.
14277 AnimatedSlide.prototype.removeElement = function( aElement )
14281 this.aSlideElement.parentNode.removeChild( aElement );
14290 AnimatedSlide.prototype.getWidth = function()
14298 * The slide height.
14300 AnimatedSlide.prototype.getHeight = function()
14308 * A number in the [0,1] range representing the slide opacity.
14310 AnimatedSlide.prototype.setOpacity = function( nValue )
14312 this.aSlideElement.setAttribute( 'opacity', nValue );
14316 * Translate the handled slide.
14319 * A number representing the translation that occurs in the x direction.
14321 * A number representing the translation that occurs in the y direction.
14323 AnimatedSlide.prototype.translate = function( nDx, nDy )
14325 var sTransformAttr = 'translate(' + nDx + ',' + nDy + ')';
14326 this.aSlideElement.setAttribute( 'transform', sTransformAttr );
14330 * Replace the current content of the <clipPath> element with the one
14331 * passed through the parameter.
14333 * @param aClipPathContent
14334 * A <g> element representing a <path> element used for clipping.
14336 AnimatedSlide.prototype.setClipPath = function( aClipPathContent )
14338 // Earlier we used to replace the current <path> element with the passed one,
14339 // anyway that does not work in IE9, so we replace the 'd' attribute, only.
14340 if( this.aClipPathContent )
14342 var sPathData = aClipPathContent.getAttribute( 'd' );
14343 this.aClipPathContent.setAttribute( 'd', sPathData );
14349 function AnimatedElement( aElement )
14353 log( 'AnimatedElement constructor: element is not valid' );
14356 this.aSlideShowContext = null;
14358 this.aBaseElement = aElement.cloneNode( true );
14359 this.aActiveElement = aElement;
14360 this.sElementId = this.aActiveElement.getAttribute( 'id' );
14362 this.aBaseBBox = this.aActiveElement.getBBox();
14363 this.nBaseCenterX = this.aBaseBBox.x + this.aBaseBBox.width / 2;
14364 this.nBaseCenterY = this.aBaseBBox.y + this.aBaseBBox.height / 2;
14367 this.aClipPathElement = null;
14368 this.aClipPathContent = null;
14370 this.aPreviousElement = null;
14371 this.aStateSet = {};
14373 this.eAdditiveMode = ADDITIVE_MODE_REPLACE;
14374 this.bIsUpdated = true;
14376 this.aTMatrix = document.documentElement.createSVGMatrix();
14377 this.aCTM = document.documentElement.createSVGMatrix();
14378 this.aICTM = document.documentElement.createSVGMatrix();
14380 this.initElement();
14383 AnimatedElement.prototype.initElement = function()
14385 this.nCenterX = this.nBaseCenterX;
14386 this.nCenterY = this.nBaseCenterY;
14387 this.nScaleFactorX = 1.0;
14388 this.nScaleFactorY = 1.0;
14389 this.nRotationAngle = 0.0;
14391 // add a transform attribute of type matrix
14392 this.aActiveElement.setAttribute( 'transform', makeMatrixString( 1, 0, 0, 1, 0, 0 ) );
14396 * Create a new clip path element and append it to the clip path def group.
14397 * Moreover the created <clipPath> element is referenced by the handled
14398 * animated element.
14401 AnimatedElement.prototype.initClipPath = function()
14403 // We create the clip path element.
14404 this.aClipPathElement = document.createElementNS( NSS['svg'], 'clipPath' );
14406 var sId = 'clip-path-' + this.sElementId;
14407 this.aClipPathElement.setAttribute( 'id', sId );
14408 this.aClipPathElement.setAttribute( 'clipPathUnits', 'userSpaceOnUse' );
14410 // We create and append a placeholder content.
14411 this.aClipPathContent = document.createElementNS( NSS['svg'], 'path' );
14412 this.aClippingBBox = this.getBBoxWithStroke();
14413 var nWidth = this.aClippingBBox.width;
14414 var nHeight = this.aClippingBBox.height;
14415 var sPathData = 'M ' + this.aClippingBBox.x + ' ' + this.aClippingBBox.y +
14416 ' h ' + nWidth + ' v ' + nHeight + ' h -' + nWidth + ' z';
14417 this.aClipPathContent.setAttribute( 'd', sPathData );
14418 this.aClipPathElement.appendChild( this.aClipPathContent );
14420 // We insert it into the svg document.
14421 var aClipPathGroup = theMetaDoc.aClipPathGroup;
14422 aClipPathGroup.appendChild( this.aClipPathElement );
14424 // Finally we set the reference to the created clip path.
14425 var sRef = 'url(#' + sId + ')';
14426 this.aActiveElement.setAttribute( 'clip-path', sRef );
14430 * Removes the related <clipPath> element from the <defs> group,
14431 * and remove the 'clip-path' attribute from the animated element.
14434 AnimatedElement.prototype.cleanClipPath = function()
14436 this.aActiveElement.removeAttribute( 'clip-path' );
14438 if( this.aClipPathElement )
14440 var aClipPathGroup = theMetaDoc.aClipPathGroup;
14441 aClipPathGroup.removeChild( this.aClipPathElement );
14442 this.aClipPathElement = null;
14443 this.aClipPathContent = null;
14447 AnimatedElement.prototype.getId = function()
14449 return this.aActiveElement.getAttribute( 'id' );
14452 AnimatedElement.prototype.getAdditiveMode = function()
14454 return this.eAdditiveMode;
14457 AnimatedElement.prototype.setAdditiveMode = function( eAdditiveMode )
14459 this.eAdditiveMode = eAdditiveMode;
14462 AnimatedElement.prototype.setToElement = function( aElement )
14466 log( 'AnimatedElement(' + this.getId() + ').setToElement: element is not valid' );
14470 var aClone = aElement.cloneNode( true );
14471 this.aPreviousElement = this.aActiveElement.parentNode.replaceChild( aClone, this.aActiveElement );
14472 this.aActiveElement = aClone;
14477 AnimatedElement.prototype.notifySlideStart = function( aSlideShowContext )
14479 if( !aSlideShowContext )
14481 log( 'AnimatedElement.notifySlideStart: slideshow context is not valid' );
14483 this.aSlideShowContext = aSlideShowContext;
14485 var aClone = this.aBaseElement.cloneNode( true );
14486 this.aActiveElement.parentNode.replaceChild( aClone, this.aActiveElement );
14487 this.aActiveElement = aClone;
14489 this.initElement();
14490 this.DBG( '.notifySlideStart invoked' );
14493 AnimatedElement.prototype.notifySlideEnd = function()
14498 AnimatedElement.prototype.notifyAnimationStart = function()
14503 AnimatedElement.prototype.notifyAnimationEnd = function()
14508 AnimatedElement.prototype.notifyNextEffectStart = function( /*nEffectIndex*/ )
14514 * Save the state of the managed animated element and append it to aStateSet
14515 * using the passed animation node id as key.
14517 * @param nAnimationNodeId
14518 * A non negative integer representing the unique id of an animation node.
14520 AnimatedElement.prototype.saveState = function( nAnimationNodeId )
14522 ANIMDBG.print( 'AnimatedElement(' + this.getId() + ').saveState(' + nAnimationNodeId +')' );
14523 if( !this.aStateSet[ nAnimationNodeId ] )
14525 this.aStateSet[ nAnimationNodeId ] = {};
14527 var aState = this.aStateSet[ nAnimationNodeId ];
14528 aState.aElement = this.aActiveElement.cloneNode( true );
14529 aState.nCenterX = this.nCenterX;
14530 aState.nCenterY = this.nCenterY;
14531 aState.nScaleFactorX = this.nScaleFactorX;
14532 aState.nScaleFactorY = this.nScaleFactorY;
14533 aState.nRotationAngle = this.nRotationAngle;
14538 * Restore the state of the managed animated element to the state with key
14539 * the passed animation node id.
14541 * @param nAnimationNodeId
14542 * A non negative integer representing the unique id of an animation node.
14545 * True if the restoring operation is successful, false otherwise.
14547 AnimatedElement.prototype.restoreState = function( nAnimationNodeId )
14549 if( !this.aStateSet[ nAnimationNodeId ] )
14551 log( 'AnimatedElement(' + this.getId() + ').restoreState: state '
14552 +nAnimationNodeId + ' is not valid' );
14556 ANIMDBG.print( 'AnimatedElement(' + this.getId() + ').restoreState(' + nAnimationNodeId +')' );
14557 var aState = this.aStateSet[ nAnimationNodeId ];
14558 var bRet = this.setToElement( aState.aElement );
14561 this.nCenterX = aState.nCenterX;
14562 this.nCenterY = aState.nCenterY;
14563 this.nScaleFactorX = aState.nScaleFactorX;
14564 this.nScaleFactorY = aState.nScaleFactorY;
14565 this.nRotationAngle = aState.nRotationAngle;
14570 AnimatedElement.prototype.getBaseBBox = function()
14572 return this.aBaseBBox;
14575 AnimatedElement.prototype.getBaseCenterX = function()
14577 return this.nBaseCenterX;
14580 AnimatedElement.prototype.getBaseCenterY = function()
14582 return this.nBaseCenterY;
14585 AnimatedElement.prototype.getBBox = function()
14587 return this.aActiveElement.parentNode.getBBox();
14590 AnimatedElement.prototype.getBBoxWithStroke = function()
14592 var aBBox = this.aActiveElement.parentNode.getBBox();
14594 var aChildrenSet = this.aActiveElement.childNodes;
14596 var sStroke, sStrokeWidth;
14597 var nStrokeWidth = 0;
14599 for( i = 0; i < aChildrenSet.length; ++i )
14601 if( ! aChildrenSet[i].getAttribute )
14604 sStroke = aChildrenSet[i].getAttribute( 'stroke' );
14605 if( sStroke && sStroke != 'none' )
14607 sStrokeWidth = aChildrenSet[i].getAttribute( 'stroke-width' );
14608 var nSW = parseFloat( sStrokeWidth );
14609 if( nSW > nStrokeWidth )
14610 nStrokeWidth = nSW;
14614 if( nStrokeWidth == 0 )
14616 sStrokeWidth = ROOT_NODE.getAttribute( 'stroke-width' );
14617 nStrokeWidth = parseFloat( sStrokeWidth );
14619 if( nStrokeWidth != 0 )
14621 // It is hard to clip properly the stroke so we try to enlarge
14622 // the resulting bounding box even more.
14623 nStrokeWidth *= 1.1;
14624 var nHalfStrokeWidth = nStrokeWidth / 2;
14625 var nDoubleStrokeWidth = nStrokeWidth * 2;
14627 // Note: IE10 don't let modify the values of an element BBox.
14628 var aEBBox = document.documentElement.createSVGRect();
14629 aEBBox.x = aBBox.x - nHalfStrokeWidth;
14630 aEBBox.y = aBBox.y - nHalfStrokeWidth;
14631 aEBBox.width = aBBox.width + nDoubleStrokeWidth;
14632 aEBBox.height = aBBox.height + nDoubleStrokeWidth;
14639 * Replace the current content of the <clipPath> element with the one
14640 * passed through the parameter.
14642 * @param aClipPathContent
14643 * A <g> element representing a <path> element used for clipping.
14645 AnimatedElement.prototype.setClipPath = function( aClipPathContent )
14647 if( this.aClipPathContent )
14649 // We need to translate the clip path to the top left corner of
14650 // the element bounding box.
14651 var aTranslation = SVGIdentityMatrix.translate( this.aClippingBBox.x,
14652 this.aClippingBBox.y);
14653 aClipPathContent.matrixTransform( aTranslation );
14654 var sPathData = aClipPathContent.getAttribute( 'd' );
14655 this.aClipPathContent.setAttribute( 'd', sPathData );
14660 AnimatedElement.prototype.getX = function()
14662 return this.nCenterX;
14665 AnimatedElement.prototype.getY = function()
14667 return this.nCenterY;
14670 AnimatedElement.prototype.getPos = function()
14672 return [this.getX(), this.getY()];
14675 AnimatedElement.prototype.getWidth = function()
14677 return this.nScaleFactorX * this.getBaseBBox().width;
14680 AnimatedElement.prototype.getHeight = function()
14682 return this.nScaleFactorY * this.getBaseBBox().height;
14685 AnimatedElement.prototype.getSize = function()
14687 return [this.getWidth(), this.getHeight()];
14690 AnimatedElement.prototype.updateTransformAttribute = function()
14692 this.aTransformAttrList = this.aActiveElement.transform.baseVal;
14693 this.aTransformAttr = this.aTransformAttrList.getItem( 0 );
14694 this.aTransformAttr.setMatrix( this.aTMatrix );
14697 AnimatedElement.prototype.setX = function( nNewCenterX )
14699 if( nNewCenterX === this.nCenterX ) return;
14701 this.aTransformAttrList = this.aActiveElement.transform.baseVal;
14702 this.aTransformAttr = this.aTransformAttrList.getItem( 0 );
14703 this.aTMatrix = this.aTransformAttr.matrix.translate( nNewCenterX - this.nCenterX, 0 );
14704 this.aTransformAttr.setMatrix( this.aTMatrix );
14705 this.nCenterX = nNewCenterX;
14708 AnimatedElement.prototype.setY = function( nNewCenterY )
14710 if( nNewCenterY === this.nCenterY ) return;
14712 this.aTransformAttrList = this.aActiveElement.transform.baseVal;
14713 this.aTransformAttr = this.aTransformAttrList.getItem( 0 );
14714 this.aTMatrix = this.aTransformAttr.matrix.translate( 0, nNewCenterY - this.nCenterY );
14715 this.aTransformAttr.setMatrix( this.aTMatrix );
14716 this.nCenterY = nNewCenterY;
14719 AnimatedElement.prototype.setPos = function( aNewPos )
14721 var nNewCenterX = aNewPos[0];
14722 var nNewCenterY = aNewPos[1];
14724 if( nNewCenterX === this.nCenterX && nNewCenterY === this.nCenterY ) return;
14726 this.aTransformAttrList = this.aActiveElement.transform.baseVal;
14727 this.aTransformAttr = this.aTransformAttrList.getItem( 0 );
14728 this.aTMatrix = this.aTransformAttr.matrix.translate( nNewCenterX - this.nCenterX, nNewCenterY - this.nCenterY );
14729 this.aTransformAttr.setMatrix( this.aTMatrix );
14730 this.nCenterX = nNewCenterX;
14731 this.nCenterY = nNewCenterY;
14734 AnimatedElement.prototype.setWidth = function( nNewWidth )
14736 ANIMDBG.print( 'AnimatedElement.setWidth: nNewWidth = ' + nNewWidth );
14737 if( nNewWidth < 0 )
14739 log('AnimatedElement(' + this.getId() + ').setWidth: negative width!');
14743 var nBaseWidth = this.getBaseBBox().width;
14744 var nScaleFactorX = nNewWidth / nBaseWidth;
14746 if( nScaleFactorX < 1e-5 ) nScaleFactorX = 1e-5;
14747 if( nScaleFactorX == this.nScaleFactorX ) return;
14749 this.aTMatrix = document.documentElement.createSVGMatrix()
14750 .translate( this.nCenterX, this.nCenterY )
14751 .rotate(this.nRotationAngle)
14752 .scaleNonUniform( nScaleFactorX, this.nScaleFactorY )
14753 .translate( -this.nBaseCenterX, -this.nBaseCenterY );
14754 this.updateTransformAttribute();
14756 this.nScaleFactorX = nScaleFactorX;
14759 AnimatedElement.prototype.setHeight = function( nNewHeight )
14761 ANIMDBG.print( 'AnimatedElement.setWidth: nNewHeight = ' + nNewHeight );
14762 if( nNewHeight < 0 )
14764 log('AnimatedElement(' + this.getId() + ').setWidth: negative height!');
14768 var nBaseHeight = this.getBaseBBox().height;
14769 var nScaleFactorY = nNewHeight / nBaseHeight;
14771 if( nScaleFactorY < 1e-5 ) nScaleFactorY = 1e-5;
14772 if( nScaleFactorY == this.nScaleFactorY ) return;
14774 this.aTMatrix = document.documentElement.createSVGMatrix()
14775 .translate( this.nCenterX, this.nCenterY )
14776 .rotate(this.nRotationAngle)
14777 .scaleNonUniform( this.nScaleFactorX, nScaleFactorY )
14778 .translate( -this.nBaseCenterX, -this.nBaseCenterY );
14779 this.updateTransformAttribute();
14781 this.nScaleFactorY = nScaleFactorY;
14784 AnimatedElement.prototype.setSize= function( aNewSize )
14786 var nNewWidth = aNewSize[0];
14787 var nNewHeight = aNewSize[1];
14788 ANIMDBG.print( 'AnimatedElement.setSize: = [' + nNewWidth + ',' + nNewHeight + ']');
14789 if( nNewWidth < 0 )
14791 log('AnimatedElement(' + this.getId() + ').setSize: negative width!');
14794 if( nNewHeight < 0 )
14796 log('AnimatedElement(' + this.getId() + ').setSize: negative height!');
14800 var nBaseWidth = this.getBaseBBox().width;
14801 var nScaleFactorX = nNewWidth / nBaseWidth;
14802 if( nScaleFactorX < 1e-5 ) nScaleFactorX = 1e-5;
14804 var nBaseHeight = this.getBaseBBox().height;
14805 var nScaleFactorY = nNewHeight / nBaseHeight;
14806 if( nScaleFactorY < 1e-5 ) nScaleFactorY = 1e-5;
14808 if( nScaleFactorX == this.nScaleFactorX && nScaleFactorY == this.nScaleFactorY ) return;
14810 this.aTMatrix = document.documentElement.createSVGMatrix()
14811 .translate( this.nCenterX, this.nCenterY )
14812 .rotate(this.nRotationAngle)
14813 .scaleNonUniform( nScaleFactorX, nScaleFactorY )
14814 .translate( -this.nBaseCenterX, -this.nBaseCenterY );
14815 this.updateTransformAttribute();
14817 this.nScaleFactorX = nScaleFactorX;
14818 this.nScaleFactorY = nScaleFactorY;
14821 AnimatedElement.prototype.getOpacity = function()
14823 return this.aActiveElement.getAttribute( 'opacity' );
14826 AnimatedElement.prototype.setOpacity = function( nValue )
14828 this.aActiveElement.setAttribute( 'opacity', nValue );
14831 AnimatedElement.prototype.getRotationAngle = function()
14833 return this.nRotationAngle;
14836 AnimatedElement.prototype.setRotationAngle = function( nNewRotAngle )
14838 this.aTMatrix = document.documentElement.createSVGMatrix()
14839 .translate( this.nCenterX, this.nCenterY )
14840 .rotate(nNewRotAngle)
14841 .scaleNonUniform( this.nScaleFactorX, this.nScaleFactorY )
14842 .translate( -this.nBaseCenterX, -this.nBaseCenterY );
14843 this.updateTransformAttribute();
14845 this.nRotationAngle = nNewRotAngle;
14848 AnimatedElement.prototype.getVisibility = function()
14851 var sVisibilityValue = this.aActiveElement.getAttribute( 'visibility' );
14852 if( !sVisibilityValue || ( sVisibilityValue === 'inherit' ) )
14853 return 'visible'; // TODO: look for parent visibility!
14855 return sVisibilityValue;
14858 AnimatedElement.prototype.setVisibility = function( sValue )
14860 if( sValue == 'visible' )
14861 sValue = 'inherit';
14862 this.aActiveElement.setAttribute( 'visibility', sValue );
14865 AnimatedElement.prototype.getStrokeStyle = function()
14867 // TODO: getStrokeStyle: implement it
14871 AnimatedElement.prototype.setStrokeStyle = function( sValue )
14873 ANIMDBG.print( 'AnimatedElement.setStrokeStyle(' + sValue + ')' );
14876 AnimatedElement.prototype.getFillStyle = function()
14878 // TODO: getFillStyle: implement it
14882 AnimatedElement.prototype.setFillStyle = function( sValue )
14884 ANIMDBG.print( 'AnimatedElement.setFillStyle(' + sValue + ')' );
14887 AnimatedElement.prototype.getFillColor = function()
14889 var aChildSet = getElementChildren( this.aActiveElement );
14890 var sFillColorValue = '';
14891 for( var i = 0; i < aChildSet.length; ++i )
14893 sFillColorValue = aChildSet[i].getAttribute( 'fill' );
14894 if( sFillColorValue && ( sFillColorValue !== 'none' ) )
14898 return colorParser( sFillColorValue );
14901 AnimatedElement.prototype.setFillColor = function( aRGBValue )
14903 assert( aRGBValue instanceof RGBColor,
14904 'AnimatedElement.setFillColor: value argument is not an instance of RGBColor' );
14906 var sValue = aRGBValue.toString( true /* clamped values */ );
14907 var aChildSet = getElementChildren( this.aActiveElement );
14909 var sFillColorValue = '';
14910 for( var i = 0; i < aChildSet.length; ++i )
14912 sFillColorValue = aChildSet[i].getAttribute( 'fill' );
14913 if( sFillColorValue && ( sFillColorValue !== 'none' ) )
14915 aChildSet[i].setAttribute( 'fill', sValue );
14920 AnimatedElement.prototype.getStrokeColor = function()
14922 var aChildSet = getElementChildren( this.aActiveElement );
14923 var sStrokeColorValue = '';
14924 for( var i = 0; i < aChildSet.length; ++i )
14926 sStrokeColorValue = aChildSet[i].getAttribute( 'stroke' );
14927 if( sStrokeColorValue && ( sStrokeColorValue !== 'none' ) )
14931 return colorParser( sStrokeColorValue );
14934 AnimatedElement.prototype.setStrokeColor = function( aRGBValue )
14936 assert( aRGBValue instanceof RGBColor,
14937 'AnimatedElement.setFillColor: value argument is not an instance of RGBColor' );
14939 var sValue = aRGBValue.toString( true /* clamped values */ );
14940 var aChildSet = getElementChildren( this.aActiveElement );
14942 var sStrokeColorValue = '';
14943 for( var i = 0; i < aChildSet.length; ++i )
14945 sStrokeColorValue = aChildSet[i].getAttribute( 'stroke' );
14946 if( sStrokeColorValue && ( sStrokeColorValue !== 'none' ) )
14948 aChildSet[i].setAttribute( 'stroke', sValue );
14953 AnimatedElement.prototype.getFontColor = function()
14955 // TODO: getFontColor implement it
14956 return new RGBColor( 0, 0, 0 );
14959 AnimatedElement.prototype.setFontColor = function( sValue )
14961 ANIMDBG.print( 'AnimatedElement.setFontColor(' + sValue + ')' );
14964 AnimatedElement.prototype.DBG = function( sMessage, nTime )
14966 aAnimatedElementDebugPrinter.print( 'AnimatedElement(' + this.getId() + ')' + sMessage, nTime );
14970 function AnimatedTextElement( aElement, aEventMultiplexer )
14972 var theDocument = document;
14974 var sTextType = aElement.getAttribute( 'class' );
14975 var bIsListItem = ( sTextType === 'ListItem' );
14976 if( ( sTextType !== 'TextParagraph' ) && !bIsListItem )
14978 log( 'AnimatedTextElement: passed element is not a paragraph.' );
14981 var aTextShapeElement = aElement.parentNode;
14982 sTextType = aTextShapeElement.getAttribute( 'class' );
14983 if( sTextType !== 'SVGTextShape' )
14985 log( 'AnimatedTextElement: element parent is not a text shape.' );
14988 var aTextShapeGroup = aTextShapeElement.parentNode;
14989 // We search for the helper group element used for inserting
14990 // the element copy to be animated; if it doesn't exist we create it.
14991 var aAnimatedElementGroup = getElementByClassName( aTextShapeGroup, 'AnimatedElements' );
14992 if( !aAnimatedElementGroup )
14994 aAnimatedElementGroup = theDocument.createElementNS( NSS['svg'], 'g' );
14995 aAnimatedElementGroup.setAttribute( 'class', 'AnimatedElements' );
14996 aTextShapeGroup.appendChild( aAnimatedElementGroup );
14999 // Create element used on animating
15000 var aAnimatableElement = theDocument.createElementNS( NSS['svg'], 'g' );
15001 var aTextElement = theDocument.createElementNS( NSS['svg'], 'text' );
15002 // Clone paragraph element <tspan>
15003 var aParagraphElement = aElement.cloneNode( true );
15005 // We create a group element for wrapping bullets, bitmaps
15006 // and text decoration
15007 this.aGraphicGroupElement = theDocument.createElementNS( NSS['svg'], 'g' );
15008 this.aGraphicGroupElement.setAttribute( 'class', 'GraphicGroup' );
15010 // In case we are dealing with a list item that utilizes a bullet char
15011 // we need to clone the related bullet char too.
15012 var aBulletCharClone = null;
15013 var aBulletCharElem = null;
15014 var bIsBulletCharStyle =
15015 ( aElement.getAttributeNS( NSS['ooo'], aOOOAttrListItemNumberingType ) === 'bullet-style' );
15016 if( bIsBulletCharStyle )
15018 var aBulletCharGroupElem = getElementByClassName( aTextShapeGroup, 'BulletChars' );
15019 if( aBulletCharGroupElem )
15021 var aBulletPlaceholderElem = getElementByClassName( aElement, 'BulletPlaceholder' );
15022 if( aBulletPlaceholderElem )
15024 var sId = aBulletPlaceholderElem.getAttribute( 'id' );
15025 sId = 'bullet-char(' + sId + ')';
15026 aBulletCharElem = theDocument.getElementById( sId );
15027 if( aBulletCharElem )
15029 aBulletCharClone = aBulletCharElem.cloneNode( true );
15033 log( 'AnimatedTextElement: ' + sId + ' not found.' );
15038 log( 'AnimatedTextElement: no bullet placeholder found' );
15043 log( 'AnimatedTextElement: no bullet char group found' );
15047 // In case there are embedded bitmaps we need to clone them
15048 var aBitmapElemSet = [];
15049 var aBitmapCloneSet = [];
15050 var aBitmapPlaceholderSet = getElementsByClassName( aElement, 'BitmapPlaceholder' );
15052 if( aBitmapPlaceholderSet )
15054 for( i = 0; i < aBitmapPlaceholderSet.length; ++i )
15056 sId = aBitmapPlaceholderSet[i].getAttribute( 'id' );
15057 var sBitmapChecksum = sId.substring( 'bitmap-placeholder'.length + 1, sId.length - 1 );
15058 sId = 'embedded-bitmap(' + sBitmapChecksum + ')';
15059 aBitmapElemSet[i] = theDocument.getElementById( sId );
15060 if( aBitmapElemSet[i] )
15062 aBitmapCloneSet[i] = aBitmapElemSet[i].cloneNode( true );
15066 log( 'AnimatedTextElement: ' + sId + ' not found.' );
15072 // Change clone element id.
15073 this.sParagraphId = sId = aParagraphElement.getAttribute( 'id' );
15074 aParagraphElement.removeAttribute( 'id' );
15075 aAnimatableElement.setAttribute( 'id', sId +'.a' );
15076 if( aBulletCharClone )
15077 aBulletCharClone.removeAttribute( 'id' );
15078 for( i = 0; i < aBitmapCloneSet.length; ++i )
15080 if( aBitmapCloneSet[i] )
15081 aBitmapCloneSet[i].removeAttribute( 'id' );
15084 // Set up visibility
15085 var sVisibilityAttr = aElement.getAttribute( 'visibility' );
15086 if( !sVisibilityAttr )
15087 sVisibilityAttr = 'inherit';
15088 aAnimatableElement.setAttribute( 'visibility', sVisibilityAttr );
15089 aParagraphElement.setAttribute( 'visibility', 'inherit' );
15090 this.aGraphicGroupElement.setAttribute( 'visibility', 'inherit' );
15091 if( aBulletCharElem )
15092 aBulletCharElem.setAttribute( 'visibility', 'hidden' );
15093 for( i = 0; i < aBitmapCloneSet.length; ++i )
15095 if( aBitmapElemSet[i] )
15096 aBitmapElemSet[i].setAttribute( 'visibility', 'hidden' );
15099 // Append each element to its parent.
15100 // <g class='AnimatedElements'>
15103 // <tspan class='TextParagraph'> ... </tspan>
15105 // <g class='GraphicGroup'>
15106 // [<g class='BulletChar'>...</g>]
15107 // [<g class='EmbeddedBitmap'>...</g>]
15110 // [<g class='EmbeddedBitmap'>...</g>]
15115 aTextElement.appendChild( aParagraphElement );
15116 aAnimatableElement.appendChild( aTextElement );
15118 if( aBulletCharClone )
15119 this.aGraphicGroupElement.appendChild( aBulletCharClone );
15120 for( i = 0; i < aBitmapCloneSet.length; ++i )
15122 if( aBitmapCloneSet[i] )
15123 this.aGraphicGroupElement.appendChild( aBitmapCloneSet[i] );
15125 aAnimatableElement.appendChild( this.aGraphicGroupElement );
15126 aAnimatedElementGroup.appendChild( aAnimatableElement );
15128 this.aParentTextElement = aElement.parentNode;
15129 this.aParagraphElement = aElement;
15130 this.aAnimatedElementGroup = aAnimatedElementGroup;
15131 this.nRunningAnimations = 0;
15133 // we collect all hyperlink ids
15134 this.aHyperlinkIdSet = [];
15135 var aHyperlinkElementSet = getElementsByClassName( this.aParagraphElement, 'UrlField' );
15137 for( i = 0; i < aHyperlinkElementSet.length; ++i )
15139 sHyperlinkId = aHyperlinkElementSet[i].getAttribute( 'id' );
15141 this.aHyperlinkIdSet.push( sHyperlinkId );
15143 log( 'error: AnimatedTextElement constructor: hyperlink element has no id' );
15146 AnimatedTextElement.superclass.constructor.call( this, aAnimatableElement, aEventMultiplexer );
15149 extend( AnimatedTextElement, AnimatedElement );
15152 AnimatedTextElement.prototype.setToElement = function( aElement )
15154 var bRet = AnimatedTextElement.superclass.setToElement.call( this, aElement );
15157 this.aGraphicGroupElement = getElementByClassName( this.aActiveElement, 'GraphicGroup' );
15159 return ( bRet && this.aGraphicGroupElement );
15162 AnimatedTextElement.prototype.notifySlideStart = function( aSlideShowContext )
15164 DBGLOG( 'AnimatedTextElement.notifySlideStart' );
15165 AnimatedTextElement.superclass.notifySlideStart.call( this, aSlideShowContext );
15166 this.aGraphicGroupElement = getElementByClassName( this.aActiveElement, 'GraphicGroup' );
15167 this.restoreBaseTextParagraph();
15170 AnimatedTextElement.prototype.notifySlideEnd = function()
15172 DBGLOG( 'AnimatedTextElement.notifySlideEnd' );
15173 this.aGraphicGroupElement.setAttribute( 'visibility', 'inherit' );
15176 AnimatedTextElement.prototype.restoreBaseTextParagraph = function()
15178 var aActiveParagraphElement = this.aActiveElement.firstElementChild.firstElementChild;
15179 if( aActiveParagraphElement )
15181 var sVisibilityAttr = this.aActiveElement.getAttribute( 'visibility' );
15182 if( !sVisibilityAttr || ( sVisibilityAttr === 'visible' ) )
15183 sVisibilityAttr = 'inherit';
15184 if( sVisibilityAttr === 'inherit' )
15185 this.aGraphicGroupElement.setAttribute( 'visibility', 'visible' );
15187 this.aGraphicGroupElement.setAttribute( 'visibility', 'hidden' );
15189 var aParagraphClone = aActiveParagraphElement.cloneNode( true );
15190 aParagraphClone.setAttribute( 'id', this.sParagraphId );
15191 aParagraphClone.setAttribute( 'visibility', sVisibilityAttr );
15192 this.aParentTextElement.replaceChild( aParagraphClone, this.aParagraphElement );
15193 this.aParagraphElement = aParagraphClone;
15196 var aEventMultiplexer = this.aSlideShowContext.aEventMultiplexer;
15197 var aHyperlinkIdSet = this.aHyperlinkIdSet;
15198 var aHyperlinkElementSet = getElementsByClassName( this.aParagraphElement, 'UrlField' );
15200 for( ; i < aHyperlinkIdSet.length; ++i )
15202 aEventMultiplexer.notifyElementChangedEvent( aHyperlinkIdSet[i], aHyperlinkElementSet[i] );
15205 this.aActiveElement.setAttribute( 'visibility', 'hidden' );
15208 AnimatedTextElement.prototype.notifyAnimationStart = function()
15210 DBGLOG( 'AnimatedTextElement.notifyAnimationStart' );
15211 if( this.nRunningAnimations === 0 )
15213 var sVisibilityAttr = this.aParagraphElement.getAttribute( 'visibility' );
15214 if( !sVisibilityAttr )
15215 sVisibilityAttr = 'inherit';
15216 this.aActiveElement.setAttribute( 'visibility', sVisibilityAttr );
15217 this.aGraphicGroupElement.setAttribute( 'visibility', 'inherit' );
15218 this.aParagraphElement.setAttribute( 'visibility', 'hidden' );
15220 ++this.nRunningAnimations;
15223 AnimatedTextElement.prototype.notifyAnimationEnd = function()
15225 DBGLOG( 'AnimatedTextElement.notifyAnimationEnd' );
15226 --this.nRunningAnimations;
15227 if( this.nRunningAnimations === 0 )
15229 this.restoreBaseTextParagraph();
15233 AnimatedTextElement.prototype.saveState = function( nAnimationNodeId )
15235 if( this.nRunningAnimations === 0 )
15237 var sVisibilityAttr = this.aParagraphElement.getAttribute( 'visibility' );
15238 this.aActiveElement.setAttribute( 'visibility', sVisibilityAttr );
15239 this.aGraphicGroupElement.setAttribute( 'visibility', 'inherit' );
15241 AnimatedTextElement.superclass.saveState.call( this, nAnimationNodeId );
15244 AnimatedTextElement.prototype.restoreState = function( nAnimationNodeId )
15246 var bRet = AnimatedTextElement.superclass.restoreState.call( this, nAnimationNodeId );
15248 this.restoreBaseTextParagraph();
15255 /** Class SlideTransition
15256 * This class is responsible for initializing the properties of a slide
15257 * transition and create the object that actually will perform the transition.
15259 * @param aAnimationsRootElement
15260 * The <defs> element wrapping all animations for the related slide.
15262 * A string representing a slide id.
15264 function SlideTransition( aAnimationsRootElement, aSlideId )
15266 this.sSlideId = aSlideId;
15267 this.bIsValid = false;
15268 this.eTransitionType = undefined;
15269 this.eTransitionSubType = undefined;
15270 this.bReverseDirection = false;
15271 this.eTransitionMode = TRANSITION_MODE_IN;
15272 this.sFadeColor = null;
15273 this.aDuration = null;
15274 this.nMinFrameCount = undefined;
15276 if( aAnimationsRootElement )
15278 if( aAnimationsRootElement.firstElementChild &&
15279 ( aAnimationsRootElement.firstElementChild.getAttributeNS( NSS['smil'], 'begin' ) === (this.sSlideId + '.begin') ) )
15281 var aTransitionFilterElement = aAnimationsRootElement.firstElementChild.firstElementChild;
15282 if( aTransitionFilterElement && ( aTransitionFilterElement.localName === 'transitionFilter' ) )
15284 this.aElement = aTransitionFilterElement;
15285 this.parseElement();
15287 aAnimationsRootElement.removeChild( aAnimationsRootElement.firstElementChild );
15292 SlideTransition.prototype.createSlideTransition = function( aLeavingSlide, aEnteringSlide )
15294 if( !this.isValid() )
15296 if( this.eTransitionType == 0 )
15299 if( !aEnteringSlide )
15301 log( 'SlideTransition.createSlideTransition: invalid entering slide.' );
15305 var aTransitionInfo = aTransitionInfoTable[this.eTransitionType][this.eTransitionSubType];
15306 var eTransitionClass = aTransitionInfo['class'];
15308 switch( eTransitionClass )
15311 case TRANSITION_INVALID:
15312 log( 'SlideTransition.createSlideTransition: transition class: TRANSITION_INVALID' );
15315 case TRANSITION_CLIP_POLYPOLYGON:
15316 var aParametricPolyPolygon
15317 = createClipPolyPolygon( this.eTransitionType, this.eTransitionSubType );
15318 return new ClippedSlideChange( aLeavingSlide, aEnteringSlide, aParametricPolyPolygon,
15319 aTransitionInfo, this.isDirectionForward() );
15321 case TRANSITION_SPECIAL:
15322 switch( this.eTransitionType )
15325 log( 'SlideTransition.createSlideTransition: ' +
15326 'transition class: TRANSITION_SPECIAL, ' +
15327 'unknown transition type: ' + this.eTransitionType );
15330 case PUSHWIPE_TRANSITION:
15332 var aDirection = null;
15333 switch( this.eTransitionSubType )
15336 log( 'SlideTransition.createSlideTransition: ' +
15337 'transition type: PUSHWIPE_TRANSITION, ' +
15338 'unknown transition subtype: ' + this.eTransitionSubType );
15340 case FROMTOP_TRANS_SUBTYPE:
15341 aDirection = { x: 0.0, y: 1.0 };
15343 case FROMBOTTOM_TRANS_SUBTYPE:
15344 aDirection = { x: 0.0, y: -1.0 };
15346 case FROMLEFT_TRANS_SUBTYPE:
15347 aDirection = { x: 1.0, y: 0.0 };
15349 case FROMRIGHT_TRANS_SUBTYPE:
15350 aDirection = { x: -1.0, y: 0.0 };
15353 return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aDirection, aDirection );
15356 case SLIDEWIPE_TRANSITION:
15358 var aInDirection = null;
15359 switch( this.eTransitionSubType )
15362 log( 'SlideTransition.createSlideTransition: ' +
15363 'transition type: SLIDEWIPE_TRANSITION, ' +
15364 'unknown transition subtype: ' + this.eTransitionSubType );
15366 case FROMTOP_TRANS_SUBTYPE:
15367 aInDirection = { x: 0.0, y: 1.0 };
15369 case FROMBOTTOM_TRANS_SUBTYPE:
15370 aInDirection = { x: 0.0, y: -1.0 };
15372 case FROMLEFT_TRANS_SUBTYPE:
15373 aInDirection = { x: 1.0, y: 0.0 };
15375 case FROMRIGHT_TRANS_SUBTYPE:
15376 aInDirection = { x: -1.0, y: 0.0 };
15379 var aNoDirection = { x: 0.0, y: 0.0 };
15380 if( !this.bReverseDirection )
15382 return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aNoDirection, aInDirection );
15386 return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aInDirection, aNoDirection );
15390 case FADE_TRANSITION:
15391 switch( this.eTransitionSubType )
15394 log( 'SlideTransition.createSlideTransition: ' +
15395 'transition type: FADE_TRANSITION, ' +
15396 'unknown transition subtype: ' + this.eTransitionSubType );
15398 case CROSSFADE_TRANS_SUBTYPE:
15399 return new FadingSlideChange( aLeavingSlide, aEnteringSlide );
15400 case FADEOVERCOLOR_TRANS_SUBTYPE:
15401 return new FadingOverColorSlideChange( aLeavingSlide, aEnteringSlide, this.getFadeColor() );
15407 SlideTransition.prototype.parseElement = function()
15409 this.bIsValid = true;
15410 var aAnimElem = this.aElement;
15413 this.eTransitionType = undefined;
15414 var sTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'type' );
15415 if( sTypeAttr && aTransitionTypeInMap[ sTypeAttr ] )
15417 this.eTransitionType = aTransitionTypeInMap[ sTypeAttr ];
15421 this.bIsValid = false;
15422 log( 'SlideTransition.parseElement: transition type not valid: ' + sTypeAttr );
15425 // subtype attribute
15426 this.eTransitionSubType = undefined;
15427 var sSubTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'subtype' );
15428 if( sSubTypeAttr === null )
15429 sSubTypeAttr = 'default';
15430 if( sSubTypeAttr && ( aTransitionSubtypeInMap[ sSubTypeAttr ] !== undefined ) )
15432 this.eTransitionSubType = aTransitionSubtypeInMap[ sSubTypeAttr ];
15436 this.bIsValid = false;
15437 log( 'SlideTransition.parseElement: transition subtype not valid: ' + sSubTypeAttr );
15440 if( this.bIsValid && aTransitionInfoTable[this.eTransitionType][this.eTransitionSubType] === undefined )
15442 this.bIsValid = false;
15443 log( 'SlideTransition.parseElement: transition not valid: type: ' + sTypeAttr + ' subtype: ' + sSubTypeAttr );
15446 // direction attribute
15447 this.bReverseDirection = false;
15448 var sDirectionAttr = aAnimElem.getAttributeNS( NSS['smil'], 'direction' );
15449 if( sDirectionAttr == 'reverse' )
15450 this.bReverseDirection = true;
15453 this.sFadeColor = null;
15454 if( this.eTransitionType == FADE_TRANSITION &&
15455 ( this.eTransitionSubType == FADEFROMCOLOR_TRANS_SUBTYPE ||
15456 this.eTransitionSubType == FADEOVERCOLOR_TRANS_SUBTYPE ||
15457 this.eTransitionSubType == FADETOCOLOR_TRANS_SUBTYPE ) )
15459 var sColorAttr = aAnimElem.getAttributeNS( NSS['smil'], 'fadeColor' );
15461 this.sFadeColor = sColorAttr;
15463 this.sFadeColor='#000000';
15468 this.aDuration = null;
15469 var sDurAttr = aAnimElem.getAttributeNS( NSS['smil'], 'dur' );
15470 this.aDuration = new Duration( sDurAttr );
15471 if( !this.aDuration.isSet() )
15473 this.aDuration = new Duration( null ); // duration == 0.0
15476 // set up min frame count value;
15477 this.nMinFrameCount = ( this.getDuration().isValue() )
15478 ? ( this.getDuration().getValue() * MINIMUM_FRAMES_PER_SECONDS )
15479 : MINIMUM_FRAMES_PER_SECONDS;
15480 if( this.nMinFrameCount < 1.0 )
15481 this.nMinFrameCount = 1;
15482 else if( this.nMinFrameCount > MINIMUM_FRAMES_PER_SECONDS )
15483 this.nMinFrameCount = MINIMUM_FRAMES_PER_SECONDS;
15487 SlideTransition.prototype.isValid = function()
15489 return this.bIsValid;
15492 SlideTransition.prototype.getTransitionType = function()
15494 return this.eTransitionType;
15497 SlideTransition.prototype.getTransitionSubType = function()
15499 return this.eTransitionSubType;
15502 SlideTransition.prototype.getTransitionMode = function()
15504 return this.eTransitionMode;
15507 SlideTransition.prototype.getFadeColor = function()
15509 return this.sFadeColor;
15512 SlideTransition.prototype.isDirectionForward = function()
15514 return !this.bReverseDirection;
15517 SlideTransition.prototype.getDuration = function()
15519 return this.aDuration;
15522 SlideTransition.prototype.getMinFrameCount = function()
15524 return this.nMinFrameCount;
15527 SlideTransition.prototype.info = function()
15530 var sInfo ='slide transition <' + this.sSlideId + '>: ';
15532 sInfo += '; type: ' + getKeyByValue(aTransitionTypeInMap, this.getTransitionType());
15534 // transition subtype
15535 sInfo += '; subtype: ' + getKeyByValue(aTransitionSubtypeInMap, this.getTransitionSubType());
15537 // transition direction
15538 if( !this.isDirectionForward() )
15539 sInfo += '; direction: reverse';
15542 sInfo += '; mode: ' + aTransitionModeOutMap[ this.getTransitionMode() ];
15545 if( this.getDuration() )
15546 sInfo += '; duration: ' + this.getDuration().info();
15556 function SlideAnimations( aSlideShowContext )
15558 this.aContext = new NodeContext( aSlideShowContext );
15559 this.aAnimationNodeMap = {};
15560 this.aAnimatedElementMap = {};
15561 this.aSourceEventElementMap = {};
15562 this.aNextEffectEventArray = new NextEffectEventArray();
15563 this.aInteractiveAnimationSequenceMap = {};
15564 this.aEventMultiplexer = new EventMultiplexer( aSlideShowContext.aTimerEventQueue );
15565 this.aRootNode = null;
15566 this.bElementsParsed = false;
15568 this.aContext.aAnimationNodeMap = this.aAnimationNodeMap;
15569 this.aContext.aAnimatedElementMap = this.aAnimatedElementMap;
15570 this.aContext.aSourceEventElementMap = this.aSourceEventElementMap;
15572 // We set up a low priority for the invocation of document.handleClick
15573 // in order to make clicks on shapes, that start interactive animation
15574 // sequence (on click), have an higher priority.
15575 this.aEventMultiplexer.registerMouseClickHandler( document, 100 );
15579 SlideAnimations.prototype.importAnimations = function( aAnimationRootElement )
15581 if( !aAnimationRootElement )
15584 this.aRootNode = createAnimationTree( aAnimationRootElement, this.aContext );
15586 return ( this.aRootNode ? true : false );
15589 SlideAnimations.prototype.parseElements = function()
15591 if( !this.aRootNode )
15595 if( !this.aRootNode.parseElement() )
15598 this.bElementsParsed = true;
15601 SlideAnimations.prototype.elementsParsed = function()
15603 return this.bElementsParsed;
15606 SlideAnimations.prototype.isFirstRun = function()
15608 return this.aContext.bFirstRun;
15611 SlideAnimations.prototype.isAnimated = function()
15613 if( !this.bElementsParsed )
15616 return this.aRootNode.hasPendingAnimation();
15619 SlideAnimations.prototype.start = function()
15621 if( !this.bElementsParsed )
15624 this.chargeSourceEvents();
15625 this.chargeInterAnimEvents();
15627 aSlideShow.setSlideEvents( this.aNextEffectEventArray,
15628 this.aInteractiveAnimationSequenceMap,
15629 this.aEventMultiplexer );
15631 if( this.aContext.bFirstRun == undefined )
15632 this.aContext.bFirstRun = true;
15633 else if( this.aContext.bFirstRun )
15634 this.aContext.bFirstRun = false;
15637 this.aContext.bIsInvalid = !this.aRootNode.init();
15638 if( this.aContext.bIsInvalid )
15641 // resolve root node
15642 return this.aRootNode.resolve();
15645 SlideAnimations.prototype.end = function( bLeftEffectsSkipped )
15647 if( !this.bElementsParsed )
15648 return; // no animations there
15651 this.aRootNode.deactivate();
15652 this.aRootNode.end();
15654 if( bLeftEffectsSkipped && this.isFirstRun() )
15656 // in case this is the first run and left events have been skipped
15657 // some next effect events for the slide could not be collected
15658 // so the next time we should behave as it was the first run again
15659 this.aContext.bFirstRun = undefined;
15661 else if( this.isFirstRun() )
15663 this.aContext.bFirstRun = false;
15666 this.aContext.bIsInvalid = false;
15669 SlideAnimations.prototype.dispose = function()
15671 if( this.aRootNode )
15673 this.aRootNode.dispose();
15677 SlideAnimations.prototype.clearNextEffectEvents = function()
15679 ANIMDBG.print( 'SlideAnimations.clearNextEffectEvents: current slide: ' + nCurSlide );
15680 this.aNextEffectEventArray.clear();
15681 this.aContext.bFirstRun = undefined;
15684 SlideAnimations.prototype.chargeSourceEvents = function()
15686 for( var id in this.aSourceEventElementMap )
15688 this.aSourceEventElementMap[id].charge();
15692 SlideAnimations.prototype.chargeInterAnimEvents = function()
15694 for( var id in this.aInteractiveAnimationSequenceMap )
15696 this.aInteractiveAnimationSequenceMap[id].chargeEvents();
15700 /**********************************************************************************************
15701 * Event classes and helper functions
15702 **********************************************************************************************/
15707 this.nId = Event.getUniqueId();
15711 Event.CURR_UNIQUE_ID = 0;
15713 Event.getUniqueId = function()
15715 ++Event.CURR_UNIQUE_ID;
15716 return Event.CURR_UNIQUE_ID;
15719 Event.prototype.getId = function()
15726 function DelayEvent( aFunctor, nTimeout )
15728 DelayEvent.superclass.constructor.call( this );
15730 this.aFunctor = aFunctor;
15731 this.nTimeout = nTimeout;
15732 this.bWasFired = false;
15734 extend( DelayEvent, Event );
15737 DelayEvent.prototype.fire = function()
15739 assert( this.isCharged(), 'DelayEvent.fire: assertion isCharged failed' );
15741 this.bWasFired = true;
15746 DelayEvent.prototype.isCharged = function()
15748 return !this.bWasFired;
15751 DelayEvent.prototype.getActivationTime = function( nCurrentTime )
15753 return ( this.nTimeout + nCurrentTime );
15756 DelayEvent.prototype.dispose = function()
15758 // don't clear unconditionally, because it may currently be executed:
15759 if( this.isCharged() )
15760 this.bWasFired = true;
15763 DelayEvent.prototype.charge = function()
15765 if( !this.isCharged() )
15766 this.bWasFired = false;
15771 function WakeupEvent( aTimer, aActivityQueue )
15773 WakeupEvent.superclass.constructor.call( this );
15775 this.aTimer = new ElapsedTime( aTimer );
15776 this.nNextTime = 0.0;
15777 this.aActivity = null;
15778 this.aActivityQueue = aActivityQueue;
15780 extend( WakeupEvent, Event );
15783 WakeupEvent.prototype.clone = function()
15785 var aWakeupEvent = new WakeupEvent( this.aTimer.getTimeBase(), this.aActivityQueue );
15786 aWakeupEvent.nNextTime = this.nNextTime;
15787 aWakeupEvent.aActivity = this.aActivity;
15788 return aWakeupEvent;
15791 WakeupEvent.prototype.dispose = function()
15793 this.aActivity = null;
15796 WakeupEvent.prototype.fire = function()
15798 if( !this.aActivity )
15801 return this.aActivityQueue.addActivity( this.aActivity );
15804 WakeupEvent.prototype.isCharged = function()
15806 // this event won't expire, we fire every time we're
15807 // re-inserted into the event queue.
15811 WakeupEvent.prototype.getActivationTime = function( nCurrentTime )
15813 var nElapsedTime = this.aTimer.getElapsedTime();
15815 return Math.max( nCurrentTime, nCurrentTime - nElapsedTime + this.nNextTime );
15818 WakeupEvent.prototype.start = function()
15820 this.aTimer.reset();
15823 WakeupEvent.prototype.setNextTimeout = function( nNextTime )
15825 this.nNextTime = nNextTime;
15828 WakeupEvent.prototype.setActivity = function( aActivity )
15830 this.aActivity = aActivity;
15835 function makeEvent( aFunctor )
15837 return new DelayEvent( aFunctor, 0.0 );
15843 function makeDelay( aFunctor, nTimeout )
15845 return new DelayEvent( aFunctor, nTimeout );
15851 function registerEvent( nNodeId, aTiming, aEvent, aNodeContext )
15853 var aSlideShowContext = aNodeContext.aContext;
15854 var eTimingType = aTiming.getType();
15856 registerEvent.DBG( aTiming );
15858 if( eTimingType == OFFSET_TIMING )
15860 aSlideShowContext.aTimerEventQueue.addEvent( aEvent );
15862 else if ( aNodeContext.bFirstRun )
15864 var aEventMultiplexer = aSlideShowContext.aEventMultiplexer;
15865 if( !aEventMultiplexer )
15867 log( 'registerEvent: event multiplexer not initialized' );
15870 var aNextEffectEventArray = aSlideShowContext.aNextEffectEventArray;
15871 if( !aNextEffectEventArray )
15873 log( 'registerEvent: next effect event array not initialized' );
15876 var aInteractiveAnimationSequenceMap =
15877 aSlideShowContext.aInteractiveAnimationSequenceMap;
15878 if( !aInteractiveAnimationSequenceMap )
15880 log( 'registerEvent: interactive animation sequence map not initialized' );
15884 switch( eTimingType )
15887 var eEventType = aTiming.getEventType();
15888 var sEventBaseElemId = aTiming.getEventBaseElementId();
15889 if( sEventBaseElemId )
15891 var aEventBaseElem = document.getElementById( sEventBaseElemId );
15892 if( !aEventBaseElem )
15894 log( 'generateEvent: EVENT_TIMING: event base element not found: ' + sEventBaseElemId );
15897 var aSourceEventElement = aNodeContext.makeSourceEventElement( sEventBaseElemId, aEventBaseElem );
15899 if( !aInteractiveAnimationSequenceMap[ nNodeId ] )
15901 aInteractiveAnimationSequenceMap[ nNodeId ] = new InteractiveAnimationSequence(nNodeId);
15904 var bEventRegistered = false;
15905 switch( eEventType )
15907 case EVENT_TRIGGER_ON_CLICK:
15908 aEventMultiplexer.registerEvent( eEventType, aSourceEventElement.getId(), aEvent );
15909 aEventMultiplexer.registerRewindedEffectHandler( aSourceEventElement.getId(),
15910 bind2( aSourceEventElement.charge, aSourceEventElement ) );
15911 bEventRegistered = true;
15914 log( 'generateEvent: not handled event type: ' + eEventType );
15916 if( bEventRegistered )
15918 var aStartEvent = aInteractiveAnimationSequenceMap[ nNodeId ].getStartEvent();
15919 var aEndEvent = aInteractiveAnimationSequenceMap[ nNodeId ].getEndEvent();
15920 aEventMultiplexer.registerEvent( eEventType, aSourceEventElement.getId(), aStartEvent );
15921 aEventMultiplexer.registerEvent( EVENT_TRIGGER_END_EVENT, nNodeId, aEndEvent );
15922 aEventMultiplexer.registerRewindedEffectHandler(
15924 bind2( InteractiveAnimationSequence.prototype.chargeEvents,
15925 aInteractiveAnimationSequenceMap[ nNodeId ] )
15929 else // no base event element present
15931 switch( eEventType )
15933 case EVENT_TRIGGER_ON_NEXT_EFFECT:
15934 aNextEffectEventArray.appendEvent( aEvent );
15937 log( 'generateEvent: not handled event type: ' + eEventType );
15941 case SYNCBASE_TIMING:
15942 eEventType = aTiming.getEventType();
15943 sEventBaseElemId = aTiming.getEventBaseElementId();
15944 if( sEventBaseElemId )
15946 var aAnimationNode = aNodeContext.aAnimationNodeMap[ sEventBaseElemId ];
15947 if( !aAnimationNode )
15949 log( 'generateEvent: SYNCBASE_TIMING: event base element not found: ' + sEventBaseElemId );
15952 aEventMultiplexer.registerEvent( eEventType, aAnimationNode.getId(), aEvent );
15956 log( 'generateEvent: SYNCBASE_TIMING: event base element not specified' );
15960 log( 'generateEvent: not handled timing type: ' + eTimingType );
15965 registerEvent.DEBUG = aRegisterEventDebugPrinter.isEnabled();
15967 registerEvent.DBG = function( aTiming, nTime )
15969 if( registerEvent.DEBUG )
15971 aRegisterEventDebugPrinter.print( 'registerEvent( timing: ' + aTiming.info() + ' )', nTime );
15978 function SourceEventElement( sId, aElement, aEventMultiplexer )
15981 this.aElement = aElement;
15982 this.aEventMultiplexer = aEventMultiplexer;
15984 this.aEventMultiplexer.registerMouseClickHandler( this, 1000 );
15986 this.bClickHandled = false;
15987 this.bIsPointerOver = false;
15988 this.aElement.addEventListener( 'mouseover', bind2( SourceEventElement.prototype.onMouseEnter, this), false );
15989 this.aElement.addEventListener( 'mouseout', bind2( SourceEventElement.prototype.onMouseLeave, this), false );
15992 SourceEventElement.prototype.getId = function()
15997 SourceEventElement.prototype.onMouseEnter = function()
15999 this.bIsPointerOver = true;
16000 this.setPointerCursor();
16003 SourceEventElement.prototype.onMouseLeave = function()
16005 this.bIsPointerOver = false;
16006 this.setDefaultCursor();
16009 SourceEventElement.prototype.charge = function()
16011 this.bClickHandled = false;
16012 this.setPointerCursor();
16015 SourceEventElement.prototype.handleClick = function( /*aMouseEvent*/ )
16017 if( !this.bIsPointerOver ) return false;
16019 if( this.bClickHandled )
16022 this.aEventMultiplexer.notifyEvent( EVENT_TRIGGER_ON_CLICK, this.getId() );
16023 aSlideShow.update();
16024 this.bClickHandled = true;
16025 this.setDefaultCursor();
16029 SourceEventElement.prototype.setPointerCursor = function()
16031 if( this.bClickHandled )
16034 this.aElement.setAttribute( 'style', 'cursor: pointer' );
16037 SourceEventElement.prototype.setDefaultCursor = function()
16039 this.aElement.setAttribute( 'style', 'cursor: default' );
16044 function HyperlinkElement( sId, aEventMultiplexer )
16046 var aElement = document.getElementById( sId );
16049 log( 'error: HyperlinkElement: no element with id: <' + sId + '> found' );
16052 if( !aEventMultiplexer )
16054 log( 'AnimatedElement constructor: event multiplexer is not valid' );
16058 this.aElement = aElement;
16059 this.aEventMultiplexer = aEventMultiplexer;
16060 this.nTargetSlideIndex = undefined;
16062 this.sURL = getNSAttribute( 'xlink', this.aElement, 'href' );
16065 if( this.sURL[0] === '#' )
16067 if( this.sURL.substr(1, 5) === 'Slide' )
16069 var sSlideIndex = this.sURL.split( ' ' )[1];
16070 this.nTargetSlideIndex = parseInt( sSlideIndex ) - 1;
16074 this.aEventMultiplexer.registerElementChangedHandler( this.sId, bind2( HyperlinkElement.prototype.onElementChanged, this) );
16075 this.aEventMultiplexer.registerMouseClickHandler( this, 1100 );
16077 this.bIsPointerOver = false;
16078 this.mouseEnterHandler = bind2( HyperlinkElement.prototype.onMouseEnter, this);
16079 this.mouseLeaveHandler = bind2( HyperlinkElement.prototype.onMouseLeave, this);
16080 this.aElement.addEventListener( 'mouseover', this.mouseEnterHandler, false );
16081 this.aElement.addEventListener( 'mouseout', this.mouseLeaveHandler, false );
16085 log( 'warning: HyperlinkElement(' + this.sId + '): url is empty' );
16089 HyperlinkElement.prototype.onElementChanged = function( aElement )
16093 log( 'error: HyperlinkElement: passed element is not valid' );
16099 this.aElement.removeEventListener( 'mouseover', this.mouseEnterHandler, false );
16100 this.aElement.removeEventListener( 'mouseout', this.mouseLeaveHandler, false );
16101 this.aElement = aElement;
16102 this.aElement.addEventListener( 'mouseover', this.mouseEnterHandler, false );
16103 this.aElement.addEventListener( 'mouseout', this.mouseLeaveHandler, false );
16107 HyperlinkElement.prototype.onMouseEnter = function()
16109 this.bIsPointerOver = true;
16110 this.setPointerCursor();
16113 HyperlinkElement.prototype.onMouseLeave = function()
16115 this.bIsPointerOver = false;
16116 this.setDefaultCursor();
16119 HyperlinkElement.prototype.handleClick = function( )
16121 if( !this.bIsPointerOver ) return false;
16123 if( this.nTargetSlideIndex !== undefined )
16125 aSlideShow.displaySlide( this.nTargetSlideIndex, true );
16129 var aWindowObject = document.defaultView;
16130 if( aWindowObject )
16132 aWindowObject.open( this.sURL, this.sId );
16136 log( 'error: HyperlinkElement.handleClick: invalid window object.' );
16143 HyperlinkElement.prototype.setPointerCursor = function()
16145 if( this.bClickHandled )
16148 this.aElement.setAttribute( 'style', 'cursor: pointer' );
16151 HyperlinkElement.prototype.setDefaultCursor = function()
16153 this.aElement.setAttribute( 'style', 'cursor: default' );
16158 function InteractiveAnimationSequence( nId )
16161 this.bIsRunning = false;
16162 this.aStartEvent = null;
16163 this.aEndEvent = null;
16166 InteractiveAnimationSequence.prototype.getId = function()
16171 InteractiveAnimationSequence.prototype.getStartEvent = function()
16173 if( !this.aStartEvent )
16176 makeEvent( bind2( InteractiveAnimationSequence.prototype.start, this ) );
16178 return this.aStartEvent;
16181 InteractiveAnimationSequence.prototype.getEndEvent = function()
16183 if( !this.aEndEvent )
16186 makeEvent( bind2( InteractiveAnimationSequence.prototype.end, this ) );
16188 return this.aEndEvent;
16191 InteractiveAnimationSequence.prototype.chargeEvents = function()
16193 if( this.aStartEvent ) this.aStartEvent.charge();
16194 if( this.aEndEvent ) this.aEndEvent.charge();
16197 InteractiveAnimationSequence.prototype.isRunning = function()
16199 return this.bIsRunning;
16202 InteractiveAnimationSequence.prototype.start = function()
16204 aSlideShow.notifyInteractiveAnimationSequenceStart( this.getId() );
16205 this.bIsRunning = true;
16208 InteractiveAnimationSequence.prototype.end = function()
16210 aSlideShow.notifyInteractiveAnimationSequenceEnd( this.getId() );
16211 this.bIsRunning = false;
16215 /** class PriorityEntry
16216 * It provides an entry type for priority queues.
16217 * Higher is the value of nPriority higher is the priority of the created entry.
16220 * The object to be prioritized.
16222 * An integral number representing the object priority.
16225 function PriorityEntry( aValue, nPriority )
16227 this.aValue = aValue;
16228 this.nPriority = nPriority;
16231 /** EventEntry.compare
16232 * Compare priority of two entries.
16235 * An instance of type PriorityEntry.
16237 * An instance of type PriorityEntry.
16239 * -1 if the left entry has lower priority of the right entry,
16240 * 1 if the left entry has higher priority of the right entry,
16241 * 0 if the two entry have the same priority
16243 PriorityEntry.compare = function( aLhsEntry, aRhsEntry )
16245 if ( aLhsEntry.nPriority < aRhsEntry.nPriority )
16249 else if (aLhsEntry.nPriority > aRhsEntry.nPriority)
16262 function EventMultiplexer( aTimerEventQueue )
16264 this.nId = EventMultiplexer.getUniqueId();
16265 this.aTimerEventQueue = aTimerEventQueue;
16266 this.aEventMap = {};
16267 this.aAnimationsEndHandler = null;
16268 this.aSkipEffectEndHandlerSet = [];
16269 this.aMouseClickHandlerSet = new PriorityQueue( PriorityEntry.compare );
16270 this.aSkipEffectEvent = null;
16271 this.aRewindCurrentEffectEvent = null;
16272 this.aRewindLastEffectEvent = null;
16273 this.aSkipInteractiveEffectEventSet = {};
16274 this.aRewindRunningInteractiveEffectEventSet = {};
16275 this.aRewindEndedInteractiveEffectEventSet = {};
16276 this.aRewindedEffectHandlerSet = {};
16277 this.aElementChangedHandlerSet = {};
16280 EventMultiplexer.CURR_UNIQUE_ID = 0;
16282 EventMultiplexer.getUniqueId = function()
16284 ++EventMultiplexer.CURR_UNIQUE_ID;
16285 return EventMultiplexer.CURR_UNIQUE_ID;
16288 EventMultiplexer.prototype.getId = function()
16293 EventMultiplexer.prototype.hasRegisteredMouseClickHandlers = function()
16295 return !this.aMouseClickHandlerSet.isEmpty();
16298 EventMultiplexer.prototype.registerMouseClickHandler = function( aHandler, nPriority )
16300 var aHandlerEntry = new PriorityEntry( aHandler, nPriority );
16301 this.aMouseClickHandlerSet.push( aHandlerEntry );
16304 EventMultiplexer.prototype.notifyMouseClick = function( aMouseEvent )
16306 var aMouseClickHandlerSet = this.aMouseClickHandlerSet.clone();
16307 while( !aMouseClickHandlerSet.isEmpty() )
16309 var aHandlerEntry = aMouseClickHandlerSet.top();
16310 aMouseClickHandlerSet.pop();
16311 if( aHandlerEntry.aValue.handleClick( aMouseEvent ) )
16316 EventMultiplexer.prototype.registerEvent = function( eEventType, aNotifierId, aEvent )
16318 this.DBG( 'registerEvent', eEventType, aNotifierId );
16319 if( !this.aEventMap[ eEventType ] )
16321 this.aEventMap[ eEventType ] = {};
16323 if( !this.aEventMap[ eEventType ][ aNotifierId ] )
16325 this.aEventMap[ eEventType ][ aNotifierId ] = [];
16327 this.aEventMap[ eEventType ][ aNotifierId ].push( aEvent );
16331 EventMultiplexer.prototype.notifyEvent = function( eEventType, aNotifierId )
16333 this.DBG( 'notifyEvent', eEventType, aNotifierId );
16334 if( this.aEventMap[ eEventType ] )
16336 if( this.aEventMap[ eEventType ][ aNotifierId ] )
16338 var aEventArray = this.aEventMap[ eEventType ][ aNotifierId ];
16339 var nSize = aEventArray.length;
16340 for( var i = 0; i < nSize; ++i )
16342 this.aTimerEventQueue.addEvent( aEventArray[i] );
16348 EventMultiplexer.prototype.registerAnimationsEndHandler = function( aHandler )
16350 this.aAnimationsEndHandler = aHandler;
16353 EventMultiplexer.prototype.notifyAnimationsEndEvent = function()
16355 if( this.aAnimationsEndHandler )
16356 this.aAnimationsEndHandler();
16359 EventMultiplexer.prototype.registerNextEffectEndHandler = function( aHandler )
16361 this.aSkipEffectEndHandlerSet.push( aHandler );
16364 EventMultiplexer.prototype.notifyNextEffectEndEvent = function()
16366 var nSize = this.aSkipEffectEndHandlerSet.length;
16367 for( var i = 0; i < nSize; ++i )
16369 (this.aSkipEffectEndHandlerSet[i])();
16371 this.aSkipEffectEndHandlerSet = [];
16374 EventMultiplexer.prototype.registerSkipEffectEvent = function( aEvent )
16376 this.aSkipEffectEvent = aEvent;
16379 EventMultiplexer.prototype.notifySkipEffectEvent = function()
16381 if( this.aSkipEffectEvent )
16383 this.aTimerEventQueue.addEvent( this.aSkipEffectEvent );
16384 this.aSkipEffectEvent = null;
16388 EventMultiplexer.prototype.registerRewindCurrentEffectEvent = function( aEvent )
16390 this.aRewindCurrentEffectEvent = aEvent;
16393 EventMultiplexer.prototype.notifyRewindCurrentEffectEvent = function()
16395 if( this.aRewindCurrentEffectEvent )
16397 this.aTimerEventQueue.addEvent( this.aRewindCurrentEffectEvent );
16398 this.aRewindCurrentEffectEvent = null;
16402 EventMultiplexer.prototype.registerRewindLastEffectEvent = function( aEvent )
16404 this.aRewindLastEffectEvent = aEvent;
16407 EventMultiplexer.prototype.notifyRewindLastEffectEvent = function()
16409 if( this.aRewindLastEffectEvent )
16411 this.aTimerEventQueue.addEvent( this.aRewindLastEffectEvent );
16412 this.aRewindLastEffectEvent = null;
16416 EventMultiplexer.prototype.registerSkipInteractiveEffectEvent = function( nNotifierId, aEvent )
16418 this.aSkipInteractiveEffectEventSet[ nNotifierId ] = aEvent;
16421 EventMultiplexer.prototype.notifySkipInteractiveEffectEvent = function( nNotifierId )
16423 if( this.aSkipInteractiveEffectEventSet[ nNotifierId ] )
16425 this.aTimerEventQueue.addEvent( this.aSkipInteractiveEffectEventSet[ nNotifierId ] );
16429 EventMultiplexer.prototype.registerRewindRunningInteractiveEffectEvent = function( nNotifierId, aEvent )
16431 this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] = aEvent;
16434 EventMultiplexer.prototype.notifyRewindRunningInteractiveEffectEvent = function( nNotifierId )
16436 if( this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] )
16438 this.aTimerEventQueue.addEvent( this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] );
16442 EventMultiplexer.prototype.registerRewindEndedInteractiveEffectEvent = function( nNotifierId, aEvent )
16444 this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] = aEvent;
16447 EventMultiplexer.prototype.notifyRewindEndedInteractiveEffectEvent = function( nNotifierId )
16449 if( this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] )
16451 this.aTimerEventQueue.addEvent( this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] );
16455 EventMultiplexer.prototype.registerRewindedEffectHandler = function( aNotifierId, aHandler )
16457 this.aRewindedEffectHandlerSet[ aNotifierId ] = aHandler;
16460 EventMultiplexer.prototype.notifyRewindedEffectEvent = function( aNotifierId )
16462 if( this.aRewindedEffectHandlerSet[ aNotifierId ] )
16464 (this.aRewindedEffectHandlerSet[ aNotifierId ])();
16468 EventMultiplexer.prototype.registerElementChangedHandler = function( aNotifierId, aHandler )
16470 this.aElementChangedHandlerSet[ aNotifierId ] = aHandler;
16473 EventMultiplexer.prototype.notifyElementChangedEvent = function( aNotifierId, aElement )
16475 if( this.aElementChangedHandlerSet[ aNotifierId ] )
16477 (this.aElementChangedHandlerSet[ aNotifierId ])( aElement );
16481 EventMultiplexer.DEBUG = aEventMultiplexerDebugPrinter.isEnabled();
16483 EventMultiplexer.prototype.DBG = function( sMethodName, eEventType, aNotifierId, nTime )
16485 if( EventMultiplexer.DEBUG )
16487 var sInfo = 'EventMultiplexer.' + sMethodName;
16488 sInfo += '( type: ' + aEventTriggerOutMap[ eEventType ];
16489 sInfo += ', notifier: ' + aNotifierId + ' )';
16490 aEventMultiplexerDebugPrinter.print( sInfo, nTime );
16496 /**********************************************************************************************
16497 * Interpolator Handler and KeyStopLerp
16498 **********************************************************************************************/
16500 var aInterpolatorHandler = {};
16502 aInterpolatorHandler.getInterpolator = function( eCalcMode, eValueType, eValueSubtype )
16504 var bHasSubtype = ( typeof( eValueSubtype ) === typeof( 0 ) );
16506 if( !bHasSubtype && aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ] )
16508 return aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ];
16510 else if( bHasSubtype && aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ][ eValueSubtype ] )
16512 return aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ][ eValueSubtype ];
16516 log( 'aInterpolatorHandler.getInterpolator: not found any valid interpolator for calc mode '
16517 + aCalcModeOutMap[eCalcMode] + ' and value type ' + aValueTypeOutMap[eValueType] );
16522 aInterpolatorHandler.aLerpFunctorMap = [];
16523 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_DISCRETE ] = [];
16524 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ] = [];
16527 // interpolators for linear calculation
16529 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ NUMBER_PROPERTY ] =
16530 function ( nFrom, nTo, nT )
16532 return ( ( 1.0 - nT )* nFrom + nT * nTo );
16535 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ COLOR_PROPERTY ] = [];
16537 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ COLOR_PROPERTY ][ COLOR_SPACE_RGB ] =
16538 function ( nFrom, nTo, nT )
16540 return RGBColor.interpolate( nFrom, nTo, nT );
16543 // For HSLColor we do not return the interpolator but a function
16544 // that generate the interpolator. The AnimationColorNode is 'aware' of that.
16545 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ COLOR_PROPERTY ][ COLOR_SPACE_HSL ] =
16548 return function ( nFrom, nTo, nT )
16550 return HSLColor.interpolate( nFrom, nTo, nT, bCCW );
16554 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ TUPLE_NUMBER_PROPERTY ] =
16555 function ( aFrom, aTo, nT )
16558 for( var i = 0; i < aFrom.length; ++i )
16560 aRes.push( ( 1.0 - nT )* aFrom[i] + nT * aTo[i] );
16568 function KeyStopLerp( aValueList )
16570 KeyStopLerp.validateInput( aValueList );
16572 this.aKeyStopList = [];
16573 this.nLastIndex = 0;
16574 this.nKeyStopDistance = aValueList[1] - aValueList[0];
16575 if( this.nKeyStopDistance <= 0 )
16576 this.nKeyStopDistance = 0.001;
16578 for( var i = 0; i < aValueList.length; ++i )
16579 this.aKeyStopList.push( aValueList[i] );
16581 this.nUpperBoundIndex = this.aKeyStopList.length - 2;
16585 KeyStopLerp.validateInput = function( aValueList )
16587 var nSize = aValueList.length;
16588 assert( nSize > 1, 'KeyStopLerp.validateInput: key stop vector must have two entries or more' );
16590 for( var i = 1; i < nSize; ++i )
16592 if( aValueList[i-1] > aValueList[i] )
16593 log( 'KeyStopLerp.validateInput: time vector is not sorted in ascending order!' );
16597 KeyStopLerp.prototype.reset = function()
16599 KeyStopLerp.validateInput( this.aKeyStopList );
16600 this.nLastIndex = 0;
16601 this.nKeyStopDistance = this.aKeyStopList[1] - this.aKeyStopList[0];
16602 if( this.nKeyStopDistance <= 0 )
16603 this.nKeyStopDistance = 0.001;
16607 KeyStopLerp.prototype.lerp = function( nAlpha )
16609 if( nAlpha > this.aKeyStopList[ this.nLastIndex + 1 ] )
16613 var nIndex = this.nLastIndex + 1;
16614 this.nLastIndex = clamp( nIndex, 0, this.nUpperBoundIndex );
16615 this.nKeyStopDistance = this.aKeyStopList[ this.nLastIndex + 1 ] - this.aKeyStopList[ this.nLastIndex ];
16617 while( ( this.nKeyStopDistance <= 0 ) && ( this.nLastIndex < this.nUpperBoundIndex ) );
16620 var nRawLerp = ( nAlpha - this.aKeyStopList[ this.nLastIndex ] ) / this.nKeyStopDistance;
16622 nRawLerp = clamp( nRawLerp, 0.0, 1.0 );
16625 aResult.nIndex = this.nLastIndex;
16626 aResult.nLerp = nRawLerp;
16631 KeyStopLerp.prototype.lerp_ported = function( nAlpha )
16633 if( ( this.aKeyStopList[ this.nLastIndex ] < nAlpha ) ||
16634 ( this.aKeyStopList[ this.nLastIndex + 1 ] >= nAlpha ) )
16637 for( ; i < this.aKeyStopList.length; ++i )
16639 if( this.aKeyStopList[i] >= nAlpha )
16642 if( this.aKeyStopList[i] > nAlpha )
16644 var nIndex = i - 1;
16645 this.nLastIndex = clamp( nIndex, 0, this.aKeyStopList.length - 2 );
16648 var nRawLerp = ( nAlpha - this.aKeyStopList[ this.nLastIndex ] ) /
16649 ( this.aKeyStopList[ this.nLastIndex+1 ] - this.aKeyStopList[ this.nLastIndex ] );
16651 nRawLerp = clamp( nRawLerp, 0.0, 1.0 );
16654 aResult.nIndex = this.nLastIndex;
16655 aResult.nLerp = nRawLerp;
16662 /**********************************************************************************************
16664 **********************************************************************************************/
16666 var aOperatorSetMap = [];
16668 // number operators
16669 aOperatorSetMap[ NUMBER_PROPERTY ] = {};
16671 aOperatorSetMap[ NUMBER_PROPERTY ].equal = function( a, b )
16673 return ( a === b );
16676 aOperatorSetMap[ NUMBER_PROPERTY ].add = function( a, b )
16681 aOperatorSetMap[ NUMBER_PROPERTY ].scale = function( k, v )
16687 aOperatorSetMap[ COLOR_PROPERTY ] = {};
16689 aOperatorSetMap[ COLOR_PROPERTY ].equal = function( a, b )
16691 return a.equal( b );
16694 aOperatorSetMap[ COLOR_PROPERTY ].add = function( a, b )
16701 aOperatorSetMap[ COLOR_PROPERTY ].scale = function( k, v )
16709 aOperatorSetMap[ ENUM_PROPERTY ] = {};
16711 aOperatorSetMap[ ENUM_PROPERTY ].equal = function( a, b )
16713 return ( a === b );
16716 aOperatorSetMap[ ENUM_PROPERTY ].add = function( a )
16721 aOperatorSetMap[ ENUM_PROPERTY ].scale = function( k, v )
16726 // string operators
16727 aOperatorSetMap[ STRING_PROPERTY ] = aOperatorSetMap[ ENUM_PROPERTY ];
16730 aOperatorSetMap[ BOOL_PROPERTY ] = aOperatorSetMap[ ENUM_PROPERTY ];
16732 // tuple number operators
16733 aOperatorSetMap[ TUPLE_NUMBER_PROPERTY ] = {};
16735 aOperatorSetMap[ TUPLE_NUMBER_PROPERTY ].equal = function( a, b )
16737 assert( a.length === b.length, 'Tuples length mismatch.' );
16738 return ( a.toString() === b.toString() );
16741 aOperatorSetMap[ TUPLE_NUMBER_PROPERTY ].add = function( a, b )
16743 assert( a.length === b.length, 'Tuples length mismatch.' );
16745 for( var i = 0; i < a.length; ++i )
16747 r.push(a[i] + b[i]);
16752 aOperatorSetMap[ TUPLE_NUMBER_PROPERTY ].scale = function( k, v )
16755 for( var i = 0; i < v.length; ++i )
16765 /**********************************************************************************************
16766 * Activity Class Hierarchy
16767 **********************************************************************************************/
16770 function ActivityParamSet()
16772 this.aEndEvent = null;
16773 this.aWakeupEvent = null;
16774 this.aTimerEventQueue = null;
16775 this.aActivityQueue = null;
16776 this.nMinDuration = undefined;
16777 this.nMinNumberOfFrames = MINIMUM_FRAMES_PER_SECONDS;
16778 this.bAutoReverse = false;
16779 this.nRepeatCount = 1.0;
16780 this.nAccelerationFraction = 0.0;
16781 this.nDecelerationFraction = 0.0;
16782 this.nSlideWidth = undefined;
16783 this.nSlideHeight = undefined;
16784 this.aFormula = null;
16785 this.aDiscreteTimes = [];
16789 function AnimationActivity()
16791 this.nId = AnimationActivity.getUniqueId();
16795 AnimationActivity.CURR_UNIQUE_ID = 0;
16797 AnimationActivity.getUniqueId = function()
16799 ++AnimationActivity.CURR_UNIQUE_ID;
16800 return AnimationActivity.CURR_UNIQUE_ID;
16803 AnimationActivity.prototype.getId = function()
16811 function SetActivity( aCommonParamSet, aAnimation, aToAttr )
16813 SetActivity.superclass.constructor.call( this );
16815 this.aAnimation = aAnimation;
16816 this.aTargetElement = null;
16817 this.aEndEvent = aCommonParamSet.aEndEvent;
16818 this.aTimerEventQueue = aCommonParamSet.aTimerEventQueue;
16819 this.aToAttr = aToAttr;
16820 this.bIsActive = true;
16822 extend( SetActivity, AnimationActivity );
16825 SetActivity.prototype.activate = function( aEndEvent )
16827 this.aEndEvent = aEndEvent;
16828 this.bIsActive = true;
16831 SetActivity.prototype.dispose = function()
16833 this.bIsActive = false;
16834 if( this.aEndEvent && this.aEndEvent.isCharged() )
16835 this.aEndEvent.dispose();
16838 SetActivity.prototype.calcTimeLag = function()
16843 SetActivity.prototype.perform = function()
16845 if( !this.isActive() )
16848 // we're going inactive immediately:
16849 this.bIsActive = false;
16851 if( this.aAnimation && this.aTargetElement )
16853 this.aAnimation.start( this.aTargetElement );
16854 this.aAnimation.perform( this.aToAttr );
16855 this.aAnimation.end();
16858 if( this.aEndEvent )
16859 this.aTimerEventQueue.addEvent( this.aEndEvent );
16863 SetActivity.prototype.isActive = function()
16865 return this.bIsActive;
16868 SetActivity.prototype.dequeued = function()
16873 SetActivity.prototype.end = function()
16878 SetActivity.prototype.setTargets = function( aTargetElement )
16880 assert( aTargetElement, 'SetActivity.setTargets: target element is not valid' );
16881 this.aTargetElement = aTargetElement;
16887 function ActivityBase( aCommonParamSet )
16889 ActivityBase.superclass.constructor.call( this );
16891 this.aTargetElement = null;
16892 this.aEndEvent = aCommonParamSet.aEndEvent;
16893 this.aTimerEventQueue = aCommonParamSet.aTimerEventQueue;
16894 this.nRepeats = aCommonParamSet.nRepeatCount;
16895 this.nAccelerationFraction = aCommonParamSet.nAccelerationFraction;
16896 this.nDecelerationFraction = aCommonParamSet.nDecelerationFraction;
16897 this.bAutoReverse = aCommonParamSet.bAutoReverse;
16899 this.bFirstPerformCall = true;
16900 this.bIsActive = true;
16903 extend( ActivityBase, AnimationActivity );
16906 ActivityBase.prototype.activate = function( aEndEvent )
16908 this.aEndEvent = aEndEvent;
16909 this.bFirstPerformCall = true;
16910 this.bIsActive = true;
16913 ActivityBase.prototype.dispose = function()
16916 this.bIsActive = false;
16919 if( this.aEndEvent )
16920 this.aEndEvent.dispose();
16922 this.aEndEvent = null;
16925 ActivityBase.prototype.perform = function()
16928 if( !this.isActive() )
16929 return false; // no, early exit.
16931 assert( !this.bFirstPerformCall, 'ActivityBase.perform: assertion (!this.FirstPerformCall) failed' );
16936 ActivityBase.prototype.calcTimeLag = function()
16938 // TODO(Q1): implement different init process!
16939 if( this.isActive() && this.bFirstPerformCall )
16941 this.bFirstPerformCall = false;
16943 // notify derived classes that we're
16945 this.startAnimation();
16950 ActivityBase.prototype.isActive = function()
16952 return this.bIsActive;
16955 ActivityBase.prototype.isDisposed = function()
16957 return ( !this.bIsActive && !this.aEndEvent );
16960 ActivityBase.prototype.dequeued = function()
16962 if( !this.isActive() )
16963 this.endAnimation();
16966 ActivityBase.prototype.setTargets = function( aTargetElement )
16968 assert( aTargetElement, 'ActivityBase.setTargets: target element is not valid' );
16970 this.aTargetElement = aTargetElement;
16973 ActivityBase.prototype.startAnimation = function()
16975 throw ( 'ActivityBase.startAnimation: abstract method invoked' );
16978 ActivityBase.prototype.endAnimation = function()
16980 throw ( 'ActivityBase.endAnimation: abstract method invoked' );
16983 ActivityBase.prototype.endActivity = function()
16985 // this is a regular activity end
16986 this.bIsActive = false;
16988 // Activity is ending, queue event, then
16989 if( this.aEndEvent )
16990 this.aTimerEventQueue.addEvent( this.aEndEvent );
16992 this.aEndEvent = null;
16996 ActivityBase.prototype.calcAcceleratedTime = function( nT )
16998 // Handle acceleration/deceleration
17001 // clamp nT to permissible [0,1] range
17002 nT = clamp( nT, 0.0, 1.0 );
17004 // take acceleration/deceleration into account. if the sum
17005 // of nAccelerationFraction and nDecelerationFraction
17006 // exceeds 1.0, ignore both (that's according to SMIL spec)
17007 if( ( this.nAccelerationFraction > 0.0 || this.nDecelerationFraction > 0.0 ) &&
17008 ( this.nAccelerationFraction + this.nDecelerationFraction <= 1.0 ) )
17010 var nC = 1.0 - 0.5*this.nAccelerationFraction - 0.5*this.nDecelerationFraction;
17012 // this variable accumulates the new time value
17015 if( nT < this.nAccelerationFraction )
17017 nTPrime += 0.5 * nT * nT / this.nAccelerationFraction; // partial first interval
17021 nTPrime += 0.5 * this.nAccelerationFraction; // full first interval
17023 if( nT <= ( 1.0 - this.nDecelerationFraction ) )
17025 nTPrime += nT - this.nAccelerationFraction; // partial second interval
17029 nTPrime += 1.0 - this.nAccelerationFraction - this.nDecelerationFraction; // full second interval
17031 var nTRelative = nT - 1.0 + this.nDecelerationFraction;
17033 nTPrime += nTRelative - 0.5*nTRelative*nTRelative / this.nDecelerationFraction;
17037 // normalize, and assign to work variable
17044 ActivityBase.prototype.getEventQueue = function()
17046 return this.aTimerEventQueue;
17049 ActivityBase.prototype.getTargetElement = function()
17051 return this.aTargetElement;
17054 ActivityBase.prototype.isRepeatCountValid = function()
17056 return !!this.nRepeats; // first ! convert to bool
17059 ActivityBase.prototype.getRepeatCount = function()
17061 return this.nRepeats;
17064 ActivityBase.prototype.isAutoReverse = function()
17066 return this.bAutoReverse;
17069 ActivityBase.prototype.end = function()
17071 if( !this.isActive() || this.isDisposed() )
17074 // assure animation is started:
17075 if( this.bFirstPerformCall )
17077 this.bFirstPerformCall = false;
17078 // notify derived classes that we're starting now
17079 this.startAnimation();
17083 this.endAnimation();
17084 this.endActivity();
17087 ActivityBase.prototype.performEnd = function()
17089 throw ( 'ActivityBase.performEnd: abstract method invoked' );
17095 function DiscreteActivityBase( aCommonParamSet )
17097 DiscreteActivityBase.superclass.constructor.call( this, aCommonParamSet );
17099 this.aOriginalWakeupEvent = aCommonParamSet.aWakeupEvent;
17100 this.aOriginalWakeupEvent.setActivity( this );
17101 this.aWakeupEvent = this.aOriginalWakeupEvent;
17102 this.aWakeupEvent = aCommonParamSet.aWakeupEvent;
17103 this.aDiscreteTimes = aCommonParamSet.aDiscreteTimes;
17104 // Simple duration of activity
17105 this.nMinSimpleDuration = aCommonParamSet.nMinDuration;
17106 // Actual number of frames shown until now.
17107 this.nCurrPerformCalls = 0;
17109 extend( DiscreteActivityBase, ActivityBase );
17112 DiscreteActivityBase.prototype.activate = function( aEndElement )
17114 DiscreteActivityBase.superclass.activate.call( this, aEndElement );
17116 this.aWakeupEvent = this.aOriginalWakeupEvent;
17117 this.aWakeupEvent.setNextTimeout( 0 );
17118 this.nCurrPerformCalls = 0;
17121 DiscreteActivityBase.prototype.startAnimation = function()
17123 this.aWakeupEvent.start();
17126 DiscreteActivityBase.prototype.calcFrameIndex = function( nCurrCalls, nVectorSize )
17128 if( this.isAutoReverse() )
17130 // every full repeat run consists of one
17131 // forward and one backward traversal.
17132 var nFrameIndex = nCurrCalls % (2 * nVectorSize);
17134 // nFrameIndex values >= nVectorSize belong to
17135 // the backward traversal
17136 if( nFrameIndex >= nVectorSize )
17137 nFrameIndex = 2*nVectorSize - nFrameIndex; // invert sweep
17139 return nFrameIndex;
17143 return nCurrCalls % nVectorSize;
17147 DiscreteActivityBase.prototype.calcRepeatCount = function( nCurrCalls, nVectorSize )
17149 if( this.isAutoReverse() )
17151 return Math.floor( nCurrCalls / (2*nVectorSize) ); // we've got 2 cycles per repeat
17155 return Math.floor( nCurrCalls / nVectorSize );
17159 DiscreteActivityBase.prototype.performDiscreteHook = function( /*nFrame, nRepeatCount*/ )
17161 throw ( 'DiscreteActivityBase.performDiscreteHook: abstract method invoked' );
17164 DiscreteActivityBase.prototype.perform = function()
17166 // call base class, for start() calls and end handling
17167 if( !SimpleContinuousActivityBase.superclass.perform.call( this ) )
17168 return false; // done, we're ended
17170 var nVectorSize = this.aDiscreteTimes.length;
17172 var nFrameIndex = this.calcFrameIndex(this.nCurrPerformCalls, nVectorSize);
17173 var nRepeatCount = this.calcRepeatCount( this.nCurrPerformCalls, nVectorSize );
17174 this.performDiscreteHook( nFrameIndex, nRepeatCount );
17176 // one more frame successfully performed
17177 ++this.nCurrPerformCalls;
17179 // calc currently reached repeat count
17180 var nCurrRepeat = this.nCurrPerformCalls / nVectorSize;
17182 // if auto-reverse is specified, halve the
17183 // effective repeat count, since we pass every
17184 // repeat run twice: once forward, once backward.
17185 if( this.isAutoReverse() )
17188 // schedule next frame, if either repeat is indefinite
17189 // (repeat forever), or we've not yet reached the requested
17191 if( !this.isRepeatCountValid() || nCurrRepeat < this.getRepeatCount() )
17193 // add wake-up event to queue (modulo vector size, to cope with repeats).
17195 // repeat is handled locally, only apply acceleration/deceleration.
17196 // Scale time vector with simple duration, offset with full repeat
17199 // Note that calcAcceleratedTime() is only applied to the current repeat's value,
17200 // not to the total resulting time. This is in accordance with the SMIL spec.
17202 nFrameIndex = this.calcFrameIndex(this.nCurrPerformCalls, nVectorSize);
17203 var nCurrentRepeatTime = this.aDiscreteTimes[nFrameIndex];
17204 nRepeatCount = this.calcRepeatCount( this.nCurrPerformCalls, nVectorSize );
17205 var nNextTimeout = this.nMinSimpleDuration * ( nRepeatCount + this.calcAcceleratedTime( nCurrentRepeatTime ) );
17206 this.aWakeupEvent.setNextTimeout( nNextTimeout );
17208 this.getEventQueue().addEvent( this.aWakeupEvent );
17212 // release event reference (relation to wake up event is circular!)
17213 this.aWakeupEvent = null;
17215 // done with this activity
17216 this.endActivity();
17219 return false; // remove from queue, will be added back by the wakeup event.
17222 DiscreteActivityBase.prototype.dispose = function()
17225 if( this.aWakeupEvent )
17226 this.aWakeupEvent.dispose();
17228 // release references
17229 this.aWakeupEvent = null;
17231 DiscreteActivityBase.superclass.dispose.call(this);
17237 function SimpleContinuousActivityBase( aCommonParamSet )
17239 SimpleContinuousActivityBase.superclass.constructor.call( this, aCommonParamSet );
17241 // Time elapsed since activity started
17242 this.aTimer = new ElapsedTime( aCommonParamSet.aActivityQueue.getTimer() );
17243 // Simple duration of activity
17244 this.nMinSimpleDuration = aCommonParamSet.nMinDuration;
17245 // Minimal number of frames to show
17246 this.nMinNumberOfFrames = aCommonParamSet.nMinNumberOfFrames;
17247 // Actual number of frames shown until now.
17248 this.nCurrPerformCalls = 0;
17251 extend( SimpleContinuousActivityBase, ActivityBase );
17254 SimpleContinuousActivityBase.prototype.startAnimation = function()
17256 // init timer. We measure animation time only when we're
17257 // actually started.
17258 this.aTimer.reset();
17261 SimpleContinuousActivityBase.prototype.calcTimeLag = function()
17263 SimpleContinuousActivityBase.superclass.calcTimeLag.call( this );
17265 if( !this.isActive() )
17268 // retrieve locally elapsed time
17269 var nCurrElapsedTime = this.aTimer.getElapsedTime();
17271 // go to great length to ensure a proper animation
17272 // run. Since we don't know how often we will be called
17273 // here, try to spread the animator calls uniquely over
17274 // the [0,1] parameter range. Be aware of the fact that
17275 // perform will be called at least mnMinNumberOfTurns
17278 // fraction of time elapsed
17279 var nFractionElapsedTime = nCurrElapsedTime / this.nMinSimpleDuration;
17281 // fraction of minimum calls performed
17282 var nFractionRequiredCalls = this.nCurrPerformCalls / this.nMinNumberOfFrames;
17284 // okay, so now, the decision is easy:
17286 // If the fraction of time elapsed is smaller than the
17287 // number of calls required to be performed, then we calc
17288 // the position on the animation range according to
17289 // elapsed time. That is, we're so to say ahead of time.
17291 // In contrary, if the fraction of time elapsed is larger,
17292 // then we're lagging, and we thus calc the position on
17293 // the animation time line according to the fraction of
17294 // calls performed. Thus, the animation is forced to slow
17295 // down, and take the required minimal number of steps,
17296 // sufficiently equally distributed across the animation
17299 if( nFractionElapsedTime < nFractionRequiredCalls )
17305 // lag global time, so all other animations lag, too:
17306 return ( ( nFractionElapsedTime - nFractionRequiredCalls ) * this.nMinSimpleDuration );
17310 SimpleContinuousActivityBase.prototype.perform = function()
17312 // call base class, for start() calls and end handling
17313 if( !SimpleContinuousActivityBase.superclass.perform.call( this ) )
17314 return false; // done, we're ended
17316 // get relative animation position
17317 var nCurrElapsedTime = this.aTimer.getElapsedTime();
17318 var nT = nCurrElapsedTime / this.nMinSimpleDuration;
17321 // one of the stop criteria reached?
17323 // will be set to true below, if one of the termination criteria matched.
17324 var bActivityEnding = false;
17326 if( this.isRepeatCountValid() )
17328 // Finite duration case
17330 // When we've autoreverse on, the repeat count doubles
17331 var nRepeatCount = this.getRepeatCount();
17332 var nEffectiveRepeat = this.isAutoReverse() ? 2.0 * nRepeatCount : nRepeatCount;
17334 // time (or frame count) elapsed?
17335 if( nEffectiveRepeat <= nT )
17337 // Ok done for now. Will not exit right here,
17338 // to give animation the chance to render the last
17340 bActivityEnding = true;
17342 // clamp animation to max permissible value
17343 nT = nEffectiveRepeat;
17348 // need to do auto-reverse?
17351 var nRelativeSimpleTime;
17352 // TODO(Q3): Refactor this mess
17353 if( this.isAutoReverse() )
17355 // divert active duration into repeat and
17356 // fractional part.
17357 nRepeats = Math.floor( nT );
17358 var nFractionalActiveDuration = nT - nRepeats;
17360 // for auto-reverse, map ranges [1,2), [3,4), ...
17361 // to ranges [0,1), [1,2), etc.
17364 // we're in an odd range, reverse sweep
17365 nRelativeSimpleTime = 1.0 - nFractionalActiveDuration;
17369 // we're in an even range, pass on as is
17370 nRelativeSimpleTime = nFractionalActiveDuration;
17373 // effective repeat count for autoreverse is half of
17374 // the input time's value (each run of an autoreverse
17375 // cycle is half of a repeat)
17380 // determine repeat
17382 // calc simple time and number of repeats from nT
17383 // Now, that's easy, since the fractional part of
17384 // nT gives the relative simple time, and the
17385 // integer part the number of full repeats:
17386 nRepeats = Math.floor( nT );
17387 nRelativeSimpleTime = nT - nRepeats;
17389 // clamp repeats to max permissible value (maRepeats.getValue() - 1.0)
17390 if( this.isRepeatCountValid() && ( nRepeats >= this.getRepeatCount() ) )
17392 // Note that this code here only gets
17393 // triggered if this.nRepeats is an
17394 // _integer_. Otherwise, nRepeats will never
17395 // reach nor exceed
17396 // maRepeats.getValue(). Thus, the code below
17397 // does not need to handle cases of fractional
17398 // repeats, and can always assume that a full
17399 // animation run has ended (with
17400 // nRelativeSimpleTime = 1.0 for
17401 // non-autoreversed activities).
17403 // with modf, nRelativeSimpleTime will never
17404 // become 1.0, since nRepeats is incremented and
17405 // nRelativeSimpleTime set to 0.0 then.
17407 // For the animation to reach its final value,
17408 // nRepeats must although become this.nRepeats - 1.0,
17409 // and nRelativeSimpleTime = 1.0.
17410 nRelativeSimpleTime = 1.0;
17416 // actually perform something
17418 this.simplePerform( nRelativeSimpleTime, nRepeats );
17420 // delayed endActivity() call from end condition check
17421 // below. Issued after the simplePerform() call above, to
17422 // give animations the chance to correctly reach the
17423 // animation end value, without spurious bail-outs because
17424 // of isActive() returning false.
17425 if( bActivityEnding )
17426 this.endActivity();
17428 // one more frame successfully performed
17429 ++this.nCurrPerformCalls;
17431 return this.isActive();
17434 SimpleContinuousActivityBase.prototype.simplePerform = function( /*nSimpleTime, nRepeatCount*/ )
17436 throw ( 'SimpleContinuousActivityBase.simplePerform: abstract method invoked' );
17442 function ContinuousKeyTimeActivityBase( aCommonParamSet )
17444 var nSize = aCommonParamSet.aDiscreteTimes.length;
17446 'ContinuousKeyTimeActivityBase constructor: assertion (aDiscreteTimes.length > 1) failed' );
17448 assert( aCommonParamSet.aDiscreteTimes[0] == 0.0,
17449 'ContinuousKeyTimeActivityBase constructor: assertion (aDiscreteTimes.front() == 0.0) failed' );
17451 assert( aCommonParamSet.aDiscreteTimes[ nSize - 1 ] <= 1.0,
17452 'ContinuousKeyTimeActivityBase constructor: assertion (aDiscreteTimes.back() <= 1.0) failed' );
17454 ContinuousKeyTimeActivityBase.superclass.constructor.call( this, aCommonParamSet );
17456 this.aLerper = new KeyStopLerp( aCommonParamSet.aDiscreteTimes );
17458 extend( ContinuousKeyTimeActivityBase, SimpleContinuousActivityBase );
17461 ContinuousKeyTimeActivityBase.prototype.activate = function( aEndElement )
17463 ContinuousKeyTimeActivityBase.superclass.activate.call( this, aEndElement );
17465 this.aLerper.reset();
17468 ContinuousKeyTimeActivityBase.prototype.performContinuousHook = function( /*nIndex, nFractionalIndex, nRepeatCount*/ )
17470 throw ( 'ContinuousKeyTimeActivityBase.performContinuousHook: abstract method invoked' );
17473 ContinuousKeyTimeActivityBase.prototype.simplePerform = function( nSimpleTime, nRepeatCount )
17475 var nAlpha = this.calcAcceleratedTime( nSimpleTime );
17477 var aLerpResult = this.aLerper.lerp( nAlpha );
17479 this.performContinuousHook( aLerpResult.nIndex, aLerpResult.nLerp, nRepeatCount );
17485 function ContinuousActivityBase( aCommonParamSet )
17487 ContinuousActivityBase.superclass.constructor.call( this, aCommonParamSet );
17490 extend( ContinuousActivityBase, SimpleContinuousActivityBase );
17493 ContinuousActivityBase.prototype.performContinuousHook = function( /*nModifiedTime, nRepeatCount*/ )
17495 throw ( 'ContinuousActivityBase.performContinuousHook: abstract method invoked' );
17498 ContinuousActivityBase.prototype.simplePerform = function( nSimpleTime, nRepeatCount )
17500 this.performContinuousHook( this.calcAcceleratedTime( nSimpleTime ), nRepeatCount );
17506 function SimpleActivity( aCommonParamSet, aNumberAnimation, eDirection )
17508 assert( ( eDirection == BACKWARD ) || ( eDirection == FORWARD ),
17509 'SimpleActivity constructor: animation direction is not valid' );
17511 assert( aNumberAnimation, 'SimpleActivity constructor: animation object is not valid' );
17513 SimpleActivity.superclass.constructor.call( this, aCommonParamSet );
17515 this.aAnimation = aNumberAnimation;
17516 this.nDirection = ( eDirection == FORWARD ) ? 1.0 : 0.0;
17518 extend( SimpleActivity, ContinuousActivityBase );
17521 SimpleActivity.prototype.startAnimation = function()
17523 if( this.isDisposed() || !this.aAnimation )
17526 ANIMDBG.print( 'SimpleActivity.startAnimation invoked' );
17527 SimpleActivity.superclass.startAnimation.call( this );
17530 this.aAnimation.start( this.getTargetElement() );
17533 SimpleActivity.prototype.endAnimation = function()
17535 if( this.aAnimation )
17536 this.aAnimation.end();
17540 SimpleActivity.prototype.performContinuousHook = function( nModifiedTime /*, nRepeatCount*/ )
17542 // nRepeatCount is not used
17544 if( this.isDisposed() || !this.aAnimation )
17547 var nT = 1.0 - this.nDirection + nModifiedTime * ( 2.0*this.nDirection - 1.0 );
17548 this.aAnimation.perform( nT );
17551 SimpleActivity.prototype.performEnd = function()
17553 if( this.aAnimation )
17554 this.aAnimation.perform( this.nDirection );
17560 // FromToByActivity< BaseType > template class
17563 function FromToByActivityTemplate( BaseType ) // template parameter
17566 function FromToByActivity( aFromValue, aToValue, aByValue,
17567 aActivityParamSet, aAnimation,
17568 aInterpolator, aOperatorSet, bAccumulate )
17570 assert( aAnimation, 'FromToByActivity constructor: invalid animation object' );
17571 assert( ( aToValue != undefined ) || ( aByValue != undefined ),
17572 'FromToByActivity constructor: one of aToValue or aByValue must be valid' );
17574 FromToByActivity.superclass.constructor.call( this, aActivityParamSet );
17576 this.aFrom = aFromValue;
17577 this.aTo = aToValue;
17578 this.aBy = aByValue;
17579 this.aStartValue = null;
17580 this.aEndValue = null;
17581 this.aPreviousValue = null;
17582 this.aStartInterpolationValue = null;
17583 this.aAnimation = aAnimation;
17584 this.aInterpolator = aInterpolator;
17585 this.equal = aOperatorSet.equal;
17586 this.add = aOperatorSet.add;
17587 this.scale = aOperatorSet.scale;
17588 this.bDynamicStartValue = false;
17589 this.nIteration = 0;
17590 this.bCumulative = bAccumulate;
17591 this.aFormula = aActivityParamSet.aFormula;
17593 extend( FromToByActivity, BaseType );
17595 FromToByActivity.prototype.initAnimatedElement = function()
17597 if( this.aAnimation && this.aFrom )
17599 var aValue = this.aFormula ? this.aFormula( this.aFrom ) : this.aFrom;
17600 this.aAnimation.perform(aValue);
17604 FromToByActivity.prototype.startAnimation = function()
17606 if( this.isDisposed() || !this.aAnimation )
17608 log( 'FromToByActivity.startAnimation: activity disposed or not valid animation' );
17612 FromToByActivity.superclass.startAnimation.call( this );
17614 this.aAnimation.start( this.getTargetElement() );
17617 var aAnimationStartValue = this.aAnimation.getUnderlyingValue();
17619 // first of all, determine general type of
17620 // animation, by inspecting which of the FromToBy values
17621 // are actually valid.
17622 // See http://www.w3.org/TR/smil20/animation.html#AnimationNS-FromToBy
17623 // for a definition
17626 // From-to or From-by animation. According to
17627 // SMIL spec, the To value takes precedence
17628 // over the By value, if both are specified
17631 // From-To animation
17632 this.aStartValue = this.aFrom;
17633 this.aEndValue = this.aTo;
17635 else if( this.aBy )
17637 // From-By animation
17638 this.aStartValue = this.aFrom;
17640 this.aEndValue = this.add( this.aStartValue, this.aBy );
17645 this.aStartValue = aAnimationStartValue;
17646 this.aStartInterpolationValue = this.aStartValue;
17648 // By or To animation. According to SMIL spec,
17649 // the To value takes precedence over the By
17650 // value, if both are specified
17655 // According to the SMIL spec
17656 // (http://www.w3.org/TR/smil20/animation.html#animationNS-ToAnimation),
17657 // the to animation interpolates between
17658 // the _running_ underlying value and the to value (as the end value)
17659 this.bDynamicStartValue = true;
17660 this.aPreviousValue = this.aStartValue;
17661 this.aEndValue = this.aTo;
17663 else if( this.aBy )
17666 this.aStartValue = aAnimationStartValue;
17668 this.aEndValue = this.add( this.aStartValue, this.aBy );
17672 ANIMDBG.print( 'FromToByActivity.startAnimation: aStartValue = ' + this.aStartValue + ', aEndValue = ' + this.aEndValue );
17675 FromToByActivity.prototype.endAnimation = function()
17677 if( this.aAnimation )
17678 this.aAnimation.end();
17681 // perform hook override for ContinuousActivityBase
17682 FromToByActivity.prototype.performContinuousHook = function( nModifiedTime, nRepeatCount )
17684 if( this.isDisposed() || !this.aAnimation )
17686 log( 'FromToByActivity.performContinuousHook: activity disposed or not valid animation' );
17691 // According to SMIL 3.0 spec 'to' animation if no other (lower priority)
17692 // animations are active or frozen then a simple interpolation is performed.
17693 // That is, the start interpolation value is constant while the animation
17694 // is running, and is equal to the underlying value retrieved when
17695 // the animation start.
17696 // However if another animation is manipulating the underlying value,
17697 // the 'to' animation will initially add to the effect of the lower priority
17698 // animation, and increasingly dominate it as it nears the end of the
17699 // simple duration, eventually overriding it completely.
17700 // That is, each time the underlying value is changed between two
17701 // computations of the animation function the new underlying value is used
17702 // as start value for the interpolation.
17704 // http://www.w3.org/TR/SMIL3/smil-animation.html#animationNS-ToAnimation
17705 // (Figure 6 - Effect of Additive to animation example)
17706 // Moreover when a 'to' animation is repeated, at each new iteration
17707 // the start interpolation value is reset to the underlying value
17708 // of the animated property when the animation started,
17709 // as it is shown in the example provided by the SMIL 3.0 spec.
17710 // This is exactly as Firefox performs SVG 'to' animations.
17711 if( this.bDynamicStartValue )
17713 if( this.nIteration != nRepeatCount )
17715 this.nIteration = nRepeatCount;
17716 this.aStartInterpolationValue = this.aStartValue;
17720 var aActualValue = this.aAnimation.getUnderlyingValue();
17721 if( !this.equal( aActualValue, this.aPreviousValue ) )
17722 this.aStartInterpolationValue = aActualValue;
17726 var aValue = this.aInterpolator( this.aStartInterpolationValue,
17727 this.aEndValue, nModifiedTime );
17729 // According to the SMIL spec:
17730 // Because 'to' animation is defined in terms of absolute values of
17731 // the target attribute, cumulative animation is not defined.
17732 if( this.bCumulative && !this.bDynamicStartValue )
17734 // aValue = this.aEndValue * nRepeatCount + aValue;
17735 aValue = this.add( this.scale( nRepeatCount, this.aEndValue ), aValue );
17738 aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17739 this.aAnimation.perform( aValue );
17741 if( this.bDynamicStartValue )
17743 this.aPreviousValue = this.aAnimation.getUnderlyingValue();
17748 // perform hook override for DiscreteActivityBase
17749 FromToByActivity.prototype.performDiscreteHook = function( /*nFrame, nRepeatCount*/ )
17751 if (this.isDisposed() || !this.aAnimation) {
17752 log('FromToByActivity.performDiscreteHook: activity disposed or not valid animation');
17757 FromToByActivity.prototype.performEnd = function()
17759 if( this.aAnimation )
17761 var aValue = this.isAutoReverse() ? this.aStartValue : this.aEndValue;
17762 aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17763 this.aAnimation.perform( aValue );
17767 FromToByActivity.prototype.dispose = function()
17769 FromToByActivity.superclass.dispose.call( this );
17773 return FromToByActivity;
17777 // FromToByActivity< ContinuousActivityBase > instantiation
17778 var LinearFromToByActivity = instantiate( FromToByActivityTemplate, ContinuousActivityBase );
17779 // FromToByActivity< DiscreteActivityBase > instantiation
17780 var DiscreteFromToByActivity = instantiate( FromToByActivityTemplate, DiscreteActivityBase );
17785 // ValueListActivity< BaseType > template class
17788 function ValueListActivityTemplate( BaseType ) // template parameter
17791 function ValueListActivity( aValueList, aActivityParamSet,
17792 aAnimation, aInterpolator,
17793 aOperatorSet, bAccumulate )
17795 assert( aAnimation, 'ValueListActivity constructor: invalid animation object' );
17796 assert( aValueList.length != 0, 'ValueListActivity: value list is empty' );
17798 ValueListActivity.superclass.constructor.call( this, aActivityParamSet );
17800 this.aValueList = aValueList;
17801 this.aAnimation = aAnimation;
17802 this.aInterpolator = aInterpolator;
17803 this.add = aOperatorSet.add;
17804 this.scale = aOperatorSet.scale;
17805 this.bCumulative = bAccumulate;
17806 this.aLastValue = this.aValueList[ this.aValueList.length - 1 ];
17807 this.aFormula = aActivityParamSet.aFormula;
17809 extend( ValueListActivity, BaseType );
17811 ValueListActivity.prototype.activate = function( aEndEvent )
17813 ValueListActivity.superclass.activate.call( this, aEndEvent );
17814 for( var i = 0; i < this.aValueList.length; ++i )
17816 ANIMDBG.print( 'createValueListActivity: value[' + i + '] = ' + this.aValueList[i] );
17820 ValueListActivity.prototype.initAnimatedElement = function()
17822 if( this.aAnimation )
17824 var aValue = this.aValueList[0];
17825 aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17826 this.aAnimation.perform(aValue);
17830 ValueListActivity.prototype.startAnimation = function()
17832 if( this.isDisposed() || !this.aAnimation )
17834 log( 'ValueListActivity.startAnimation: activity disposed or not valid animation' );
17838 ValueListActivity.superclass.startAnimation.call( this );
17840 this.aAnimation.start( this.getTargetElement() );
17843 ValueListActivity.prototype.endAnimation = function()
17845 if( this.aAnimation )
17846 this.aAnimation.end();
17849 // perform hook override for ContinuousKeyTimeActivityBase base
17850 ValueListActivity.prototype.performContinuousHook = function( nIndex, nFractionalIndex, nRepeatCount )
17852 if( this.isDisposed() || !this.aAnimation )
17854 log( 'ValueListActivity.performContinuousHook: activity disposed or not valid animation' );
17858 assert( ( nIndex + 1 ) < this.aValueList.length,
17859 'ValueListActivity.performContinuousHook: assertion (nIndex + 1 < this.aValueList.length) failed' );
17861 // interpolate between nIndex and nIndex+1 values
17863 var aValue = this.aInterpolator( this.aValueList[ nIndex ],
17864 this.aValueList[ nIndex+1 ],
17865 nFractionalIndex );
17867 if( this.bCumulative )
17869 //aValue = aValue + nRepeatCount * this.aLastValue;
17870 aValue = this.add( aValue, this.scale( nRepeatCount, this.aLastValue ) );
17873 aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17874 this.aAnimation.perform( aValue );
17877 // perform hook override for DiscreteActivityBase base
17878 ValueListActivity.prototype.performDiscreteHook = function( nFrame, nRepeatCount )
17880 if( this.isDisposed() || !this.aAnimation )
17882 log( 'ValueListActivity.performDiscreteHook: activity disposed or not valid animation' );
17886 assert( nFrame < this.aValueList.length,
17887 'ValueListActivity.performDiscreteHook: assertion ( nFrame < this.aValueList.length) failed' );
17889 // this is discrete, thus no lerp here.
17890 var aValue = this.aValueList[nFrame];
17892 if( this.bCumulative )
17894 aValue = this.add( aValue, this.scale( nRepeatCount, this.aLastValue ) );
17895 // for numbers: aValue = aValue + nRepeatCount * this.aLastValue;
17896 // for enums, bools or strings: aValue = aValue;
17899 aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17900 this.aAnimation.perform( aValue );
17903 ValueListActivity.prototype.performEnd = function()
17905 if( this.aAnimation )
17907 var aValue = this.aFormula ? this.aFormula( this.aLastValue ) : this.aLastValue;
17908 this.aAnimation.perform( aValue );
17912 ValueListActivity.prototype.dispose = function()
17914 ValueListActivity.superclass.dispose.call( this );
17918 return ValueListActivity;
17922 // ValueListActivity< ContinuousKeyTimeActivityBase > instantiation
17923 var LinearValueListActivity = instantiate( ValueListActivityTemplate, ContinuousKeyTimeActivityBase );
17924 // ValueListActivity< DiscreteActivityBase > instantiation
17925 var DiscreteValueListActivity = instantiate( ValueListActivityTemplate, DiscreteActivityBase );
17929 /**********************************************************************************************
17931 **********************************************************************************************/
17934 function createActivity( aActivityParamSet, aAnimationNode, aAnimation, aInterpolator )
17936 var eCalcMode = aAnimationNode.getCalcMode();
17938 var sAttributeName = aAnimationNode.getAttributeName();
17939 var aAttributeProp = aAttributeMap[ sAttributeName ];
17941 var eValueType = aAttributeProp[ 'type' ];
17942 var eValueSubtype = aAttributeProp[ 'subtype' ];
17944 // do we need to get an interpolator ?
17945 if( ! aInterpolator )
17947 aInterpolator = aInterpolatorHandler.getInterpolator( eCalcMode,
17952 // is it cumulative ?
17953 var bAccumulate = ( aAnimationNode.getAccumulate() === ACCUMULATE_MODE_SUM )
17954 && !( eValueType === BOOL_PROPERTY ||
17955 eValueType === STRING_PROPERTY ||
17956 eValueType === ENUM_PROPERTY );
17958 if( aAnimationNode.getFormula() )
17960 var sFormula = aAnimationNode.getFormula();
17961 var reMath = /abs|sqrt|asin|acos|atan|sin|cos|tan|exp|log|min|max/g;
17962 sFormula = sFormula.replace(reMath, 'Math.$&');
17963 sFormula = sFormula.replace(/pi(?!\w)/g, 'Math.PI');
17964 sFormula = sFormula.replace(/e(?!\w)/g, 'Math.E');
17965 sFormula = sFormula.replace(/\$/g, '__PARAM0__');
17967 var aAnimatedElement = aAnimationNode.getAnimatedElement();
17968 var aBBox = aAnimatedElement.getBaseBBox();
17970 // the following variable are used for evaluating sFormula
17971 /* eslint-disable no-unused-vars */
17972 var width = aBBox.width / aActivityParamSet.nSlideWidth;
17973 var height = aBBox.height / aActivityParamSet.nSlideHeight;
17974 var x = ( aBBox.x + aBBox.width / 2 ) / aActivityParamSet.nSlideWidth;
17975 var y = ( aBBox.y + aBBox.height / 2 ) / aActivityParamSet.nSlideHeight;
17977 aActivityParamSet.aFormula = function( __PARAM0__ ) {
17979 return eval(sFormula);
17981 /* eslint-enable no-unused-vars */
17984 aActivityParamSet.aDiscreteTimes = aAnimationNode.getKeyTimes();
17986 // do we have a value list ?
17987 var aValueSet = aAnimationNode.getValues();
17988 var nValueSetSize = aValueSet.length;
17990 if( nValueSetSize != 0 )
17992 // Value list activity
17994 if( aActivityParamSet.aDiscreteTimes.length == 0 )
17996 for( var i = 0; i < nValueSetSize; ++i )
17997 aActivityParamSet.aDiscreteTimes[i].push( i / nValueSetSize );
18000 switch( eCalcMode )
18002 case CALC_MODE_DISCRETE:
18003 aActivityParamSet.aWakeupEvent =
18004 new WakeupEvent( aActivityParamSet.aTimerEventQueue.getTimer(),
18005 aActivityParamSet.aActivityQueue );
18007 return createValueListActivity( aActivityParamSet,
18011 DiscreteValueListActivity,
18016 log( 'createActivity: unexpected calculation mode: ' + eCalcMode );
18017 // FALLTHROUGH intended
18018 case CALC_MODE_PACED :
18019 case CALC_MODE_SPLINE :
18020 case CALC_MODE_LINEAR:
18021 return createValueListActivity( aActivityParamSet,
18025 LinearValueListActivity,
18032 // FromToBy activity
18033 switch( eCalcMode )
18035 case CALC_MODE_DISCRETE:
18036 log( 'createActivity: discrete calculation case not yet implemented' );
18037 aActivityParamSet.aWakeupEvent =
18038 new WakeupEvent( aActivityParamSet.aTimerEventQueue.getTimer(),
18039 aActivityParamSet.aActivityQueue );
18040 return createFromToByActivity( aActivityParamSet,
18044 DiscreteFromToByActivity,
18049 log( 'createActivity: unexpected calculation mode: ' + eCalcMode );
18050 // FALLTHROUGH intended
18051 case CALC_MODE_PACED :
18052 case CALC_MODE_SPLINE :
18053 case CALC_MODE_LINEAR:
18054 return createFromToByActivity( aActivityParamSet,
18058 LinearFromToByActivity,
18068 function createValueListActivity( aActivityParamSet, aAnimationNode, aAnimation,
18069 aInterpolator, ClassTemplateInstance, bAccumulate, eValueType )
18071 var aAnimatedElement = aAnimationNode.getAnimatedElement();
18072 var aOperatorSet = aOperatorSetMap[ eValueType ];
18073 assert( aOperatorSet, 'createValueListActivity: no operator set found' );
18075 var aValueSet = aAnimationNode.getValues();
18077 var aValueList = [];
18079 extractAttributeValues( eValueType,
18082 aAnimatedElement.getBaseBBox(),
18083 aActivityParamSet.nSlideWidth,
18084 aActivityParamSet.nSlideHeight );
18086 for( var i = 0; i < aValueList.length; ++i )
18088 ANIMDBG.print( 'createValueListActivity: value[' + i + '] = ' + aValueList[i] );
18091 return new ClassTemplateInstance( aValueList, aActivityParamSet, aAnimation,
18092 aInterpolator, aOperatorSet, bAccumulate );
18098 function createFromToByActivity( aActivityParamSet, aAnimationNode, aAnimation,
18099 aInterpolator, ClassTemplateInstance, bAccumulate, eValueType )
18102 var aAnimatedElement = aAnimationNode.getAnimatedElement();
18103 var aOperatorSet = aOperatorSetMap[ eValueType ];
18104 assert( aOperatorSet, 'createFromToByActivity: no operator set found' );
18106 var aValueSet = [];
18107 aValueSet[0] = aAnimationNode.getFromValue();
18108 aValueSet[1] = aAnimationNode.getToValue();
18109 aValueSet[2] = aAnimationNode.getByValue();
18111 ANIMDBG.print( 'createFromToByActivity: value type: ' + aValueTypeOutMap[eValueType] +
18112 ', aFrom = ' + aValueSet[0] +
18113 ', aTo = ' + aValueSet[1] +
18114 ', aBy = ' + aValueSet[2] );
18116 var aValueList = [];
18118 extractAttributeValues( eValueType,
18121 aAnimatedElement.getBaseBBox(),
18122 aActivityParamSet.nSlideWidth,
18123 aActivityParamSet.nSlideHeight );
18125 ANIMDBG.print( 'createFromToByActivity: ' +
18126 ', aFrom = ' + aValueList[0] +
18127 ', aTo = ' + aValueList[1] +
18128 ', aBy = ' + aValueList[2] );
18130 return new ClassTemplateInstance( aValueList[0], aValueList[1], aValueList[2],
18131 aActivityParamSet, aAnimation,
18132 aInterpolator, aOperatorSet, bAccumulate );
18137 function extractAttributeValues( eValueType, aValueList, aValueSet, aBBox, nSlideWidth, nSlideHeight )
18140 switch( eValueType )
18142 case NUMBER_PROPERTY :
18143 evalValuesAttribute( aValueList, aValueSet, aBBox, nSlideWidth, nSlideHeight );
18145 case BOOL_PROPERTY :
18146 for( i = 0; i < aValueSet.length; ++i )
18148 var aValue = booleanParser( aValueSet[i] );
18149 aValueList.push( aValue );
18152 case STRING_PROPERTY :
18153 for( i = 0; i < aValueSet.length; ++i )
18155 aValueList.push( aValueSet[i] );
18158 case ENUM_PROPERTY :
18159 for( i = 0; i < aValueSet.length; ++i )
18161 aValueList.push( aValueSet[i] );
18164 case COLOR_PROPERTY :
18165 for( i = 0; i < aValueSet.length; ++i )
18167 aValue = colorParser( aValueSet[i] );
18168 aValueList.push( aValue );
18171 case TUPLE_NUMBER_PROPERTY :
18172 for( i = 0; i < aValueSet.length; ++i )
18174 if( typeof aValueSet[i] === 'string' )
18176 var aTuple = aValueSet[i].split(',');
18178 evalValuesAttribute(aValue, aTuple, aBBox, nSlideWidth, nSlideHeight);
18179 aValueList.push(aValue);
18183 aValueList.push( undefined );
18188 log( 'createValueListActivity: unexpected value type: ' + eValueType );
18194 function evalValuesAttribute( aValueList, aValueSet, aBBox, nSlideWidth, nSlideHeight )
18196 // the following variables are used for evaluating sValue later
18197 /* eslint-disable no-unused-vars */
18198 var width = aBBox.width / nSlideWidth;
18199 var height = aBBox.height / nSlideHeight;
18200 var x = ( aBBox.x + aBBox.width / 2 ) / nSlideWidth;
18201 var y = ( aBBox.y + aBBox.height / 2 ) / nSlideHeight;
18202 /* eslint-enable no-unused-vars */
18204 var reMath = /abs|sqrt|asin|acos|atan|sin|cos|tan|exp|log|min|max/g;
18206 for( var i = 0; i < aValueSet.length; ++i )
18208 var sValue = aValueSet[i];
18211 sValue = sValue.replace(reMath, 'Math.$&');
18212 sValue = sValue.replace(/pi(?!\w)/g, 'Math.PI');
18213 sValue = sValue.replace(/e(?!\w)/g, 'Math.E');
18215 var aValue = eval( sValue );
18216 aValueList.push( aValue );
18222 /**********************************************************************************************
18223 * SlideShow, SlideShowContext and FrameSynchronization
18224 **********************************************************************************************/
18228 // direction of animation, important: not change the values!
18232 var MAXIMUM_FRAME_COUNT = 60;
18233 var MINIMUM_TIMEOUT = 1.0 / MAXIMUM_FRAME_COUNT;
18234 var MAXIMUM_TIMEOUT = 4.0;
18235 var MINIMUM_FRAMES_PER_SECONDS = 10;
18236 var PREFERRED_FRAMES_PER_SECONDS = 50;
18237 var PREFERRED_FRAME_RATE = 1.0 / PREFERRED_FRAMES_PER_SECONDS;
18240 function Effect( nId )
18242 this.nId = ( typeof( nId ) === typeof( 1 ) ) ? nId : -1;
18243 this.eState = Effect.NOT_STARTED;
18245 Effect.NOT_STARTED = 0;
18246 Effect.PLAYING = 1;
18249 Effect.prototype.getId = function()
18254 Effect.prototype.isMainEffect = function()
18256 return ( this.nId === -1 );
18259 Effect.prototype.isPlaying = function()
18261 return ( this.eState === Effect.PLAYING );
18264 Effect.prototype.isEnded = function()
18266 return ( this.eState === Effect.ENDED );
18269 Effect.prototype.start = function()
18271 assert( this.eState === Effect.NOT_STARTED, 'Effect.start: wrong state.' );
18272 this.eState = Effect.PLAYING;
18275 Effect.prototype.end = function()
18277 assert( this.eState === Effect.PLAYING, 'Effect.end: wrong state.' );
18278 this.eState = Effect.ENDED;
18283 function SlideShow()
18285 this.aTimer = new ElapsedTime();
18286 this.aFrameSynchronization = new FrameSynchronization( PREFERRED_FRAME_RATE );
18287 this.aTimerEventQueue = new TimerEventQueue( this.aTimer );
18288 this.aActivityQueue = new ActivityQueue( this.aTimer );
18289 this.aNextEffectEventArray = null;
18290 this.aInteractiveAnimationSequenceMap = null;
18291 this.aEventMultiplexer = null;
18293 this.aContext = new SlideShowContext( this.aTimerEventQueue,
18294 this.aEventMultiplexer,
18295 this.aNextEffectEventArray,
18296 this.aInteractiveAnimationSequenceMap,
18297 this.aActivityQueue );
18298 this.bIsIdle = true;
18299 this.bIsEnabled = true;
18300 this.bNoSlideTransition = false;
18301 this.bIsTransitionRunning = false;
18303 this.nCurrentEffect = 0;
18304 this.bIsNextEffectRunning = false;
18305 this.bIsRewinding = false;
18306 this.bIsSkipping = false;
18307 this.bIsSkippingAll = false;
18308 this.nTotalInteractivePlayingEffects = 0;
18309 this.aStartedEffectList = [];
18310 this.aStartedEffectIndexMap = {};
18311 this.aStartedEffectIndexMap[ -1 ] = undefined;
18312 this.automaticAdvanceTimeout = null;
18315 SlideShow.prototype.setSlideEvents = function( aNextEffectEventArray,
18316 aInteractiveAnimationSequenceMap,
18317 aEventMultiplexer )
18319 if( !aNextEffectEventArray )
18320 log( 'SlideShow.setSlideEvents: aNextEffectEventArray is not valid' );
18322 if( !aInteractiveAnimationSequenceMap )
18323 log( 'SlideShow.setSlideEvents:aInteractiveAnimationSequenceMap is not valid' );
18325 if( !aEventMultiplexer )
18326 log( 'SlideShow.setSlideEvents: aEventMultiplexer is not valid' );
18328 this.aContext.aNextEffectEventArray = aNextEffectEventArray;
18329 this.aNextEffectEventArray = aNextEffectEventArray;
18330 this.aContext.aInteractiveAnimationSequenceMap = aInteractiveAnimationSequenceMap;
18331 this.aInteractiveAnimationSequenceMap = aInteractiveAnimationSequenceMap;
18332 this.aContext.aEventMultiplexer = aEventMultiplexer;
18333 this.aEventMultiplexer = aEventMultiplexer;
18334 this.nCurrentEffect = 0;
18337 SlideShow.prototype.createSlideTransition = function( aSlideTransitionHandler, aLeavingSlide, aEnteringSlide, aTransitionEndEvent )
18339 if( !aEnteringSlide )
18341 log( 'SlideShow.createSlideTransition: entering slide element is not valid.' );
18345 if( this.bNoSlideTransition ) return null;
18347 var aAnimatedLeavingSlide = null;
18348 if( aLeavingSlide )
18349 aAnimatedLeavingSlide = new AnimatedSlide( aLeavingSlide );
18350 var aAnimatedEnteringSlide = new AnimatedSlide( aEnteringSlide );
18352 var aSlideTransition = aSlideTransitionHandler.createSlideTransition( aAnimatedLeavingSlide, aAnimatedEnteringSlide );
18353 if( !aSlideTransition ) return null;
18355 // compute duration
18356 var nDuration = 0.001;
18357 if( aSlideTransitionHandler.getDuration().isValue() )
18359 nDuration = aSlideTransitionHandler.getDuration().getValue();
18363 log( 'SlideShow.createSlideTransition: duration is not a number' );
18366 var aCommonParameterSet = new ActivityParamSet();
18367 aCommonParameterSet.aEndEvent = aTransitionEndEvent;
18368 aCommonParameterSet.aTimerEventQueue = this.aTimerEventQueue;
18369 aCommonParameterSet.aActivityQueue = this.aActivityQueue;
18370 aCommonParameterSet.nMinDuration = nDuration;
18371 aCommonParameterSet.nMinNumberOfFrames = aSlideTransitionHandler.getMinFrameCount();
18372 aCommonParameterSet.nSlideWidth = WIDTH;
18373 aCommonParameterSet.nSlideHeight = HEIGHT;
18375 return new SimpleActivity( aCommonParameterSet, aSlideTransition, FORWARD );
18379 SlideShow.prototype.isEnabled = function()
18381 return this.bIsEnabled;
18384 SlideShow.prototype.isRunning = function()
18386 return !this.bIsIdle;
18389 SlideShow.prototype.isTransitionPlaying = function()
18391 return this.bIsTransitionRunning;
18394 SlideShow.prototype.isMainEffectPlaying = function()
18396 return this.bIsNextEffectRunning;
18399 SlideShow.prototype.isInteractiveEffectPlaying = function()
18401 return ( this.nTotalInteractivePlayingEffects > 0 );
18404 SlideShow.prototype.isAnyEffectPlaying = function()
18406 return ( this.isMainEffectPlaying() || this.isInteractiveEffectPlaying() );
18409 SlideShow.prototype.hasAnyEffectStarted = function()
18411 return ( this.aStartedEffectList.length > 0 );
18414 SlideShow.prototype.notifyNextEffectStart = function()
18416 assert( !this.bIsNextEffectRunning,
18417 'SlideShow.notifyNextEffectStart: an effect is already started.' );
18418 this.bIsNextEffectRunning = true;
18419 this.aEventMultiplexer.registerNextEffectEndHandler( bind2( SlideShow.prototype.notifyNextEffectEnd, this ) );
18420 var aEffect = new Effect();
18422 this.aStartedEffectIndexMap[ -1 ] = this.aStartedEffectList.length;
18423 this.aStartedEffectList.push( aEffect );
18425 var aAnimatedElementMap = theMetaDoc.aMetaSlideSet[nCurSlide].aSlideAnimationsHandler.aAnimatedElementMap;
18426 for( var sId in aAnimatedElementMap )
18427 aAnimatedElementMap[ sId ].notifyNextEffectStart( this.nCurrentEffect );
18430 SlideShow.prototype.notifyNextEffectEnd = function()
18432 assert( this.bIsNextEffectRunning,
18433 'SlideShow.notifyNextEffectEnd: effect already ended.' );
18434 this.bIsNextEffectRunning = false;
18436 this.aStartedEffectList[ this.aStartedEffectIndexMap[ -1 ] ].end();
18437 if( this.automaticAdvanceTimeout !== null )
18439 if( this.automaticAdvanceTimeout['rewindedEffect'] === this.nCurrentEffect )
18441 this.automaticAdvanceTimeout = null;
18442 this.notifyAnimationsEnd();
18447 SlideShow.prototype.notifyAnimationsEnd = function()
18449 if( nCurSlide + 1 === theMetaDoc.nNumberOfSlides )
18452 assert (this.automaticAdvanceTimeout === null,
18453 'SlideShow.notifyAnimationsEnd: Timeout already set.')
18455 var nTimeout = Math.ceil(theMetaDoc.aMetaSlideSet[nCurSlide].fDuration * 1000);
18459 this.automaticAdvanceTimeout = window.setTimeout('switchSlide(1, false)', nTimeout);
18462 SlideShow.prototype.notifySlideStart = function( nNewSlideIndex, nOldSlideIndex )
18464 this.nCurrentEffect = 0;
18465 this.bIsRewinding = false;
18466 this.bIsSkipping = false;
18467 this.bIsSkippingAll = false;
18468 this.nTotalInteractivePlayingEffects = 0;
18469 this.aStartedEffectList = [];
18470 this.aStartedEffectIndexMap = {};
18471 this.aStartedEffectIndexMap[ -1 ] = undefined;
18473 var aAnimatedElementMap;
18475 if( nOldSlideIndex !== undefined )
18477 aAnimatedElementMap = theMetaDoc.aMetaSlideSet[nOldSlideIndex].aSlideAnimationsHandler.aAnimatedElementMap;
18478 for( sId in aAnimatedElementMap )
18479 aAnimatedElementMap[ sId ].notifySlideEnd();
18482 aAnimatedElementMap = theMetaDoc.aMetaSlideSet[nNewSlideIndex].aSlideAnimationsHandler.aAnimatedElementMap;
18483 for( sId in aAnimatedElementMap )
18484 aAnimatedElementMap[ sId ].notifySlideStart( this.aContext );
18487 SlideShow.prototype.notifyTransitionEnd = function( nSlideIndex )
18489 // reset the presentation clip path on the leaving slide
18490 // to the standard one when transition ends
18491 if( theMetaDoc.getCurrentSlide() )
18493 var sRef = 'url(#' + aPresentationClipPathId + ')';
18494 theMetaDoc.getCurrentSlide().slideElement.setAttribute('clip-path', sRef);
18497 this.bIsTransitionRunning = false;
18498 if( this.bIsRewinding )
18500 theMetaDoc.aMetaSlideSet[nSlideIndex].hide();
18501 var nIndex = nCurSlide !== undefined ? nCurSlide : -1;
18502 this.displaySlide( nIndex, true );
18503 this.skipAllEffects();
18504 this.bIsRewinding = false;
18508 theMetaDoc.setCurrentSlide(nSlideIndex);
18510 if( this.aSlideViewElement )
18512 theMetaDoc.getCurrentSlide().aVisibilityStatusElement.parentNode.removeChild( this.aSlideViewElement );
18513 this.aSlideViewElement = null;
18515 if( this.isEnabled() )
18517 // clear all queues
18520 var aCurrentSlide = theMetaDoc.getCurrentSlide();
18521 if( aCurrentSlide.aSlideAnimationsHandler.elementsParsed() )
18523 aCurrentSlide.aSlideAnimationsHandler.start();
18524 this.aEventMultiplexer.registerAnimationsEndHandler( bind2( SlideShow.prototype.notifyAnimationsEnd, this ) );
18527 this.notifyAnimationsEnd();
18532 this.notifyAnimationsEnd();
18535 SlideShow.prototype.notifyInteractiveAnimationSequenceStart = function( nNodeId )
18537 ++this.nTotalInteractivePlayingEffects;
18538 var aEffect = new Effect( nNodeId );
18540 this.aStartedEffectIndexMap[ nNodeId ] = this.aStartedEffectList.length;
18541 this.aStartedEffectList.push( aEffect );
18544 SlideShow.prototype.notifyInteractiveAnimationSequenceEnd = function( nNodeId )
18546 assert( this.isInteractiveEffectPlaying(),
18547 'SlideShow.notifyInteractiveAnimationSequenceEnd: no interactive effect playing.' );
18549 this.aStartedEffectList[ this.aStartedEffectIndexMap[ nNodeId ] ].end();
18550 --this.nTotalInteractivePlayingEffects;
18554 * Start the next effect belonging to the main animation sequence if any.
18555 * If there is an already playing effect belonging to any animation sequence
18558 * @return {Boolean}
18559 * False if there is no more effect to start, true otherwise.
18561 SlideShow.prototype.nextEffect = function()
18563 if( !this.isEnabled() )
18566 if( this.isTransitionPlaying() )
18568 this.skipTransition();
18572 if( this.isAnyEffectPlaying() )
18574 this.skipAllPlayingEffects();
18578 if( !this.aNextEffectEventArray )
18581 if( this.nCurrentEffect >= this.aNextEffectEventArray.size() )
18584 this.notifyNextEffectStart();
18586 this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
18587 ++this.nCurrentEffect;
18593 * Skip the current playing slide transition.
18595 SlideShow.prototype.skipTransition = function()
18597 if( this.bIsSkipping || this.bIsRewinding )
18600 this.bIsSkipping = true;
18602 this.aActivityQueue.endAll();
18603 this.aTimerEventQueue.forceEmpty();
18604 this.aActivityQueue.endAll();
18606 this.bIsSkipping = false;
18609 /** skipAllPlayingEffects
18610 * Skip all playing effect, independently to which animation sequence they
18614 SlideShow.prototype.skipAllPlayingEffects = function()
18616 if( this.bIsSkipping || this.bIsRewinding )
18619 this.bIsSkipping = true;
18620 // TODO: The correct order should be based on the left playing time.
18621 for( var i = 0; i < this.aStartedEffectList.length; ++i )
18623 var aEffect = this.aStartedEffectList[i];
18624 if( aEffect.isPlaying() )
18626 if( aEffect.isMainEffect() )
18627 this.aEventMultiplexer.notifySkipEffectEvent();
18629 this.aEventMultiplexer.notifySkipInteractiveEffectEvent( aEffect.getId() );
18633 this.bIsSkipping = false;
18638 * Skip the next effect to be played (if any) that belongs to the main
18639 * animation sequence.
18640 * Require: no effect is playing.
18642 * @return {Boolean}
18643 * False if there is no more effect to skip, true otherwise.
18645 SlideShow.prototype.skipNextEffect = function()
18647 if( this.bIsSkipping || this.bIsRewinding )
18650 assert( !this.isAnyEffectPlaying(),
18651 'SlideShow.skipNextEffect' );
18653 if( !this.aNextEffectEventArray )
18656 if( this.nCurrentEffect >= this.aNextEffectEventArray.size() )
18659 this.notifyNextEffectStart();
18661 this.bIsSkipping = true;
18662 this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
18663 this.aEventMultiplexer.notifySkipEffectEvent();
18664 ++this.nCurrentEffect;
18666 this.bIsSkipping = false;
18670 /** skipPlayingOrNextEffect
18671 * Skip the next effect to be played that belongs to the main animation
18672 * sequence or all playing effects.
18674 * @return {Boolean}
18675 * False if there is no more effect to skip, true otherwise.
18677 SlideShow.prototype.skipPlayingOrNextEffect = function()
18679 if( this.isTransitionPlaying() )
18681 this.skipTransition();
18685 if( this.isAnyEffectPlaying() )
18686 return this.skipAllPlayingEffects();
18688 return this.skipNextEffect();
18693 * Skip all left effects that belongs to the main animation sequence and all
18694 * playing effects on the current slide.
18696 * @return {Boolean}
18697 * True if it already skipping or when it has ended skipping,
18698 * false if the next slide needs to be displayed.
18700 SlideShow.prototype.skipAllEffects = function()
18702 if( this.bIsSkippingAll )
18705 this.bIsSkippingAll = true;
18707 if( this.isTransitionPlaying() )
18709 this.skipTransition();
18712 if( this.isAnyEffectPlaying() )
18714 this.skipAllPlayingEffects();
18716 else if( !this.aNextEffectEventArray
18717 || ( this.nCurrentEffect >= this.aNextEffectEventArray.size() ) )
18719 this.bIsSkippingAll = false;
18723 // Pay attention here: a new next effect event is appended to
18724 // aNextEffectEventArray only after the related animation node has been
18725 // resolved, that is only after the animation node related to the previous
18726 // effect has notified to be deactivated to the main sequence time container.
18727 // So you should avoid any optimization here because the size of
18728 // aNextEffectEventArray will going on increasing after every skip action.
18729 while( this.nCurrentEffect < this.aNextEffectEventArray.size() )
18731 this.skipNextEffect();
18733 this.bIsSkippingAll = false;
18737 /** rewindTransition
18738 * Rewind the current playing slide transition.
18740 SlideShow.prototype.rewindTransition = function()
18742 if( this.bIsSkipping || this.bIsRewinding )
18745 this.bIsRewinding = true;
18746 this.aActivityQueue.endAll();
18748 this.bIsRewinding = false;
18752 * Rewind all the effects started after at least one of the current playing
18753 * effects. If there is no playing effect, it rewinds the last played one,
18754 * both in case it belongs to the main or to an interactive animation sequence.
18757 SlideShow.prototype.rewindEffect = function()
18759 if( this.bIsSkipping || this.bIsRewinding )
18762 if( this.automaticAdvanceTimeout !== null && !this.automaticAdvanceTimeout['rewindedEffect'] )
18764 window.clearTimeout( this.automaticAdvanceTimeout );
18765 this.automaticAdvanceTimeout = { 'rewindedEffect': this.nCurrentEffect };
18768 if( !this.hasAnyEffectStarted() )
18770 this.rewindToPreviousSlide();
18774 this.bIsRewinding = true;
18776 var nFirstPlayingEffectIndex = undefined;
18779 for( ; i < this.aStartedEffectList.length; ++i )
18781 var aEffect = this.aStartedEffectList[i];
18782 if( aEffect.isPlaying() )
18784 nFirstPlayingEffectIndex = i;
18789 // There is at least one playing effect.
18790 if( nFirstPlayingEffectIndex !== undefined )
18792 i = this.aStartedEffectList.length - 1;
18793 for( ; i >= nFirstPlayingEffectIndex; --i )
18795 aEffect = this.aStartedEffectList[i];
18796 if( aEffect.isPlaying() )
18798 if( aEffect.isMainEffect() )
18800 this.aEventMultiplexer.notifyRewindCurrentEffectEvent();
18801 if( this.nCurrentEffect > 0 )
18802 --this.nCurrentEffect;
18806 this.aEventMultiplexer.notifyRewindRunningInteractiveEffectEvent( aEffect.getId() );
18809 else if( aEffect.isEnded() )
18811 if( aEffect.isMainEffect() )
18813 this.aEventMultiplexer.notifyRewindLastEffectEvent();
18814 if( this.nCurrentEffect > 0 )
18815 --this.nCurrentEffect;
18819 this.aEventMultiplexer.notifyRewindEndedInteractiveEffectEvent( aEffect.getId() );
18825 // Pay attention here: we need to remove all rewinded effects from
18826 // the started effect list only after updating.
18827 i = this.aStartedEffectList.length - 1;
18828 for( ; i >= nFirstPlayingEffectIndex; --i )
18830 aEffect = this.aStartedEffectList.pop();
18831 if( !aEffect.isMainEffect() )
18832 delete this.aStartedEffectIndexMap[ aEffect.getId() ];
18835 else // there is no playing effect
18837 aEffect = this.aStartedEffectList.pop();
18838 if( !aEffect.isMainEffect() )
18839 delete this.aStartedEffectIndexMap[ aEffect.getId() ];
18840 if( aEffect.isEnded() ) // Well that is almost an assertion.
18842 if( aEffect.isMainEffect() )
18844 this.aEventMultiplexer.notifyRewindLastEffectEvent();
18845 if( this.nCurrentEffect > 0 )
18846 --this.nCurrentEffect;
18850 this.aEventMultiplexer.notifyRewindEndedInteractiveEffectEvent( aEffect.getId() );
18856 this.bIsRewinding = false;
18859 /** rewindToPreviousSlide
18860 * Displays the previous slide with all effects, that belong to the main
18861 * animation sequence, played.
18864 SlideShow.prototype.rewindToPreviousSlide = function()
18866 if( this.isTransitionPlaying() )
18868 this.rewindTransition();
18871 if( this.isAnyEffectPlaying() )
18873 var nNewSlide = nCurSlide - 1;
18874 this.displaySlide( nNewSlide, true );
18875 this.skipAllEffects();
18878 /** rewindAllEffects
18879 * Rewind all effects already played on the current slide.
18882 SlideShow.prototype.rewindAllEffects = function()
18884 if( !this.hasAnyEffectStarted() )
18886 this.rewindToPreviousSlide();
18890 while( this.hasAnyEffectStarted() )
18892 this.rewindEffect();
18896 SlideShow.prototype.exitSlideShowInApp = function()
18898 if (window.webkit !== undefined &&
18899 window.webkit.messageHandlers !== undefined &&
18900 window.webkit.messageHandlers.lok !== undefined)
18901 window.webkit.messageHandlers.lok.postMessage('EXITSLIDESHOW', '*');
18902 // FIXME remove this in a follow-up commit
18903 if (window.webkit !== undefined &&
18904 window.webkit.messageHandlers !== undefined &&
18905 window.webkit.messageHandlers.cool !== undefined)
18906 window.webkit.messageHandlers.cool.postMessage('EXITSLIDESHOW', '*');
18909 SlideShow.prototype.displaySlide = function( nNewSlide, bSkipSlideTransition )
18911 var aMetaDoc = theMetaDoc;
18912 var nSlides = aMetaDoc.nNumberOfSlides;
18913 if( nNewSlide < 0 && nSlides > 0 )
18914 nNewSlide = nSlides - 1;
18915 else if( nNewSlide >= nSlides ) {
18917 // In the iOS app, exit the slideshow when going past the end.
18918 this.exitSlideShowInApp();
18921 if( ( currentMode === INDEX_MODE ) && ( nNewSlide === nCurSlide ) )
18923 aMetaDoc.getCurrentSlide().show();
18927 if( this.isTransitionPlaying() )
18929 this.skipTransition();
18932 // handle current slide
18933 var nOldSlide = nCurSlide;
18934 if( nOldSlide !== undefined )
18936 var oldMetaSlide = aMetaDoc.aMetaSlideSet[nOldSlide];
18937 if( this.isEnabled() )
18939 if( oldMetaSlide.aSlideAnimationsHandler.isAnimated() )
18941 // force end animations
18942 oldMetaSlide.aSlideAnimationsHandler.end( bSkipSlideTransition );
18944 // clear all queues
18949 if( this.automaticAdvanceTimeout !== null )
18951 window.clearTimeout( this.automaticAdvanceTimeout );
18952 this.automaticAdvanceTimeout = null;
18956 this.notifySlideStart( nNewSlide, nOldSlide );
18958 if( this.isEnabled() && !bSkipSlideTransition )
18960 // create slide transition and add to activity queue
18961 if ( ( ( nOldSlide !== undefined ) &&
18962 ( ( nNewSlide > nOldSlide ) ||
18963 ( ( nNewSlide == 0) && ( nOldSlide == (aMetaDoc.nNumberOfSlides - 1) ) ) ) ) ||
18964 ( ( nOldSlide === undefined ) && ( nNewSlide == 0) ) // for transition on first slide
18968 var aOldMetaSlide = null;
18969 if( nOldSlide === undefined ) // for transition on first slide
18971 aOldMetaSlide = aMetaDoc.theMetaDummySlide;
18975 aOldMetaSlide = aMetaDoc.aMetaSlideSet[nOldSlide];
18977 var aNewMetaSlide = aMetaDoc.aMetaSlideSet[nNewSlide];
18979 var aSlideTransitionHandler = aNewMetaSlide.aTransitionHandler;
18980 if( aSlideTransitionHandler && aSlideTransitionHandler.isValid() )
18982 // clipPath element used for the leaving slide in order
18983 // to avoid that slide borders are visible during transition
18984 var sRef = 'url(#' + aPresentationClipPathShrinkId + ')';
18985 aOldMetaSlide.slideElement.setAttribute( 'clip-path', sRef );
18987 // when we switch from the last to the first slide we need to hide the last slide
18988 // or nobody will see the transition, hence we create a view of the last slide and
18989 // we place it before the first slide
18990 if( nOldSlide > nNewSlide )
18992 this.aSlideViewElement = document.createElementNS( NSS['svg'], 'use' );
18993 setNSAttribute( 'xlink', this.aSlideViewElement, 'href', '#' + aOldMetaSlide.slideContainerId );
18994 aNewMetaSlide.aVisibilityStatusElement.parentNode.insertBefore( this.aSlideViewElement, aNewMetaSlide.aVisibilityStatusElement );
18995 aOldMetaSlide.hide();
18998 var aLeavingSlide = aOldMetaSlide;
18999 var aEnteringSlide = aNewMetaSlide;
19000 var aTransitionEndEvent = makeEvent( bind2( this.notifyTransitionEnd, this, nNewSlide ) );
19002 var aTransitionActivity =
19003 this.createSlideTransition( aSlideTransitionHandler, aLeavingSlide,
19004 aEnteringSlide, aTransitionEndEvent );
19006 if( aTransitionActivity )
19008 this.bIsTransitionRunning = true;
19009 this.aActivityQueue.addActivity( aTransitionActivity );
19014 this.notifyTransitionEnd( nNewSlide );
19019 this.notifyTransitionEnd( nNewSlide );
19024 this.notifyTransitionEnd( nNewSlide );
19029 this.notifyTransitionEnd( nNewSlide );
19034 SlideShow.prototype.update = function()
19036 this.aTimer.holdTimer();
19039 this.aTimerEventQueue.process();
19040 this.aActivityQueue.process();
19042 this.aFrameSynchronization.synchronize();
19044 this.aActivityQueue.processDequeued();
19046 this.aTimer.releaseTimer();
19048 var bActivitiesLeft = ( ! this.aActivityQueue.isEmpty() );
19049 var bTimerEventsLeft = ( ! this.aTimerEventQueue.isEmpty() );
19050 var bEventsLeft = ( bActivitiesLeft || bTimerEventsLeft );
19056 if( bActivitiesLeft )
19058 nNextTimeout = MINIMUM_TIMEOUT;
19059 this.aFrameSynchronization.activate();
19063 nNextTimeout = this.aTimerEventQueue.nextTimeout();
19064 if( nNextTimeout < MINIMUM_TIMEOUT )
19065 nNextTimeout = MINIMUM_TIMEOUT;
19066 else if( nNextTimeout > MAXIMUM_TIMEOUT )
19067 nNextTimeout = MAXIMUM_TIMEOUT;
19068 this.aFrameSynchronization.deactivate();
19071 this.bIsIdle = false;
19072 window.setTimeout( 'aSlideShow.update()', nNextTimeout * 1000 );
19076 this.bIsIdle = true;
19080 SlideShow.prototype.dispose = function()
19082 // clear all queues
19083 this.aTimerEventQueue.clear();
19084 this.aActivityQueue.clear();
19085 this.aNextEffectEventArray = null;
19086 this.aEventMultiplexer = null;
19089 SlideShow.prototype.getContext = function()
19091 return this.aContext;
19094 // the SlideShow global instance
19095 var aSlideShow = null;
19100 function SlideShowContext( aTimerEventQueue, aEventMultiplexer, aNextEffectEventArray, aInteractiveAnimationSequenceMap, aActivityQueue)
19102 this.aTimerEventQueue = aTimerEventQueue;
19103 this.aEventMultiplexer = aEventMultiplexer;
19104 this.aNextEffectEventArray = aNextEffectEventArray;
19105 this.aInteractiveAnimationSequenceMap = aInteractiveAnimationSequenceMap;
19106 this.aActivityQueue = aActivityQueue;
19107 this.bIsSkipping = false;
19113 function FrameSynchronization( nFrameDuration )
19115 this.nFrameDuration = nFrameDuration;
19116 this.aTimer = new ElapsedTime();
19117 this.nNextFrameTargetTime = 0.0;
19118 this.bIsActive = false;
19120 this.markCurrentFrame();
19124 FrameSynchronization.prototype.markCurrentFrame = function()
19126 this.nNextFrameTargetTime = this.aTimer.getElapsedTime() + this.nFrameDuration;
19129 FrameSynchronization.prototype.synchronize = function()
19131 if( this.bIsActive )
19133 // Do busy waiting for now.
19134 while( this.aTimer.getElapsedTime() < this.nNextFrameTargetTime )
19138 this.markCurrentFrame();
19142 FrameSynchronization.prototype.activate = function()
19144 this.bIsActive = true;
19147 FrameSynchronization.prototype.deactivate = function()
19149 this.bIsActive = false;
19154 /**********************************************************************************************
19155 * TimerEventQueue, ActivityQueue and ElapsedTime
19156 **********************************************************************************************/
19159 function NextEffectEventArray()
19161 this.aEventArray = [];
19165 NextEffectEventArray.prototype.size = function()
19167 return this.aEventArray.length;
19170 NextEffectEventArray.prototype.at = function( nIndex )
19172 return this.aEventArray[ nIndex ];
19175 NextEffectEventArray.prototype.appendEvent = function( aEvent )
19177 var nSize = this.size();
19178 for( var i = 0; i < nSize; ++i )
19180 if( this.aEventArray[i].getId() == aEvent.getId() )
19182 aNextEffectEventArrayDebugPrinter.print( 'NextEffectEventArray.appendEvent: event(' + aEvent.getId() + ') already present' );
19186 this.aEventArray.push( aEvent );
19187 aNextEffectEventArrayDebugPrinter.print( 'NextEffectEventArray.appendEvent: event(' + aEvent.getId() + ') appended' );
19191 NextEffectEventArray.prototype.clear = function( )
19193 this.aEventArray = [];
19199 function TimerEventQueue( aTimer )
19201 this.aTimer = aTimer;
19202 this.aEventSet = new PriorityQueue( EventEntry.compare );
19206 TimerEventQueue.prototype.addEvent = function( aEvent )
19208 this.DBG( 'TimerEventQueue.addEvent event(' + aEvent.getId() + ') appended.' );
19211 log( 'TimerEventQueue.addEvent: null event' );
19215 var nTime = aEvent.getActivationTime( this.aTimer.getElapsedTime() );
19216 var aEventEntry = new EventEntry( aEvent, nTime );
19217 this.aEventSet.push( aEventEntry );
19222 TimerEventQueue.prototype.forceEmpty = function()
19224 this.process_(true);
19228 TimerEventQueue.prototype.process = function()
19230 this.process_(false);
19233 TimerEventQueue.prototype.process_ = function( bFireAllEvents )
19235 var nCurrentTime = this.aTimer.getElapsedTime();
19237 while( !this.isEmpty() && ( bFireAllEvents || ( this.aEventSet.top().nActivationTime <= nCurrentTime ) ) )
19239 var aEventEntry = this.aEventSet.top();
19240 this.aEventSet.pop();
19242 var aEvent = aEventEntry.aEvent;
19243 if( aEvent.isCharged() )
19248 TimerEventQueue.prototype.isEmpty = function()
19250 return this.aEventSet.isEmpty();
19253 TimerEventQueue.prototype.nextTimeout = function()
19255 var nTimeout = Number.MAX_VALUE;
19256 var nCurrentTime = this.aTimer.getElapsedTime();
19257 if( !this.isEmpty() )
19258 nTimeout = this.aEventSet.top().nActivationTime - nCurrentTime;
19262 TimerEventQueue.prototype.clear = function()
19264 this.DBG( 'TimerEventQueue.clear invoked' );
19265 this.aEventSet.clear();
19268 TimerEventQueue.prototype.getTimer = function()
19270 return this.aTimer;
19273 TimerEventQueue.prototype.DBG = function( sMessage, nTime )
19275 aTimerEventQueueDebugPrinter.print( sMessage, nTime );
19279 TimerEventQueue.prototype.insert = function( aEventEntry )
19281 var nHoleIndex = this.aEventSet.length;
19282 var nParent = Math.floor( ( nHoleIndex - 1 ) / 2 );
19284 while( ( nHoleIndex > 0 ) && this.aEventSet[ nParent ].compare( aEventEntry ) )
19286 this.aEventSet[ nHoleIndex ] = this.aEventSet[ nParent ];
19287 nHoleIndex = nParent;
19288 nParent = Math.floor( ( nHoleIndex - 1 ) / 2 );
19290 this.aEventSet[ nHoleIndex ] = aEventEntry;
19296 function EventEntry( aEvent, nTime )
19298 this.aEvent = aEvent;
19299 this.nActivationTime = nTime;
19303 EventEntry.compare = function( aLhsEventEntry, aRhsEventEntry )
19305 if ( aLhsEventEntry.nActivationTime > aRhsEventEntry.nActivationTime )
19309 else if ( aLhsEventEntry.nActivationTime < aRhsEventEntry.nActivationTime )
19322 function ActivityQueue( aTimer )
19324 this.aTimer = aTimer;
19325 this.aCurrentActivityWaitingSet = [];
19326 this.aCurrentActivityReinsertSet = [];
19327 this.aDequeuedActivitySet = [];
19331 ActivityQueue.prototype.dispose = function()
19333 var nSize = this.aCurrentActivityWaitingSet.length;
19335 for( i = 0; i < nSize; ++i )
19336 this.aCurrentActivityWaitingSet[i].dispose();
19338 nSize = this.aCurrentActivityReinsertSet.length;
19339 for( i = 0; i < nSize; ++i )
19340 this.aCurrentActivityReinsertSet[i].dispose();
19343 ActivityQueue.prototype.addActivity = function( aActivity )
19347 log( 'ActivityQueue.addActivity: activity is not valid' );
19351 this.aCurrentActivityWaitingSet.push( aActivity );
19352 aActivityQueueDebugPrinter.print( 'ActivityQueue.addActivity: activity appended' );
19356 ActivityQueue.prototype.process = function()
19358 var nSize = this.aCurrentActivityWaitingSet.length;
19360 for( var i = 0; i < nSize; ++i )
19362 nLag = Math.max( nLag,this.aCurrentActivityWaitingSet[i].calcTimeLag() );
19366 this.aTimer.adjustTimer( -nLag, true );
19369 while( this.aCurrentActivityWaitingSet.length != 0 )
19371 var aActivity = this.aCurrentActivityWaitingSet.shift();
19372 var bReinsert = false;
19374 bReinsert = aActivity.perform();
19378 this.aCurrentActivityReinsertSet.push( aActivity );
19382 this.aDequeuedActivitySet.push( aActivity );
19386 if( this.aCurrentActivityReinsertSet.length != 0 )
19388 // TODO: optimization, try to swap reference here
19389 this.aCurrentActivityWaitingSet = this.aCurrentActivityReinsertSet;
19390 this.aCurrentActivityReinsertSet = [];
19394 ActivityQueue.prototype.processDequeued = function()
19396 // notify all dequeued activities from last round
19397 var nSize = this.aDequeuedActivitySet.length;
19398 for( var i = 0; i < nSize; ++i )
19399 this.aDequeuedActivitySet[i].dequeued();
19401 this.aDequeuedActivitySet = [];
19404 ActivityQueue.prototype.isEmpty = function()
19406 return ( ( this.aCurrentActivityWaitingSet.length == 0 ) &&
19407 ( this.aCurrentActivityReinsertSet.length == 0 ) );
19410 ActivityQueue.prototype.clear = function()
19412 aActivityQueueDebugPrinter.print( 'ActivityQueue.clear invoked' );
19413 var nSize = this.aCurrentActivityWaitingSet.length;
19415 for( i = 0; i < nSize; ++i )
19416 this.aCurrentActivityWaitingSet[i].dequeued();
19417 this.aCurrentActivityWaitingSet = [];
19419 nSize = this.aCurrentActivityReinsertSet.length;
19420 for( i = 0; i < nSize; ++i )
19421 this.aCurrentActivityReinsertSet[i].dequeued();
19422 this.aCurrentActivityReinsertSet = [];
19425 ActivityQueue.prototype.endAll = function()
19427 aActivityQueueDebugPrinter.print( 'ActivityQueue.endAll invoked' );
19428 var nSize = this.aCurrentActivityWaitingSet.length;
19430 for( i = 0; i < nSize; ++i )
19431 this.aCurrentActivityWaitingSet[i].end();
19432 this.aCurrentActivityWaitingSet = [];
19434 nSize = this.aCurrentActivityReinsertSet.length;
19435 for( i = 0; i < nSize; ++i )
19436 this.aCurrentActivityReinsertSet[i].end();
19437 this.aCurrentActivityReinsertSet = [];
19440 ActivityQueue.prototype.getTimer = function()
19442 return this.aTimer;
19445 ActivityQueue.prototype.size = function()
19447 return ( this.aCurrentActivityWaitingSet.length +
19448 this.aCurrentActivityReinsertSet.length +
19449 this.aDequeuedActivitySet.length );
19455 function ElapsedTime( aTimeBase )
19457 this.aTimeBase = aTimeBase;
19458 this.nLastQueriedTime = 0.0;
19459 this.nStartTime = this.getCurrentTime();
19460 this.nFrozenTime = 0.0;
19461 this.bInPauseMode = false;
19462 this.bInHoldMode = false;
19466 ElapsedTime.prototype.getTimeBase = function()
19468 return this.aTimeBase;
19471 ElapsedTime.prototype.reset = function()
19473 this.nLastQueriedTime = 0.0;
19474 this.nStartTime = this.getCurrentTime();
19475 this.nFrozenTime = 0.0;
19476 this.bInPauseMode = false;
19477 this.bInHoldMode = false;
19480 ElapsedTime.prototype.getElapsedTime = function()
19482 this.nLastQueriedTime = this.getElapsedTimeImpl();
19483 return this.nLastQueriedTime;
19486 ElapsedTime.prototype.pauseTimer = function()
19488 this.nFrozenTime = this.getElapsedTimeImpl();
19489 this.bInPauseMode = true;
19492 ElapsedTime.prototype.continueTimer = function()
19494 this.bInPauseMode = false;
19496 // stop pausing, time runs again. Note that
19497 // getElapsedTimeImpl() honors hold mode, i.e. a
19498 // continueTimer() in hold mode will preserve the latter
19499 var nPauseDuration = this.getElapsedTimeImpl() - this.nFrozenTime;
19501 // adjust start time, such that subsequent getElapsedTime() calls
19502 // will virtually start from m_fFrozenTime.
19503 this.nStartTime += nPauseDuration;
19506 ElapsedTime.prototype.adjustTimer = function( nOffset, bLimitToLastQueriedTime )
19508 if( bLimitToLastQueriedTime == undefined )
19509 bLimitToLastQueriedTime = true;
19511 // to make getElapsedTime() become _larger_, have to reduce nStartTime.
19512 this.nStartTime -= nOffset;
19514 // also adjust frozen time, this method must _always_ affect the
19515 // value returned by getElapsedTime()!
19516 if( this.bInHoldMode || this.bInPauseMode )
19517 this.nFrozenTime += nOffset;
19520 ElapsedTime.prototype.holdTimer = function()
19522 // when called during hold mode (e.g. more than once per time
19523 // object), the original hold time will be maintained.
19524 this.nFrozenTime = this.getElapsedTimeImpl();
19525 this.bInHoldMode = true;
19528 ElapsedTime.prototype.releaseTimer = function()
19530 this.bInHoldMode = false;
19533 ElapsedTime.prototype.getSystemTime = function()
19535 return ( getCurrentSystemTime() / 1000.0 );
19538 ElapsedTime.prototype.getCurrentTime = function()
19541 if ( !this.aTimeBase )
19543 nCurrentTime = this.getSystemTime();
19547 nCurrentTime = this.aTimeBase.getElapsedTimeImpl();
19550 assert( ( typeof( nCurrentTime ) === typeof( 0 ) ) && isFinite( nCurrentTime ),
19551 'ElapsedTime.getCurrentTime: assertion failed: nCurrentTime == ' + nCurrentTime );
19554 return nCurrentTime;
19557 ElapsedTime.prototype.getElapsedTimeImpl = function()
19559 if( this.bInHoldMode || this.bInPauseMode )
19561 return this.nFrozenTime;
19564 var nCurTime = this.getCurrentTime();
19565 return ( nCurTime - this.nStartTime );
19573 * Several parts of the above code are the result of the porting,
19574 * started on August 2011, of the C++ code included in the source files
19575 * placed under the folder '/slideshow/source' and subfolders.
19576 * @source https://cgit.freedesktop.org/libreoffice/core/tree/slideshow/source
19580 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */