Revert naming back to lool to fix break with LibreOffice Online
[LibreOffice.git] / filter / source / svg / presentation_engine.js
blobe08689d4901d387010fe4c50ff5ebfa23c950719
1 /* -*- Mode: JS; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
4  *                      - Presentation Engine -                            *
5  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
7 /**
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)
12  *
13  *  NOTE: This file combines several works, under different
14  *  licenses. See the @licstart / @licend sections below.
15  */
17 /*! Hammer.JS - v2.0.7 - 2016-04-22
18  * http://hammerjs.github.io/
19  *
20  * Copyright (c) 2016 Jorik Tangelder;
21  * Licensed under the MIT license */
22 (function(window, document, exportName, undefined) {
23   'use strict';
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;
31 var abs = Math.abs;
32 var now = Date.now;
34 /**
35  * polyfill for IE11
36  */
37 if (!Math.trunc) {
38     Math.trunc = function (v) {
39         return v < 0 ? Math.ceil(v) : Math.floor(v);
40     };
43 /**
44  * set a timeout with a given scope
45  * @param {Function} fn
46  * @param {Number} timeout
47  * @param {Object} context
48  * @returns {number}
49  */
50 function setTimeoutContext(fn, timeout, context) {
51     return setTimeout(bindFn(fn, context), timeout);
54 /**
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
59  * @param {String} fn
60  * @param {Object} [context]
61  * @returns {Boolean}
62  */
63 function invokeArrayArg(arg, fn, context) {
64     if (Array.isArray(arg)) {
65         each(arg, context[fn], context);
66         return true;
67     }
68     return false;
71 /**
72  * walk objects and arrays
73  * @param {Object} obj
74  * @param {Function} iterator
75  * @param {Object} context
76  */
77 function each(obj, iterator, context) {
78     var i;
80     if (!obj) {
81         return;
82     }
84     if (obj.forEach) {
85         obj.forEach(iterator, context);
86     } else if (obj.length !== undefined) {
87         i = 0;
88         while (i < obj.length) {
89             iterator.call(context, obj[i], i, obj);
90             i++;
91         }
92     } else {
93         for (i in obj) {
94             obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
95         }
96     }
99 /**
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.
105  */
106 function deprecate(method, name, message) {
107     var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
108     return function() {
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);
115         if (log) {
116             log.call(window.console, deprecationMessage, stack);
117         }
118         return method.apply(this, arguments);
119     };
123  * extend object.
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
128  */
129 var assign;
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');
134         }
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];
143                     }
144                 }
145             }
146         }
147         return output;
148     };
149 } else {
150     assign = Object.assign;
154  * extend object.
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
160  */
161 var extend = deprecate(function extend(dest, src, merge) {
162     var keys = Object.keys(src);
163     var i = 0;
164     while (i < keys.length) {
165         if (!merge || (merge && dest[keys[i]] === undefined)) {
166             dest[keys[i]] = src[keys[i]];
167         }
168         i++;
169     }
170     return dest;
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
179  */
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]
189  */
190 function inherit(child, base, properties) {
191     var baseP = base.prototype,
192         childP;
194     childP = child.prototype = Object.create(baseP);
195     childP.constructor = child;
196     childP._super = baseP;
198     if (properties) {
199         assign(childP, properties);
200     }
204  * simple function bind
205  * @param {Function} fn
206  * @param {Object} context
207  * @returns {Function}
208  */
209 function bindFn(fn, context) {
210     return function boundFn() {
211         return fn.apply(context, arguments);
212     };
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]
220  * @returns {Boolean}
221  */
222 function boolOrFn(val, args) {
223     if (typeof val == TYPE_FUNCTION) {
224         return val.apply(args ? args[0] || undefined : undefined, args);
225     }
226     return val;
230  * use the val2 when val1 is undefined
231  * @param {*} val1
232  * @param {*} val2
233  * @returns {*}
234  */
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
244  */
245 function addEventListeners(target, types, handler) {
246     each(splitStr(types), function(type) {
247         target.addEventListener(type, handler, false);
248     });
252  * removeEventListener with multiple events at once
253  * @param {EventTarget} target
254  * @param {String} types
255  * @param {Function} handler
256  */
257 function removeEventListeners(target, types, handler) {
258     each(splitStr(types), function(type) {
259         target.removeEventListener(type, handler, false);
260     });
264  * find if a node is in the given parent
265  * @method hasParent
266  * @param {HTMLElement} node
267  * @param {HTMLElement} parent
268  * @return {Boolean} found
269  */
270 function hasParent(node, parent) {
271     while (node) {
272         if (node == parent) {
273             return true;
274         }
275         node = node.parentNode;
276     }
277     return false;
281  * small indexOf wrapper
282  * @param {String} str
283  * @param {String} find
284  * @returns {Boolean} found
285  */
286 function inStr(str, find) {
287     return str.indexOf(find) > -1;
291  * split string on whitespace
292  * @param {String} str
293  * @returns {Array} words
294  */
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
301  * @param {Array} src
302  * @param {String} find
303  * @param {String} [findByKey]
304  * @return {Boolean|Number} false when not found, or the index
305  */
306 function inArray(src, find, findByKey) {
307     if (src.indexOf && !findByKey) {
308         return src.indexOf(find);
309     } else {
310         var i = 0;
311         while (i < src.length) {
312             if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
313                 return i;
314             }
315             i++;
316         }
317         return -1;
318     }
322  * convert array-like objects to real arrays
323  * @param {Object} obj
324  * @returns {Array}
325  */
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}]
336  */
337 function uniqueArray(src, key, sort) {
338     var results = [];
339     var values = [];
340     var i = 0;
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]);
346         }
347         values[i] = val;
348         i++;
349     }
351     if (sort) {
352         if (!key) {
353             results = results.sort();
354         } else {
355             results = results.sort(function sortUniqueArray(a, b) {
356                 return a[key] > b[key];
357             });
358         }
359     }
361     return results;
365  * get the prefixed property
366  * @param {Object} obj
367  * @param {String} property
368  * @returns {String|Undefined} prefixed
369  */
370 function prefixed(obj, property) {
371     // tml: Have to check for obj being undefined
372     if (obj === undefined) {
373         return undefined;
374     }
376     var prefix, prop;
377     var camelProp = property[0].toUpperCase() + property.slice(1);
379     var i = 0;
380     while (i < VENDOR_PREFIXES.length) {
381         prefix = VENDOR_PREFIXES[i];
382         prop = (prefix) ? prefix + camelProp : property;
384         if (prop in obj) {
385             return prop;
386         }
387         i++;
388     }
389     return undefined;
393  * get a unique id
394  * @returns {number} uniqueId
395  */
396 var _uniqueId = 1;
397 function uniqueId() {
398     return _uniqueId++;
402  * get the window object of an element
403  * @param {HTMLElement} element
404  * @returns {DocumentView|Window}
405  */
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;
424 var INPUT_START = 1;
425 var INPUT_MOVE = 2;
426 var INPUT_END = 4;
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
446  * @returns {Input}
447  * @constructor
448  */
449 function Input(manager, callback) {
450     var self = this;
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])) {
460             self.handler(ev);
461         }
462     };
464     this.init();
468 Input.prototype = {
469     /**
470      * should handle the inputEvent data and trigger the callback
471      * @virtual
472      */
473     handler: function() { },
475     /**
476      * bind the events
477      */
478     init: 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);
482     },
484     /**
485      * unbind the events
486      */
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);
491     }
495  * create new input type manager
496  * called by the Manager constructor
497  * @param {Hammer} manager
498  * @returns {Input}
499  */
500 function createInputInstance(manager) {
501     var Type;
502     var inputClass = manager.options.inputClass;
504     if (inputClass) {
505         Type = inputClass;
506     } else if (!SUPPORT_TOUCH && SUPPORT_POINTER_EVENTS) {
507         Type = PointerEventInput;
508     } else if (SUPPORT_ONLY_TOUCH) {
509         Type = TouchInput;
510     } else if (!SUPPORT_TOUCH) {
511         Type = MouseInput;
512     } else {
513         Type = TouchMouseInput;
514     }
515     return new (Type)(manager, inputHandler);
519  * handle input events
520  * @param {Manager} manager
521  * @param {String} eventType
522  * @param {Object} input
523  */
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;
533     if (isFirst) {
534         manager.session = {};
535     }
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);
544     // emit secret event
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
555  */
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);
564     }
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;
571     }
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;
604     }
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
618         };
620         offset = session.offsetDelta = {
621             x: center.x,
622             y: center.y
623         };
624     }
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
634  */
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);
645         velocityX = v.x;
646         velocityY = v.y;
647         velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
648         direction = getDirection(deltaX, deltaY);
650         session.lastInterval = input;
651     } else {
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;
657     }
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
669  */
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
673     var pointers = [];
674     var i = 0;
675     while (i < input.pointers.length) {
676         pointers[i] = {
677             clientX: round(input.pointers[i].clientX),
678             clientY: round(input.pointers[i].clientY)
679         };
680         i++;
681     }
683     return {
684         timeStamp: now(),
685         pointers: pointers,
686         center: getCenter(pointers),
687         deltaX: input.deltaX,
688         deltaY: input.deltaY
689     };
693  * get the center of all the pointers
694  * @param {Array} pointers
695  * @return {Object} center contains `x` and `y` properties
696  */
697 function getCenter(pointers) {
698     var pointersLength = pointers.length;
700     // no need to loop when only one touch
701     if (pointersLength === 1) {
702         return {
703             x: round(pointers[0].clientX),
704             y: round(pointers[0].clientY)
705         };
706     }
708     var x = 0, y = 0, i = 0;
709     while (i < pointersLength) {
710         x += pointers[i].clientX;
711         y += pointers[i].clientY;
712         i++;
713     }
715     return {
716         x: round(x / pointersLength),
717         y: round(y / pointersLength)
718     };
722  * calculate the velocity between two points. unit is in px per ms.
723  * @param {Number} deltaTime
724  * @param {Number} x
725  * @param {Number} y
726  * @return {Object} velocity `x` and `y`
727  */
728 function getVelocity(deltaTime, x, y) {
729     return {
730         x: x / deltaTime || 0,
731         y: y / deltaTime || 0
732     };
736  * get the direction between two points
737  * @param {Number} x
738  * @param {Number} y
739  * @return {Number} direction
740  */
741 function getDirection(x, y) {
742     if (x === y) {
743         return DIRECTION_NONE;
744     }
746     if (abs(x) >= abs(y)) {
747         return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
748     }
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
758  */
759 function getDistance(p1, p2, props) {
760     if (!props) {
761         props = PROPS_XY;
762     }
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
771  * @param {Object} p1
772  * @param {Object} p2
773  * @param {Array} [props] containing x and y keys
774  * @return {Number} angle
775  */
776 function getAngle(p1, p2, props) {
777     if (!props) {
778         props = PROPS_XY;
779     }
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
790  */
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
801  */
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,
809     mouseup: INPUT_END
812 var MOUSE_ELEMENT_EVENTS = 'mousedown';
813 var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
816  * Mouse events input
817  * @constructor
818  * @extends Input
819  */
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, {
830     /**
831      * handle mouse events
832      * @param {Object} ev
833      */
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) {
840             this.pressed = true;
841         }
843         if (eventType & INPUT_MOVE && ev.which !== 1) {
844             eventType = INPUT_END;
845         }
847         // mouse must be down
848         if (!this.pressed) {
849             return;
850         }
852         if (eventType & INPUT_END) {
853             this.pressed = false;
854         }
856         this.callback(this.manager, eventType, {
857             pointers: [ev],
858             changedPointers: [ev],
859             pointerType: INPUT_TYPE_MOUSE,
860             srcEvent: ev
861         });
862     }
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 = {
875     2: INPUT_TYPE_TOUCH,
876     3: INPUT_TYPE_PEN,
877     4: INPUT_TYPE_MOUSE,
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
892  * @constructor
893  * @extends Input
894  */
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, {
905     /**
906      * handle mouse events
907      * @param {Object} ev
908      */
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) {
926                 store.push(ev);
927                 storeIndex = store.length - 1;
928             }
929         } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
930             removePointer = true;
931         }
933         // it not found, so the pointer hasn't been down (so it's probably a hover)
934         if (storeIndex < 0) {
935             return;
936         }
938         // update the event in the store
939         store[storeIndex] = ev;
941         this.callback(this.manager, eventType, {
942             pointers: store,
943             changedPointers: [ev],
944             pointerType: pointerType,
945             srcEvent: ev
946         });
948         if (removePointer) {
949             // remove from the store
950             store.splice(storeIndex, 1);
951         }
952     }
955 var SINGLE_TOUCH_INPUT_MAP = {
956     touchstart: INPUT_START,
957     touchmove: INPUT_MOVE,
958     touchend: INPUT_END,
959     touchcancel: INPUT_CANCEL
962 var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
963 var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
966  * Touch events input
967  * @constructor
968  * @extends Input
969  */
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) {
985             this.started = true;
986         }
988         if (!this.started) {
989             return;
990         }
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;
997         }
999         this.callback(this.manager, type, {
1000             pointers: touches[0],
1001             changedPointers: touches[1],
1002             pointerType: INPUT_TYPE_TOUCH,
1003             srcEvent: ev
1004         });
1005     }
1009  * @this {TouchInput}
1010  * @param {Object} ev
1011  * @param {Number} type flag
1012  * @returns {undefined|Array} [all, changed]
1013  */
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);
1020     }
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
1036  * @constructor
1037  * @extends Input
1038  */
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);
1051         if (!touches) {
1052             return;
1053         }
1055         this.callback(this.manager, type, {
1056             pointers: touches[0],
1057             changedPointers: touches[1],
1058             pointerType: INPUT_TYPE_TOUCH,
1059             srcEvent: ev
1060         });
1061     }
1065  * @this {TouchInput}
1066  * @param {Object} ev
1067  * @param {Number} type flag
1068  * @returns {undefined|Array} [all, changed]
1069  */
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];
1078     }
1080     var i,
1081         targetTouches,
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);
1089     });
1091     // collect touches
1092     if (type === INPUT_START) {
1093         i = 0;
1094         while (i < targetTouches.length) {
1095             targetIds[targetTouches[i].identifier] = true;
1096             i++;
1097         }
1098     }
1100     // filter changed touches to only contain touches that exist in the collected target ids
1101     i = 0;
1102     while (i < changedTouches.length) {
1103         if (targetIds[changedTouches[i].identifier]) {
1104             changedTargetTouches.push(changedTouches[i]);
1105         }
1107         // cleanup removed touches
1108         if (type & (INPUT_END | INPUT_CANCEL)) {
1109             delete targetIds[changedTouches[i].identifier];
1110         }
1111         i++;
1112     }
1114     if (!changedTargetTouches.length) {
1115         return;
1116     }
1118     return [
1119         // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
1120         uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
1121         changedTargetTouches
1122     ];
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.
1131  * @constructor
1132  * @extends Input
1133  */
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, {
1150     /**
1151      * handle mouse and touch events
1152      * @param {Hammer} manager
1153      * @param {String} inputEvent
1154      * @param {Object} inputData
1155      */
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) {
1162             return;
1163         }
1165         // when we're in a touch event, record touches to  de-dupe synthetic mouse event
1166         if (isTouch) {
1167             recordTouches.call(this, inputEvent, inputData);
1168         } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
1169             return;
1170         }
1172         this.callback(manager, inputEvent, inputData);
1173     },
1175     /**
1176      * remove the event listeners
1177      */
1178     destroy: function destroy() {
1179         this.touch.destroy();
1180         this.mouse.destroy();
1181     }
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);
1190     }
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);
1202             if (i > -1) {
1203                 lts.splice(i, 1);
1204             }
1205         };
1206         setTimeout(removeLastTouch, DEDUP_TIMEOUT);
1207     }
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) {
1216             return true;
1217         }
1218     }
1219     return false;
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();
1235  * Touch Action
1236  * sets the touchAction property or uses the js alternative
1237  * @param {Manager} manager
1238  * @param {String} value
1239  * @constructor
1240  */
1241 function TouchAction(manager, value) {
1242     this.manager = manager;
1243     this.set(value);
1246 TouchAction.prototype = {
1247     /**
1248      * set the touchAction value on the element or enable the polyfill
1249      * @param {String} value
1250      */
1251     set: function(value) {
1252         // find out the touch-action by the event handlers
1253         if (value == TOUCH_ACTION_COMPUTE) {
1254             value = this.compute();
1255         }
1257         if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
1258             this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
1259         }
1260         this.actions = value.toLowerCase().trim();
1261     },
1263     /**
1264      * just re-set the touchAction value
1265      */
1266     update: function() {
1267         this.set(this.manager.options.touchAction);
1268     },
1270     /**
1271      * compute the value for the touchAction property based on the recognizer's settings
1272      * @returns {String} value
1273      */
1274     compute: function() {
1275         var actions = [];
1276         each(this.manager.recognizers, function(recognizer) {
1277             if (boolOrFn(recognizer.options.enable, [recognizer])) {
1278                 actions = actions.concat(recognizer.getTouchAction());
1279             }
1280         });
1281         return cleanTouchActions(actions.join(' '));
1282     },
1284     /**
1285      * this method is called on each input cycle and provides the preventing of the browser behavior
1286      * @param {Object} input
1287      */
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();
1295             return;
1296         }
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];
1303         if (hasNone) {
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) {
1311                 return;
1312             }
1313         }
1315         if (hasPanX && hasPanY) {
1316             // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
1317             return;
1318         }
1320         if (hasNone ||
1321             (hasPanY && direction & DIRECTION_HORIZONTAL) ||
1322             (hasPanX && direction & DIRECTION_VERTICAL)) {
1323             return this.preventSrc(srcEvent);
1324         }
1325     },
1327     /**
1328      * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
1329      * @param {Object} srcEvent
1330      */
1331     preventSrc: function(srcEvent) {
1332         this.manager.session.prevented = true;
1333         srcEvent.preventDefault();
1334     }
1338  * when the touchActions are collected they are not a valid value, so we need to clean things up. *
1339  * @param {String} actions
1340  * @returns {*}
1341  */
1342 function cleanTouchActions(actions) {
1343     // none
1344     if (inStr(actions, TOUCH_ACTION_NONE)) {
1345         return TOUCH_ACTION_NONE;
1346     }
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;
1357     }
1359     // pan-x OR pan-y
1360     if (hasPanX || hasPanY) {
1361         return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
1362     }
1364     // manipulation
1365     if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
1366         return TOUCH_ACTION_MANIPULATION;
1367     }
1369     return TOUCH_ACTION_AUTO;
1372 function getTouchActionProps() {
1373     if (!NATIVE_TOUCH_ACTION) {
1374         return false;
1375     }
1376     var touchMap = {};
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;
1383     });
1384     return touchMap;
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.
1399  *               Possible
1400  *                  |
1401  *            +-----+---------------+
1402  *            |                     |
1403  *      +-----+-----+               |
1404  *      |           |               |
1405  *   Failed      Cancelled          |
1406  *                          +-------+------+
1407  *                          |              |
1408  *                      Recognized       Began
1409  *                                         |
1410  *                                      Changed
1411  *                                         |
1412  *                                  Ended/Recognized
1413  */
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;
1423  * Recognizer
1424  * Every recognizer needs to extend from this class.
1425  * @constructor
1426  * @param {Object} options
1427  */
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 = {
1445     /**
1446      * @virtual
1447      * @type {Object}
1448      */
1449     defaults: {},
1451     /**
1452      * set options
1453      * @param {Object} options
1454      * @return {Recognizer}
1455      */
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();
1461         return this;
1462     },
1464     /**
1465      * recognize simultaneous with another recognizer.
1466      * @param {Recognizer} otherRecognizer
1467      * @returns {Recognizer} this
1468      */
1469     recognizeWith: function(otherRecognizer) {
1470         if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
1471             return this;
1472         }
1474         var simultaneous = this.simultaneous;
1475         otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1476         if (!simultaneous[otherRecognizer.id]) {
1477             simultaneous[otherRecognizer.id] = otherRecognizer;
1478             otherRecognizer.recognizeWith(this);
1479         }
1480         return this;
1481     },
1483     /**
1484      * drop the simultaneous link. It doesn't remove the link on the other recognizer.
1485      * @param {Recognizer} otherRecognizer
1486      * @returns {Recognizer} this
1487      */
1488     dropRecognizeWith: function(otherRecognizer) {
1489         if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
1490             return this;
1491         }
1493         otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1494         delete this.simultaneous[otherRecognizer.id];
1495         return this;
1496     },
1498     /**
1499      * recognizer can only run when another is failing
1500      * @param {Recognizer} otherRecognizer
1501      * @returns {Recognizer} this
1502      */
1503     requireFailure: function(otherRecognizer) {
1504         if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
1505             return this;
1506         }
1508         var requireFail = this.requireFail;
1509         otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1510         if (inArray(requireFail, otherRecognizer) === -1) {
1511             requireFail.push(otherRecognizer);
1512             otherRecognizer.requireFailure(this);
1513         }
1514         return this;
1515     },
1517     /**
1518      * drop the requireFailure link. It does not remove the link on the other recognizer.
1519      * @param {Recognizer} otherRecognizer
1520      * @returns {Recognizer} this
1521      */
1522     dropRequireFailure: function(otherRecognizer) {
1523         if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
1524             return this;
1525         }
1527         otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1528         var index = inArray(this.requireFail, otherRecognizer);
1529         if (index > -1) {
1530             this.requireFail.splice(index, 1);
1531         }
1532         return this;
1533     },
1535     /**
1536      * has require failures boolean
1537      * @returns {boolean}
1538      */
1539     hasRequireFailures: function() {
1540         return this.requireFail.length > 0;
1541     },
1543     /**
1544      * if the recognizer can recognize simultaneous with another recognizer
1545      * @param {Recognizer} otherRecognizer
1546      * @returns {Boolean}
1547      */
1548     canRecognizeWith: function(otherRecognizer) {
1549         return !!this.simultaneous[otherRecognizer.id];
1550     },
1552     /**
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
1556      */
1557     emit: function(input) {
1558         var self = this;
1559         var state = this.state;
1561         function emit(event) {
1562             self.manager.emit(event, input);
1563         }
1565         // 'panstart' and 'panmove'
1566         if (state < STATE_ENDED) {
1567             emit(self.options.event + stateStr(state));
1568         }
1570         emit(self.options.event); // simple 'eventName' events
1572         if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
1573             emit(input.additionalEvent);
1574         }
1576         // panend and pancancel
1577         if (state >= STATE_ENDED) {
1578             emit(self.options.event + stateStr(state));
1579         }
1580     },
1582     /**
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
1587      */
1588     tryEmit: function(input) {
1589         if (this.canEmit()) {
1590             return this.emit(input);
1591         }
1592         // it's failing anyway
1593         this.state = STATE_FAILED;
1594     },
1596     /**
1597      * can we emit?
1598      * @returns {boolean}
1599      */
1600     canEmit: function() {
1601         var i = 0;
1602         while (i < this.requireFail.length) {
1603             if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
1604                 return false;
1605             }
1606             i++;
1607         }
1608         return true;
1609     },
1611     /**
1612      * update the recognizer
1613      * @param {Object} inputData
1614      */
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])) {
1622             this.reset();
1623             this.state = STATE_FAILED;
1624             return;
1625         }
1627         // reset when we've reached the end
1628         if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
1629             this.state = STATE_POSSIBLE;
1630         }
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);
1638         }
1639     },
1641     /**
1642      * return the state of the recognizer
1643      * the actual recognizing happens in this method
1644      * @virtual
1645      * @param {Object} inputData
1646      * @returns {Const} STATE
1647      */
1648     process: function(inputData) { }, // jshint ignore:line
1650     /**
1651      * return the preferred touch-action
1652      * @virtual
1653      * @returns {Array}
1654      */
1655     getTouchAction: function() { },
1657     /**
1658      * called when the gesture isn't allowed to recognize
1659      * like when another is being recognized or it is disabled
1660      * @virtual
1661      */
1662     reset: function() { }
1666  * get a usable string, used as event postfix
1667  * @param {Const} state
1668  * @returns {String} state
1669  */
1670 function stateStr(state) {
1671     if (state & STATE_CANCELLED) {
1672         return 'cancel';
1673     } else if (state & STATE_ENDED) {
1674         return 'end';
1675     } else if (state & STATE_CHANGED) {
1676         return 'move';
1677     } else if (state & STATE_BEGAN) {
1678         return 'start';
1679     }
1680     return '';
1684  * direction cons to string
1685  * @param {Const} direction
1686  * @returns {String}
1687  */
1688 function directionStr(direction) {
1689     if (direction == DIRECTION_DOWN) {
1690         return 'down';
1691     } else if (direction == DIRECTION_UP) {
1692         return 'up';
1693     } else if (direction == DIRECTION_LEFT) {
1694         return 'left';
1695     } else if (direction == DIRECTION_RIGHT) {
1696         return 'right';
1697     }
1698     return '';
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}
1706  */
1707 function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
1708     var manager = recognizer.manager;
1709     if (manager) {
1710         return manager.get(otherRecognizer);
1711     }
1712     return otherRecognizer;
1716  * This recognizer is just used as a base for the simple attribute recognizers.
1717  * @constructor
1718  * @extends Recognizer
1719  */
1720 function AttrRecognizer() {
1721     Recognizer.apply(this, arguments);
1724 inherit(AttrRecognizer, Recognizer, {
1725     /**
1726      * @namespace
1727      * @memberof AttrRecognizer
1728      */
1729     defaults: {
1730         /**
1731          * @type {Number}
1732          * @default 1
1733          */
1734         pointers: 1
1735     },
1737     /**
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
1742      */
1743     attrTest: function(input) {
1744         var optionPointers = this.options.pointers;
1745         return optionPointers === 0 || input.pointers.length === optionPointers;
1746     },
1748     /**
1749      * Process the input and return the state for the recognizer
1750      * @memberof AttrRecognizer
1751      * @param {Object} input
1752      * @returns {*} State
1753      */
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)) {
1768                 return STATE_BEGAN;
1769             }
1770             return state | STATE_CHANGED;
1771         }
1772         return STATE_FAILED;
1773     }
1777  * Pan
1778  * Recognized when the pointer is down and moved in the allowed direction.
1779  * @constructor
1780  * @extends AttrRecognizer
1781  */
1782 function PanRecognizer() {
1783     AttrRecognizer.apply(this, arguments);
1785     this.pX = null;
1786     this.pY = null;
1789 inherit(PanRecognizer, AttrRecognizer, {
1790     /**
1791      * @namespace
1792      * @memberof PanRecognizer
1793      */
1794     defaults: {
1795         event: 'pan',
1796         threshold: 10,
1797         pointers: 1,
1798         direction: DIRECTION_ALL
1799     },
1801     getTouchAction: function() {
1802         var direction = this.options.direction;
1803         var actions = [];
1804         if (direction & DIRECTION_HORIZONTAL) {
1805             actions.push(TOUCH_ACTION_PAN_Y);
1806         }
1807         if (direction & DIRECTION_VERTICAL) {
1808             actions.push(TOUCH_ACTION_PAN_X);
1809         }
1810         return actions;
1811     },
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;
1821         // lock to axis?
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);
1827             } else {
1828                 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
1829                 hasMoved = y != this.pY;
1830                 distance = Math.abs(input.deltaY);
1831             }
1832         }
1833         input.direction = direction;
1834         return hasMoved && distance > options.threshold && direction & options.direction;
1835     },
1837     attrTest: function(input) {
1838         return AttrRecognizer.prototype.attrTest.call(this, input) &&
1839             (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
1840     },
1842     emit: function(input) {
1844         this.pX = input.deltaX;
1845         this.pY = input.deltaY;
1847         var direction = directionStr(input.direction);
1849         if (direction) {
1850             input.additionalEvent = this.options.event + direction;
1851         }
1852         this._super.emit.call(this, input);
1853     }
1857  * Pinch
1858  * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
1859  * @constructor
1860  * @extends AttrRecognizer
1861  */
1862 function PinchRecognizer() {
1863     AttrRecognizer.apply(this, arguments);
1866 inherit(PinchRecognizer, AttrRecognizer, {
1867     /**
1868      * @namespace
1869      * @memberof PinchRecognizer
1870      */
1871     defaults: {
1872         event: 'pinch',
1873         threshold: 0,
1874         pointers: 2
1875     },
1877     getTouchAction: function() {
1878         return [TOUCH_ACTION_NONE];
1879     },
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);
1884     },
1886     emit: function(input) {
1887         if (input.scale !== 1) {
1888             var inOut = input.scale < 1 ? 'in' : 'out';
1889             input.additionalEvent = this.options.event + inOut;
1890         }
1891         this._super.emit.call(this, input);
1892     }
1896  * Press
1897  * Recognized when the pointer is down for x ms without any movement.
1898  * @constructor
1899  * @extends Recognizer
1900  */
1901 function PressRecognizer() {
1902     Recognizer.apply(this, arguments);
1904     this._timer = null;
1905     this._input = null;
1908 inherit(PressRecognizer, Recognizer, {
1909     /**
1910      * @namespace
1911      * @memberof PressRecognizer
1912      */
1913     defaults: {
1914         event: 'press',
1915         pointers: 1,
1916         time: 251, // minimal time of the pointer to be pressed
1917         threshold: 9 // a minimal movement is ok, but keep it low
1918     },
1920     getTouchAction: function() {
1921         return [TOUCH_ACTION_AUTO];
1922     },
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)) {
1935             this.reset();
1936         } else if (input.eventType & INPUT_START) {
1937             this.reset();
1938             this._timer = setTimeoutContext(function() {
1939                 this.state = STATE_RECOGNIZED;
1940                 this.tryEmit();
1941             }, options.time, this);
1942         } else if (input.eventType & INPUT_END) {
1943             return STATE_RECOGNIZED;
1944         }
1945         return STATE_FAILED;
1946     },
1948     reset: function() {
1949         clearTimeout(this._timer);
1950     },
1952     emit: function(input) {
1953         if (this.state !== STATE_RECOGNIZED) {
1954             return;
1955         }
1957         if (input && (input.eventType & INPUT_END)) {
1958             this.manager.emit(this.options.event + 'up', input);
1959         } else {
1960             this._input.timeStamp = now();
1961             this.manager.emit(this.options.event, this._input);
1962         }
1963     }
1967  * Rotate
1968  * Recognized when two or more pointer are moving in a circular motion.
1969  * @constructor
1970  * @extends AttrRecognizer
1971  */
1972 function RotateRecognizer() {
1973     AttrRecognizer.apply(this, arguments);
1976 inherit(RotateRecognizer, AttrRecognizer, {
1977     /**
1978      * @namespace
1979      * @memberof RotateRecognizer
1980      */
1981     defaults: {
1982         event: 'rotate',
1983         threshold: 0,
1984         pointers: 2
1985     },
1987     getTouchAction: function() {
1988         return [TOUCH_ACTION_NONE];
1989     },
1991     attrTest: function(input) {
1992         return this._super.attrTest.call(this, input) &&
1993             (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
1994     }
1998  * Swipe
1999  * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
2000  * @constructor
2001  * @extends AttrRecognizer
2002  */
2003 function SwipeRecognizer() {
2004     AttrRecognizer.apply(this, arguments);
2007 inherit(SwipeRecognizer, AttrRecognizer, {
2008     /**
2009      * @namespace
2010      * @memberof SwipeRecognizer
2011      */
2012     defaults: {
2013         event: 'swipe',
2014         threshold: 10,
2015         velocity: 0.3,
2016         direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
2017         pointers: 1
2018     },
2020     getTouchAction: function() {
2021         return PanRecognizer.prototype.getTouchAction.call(this);
2022     },
2024     attrTest: function(input) {
2025         var direction = this.options.direction;
2026         var velocity;
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;
2034         }
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;
2041     },
2043     emit: function(input) {
2044         var direction = directionStr(input.offsetDirection);
2045         if (direction) {
2046             this.manager.emit(this.options.event + direction, input);
2047         }
2049         this.manager.emit(this.options.event, input);
2050     }
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
2056  * a single tap.
2058  * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
2059  * multi-taps being recognized.
2060  * @constructor
2061  * @extends Recognizer
2062  */
2063 function TapRecognizer() {
2064     Recognizer.apply(this, arguments);
2066     // previous time and center,
2067     // used for tap counting
2068     this.pTime = false;
2069     this.pCenter = false;
2071     this._timer = null;
2072     this._input = null;
2073     this.count = 0;
2076 inherit(TapRecognizer, Recognizer, {
2077     /**
2078      * @namespace
2079      * @memberof PinchRecognizer
2080      */
2081     defaults: {
2082         event: 'tap',
2083         pointers: 1,
2084         taps: 1,
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
2089     },
2091     getTouchAction: function() {
2092         return [TOUCH_ACTION_MANIPULATION];
2093     },
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;
2102         this.reset();
2104         if ((input.eventType & INPUT_START) && (this.count === 0)) {
2105             return this.failTimeout();
2106         }
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();
2113             }
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) {
2122                 this.count = 1;
2123             } else {
2124                 this.count += 1;
2125             }
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;
2137                 } else {
2138                     this._timer = setTimeoutContext(function() {
2139                         this.state = STATE_RECOGNIZED;
2140                         this.tryEmit();
2141                     }, options.interval, this);
2142                     return STATE_BEGAN;
2143                 }
2144             }
2145         }
2146         return STATE_FAILED;
2147     },
2149     failTimeout: function() {
2150         this._timer = setTimeoutContext(function() {
2151             this.state = STATE_FAILED;
2152         }, this.options.interval, this);
2153         return STATE_FAILED;
2154     },
2156     reset: function() {
2157         clearTimeout(this._timer);
2158     },
2160     emit: function() {
2161         if (this.state == STATE_RECOGNIZED) {
2162             this._input.tapCount = this.count;
2163             this.manager.emit(this.options.event, this._input);
2164         }
2165     }
2169  * Simple way to create a manager with a default set of recognizers.
2170  * @param {HTMLElement} element
2171  * @param {Object} [options]
2172  * @constructor
2173  */
2174 function Hammer(element, options) {
2175     options = options || {};
2176     options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);
2177     return new Manager(element, options);
2181  * @const {string}
2182  */
2183 Hammer.VERSION = '2.0.7';
2186  * default settings
2187  * @namespace
2188  */
2189 Hammer.defaults = {
2190     /**
2191      * set if DOM events are being triggered.
2192      * But this is slower and unused by simple implementations, so disabled by default.
2193      * @type {Boolean}
2194      * @default false
2195      */
2196     domEvents: false,
2198     /**
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.
2201      * @type {String}
2202      * @default compute
2203      */
2204     touchAction: TOUCH_ACTION_COMPUTE,
2206     /**
2207      * @type {Boolean}
2208      * @default true
2209      */
2210     enable: true,
2212     /**
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}
2217      * @default null
2218      */
2219     inputTarget: null,
2221     /**
2222      * force an input class
2223      * @type {Null|Function}
2224      * @default null
2225      */
2226     inputClass: null,
2228     /**
2229      * Default recognizer setup when calling `Hammer()`
2230      * When creating a new Manager these will be skipped.
2231      * @type {Array}
2232      */
2233     preset: [
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']],
2239         [TapRecognizer],
2240         [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
2241         [PressRecognizer]
2242     ],
2244     /**
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.
2247      * @namespace
2248      */
2249     cssProps: {
2250         /**
2251          * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
2252          * @type {String}
2253          * @default 'none'
2254          */
2255         userSelect: 'none',
2257         /**
2258          * Disable the Windows Phone grippers when pressing an element.
2259          * @type {String}
2260          * @default 'none'
2261          */
2262         touchSelect: 'none',
2264         /**
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.
2268          * @type {String}
2269          * @default 'none'
2270          */
2271         touchCallout: 'none',
2273         /**
2274          * Specifies whether zooming is enabled. Used by IE10>
2275          * @type {String}
2276          * @default 'none'
2277          */
2278         contentZooming: 'none',
2280         /**
2281          * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
2282          * @type {String}
2283          * @default 'none'
2284          */
2285         userDrag: 'none',
2287         /**
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.
2290          * @type {String}
2291          * @default 'rgba(0,0,0,0)'
2292          */
2293         tapHighlightColor: 'rgba(0,0,0,0)'
2294     }
2297 var STOP = 1;
2298 var FORCED_STOP = 2;
2301  * Manager
2302  * @param {HTMLElement} element
2303  * @param {Object} [options]
2304  * @constructor
2305  */
2306 function Manager(element, options) {
2307     this.options = assign({}, Hammer.defaults, options || {});
2309     this.options.inputTarget = this.options.inputTarget || element;
2311     this.handlers = {};
2312     this.session = {};
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]);
2326     }, this);
2329 Manager.prototype = {
2330     /**
2331      * set options
2332      * @param {Object} options
2333      * @returns {Manager}
2334      */
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();
2341         }
2342         if (options.inputTarget) {
2343             // Clean up existing event listeners and reinitialize
2344             this.input.destroy();
2345             this.input.target = options.inputTarget;
2346             this.input.init();
2347         }
2348         return this;
2349     },
2351     /**
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]
2356      */
2357     stop: function(force) {
2358         this.session.stopped = force ? FORCED_STOP : STOP;
2359     },
2361     /**
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
2366      */
2367     recognize: function(inputData) {
2368         var session = this.session;
2369         if (session.stopped) {
2370             return;
2371         }
2373         // run the touch-action polyfill
2374         this.touchAction.preventDefaults(inputData);
2376         var recognizer;
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;
2388         }
2390         var i = 0;
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);
2404             } else {
2405                 recognizer.reset();
2406             }
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;
2412             }
2413             i++;
2414         }
2415     },
2417     /**
2418      * get a recognizer by its event name.
2419      * @param {Recognizer|String} recognizer
2420      * @returns {Recognizer|Null}
2421      */
2422     get: function(recognizer) {
2423         if (recognizer instanceof Recognizer) {
2424             return recognizer;
2425         }
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];
2431             }
2432         }
2433         return null;
2434     },
2436     /**
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}
2441      */
2442     add: function(recognizer) {
2443         if (invokeArrayArg(recognizer, 'add', this)) {
2444             return this;
2445         }
2447         // remove existing
2448         var existing = this.get(recognizer.options.event);
2449         if (existing) {
2450             this.remove(existing);
2451         }
2453         this.recognizers.push(recognizer);
2454         recognizer.manager = this;
2456         this.touchAction.update();
2457         return recognizer;
2458     },
2460     /**
2461      * remove a recognizer by name or instance
2462      * @param {Recognizer|String} recognizer
2463      * @returns {Manager}
2464      */
2465     remove: function(recognizer) {
2466         if (invokeArrayArg(recognizer, 'remove', this)) {
2467             return this;
2468         }
2470         recognizer = this.get(recognizer);
2472         // let's make sure this recognizer exists
2473         if (recognizer) {
2474             var recognizers = this.recognizers;
2475             var index = inArray(recognizers, recognizer);
2477             if (index !== -1) {
2478                 recognizers.splice(index, 1);
2479                 this.touchAction.update();
2480             }
2481         }
2483         return this;
2484     },
2486     /**
2487      * bind event
2488      * @param {String} events
2489      * @param {Function} handler
2490      * @returns {EventEmitter} this
2491      */
2492     on: function(events, handler) {
2493         if (events === undefined) {
2494             return;
2495         }
2496         if (handler === undefined) {
2497             return;
2498         }
2500         var handlers = this.handlers;
2501         each(splitStr(events), function(event) {
2502             handlers[event] = handlers[event] || [];
2503             handlers[event].push(handler);
2504         });
2505         return this;
2506     },
2508     /**
2509      * unbind event, leave emit blank to remove all handlers
2510      * @param {String} events
2511      * @param {Function} [handler]
2512      * @returns {EventEmitter} this
2513      */
2514     off: function(events, handler) {
2515         if (events === undefined) {
2516             return;
2517         }
2519         var handlers = this.handlers;
2520         each(splitStr(events), function(event) {
2521             if (!handler) {
2522                 delete handlers[event];
2523             } else {
2524                 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
2525             }
2526         });
2527         return this;
2528     },
2530     /**
2531      * emit event to the listeners
2532      * @param {String} event
2533      * @param {Object} data
2534      */
2535     emit: function(event, data) {
2536         // we also want to trigger dom events
2537         if (this.options.domEvents) {
2538             triggerDomEvent(event, data);
2539         }
2541         // no handlers, so skip it all
2542         var handlers = this.handlers[event] && this.handlers[event].slice();
2543         if (!handlers || !handlers.length) {
2544             return;
2545         }
2547         data.type = event;
2548         data.preventDefault = function() {
2549             data.srcEvent.preventDefault();
2550         };
2552         var i = 0;
2553         while (i < handlers.length) {
2554             handlers[i](data);
2555             i++;
2556         }
2557     },
2559     /**
2560      * destroy the manager and unbinds all events
2561      * it doesn't unbind dom events, that is the user own responsibility
2562      */
2563     destroy: function() {
2564         this.element && toggleCssProps(this, false);
2566         this.handlers = {};
2567         this.session = {};
2568         this.input.destroy();
2569         this.element = null;
2570     }
2574  * add/remove the css properties as defined in manager.options.cssProps
2575  * @param {Manager} manager
2576  * @param {Boolean} add
2577  */
2578 function toggleCssProps(manager, add) {
2579     var element = manager.element;
2580     if (!element.style) {
2581         return;
2582     }
2583     var prop;
2584     each(manager.options.cssProps, function(value, name) {
2585         prop = prefixed(element.style, name);
2586         if (add) {
2587             manager.oldCssProps[prop] = element.style[prop];
2588             element.style[prop] = value;
2589         } else {
2590             element.style[prop] = manager.oldCssProps[prop] || '';
2591         }
2592     });
2593     if (!add) {
2594         manager.oldCssProps = {};
2595     }
2599  * trigger dom event
2600  * @param {String} event
2601  * @param {Object} data
2602  */
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);
2610 assign(Hammer, {
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,
2633     Manager: Manager,
2634     Input: Input,
2635     TouchAction: TouchAction,
2637     TouchInput: TouchInput,
2638     MouseInput: MouseInput,
2639     PointerEventInput: PointerEventInput,
2640     TouchMouseInput: TouchMouseInput,
2641     SingleTouchInput: SingleTouchInput,
2643     Recognizer: Recognizer,
2644     AttrRecognizer: AttrRecognizer,
2645     Tap: TapRecognizer,
2646     Pan: PanRecognizer,
2647     Swipe: SwipeRecognizer,
2648     Pinch: PinchRecognizer,
2649     Rotate: RotateRecognizer,
2650     Press: PressRecognizer,
2652     on: addEventListeners,
2653     off: removeEventListeners,
2654     each: each,
2655     merge: merge,
2656     extend: extend,
2657     assign: assign,
2658     inherit: inherit,
2659     bindFn: bindFn,
2660     prefixed: prefixed
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) {
2669     define(function() {
2670         return Hammer;
2671     });
2672 } else if (typeof module != 'undefined' && module.exports) {
2673     module.exports = Hammer;
2674 } else {
2675     window[exportName] = Hammer;
2678 })(window, document, 'Hammer');
2680 /*****
2681  * @licstart
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.
2685  */
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/.
2710 /*****
2711  *  You can find the complete source code of the JessyInk project at:
2712  *  @source http://code.google.com/p/jessyink/
2713  */
2715 /*****
2716  * @licend
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.
2720  */
2724 /*****
2725  * @jessyinkstart
2727  *  The following code is a derivative work of some parts of the JessyInk
2728  *  project.
2729  *  @source http://code.google.com/p/jessyink/
2730  */
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'
2740  */
2741 function getElementsByProperty( node, name )
2743     var elements = [];
2745     if( node.getAttribute( name ) )
2746         elements.push( node );
2748     for( var counter = 0; counter < node.childNodes.length; ++counter )
2749     {
2750         if( node.childNodes[counter].nodeType == 1 )
2751         {
2752             var subElements = getElementsByProperty( node.childNodes[counter], name );
2753             elements = elements.concat( subElements );
2754         }
2755     }
2756     return elements;
2759 /** Event handler for key press.
2761  *  @param aEvt the event
2762  */
2763 function onKeyDown( aEvt )
2765     if ( !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) {
2774         switch (aEvt.key) {
2775         case 'UIKeyInputLeftArrow':
2776             code = LEFT_KEY;
2777             break;
2778         case 'UIKeyInputUpArrow':
2779             code = UP_KEY;
2780             break;
2781         case 'UIKeyInputRightArrow':
2782             code = RIGHT_KEY;
2783             break;
2784         case 'UIKeyInputDownArrow':
2785             code = DOWN_KEY;
2786             break;
2787         }
2789         // console.log('     now: ' + code);
2790     }
2792     if( !processingEffect && keyCodeDictionary[currentMode] && keyCodeDictionary[currentMode][code] )
2793     {
2794         return keyCodeDictionary[currentMode][code]();
2795     }
2796     else
2797     {
2798         document.onkeypress = onKeyPress;
2799         return null;
2800     }
2802 //Set event handler for key down.
2803 document.onkeydown = onKeyDown;
2805 /** Event handler for key press.
2807  *  @param aEvt the event
2808  */
2809 function onKeyPress( aEvt )
2811     document.onkeypress = null;
2813     if ( !aEvt )
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]();
2821     return null;
2824 /** Function to supply the default key code dictionary.
2826  *  @returns Object default key code dictionary
2827  */
2828 function getDefaultKeyCodeDictionary()
2830     var keyCodeDict = {};
2832     keyCodeDict[SLIDE_MODE] = {};
2833     keyCodeDict[INDEX_MODE] = {};
2835     // slide 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(); };
2860     // index mode
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(); };
2884     return keyCodeDict;
2887 /** Function to supply the default char code dictionary.
2889  *  @returns Object char code dictionary
2890  */
2891 function getDefaultCharCodeDictionary()
2893     var charCodeDict = {};
2895     charCodeDict[SLIDE_MODE] = {};
2896     charCodeDict[INDEX_MODE] = {};
2898     // slide mode
2899     charCodeDict[SLIDE_MODE]['i']
2900         = function () { return toggleSlideIndex(); };
2902     // index mode
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 )
2920     if (!aEvt)
2921         aEvt = window.event;
2923     var nOffset = 0;
2925     if( aEvt.button == 0 )
2926         nOffset = 1;
2927     else if( aEvt.button == 2 )
2928         nOffset = -1;
2930     if( 0 != nOffset )
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
2942  */
2943 function slideOnMouseWheel(aEvt)
2945     var delta = 0;
2947     if (!aEvt)
2948         aEvt = window.event;
2950     if (aEvt.wheelDelta)
2951     { // IE Opera
2952         delta = aEvt.wheelDelta/120;
2953     }
2954     else if (aEvt.detail)
2955     { // MOZ
2956         delta = -aEvt.detail/3;
2957     }
2959     if (delta > 0)
2960         skipEffects(-1);
2961     else if (delta < 0)
2962         skipEffects(1);
2964     if (aEvt.preventDefault)
2965         aEvt.preventDefault();
2967     aEvt.returnValue = false;
2970 //Mozilla
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
2977 window.onmousewheel
2978     = function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_WHEEL ); };
2980 /** Function to handle all mouse events.
2982  *  @param  aEvt    event
2983  *  @param  anAction  type of event (e.g. mouse up, mouse wheel)
2984  */
2985 function mouseHandlerDispatch( aEvt, anAction )
2987     if( !aEvt )
2988         aEvt = window.event;
2990     var retVal = true;
2992     if ( mouseHandlerDictionary[currentMode] && mouseHandlerDictionary[currentMode][anAction] )
2993     {
2994         var subRetVal = mouseHandlerDictionary[currentMode][anAction]( aEvt );
2996         if( subRetVal != null && subRetVal != undefined )
2997             retVal = subRetVal;
2998     }
3000     if( aEvt.preventDefault && !retVal )
3001         aEvt.preventDefault();
3003     aEvt.returnValue = retVal;
3005     return retVal;
3008 //Set mouse event handler.
3009 document.onmouseup = function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_UP ); };
3012 /** mouseClickHelper
3014  * @return {Object}
3015  *   a mouse click handler
3016  */
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 )
3028     {
3029         var aWindowObject = document.defaultView;
3030         if( aWindowObject )
3031         {
3032             var aTextSelection = aWindowObject.getSelection();
3033             var sSelectedText =  aTextSelection.toString();
3034             if( sSelectedText )
3035             {
3036                 DBGLOG( 'text selection: ' + sSelectedText );
3037                 if( sLastSelectedText !== sSelectedText )
3038                 {
3039                     bTextHasBeenSelected = true;
3040                     sLastSelectedText = sSelectedText;
3041                 }
3042                 else
3043                 {
3044                     bTextHasBeenSelected = false;
3045                 }
3046                 return null;
3047             }
3048             else if( bTextHasBeenSelected )
3049             {
3050                 bTextHasBeenSelected = false;
3051                 sLastSelectedText = '';
3052                 return null;
3053             }
3054         }
3055         else
3056         {
3057             log( 'error: HyperlinkElement.handleClick: invalid window object.' );
3058         }
3059     }
3061     var aSlideAnimationsHandler = theMetaDoc.aMetaSlideSet[nCurSlide].aSlideAnimationsHandler;
3062     if( aSlideAnimationsHandler )
3063     {
3064         var aCurrentEventMultiplexer = aSlideAnimationsHandler.aEventMultiplexer;
3065         if( aCurrentEventMultiplexer )
3066         {
3067             if( aCurrentEventMultiplexer.hasRegisteredMouseClickHandlers() )
3068             {
3069                 return aCurrentEventMultiplexer.notifyMouseClick( aEvt );
3070             }
3071         }
3072     }
3073     return slideOnMouseUp( aEvt );
3077 /** Function to supply the default mouse handler dictionary.
3079  *  @returns Object default mouse handler dictionary
3080  */
3081 function getDefaultMouseHandlerDictionary()
3083     var mouseHandlerDict = {};
3085     mouseHandlerDict[SLIDE_MODE] = {};
3086     mouseHandlerDict[INDEX_MODE] = {};
3088     // slide mode
3089     mouseHandlerDict[SLIDE_MODE][MOUSE_UP]
3090         = mouseClickHelper;
3092     mouseHandlerDict[SLIDE_MODE][MOUSE_WHEEL]
3093         = function( aEvt ) { return slideOnMouseWheel( aEvt ); };
3095     // index mode
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.
3112  *  INDEX_OFFSET = -1
3113  *  indexSetPageSlide(activeSlide);
3114  */
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;
3124     if( offset < 0 )
3125         offset = 0;
3127     //if different from kept offset, then record and change the page
3128     if( offset != INDEX_OFFSET )
3129     {
3130         INDEX_OFFSET = offset;
3131         displayIndex( INDEX_OFFSET );
3132     }
3134     //set the selected thumbnail and the current slide
3135     theSlideIndexPage.setSelection( nSelectedThumbnailIndex );
3139 /*****
3140  * @jessyinkend
3142  *  The above code is a derivative work of some parts of the JessyInk project.
3143  *  @source http://code.google.com/p/jessyink/
3144  */
3150 /*****
3151  * @licstart
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.
3155  */
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  ****************************************************************************/
3190 /*****
3191  * @licend
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.
3195  */
3199 /*****
3200  * @dojostart
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
3204  */
3206 function has( name )
3208     return has.cache[name];
3211 has.cache = {};
3213 has.add = function( name, test )
3215     has.cache[name] = test;
3218 function configureDetectionTools()
3220     if( !navigator )
3221     {
3222         log( 'error: configureDetectionTools: configuration failed' );
3223         return null;
3224     }
3226     var n = navigator,
3227     dua = n.userAgent,
3228     dav = n.appVersion,
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);
3241     if(!has('webkit')){
3242         // Opera
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);
3247         }
3249         // Mozilla and firefox
3250         if(dua.indexOf('Gecko') >= 0 && !has('khtml') && !has('webkit')){
3251             has.add('mozilla', tv);
3252         }
3253         if(has('mozilla')){
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);
3256         }
3258         // IE
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){
3270                 isIE = mode;
3271             }
3273             has.add('ie', isIE);
3274         }
3276         // Wii
3277         has.add('wii', typeof opera != 'undefined' && opera.wiiremote);
3278     }
3280     var detect =
3281     {
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.)
3285                 isFF: has('ff'),
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.)
3290                 isIE: has('ie'),
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'),
3324                 // isMac: Boolean
3325                 //              True if the client runs on Mac
3326                 isMac: has('mac'),
3328                 // isIos: Boolean
3329                 //              True if client is iPhone, iPod, or iPad
3330                 isIos: has('ios'),
3332                 // isAndroid: Number|undefined
3333                 //              Version as a Number if client is android browser. undefined otherwise.
3334                 isAndroid: has('android'),
3336                 // isWii: Boolean
3337                 //              True if client is Wii
3338                 isWii: has('wii'),
3340                 // isQuirks: Boolean
3341                 //              Page is in quirks mode.
3342                 isQuirks: has('quirks'),
3344                 // isAir: Boolean
3345                 //              True if client is Adobe Air
3346                 isAir: has('air')
3347     };
3348     return detect;
3351 /*****
3352  * @dojoend
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
3356  */
3358 /*****
3359  * @licstart
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.
3363  */
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
3374  *   met:
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
3381  *   distribution.
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  ****************************************************************************/
3400 /*****
3401  * @licend
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.
3405  */
3408 /*****
3409  * @svgpathstart
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
3418  */
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;
3427         }
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);
3456         }
3458         window.SVGPathSegClosePath = function(owningPathSegList) {
3459             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CLOSEPATH, 'z', owningPathSegList);
3460         }
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);
3468             this._x = x;
3469             this._y = y;
3470         }
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);
3480             this._x = x;
3481             this._y = y;
3482         }
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);
3492             this._x = x;
3493             this._y = y;
3494         }
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);
3504             this._x = x;
3505             this._y = y;
3506         }
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);
3516             this._x = x;
3517             this._y = y;
3518             this._x1 = x1;
3519             this._y1 = y1;
3520             this._x2 = x2;
3521             this._y2 = y2;
3522         }
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);
3536             this._x = x;
3537             this._y = y;
3538             this._x1 = x1;
3539             this._y1 = y1;
3540             this._x2 = x2;
3541             this._y2 = y2;
3542         }
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);
3556             this._x = x;
3557             this._y = y;
3558             this._x1 = x1;
3559             this._y1 = y1;
3560         }
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);
3572             this._x = x;
3573             this._y = y;
3574             this._x1 = x1;
3575             this._y1 = y1;
3576         }
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);
3588             this._x = x;
3589             this._y = y;
3590             this._r1 = r1;
3591             this._r2 = r2;
3592             this._angle = angle;
3593             this._largeArcFlag = largeArcFlag;
3594             this._sweepFlag = sweepFlag;
3595         }
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);
3610             this._x = x;
3611             this._y = y;
3612             this._r1 = r1;
3613             this._r2 = r2;
3614             this._angle = angle;
3615             this._largeArcFlag = largeArcFlag;
3616             this._sweepFlag = sweepFlag;
3617         }
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);
3632             this._x = x;
3633         }
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);
3642             this._x = x;
3643         }
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);
3652             this._y = y;
3653         }
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);
3662             this._y = y;
3663         }
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);
3672             this._x = x;
3673             this._y = y;
3674             this._x2 = x2;
3675             this._y2 = y2;
3676         }
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);
3688             this._x = x;
3689             this._y = y;
3690             this._x2 = x2;
3691             this._y2 = y2;
3692         }
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);
3704             this._x = x;
3705             this._y = y;
3706         }
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);
3716             this._x = x;
3717             this._y = y;
3718         }
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)
3762                     return 0;
3764                 do {
3765                     measurementElement.pathSegList.removeItem(lastPathSegment);
3766                     if (distance > measurementElement.getTotalLength())
3767                         break;
3768                     lastPathSegment--;
3769                 } while (lastPathSegment > 0);
3770                 return lastPathSegment;
3771             }
3772         }
3773     }
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);
3790         }
3792         window.SVGPathSegList.prototype.classname = 'SVGPathSegList';
3794         Object.defineProperty(window.SVGPathSegList.prototype, 'numberOfItems', {
3795             get: function() {
3796                 this._checkPathSynchronizedToList();
3797                 return this._list.length;
3798             },
3799             enumerable: true
3800         });
3802         // The length property was not specified but was in Firefox 58.
3803         Object.defineProperty(window.SVGPathSegList.prototype, 'length', {
3804             get: function() {
3805                 this._checkPathSynchronizedToList();
3806                 return this._list.length;
3807             },
3808             enumerable: true
3809         });
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', {
3814             get: function() {
3815                 if (!this._pathSegList)
3816                     this._pathSegList = new window.SVGPathSegList(this);
3817                 return this._pathSegList;
3818             },
3819             enumerable: true
3820         });
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());
3831         }
3833         window.SVGPathSegList.prototype._updateListFromPathMutations = function(mutationRecords) {
3834             if (!this._pathElement)
3835                 return;
3836             var hasPathMutations = false;
3837             mutationRecords.forEach(function(record) {
3838                 if (record.attributeName == 'd')
3839                     hasPathMutations = true;
3840             });
3841             if (hasPathMutations)
3842                 this._list = this._parsePath(this._pathElement.getAttribute('d'));
3843         }
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);
3850         }
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();
3855         }
3857         window.SVGPathSegList.prototype.clear = function() {
3858             this._checkPathSynchronizedToList();
3860             this._list.forEach(function(pathSeg) {
3861                 pathSeg._owningPathSegList = null;
3862             });
3863             this._list = [];
3864             this._writeListToPath();
3865         }
3867         window.SVGPathSegList.prototype.initialize = function(newItem) {
3868             this._checkPathSynchronizedToList();
3870             this._list = [newItem];
3871             newItem._owningPathSegList = this;
3872             this._writeListToPath();
3873             return newItem;
3874         }
3876         window.SVGPathSegList.prototype._checkValidIndex = function(index) {
3877             if (isNaN(index) || index < 0 || index >= this.numberOfItems)
3878                 throw 'INDEX_SIZE_ERR';
3879         }
3881         window.SVGPathSegList.prototype.getItem = function(index) {
3882             this._checkPathSynchronizedToList();
3884             this._checkValidIndex(index);
3885             return this._list[index];
3886         }
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();
3897             }
3898             this._list.splice(index, 0, newItem);
3899             newItem._owningPathSegList = this;
3900             this._writeListToPath();
3901             return newItem;
3902         }
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();
3910             }
3911             this._checkValidIndex(index);
3912             this._list[index] = newItem;
3913             newItem._owningPathSegList = this;
3914             this._writeListToPath();
3915             return newItem;
3916         }
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();
3925             return item;
3926         }
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();
3934             }
3935             this._list.push(newItem);
3936             newItem._owningPathSegList = this;
3937             // TODO: Optimize this to just append to the existing attribute.
3938             this._writeListToPath();
3939             return newItem;
3940         };
3942         window.SVGPathSegList.prototype.matrixTransform = function(aSVGMatrix) {
3943             this._checkPathSynchronizedToList();
3945             var nLength = this._list.length;
3946             for( var i = 0; i < nLength; ++i )
3947             {
3948                 var nX;
3949                 var aPathSeg = this._list[i];
3950                 switch( aPathSeg.pathSegTypeAsLetter )
3951                 {
3952                     case 'C':
3953                         nX = aPathSeg._x2;
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
3957                     case 'Q':
3958                         nX = aPathSeg._x1;
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
3962                     case 'M':
3963                     case 'L':
3964                         nX = aPathSeg._x;
3965                         aPathSeg._x = aSVGMatrix.a * nX + aSVGMatrix.c * aPathSeg._y + aSVGMatrix.e;
3966                         aPathSeg._y = aSVGMatrix.b * nX + aSVGMatrix.d * aPathSeg._y + aSVGMatrix.f;
3967                         break;
3968                     default:
3969                         log( 'SVGPathSeg.matrixTransform: unexpected path segment type: '
3970                             + aPathSeg.pathSegTypeAsLetter );
3971                 }
3972             }
3974             this._writeListToPath();
3975         };
3977         window.SVGPathSegList.prototype.changeOrientation = function() {
3978             this._checkPathSynchronizedToList();
3980             var aPathSegList = this._list;
3981             var nLength = aPathSegList.length;
3982             if( nLength == 0 ) return;
3984             var nCurrentX = 0;
3985             var nCurrentY = 0;
3987             var aPathSeg = aPathSegList[0];
3988             if( aPathSeg.pathSegTypeAsLetter == 'M' )
3989             {
3990                 nCurrentX = aPathSeg.x;
3991                 nCurrentY = aPathSeg.y;
3992                 aPathSegList.shift();
3993                 --nLength;
3994             }
3996             var i;
3997             for( i = 0; i < nLength; ++i )
3998             {
3999                 aPathSeg = aPathSegList[i];
4000                 switch( aPathSeg.pathSegTypeAsLetter )
4001                 {
4002                     case 'C':
4003                         var nX = aPathSeg._x1;
4004                         aPathSeg._x1 = aPathSeg._x2;
4005                         aPathSeg._x2 = nX;
4006                         var nY = aPathSeg._y1;
4007                         aPathSeg._y1 = aPathSeg._y2;
4008                         aPathSeg._y2 = nY;
4009                     // fall through intended
4010                     case 'M':
4011                     case 'L':
4012                     case 'Q':
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;
4018                         break;
4019                     default:
4020                         log( 'SVGPathSegList.changeOrientation: unexpected path segment type: '
4021                             + aPathSeg.pathSegTypeAsLetter );
4022                 }
4024         }
4026             aPathSegList.reverse();
4028             var aMovePathSeg = new window.SVGPathSegMovetoAbs( this, nCurrentX, nCurrentY );
4029             aPathSegList.unshift( aMovePathSeg );
4031             this._writeListToPath();
4032         };
4034         window.SVGPathSegList._pathSegArrayAsString = function(pathSegArray) {
4035             var string = '';
4036             var first = true;
4037             pathSegArray.forEach(function(pathSeg) {
4038                 if (first) {
4039                     first = false;
4040                     string += pathSeg._asPathString();
4041                 } else {
4042                     string += ' ' + pathSeg._asPathString();
4043                 }
4044             });
4045             return string;
4046         }
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)
4051                 return [];
4053             var owningPathSegList = this;
4055             var Builder = function() {
4056                 this.pathSegList = [];
4057             }
4059             Builder.prototype.appendSegment = function(pathSeg) {
4060                 this.pathSegList.push(pathSeg);
4061             }
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();
4070             }
4072             Source.prototype._isCurrentSpace = function() {
4073                 var character = this._string[this._currentIndex];
4074                 return character <= ' ' && (character == ' ' || character == '\n' || character == '\t' || character == '\r' || character == '\f');
4075             }
4077             Source.prototype._skipOptionalSpaces = function() {
4078                 while (this._currentIndex < this._endIndex && this._isCurrentSpace())
4079                     this._currentIndex++;
4080                 return this._currentIndex < this._endIndex;
4081             }
4083             Source.prototype._skipOptionalSpacesOrDelimiter = function() {
4084                 if (this._currentIndex < this._endIndex && !this._isCurrentSpace() && this._string.charAt(this._currentIndex) != ',')
4085                     return false;
4086                 if (this._skipOptionalSpaces()) {
4087                     if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == ',') {
4088                         this._currentIndex++;
4089                         this._skipOptionalSpaces();
4090                     }
4091                 }
4092                 return this._currentIndex < this._endIndex;
4093             }
4095             Source.prototype.hasMoreData = function() {
4096                 return this._currentIndex < this._endIndex;
4097             }
4099             Source.prototype.peekSegmentType = function() {
4100                 var lookahead = this._string[this._currentIndex];
4101                 return this._pathSegTypeFromChar(lookahead);
4102             }
4104             Source.prototype._pathSegTypeFromChar = function(lookahead) {
4105                 switch (lookahead) {
4106                     case 'Z':
4107                     case 'z':
4108                         return window.SVGPathSeg.PATHSEG_CLOSEPATH;
4109                     case 'M':
4110                         return window.SVGPathSeg.PATHSEG_MOVETO_ABS;
4111                     case 'm':
4112                         return window.SVGPathSeg.PATHSEG_MOVETO_REL;
4113                     case 'L':
4114                         return window.SVGPathSeg.PATHSEG_LINETO_ABS;
4115                     case 'l':
4116                         return window.SVGPathSeg.PATHSEG_LINETO_REL;
4117                     case 'C':
4118                         return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS;
4119                     case 'c':
4120                         return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL;
4121                     case 'Q':
4122                         return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS;
4123                     case 'q':
4124                         return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL;
4125                     case 'A':
4126                         return window.SVGPathSeg.PATHSEG_ARC_ABS;
4127                     case 'a':
4128                         return window.SVGPathSeg.PATHSEG_ARC_REL;
4129                     case 'H':
4130                         return window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS;
4131                     case 'h':
4132                         return window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL;
4133                     case 'V':
4134                         return window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS;
4135                     case 'v':
4136                         return window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL;
4137                     case 'S':
4138                         return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS;
4139                     case 's':
4140                         return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL;
4141                     case 'T':
4142                         return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS;
4143                     case 't':
4144                         return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL;
4145                     default:
4146                         return window.SVGPathSeg.PATHSEG_UNKNOWN;
4147                 }
4148             }
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;
4158                 }
4159                 return window.SVGPathSeg.PATHSEG_UNKNOWN;
4160             }
4162             Source.prototype.initialCommandIsMoveTo = function() {
4163                 // If the path is empty it is still valid, so return true.
4164                 if (!this.hasMoreData())
4165                     return true;
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;
4169             }
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() {
4174                 var exponent = 0;
4175                 var integer = 0;
4176                 var frac = 1;
4177                 var decimal = 0;
4178                 var sign = 1;
4179                 var expsign = 1;
4181                 var startIndex = this._currentIndex;
4183                 this._skipOptionalSpaces();
4185                 // Read the sign.
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++;
4190                     sign = -1;
4191                 }
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+-.].
4195                     return undefined;
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;
4204                     var multiplier = 1;
4205                     while (scanIntPartIndex >= startIntPartIndex) {
4206                         integer += multiplier * (this._string.charAt(scanIntPartIndex--) - '0');
4207                         multiplier *= 10;
4208                     }
4209                 }
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')
4217                         return undefined;
4218                     while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
4219                         frac *= 10;
4220                         decimal += (this._string.charAt(this._currentIndex) - '0') / frac;
4221                         this._currentIndex += 1;
4222                     }
4223                 }
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++;
4234                         expsign = -1;
4235                     }
4237                     // There must be an exponent.
4238                     if (this._currentIndex >= this._endIndex || this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9')
4239                         return undefined;
4241                     while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
4242                         exponent *= 10;
4243                         exponent += (this._string.charAt(this._currentIndex) - '0');
4244                         this._currentIndex++;
4245                     }
4246                 }
4248                 var number = integer + decimal;
4249                 number *= sign;
4251                 if (exponent)
4252                     number *= Math.pow(10, expsign * exponent);
4254                 if (startIndex == this._currentIndex)
4255                     return undefined;
4257                 this._skipOptionalSpacesOrDelimiter();
4259                 return number;
4260             }
4262             Source.prototype._parseArcFlag = function() {
4263                 if (this._currentIndex >= this._endIndex)
4264                     return undefined;
4265                 var flag = false;
4266                 var flagChar = this._string.charAt(this._currentIndex++);
4267                 if (flagChar == '0')
4268                     flag = false;
4269                 else if (flagChar == '1')
4270                     flag = true;
4271                 else
4272                     return undefined;
4274                 this._skipOptionalSpacesOrDelimiter();
4275                 return flag;
4276             }
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)
4284                         return null;
4285                     command = this._nextCommandHelper(lookahead, this._previousCommand);
4286                     if (command == window.SVGPathSeg.PATHSEG_UNKNOWN)
4287                         return null;
4288                 } else {
4289                     this._currentIndex++;
4290                 }
4292                 this._previousCommand = command;
4294                 switch (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);
4342                     default:
4343                         throw 'Unknown path seg type.'
4344                 }
4345             }
4347             var builder = new Builder();
4348             var source = new Source(string);
4350             if (!source.initialCommandIsMoveTo())
4351                 return [];
4352             while (source.hasMoreData()) {
4353                 var pathSeg = source.parseSegment();
4354                 if (!pathSeg)
4355                     return [];
4356                 builder.appendSegment(pathSeg);
4357             }
4359             return builder.pathSegList;
4360         }
4361     }
4362 }());
4364 /*****
4365  * @svgpathend
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
4374  */
4377 /*****
4378  * @licstart
4380  * The following is the license notice for the part of JavaScript code  of
4381  * this page included between the '@libreofficestart' and the '@libreofficeend'
4382  * notes.
4383  */
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  ************************************************************************/
4405 /*****
4406  * @licend
4408  * The above is the license notice for the part of JavaScript code  of
4409  * this page included between the '@libreofficestart' and the '@libreofficeend'
4410  * notes.
4411  */
4414 /*****
4415  * @libreofficestart
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
4424  */
4427 window.onload = init;
4430 // ooo elements
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';
4437 // ooo attributes
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.
4476 var NSS = {};
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.
4487 var SLIDE_MODE = 1;
4488 var INDEX_MODE = 2;
4490 // Mouse handler actions.
4491 var MOUSE_UP = 1;
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;
4496 // Key-codes.
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
4505 var ENTER_KEY = 13;
4506 var SPACE_KEY = 32;
4507 var ESCAPE_KEY = 27;
4508 var Q_KEY = 81;
4510 // Visibility Values
4511 var HIDDEN = 0;
4512 var VISIBLE = 1;
4513 var INHERIT = 2;
4514 var aVisibilityAttributeValue = [ 'hidden', 'visible', 'inherit' ];  // eslint-disable-line no-unused-vars
4515 var aVisibilityValue = { 'hidden' : HIDDEN, 'visible' : VISIBLE, 'inherit' : INHERIT };
4517 // Parameters
4518 var ROOT_NODE = document.getElementsByTagNameNS( NSS['svg'], 'svg' )[0];
4519 var WIDTH = 0;
4520 var HEIGHT = 0;
4521 var INDEX_COLUMNS_DEFAULT = 3;
4522 var INDEX_OFFSET = 0;
4524 // Initialization.
4525 var Detect = configureDetectionTools();
4526 var theMetaDoc;
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;
4550     return new F();
4554 function extend( aSubType, aSuperType )
4556     if (!aSuperType || !aSubType)
4557     {
4558         alert('extend failed, verify dependencies');
4559     }
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)
4570     {
4571         sp.constructor = aSuperType;
4572     }
4574     return aSubType;
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 )
4586     {
4587         if( TemplateClass.instanceSet[i].base === BaseType )
4588             return TemplateClass.instanceSet[i].instance;
4589     }
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' ) );
4611     this.left = x;
4612     this.right = x + width;
4613     this.top = y;
4614     this.bottom = y + height;
4618  * Returns key corresponding to a value in object, null otherwise.
4620  * @param Object
4621  * @param value
4622  */
4623 function getKeyByValue(aObj, value) {
4624   for(var key in aObj) {
4625     if(aObj[key] == value)
4626       return key;
4627   }
4628   return null;
4631 function log( message )
4633     if( typeof console == 'object' )
4634     {
4635         // eslint-disable-next-line no-console
4636         console.log( message );
4637     }
4638     else if( typeof opera == 'object' )
4639     {
4640         opera.postError( message );
4641     }
4642     // eslint-disable-next-line no-undef
4643     else if( typeof java == 'object' && typeof java.lang == 'object' )
4644     {
4645         // eslint-disable-next-line no-undef
4646         java.lang.System.out.println( message );
4647     }
4650 function getNSAttribute( sNSPrefix, aElem, sAttrName )
4652     if( !aElem ) return null;
4653     if( 'getAttributeNS' in aElem )
4654     {
4655         return aElem.getAttributeNS( NSS[sNSPrefix], sAttrName );
4656     }
4657     else
4658     {
4659         return aElem.getAttribute( sNSPrefix + ':' + sAttrName );
4660     }
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 )
4672     {
4673         aElem.setAttributeNS( NSS[sNSPrefix], sAttrName, aValue );
4674         return true;
4675     }
4676     else
4677     {
4678         aElem.setAttribute(sNSPrefix + ':' + sAttrName, aValue );
4679         return true;
4680     }
4683 function getElementsByClassName( aElem, sClassName )
4686     var aElementSet = [];
4687     // not all browsers support the 'getElementsByClassName' method
4688     if( 'getElementsByClassName' in aElem )
4689     {
4690         aElementSet = aElem.getElementsByClassName( sClassName );
4691     }
4692     else
4693     {
4694         var aElementSetByClassProperty = getElementsByProperty( aElem, 'class' );
4695         for( var i = 0; i < aElementSetByClassProperty.length; ++i )
4696         {
4697             var sAttrClassName = aElementSetByClassProperty[i].getAttribute( 'class' );
4698             if( sAttrClassName == sClassName )
4699             {
4700                 aElementSet.push( aElementSetByClassProperty[i] );
4701             }
4702         }
4703     }
4704     return aElementSet;
4707 function getElementByClassName( aElem, sClassName /*, sTagName */)
4709     var aElementSet = getElementsByClassName( aElem, sClassName );
4710     if ( aElementSet.length == 1 )
4711         return aElementSet[0];
4712     else
4713         return null;
4716 function getClassAttribute(  aElem )
4718     if( aElem )
4719         return aElem.getAttribute( 'class' );
4720     return '';
4723 function createElementGroup( aParentElement, aElementList, nFrom, nCount, sGroupClass, sGroupId )
4725     var nTo = nFrom + nCount;
4726     if( nCount < 1 || aElementList.length < nTo )
4727     {
4728         log( 'createElementGroup: not enough elements available.' );
4729         return;
4730     }
4731     var firstElement = aElementList[nFrom];
4732     if( !firstElement )
4733     {
4734         log( 'createElementGroup: element not found.' );
4735         return;
4736     }
4737     var aGroupElement = document.createElementNS( NSS['svg'], 'g' );
4738     if( sGroupId )
4739         aGroupElement.setAttribute( 'id', sGroupId );
4740     if( sGroupClass )
4741         aGroupElement.setAttribute( 'class', sGroupClass );
4742     aParentElement.insertBefore( aGroupElement, firstElement );
4743     var i = nFrom;
4744     for( ; i < nTo; ++i )
4745     {
4746         aParentElement.removeChild( aElementList[i] );
4747         aGroupElement.appendChild( aElementList[i] );
4748     }
4751 function initVisibilityProperty( aElement )
4753     var nVisibility = VISIBLE;
4754     var sVisibility = aElement.getAttribute( 'visibility' );
4755     if( sVisibility ) nVisibility = aVisibilityValue[ sVisibility ];
4756     return nVisibility;
4759 function getSafeIndex( nIndex, nMin, nMax )
4761     if( nIndex < nMin )
4762         return nMin;
4763     else if( nIndex > nMax )
4764         return nMax;
4765     else
4766         return nIndex;
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, ' '));
4777 /** getRandomInt
4779  * @param nMax
4780  * @returns {number}
4781  *   an integer in [0,nMax[
4782  */
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() )
4826     {
4827         var sInfo = 'DBG: ' + sMessage;
4828         if( nTime )
4829             sInfo += ' (at: ' + String( nTime / 1000 ) + 's)';
4830         log( sInfo );
4831     }
4835 // - Debug Printers -
4836 var aGenericDebugPrinter = new DebugPrinter();
4837 aGenericDebugPrinter.on();
4838 var DBGLOG = bind2( DebugPrinter.prototype.print, aGenericDebugPrinter );
4840 var NAVDBG = new DebugPrinter();
4841 NAVDBG.off();
4843 var ANIMDBG = new DebugPrinter();
4844 ANIMDBG.off();
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
4875  *    for each slide;
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.
4879  */
4880 function MetaDocument()
4882     // We look for the svg element that provides the following presentation
4883     // properties:
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 !== '')
4901     {
4902         this.nStartSlideNumber = parseInt(aParmStartSlideNumber);
4903     }
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 )
4940     {
4941         var sMetaSlideId = aOOOElemMetaSlide + '_' + i;
4942         this.aMetaSlideSet.push( new MetaSlide( sMetaSlideId, this ) );
4943     }
4944     assert( this.aMetaSlideSet.length == this.nNumberOfSlides,
4945             'MetaDocument: aMetaSlideSet.length != nNumberOfSlides.' );
4948 MetaDocument.prototype =
4950 /*** public methods ***/
4952 /** getCurrentSlide
4954  *  @return
4955  *      The MetaSlide object handling the current slide.
4956  */
4957 getCurrentSlide : function()
4959     return this.aMetaSlideSet[nCurSlide];
4962 /** setCurrentSlide
4964  *  @param nSlideIndex
4965  *      The index of the slide to show.
4966  */
4967 setCurrentSlide : function( nSlideIndex )
4969     if( nSlideIndex >= 0 &&  nSlideIndex < this.nNumberOfSlides )
4970     {
4971         if( nCurSlide !== undefined )
4972             this.aMetaSlideSet[nCurSlide].hide();
4973         this.aMetaSlideSet[nSlideIndex].show();
4974         nCurSlide = nSlideIndex;
4975     }
4976     else
4977     {
4978         log('MetaDocument.setCurrentSlide: slide index out of range: ' + nSlideIndex );
4979     }
4982 /*** private methods ***/
4984 initSlideAnimationsMap : function()
4986     var aAnimationsSection = document.getElementById( 'presentation-animations' );
4987     if( aAnimationsSection )
4988     {
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 )
4996         {
4997             var sSlideId = aAnimationsDefSet[i].getAttributeNS( NSS['ooo'], aOOOAttrSlide );
4998             var aChildSet = getElementChildren( aAnimationsDefSet[i] );
4999             if( sSlideId && ( aChildSet.length === 1 ) )
5000             {
5001                 this.aSlideAnimationsMap[ sSlideId ] = aChildSet[0];
5002             }
5003         }
5004     }
5007 }; // end MetaDocument prototype
5009 /** Class MetaSlide
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.
5019  *  @param aMetaDoc
5020  *      The MetaDocument global object.
5021  */
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) );
5041     else
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 )
5080     {
5081         this.backgroundId = this.backgroundElement.getAttribute( 'id' );
5082     }
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 )
5124     {
5125         this.aTransitionHandler = new SlideTransition( this.getSlideAnimationsRoot(), this.slideId );
5126     }
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 ***/
5150 /** show
5151  *  Set the visibility property of the slide to 'inherit'
5152  *  and update the master page view.
5153  */
5154 show : function()
5156     this.updateMasterPageView();
5157     this.aVisibilityStatusElement.setAttribute( 'visibility', 'inherit' );
5160 /** hide
5161  *  Set the visibility property of the slide to 'hidden'.
5162  */
5163 hide : function()
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.
5172  */
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 )
5178     {
5179         this.aMasterPageView = new MasterPageView( this );
5180         this.aMasterPageView.attachToSlide();
5181     }
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' );
5191     var i = 0;
5192     for( ; i < aPlaceholderList.length; ++i )
5193     {
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();
5200     }
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 ) )
5210     {
5211         this.theMetaDoc.aMasterPageSet[ sMasterPageId ] = new MasterPage( sMasterPageId, this );
5213         // We initialize aTextFieldHandlerSet[ sMasterPageId ] to an empty
5214         // collection.
5215         this.theMetaDoc.aTextFieldHandlerSet[ sMasterPageId ] = {};
5216     }
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 );
5225     else
5226         return -1;
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 );
5245     if( sVisibility )
5246         nVisibility = aVisibilityValue[ sVisibility ];
5247     return nVisibility;
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 ] )
5265     {
5266         var aTextField;
5267         var aTextFieldElem = document.getElementById( sTextFieldId );
5268         var sClassName = getClassAttribute( aTextFieldElem );
5269         if( sClassName == 'FixedDateTimeField' )
5270         {
5271             aTextField = new FixedTextByElementProvider( aTextFieldElem );
5272             this.bIsDateTimeVariable = false;
5273         }
5274         else if( sClassName == 'VariableDateTimeField' )
5275         {
5276             aTextField = new CurrentDateTimeProvider( aTextFieldElem );
5277             this.bIsDateTimeVariable = true;
5278         }
5279         else
5280         {
5281             aTextField = null;
5282         }
5283         this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ] = aTextField;
5284     }
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 ] )
5298     {
5299         var aTextFieldElem = document.getElementById( sTextFieldId );
5300         this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ]
5301             = new FixedTextByElementProvider( aTextFieldElem );
5302     }
5303     return this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ];
5306 collectTextShapes : function()
5308     var aTextShapeSet = [];
5309     var aTextShapeIndexElem = getElementByClassName( document, 'TextShapeIndex' );
5310     if( aTextShapeIndexElem )
5311     {
5312         var aIndexEntryList = getElementChildren( aTextShapeIndexElem );
5313         var i;
5314         for( i = 0; i < aIndexEntryList.length; ++i )
5315         {
5316             var sSlideId = getOOOAttribute( aIndexEntryList[i], 'slide' );
5317             if( sSlideId === this.slideId )
5318             {
5319                 var sTextShapeIds = getOOOAttribute( aIndexEntryList[i], 'id-list' );
5320                 if( sTextShapeIds )
5321                 {
5322                     var aTextShapeIdSet =  sTextShapeIds.split( ' ' );
5323                     var j;
5324                     for( j = 0; j < aTextShapeIdSet.length; ++j )
5325                     {
5326                         var aTextShapeElem = document.getElementById( aTextShapeIdSet[j] );
5327                         if( aTextShapeElem )
5328                         {
5329                             aTextShapeSet.push( aTextShapeElem );
5330                         }
5331                         else
5332                         {
5333                             log( 'warning: MetaSlide.collectTextShapes: text shape with id <' + aTextShapeIdSet[j] + '> is not valid.'  );
5334                         }
5335                     }
5336                 }
5337                 break;
5338             }
5339         }
5340     }
5341     return aTextShapeSet;
5344 initHyperlinks : function()
5346     var aHyperlinkSet = {};
5347     var i;
5348     for( i = 0; i < this.aTextShapeSet.length; ++i )
5349     {
5350         if( this.aTextShapeSet[i] )
5351         {
5352             var aHyperlinkIdList = getElementByClassName( this.aTextShapeSet[i], 'HyperlinkIdList' );
5353             if( aHyperlinkIdList )
5354             {
5355                 var sHyperlinkIds = aHyperlinkIdList.textContent;
5356                 if( sHyperlinkIds )
5357                 {
5358                     var aHyperlinkIdSet = sHyperlinkIds.trim().split( ' ' );
5359                     var j;
5360                     for( j = 0; j < aHyperlinkIdSet.length; ++j )
5361                     {
5362                         var sId = aHyperlinkIdSet[j];
5363                         aHyperlinkSet[ sId ] = new HyperlinkElement( sId, this.aSlideAnimationsHandler.aEventMultiplexer );
5364                     }
5365                 }
5366             }
5367         }
5368     }
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' )
5384     {
5385         var aPlaceholderElement = getElementByClassName( elem, 'PlaceholderText' );
5386         if (aPlaceholderElement)
5387         {
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;
5397         }
5398     }
5399     return sFieldType;
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'>
5421  *          background image
5422  *      </g>
5423  *      <g class='BackgroundObjects'>
5424  *          <g class='Date/Time'>
5425  *              date/time placeholder
5426  *          </g>
5427  *          <g class='Header'>
5428  *              header placeholder
5429  *          </g>
5430  *          <g class='Footer'>
5431  *              footer placeholder
5432  *          </g>
5433  *          <g class='Slide_Number'>
5434  *              slide number placeholder
5435  *          </g>
5436  *          shapes
5437  *      </g>
5438  *  </g>
5440  *  @param sMasterPageId
5441  *      A string representing the value of the id attribute of the master page
5442  *      element to be handled.
5443  * @param aMetaSlide
5444  *     A meta slide having as master page the one with the passed id.
5445  */
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 )
5459     {
5460         this.backgroundId = this.background.getAttribute( 'id' );
5461         this.backgroundVisibility = initVisibilityProperty( this.background );
5462     }
5463     else
5464     {
5465         this.backgroundId = '';
5466         log( 'MasterPage: the background element is not valid.' );
5467     }
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 )
5474     {
5475         this.backgroundObjectsId = this.backgroundObjects.getAttribute( 'id' );
5476         this.backgroundObjectsVisibility = initVisibilityProperty( this.backgroundObjects );
5478         if( this.backgroundObjectsVisibility != HIDDEN )
5479         {
5480             var aBackgroundObjectList = getElementChildren( this.backgroundObjects );
5481             var nFrom = 0;
5482             var nCount = 0;
5483             var nSubGroupId = 1;
5484             var sClass;
5485             var sId = '';
5486             var i = 0;
5487             for( ; i < aBackgroundObjectList.length; ++i )
5488             {
5489                 var aObject = aBackgroundObjectList[i];
5490                 sClass = null;
5491                 var sFieldType = getTextFieldType( aObject );
5492                 if( sFieldType && aObject.firstElementChild )
5493                 {
5494                     var sObjId = aObject.firstElementChild.getAttribute( 'id' );
5495                     if( sObjId )
5496                     {
5497                          sClass = sFieldType + '.' + sObjId;
5498                          aObject.setAttribute('class', sClass);
5499                     }
5500                 }
5501                 if( !sClass )
5502                 {
5503                     sClass = aBackgroundObjectList[i].getAttribute('class');
5504                 }
5505                 if( !sClass || !isTextFieldByClassName( sClass ) )
5506                 {
5507                     if( nCount === 0 )
5508                     {
5509                         nFrom = i;
5510                         sId = this.backgroundObjectsId + '.' + nSubGroupId;
5511                         ++nSubGroupId;
5512                         this.aBackgroundObjectSubGroupIdList.push( sId );
5513                     }
5514                     ++nCount;
5515                 }
5516                 else
5517                 {
5518                     this.aBackgroundObjectSubGroupIdList.push( sClass );
5519                     if( nCount !== 0 )
5520                     {
5521                         createElementGroup( this.backgroundObjects, aBackgroundObjectList, nFrom, nCount, 'BackgroundObjectSubgroup', sId );
5522                         nCount = 0;
5523                     }
5524                 }
5525             }
5526             if( nCount !== 0 )
5527             {
5528                 createElementGroup( this.backgroundObjects, aBackgroundObjectList, nFrom, nCount, 'BackgroundObjectSubgroup', sId );
5529             }
5530         }
5531     }
5532     else
5533     {
5534         this.backgroundObjectsId = '';
5535         log( 'MasterPage: the background objects element is not valid.' );
5536     }
5538     // We populate the collection of placeholders.
5539     this.aPlaceholderShapeSet = {};
5540     this.initPlaceholderShapes();
5543 MasterPage.prototype =
5545 /*** private methods ***/
5547 initPlaceholderShapes : function()
5549     var sClassName;
5550     var i = 0;
5551     for( ; i < this.aBackgroundObjectSubGroupIdList.length; ++i )
5552     {
5553         sClassName = this.aBackgroundObjectSubGroupIdList[i];
5554         if( isTextFieldByClassName( sClassName ) )
5555             this.aPlaceholderShapeSet[ sClassName ] = new PlaceholderShape( this, sClassName );
5556     }
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
5565  *  element.
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.
5572  *  @param sClassName
5573  *      A string representing the value of the class attribute of the text
5574  *      field element to be handled.
5575  */
5576 function PlaceholderShape( aMasterPage, sClassName )
5578     this.masterPage = aMasterPage;
5579     this.className = sClassName;
5581     this.element = null;
5582     this.textElement = null;
5583     this.init();
5586 /* public methods */
5587 PlaceholderShape.prototype.isValid = function()
5589     return ( this.element && this.textElement );
5592 /* private methods */
5594 /** init
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.
5598  */
5599 PlaceholderShape.prototype.init = function()
5601     var aTextFieldElement = getElementByClassName( this.masterPage.backgroundObjects, this.className );
5602     if( aTextFieldElement )
5603     {
5604         var aTextElem = getElementByClassName( aTextFieldElement, 'SVGTextShape' );
5605         if( aTextElem )
5606         {
5607             var aPlaceholderElement = getElementByClassName(aTextElem, 'PlaceholderText');
5608             if( aPlaceholderElement )
5609             {
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
5625                 // to be wrapped.
5626                 var aSVGRectElem = getElementByClassName( aTextFieldElement, 'BoundingBox' );
5627                 if( aSVGRectElem )
5628                 {
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' )
5635                     {
5636                         sTextAnchor = 'start';
5637                         sX = String( Math.trunc( aRect.left + nMargin ) );
5638                     }
5639                     else if( sTextAdjust == 'right' )
5640                     {
5641                         sTextAnchor = 'end';
5642                         sX = String( Math.trunc( aRect.right - nMargin ) );
5643                     }
5644                     else if( sTextAdjust == 'center' )
5645                     {
5646                         sTextAnchor = 'middle';
5647                         var nMiddle = ( aRect.left + aRect.right ) / 2;
5648                         sX = String( parseInt( String( nMiddle ) ) );
5649                     }
5650                     if( sTextAnchor )
5651                     {
5652                         aTextElem.setAttribute( 'text-anchor', sTextAnchor );
5653                         if( sX )
5654                             aTextElem.setAttribute( 'x', sX );
5656                         var aTSpanElements = getElementsByClassName( aTextElem, 'TextPosition' );
5657                         if( aTSpanElements )
5658                         {
5659                             var i = 0;
5660                             for( ; i < aTSpanElements.length; ++i )
5661                             {
5662                                 var aTSpanElem = aTSpanElements[i];
5663                                 aTSpanElem.removeAttribute( 'x' );
5664                                 if( i !== 0 )
5665                                     aTSpanElem.removeAttribute( 'y' );
5666                             }
5667                         }
5668                     }
5669                 }
5671                 // date/time fields were not exported correctly when positioned chars are used
5672                 if( this.masterPage.metaSlide.theMetaDoc.bIsUsePositionedChars )
5673                 {
5674                     // We remove all text lines but the first one used as placeholder.
5675                     var aTextLineGroupElem = aPlaceholderElement.parentNode.parentNode;
5676                     if( aTextLineGroupElem )
5677                     {
5678                         // Just to be sure it is the element we are looking for.
5679                         var sFontFamilyAttr = aTextLineGroupElem.getAttribute( 'font-family' );
5680                         if( sFontFamilyAttr )
5681                         {
5682                             var aChildSet = getElementChildren( aTextLineGroupElem );
5683                             if( aChildSet.length > 1 )
5684                                 var i = 1;
5685                             for( ; i < aChildSet.length; ++i )
5686                             {
5687                                 aTextLineGroupElem.removeChild( aChildSet[i] );
5688                             }
5689                         }
5690                     }
5691                 }
5692                 this.textElement = aPlaceholderElement;
5693             }
5694         }
5695         this.element = aTextFieldElement;
5696     }
5699 /** Class MasterPageView
5700  *  This class is used to creates a svg element of class MasterPageView and its
5701  *  sub-elements.
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
5711  *                  ...
5712  *          </g>
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
5717  *      </g>
5718  *  </g>
5720  *  Sub-elements are present only if they are visible.
5722  *  @param aMetaSlide
5723  *      The MetaSlide object managing the slide element that targets
5724  *      the master page view element created by an instance of MasterPageView.
5725  */
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 ***/
5738 /** attachToSlide
5739  *  Prepend the master slide view element to the slide element.
5740  */
5741 MasterPageView.prototype.attachToSlide = function()
5743     if( !this.bIsAttached )
5744     {
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;
5750     }
5753 /** detachFromSlide
5754  *  Remove the master slide view element from the slide element.
5755  */
5756 MasterPageView.prototype.detachFromSlide = function()
5758     if( this.bIsAttached )
5759     {
5760         this.aSlideElement.removeChild( this.aMPVElement );
5761         this.bIsAttached = false;
5762     }
5765 /** update
5766  *  Update the content of text fields placed on the master page.
5767  */
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 )
5799     {
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 );
5806         // node linking
5807         aMasterPageViewElement.appendChild( this.aBackgroundElement );
5808     }
5810     // init the BackgroundObjects element
5811     if( this.aMetaSlide.nAreMasterObjectsVisible )
5812     {
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;
5826         var i = 0;
5827         var sId;
5828         for( ; i < aBackgroundObjectSubGroupIdList.length; ++i )
5829         {
5830             sId = aBackgroundObjectSubGroupIdList[i];
5831             if( sId.indexOf( aSlideNumberClassName ) == 0 )
5832             {
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] )
5842                 {
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;
5850                 }
5851             }
5852             else if( sId === aDateTimeClassName )
5853             {
5854                 // Date/Time field
5855                 if( this.aMetaSlide.nIsDateTimeVisible )
5856                 {
5857                     this.aDateTimeFieldHandler =
5858                         this.initTextFieldHandler( aDateTimeClassName, aPlaceholderShapeSet,
5859                                                    aTextFieldContentProviderSet, aDefsElement,
5860                                                    aTextFieldHandlerSet, sMasterSlideId );
5861                 }
5862             }
5863             else if( sId === aFooterClassName )
5864             {
5865                 // Footer Field
5866                 if( this.aMetaSlide.nIsFooterVisible )
5867                 {
5868                     this.aFooterFieldHandler =
5869                         this.initTextFieldHandler( aFooterClassName, aPlaceholderShapeSet,
5870                                                    aTextFieldContentProviderSet, aDefsElement,
5871                                                    aTextFieldHandlerSet, sMasterSlideId );
5872                 }
5873             }
5874             else if( sId === aHeaderClassName )
5875             {
5876                 // Header Field
5877                 if( this.aMetaSlide.nIsHeaderVisible )
5878                 {
5879                     this.aHeaderFieldHandler =
5880                         this.initTextFieldHandler( aHeaderClassName, aPlaceholderShapeSet,
5881                                                    aTextFieldContentProviderSet, aDefsElement,
5882                                                    aTextFieldHandlerSet, sMasterSlideId );
5883                 }
5884             }
5885             else if( sId.indexOf( aDateClassName ) == 0
5886                 || sId.indexOf( aTimeClassName ) == 0
5887                 || sId.indexOf( aSlideNameClassName ) == 0 )
5888             {
5889                 this.initTextFieldHandler( sId, aPlaceholderShapeSet,
5890                                            aTextFieldContentProviderSet, aDefsElement,
5891                                            aTextFieldHandlerSet, sMasterSlideId );
5892             }
5893             else
5894             {
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 );
5901                 // node linking
5902                 this.aBackgroundObjectsElement.appendChild( aBackgroundSubGroupElement );
5903             }
5905         }
5906         // node linking
5907         aMasterPageViewElement.appendChild( this.aBackgroundObjectsElement );
5908     }
5910     return aMasterPageViewElement;
5913 MasterPageView.prototype.initTextFieldHandler =
5914     function( sId, aPlaceholderShapeSet, aTextFieldContentProviderSet,
5915               aDefsElement, aTextFieldHandlerSet, sMasterSlideId )
5917     var sRefId = null;
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 )
5924     {
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 ] )
5929         {
5930             aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ] =
5931                 new TextFieldHandler( aPlaceholderShape,
5932                                       aTextFieldContentProvider );
5933             aTextFieldHandler = aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ];
5934             aTextFieldHandler.update();
5935             aTextFieldHandler.appendTo( aDefsElement );
5936         }
5937         else
5938         {
5939             aTextFieldHandler = aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ];
5940         }
5941         sRefId = aTextFieldHandler.sId;
5942     }
5943     else if( aPlaceholderShape && aPlaceholderShape.element && aPlaceholderShape.element.firstElementChild
5944         && !aPlaceholderShape.textElement && !aTextFieldContentProvider )
5945     {
5946         sRefId = aPlaceholderShape.element.firstElementChild.getAttribute('id');
5947     }
5949     if( sRefId )
5950     {
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);
5957         // node linking
5958         this.aBackgroundObjectsElement.appendChild( aTextFieldElement );
5959     }
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
5971  *      demanded.
5972  */
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 ***/
6016 /** appendTo
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.
6021  */
6022 TextFieldHandler.prototype.appendTo = function( aParentNode )
6024     if( !this.aTextFieldElement )
6025     {
6026         log( 'TextFieldHandler.appendTo: aTextFieldElement is not defined' );
6027         return;
6028     }
6029     if( !aParentNode )
6030     {
6031         log( 'TextFieldHandler.appendTo: parent node is not defined' );
6032         return;
6033     }
6035     aParentNode.appendChild( this.aTextFieldElement );
6038 /** setTextContent
6039  *  Modify the content of the cloned text field.
6041  *  @param sText
6042  *      A string representing the new content of the cloned text field.
6043  */
6044 TextFieldHandler.prototype.setTextContent = function( sText )
6046     if( !this.aTextPlaceholderElement )
6047     {
6048         log( 'PlaceholderShape.setTextContent: text element is not valid in placeholder of type '
6049              + this.className + ' that belongs to master slide ' + this.masterPage.id );
6050         return;
6051     }
6052     this.aTextPlaceholderElement.textContent = sText;
6055 /** update
6056  *  Update the content of the handled text field. The new content is provided
6057  *  directly from the TextContentProvider data member.
6058  */
6059 TextFieldHandler.prototype.update = function()
6061     if( !this.aTextContentProvider )
6062         log('TextFieldHandler.update: text content provider not defined.');
6063     else
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
6073  *      to be cloned.
6074  *  @param aTextContentProvider
6075  *      A SlideNumberProvider object to which the actual content updating is
6076  *      demanded.
6077  */
6078 function SlideNumberFieldHandler( aPlaceholderShape, aTextContentProvider )
6080     SlideNumberFieldHandler.superclass.constructor.call( this, aPlaceholderShape, aTextContentProvider );
6082 extend( SlideNumberFieldHandler, TextFieldHandler );
6084 /*** public methods ***/
6086 /** update
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.
6091  */
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.');
6099     else
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
6108  * of text fields.
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.
6118  */
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.
6138  *  @param aText
6139  *      a string containing the text to be substituted.
6140  */
6141 function FixedTextProvider( aText )
6143     FixedTextProvider.superclass.constructor.call( this );
6144     this.text = aText;
6146 extend( FixedTextProvider, TextFieldContentProvider );
6148 /*** public methods ***/
6150 /** update
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.
6156  */
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.
6169  */
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.
6183  */
6184 function CurrentDateTimeProvider( aTextFieldContentElement, sDateTimeFormat )
6186     CurrentDateTimeProvider.superclass.constructor.call( this, aTextFieldContentElement );
6187     if( aTextFieldContentElement )
6188         this.dateTimeFormat = getOOOAttribute( aTextFieldContentElement, aOOOAttrDateTimeFormat );
6189     else
6190     {
6191         this.dateTimeFormat = sDateTimeFormat;
6192     }
6194 extend( CurrentDateTimeProvider, TextFieldContentProvider );
6196 /*** public methods ***/
6198 /** update
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.
6204  */
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
6216     var sDate;
6217     if( this.dateTimeFormat === '<date>' )
6218         sDate = new Date().toLocaleDateString();
6219     else if( this.dateTimeFormat === '<time>' )
6220         sDate = new Date().toLocaleTimeString();
6221     else
6222         sDate = new Date().toLocaleDateString();
6223     return sDate;
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.
6229  */
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
6243  *  @return
6244  *      The page numbering type.
6245  */
6246 SlideNumberProvider.prototype.getNumberingType = function()
6248     return this.pageNumberingType;
6251 /** update
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.
6259  */
6261 SlideNumberProvider.prototype.update = function( aSlideNumberField, nSlideNumber )
6263     if( nSlideNumber === undefined )
6264     {
6265         if( nCurSlide === undefined )
6266             nSlideNumber = this.nInitialSlideNumber;
6267         else
6268             nSlideNumber = nCurSlide + 1;
6269     }
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
6291  */
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 )
6328     {
6329         this.aThumbnailSet[i] = new Thumbnail( this, i );
6330         this.aThumbnailSet[i].updateView();
6331     }
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' );
6353 /** setSelection
6355  * Change the selected thumbnail from the current one to the thumbnail with index nIndex.
6357  * @param nIndex - the thumbnail index
6358  */
6359 SlideIndexPage.prototype.setSelection = function( nIndex )
6361     nIndex = getSafeIndex( nIndex, 0, this.getTotalThumbnails() - 1 );
6362     if( this.curThumbnailIndex != nIndex )
6363     {
6364         this.aThumbnailSet[ this.curThumbnailIndex ].unselect();
6365         this.aThumbnailSet[ nIndex ].select();
6366         this.curThumbnailIndex = nIndex;
6367     }
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
6449  */
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
6464     var i;
6465     for( i = this.totalThumbnails; i < nOldTotalThumbnails; ++i )
6466     {
6467         this.aThumbnailSet[i].removeElement();
6468     }
6470     // if we increased the number of used columns we create the needed thumbnail objects
6471     for( i = nOldTotalThumbnails; i < this.totalThumbnails; ++i )
6472     {
6473         this.aThumbnailSet[i] = new Thumbnail( this, i );
6474     }
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 )
6496     {
6497         this.aThumbnailSet[i].updateView();
6498     }
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
6504     INDEX_OFFSET = -1;
6505     indexSetPageSlide( this.selectedSlideIndex );
6507     ROOT_NODE.unsuspendRedraw( suspendHandle );
6508     ROOT_NODE.forceRedraw();
6512 /** Class Thumbnail **
6513  *  This class handles a slide thumbnail.
6514  */
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 )
6544     {
6545         this.thumbnailElement.setAttribute( 'display', 'inherit' );
6546         this.visibility = VISIBLE;
6547     }
6550 Thumbnail.prototype.hide = function()
6552     if( this.visibility == VISIBLE )
6553     {
6554         this.thumbnailElement.setAttribute( 'display', 'none' );
6555         this.visibility = HIDDEN;
6556     }
6559 Thumbnail.prototype.select = function()
6561     if( !this.isSelected )
6562     {
6563         this.borderElement.setAttribute( 'stroke', this.sSelectionBorderColor );
6564         this.isSelected = true;
6565     }
6568 Thumbnail.prototype.unselect = function()
6570     if( this.isSelected )
6571     {
6572         this.borderElement.setAttribute( 'stroke', this.sNormalBorderColor );
6573         this.isSelected = false;
6574     }
6577 /** updateView
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.
6583  */
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()' );
6599 /** update
6601  * This method update the content of the thumbnail view
6603  * @param nIndex - the index of the slide to be shown in the thumbnail
6604  */
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( ' ' );
6660     return sTransform;
6663 Thumbnail.prototype.onMouseOver = function()
6665     if( ( currentMode == INDEX_MODE ) && ( this.container.curThumbnailIndex !=  this.index ) )
6666     {
6667         this.container.setSelection( this.index );
6668     }
6675 /** Initialization function.
6676  *  The whole presentation is set-up in this function.
6677  */
6678 function init()
6680     var VIEWBOX = ROOT_NODE.getAttribute('viewBox');
6682     if( VIEWBOX )
6683     {
6684         WIDTH = ROOT_NODE.viewBox.animVal.width;
6685         HEIGHT = ROOT_NODE.viewBox.animVal.height;
6686     }
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);
6699     });
6700     hammer.on('swiperight', function() {
6701         switchSlide(-1, false);
6702     });
6703     hammer.get('swipe').set({ direction: Hammer.DIRECTION_ALL });
6704     hammer.on('swipeup', function() {
6705         aSlideShow.exitSlideShowInApp();
6706     });
6707     hammer.on('swipedown', function() {
6708         aSlideShow.exitSlideShowInApp();
6709     });
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 )
6723     if (!condition)
6724     {
6725         presentationEngineStop( message );
6726         if (typeof console == 'object')
6727             // eslint-disable-next-line no-console
6728             console.trace();
6729         throw new Error( message );
6730     }
6733 function dispatchEffects(dir)
6735     // TODO to be implemented
6737     if( dir == 1 )
6738     {
6739         var bRet = aSlideShow.nextEffect();
6741         if( !bRet )
6742         {
6743             switchSlide( 1, false );
6744         }
6745     }
6746     else
6747     {
6748         switchSlide( dir, false );
6749     }
6752 function skipAllEffects()
6754     var bRet = aSlideShow.skipAllEffects();
6755     if( !bRet )
6756     {
6757         switchSlide( 1, true );
6758     }
6761 function skipEffects(dir)
6763     if( dir == 1 )
6764     {
6765         var bRet = aSlideShow.skipPlayingOrNextEffect();
6767         if( !bRet )
6768         {
6769             switchSlide( 1, true );
6770         }
6771     }
6772     else
6773     {
6774         switchSlide( dir, true );
6775     }
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
6787  */
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;
6797     var j = 0;
6798     for( var i = offsetNumber; i < nEnd; ++i, ++j )
6799     {
6800         aThumbnailSet[j].update( i );
6801         aThumbnailSet[j].show();
6802     }
6803     for( ; j < nTotalThumbnails; ++j )
6804     {
6805         aThumbnailSet[j].hide();
6806     }
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.
6814  */
6815 function toggleSlideIndex()
6817     if( currentMode == SLIDE_MODE )
6818     {
6820         theMetaDoc.getCurrentSlide().hide();
6821         INDEX_OFFSET = -1;
6822         indexSetPageSlide( nCurSlide );
6823         theSlideIndexPage.show();
6824         currentMode = INDEX_MODE;
6825     }
6826     else if( currentMode == INDEX_MODE )
6827     {
6828         theSlideIndexPage.hide();
6829         var nNewSlide = theSlideIndexPage.selectedSlideIndex;
6831         aSlideShow.displaySlide( nNewSlide, true );
6832         currentMode = SLIDE_MODE;
6833     }
6836 /** Function that exit from the index mode without changing the shown slide
6838  */
6839 function abandonIndexMode()
6841     theSlideIndexPage.selectedSlideIndex = nCurSlide;
6842     toggleSlideIndex();
6849 /*********************************************************************************************
6850  *********************************************************************************************
6851  *********************************************************************************************
6853                                   ***** ANIMATION ENGINE *****
6855  *********************************************************************************************
6856  *********************************************************************************************
6857  *********************************************************************************************/
6864 // helper functions
6867 var CURR_UNIQUE_ID = 0;
6869 function getUniqueId()
6871     ++CURR_UNIQUE_ID;
6872     return CURR_UNIQUE_ID;
6875 function mem_fn( sMethodName )
6877     return  function( aObject )
6878     {
6879         var aMethod = aObject[ sMethodName ];
6880         if( aMethod )
6881             aMethod.call( aObject );
6882         else
6883             log( 'method sMethodName not found' );
6884     };
6887 function bind( aObject, aMethod )
6889     return  function()
6890     {
6891         return aMethod.call( aObject, arguments[0] );
6892     };
6895 function bind2( aFunction )
6897     if( !aFunction  )
6898         log( 'bind2: passed function is not valid.' );
6900     var aBoundArgList = arguments;
6902     var aResultFunction = null;
6904     switch( aBoundArgList.length )
6905     {
6906         case 1: aResultFunction = function()
6907                 {
6908                     return aFunction.call( arguments[0], arguments[1],
6909                                            arguments[2], arguments[3],
6910                                            arguments[4] );
6911                 };
6912                 break;
6913         case 2: aResultFunction = function()
6914                 {
6915                     return aFunction.call( aBoundArgList[1], arguments[0],
6916                                            arguments[1], arguments[2],
6917                                            arguments[3] );
6918                 };
6919                 break;
6920         case 3: aResultFunction = function()
6921                 {
6922                     return aFunction.call( aBoundArgList[1], aBoundArgList[2],
6923                                            arguments[0], arguments[1],
6924                                            arguments[2] );
6925                 };
6926                 break;
6927         case 4: aResultFunction = function()
6928                 {
6929                     return aFunction.call( aBoundArgList[1], aBoundArgList[2],
6930                                            aBoundArgList[3], arguments[0],
6931                                            arguments[1] );
6932                 };
6933                 break;
6934         case 5: aResultFunction = function()
6935                 {
6936                     return aFunction.call( aBoundArgList[1], aBoundArgList[2],
6937                                            aBoundArgList[3], aBoundArgList[4],
6938                                            arguments[0] );
6939                 };
6940                 break;
6941         default:
6942             log( 'bind2: arity not handled.' );
6943     }
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
6964  *  @returns   Array
6965  *      an array that contains all children elements
6966  */
6967 function getElementChildren( aElement )
6969     var aChildrenArray = [];
6971     var nSize = aElement.childNodes.length;
6973     for( var i = 0; i < nSize; ++i )
6974     {
6975         if( aElement.childNodes[i].nodeType == 1 )
6976             aChildrenArray.push( aElement.childNodes[i] );
6977     }
6979     return aChildrenArray;
6982 function removeWhiteSpaces( str )
6984     if( !str )
6985         return '';
6987     var re = / */;
6988     var aSplitString = str.split( re );
6989     return aSplitString.join('');
6992 function clamp( nValue, nMinimum, nMaximum )
6994     if( nValue < nMinimum )
6995     {
6996         return nMinimum;
6997     }
6998     else if( nValue > nMaximum )
6999     {
7000         return nMaximum;
7001     }
7002     else
7003     {
7004         return nValue;
7005     }
7008 function makeMatrixString( a, b, c, d, e, f )
7010     var s = 'matrix(';
7011     s += a + ', ';
7012     s += b + ', ';
7013     s += c + ', ';
7014     s += d + ', ';
7015     s += e + ', ';
7016     s += f + ')';
7018     return s;
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' )
7037         return undefined;
7038     if( sValue === '.' )
7039         return undefined;
7040     var reFloatNumber = /^[+-]?[0-9]*[.]?[0-9]*$/;
7042     if( reFloatNumber.test( sValue ) )
7043         return parseFloat( sValue );
7044     else
7045         return undefined;
7048 function booleanParser( sValue )
7050     if( typeof sValue !== 'string' )
7051         return undefined;
7053     sValue = sValue.toLowerCase();
7054     if( sValue === 'true' )
7055         return true;
7056     else if( sValue === 'false' )
7057         return false;
7058     else
7059         return undefined;
7062 function colorParser( sValue )
7064     if( typeof sValue !== 'string' )
7065         return undefined;
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 )
7072     {
7073         return new HSLColor( nHue, nSaturation / 100, nLuminance / 100 );
7074     }
7076     // eslint-disable-next-line no-unused-vars
7077     function rgb( nRed, nGreen, nBlue )
7078     {
7079         return new RGBColor( nRed / 255, nGreen / 255, nBlue / 255 );
7080     }
7082     // eslint-disable-next-line no-unused-vars
7083     function prgb( nRed, nGreen, nBlue )
7084     {
7085         return new RGBColor( nRed / 100, nGreen / 100, nBlue / 100 );
7086     }
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 ) )
7114     {
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 );
7122     }
7123     else if( reHSLPercent.test( sValue ) )
7124     {
7125         sValue = sValue.replace( '%', '' ).replace( '%', '' );
7126         return eval( sValue );
7127     }
7128     else if( reRGBInteger.test( sValue ) )
7129     {
7130         return eval( sValue );
7131     }
7132     else if( reRGBPercent.test( sValue ) )
7133     {
7134         sValue = 'p' + sValue.replace( '%', '' ).replace( '%', '' ).replace( '%', '' );
7135         return eval( sValue );
7136     }
7137     else
7138     {
7139         return null;
7140     }
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
7154     this.nRed = nRed;
7155     this.nGreen = nGreen;
7156     this.nBlue = nBlue;
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;
7177     return this;
7180 RGBColor.prototype.scale = function( aT )
7182     this.nRed *= aT;
7183     this.nGreen *= aT;
7184     this.nBlue *= aT;
7185     return this;
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;
7211     var nHue = 0.0;
7212     if( nDelta !== 0 )
7213     {
7214         nSaturation = ( nLuminance > 0.5 ) ?
7215                             ( nDelta / ( 2.0 - nMax - nMin) ) :
7216                             ( nDelta / ( nMax + nMin ) );
7218         if( nRed == nMax )
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;
7225         nHue *= 60.0;
7227         if( nHue < 0.0 )
7228             nHue += 360.0;
7229     }
7231     return new HSLColor( nHue, nSaturation, nLuminance );
7235 RGBColor.prototype.toString = function( bClamped )
7237     var aRGBColor;
7238     if( bClamped )
7239     {
7240         aRGBColor = RGBColor.clamp( this );
7241     }
7242     else
7243     {
7244         aRGBColor = this;
7245     }
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 );
7262     return aResult;
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
7272     this.nHue = nHue;
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();
7298     return this;
7301 HSLColor.prototype.scale = function( aT )
7303     this.nHue *= aT;
7304     this.nSaturation *= aT;
7305     this.nLuminance *= aT;
7306     this.normalizeHue();
7307     return this;
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 )
7344     {
7345         return new RGBColor( nLuminance, nLuminance, nLuminance );
7346     }
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 )
7363     nHue = nHue % 360;
7364     if( nHue < 0 )
7365         nHue += 360;
7367     if( nHue < 60.0 )
7368         return nValue1 + ( nValue2 - nValue1 ) * nHue / 60.0;
7369     else if( nHue < 180.0 )
7370         return nValue2;
7371     else if( nHue < 240.0 )
7372         return ( nValue1 + ( nValue2 - nValue1 ) * ( 240.0 - nHue ) / 60.0 );
7373     else
7374         return nValue1;
7377 HSLColor.interpolate = function( aFrom, aTo, nT, bCCW )
7379     var nS = 1.0 - nT;
7381     var nHue = 0.0;
7382     if( aFrom.nHue <= aTo.nHue && !bCCW )
7383     {
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;
7391     }
7392     else if( aFrom.nHue > aTo.nHue && bCCW )
7393     {
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);
7401     }
7402     else
7403     {
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;
7409     }
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.
7451  *  @param aPath
7452  *      An object of type SVGPathElement to be prepended.
7453  */
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.
7464  *  @param aPath
7465  *      An object of type SVGPathElement to be appended.
7466  */
7467 SVGPathElement.prototype.appendPath = function( aPath )
7469     var sPathData = this.getAttribute( 'd' );
7470     sPathData += ( ' ' + aPath.getAttribute( 'd' ) );
7471     this.setAttribute( 'd', sPathData );
7474 /** flipOnYAxis
7475  *  Flips the SVG Path element along y-axis.
7477  *  @param aPath
7478  *      An object of type SVGPathElement to be flipped.
7479  */
7480 function flipOnYAxis( aPath )
7482     var aPolyPath = aPath.cloneNode(true);
7483     var aTransform = document.documentElement.createSVGMatrix();
7484     aTransform.a = -1;
7485     aTransform.e = 1;
7486     aPolyPath.matrixTransform(aTransform);
7487     return aPolyPath;
7490 /** flipOnXAxis
7491  *  Flips the SVG Path element along x-axis
7493  *  @param aPath
7494  *      An object of type SVGPathElement to be flipped
7495  */
7496 function flipOnXAxis( aPath )
7498     var aPolyPath = aPath.cloneNode(true);
7499     var aTransform = document.documentElement.createSVGMatrix();
7500     aTransform.d = -1;
7501     aTransform.f = 1;
7502     aPolyPath.matrixTransform(aTransform);
7503     return aPolyPath;
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.
7512  *  @param aSVGMatrix
7513  *      An SVGMatrix instance.
7514  */
7515 SVGPathElement.prototype.matrixTransform = function( aSVGMatrix )
7517     if( SVGPathSegList.prototype.matrixTransform )
7518     {
7519         this.pathSegList.matrixTransform( aSVGMatrix );
7520         return;
7521     }
7523     var aPathSegList = this.pathSegList;
7524     var nLength = aPathSegList.numberOfItems;
7525     var i;
7526     for( i = 0; i < nLength; ++i )
7527     {
7528         aPathSegList.getItem( i ).matrixTransform( aSVGMatrix );
7529     }
7532 /** SVGPathElement.changeOrientation
7533  *  Invert the path orientation by inverting the path command list.
7535  */
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 )
7543     {
7544         aPathSegList.changeOrientation();
7545         return;
7546     }
7548     var nCurrentX = 0;
7549     var nCurrentY = 0;
7551     var aPathSeg = aPathSegList.getItem( 0 );
7552     if( aPathSeg.pathSegTypeAsLetter == 'M' )
7553     {
7554         nCurrentX = aPathSeg.x;
7555         nCurrentY = aPathSeg.y;
7556         aPathSegList.removeItem( 0 );
7557         --nLength;
7558     }
7560     var i;
7561     for( i = 0; i < nLength; ++i )
7562     {
7563         aPathSeg = aPathSegList.getItem( i );
7564         var aPoint = aPathSeg.changeOrientation( nCurrentX, nCurrentY );
7565         nCurrentX = aPoint.x;
7566         nCurrentY = aPoint.y;
7567     }
7570     for( i = nLength - 2; i >= 0; --i )
7571     {
7572         aPathSeg = aPathSegList.removeItem( i );
7573         aPathSegList.appendItem( aPathSeg );
7574     }
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.
7587  */
7591 {   // Firefox, Google Chrome, Internet Explorer, Safari.
7593     SVGPathSegMovetoAbs.prototype.matrixTransform = function( aSVGMatrix )
7594     {
7595         SVGPathMatrixTransform( this, aSVGMatrix );
7596     };
7598     SVGPathSegLinetoAbs.prototype.matrixTransform = function( aSVGMatrix )
7599     {
7600         SVGPathMatrixTransform( this, aSVGMatrix );
7601     };
7603     SVGPathSegCurvetoQuadraticAbs.prototype.matrixTransform = function( aSVGMatrix )
7604     {
7605         SVGPathMatrixTransform( this, aSVGMatrix );
7606         var nX = this.x1;
7607         this.x1 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y1 + aSVGMatrix.e;
7608         this.y1 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y1 + aSVGMatrix.f;
7609     };
7611     SVGPathSegCurvetoCubicAbs.prototype.matrixTransform = function( aSVGMatrix )
7612     {
7613         SVGPathMatrixTransform( this, aSVGMatrix );
7614         var nX = this.x1;
7615         this.x1 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y1 + aSVGMatrix.e;
7616         this.y1 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y1 + aSVGMatrix.f;
7617         nX = this.x2;
7618         this.x2 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y2 + aSVGMatrix.e;
7619         this.y2 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y2 + aSVGMatrix.f;
7620     };
7623     SVGPathSegMovetoAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7624     {
7625         var aPoint = { x: this.x, y: this.y };
7626         this.x = nCurrentX;
7627         this.y = nCurrentY;
7628         return aPoint;
7629     };
7631     SVGPathSegLinetoAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7632     {
7633         var aPoint = { x: this.x, y: this.y };
7634         this.x = nCurrentX;
7635         this.y = nCurrentY;
7636         return aPoint;
7637     };
7639     SVGPathSegCurvetoQuadraticAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7640     {
7641         var aPoint = { x: this.x, y: this.y };
7642         this.x = nCurrentX;
7643         this.y = nCurrentY;
7644         return aPoint;
7645     };
7647     SVGPathSegCurvetoCubicAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7648     {
7649         var aPoint = { x: this.x, y: this.y };
7650         this.x = nCurrentX;
7651         this.y = nCurrentY;
7652         var nX = this.x1;
7653         this.x1 = this.x2;
7654         this.x2 = nX;
7655         var nY = this.y1;
7656         this.y1 = this.y2;
7657         this.y2 = nY;
7658         return aPoint;
7659     };
7662 catch( e )
7663 {   // Opera
7665     if( e.name == 'ReferenceError' )
7666     {
7667         SVGPathSeg.prototype.matrixTransform = function( aSVGMatrix )
7668         {
7669             var nX;
7670             switch( this.pathSegTypeAsLetter )
7671             {
7672                 case 'C':
7673                     nX = this.x2;
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
7677                 case 'Q':
7678                     nX = this.x1;
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
7682                 case 'M':
7683                 case 'L':
7684                     SVGPathMatrixTransform( this, aSVGMatrix );
7685                     break;
7686                 default:
7687                     log( 'SVGPathSeg.matrixTransform: unexpected path segment type: '
7688                              + this.pathSegTypeAsLetter );
7689             }
7690         };
7692         SVGPathSeg.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7693         {
7694             switch( this.pathSegTypeAsLetter )
7695             {
7696                 case 'C':
7697                     var nX = this.x1;
7698                     this.x1 = this.x2;
7699                     this.x2 = nX;
7700                     var nY = this.y1;
7701                     this.y1 = this.y2;
7702                     this.y2 = nY;
7703                 // fall through intended
7704                 case 'M':
7705                 case 'L':
7706                 case 'Q':
7707                     var aPoint = { x: this.x, y: this.y };
7708                     this.x = nCurrentX;
7709                     this.y = nCurrentY;
7710                     return aPoint;
7711                 default:
7712                     log( 'SVGPathSeg.changeOrientation: unexpected path segment type: '
7713                              + this.pathSegTypeAsLetter );
7714                     return null;
7715             }
7716         }
7717     }
7718     else throw e;
7721 function SVGPathMatrixTransform( aPath, aSVGMatrix )
7723     var nX = aPath.x;
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;
7743     var dest = [];
7744     var i, l;
7745     for( i = 0, l = src.length; i < l; ++i )
7746     {
7747         if( i in src )
7748         {
7749             dest.push( src[i] );
7750         }
7751     }
7752     aCopy.aSequence = dest;
7753     return aCopy;
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  **********************************************************************************************/
7791 // Node Types
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 ];
7828     else
7829         return ANIMATION_NODE_CUSTOM;
7836 // Node States
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 )
7847     {
7848         case INVALID_NODE:
7849             return 'INVALID';
7850         case UNRESOLVED_NODE:
7851             return 'UNRESOLVED';
7852         case RESOLVED_NODE:
7853             return 'RESOLVED';
7854         case ACTIVE_NODE:
7855             return 'ACTIVE';
7856         case FROZEN_NODE:
7857             return 'FROZEN';
7858         case ENDED_NODE:
7859             return 'ENDED';
7860         default:
7861             return 'UNKNOWN';
7862     }
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' ];
7888 // Preset Classes
7889 var aPresetClassInMap = {};
7892 // Preset Ids
7893 var aPresetIdInMap = {};
7896 // Restart Modes
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' ];
7913 // Fill Modes
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' ];
7934 // Additive Modes
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' ];
7953 // Accumulate Modes
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' ];
7975 // Color Spaces
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' ];
7984 // Clock Directions
7985 var CLOCKWISE               = 0;
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' ];
8005 // Attribute Map
8006 var aAttributeMap =
8008         'height':           {   'type':         NUMBER_PROPERTY,
8009                                 'get':          'getHeight',
8010                                 'set':          'setHeight',
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,
8019                                 'get':          'getSize',
8020                                 'set':          'setSize'                       },
8022         'translate':       {   'type':          TUPLE_NUMBER_PROPERTY,
8023                                 'get':          'getPos',
8024                                 'set':          'setPos'                        },
8026         'rotate':           {   'type':         NUMBER_PROPERTY,
8027                                 'get':          'getRotationAngle',
8028                                 'set':          'setRotationAngle'              },
8030         'width':            {   'type':         NUMBER_PROPERTY,
8031                                 'get':          'getWidth',
8032                                 'set':          'setWidth',
8033                                 'getmod':       'makeScaler( 1/nWidth )',
8034                                 'setmod':       'makeScaler( nWidth)'           },
8036         'x':                {   'type':         NUMBER_PROPERTY,
8037                                 'get':          'getX',
8038                                 'set':          'setX',
8039                                 'getmod':       'makeScaler( 1/nWidth )',
8040                                 'setmod':       'makeScaler( nWidth)'           },
8042         'y':                {   'type':         NUMBER_PROPERTY,
8043                                 'get':          'getY',
8044                                 'set':          'setY',
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.
8084  */
8086 // Transition Types
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.
8145  */
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
8333 // Transition Modes
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] = {};
8370 // subtype: default
8371 aTransitionInfoTable[0][0] =
8373     'class' : TRANSITION_INVALID,
8374     'rotationAngle' : 0.0,
8375     'scaleX' : 0.0,
8376     'scaleY' : 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,
8387     'scaleX' : 1.0,
8388     'scaleY' : 1.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,
8397     'scaleX' : 1.0,
8398     'scaleY' : 1.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,
8407     'scaleX' : 1.0,
8408     'scaleY' : 1.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,
8417     'scaleX' : 1.0,
8418     'scaleY' : 1.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,
8427     'scaleX' : 1.0,
8428     'scaleY' : 1.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,
8437     'scaleX' : 1.0,
8438     'scaleY' : 1.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,
8449     'scaleX' : 1.0,
8450     'scaleY' : 1.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,
8459     'scaleX' : 1.0,
8460     'scaleY' : 1.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,
8469     'scaleX' : 1.0,
8470     'scaleY' : 1.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,
8479     'scaleX' : 1.0,
8480     'scaleY' : 1.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,
8489     'scaleX' : 1.0,
8490     'scaleY' : 1.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,
8499     'scaleX' : 1.0,
8500     'scaleY' : 1.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,
8509     'scaleX' : 1.0,
8510     'scaleY' : 1.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,
8519     'scaleX' : 1.0,
8520     'scaleY' : 1.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,
8529     'scaleX' : 1.0,
8530     'scaleY' : 1.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,
8539     'scaleX' : 1.0,
8540     'scaleY' : 1.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,
8551     'scaleX' : 1.0,
8552     'scaleY' : 1.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,
8561     'scaleX' : 1.0,
8562     'scaleY' : 1.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,
8571     'scaleX' : 1.0,
8572     'scaleY' : 1.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,
8581     'scaleX' : 1.0,
8582     'scaleY' : 1.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,
8591     'scaleX' : 1.0,
8592     'scaleY' : 1.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,
8601     'scaleX' : 1.0,
8602     'scaleY' : 1.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,
8611     'scaleX' : 1.0,
8612     'scaleY' : 1.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,
8621     'scaleX' : 1.0,
8622     'scaleY' : 1.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,
8633     'scaleX' : 1.0,
8634     'scaleY' : 1.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,
8643     'scaleX' : 1.0,
8644     'scaleY' : 1.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,
8653     'scaleX' : 1.0,
8654     'scaleY' : 1.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,
8663     'scaleX' : 1.0,
8664     'scaleY' : 1.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,
8673     'scaleX' : 1.0,
8674     'scaleY' : 1.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,
8683     'scaleX' : 1.0,
8684     'scaleY' : 1.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,
8695     'scaleX': 1.0,
8696     'scaleY': 1.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,
8705     'scaleX': 1.0,
8706     'scaleY': 1.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,
8747     'scaleX': 1,
8748     'scaleY': 1,
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,
8759     'scaleX': 1.0,
8760     'scaleY': 1.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,
8782     'scaleX' : 1.0,
8783     'scaleY' : 1.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,
8792     'scaleX' : 1.0,
8793     'scaleY' : 1.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,
8804     'scaleX' : 1.0,
8805     'scaleY' : 1.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,
8814     'scaleX' : 1.0,
8815     'scaleY' : 1.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,
8826     'scaleX' : 1.0,
8827     'scaleY' : 1.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,
8836     'scaleX' : 1.0,
8837     'scaleY' : 1.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,
8848     'scaleX' : 1.0,
8849     'scaleY' : 1.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,
8858     'scaleX' : 1.0,
8859     'scaleY' : 1.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,
8868     'scaleX' : 1.0,
8869     'scaleY' : 1.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,
8878     'scaleX' : 1.0,
8879     'scaleY' : 1.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,
8890     'scaleX' : 1.0,
8891     'scaleY' : 1.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,
8900     'scaleX' : 1.0,
8901     'scaleY' : 1.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,
8910     'scaleX' : 1.0,
8911     'scaleY' : 1.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,
8920     'scaleX' : 1.0,
8921     'scaleY' : 1.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,
8930     'scaleX' : 1.0,
8931     'scaleY' : 1.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,
8940     'scaleX' : 1.0,
8941     'scaleY' : 1.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,
8950     'scaleX' : 1.0,
8951     'scaleY' : 1.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,
8960     'scaleX' : 1.0,
8961     'scaleY' : 1.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,
8973     'scaleX' : 1.0,
8974     'scaleY' : 1.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,
8985     'scaleX' : 1.0,
8986     'scaleY' : 1.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,
8995     'scaleX' : 1.0,
8996     'scaleY' : 1.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,
9005     'scaleX' : 1.0,
9006     'scaleY' : 1.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,
9017     'scaleX' : 1.0,
9018     'scaleY' : 1.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,
9027     'scaleX' : 1.0,
9028     'scaleY' : 1.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,
9037     'scaleX' : 1.0,
9038     'scaleY' : 1.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,
9047     'scaleX' : 1.0,
9048     'scaleY' : 1.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,
9059     'scaleX' : 1.0,
9060     'scaleY' : 1.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,
9069     'scaleX' : 1.0,
9070     'scaleY' : 1.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,
9079     'scaleX' : 1.0,
9080     'scaleY' : 1.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,
9089     'scaleX' : 1.0,
9090     'scaleY' : 1.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,
9101     'scaleX' : 1.0,
9102     'scaleY' : 1.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,
9111     'scaleX' : 1.0,
9112     'scaleY' : 1.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,
9121     'scaleX' : 1.0,
9122     'scaleY' : 1.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,
9131     'scaleX' : 1.0,
9132     'scaleY' : 1.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,
9141     'scaleX' : 1.0,
9142     'scaleY' : 1.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,
9151     'scaleX' : 1.0,
9152     'scaleY' : 1.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,
9168     'scaleX' : 1.0,
9169     'scaleY' : 1.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,
9178     'scaleX' : 1.0,
9179     'scaleY' : 1.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,
9193     'scaleX' : 1.0,
9194     'scaleY' : 1.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,
9206     'scaleX' : 1.0,
9207     'scaleY' : 1.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,
9216     'scaleX' : 1.0,
9217     'scaleY' : 1.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,
9226     'scaleX' : 1.0,
9227     'scaleY' : 1.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,
9236     'scaleX' : 1.0,
9237     'scaleY' : 1.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,
9246     'scaleX' : 1.0,
9247     'scaleY' : 1.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,
9256     'scaleX' : 1.0,
9257     'scaleY' : 1.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,
9266     'scaleX' : 1.0,
9267     'scaleY' : 1.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,
9276     'scaleX' : 1.0,
9277     'scaleY' : 1.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,
9291     'scaleX' : 1.0,
9292     'scaleY' : 1.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,
9306     'scaleX' : 1.0,
9307     'scaleY' : 1.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,
9319     'scaleX' : 1.0,
9320     'scaleY' : 1.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,
9329     'scaleX' : 1.0,
9330     'scaleY' : 1.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,
9341     'scaleX' : 1.0,
9342     'scaleY' : 1.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,
9351     'scaleX' : 1.0,
9352     'scaleY' : 1.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,
9363     'scaleX' : 1.0,
9364     'scaleY' : 1.0,
9365     'reverseMethod' : REVERSEMETHOD_IGNORE,
9366     'outInvertsSweep' : true,
9367     'scaleIsotropically' : true
9371 // Transition tables
9373 function createStateTransitionTable()
9375     var aSTT = {};
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
9382     var aTable =
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
9392     aTable =
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
9404     aTable =
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
9414     aTable =
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
9426     aTable =
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
9436     aTable =
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
9447     return aSTT;
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 )
9459     {
9460         log( 'getTransitionTable: unexpected restart mode: ' + eRestartMode
9461                  + '. Used NEVER instead.');
9462         eRestartMode = RESTART_MODE_NEVER;
9463     }
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 )
9469     {
9470         eFillMode = FILL_MODE_REMOVE;
9471     }
9473     return aStateTransitionTable[eRestartMode][eFillMode];
9480 // Event Triggers
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;
9510     else
9511         return EVENT_TRIGGER_UNKNOWN;
9518 // Timing Types
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' ];
9530 // Char Codes
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 )
9576     {
9577         this.eTimingType = OFFSET_TIMING;
9578         return;
9579     }
9581     if( this.sTimingDescription == 'indefinite' )
9582         this.eTimingType = INDEFINITE_TIMING;
9583     else
9584     {
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 ) ) )
9590         {
9591             var sClockValue
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 )
9598             {
9599                 this.eTimingType = OFFSET_TIMING;
9600                 this.nOffset = bPositiveOffset ? TimeInSec : -TimeInSec;
9601             }
9602         }
9603         else
9604         {
9605             var aTimingSplit = [];
9606             bPositiveOffset = true;
9607             if( this.sTimingDescription.indexOf( '+' ) != -1 )
9608             {
9609                 aTimingSplit = this.sTimingDescription.split( '+' );
9610             }
9611             else if( this.sTimingDescription.indexOf( '-' ) != -1 )
9612             {
9613                 aTimingSplit = this.sTimingDescription.split( '-' );
9614                 bPositiveOffset = false;
9615             }
9616             else
9617             {
9618                 aTimingSplit[0] = this.sTimingDescription;
9619                 aTimingSplit[1] = '';
9620             }
9622             if( aTimingSplit[0].indexOf( '.' ) != -1 )
9623             {
9624                 var aEventSplit = aTimingSplit[0].split( '.' );
9625                 this.sEventBaseElementId = aEventSplit[0];
9626                 this.eEventType = getEventTriggerType( aEventSplit[1] );
9627             }
9628             else
9629             {
9630                 this.eEventType = getEventTriggerType( aTimingSplit[0] );
9631             }
9633             if( this.eEventType == EVENT_TRIGGER_UNKNOWN )
9634                 return;
9636             if( ( this.eEventType == EVENT_TRIGGER_BEGIN_EVENT ) ||
9637                 ( this.eEventType == EVENT_TRIGGER_END_EVENT ) )
9638             {
9639                 this.eTimingType = SYNCBASE_TIMING;
9640             }
9641             else
9642             {
9643                 this.eTimingType = EVENT_TIMING;
9644             }
9646             if( aTimingSplit[1] )
9647             {
9648                 sClockValue = aTimingSplit[1];
9649                 TimeInSec = Timing.parseClockValue( sClockValue );
9650                 if( TimeInSec != undefined )
9651                 {
9652                     this.nOffset = ( bPositiveOffset ) ? TimeInSec : -TimeInSec;
9653                 }
9654                 else
9655                 {
9656                     this.eTimingType = UNKNOWN_TIMING;
9657                 }
9659             }
9660         }
9661     }
9665 Timing.parseClockValue = function( sClockValue )
9667     if( !sClockValue )
9668         return 0.0;
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 ) )
9677     {
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;
9688     }
9689     else if( rePartialClockValue.test( sClockValue ) )
9690     {
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;
9699     }
9700     else if( reTimeCountValue.test( sClockValue ) )
9701     {
9702         aClockTimeParts = reTimeCountValue.exec( sClockValue );
9704         var nTimeCount = parseInt( aClockTimeParts[1] );
9705         if( aClockTimeParts[2] )
9706             nTimeCount += parseFloat( aClockTimeParts[2] );
9708         if( aClockTimeParts[3] )
9709         {
9710             if( aClockTimeParts[3] == 'h' )
9711             {
9712                 nTimeInSec = nTimeCount * 3600;
9713             }
9714             else if( aClockTimeParts[3] == 'min' )
9715             {
9716                 nTimeInSec = nTimeCount * 60;
9717             }
9718             else if( aClockTimeParts[3] == 's' )
9719             {
9720                 nTimeInSec = nTimeCount;
9721             }
9722             else if( aClockTimeParts[3] == 'ms' )
9723             {
9724                 nTimeInSec = nTimeCount / 1000;
9725             }
9726         }
9727         else
9728         {
9729             nTimeInSec = nTimeCount;
9730         }
9732     }
9734     if( nTimeInSec )
9735         nTimeInSec = parseFloat( nTimeInSec.toFixed( 3 ) );
9736     return nTimeInSec;
9739 Timing.prototype.info = function( bVerbose )
9742     var sInfo = '';
9744     if( bVerbose )
9745     {
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() ];
9752     }
9753     else
9754     {
9755         switch( this.getType() )
9756         {
9757             case INDEFINITE_TIMING:
9758                 sInfo += 'indefinite';
9759                 break;
9760             case OFFSET_TIMING:
9761                 sInfo += this.getOffset();
9762                 break;
9763             case EVENT_TIMING:
9764             case SYNCBASE_TIMING:
9765                 if( this.getEventBaseElementId() )
9766                     sInfo += this.getEventBaseElementId() + '.';
9767                 sInfo += aEventTriggerOutMap[ this.getEventType() ];
9768                 if( this.getOffset() )
9769                 {
9770                     if( this.getOffset() > 0 )
9771                         sInfo += '+';
9772                     sInfo += this.getOffset();
9773                 }
9774         }
9775     }
9777     return sInfo;
9783 function Duration( sDurationAttribute )
9785     this.bIndefinite = false;
9786     this.bMedia = false;
9787     this.nValue = undefined;
9788     this.bDefined = false;
9790     if( !sDurationAttribute )
9791         return;
9793     if( sDurationAttribute == 'indefinite' )
9794         this.bIndefinite = true;
9795     else if( sDurationAttribute == 'media' )
9796         this.bMedia = true;
9797     else
9798     {
9799         this.nValue = Timing.parseClockValue( sDurationAttribute );
9800         if( this.nValue <= 0.0 )
9801             this.nValue = 0.001;  // duration must be always greater than 0
9802     }
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()
9819     return this.bMedia;
9822 Duration.prototype.isValue = function()
9824     return this.nValue != undefined;
9827 Duration.prototype.getValue= function()
9829     return this.nValue;
9832 Duration.prototype.info= function()
9834     var sInfo;
9836     if( this.isIndefinite() )
9837         sInfo = 'indefinite';
9838     else if( this.isMedia() )
9839         sInfo = 'media';
9840     else if( this.getValue() )
9841         sInfo = this.getValue();
9843     return sInfo;
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 )
9882     {
9883         log( 'NodeContext.makeSourceEventElement: event base element is not valid' );
9884         return null;
9885     }
9887     if( !this.aContext.aEventMultiplexer )
9888     {
9889         log( 'NodeContext.makeSourceEventElement: event multiplexer not initialized' );
9890         return null;
9891     }
9893     if( !this.aSourceEventElementMap[ sId ] )
9894     {
9895         this.aSourceEventElementMap[ sId ] = new SourceEventElement( sId, aEventBaseElem, this.aContext.aEventMultiplexer );
9896     }
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 )
9914     {
9915         log( 'StateTransition.enter: commit() before enter()ing again!' );
9916         return false;
9917     }
9918     if( !bForce && !this.aNode.isTransition( this.aNode.getState(), eNodeState  ) )
9919         return false;
9921     // recursion detection:
9922     if( ( this.aNode.nCurrentStateTransition & eNodeState ) != 0 )
9923         return false; // already in wanted transition
9925     // mark transition:
9926     this.aNode.nCurrentStateTransition |= eNodeState;
9927     this.eToState = eNodeState;
9928     return true;
9931 StateTransition.prototype.commit = function()
9933     if( this.eToState != INVALID_NODE )
9934     {
9935         this.aNode.eCurrentState = this.eToState;
9936         this.clear();
9937     }
9940 StateTransition.prototype.clear = function()
9942     if( this.eToState != INVALID_NODE )
9943     {
9944         this.aNode.nCurrentStateTransition &= ~this.eToState;
9945         this.eToState = INVALID_NODE;
9946     }
9952 function BaseNode( aAnimElem, aParentNode, aNodeContext )
9954     this.nId = getUniqueId();
9955     this.sClassName = 'BaseNode';
9957     if( !aAnimElem )
9958         log( 'BaseNode(id:' + this.nId + ') constructor: aAnimElem is not valid' );
9960     if( !aNodeContext )
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;
9979     this.aBegin = null;
9980     this.aDuration = null;
9981     this.aEnd = 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()
9997     return this.nId;
10000 BaseNode.prototype.parseElement = function()
10002     var aAnimElem = this.aElement;
10004     // id attribute
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'
10008     if( sIdAttr )
10009         this.aNodeContext.aAnimationNodeMap[ sIdAttr ] = this;
10011     // begin attribute
10012     this.aBegin = null;
10013     var sBeginAttr = aAnimElem.getAttributeNS( NSS['smil'], 'begin' );
10014     this.aBegin = new Timing( this, sBeginAttr );
10015     this.aBegin.parse();
10017     // end attribute
10018     this.aEnd = null;
10019     var sEndAttr = aAnimElem.getAttributeNS( NSS['smil'], 'end' );
10020     if( sEndAttr )
10021     {
10022         this.aEnd = new Timing( this, sEndAttr );
10023         this.aEnd.parse();
10024     }
10026     // dur attribute
10027     this.aDuration = null;
10028     var sDurAttr = aAnimElem.getAttributeNS( NSS['smil'], 'dur' );
10029     this.aDuration = new Duration( sDurAttr );
10030     if( !this.aDuration.isSet() )
10031     {
10032         if( this.isContainer() )
10033             this.aDuration = null;
10034         else
10035             this.aDuration = new Duration( 'indefinite' );
10036     }
10038     // fill attribute
10039     var sFillAttr = aAnimElem.getAttributeNS( NSS['smil'], 'fill' );
10040     if( sFillAttr && aFillModeInMap[ sFillAttr ])
10041         this.eFillMode = aFillModeInMap[ sFillAttr ];
10042     else
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 ];
10049     else
10050         this.eRestartMode = RESTART_MODE_DEFAULT;
10052     // repeatCount attribute
10053     var sRepeatCount = aAnimElem.getAttributeNS( NSS['smil'], 'repeatCount' );
10054     if( !sRepeatCount )
10055         this.nRepeatCount = 1;
10056     else
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();
10088         else
10089             this.eFillMode = FILL_MODE_AUTO;
10091     if( this.eFillMode ==  FILL_MODE_AUTO ) // see SMIL recommendation document
10092     {
10093         this.eFillMode = ( this.aEnd ||
10094                            ( this.nRepeatCount != 1) ||
10095                            ( this.aDuration && !this.aDuration.isIndefinite() ) )
10096                               ? FILL_MODE_REMOVE
10097                               : FILL_MODE_FREEZE;
10098     }
10100     // resolve restart value
10101     if( this.eRestartMode == RESTART_MODE_DEFAULT )
10102         if( this.getParentNode() )
10103             this.eRestartMode = this.getParentNode().getRestartMode();
10104         else
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 )
10114     {
10115         this.nAccelerate = 0.0;
10116         this.nDecelerate = 0.0;
10117     }
10119     this.aStateTransTable = getTransitionTable( this.getRestartMode(), this.getFillMode() );
10121     return true;
10124 BaseNode.prototype.getParentNode = function()
10126     return this.aParentNode;
10129 BaseNode.prototype.init = function()
10131     this.DBG( this.callInfo( 'init' ) );
10132     if( ! this.checkValidNode() )
10133         return false;
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() )
10147         return false;
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() )
10159     {
10160         aStateTrans.commit();
10162         if( this.aActivationEvent )
10163         {
10164             this.aActivationEvent.charge();
10165         }
10166         else
10167         {
10168             this.aActivationEvent = makeDelay( bind( this, this.activate ), this.getBegin().getOffset() + this.nStartDelay );
10169         }
10170         registerEvent( this.getId(), this.getBegin(), this.aActivationEvent, this.aNodeContext );
10172         return true;
10173     }
10175     return false;
10178 BaseNode.prototype.activate = function()
10180     if( ! this.checkValidNode() )
10181         return false;
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 ) )
10191     {
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() );
10197         return true;
10198     }
10199     return false;
10202 BaseNode.prototype.deactivate = function()
10204     if( this.inStateOrTransition( ENDED_NODE | FROZEN_NODE ) || !this.checkValidNode() )
10205         return;
10207     if( this.isTransition( this.eCurrentState, FROZEN_NODE ) )
10208     {
10209         this.DBG( this.callInfo( 'deactivate' ), getCurrentSystemTime() );
10211         var aStateTrans = new StateTransition( this );
10212         if( aStateTrans.enter( FROZEN_NODE, true /* FORCE */ ) )
10213         {
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();
10223         }
10224     }
10225     else
10226     {
10227         this.end();
10228     }
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() )
10236         return;
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 */ ) )
10245     {
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();
10258     }
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())
10278         return false;
10280     if( !aNotifiee )
10281     {
10282         log( 'BaseNode.registerDeactivatingListener(): invalid notifiee' );
10283         return false;
10284     }
10285     this.aDeactivatingListenerArray.push( aNotifiee );
10287     return true;
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 )
10309     {
10310         this.aDeactivationEvent.charge();
10311     }
10312     else
10313     {
10314         if( typeof( nDelay ) == typeof(0) )
10315             this.aDeactivationEvent = makeDelay( bind( this, this.deactivate ), nDelay );
10316         else
10317             this.aDeactivationEvent = null;
10318     }
10319     return this.aDeactivationEvent;
10322 BaseNode.prototype.scheduleDeactivationEvent = function( aEvent )
10324     this.DBG( this.callInfo( 'scheduleDeactivationEvent' ) );
10326     if( !aEvent )
10327     {
10328         if( this.getDuration() && this.getDuration().isValue() )
10329             aEvent = this.makeDeactivationEvent( this.getDuration().getValue() );
10330     }
10331     if( aEvent )
10332     {
10333         this.aContext.aTimerEventQueue.addEvent( aEvent );
10334     }
10337 BaseNode.prototype.checkValidNode = function()
10339     return ( this.eCurrentState != INVALID_NODE );
10342 BaseNode.prototype.init_st = function()
10344     return true;
10347 BaseNode.prototype.resolve_st = function()
10349     return true;
10352 BaseNode.prototype.activate_st = function()
10354     this.scheduleDeactivationEvent();
10357 BaseNode.prototype.deactivate_st = function( /*aNodeState*/ )
10359     // empty body
10362 BaseNode.prototype.notifyEndListeners = function()
10364     var nDeactivatingListenerCount = this.aDeactivatingListenerArray.length;
10366     for( var i = 0; i < nDeactivatingListenerCount; ++i )
10367     {
10368         this.aDeactivatingListenerArray[i].notifyDeactivating( this );
10369     }
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()
10411     return this.aEnd;
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() );
10451     if( bVerbose )
10452     {
10453         // is container
10454         sInfo += ';  is container: ' + this.isContainer();
10456         // begin
10457         if( this.getBegin() )
10458             sInfo += ';  begin: ' + this.getBegin().info();
10460         // duration
10461         if( this.getDuration() )
10462             sInfo += ';  dur: ' + this.getDuration().info();
10464         // end
10465         if( this.getEnd() )
10466             sInfo += ';  end: ' + this.getEnd().info();
10468         // fill mode
10469         if( this.getFillMode() )
10470             sInfo += ';  fill: ' + aFillModeOutMap[ this.getFillMode() ];
10472         // restart mode
10473         if( this.getRestartMode() )
10474             sInfo += ';  restart: ' + aRestartModeOutMap[ this.getRestartMode() ];
10476         // repeatCount
10477         if( this.getRepeatCount() && ( this.getRepeatCount() != 1.0 ) )
10478             sInfo += ';  repeatCount: ' + this.getRepeatCount();
10480         // accelerate
10481         if( this.getAccelerateValue() )
10482             sInfo += ';  accelerate: ' + this.getAccelerateValue();
10484         // decelerate
10485         if( this.getDecelerateValue() )
10486             sInfo += ';  decelerate: ' + this.getDecelerateValue();
10488         // auto reverse
10489         if( this.isAutoReverseEnabled() )
10490             sInfo += ';  autoReverse: true';
10492     }
10494     return sInfo;
10497 BaseNode.prototype.callInfo = function( sMethodName )
10499     var sInfo = this.sClassName +
10500                 '( ' + this.getId() +
10501                 ', ' + getNodeStateName( this.getState() ) +
10502                 ' ).' + sMethodName;
10503     return sInfo;
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 )
10545     {
10546         this.eCurrentState = INVALID_NODE;
10547         log( 'AnimationBaseNode.parseElement: target element not found: ' + sTargetElementAttr );
10548     }
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];
10558     else
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 )
10572     {
10573         // set up target element initial visibility
10574         if( aAnimElem.getAttributeNS( NSS['smil'], 'attributeName' ) === 'visibility' )
10575         {
10576             if( aAnimElem.getAttributeNS( NSS['smil'], 'to' ) === 'visible' )
10577                 this.aTargetElement.setAttribute( 'visibility', 'hidden' );
10578         }
10580         // create animated element
10581         if( !this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ] )
10582         {
10583             if( this.bIsTargetTextElement )
10584             {
10585                 this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ]
10586                     = new AnimatedTextElement( this.aTargetElement );
10587             }
10588             else
10589             {
10590                 this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ]
10591                     = new AnimatedElement( this.aTargetElement );
10592             }
10593         }
10594         this.aAnimatedElement = this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ];
10596         // set additive mode
10597         this.aAnimatedElement.setAdditiveMode( this.eAdditiveMode );
10598     }
10601     return bRet;
10604 AnimationBaseNode.prototype.init_st = function()
10606     if( this.aActivity )
10607         this.aActivity.activate( makeEvent( bind( this, this.deactivate ) ) );
10608     else
10609         this.aActivity = this.createActivity();
10610     return true;
10613 AnimationBaseNode.prototype.resolve_st = function()
10615     return true;
10618 AnimationBaseNode.prototype.activate_st = function()
10620     if( this.aActivity )
10621     {
10622         this.saveStateOfAnimatedElement();
10623         this.aActivity.setTargets( this.getAnimatedElement() );
10624         if( this.getContext().bIsSkipping  )
10625         {
10626             this.aActivity.end();
10627         }
10628         else
10629         {
10630             this.getContext().aActivityQueue.addActivity( this.aActivity );
10631         }
10632     }
10633     else
10634     {
10635         AnimationBaseNode.superclass.scheduleDeactivationEvent.call( this );
10636     }
10639 AnimationBaseNode.prototype.deactivate_st = function( eDestState )
10641     if( eDestState == FROZEN_NODE )
10642     {
10643         if( this.aActivity )
10644             this.aActivity.end();
10645     }
10646     if( eDestState == ENDED_NODE )
10647     {
10648         if( this.aActivity )
10649             this.aActivity.dispose();
10650         if( ( this.getFillMode() == FILL_MODE_REMOVE ) && this.getAnimatedElement()  )
10651             this.removeEffect();
10652     }
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() )
10666     {
10667         nDuration = this.getDuration().getValue();
10668     }
10669     else
10670     {
10671         log( 'AnimationBaseNode.fillActivityParams: duration is not a number' );
10672     }
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()
10694     return true;
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 );
10739     if( bVerbose )
10740     {
10741         // min frame count
10742         if( this.getMinFrameCount() )
10743             sInfo += ';  min frame count: ' + this.getMinFrameCount();
10745         // additive mode
10746         sInfo += ';  additive: ' + aAddittiveModeOutMap[ this.getAdditiveMode() ];
10748         // target element
10749         if( this.getTargetElement() )
10750         {
10751             var sElemId = this.getTargetElement().getAttribute( 'id' );
10752             sInfo += ';  targetElement: ' +  sElemId;
10753         }
10754     }
10756     return sInfo;
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 )
10781     {
10782         this.eCurrentState = INVALID_NODE;
10783         log( 'AnimationBaseNode2.parseElement: target attribute name not found: ' + this.sAttributeName );
10784     }
10786     // to attribute
10787     this.aToValue = aAnimElem.getAttributeNS( NSS['smil'], 'to' );
10789     return bRet;
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 );
10806     if( bVerbose )
10807     {
10808         // attribute name
10809         if( this.getAttributeName() )
10810             sInfo += ';  attributeName: ' + this.getAttributeName();
10812         // To
10813         if( this.getToValue() )
10814             sInfo += ';  to: ' + this.getToValue();
10815     }
10817     return sInfo;
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 ];
10856     // from attribute
10857     this.aFromValue = aAnimElem.getAttributeNS( NSS['smil'], 'from' );
10859     // by attribute
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 )
10867     {
10868         var aKeyTimes = sKeyTimesAttr.split( ';' );
10869         for( var i = 0; i < aKeyTimes.length; ++i )
10870             this.aKeyTimes.push( parseFloat( aKeyTimes[i] ) );
10871     }
10873     // values attribute
10874     var sValuesAttr = aAnimElem.getAttributeNS( NSS['smil'], 'values' );
10875     if( sValuesAttr )
10876     {
10877         this.aValues = sValuesAttr.split( ';' );
10878     }
10879     else
10880     {
10881         this.aValues = [];
10882     }
10884     // formula attribute
10885     this.aFormula = aAnimElem.getAttributeNS( NSS['anim'], 'formula' );
10887     return bRet;
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 );
10929     if( bVerbose )
10930     {
10931         // accumulate mode
10932         if( this.getAccumulate() )
10933             sInfo += ';  accumulate: ' + aAccumulateModeOutMap[ this.getAccumulate() ];
10935         // calcMode
10936         sInfo += ';  calcMode: ' + aCalcModeOutMap[ this.getCalcMode() ];
10938         // from
10939         if( this.getFromValue() )
10940             sInfo += ';  from: ' + this.getFromValue();
10942         // by
10943         if( this.getByValue() )
10944             sInfo += ';  by: ' + this.getByValue();
10946         // keyTimes
10947         if( this.getKeyTimes().length )
10948             sInfo += ';  keyTimes: ' + this.getKeyTimes().join( ',' );
10950         // values
10951         if( this.getValues().length )
10952             sInfo += ';  values: ' + this.getValues().join( ',' );
10954         // formula
10955         if( this.getFormula() )
10956             sInfo += ';  formula: ' + this.getFormula();
10957     }
10959     return sInfo;
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 )
11013     {
11014         this.aChildrenArray[i].parseElement();
11015     }
11018     // resolve duration
11019     this.bDurationIndefinite
11020             = ( !this.getDuration() || this.getDuration().isIndefinite()  ) &&
11021               ( !this.getEnd() || ( this.getEnd().getType() != OFFSET_TIMING ) );
11023     return bRet;
11026 BaseContainerNode.prototype.appendChildNode = function( aAnimationNode )
11028     if( ! this.checkValidNode() )
11029         return ;
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 )
11053     {
11054         if( this.aChildrenArray[i].init() )
11055         {
11056             ++nInitChildren;
11057         }
11058     }
11059     return ( nChildrenCount == nInitChildren );
11063 BaseContainerNode.prototype.deactivate_st = function( eDestState )
11065     this.nLeftIterations = 0;
11066     if( eDestState == FROZEN_NODE )
11067     {
11068         // deactivate all children that are not FROZEN or ENDED:
11069         this.forEachChildNode( mem_fn( 'deactivate' ), ~( FROZEN_NODE | ENDED_NODE ) );
11070     }
11071     else
11072     {
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();
11077     }
11080 BaseContainerNode.prototype.hasPendingAnimation = function()
11082     var nChildrenCount = this.aChildrenArray.length;
11083     for( var i = 0; i < nChildrenCount; ++i )
11084     {
11085         if( this.aChildrenArray[i].hasPendingAnimation() )
11086             return true;
11087     }
11088     return false;
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 )
11110     {
11111         if( this.aChildrenArray[i].getId() == aAnimationNode.getId() )
11112             return true;
11113     }
11114     return false;
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 ) )
11126     {
11127         log( 'BaseContainerNode.notifyDeactivatedChild: unknown child notifier!' );
11128         return false;
11129     }
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() )
11140     {
11141         if( this.nLeftIterations >= 1.0 )
11142         {
11143             this.nLeftIterations -= 1.0;
11144         }
11145         if( this.nLeftIterations >= 1.0 )
11146         {
11147             bFinished = false;
11148             var aRepetitionEvent = makeDelay( bind( this, this.repeat ), 0.0 );
11149             this.aContext.aTimerEventQueue.addEvent( aRepetitionEvent );
11150         }
11151         else
11152         {
11153             this.deactivate();
11154         }
11155     }
11157     return bFinished;
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();
11166     if( bInitialized )
11167         this.activate_st();
11168     return bInitialized;
11171 BaseContainerNode.prototype.removeEffect = function()
11173     var nChildrenCount = this.aChildrenArray.length;
11174     if( nChildrenCount == 0 )
11175         return;
11176     // We remove effect in reverse order.
11177     for( var i = nChildrenCount - 1; i >= 0; --i )
11178     {
11179         if( ( this.aChildrenArray[i].getState() & ( FROZEN_NODE | ENDED_NODE ) ) == 0 )
11180         {
11181             log( 'BaseContainerNode.removeEffect: child(id:'
11182                  + this.aChildrenArray[i].getId() + ') is neither frozen nor ended;'
11183                  + ' state: '
11184                  + aTransitionModeOutMap[ this.aChildrenArray[i].getState() ] );
11185             continue;
11186         }
11187         this.aChildrenArray[i].removeEffect();
11188     }
11191 BaseContainerNode.prototype.saveStateOfAnimatedElement = function()
11193     var nChildrenCount = this.aChildrenArray.length;
11194     for( var i = 0; i < nChildrenCount; ++i )
11195     {
11196         this.aChildrenArray[i].saveStateOfAnimatedElement();
11197     }
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 )
11207     {
11208         if( ( eNodeStateMask != -1 ) && ( ( this.aChildrenArray[i].getState() & eNodeStateMask ) == 0 ) )
11209             continue;
11210         aFunction( this.aChildrenArray[i] );
11211     }
11214 BaseContainerNode.prototype.dispose = function()
11216     var nChildrenCount = this.aChildrenArray.length;
11217     for( var i = 0; i < nChildrenCount; ++i )
11218     {
11219         this.aChildrenArray[i].dispose();
11220     }
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 );
11234     if( bVerbose )
11235     {
11236         // impress node type
11237         if( this.getImpressNodeType() )
11238             sInfo += ';  node-type: ' + aImpressNodeTypeOutMap[ this.getImpressNodeType() ];
11239     }
11241     var nChildrenCount = this.aChildrenArray.length;
11242     for( var i = 0; i < nChildrenCount; ++i )
11243     {
11244         sInfo += '\n';
11245         sInfo += this.aChildrenArray[i].info( bVerbose );
11246     }
11248     return sInfo;
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 )
11266     {
11267         if( this.aChildrenArray[i].resolve() )
11268         {
11269             ++nResolvedChildren;
11270         }
11271     }
11273     if( nChildrenCount != nResolvedChildren )
11274     {
11275         log( 'ParallelTimeContainer.activate_st: resolving all children failed' );
11276         return;
11277     }
11280     if( this.isDurationIndefinite() && ( nChildrenCount == 0  ) )
11281     {
11282         this.scheduleDeactivationEvent( this.makeDeactivationEvent( 0.0 ) );
11283     }
11284     else
11285     {
11286         this.scheduleDeactivationEvent();
11287     }
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 )
11315     {
11316         if( this.resolveChild( this.aChildrenArray[ this.nFinishedChildren ] ) )
11317             break;
11318         else
11319             log( 'SequentialTimeContainer.activate_st: resolving child failed!' );
11320     }
11322     if( this.isDurationIndefinite() && ( ( nChildrenCount == 0 ) || ( this.nFinishedChildren >= nChildrenCount ) ) )
11323     {
11324         // deactivate ASAP:
11325         this.scheduleDeactivationEvent( this.makeDeactivationEvent( 0.0 ) );
11326     }
11327     else
11328     {
11329         this.scheduleDeactivationEvent();
11330     }
11333 SequentialTimeContainer.prototype.notifyDeactivating = function( aNotifier )
11335     // If we are rewinding we have not to resolve the next child.
11336     if( this.bIsRewinding )
11337         return;
11339     if( this.notifyDeactivatedChild( aNotifier ) )
11340         return;
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 ) )
11351     {
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).
11357         this.deactivate();
11358     }
11361 /** skipEffect
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
11367  *      played.
11368  */
11369 SequentialTimeContainer.prototype.skipEffect = function( aChildNode )
11371     if( this.isChildNode( aChildNode ) )
11372     {
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 );
11384     }
11385     else
11386     {
11387         log( 'SequentialTimeContainer.skipEffect: unknown child: '
11388                  + aChildNode.getId() );
11389     }
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
11398  *      played
11399  */
11400 SequentialTimeContainer.prototype.rewindCurrentEffect = function( aChildNode )
11402     if( this.isChildNode( aChildNode ) )
11403     {
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.
11426         aChildNode.end();
11427         aChildNode.removeEffect();
11428         // Finally we place the child node to the 'unresolved' state and
11429         // resolve it again.
11430         aChildNode.init();
11431         this.resolveChild( aChildNode );
11432         this.notifyRewindedEvent( aChildNode );
11433         this.bIsRewinding = false;
11434     }
11435     else
11436     {
11437         log( 'SequentialTimeContainer.rewindCurrentEffect: unknown child: '
11438                  + aChildNode.getId() );
11439     }
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
11448  *      to be played.
11449  */
11450 SequentialTimeContainer.prototype.rewindLastEffect = function( aChildNode )
11452     if( this.isChildNode( aChildNode ) )
11453     {
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();
11464         aChildNode.end();
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
11470         // some issue.
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.
11485         aChildNode.init();
11486         this.resolveChild( aPreviousChildNode );
11487         this.notifyRewindedEvent( aChildNode );
11488         this.bIsRewinding = false;
11489     }
11490     else
11491     {
11492         log( 'SequentialTimeContainer.rewindLastEffect: unknown child: '
11493                  + aChildNode.getId() );
11494     }
11497 /** resolveChild
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
11501  *  registered.
11503  *  @param aChildNode
11504  *      An animation node representing the root node of the next shape effect
11505  *      to be played.
11506  *  @return
11507  *      It returns true if the passed child has been resolved successfully,
11508  *      false otherwise.
11509  */
11510 SequentialTimeContainer.prototype.resolveChild = function( aChildNode )
11512     var bResolved = aChildNode.resolve();
11514     if( bResolved && ( this.isMainSequenceRootNode() || this.isInteractiveSequenceRootNode() ) )
11515     {
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() )
11529         {
11530             this.aContext.aEventMultiplexer.registerSkipEffectEvent( this.aCurrentSkipEvent );
11531             this.aContext.aEventMultiplexer.registerRewindCurrentEffectEvent( this.aRewindCurrentEffectEvent );
11532             this.aContext.aEventMultiplexer.registerRewindLastEffectEvent( this.aRewindLastEffectEvent );
11533         }
11534         else if( this.isInteractiveSequenceRootNode() )
11535         {
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 );
11539         }
11540     }
11541     return bResolved;
11544 SequentialTimeContainer.prototype.notifyRewindedEvent = function( aChildNode )
11546     if( this.isInteractiveSequenceRootNode() )
11547     {
11548         this.aContext.aEventMultiplexer.notifyRewindedEffectEvent( aChildNode.getId() );
11550         var sId = aChildNode.getBegin().getEventBaseElementId();
11551         if( sId )
11552         {
11553             this.aContext.aEventMultiplexer.notifyRewindedEffectEvent( sId );
11554         }
11555     }
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 ) )
11618     {
11619         this.eCurrentState = INVALID_NODE;
11620         log( 'AnimationTransformNode.parseElement: transformation type not found: ' + sTransformType );
11621     }
11622     else
11623     {
11624         this.sAttributeName = sTransformType;
11625     }
11627     return bRet;
11630 AnimationTransformNode.prototype.createActivity = function()
11632     var aActivityParamSet = this.fillActivityParams();
11633     var aAnimation;
11635     if( this.getAttributeName() === 'scale' || this.getAttributeName() === 'translate' )
11636     {
11637         aAnimation = createPairPropertyAnimation( this.getAttributeName(),
11638                                                   this.getAnimatedElement(),
11639                                                   this.aNodeContext.aSlideWidth,
11640                                                   this.aNodeContext.aSlideHeight );
11642     }
11643     else
11644     {
11645         aAnimation = createPropertyAnimation( this.getAttributeName(),
11646                                               this.getAnimatedElement(),
11647                                               this.aNodeContext.aSlideWidth,
11648                                               this.aNodeContext.aSlideHeight );
11649     }
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 ];
11712     return bRet;
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;
11725     var aInterpolator;
11726     if( this.getColorInterpolation() === COLOR_SPACE_HSL )
11727     {
11728         ANIMDBG.print( 'AnimationColorNode.createActivity: color space hsl'  );
11729         aColorAnimation = new HSLAnimationWrapper( aAnimation );
11730         var aInterpolatorMaker = aInterpolatorHandler.getInterpolator( this.getCalcMode(),
11731                                                                        COLOR_PROPERTY,
11732                                                                        COLOR_SPACE_HSL );
11733         aInterpolator = aInterpolatorMaker( this.getColorInterpolationDirection() );
11734     }
11735     else
11736     {
11737         ANIMDBG.print( 'AnimationColorNode.createActivity: color space rgb'  );
11738         aColorAnimation = aAnimation;
11739         aInterpolator = aInterpolatorHandler.getInterpolator( this.getCalcMode(),
11740                                                               COLOR_PROPERTY,
11741                                                               COLOR_SPACE_RGB );
11742     }
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 );
11761     if( bVerbose )
11762     {
11763         // color interpolation
11764         sInfo += ';  color-interpolation: ' + aColorSpaceOutMap[ this.getColorInterpolation() ];
11766         // color interpolation direction
11767         sInfo += ';  color-interpolation-direction: ' + aClockDirectionOutMap[ this.getColorInterpolationDirection() ];
11768     }
11769     return sInfo;
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,
11797                                   this );
11800 AnimationTransitionFilterNode.prototype.parseElement = function()
11802     var bRet = AnimationTransitionFilterNode.superclass.parseElement.call( this );
11803     var bIsValidTransition = true;
11805     var aAnimElem = this.aElement;
11807     // type attribute
11808     this.eTransitionType = undefined;
11809     var sTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'type' );
11810     if( sTypeAttr && aTransitionTypeInMap[ sTypeAttr ] )
11811     {
11812         this.eTransitionType = aTransitionTypeInMap[ sTypeAttr ];
11813     }
11814     else
11815     {
11816         bIsValidTransition = false;
11817         log( 'AnimationTransitionFilterNode.parseElement: transition type not valid: ' + sTypeAttr );
11818     }
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  ) )
11826     {
11827         this.eTransitionSubType = aTransitionSubtypeInMap[ sSubTypeAttr ];
11828     }
11829     else
11830     {
11831         bIsValidTransition = false;
11832         log( 'AnimationTransitionFilterNode.parseElement: transition subtype not valid: ' + sSubTypeAttr );
11833     }
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 )
11839     {
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' );
11843     }
11845     // direction attribute
11846     this.bReverseDirection = false;
11847     var sDirectionAttr = aAnimElem.getAttributeNS( NSS['smil'], 'direction' );
11848     if( sDirectionAttr == 'reverse' )
11849         this.bReverseDirection = true;
11851     // mode attribute:
11852     this.eTransitionMode = TRANSITION_MODE_IN;
11853     var sModeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'mode' );
11854     if( sModeAttr === 'out' )
11855         this.eTransitionMode = TRANSITION_MODE_OUT;
11857     return bRet;
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 );
11884     if( bVerbose )
11885     {
11886         // transition type
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';
11895     }
11897     return sInfo;
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 )
11925     {
11926         case ANIMATION_NODE_PAR:
11927             aCreatedNode = aCreatedContainer =
11928                 new ParallelTimeContainer( aElement, aParentNode, aNodeContext );
11929             break;
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 );
11936             break;
11937         case ANIMATION_NODE_SEQ:
11938             aCreatedNode = aCreatedContainer =
11939                 new SequentialTimeContainer( aElement, aParentNode, aNodeContext );
11940             break;
11941         case ANIMATION_NODE_ANIMATE:
11942             aCreatedNode = new PropertyAnimationNode( aElement, aParentNode, aNodeContext );
11943             break;
11944         case ANIMATION_NODE_SET:
11945             aCreatedNode = new AnimationSetNode( aElement, aParentNode, aNodeContext );
11946             break;
11947         case ANIMATION_NODE_ANIMATEMOTION:
11948             //aCreatedNode = new AnimationPathMotionNode( aElement, aParentNode, aNodeContext );
11949             //break;
11950             log( 'createAnimationNode: ANIMATEMOTION not implemented' );
11951             return null;
11952         case ANIMATION_NODE_ANIMATECOLOR:
11953             aCreatedNode = new AnimationColorNode( aElement, aParentNode, aNodeContext );
11954             break;
11955         case ANIMATION_NODE_ANIMATETRANSFORM:
11956             aCreatedNode = new AnimationTransformNode( aElement, aParentNode, aNodeContext );
11957             break;
11958         case ANIMATION_NODE_TRANSITIONFILTER:
11959             aCreatedNode = new AnimationTransitionFilterNode( aElement, aParentNode, aNodeContext );
11960             break;
11961          case ANIMATION_NODE_AUDIO:
11962             log( 'createAnimationNode: AUDIO not implemented' );
11963             return null;
11964          case ANIMATION_NODE_COMMAND:
11965             log( 'createAnimationNode: COMMAND not implemented' );
11966             return null;
11967         default:
11968             log( 'createAnimationNode: invalid Animation Node Type: ' + eAnimationNodeType );
11969             return null;
11970     }
11972     if( aCreatedContainer )
11973     {
11974         if( eAnimationNodeType == ANIMATION_NODE_ITERATE )
11975         {
11976             createIteratedNodes( aElement, aCreatedContainer, aNodeContext );
11977         }
11978         else
11979         {
11980             var aChildrenArray = getElementChildren( aElement );
11981             for( var i = 0; i < aChildrenArray.length; ++i )
11982             {
11983                 if( !createChildNode( aChildrenArray[i], aCreatedContainer, aNodeContext ) )
11984                 {
11985                     aCreatedContainer.removeAllChildrenNodes();
11986                     break;
11987                 }
11988             }
11989         }
11990     }
11992     return aCreatedNode;
11998 function createChildNode( aElement, aParentNode, aNodeContext )
12000     var aChildNode = createAnimationNode( aElement, aParentNode, aNodeContext );
12002     if( !aChildNode )
12003     {
12004         log( 'createChildNode: child node creation failed' );
12005         return false;
12006     }
12007     else
12008     {
12009         aParentNode.appendChildNode( aChildNode );
12010         return true;
12011     }
12017 function createIteratedNodes( /*aElement, aContainerNode, aNodeContext*/ )
12019     // not implemented
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 ) )
12033     {
12034         log( 'makeScaler: not valid param passed: ' + nScale );
12035         return null;
12036     }
12038     return  function( nValue )
12039             {
12040                 return ( nScale * nValue );
12041             };
12046 // eslint-disable-next-line no-unused-vars
12047 function createPropertyAnimation( sAttrName, aAnimatedElement, nWidth, nHeight )
12049     if( !aAttributeMap[ sAttrName ] )
12050     {
12051         log( 'createPropertyAnimation: attribute is unknown' );
12052         return null;
12053     }
12056     var aFunctorSet = aAttributeMap[ sAttrName ];
12058     var sGetValueMethod =   aFunctorSet.get;
12059     var sSetValueMethod =   aFunctorSet.set;
12061     if( !sGetValueMethod || !sSetValueMethod  )
12062     {
12063         log( 'createPropertyAnimation: attribute is not handled' );
12064         return null;
12065     }
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 ] ),
12073                                  aGetModifier,
12074                                  aSetModifier);
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' )
12089     {
12090         aDefaultValue[0] = aSizeReference[0] = aAnimatedElement.getBaseBBox().width;
12091         aDefaultValue[1] = aSizeReference[1] = aAnimatedElement.getBaseBBox().height;
12092     }
12093     else if( sTransformType === 'translate' )
12094     {
12095         aDefaultValue[0] = aAnimatedElement.getBaseCenterX();
12096         aDefaultValue[1] = aAnimatedElement.getBaseCenterY();
12097         aSizeReference[0] = nWidth;
12098         aSizeReference[1] = nHeight;
12099     }
12100     else
12101     {
12102         log( 'createPairPropertyAnimation: transform type is not handled' );
12103         return null;
12104     }
12106     return new TupleAnimation( bind( aAnimatedElement, aAnimatedElement[ sGetValueMethod ] ),
12107                                bind( aAnimatedElement, aAnimatedElement[ sSetValueMethod ] ),
12108                                aDefaultValue,
12109                                aSizeReference );
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.
12129  */
12130 function createShapeTransition( aActivityParamSet, aAnimatedElement,
12131                                 nSlideWidth, nSlideHeight,
12132                                 aAnimatedTransitionFilterNode )
12134     if( !aAnimatedTransitionFilterNode )
12135     {
12136         log( 'createShapeTransition: the animated transition filter node is not valid.' );
12137         return null;
12138     }
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 )
12148     {
12149         default:
12150         case TRANSITION_INVALID:
12151             log( 'createShapeTransition: transition class: TRANSITION_INVALID' );
12152             return null;
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 )
12164             {
12165                 // no special transition filter provided
12166                 // we map everything to crossfade
12167                 default:
12168                     var aAnimation
12169                         = createPropertyAnimation( 'opacity',
12170                                                    aAnimatedElement,
12171                                                    nSlideWidth,
12172                                                    nSlideHeight );
12173                     return new SimpleActivity( aActivityParamSet, aAnimation, bModeIn );
12174             }
12175     }
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.
12192  *  @param bModeIn
12193  *      If true the element to be animated becomes more visible as the transition
12194  *      progress else it becomes less visible.
12195  */
12196 function ClippingAnimation( aParametricPolyPolygon, aTransitionInfo,
12197                             bDirectionForward, bModeIn )
12199     this.aClippingFunctor = new ClippingFunctor( aParametricPolyPolygon,
12200                                                  aTransitionInfo,
12201                                                  bDirectionForward, bModeIn );
12202     this.bAnimationStarted = false;
12205 /** start
12206  *  This method notifies to the element involved in the transition that
12207  *  the animation is starting and creates the <clipPath> element used for
12208  *  the transition.
12210  *  @param aAnimatableElement
12211  *      The element to be animated.
12212  */
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;
12226 /** end
12227  *  The transition clean up is performed here.
12228  */
12229 ClippingAnimation.prototype.end = function()
12231     if( this.bAnimationStarted )
12232     {
12233         this.aAnimatableElement.cleanClipPath();
12234         this.bAnimationStarted = false;
12235         this.aAnimatableElement.notifyAnimationEnd();
12236     }
12239 /** perform
12240  *  This method set the position of the element to be animated according to
12241  *  the passed  time value.
12243  *  @param nValue
12244  *      The time parameter.
12245  */
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()
12256     return 0.0;
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 )
12290     {
12291         this.bAnimationStarted = false;
12292         this.aAnimatableElement.notifyAnimationEnd();
12293     }
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 );
12309     return 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);
12329     var aValue = [];
12330     for( var i = 0; i < aNormValue.length; ++i )
12331     {
12332         aValue.push( aNormValue[i] * this.aReferenceSize[i] );
12333     }
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 )
12345     {
12346         aNormValue.push( aValue[i] / this.aReferenceSize[i] );
12347     }
12349     return aNormValue;
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.
12392  */
12393 function SlideChangeBase(aLeavingSlide, aEnteringSlide)
12395     this.aLeavingSlide = aLeavingSlide;
12396     this.aEnteringSlide = aEnteringSlide;
12397     this.bIsFinished = false;
12400 /** start
12401  *  The transition initialization is performed here.
12402  */
12403 SlideChangeBase.prototype.start = function()
12407 /** end
12408  *  The transition clean up is performed here.
12409  */
12410 SlideChangeBase.prototype.end = function()
12412     if( this.bIsFinished )
12413         return;
12415     this.aLeavingSlide.hide();
12416     this.aEnteringSlide.reset();
12417     this.aLeavingSlide.reset();
12419     this.bIsFinished = true;
12422 /** perform
12423  *  This method is responsible for performing the slide transition.
12425  *  @param nValue
12426  *      The time parameter.
12427  *  @return {Boolean}
12428  *      If the transition is performed returns tue else returns false.
12429  */
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 );
12440     return true;
12443 SlideChangeBase.prototype.getUnderlyingValue = function()
12445     return 0.0;
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.
12469  */
12470 function FadingSlideChange( aLeavingSlide, aEnteringSlide )
12472     FadingSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
12473     this.bFirstRun = true;
12475 extend( FadingSlideChange, SlideChangeBase );
12477 /** start
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
12481  *  visible.
12482  */
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();
12492 /** performIn
12493  *  This method set the opacity of the entering slide according to the passed
12494  *  time value.
12496  *  @param nT
12497  *      The time parameter.
12498  */
12499 FadingSlideChange.prototype.performIn = function( nT )
12501     this.aEnteringSlide.setOpacity( nT );
12504 /** performOut
12505  *  This method set the opacity of the leaving slide according to the passed
12506  *  time value.
12508  *  @param nT
12509  *      The time parameter.
12510  */
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.
12531  */
12532 function FadingOverColorSlideChange( aLeavingSlide, aEnteringSlide, sFadeColor )
12534     FadingSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
12535     this.sFadeColor = sFadeColor;
12536     if( !this.sFadeColor )
12537     {
12538         log( 'FadingOverColorSlideChange: sFadeColor not valid.' );
12539         this.sFadeColor = '#000000';
12540     }
12541     this.aColorPlaneElement = this.createColorPlaneElement();
12543 extend( FadingOverColorSlideChange, SlideChangeBase );
12545 /** start
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.
12551  */
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();
12562 /** end
12563  *  This method removes the color plane element.
12564  */
12565 FadingOverColorSlideChange.prototype.end = function()
12567     FadingOverColorSlideChange.superclass.end.call( this );
12568     this.aLeavingSlide.removeElement( this.aColorPlaneElement );
12571 /** performIn
12572  *  This method set the opacity of the entering slide according to the passed
12573  *  time value.
12575  *  @param nT
12576  *      The time parameter.
12577  */
12578 FadingOverColorSlideChange.prototype.performIn = function( nT )
12580     this.aEnteringSlide.setOpacity( (nT > 0.55) ? 2.0*(nT-0.55) : 0.0 );
12583 /** performOut
12584  *  This method set the opacity of the leaving slide according to the passed
12585  *  time value.
12587  *  @param nT
12588  *      The time parameter.
12589  */
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}.
12619  */
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 );
12629 /** start
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
12633  *  visible.
12634  */
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();
12647 /** performIn
12648  *  This method set the position of the entering slide according to the passed
12649  *  time value.
12651  *  @param nT
12652  *      The time parameter.
12653  */
12654 MovingSlideChange.prototype.performIn = function( nT )
12656     var nS = nT - 1;
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 );
12662 /** performOut
12663  *  This method set the position of the leaving slide according to the passed
12664  *  time value.
12666  *  @param nT
12667  *      The time parameter.
12668  */
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.
12694  */
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 );
12706 /** start
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
12710  *  visible.
12711  */
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();
12720 /** performIn
12721  *  This method set the position of the entering slide according to the passed
12722  *  time value.
12724  *  @param nT
12725  *      The time parameter.
12726  */
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( )
12737     // empty body
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.
12755  *  @param bIsModeIn
12756  *      The direction the filter effect has to be performed
12757  */
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 )
12773     {
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 );
12784     }
12785     else
12786     {
12787         this.aStaticTransformation = document.documentElement.createSVGMatrix();
12788     }
12790     if( !bIsDirectionForward )
12791     {
12792         var aMatrix = null;
12793         switch( aTransitionInfo.reverseMethod )
12794         {
12795             default:
12796                 log( 'ClippingFunctor: unexpected reverse method.' );
12797                 break;
12798             case REVERSEMETHOD_IGNORE:
12799                 break;
12800             case REVERSEMETHOD_INVERT_SWEEP:
12801                 this.bForwardParameterSweep = !this.bForwardParameterSweep;
12802                 break;
12803             case REVERSEMETHOD_SUBTRACT_POLYGON:
12804                 this.bSubtractPolygon = !this.bSubtractPolygon;
12805                 break;
12806             case REVERSEMETHOD_SUBTRACT_AND_INVERT:
12807                 this.bForwardParameterSweep = !this.bForwardParameterSweep;
12808                 this.bSubtractPolygon = !this.bSubtractPolygon;
12809                 break;
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 );
12814                 break;
12815             case REVERSEMETHOD_FLIP_X:
12816                 aMatrix = document.documentElement.createSVGMatrix();
12817                 // |-1  0  1 |
12818                 // | 0  1  0 |
12819                 aMatrix.a = -1; aMatrix.e = 1.0;
12820                 this.aStaticTransformation = aMatrix.multiply( this.aStaticTransformation );
12821                 this.bFlip = true;
12822                 break;
12823             case REVERSEMETHOD_FLIP_Y:
12824                 aMatrix = document.documentElement.createSVGMatrix();
12825                 // | 1  0  0 |
12826                 // | 0 -1  1 |
12827                 aMatrix.d = -1; aMatrix.f = 1.0;
12828                 this.aStaticTransformation = aMatrix.multiply( this.aStaticTransformation );
12829                 this.bFlip = true;
12830                 break;
12831         }
12832     }
12834     if( !bIsModeIn )
12835     {
12836         if( aTransitionInfo.outInvertsSweep )
12837         {
12838             this.bForwardParameterSweep = !this.bForwardParameterSweep;
12839         }
12840         else
12841         {
12842             this.bSubtractPolygon = !this.bSubtractPolygon;
12843         }
12844     }
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' );
12852 /** perform
12854  *  @param nT
12855  *      A parameter in [0,1] representing normalized time.
12856  *  @param nWidth
12857  *      The width of the bounding box of the slide/shape to be clipped.
12858  *  @param nHeight
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
12862  *      operation.
12863  */
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'
12870     // clip-rule.
12871     // See: http://www.w3.org/TR/SVG11/painting.html#FillRuleProperty
12873     if( this.bSubtractPolygon )
12874     {
12875         aClipPoly.changeOrientation();
12876         aClipPoly.prependPath( ClippingFunctor.aBoundingPath );
12877     }
12879     var aMatrix;
12880     if( this.bScaleIsotropically )
12881     {
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 );
12888     }
12889     else
12890     {
12891         aMatrix = SVGIdentityMatrix.scaleNonUniform( nWidth, nHeight );
12892         aMatrix = aMatrix.multiply( this.aStaticTransformation );
12893     }
12895     aClipPoly.matrixTransform( aMatrix );
12897     return aClipPoly;
12903 /** createClipPolyPolygon
12905  *  @param nType
12906  *      An enumerator representing the transition type.
12907  *  @param nSubtype
12908  *      An enumerator representing the transition subtype.
12909  *  @return
12910  *      An object that handles a parametric <path> element.
12911  */
12912 function createClipPolyPolygon( nType, nSubtype )
12914     switch( nType )
12915     {
12916         default:
12917             log( 'createClipPolyPolygon: unknown transition type: ' + nType );
12918             return null;
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:
12934             var nBlades;
12935             switch( nSubtype )
12936             {
12937                 case ONEBLADE_TRANS_SUBTYPE:
12938                     nBlades = 1;
12939                     break;
12940                 case DEFAULT_TRANS_SUBTYPE:
12941                 case TWOBLADEVERTICAL_TRANS_SUBTYPE:
12942                     nBlades = 2;
12943                     break;
12944                 case TWOBLADEHORIZONTAL_TRANS_SUBTYPE:
12945                     nBlades = 2;
12946                     break;
12947                 case THREEBLADE_TRANS_SUBTYPE:
12948                     nBlades = 3;
12949                     break;
12950                 case FOURBLADE_TRANS_SUBTYPE:
12951                     nBlades = 4;
12952                     break;
12953                 case EIGHTBLADE_TRANS_SUBTYPE:
12954                     nBlades = 8;
12955                     break;
12956                 default:
12957                     log( 'createClipPolyPolygon: unknown subtype: ' + nSubtype );
12958                     return null;
12959             }
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:
12972             switch(nSubtype)
12973             {
12974                 case RECTANGLE_TRANS_SUBTYPE:
12975                     return new IrisWipePath(0);
12976                 case DIAMOND_TRANS_SUBTYPE:
12977                     return new IrisWipePath(1);
12978                 default:
12979                     log( 'createClipPolyPolygon: unknown subtype: ' + nSubtype );
12980                     return null;
12981             }
12982         case BARNDOORWIPE_TRANSITION:
12983             return new BarnDoorWipePath(false);
12984         case SINGLESWEEPWIPE_TRANSITION:
12985             return new SweepWipePath(
12986                 // center
12987                 nSubtype == CLOCKWISETOP_TRANS_SUBTYPE ||
12988                 nSubtype == CLOCKWISERIGHT_TRANS_SUBTYPE ||
12989                 nSubtype == CLOCKWISEBOTTOM_TRANS_SUBTYPE ||
12990                 nSubtype == CLOCKWISELEFT_TRANS_SUBTYPE,
12991                 // single
12992                 true,
12993                 // oppositeVertical
12994                 false,
12995                 // flipOnYAxis
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:
13003             switch(nSubtype) {
13004                 case DOUBLEBARNDOOR_TRANS_SUBTYPE:
13005                     return new BarnDoorWipePath(true /* Doubled */);
13006                 case DOUBLEDIAMOND_TRANS_SUBTYPE:
13007                     return new DoubleDiamondWipePath();
13008                 default:
13009                     log( 'createClipPolyPolygon: unhandled subtype: ' + nSubtype );
13010                     return null;
13011             }
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   ,
13022                                              // flipOnYAxis
13023                                              nSubtype == TOPLEFTVERTICAL_TRANS_SUBTYPE     ||
13024                                              nSubtype == TOPRIGHTDIAGONAL_TRANS_SUBTYPE    ||
13025                                              nSubtype == BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE
13026                                              );
13027         case PARALLELSNAKESWIPE_TRANSITION:
13028             return new ParallelSnakesWipePath(
13029                 8 * 8, // elements
13030                 // diagonal
13031                 nSubtype == DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
13032                 nSubtype == DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
13033                 // flipOnYAxis
13034                 nSubtype == VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
13035                 nSubtype == HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE  ||
13036                 nSubtype == DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
13037                 // opposite
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
13044                 );
13046         case SPIRALWIPE_TRANSITION:
13047             return new SpiralWipePath(
13048                 8 * 8, // elements
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(
13056                 // elements
13057                 8 * 8,
13058                 // fourBox
13059                 nSubtype == FOURBOXVERTICAL_TRANS_SUBTYPE ||
13060                 nSubtype == FOURBOXHORIZONTAL_TRANS_SUBTYPE );
13061     }
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 );
13072     return aPath;
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 );
13080     return aPath;
13083 function pruneScaleValue( nVal )
13085     if( nVal < 0.0 )
13086         return (nVal < -0.00001 ? nVal : -0.00001);
13087     else
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.
13097  *  @param nBars
13098  *     The number of bars to be generated.
13099  */
13100 function BarWipePath( nBars /* nBars > 1: blinds effect */ )
13102     this.nBars = nBars;
13103     if( this.nBars === undefined || this.nBars < 1 )
13104         this.nBars = 1;
13105     this.aBasePath = createUnitSquarePath();
13108 /** perform
13110  *  @param nT
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.
13114  */
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 )
13124     {
13125         var i;
13126         var aTransform;
13127         var aPath;
13128         for( i = this.nBars - 1; i > 0; --i )
13129         {
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 );
13135         }
13136     }
13137     return aPolyPath;
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.
13147  */
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);
13158     }
13159     else {
13160         aTransform = aTransform.scale(d, d);
13161     }
13162     var aPath = this.aBasePath.cloneNode(true);
13163     aPath.matrixTransform(aTransform);
13164     return aPath;
13167 /* Class SweepWipePath
13170  */
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 ) {
13180     nT /= 2.0;
13181     if(!this.bCenter)
13182         nT /= 2.0;
13183     if(!this.bSingle && !this.bOppositeVertical)
13184         nT /= 2.0;
13186     var poly = PinWheelWipePath.calcCenteredClock( nT + 0.25, 1.0 );
13187     var aTransform;
13189     if(this.bCenter) {
13190         aTransform = SVGIdentityMatrix.translate(0.5, 0.0);
13191         poly.matrixTransform(aTransform);
13192     }
13193     var res = poly;
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();
13201         }
13202         else {
13203             aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
13204             aTransform.rotate(Math.PI);
13205             aTransform.translate(0.5, 0.5);
13206             poly.matrixTransform(aTransform);
13207         }
13208         res.appendPath(poly);
13209     }
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.
13219  */
13220 function FourBoxWipePath( bCornersOut )
13222     this.bCornersOut = bCornersOut;
13223     this.aBasePath = createUnitSquarePath();
13226 FourBoxWipePath.prototype.perform = function( nT )
13228     var aMatrix;
13229     var d = pruneScaleValue( nT / 2.0 );
13231     if( this.bCornersOut )
13232     {
13233         aMatrix = SVGIdentityMatrix.translate( -0.25, -0.25 ).scale( d ).translate( -0.5, -0.5 );
13234     }
13235     else
13236     {
13237         aMatrix = SVGIdentityMatrix.translate( -0.5, -0.5 ).scale( d );
13238     }
13241     var aTransform = aMatrix;
13242     // top left
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 );
13274     return aPolyPath;
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
13283  *  transition.
13285  *  @param eSubtype
13286  *      The transition subtype.
13287  */
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 );
13314     return aEllipse;
13318  * Class FanWipePath
13320  */
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);
13333   // flip on y-axis
13334   var aTransform = SVGIdentityMatrix.flipY();
13335   aTransform = aTransform.scaleNonUniform(-1.0, 1.0);
13336   poly.matrixTransform(aTransform);
13337   res.appendPath(poly);
13339   if(this.bCenter) {
13340       aTransform = SVGIdentityMatrix.scaleNonUniform(0.5, 0.5).translate(0.5, 0.5);
13341       res.matrixTransform(aTransform);
13343       if(!this.bSingle)
13344           res.appendPath(flipOnXAxis(res));
13345   }
13346   else {
13347       aTransform = SVGIdentityMatrix.scaleNonUniform(0.5, 1.0).translate(0.5, 1.0);
13348       res.matrixTransform(aTransform);
13349   }
13350   return res;
13354  * Class ClockWipePath
13356  */
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 );
13364     return aPolyPath;
13367 /** Class PinWheelWipePath
13368  *  This class handles a parametric poly-path that is used for performing
13369  *  a spinWheelWipe transition.
13371  *  @param nBlades
13372  *      Number of blades generated by the transition.
13373  */
13374 function PinWheelWipePath( nBlades )
13376     this.nBlades = nBlades;
13377     if( !this.nBlades || this.nBlades < 1 )
13378         this.nBlades = 1;
13381 PinWheelWipePath.calcCenteredClock = function( nT, nE )
13383     var nMAX_EDGE = 2;
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 + ' ';
13392     if( nT >= 0.875 )
13393         // L -e -e
13394         sPathData += 'L ' + '-' + nE + ' -' + nE + ' ';
13395     if( nT >= 0.625 )
13396         // L -e e
13397         sPathData += 'L ' + '-' + nE + ' ' + nE + ' ';
13398     if( nT >= 0.375 )
13399         // L e e
13400         sPathData += 'L ' + nE + ' ' + nE + ' ';
13401      if( nT >= 0.125 )
13402         // L e -e
13403         sPathData += 'L ' + nE + ' -' + nE + ' ';
13405     // L 0 -e
13406     sPathData += 'L 0 -' + nE + ' ';
13407     sPathData += 'L 0 0 ';
13408     // Z
13409     sPathData += 'L '  + aPoint.x + ' ' + aPoint.y;
13411     var aPath = document.createElementNS( NSS['svg'], 'path' );
13412     aPath.setAttribute( 'd', sPathData );
13413     return aPath;
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 );
13422     var aPath;
13423     var aRotation;
13424     var i;
13425     for( i = this.nBlades - 1; i > 0; --i )
13426     {
13427         aRotation = SVGIdentityMatrix.rotate( (i * 360) / this.nBlades );
13428         aPath = aBasePath.cloneNode( true );
13429         aPath.matrixTransform( aRotation );
13430         aPolyPath.appendPath( aPath );
13431     }
13433     var aTransform = SVGIdentityMatrix.translate( 0.5, 0.5 ).scale( 0.5 );
13434     aPolyPath.matrixTransform( aTransform );
13436     return aPolyPath;
13439 /** Class BarnDoorWipe
13440   *
13441   * @param doubled
13442   */
13443 function BarnDoorWipePath(doubled) {
13444     this.aBasePath = createUnitSquarePath();
13445     this.doubled   = doubled;
13448 BarnDoorWipePath.prototype.perform = function( nT ) {
13449     if(this.doubled)
13450         nT /= 2.0;
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);
13455     var res = aPath;
13457     if(this.doubled) {
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);
13462     }
13463     return res;
13466 /** Class WaterfallWipe
13467   *
13468   * @param nElements
13469   *     Number of cells to be used
13470   * @param bFlipOnYAxis
13471   *     Whether to flip on y-axis or not.
13472   */
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 + ' ';
13486     }
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
13513  */
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);
13537     return res;
13540 /** Class Iriswipe
13541   *
13542   * @param unitRect
13543   *
13544   */
13545 function IrisWipePath(unitRect) {
13546     this.unitRect = unitRect;
13547     this.aBasePath = createUnitSquarePath();
13551 /** perform
13552   *
13553   *  @param nT
13554   *      A parameter in [0,1] representing the diamond or rectangle.
13555   *  @return SVGPathElement
13556   *      A svg <path> element representing a transition.
13557   */
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);
13564     return aPath;
13568  * Class ZigZagWipePath
13570  * @param nZigs
13572  */
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 + ' ';
13584     }
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));
13592     return res;
13596  * Class BarnZigZagWipePath
13598  * @param nZigs
13600  */
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);
13622     return res;
13625 /** Class CheckerBoardWipePath
13627  *  @param unitsPerEdge
13628  *     The number of cells (per line and column) in the checker board.
13629  */
13630 function CheckerBoardWipePath( unitsPerEdge )
13632     this.unitsPerEdge = unitsPerEdge;
13633     if( this.unitsPerEdge === undefined || this.unitsPerEdge < 1 )
13634         this.unitsPerEdge = 10;
13635     this.aBasePath = createUnitSquarePath();
13638 /** perform
13640  *  @param nT
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.
13644  */
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;
13652     var i, j;
13653     var aTransform;
13654     var aPath;
13655     for ( i = this.unitsPerEdge; i--; )
13656     {
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--;)
13665         {
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 );
13671         }
13673         aMatrix = SVGIdentityMatrix.translate( 0.0, d ).multiply( aMatrix ); // next line
13674     }
13676     return aPolyPath;
13681 /** Class RandomWipePath
13683  *  @param nElements
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
13688  */
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
13700     {
13701         fEdgeLength = 1.0 / nElements;
13702         for( nPos = 0; nPos < nElements; ++nPos )
13703         {
13704             this.aPositionArray[nPos] = { x: 0.0, y: pruneScaleValue( nPos * fEdgeLength ) }
13705         }
13706         aTransform = SVGIdentityMatrix.scaleNonUniform( 1.0, pruneScaleValue( fEdgeLength ) );
13707     }
13708     else // dissolve wipe
13709     {
13710         var nSqrtElements = Math.round( Math.sqrt( nElements ) );
13711         fEdgeLength = 1.0 / nSqrtElements;
13712         for( nPos = 0; nPos < nElements; ++nPos )
13713         {
13714             this.aPositionArray[nPos] = {
13715                 x: pruneScaleValue( ( nPos % nSqrtElements ) * fEdgeLength ),
13716                 y: pruneScaleValue( ( nPos / nSqrtElements ) * fEdgeLength ) }
13717         }
13718         aTransform = SVGIdentityMatrix.scale( pruneScaleValue( fEdgeLength ) );
13719     }
13720     this.aBasePath.matrixTransform( aTransform );
13722     var nPos1, nPos2;
13723     var tmp;
13724     for( nPos1 = nElements - 1; nPos1 > 0; --nPos1 )
13725     {
13726         nPos2 = getRandomInt( nPos1 + 1 );
13727         tmp = this.aPositionArray[nPos1];
13728         this.aPositionArray[nPos1] = this.aPositionArray[nPos2];
13729         this.aPositionArray[nPos2] = tmp;
13730     }
13733 /** perform
13735  *  @param nT
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.
13739  */
13740 RandomWipePath.prototype.perform = function( nT )
13742     var aPolyPath = createEmptyPath();
13743     var aPoint;
13744     var aPath;
13745     var aTransform;
13746     var nElements = Math.round( nT * this.nElements );
13747     if( nElements === 0 )
13748     {
13749         return aPolyPath;
13750     }
13751     // check if we need to reset the clip path
13752     if( this.nAlreadyAppendedElements >= nElements )
13753     {
13754         this.nAlreadyAppendedElements = 0;
13755         this.aClipPath = createEmptyPath();
13756     }
13757     var nPos;
13758     for( nPos = this.nAlreadyAppendedElements; nPos < nElements; ++nPos )
13759     {
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 );
13765     }
13767     this.nAlreadyAppendedElements = nElements;
13768     this.aClipPath.appendPath( aPolyPath );
13770     return this.aClipPath.cloneNode( true );
13773 /** Class SnakeWipeSlide
13775  *  @param nElements
13776  *  @param bDiagonal
13777  *  @param bFlipOnYaxis
13778  */
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);
13796     if(line != 0) {
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 + ' ';
13801         aPath += 'L 0 0 ';
13802         let poly = document.createElementNS( NSS['svg'], 'path');
13803         poly.setAttribute('d', aPath);
13804         aPolyPath.appendPath(poly);
13805     }
13806     if(col != 0) {
13807         var offset = 0.0;
13808         if((line_ & 1) == 1) {
13809             // odd line: => right to left
13810             offset = (1.0 - col);
13811         }
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);
13820     }
13822     return aPolyPath;
13825 SnakeWipePath.prototype.calcHalfDiagonalSnake = function(nT, bIn) {
13826     var res = createEmptyPath();
13828     if(bIn) {
13829         const sqrtArea2 = Math.sqrt(nT * this.sqrtElements * this.sqrtElements);
13830         const edge = pruneScaleValue(sqrtArea2 / this.sqrtElements);
13832         var aPath, aPoint = document.documentElement.createSVGPoint();
13833         if(edge) {
13834             aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
13835             aPoint.y = edge;
13836             aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13837             aPoint.x = edge;
13838             aPoint.y = 0.0;
13839             aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13840             aPoint.x = 0.0;
13841             aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13842             const poly = document.createElementNS( NSS['svg'], 'path');
13843             poly.setAttribute('d', aPath);
13844             res.appendPath(poly);
13845         }
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 + ' ';
13851         aPoint.y = height;
13852         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13853         aPoint.x = len + a;
13854         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13855         aPoint.y = 0.0;
13856         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13857         aPoint.x = 0.0;
13858         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13859         const poly = document.createElementNS( NSS['svg'], 'path');
13860         poly.setAttribute('d', aPath);
13861         let aTransform;
13863         if((Math.floor(sqrtArea2) & 1) == 1) {
13864             // odd line
13865             aTransform = SVGIdentityMatrix.rotate((Math.PI)/2 + (Math.PI)/4);
13866             aTransform.translate(edge + this.elementEdge, 0.0);
13867         }
13868         else {
13869             aTransform = SVGIdentityMatrix.translate(-a, 0.0);
13870             aTransform.rotate(-(Math.PI/4));
13871             aTransform.translate(0.0, edge);
13872         }
13874         poly.matrixTransform(aTransform);
13875         res.appendPath(poly);
13876     }
13877     else { //out
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();
13882         if(edge != 0) {
13883             aPoint.y = 1.0;
13884             aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
13885             aPoint.x = edge;
13886             aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13887             aPoint.x = 1.0;
13888             aPoint.y = edge;
13889             aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13890             aPoint.y = 0.0;
13891             aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13892             aPoint.x = 0.0;
13893             aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13894             const poly = document.createElementNS( NSS['svg'], 'path');
13895             poly.setAttribute('d', aPath);
13896             res.appendPath(poly);
13897         }
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 + ' ';
13903         aPoint.y = height;
13904         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13905         aPoint.x = len + a;
13906         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13907         aPoint.y = 0.0;
13908         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13909         aPoint.x = 0.0;
13910         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13911         const poly = document.createElementNS( NSS['svg'], 'path');
13912         poly.setAttribute('d', aPath);
13913         let aTransform;
13915         if((Math.floor(sqrtArea2) & 1) == 1) {
13916             // odd line
13917             aTransform = SVGIdentityMatrix.translate(0.0, -height);
13918             aTransform.rotate(Math.PI/2 + Math.PI/4);
13919             aTransform.translate(1.0, edge);
13920         }
13921         else {
13922             aTransform = SVGIdentityMatrix.rotate(-(Math.PI/4));
13923             aTransform = aTransform.translate(edge, 1.0);
13924         }
13925         poly.matrixTransform(aTransform);
13926         res.appendPath(poly);
13927     }
13928     return res;
13931 SnakeWipePath.prototype.perform = function(nT) {
13932     var res = createEmptyPath();
13933     if(this.diagonal) {
13934         if(nT >= 0.5) {
13935             res.appendPath(this.calcHalfDiagonalSnake(1.0, true));
13936             res.appendPath(this.calcHalfDiagonalSnake(2.0*(nT-0.5), false));
13937         }
13938         else
13939             res.appendPath(this.calcHalfDiagonalSnake(2.0*nT, true));
13940     }
13941     else
13942         res = this.calcSnake(nT);
13944     return this.flipOnYAxis ? flipOnYAxis(res) : res;
13947 /** Class ParallelSnakesWipePath
13948  *  Generates a parallel snakes wipe:
13950  *  @param nElements
13951  *  @param bDiagonal
13952  *  @param bFlipOnYAxis
13953  *  @param bOpposite
13954  */
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);
13982     }
13983     else {
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);
13992     }
13994     return this.flipOnYAxis ? flipOnYAxis(res) : res;
13997 /** SpiralWipePath
13999  *  @param nElements
14000  *      number of elements in the spiral animation
14001  *  @param bFlipOnYAxis
14002  *      boolean value indicating whether to flip on y-axis or not.
14003  */
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;
14029         while(len > 0) {
14030             var alen = Math.min(len, edge1);
14031             len -= alen;
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 );
14040             w -= Math.PI / 2;
14041             aTransform = aTransform.translate(0.5, 0.5);
14042             poly.matrixTransform(aTransform);
14043             res.appendPath(poly);
14044         }
14045     }
14047     return res;
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:
14062  */
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));
14083     }
14084     else {
14085         aTransform = SVGIdentityMatrix.scale(1.0, 0.5);
14086         innerSpiral.matrixTransform(aTransform);
14087         res.appendPath(innerSpiral);
14088         res.appendPath(flipOnXAxis(innerSpiral));
14089     }
14090     return this.bFlipOnYAxis ? flipOnYAxis(res) : res;
14093 /** Class VeeWipePath
14094   *
14095   */
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.
14118  */
14119 function AnimatedSlide( aMetaSlide )
14121     if( !aMetaSlide )
14122     {
14123         log( 'AnimatedSlide constructor: meta slide is not valid' );
14124     }
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;
14137 /** show
14138  *  Set the visibility property of the slide to 'inherit'
14139  *  and update the master page view.
14140  */
14141 AnimatedSlide.prototype.show = function()
14143     this.aMetaSlide.show();
14146 /** hide
14147  *  Set the visibility property of the slide to 'hidden'.
14148  */
14149 AnimatedSlide.prototype.hide = function()
14151     this.aMetaSlide.hide();
14154 /** notifyUsedAttribute
14155  *  Populate the set of attribute used for the transition.
14157  *  @param sName
14158  *      A string representing an attribute name.
14159  */
14160 AnimatedSlide.prototype.notifyUsedAttribute = function( sName )
14162     if( sName == 'clip-path' )
14163     {
14164         this.initClipPath();
14165         this.bIsClipped = true;
14166     }
14167     else
14168     {
14169         this.aUsedAttributeSet.push( sName );
14170     }
14173 /** reset
14174  *  Remove from the handled slide element any attribute that was appended for
14175  *  performing the transition.
14176  */
14177 AnimatedSlide.prototype.reset = function()
14179     if( this.bIsClipped )
14180     {
14181         this.cleanClipPath();
14182         this.bIsClipped = false;
14183     }
14185     var i;
14186     for( i = 0; i < this.aUsedAttributeSet.length; ++i )
14187     {
14188         var sAttrName = this.aUsedAttributeSet[i];
14189         this.aSlideElement.removeAttribute( sAttrName );
14190     }
14191     this.aUsedAttributeSet = [];
14194 /** initClipPath
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
14197  *  element.
14198  */
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 );
14225 /** cleanClipPath
14226  *  Removes the related <clipPath> element from the <defs> group,
14227  *  and remove the 'clip-path' attribute from the slide element.
14229  */
14230 AnimatedSlide.prototype.cleanClipPath = function()
14232     this.aSlideElement.parentNode.removeAttribute( 'clip-path' );
14234     if( this.aClipPathElement )
14235     {
14236         var aClipPathGroup = theMetaDoc.aClipPathGroup;
14237         aClipPathGroup.removeChild( this.aClipPathElement );
14238         this.aClipPathElement = null;
14239         this.aClipPathContent = null;
14240     }
14243 /** insertBefore
14244  *  Insert an svg element before the handled slide element.
14246  *  @param aElement
14247  *      A svg element.
14248  */
14249 AnimatedSlide.prototype.insertBefore = function( aElement )
14251     if( aElement )
14252     {
14253         this.aSlideElement.parentNode.insertBefore( aElement, this.aSlideElement );
14254     }
14257 /** appendElement
14258  *  Insert an svg element after the handled slide element.
14260  *  @param aElement
14261  *      A svg element.
14262  */
14263 AnimatedSlide.prototype.appendElement = function( aElement )
14265     if( aElement )
14266     {
14267          this.aSlideElement.parentNode.appendChild( aElement );
14268     }
14271 /** removeElement
14272  *  Remove an svg element.
14274  *  @param aElement
14275  *      A svg element.
14276  */
14277 AnimatedSlide.prototype.removeElement = function( aElement )
14279     if( aElement )
14280     {
14281         this.aSlideElement.parentNode.removeChild( aElement );
14282     }
14285 /** getWidth
14287  *  @return {Number}
14288  *      The slide width.
14289  */
14290 AnimatedSlide.prototype.getWidth = function()
14292     return WIDTH;
14295 /** getHeight
14297  *  @return {Number}
14298  *      The slide height.
14299  */
14300 AnimatedSlide.prototype.getHeight = function()
14302     return HEIGHT;
14305 /** setOpacity
14307  *  @param nValue
14308  *      A number in the [0,1] range representing the slide opacity.
14309  */
14310 AnimatedSlide.prototype.setOpacity = function( nValue )
14312     this.aSlideElement.setAttribute( 'opacity', nValue );
14315 /** translate
14316  *  Translate the handled slide.
14318  *  @param nDx
14319  *     A number representing the translation that occurs in the x direction.
14320  *  @param nDy
14321  *     A number representing the translation that occurs in the y direction.
14322  */
14323 AnimatedSlide.prototype.translate = function( nDx, nDy )
14325     var sTransformAttr = 'translate(' + nDx + ',' + nDy + ')';
14326     this.aSlideElement.setAttribute( 'transform', sTransformAttr );
14329 /** setClipPath
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.
14335  */
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 )
14341     {
14342         var sPathData = aClipPathContent.getAttribute( 'd' );
14343         this.aClipPathContent.setAttribute( 'd', sPathData );
14344     }
14349 function AnimatedElement( aElement )
14351     if( !aElement )
14352     {
14353         log( 'AnimatedElement constructor: element is not valid' );
14354     }
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 ) );
14395 /** initClipPath
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.
14400  */
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 );
14429 /** cleanClipPath
14430  *  Removes the related <clipPath> element from the <defs> group,
14431  *  and remove the 'clip-path' attribute from the animated element.
14433  */
14434 AnimatedElement.prototype.cleanClipPath = function()
14436     this.aActiveElement.removeAttribute( 'clip-path' );
14438     if( this.aClipPathElement )
14439     {
14440         var aClipPathGroup = theMetaDoc.aClipPathGroup;
14441         aClipPathGroup.removeChild( this.aClipPathElement );
14442         this.aClipPathElement = null;
14443         this.aClipPathContent = null;
14444     }
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 )
14464     if( !aElement )
14465     {
14466         log( 'AnimatedElement(' + this.getId() + ').setToElement: element is not valid' );
14467         return false;
14468     }
14470     var aClone = aElement.cloneNode( true );
14471     this.aPreviousElement = this.aActiveElement.parentNode.replaceChild( aClone, this.aActiveElement );
14472     this.aActiveElement = aClone;
14474     return true;
14477 AnimatedElement.prototype.notifySlideStart = function( aSlideShowContext )
14479     if( !aSlideShowContext )
14480     {
14481         log( 'AnimatedElement.notifySlideStart: slideshow context is not valid' );
14482     }
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()
14495     // empty body
14498 AnimatedElement.prototype.notifyAnimationStart = function()
14500     // empty body
14503 AnimatedElement.prototype.notifyAnimationEnd = function()
14505     // empty body
14508 AnimatedElement.prototype.notifyNextEffectStart = function( /*nEffectIndex*/ )
14510     // empty body
14513 /** saveState
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.
14519  */
14520 AnimatedElement.prototype.saveState = function( nAnimationNodeId )
14522     ANIMDBG.print( 'AnimatedElement(' + this.getId() + ').saveState(' + nAnimationNodeId +')' );
14523     if( !this.aStateSet[ nAnimationNodeId ] )
14524     {
14525         this.aStateSet[ nAnimationNodeId ] = {};
14526     }
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;
14537 /** restoreState
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.
14544  *  @return
14545  *      True if the restoring operation is successful, false otherwise.
14546  */
14547 AnimatedElement.prototype.restoreState = function( nAnimationNodeId )
14549     if( !this.aStateSet[ nAnimationNodeId ] )
14550     {
14551         log( 'AnimatedElement(' + this.getId() + ').restoreState: state '
14552                  +nAnimationNodeId  + ' is not valid' );
14553         return false;
14554     }
14556     ANIMDBG.print( 'AnimatedElement(' + this.getId() + ').restoreState(' + nAnimationNodeId +')' );
14557     var aState = this.aStateSet[ nAnimationNodeId ];
14558     var bRet = this.setToElement( aState.aElement );
14559     if( bRet )
14560     {
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;
14566     }
14567     return bRet;
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;
14598     var i;
14599     for( i = 0; i < aChildrenSet.length; ++i )
14600     {
14601         if( ! aChildrenSet[i].getAttribute  )
14602             continue;
14604         sStroke = aChildrenSet[i].getAttribute( 'stroke' );
14605         if( sStroke && sStroke != 'none' )
14606         {
14607             sStrokeWidth = aChildrenSet[i].getAttribute( 'stroke-width' );
14608             var nSW = parseFloat( sStrokeWidth );
14609             if( nSW > nStrokeWidth )
14610                 nStrokeWidth = nSW;
14611         }
14612     }
14614     if( nStrokeWidth == 0 )
14615     {
14616         sStrokeWidth = ROOT_NODE.getAttribute( 'stroke-width' );
14617         nStrokeWidth = parseFloat( sStrokeWidth );
14618     }
14619     if( nStrokeWidth != 0 )
14620     {
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;
14633         aBBox = aEBBox;
14634     }
14635     return aBBox;
14638 /** setClipPath
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.
14644  */
14645 AnimatedElement.prototype.setClipPath = function( aClipPathContent )
14647     if( this.aClipPathContent )
14648     {
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 );
14656     }
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 )
14738     {
14739         log('AnimatedElement(' + this.getId() + ').setWidth: negative width!');
14740         nNewWidth = 0;
14741     }
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 )
14763     {
14764         log('AnimatedElement(' + this.getId() + ').setWidth: negative height!');
14765         nNewHeight = 0;
14766     }
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 )
14790     {
14791         log('AnimatedElement(' + this.getId() + ').setSize: negative width!');
14792         nNewWidth = 0;
14793     }
14794     if( nNewHeight < 0 )
14795     {
14796         log('AnimatedElement(' + this.getId() + ').setSize: negative height!');
14797         nNewHeight = 0;
14798     }
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!
14854     else
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
14868     return 'solid';
14871 AnimatedElement.prototype.setStrokeStyle = function( sValue )
14873     ANIMDBG.print( 'AnimatedElement.setStrokeStyle(' + sValue + ')' );
14876 AnimatedElement.prototype.getFillStyle = function()
14878     // TODO: getFillStyle: implement it
14879     return 'solid';
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 )
14892     {
14893         sFillColorValue = aChildSet[i].getAttribute( 'fill' );
14894         if( sFillColorValue && ( sFillColorValue !== 'none' ) )
14895             break;
14896     }
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 )
14911     {
14912         sFillColorValue = aChildSet[i].getAttribute( 'fill' );
14913         if( sFillColorValue && ( sFillColorValue !== 'none' ) )
14914         {
14915             aChildSet[i].setAttribute( 'fill', sValue );
14916         }
14917     }
14920 AnimatedElement.prototype.getStrokeColor = function()
14922     var aChildSet = getElementChildren( this.aActiveElement );
14923     var sStrokeColorValue = '';
14924     for( var i = 0; i <  aChildSet.length; ++i )
14925     {
14926         sStrokeColorValue = aChildSet[i].getAttribute( 'stroke' );
14927         if( sStrokeColorValue && ( sStrokeColorValue !== 'none' ) )
14928             break;
14929     }
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 )
14944     {
14945         sStrokeColorValue = aChildSet[i].getAttribute( 'stroke' );
14946         if( sStrokeColorValue && ( sStrokeColorValue !== 'none' ) )
14947         {
14948             aChildSet[i].setAttribute( 'stroke', sValue );
14949         }
14950     }
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 )
14977     {
14978         log( 'AnimatedTextElement: passed element is not a paragraph.' );
14979         return;
14980     }
14981     var aTextShapeElement = aElement.parentNode;
14982     sTextType = aTextShapeElement.getAttribute( 'class' );
14983     if( sTextType !== 'SVGTextShape' )
14984     {
14985         log( 'AnimatedTextElement: element parent is not a text shape.' );
14986         return;
14987     }
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 )
14993     {
14994         aAnimatedElementGroup = theDocument.createElementNS( NSS['svg'], 'g' );
14995         aAnimatedElementGroup.setAttribute( 'class', 'AnimatedElements' );
14996         aTextShapeGroup.appendChild( aAnimatedElementGroup );
14997     }
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 )
15017     {
15018         var aBulletCharGroupElem = getElementByClassName( aTextShapeGroup, 'BulletChars' );
15019         if( aBulletCharGroupElem )
15020         {
15021             var aBulletPlaceholderElem = getElementByClassName( aElement, 'BulletPlaceholder' );
15022             if( aBulletPlaceholderElem )
15023             {
15024                 var sId = aBulletPlaceholderElem.getAttribute( 'id' );
15025                 sId = 'bullet-char(' + sId + ')';
15026                 aBulletCharElem = theDocument.getElementById( sId );
15027                 if( aBulletCharElem )
15028                 {
15029                     aBulletCharClone = aBulletCharElem.cloneNode( true );
15030                 }
15031                 else
15032                 {
15033                     log( 'AnimatedTextElement: ' + sId + ' not found.' );
15034                 }
15035             }
15036             else
15037             {
15038                 log( 'AnimatedTextElement: no bullet placeholder found' );
15039             }
15040         }
15041         else
15042         {
15043             log( 'AnimatedTextElement: no bullet char group found' );
15044         }
15045     }
15047     // In case there are embedded bitmaps we need to clone them
15048     var aBitmapElemSet = [];
15049     var aBitmapCloneSet = [];
15050     var aBitmapPlaceholderSet = getElementsByClassName( aElement, 'BitmapPlaceholder' );
15051     var i;
15052     if( aBitmapPlaceholderSet )
15053     {
15054         for( i = 0; i < aBitmapPlaceholderSet.length; ++i )
15055         {
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] )
15061             {
15062                 aBitmapCloneSet[i] = aBitmapElemSet[i].cloneNode( true );
15063             }
15064             else
15065             {
15066                 log( 'AnimatedTextElement: ' + sId + ' not found.' );
15067             }
15068         }
15069     }
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 )
15079     {
15080         if( aBitmapCloneSet[i] )
15081             aBitmapCloneSet[i].removeAttribute( 'id' );
15082     }
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 )
15094     {
15095         if( aBitmapElemSet[i] )
15096             aBitmapElemSet[i].setAttribute( 'visibility', 'hidden' );
15097     }
15099     // Append each element to its parent.
15100     // <g class='AnimatedElements'>
15101     //   <g>
15102     //     <text>
15103     //       <tspan class='TextParagraph'> ... </tspan>
15104     //     </text>
15105     //     <g class='GraphicGroup'>
15106     //       [<g class='BulletChar'>...</g>]
15107     //       [<g class='EmbeddedBitmap'>...</g>]
15108     //       .
15109     //       .
15110     //       [<g class='EmbeddedBitmap'>...</g>]
15111     //     </g>
15112     //   </g>
15113     // </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 )
15121     {
15122         if( aBitmapCloneSet[i] )
15123             this.aGraphicGroupElement.appendChild( aBitmapCloneSet[i] );
15124     }
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' );
15136     var sHyperlinkId;
15137     for( i = 0; i < aHyperlinkElementSet.length; ++i )
15138     {
15139         sHyperlinkId = aHyperlinkElementSet[i].getAttribute( 'id' );
15140         if( sHyperlinkId )
15141            this.aHyperlinkIdSet.push( sHyperlinkId );
15142         else
15143             log( 'error: AnimatedTextElement constructor: hyperlink element has no id' );
15144     }
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 );
15155     if( bRet )
15156     {
15157         this.aGraphicGroupElement = getElementByClassName( this.aActiveElement, 'GraphicGroup' );
15158     }
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 )
15180     {
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' );
15186         else
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' );
15199         var i = 0;
15200         for( ; i < aHyperlinkIdSet.length; ++i )
15201         {
15202             aEventMultiplexer.notifyElementChangedEvent( aHyperlinkIdSet[i], aHyperlinkElementSet[i] );
15203         }
15204     }
15205     this.aActiveElement.setAttribute( 'visibility', 'hidden' );
15208 AnimatedTextElement.prototype.notifyAnimationStart = function()
15210     DBGLOG( 'AnimatedTextElement.notifyAnimationStart' );
15211     if( this.nRunningAnimations === 0 )
15212     {
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' );
15219     }
15220     ++this.nRunningAnimations;
15223 AnimatedTextElement.prototype.notifyAnimationEnd = function()
15225     DBGLOG( 'AnimatedTextElement.notifyAnimationEnd' );
15226     --this.nRunningAnimations;
15227     if( this.nRunningAnimations === 0 )
15228     {
15229         this.restoreBaseTextParagraph();
15230     }
15233 AnimatedTextElement.prototype.saveState = function( nAnimationNodeId )
15235     if( this.nRunningAnimations === 0 )
15236     {
15237         var sVisibilityAttr = this.aParagraphElement.getAttribute( 'visibility' );
15238         this.aActiveElement.setAttribute( 'visibility', sVisibilityAttr );
15239         this.aGraphicGroupElement.setAttribute( 'visibility', 'inherit' );
15240     }
15241     AnimatedTextElement.superclass.saveState.call( this, nAnimationNodeId );
15244 AnimatedTextElement.prototype.restoreState = function( nAnimationNodeId )
15246     var bRet = AnimatedTextElement.superclass.restoreState.call( this, nAnimationNodeId );
15247     if( bRet )
15248         this.restoreBaseTextParagraph();
15249     return bRet;
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.
15261  *  @param aSlideId
15262  *      A string representing a slide id.
15263  */
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 )
15277     {
15278         if( aAnimationsRootElement.firstElementChild &&
15279             ( aAnimationsRootElement.firstElementChild.getAttributeNS( NSS['smil'], 'begin' ) === (this.sSlideId + '.begin') ) )
15280         {
15281             var aTransitionFilterElement = aAnimationsRootElement.firstElementChild.firstElementChild;
15282             if( aTransitionFilterElement && ( aTransitionFilterElement.localName === 'transitionFilter' ) )
15283             {
15284                 this.aElement = aTransitionFilterElement;
15285                 this.parseElement();
15286             }
15287             aAnimationsRootElement.removeChild( aAnimationsRootElement.firstElementChild );
15288         }
15289     }
15292 SlideTransition.prototype.createSlideTransition = function( aLeavingSlide, aEnteringSlide )
15294     if( !this.isValid() )
15295         return null;
15296     if( this.eTransitionType == 0 )
15297         return null;
15299     if( !aEnteringSlide )
15300     {
15301         log( 'SlideTransition.createSlideTransition: invalid entering slide.' );
15302         return null;
15303     }
15305     var aTransitionInfo = aTransitionInfoTable[this.eTransitionType][this.eTransitionSubType];
15306     var eTransitionClass = aTransitionInfo['class'];
15308     switch( eTransitionClass )
15309     {
15310         default:
15311         case TRANSITION_INVALID:
15312             log( 'SlideTransition.createSlideTransition: transition class: TRANSITION_INVALID' );
15313             return null;
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 )
15323             {
15324                 default:
15325                     log( 'SlideTransition.createSlideTransition: ' +
15326                          'transition class: TRANSITION_SPECIAL, ' +
15327                          'unknown transition type: ' + this.eTransitionType );
15328                     return null;
15330                 case PUSHWIPE_TRANSITION:
15331                 {
15332                     var aDirection = null;
15333                     switch( this.eTransitionSubType )
15334                     {
15335                         default:
15336                             log( 'SlideTransition.createSlideTransition: ' +
15337                                  'transition type: PUSHWIPE_TRANSITION, ' +
15338                                  'unknown transition subtype: ' + this.eTransitionSubType );
15339                             return null;
15340                         case FROMTOP_TRANS_SUBTYPE:
15341                             aDirection = { x: 0.0, y: 1.0 };
15342                             break;
15343                         case FROMBOTTOM_TRANS_SUBTYPE:
15344                             aDirection = { x: 0.0, y: -1.0 };
15345                             break;
15346                         case FROMLEFT_TRANS_SUBTYPE:
15347                             aDirection = { x: 1.0, y: 0.0 };
15348                             break;
15349                         case FROMRIGHT_TRANS_SUBTYPE:
15350                             aDirection = { x: -1.0, y: 0.0 };
15351                             break;
15352                     }
15353                     return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aDirection, aDirection );
15354                 }
15356                 case SLIDEWIPE_TRANSITION:
15357                 {
15358                     var aInDirection = null;
15359                     switch( this.eTransitionSubType )
15360                     {
15361                         default:
15362                             log( 'SlideTransition.createSlideTransition: ' +
15363                                  'transition type: SLIDEWIPE_TRANSITION, ' +
15364                                  'unknown transition subtype: ' + this.eTransitionSubType );
15365                             return null;
15366                         case FROMTOP_TRANS_SUBTYPE:
15367                             aInDirection = { x: 0.0, y: 1.0 };
15368                             break;
15369                         case FROMBOTTOM_TRANS_SUBTYPE:
15370                             aInDirection = { x: 0.0, y: -1.0 };
15371                             break;
15372                         case FROMLEFT_TRANS_SUBTYPE:
15373                             aInDirection = { x: 1.0, y: 0.0 };
15374                             break;
15375                         case FROMRIGHT_TRANS_SUBTYPE:
15376                             aInDirection = { x: -1.0, y: 0.0 };
15377                             break;
15378                     }
15379                     var aNoDirection = { x: 0.0, y: 0.0 };
15380                     if( !this.bReverseDirection )
15381                     {
15382                         return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aNoDirection, aInDirection );
15383                     }
15384                     else
15385                     {
15386                         return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aInDirection, aNoDirection );
15387                     }
15388                 }
15390                 case FADE_TRANSITION:
15391                     switch( this.eTransitionSubType )
15392                     {
15393                         default:
15394                             log( 'SlideTransition.createSlideTransition: ' +
15395                                  'transition type: FADE_TRANSITION, ' +
15396                                  'unknown transition subtype: ' + this.eTransitionSubType );
15397                             return null;
15398                         case CROSSFADE_TRANS_SUBTYPE:
15399                             return new FadingSlideChange( aLeavingSlide, aEnteringSlide );
15400                         case FADEOVERCOLOR_TRANS_SUBTYPE:
15401                             return new FadingOverColorSlideChange( aLeavingSlide, aEnteringSlide, this.getFadeColor() );
15402                     }
15403             }
15404     }
15407 SlideTransition.prototype.parseElement = function()
15409     this.bIsValid = true;
15410     var aAnimElem = this.aElement;
15412     // type attribute
15413     this.eTransitionType = undefined;
15414     var sTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'type' );
15415     if( sTypeAttr && aTransitionTypeInMap[ sTypeAttr ] )
15416     {
15417         this.eTransitionType = aTransitionTypeInMap[ sTypeAttr ];
15418     }
15419     else
15420     {
15421         this.bIsValid = false;
15422         log( 'SlideTransition.parseElement: transition type not valid: ' + sTypeAttr );
15423     }
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 ) )
15431     {
15432         this.eTransitionSubType = aTransitionSubtypeInMap[ sSubTypeAttr ];
15433     }
15434     else
15435     {
15436         this.bIsValid = false;
15437         log( 'SlideTransition.parseElement: transition subtype not valid: ' + sSubTypeAttr );
15438     }
15440     if( this.bIsValid && aTransitionInfoTable[this.eTransitionType][this.eTransitionSubType] === undefined )
15441     {
15442         this.bIsValid = false;
15443         log( 'SlideTransition.parseElement: transition not valid: type: ' + sTypeAttr + ' subtype: ' + sSubTypeAttr );
15444     }
15446     // direction attribute
15447     this.bReverseDirection = false;
15448     var sDirectionAttr = aAnimElem.getAttributeNS( NSS['smil'], 'direction' );
15449     if( sDirectionAttr == 'reverse' )
15450         this.bReverseDirection = true;
15452     // fade color
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 ) )
15458     {
15459         var sColorAttr = aAnimElem.getAttributeNS( NSS['smil'], 'fadeColor' );
15460         if( sColorAttr )
15461             this.sFadeColor = sColorAttr;
15462         else
15463             this.sFadeColor='#000000';
15464     }
15467     // dur attribute
15468     this.aDuration = null;
15469     var sDurAttr = aAnimElem.getAttributeNS( NSS['smil'], 'dur' );
15470     this.aDuration = new Duration( sDurAttr );
15471     if( !this.aDuration.isSet() )
15472     {
15473         this.aDuration = new Duration( null ); // duration == 0.0
15474     }
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 + '>: ';
15531     // transition type
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';
15541     // transition mode
15542     sInfo += '; mode: ' + aTransitionModeOutMap[ this.getTransitionMode() ];
15544     // duration
15545     if( this.getDuration() )
15546         sInfo += '; duration: ' + this.getDuration().info();
15548     return sInfo;
15554 // SlideAnimations
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 )
15582         return false;
15584     this.aRootNode = createAnimationTree( aAnimationRootElement, this.aContext );
15586     return ( this.aRootNode ? true : false );
15589 SlideAnimations.prototype.parseElements = function()
15591     if( !this.aRootNode )
15592         return false;
15594     // parse all nodes
15595     if( !this.aRootNode.parseElement() )
15596         return false;
15597     else
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 )
15614         return false;
15616     return this.aRootNode.hasPendingAnimation();
15619 SlideAnimations.prototype.start = function()
15621     if( !this.bElementsParsed )
15622         return false;
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;
15636     // init all nodes
15637     this.aContext.bIsInvalid = !this.aRootNode.init();
15638     if( this.aContext.bIsInvalid )
15639         return false;
15641     // resolve root node
15642     return this.aRootNode.resolve();
15645 SlideAnimations.prototype.end = function( bLeftEffectsSkipped )
15647     if( !this.bElementsParsed )
15648         return; // no animations there
15650     // end root node
15651     this.aRootNode.deactivate();
15652     this.aRootNode.end();
15654     if( bLeftEffectsSkipped && this.isFirstRun() )
15655     {
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;
15660     }
15661     else if( this.isFirstRun() )
15662     {
15663         this.aContext.bFirstRun = false;
15664     }
15666     this.aContext.bIsInvalid = false;
15669 SlideAnimations.prototype.dispose = function()
15671     if( this.aRootNode )
15672     {
15673         this.aRootNode.dispose();
15674     }
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 )
15687     {
15688         this.aSourceEventElementMap[id].charge();
15689     }
15692 SlideAnimations.prototype.chargeInterAnimEvents = function()
15694     for( var id in this.aInteractiveAnimationSequenceMap )
15695     {
15696         this.aInteractiveAnimationSequenceMap[id].chargeEvents();
15697     }
15700 /**********************************************************************************************
15701  *      Event classes and helper functions
15702  **********************************************************************************************/
15705 function Event()
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()
15721     return this.nId;
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;
15742     this.aFunctor();
15743     return 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 )
15799         return false;
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.
15808     return true;
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 )
15859     {
15860         aSlideShowContext.aTimerEventQueue.addEvent( aEvent );
15861     }
15862     else if ( aNodeContext.bFirstRun )
15863     {
15864         var aEventMultiplexer = aSlideShowContext.aEventMultiplexer;
15865         if( !aEventMultiplexer )
15866         {
15867             log( 'registerEvent: event multiplexer not initialized' );
15868             return;
15869         }
15870         var aNextEffectEventArray = aSlideShowContext.aNextEffectEventArray;
15871         if( !aNextEffectEventArray )
15872         {
15873             log( 'registerEvent: next effect event array not initialized' );
15874             return;
15875         }
15876         var aInteractiveAnimationSequenceMap =
15877             aSlideShowContext.aInteractiveAnimationSequenceMap;
15878         if( !aInteractiveAnimationSequenceMap )
15879         {
15880             log( 'registerEvent: interactive animation sequence map not initialized' );
15881             return;
15882         }
15884         switch( eTimingType )
15885         {
15886             case EVENT_TIMING:
15887                 var eEventType = aTiming.getEventType();
15888                 var sEventBaseElemId = aTiming.getEventBaseElementId();
15889                 if( sEventBaseElemId )
15890                 {
15891                     var aEventBaseElem = document.getElementById( sEventBaseElemId );
15892                     if( !aEventBaseElem )
15893                     {
15894                         log( 'generateEvent: EVENT_TIMING: event base element not found: ' + sEventBaseElemId );
15895                         return;
15896                     }
15897                     var aSourceEventElement = aNodeContext.makeSourceEventElement( sEventBaseElemId, aEventBaseElem );
15899                     if( !aInteractiveAnimationSequenceMap[ nNodeId ] )
15900                     {
15901                         aInteractiveAnimationSequenceMap[ nNodeId ] = new InteractiveAnimationSequence(nNodeId);
15902                     }
15904                     var bEventRegistered = false;
15905                     switch( eEventType )
15906                     {
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;
15912                             break;
15913                         default:
15914                             log( 'generateEvent: not handled event type: ' + eEventType );
15915                     }
15916                     if( bEventRegistered )
15917                     {
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(
15923                             nNodeId,
15924                             bind2( InteractiveAnimationSequence.prototype.chargeEvents,
15925                                    aInteractiveAnimationSequenceMap[ nNodeId ] )
15926                         );
15927                     }
15928                 }
15929                 else  // no base event element present
15930                 {
15931                     switch( eEventType )
15932                     {
15933                         case EVENT_TRIGGER_ON_NEXT_EFFECT:
15934                             aNextEffectEventArray.appendEvent( aEvent );
15935                             break;
15936                         default:
15937                             log( 'generateEvent: not handled event type: ' + eEventType );
15938                     }
15939                 }
15940                 break;
15941             case SYNCBASE_TIMING:
15942                 eEventType = aTiming.getEventType();
15943                 sEventBaseElemId = aTiming.getEventBaseElementId();
15944                 if( sEventBaseElemId )
15945                 {
15946                     var aAnimationNode = aNodeContext.aAnimationNodeMap[ sEventBaseElemId ];
15947                     if( !aAnimationNode )
15948                     {
15949                         log( 'generateEvent: SYNCBASE_TIMING: event base element not found: ' + sEventBaseElemId );
15950                         return;
15951                     }
15952                     aEventMultiplexer.registerEvent( eEventType, aAnimationNode.getId(), aEvent );
15953                 }
15954                 else
15955                 {
15956                     log( 'generateEvent: SYNCBASE_TIMING: event base element not specified' );
15957                 }
15958                 break;
15959             default:
15960                 log( 'generateEvent: not handled timing type: ' + eTimingType );
15961         }
15962     }
15965 registerEvent.DEBUG = aRegisterEventDebugPrinter.isEnabled();
15967 registerEvent.DBG = function( aTiming, nTime )
15969     if( registerEvent.DEBUG )
15970     {
15971         aRegisterEventDebugPrinter.print( 'registerEvent( timing: ' + aTiming.info() + ' )', nTime );
15972     }
15978 function SourceEventElement( sId, aElement, aEventMultiplexer )
15980     this.sId = sId;
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()
15994     return this.sId;
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 )
16020         return false;
16022     this.aEventMultiplexer.notifyEvent( EVENT_TRIGGER_ON_CLICK, this.getId() );
16023     aSlideShow.update();
16024     this.bClickHandled = true;
16025     this.setDefaultCursor();
16026     return true;
16029 SourceEventElement.prototype.setPointerCursor = function()
16031     if( this.bClickHandled )
16032         return;
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 );
16047     if( !aElement )
16048     {
16049         log( 'error: HyperlinkElement: no element with id: <' + sId + '> found' );
16050         return;
16051     }
16052     if( !aEventMultiplexer )
16053     {
16054         log( 'AnimatedElement constructor: event multiplexer is not valid' );
16055     }
16057     this.sId = sId;
16058     this.aElement = aElement;
16059     this.aEventMultiplexer = aEventMultiplexer;
16060     this.nTargetSlideIndex = undefined;
16062     this.sURL = getNSAttribute( 'xlink', this.aElement, 'href' );
16063     if( this.sURL )
16064     {
16065         if( this.sURL[0] === '#' )
16066         {
16067             if( this.sURL.substr(1, 5) === 'Slide' )
16068             {
16069                 var sSlideIndex = this.sURL.split( ' ' )[1];
16070                 this.nTargetSlideIndex = parseInt( sSlideIndex ) - 1;
16071             }
16072         }
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 );
16082     }
16083     else
16084     {
16085         log( 'warning: HyperlinkElement(' + this.sId + '): url is empty' );
16086     }
16089 HyperlinkElement.prototype.onElementChanged = function( aElement )
16091     if( !aElement )
16092     {
16093         log( 'error: HyperlinkElement: passed element is not valid' );
16094         return;
16095     }
16097     if( this.sURL )
16098     {
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 );
16104     }
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 )
16124     {
16125         aSlideShow.displaySlide( this.nTargetSlideIndex, true );
16126     }
16127     else
16128     {
16129         var aWindowObject = document.defaultView;
16130         if( aWindowObject )
16131         {
16132             aWindowObject.open( this.sURL, this.sId );
16133         }
16134         else
16135         {
16136             log( 'error: HyperlinkElement.handleClick: invalid window object.' );
16137         }
16138     }
16140     return true;
16143 HyperlinkElement.prototype.setPointerCursor = function()
16145     if( this.bClickHandled )
16146         return;
16148     this.aElement.setAttribute( 'style', 'cursor: pointer' );
16151 HyperlinkElement.prototype.setDefaultCursor = function()
16153     this.aElement.setAttribute( 'style', 'cursor: default' );
16158 function InteractiveAnimationSequence( nId )
16160     this.nId = nId;
16161     this.bIsRunning = false;
16162     this.aStartEvent = null;
16163     this.aEndEvent = null;
16166 InteractiveAnimationSequence.prototype.getId = function()
16168     return this.nId;
16171 InteractiveAnimationSequence.prototype.getStartEvent = function()
16173     if( !this.aStartEvent )
16174     {
16175         this.aStartEvent =
16176             makeEvent( bind2( InteractiveAnimationSequence.prototype.start, this ) );
16177     }
16178     return this.aStartEvent;
16181 InteractiveAnimationSequence.prototype.getEndEvent = function()
16183     if( !this.aEndEvent )
16184     {
16185         this.aEndEvent =
16186             makeEvent( bind2( InteractiveAnimationSequence.prototype.end, this ) );
16187     }
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.
16219  *  @param aValue
16220  *      The object to be prioritized.
16221  *  @param nPriority
16222  *      An integral number representing the object priority.
16224  */
16225 function PriorityEntry( aValue, nPriority )
16227     this.aValue = aValue;
16228     this.nPriority = nPriority;
16231 /** EventEntry.compare
16232  *  Compare priority of two entries.
16234  *  @param aLhsEntry
16235  *      An instance of type PriorityEntry.
16236  *  @param aRhsEntry
16237  *      An instance of type PriorityEntry.
16238  *  @return Integer
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
16242  */
16243 PriorityEntry.compare = function( aLhsEntry, aRhsEntry )
16245     if ( aLhsEntry.nPriority < aRhsEntry.nPriority )
16246     {
16247         return -1;
16248     }
16249     else if (aLhsEntry.nPriority > aRhsEntry.nPriority)
16250     {
16251         return 1;
16252     }
16253     else
16254     {
16255         return 0;
16256     }
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()
16290     return this.nId;
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() )
16308     {
16309         var aHandlerEntry = aMouseClickHandlerSet.top();
16310         aMouseClickHandlerSet.pop();
16311         if( aHandlerEntry.aValue.handleClick( aMouseEvent ) )
16312             break;
16313     }
16316 EventMultiplexer.prototype.registerEvent = function( eEventType, aNotifierId, aEvent )
16318     this.DBG( 'registerEvent', eEventType, aNotifierId );
16319     if( !this.aEventMap[ eEventType ] )
16320     {
16321         this.aEventMap[ eEventType ] = {};
16322     }
16323     if( !this.aEventMap[ eEventType ][ aNotifierId ] )
16324     {
16325         this.aEventMap[ eEventType ][ aNotifierId ] = [];
16326     }
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 ] )
16335     {
16336         if( this.aEventMap[ eEventType ][ aNotifierId ] )
16337         {
16338             var aEventArray = this.aEventMap[ eEventType ][ aNotifierId ];
16339             var nSize = aEventArray.length;
16340             for( var i = 0; i < nSize; ++i )
16341             {
16342                 this.aTimerEventQueue.addEvent( aEventArray[i] );
16343             }
16344         }
16345     }
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 )
16368     {
16369         (this.aSkipEffectEndHandlerSet[i])();
16370     }
16371     this.aSkipEffectEndHandlerSet = [];
16374 EventMultiplexer.prototype.registerSkipEffectEvent = function( aEvent )
16376     this.aSkipEffectEvent = aEvent;
16379 EventMultiplexer.prototype.notifySkipEffectEvent = function()
16381     if( this.aSkipEffectEvent )
16382     {
16383         this.aTimerEventQueue.addEvent( this.aSkipEffectEvent );
16384         this.aSkipEffectEvent = null;
16385     }
16388 EventMultiplexer.prototype.registerRewindCurrentEffectEvent = function( aEvent )
16390     this.aRewindCurrentEffectEvent = aEvent;
16393 EventMultiplexer.prototype.notifyRewindCurrentEffectEvent = function()
16395     if( this.aRewindCurrentEffectEvent )
16396     {
16397         this.aTimerEventQueue.addEvent( this.aRewindCurrentEffectEvent );
16398         this.aRewindCurrentEffectEvent = null;
16399     }
16402 EventMultiplexer.prototype.registerRewindLastEffectEvent = function( aEvent )
16404     this.aRewindLastEffectEvent = aEvent;
16407 EventMultiplexer.prototype.notifyRewindLastEffectEvent = function()
16409     if( this.aRewindLastEffectEvent )
16410     {
16411         this.aTimerEventQueue.addEvent( this.aRewindLastEffectEvent );
16412         this.aRewindLastEffectEvent = null;
16413     }
16416 EventMultiplexer.prototype.registerSkipInteractiveEffectEvent = function( nNotifierId, aEvent )
16418     this.aSkipInteractiveEffectEventSet[ nNotifierId ] = aEvent;
16421 EventMultiplexer.prototype.notifySkipInteractiveEffectEvent = function( nNotifierId )
16423     if( this.aSkipInteractiveEffectEventSet[ nNotifierId ] )
16424     {
16425         this.aTimerEventQueue.addEvent( this.aSkipInteractiveEffectEventSet[ nNotifierId ] );
16426     }
16429 EventMultiplexer.prototype.registerRewindRunningInteractiveEffectEvent = function( nNotifierId, aEvent )
16431     this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] = aEvent;
16434 EventMultiplexer.prototype.notifyRewindRunningInteractiveEffectEvent = function( nNotifierId )
16436     if( this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] )
16437     {
16438         this.aTimerEventQueue.addEvent( this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] );
16439     }
16442 EventMultiplexer.prototype.registerRewindEndedInteractiveEffectEvent = function( nNotifierId, aEvent )
16444     this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] = aEvent;
16447 EventMultiplexer.prototype.notifyRewindEndedInteractiveEffectEvent = function( nNotifierId )
16449     if( this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] )
16450     {
16451         this.aTimerEventQueue.addEvent( this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] );
16452     }
16455 EventMultiplexer.prototype.registerRewindedEffectHandler = function( aNotifierId, aHandler )
16457     this.aRewindedEffectHandlerSet[ aNotifierId ] = aHandler;
16460 EventMultiplexer.prototype.notifyRewindedEffectEvent = function( aNotifierId )
16462     if( this.aRewindedEffectHandlerSet[ aNotifierId ] )
16463     {
16464         (this.aRewindedEffectHandlerSet[ aNotifierId ])();
16465     }
16468 EventMultiplexer.prototype.registerElementChangedHandler = function( aNotifierId, aHandler )
16470     this.aElementChangedHandlerSet[ aNotifierId ] = aHandler;
16473 EventMultiplexer.prototype.notifyElementChangedEvent = function( aNotifierId, aElement )
16475     if( this.aElementChangedHandlerSet[ aNotifierId ] )
16476     {
16477         (this.aElementChangedHandlerSet[ aNotifierId ])( aElement );
16478     }
16481 EventMultiplexer.DEBUG = aEventMultiplexerDebugPrinter.isEnabled();
16483 EventMultiplexer.prototype.DBG = function( sMethodName, eEventType, aNotifierId, nTime )
16485     if( EventMultiplexer.DEBUG )
16486     {
16487         var sInfo = 'EventMultiplexer.' + sMethodName;
16488         sInfo += '( type: ' + aEventTriggerOutMap[ eEventType ];
16489         sInfo += ', notifier: ' + aNotifierId + ' )';
16490         aEventMultiplexerDebugPrinter.print( sInfo, nTime );
16491     }
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 ] )
16507     {
16508         return aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ];
16509     }
16510     else if( bHasSubtype && aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ][ eValueSubtype ] )
16511     {
16512         return aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ][ eValueSubtype ];
16513     }
16514     else
16515     {
16516         log( 'aInterpolatorHandler.getInterpolator: not found any valid interpolator for calc mode '
16517              + aCalcModeOutMap[eCalcMode]  + ' and value type ' + aValueTypeOutMap[eValueType]  );
16518         return null;
16519     }
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 )
16531     {
16532         return ( ( 1.0 - nT )* nFrom + nT * nTo );
16533     };
16535 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ COLOR_PROPERTY ] = [];
16537 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ COLOR_PROPERTY ][ COLOR_SPACE_RGB ] =
16538     function ( nFrom, nTo, nT )
16539     {
16540         return RGBColor.interpolate( nFrom, nTo, nT );
16541     };
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 ] =
16546     function ( bCCW  )
16547     {
16548         return  function ( nFrom, nTo, nT )
16549                 {
16550                     return HSLColor.interpolate( nFrom, nTo, nT, bCCW );
16551                 };
16552     };
16554 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ TUPLE_NUMBER_PROPERTY ] =
16555     function ( aFrom, aTo, nT )
16556     {
16557         var aRes = [];
16558         for( var i = 0; i < aFrom.length; ++i )
16559         {
16560             aRes.push( ( 1.0 - nT )* aFrom[i] + nT * aTo[i] );
16561         }
16562         return aRes;
16563     };
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 )
16591     {
16592         if( aValueList[i-1] > aValueList[i] )
16593             log( 'KeyStopLerp.validateInput: time vector is not sorted in ascending order!' );
16594     }
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 ] )
16610     {
16611         do
16612         {
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 ];
16616         }
16617         while( ( this.nKeyStopDistance <= 0 ) && ( this.nLastIndex < this.nUpperBoundIndex ) );
16618     }
16620     var nRawLerp = ( nAlpha - this.aKeyStopList[ this.nLastIndex ] ) / this.nKeyStopDistance;
16622     nRawLerp = clamp( nRawLerp, 0.0, 1.0 );
16624     var aResult = {};
16625     aResult.nIndex = this.nLastIndex;
16626     aResult.nLerp = nRawLerp;
16628     return aResult;
16631 KeyStopLerp.prototype.lerp_ported = function( nAlpha )
16633     if( ( this.aKeyStopList[ this.nLastIndex ] < nAlpha ) ||
16634         ( this.aKeyStopList[ this.nLastIndex + 1 ] >= nAlpha ) )
16635     {
16636         var i = 0;
16637         for( ; i < this.aKeyStopList.length; ++i )
16638         {
16639             if( this.aKeyStopList[i] >= nAlpha )
16640                 break;
16641         }
16642         if( this.aKeyStopList[i] > nAlpha )
16643             --i;
16644         var nIndex = i - 1;
16645         this.nLastIndex = clamp( nIndex, 0, this.aKeyStopList.length - 2 );
16646     }
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 );
16653     var aResult = {};
16654     aResult.nIndex = this.nLastIndex;
16655     aResult.nLerp = nRawLerp;
16657     return aResult;
16662 /**********************************************************************************************
16663  *      Operators
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 )
16678     return ( a + b );
16681 aOperatorSetMap[ NUMBER_PROPERTY ].scale = function( k, v )
16683     return ( k * v );
16686 // color operators
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 )
16696     var c = a.clone();
16697     c.add( b );
16698     return c;
16701 aOperatorSetMap[ COLOR_PROPERTY ].scale = function( k, v )
16703     var r = v.clone();
16704     r.scale( k );
16705     return r;
16708 // enum operators
16709 aOperatorSetMap[ ENUM_PROPERTY ] = {};
16711 aOperatorSetMap[ ENUM_PROPERTY ].equal = function( a, b )
16713     return ( a === b );
16716 aOperatorSetMap[ ENUM_PROPERTY ].add = function( a )
16718     return a;
16721 aOperatorSetMap[ ENUM_PROPERTY ].scale = function( k, v )
16723     return v;
16726 // string operators
16727 aOperatorSetMap[ STRING_PROPERTY ] = aOperatorSetMap[ ENUM_PROPERTY ];
16729 // bool operators
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.' );
16744     var r = [];
16745     for( var i = 0; i < a.length; ++i )
16746     {
16747         r.push(a[i] + b[i]);
16748     }
16749     return r;
16752 aOperatorSetMap[ TUPLE_NUMBER_PROPERTY ].scale = function( k, v )
16754     var r = [];
16755     for( var i = 0; i < v.length; ++i )
16756     {
16757         r.push(k * v[i]);
16758     }
16759     return r;
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()
16805     return this.nId;
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()
16840     return 0.0;
16843 SetActivity.prototype.perform = function()
16845     if( !this.isActive() )
16846         return false;
16848     // we're going inactive immediately:
16849     this.bIsActive = false;
16851     if( this.aAnimation && this.aTargetElement )
16852     {
16853         this.aAnimation.start( this.aTargetElement );
16854         this.aAnimation.perform( this.aToAttr );
16855         this.aAnimation.end();
16856     }
16858     if( this.aEndEvent )
16859         this.aTimerEventQueue.addEvent( this.aEndEvent );
16863 SetActivity.prototype.isActive = function()
16865     return this.bIsActive;
16868 SetActivity.prototype.dequeued = function()
16870     // empty body
16873 SetActivity.prototype.end = function()
16875     this.perform();
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()
16915     // deactivate
16916     this.bIsActive = false;
16918     // dispose event
16919     if( this.aEndEvent )
16920         this.aEndEvent.dispose();
16922     this.aEndEvent = null;
16925 ActivityBase.prototype.perform = function()
16927     // still active?
16928     if( !this.isActive() )
16929         return false; // no, early exit.
16931     assert( !this.bFirstPerformCall, 'ActivityBase.perform: assertion (!this.FirstPerformCall) failed' );
16933     return true;
16936 ActivityBase.prototype.calcTimeLag = function()
16938     // TODO(Q1): implement different init process!
16939     if( this.isActive() && this.bFirstPerformCall )
16940     {
16941         this.bFirstPerformCall = false;
16943         // notify derived classes that we're
16944         // starting now
16945         this.startAnimation();
16946     }
16947     return 0.0;
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 ) )
17009     {
17010         var nC = 1.0 - 0.5*this.nAccelerationFraction - 0.5*this.nDecelerationFraction;
17012         // this variable accumulates the new time value
17013         var nTPrime = 0.0;
17015         if( nT < this.nAccelerationFraction )
17016         {
17017             nTPrime += 0.5 * nT * nT / this.nAccelerationFraction; // partial first interval
17018         }
17019         else
17020         {
17021             nTPrime += 0.5 * this.nAccelerationFraction; // full first interval
17023             if( nT <= ( 1.0 - this.nDecelerationFraction ) )
17024             {
17025                 nTPrime += nT - this.nAccelerationFraction; // partial second interval
17026             }
17027             else
17028             {
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;
17034             }
17035         }
17037         // normalize, and assign to work variable
17038         nT = nTPrime / nC;
17040     }
17041     return nT;
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() )
17072         return;
17074     // assure animation is started:
17075     if( this.bFirstPerformCall )
17076     {
17077         this.bFirstPerformCall = false;
17078         // notify derived classes that we're starting now
17079         this.startAnimation();
17080     }
17082     this.performEnd();
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() )
17129     {
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;
17140     }
17141     else
17142     {
17143         return nCurrCalls % nVectorSize;
17144     }
17147 DiscreteActivityBase.prototype.calcRepeatCount = function( nCurrCalls, nVectorSize )
17149     if( this.isAutoReverse() )
17150     {
17151         return Math.floor( nCurrCalls / (2*nVectorSize) ); // we've got 2 cycles per repeat
17152     }
17153     else
17154     {
17155         return Math.floor( nCurrCalls / nVectorSize );
17156     }
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() )
17186         nCurrRepeat /= 2;
17188     // schedule next frame, if either repeat is indefinite
17189     // (repeat forever), or we've not yet reached the requested
17190     // repeat count
17191     if( !this.isRepeatCountValid() || nCurrRepeat < this.getRepeatCount() )
17192     {
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
17197         // times.
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 );
17209     }
17210     else
17211     {
17212         // release event reference (relation to wake up event is circular!)
17213         this.aWakeupEvent = null;
17215         // done with this activity
17216         this.endActivity();
17217     }
17219     return false; // remove from queue, will be added back by the wakeup event.
17222 DiscreteActivityBase.prototype.dispose = function()
17224     // dispose event
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() )
17266         return 0.0;
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
17276     // times.
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:
17285     //
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.
17290     //
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
17297     // time line.
17299     if( nFractionElapsedTime < nFractionRequiredCalls )
17300     {
17301         return 0.0;
17302     }
17303     else
17304     {
17305         // lag global time, so all other animations lag, too:
17306         return ( ( nFractionElapsedTime - nFractionRequiredCalls ) * this.nMinSimpleDuration );
17307     }
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() )
17327     {
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 )
17336         {
17337             // Ok done for now. Will not exit right here,
17338             // to give animation the chance to render the last
17339             // frame below
17340             bActivityEnding = true;
17342             // clamp animation to max permissible value
17343             nT = nEffectiveRepeat;
17344         }
17345     }
17348     // need to do auto-reverse?
17350     var nRepeats;
17351     var nRelativeSimpleTime;
17352     // TODO(Q3): Refactor this mess
17353     if( this.isAutoReverse() )
17354     {
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.
17362         if( nRepeats % 2 )
17363         {
17364             // we're in an odd range, reverse sweep
17365             nRelativeSimpleTime = 1.0 - nFractionalActiveDuration;
17366         }
17367         else
17368         {
17369             // we're in an even range, pass on as is
17370             nRelativeSimpleTime = nFractionalActiveDuration;
17371         }
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)
17376         nRepeats /= 2;
17377     }
17378     else
17379     {
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() ) )
17391         {
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.
17406             //
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;
17411             nRepeats -= 1.0;
17412         }
17413     }
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;
17445     assert( nSize > 1,
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 )
17524         return;
17526     ANIMDBG.print( 'SimpleActivity.startAnimation invoked' );
17527     SimpleActivity.superclass.startAnimation.call( this );
17529     // start animation
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 )
17545         return;
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 )
17569     {
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;
17592     }
17593     extend( FromToByActivity, BaseType );
17595     FromToByActivity.prototype.initAnimatedElement = function()
17596     {
17597         if( this.aAnimation && this.aFrom )
17598         {
17599             var aValue = this.aFormula ? this.aFormula( this.aFrom ) : this.aFrom;
17600             this.aAnimation.perform(aValue);
17601         }
17602     };
17604     FromToByActivity.prototype.startAnimation = function()
17605     {
17606         if( this.isDisposed() || !this.aAnimation  )
17607         {
17608             log( 'FromToByActivity.startAnimation: activity disposed or not valid animation' );
17609             return;
17610         }
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
17624         if( this.aFrom )
17625         {
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
17629             if( this.aTo )
17630             {
17631                 // From-To animation
17632                 this.aStartValue = this.aFrom;
17633                 this.aEndValue = this.aTo;
17634             }
17635             else if( this.aBy )
17636             {
17637                 // From-By animation
17638                 this.aStartValue = this.aFrom;
17640                 this.aEndValue = this.add( this.aStartValue, this.aBy );
17641             }
17642         }
17643         else
17644         {
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
17651             if( this.aTo )
17652             {
17653                 // To animation
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;
17662             }
17663             else if( this.aBy )
17664             {
17665                 // By animation
17666                 this.aStartValue = aAnimationStartValue;
17668                 this.aEndValue = this.add( this.aStartValue, this.aBy );
17669             }
17670         }
17672         ANIMDBG.print( 'FromToByActivity.startAnimation: aStartValue = ' + this.aStartValue + ', aEndValue = ' + this.aEndValue );
17673     };
17675     FromToByActivity.prototype.endAnimation = function()
17676     {
17677         if( this.aAnimation )
17678             this.aAnimation.end();
17679     };
17681     // perform hook override for ContinuousActivityBase
17682     FromToByActivity.prototype.performContinuousHook = function( nModifiedTime, nRepeatCount )
17683     {
17684         if( this.isDisposed() || !this.aAnimation  )
17685         {
17686             log( 'FromToByActivity.performContinuousHook: activity disposed or not valid animation' );
17687             return;
17688         }
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.
17703         // See:
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 )
17712         {
17713             if( this.nIteration != nRepeatCount )
17714             {
17715                 this.nIteration = nRepeatCount;
17716                 this.aStartInterpolationValue =  this.aStartValue;
17717             }
17718             else
17719             {
17720                 var aActualValue = this.aAnimation.getUnderlyingValue();
17721                 if( !this.equal( aActualValue, this.aPreviousValue ) )
17722                     this.aStartInterpolationValue = aActualValue;
17723             }
17724         }
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 )
17733         {
17734             // aValue = this.aEndValue * nRepeatCount + aValue;
17735             aValue = this.add( this.scale( nRepeatCount, this.aEndValue ), aValue );
17736         }
17738         aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17739         this.aAnimation.perform( aValue );
17741         if( this.bDynamicStartValue )
17742         {
17743             this.aPreviousValue = this.aAnimation.getUnderlyingValue();
17744         }
17746     };
17748     // perform hook override for DiscreteActivityBase
17749     FromToByActivity.prototype.performDiscreteHook = function( /*nFrame, nRepeatCount*/ )
17750     {
17751         if (this.isDisposed() || !this.aAnimation) {
17752             log('FromToByActivity.performDiscreteHook: activity disposed or not valid animation');
17753             return;
17754         }
17755     };
17757     FromToByActivity.prototype.performEnd = function()
17758     {
17759         if( this.aAnimation )
17760         {
17761             var aValue = this.isAutoReverse() ? this.aStartValue : this.aEndValue;
17762             aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17763             this.aAnimation.perform( aValue );
17764         }
17765     };
17767     FromToByActivity.prototype.dispose = function()
17768     {
17769         FromToByActivity.superclass.dispose.call( this );
17770     };
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 )
17794     {
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;
17808     }
17809     extend( ValueListActivity, BaseType );
17811     ValueListActivity.prototype.activate = function( aEndEvent )
17812     {
17813         ValueListActivity.superclass.activate.call( this, aEndEvent );
17814         for( var i = 0; i < this.aValueList.length; ++i )
17815         {
17816             ANIMDBG.print( 'createValueListActivity: value[' + i + '] = ' + this.aValueList[i] );
17817         }
17818     };
17820     ValueListActivity.prototype.initAnimatedElement = function()
17821     {
17822         if( this.aAnimation )
17823         {
17824             var aValue = this.aValueList[0];
17825             aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17826             this.aAnimation.perform(aValue);
17827         }
17828     };
17830     ValueListActivity.prototype.startAnimation = function()
17831     {
17832         if( this.isDisposed() || !this.aAnimation  )
17833         {
17834             log( 'ValueListActivity.startAnimation: activity disposed or not valid animation' );
17835             return;
17836         }
17838         ValueListActivity.superclass.startAnimation.call( this );
17840         this.aAnimation.start( this.getTargetElement() );
17841     };
17843     ValueListActivity.prototype.endAnimation = function()
17844     {
17845         if( this.aAnimation )
17846             this.aAnimation.end();
17847     };
17849     // perform hook override for ContinuousKeyTimeActivityBase base
17850     ValueListActivity.prototype.performContinuousHook = function( nIndex, nFractionalIndex, nRepeatCount )
17851     {
17852         if( this.isDisposed() || !this.aAnimation  )
17853         {
17854             log( 'ValueListActivity.performContinuousHook: activity disposed or not valid animation' );
17855             return;
17856         }
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 )
17868         {
17869             //aValue = aValue + nRepeatCount * this.aLastValue;
17870             aValue = this.add( aValue, this.scale( nRepeatCount, this.aLastValue ) );
17871         }
17873         aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17874         this.aAnimation.perform( aValue );
17875     };
17877     // perform hook override for DiscreteActivityBase base
17878     ValueListActivity.prototype.performDiscreteHook = function( nFrame, nRepeatCount )
17879     {
17880         if( this.isDisposed() || !this.aAnimation  )
17881         {
17882             log( 'ValueListActivity.performDiscreteHook: activity disposed or not valid animation' );
17883             return;
17884         }
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 )
17893         {
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;
17897         }
17899         aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17900         this.aAnimation.perform( aValue );
17901     };
17903     ValueListActivity.prototype.performEnd = function()
17904     {
17905         if( this.aAnimation )
17906         {
17907             var aValue = this.aFormula ? this.aFormula( this.aLastValue ) : this.aLastValue;
17908             this.aAnimation.perform( aValue );
17909         }
17910     };
17912     ValueListActivity.prototype.dispose = function()
17913     {
17914         ValueListActivity.superclass.dispose.call( this );
17915     };
17918     return ValueListActivity;
17922 //  ValueListActivity< ContinuousKeyTimeActivityBase > instantiation
17923 var LinearValueListActivity = instantiate( ValueListActivityTemplate, ContinuousKeyTimeActivityBase );
17924 //  ValueListActivity< DiscreteActivityBase > instantiation
17925 var DiscreteValueListActivity = instantiate( ValueListActivityTemplate, DiscreteActivityBase );
17929 /**********************************************************************************************
17930  *      Activity Factory
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 )
17946     {
17947         aInterpolator = aInterpolatorHandler.getInterpolator( eCalcMode,
17948                                                               eValueType,
17949                                                               eValueSubtype );
17950     }
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() )
17959     {
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);
17980         };
17981         /* eslint-enable no-unused-vars */
17982     }
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 )
17991     {
17992         // Value list activity
17994         if( aActivityParamSet.aDiscreteTimes.length == 0 )
17995         {
17996             for( var i = 0; i < nValueSetSize; ++i )
17997                 aActivityParamSet.aDiscreteTimes[i].push( i / nValueSetSize );
17998         }
18000         switch( eCalcMode )
18001         {
18002             case CALC_MODE_DISCRETE:
18003                 aActivityParamSet.aWakeupEvent =
18004                         new WakeupEvent( aActivityParamSet.aTimerEventQueue.getTimer(),
18005                                          aActivityParamSet.aActivityQueue );
18007                 return createValueListActivity( aActivityParamSet,
18008                                                 aAnimationNode,
18009                                                 aAnimation,
18010                                                 aInterpolator,
18011                                                 DiscreteValueListActivity,
18012                                                 bAccumulate,
18013                                                 eValueType );
18015             default:
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,
18022                                                 aAnimationNode,
18023                                                 aAnimation,
18024                                                 aInterpolator,
18025                                                 LinearValueListActivity,
18026                                                 bAccumulate,
18027                                                 eValueType );
18028         }
18029     }
18030     else
18031     {
18032         // FromToBy activity
18033         switch( eCalcMode )
18034         {
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,
18041                                                 aAnimationNode,
18042                                                 aAnimation,
18043                                                 aInterpolator,
18044                                                 DiscreteFromToByActivity,
18045                                                 bAccumulate,
18046                                                 eValueType );
18048             default:
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,
18055                                                 aAnimationNode,
18056                                                 aAnimation,
18057                                                 aInterpolator,
18058                                                 LinearFromToByActivity,
18059                                                 bAccumulate,
18060                                                 eValueType );
18061         }
18062     }
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,
18080                             aValueList,
18081                             aValueSet,
18082                             aAnimatedElement.getBaseBBox(),
18083                             aActivityParamSet.nSlideWidth,
18084                             aActivityParamSet.nSlideHeight );
18086     for( var i = 0; i < aValueList.length; ++i )
18087     {
18088         ANIMDBG.print( 'createValueListActivity: value[' + i + '] = ' + aValueList[i] );
18089     }
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,
18119                             aValueList,
18120                             aValueSet,
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 )
18139     var i;
18140     switch( eValueType )
18141     {
18142         case NUMBER_PROPERTY :
18143             evalValuesAttribute( aValueList, aValueSet, aBBox, nSlideWidth, nSlideHeight );
18144             break;
18145         case BOOL_PROPERTY :
18146             for( i = 0; i < aValueSet.length; ++i )
18147             {
18148                 var aValue = booleanParser( aValueSet[i] );
18149                 aValueList.push( aValue );
18150             }
18151             break;
18152         case STRING_PROPERTY :
18153             for( i = 0; i < aValueSet.length; ++i )
18154             {
18155                 aValueList.push( aValueSet[i] );
18156             }
18157             break;
18158         case ENUM_PROPERTY :
18159             for( i = 0; i < aValueSet.length; ++i )
18160             {
18161                 aValueList.push( aValueSet[i] );
18162             }
18163             break;
18164         case COLOR_PROPERTY :
18165             for( i = 0; i < aValueSet.length; ++i )
18166             {
18167                 aValue = colorParser( aValueSet[i] );
18168                 aValueList.push( aValue );
18169             }
18170             break;
18171         case TUPLE_NUMBER_PROPERTY :
18172             for( i = 0; i < aValueSet.length; ++i )
18173             {
18174                 if( typeof aValueSet[i] === 'string' )
18175                 {
18176                     var aTuple = aValueSet[i].split(',');
18177                     aValue = [];
18178                     evalValuesAttribute(aValue, aTuple, aBBox, nSlideWidth, nSlideHeight);
18179                     aValueList.push(aValue);
18180                 }
18181                 else
18182                 {
18183                     aValueList.push( undefined );
18184                 }
18185             }
18186             break;
18187         default:
18188             log( 'createValueListActivity: unexpected value type: ' + eValueType );
18189     }
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 )
18207     {
18208         var sValue = aValueSet[i];
18209         if(sValue)
18210         {
18211             sValue = sValue.replace(reMath, 'Math.$&');
18212             sValue = sValue.replace(/pi(?!\w)/g, 'Math.PI');
18213             sValue = sValue.replace(/e(?!\w)/g, 'Math.E');
18214         }
18215         var aValue =  eval( sValue );
18216         aValueList.push( aValue );
18217     }
18222 /**********************************************************************************************
18223  *      SlideShow, SlideShowContext and FrameSynchronization
18224  **********************************************************************************************/
18228 // direction of animation, important: not change the values!
18229 var BACKWARD    = 0;
18230 var FORWARD     = 1;
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;
18247 Effect.ENDED = 2;
18249 Effect.prototype.getId = function()
18251     return this.nId;
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 )
18340     {
18341         log( 'SlideShow.createSlideTransition: entering slide element is not valid.' );
18342         return null;
18343     }
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() )
18358     {
18359         nDuration = aSlideTransitionHandler.getDuration().getValue();
18360     }
18361     else
18362     {
18363         log( 'SlideShow.createSlideTransition: duration is not a number' );
18364     }
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();
18421     aEffect.start();
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 )
18438     {
18439         if( this.automaticAdvanceTimeout['rewindedEffect'] === this.nCurrentEffect )
18440         {
18441             this.automaticAdvanceTimeout = null;
18442             this.notifyAnimationsEnd();
18443         }
18444     }
18447 SlideShow.prototype.notifyAnimationsEnd = function()
18449     if( nCurSlide + 1 === theMetaDoc.nNumberOfSlides )
18450         return;
18452     assert (this.automaticAdvanceTimeout === null,
18453         'SlideShow.notifyAnimationsEnd: Timeout already set.')
18455     var nTimeout = Math.ceil(theMetaDoc.aMetaSlideSet[nCurSlide].fDuration * 1000);
18456     if( nTimeout < 0 )
18457         return;
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;
18474     var sId;
18475     if( nOldSlideIndex !== undefined )
18476     {
18477         aAnimatedElementMap = theMetaDoc.aMetaSlideSet[nOldSlideIndex].aSlideAnimationsHandler.aAnimatedElementMap;
18478         for( sId in aAnimatedElementMap )
18479             aAnimatedElementMap[ sId ].notifySlideEnd();
18480     }
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() )
18492     {
18493         var sRef = 'url(#' + aPresentationClipPathId + ')';
18494         theMetaDoc.getCurrentSlide().slideElement.setAttribute('clip-path', sRef);
18495     }
18497     this.bIsTransitionRunning = false;
18498     if( this.bIsRewinding )
18499     {
18500         theMetaDoc.aMetaSlideSet[nSlideIndex].hide();
18501         var nIndex = nCurSlide !== undefined ? nCurSlide : -1;
18502         this.displaySlide( nIndex, true );
18503         this.skipAllEffects();
18504         this.bIsRewinding = false;
18505         return;
18506     }
18508     theMetaDoc.setCurrentSlide(nSlideIndex);
18510     if( this.aSlideViewElement )
18511     {
18512         theMetaDoc.getCurrentSlide().aVisibilityStatusElement.parentNode.removeChild( this.aSlideViewElement );
18513         this.aSlideViewElement = null;
18514     }
18515     if( this.isEnabled() )
18516     {
18517         // clear all queues
18518         this.dispose();
18520         var aCurrentSlide = theMetaDoc.getCurrentSlide();
18521         if( aCurrentSlide.aSlideAnimationsHandler.elementsParsed() )
18522         {
18523                         aCurrentSlide.aSlideAnimationsHandler.start();
18524                         this.aEventMultiplexer.registerAnimationsEndHandler( bind2( SlideShow.prototype.notifyAnimationsEnd, this ) );
18525         }
18526         else
18527             this.notifyAnimationsEnd();
18529         this.update();
18530     }
18531     else
18532         this.notifyAnimationsEnd();
18535 SlideShow.prototype.notifyInteractiveAnimationSequenceStart = function( nNodeId )
18537     ++this.nTotalInteractivePlayingEffects;
18538     var aEffect = new Effect( nNodeId );
18539     aEffect.start();
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;
18553 /** nextEffect
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
18556  *  it is skipped.
18558  *  @return {Boolean}
18559  *      False if there is no more effect to start, true otherwise.
18560  */
18561 SlideShow.prototype.nextEffect = function()
18563     if( !this.isEnabled() )
18564         return false;
18566     if( this.isTransitionPlaying() )
18567     {
18568         this.skipTransition();
18569         return true;
18570     }
18572     if( this.isAnyEffectPlaying() )
18573     {
18574         this.skipAllPlayingEffects();
18575         return true;
18576     }
18578     if( !this.aNextEffectEventArray )
18579         return false;
18581     if( this.nCurrentEffect >= this.aNextEffectEventArray.size() )
18582         return false;
18584     this.notifyNextEffectStart();
18586     this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
18587     ++this.nCurrentEffect;
18588     this.update();
18589     return true;
18592 /** skipTransition
18593  *  Skip the current playing slide transition.
18594  */
18595 SlideShow.prototype.skipTransition  = function()
18597     if( this.bIsSkipping || this.bIsRewinding )
18598         return;
18600     this.bIsSkipping = true;
18602     this.aActivityQueue.endAll();
18603     this.aTimerEventQueue.forceEmpty();
18604     this.aActivityQueue.endAll();
18605     this.update();
18606     this.bIsSkipping = false;
18609 /** skipAllPlayingEffects
18610  *  Skip all playing effect, independently to which animation sequence they
18611  *  belong.
18613  */
18614 SlideShow.prototype.skipAllPlayingEffects  = function()
18616     if( this.bIsSkipping || this.bIsRewinding )
18617         return true;
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 )
18622     {
18623         var aEffect = this.aStartedEffectList[i];
18624         if( aEffect.isPlaying() )
18625         {
18626             if( aEffect.isMainEffect() )
18627                 this.aEventMultiplexer.notifySkipEffectEvent();
18628             else
18629                 this.aEventMultiplexer.notifySkipInteractiveEffectEvent( aEffect.getId() );
18630         }
18631     }
18632     this.update();
18633     this.bIsSkipping = false;
18634     return true;
18637 /** skipNextEffect
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.
18644  */
18645 SlideShow.prototype.skipNextEffect = function()
18647     if( this.bIsSkipping || this.bIsRewinding )
18648         return true;
18650     assert( !this.isAnyEffectPlaying(),
18651             'SlideShow.skipNextEffect' );
18653     if( !this.aNextEffectEventArray )
18654         return false;
18656     if( this.nCurrentEffect >= this.aNextEffectEventArray.size() )
18657         return false;
18659     this.notifyNextEffectStart();
18661     this.bIsSkipping = true;
18662     this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
18663     this.aEventMultiplexer.notifySkipEffectEvent();
18664     ++this.nCurrentEffect;
18665     this.update();
18666     this.bIsSkipping = false;
18667     return true;
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.
18676  */
18677 SlideShow.prototype.skipPlayingOrNextEffect = function()
18679     if( this.isTransitionPlaying() )
18680     {
18681         this.skipTransition();
18682         return true;
18683     }
18685     if( this.isAnyEffectPlaying() )
18686         return this.skipAllPlayingEffects();
18687     else
18688         return this.skipNextEffect();
18692 /** skipAllEffects
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.
18699  */
18700 SlideShow.prototype.skipAllEffects = function()
18702     if( this.bIsSkippingAll )
18703         return true;
18705     this.bIsSkippingAll = true;
18707     if( this.isTransitionPlaying() )
18708     {
18709         this.skipTransition();
18710     }
18712     if( this.isAnyEffectPlaying() )
18713     {
18714         this.skipAllPlayingEffects();
18715     }
18716     else if( !this.aNextEffectEventArray
18717                || ( this.nCurrentEffect >= this.aNextEffectEventArray.size() ) )
18718     {
18719         this.bIsSkippingAll = false;
18720         return false;
18721     }
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() )
18730     {
18731         this.skipNextEffect();
18732     }
18733     this.bIsSkippingAll = false;
18734     return true;
18737 /** rewindTransition
18738  * Rewind the current playing slide transition.
18739  */
18740 SlideShow.prototype.rewindTransition = function()
18742     if( this.bIsSkipping || this.bIsRewinding )
18743     return;
18745     this.bIsRewinding = true;
18746     this.aActivityQueue.endAll();
18747     this.update();
18748     this.bIsRewinding = false;
18751 /** rewindEffect
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.
18756  */
18757 SlideShow.prototype.rewindEffect = function()
18759     if( this.bIsSkipping || this.bIsRewinding )
18760         return;
18762         if( this.automaticAdvanceTimeout !== null && !this.automaticAdvanceTimeout['rewindedEffect'] )
18763         {
18764                 window.clearTimeout( this.automaticAdvanceTimeout );
18765                 this.automaticAdvanceTimeout = { 'rewindedEffect': this.nCurrentEffect };
18766         }
18768     if( !this.hasAnyEffectStarted() )
18769     {
18770         this.rewindToPreviousSlide();
18771         return;
18772     }
18774     this.bIsRewinding = true;
18776     var nFirstPlayingEffectIndex = undefined;
18778     var i = 0;
18779     for( ; i < this.aStartedEffectList.length; ++i )
18780     {
18781         var aEffect = this.aStartedEffectList[i];
18782         if( aEffect.isPlaying() )
18783         {
18784             nFirstPlayingEffectIndex = i;
18785             break;
18786         }
18787     }
18789     // There is at least one playing effect.
18790     if( nFirstPlayingEffectIndex !== undefined )
18791     {
18792         i = this.aStartedEffectList.length - 1;
18793         for( ; i >= nFirstPlayingEffectIndex; --i )
18794         {
18795             aEffect = this.aStartedEffectList[i];
18796             if( aEffect.isPlaying() )
18797             {
18798                 if( aEffect.isMainEffect() )
18799                 {
18800                     this.aEventMultiplexer.notifyRewindCurrentEffectEvent();
18801                     if( this.nCurrentEffect > 0 )
18802                         --this.nCurrentEffect;
18803                 }
18804                 else
18805                 {
18806                     this.aEventMultiplexer.notifyRewindRunningInteractiveEffectEvent( aEffect.getId() );
18807                 }
18808             }
18809             else if( aEffect.isEnded() )
18810             {
18811                 if( aEffect.isMainEffect() )
18812                 {
18813                     this.aEventMultiplexer.notifyRewindLastEffectEvent();
18814                     if( this.nCurrentEffect > 0 )
18815                         --this.nCurrentEffect;
18816                 }
18817                 else
18818                 {
18819                     this.aEventMultiplexer.notifyRewindEndedInteractiveEffectEvent( aEffect.getId() );
18820                 }
18821             }
18822         }
18823         this.update();
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 )
18829         {
18830             aEffect = this.aStartedEffectList.pop();
18831             if( !aEffect.isMainEffect() )
18832                 delete this.aStartedEffectIndexMap[ aEffect.getId() ];
18833         }
18834     }
18835     else  // there is no playing effect
18836     {
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.
18841         {
18842             if( aEffect.isMainEffect() )
18843             {
18844                 this.aEventMultiplexer.notifyRewindLastEffectEvent();
18845                 if( this.nCurrentEffect > 0 )
18846                     --this.nCurrentEffect;
18847             }
18848             else
18849             {
18850                 this.aEventMultiplexer.notifyRewindEndedInteractiveEffectEvent( aEffect.getId() );
18851             }
18852         }
18853         this.update();
18854     }
18856     this.bIsRewinding = false;
18859 /** rewindToPreviousSlide
18860  *  Displays the previous slide with all effects, that belong to the main
18861  *  animation sequence, played.
18863  */
18864 SlideShow.prototype.rewindToPreviousSlide = function()
18866     if( this.isTransitionPlaying() )
18867     {
18868         this.rewindTransition();
18869         return;
18870     }
18871     if( this.isAnyEffectPlaying() )
18872         return;
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.
18881  */
18882 SlideShow.prototype.rewindAllEffects = function()
18884     if( !this.hasAnyEffectStarted() )
18885     {
18886         this.rewindToPreviousSlide();
18887         return;
18888     }
18890     while( this.hasAnyEffectStarted() )
18891     {
18892         this.rewindEffect();
18893     }
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 ) {
18916         nNewSlide = 0;
18917         // In the iOS app, exit the slideshow when going past the end.
18918         this.exitSlideShowInApp();
18919     }
18921     if( ( currentMode === INDEX_MODE ) && ( nNewSlide === nCurSlide ) )
18922     {
18923         aMetaDoc.getCurrentSlide().show();
18924         return;
18925     }
18927     if( this.isTransitionPlaying() )
18928     {
18929         this.skipTransition();
18930     }
18932     // handle current slide
18933     var nOldSlide = nCurSlide;
18934     if( nOldSlide !== undefined )
18935     {
18936         var oldMetaSlide = aMetaDoc.aMetaSlideSet[nOldSlide];
18937         if( this.isEnabled() )
18938         {
18939             if( oldMetaSlide.aSlideAnimationsHandler.isAnimated() )
18940             {
18941                 // force end animations
18942                 oldMetaSlide.aSlideAnimationsHandler.end( bSkipSlideTransition );
18944                 // clear all queues
18945                 this.dispose();
18946             }
18947         }
18949         if( this.automaticAdvanceTimeout !== null )
18950         {
18951             window.clearTimeout( this.automaticAdvanceTimeout );
18952             this.automaticAdvanceTimeout = null;
18953         }
18954     }
18956     this.notifySlideStart( nNewSlide, nOldSlide );
18958     if( this.isEnabled() && !bSkipSlideTransition  )
18959     {
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
18965            )
18966         {
18968             var aOldMetaSlide = null;
18969             if( nOldSlide === undefined ) // for transition on first slide
18970             {
18971                 aOldMetaSlide = aMetaDoc.theMetaDummySlide;
18972             }
18973             else
18974             {
18975                 aOldMetaSlide = aMetaDoc.aMetaSlideSet[nOldSlide];
18976             }
18977             var aNewMetaSlide = aMetaDoc.aMetaSlideSet[nNewSlide];
18979             var aSlideTransitionHandler = aNewMetaSlide.aTransitionHandler;
18980             if( aSlideTransitionHandler && aSlideTransitionHandler.isValid() )
18981             {
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 )
18991                 {
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();
18996                 }
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 )
19007                 {
19008                     this.bIsTransitionRunning = true;
19009                     this.aActivityQueue.addActivity( aTransitionActivity );
19010                     this.update();
19011                 }
19012                 else
19013                 {
19014                     this.notifyTransitionEnd( nNewSlide );
19015                 }
19016             }
19017             else
19018             {
19019                 this.notifyTransitionEnd( nNewSlide );
19020             }
19021         }
19022         else
19023         {
19024             this.notifyTransitionEnd( nNewSlide );
19025         }
19026     }
19027     else
19028     {
19029         this.notifyTransitionEnd( nNewSlide );
19030     }
19034 SlideShow.prototype.update = function()
19036     this.aTimer.holdTimer();
19038     // process queues
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 );
19053     if( bEventsLeft )
19054     {
19055         var nNextTimeout;
19056         if( bActivitiesLeft )
19057         {
19058             nNextTimeout = MINIMUM_TIMEOUT;
19059             this.aFrameSynchronization.activate();
19060         }
19061         else
19062         {
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();
19069         }
19071         this.bIsIdle = false;
19072         window.setTimeout( 'aSlideShow.update()', nNextTimeout * 1000 );
19073     }
19074     else
19075     {
19076         this.bIsIdle = true;
19077     }
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 )
19132     {
19133         // Do busy waiting for now.
19134         while( this.aTimer.getElapsedTime() < this.nNextFrameTargetTime )
19135             ;
19136     }
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 )
19179     {
19180         if( this.aEventArray[i].getId() == aEvent.getId() )
19181         {
19182             aNextEffectEventArrayDebugPrinter.print( 'NextEffectEventArray.appendEvent: event(' + aEvent.getId() + ') already present' );
19183             return false;
19184         }
19185     }
19186     this.aEventArray.push( aEvent );
19187     aNextEffectEventArrayDebugPrinter.print( 'NextEffectEventArray.appendEvent: event(' + aEvent.getId() + ') appended' );
19188     return true;
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.' );
19209     if( !aEvent )
19210     {
19211         log( 'TimerEventQueue.addEvent: null event' );
19212         return false;
19213     }
19215     var nTime = aEvent.getActivationTime( this.aTimer.getElapsedTime() );
19216     var aEventEntry = new EventEntry( aEvent, nTime );
19217     this.aEventSet.push( aEventEntry );
19219     return true;
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 ) ) )
19238     {
19239         var aEventEntry = this.aEventSet.top();
19240         this.aEventSet.pop();
19242         var aEvent = aEventEntry.aEvent;
19243         if( aEvent.isCharged() )
19244             aEvent.fire();
19245     }
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;
19259     return nTimeout;
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 ) )
19285     {
19286         this.aEventSet[ nHoleIndex ] = this.aEventSet[ nParent ];
19287         nHoleIndex = nParent;
19288         nParent = Math.floor( ( nHoleIndex - 1 ) / 2 );
19289     }
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 )
19306     {
19307         return -1;
19308     }
19309     else if ( aLhsEventEntry.nActivationTime < aRhsEventEntry.nActivationTime )
19310     {
19311         return 1;
19312     }
19313     else
19314     {
19315         return 0;
19316     }
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;
19334     var i;
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 )
19345     if( !aActivity )
19346     {
19347         log( 'ActivityQueue.addActivity: activity is not valid' );
19348         return false;
19349     }
19351     this.aCurrentActivityWaitingSet.push( aActivity );
19352     aActivityQueueDebugPrinter.print( 'ActivityQueue.addActivity: activity appended' );
19353     return true;
19356 ActivityQueue.prototype.process = function()
19358     var nSize = this.aCurrentActivityWaitingSet.length;
19359     var nLag = 0.0;
19360     for( var i = 0; i < nSize; ++i )
19361     {
19362         nLag = Math.max( nLag,this.aCurrentActivityWaitingSet[i].calcTimeLag()  );
19363     }
19365     if( nLag > 0.0 )
19366         this.aTimer.adjustTimer( -nLag, true );
19369     while( this.aCurrentActivityWaitingSet.length != 0 )
19370     {
19371         var aActivity = this.aCurrentActivityWaitingSet.shift();
19372         var bReinsert = false;
19374         bReinsert = aActivity.perform();
19376         if( bReinsert )
19377         {
19378             this.aCurrentActivityReinsertSet.push( aActivity );
19379         }
19380         else
19381         {
19382             this.aDequeuedActivitySet.push( aActivity );
19383         }
19384     }
19386     if( this.aCurrentActivityReinsertSet.length != 0 )
19387     {
19388         // TODO: optimization, try to swap reference here
19389         this.aCurrentActivityWaitingSet = this.aCurrentActivityReinsertSet;
19390         this.aCurrentActivityReinsertSet = [];
19391     }
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;
19414     var i;
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;
19429     var i;
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()
19540     var nCurrentTime;
19541     if ( !this.aTimeBase )
19542     {
19543         nCurrentTime = this.getSystemTime();
19544     }
19545     else
19546     {
19547         nCurrentTime = this.aTimeBase.getElapsedTimeImpl();
19548     }
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 )
19560     {
19561         return this.nFrozenTime;
19562     }
19564     var nCurTime = this.getCurrentTime();
19565     return ( nCurTime - this.nStartTime );
19570 /*****
19571  * @libreofficeend
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
19578  */
19580 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */