Version 6.4.0.3, tag libreoffice-6.4.0.3
[LibreOffice.git] / filter / source / svg / presentation_engine.js
blob5e67ecd2ea7e8787a5e79ac17783a89eecfe5e1e
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  * set a timeout with a given scope
36  * @param {Function} fn
37  * @param {Number} timeout
38  * @param {Object} context
39  * @returns {number}
40  */
41 function setTimeoutContext(fn, timeout, context) {
42     return setTimeout(bindFn(fn, context), timeout);
45 /**
46  * if the argument is an array, we want to execute the fn on each entry
47  * if it aint an array we don't want to do a thing.
48  * this is used by all the methods that accept a single and array argument.
49  * @param {*|Array} arg
50  * @param {String} fn
51  * @param {Object} [context]
52  * @returns {Boolean}
53  */
54 function invokeArrayArg(arg, fn, context) {
55     if (Array.isArray(arg)) {
56         each(arg, context[fn], context);
57         return true;
58     }
59     return false;
62 /**
63  * walk objects and arrays
64  * @param {Object} obj
65  * @param {Function} iterator
66  * @param {Object} context
67  */
68 function each(obj, iterator, context) {
69     var i;
71     if (!obj) {
72         return;
73     }
75     if (obj.forEach) {
76         obj.forEach(iterator, context);
77     } else if (obj.length !== undefined) {
78         i = 0;
79         while (i < obj.length) {
80             iterator.call(context, obj[i], i, obj);
81             i++;
82         }
83     } else {
84         for (i in obj) {
85             obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
86         }
87     }
90 /**
91  * wrap a method with a deprecation warning and stack trace
92  * @param {Function} method
93  * @param {String} name
94  * @param {String} message
95  * @returns {Function} A new function wrapping the supplied method.
96  */
97 function deprecate(method, name, message) {
98     var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
99     return function() {
100         var e = new Error('get-stack-trace');
101         var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
102             .replace(/^\s+at\s+/gm, '')
103             .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
105         var log = window.console && (window.console.warn || window.console.log);
106         if (log) {
107             log.call(window.console, deprecationMessage, stack);
108         }
109         return method.apply(this, arguments);
110     };
114  * extend object.
115  * means that properties in dest will be overwritten by the ones in src.
116  * @param {Object} target
117  * @param {...Object} objects_to_assign
118  * @returns {Object} target
119  */
120 var assign;
121 if (typeof Object.assign !== 'function') {
122     assign = function assign(target) {
123         if (target === undefined || target === null) {
124             throw new TypeError('Cannot convert undefined or null to object');
125         }
127         var output = Object(target);
128         for (var index = 1; index < arguments.length; index++) {
129             var source = arguments[index];
130             if (source !== undefined && source !== null) {
131                 for (var nextKey in source) {
132                     if (source.hasOwnProperty(nextKey)) {
133                         output[nextKey] = source[nextKey];
134                     }
135                 }
136             }
137         }
138         return output;
139     };
140 } else {
141     assign = Object.assign;
145  * extend object.
146  * means that properties in dest will be overwritten by the ones in src.
147  * @param {Object} dest
148  * @param {Object} src
149  * @param {Boolean} [merge=false]
150  * @returns {Object} dest
151  */
152 var extend = deprecate(function extend(dest, src, merge) {
153     var keys = Object.keys(src);
154     var i = 0;
155     while (i < keys.length) {
156         if (!merge || (merge && dest[keys[i]] === undefined)) {
157             dest[keys[i]] = src[keys[i]];
158         }
159         i++;
160     }
161     return dest;
162 }, 'extend', 'Use `assign`.');
165  * merge the values from src in the dest.
166  * means that properties that exist in dest will not be overwritten by src
167  * @param {Object} dest
168  * @param {Object} src
169  * @returns {Object} dest
170  */
171 var merge = deprecate(function merge(dest, src) {
172     return extend(dest, src, true);
173 }, 'merge', 'Use `assign`.');
176  * simple class inheritance
177  * @param {Function} child
178  * @param {Function} base
179  * @param {Object} [properties]
180  */
181 function inherit(child, base, properties) {
182     var baseP = base.prototype,
183         childP;
185     childP = child.prototype = Object.create(baseP);
186     childP.constructor = child;
187     childP._super = baseP;
189     if (properties) {
190         assign(childP, properties);
191     }
195  * simple function bind
196  * @param {Function} fn
197  * @param {Object} context
198  * @returns {Function}
199  */
200 function bindFn(fn, context) {
201     return function boundFn() {
202         return fn.apply(context, arguments);
203     };
207  * let a boolean value also be a function that must return a boolean
208  * this first item in args will be used as the context
209  * @param {Boolean|Function} val
210  * @param {Array} [args]
211  * @returns {Boolean}
212  */
213 function boolOrFn(val, args) {
214     if (typeof val == TYPE_FUNCTION) {
215         return val.apply(args ? args[0] || undefined : undefined, args);
216     }
217     return val;
221  * use the val2 when val1 is undefined
222  * @param {*} val1
223  * @param {*} val2
224  * @returns {*}
225  */
226 function ifUndefined(val1, val2) {
227     return (val1 === undefined) ? val2 : val1;
231  * addEventListener with multiple events at once
232  * @param {EventTarget} target
233  * @param {String} types
234  * @param {Function} handler
235  */
236 function addEventListeners(target, types, handler) {
237     each(splitStr(types), function(type) {
238         target.addEventListener(type, handler, false);
239     });
243  * removeEventListener with multiple events at once
244  * @param {EventTarget} target
245  * @param {String} types
246  * @param {Function} handler
247  */
248 function removeEventListeners(target, types, handler) {
249     each(splitStr(types), function(type) {
250         target.removeEventListener(type, handler, false);
251     });
255  * find if a node is in the given parent
256  * @method hasParent
257  * @param {HTMLElement} node
258  * @param {HTMLElement} parent
259  * @return {Boolean} found
260  */
261 function hasParent(node, parent) {
262     while (node) {
263         if (node == parent) {
264             return true;
265         }
266         node = node.parentNode;
267     }
268     return false;
272  * small indexOf wrapper
273  * @param {String} str
274  * @param {String} find
275  * @returns {Boolean} found
276  */
277 function inStr(str, find) {
278     return str.indexOf(find) > -1;
282  * split string on whitespace
283  * @param {String} str
284  * @returns {Array} words
285  */
286 function splitStr(str) {
287     return str.trim().split(/\s+/g);
291  * find if an array contains the object using indexOf or a simple polyFill
292  * @param {Array} src
293  * @param {String} find
294  * @param {String} [findByKey]
295  * @return {Boolean|Number} false when not found, or the index
296  */
297 function inArray(src, find, findByKey) {
298     if (src.indexOf && !findByKey) {
299         return src.indexOf(find);
300     } else {
301         var i = 0;
302         while (i < src.length) {
303             if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
304                 return i;
305             }
306             i++;
307         }
308         return -1;
309     }
313  * convert array-like objects to real arrays
314  * @param {Object} obj
315  * @returns {Array}
316  */
317 function toArray(obj) {
318     return Array.prototype.slice.call(obj, 0);
322  * unique array with objects based on a key (like 'id') or just by the array's value
323  * @param {Array} src [{id:1},{id:2},{id:1}]
324  * @param {String} [key]
325  * @param {Boolean} [sort=False]
326  * @returns {Array} [{id:1},{id:2}]
327  */
328 function uniqueArray(src, key, sort) {
329     var results = [];
330     var values = [];
331     var i = 0;
333     while (i < src.length) {
334         var val = key ? src[i][key] : src[i];
335         if (inArray(values, val) < 0) {
336             results.push(src[i]);
337         }
338         values[i] = val;
339         i++;
340     }
342     if (sort) {
343         if (!key) {
344             results = results.sort();
345         } else {
346             results = results.sort(function sortUniqueArray(a, b) {
347                 return a[key] > b[key];
348             });
349         }
350     }
352     return results;
356  * get the prefixed property
357  * @param {Object} obj
358  * @param {String} property
359  * @returns {String|Undefined} prefixed
360  */
361 function prefixed(obj, property) {
362     // tml: Have to check for obj being undefined 
363     if (obj === undefined) {
364         return undefined;
365     }
367     var prefix, prop;
368     var camelProp = property[0].toUpperCase() + property.slice(1);
370     var i = 0;
371     while (i < VENDOR_PREFIXES.length) {
372         prefix = VENDOR_PREFIXES[i];
373         prop = (prefix) ? prefix + camelProp : property;
375         if (prop in obj) {
376             return prop;
377         }
378         i++;
379     }
380     return undefined;
384  * get a unique id
385  * @returns {number} uniqueId
386  */
387 var _uniqueId = 1;
388 function uniqueId() {
389     return _uniqueId++;
393  * get the window object of an element
394  * @param {HTMLElement} element
395  * @returns {DocumentView|Window}
396  */
397 function getWindowForElement(element) {
398     var doc = element.ownerDocument || element;
399     return (doc.defaultView || doc.parentWindow || window);
402 var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
404 var SUPPORT_TOUCH = ('ontouchstart' in window);
405 var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
406 var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
408 var INPUT_TYPE_TOUCH = 'touch';
409 var INPUT_TYPE_PEN = 'pen';
410 var INPUT_TYPE_MOUSE = 'mouse';
411 var INPUT_TYPE_KINECT = 'kinect';
413 var COMPUTE_INTERVAL = 25;
415 var INPUT_START = 1;
416 var INPUT_MOVE = 2;
417 var INPUT_END = 4;
418 var INPUT_CANCEL = 8;
420 var DIRECTION_NONE = 1;
421 var DIRECTION_LEFT = 2;
422 var DIRECTION_RIGHT = 4;
423 var DIRECTION_UP = 8;
424 var DIRECTION_DOWN = 16;
426 var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
427 var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
428 var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
430 var PROPS_XY = ['x', 'y'];
431 var PROPS_CLIENT_XY = ['clientX', 'clientY'];
434  * create new input type manager
435  * @param {Manager} manager
436  * @param {Function} callback
437  * @returns {Input}
438  * @constructor
439  */
440 function Input(manager, callback) {
441     var self = this;
442     this.manager = manager;
443     this.callback = callback;
444     this.element = manager.element;
445     this.target = manager.options.inputTarget;
447     // smaller wrapper around the handler, for the scope and the enabled state of the manager,
448     // so when disabled the input events are completely bypassed.
449     this.domHandler = function(ev) {
450         if (boolOrFn(manager.options.enable, [manager])) {
451             self.handler(ev);
452         }
453     };
455     this.init();
459 Input.prototype = {
460     /**
461      * should handle the inputEvent data and trigger the callback
462      * @virtual
463      */
464     handler: function() { },
466     /**
467      * bind the events
468      */
469     init: function() {
470         this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
471         this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
472         this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
473     },
475     /**
476      * unbind the events
477      */
478     destroy: function() {
479         this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
480         this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
481         this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
482     }
486  * create new input type manager
487  * called by the Manager constructor
488  * @param {Hammer} manager
489  * @returns {Input}
490  */
491 function createInputInstance(manager) {
492     var Type;
493     var inputClass = manager.options.inputClass;
495     if (inputClass) {
496         Type = inputClass;
497     } else if (!SUPPORT_TOUCH && SUPPORT_POINTER_EVENTS) {
498         Type = PointerEventInput;
499     } else if (SUPPORT_ONLY_TOUCH) {
500         Type = TouchInput;
501     } else if (!SUPPORT_TOUCH) {
502         Type = MouseInput;
503     } else {
504         Type = TouchMouseInput;
505     }
506     return new (Type)(manager, inputHandler);
510  * handle input events
511  * @param {Manager} manager
512  * @param {String} eventType
513  * @param {Object} input
514  */
515 function inputHandler(manager, eventType, input) {
516     var pointersLen = input.pointers.length;
517     var changedPointersLen = input.changedPointers.length;
518     var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
519     var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
521     input.isFirst = !!isFirst;
522     input.isFinal = !!isFinal;
524     if (isFirst) {
525         manager.session = {};
526     }
528     // source event is the normalized value of the domEvents
529     // like 'touchstart, mouseup, pointerdown'
530     input.eventType = eventType;
532     // compute scale, rotation etc
533     computeInputData(manager, input);
535     // emit secret event
536     manager.emit('hammer.input', input);
538     manager.recognize(input);
539     manager.session.prevInput = input;
543  * extend the data with some usable properties like scale, rotate, velocity etc
544  * @param {Object} manager
545  * @param {Object} input
546  */
547 function computeInputData(manager, input) {
548     var session = manager.session;
549     var pointers = input.pointers;
550     var pointersLength = pointers.length;
552     // store the first input to calculate the distance and direction
553     if (!session.firstInput) {
554         session.firstInput = simpleCloneInputData(input);
555     }
557     // to compute scale and rotation we need to store the multiple touches
558     if (pointersLength > 1 && !session.firstMultiple) {
559         session.firstMultiple = simpleCloneInputData(input);
560     } else if (pointersLength === 1) {
561         session.firstMultiple = false;
562     }
564     var firstInput = session.firstInput;
565     var firstMultiple = session.firstMultiple;
566     var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
568     var center = input.center = getCenter(pointers);
569     input.timeStamp = now();
570     input.deltaTime = input.timeStamp - firstInput.timeStamp;
572     input.angle = getAngle(offsetCenter, center);
573     input.distance = getDistance(offsetCenter, center);
575     computeDeltaXY(session, input);
576     input.offsetDirection = getDirection(input.deltaX, input.deltaY);
578     var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
579     input.overallVelocityX = overallVelocity.x;
580     input.overallVelocityY = overallVelocity.y;
581     input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
583     input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
584     input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
586     input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
587         session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
589     computeIntervalInputData(session, input);
591     // find the correct target
592     var target = manager.element;
593     if (hasParent(input.srcEvent.target, target)) {
594         target = input.srcEvent.target;
595     }
596     input.target = target;
599 function computeDeltaXY(session, input) {
600     var center = input.center;
601     var offset = session.offsetDelta || {};
602     var prevDelta = session.prevDelta || {};
603     var prevInput = session.prevInput || {};
605     if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
606         prevDelta = session.prevDelta = {
607             x: prevInput.deltaX || 0,
608             y: prevInput.deltaY || 0
609         };
611         offset = session.offsetDelta = {
612             x: center.x,
613             y: center.y
614         };
615     }
617     input.deltaX = prevDelta.x + (center.x - offset.x);
618     input.deltaY = prevDelta.y + (center.y - offset.y);
622  * velocity is calculated every x ms
623  * @param {Object} session
624  * @param {Object} input
625  */
626 function computeIntervalInputData(session, input) {
627     var last = session.lastInterval || input,
628         deltaTime = input.timeStamp - last.timeStamp,
629         velocity, velocityX, velocityY, direction;
631     if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
632         var deltaX = input.deltaX - last.deltaX;
633         var deltaY = input.deltaY - last.deltaY;
635         var v = getVelocity(deltaTime, deltaX, deltaY);
636         velocityX = v.x;
637         velocityY = v.y;
638         velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
639         direction = getDirection(deltaX, deltaY);
641         session.lastInterval = input;
642     } else {
643         // use latest velocity info if it doesn't overtake a minimum period
644         velocity = last.velocity;
645         velocityX = last.velocityX;
646         velocityY = last.velocityY;
647         direction = last.direction;
648     }
650     input.velocity = velocity;
651     input.velocityX = velocityX;
652     input.velocityY = velocityY;
653     input.direction = direction;
657  * create a simple clone from the input used for storage of firstInput and firstMultiple
658  * @param {Object} input
659  * @returns {Object} clonedInputData
660  */
661 function simpleCloneInputData(input) {
662     // make a simple copy of the pointers because we will get a reference if we don't
663     // we only need clientXY for the calculations
664     var pointers = [];
665     var i = 0;
666     while (i < input.pointers.length) {
667         pointers[i] = {
668             clientX: round(input.pointers[i].clientX),
669             clientY: round(input.pointers[i].clientY)
670         };
671         i++;
672     }
674     return {
675         timeStamp: now(),
676         pointers: pointers,
677         center: getCenter(pointers),
678         deltaX: input.deltaX,
679         deltaY: input.deltaY
680     };
684  * get the center of all the pointers
685  * @param {Array} pointers
686  * @return {Object} center contains `x` and `y` properties
687  */
688 function getCenter(pointers) {
689     var pointersLength = pointers.length;
691     // no need to loop when only one touch
692     if (pointersLength === 1) {
693         return {
694             x: round(pointers[0].clientX),
695             y: round(pointers[0].clientY)
696         };
697     }
699     var x = 0, y = 0, i = 0;
700     while (i < pointersLength) {
701         x += pointers[i].clientX;
702         y += pointers[i].clientY;
703         i++;
704     }
706     return {
707         x: round(x / pointersLength),
708         y: round(y / pointersLength)
709     };
713  * calculate the velocity between two points. unit is in px per ms.
714  * @param {Number} deltaTime
715  * @param {Number} x
716  * @param {Number} y
717  * @return {Object} velocity `x` and `y`
718  */
719 function getVelocity(deltaTime, x, y) {
720     return {
721         x: x / deltaTime || 0,
722         y: y / deltaTime || 0
723     };
727  * get the direction between two points
728  * @param {Number} x
729  * @param {Number} y
730  * @return {Number} direction
731  */
732 function getDirection(x, y) {
733     if (x === y) {
734         return DIRECTION_NONE;
735     }
737     if (abs(x) >= abs(y)) {
738         return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
739     }
740     return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
744  * calculate the absolute distance between two points
745  * @param {Object} p1 {x, y}
746  * @param {Object} p2 {x, y}
747  * @param {Array} [props] containing x and y keys
748  * @return {Number} distance
749  */
750 function getDistance(p1, p2, props) {
751     if (!props) {
752         props = PROPS_XY;
753     }
754     var x = p2[props[0]] - p1[props[0]],
755         y = p2[props[1]] - p1[props[1]];
757     return Math.sqrt((x * x) + (y * y));
761  * calculate the angle between two coordinates
762  * @param {Object} p1
763  * @param {Object} p2
764  * @param {Array} [props] containing x and y keys
765  * @return {Number} angle
766  */
767 function getAngle(p1, p2, props) {
768     if (!props) {
769         props = PROPS_XY;
770     }
771     var x = p2[props[0]] - p1[props[0]],
772         y = p2[props[1]] - p1[props[1]];
773     return Math.atan2(y, x) * 180 / Math.PI;
777  * calculate the rotation degrees between two pointersets
778  * @param {Array} start array of pointers
779  * @param {Array} end array of pointers
780  * @return {Number} rotation
781  */
782 function getRotation(start, end) {
783     return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
787  * calculate the scale factor between two pointersets
788  * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
789  * @param {Array} start array of pointers
790  * @param {Array} end array of pointers
791  * @return {Number} scale
792  */
793 function getScale(start, end) {
794     return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
797 var MOUSE_INPUT_MAP = {
798     mousedown: INPUT_START,
799     mousemove: INPUT_MOVE,
800     mouseup: INPUT_END
803 var MOUSE_ELEMENT_EVENTS = 'mousedown';
804 var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
807  * Mouse events input
808  * @constructor
809  * @extends Input
810  */
811 function MouseInput() {
812     this.evEl = MOUSE_ELEMENT_EVENTS;
813     this.evWin = MOUSE_WINDOW_EVENTS;
815     this.pressed = false; // mousedown state
817     Input.apply(this, arguments);
820 inherit(MouseInput, Input, {
821     /**
822      * handle mouse events
823      * @param {Object} ev
824      */
825     handler: function MEhandler(ev) {
826         // console.log('==> MouseInput handler');
827         var eventType = MOUSE_INPUT_MAP[ev.type];
829         // on start we want to have the left mouse button down
830         if (eventType & INPUT_START && ev.button === 0) {
831             this.pressed = true;
832         }
834         if (eventType & INPUT_MOVE && ev.which !== 1) {
835             eventType = INPUT_END;
836         }
838         // mouse must be down
839         if (!this.pressed) {
840             return;
841         }
843         if (eventType & INPUT_END) {
844             this.pressed = false;
845         }
847         this.callback(this.manager, eventType, {
848             pointers: [ev],
849             changedPointers: [ev],
850             pointerType: INPUT_TYPE_MOUSE,
851             srcEvent: ev
852         });
853     }
856 var POINTER_INPUT_MAP = {
857     pointerdown: INPUT_START,
858     pointermove: INPUT_MOVE,
859     pointerup: INPUT_END,
860     pointercancel: INPUT_CANCEL,
861     pointerout: INPUT_CANCEL
864 // in IE10 the pointer types is defined as an enum
865 var IE10_POINTER_TYPE_ENUM = {
866     2: INPUT_TYPE_TOUCH,
867     3: INPUT_TYPE_PEN,
868     4: INPUT_TYPE_MOUSE,
869     5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
872 var POINTER_ELEMENT_EVENTS = 'pointerdown';
873 var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
875 // IE10 has prefixed support, and case-sensitive
876 if (window.MSPointerEvent && !window.PointerEvent) {
877     POINTER_ELEMENT_EVENTS = 'MSPointerDown';
878     POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
882  * Pointer events input
883  * @constructor
884  * @extends Input
885  */
886 function PointerEventInput() {
887     this.evEl = POINTER_ELEMENT_EVENTS;
888     this.evWin = POINTER_WINDOW_EVENTS;
890     Input.apply(this, arguments);
892     this.store = (this.manager.session.pointerEvents = []);
895 inherit(PointerEventInput, Input, {
896     /**
897      * handle mouse events
898      * @param {Object} ev
899      */
900     handler: function PEhandler(ev) {
901         // console.log('==> PointerEventInput handler');
902         var store = this.store;
903         var removePointer = false;
905         var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
906         var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
907         var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
909         var isTouch = (pointerType == INPUT_TYPE_TOUCH);
911         // get index of the event in the store
912         var storeIndex = inArray(store, ev.pointerId, 'pointerId');
914         // start and mouse must be down
915         if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
916             if (storeIndex < 0) {
917                 store.push(ev);
918                 storeIndex = store.length - 1;
919             }
920         } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
921             removePointer = true;
922         }
924         // it not found, so the pointer hasn't been down (so it's probably a hover)
925         if (storeIndex < 0) {
926             return;
927         }
929         // update the event in the store
930         store[storeIndex] = ev;
932         this.callback(this.manager, eventType, {
933             pointers: store,
934             changedPointers: [ev],
935             pointerType: pointerType,
936             srcEvent: ev
937         });
939         if (removePointer) {
940             // remove from the store
941             store.splice(storeIndex, 1);
942         }
943     }
946 var SINGLE_TOUCH_INPUT_MAP = {
947     touchstart: INPUT_START,
948     touchmove: INPUT_MOVE,
949     touchend: INPUT_END,
950     touchcancel: INPUT_CANCEL
953 var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
954 var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
957  * Touch events input
958  * @constructor
959  * @extends Input
960  */
961 function SingleTouchInput() {
962     this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
963     this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
964     this.started = false;
966     Input.apply(this, arguments);
969 inherit(SingleTouchInput, Input, {
970     handler: function TEhandler(ev) {
971         // console.log('==> SingleTouchInput handler');
972         var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
974         // should we handle the touch events?
975         if (type === INPUT_START) {
976             this.started = true;
977         }
979         if (!this.started) {
980             return;
981         }
983         var touches = normalizeSingleTouches.call(this, ev, type);
985         // when done, reset the started state
986         if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
987             this.started = false;
988         }
990         this.callback(this.manager, type, {
991             pointers: touches[0],
992             changedPointers: touches[1],
993             pointerType: INPUT_TYPE_TOUCH,
994             srcEvent: ev
995         });
996     }
1000  * @this {TouchInput}
1001  * @param {Object} ev
1002  * @param {Number} type flag
1003  * @returns {undefined|Array} [all, changed]
1004  */
1005 function normalizeSingleTouches(ev, type) {
1006     var all = toArray(ev.touches);
1007     var changed = toArray(ev.changedTouches);
1009     if (type & (INPUT_END | INPUT_CANCEL)) {
1010         all = uniqueArray(all.concat(changed), 'identifier', true);
1011     }
1013     return [all, changed];
1016 var TOUCH_INPUT_MAP = {
1017     touchstart: INPUT_START,
1018     touchmove: INPUT_MOVE,
1019     touchend: INPUT_END,
1020     touchcancel: INPUT_CANCEL
1023 var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
1026  * Multi-user touch events input
1027  * @constructor
1028  * @extends Input
1029  */
1030 function TouchInput() {
1031     this.evTarget = TOUCH_TARGET_EVENTS;
1032     this.targetIds = {};
1034     Input.apply(this, arguments);
1037 inherit(TouchInput, Input, {
1038     handler: function MTEhandler(ev) {
1039         // console.log('==> TouchInput handler');
1040         var type = TOUCH_INPUT_MAP[ev.type];
1041         var touches = getTouches.call(this, ev, type);
1042         if (!touches) {
1043             return;
1044         }
1046         this.callback(this.manager, type, {
1047             pointers: touches[0],
1048             changedPointers: touches[1],
1049             pointerType: INPUT_TYPE_TOUCH,
1050             srcEvent: ev
1051         });
1052     }
1056  * @this {TouchInput}
1057  * @param {Object} ev
1058  * @param {Number} type flag
1059  * @returns {undefined|Array} [all, changed]
1060  */
1061 function getTouches(ev, type) {
1062     var allTouches = toArray(ev.touches);
1063     var targetIds = this.targetIds;
1065     // when there is only one touch, the process can be simplified
1066     if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
1067         targetIds[allTouches[0].identifier] = true;
1068         return [allTouches, allTouches];
1069     }
1071     var i,
1072         targetTouches,
1073         changedTouches = toArray(ev.changedTouches),
1074         changedTargetTouches = [],
1075         target = this.target;
1077     // get target touches from touches
1078     targetTouches = allTouches.filter(function(touch) {
1079         return hasParent(touch.target, target);
1080     });
1082     // collect touches
1083     if (type === INPUT_START) {
1084         i = 0;
1085         while (i < targetTouches.length) {
1086             targetIds[targetTouches[i].identifier] = true;
1087             i++;
1088         }
1089     }
1091     // filter changed touches to only contain touches that exist in the collected target ids
1092     i = 0;
1093     while (i < changedTouches.length) {
1094         if (targetIds[changedTouches[i].identifier]) {
1095             changedTargetTouches.push(changedTouches[i]);
1096         }
1098         // cleanup removed touches
1099         if (type & (INPUT_END | INPUT_CANCEL)) {
1100             delete targetIds[changedTouches[i].identifier];
1101         }
1102         i++;
1103     }
1105     if (!changedTargetTouches.length) {
1106         return;
1107     }
1109     return [
1110         // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
1111         uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
1112         changedTargetTouches
1113     ];
1117  * Combined touch and mouse input
1119  * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
1120  * This because touch devices also emit mouse events while doing a touch.
1122  * @constructor
1123  * @extends Input
1124  */
1126 var DEDUP_TIMEOUT = 2500;
1127 var DEDUP_DISTANCE = 25;
1129 function TouchMouseInput() {
1130     Input.apply(this, arguments);
1132     var handler = bindFn(this.handler, this);
1133     this.touch = new TouchInput(this.manager, handler);
1134     this.mouse = new MouseInput(this.manager, handler);
1136     this.primaryTouch = null;
1137     this.lastTouches = [];
1140 inherit(TouchMouseInput, Input, {
1141     /**
1142      * handle mouse and touch events
1143      * @param {Hammer} manager
1144      * @param {String} inputEvent
1145      * @param {Object} inputData
1146      */
1147     handler: function TMEhandler(manager, inputEvent, inputData) {
1148         // console.log('==> TouchMouseInput handler');
1149         var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
1150             isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
1152         if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
1153             return;
1154         }
1156         // when we're in a touch event, record touches to  de-dupe synthetic mouse event
1157         if (isTouch) {
1158             recordTouches.call(this, inputEvent, inputData);
1159         } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
1160             return;
1161         }
1163         this.callback(manager, inputEvent, inputData);
1164     },
1166     /**
1167      * remove the event listeners
1168      */
1169     destroy: function destroy() {
1170         this.touch.destroy();
1171         this.mouse.destroy();
1172     }
1175 function recordTouches(eventType, eventData) {
1176     if (eventType & INPUT_START) {
1177         this.primaryTouch = eventData.changedPointers[0].identifier;
1178         setLastTouch.call(this, eventData);
1179     } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
1180         setLastTouch.call(this, eventData);
1181     }
1184 function setLastTouch(eventData) {
1185     var touch = eventData.changedPointers[0];
1187     if (touch.identifier === this.primaryTouch) {
1188         var lastTouch = {x: touch.clientX, y: touch.clientY};
1189         this.lastTouches.push(lastTouch);
1190         var lts = this.lastTouches;
1191         var removeLastTouch = function() {
1192             var i = lts.indexOf(lastTouch);
1193             if (i > -1) {
1194                 lts.splice(i, 1);
1195             }
1196         };
1197         setTimeout(removeLastTouch, DEDUP_TIMEOUT);
1198     }
1201 function isSyntheticEvent(eventData) {
1202     var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
1203     for (var i = 0; i < this.lastTouches.length; i++) {
1204         var t = this.lastTouches[i];
1205         var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
1206         if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
1207             return true;
1208         }
1209     }
1210     return false;
1213 var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
1214 var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
1216 // magical touchAction value
1217 var TOUCH_ACTION_COMPUTE = 'compute';
1218 var TOUCH_ACTION_AUTO = 'auto';
1219 var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
1220 var TOUCH_ACTION_NONE = 'none';
1221 var TOUCH_ACTION_PAN_X = 'pan-x';
1222 var TOUCH_ACTION_PAN_Y = 'pan-y';
1223 var TOUCH_ACTION_MAP = getTouchActionProps();
1226  * Touch Action
1227  * sets the touchAction property or uses the js alternative
1228  * @param {Manager} manager
1229  * @param {String} value
1230  * @constructor
1231  */
1232 function TouchAction(manager, value) {
1233     this.manager = manager;
1234     this.set(value);
1237 TouchAction.prototype = {
1238     /**
1239      * set the touchAction value on the element or enable the polyfill
1240      * @param {String} value
1241      */
1242     set: function(value) {
1243         // find out the touch-action by the event handlers
1244         if (value == TOUCH_ACTION_COMPUTE) {
1245             value = this.compute();
1246         }
1248         if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
1249             this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
1250         }
1251         this.actions = value.toLowerCase().trim();
1252     },
1254     /**
1255      * just re-set the touchAction value
1256      */
1257     update: function() {
1258         this.set(this.manager.options.touchAction);
1259     },
1261     /**
1262      * compute the value for the touchAction property based on the recognizer's settings
1263      * @returns {String} value
1264      */
1265     compute: function() {
1266         var actions = [];
1267         each(this.manager.recognizers, function(recognizer) {
1268             if (boolOrFn(recognizer.options.enable, [recognizer])) {
1269                 actions = actions.concat(recognizer.getTouchAction());
1270             }
1271         });
1272         return cleanTouchActions(actions.join(' '));
1273     },
1275     /**
1276      * this method is called on each input cycle and provides the preventing of the browser behavior
1277      * @param {Object} input
1278      */
1279     preventDefaults: function(input) {
1280         var srcEvent = input.srcEvent;
1281         var direction = input.offsetDirection;
1283         // if the touch action did prevented once this session
1284         if (this.manager.session.prevented) {
1285             srcEvent.preventDefault();
1286             return;
1287         }
1289         var actions = this.actions;
1290         var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
1291         var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
1292         var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
1294         if (hasNone) {
1295             //do not prevent defaults if this is a tap gesture
1297             var isTapPointer = input.pointers.length === 1;
1298             var isTapMovement = input.distance < 2;
1299             var isTapTouchTime = input.deltaTime < 250;
1301             if (isTapPointer && isTapMovement && isTapTouchTime) {
1302                 return;
1303             }
1304         }
1306         if (hasPanX && hasPanY) {
1307             // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
1308             return;
1309         }
1311         if (hasNone ||
1312             (hasPanY && direction & DIRECTION_HORIZONTAL) ||
1313             (hasPanX && direction & DIRECTION_VERTICAL)) {
1314             return this.preventSrc(srcEvent);
1315         }
1316     },
1318     /**
1319      * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
1320      * @param {Object} srcEvent
1321      */
1322     preventSrc: function(srcEvent) {
1323         this.manager.session.prevented = true;
1324         srcEvent.preventDefault();
1325     }
1329  * when the touchActions are collected they are not a valid value, so we need to clean things up. *
1330  * @param {String} actions
1331  * @returns {*}
1332  */
1333 function cleanTouchActions(actions) {
1334     // none
1335     if (inStr(actions, TOUCH_ACTION_NONE)) {
1336         return TOUCH_ACTION_NONE;
1337     }
1339     var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
1340     var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
1342     // if both pan-x and pan-y are set (different recognizers
1343     // for different directions, e.g. horizontal pan but vertical swipe?)
1344     // we need none (as otherwise with pan-x pan-y combined none of these
1345     // recognizers will work, since the browser would handle all panning
1346     if (hasPanX && hasPanY) {
1347         return TOUCH_ACTION_NONE;
1348     }
1350     // pan-x OR pan-y
1351     if (hasPanX || hasPanY) {
1352         return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
1353     }
1355     // manipulation
1356     if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
1357         return TOUCH_ACTION_MANIPULATION;
1358     }
1360     return TOUCH_ACTION_AUTO;
1363 function getTouchActionProps() {
1364     if (!NATIVE_TOUCH_ACTION) {
1365         return false;
1366     }
1367     var touchMap = {};
1368     var cssSupports = window.CSS && window.CSS.supports;
1369     ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
1371         // If css.supports is not supported but there is native touch-action assume it supports
1372         // all values. This is the case for IE 10 and 11.
1373         touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
1374     });
1375     return touchMap;
1379  * Recognizer flow explained; *
1380  * All recognizers have the initial state of POSSIBLE when an input session starts.
1381  * The definition of an input session is from the first input until the last input, with all it's movement in it. *
1382  * Example session for mouse-input: mousedown -> mousemove -> mouseup
1384  * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
1385  * which determines with state it should be.
1387  * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
1388  * POSSIBLE to give it another change on the next cycle.
1390  *               Possible
1391  *                  |
1392  *            +-----+---------------+
1393  *            |                     |
1394  *      +-----+-----+               |
1395  *      |           |               |
1396  *   Failed      Cancelled          |
1397  *                          +-------+------+
1398  *                          |              |
1399  *                      Recognized       Began
1400  *                                         |
1401  *                                      Changed
1402  *                                         |
1403  *                                  Ended/Recognized
1404  */
1405 var STATE_POSSIBLE = 1;
1406 var STATE_BEGAN = 2;
1407 var STATE_CHANGED = 4;
1408 var STATE_ENDED = 8;
1409 var STATE_RECOGNIZED = STATE_ENDED;
1410 var STATE_CANCELLED = 16;
1411 var STATE_FAILED = 32;
1414  * Recognizer
1415  * Every recognizer needs to extend from this class.
1416  * @constructor
1417  * @param {Object} options
1418  */
1419 function Recognizer(options) {
1420     this.options = assign({}, this.defaults, options || {});
1422     this.id = uniqueId();
1424     this.manager = null;
1426     // default is enable true
1427     this.options.enable = ifUndefined(this.options.enable, true);
1429     this.state = STATE_POSSIBLE;
1431     this.simultaneous = {};
1432     this.requireFail = [];
1435 Recognizer.prototype = {
1436     /**
1437      * @virtual
1438      * @type {Object}
1439      */
1440     defaults: {},
1442     /**
1443      * set options
1444      * @param {Object} options
1445      * @return {Recognizer}
1446      */
1447     set: function(options) {
1448         assign(this.options, options);
1450         // also update the touchAction, in case something changed about the directions/enabled state
1451         this.manager && this.manager.touchAction.update();
1452         return this;
1453     },
1455     /**
1456      * recognize simultaneous with another recognizer.
1457      * @param {Recognizer} otherRecognizer
1458      * @returns {Recognizer} this
1459      */
1460     recognizeWith: function(otherRecognizer) {
1461         if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
1462             return this;
1463         }
1465         var simultaneous = this.simultaneous;
1466         otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1467         if (!simultaneous[otherRecognizer.id]) {
1468             simultaneous[otherRecognizer.id] = otherRecognizer;
1469             otherRecognizer.recognizeWith(this);
1470         }
1471         return this;
1472     },
1474     /**
1475      * drop the simultaneous link. It doesn't remove the link on the other recognizer.
1476      * @param {Recognizer} otherRecognizer
1477      * @returns {Recognizer} this
1478      */
1479     dropRecognizeWith: function(otherRecognizer) {
1480         if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
1481             return this;
1482         }
1484         otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1485         delete this.simultaneous[otherRecognizer.id];
1486         return this;
1487     },
1489     /**
1490      * recognizer can only run when another is failing
1491      * @param {Recognizer} otherRecognizer
1492      * @returns {Recognizer} this
1493      */
1494     requireFailure: function(otherRecognizer) {
1495         if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
1496             return this;
1497         }
1499         var requireFail = this.requireFail;
1500         otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1501         if (inArray(requireFail, otherRecognizer) === -1) {
1502             requireFail.push(otherRecognizer);
1503             otherRecognizer.requireFailure(this);
1504         }
1505         return this;
1506     },
1508     /**
1509      * drop the requireFailure link. It does not remove the link on the other recognizer.
1510      * @param {Recognizer} otherRecognizer
1511      * @returns {Recognizer} this
1512      */
1513     dropRequireFailure: function(otherRecognizer) {
1514         if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
1515             return this;
1516         }
1518         otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1519         var index = inArray(this.requireFail, otherRecognizer);
1520         if (index > -1) {
1521             this.requireFail.splice(index, 1);
1522         }
1523         return this;
1524     },
1526     /**
1527      * has require failures boolean
1528      * @returns {boolean}
1529      */
1530     hasRequireFailures: function() {
1531         return this.requireFail.length > 0;
1532     },
1534     /**
1535      * if the recognizer can recognize simultaneous with another recognizer
1536      * @param {Recognizer} otherRecognizer
1537      * @returns {Boolean}
1538      */
1539     canRecognizeWith: function(otherRecognizer) {
1540         return !!this.simultaneous[otherRecognizer.id];
1541     },
1543     /**
1544      * You should use `tryEmit` instead of `emit` directly to check
1545      * that all the needed recognizers has failed before emitting.
1546      * @param {Object} input
1547      */
1548     emit: function(input) {
1549         var self = this;
1550         var state = this.state;
1552         function emit(event) {
1553             self.manager.emit(event, input);
1554         }
1556         // 'panstart' and 'panmove'
1557         if (state < STATE_ENDED) {
1558             emit(self.options.event + stateStr(state));
1559         }
1561         emit(self.options.event); // simple 'eventName' events
1563         if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
1564             emit(input.additionalEvent);
1565         }
1567         // panend and pancancel
1568         if (state >= STATE_ENDED) {
1569             emit(self.options.event + stateStr(state));
1570         }
1571     },
1573     /**
1574      * Check that all the require failure recognizers has failed,
1575      * if true, it emits a gesture event,
1576      * otherwise, setup the state to FAILED.
1577      * @param {Object} input
1578      */
1579     tryEmit: function(input) {
1580         if (this.canEmit()) {
1581             return this.emit(input);
1582         }
1583         // it's failing anyway
1584         this.state = STATE_FAILED;
1585     },
1587     /**
1588      * can we emit?
1589      * @returns {boolean}
1590      */
1591     canEmit: function() {
1592         var i = 0;
1593         while (i < this.requireFail.length) {
1594             if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
1595                 return false;
1596             }
1597             i++;
1598         }
1599         return true;
1600     },
1602     /**
1603      * update the recognizer
1604      * @param {Object} inputData
1605      */
1606     recognize: function(inputData) {
1607         // make a new copy of the inputData
1608         // so we can change the inputData without messing up the other recognizers
1609         var inputDataClone = assign({}, inputData);
1611         // is it enabled and allow recognizing?
1612         if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
1613             this.reset();
1614             this.state = STATE_FAILED;
1615             return;
1616         }
1618         // reset when we've reached the end
1619         if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
1620             this.state = STATE_POSSIBLE;
1621         }
1623         this.state = this.process(inputDataClone);
1625         // the recognizer has recognized a gesture
1626         // so trigger an event
1627         if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
1628             this.tryEmit(inputDataClone);
1629         }
1630     },
1632     /**
1633      * return the state of the recognizer
1634      * the actual recognizing happens in this method
1635      * @virtual
1636      * @param {Object} inputData
1637      * @returns {Const} STATE
1638      */
1639     process: function(inputData) { }, // jshint ignore:line
1641     /**
1642      * return the preferred touch-action
1643      * @virtual
1644      * @returns {Array}
1645      */
1646     getTouchAction: function() { },
1648     /**
1649      * called when the gesture isn't allowed to recognize
1650      * like when another is being recognized or it is disabled
1651      * @virtual
1652      */
1653     reset: function() { }
1657  * get a usable string, used as event postfix
1658  * @param {Const} state
1659  * @returns {String} state
1660  */
1661 function stateStr(state) {
1662     if (state & STATE_CANCELLED) {
1663         return 'cancel';
1664     } else if (state & STATE_ENDED) {
1665         return 'end';
1666     } else if (state & STATE_CHANGED) {
1667         return 'move';
1668     } else if (state & STATE_BEGAN) {
1669         return 'start';
1670     }
1671     return '';
1675  * direction cons to string
1676  * @param {Const} direction
1677  * @returns {String}
1678  */
1679 function directionStr(direction) {
1680     if (direction == DIRECTION_DOWN) {
1681         return 'down';
1682     } else if (direction == DIRECTION_UP) {
1683         return 'up';
1684     } else if (direction == DIRECTION_LEFT) {
1685         return 'left';
1686     } else if (direction == DIRECTION_RIGHT) {
1687         return 'right';
1688     }
1689     return '';
1693  * get a recognizer by name if it is bound to a manager
1694  * @param {Recognizer|String} otherRecognizer
1695  * @param {Recognizer} recognizer
1696  * @returns {Recognizer}
1697  */
1698 function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
1699     var manager = recognizer.manager;
1700     if (manager) {
1701         return manager.get(otherRecognizer);
1702     }
1703     return otherRecognizer;
1707  * This recognizer is just used as a base for the simple attribute recognizers.
1708  * @constructor
1709  * @extends Recognizer
1710  */
1711 function AttrRecognizer() {
1712     Recognizer.apply(this, arguments);
1715 inherit(AttrRecognizer, Recognizer, {
1716     /**
1717      * @namespace
1718      * @memberof AttrRecognizer
1719      */
1720     defaults: {
1721         /**
1722          * @type {Number}
1723          * @default 1
1724          */
1725         pointers: 1
1726     },
1728     /**
1729      * Used to check if it the recognizer receives valid input, like input.distance > 10.
1730      * @memberof AttrRecognizer
1731      * @param {Object} input
1732      * @returns {Boolean} recognized
1733      */
1734     attrTest: function(input) {
1735         var optionPointers = this.options.pointers;
1736         return optionPointers === 0 || input.pointers.length === optionPointers;
1737     },
1739     /**
1740      * Process the input and return the state for the recognizer
1741      * @memberof AttrRecognizer
1742      * @param {Object} input
1743      * @returns {*} State
1744      */
1745     process: function(input) {
1746         var state = this.state;
1747         var eventType = input.eventType;
1749         var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
1750         var isValid = this.attrTest(input);
1752         // on cancel input and we've recognized before, return STATE_CANCELLED
1753         if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
1754             return state | STATE_CANCELLED;
1755         } else if (isRecognized || isValid) {
1756             if (eventType & INPUT_END) {
1757                 return state | STATE_ENDED;
1758             } else if (!(state & STATE_BEGAN)) {
1759                 return STATE_BEGAN;
1760             }
1761             return state | STATE_CHANGED;
1762         }
1763         return STATE_FAILED;
1764     }
1768  * Pan
1769  * Recognized when the pointer is down and moved in the allowed direction.
1770  * @constructor
1771  * @extends AttrRecognizer
1772  */
1773 function PanRecognizer() {
1774     AttrRecognizer.apply(this, arguments);
1776     this.pX = null;
1777     this.pY = null;
1780 inherit(PanRecognizer, AttrRecognizer, {
1781     /**
1782      * @namespace
1783      * @memberof PanRecognizer
1784      */
1785     defaults: {
1786         event: 'pan',
1787         threshold: 10,
1788         pointers: 1,
1789         direction: DIRECTION_ALL
1790     },
1792     getTouchAction: function() {
1793         var direction = this.options.direction;
1794         var actions = [];
1795         if (direction & DIRECTION_HORIZONTAL) {
1796             actions.push(TOUCH_ACTION_PAN_Y);
1797         }
1798         if (direction & DIRECTION_VERTICAL) {
1799             actions.push(TOUCH_ACTION_PAN_X);
1800         }
1801         return actions;
1802     },
1804     directionTest: function(input) {
1805         var options = this.options;
1806         var hasMoved = true;
1807         var distance = input.distance;
1808         var direction = input.direction;
1809         var x = input.deltaX;
1810         var y = input.deltaY;
1812         // lock to axis?
1813         if (!(direction & options.direction)) {
1814             if (options.direction & DIRECTION_HORIZONTAL) {
1815                 direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
1816                 hasMoved = x != this.pX;
1817                 distance = Math.abs(input.deltaX);
1818             } else {
1819                 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
1820                 hasMoved = y != this.pY;
1821                 distance = Math.abs(input.deltaY);
1822             }
1823         }
1824         input.direction = direction;
1825         return hasMoved && distance > options.threshold && direction & options.direction;
1826     },
1828     attrTest: function(input) {
1829         return AttrRecognizer.prototype.attrTest.call(this, input) &&
1830             (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
1831     },
1833     emit: function(input) {
1835         this.pX = input.deltaX;
1836         this.pY = input.deltaY;
1838         var direction = directionStr(input.direction);
1840         if (direction) {
1841             input.additionalEvent = this.options.event + direction;
1842         }
1843         this._super.emit.call(this, input);
1844     }
1848  * Pinch
1849  * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
1850  * @constructor
1851  * @extends AttrRecognizer
1852  */
1853 function PinchRecognizer() {
1854     AttrRecognizer.apply(this, arguments);
1857 inherit(PinchRecognizer, AttrRecognizer, {
1858     /**
1859      * @namespace
1860      * @memberof PinchRecognizer
1861      */
1862     defaults: {
1863         event: 'pinch',
1864         threshold: 0,
1865         pointers: 2
1866     },
1868     getTouchAction: function() {
1869         return [TOUCH_ACTION_NONE];
1870     },
1872     attrTest: function(input) {
1873         return this._super.attrTest.call(this, input) &&
1874             (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
1875     },
1877     emit: function(input) {
1878         if (input.scale !== 1) {
1879             var inOut = input.scale < 1 ? 'in' : 'out';
1880             input.additionalEvent = this.options.event + inOut;
1881         }
1882         this._super.emit.call(this, input);
1883     }
1887  * Press
1888  * Recognized when the pointer is down for x ms without any movement.
1889  * @constructor
1890  * @extends Recognizer
1891  */
1892 function PressRecognizer() {
1893     Recognizer.apply(this, arguments);
1895     this._timer = null;
1896     this._input = null;
1899 inherit(PressRecognizer, Recognizer, {
1900     /**
1901      * @namespace
1902      * @memberof PressRecognizer
1903      */
1904     defaults: {
1905         event: 'press',
1906         pointers: 1,
1907         time: 251, // minimal time of the pointer to be pressed
1908         threshold: 9 // a minimal movement is ok, but keep it low
1909     },
1911     getTouchAction: function() {
1912         return [TOUCH_ACTION_AUTO];
1913     },
1915     process: function(input) {
1916         var options = this.options;
1917         var validPointers = input.pointers.length === options.pointers;
1918         var validMovement = input.distance < options.threshold;
1919         var validTime = input.deltaTime > options.time;
1921         this._input = input;
1923         // we only allow little movement
1924         // and we've reached an end event, so a tap is possible
1925         if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
1926             this.reset();
1927         } else if (input.eventType & INPUT_START) {
1928             this.reset();
1929             this._timer = setTimeoutContext(function() {
1930                 this.state = STATE_RECOGNIZED;
1931                 this.tryEmit();
1932             }, options.time, this);
1933         } else if (input.eventType & INPUT_END) {
1934             return STATE_RECOGNIZED;
1935         }
1936         return STATE_FAILED;
1937     },
1939     reset: function() {
1940         clearTimeout(this._timer);
1941     },
1943     emit: function(input) {
1944         if (this.state !== STATE_RECOGNIZED) {
1945             return;
1946         }
1948         if (input && (input.eventType & INPUT_END)) {
1949             this.manager.emit(this.options.event + 'up', input);
1950         } else {
1951             this._input.timeStamp = now();
1952             this.manager.emit(this.options.event, this._input);
1953         }
1954     }
1958  * Rotate
1959  * Recognized when two or more pointer are moving in a circular motion.
1960  * @constructor
1961  * @extends AttrRecognizer
1962  */
1963 function RotateRecognizer() {
1964     AttrRecognizer.apply(this, arguments);
1967 inherit(RotateRecognizer, AttrRecognizer, {
1968     /**
1969      * @namespace
1970      * @memberof RotateRecognizer
1971      */
1972     defaults: {
1973         event: 'rotate',
1974         threshold: 0,
1975         pointers: 2
1976     },
1978     getTouchAction: function() {
1979         return [TOUCH_ACTION_NONE];
1980     },
1982     attrTest: function(input) {
1983         return this._super.attrTest.call(this, input) &&
1984             (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
1985     }
1989  * Swipe
1990  * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
1991  * @constructor
1992  * @extends AttrRecognizer
1993  */
1994 function SwipeRecognizer() {
1995     AttrRecognizer.apply(this, arguments);
1998 inherit(SwipeRecognizer, AttrRecognizer, {
1999     /**
2000      * @namespace
2001      * @memberof SwipeRecognizer
2002      */
2003     defaults: {
2004         event: 'swipe',
2005         threshold: 10,
2006         velocity: 0.3,
2007         direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
2008         pointers: 1
2009     },
2011     getTouchAction: function() {
2012         return PanRecognizer.prototype.getTouchAction.call(this);
2013     },
2015     attrTest: function(input) {
2016         var direction = this.options.direction;
2017         var velocity;
2019         if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
2020             velocity = input.overallVelocity;
2021         } else if (direction & DIRECTION_HORIZONTAL) {
2022             velocity = input.overallVelocityX;
2023         } else if (direction & DIRECTION_VERTICAL) {
2024             velocity = input.overallVelocityY;
2025         }
2027         return this._super.attrTest.call(this, input) &&
2028             direction & input.offsetDirection &&
2029             input.distance > this.options.threshold &&
2030             input.maxPointers == this.options.pointers &&
2031             abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
2032     },
2034     emit: function(input) {
2035         var direction = directionStr(input.offsetDirection);
2036         if (direction) {
2037             this.manager.emit(this.options.event + direction, input);
2038         }
2040         this.manager.emit(this.options.event, input);
2041     }
2045  * A tap is recognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
2046  * between the given interval and position. The delay option can be used to recognize multi-taps without firing
2047  * a single tap.
2049  * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
2050  * multi-taps being recognized.
2051  * @constructor
2052  * @extends Recognizer
2053  */
2054 function TapRecognizer() {
2055     Recognizer.apply(this, arguments);
2057     // previous time and center,
2058     // used for tap counting
2059     this.pTime = false;
2060     this.pCenter = false;
2062     this._timer = null;
2063     this._input = null;
2064     this.count = 0;
2067 inherit(TapRecognizer, Recognizer, {
2068     /**
2069      * @namespace
2070      * @memberof PinchRecognizer
2071      */
2072     defaults: {
2073         event: 'tap',
2074         pointers: 1,
2075         taps: 1,
2076         interval: 300, // max time between the multi-tap taps
2077         time: 250, // max time of the pointer to be down (like finger on the screen)
2078         threshold: 9, // a minimal movement is ok, but keep it low
2079         posThreshold: 10 // a multi-tap can be a bit off the initial position
2080     },
2082     getTouchAction: function() {
2083         return [TOUCH_ACTION_MANIPULATION];
2084     },
2086     process: function(input) {
2087         var options = this.options;
2089         var validPointers = input.pointers.length === options.pointers;
2090         var validMovement = input.distance < options.threshold;
2091         var validTouchTime = input.deltaTime < options.time;
2093         this.reset();
2095         if ((input.eventType & INPUT_START) && (this.count === 0)) {
2096             return this.failTimeout();
2097         }
2099         // we only allow little movement
2100         // and we've reached an end event, so a tap is possible
2101         if (validMovement && validTouchTime && validPointers) {
2102             if (input.eventType != INPUT_END) {
2103                 return this.failTimeout();
2104             }
2106             var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
2107             var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
2109             this.pTime = input.timeStamp;
2110             this.pCenter = input.center;
2112             if (!validMultiTap || !validInterval) {
2113                 this.count = 1;
2114             } else {
2115                 this.count += 1;
2116             }
2118             this._input = input;
2120             // if tap count matches we have recognized it,
2121             // else it has begun recognizing...
2122             var tapCount = this.count % options.taps;
2123             if (tapCount === 0) {
2124                 // no failing requirements, immediately trigger the tap event
2125                 // or wait as long as the multitap interval to trigger
2126                 if (!this.hasRequireFailures()) {
2127                     return STATE_RECOGNIZED;
2128                 } else {
2129                     this._timer = setTimeoutContext(function() {
2130                         this.state = STATE_RECOGNIZED;
2131                         this.tryEmit();
2132                     }, options.interval, this);
2133                     return STATE_BEGAN;
2134                 }
2135             }
2136         }
2137         return STATE_FAILED;
2138     },
2140     failTimeout: function() {
2141         this._timer = setTimeoutContext(function() {
2142             this.state = STATE_FAILED;
2143         }, this.options.interval, this);
2144         return STATE_FAILED;
2145     },
2147     reset: function() {
2148         clearTimeout(this._timer);
2149     },
2151     emit: function() {
2152         if (this.state == STATE_RECOGNIZED) {
2153             this._input.tapCount = this.count;
2154             this.manager.emit(this.options.event, this._input);
2155         }
2156     }
2160  * Simple way to create a manager with a default set of recognizers.
2161  * @param {HTMLElement} element
2162  * @param {Object} [options]
2163  * @constructor
2164  */
2165 function Hammer(element, options) {
2166     options = options || {};
2167     options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);
2168     return new Manager(element, options);
2172  * @const {string}
2173  */
2174 Hammer.VERSION = '2.0.7';
2177  * default settings
2178  * @namespace
2179  */
2180 Hammer.defaults = {
2181     /**
2182      * set if DOM events are being triggered.
2183      * But this is slower and unused by simple implementations, so disabled by default.
2184      * @type {Boolean}
2185      * @default false
2186      */
2187     domEvents: false,
2189     /**
2190      * The value for the touchAction property/fallback.
2191      * When set to `compute` it will magically set the correct value based on the added recognizers.
2192      * @type {String}
2193      * @default compute
2194      */
2195     touchAction: TOUCH_ACTION_COMPUTE,
2197     /**
2198      * @type {Boolean}
2199      * @default true
2200      */
2201     enable: true,
2203     /**
2204      * EXPERIMENTAL FEATURE -- can be removed/changed
2205      * Change the parent input target element.
2206      * If Null, then it is being set the to main element.
2207      * @type {Null|EventTarget}
2208      * @default null
2209      */
2210     inputTarget: null,
2212     /**
2213      * force an input class
2214      * @type {Null|Function}
2215      * @default null
2216      */
2217     inputClass: null,
2219     /**
2220      * Default recognizer setup when calling `Hammer()`
2221      * When creating a new Manager these will be skipped.
2222      * @type {Array}
2223      */
2224     preset: [
2225         // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
2226         [RotateRecognizer, {enable: false}],
2227         [PinchRecognizer, {enable: false}, ['rotate']],
2228         [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
2229         [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
2230         [TapRecognizer],
2231         [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
2232         [PressRecognizer]
2233     ],
2235     /**
2236      * Some CSS properties can be used to improve the working of Hammer.
2237      * Add them to this method and they will be set when creating a new Manager.
2238      * @namespace
2239      */
2240     cssProps: {
2241         /**
2242          * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
2243          * @type {String}
2244          * @default 'none'
2245          */
2246         userSelect: 'none',
2248         /**
2249          * Disable the Windows Phone grippers when pressing an element.
2250          * @type {String}
2251          * @default 'none'
2252          */
2253         touchSelect: 'none',
2255         /**
2256          * Disables the default callout shown when you touch and hold a touch target.
2257          * On iOS, when you touch and hold a touch target such as a link, Safari displays
2258          * a callout containing information about the link. This property allows you to disable that callout.
2259          * @type {String}
2260          * @default 'none'
2261          */
2262         touchCallout: 'none',
2264         /**
2265          * Specifies whether zooming is enabled. Used by IE10>
2266          * @type {String}
2267          * @default 'none'
2268          */
2269         contentZooming: 'none',
2271         /**
2272          * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
2273          * @type {String}
2274          * @default 'none'
2275          */
2276         userDrag: 'none',
2278         /**
2279          * Overrides the highlight color shown when the user taps a link or a JavaScript
2280          * clickable element in iOS. This property obeys the alpha value, if specified.
2281          * @type {String}
2282          * @default 'rgba(0,0,0,0)'
2283          */
2284         tapHighlightColor: 'rgba(0,0,0,0)'
2285     }
2288 var STOP = 1;
2289 var FORCED_STOP = 2;
2292  * Manager
2293  * @param {HTMLElement} element
2294  * @param {Object} [options]
2295  * @constructor
2296  */
2297 function Manager(element, options) {
2298     this.options = assign({}, Hammer.defaults, options || {});
2300     this.options.inputTarget = this.options.inputTarget || element;
2302     this.handlers = {};
2303     this.session = {};
2304     this.recognizers = [];
2305     this.oldCssProps = {};
2307     this.element = element;
2308     this.input = createInputInstance(this);
2309     this.touchAction = new TouchAction(this, this.options.touchAction);
2311     toggleCssProps(this, true);
2313     each(this.options.recognizers, function(item) {
2314         var recognizer = this.add(new (item[0])(item[1]));
2315         item[2] && recognizer.recognizeWith(item[2]);
2316         item[3] && recognizer.requireFailure(item[3]);
2317     }, this);
2320 Manager.prototype = {
2321     /**
2322      * set options
2323      * @param {Object} options
2324      * @returns {Manager}
2325      */
2326     set: function(options) {
2327         assign(this.options, options);
2329         // Options that need a little more setup
2330         if (options.touchAction) {
2331             this.touchAction.update();
2332         }
2333         if (options.inputTarget) {
2334             // Clean up existing event listeners and reinitialize
2335             this.input.destroy();
2336             this.input.target = options.inputTarget;
2337             this.input.init();
2338         }
2339         return this;
2340     },
2342     /**
2343      * stop recognizing for this session.
2344      * This session will be discarded, when a new [input]start event is fired.
2345      * When forced, the recognizer cycle is stopped immediately.
2346      * @param {Boolean} [force]
2347      */
2348     stop: function(force) {
2349         this.session.stopped = force ? FORCED_STOP : STOP;
2350     },
2352     /**
2353      * run the recognizers!
2354      * called by the inputHandler function on every movement of the pointers (touches)
2355      * it walks through all the recognizers and tries to detect the gesture that is being made
2356      * @param {Object} inputData
2357      */
2358     recognize: function(inputData) {
2359         var session = this.session;
2360         if (session.stopped) {
2361             return;
2362         }
2364         // run the touch-action polyfill
2365         this.touchAction.preventDefaults(inputData);
2367         var recognizer;
2368         var recognizers = this.recognizers;
2370         // this holds the recognizer that is being recognized.
2371         // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
2372         // if no recognizer is detecting a thing, it is set to `null`
2373         var curRecognizer = session.curRecognizer;
2375         // reset when the last recognizer is recognized
2376         // or when we're in a new session
2377         if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
2378             curRecognizer = session.curRecognizer = null;
2379         }
2381         var i = 0;
2382         while (i < recognizers.length) {
2383             recognizer = recognizers[i];
2385             // find out if we are allowed try to recognize the input for this one.
2386             // 1.   allow if the session is NOT forced stopped (see the .stop() method)
2387             // 2.   allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
2388             //      that is being recognized.
2389             // 3.   allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
2390             //      this can be setup with the `recognizeWith()` method on the recognizer.
2391             if (session.stopped !== FORCED_STOP && ( // 1
2392                     !curRecognizer || recognizer == curRecognizer || // 2
2393                     recognizer.canRecognizeWith(curRecognizer))) { // 3
2394                 recognizer.recognize(inputData);
2395             } else {
2396                 recognizer.reset();
2397             }
2399             // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
2400             // current active recognizer. but only if we don't already have an active recognizer
2401             if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
2402                 curRecognizer = session.curRecognizer = recognizer;
2403             }
2404             i++;
2405         }
2406     },
2408     /**
2409      * get a recognizer by its event name.
2410      * @param {Recognizer|String} recognizer
2411      * @returns {Recognizer|Null}
2412      */
2413     get: function(recognizer) {
2414         if (recognizer instanceof Recognizer) {
2415             return recognizer;
2416         }
2418         var recognizers = this.recognizers;
2419         for (var i = 0; i < recognizers.length; i++) {
2420             if (recognizers[i].options.event == recognizer) {
2421                 return recognizers[i];
2422             }
2423         }
2424         return null;
2425     },
2427     /**
2428      * add a recognizer to the manager
2429      * existing recognizers with the same event name will be removed
2430      * @param {Recognizer} recognizer
2431      * @returns {Recognizer|Manager}
2432      */
2433     add: function(recognizer) {
2434         if (invokeArrayArg(recognizer, 'add', this)) {
2435             return this;
2436         }
2438         // remove existing
2439         var existing = this.get(recognizer.options.event);
2440         if (existing) {
2441             this.remove(existing);
2442         }
2444         this.recognizers.push(recognizer);
2445         recognizer.manager = this;
2447         this.touchAction.update();
2448         return recognizer;
2449     },
2451     /**
2452      * remove a recognizer by name or instance
2453      * @param {Recognizer|String} recognizer
2454      * @returns {Manager}
2455      */
2456     remove: function(recognizer) {
2457         if (invokeArrayArg(recognizer, 'remove', this)) {
2458             return this;
2459         }
2461         recognizer = this.get(recognizer);
2463         // let's make sure this recognizer exists
2464         if (recognizer) {
2465             var recognizers = this.recognizers;
2466             var index = inArray(recognizers, recognizer);
2468             if (index !== -1) {
2469                 recognizers.splice(index, 1);
2470                 this.touchAction.update();
2471             }
2472         }
2474         return this;
2475     },
2477     /**
2478      * bind event
2479      * @param {String} events
2480      * @param {Function} handler
2481      * @returns {EventEmitter} this
2482      */
2483     on: function(events, handler) {
2484         if (events === undefined) {
2485             return;
2486         }
2487         if (handler === undefined) {
2488             return;
2489         }
2491         var handlers = this.handlers;
2492         each(splitStr(events), function(event) {
2493             handlers[event] = handlers[event] || [];
2494             handlers[event].push(handler);
2495         });
2496         return this;
2497     },
2499     /**
2500      * unbind event, leave emit blank to remove all handlers
2501      * @param {String} events
2502      * @param {Function} [handler]
2503      * @returns {EventEmitter} this
2504      */
2505     off: function(events, handler) {
2506         if (events === undefined) {
2507             return;
2508         }
2510         var handlers = this.handlers;
2511         each(splitStr(events), function(event) {
2512             if (!handler) {
2513                 delete handlers[event];
2514             } else {
2515                 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
2516             }
2517         });
2518         return this;
2519     },
2521     /**
2522      * emit event to the listeners
2523      * @param {String} event
2524      * @param {Object} data
2525      */
2526     emit: function(event, data) {
2527         // we also want to trigger dom events
2528         if (this.options.domEvents) {
2529             triggerDomEvent(event, data);
2530         }
2532         // no handlers, so skip it all
2533         var handlers = this.handlers[event] && this.handlers[event].slice();
2534         if (!handlers || !handlers.length) {
2535             return;
2536         }
2538         data.type = event;
2539         data.preventDefault = function() {
2540             data.srcEvent.preventDefault();
2541         };
2543         var i = 0;
2544         while (i < handlers.length) {
2545             handlers[i](data);
2546             i++;
2547         }
2548     },
2550     /**
2551      * destroy the manager and unbinds all events
2552      * it doesn't unbind dom events, that is the user own responsibility
2553      */
2554     destroy: function() {
2555         this.element && toggleCssProps(this, false);
2557         this.handlers = {};
2558         this.session = {};
2559         this.input.destroy();
2560         this.element = null;
2561     }
2565  * add/remove the css properties as defined in manager.options.cssProps
2566  * @param {Manager} manager
2567  * @param {Boolean} add
2568  */
2569 function toggleCssProps(manager, add) {
2570     var element = manager.element;
2571     if (!element.style) {
2572         return;
2573     }
2574     var prop;
2575     each(manager.options.cssProps, function(value, name) {
2576         prop = prefixed(element.style, name);
2577         if (add) {
2578             manager.oldCssProps[prop] = element.style[prop];
2579             element.style[prop] = value;
2580         } else {
2581             element.style[prop] = manager.oldCssProps[prop] || '';
2582         }
2583     });
2584     if (!add) {
2585         manager.oldCssProps = {};
2586     }
2590  * trigger dom event
2591  * @param {String} event
2592  * @param {Object} data
2593  */
2594 function triggerDomEvent(event, data) {
2595     var gestureEvent = document.createEvent('Event');
2596     gestureEvent.initEvent(event, true, true);
2597     gestureEvent.gesture = data;
2598     data.target.dispatchEvent(gestureEvent);
2601 assign(Hammer, {
2602     INPUT_START: INPUT_START,
2603     INPUT_MOVE: INPUT_MOVE,
2604     INPUT_END: INPUT_END,
2605     INPUT_CANCEL: INPUT_CANCEL,
2607     STATE_POSSIBLE: STATE_POSSIBLE,
2608     STATE_BEGAN: STATE_BEGAN,
2609     STATE_CHANGED: STATE_CHANGED,
2610     STATE_ENDED: STATE_ENDED,
2611     STATE_RECOGNIZED: STATE_RECOGNIZED,
2612     STATE_CANCELLED: STATE_CANCELLED,
2613     STATE_FAILED: STATE_FAILED,
2615     DIRECTION_NONE: DIRECTION_NONE,
2616     DIRECTION_LEFT: DIRECTION_LEFT,
2617     DIRECTION_RIGHT: DIRECTION_RIGHT,
2618     DIRECTION_UP: DIRECTION_UP,
2619     DIRECTION_DOWN: DIRECTION_DOWN,
2620     DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
2621     DIRECTION_VERTICAL: DIRECTION_VERTICAL,
2622     DIRECTION_ALL: DIRECTION_ALL,
2624     Manager: Manager,
2625     Input: Input,
2626     TouchAction: TouchAction,
2628     TouchInput: TouchInput,
2629     MouseInput: MouseInput,
2630     PointerEventInput: PointerEventInput,
2631     TouchMouseInput: TouchMouseInput,
2632     SingleTouchInput: SingleTouchInput,
2634     Recognizer: Recognizer,
2635     AttrRecognizer: AttrRecognizer,
2636     Tap: TapRecognizer,
2637     Pan: PanRecognizer,
2638     Swipe: SwipeRecognizer,
2639     Pinch: PinchRecognizer,
2640     Rotate: RotateRecognizer,
2641     Press: PressRecognizer,
2643     on: addEventListeners,
2644     off: removeEventListeners,
2645     each: each,
2646     merge: merge,
2647     extend: extend,
2648     assign: assign,
2649     inherit: inherit,
2650     bindFn: bindFn,
2651     prefixed: prefixed
2654 // this prevents errors when Hammer is loaded in the presence of an AMD
2655 //  style loader but by script tag, not by the loader.
2656 var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
2657 freeGlobal.Hammer = Hammer;
2659 if (typeof define === 'function' && define.amd) {
2660     define(function() {
2661         return Hammer;
2662     });
2663 } else if (typeof module != 'undefined' && module.exports) {
2664     module.exports = Hammer;
2665 } else {
2666     window[exportName] = Hammer;
2669 })(window, document, 'Hammer');
2671 /*****
2672  * @licstart
2674  * The following is the license notice for the part of JavaScript code of this
2675  * page included between the '@jessyinkstart' and the '@jessyinkend' notes.
2676  */
2678 /*****  ******************************************************************
2680  * Copyright 2008-2013 Hannes Hochreiner
2682  * The JavaScript code included between the start note '@jessyinkstart'
2683  * and the end note '@jessyinkend' is subject to the terms of the Mozilla
2684  * Public License, v. 2.0. If a copy of the MPL was not distributed with
2685  * this file, You can obtain one at http://mozilla.org/MPL/2.0/.
2687  * Alternatively, you can redistribute and/or that part of this file
2688  * under the terms of the GNU General Public License as published by
2689  * the Free Software Foundation, either version 3 of the License, or
2690  * (at your option) any later version.
2692  * This program is distributed in the hope that it will be useful,
2693  * but WITHOUT ANY WARRANTY; without even the implied warranty of
2694  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2695  * GNU General Public License for more details.
2697  * You should have received a copy of the GNU General Public License
2698  * along with this program.  If not, see http://www.gnu.org/licenses/.
2701 /*****
2702  *  You can find the complete source code of the JessyInk project at:
2703  *  @source http://code.google.com/p/jessyink/
2704  */
2706 /*****
2707  * @licend
2709  * The above is the license notice for the part of JavaScript code of this
2710  * page included between the '@jessyinkstart' and the '@jessyinkend' notes.
2711  */
2715 /*****
2716  * @jessyinkstart
2718  *  The following code is a derivative work of some parts of the JessyInk
2719  *  project.
2720  *  @source http://code.google.com/p/jessyink/
2721  */
2723 /** Convenience function to get an element depending on whether it has a
2724  *  property with a particular name.
2726  *  @param node   element of the document
2727  *  @param name   attribute name
2729  *  @returns   Array array containing all the elements of the tree with root
2730  *             'node' that own the property 'name'
2731  */
2732 function getElementsByProperty( node, name )
2734     var elements = [];
2736     if( node.getAttribute( name ) )
2737         elements.push( node );
2739     for( var counter = 0; counter < node.childNodes.length; ++counter )
2740     {
2741         if( node.childNodes[counter].nodeType == 1 )
2742         {
2743             var subElements = getElementsByProperty( node.childNodes[counter], name );
2744             elements = elements.concat( subElements );
2745         }
2746     }
2747     return elements;
2750 /** Event handler for key press.
2752  *  @param aEvt the event
2753  */
2754 function onKeyDown( aEvt )
2756     if ( !aEvt )
2757         aEvt = window.event;
2759     var code = aEvt.keyCode || aEvt.charCode;
2761     // console.log('===> onKeyDown: ' + code);
2763     // Handle arrow keys in iOS WebKit (including Mobile Safari)
2764     if (code == 0 && aEvt.key != undefined) {
2765         switch (aEvt.key) {
2766         case 'UIKeyInputLeftArrow':
2767             code = LEFT_KEY;
2768             break;
2769         case 'UIKeyInputUpArrow':
2770             code = UP_KEY;
2771             break;
2772         case 'UIKeyInputRightArrow':
2773             code = RIGHT_KEY;
2774             break;
2775         case 'UIKeyInputDownArrow':
2776             code = DOWN_KEY;
2777             break;
2778         }
2780         // console.log('     now: ' + code);
2781     }
2783     if( !processingEffect && keyCodeDictionary[currentMode] && keyCodeDictionary[currentMode][code] )
2784     {
2785         return keyCodeDictionary[currentMode][code]();
2786     }
2787     else
2788     {
2789         document.onkeypress = onKeyPress;
2790         return null;
2791     }
2793 //Set event handler for key down.
2794 document.onkeydown = onKeyDown;
2796 /** Event handler for key press.
2798  *  @param aEvt the event
2799  */
2800 function onKeyPress( aEvt )
2802     document.onkeypress = null;
2804     if ( !aEvt )
2805         aEvt = window.event;
2807     var str = String.fromCharCode( aEvt.keyCode || aEvt.charCode );
2809     if ( !processingEffect && charCodeDictionary[currentMode] && charCodeDictionary[currentMode][str] )
2810         return charCodeDictionary[currentMode][str]();
2812     return null;
2815 /** Function to supply the default key code dictionary.
2817  *  @returns Object default key code dictionary
2818  */
2819 function getDefaultKeyCodeDictionary()
2821     var keyCodeDict = {};
2823     keyCodeDict[SLIDE_MODE] = {};
2824     keyCodeDict[INDEX_MODE] = {};
2826     // slide mode
2827     keyCodeDict[SLIDE_MODE][LEFT_KEY]
2828         = function() { return aSlideShow.rewindEffect(); };
2829     keyCodeDict[SLIDE_MODE][RIGHT_KEY]
2830         = function() { return dispatchEffects(1); };
2831     keyCodeDict[SLIDE_MODE][UP_KEY]
2832         = function() { return aSlideShow.rewindEffect(); };
2833     keyCodeDict[SLIDE_MODE][DOWN_KEY]
2834         = function() { return skipEffects(1); };
2835     keyCodeDict[SLIDE_MODE][PAGE_UP_KEY]
2836         = function() { return aSlideShow.rewindAllEffects(); };
2837     keyCodeDict[SLIDE_MODE][PAGE_DOWN_KEY]
2838         = function() { return skipAllEffects(); };
2839     keyCodeDict[SLIDE_MODE][HOME_KEY]
2840         = function() { return aSlideShow.displaySlide( 0, true ); };
2841     keyCodeDict[SLIDE_MODE][END_KEY]
2842         = function() { return aSlideShow.displaySlide( theMetaDoc.nNumberOfSlides - 1, true ); };
2843     keyCodeDict[SLIDE_MODE][SPACE_KEY]
2844         = function() { return dispatchEffects(1); };
2845     // The ESC key can't actually be handled on iOS, it seems to be hardcoded to work like the home button? But try anyway.
2846     keyCodeDict[SLIDE_MODE][ESCAPE_KEY]
2847         = function() { return aSlideShow.exitSlideShowInApp(); };
2848     keyCodeDict[SLIDE_MODE][Q_KEY]
2849         = function() { return aSlideShow.exitSlideShowInApp(); };
2851     // index mode
2852     keyCodeDict[INDEX_MODE][LEFT_KEY]
2853         = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex - 1 ); };
2854     keyCodeDict[INDEX_MODE][RIGHT_KEY]
2855         = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex + 1 ); };
2856     keyCodeDict[INDEX_MODE][UP_KEY]
2857         = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex - theSlideIndexPage.indexColumns ); };
2858     keyCodeDict[INDEX_MODE][DOWN_KEY]
2859         = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex + theSlideIndexPage.indexColumns ); };
2860     keyCodeDict[INDEX_MODE][PAGE_UP_KEY]
2861         = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex - theSlideIndexPage.getTotalThumbnails() ); };
2862     keyCodeDict[INDEX_MODE][PAGE_DOWN_KEY]
2863         = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex + theSlideIndexPage.getTotalThumbnails() ); };
2864     keyCodeDict[INDEX_MODE][HOME_KEY]
2865         = function() { return indexSetPageSlide( 0 ); };
2866     keyCodeDict[INDEX_MODE][END_KEY]
2867         = function() { return indexSetPageSlide( theMetaDoc.nNumberOfSlides - 1 ); };
2868     keyCodeDict[INDEX_MODE][ENTER_KEY]
2869         = function() { return toggleSlideIndex(); };
2870     keyCodeDict[INDEX_MODE][SPACE_KEY]
2871         = function() { return toggleSlideIndex(); };
2872     keyCodeDict[INDEX_MODE][ESCAPE_KEY]
2873         = function() { return abandonIndexMode(); };
2875     return keyCodeDict;
2878 /** Function to supply the default char code dictionary.
2880  *  @returns Object char code dictionary
2881  */
2882 function getDefaultCharCodeDictionary()
2884     var charCodeDict = {};
2886     charCodeDict[SLIDE_MODE] = {};
2887     charCodeDict[INDEX_MODE] = {};
2889     // slide mode
2890     charCodeDict[SLIDE_MODE]['i']
2891         = function () { return toggleSlideIndex(); };
2893     // index mode
2894     charCodeDict[INDEX_MODE]['i']
2895         = function () { return toggleSlideIndex(); };
2896     charCodeDict[INDEX_MODE]['-']
2897         = function () { return theSlideIndexPage.decreaseNumberOfColumns(); };
2898     charCodeDict[INDEX_MODE]['=']
2899         = function () { return theSlideIndexPage.increaseNumberOfColumns(); };
2900     charCodeDict[INDEX_MODE]['+']
2901         = function () { return theSlideIndexPage.increaseNumberOfColumns(); };
2902     charCodeDict[INDEX_MODE]['0']
2903         = function () { return theSlideIndexPage.resetNumberOfColumns(); };
2905     return charCodeDict;
2909 function slideOnMouseUp( aEvt )
2911     if (!aEvt)
2912         aEvt = window.event;
2914     var nOffset = 0;
2916     if( aEvt.button == 0 )
2917         nOffset = 1;
2918     else if( aEvt.button == 2 )
2919         nOffset = -1;
2921     if( 0 != nOffset )
2922         dispatchEffects( nOffset );
2923     return true; // the click has been handled
2926 document.handleClick = slideOnMouseUp;
2929 /** Event handler for mouse wheel events in slide mode.
2930  *  based on http://adomas.org/javascript-mouse-wheel/
2932  *  @param aEvt the event
2933  */
2934 function slideOnMouseWheel(aEvt)
2936     var delta = 0;
2938     if (!aEvt)
2939         aEvt = window.event;
2941     if (aEvt.wheelDelta)
2942     { // IE Opera
2943         delta = aEvt.wheelDelta/120;
2944     }
2945     else if (aEvt.detail)
2946     { // MOZ
2947         delta = -aEvt.detail/3;
2948     }
2950     if (delta > 0)
2951         skipEffects(-1);
2952     else if (delta < 0)
2953         skipEffects(1);
2955     if (aEvt.preventDefault)
2956         aEvt.preventDefault();
2958     aEvt.returnValue = false;
2961 //Mozilla
2962 if( window.addEventListener )
2964     window.addEventListener( 'DOMMouseScroll', function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_WHEEL ); }, false );
2967 //Opera Safari OK - may not work in IE
2968 window.onmousewheel
2969     = function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_WHEEL ); };
2971 /** Function to handle all mouse events.
2973  *  @param  aEvt    event
2974  *  @param  anAction  type of event (e.g. mouse up, mouse wheel)
2975  */
2976 function mouseHandlerDispatch( aEvt, anAction )
2978     if( !aEvt )
2979         aEvt = window.event;
2981     var retVal = true;
2983     if ( mouseHandlerDictionary[currentMode] && mouseHandlerDictionary[currentMode][anAction] )
2984     {
2985         var subRetVal = mouseHandlerDictionary[currentMode][anAction]( aEvt );
2987         if( subRetVal != null && subRetVal != undefined )
2988             retVal = subRetVal;
2989     }
2991     if( aEvt.preventDefault && !retVal )
2992         aEvt.preventDefault();
2994     aEvt.returnValue = retVal;
2996     return retVal;
2999 //Set mouse event handler.
3000 document.onmouseup = function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_UP ); };
3003 /** mouseClickHelper
3005  * @return {Object}
3006  *   a mouse click handler
3007  */
3008 function mouseClickHelper( aEvt )
3010     // In case text is selected we stay on the current slide.
3011     // Anyway if we are dealing with Firefox there is an issue:
3012     // Firefox supports a naive way of selecting svg text, if you click
3013     // on text the current selection is set to the whole text fragment
3014     // wrapped by the related <tspan> element.
3015     // That means until you click on text you never move to the next slide.
3016     // In order to avoid this case we do not test the status of current
3017     // selection, when the presentation is running on a mozilla browser.
3018     if( !Detect.isMozilla )
3019     {
3020         var aWindowObject = document.defaultView;
3021         if( aWindowObject )
3022         {
3023             var aTextSelection = aWindowObject.getSelection();
3024             var sSelectedText =  aTextSelection.toString();
3025             if( sSelectedText )
3026             {
3027                 DBGLOG( 'text selection: ' + sSelectedText );
3028                 if( sLastSelectedText !== sSelectedText )
3029                 {
3030                     bTextHasBeenSelected = true;
3031                     sLastSelectedText = sSelectedText;
3032                 }
3033                 else
3034                 {
3035                     bTextHasBeenSelected = false;
3036                 }
3037                 return null;
3038             }
3039             else if( bTextHasBeenSelected )
3040             {
3041                 bTextHasBeenSelected = false;
3042                 sLastSelectedText = '';
3043                 return null;
3044             }
3045         }
3046         else
3047         {
3048             log( 'error: HyperlinkElement.handleClick: invalid window object.' );
3049         }
3050     }
3052     var aSlideAnimationsHandler = theMetaDoc.aMetaSlideSet[nCurSlide].aSlideAnimationsHandler;
3053     if( aSlideAnimationsHandler )
3054     {
3055         var aCurrentEventMultiplexer = aSlideAnimationsHandler.aEventMultiplexer;
3056         if( aCurrentEventMultiplexer )
3057         {
3058             if( aCurrentEventMultiplexer.hasRegisteredMouseClickHandlers() )
3059             {
3060                 return aCurrentEventMultiplexer.notifyMouseClick( aEvt );
3061             }
3062         }
3063     }
3064     return slideOnMouseUp( aEvt );
3068 /** Function to supply the default mouse handler dictionary.
3070  *  @returns Object default mouse handler dictionary
3071  */
3072 function getDefaultMouseHandlerDictionary()
3074     var mouseHandlerDict = {};
3076     mouseHandlerDict[SLIDE_MODE] = {};
3077     mouseHandlerDict[INDEX_MODE] = {};
3079     // slide mode
3080     mouseHandlerDict[SLIDE_MODE][MOUSE_UP]
3081         = mouseClickHelper;
3083     mouseHandlerDict[SLIDE_MODE][MOUSE_WHEEL]
3084         = function( aEvt ) { return slideOnMouseWheel( aEvt ); };
3086     // index mode
3087     mouseHandlerDict[INDEX_MODE][MOUSE_UP]
3088         = function( ) { return toggleSlideIndex(); };
3090     return mouseHandlerDict;
3093 /** Function to set the page and active slide in index view.
3095  *  @param nIndex index of the active slide
3097  *  NOTE: To force a redraw,
3098  *  set INDEX_OFFSET to -1 before calling indexSetPageSlide().
3100  *  This is necessary for zooming (otherwise the index might not
3101  *  get redrawn) and when switching to index mode.
3103  *  INDEX_OFFSET = -1
3104  *  indexSetPageSlide(activeSlide);
3105  */
3106 function indexSetPageSlide( nIndex )
3108     var aMetaSlideSet = theMetaDoc.aMetaSlideSet;
3109     nIndex = getSafeIndex( nIndex, 0, aMetaSlideSet.length - 1 );
3111     //calculate the offset
3112     var nSelectedThumbnailIndex = nIndex % theSlideIndexPage.getTotalThumbnails();
3113     var offset = nIndex - nSelectedThumbnailIndex;
3115     if( offset < 0 )
3116         offset = 0;
3118     //if different from kept offset, then record and change the page
3119     if( offset != INDEX_OFFSET )
3120     {
3121         INDEX_OFFSET = offset;
3122         displayIndex( INDEX_OFFSET );
3123     }
3125     //set the selected thumbnail and the current slide
3126     theSlideIndexPage.setSelection( nSelectedThumbnailIndex );
3130 /*****
3131  * @jessyinkend
3133  *  The above code is a derivative work of some parts of the JessyInk project.
3134  *  @source http://code.google.com/p/jessyink/
3135  */
3141 /*****
3142  * @licstart
3144  * The following is the license notice for the part of JavaScript code of this
3145  * page included between the '@dojostart' and the '@dojoend' notes.
3146  */
3148 /*****  **********************************************************************
3150  *  The 'New' BSD License:
3151  *  **********************
3152  *  Copyright (c) 2005-2012, The Dojo Foundation
3153  *  All rights reserved.
3155  *  Redistribution and use in source and binary forms, with or without
3156  *  modification, are permitted provided that the following conditions are met:
3158  *    * Redistributions of source code must retain the above copyright notice,
3159  *      this list of conditions and the following disclaimer.
3160  *    * Redistributions in binary form must reproduce the above copyright notice,
3161  *      this list of conditions and the following disclaimer in the documentation
3162  *      and/or other materials provided with the distribution.
3163  *    * Neither the name of the Dojo Foundation nor the names of its contributors
3164  *      may be used to endorse or promote products derived from this software
3165  *      without specific prior written permission.
3167  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND
3168  *  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
3169  *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
3170  *  DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
3171  *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3172  *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
3173  *  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
3174  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
3175  *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3176  *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3178  ****************************************************************************/
3181 /*****
3182  * @licend
3184  * The above is the license notice for the part of JavaScript code of this
3185  * page included between the '@dojostart' and the '@dojoend' notes.
3186  */
3190 /*****
3191  * @dojostart
3193  *  The following code is a derivative work of some part of the dojox.gfx library.
3194  *  @source http://svn.dojotoolkit.org/src/dojox/trunk/_base/sniff.js
3195  */
3197 function has( name )
3199     return has.cache[name];
3202 has.cache = {};
3204 has.add = function( name, test )
3206     has.cache[name] = test;
3209 function configureDetectionTools()
3211     if( !navigator )
3212     {
3213         log( 'error: configureDetectionTools: configuration failed' );
3214         return null;
3215     }
3217     var n = navigator,
3218     dua = n.userAgent,
3219     dav = n.appVersion,
3220     tv = parseFloat(dav);
3222     has.add('air', dua.indexOf('AdobeAIR') >= 0);
3223     has.add('khtml', dav.indexOf('Konqueror') >= 0 ? tv : undefined);
3224     has.add('webkit', parseFloat(dua.split('WebKit/')[1]) || undefined);
3225     has.add('chrome', parseFloat(dua.split('Chrome/')[1]) || undefined);
3226     has.add('safari', dav.indexOf('Safari')>=0 && !has('chrome') ? parseFloat(dav.split('Version/')[1]) : undefined);
3227     has.add('mac', dav.indexOf('Macintosh') >= 0);
3228     has.add('quirks', document.compatMode == 'BackCompat');
3229     has.add('ios', /iPhone|iPod|iPad/.test(dua));
3230     has.add('android', parseFloat(dua.split('Android ')[1]) || undefined);
3232     if(!has('webkit')){
3233         // Opera
3234         if(dua.indexOf('Opera') >= 0){
3235             // see http://dev.opera.com/articles/view/opera-ua-string-changes and http://www.useragentstring.com/pages/Opera/
3236             // 9.8 has both styles; <9.8, 9.9 only old style
3237             has.add('opera', tv >= 9.8 ? parseFloat(dua.split('Version/')[1]) || tv : tv);
3238         }
3240         // Mozilla and firefox
3241         if(dua.indexOf('Gecko') >= 0 && !has('khtml') && !has('webkit')){
3242             has.add('mozilla', tv);
3243         }
3244         if(has('mozilla')){
3245             //We really need to get away from this. Consider a sane isGecko approach for the future.
3246             has.add('ff', parseFloat(dua.split('Firefox/')[1] || dua.split('Minefield/')[1]) || undefined);
3247         }
3249         // IE
3250         if(document.all && !has('opera')){
3251             var isIE = parseFloat(dav.split('MSIE ')[1]) || undefined;
3253             //In cases where the page has an HTTP header or META tag with
3254             //X-UA-Compatible, then it is in emulation mode.
3255             //Make sure isIE reflects the desired version.
3256             //document.documentMode of 5 means quirks mode.
3257             //Only switch the value if documentMode's major version
3258             //is different from isIE major version.
3259             var mode = document.documentMode;
3260             if(mode && mode != 5 && Math.floor(isIE) != mode){
3261                 isIE = mode;
3262             }
3264             has.add('ie', isIE);
3265         }
3267         // Wii
3268         has.add('wii', typeof opera != 'undefined' && opera.wiiremote);
3269     }
3271     var detect =
3272     {
3273                 // isFF: Number|undefined
3274                 //              Version as a Number if client is FireFox. undefined otherwise. Corresponds to
3275                 //              major detected FireFox version (1.5, 2, 3, etc.)
3276                 isFF: has('ff'),
3278                 // isIE: Number|undefined
3279                 //              Version as a Number if client is MSIE(PC). undefined otherwise. Corresponds to
3280                 //              major detected IE version (6, 7, 8, etc.)
3281                 isIE: has('ie'),
3283                 // isKhtml: Number|undefined
3284                 //              Version as a Number if client is a KHTML browser. undefined otherwise. Corresponds to major
3285                 //              detected version.
3286                 isKhtml: has('khtml'),
3288                 // isWebKit: Number|undefined
3289                 //              Version as a Number if client is a WebKit-derived browser (Konqueror,
3290                 //              Safari, Chrome, etc.). undefined otherwise.
3291                 isWebKit: has('webkit'),
3293                 // isMozilla: Number|undefined
3294                 //              Version as a Number if client is a Mozilla-based browser (Firefox,
3295                 //              SeaMonkey). undefined otherwise. Corresponds to major detected version.
3296                 isMozilla: has('mozilla'),
3297                 // isMoz: Number|undefined
3298                 //              Version as a Number if client is a Mozilla-based browser (Firefox,
3299                 //              SeaMonkey). undefined otherwise. Corresponds to major detected version.
3300                 isMoz: has('mozilla'),
3302                 // isOpera: Number|undefined
3303                 //              Version as a Number if client is Opera. undefined otherwise. Corresponds to
3304                 //              major detected version.
3305                 isOpera: has('opera'),
3307                 // isSafari: Number|undefined
3308                 //              Version as a Number if client is Safari or iPhone. undefined otherwise.
3309                 isSafari: has('safari'),
3311                 // isChrome: Number|undefined
3312                 //              Version as a Number if client is Chrome browser. undefined otherwise.
3313                 isChrome: has('chrome'),
3315                 // isMac: Boolean
3316                 //              True if the client runs on Mac
3317                 isMac: has('mac'),
3319                 // isIos: Boolean
3320                 //              True if client is iPhone, iPod, or iPad
3321                 isIos: has('ios'),
3323                 // isAndroid: Number|undefined
3324                 //              Version as a Number if client is android browser. undefined otherwise.
3325                 isAndroid: has('android'),
3327                 // isWii: Boolean
3328                 //              True if client is Wii
3329                 isWii: has('wii'),
3331                 // isQuirks: Boolean
3332                 //              Page is in quirks mode.
3333                 isQuirks: has('quirks'),
3335                 // isAir: Boolean
3336                 //              True if client is Adobe Air
3337                 isAir: has('air')
3338     };
3339     return detect;
3342 /*****
3343  * @dojoend
3345  *  The above code is a derivative work of some part of the dojox.gfx library.
3346  *  @source http://svn.dojotoolkit.org/src/dojox/trunk/_base/sniff.js
3347  */
3349 /*****
3350  * @licstart
3352  * The following is the license notice for the part of JavaScript code of this
3353  * file included between the '@svgpathstart' and the '@svgpathend' notes.
3354  */
3356 /*****  **********************************************************************
3358  *   Copyright 2015 The Chromium Authors. All rights reserved.
3360  *   The Chromium Authors can be found at
3361  *   http://src.chromium.org/svn/trunk/src/AUTHORS
3363  *   Redistribution and use in source and binary forms, with or without
3364  *   modification, are permitted provided that the following conditions are
3365  *   met:
3367  *   * Redistributions of source code must retain the above copyright
3368  *   notice, this list of conditions and the following disclaimer.
3369  *   * Redistributions in binary form must reproduce the above
3370  *   copyright notice, this list of conditions and the following disclaimer
3371  *   in the documentation and/or other materials provided with the
3372  *   distribution.
3373  *   * Neither the name of Google Inc. nor the names of its
3374  *   contributors may be used to endorse or promote products derived from
3375  *   this software without specific prior written permission.
3377  *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
3378  *   'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
3379  *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
3380  *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
3381  *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
3382  *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
3383  *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
3384  *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3385  *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3386  *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3387  *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3389  ****************************************************************************/
3391 /*****
3392  * @licend
3394  * The above is the license notice for the part of JavaScript code of this
3395  * file included between the '@svgpathstart' and the '@svgpathend' notes.
3396  */
3399 /*****
3400  * @svgpathstart
3402  *  The following code is a derivative work of some part of the SVGPathSeg API.
3404  *  This API is a drop-in replacement for the SVGPathSeg and SVGPathSegList APIs that were removed from
3405  *  SVG2 (https://lists.w3.org/Archives/Public/www-svg/2015Jun/0044.html), including the latest spec
3406  *  changes which were implemented in Firefox 43 and Chrome 46.
3408  *  @source https://github.com/progers/pathseg
3409  */
3411 (function() { 'use strict';
3412     if (!('SVGPathSeg' in window)) {
3413         // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSeg
3414         window.SVGPathSeg = function(type, typeAsLetter, owningPathSegList) {
3415             this.pathSegType = type;
3416             this.pathSegTypeAsLetter = typeAsLetter;
3417             this._owningPathSegList = owningPathSegList;
3418         }
3420         window.SVGPathSeg.prototype.classname = 'SVGPathSeg';
3422         window.SVGPathSeg.PATHSEG_UNKNOWN = 0;
3423         window.SVGPathSeg.PATHSEG_CLOSEPATH = 1;
3424         window.SVGPathSeg.PATHSEG_MOVETO_ABS = 2;
3425         window.SVGPathSeg.PATHSEG_MOVETO_REL = 3;
3426         window.SVGPathSeg.PATHSEG_LINETO_ABS = 4;
3427         window.SVGPathSeg.PATHSEG_LINETO_REL = 5;
3428         window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS = 6;
3429         window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL = 7;
3430         window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS = 8;
3431         window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL = 9;
3432         window.SVGPathSeg.PATHSEG_ARC_ABS = 10;
3433         window.SVGPathSeg.PATHSEG_ARC_REL = 11;
3434         window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS = 12;
3435         window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL = 13;
3436         window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS = 14;
3437         window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL = 15;
3438         window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS = 16;
3439         window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL = 17;
3440         window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS = 18;
3441         window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL = 19;
3443         // Notify owning PathSegList on any changes so they can be synchronized back to the path element.
3444         window.SVGPathSeg.prototype._segmentChanged = function() {
3445             if (this._owningPathSegList)
3446                 this._owningPathSegList.segmentChanged(this);
3447         }
3449         window.SVGPathSegClosePath = function(owningPathSegList) {
3450             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CLOSEPATH, 'z', owningPathSegList);
3451         }
3452         window.SVGPathSegClosePath.prototype = Object.create(window.SVGPathSeg.prototype);
3453         window.SVGPathSegClosePath.prototype.toString = function() { return '[object SVGPathSegClosePath]'; }
3454         window.SVGPathSegClosePath.prototype._asPathString = function() { return this.pathSegTypeAsLetter; }
3455         window.SVGPathSegClosePath.prototype.clone = function() { return new window.SVGPathSegClosePath(undefined); }
3457         window.SVGPathSegMovetoAbs = function(owningPathSegList, x, y) {
3458             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_MOVETO_ABS, 'M', owningPathSegList);
3459             this._x = x;
3460             this._y = y;
3461         }
3462         window.SVGPathSegMovetoAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3463         window.SVGPathSegMovetoAbs.prototype.toString = function() { return '[object SVGPathSegMovetoAbs]'; }
3464         window.SVGPathSegMovetoAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
3465         window.SVGPathSegMovetoAbs.prototype.clone = function() { return new window.SVGPathSegMovetoAbs(undefined, this._x, this._y); }
3466         Object.defineProperty(window.SVGPathSegMovetoAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3467         Object.defineProperty(window.SVGPathSegMovetoAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3469         window.SVGPathSegMovetoRel = function(owningPathSegList, x, y) {
3470             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_MOVETO_REL, 'm', owningPathSegList);
3471             this._x = x;
3472             this._y = y;
3473         }
3474         window.SVGPathSegMovetoRel.prototype = Object.create(window.SVGPathSeg.prototype);
3475         window.SVGPathSegMovetoRel.prototype.toString = function() { return '[object SVGPathSegMovetoRel]'; }
3476         window.SVGPathSegMovetoRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
3477         window.SVGPathSegMovetoRel.prototype.clone = function() { return new window.SVGPathSegMovetoRel(undefined, this._x, this._y); }
3478         Object.defineProperty(window.SVGPathSegMovetoRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3479         Object.defineProperty(window.SVGPathSegMovetoRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3481         window.SVGPathSegLinetoAbs = function(owningPathSegList, x, y) {
3482             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_ABS, 'L', owningPathSegList);
3483             this._x = x;
3484             this._y = y;
3485         }
3486         window.SVGPathSegLinetoAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3487         window.SVGPathSegLinetoAbs.prototype.toString = function() { return '[object SVGPathSegLinetoAbs]'; }
3488         window.SVGPathSegLinetoAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
3489         window.SVGPathSegLinetoAbs.prototype.clone = function() { return new window.SVGPathSegLinetoAbs(undefined, this._x, this._y); }
3490         Object.defineProperty(window.SVGPathSegLinetoAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3491         Object.defineProperty(window.SVGPathSegLinetoAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3493         window.SVGPathSegLinetoRel = function(owningPathSegList, x, y) {
3494             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_REL, 'l', owningPathSegList);
3495             this._x = x;
3496             this._y = y;
3497         }
3498         window.SVGPathSegLinetoRel.prototype = Object.create(window.SVGPathSeg.prototype);
3499         window.SVGPathSegLinetoRel.prototype.toString = function() { return '[object SVGPathSegLinetoRel]'; }
3500         window.SVGPathSegLinetoRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
3501         window.SVGPathSegLinetoRel.prototype.clone = function() { return new window.SVGPathSegLinetoRel(undefined, this._x, this._y); }
3502         Object.defineProperty(window.SVGPathSegLinetoRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3503         Object.defineProperty(window.SVGPathSegLinetoRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3505         window.SVGPathSegCurvetoCubicAbs = function(owningPathSegList, x, y, x1, y1, x2, y2) {
3506             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS, 'C', owningPathSegList);
3507             this._x = x;
3508             this._y = y;
3509             this._x1 = x1;
3510             this._y1 = y1;
3511             this._x2 = x2;
3512             this._y2 = y2;
3513         }
3514         window.SVGPathSegCurvetoCubicAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3515         window.SVGPathSegCurvetoCubicAbs.prototype.toString = function() { return '[object SVGPathSegCurvetoCubicAbs]'; }
3516         window.SVGPathSegCurvetoCubicAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
3517         window.SVGPathSegCurvetoCubicAbs.prototype.clone = function() { return new window.SVGPathSegCurvetoCubicAbs(undefined, this._x, this._y, this._x1, this._y1, this._x2, this._y2); }
3518         Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3519         Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3520         Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'x1', { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
3521         Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'y1', { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
3522         Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'x2', { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
3523         Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'y2', { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
3525         window.SVGPathSegCurvetoCubicRel = function(owningPathSegList, x, y, x1, y1, x2, y2) {
3526             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL, 'c', owningPathSegList);
3527             this._x = x;
3528             this._y = y;
3529             this._x1 = x1;
3530             this._y1 = y1;
3531             this._x2 = x2;
3532             this._y2 = y2;
3533         }
3534         window.SVGPathSegCurvetoCubicRel.prototype = Object.create(window.SVGPathSeg.prototype);
3535         window.SVGPathSegCurvetoCubicRel.prototype.toString = function() { return '[object SVGPathSegCurvetoCubicRel]'; }
3536         window.SVGPathSegCurvetoCubicRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
3537         window.SVGPathSegCurvetoCubicRel.prototype.clone = function() { return new window.SVGPathSegCurvetoCubicRel(undefined, this._x, this._y, this._x1, this._y1, this._x2, this._y2); }
3538         Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3539         Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3540         Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'x1', { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
3541         Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'y1', { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
3542         Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'x2', { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
3543         Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'y2', { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
3545         window.SVGPathSegCurvetoQuadraticAbs = function(owningPathSegList, x, y, x1, y1) {
3546             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS, 'Q', owningPathSegList);
3547             this._x = x;
3548             this._y = y;
3549             this._x1 = x1;
3550             this._y1 = y1;
3551         }
3552         window.SVGPathSegCurvetoQuadraticAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3553         window.SVGPathSegCurvetoQuadraticAbs.prototype.toString = function() { return '[object SVGPathSegCurvetoQuadraticAbs]'; }
3554         window.SVGPathSegCurvetoQuadraticAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x + ' ' + this._y; }
3555         window.SVGPathSegCurvetoQuadraticAbs.prototype.clone = function() { return new window.SVGPathSegCurvetoQuadraticAbs(undefined, this._x, this._y, this._x1, this._y1); }
3556         Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3557         Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3558         Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype, 'x1', { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
3559         Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype, 'y1', { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
3561         window.SVGPathSegCurvetoQuadraticRel = function(owningPathSegList, x, y, x1, y1) {
3562             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL, 'q', owningPathSegList);
3563             this._x = x;
3564             this._y = y;
3565             this._x1 = x1;
3566             this._y1 = y1;
3567         }
3568         window.SVGPathSegCurvetoQuadraticRel.prototype = Object.create(window.SVGPathSeg.prototype);
3569         window.SVGPathSegCurvetoQuadraticRel.prototype.toString = function() { return '[object SVGPathSegCurvetoQuadraticRel]'; }
3570         window.SVGPathSegCurvetoQuadraticRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x + ' ' + this._y; }
3571         window.SVGPathSegCurvetoQuadraticRel.prototype.clone = function() { return new window.SVGPathSegCurvetoQuadraticRel(undefined, this._x, this._y, this._x1, this._y1); }
3572         Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3573         Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3574         Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype, 'x1', { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
3575         Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype, 'y1', { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
3577         window.SVGPathSegArcAbs = function(owningPathSegList, x, y, r1, r2, angle, largeArcFlag, sweepFlag) {
3578             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_ARC_ABS, 'A', owningPathSegList);
3579             this._x = x;
3580             this._y = y;
3581             this._r1 = r1;
3582             this._r2 = r2;
3583             this._angle = angle;
3584             this._largeArcFlag = largeArcFlag;
3585             this._sweepFlag = sweepFlag;
3586         }
3587         window.SVGPathSegArcAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3588         window.SVGPathSegArcAbs.prototype.toString = function() { return '[object SVGPathSegArcAbs]'; }
3589         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; }
3590         window.SVGPathSegArcAbs.prototype.clone = function() { return new window.SVGPathSegArcAbs(undefined, this._x, this._y, this._r1, this._r2, this._angle, this._largeArcFlag, this._sweepFlag); }
3591         Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3592         Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3593         Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'r1', { get: function() { return this._r1; }, set: function(r1) { this._r1 = r1; this._segmentChanged(); }, enumerable: true });
3594         Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'r2', { get: function() { return this._r2; }, set: function(r2) { this._r2 = r2; this._segmentChanged(); }, enumerable: true });
3595         Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'angle', { get: function() { return this._angle; }, set: function(angle) { this._angle = angle; this._segmentChanged(); }, enumerable: true });
3596         Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'largeArcFlag', { get: function() { return this._largeArcFlag; }, set: function(largeArcFlag) { this._largeArcFlag = largeArcFlag; this._segmentChanged(); }, enumerable: true });
3597         Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'sweepFlag', { get: function() { return this._sweepFlag; }, set: function(sweepFlag) { this._sweepFlag = sweepFlag; this._segmentChanged(); }, enumerable: true });
3599         window.SVGPathSegArcRel = function(owningPathSegList, x, y, r1, r2, angle, largeArcFlag, sweepFlag) {
3600             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_ARC_REL, 'a', owningPathSegList);
3601             this._x = x;
3602             this._y = y;
3603             this._r1 = r1;
3604             this._r2 = r2;
3605             this._angle = angle;
3606             this._largeArcFlag = largeArcFlag;
3607             this._sweepFlag = sweepFlag;
3608         }
3609         window.SVGPathSegArcRel.prototype = Object.create(window.SVGPathSeg.prototype);
3610         window.SVGPathSegArcRel.prototype.toString = function() { return '[object SVGPathSegArcRel]'; }
3611         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; }
3612         window.SVGPathSegArcRel.prototype.clone = function() { return new window.SVGPathSegArcRel(undefined, this._x, this._y, this._r1, this._r2, this._angle, this._largeArcFlag, this._sweepFlag); }
3613         Object.defineProperty(window.SVGPathSegArcRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3614         Object.defineProperty(window.SVGPathSegArcRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3615         Object.defineProperty(window.SVGPathSegArcRel.prototype, 'r1', { get: function() { return this._r1; }, set: function(r1) { this._r1 = r1; this._segmentChanged(); }, enumerable: true });
3616         Object.defineProperty(window.SVGPathSegArcRel.prototype, 'r2', { get: function() { return this._r2; }, set: function(r2) { this._r2 = r2; this._segmentChanged(); }, enumerable: true });
3617         Object.defineProperty(window.SVGPathSegArcRel.prototype, 'angle', { get: function() { return this._angle; }, set: function(angle) { this._angle = angle; this._segmentChanged(); }, enumerable: true });
3618         Object.defineProperty(window.SVGPathSegArcRel.prototype, 'largeArcFlag', { get: function() { return this._largeArcFlag; }, set: function(largeArcFlag) { this._largeArcFlag = largeArcFlag; this._segmentChanged(); }, enumerable: true });
3619         Object.defineProperty(window.SVGPathSegArcRel.prototype, 'sweepFlag', { get: function() { return this._sweepFlag; }, set: function(sweepFlag) { this._sweepFlag = sweepFlag; this._segmentChanged(); }, enumerable: true });
3621         window.SVGPathSegLinetoHorizontalAbs = function(owningPathSegList, x) {
3622             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS, 'H', owningPathSegList);
3623             this._x = x;
3624         }
3625         window.SVGPathSegLinetoHorizontalAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3626         window.SVGPathSegLinetoHorizontalAbs.prototype.toString = function() { return '[object SVGPathSegLinetoHorizontalAbs]'; }
3627         window.SVGPathSegLinetoHorizontalAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x; }
3628         window.SVGPathSegLinetoHorizontalAbs.prototype.clone = function() { return new window.SVGPathSegLinetoHorizontalAbs(undefined, this._x); }
3629         Object.defineProperty(window.SVGPathSegLinetoHorizontalAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3631         window.SVGPathSegLinetoHorizontalRel = function(owningPathSegList, x) {
3632             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL, 'h', owningPathSegList);
3633             this._x = x;
3634         }
3635         window.SVGPathSegLinetoHorizontalRel.prototype = Object.create(window.SVGPathSeg.prototype);
3636         window.SVGPathSegLinetoHorizontalRel.prototype.toString = function() { return '[object SVGPathSegLinetoHorizontalRel]'; }
3637         window.SVGPathSegLinetoHorizontalRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x; }
3638         window.SVGPathSegLinetoHorizontalRel.prototype.clone = function() { return new window.SVGPathSegLinetoHorizontalRel(undefined, this._x); }
3639         Object.defineProperty(window.SVGPathSegLinetoHorizontalRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3641         window.SVGPathSegLinetoVerticalAbs = function(owningPathSegList, y) {
3642             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS, 'V', owningPathSegList);
3643             this._y = y;
3644         }
3645         window.SVGPathSegLinetoVerticalAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3646         window.SVGPathSegLinetoVerticalAbs.prototype.toString = function() { return '[object SVGPathSegLinetoVerticalAbs]'; }
3647         window.SVGPathSegLinetoVerticalAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._y; }
3648         window.SVGPathSegLinetoVerticalAbs.prototype.clone = function() { return new window.SVGPathSegLinetoVerticalAbs(undefined, this._y); }
3649         Object.defineProperty(window.SVGPathSegLinetoVerticalAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3651         window.SVGPathSegLinetoVerticalRel = function(owningPathSegList, y) {
3652             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL, 'v', owningPathSegList);
3653             this._y = y;
3654         }
3655         window.SVGPathSegLinetoVerticalRel.prototype = Object.create(window.SVGPathSeg.prototype);
3656         window.SVGPathSegLinetoVerticalRel.prototype.toString = function() { return '[object SVGPathSegLinetoVerticalRel]'; }
3657         window.SVGPathSegLinetoVerticalRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._y; }
3658         window.SVGPathSegLinetoVerticalRel.prototype.clone = function() { return new window.SVGPathSegLinetoVerticalRel(undefined, this._y); }
3659         Object.defineProperty(window.SVGPathSegLinetoVerticalRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3661         window.SVGPathSegCurvetoCubicSmoothAbs = function(owningPathSegList, x, y, x2, y2) {
3662             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS, 'S', owningPathSegList);
3663             this._x = x;
3664             this._y = y;
3665             this._x2 = x2;
3666             this._y2 = y2;
3667         }
3668         window.SVGPathSegCurvetoCubicSmoothAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3669         window.SVGPathSegCurvetoCubicSmoothAbs.prototype.toString = function() { return '[object SVGPathSegCurvetoCubicSmoothAbs]'; }
3670         window.SVGPathSegCurvetoCubicSmoothAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
3671         window.SVGPathSegCurvetoCubicSmoothAbs.prototype.clone = function() { return new window.SVGPathSegCurvetoCubicSmoothAbs(undefined, this._x, this._y, this._x2, this._y2); }
3672         Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3673         Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3674         Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype, 'x2', { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
3675         Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype, 'y2', { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
3677         window.SVGPathSegCurvetoCubicSmoothRel = function(owningPathSegList, x, y, x2, y2) {
3678             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL, 's', owningPathSegList);
3679             this._x = x;
3680             this._y = y;
3681             this._x2 = x2;
3682             this._y2 = y2;
3683         }
3684         window.SVGPathSegCurvetoCubicSmoothRel.prototype = Object.create(window.SVGPathSeg.prototype);
3685         window.SVGPathSegCurvetoCubicSmoothRel.prototype.toString = function() { return '[object SVGPathSegCurvetoCubicSmoothRel]'; }
3686         window.SVGPathSegCurvetoCubicSmoothRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
3687         window.SVGPathSegCurvetoCubicSmoothRel.prototype.clone = function() { return new window.SVGPathSegCurvetoCubicSmoothRel(undefined, this._x, this._y, this._x2, this._y2); }
3688         Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3689         Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3690         Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype, 'x2', { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
3691         Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype, 'y2', { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
3693         window.SVGPathSegCurvetoQuadraticSmoothAbs = function(owningPathSegList, x, y) {
3694             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS, 'T', owningPathSegList);
3695             this._x = x;
3696             this._y = y;
3697         }
3698         window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype = Object.create(window.SVGPathSeg.prototype);
3699         window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype.toString = function() { return '[object SVGPathSegCurvetoQuadraticSmoothAbs]'; }
3700         window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
3701         window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype.clone = function() { return new window.SVGPathSegCurvetoQuadraticSmoothAbs(undefined, this._x, this._y); }
3702         Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3703         Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3705         window.SVGPathSegCurvetoQuadraticSmoothRel = function(owningPathSegList, x, y) {
3706             window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, 't', owningPathSegList);
3707             this._x = x;
3708             this._y = y;
3709         }
3710         window.SVGPathSegCurvetoQuadraticSmoothRel.prototype = Object.create(window.SVGPathSeg.prototype);
3711         window.SVGPathSegCurvetoQuadraticSmoothRel.prototype.toString = function() { return '[object SVGPathSegCurvetoQuadraticSmoothRel]'; }
3712         window.SVGPathSegCurvetoQuadraticSmoothRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
3713         window.SVGPathSegCurvetoQuadraticSmoothRel.prototype.clone = function() { return new window.SVGPathSegCurvetoQuadraticSmoothRel(undefined, this._x, this._y); }
3714         Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
3715         Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
3717         // Add createSVGPathSeg* functions to window.SVGPathElement.
3718         // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-Interfacewindow.SVGPathElement.
3719         window.SVGPathElement.prototype.createSVGPathSegClosePath = function() { return new window.SVGPathSegClosePath(undefined); }
3720         window.SVGPathElement.prototype.createSVGPathSegMovetoAbs = function(x, y) { return new window.SVGPathSegMovetoAbs(undefined, x, y); }
3721         window.SVGPathElement.prototype.createSVGPathSegMovetoRel = function(x, y) { return new window.SVGPathSegMovetoRel(undefined, x, y); }
3722         window.SVGPathElement.prototype.createSVGPathSegLinetoAbs = function(x, y) { return new window.SVGPathSegLinetoAbs(undefined, x, y); }
3723         window.SVGPathElement.prototype.createSVGPathSegLinetoRel = function(x, y) { return new window.SVGPathSegLinetoRel(undefined, x, y); }
3724         window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicAbs = function(x, y, x1, y1, x2, y2) { return new window.SVGPathSegCurvetoCubicAbs(undefined, x, y, x1, y1, x2, y2); }
3725         window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicRel = function(x, y, x1, y1, x2, y2) { return new window.SVGPathSegCurvetoCubicRel(undefined, x, y, x1, y1, x2, y2); }
3726         window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticAbs = function(x, y, x1, y1) { return new window.SVGPathSegCurvetoQuadraticAbs(undefined, x, y, x1, y1); }
3727         window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticRel = function(x, y, x1, y1) { return new window.SVGPathSegCurvetoQuadraticRel(undefined, x, y, x1, y1); }
3728         window.SVGPathElement.prototype.createSVGPathSegArcAbs = function(x, y, r1, r2, angle, largeArcFlag, sweepFlag) { return new window.SVGPathSegArcAbs(undefined, x, y, r1, r2, angle, largeArcFlag, sweepFlag); }
3729         window.SVGPathElement.prototype.createSVGPathSegArcRel = function(x, y, r1, r2, angle, largeArcFlag, sweepFlag) { return new window.SVGPathSegArcRel(undefined, x, y, r1, r2, angle, largeArcFlag, sweepFlag); }
3730         window.SVGPathElement.prototype.createSVGPathSegLinetoHorizontalAbs = function(x) { return new window.SVGPathSegLinetoHorizontalAbs(undefined, x); }
3731         window.SVGPathElement.prototype.createSVGPathSegLinetoHorizontalRel = function(x) { return new window.SVGPathSegLinetoHorizontalRel(undefined, x); }
3732         window.SVGPathElement.prototype.createSVGPathSegLinetoVerticalAbs = function(y) { return new window.SVGPathSegLinetoVerticalAbs(undefined, y); }
3733         window.SVGPathElement.prototype.createSVGPathSegLinetoVerticalRel = function(y) { return new window.SVGPathSegLinetoVerticalRel(undefined, y); }
3734         window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothAbs = function(x, y, x2, y2) { return new window.SVGPathSegCurvetoCubicSmoothAbs(undefined, x, y, x2, y2); }
3735         window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothRel = function(x, y, x2, y2) { return new window.SVGPathSegCurvetoCubicSmoothRel(undefined, x, y, x2, y2); }
3736         window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothAbs = function(x, y) { return new window.SVGPathSegCurvetoQuadraticSmoothAbs(undefined, x, y); }
3737         window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothRel = function(x, y) { return new window.SVGPathSegCurvetoQuadraticSmoothRel(undefined, x, y); }
3739         if (!('getPathSegAtLength' in window.SVGPathElement.prototype)) {
3740             // Add getPathSegAtLength to SVGPathElement.
3741             // Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-__svg__SVGPathElement__getPathSegAtLength
3742             // This polyfill requires SVGPathElement.getTotalLength to implement the distance-along-a-path algorithm.
3743             window.SVGPathElement.prototype.getPathSegAtLength = function(distance) {
3744                 if (distance === undefined || !isFinite(distance))
3745                     throw 'Invalid arguments.';
3747                 var measurementElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
3748                 measurementElement.setAttribute('d', this.getAttribute('d'));
3749                 var lastPathSegment = measurementElement.pathSegList.numberOfItems - 1;
3751                 // If the path is empty, return 0.
3752                 if (lastPathSegment <= 0)
3753                     return 0;
3755                 do {
3756                     measurementElement.pathSegList.removeItem(lastPathSegment);
3757                     if (distance > measurementElement.getTotalLength())
3758                         break;
3759                     lastPathSegment--;
3760                 } while (lastPathSegment > 0);
3761                 return lastPathSegment;
3762             }
3763         }
3764     }
3766     // Checking for SVGPathSegList in window checks for the case of an implementation without the
3767     // SVGPathSegList API.
3768     // The second check for appendItem is specific to Firefox 59+ which removed only parts of the
3769     // SVGPathSegList API (e.g., appendItem). In this case we need to re-implement the entire API
3770     // so the polyfill data (i.e., _list) is used throughout.
3771     if (!('SVGPathSegList' in window) || !('appendItem' in window.SVGPathSegList.prototype)) {
3772         // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSegList
3773         window.SVGPathSegList = function(pathElement) {
3774             this._pathElement = pathElement;
3775             this._list = this._parsePath(this._pathElement.getAttribute('d'));
3777             // Use a MutationObserver to catch changes to the path's 'd' attribute.
3778             this._mutationObserverConfig = { 'attributes': true, 'attributeFilter': ['d'] };
3779             this._pathElementMutationObserver = new MutationObserver(this._updateListFromPathMutations.bind(this));
3780             this._pathElementMutationObserver.observe(this._pathElement, this._mutationObserverConfig);
3781         }
3783         window.SVGPathSegList.prototype.classname = 'SVGPathSegList';
3785         Object.defineProperty(window.SVGPathSegList.prototype, 'numberOfItems', {
3786             get: function() {
3787                 this._checkPathSynchronizedToList();
3788                 return this._list.length;
3789             },
3790             enumerable: true
3791         });
3793         // The length property was not specified but was in Firefox 58.
3794         Object.defineProperty(window.SVGPathSegList.prototype, 'length', {
3795             get: function() {
3796                 this._checkPathSynchronizedToList();
3797                 return this._list.length;
3798             },
3799             enumerable: true
3800         });
3802         // Add the pathSegList accessors to window.SVGPathElement.
3803         // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGAnimatedPathData
3804         Object.defineProperty(window.SVGPathElement.prototype, 'pathSegList', {
3805             get: function() {
3806                 if (!this._pathSegList)
3807                     this._pathSegList = new window.SVGPathSegList(this);
3808                 return this._pathSegList;
3809             },
3810             enumerable: true
3811         });
3812         // FIXME: The following are not implemented and simply return window.SVGPathElement.pathSegList.
3813         Object.defineProperty(window.SVGPathElement.prototype, 'normalizedPathSegList', { get: function() { return this.pathSegList; }, enumerable: true });
3814         Object.defineProperty(window.SVGPathElement.prototype, 'animatedPathSegList', { get: function() { return this.pathSegList; }, enumerable: true });
3815         Object.defineProperty(window.SVGPathElement.prototype, 'animatedNormalizedPathSegList', { get: function() { return this.pathSegList; }, enumerable: true });
3817         // Process any pending mutations to the path element and update the list as needed.
3818         // This should be the first call of all public functions and is needed because
3819         // MutationObservers are not synchronous so we can have pending asynchronous mutations.
3820         window.SVGPathSegList.prototype._checkPathSynchronizedToList = function() {
3821             this._updateListFromPathMutations(this._pathElementMutationObserver.takeRecords());
3822         }
3824         window.SVGPathSegList.prototype._updateListFromPathMutations = function(mutationRecords) {
3825             if (!this._pathElement)
3826                 return;
3827             var hasPathMutations = false;
3828             mutationRecords.forEach(function(record) {
3829                 if (record.attributeName == 'd')
3830                     hasPathMutations = true;
3831             });
3832             if (hasPathMutations)
3833                 this._list = this._parsePath(this._pathElement.getAttribute('d'));
3834         }
3836         // Serialize the list and update the path's 'd' attribute.
3837         window.SVGPathSegList.prototype._writeListToPath = function() {
3838             this._pathElementMutationObserver.disconnect();
3839             this._pathElement.setAttribute('d', window.SVGPathSegList._pathSegArrayAsString(this._list));
3840             this._pathElementMutationObserver.observe(this._pathElement, this._mutationObserverConfig);
3841         }
3843         // When a path segment changes the list needs to be synchronized back to the path element.
3844         window.SVGPathSegList.prototype.segmentChanged = function(pathSeg) {
3845             this._writeListToPath();
3846         }
3848         window.SVGPathSegList.prototype.clear = function() {
3849             this._checkPathSynchronizedToList();
3851             this._list.forEach(function(pathSeg) {
3852                 pathSeg._owningPathSegList = null;
3853             });
3854             this._list = [];
3855             this._writeListToPath();
3856         }
3858         window.SVGPathSegList.prototype.initialize = function(newItem) {
3859             this._checkPathSynchronizedToList();
3861             this._list = [newItem];
3862             newItem._owningPathSegList = this;
3863             this._writeListToPath();
3864             return newItem;
3865         }
3867         window.SVGPathSegList.prototype._checkValidIndex = function(index) {
3868             if (isNaN(index) || index < 0 || index >= this.numberOfItems)
3869                 throw 'INDEX_SIZE_ERR';
3870         }
3872         window.SVGPathSegList.prototype.getItem = function(index) {
3873             this._checkPathSynchronizedToList();
3875             this._checkValidIndex(index);
3876             return this._list[index];
3877         }
3879         window.SVGPathSegList.prototype.insertItemBefore = function(newItem, index) {
3880             this._checkPathSynchronizedToList();
3882             // Spec: If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list.
3883             if (index > this.numberOfItems)
3884                 index = this.numberOfItems;
3885             if (newItem._owningPathSegList) {
3886                 // SVG2 spec says to make a copy.
3887                 newItem = newItem.clone();
3888             }
3889             this._list.splice(index, 0, newItem);
3890             newItem._owningPathSegList = this;
3891             this._writeListToPath();
3892             return newItem;
3893         }
3895         window.SVGPathSegList.prototype.replaceItem = function(newItem, index) {
3896             this._checkPathSynchronizedToList();
3898             if (newItem._owningPathSegList) {
3899                 // SVG2 spec says to make a copy.
3900                 newItem = newItem.clone();
3901             }
3902             this._checkValidIndex(index);
3903             this._list[index] = newItem;
3904             newItem._owningPathSegList = this;
3905             this._writeListToPath();
3906             return newItem;
3907         }
3909         window.SVGPathSegList.prototype.removeItem = function(index) {
3910             this._checkPathSynchronizedToList();
3912             this._checkValidIndex(index);
3913             var item = this._list[index];
3914             this._list.splice(index, 1);
3915             this._writeListToPath();
3916             return item;
3917         }
3919         window.SVGPathSegList.prototype.appendItem = function(newItem) {
3920             this._checkPathSynchronizedToList();
3922             if (newItem._owningPathSegList) {
3923                 // SVG2 spec says to make a copy.
3924                 newItem = newItem.clone();
3925             }
3926             this._list.push(newItem);
3927             newItem._owningPathSegList = this;
3928             // TODO: Optimize this to just append to the existing attribute.
3929             this._writeListToPath();
3930             return newItem;
3931         };
3933         window.SVGPathSegList.prototype.matrixTransform = function(aSVGMatrix) {
3934             this._checkPathSynchronizedToList();
3936             var nLength = this._list.length;
3937             for( var i = 0; i < nLength; ++i )
3938             {
3939                 var nX;
3940                 var aPathSeg = this._list[i];
3941                 switch( aPathSeg.pathSegTypeAsLetter )
3942                 {
3943                     case 'C':
3944                         nX = aPathSeg._x2;
3945                         aPathSeg._x2 = aSVGMatrix.a * nX + aSVGMatrix.c * aPathSeg._y2 + aSVGMatrix.e;
3946                         aPathSeg._y2 = aSVGMatrix.b * nX + aSVGMatrix.d * aPathSeg._y2 + aSVGMatrix.f;
3947                     // fall through intended
3948                     case 'Q':
3949                         nX = aPathSeg._x1;
3950                         aPathSeg._x1 = aSVGMatrix.a * nX + aSVGMatrix.c * aPathSeg._y1 + aSVGMatrix.e;
3951                         aPathSeg._y1 = aSVGMatrix.b * nX + aSVGMatrix.d * aPathSeg._y1 + aSVGMatrix.f;
3952                     // fall through intended
3953                     case 'M':
3954                     case 'L':
3955                         nX = aPathSeg._x;
3956                         aPathSeg._x = aSVGMatrix.a * nX + aSVGMatrix.c * aPathSeg._y + aSVGMatrix.e;
3957                         aPathSeg._y = aSVGMatrix.b * nX + aSVGMatrix.d * aPathSeg._y + aSVGMatrix.f;
3958                         break;
3959                     default:
3960                         log( 'SVGPathSeg.matrixTransform: unexpected path segment type: '
3961                             + aPathSeg.pathSegTypeAsLetter );
3962                 }
3963             }
3965             this._writeListToPath();
3966         };
3968         window.SVGPathSegList.prototype.changeOrientation = function() {
3969             this._checkPathSynchronizedToList();
3971             var aPathSegList = this._list;
3972             var nLength = aPathSegList.length;
3973             if( nLength == 0 ) return;
3975             var nCurrentX = 0;
3976             var nCurrentY = 0;
3978             var aPathSeg = aPathSegList[0];
3979             if( aPathSeg.pathSegTypeAsLetter == 'M' )
3980             {
3981                 nCurrentX = aPathSeg.x;
3982                 nCurrentY = aPathSeg.y;
3983                 aPathSegList.shift();
3984                 --nLength;
3985             }
3987             var i;
3988             for( i = 0; i < nLength; ++i )
3989             {
3990                 aPathSeg = aPathSegList[i];
3991                 switch( aPathSeg.pathSegTypeAsLetter )
3992                 {
3993                     case 'C':
3994                         var nX = aPathSeg._x1;
3995                         aPathSeg._x1 = aPathSeg._x2;
3996                         aPathSeg._x2 = nX;
3997                         var nY = aPathSeg._y1;
3998                         aPathSeg._y1 = aPathSeg._y2;
3999                         aPathSeg._y2 = nY;
4000                     // fall through intended
4001                     case 'M':
4002                     case 'L':
4003                     case 'Q':
4004                         var aPoint = { x: aPathSeg._x, y: aPathSeg._y };
4005                         aPathSeg._x = nCurrentX;
4006                         aPathSeg._y = nCurrentY;
4007                         nCurrentX = aPoint.x;
4008                         nCurrentY = aPoint.y;
4009                         break;
4010                     default:
4011                         log( 'SVGPathSegList.changeOrientation: unexpected path segment type: '
4012                             + aPathSeg.pathSegTypeAsLetter );
4013                 }
4015         }
4017             aPathSegList.reverse();
4019             var aMovePathSeg = new window.SVGPathSegMovetoAbs( this, nCurrentX, nCurrentY );
4020             aPathSegList.unshift( aMovePathSeg );
4022             this._writeListToPath();
4023         };
4025         window.SVGPathSegList._pathSegArrayAsString = function(pathSegArray) {
4026             var string = '';
4027             var first = true;
4028             pathSegArray.forEach(function(pathSeg) {
4029                 if (first) {
4030                     first = false;
4031                     string += pathSeg._asPathString();
4032                 } else {
4033                     string += ' ' + pathSeg._asPathString();
4034                 }
4035             });
4036             return string;
4037         }
4039         // This closely follows SVGPathParser::parsePath from Source/core/svg/SVGPathParser.cpp.
4040         window.SVGPathSegList.prototype._parsePath = function(string) {
4041             if (!string || string.length == 0)
4042                 return [];
4044             var owningPathSegList = this;
4046             var Builder = function() {
4047                 this.pathSegList = [];
4048             }
4050             Builder.prototype.appendSegment = function(pathSeg) {
4051                 this.pathSegList.push(pathSeg);
4052             }
4054             var Source = function(string) {
4055                 this._string = string;
4056                 this._currentIndex = 0;
4057                 this._endIndex = this._string.length;
4058                 this._previousCommand = window.SVGPathSeg.PATHSEG_UNKNOWN;
4060                 this._skipOptionalSpaces();
4061             }
4063             Source.prototype._isCurrentSpace = function() {
4064                 var character = this._string[this._currentIndex];
4065                 return character <= ' ' && (character == ' ' || character == '\n' || character == '\t' || character == '\r' || character == '\f');
4066             }
4068             Source.prototype._skipOptionalSpaces = function() {
4069                 while (this._currentIndex < this._endIndex && this._isCurrentSpace())
4070                     this._currentIndex++;
4071                 return this._currentIndex < this._endIndex;
4072             }
4074             Source.prototype._skipOptionalSpacesOrDelimiter = function() {
4075                 if (this._currentIndex < this._endIndex && !this._isCurrentSpace() && this._string.charAt(this._currentIndex) != ',')
4076                     return false;
4077                 if (this._skipOptionalSpaces()) {
4078                     if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == ',') {
4079                         this._currentIndex++;
4080                         this._skipOptionalSpaces();
4081                     }
4082                 }
4083                 return this._currentIndex < this._endIndex;
4084             }
4086             Source.prototype.hasMoreData = function() {
4087                 return this._currentIndex < this._endIndex;
4088             }
4090             Source.prototype.peekSegmentType = function() {
4091                 var lookahead = this._string[this._currentIndex];
4092                 return this._pathSegTypeFromChar(lookahead);
4093             }
4095             Source.prototype._pathSegTypeFromChar = function(lookahead) {
4096                 switch (lookahead) {
4097                     case 'Z':
4098                     case 'z':
4099                         return window.SVGPathSeg.PATHSEG_CLOSEPATH;
4100                     case 'M':
4101                         return window.SVGPathSeg.PATHSEG_MOVETO_ABS;
4102                     case 'm':
4103                         return window.SVGPathSeg.PATHSEG_MOVETO_REL;
4104                     case 'L':
4105                         return window.SVGPathSeg.PATHSEG_LINETO_ABS;
4106                     case 'l':
4107                         return window.SVGPathSeg.PATHSEG_LINETO_REL;
4108                     case 'C':
4109                         return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS;
4110                     case 'c':
4111                         return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL;
4112                     case 'Q':
4113                         return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS;
4114                     case 'q':
4115                         return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL;
4116                     case 'A':
4117                         return window.SVGPathSeg.PATHSEG_ARC_ABS;
4118                     case 'a':
4119                         return window.SVGPathSeg.PATHSEG_ARC_REL;
4120                     case 'H':
4121                         return window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS;
4122                     case 'h':
4123                         return window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL;
4124                     case 'V':
4125                         return window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS;
4126                     case 'v':
4127                         return window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL;
4128                     case 'S':
4129                         return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS;
4130                     case 's':
4131                         return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL;
4132                     case 'T':
4133                         return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS;
4134                     case 't':
4135                         return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL;
4136                     default:
4137                         return window.SVGPathSeg.PATHSEG_UNKNOWN;
4138                 }
4139             }
4141             Source.prototype._nextCommandHelper = function(lookahead, previousCommand) {
4142                 // Check for remaining coordinates in the current command.
4143                 if ((lookahead == '+' || lookahead == '-' || lookahead == '.' || (lookahead >= '0' && lookahead <= '9')) && previousCommand != window.SVGPathSeg.PATHSEG_CLOSEPATH) {
4144                     if (previousCommand == window.SVGPathSeg.PATHSEG_MOVETO_ABS)
4145                         return window.SVGPathSeg.PATHSEG_LINETO_ABS;
4146                     if (previousCommand == window.SVGPathSeg.PATHSEG_MOVETO_REL)
4147                         return window.SVGPathSeg.PATHSEG_LINETO_REL;
4148                     return previousCommand;
4149                 }
4150                 return window.SVGPathSeg.PATHSEG_UNKNOWN;
4151             }
4153             Source.prototype.initialCommandIsMoveTo = function() {
4154                 // If the path is empty it is still valid, so return true.
4155                 if (!this.hasMoreData())
4156                     return true;
4157                 var command = this.peekSegmentType();
4158                 // Path must start with moveTo.
4159                 return command == window.SVGPathSeg.PATHSEG_MOVETO_ABS || command == window.SVGPathSeg.PATHSEG_MOVETO_REL;
4160             }
4162             // Parse a number from an SVG path. This very closely follows genericParseNumber(...) from Source/core/svg/SVGParserUtilities.cpp.
4163             // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-PathDataBNF
4164             Source.prototype._parseNumber = function() {
4165                 var exponent = 0;
4166                 var integer = 0;
4167                 var frac = 1;
4168                 var decimal = 0;
4169                 var sign = 1;
4170                 var expsign = 1;
4172                 var startIndex = this._currentIndex;
4174                 this._skipOptionalSpaces();
4176                 // Read the sign.
4177                 if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == '+')
4178                     this._currentIndex++;
4179                 else if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == '-') {
4180                     this._currentIndex++;
4181                     sign = -1;
4182                 }
4184                 if (this._currentIndex == this._endIndex || ((this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9') && this._string.charAt(this._currentIndex) != '.'))
4185                 // The first character of a number must be one of [0-9+-.].
4186                     return undefined;
4188                 // Read the integer part, build right-to-left.
4189                 var startIntPartIndex = this._currentIndex;
4190                 while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9')
4191                     this._currentIndex++; // Advance to first non-digit.
4193                 if (this._currentIndex != startIntPartIndex) {
4194                     var scanIntPartIndex = this._currentIndex - 1;
4195                     var multiplier = 1;
4196                     while (scanIntPartIndex >= startIntPartIndex) {
4197                         integer += multiplier * (this._string.charAt(scanIntPartIndex--) - '0');
4198                         multiplier *= 10;
4199                     }
4200                 }
4202                 // Read the decimals.
4203                 if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == '.') {
4204                     this._currentIndex++;
4206                     // There must be a least one digit following the .
4207                     if (this._currentIndex >= this._endIndex || this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9')
4208                         return undefined;
4209                     while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
4210                         frac *= 10;
4211                         decimal += (this._string.charAt(this._currentIndex) - '0') / frac;
4212                         this._currentIndex += 1;
4213                     }
4214                 }
4216                 // Read the exponent part.
4217                 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')) {
4218                     this._currentIndex++;
4220                     // Read the sign of the exponent.
4221                     if (this._string.charAt(this._currentIndex) == '+') {
4222                         this._currentIndex++;
4223                     } else if (this._string.charAt(this._currentIndex) == '-') {
4224                         this._currentIndex++;
4225                         expsign = -1;
4226                     }
4228                     // There must be an exponent.
4229                     if (this._currentIndex >= this._endIndex || this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9')
4230                         return undefined;
4232                     while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
4233                         exponent *= 10;
4234                         exponent += (this._string.charAt(this._currentIndex) - '0');
4235                         this._currentIndex++;
4236                     }
4237                 }
4239                 var number = integer + decimal;
4240                 number *= sign;
4242                 if (exponent)
4243                     number *= Math.pow(10, expsign * exponent);
4245                 if (startIndex == this._currentIndex)
4246                     return undefined;
4248                 this._skipOptionalSpacesOrDelimiter();
4250                 return number;
4251             }
4253             Source.prototype._parseArcFlag = function() {
4254                 if (this._currentIndex >= this._endIndex)
4255                     return undefined;
4256                 var flag = false;
4257                 var flagChar = this._string.charAt(this._currentIndex++);
4258                 if (flagChar == '0')
4259                     flag = false;
4260                 else if (flagChar == '1')
4261                     flag = true;
4262                 else
4263                     return undefined;
4265                 this._skipOptionalSpacesOrDelimiter();
4266                 return flag;
4267             }
4269             Source.prototype.parseSegment = function() {
4270                 var lookahead = this._string[this._currentIndex];
4271                 var command = this._pathSegTypeFromChar(lookahead);
4272                 if (command == window.SVGPathSeg.PATHSEG_UNKNOWN) {
4273                     // Possibly an implicit command. Not allowed if this is the first command.
4274                     if (this._previousCommand == window.SVGPathSeg.PATHSEG_UNKNOWN)
4275                         return null;
4276                     command = this._nextCommandHelper(lookahead, this._previousCommand);
4277                     if (command == window.SVGPathSeg.PATHSEG_UNKNOWN)
4278                         return null;
4279                 } else {
4280                     this._currentIndex++;
4281                 }
4283                 this._previousCommand = command;
4285                 switch (command) {
4286                     case window.SVGPathSeg.PATHSEG_MOVETO_REL:
4287                         return new window.SVGPathSegMovetoRel(owningPathSegList, this._parseNumber(), this._parseNumber());
4288                     case window.SVGPathSeg.PATHSEG_MOVETO_ABS:
4289                         return new window.SVGPathSegMovetoAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
4290                     case window.SVGPathSeg.PATHSEG_LINETO_REL:
4291                         return new window.SVGPathSegLinetoRel(owningPathSegList, this._parseNumber(), this._parseNumber());
4292                     case window.SVGPathSeg.PATHSEG_LINETO_ABS:
4293                         return new window.SVGPathSegLinetoAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
4294                     case window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL:
4295                         return new window.SVGPathSegLinetoHorizontalRel(owningPathSegList, this._parseNumber());
4296                     case window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS:
4297                         return new window.SVGPathSegLinetoHorizontalAbs(owningPathSegList, this._parseNumber());
4298                     case window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL:
4299                         return new window.SVGPathSegLinetoVerticalRel(owningPathSegList, this._parseNumber());
4300                     case window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS:
4301                         return new window.SVGPathSegLinetoVerticalAbs(owningPathSegList, this._parseNumber());
4302                     case window.SVGPathSeg.PATHSEG_CLOSEPATH:
4303                         this._skipOptionalSpaces();
4304                         return new window.SVGPathSegClosePath(owningPathSegList);
4305                     case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL:
4306                         var points = {x1: this._parseNumber(), y1: this._parseNumber(), x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
4307                         return new window.SVGPathSegCurvetoCubicRel(owningPathSegList, points.x, points.y, points.x1, points.y1, points.x2, points.y2);
4308                     case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS:
4309                         var points = {x1: this._parseNumber(), y1: this._parseNumber(), x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
4310                         return new window.SVGPathSegCurvetoCubicAbs(owningPathSegList, points.x, points.y, points.x1, points.y1, points.x2, points.y2);
4311                     case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
4312                         var points = {x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
4313                         return new window.SVGPathSegCurvetoCubicSmoothRel(owningPathSegList, points.x, points.y, points.x2, points.y2);
4314                     case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
4315                         var points = {x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
4316                         return new window.SVGPathSegCurvetoCubicSmoothAbs(owningPathSegList, points.x, points.y, points.x2, points.y2);
4317                     case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL:
4318                         var points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
4319                         return new window.SVGPathSegCurvetoQuadraticRel(owningPathSegList, points.x, points.y, points.x1, points.y1);
4320                     case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS:
4321                         var points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
4322                         return new window.SVGPathSegCurvetoQuadraticAbs(owningPathSegList, points.x, points.y, points.x1, points.y1);
4323                     case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
4324                         return new window.SVGPathSegCurvetoQuadraticSmoothRel(owningPathSegList, this._parseNumber(), this._parseNumber());
4325                     case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
4326                         return new window.SVGPathSegCurvetoQuadraticSmoothAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
4327                     case window.SVGPathSeg.PATHSEG_ARC_REL:
4328                         var points = {x1: this._parseNumber(), y1: this._parseNumber(), arcAngle: this._parseNumber(), arcLarge: this._parseArcFlag(), arcSweep: this._parseArcFlag(), x: this._parseNumber(), y: this._parseNumber()};
4329                         return new window.SVGPathSegArcRel(owningPathSegList, points.x, points.y, points.x1, points.y1, points.arcAngle, points.arcLarge, points.arcSweep);
4330                     case window.SVGPathSeg.PATHSEG_ARC_ABS:
4331                         var points = {x1: this._parseNumber(), y1: this._parseNumber(), arcAngle: this._parseNumber(), arcLarge: this._parseArcFlag(), arcSweep: this._parseArcFlag(), x: this._parseNumber(), y: this._parseNumber()};
4332                         return new window.SVGPathSegArcAbs(owningPathSegList, points.x, points.y, points.x1, points.y1, points.arcAngle, points.arcLarge, points.arcSweep);
4333                     default:
4334                         throw 'Unknown path seg type.'
4335                 }
4336             }
4338             var builder = new Builder();
4339             var source = new Source(string);
4341             if (!source.initialCommandIsMoveTo())
4342                 return [];
4343             while (source.hasMoreData()) {
4344                 var pathSeg = source.parseSegment();
4345                 if (!pathSeg)
4346                     return [];
4347                 builder.appendSegment(pathSeg);
4348             }
4350             return builder.pathSegList;
4351         }
4352     }
4353 }());
4355 /*****
4356  * @svgpathend
4358  *  The above code is a derivative work of some part of the SVGPathSeg API.
4360  *  This API is a drop-in replacement for the SVGPathSeg and SVGPathSegList APIs that were removed from
4361  *  SVG2 (https://lists.w3.org/Archives/Public/www-svg/2015Jun/0044.html), including the latest spec
4362  *  changes which were implemented in Firefox 43 and Chrome 46.
4364  *  @source https://github.com/progers/pathseg
4365  */
4368 /*****
4369  * @licstart
4371  * The following is the license notice for the part of JavaScript code  of
4372  * this page included between the '@libreofficestart' and the '@libreofficeend'
4373  * notes.
4374  */
4376 /*****  ******************************************************************
4378  * This file is part of the LibreOffice project.
4380  * This Source Code Form is subject to the terms of the Mozilla Public
4381  * License, v. 2.0. If a copy of the MPL was not distributed with this
4382  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
4384  * This file incorporates work covered by the following license notice:
4386  *   Licensed to the Apache Software Foundation (ASF) under one or more
4387  *   contributor license agreements. See the NOTICE file distributed
4388  *   with this work for additional information regarding copyright
4389  *   ownership. The ASF licenses this file to you under the Apache
4390  *   License, Version 2.0 (the 'License'); you may not use this file
4391  *   except in compliance with the License. You may obtain a copy of
4392  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
4394  ************************************************************************/
4396 /*****
4397  * @licend
4399  * The above is the license notice for the part of JavaScript code  of
4400  * this page included between the '@libreofficestart' and the '@libreofficeend'
4401  * notes.
4402  */
4405 /*****
4406  * @libreofficestart
4408  * Several parts of the following code are the result of the porting,
4409  * started on August 2011, of the C++ code included in the source
4410  * files placed under the folder '/slideshow/source' and
4411  * sub-folders. This got later rebased onto the AL2-licensed versions
4412  * of those files in early 2013.
4413  * @source https://cgit.freedesktop.org/libreoffice/core/tree/slideshow/source
4415  */
4418 window.onload = init;
4421 // ooo elements
4422 var aOOOElemMetaSlides = 'ooo:meta_slides';
4423 var aOOOElemMetaSlide = 'ooo:meta_slide';
4424 var aOOOElemTextField = 'ooo:text_field';
4425 var aPresentationClipPathId = 'presentation_clip_path';
4426 var aPresentationClipPathShrinkId = 'presentation_clip_path_shrink';
4428 // ooo attributes
4429 var aOOOAttrNumberOfSlides = 'number-of-slides';
4430 var aOOOAttrStartSlideNumber= 'start-slide-number';
4431 var aOOOAttrNumberingType = 'page-numbering-type';
4432 var aOOOAttrListItemNumberingType= 'numbering-type';
4433 var aOOOAttrUsePositionedChars = 'use-positioned-chars';
4435 var aOOOAttrSlide = 'slide';
4436 var aOOOAttrMaster = 'master';
4437 var aOOOAttrSlideDuration = 'slide-duration';
4438 var aOOOAttrHasTransition = 'has-transition';
4439 var aOOOAttrBackgroundVisibility = 'background-visibility';
4440 var aOOOAttrMasterObjectsVisibility = 'master-objects-visibility';
4441 var aOOOAttrPageNumberVisibility = 'page-number-visibility';
4442 var aOOOAttrDateTimeVisibility = 'date-time-visibility';
4443 var aOOOAttrFooterVisibility = 'footer-visibility';
4444 var aOOOAttrHeaderVisibility = 'header-visibility';
4445 var aOOOAttrDateTimeField = 'date-time-field';
4446 var aOOOAttrFooterField = 'footer-field';
4447 var aOOOAttrHeaderField = 'header-field';
4449 var aOOOAttrDateTimeFormat = 'date-time-format';
4451 var aOOOAttrTextAdjust = 'text-adjust';
4453 // element class names
4454 var aClipPathGroupClassName = 'ClipPathGroup';
4455 var aPageClassName = 'Page';
4456 var aSlideNumberClassName = 'Slide_Number';
4457 var aDateTimeClassName = 'Date/Time';
4458 var aFooterClassName = 'Footer';
4459 var aHeaderClassName = 'Header';
4461 // Creating a namespace dictionary.
4462 var NSS = {};
4463 NSS['svg']='http://www.w3.org/2000/svg';
4464 NSS['rdf']='http://www.w3.org/1999/02/22-rdf-syntax-ns#';
4465 NSS['xlink']='http://www.w3.org/1999/xlink';
4466 NSS['xml']='http://www.w3.org/XML/1998/namespace';
4467 NSS['ooo'] = 'http://xml.openoffice.org/svg/export';
4468 NSS['presentation'] = 'http://sun.com/xmlns/staroffice/presentation';
4469 NSS['smil'] = 'http://www.w3.org/2001/SMIL20/';
4470 NSS['anim'] = 'urn:oasis:names:tc:opendocument:xmlns:animation:1.0';
4472 // Presentation modes.
4473 var SLIDE_MODE = 1;
4474 var INDEX_MODE = 2;
4476 // Mouse handler actions.
4477 var MOUSE_UP = 1;
4478 var MOUSE_DOWN = 2; // eslint-disable-line no-unused-vars
4479 var MOUSE_MOVE = 3; // eslint-disable-line no-unused-vars
4480 var MOUSE_WHEEL = 4;
4482 // Key-codes.
4483 var LEFT_KEY = 37;          // cursor left keycode
4484 var UP_KEY = 38;            // cursor up keycode
4485 var RIGHT_KEY = 39;         // cursor right keycode
4486 var DOWN_KEY = 40;          // cursor down keycode
4487 var PAGE_UP_KEY = 33;       // page up keycode
4488 var PAGE_DOWN_KEY = 34;     // page down keycode
4489 var HOME_KEY = 36;          // home keycode
4490 var END_KEY = 35;           // end keycode
4491 var ENTER_KEY = 13;
4492 var SPACE_KEY = 32;
4493 var ESCAPE_KEY = 27;
4494 var Q_KEY = 81;
4496 // Visibility Values
4497 var HIDDEN = 0;
4498 var VISIBLE = 1;
4499 var INHERIT = 2;
4500 var aVisibilityAttributeValue = [ 'hidden', 'visible', 'inherit' ];  // eslint-disable-line no-unused-vars
4501 var aVisibilityValue = { 'hidden' : HIDDEN, 'visible' : VISIBLE, 'inherit' : INHERIT };
4503 // Parameters
4504 var ROOT_NODE = document.getElementsByTagNameNS( NSS['svg'], 'svg' )[0];
4505 var WIDTH = 0;
4506 var HEIGHT = 0;
4507 var INDEX_COLUMNS_DEFAULT = 3;
4508 var INDEX_OFFSET = 0;
4510 // Initialization.
4511 var Detect = configureDetectionTools();
4512 var theMetaDoc;
4513 var theSlideIndexPage;
4514 var currentMode = SLIDE_MODE;
4515 var processingEffect = false;
4516 var nCurSlide = undefined;
4517 var bTextHasBeenSelected = false;
4518 var sLastSelectedText = '';
4521 // Initialize char and key code dictionaries.
4522 var charCodeDictionary = getDefaultCharCodeDictionary();
4523 var keyCodeDictionary = getDefaultKeyCodeDictionary();
4525 // Initialize mouse handler dictionary.
4526 var mouseHandlerDictionary = getDefaultMouseHandlerDictionary();
4528 /***************************
4529  ** OOP support functions **
4530  ***************************/
4532 function object( aObject )
4534     var F = function() {};
4535     F.prototype = aObject;
4536     return new F();
4540 function extend( aSubType, aSuperType )
4542     if (!aSuperType || !aSubType)
4543     {
4544         alert('extend failed, verify dependencies');
4545     }
4546     var OP = Object.prototype;
4547     var sp = aSuperType.prototype;
4548     var rp = object( sp );
4549     aSubType.prototype = rp;
4551     rp.constructor = aSubType;
4552     aSubType.superclass = sp;
4554     // assign constructor property
4555     if (aSuperType != Object && sp.constructor == OP.constructor)
4556     {
4557         sp.constructor = aSuperType;
4558     }
4560     return aSubType;
4564 function instantiate( TemplateClass, BaseType )
4566     if( !TemplateClass.instanceSet )
4567         TemplateClass.instanceSet = [];
4569     var nSize = TemplateClass.instanceSet.length;
4571     for( var i = 0; i < nSize; ++i )
4572     {
4573         if( TemplateClass.instanceSet[i].base === BaseType )
4574             return TemplateClass.instanceSet[i].instance;
4575     }
4577     TemplateClass.instanceSet[ nSize ] = {};
4578     TemplateClass.instanceSet[ nSize ].base = BaseType;
4579     TemplateClass.instanceSet[ nSize ].instance = TemplateClass( BaseType );
4581     return TemplateClass.instanceSet[ nSize ].instance;
4586 /**********************************
4587  ** Helper functions and classes **
4588  **********************************/
4590 function Rectangle( aSVGRectElem )
4592     var x = parseInt( aSVGRectElem.getAttribute( 'x' ) );
4593     var y = parseInt( aSVGRectElem.getAttribute( 'y' ) );
4594     var width = parseInt( aSVGRectElem.getAttribute( 'width' ) );
4595     var height = parseInt( aSVGRectElem.getAttribute( 'height' ) );
4597     this.left = x;
4598     this.right = x + width;
4599     this.top = y;
4600     this.bottom = y + height;
4604  * Returns key corresponding to a value in object, null otherwise.
4606  * @param Object
4607  * @param value
4608  */
4609 function getKeyByValue(aObj, value) {
4610   for(var key in aObj) {
4611     if(aObj[key] == value)
4612       return key;
4613   }
4614   return null;
4617 function log( message )
4619     if( typeof console == 'object' )
4620     {
4621         // eslint-disable-next-line no-console
4622         console.log( message );
4623     }
4624     else if( typeof opera == 'object' )
4625     {
4626         opera.postError( message );
4627     }
4628     // eslint-disable-next-line no-undef
4629     else if( typeof java == 'object' && typeof java.lang == 'object' )
4630     {
4631         // eslint-disable-next-line no-undef
4632         java.lang.System.out.println( message );
4633     }
4636 function getNSAttribute( sNSPrefix, aElem, sAttrName )
4638     if( !aElem ) return null;
4639     if( 'getAttributeNS' in aElem )
4640     {
4641         return aElem.getAttributeNS( NSS[sNSPrefix], sAttrName );
4642     }
4643     else
4644     {
4645         return aElem.getAttribute( sNSPrefix + ':' + sAttrName );
4646     }
4649 function getOOOAttribute( aElem, sAttrName )
4651     return getNSAttribute( 'ooo', aElem, sAttrName );
4654 function setNSAttribute( sNSPrefix, aElem, sAttrName, aValue )
4656     if( !aElem ) return false;
4657     if( 'setAttributeNS' in aElem )
4658     {
4659         aElem.setAttributeNS( NSS[sNSPrefix], sAttrName, aValue );
4660         return true;
4661     }
4662     else
4663     {
4664         aElem.setAttribute(sNSPrefix + ':' + sAttrName, aValue );
4665         return true;
4666     }
4669 function getElementsByClassName( aElem, sClassName )
4672     var aElementSet = [];
4673     // not all browsers support the 'getElementsByClassName' method
4674     if( 'getElementsByClassName' in aElem )
4675     {
4676         aElementSet = aElem.getElementsByClassName( sClassName );
4677     }
4678     else
4679     {
4680         var aElementSetByClassProperty = getElementsByProperty( aElem, 'class' );
4681         for( var i = 0; i < aElementSetByClassProperty.length; ++i )
4682         {
4683             var sAttrClassName = aElementSetByClassProperty[i].getAttribute( 'class' );
4684             if( sAttrClassName == sClassName )
4685             {
4686                 aElementSet.push( aElementSetByClassProperty[i] );
4687             }
4688         }
4689     }
4690     return aElementSet;
4693 function getElementByClassName( aElem, sClassName /*, sTagName */)
4695     var aElementSet = getElementsByClassName( aElem, sClassName );
4696     if ( aElementSet.length == 1 )
4697         return aElementSet[0];
4698     else
4699         return null;
4702 function getClassAttribute(  aElem )
4704     if( aElem )
4705         return aElem.getAttribute( 'class' );
4706     return '';
4709 function createElementGroup( aParentElement, aElementList, nFrom, nCount, sGroupClass, sGroupId )
4711     var nTo = nFrom + nCount;
4712     if( nCount < 1 || aElementList.length < nTo )
4713     {
4714         log( 'createElementGroup: not enough elements available.' );
4715         return;
4716     }
4717     var firstElement = aElementList[nFrom];
4718     if( !firstElement )
4719     {
4720         log( 'createElementGroup: element not found.' );
4721         return;
4722     }
4723     var aGroupElement = document.createElementNS( NSS['svg'], 'g' );
4724     if( sGroupId )
4725         aGroupElement.setAttribute( 'id', sGroupId );
4726     if( sGroupClass )
4727         aGroupElement.setAttribute( 'class', sGroupClass );
4728     aParentElement.insertBefore( aGroupElement, firstElement );
4729     var i = nFrom;
4730     for( ; i < nTo; ++i )
4731     {
4732         aParentElement.removeChild( aElementList[i] );
4733         aGroupElement.appendChild( aElementList[i] );
4734     }
4737 function initVisibilityProperty( aElement )
4739     var nVisibility = VISIBLE;
4740     var sVisibility = aElement.getAttribute( 'visibility' );
4741     if( sVisibility ) nVisibility = aVisibilityValue[ sVisibility ];
4742     return nVisibility;
4745 function getSafeIndex( nIndex, nMin, nMax )
4747     if( nIndex < nMin )
4748         return nMin;
4749     else if( nIndex > nMax )
4750         return nMax;
4751     else
4752         return nIndex;
4755 /** getRandomInt
4757  * @param nMax
4758  * @returns {number}
4759  *   an integer in [0,nMax[
4760  */
4761 function getRandomInt( nMax )
4763     return Math.floor( Math.random() * nMax );
4766 function isTextFieldElement( aElement ) // eslint-disable-line no-unused-vars
4768     var sClassName = aElement.getAttribute( 'class' );
4769     return ( sClassName === aSlideNumberClassName ) ||
4770            ( sClassName === aFooterClassName ) ||
4771            ( sClassName === aHeaderClassName ) ||
4772            ( sClassName === aDateTimeClassName );
4776 /*********************
4777  ** Debug Utilities **
4778  *********************/
4780 function DebugPrinter()
4782     this.bEnabled = false;
4786 DebugPrinter.prototype.on = function()
4788     this.bEnabled = true;
4791 DebugPrinter.prototype.off = function()
4793     this.bEnabled = false;
4796 DebugPrinter.prototype.isEnabled = function()
4798     return this.bEnabled;
4801 DebugPrinter.prototype.print = function( sMessage, nTime )
4803     if( this.isEnabled() )
4804     {
4805         var sInfo = 'DBG: ' + sMessage;
4806         if( nTime )
4807             sInfo += ' (at: ' + String( nTime / 1000 ) + 's)';
4808         log( sInfo );
4809     }
4813 // - Debug Printers -
4814 var aGenericDebugPrinter = new DebugPrinter();
4815 aGenericDebugPrinter.on();
4816 var DBGLOG = bind2( DebugPrinter.prototype.print, aGenericDebugPrinter );
4818 var NAVDBG = new DebugPrinter();
4819 NAVDBG.off();
4821 var ANIMDBG = new DebugPrinter();
4822 ANIMDBG.off();
4824 var aRegisterEventDebugPrinter = new DebugPrinter();
4825 aRegisterEventDebugPrinter.off();
4827 var aTimerEventQueueDebugPrinter = new DebugPrinter();
4828 aTimerEventQueueDebugPrinter.off();
4830 var aEventMultiplexerDebugPrinter = new DebugPrinter();
4831 aEventMultiplexerDebugPrinter.off();
4833 var aNextEffectEventArrayDebugPrinter = new DebugPrinter();
4834 aNextEffectEventArrayDebugPrinter.off();
4836 var aActivityQueueDebugPrinter = new DebugPrinter();
4837 aActivityQueueDebugPrinter.off();
4839 var aAnimatedElementDebugPrinter = new DebugPrinter();
4840 aAnimatedElementDebugPrinter.off();
4845 /************************
4846  ***   Core Classes   ***
4847  ************************/
4849 /** Class MetaDocument
4850  *  This class provides a pool of properties related to the whole presentation.
4851  *  Moreover it is responsible for:
4852  *  - initializing the set of MetaSlide objects that handle the meta information
4853  *    for each slide;
4854  *  - creating a map with key an id and value the svg element containing
4855  *    the animations performed on the slide with such an id.
4857  */
4858 function MetaDocument()
4860     // We look for the svg element that provides the following presentation
4861     // properties:
4862     // - the number of slides in the presentation;
4863     // - the type of numbering used in the presentation.
4864     // Moreover it wraps svg elements providing meta information on each slide
4865     // and svg elements providing content and properties of each text field.
4866     var aMetaDocElem = document.getElementById( aOOOElemMetaSlides );
4867     assert( aMetaDocElem, 'MetaDocument: the svg element with id:' + aOOOElemMetaSlides + 'is not valid.');
4869     // We initialize general presentation properties:
4870     // - the number of slides in the presentation;
4871     this.nNumberOfSlides = parseInt( aMetaDocElem.getAttributeNS( NSS['ooo'], aOOOAttrNumberOfSlides ) );
4872     assert( typeof this.nNumberOfSlides == 'number' && this.nNumberOfSlides > 0,
4873             'MetaDocument: number of slides is zero or undefined.' );
4874     // - the index of the slide to show when the presentation starts;
4875     this.nStartSlideNumber = parseInt( aMetaDocElem.getAttributeNS( NSS['ooo'], aOOOAttrStartSlideNumber ) ) || 0;
4876     // - the numbering type used in the presentation, default type is arabic.
4877     this.sPageNumberingType = aMetaDocElem.getAttributeNS( NSS['ooo'], aOOOAttrNumberingType ) || 'arabic';
4878     // - the way text is exported
4879     this.bIsUsePositionedChars = ( aMetaDocElem.getAttributeNS( NSS['ooo'], aOOOAttrUsePositionedChars ) === 'true' );
4881     // The <defs> element used for wrapping <clipPath>.
4882     this.aClipPathGroup = getElementByClassName( ROOT_NODE, aClipPathGroupClassName );
4883     assert( this.aClipPathGroup, 'MetaDocument: the clip path group element is not valid.');
4885     // The <clipPath> element used to clip all slides.
4886     this.aPresentationClipPath = document.getElementById( aPresentationClipPathId );
4887     assert( this.aPresentationClipPath,
4888             'MetaDocument: the presentation clip path element element is not valid.');
4890     // The collections for handling properties of each slide, svg elements
4891     // related to master pages and content and properties of text fields.
4892     this.aMetaSlideSet = [];
4893     this.aMasterPageSet = {};
4894     this.aTextFieldHandlerSet = {};
4895     this.aTextFieldContentProviderSet = [];
4896     this.aSlideNumberProvider = new SlideNumberProvider( this.nStartSlideNumber + 1, this.sPageNumberingType );
4898     // We create a map with key an id and value the svg element containing
4899     // the animations performed on the slide with such an id.
4900     this.bIsAnimated = false;
4901     this.aSlideAnimationsMap = {};
4902     this.initSlideAnimationsMap();
4904     // We initialize dummy slide - used as leaving slide for transition on the first slide
4905     this.theMetaDummySlide = new MetaSlide( 'ooo:meta_dummy_slide', this );
4907     // We initialize the set of MetaSlide objects that handle the meta
4908     // information for each slide.
4909     for( var i = 0; i < this.nNumberOfSlides; ++i )
4910     {
4911         var sMetaSlideId = aOOOElemMetaSlide + '_' + i;
4912         this.aMetaSlideSet.push( new MetaSlide( sMetaSlideId, this ) );
4913     }
4914     assert( this.aMetaSlideSet.length == this.nNumberOfSlides,
4915             'MetaDocument: aMetaSlideSet.length != nNumberOfSlides.' );
4918 MetaDocument.prototype =
4920 /*** public methods ***/
4922 /** getCurrentSlide
4924  *  @return
4925  *      The MetaSlide object handling the current slide.
4926  */
4927 getCurrentSlide : function()
4929     return this.aMetaSlideSet[nCurSlide];
4932 /** setCurrentSlide
4934  *  @param nSlideIndex
4935  *      The index of the slide to show.
4936  */
4937 setCurrentSlide : function( nSlideIndex )
4939     if( nSlideIndex >= 0 &&  nSlideIndex < this.nNumberOfSlides )
4940     {
4941         if( nCurSlide !== undefined )
4942             this.aMetaSlideSet[nCurSlide].hide();
4943         this.aMetaSlideSet[nSlideIndex].show();
4944         nCurSlide = nSlideIndex;
4945     }
4946     else
4947     {
4948         log('MetaDocument.setCurrentSlide: slide index out of range: ' + nSlideIndex );
4949     }
4952 /*** private methods ***/
4954 initSlideAnimationsMap : function()
4956     var aAnimationsSection = document.getElementById( 'presentation-animations' );
4957     if( aAnimationsSection )
4958     {
4959         var aAnimationsDefSet = aAnimationsSection.getElementsByTagName( 'defs' );
4961         // we have at least one slide with animations ?
4962         this.bIsAnimated = ( typeof aAnimationsDefSet.length =='number' &&
4963                              aAnimationsDefSet.length > 0 );
4965         for( var i = 0; i < aAnimationsDefSet.length; ++i )
4966         {
4967             var sSlideId = aAnimationsDefSet[i].getAttributeNS( NSS['ooo'], aOOOAttrSlide );
4968             var aChildSet = getElementChildren( aAnimationsDefSet[i] );
4969             if( sSlideId && ( aChildSet.length === 1 ) )
4970             {
4971                 this.aSlideAnimationsMap[ sSlideId ] = aChildSet[0];
4972             }
4973         }
4974     }
4977 }; // end MetaDocument prototype
4979 /** Class MetaSlide
4980  *  This class is responsible for:
4981  *  - parsing and initializing slide properties;
4982  *  - creating a MasterSlide object that provides direct access to the target
4983  *    master slide and its sub-elements;
4984  *  - initializing text field content providers;
4985  *  - initializing the slide animation handler.
4987  *  @param sMetaSlideId
4988  *      The string representing the id attribute of the meta-slide element.
4989  *  @param aMetaDoc
4990  *      The MetaDocument global object.
4991  */
4992 function MetaSlide( sMetaSlideId, aMetaDoc )
4994     this.theDocument = document;
4995     this.id = sMetaSlideId;
4996     this.theMetaDoc = aMetaDoc;
4998     // We get a reference to the meta-slide element.
4999     this.element = this.theDocument.getElementById( this.id );
5000     assert( this.element,
5001             'MetaSlide: meta_slide element <' + this.id + '> not found.' );
5003     // We get a reference to the slide element.
5004     this.slideId = this.element.getAttributeNS( NSS['ooo'], aOOOAttrSlide );
5005     this.slideElement = this.theDocument.getElementById( this.slideId );
5006     assert( this.slideElement,
5007             'MetaSlide: slide element <' + this.slideId + '> not found.' );
5009     if( this.slideId !== 'dummy_slide' )
5010         this.nSlideNumber = parseInt( this.slideId.substr(2) );
5011     else
5012         this.nSlideNumber= -1;
5014     // Each slide element is double wrapped by <g> elements.
5015     // The outer <g> element is responsible for
5016     // the slide element visibility. In fact the visibility attribute has
5017     // to be set on the parent of the slide element and not directly on
5018     // the slide element. The reason is that in index mode each slide
5019     // rendered in a thumbnail view is targeted by a <use> element, however
5020     // when the visibility attribute is set directly on the referred slide
5021     // element its visibility is not overridden by the visibility attribute
5022     // defined by the targeting <use> element. The previous solution was,
5023     // when the user switched to index mode, to set up the visibility attribute
5024     // of all slides rendered in a thumbnail to 'visible'.
5025     // Obviously the slides were not really visible because the grid of
5026     // thumbnails was above them, anyway Firefox performance was really bad.
5027     // The workaround of setting up the visibility attribute on the slide
5028     // parent element let us to make visible a slide in a <use> element
5029     // even if the slide parent element visibility is set to 'hidden'.
5030     // The inner <g> element is used in order to append some element
5031     // before or after the slide, operation that can be needed for some
5032     // slide transition (e.g. fade through black). In this way we can
5033     // create a view of both the slide and the appended elements that turns out
5034     // to be useful for handling transition from the last to the first slide.
5035     this.aContainerElement = this.slideElement.parentNode;
5036     this.slideContainerId = this.aContainerElement.getAttribute( 'id' );
5037     this.aVisibilityStatusElement = this.aContainerElement.parentNode;
5039     // We get a reference to the draw page element, where all shapes specific
5040     // of this slide live.
5041     this.pageElement = getElementByClassName( this.slideElement, aPageClassName );
5042     assert( this.pageElement,
5043             'MetaSlide: page element <' + this.slideId + '> not found.' );
5045     // We initialize the MasterPage object that provides direct access to
5046     // the target master page element.
5047     this.masterPage = this.initMasterPage();
5049     // We initialize visibility properties of the target master page elements.
5050     this.nAreMasterObjectsVisible     = this.initVisibilityProperty( aOOOAttrMasterObjectsVisibility,  VISIBLE );
5051     this.nIsBackgroundVisible         = this.initVisibilityProperty( aOOOAttrBackgroundVisibility,     VISIBLE );
5052     this.nIsPageNumberVisible         = this.initVisibilityProperty( aOOOAttrPageNumberVisibility,     HIDDEN );
5053     this.nIsDateTimeVisible           = this.initVisibilityProperty( aOOOAttrDateTimeVisibility,       VISIBLE );
5054     this.nIsFooterVisible             = this.initVisibilityProperty( aOOOAttrFooterVisibility,         VISIBLE );
5055     this.nIsHeaderVisible             = this.initVisibilityProperty( aOOOAttrHeaderVisibility,         VISIBLE );
5057     // This property tell us if the date/time field need to be updated
5058     // each time the slide is shown. It is initialized in
5059     // the initDateTimeFieldContentProvider method.
5060     this.bIsDateTimeVariable = undefined;
5062     // We initialize the objects responsible to provide the content to text field.
5063     this.aTextFieldContentProviderSet = {};
5064     this.aTextFieldContentProviderSet[aSlideNumberClassName]   = this.initSlideNumberFieldContentProvider();
5065     this.aTextFieldContentProviderSet[aDateTimeClassName]      = this.initDateTimeFieldContentProvider( aOOOAttrDateTimeField );
5066     this.aTextFieldContentProviderSet[aFooterClassName]        = this.initFixedTextFieldContentProvider( aOOOAttrFooterField );
5067     this.aTextFieldContentProviderSet[aHeaderClassName]        = this.initFixedTextFieldContentProvider( aOOOAttrHeaderField );
5069     // We init the slide duration when automatic slide transition is enabled
5070     this.fDuration = this.initSlideDuration();
5072     // We look for slide transition.
5073     this.aTransitionHandler = null;
5074     this.bHasTransition = this.initHasTransition() || true;
5075     if( this.bHasTransition )
5076     {
5077         this.aTransitionHandler = new SlideTransition( this.getSlideAnimationsRoot(), this.slideId );
5078     }
5080     // We initialize the SlideAnimationsHandler object
5081     this.aSlideAnimationsHandler = new SlideAnimations( aSlideShow.getContext() );
5082     this.aSlideAnimationsHandler.importAnimations( this.getSlideAnimationsRoot() );
5083     this.aSlideAnimationsHandler.parseElements();
5085     // this statement is used only for debugging
5086     // eslint-disable-next-line no-constant-condition
5087     if( false && this.aSlideAnimationsHandler.aRootNode )
5088         log( this.aSlideAnimationsHandler.aRootNode.info( true ) );
5090     // We collect text shapes included in this slide .
5091     this.aTextShapeSet = this.collectTextShapes();
5093     // We initialize hyperlinks
5094     this.aHyperlinkSet = this.initHyperlinks();
5098 MetaSlide.prototype =
5100 /*** public methods ***/
5102 /** show
5103  *  Set the visibility property of the slide to 'inherit'
5104  *  and update the master page view.
5105  */
5106 show : function()
5108     this.updateMasterPageView();
5109     this.aVisibilityStatusElement.setAttribute( 'visibility', 'inherit' );
5112 /** hide
5113  *  Set the visibility property of the slide to 'hidden'.
5114  */
5115 hide : function()
5117     this.aVisibilityStatusElement.setAttribute( 'visibility', 'hidden' );
5120 /** updateMasterPageView
5121  *  On first call it creates a master page view element and insert it at
5122  *  the begin of the slide element. Moreover it updates the text fields
5123  *  included in the master page view.
5124  */
5125 updateMasterPageView : function()
5127     // The master page view element is generated and attached on first time
5128     // the slide is shown.
5129     if( !this.aMasterPageView )
5130     {
5131         this.aMasterPageView = new MasterPageView( this );
5132         this.aMasterPageView.attachToSlide();
5133     }
5134     this.aMasterPageView.update();
5137 /*** private methods ***/
5138 initMasterPage : function()
5140     var sMasterPageId = this.element.getAttributeNS( NSS['ooo'], aOOOAttrMaster );
5142     // Check that the master page handler object has not already been
5143     // created by another slide that target the same master page.
5144     if( !this.theMetaDoc.aMasterPageSet.hasOwnProperty( sMasterPageId ) )
5145     {
5146         this.theMetaDoc.aMasterPageSet[ sMasterPageId ] = new MasterPage( sMasterPageId, this );
5148         // We initialize aTextFieldHandlerSet[ sMasterPageId ] to an empty
5149         // collection.
5150         this.theMetaDoc.aTextFieldHandlerSet[ sMasterPageId ] = {};
5151     }
5152     return this.theMetaDoc.aMasterPageSet[ sMasterPageId ];
5155 initSlideDuration : function()
5157     var sSlideDuration = this.element.getAttributeNS( NSS['ooo'], aOOOAttrSlideDuration );
5158     if( sSlideDuration && sSlideDuration.length > 0 )
5159         return parseFloat( sSlideDuration );
5160     else
5161         return -1;
5164 initHasTransition : function()
5166     var sHasTransition = this.element.getAttributeNS( NSS['ooo'], aOOOAttrHasTransition );
5167     return ( sHasTransition === 'true' );
5170 initVisibilityProperty : function( aVisibilityAttribute, nDefaultValue )
5172     var nVisibility = nDefaultValue;
5173     var sVisibility = getOOOAttribute( this.element, aVisibilityAttribute );
5174     if( sVisibility )
5175         nVisibility = aVisibilityValue[ sVisibility ];
5176     return nVisibility;
5179 initSlideNumberFieldContentProvider : function()
5181     return this.theMetaDoc.aSlideNumberProvider;
5184 initDateTimeFieldContentProvider : function( aOOOAttrDateTimeField )
5186     var sTextFieldId = getOOOAttribute( this.element, aOOOAttrDateTimeField );
5187     if( !sTextFieldId )  return null;
5189     var nLength = aOOOElemTextField.length + 1;
5190     var nIndex = parseInt(sTextFieldId.substring( nLength ) );
5191     if( typeof nIndex != 'number') return null;
5193     if( !this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ] )
5194     {
5195         var aTextField;
5196         var aTextFieldElem = document.getElementById( sTextFieldId );
5197         var sClassName = getClassAttribute( aTextFieldElem );
5198         if( sClassName == 'FixedDateTimeField' )
5199         {
5200             aTextField = new FixedTextProvider( aTextFieldElem );
5201             this.bIsDateTimeVariable = false;
5202         }
5203         else if( sClassName == 'VariableDateTimeField' )
5204         {
5205             aTextField = new CurrentDateTimeProvider( aTextFieldElem );
5206             this.bIsDateTimeVariable = true;
5207         }
5208         else
5209         {
5210             aTextField = null;
5211         }
5212         this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ] = aTextField;
5213     }
5214     return this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ];
5217 initFixedTextFieldContentProvider : function( aOOOAttribute )
5219     var sTextFieldId = getOOOAttribute( this.element, aOOOAttribute );
5220     if( !sTextFieldId ) return null;
5222     var nLength = aOOOElemTextField.length + 1;
5223     var nIndex = parseInt( sTextFieldId.substring( nLength ) );
5224     if( typeof nIndex != 'number') return null;
5226     if( !this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ] )
5227     {
5228         var aTextFieldElem = document.getElementById( sTextFieldId );
5229         this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ]
5230             = new FixedTextProvider( aTextFieldElem );
5231     }
5232     return this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ];
5235 collectTextShapes : function()
5237     var aTextShapeSet = [];
5238     var aTextShapeIndexElem = getElementByClassName( document, 'TextShapeIndex' );
5239     if( aTextShapeIndexElem )
5240     {
5241         var aIndexEntryList = getElementChildren( aTextShapeIndexElem );
5242         var i;
5243         for( i = 0; i < aIndexEntryList.length; ++i )
5244         {
5245             var sSlideId = getOOOAttribute( aIndexEntryList[i], 'slide' );
5246             if( sSlideId === this.slideId )
5247             {
5248                 var sTextShapeIds = getOOOAttribute( aIndexEntryList[i], 'id-list' );
5249                 if( sTextShapeIds )
5250                 {
5251                     var aTextShapeIdSet =  sTextShapeIds.split( ' ' );
5252                     var j;
5253                     for( j = 0; j < aTextShapeIdSet.length; ++j )
5254                     {
5255                         var aTextShapeElem = document.getElementById( aTextShapeIdSet[j] );
5256                         if( aTextShapeElem )
5257                         {
5258                             aTextShapeSet.push( aTextShapeElem );
5259                         }
5260                         else
5261                         {
5262                             log( 'warning: MetaSlide.collectTextShapes: text shape with id <' + aTextShapeIdSet[j] + '> is not valid.'  );
5263                         }
5264                     }
5265                 }
5266                 break;
5267             }
5268         }
5269     }
5270     return aTextShapeSet;
5273 initHyperlinks : function()
5275     var aHyperlinkSet = {};
5276     var i;
5277     for( i = 0; i < this.aTextShapeSet.length; ++i )
5278     {
5279         if( this.aTextShapeSet[i] )
5280         {
5281             var aHyperlinkIdList = getElementByClassName( this.aTextShapeSet[i], 'HyperlinkIdList' );
5282             if( aHyperlinkIdList )
5283             {
5284                 var sHyperlinkIds = aHyperlinkIdList.textContent;
5285                 if( sHyperlinkIds )
5286                 {
5287                     var aHyperlinkIdSet = sHyperlinkIds.trim().split( ' ' );
5288                     var j;
5289                     for( j = 0; j < aHyperlinkIdSet.length; ++j )
5290                     {
5291                         var sId = aHyperlinkIdSet[j];
5292                         aHyperlinkSet[ sId ] = new HyperlinkElement( sId, this.aSlideAnimationsHandler.aEventMultiplexer );
5293                     }
5294                 }
5295             }
5296         }
5297     }
5298     return aHyperlinkSet;
5301 getSlideAnimationsRoot : function()
5303     return this.theMetaDoc.aSlideAnimationsMap[ this.slideId ];
5306 }; // end MetaSlide prototype
5308 /** Class MasterPage
5309  *  This class gives direct access to a master page element and to the following
5310  *  elements included in the master page:
5311  *  - the background element,
5312  *  - the background objects group element,
5313  *  Moreover for each text field element a Placeholder object is created which
5314  *  manages the text field element itself.
5316  *  The master page element structure is the following:
5317  *  <g class='Master_Slide'>
5318  *      <g class='Background'>
5319  *          background image
5320  *      </g>
5321  *      <g class='BackgroundObjects'>
5322  *          <g class='Date/Time'>
5323  *              date/time placeholder
5324  *          </g>
5325  *          <g class='Header'>
5326  *              header placeholder
5327  *          </g>
5328  *          <g class='Footer'>
5329  *              footer placeholder
5330  *          </g>
5331  *          <g class='Slide_Number'>
5332  *              slide number placeholder
5333  *          </g>
5334  *          shapes
5335  *      </g>
5336  *  </g>
5338  *  @param sMasterPageId
5339  *      A string representing the value of the id attribute of the master page
5340  *      element to be handled.
5341  * @param aMetaSlide
5342  *     A meta slide having as master page the one with the passed id.
5343  */
5344 function MasterPage( sMasterPageId, aMetaSlide )
5346     this.id = sMasterPageId;
5347     this.metaSlide = aMetaSlide;
5349     // The master page element to be handled.
5350     this.element = document.getElementById( this.id );
5351     assert( this.element,
5352             'MasterPage: master page element <' + this.id + '> not found.' );
5354     // The master page background element and its id attribute.
5355     this.background = getElementByClassName( this.element, 'Background' );
5356     if( this.background )
5357     {
5358         this.backgroundId = this.background.getAttribute( 'id' );
5359         this.backgroundVisibility = initVisibilityProperty( this.background );
5360     }
5361     else
5362     {
5363         this.backgroundId = '';
5364         log( 'MasterPage: the background element is not valid.' );
5365     }
5367     // The background objects group element that contains every element presents
5368     // on the master page except the background element.
5369     this.backgroundObjects = getElementByClassName( this.element, 'BackgroundObjects' );
5370     if( this.backgroundObjects )
5371     {
5372         this.backgroundObjectsId = this.backgroundObjects.getAttribute( 'id' );
5373         this.backgroundObjectsVisibility = initVisibilityProperty( this.backgroundObjects );
5375         if( this.backgroundObjectsVisibility != HIDDEN )
5376         {
5377             var aBackgroundObjectList = getElementChildren( this.backgroundObjects );
5378             var nFrom = 0;
5379             var nCount = 0;
5380             var nSubGroupId = 1;
5381             var sClass;
5382             var sId = '';
5383             this.aBackgroundObjectSubGroupIdList = [];
5384             var i = 0;
5385             for( ; i < aBackgroundObjectList.length; ++i )
5386             {
5387                 sClass = aBackgroundObjectList[i].getAttribute( 'class' );
5388                 if( !sClass || ( ( sClass !== aDateTimeClassName ) && ( sClass !== aFooterClassName )
5389                                      && ( sClass !== aHeaderClassName ) && ( sClass !== aSlideNumberClassName ) ) )
5390                 {
5391                     if( nCount === 0 )
5392                     {
5393                         nFrom = i;
5394                         sId = this.backgroundObjectsId + '.' + nSubGroupId;
5395                         ++nSubGroupId;
5396                         this.aBackgroundObjectSubGroupIdList.push( sId );
5397                     }
5398                     ++nCount;
5399                 }
5400                 else
5401                 {
5402                     this.aBackgroundObjectSubGroupIdList.push( sClass );
5403                     if( nCount !== 0 )
5404                     {
5405                         createElementGroup( this.backgroundObjects, aBackgroundObjectList, nFrom, nCount, 'BackgroundObjectSubgroup', sId );
5406                         nCount = 0;
5407                     }
5408                 }
5409             }
5410             if( nCount !== 0 )
5411             {
5412                 createElementGroup( this.backgroundObjects, aBackgroundObjectList, nFrom, nCount, 'BackgroundObjectSubgroup', sId );
5413             }
5414         }
5415     }
5416     else
5417     {
5418         this.backgroundObjectsId = '';
5419         log( 'MasterPage: the background objects element is not valid.' );
5420     }
5422     // We populate the collection of placeholders.
5423     this.aPlaceholderShapeSet = {};
5424     this.initPlaceholderShapes();
5427 MasterPage.prototype =
5429 /*** private methods ***/
5431 initPlaceholderShapes : function()
5433     this.aPlaceholderShapeSet[ aSlideNumberClassName ] = new PlaceholderShape( this, aSlideNumberClassName );
5434     this.aPlaceholderShapeSet[ aDateTimeClassName ] = new PlaceholderShape( this, aDateTimeClassName );
5435     this.aPlaceholderShapeSet[ aFooterClassName ] = new PlaceholderShape( this, aFooterClassName );
5436     this.aPlaceholderShapeSet[ aHeaderClassName ] = new PlaceholderShape( this, aHeaderClassName );
5439 }; // end MasterPage prototype
5441 /** Class PlaceholderShape
5442  *  This class provides direct access to a text field element and
5443  *  to the embedded placeholder element.
5444  *  Moreover it set up the text adjustment and position for the placeholder
5445  *  element.
5446  *  Note: the text field element included in a master page is used only as
5447  *  a template element, it is cloned for each specific text content
5448  *  (see the TextFieldContentProvider class and its derived classes).
5450  *  @param aMasterPage
5451  *      The master page object to which the text field to be handled belongs.
5452  *  @param sClassName
5453  *      A string representing the value of the class attribute of the text
5454  *      field element to be handled.
5455  */
5456 function PlaceholderShape( aMasterPage, sClassName )
5458     this.masterPage = aMasterPage;
5459     this.className = sClassName;
5461     this.element = null;
5462     this.textElement = null;
5463     this.init();
5466 /* public methods */
5467 PlaceholderShape.prototype.isValid = function()
5469     return ( this.element && this.textElement );
5472 /* private methods */
5474 /** init
5475  *  In case a text field element of class type 'className' exists and such
5476  *  an element embeds a placeholder element, the text adjustment and position
5477  *  of the placeholder element is set up.
5478  */
5479 PlaceholderShape.prototype.init = function()
5482     var aTextFieldElement = getElementByClassName( this.masterPage.backgroundObjects, this.className );
5483     if( aTextFieldElement )
5484     {
5485         var aPlaceholderElement = getElementByClassName( aTextFieldElement, 'PlaceholderText' );
5486         if( aPlaceholderElement )
5487         {
5488             // Each text field element has an invisible rectangle that can be
5489             // regarded as the text field bounding box.
5490             // We exploit such a feature and the exported text adjust attribute
5491             // value in order to set up correctly the position and text
5492             // adjustment for the placeholder element.
5493             var aSVGRectElem = getElementByClassName( aTextFieldElement, 'BoundingBox' );
5494             if( aSVGRectElem )
5495             {
5496                 var aRect = new Rectangle( aSVGRectElem );
5497                 var sTextAdjust = getOOOAttribute( aTextFieldElement, aOOOAttrTextAdjust ) || 'left';
5498                 var sTextAnchor, sX;
5499                 if( sTextAdjust == 'left' )
5500                 {
5501                     sTextAnchor = 'start';
5502                     sX = String( aRect.left );
5503                 }
5504                 else if( sTextAdjust == 'right' )
5505                 {
5506                     sTextAnchor = 'end';
5507                     sX = String( aRect.right );
5508                 }
5509                 else if( sTextAdjust == 'center' )
5510                 {
5511                     sTextAnchor = 'middle';
5512                     var nMiddle = ( aRect.left + aRect.right ) / 2;
5513                     sX = String( parseInt( String( nMiddle ) ) );
5514                 }
5515                 if( sTextAnchor )
5516                     aPlaceholderElement.setAttribute( 'text-anchor', sTextAnchor );
5517                 if( sX )
5518                     aPlaceholderElement.setAttribute( 'x', sX );
5519             }
5521             // date/time fields were not exported correctly when positioned chars are used
5522             if( this.masterPage.metaSlide.theMetaDoc.bIsUsePositionedChars )
5523             {
5524                 // We remove all text lines but the first one used as placeholder.
5525                 var aTextLineGroupElem = aPlaceholderElement.parentNode.parentNode;
5526                 if( aTextLineGroupElem )
5527                 {
5528                     // Just to be sure it is the element we are looking for.
5529                     var sFontFamilyAttr = aTextLineGroupElem.getAttribute( 'font-family' );
5530                     if( sFontFamilyAttr )
5531                     {
5532                         var aChildSet = getElementChildren( aTextLineGroupElem );
5533                         if( aChildSet.length > 1  )
5534                             var i = 1;
5535                         for( ; i < aChildSet.length; ++i )
5536                         {
5537                             aTextLineGroupElem.removeChild( aChildSet[i] );
5538                         }
5539                     }
5540                 }
5541             }
5542             this.element = aTextFieldElement;
5543             this.textElement = aPlaceholderElement;
5544         }
5545     }
5548 /** Class MasterPageView
5549  *  This class is used to creates a svg element of class MasterPageView and its
5550  *  sub-elements.
5551  *  It is also responsible for updating the content of the included text fields.
5553  *  MasterPageView element structure:
5555  *  <g class='MasterPageView'>
5556  *      <use class='Background'>               // reference to master page background element
5557  *      <g class='BackgroundObjects'>
5558  *          <use class='BackgroundObjectSubGroup'>     // reference to the group of shapes on the master page that are below text fields
5559  *          <g class='Slide_Number'>                   // a cloned element
5560  *                  ...
5561  *          </g>
5562  *          <use class='Date/Time'>                    // reference to a clone
5563  *          <use class='Footer'>
5564  *          <use class='Header'>
5565  *          <use class='BackgroundObjectSubGroup'>     // reference to the group of shapes on the master page that are above text fields
5566  *      </g>
5567  *  </g>
5569  *  Sub-elements are present only if they are visible.
5571  *  @param aMetaSlide
5572  *      The MetaSlide object managing the slide element that targets
5573  *      the master page view element created by an instance of MasterPageView.
5574  */
5575 function MasterPageView( aMetaSlide )
5577     this.aMetaSlide = aMetaSlide;
5578     this.aSlideElement = aMetaSlide.slideElement;
5579     this.aPageElement = aMetaSlide.pageElement;
5580     this.aMasterPage = aMetaSlide.masterPage;
5581     this.aMPVElement = this.createElement();
5582     this.bIsAttached = false;
5585 /*** public methods ***/
5587 /** attachToSlide
5588  *  Prepend the master slide view element to the slide element.
5589  */
5590 MasterPageView.prototype.attachToSlide = function()
5592     if( !this.bIsAttached )
5593     {
5594         var aInsertedElement = this.aSlideElement.insertBefore( this.aMPVElement, this.aPageElement );
5595         assert( aInsertedElement === this.aMPVElement,
5596                 'MasterPageView.attachToSlide: aInsertedElement != this.aMPVElement' );
5598         this.bIsAttached = true;
5599     }
5602 /** detachFromSlide
5603  *  Remove the master slide view element from the slide element.
5604  */
5605 MasterPageView.prototype.detachFromSlide = function()
5607     if( this.bIsAttached )
5608     {
5609         this.aSlideElement.removeChild( this.aMPVElement );
5610         this.bIsAttached = false;
5611     }
5614 /** update
5615  *  Update the content of text fields placed on the master page.
5616  */
5617 MasterPageView.prototype.update = function()
5619     if( this.aDateTimeFieldHandler && this.aMetaSlide.bIsDateTimeVariable )
5620         this.aDateTimeFieldHandler.update();
5623 /*** private methods ***/
5625 MasterPageView.prototype.createElement = function()
5627     var theDocument = document;
5628     var aMasterPageViewElement = theDocument.createElementNS( NSS['svg'], 'g' );
5629     assert( aMasterPageViewElement,
5630             'MasterPageView.createElement: failed to create a master page view element.' );
5631     aMasterPageViewElement.setAttribute( 'class', 'MasterPageView' );
5633     // we place a white rect below any else element
5634     // that is also a workaround for some kind of slide transition
5635     // when the master page is empty
5636     var aWhiteRect = theDocument.createElementNS( NSS['svg'], 'rect' );
5637     var nWidthExt = WIDTH / 1000;
5638     var nHeightExt = HEIGHT / 1000;
5639     aWhiteRect.setAttribute( 'x', String( -nWidthExt / 2 ) );
5640     aWhiteRect.setAttribute( 'y', String( -nHeightExt / 2 ) );
5641     aWhiteRect.setAttribute( 'width', String( WIDTH + nWidthExt ) );
5642     aWhiteRect.setAttribute( 'height', String( HEIGHT + nHeightExt ) );
5643     aWhiteRect.setAttribute( 'fill', '#FFFFFF' );
5644     aMasterPageViewElement.appendChild( aWhiteRect );
5646     // init the Background element
5647     if( this.aMetaSlide.nIsBackgroundVisible )
5648     {
5649         this.aBackgroundElement = theDocument.createElementNS( NSS['svg'], 'use' );
5650         this.aBackgroundElement.setAttribute( 'class', 'Background' );
5651         setNSAttribute( 'xlink', this.aBackgroundElement,
5652                         'href', '#' + this.aMasterPage.backgroundId );
5654         // node linking
5655         aMasterPageViewElement.appendChild( this.aBackgroundElement );
5656     }
5658     // init the BackgroundObjects element
5659     if( this.aMetaSlide.nAreMasterObjectsVisible )
5660     {
5661         this.aBackgroundObjectsElement = theDocument.createElementNS( NSS['svg'], 'g' );
5662         this.aBackgroundObjectsElement.setAttribute( 'class', 'BackgroundObjects' );
5664         // clone and initialize text field elements
5665         var aBackgroundObjectSubGroupIdList = this.aMasterPage.aBackgroundObjectSubGroupIdList;
5666         this.aBackgroundSubGroupElementSet = [];
5667         var aPlaceholderShapeSet = this.aMasterPage.aPlaceholderShapeSet;
5668         var aTextFieldContentProviderSet = this.aMetaSlide.aTextFieldContentProviderSet;
5669         // where cloned elements are appended
5670         var aDefsElement = this.aMetaSlide.element.parentNode;
5671         var aTextFieldHandlerSet = this.aMetaSlide.theMetaDoc.aTextFieldHandlerSet;
5672         var sMasterSlideId = this.aMasterPage.id;
5674         var i = 0;
5675         var sId;
5676         for( ; i < aBackgroundObjectSubGroupIdList.length; ++i )
5677         {
5678             sId = aBackgroundObjectSubGroupIdList[i];
5679             if( sId === aSlideNumberClassName )
5680             {
5681                 // Slide Number Field
5682                 // The cloned element is appended directly to the field group element
5683                 // since there is no slide number field content shared between two slide
5684                 // (because the slide number of two slide is always different).
5685                 if( aPlaceholderShapeSet[aSlideNumberClassName] &&
5686                     aPlaceholderShapeSet[aSlideNumberClassName].isValid() &&
5687                     this.aMetaSlide.nIsPageNumberVisible &&
5688                     aTextFieldContentProviderSet[aSlideNumberClassName] )
5689                 {
5690                     this.aSlideNumberFieldHandler =
5691                         new SlideNumberFieldHandler( aPlaceholderShapeSet[aSlideNumberClassName],
5692                                                      aTextFieldContentProviderSet[aSlideNumberClassName] );
5693                     this.aSlideNumberFieldHandler.update( this.aMetaSlide.nSlideNumber );
5694                     this.aSlideNumberFieldHandler.appendTo( this.aBackgroundObjectsElement );
5695                 }
5696             }
5697             else if( sId === aDateTimeClassName )
5698             {
5699                 // Date/Time field
5700                 if( this.aMetaSlide.nIsDateTimeVisible )
5701                 {
5702                     this.aDateTimeFieldHandler =
5703                         this.initTextFieldHandler( aDateTimeClassName, aPlaceholderShapeSet,
5704                                                    aTextFieldContentProviderSet, aDefsElement,
5705                                                    aTextFieldHandlerSet, sMasterSlideId );
5706                 }
5707             }
5708             else if( sId === aFooterClassName )
5709             {
5710                 // Footer Field
5711                 if( this.aMetaSlide.nIsFooterVisible )
5712                 {
5713                     this.aFooterFieldHandler =
5714                         this.initTextFieldHandler( aFooterClassName, aPlaceholderShapeSet,
5715                                                    aTextFieldContentProviderSet, aDefsElement,
5716                                                    aTextFieldHandlerSet, sMasterSlideId );
5717                 }
5718             }
5719             else if( sId === aHeaderClassName )
5720             {
5721                 // Header Field
5722                 if( this.aMetaSlide.nIsHeaderVisible )
5723                 {
5724                     this.aHeaderFieldHandler =
5725                         this.initTextFieldHandler( aHeaderClassName, aPlaceholderShapeSet,
5726                                                    aTextFieldContentProviderSet, aDefsElement,
5727                                                    aTextFieldHandlerSet, sMasterSlideId );
5728                 }
5729             }
5730             else
5731             {
5732                 // init BackgroundObjectSubGroup elements
5733                 var aBackgroundSubGroupElement = theDocument.createElementNS( NSS['svg'], 'use' );
5734                 aBackgroundSubGroupElement.setAttribute( 'class', 'BackgroundObjectSubGroup' );
5735                 setNSAttribute( 'xlink', aBackgroundSubGroupElement,
5736                                 'href', '#' + sId );
5737                 this.aBackgroundSubGroupElementSet.push( aBackgroundSubGroupElement );
5738                 // node linking
5739                 this.aBackgroundObjectsElement.appendChild( aBackgroundSubGroupElement );
5740             }
5742         }
5743         // node linking
5744         aMasterPageViewElement.appendChild( this.aBackgroundObjectsElement );
5745     }
5747     return aMasterPageViewElement;
5750 MasterPageView.prototype.initTextFieldHandler =
5751     function( sClassName, aPlaceholderShapeSet, aTextFieldContentProviderSet,
5752               aDefsElement, aTextFieldHandlerSet, sMasterSlideId )
5754     var aTextFieldHandler = null;
5755     if( aPlaceholderShapeSet[sClassName] &&
5756         aPlaceholderShapeSet[sClassName].isValid()
5757         && aTextFieldContentProviderSet[sClassName] )
5758     {
5759         var sTextFieldContentProviderId = aTextFieldContentProviderSet[sClassName].sId;
5760         // We create only one single TextFieldHandler object (and so one only
5761         // text field clone) per master slide and text content.
5762         if ( !aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ] )
5763         {
5764             aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ] =
5765                 new TextFieldHandler( aPlaceholderShapeSet[sClassName],
5766                                       aTextFieldContentProviderSet[sClassName] );
5767             aTextFieldHandler = aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ];
5768             aTextFieldHandler.update();
5769             aTextFieldHandler.appendTo( aDefsElement );
5770         }
5771         else
5772         {
5773             aTextFieldHandler = aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ];
5774         }
5776         // We create a <use> element referring to the cloned text field and
5777         // append it to the field group element.
5778         var aTextFieldElement = document.createElementNS( NSS['svg'], 'use' );
5779         aTextFieldElement.setAttribute( 'class', sClassName );
5780         setNSAttribute( 'xlink', aTextFieldElement,
5781                         'href', '#' + aTextFieldHandler.sId );
5782         // node linking
5783         this.aBackgroundObjectsElement.appendChild( aTextFieldElement );
5784     }
5785     return aTextFieldHandler;
5788 /** Class TextFieldHandler
5789  *  This class clone a text field field of a master page and set up
5790  *  the content of the cloned element on demand.
5792  *  @param aPlaceholderShape
5793  *      A PlaceholderShape object that provides the text field to be cloned.
5794  *  @param aTextContentProvider
5795  *      A TextContentProvider object to which the actual content updating is
5796  *      demanded.
5797  */
5798 function TextFieldHandler( aPlaceholderShape, aTextContentProvider )
5800     this.aPlaceHolderShape = aPlaceholderShape;
5801     this.aTextContentProvider = aTextContentProvider;
5802     assert( this.aTextContentProvider,
5803             'TextFieldHandler: text content provider not defined.' );
5804     this.sId = 'tf' + String( TextFieldHandler.getUniqueId() );
5805     // The cloned text field element to be handled.
5806     this.aTextFieldElement = null;
5807     // The actual <text> element where the field content has to be placed.
5808     this.aTextPlaceholderElement = null;
5809     this.cloneElement();
5812 /*** private methods ***/
5814 TextFieldHandler.CURR_UNIQUE_ID = 0;
5816 TextFieldHandler.getUniqueId = function()
5818     ++TextFieldHandler.CURR_UNIQUE_ID;
5819     return TextFieldHandler.CURR_UNIQUE_ID;
5822 TextFieldHandler.prototype.cloneElement = function()
5824     assert( this.aPlaceHolderShape && this.aPlaceHolderShape.isValid(),
5825             'TextFieldHandler.cloneElement: placeholder shape is not valid.' );
5826     // The cloned text field element.
5827     this.aTextFieldElement = this.aPlaceHolderShape.element.cloneNode( true /* deep clone */ );
5828     assert( this.aTextFieldElement,
5829             'TextFieldHandler.cloneElement: aTextFieldElement is not defined' );
5830     this.aTextFieldElement.setAttribute( 'id', this.sId );
5831     // Text field placeholder visibility is always set to 'hidden'.
5832     this.aTextFieldElement.removeAttribute( 'visibility' );
5833     // The actual <text> element where the field content has to be placed.
5834     this.aTextPlaceholderElement = getElementByClassName( this.aTextFieldElement, 'PlaceholderText' );
5835     assert( this.aTextPlaceholderElement,
5836             'TextFieldHandler.cloneElement: aTextPlaceholderElement is not defined' );
5839 /*** public methods ***/
5841 /** appendTo
5842  *  Append the cloned text field element to a svg element.
5844  *  @param aParentNode
5845  *      The svg element to which the cloned text field has to be appended.
5846  */
5847 TextFieldHandler.prototype.appendTo = function( aParentNode )
5849     if( !this.aTextFieldElement )
5850     {
5851         log( 'TextFieldHandler.appendTo: aTextFieldElement is not defined' );
5852         return;
5853     }
5854     if( !aParentNode )
5855     {
5856         log( 'TextFieldHandler.appendTo: parent node is not defined' );
5857         return;
5858     }
5860     aParentNode.appendChild( this.aTextFieldElement );
5863 /** setTextContent
5864  *  Modify the content of the cloned text field.
5866  *  @param sText
5867  *      A string representing the new content of the cloned text field.
5868  */
5869 TextFieldHandler.prototype.setTextContent = function( sText )
5871     if( !this.aTextPlaceholderElement )
5872     {
5873         log( 'PlaceholderShape.setTextContent: text element is not valid in placeholder of type '
5874              + this.className + ' that belongs to master slide ' + this.masterPage.id );
5875         return;
5876     }
5877     this.aTextPlaceholderElement.textContent = sText;
5880 /** update
5881  *  Update the content of the handled text field. The new content is provided
5882  *  directly from the TextContentProvider data member.
5883  */
5884 TextFieldHandler.prototype.update = function()
5886     if( !this.aTextContentProvider )
5887         log('TextFieldHandler.update: text content provider not defined.');
5888     else
5889         this.aTextContentProvider.update( this );
5892 /** SlideNumberFieldHandler
5893  *  This class clone the slide number field of a master page and set up
5894  *  the content of the cloned element on demand.
5896  *  @param aPlaceholderShape
5897  *      A PlaceholderShape object that provides the slide number field
5898  *      to be cloned.
5899  *  @param aTextContentProvider
5900  *      A SlideNumberProvider object to which the actual content updating is
5901  *      demanded.
5902  */
5903 function SlideNumberFieldHandler( aPlaceholderShape, aTextContentProvider )
5905     SlideNumberFieldHandler.superclass.constructor.call( this, aPlaceholderShape, aTextContentProvider );
5907 extend( SlideNumberFieldHandler, TextFieldHandler );
5909 /*** public methods ***/
5911 /** update
5912  *  Update the content of the handled slide number field with the passed number.
5914  * @param nPageNumber
5915  *      The number representing the new content of the slide number field.
5916  */
5917 SlideNumberFieldHandler.prototype.update = function( nPageNumber )
5919     // The actual content updating is demanded to the related
5920     // SlideNumberProvider instance that have the needed info on
5921     // the numbering type.
5922     if( !this.aTextContentProvider )
5923         log('TextFieldHandler.update: text content provider not defined.');
5924     else
5925         this.aTextContentProvider.update( this, nPageNumber );
5929 /******************************************************************************
5930  * Text Field Content Provider Class Hierarchy
5932  * The following classes are responsible to format and set the text content
5933  * of text fields.
5935  ******************************************************************************/
5937 /** Class TextFieldContentProvider
5938  *  This class is the root abstract class of the hierarchy.
5940  *  @param aTextFieldContentElement
5941  *      The svg element that contains the text content for one or more
5942  *      master slide text field.
5943  */
5944 function TextFieldContentProvider( aTextFieldContentElement )
5946     // This id is used as key for the theMetaDoc.aTextFieldHandlerSet object.
5947     if( aTextFieldContentElement )
5948         this.sId = aTextFieldContentElement.getAttribute( 'id' );
5951 /** Class FixedTextProvider
5952  *  This class handles text field with a fixed text.
5953  *  The text content is provided by the 'text' property.
5955  *  @param aTextFieldContentElement
5956  *      The svg element that contains the text content for one or more
5957  *      master slide text field.
5958  */
5959 function FixedTextProvider( aTextFieldContentElement )
5961     FixedTextProvider.superclass.constructor.call( this, aTextFieldContentElement );
5962     this.text = aTextFieldContentElement.textContent;
5964 extend( FixedTextProvider, TextFieldContentProvider );
5966 /*** public methods ***/
5968 /** update
5969  *  Set up the content of a fixed text field.
5971  *  @param aFixedTextField
5972  *      An object that implement a setTextContent( String ) method in order
5973  *      to set the content of a given text field.
5974  */
5975 FixedTextProvider.prototype.update = function( aFixedTextField )
5977     aFixedTextField.setTextContent( this.text );
5980 /** Class CurrentDateTimeProvider
5981  *  Provide the text content to a date/time field by generating the current
5982  *  date/time in the format specified by the 'dateTimeFormat' property.
5984  *  @param aTextFieldContentElement
5985  *      The svg element that contains the date/time format for one or more
5986  *      master slide date/time field.
5987  */
5988 function CurrentDateTimeProvider( aTextFieldContentElement )
5990     CurrentDateTimeProvider.superclass.constructor.call( this, aTextFieldContentElement );
5991     this.dateTimeFormat = getOOOAttribute( aTextFieldContentElement, aOOOAttrDateTimeFormat );
5993 extend( CurrentDateTimeProvider, TextFieldContentProvider );
5995 /*** public methods ***/
5997 /** update
5998  *  Set up the content of a variable date/time field.
6000  *  @param aDateTimeField
6001  *      An object that implement a setTextContent( String ) method in order
6002  *      to set the content of a given text field.
6003  */
6004 CurrentDateTimeProvider.prototype.update = function( aDateTimeField )
6006     var sText = this.createDateTimeText( this.dateTimeFormat );
6007     aDateTimeField.setTextContent( sText );
6010 /*** private methods ***/
6012 CurrentDateTimeProvider.prototype.createDateTimeText = function( /*sDateTimeFormat*/ )
6014     // TODO handle date/time format
6015     var aDate = new Date();
6016     var sDate = aDate.toLocaleString();
6017     return sDate;
6020 /** Class SlideNumberProvider
6021  *  Provides the text content to the related text field by generating
6022  *  the current page number in the given page numbering type.
6023  */
6024 function SlideNumberProvider( nInitialSlideNumber, sPageNumberingType )
6026     SlideNumberProvider.superclass.constructor.call( this, null );
6027     this.nInitialSlideNumber = nInitialSlideNumber;
6028     this.pageNumberingType = sPageNumberingType;
6031 extend( SlideNumberProvider, TextFieldContentProvider );
6033 /*** public methods ***/
6035 /** getNumberingType
6037  *  @return
6038  *      The page numbering type.
6039  */
6040 SlideNumberProvider.prototype.getNumberingType = function()
6042     return this.pageNumberingType;
6045 /** update
6046  *  Set up the content of a slide number field.
6048  *  @param aSlideNumberField
6049  *      An object that implement a setTextContent( String ) method in order
6050  *      to set the content of a given text field.
6051  *  @param nSlideNumber
6052  *      An integer representing the slide number.
6053  */
6055 SlideNumberProvider.prototype.update = function( aSlideNumberField, nSlideNumber )
6057     if( nSlideNumber === undefined )
6058     {
6059         if( nCurSlide === undefined )
6060             nSlideNumber = this.nInitialSlideNumber;
6061         else
6062             nSlideNumber = nCurSlide + 1;
6063     }
6064     var sText = this.createSlideNumberText( nSlideNumber, this.getNumberingType() );
6065     aSlideNumberField.setTextContent( sText );
6068 /*** private methods ***/
6070 SlideNumberProvider.prototype.createSlideNumberText = function( nSlideNumber /*, sNumberingType*/ )
6072     // TODO handle page numbering type
6073     return String( nSlideNumber );
6079 /********************************
6080  ** Slide Index Classes **
6081  ********************************/
6083 /** Class SlideIndexPage **
6084  *  This class is responsible for handling the slide index page
6085  */
6086 function SlideIndexPage()
6088     this.pageElementId = 'slide_index';
6089     this.pageBgColor = 'rgb(252,252,252)';
6090     this.pageElement = this.createPageElement();
6091     assert( this.pageElement, 'SlideIndexPage: pageElement is not valid' );
6092     this.indexColumns = INDEX_COLUMNS_DEFAULT;
6093     this.totalThumbnails = this.indexColumns * this.indexColumns;
6094     this.selectedSlideIndex = undefined;
6096     // set up layout parameters
6097     this.xSpacingFactor = 600/28000;
6098     this.ySpacingFactor = 450/21000;
6099     this.xSpacing = WIDTH * this.xSpacingFactor;
6100     this.ySpacing = HEIGHT * this.ySpacingFactor;
6101     this.halfBorderWidthFactor = ( 300/28000 ) * ( this.indexColumns / 3 );
6102     this.halfBorderWidth = WIDTH * this.halfBorderWidthFactor;
6103     this.borderWidth = 2 * this.halfBorderWidth;
6104     // the following formula is used to compute the slide shrinking factor:
6105     // scaleFactor = ( WIDTH - ( columns + 1 ) * xSpacing ) / ( columns * ( WIDTH + borderWidth ) )
6106     // indeed we can divide everything by WIDTH:
6107     this.scaleFactor = ( 1 - ( this.indexColumns + 1 ) * this.xSpacingFactor ) /
6108                             ( this.indexColumns * ( 1 + 2 * this.halfBorderWidthFactor ) );
6110     // We create a Thumbnail Border and Thumbnail MouseArea rectangle template that will be
6111     // used by every Thumbnail. The Mouse Area rectangle is used in order to trigger the
6112     // mouseover event properly even when the slide background is hidden.
6113     this.thumbnailMouseAreaTemplateId = 'thumbnail_mouse_area';
6114     this.thumbnailMouseAreaTemplateElement = null;
6115     this.thumbnailBorderTemplateId = 'thumbnail_border';
6116     this.thumbnailBorderTemplateElement = null;
6117     this.createTemplateElements();
6119     // Now we create the grid of thumbnails
6120     this.aThumbnailSet = new Array( this.totalThumbnails );
6121     for( var i = 0; i < this.totalThumbnails; ++i )
6122     {
6123         this.aThumbnailSet[i] = new Thumbnail( this, i );
6124         this.aThumbnailSet[i].updateView();
6125     }
6127     this.curThumbnailIndex = 0;
6131 /* public methods */
6132 SlideIndexPage.prototype.getTotalThumbnails = function()
6134     return this.totalThumbnails;
6137 SlideIndexPage.prototype.show = function()
6139     this.pageElement.setAttribute( 'display', 'inherit' );
6142 SlideIndexPage.prototype.hide = function()
6144     this.pageElement.setAttribute( 'display', 'none' );
6147 /** setSelection
6149  * Change the selected thumbnail from the current one to the thumbnail with index nIndex.
6151  * @param nIndex - the thumbnail index
6152  */
6153 SlideIndexPage.prototype.setSelection = function( nIndex )
6155     nIndex = getSafeIndex( nIndex, 0, this.getTotalThumbnails() - 1 );
6156     if( this.curThumbnailIndex != nIndex )
6157     {
6158         this.aThumbnailSet[ this.curThumbnailIndex ].unselect();
6159         this.aThumbnailSet[ nIndex ].select();
6160         this.curThumbnailIndex = nIndex;
6161     }
6162     this.selectedSlideIndex = this.aThumbnailSet[ nIndex ].slideIndex;
6165 SlideIndexPage.prototype.createPageElement = function()
6167     var aPageElement = document.createElementNS( NSS['svg'], 'g' );
6168     aPageElement.setAttribute( 'id', this.pageElementId );
6169     aPageElement.setAttribute( 'display', 'none' );
6170     aPageElement.setAttribute( 'visibility', 'visible' );
6172     // the slide index page background
6173     var sPageBgColor = this.pageBgColor + ';';
6174     var aRectElement = document.createElementNS( NSS['svg'], 'rect' );
6175     aRectElement.setAttribute( 'x', 0 );
6176     aRectElement.setAttribute( 'y', 0 );
6177     aRectElement.setAttribute( 'width', WIDTH );
6178     aRectElement.setAttribute( 'height', HEIGHT );
6179     aRectElement.setAttribute( 'style', 'stroke:none;fill:' + sPageBgColor );
6181     aPageElement.appendChild( aRectElement );
6182     // The index page is appended after all slide elements
6183     // so when it is displayed it covers them all
6184     ROOT_NODE.appendChild( aPageElement );
6185     return( document.getElementById( this.pageElementId ) );
6188 SlideIndexPage.prototype.createTemplateElements = function()
6190     // We define a Rect element as a template of thumbnail border for all slide-thumbnails.
6191     // The stroke color is defined individually by each thumbnail according to
6192     // its selection status.
6193     var aDefsElement = document.createElementNS( NSS['svg'], 'defs' );
6194     var aRectElement = document.createElementNS( NSS['svg'], 'rect' );
6195     aRectElement.setAttribute( 'id', this.thumbnailBorderTemplateId );
6196     aRectElement.setAttribute( 'x', -this.halfBorderWidth );
6197     aRectElement.setAttribute( 'y', -this.halfBorderWidth );
6198     aRectElement.setAttribute( 'rx', this.halfBorderWidth );
6199     aRectElement.setAttribute( 'ry', this.halfBorderWidth );
6200     aRectElement.setAttribute( 'width', WIDTH + this.halfBorderWidth );
6201     aRectElement.setAttribute( 'height', HEIGHT + this.halfBorderWidth );
6202     aRectElement.setAttribute( 'stroke-width', this.borderWidth );
6203     aRectElement.setAttribute( 'fill', 'none' );
6204     aDefsElement.appendChild( aRectElement );
6206     // We define a Rect element as a template of mouse area for triggering the mouseover event.
6207     // A copy is used by each thumbnail element.
6208     aRectElement = document.createElementNS( NSS['svg'], 'rect' );
6209     aRectElement.setAttribute( 'id', this.thumbnailMouseAreaTemplateId );
6210     aRectElement.setAttribute( 'x', 0 );
6211     aRectElement.setAttribute( 'y', 0 );
6212     aRectElement.setAttribute( 'width', WIDTH );
6213     aRectElement.setAttribute( 'height', HEIGHT );
6214     aRectElement.setAttribute( 'fill', this.pageBgColor );
6215     aDefsElement.appendChild( aRectElement );
6217     this.pageElement.appendChild( aDefsElement );
6219     this.thumbnailMouseAreaTemplateElement = document.getElementById( this.thumbnailMouseAreaTemplateId );
6220     this.thumbnailBorderTemplateElement = document.getElementById( this.thumbnailBorderTemplateId );
6223 SlideIndexPage.prototype.decreaseNumberOfColumns  = function()
6225     this.setNumberOfColumns( this.indexColumns - 1 );
6228 SlideIndexPage.prototype.increaseNumberOfColumns  = function()
6230     this.setNumberOfColumns( this.indexColumns + 1 );
6233 SlideIndexPage.prototype.resetNumberOfColumns  = function()
6235     this.setNumberOfColumns( INDEX_COLUMNS_DEFAULT );
6238 /** setNumberOfColumns
6240  * Change the size of the thumbnail grid.
6242  * @param nNumberOfColumns - the new number of columns/rows of the thumbnail grid
6243  */
6244 SlideIndexPage.prototype.setNumberOfColumns  = function( nNumberOfColumns )
6246     if( this.indexColumns == nNumberOfColumns )  return;
6247     if( nNumberOfColumns < 2 || nNumberOfColumns > 6 ) return;
6249     var suspendHandle = ROOT_NODE.suspendRedraw(500);
6251     var nOldTotalThumbnails = this.totalThumbnails;
6252     this.indexColumns = nNumberOfColumns;
6253     this.totalThumbnails = nNumberOfColumns * nNumberOfColumns;
6255     this.aThumbnailSet[this.curThumbnailIndex].unselect();
6257     // if we decreased the number of used columns we remove the exceeding thumbnail elements
6258     var i;
6259     for( i = this.totalThumbnails; i < nOldTotalThumbnails; ++i )
6260     {
6261         this.aThumbnailSet[i].removeElement();
6262     }
6264     // if we increased the number of used columns we create the needed thumbnail objects
6265     for( i = nOldTotalThumbnails; i < this.totalThumbnails; ++i )
6266     {
6267         this.aThumbnailSet[i] = new Thumbnail( this, i );
6268     }
6270     // we set up layout parameters that depend on the number of columns
6271     this.halfBorderWidthFactor = ( 300/28000 ) * ( this.indexColumns / 3 );
6272     this.halfBorderWidth = WIDTH * this.halfBorderWidthFactor;
6273     this.borderWidth = 2 * this.halfBorderWidth;
6274     // scaleFactor = ( WIDTH - ( columns + 1 ) * xSpacing ) / ( columns * ( WIDTH + borderWidth ) )
6275     this.scaleFactor = ( 1 - ( this.indexColumns + 1 ) * this.xSpacingFactor ) /
6276                             ( this.indexColumns * ( 1 + 2 * this.halfBorderWidthFactor ) );
6278     // update the thumbnail border size
6279     var aRectElement = this.thumbnailBorderTemplateElement;
6280     aRectElement.setAttribute( 'x', -this.halfBorderWidth );
6281     aRectElement.setAttribute( 'y', -this.halfBorderWidth );
6282     aRectElement.setAttribute( 'rx', this.halfBorderWidth );
6283     aRectElement.setAttribute( 'ry', this.halfBorderWidth );
6284     aRectElement.setAttribute( 'width', WIDTH + this.halfBorderWidth );
6285     aRectElement.setAttribute( 'height', HEIGHT + this.halfBorderWidth );
6286     aRectElement.setAttribute( 'stroke-width', this.borderWidth );
6288     // now we update the displacement on the index page of each thumbnail (old and new)
6289     for( i = 0; i < this.totalThumbnails; ++i )
6290     {
6291         this.aThumbnailSet[i].updateView();
6292     }
6294     this.curThumbnailIndex = this.selectedSlideIndex % this.totalThumbnails;
6295     this.aThumbnailSet[this.curThumbnailIndex].select();
6297     // needed for forcing the indexSetPageSlide routine to update the INDEX_OFFSET
6298     INDEX_OFFSET = -1;
6299     indexSetPageSlide( this.selectedSlideIndex );
6301     ROOT_NODE.unsuspendRedraw( suspendHandle );
6302     ROOT_NODE.forceRedraw();
6306 /** Class Thumbnail **
6307  *  This class handles a slide thumbnail.
6308  */
6309 function Thumbnail( aSlideIndexPage, nIndex )
6311     this.container = aSlideIndexPage;
6312     this.index = nIndex;//= getSafeIndex( nIndex, 0, this.container.getTotalThumbnails() );
6313     this.pageElement = this.container.pageElement;
6314     this.thumbnailId = 'thumbnail' + this.index;
6315     this.thumbnailElement = this.createThumbnailElement();
6316     this.slideElement = getElementByClassName( this.thumbnailElement, 'Slide' );
6317     this.borderElement = getElementByClassName( this.thumbnailElement, 'Border' );
6318     this.mouseAreaElement = getElementByClassName( this.thumbnailElement, 'MouseArea' );
6319     this.aTransformSet = new Array( 3 );
6320     this.visibility = VISIBLE;
6321     this.isSelected = false;
6324 /* static const class member */
6325 Thumbnail.prototype.sNormalBorderColor = 'rgb(216,216,216)';
6326 Thumbnail.prototype.sSelectionBorderColor = 'rgb(92,92,255)';
6328 /* public methods */
6329 Thumbnail.prototype.removeElement = function()
6331     if( this.thumbnailElement )
6332         this.container.pageElement.removeChild( this.thumbnailElement );
6335 Thumbnail.prototype.show = function()
6337     if( this.visibility == HIDDEN )
6338     {
6339         this.thumbnailElement.setAttribute( 'display', 'inherit' );
6340         this.visibility = VISIBLE;
6341     }
6344 Thumbnail.prototype.hide = function()
6346     if( this.visibility == VISIBLE )
6347     {
6348         this.thumbnailElement.setAttribute( 'display', 'none' );
6349         this.visibility = HIDDEN;
6350     }
6353 Thumbnail.prototype.select = function()
6355     if( !this.isSelected )
6356     {
6357         this.borderElement.setAttribute( 'stroke', this.sSelectionBorderColor );
6358         this.isSelected = true;
6359     }
6362 Thumbnail.prototype.unselect = function()
6364     if( this.isSelected )
6365     {
6366         this.borderElement.setAttribute( 'stroke', this.sNormalBorderColor );
6367         this.isSelected = false;
6368     }
6371 /** updateView
6373  *  This method updates the displacement of the thumbnail on the slide index page,
6374  *  the value of the row, column coordinates of the thumbnail in the grid, and
6375  *  the onmouseover property of the thumbnail element.
6377  */
6378 Thumbnail.prototype.updateView = function()
6380     this.column = this.index % this.container.indexColumns;
6381     this.row = ( this.index - this.column ) / this.container.indexColumns;
6382     this.halfBorderWidth = this.container.halfBorderWidth;
6383     this.borderWidth = this.container.borderWidth;
6384     this.width = ( WIDTH + this.borderWidth ) * this.container.scaleFactor;
6385     this.height = ( HEIGHT + this.borderWidth ) * this.container.scaleFactor;
6386     this.aTransformSet[2] = 'translate(' + this.halfBorderWidth + ' ' + this.halfBorderWidth + ')';
6387     this.aTransformSet[1] = 'scale(' + this.container.scaleFactor + ')';
6388     var sTransformAttrValue = this.computeTransform();
6389     this.thumbnailElement.setAttribute( 'transform', sTransformAttrValue );
6390     this.mouseAreaElement.setAttribute( 'onmouseover', 'theSlideIndexPage.aThumbnailSet[' + this.index  + '].onMouseOver()' );
6393 /** update
6395  * This method update the content of the thumbnail view
6397  * @param nIndex - the index of the slide to be shown in the thumbnail
6398  */
6399 Thumbnail.prototype.update = function( nIndex )
6401     if( this.slideIndex == nIndex )  return;
6403     var aMetaSlide = theMetaDoc.aMetaSlideSet[nIndex];
6404     aMetaSlide.updateMasterPageView();
6405     setNSAttribute( 'xlink', this.slideElement, 'href', '#' + aMetaSlide.slideId );
6406     this.slideIndex = nIndex;
6409 Thumbnail.prototype.clear = function( )
6411     setNSAttribute( 'xlink', this.slideElement, 'href', '' );
6414 /* private methods */
6415 Thumbnail.prototype.createThumbnailElement = function()
6417     var aThumbnailElement = document.createElementNS( NSS['svg'], 'g' );
6418     aThumbnailElement.setAttribute( 'id', this.thumbnailId );
6419     aThumbnailElement.setAttribute( 'display', 'inherit' );
6421     var aSlideElement = document.createElementNS( NSS['svg'], 'use' );
6422     setNSAttribute( 'xlink', aSlideElement, 'href', '' );
6423     aSlideElement.setAttribute( 'class', 'Slide' );
6424     aThumbnailElement.appendChild( aSlideElement );
6426     var aMouseAreaElement = document.createElementNS( NSS['svg'], 'use' );
6427     setNSAttribute( 'xlink', aMouseAreaElement, 'href', '#' + this.container.thumbnailMouseAreaTemplateId );
6428     aMouseAreaElement.setAttribute( 'class', 'MouseArea' );
6429     aMouseAreaElement.setAttribute( 'opacity', 0.0 );
6430     aThumbnailElement.appendChild( aMouseAreaElement );
6432     var aBorderElement = document.createElementNS( NSS['svg'], 'use' );
6433     setNSAttribute( 'xlink', aBorderElement, 'href', '#' + this.container.thumbnailBorderTemplateId );
6434     aBorderElement.setAttribute( 'stroke', this.sNormalBorderColor );
6435     aBorderElement.setAttribute( 'class', 'Border' );
6436     aThumbnailElement.appendChild( aBorderElement );
6438     this.container.pageElement.appendChild( aThumbnailElement );
6439     return( document.getElementById( this.thumbnailId ) );
6442 Thumbnail.prototype.computeTransform = function()
6444     var nXSpacing = this.container.xSpacing;
6445     var nYSpacing = this.container.ySpacing;
6447     var nXOffset = nXSpacing + ( this.width + nXSpacing ) * this.column;
6448     var nYOffset = nYSpacing + ( this.height + nYSpacing ) * this.row;
6450     this.aTransformSet[0] = 'translate(' + nXOffset + ' ' + nYOffset + ')';
6452     var sTransform = this.aTransformSet.join( ' ' );
6454     return sTransform;
6457 Thumbnail.prototype.onMouseOver = function()
6459     if( ( currentMode == INDEX_MODE ) && ( this.container.curThumbnailIndex !=  this.index ) )
6460     {
6461         this.container.setSelection( this.index );
6462     }
6469 /** Initialization function.
6470  *  The whole presentation is set-up in this function.
6471  */
6472 function init()
6474     var VIEWBOX = ROOT_NODE.getAttribute('viewBox');
6476     if( VIEWBOX )
6477     {
6478         WIDTH = ROOT_NODE.viewBox.animVal.width;
6479         HEIGHT = ROOT_NODE.viewBox.animVal.height;
6480     }
6482     aSlideShow = new SlideShow();
6483     theMetaDoc =  new MetaDocument();
6484     aSlideShow.bIsEnabled = theMetaDoc.bIsAnimated;
6485     theSlideIndexPage = new SlideIndexPage();
6486     aSlideShow.displaySlide( theMetaDoc.nStartSlideNumber, false );
6488     // Allow slide switching with swipe gestures left
6489     // and right. Swiping up or down will exit the slideshow.
6490     var hammer = new Hammer(ROOT_NODE);
6491     hammer.on('swipeleft', function() {
6492         switchSlide(1, false);
6493     });
6494     hammer.on('swiperight', function() {
6495         switchSlide(-1, false);
6496     });
6497     hammer.get('swipe').set({ direction: Hammer.DIRECTION_ALL });
6498     hammer.on('swipeup', function() {
6499         aSlideShow.exitSlideShowInApp();
6500     });
6501     hammer.on('swipedown', function() {
6502         aSlideShow.exitSlideShowInApp();
6503     });
6506 function presentationEngineStop(message)
6508     alert( message + '\nThe presentation engine will be stopped' );
6509     document.onkeydown = null;
6510     document.onkeypress = null;
6511     document.onclick = null;
6512     window.onmousewheel = null;
6515 function assert( condition, message )
6517     if (!condition)
6518     {
6519         presentationEngineStop( message );
6520         if (typeof console == 'object')
6521             // eslint-disable-next-line no-console
6522             console.trace();
6523         throw new Error( message );
6524     }
6527 function dispatchEffects(dir)
6529     // TODO to be implemented
6531     if( dir == 1 )
6532     {
6533         var bRet = aSlideShow.nextEffect();
6535         if( !bRet )
6536         {
6537             switchSlide( 1, false );
6538         }
6539     }
6540     else
6541     {
6542         switchSlide( dir, false );
6543     }
6546 function skipAllEffects()
6548     var bRet = aSlideShow.skipAllEffects();
6549     if( !bRet )
6550     {
6551         switchSlide( 1, true );
6552     }
6555 function skipEffects(dir)
6557     if( dir == 1 )
6558     {
6559         var bRet = aSlideShow.skipPlayingOrNextEffect();
6561         if( !bRet )
6562         {
6563             switchSlide( 1, true );
6564         }
6565     }
6566     else
6567     {
6568         switchSlide( dir, true );
6569     }
6572 function switchSlide( nOffset, bSkipTransition )
6574     var nNextSlide = nCurSlide + nOffset;
6575     aSlideShow.displaySlide( nNextSlide, bSkipTransition );
6578 /** Function to display the index sheet.
6580  *  @param offsetNumber offset number
6581  */
6582 function displayIndex( offsetNumber )
6584     var aMetaSlideSet = theMetaDoc.aMetaSlideSet;
6585     offsetNumber = getSafeIndex( offsetNumber, 0, aMetaSlideSet.length - 1 );
6587     var nTotalThumbnails = theSlideIndexPage.getTotalThumbnails();
6588     var nEnd = Math.min( offsetNumber + nTotalThumbnails, aMetaSlideSet.length);
6590     var aThumbnailSet = theSlideIndexPage.aThumbnailSet;
6591     var j = 0;
6592     for( var i = offsetNumber; i < nEnd; ++i, ++j )
6593     {
6594         aThumbnailSet[j].update( i );
6595         aThumbnailSet[j].show();
6596     }
6597     for( ; j < nTotalThumbnails; ++j )
6598     {
6599         aThumbnailSet[j].hide();
6600     }
6602     //do we need to save the current offset?
6603     if (INDEX_OFFSET != offsetNumber)
6604         INDEX_OFFSET = offsetNumber;
6607 /** Function to toggle between index and slide mode.
6608  */
6609 function toggleSlideIndex()
6611     if( currentMode == SLIDE_MODE )
6612     {
6614         theMetaDoc.getCurrentSlide().hide();
6615         INDEX_OFFSET = -1;
6616         indexSetPageSlide( nCurSlide );
6617         theSlideIndexPage.show();
6618         currentMode = INDEX_MODE;
6619     }
6620     else if( currentMode == INDEX_MODE )
6621     {
6622         theSlideIndexPage.hide();
6623         var nNewSlide = theSlideIndexPage.selectedSlideIndex;
6625         aSlideShow.displaySlide( nNewSlide, true );
6626         currentMode = SLIDE_MODE;
6627     }
6630 /** Function that exit from the index mode without changing the shown slide
6632  */
6633 function abandonIndexMode()
6635     theSlideIndexPage.selectedSlideIndex = nCurSlide;
6636     toggleSlideIndex();
6643 /*********************************************************************************************
6644  *********************************************************************************************
6645  *********************************************************************************************
6647                                   ***** ANIMATION ENGINE *****
6649  *********************************************************************************************
6650  *********************************************************************************************
6651  *********************************************************************************************/
6658 // helper functions
6661 var CURR_UNIQUE_ID = 0;
6663 function getUniqueId()
6665     ++CURR_UNIQUE_ID;
6666     return CURR_UNIQUE_ID;
6669 function mem_fn( sMethodName )
6671     return  function( aObject )
6672     {
6673         var aMethod = aObject[ sMethodName ];
6674         if( aMethod )
6675             aMethod.call( aObject );
6676         else
6677             log( 'method sMethodName not found' );
6678     };
6681 function bind( aObject, aMethod )
6683     return  function()
6684     {
6685         return aMethod.call( aObject, arguments[0] );
6686     };
6689 function bind2( aFunction )
6691     if( !aFunction  )
6692         log( 'bind2: passed function is not valid.' );
6694     var aBoundArgList = arguments;
6696     var aResultFunction = null;
6698     switch( aBoundArgList.length )
6699     {
6700         case 1: aResultFunction = function()
6701                 {
6702                     return aFunction.call( arguments[0], arguments[1],
6703                                            arguments[2], arguments[3],
6704                                            arguments[4] );
6705                 };
6706                 break;
6707         case 2: aResultFunction = function()
6708                 {
6709                     return aFunction.call( aBoundArgList[1], arguments[0],
6710                                            arguments[1], arguments[2],
6711                                            arguments[3] );
6712                 };
6713                 break;
6714         case 3: aResultFunction = function()
6715                 {
6716                     return aFunction.call( aBoundArgList[1], aBoundArgList[2],
6717                                            arguments[0], arguments[1],
6718                                            arguments[2] );
6719                 };
6720                 break;
6721         case 4: aResultFunction = function()
6722                 {
6723                     return aFunction.call( aBoundArgList[1], aBoundArgList[2],
6724                                            aBoundArgList[3], arguments[0],
6725                                            arguments[1] );
6726                 };
6727                 break;
6728         case 5: aResultFunction = function()
6729                 {
6730                     return aFunction.call( aBoundArgList[1], aBoundArgList[2],
6731                                            aBoundArgList[3], aBoundArgList[4],
6732                                            arguments[0] );
6733                 };
6734                 break;
6735         default:
6736             log( 'bind2: arity not handled.' );
6737     }
6739     return aResultFunction;
6742 function getCurrentSystemTime()
6744     return ( new Date() ).getTime();
6747 // eslint-disable-next-line no-unused-vars
6748 function getSlideAnimationsRoot( sSlideId )
6750     return theMetaDoc.aSlideAnimationsMap[ sSlideId ];
6753 /** This function return an array populated with all children nodes of the
6754  *  passed element that are elements
6756  *  @param aElement   any XML element
6758  *  @returns   Array
6759  *      an array that contains all children elements
6760  */
6761 function getElementChildren( aElement )
6763     var aChildrenArray = [];
6765     var nSize = aElement.childNodes.length;
6767     for( var i = 0; i < nSize; ++i )
6768     {
6769         if( aElement.childNodes[i].nodeType == 1 )
6770             aChildrenArray.push( aElement.childNodes[i] );
6771     }
6773     return aChildrenArray;
6776 function removeWhiteSpaces( str )
6778     if( !str )
6779         return '';
6781     var re = / */;
6782     var aSplitString = str.split( re );
6783     return aSplitString.join('');
6786 function clamp( nValue, nMinimum, nMaximum )
6788     if( nValue < nMinimum )
6789     {
6790         return nMinimum;
6791     }
6792     else if( nValue > nMaximum )
6793     {
6794         return nMaximum;
6795     }
6796     else
6797     {
6798         return nValue;
6799     }
6802 function makeMatrixString( a, b, c, d, e, f )
6804     var s = 'matrix(';
6805     s += a + ', ';
6806     s += b + ', ';
6807     s += c + ', ';
6808     s += d + ', ';
6809     s += e + ', ';
6810     s += f + ')';
6812     return s;
6815 // eslint-disable-next-line no-unused-vars
6816 function matrixToString( aSVGMatrix )
6818     return makeMatrixString( aSVGMatrix.a, aSVGMatrix.b, aSVGMatrix.c,
6819                              aSVGMatrix.d, aSVGMatrix.e, aSVGMatrix.f );
6825 // Attribute Parsers
6827 // eslint-disable-next-line no-unused-vars
6828 function numberParser( sValue )
6830     if( sValue === '.' )
6831         return undefined;
6832     var reFloatNumber = /^[+-]?[0-9]*[.]?[0-9]*$/;
6834     if( reFloatNumber.test( sValue ) )
6835         return parseFloat( sValue );
6836     else
6837         return undefined;
6840 function booleanParser( sValue )
6842     sValue = sValue.toLowerCase();
6843     if( sValue === 'true' )
6844         return true;
6845     else if( sValue === 'false' )
6846         return false;
6847     else
6848         return undefined;
6851 function colorParser( sValue )
6853     // The following 3 color functions are used in evaluating sValue string
6854     // so don't remove them.
6856     // eslint-disable-next-line no-unused-vars
6857     function hsl( nHue, nSaturation, nLuminance )
6858     {
6859         return new HSLColor( nHue, nSaturation / 100, nLuminance / 100 );
6860     }
6862     // eslint-disable-next-line no-unused-vars
6863     function rgb( nRed, nGreen, nBlue )
6864     {
6865         return new RGBColor( nRed / 255, nGreen / 255, nBlue / 255 );
6866     }
6868     // eslint-disable-next-line no-unused-vars
6869     function prgb( nRed, nGreen, nBlue )
6870     {
6871         return new RGBColor( nRed / 100, nGreen / 100, nBlue / 100 );
6872     }
6874     var sCommaPattern = ' *[,] *';
6875     var sIntegerPattern = '[+-]?[0-9]+';
6876     var sHexDigitPattern = '[0-9A-Fa-f]';
6878     var sHexColorPattern = '#(' + sHexDigitPattern + '{2})('
6879                                 + sHexDigitPattern + '{2})('
6880                                 + sHexDigitPattern + '{2})';
6882     var sRGBIntegerPattern = 'rgb[(] *' + sIntegerPattern + sCommaPattern
6883                                         + sIntegerPattern + sCommaPattern
6884                                         + sIntegerPattern + ' *[)]';
6886     var sRGBPercentPattern = 'rgb[(] *' + sIntegerPattern + '%' + sCommaPattern
6887                                         + sIntegerPattern + '%' + sCommaPattern
6888                                         + sIntegerPattern + '%' + ' *[)]';
6890     var sHSLPercentPattern = 'hsl[(] *' + sIntegerPattern + sCommaPattern
6891                                         + sIntegerPattern + '%' + sCommaPattern
6892                                         + sIntegerPattern + '%' + ' *[)]';
6894     var reHexColor = new RegExp( sHexColorPattern );
6895     var reRGBInteger = new RegExp( sRGBIntegerPattern );
6896     var reRGBPercent = new RegExp( sRGBPercentPattern );
6897     var reHSLPercent = new RegExp( sHSLPercentPattern );
6899     if( reHexColor.test( sValue ) )
6900     {
6901         var aRGBTriple = reHexColor.exec( sValue );
6903         var nRed    = parseInt( aRGBTriple[1], 16 ) / 255;
6904         var nGreen  = parseInt( aRGBTriple[2], 16 ) / 255;
6905         var nBlue   = parseInt( aRGBTriple[3], 16 ) / 255;
6907         return new RGBColor( nRed, nGreen, nBlue );
6908     }
6909     else if( reHSLPercent.test( sValue ) )
6910     {
6911         sValue = sValue.replace( '%', '' ).replace( '%', '' );
6912         return eval( sValue );
6913     }
6914     else if( reRGBInteger.test( sValue ) )
6915     {
6916         return eval( sValue );
6917     }
6918     else if( reRGBPercent.test( sValue ) )
6919     {
6920         sValue = 'p' + sValue.replace( '%', '' ).replace( '%', '' ).replace( '%', '' );
6921         return eval( sValue );
6922     }
6923     else
6924     {
6925         return null;
6926     }
6931 /**********************************************************************************************
6932  *      RGB and HSL color classes
6933  **********************************************************************************************/
6936 function RGBColor( nRed, nGreen, nBlue )
6938     this.eColorSpace = COLOR_SPACE_RGB;
6939     // values in the [0,1] range
6940     this.nRed = nRed;
6941     this.nGreen = nGreen;
6942     this.nBlue = nBlue;
6946 RGBColor.prototype.clone = function()
6948     return new RGBColor( this.nRed, this.nGreen, this.nBlue );
6951 RGBColor.prototype.equal = function( aRGBColor )
6953     return ( this.nRed == aRGBColor.nRed ) &&
6954            ( this.nGreen == aRGBColor.nGreen ) &&
6955            ( this.nBlue == aRGBColor.nBlue );
6958 RGBColor.prototype.add = function( aRGBColor )
6960     this.nRed += aRGBColor.nRed;
6961     this.nGreen += aRGBColor.nGreen;
6962     this.nBlue += aRGBColor.nBlue;
6963     return this;
6966 RGBColor.prototype.scale = function( aT )
6968     this.nRed *= aT;
6969     this.nGreen *= aT;
6970     this.nBlue *= aT;
6971     return this;
6974 RGBColor.clamp = function( aRGBColor )
6976     var aClampedRGBColor = new RGBColor( 0, 0, 0 );
6978     aClampedRGBColor.nRed   = clamp( aRGBColor.nRed, 0.0, 1.0 );
6979     aClampedRGBColor.nGreen = clamp( aRGBColor.nGreen, 0.0, 1.0 );
6980     aClampedRGBColor.nBlue  = clamp( aRGBColor.nBlue, 0.0, 1.0 );
6982     return aClampedRGBColor;
6985 RGBColor.prototype.convertToHSL = function()
6987     var nRed   = clamp( this.nRed, 0.0, 1.0 );
6988     var nGreen = clamp( this.nGreen, 0.0, 1.0 );
6989     var nBlue  = clamp( this.nBlue, 0.0, 1.0 );
6991     var nMax = Math.max( nRed, nGreen, nBlue );
6992     var nMin = Math.min( nRed, nGreen, nBlue );
6993     var nDelta = nMax - nMin;
6995     var nLuminance  = ( nMax + nMin ) / 2.0;
6996     var nSaturation = 0.0;
6997     var nHue = 0.0;
6998     if( nDelta !== 0 )
6999     {
7000         nSaturation = ( nLuminance > 0.5 ) ?
7001                             ( nDelta / ( 2.0 - nMax - nMin) ) :
7002                             ( nDelta / ( nMax + nMin ) );
7004         if( nRed == nMax )
7005             nHue = ( nGreen - nBlue ) / nDelta;
7006         else if( nGreen == nMax )
7007             nHue = 2.0 + ( nBlue - nRed ) / nDelta;
7008         else if( nBlue == nMax )
7009             nHue = 4.0 + ( nRed - nGreen ) / nDelta;
7011         nHue *= 60.0;
7013         if( nHue < 0.0 )
7014             nHue += 360.0;
7015     }
7017     return new HSLColor( nHue, nSaturation, nLuminance );
7021 RGBColor.prototype.toString = function( bClamped )
7023     var aRGBColor;
7024     if( bClamped )
7025     {
7026         aRGBColor = RGBColor.clamp( this );
7027     }
7028     else
7029     {
7030         aRGBColor = this;
7031     }
7033     var nRed = Math.round( aRGBColor.nRed * 255 );
7034     var nGreen = Math.round( aRGBColor.nGreen * 255 );
7035     var nBlue = Math.round( aRGBColor.nBlue * 255 );
7037     return ( 'rgb(' + nRed + ',' + nGreen + ',' + nBlue + ')' );
7040 RGBColor.interpolate = function( aStartRGB , aEndRGB, nT )
7042     var aResult = aStartRGB.clone();
7043     var aTEndRGB = aEndRGB.clone();
7044     aResult.scale( 1.0 - nT );
7045     aTEndRGB.scale( nT );
7046     aResult.add( aTEndRGB );
7048     return aResult;
7054 function HSLColor( nHue, nSaturation, nLuminance )
7056     this.eColorSpace = COLOR_SPACE_HSL;
7057     // Hue is in the [0,360[ range, Saturation and Luminance are in the [0,1] range
7058     this.nHue = nHue;
7059     this.nSaturation = nSaturation;
7060     this.nLuminance = nLuminance;
7062     this.normalizeHue();
7066 HSLColor.prototype.clone = function()
7068     return new HSLColor( this.nHue, this.nSaturation, this.nLuminance );
7071 HSLColor.prototype.equal = function( aHSLColor )
7073     return ( this.nHue == aHSLColor.nHue ) &&
7074            ( this.nSaturation += aHSLColor.nSaturation ) &&
7075            ( this.nLuminance += aHSLColor.nLuminance );
7078 HSLColor.prototype.add = function( aHSLColor )
7080     this.nHue += aHSLColor.nHue;
7081     this.nSaturation += aHSLColor.nSaturation;
7082     this.nLuminance += aHSLColor.nLuminance;
7083     this.normalizeHue();
7084     return this;
7087 HSLColor.prototype.scale = function( aT )
7089     this.nHue *= aT;
7090     this.nSaturation *= aT;
7091     this.nLuminance *= aT;
7092     this.normalizeHue();
7093     return this;
7096 HSLColor.clamp = function( aHSLColor )
7098     var aClampedHSLColor = new HSLColor( 0, 0, 0 );
7100     aClampedHSLColor.nHue = aHSLColor.nHue % 360;
7101     if( aClampedHSLColor.nHue < 0 )
7102         aClampedHSLColor.nHue += 360;
7103     aClampedHSLColor.nSaturation = clamp( aHSLColor.nSaturation, 0.0, 1.0 );
7104     aClampedHSLColor.nLuminance = clamp( aHSLColor.nLuminance, 0.0, 1.0 );
7107 HSLColor.prototype.normalizeHue = function()
7109     this.nHue = this.nHue % 360;
7110     if( this.nHue < 0 ) this.nHue += 360;
7113 HSLColor.prototype.toString = function()
7115     return 'hsl(' + this.nHue.toFixed( 3 ) + ','
7116                   + this.nSaturation.toFixed( 3 ) + ','
7117                   + this.nLuminance.toFixed( 3 ) + ')';
7120 HSLColor.prototype.convertToRGB = function()
7123     var nHue = this.nHue % 360;
7124     if( nHue < 0 ) nHue += 360;
7125     var nSaturation =  clamp( this.nSaturation, 0.0, 1.0 );
7126     var nLuminance = clamp( this.nLuminance, 0.0, 1.0 );
7129     if( nSaturation === 0 )
7130     {
7131         return new RGBColor( nLuminance, nLuminance, nLuminance );
7132     }
7134     var nVal1 = ( nLuminance <= 0.5 ) ?
7135                         ( nLuminance * (1.0 + nSaturation) ) :
7136                         ( nLuminance + nSaturation - nLuminance * nSaturation );
7138     var nVal2 = 2.0 * nLuminance - nVal1;
7140     var nRed    = HSLColor.hsl2rgbHelper( nVal2, nVal1, nHue + 120 );
7141     var nGreen  = HSLColor.hsl2rgbHelper( nVal2, nVal1, nHue );
7142     var nBlue   = HSLColor.hsl2rgbHelper( nVal2, nVal1, nHue - 120 );
7144     return new RGBColor( nRed, nGreen, nBlue );
7147 HSLColor.hsl2rgbHelper = function( nValue1, nValue2, nHue )
7149     nHue = nHue % 360;
7150     if( nHue < 0 )
7151         nHue += 360;
7153     if( nHue < 60.0 )
7154         return nValue1 + ( nValue2 - nValue1 ) * nHue / 60.0;
7155     else if( nHue < 180.0 )
7156         return nValue2;
7157     else if( nHue < 240.0 )
7158         return ( nValue1 + ( nValue2 - nValue1 ) * ( 240.0 - nHue ) / 60.0 );
7159     else
7160         return nValue1;
7163 HSLColor.interpolate = function( aFrom, aTo, nT, bCCW )
7165     var nS = 1.0 - nT;
7167     var nHue = 0.0;
7168     if( aFrom.nHue <= aTo.nHue && !bCCW )
7169     {
7170         // interpolate hue clockwise. That is, hue starts at
7171         // high values and ends at low ones. Therefore, we
7172         // must 'cross' the 360 degrees and start at low
7173         // values again (imagine the hues to lie on the
7174         // circle, where values above 360 degrees are mapped
7175         // back to [0,360)).
7176         nHue = nS * (aFrom.nHue + 360.0) + nT * aTo.nHue;
7177     }
7178     else if( aFrom.nHue > aTo.nHue && bCCW )
7179     {
7180         // interpolate hue counter-clockwise. That is, hue
7181         // starts at high values and ends at low
7182         // ones. Therefore, we must 'cross' the 360 degrees
7183         // and start at low values again (imagine the hues to
7184         // lie on the circle, where values above 360 degrees
7185         // are mapped back to [0,360)).
7186         nHue = nS * aFrom.nHue + nT * (aTo.nHue + 360.0);
7187     }
7188     else
7189     {
7190         // interpolate hue counter-clockwise. That is, hue
7191         // starts at low values and ends at high ones (imagine
7192         // the hue value as degrees on a circle, with
7193         // increasing values going counter-clockwise)
7194         nHue = nS * aFrom.nHue + nT * aTo.nHue;
7195     }
7197     var nSaturation = nS * aFrom.nSaturation + nT * aTo.nSaturation;
7198     var nLuminance = nS * aFrom.nLuminance + nT * aTo.nLuminance;
7200     return new HSLColor( nHue, nSaturation, nLuminance );
7205 /**********************************************************************************************
7206  *      SVGMatrix extensions
7207  **********************************************************************************************/
7209 var SVGIdentityMatrix = document.documentElement.createSVGMatrix();
7211 SVGMatrix.prototype.setToIdentity = function()
7213     this.a = this.d = 1;
7214     this.b = this.c = this.d = this.e = 0;
7217 SVGMatrix.prototype.setToRotationAroundPoint = function( nX, nY, nAngle )
7219     // convert to radians
7220     nAngle = Math.PI * nAngle / 180;
7221     var nSin = Math.sin( nAngle );
7222     var nCos = Math.cos( nAngle );
7224     this.a = nCos; this.c = -nSin; this.e = nX * (1 - nCos) + nY * nSin;
7225     this.b = nSin; this.d =  nCos; this.f = nY * (1 - nCos) - nX * nSin;
7230 /**********************************************************************************************
7231  *      SVGPath extensions
7232  **********************************************************************************************/
7234 /** SVGPathElement.prependPath
7235  *  Merge the two path together by inserting the passed path before this one.
7237  *  @param aPath
7238  *      An object of type SVGPathElement to be prepended.
7239  */
7240 SVGPathElement.prototype.prependPath = function( aPath )
7242     var sPathData = aPath.getAttribute( 'd' );
7243     sPathData += ( ' ' + this.getAttribute( 'd' ) );
7244     this.setAttribute( 'd', sPathData );
7247 /** SVGPathElement.appendPath
7248  *  Merge the two path together by inserting the passed path after this one.
7250  *  @param aPath
7251  *      An object of type SVGPathElement to be appended.
7252  */
7253 SVGPathElement.prototype.appendPath = function( aPath )
7255     var sPathData = this.getAttribute( 'd' );
7256     sPathData += ( ' ' + aPath.getAttribute( 'd' ) );
7257     this.setAttribute( 'd', sPathData );
7260 /** flipOnYAxis
7261  *  Flips the SVG Path element along y-axis.
7263  *  @param aPath
7264  *      An object of type SVGPathElement to be flipped.
7265  */
7266 function flipOnYAxis( aPath )
7268     var aPolyPath = aPath.cloneNode(true);
7269     var aTransform = document.documentElement.createSVGMatrix();
7270     aTransform.a = -1;
7271     aTransform.e = 1;
7272     aPolyPath.matrixTransform(aTransform);
7273     return aPolyPath;
7276 /** flipOnXAxis
7277  *  Flips the SVG Path element along x-axis
7279  *  @param aPath
7280  *      An object of type SVGPathElement to be flipped
7281  */
7282 function flipOnXAxis( aPath )
7284     var aPolyPath = aPath.cloneNode(true);
7285     var aTransform = document.documentElement.createSVGMatrix();
7286     aTransform.d = -1;
7287     aTransform.f = 1;
7288     aPolyPath.matrixTransform(aTransform);
7289     return aPolyPath;
7292 /** SVGPathElement.matrixTransform
7293  *  Apply the transformation defined by the passed matrix to the referenced
7294  *  svg <path> element.
7295  *  After the transformation 'this' element is modified in order to reference
7296  *  the transformed path.
7298  *  @param aSVGMatrix
7299  *      An SVGMatrix instance.
7300  */
7301 SVGPathElement.prototype.matrixTransform = function( aSVGMatrix )
7303     if( SVGPathSegList.prototype.matrixTransform )
7304     {
7305         this.pathSegList.matrixTransform( aSVGMatrix );
7306         return;
7307     }
7309     var aPathSegList = this.pathSegList;
7310     var nLength = aPathSegList.numberOfItems;
7311     var i;
7312     for( i = 0; i < nLength; ++i )
7313     {
7314         aPathSegList.getItem( i ).matrixTransform( aSVGMatrix );
7315     }
7318 /** SVGPathElement.changeOrientation
7319  *  Invert the path orientation by inverting the path command list.
7321  */
7322 SVGPathElement.prototype.changeOrientation = function()
7324     var aPathSegList = this.pathSegList;
7325     var nLength = aPathSegList.numberOfItems;
7326     if( nLength == 0 ) return;
7328     if( SVGPathSegList.prototype.changeOrientation )
7329     {
7330         aPathSegList.changeOrientation();
7331         return;
7332     }
7334     var nCurrentX = 0;
7335     var nCurrentY = 0;
7337     var aPathSeg = aPathSegList.getItem( 0 );
7338     if( aPathSeg.pathSegTypeAsLetter == 'M' )
7339     {
7340         nCurrentX = aPathSeg.x;
7341         nCurrentY = aPathSeg.y;
7342         aPathSegList.removeItem( 0 );
7343         --nLength;
7344     }
7346     var i;
7347     for( i = 0; i < nLength; ++i )
7348     {
7349         aPathSeg = aPathSegList.getItem( i );
7350         var aPoint = aPathSeg.changeOrientation( nCurrentX, nCurrentY );
7351         nCurrentX = aPoint.x;
7352         nCurrentY = aPoint.y;
7353     }
7356     for( i = nLength - 2; i >= 0; --i )
7357     {
7358         aPathSeg = aPathSegList.removeItem( i );
7359         aPathSegList.appendItem( aPathSeg );
7360     }
7362     var aMovePathSeg = this.createSVGPathSegMovetoAbs( nCurrentX, nCurrentY );
7363     aPathSegList.insertItemBefore( aMovePathSeg, 0 );
7367 /** matrixTransform and changeOrientation
7368  *  We implement these methods for each path segment type still present
7369  *  after the path normalization (M, L, Q, C).
7371  *  Note: Opera doesn't have any SVGPathSeg* class and rises an error.
7372  *  We exploit this fact for providing a different implementation.
7373  */
7377 {   // Firefox, Google Chrome, Internet Explorer, Safari.
7379     SVGPathSegMovetoAbs.prototype.matrixTransform = function( aSVGMatrix )
7380     {
7381         SVGPathMatrixTransform( this, aSVGMatrix );
7382     };
7384     SVGPathSegLinetoAbs.prototype.matrixTransform = function( aSVGMatrix )
7385     {
7386         SVGPathMatrixTransform( this, aSVGMatrix );
7387     };
7389     SVGPathSegCurvetoQuadraticAbs.prototype.matrixTransform = function( aSVGMatrix )
7390     {
7391         SVGPathMatrixTransform( this, aSVGMatrix );
7392         var nX = this.x1;
7393         this.x1 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y1 + aSVGMatrix.e;
7394         this.y1 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y1 + aSVGMatrix.f;
7395     };
7397     SVGPathSegCurvetoCubicAbs.prototype.matrixTransform = function( aSVGMatrix )
7398     {
7399         SVGPathMatrixTransform( this, aSVGMatrix );
7400         var nX = this.x1;
7401         this.x1 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y1 + aSVGMatrix.e;
7402         this.y1 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y1 + aSVGMatrix.f;
7403         nX = this.x2;
7404         this.x2 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y2 + aSVGMatrix.e;
7405         this.y2 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y2 + aSVGMatrix.f;
7406     };
7409     SVGPathSegMovetoAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7410     {
7411         var aPoint = { x: this.x, y: this.y };
7412         this.x = nCurrentX;
7413         this.y = nCurrentY;
7414         return aPoint;
7415     };
7417     SVGPathSegLinetoAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7418     {
7419         var aPoint = { x: this.x, y: this.y };
7420         this.x = nCurrentX;
7421         this.y = nCurrentY;
7422         return aPoint;
7423     };
7425     SVGPathSegCurvetoQuadraticAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7426     {
7427         var aPoint = { x: this.x, y: this.y };
7428         this.x = nCurrentX;
7429         this.y = nCurrentY;
7430         return aPoint;
7431     };
7433     SVGPathSegCurvetoCubicAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7434     {
7435         var aPoint = { x: this.x, y: this.y };
7436         this.x = nCurrentX;
7437         this.y = nCurrentY;
7438         var nX = this.x1;
7439         this.x1 = this.x2;
7440         this.x2 = nX;
7441         var nY = this.y1;
7442         this.y1 = this.y2;
7443         this.y2 = nY;
7444         return aPoint;
7445     };
7448 catch( e )
7449 {   // Opera
7451     if( e.name == 'ReferenceError' )
7452     {
7453         SVGPathSeg.prototype.matrixTransform = function( aSVGMatrix )
7454         {
7455             var nX;
7456             switch( this.pathSegTypeAsLetter )
7457             {
7458                 case 'C':
7459                     nX = this.x2;
7460                     this.x2 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y2 + aSVGMatrix.e;
7461                     this.y2 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y2 + aSVGMatrix.f;
7462                 // fall through intended
7463                 case 'Q':
7464                     nX = this.x1;
7465                     this.x1 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y1 + aSVGMatrix.e;
7466                     this.y1 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y1 + aSVGMatrix.f;
7467                 // fall through intended
7468                 case 'M':
7469                 case 'L':
7470                     SVGPathMatrixTransform( this, aSVGMatrix );
7471                     break;
7472                 default:
7473                     log( 'SVGPathSeg.matrixTransform: unexpected path segment type: '
7474                              + this.pathSegTypeAsLetter );
7475             }
7476         };
7478         SVGPathSeg.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7479         {
7480             switch( this.pathSegTypeAsLetter )
7481             {
7482                 case 'C':
7483                     var nX = this.x1;
7484                     this.x1 = this.x2;
7485                     this.x2 = nX;
7486                     var nY = this.y1;
7487                     this.y1 = this.y2;
7488                     this.y2 = nY;
7489                 // fall through intended
7490                 case 'M':
7491                 case 'L':
7492                 case 'Q':
7493                     var aPoint = { x: this.x, y: this.y };
7494                     this.x = nCurrentX;
7495                     this.y = nCurrentY;
7496                     return aPoint;
7497                 default:
7498                     log( 'SVGPathSeg.changeOrientation: unexpected path segment type: '
7499                              + this.pathSegTypeAsLetter );
7500                     return null;
7501             }
7502         }
7503     }
7504     else throw e;
7507 function SVGPathMatrixTransform( aPath, aSVGMatrix )
7509     var nX = aPath.x;
7510     aPath.x = aSVGMatrix.a * nX + aSVGMatrix.c * aPath.y + aSVGMatrix.e;
7511     aPath.y = aSVGMatrix.b * nX + aSVGMatrix.d * aPath.y + aSVGMatrix.f;
7515 /**********************************************************************************************
7516  *      simple PriorityQueue
7517  **********************************************************************************************/
7519 function PriorityQueue( aCompareFunc )
7521     this.aSequence = [];
7522     this.aCompareFunc = aCompareFunc;
7525 PriorityQueue.prototype.clone = function()
7527     var aCopy = new PriorityQueue( this.aCompareFunc );
7528     var src = this.aSequence;
7529     var dest = [];
7530     var i, l;
7531     for( i = 0, l = src.length; i < l; ++i )
7532     {
7533         if( i in src )
7534         {
7535             dest.push( src[i] );
7536         }
7537     }
7538     aCopy.aSequence = dest;
7539     return aCopy;
7542 PriorityQueue.prototype.top = function()
7544     return this.aSequence[this.aSequence.length - 1];
7547 PriorityQueue.prototype.isEmpty = function()
7549     return ( this.aSequence.length === 0 );
7552 PriorityQueue.prototype.push = function( aValue )
7554     this.aSequence.unshift( aValue );
7555     this.aSequence.sort(this.aCompareFunc);
7558 PriorityQueue.prototype.clear = function()
7560     this.aSequence = [];
7563 PriorityQueue.prototype.pop = function()
7565     return this.aSequence.pop();
7571 /**********************************************************************************************
7572  *      AnimationNode Class Hierarchy
7573  **********************************************************************************************/
7577 // Node Types
7578 var ANIMATION_NODE_CUSTOM               = 0;
7579 var ANIMATION_NODE_PAR                  = 1;
7580 var ANIMATION_NODE_SEQ                  = 2;
7581 var ANIMATION_NODE_ITERATE              = 3;
7582 var ANIMATION_NODE_ANIMATE              = 4;
7583 var ANIMATION_NODE_SET                  = 5;
7584 var ANIMATION_NODE_ANIMATEMOTION        = 6;
7585 var ANIMATION_NODE_ANIMATECOLOR         = 7;
7586 var ANIMATION_NODE_ANIMATETRANSFORM     = 8;
7587 var ANIMATION_NODE_TRANSITIONFILTER     = 9;
7588 var ANIMATION_NODE_AUDIO                = 10;
7589 var ANIMATION_NODE_COMMAND              = 11;
7591 var aAnimationNodeTypeInMap = {
7592     'par'               : ANIMATION_NODE_PAR,
7593     'seq'               : ANIMATION_NODE_SEQ,
7594     'iterate'           : ANIMATION_NODE_ITERATE,
7595     'animate'           : ANIMATION_NODE_ANIMATE,
7596     'set'               : ANIMATION_NODE_SET,
7597     'animatemotion'     : ANIMATION_NODE_ANIMATEMOTION,
7598     'animatecolor'      : ANIMATION_NODE_ANIMATECOLOR,
7599     'animatetransform'  : ANIMATION_NODE_ANIMATETRANSFORM,
7600     'transitionfilter'  : ANIMATION_NODE_TRANSITIONFILTER,
7601     'audio'             : ANIMATION_NODE_AUDIO,
7602     'command'           : ANIMATION_NODE_COMMAND
7608 function getAnimationElementType( aElement )
7610     var sName = aElement.localName.toLowerCase();
7612     if( sName && aAnimationNodeTypeInMap[ sName ] )
7613         return aAnimationNodeTypeInMap[ sName ];
7614     else
7615         return ANIMATION_NODE_CUSTOM;
7622 // Node States
7623 var INVALID_NODE                = 0;
7624 var UNRESOLVED_NODE             = 1;
7625 var RESOLVED_NODE               = 2;
7626 var ACTIVE_NODE                 = 4;
7627 var FROZEN_NODE                 = 8;
7628 var ENDED_NODE                  = 16;
7630 function getNodeStateName( eNodeState )
7632     switch( eNodeState )
7633     {
7634         case INVALID_NODE:
7635             return 'INVALID';
7636         case UNRESOLVED_NODE:
7637             return 'UNRESOLVED';
7638         case RESOLVED_NODE:
7639             return 'RESOLVED';
7640         case ACTIVE_NODE:
7641             return 'ACTIVE';
7642         case FROZEN_NODE:
7643             return 'FROZEN';
7644         case ENDED_NODE:
7645             return 'ENDED';
7646         default:
7647             return 'UNKNOWN';
7648     }
7652 // Impress Node Types
7653 var IMPRESS_DEFAULT_NODE                    = 0;
7654 var IMPRESS_ON_CLICK_NODE                   = 1;
7655 var IMPRESS_WITH_PREVIOUS_NODE              = 2;
7656 var IMPRESS_AFTER_PREVIOUS_NODE             = 3;
7657 var IMPRESS_MAIN_SEQUENCE_NODE              = 4;
7658 var IMPRESS_TIMING_ROOT_NODE                = 5;
7659 var IMPRESS_INTERACTIVE_SEQUENCE_NODE       = 6;
7661 var aImpressNodeTypeInMap = {
7662     'on-click'                  : IMPRESS_ON_CLICK_NODE,
7663     'with-previous'             : IMPRESS_WITH_PREVIOUS_NODE,
7664     'after-previous'            : IMPRESS_AFTER_PREVIOUS_NODE,
7665     'main-sequence'             : IMPRESS_MAIN_SEQUENCE_NODE,
7666     'timing-root'               : IMPRESS_TIMING_ROOT_NODE,
7667     'interactive-sequence'      : IMPRESS_INTERACTIVE_SEQUENCE_NODE
7670 var aImpressNodeTypeOutMap = [ 'default', 'on-click', 'with-previous', 'after-previous',
7671                             'main-sequence', 'timing-root', 'interactive-sequence' ];
7674 // Preset Classes
7675 var aPresetClassInMap = {};
7678 // Preset Ids
7679 var aPresetIdInMap = {};
7682 // Restart Modes
7683 var RESTART_MODE_DEFAULT            = 0;
7684 var RESTART_MODE_INHERIT            = 0; // eslint-disable-line no-unused-vars
7685 var RESTART_MODE_ALWAYS             = 1;
7686 var RESTART_MODE_WHEN_NOT_ACTIVE    = 2;
7687 var RESTART_MODE_NEVER              = 3;
7689 var aRestartModeInMap = {
7690     'inherit'       : RESTART_MODE_DEFAULT,
7691     'always'        : RESTART_MODE_ALWAYS,
7692     'whenNotActive' : RESTART_MODE_WHEN_NOT_ACTIVE,
7693     'never'         : RESTART_MODE_NEVER
7696 var aRestartModeOutMap = [ 'inherit','always', 'whenNotActive', 'never' ];
7699 // Fill Modes
7700 var FILL_MODE_DEFAULT           = 0;
7701 var FILL_MODE_INHERIT           = 0; // eslint-disable-line no-unused-vars
7702 var FILL_MODE_REMOVE            = 1;
7703 var FILL_MODE_FREEZE            = 2;
7704 var FILL_MODE_HOLD              = 3;
7705 var FILL_MODE_TRANSITION        = 4;
7706 var FILL_MODE_AUTO              = 5;
7708 var aFillModeInMap = {
7709     'inherit'       : FILL_MODE_DEFAULT,
7710     'remove'        : FILL_MODE_REMOVE,
7711     'freeze'        : FILL_MODE_FREEZE,
7712     'hold'          : FILL_MODE_HOLD,
7713     'transition'    : FILL_MODE_TRANSITION,
7714     'auto'          : FILL_MODE_AUTO
7717 var aFillModeOutMap = [ 'inherit', 'remove', 'freeze', 'hold', 'transition', 'auto' ];
7720 // Additive Modes
7721 var ADDITIVE_MODE_UNKNOWN       = 0; // eslint-disable-line no-unused-vars
7722 var ADDITIVE_MODE_BASE          = 1;
7723 var ADDITIVE_MODE_SUM           = 2;
7724 var ADDITIVE_MODE_REPLACE       = 3;
7725 var ADDITIVE_MODE_MULTIPLY      = 4;
7726 var ADDITIVE_MODE_NONE          = 5;
7728 var aAddittiveModeInMap = {
7729     'base'          : ADDITIVE_MODE_BASE,
7730     'sum'           : ADDITIVE_MODE_SUM,
7731     'replace'       : ADDITIVE_MODE_REPLACE,
7732     'multiply'      : ADDITIVE_MODE_MULTIPLY,
7733     'none'          : ADDITIVE_MODE_NONE
7736 var aAddittiveModeOutMap = [ 'unknown', 'base', 'sum', 'replace', 'multiply', 'none' ];
7739 // Accumulate Modes
7740 var ACCUMULATE_MODE_NONE        = 0;
7741 var ACCUMULATE_MODE_SUM         = 1;
7743 var aAccumulateModeOutMap = [ 'none', 'sum' ];
7745 // Calculation Modes
7746 var CALC_MODE_DISCRETE          = 1;
7747 var CALC_MODE_LINEAR            = 2;
7748 var CALC_MODE_PACED             = 3;
7749 var CALC_MODE_SPLINE            = 4;
7751 var aCalcModeInMap = {
7752     'discrete'      : CALC_MODE_DISCRETE,
7753     'linear'        : CALC_MODE_LINEAR,
7754     'paced'         : CALC_MODE_PACED,
7755     'spline'        : CALC_MODE_SPLINE
7758 var aCalcModeOutMap = [ 'unknown', 'discrete', 'linear', 'paced', 'spline' ];
7761 // Color Spaces
7762 var COLOR_SPACE_RGB = 0;
7763 var COLOR_SPACE_HSL = 1;
7765 var aColorSpaceInMap = { 'rgb': COLOR_SPACE_RGB, 'hsl': COLOR_SPACE_HSL };
7767 var aColorSpaceOutMap = [ 'rgb', 'hsl' ];
7770 // Clock Directions
7771 var CLOCKWISE               = 0;
7772 var COUNTERCLOCKWISE        = 1;
7774 var aClockDirectionInMap = { 'clockwise': CLOCKWISE, 'counter-clockwise': COUNTERCLOCKWISE };
7776 var aClockDirectionOutMap = [ 'clockwise', 'counter-clockwise' ];
7779 // Attribute Value Types
7780 var UNKNOWN_PROPERTY        = 0; // eslint-disable-line no-unused-vars
7781 var NUMBER_PROPERTY         = 1;
7782 var ENUM_PROPERTY           = 2;
7783 var COLOR_PROPERTY          = 3;
7784 var STRING_PROPERTY         = 4;
7785 var BOOL_PROPERTY           = 5;
7787 var aValueTypeOutMap = [ 'unknown', 'number', 'enum', 'color', 'string', 'boolean' ];
7790 // Attribute Map
7791 var aAttributeMap =
7793         'height':           {   'type':         NUMBER_PROPERTY,
7794                                 'get':          'getHeight',
7795                                 'set':          'setHeight',
7796                                 'getmod':       'makeScaler( 1/nHeight )',
7797                                 'setmod':       'makeScaler( nHeight)'          },
7799         'opacity':          {   'type':         NUMBER_PROPERTY,
7800                                 'get':          'getOpacity',
7801                                 'set':          'setOpacity'                    },
7803         'rotate':           {   'type':         NUMBER_PROPERTY,
7804                                 'get':          'getRotationAngle',
7805                                 'set':          'setRotationAngle'              },
7807         'width':            {   'type':         NUMBER_PROPERTY,
7808                                 'get':          'getWidth',
7809                                 'set':          'setWidth',
7810                                 'getmod':       'makeScaler( 1/nWidth )',
7811                                 'setmod':       'makeScaler( nWidth)'           },
7813         'x':                {   'type':         NUMBER_PROPERTY,
7814                                 'get':          'getX',
7815                                 'set':          'setX',
7816                                 'getmod':       'makeScaler( 1/nWidth )',
7817                                 'setmod':       'makeScaler( nWidth)'           },
7819         'y':                {   'type':         NUMBER_PROPERTY,
7820                                 'get':          'getY',
7821                                 'set':          'setY',
7822                                 'getmod':       'makeScaler( 1/nHeight )',
7823                                 'setmod':       'makeScaler( nHeight)'          },
7825         'fill':             {   'type':         ENUM_PROPERTY,
7826                                 'get':          'getFillStyle',
7827                                 'set':          'setFillStyle'                  },
7829         'stroke':           {   'type':         ENUM_PROPERTY,
7830                                 'get':          'getStrokeStyle',
7831                                 'set':          'setStrokeStyle'                },
7833         'visibility':       {   'type':         ENUM_PROPERTY,
7834                                 'get':          'getVisibility',
7835                                 'set':          'setVisibility'                 },
7837         'fill-color':       {   'type':         COLOR_PROPERTY,
7838                                 'get':          'getFillColor',
7839                                 'set':          'setFillColor'                  },
7841         'stroke-color':     {   'type':         COLOR_PROPERTY,
7842                                 'get':          'getStrokeColor',
7843                                 'set':          'setStrokeColor'                },
7845         'color':            {   'type':         COLOR_PROPERTY,
7846                                 'get':          'getFontColor',
7847                                 'set':          'setFontColor'                  }
7852 // Transition Classes
7853 var TRANSITION_INVALID              = 0;    // Invalid type
7854 var TRANSITION_CLIP_POLYPOLYGON     = 1;    // Transition expressed by parametric clip polygon
7855 var TRANSITION_SPECIAL              = 2;    // Transition expressed by hand-crafted function
7858  * All Transition types should be in sync with aTransitionTypeInMap:
7859  * Comments '//' followed by integers represent the transition values in their
7860  * C++ implementations.
7861  */
7863 // Transition Types
7864 var BARWIPE_TRANSITION              = 1;
7865 var BOXWIPE_TRANSITION              = 2;
7866 var FOURBOXWIPE_TRANSITION          = 3;
7867 var ELLIPSEWIPE_TRANSITION          = 4; // 17
7868 var CLOCKWIPE_TRANSITION            = 5; // 22
7869 var PINWHEELWIPE_TRANSITION         = 6; // 23
7870 var PUSHWIPE_TRANSITION             = 7; // 35
7871 var SLIDEWIPE_TRANSITION            = 8; // 36
7872 var FADE_TRANSITION                 = 9; // 37
7873 var RANDOMBARWIPE_TRANSITION        = 10; // 38
7874 var CHECKERBOARDWIPE_TRANSITION     = 11; // 39
7875 var DISSOLVE_TRANSITION             = 12; // 40
7876 var SNAKEWIPE_TRANSITION            = 13; // 30
7877 var PARALLELSNAKESWIPE_TRANSITION   = 14; // 32
7878 var IRISWIPE_TRANSITION             = 15; // 12
7879 var BARNDOORWIPE_TRANSITION         = 16; // 4
7880 var VEEWIPE_TRANSITION              = 17; // 8
7881 var ZIGZAGWIPE_TRANSITION           = 18; // 10
7882 var BARNZIGZAGWIPE_TRANSITION       = 19; // 11
7883 var FANWIPE_TRANSITION              = 20; // 25
7884 var SINGLESWEEPWIPE_TRANSITION      = 21; // 24
7885 var WATERFALLWIPE_TRANSITION        = 22; // 34
7886 var SPIRALWIPE_TRANSITION           = 23; // 31
7887 var MISCDIAGONALWIPE_TRANSITION     = 24; // 7
7888 var BOXSNAKESWIPE_TRANSITION        = 25; // 33
7890 var aTransitionTypeInMap = {
7891     'barWipe'           : BARWIPE_TRANSITION,
7892     'boxWipe'           : BOXWIPE_TRANSITION,
7893     'barnDoorWipe'      : BARNDOORWIPE_TRANSITION,
7894     'fourBoxWipe'       : FOURBOXWIPE_TRANSITION,
7895     'ellipseWipe'       : ELLIPSEWIPE_TRANSITION,
7896     'clockWipe'         : CLOCKWIPE_TRANSITION,
7897     'pinWheelWipe'      : PINWHEELWIPE_TRANSITION,
7898     'miscDiagonalWipe'  : MISCDIAGONALWIPE_TRANSITION,
7899     'pushWipe'          : PUSHWIPE_TRANSITION,
7900     'slideWipe'         : SLIDEWIPE_TRANSITION,
7901     'fade'              : FADE_TRANSITION,
7902     'fanWipe'           : FANWIPE_TRANSITION,
7903     'randomBarWipe'     : RANDOMBARWIPE_TRANSITION,
7904     'checkerBoardWipe'  : CHECKERBOARDWIPE_TRANSITION,
7905     'dissolve'          : DISSOLVE_TRANSITION,
7906     'singleSweepWipe'   : SINGLESWEEPWIPE_TRANSITION,
7907     'snakeWipe'         : SNAKEWIPE_TRANSITION,
7908     'parallelSnakesWipe': PARALLELSNAKESWIPE_TRANSITION,
7909     'spiralWipe'        : SPIRALWIPE_TRANSITION,
7910     'boxSnakesWipe'     : BOXSNAKESWIPE_TRANSITION,
7911     'irisWipe'          : IRISWIPE_TRANSITION,
7912     'veeWipe'           : VEEWIPE_TRANSITION,
7913     'zigZagWipe'        : ZIGZAGWIPE_TRANSITION,
7914     'barnZigZagWipe'    : BARNZIGZAGWIPE_TRANSITION,
7915     'waterfallWipe'     : WATERFALLWIPE_TRANSITION
7919  * All Transition subtypes should be in sync with aTransitionSubtypeInMap:
7920  * Comments '//' followed by integers represent the transition values in their
7921  * C++ implementations.
7922  */
7923 // Transition Subtypes
7924 var DEFAULT_TRANS_SUBTYPE                       = 0;
7925 var LEFTTORIGHT_TRANS_SUBTYPE                   = 1;
7926 var TOPTOBOTTOM_TRANS_SUBTYPE                   = 2;
7927 var CORNERSIN_TRANS_SUBTYPE                     = 3; // 11
7928 var CORNERSOUT_TRANS_SUBTYPE                    = 4;
7929 var VERTICAL_TRANS_SUBTYPE                      = 5;
7930 var HORIZONTAL_TRANS_SUBTYPE                    = 6; // 14
7931 var DOWN_TRANS_SUBTYPE                          = 7; // 19
7932 var CIRCLE_TRANS_SUBTYPE                        = 8; // 27
7933 var CLOCKWISETWELVE_TRANS_SUBTYPE               = 9; // 33
7934 var CLOCKWISETHREE_TRANS_SUBTYPE                = 10;
7935 var CLOCKWISESIX_TRANS_SUBTYPE                  = 11;
7936 var CLOCKWISENINE_TRANS_SUBTYPE                 = 12;
7937 var TWOBLADEVERTICAL_TRANS_SUBTYPE              = 13;
7938 var TWOBLADEHORIZONTAL_TRANS_SUBTYPE            = 14;
7939 var FOURBLADE_TRANS_SUBTYPE                     = 15; // 39
7940 var FROMLEFT_TRANS_SUBTYPE                      = 16; // 97
7941 var FROMTOP_TRANS_SUBTYPE                       = 17;
7942 var FROMRIGHT_TRANS_SUBTYPE                     = 18;
7943 var FROMBOTTOM_TRANS_SUBTYPE                    = 19;
7944 var CROSSFADE_TRANS_SUBTYPE                     = 20;
7945 var FADETOCOLOR_TRANS_SUBTYPE                   = 21;
7946 var FADEFROMCOLOR_TRANS_SUBTYPE                 = 22;
7947 var FADEOVERCOLOR_TRANS_SUBTYPE                 = 23;
7948 var THREEBLADE_TRANS_SUBTYPE                    = 24;
7949 var EIGHTBLADE_TRANS_SUBTYPE                    = 25;
7950 var ONEBLADE_TRANS_SUBTYPE                      = 26; // 107
7951 var ACROSS_TRANS_SUBTYPE                        = 27;
7952 var TOPLEFTVERTICAL_TRANS_SUBTYPE               = 28; // 109
7953 var TOPLEFTHORIZONTAL_TRANS_SUBTYPE             = 29; // 64
7954 var TOPLEFTDIAGONAL_TRANS_SUBTYPE               = 30; // 65
7955 var TOPRIGHTDIAGONAL_TRANS_SUBTYPE              = 31; // 66
7956 var BOTTOMRIGHTDIAGONAL_TRANS_SUBTYPE           = 32; // 67
7957 var BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE            = 33; // 68
7958 var RECTANGLE_TRANS_SUBTYPE                     = 34; // 101
7959 var DIAMOND_TRANS_SUBTYPE                       = 35; // 102
7960 var TOPLEFT_TRANS_SUBTYPE                       = 36; // 3
7961 var TOPRIGHT_TRANS_SUBTYPE                      = 37; // 4
7962 var BOTTOMRIGHT_TRANS_SUBTYPE                   = 38; // 5
7963 var BOTTOMLEFT_TRANS_SUBTYPE                    = 39; // 6
7964 var TOPCENTER_TRANS_SUBTYPE                     = 40; // 7
7965 var RIGHTCENTER_TRANS_SUBTYPE                   = 41; // 8
7966 var BOTTOMCENTER_TRANS_SUBTYPE                  = 42; // 9
7967 var LEFTCENTER_TRANS_SUBTYPE                    = 43; // 10
7968 var LEFT_TRANS_SUBTYPE                          = 44; // 20
7969 var UP_TRANS_SUBTYPE                            = 45; // 21
7970 var RIGHT_TRANS_SUBTYPE                         = 46; // 22
7971 var DIAGONALBOTTOMLEFT_TRANS_SUBTYPE            = 47; // 15
7972 var DIAGONALTOPLEFT_TRANS_SUBTYPE               = 48; // 16
7973 var CENTERTOP_TRANS_SUBTYPE                     = 49; // 48
7974 var CENTERRIGHT_TRANS_SUBTYPE                   = 50; // 49
7975 var TOP_TRANS_SUBTYPE                           = 51; // 50
7976 var BOTTOM_TRANS_SUBTYPE                        = 52; // 52
7977 var CLOCKWISETOP_TRANS_SUBTYPE                  = 53; // 40
7978 var CLOCKWISERIGHT_TRANS_SUBTYPE                = 54; // 41
7979 var CLOCKWISEBOTTOM_TRANS_SUBTYPE               = 55; // 42
7980 var CLOCKWISELEFT_TRANS_SUBTYPE                 = 56; // 43
7981 var CLOCKWISETOPLEFT_TRANS_SUBTYPE              = 57; // 44
7982 var COUNTERCLOCKWISEBOTTOMLEFT_TRANS_SUBTYPE    = 58; // 45
7983 var CLOCKWISEBOTTOMRIGHT_TRANS_SUBTYPE          = 59; // 46
7984 var COUNTERCLOCKWISETOPRIGHT_TRANS_SUBTYPE      = 60; // 47
7985 var VERTICALLEFT_TRANS_SUBTYPE                  = 61; // 93
7986 var VERTICALRIGHT_TRANS_SUBTYPE                 = 62; // 94
7987 var HORIZONTALLEFT_TRANS_SUBTYPE                = 63; // 95
7988 var HORIZONTALRIGHT_TRANS_SUBTYPE               = 64; // 96
7989 var TOPLEFTCLOCKWISE_TRANS_SUBTYPE              = 65; // 69
7990 var TOPRIGHTCLOCKWISE_TRANS_SUBTYPE             = 66; // 70
7991 var BOTTOMRIGHTCLOCKWISE_TRANS_SUBTYPE          = 67; // 71
7992 var BOTTOMLEFTCLOCKWISE_TRANS_SUBTYPE           = 68; // 72
7993 var TOPLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE       = 69; // 73
7994 var TOPRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE      = 70; // 74
7995 var BOTTOMRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE   = 71; // 75
7996 var BOTTOMLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE    = 72; // 76
7997 var DOUBLEBARNDOOR_TRANS_SUBTYPE                = 73; // 17
7998 var DOUBLEDIAMOND_TRANS_SUBTYPE                 = 74; // 18
7999 var VERTICALTOPSAME_TRANS_SUBTYPE               = 75; // 77
8000 var VERTICALBOTTOMSAME_TRANS_SUBTYPE            = 76; // 78
8001 var VERTICALTOPLEFTOPPOSITE_TRANS_SUBTYPE       = 77; // 79
8002 var VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE    = 78; // 80
8003 var HORIZONTALLEFTSAME_TRANS_SUBTYPE            = 79; // 81
8004 var HORIZONTALRIGHTSAME_TRANS_SUBTYPE           = 80; // 82
8005 var HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE     = 81; // 83
8006 var HORIZONTALTOPRIGHTOPPOSITE_TRANS_SUBTYPE    = 82; // 84
8007 var DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE    = 83; // 85
8008 var DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE       = 84; // 86
8009 var TWOBOXTOP_TRANS_SUBTYPE                     = 85; // 87
8010 var TWOBOXBOTTOM_TRANS_SUBTYPE                  = 86; // 88
8011 var TWOBOXLEFT_TRANS_SUBTYPE                    = 87; // 89
8012 var TWOBOXRIGHT_TRANS_SUBTYPE                   = 88; // 90
8013 var FOURBOXVERTICAL_TRANS_SUBTYPE               = 89; // 91
8014 var FOURBOXHORIZONTAL_TRANS_SUBTYPE             = 90; // 92
8016 var aTransitionSubtypeInMap = {
8017     'default'                       : DEFAULT_TRANS_SUBTYPE,
8018     'leftToRight'                   : LEFTTORIGHT_TRANS_SUBTYPE,
8019     'topToBottom'                   : TOPTOBOTTOM_TRANS_SUBTYPE,
8020     'cornersIn'                     : CORNERSIN_TRANS_SUBTYPE,
8021     'cornersOut'                    : CORNERSOUT_TRANS_SUBTYPE,
8022     'vertical'                      : VERTICAL_TRANS_SUBTYPE,
8023     'centerTop'                     : CENTERTOP_TRANS_SUBTYPE,
8024     'centerRight'                   : CENTERRIGHT_TRANS_SUBTYPE,
8025     'top'                           : TOP_TRANS_SUBTYPE,
8026     'right'                         : RIGHT_TRANS_SUBTYPE,
8027     'bottom'                        : BOTTOM_TRANS_SUBTYPE,
8028     'left'                          : LEFT_TRANS_SUBTYPE,
8029     'horizontal'                    : HORIZONTAL_TRANS_SUBTYPE,
8030     'down'                          : DOWN_TRANS_SUBTYPE,
8031     'circle'                        : CIRCLE_TRANS_SUBTYPE,
8032     'clockwiseTwelve'               : CLOCKWISETWELVE_TRANS_SUBTYPE,
8033     'clockwiseThree'                : CLOCKWISETHREE_TRANS_SUBTYPE,
8034     'clockwiseSix'                  : CLOCKWISESIX_TRANS_SUBTYPE,
8035     'clockwiseNine'                 : CLOCKWISENINE_TRANS_SUBTYPE,
8036     'clockwiseRight'                : CLOCKWISERIGHT_TRANS_SUBTYPE,
8037     'clockwiseTop'                  : CLOCKWISETOP_TRANS_SUBTYPE,
8038     'clockwiseBottom'               : CLOCKWISEBOTTOM_TRANS_SUBTYPE,
8039     'clockwiseLeft'                 : CLOCKWISELEFT_TRANS_SUBTYPE,
8040     'clockwiseTopLeft'              : CLOCKWISETOPLEFT_TRANS_SUBTYPE,
8041     'counterClockwiseBottomLeft'    : COUNTERCLOCKWISEBOTTOMLEFT_TRANS_SUBTYPE,
8042     'clockwiseBottomRight'          : CLOCKWISEBOTTOMRIGHT_TRANS_SUBTYPE,
8043     'counterClockwiseTopRight'      : COUNTERCLOCKWISETOPRIGHT_TRANS_SUBTYPE,
8044     'twoBladeVertical'              : TWOBLADEVERTICAL_TRANS_SUBTYPE,
8045     'twoBladeHorizontal'            : TWOBLADEHORIZONTAL_TRANS_SUBTYPE,
8046     'fourBlade'                     : FOURBLADE_TRANS_SUBTYPE,
8047     'fromLeft'                      : FROMLEFT_TRANS_SUBTYPE,
8048     'fromTop'                       : FROMTOP_TRANS_SUBTYPE,
8049     'fromRight'                     : FROMRIGHT_TRANS_SUBTYPE,
8050     'fromBottom'                    : FROMBOTTOM_TRANS_SUBTYPE,
8051     'crossfade'                     : CROSSFADE_TRANS_SUBTYPE,
8052     'fadeToColor'                   : FADETOCOLOR_TRANS_SUBTYPE,
8053     'fadeFromColor'                 : FADEFROMCOLOR_TRANS_SUBTYPE,
8054     'fadeOverColor'                 : FADEOVERCOLOR_TRANS_SUBTYPE,
8055     'threeBlade'                    : THREEBLADE_TRANS_SUBTYPE,
8056     'eightBlade'                    : EIGHTBLADE_TRANS_SUBTYPE,
8057     'oneBlade'                      : ONEBLADE_TRANS_SUBTYPE,
8058     'across'                        : ACROSS_TRANS_SUBTYPE,
8059     'topLeftVertical'               : TOPLEFTVERTICAL_TRANS_SUBTYPE,
8060     'topLeftHorizontal'             : TOPLEFTHORIZONTAL_TRANS_SUBTYPE,
8061     'topLeftDiagonal'               : TOPLEFTDIAGONAL_TRANS_SUBTYPE,
8062     'topRightDiagonal'              : TOPRIGHTDIAGONAL_TRANS_SUBTYPE,
8063     'bottomRightDiagonal'           : BOTTOMRIGHTDIAGONAL_TRANS_SUBTYPE,
8064     'topLeftClockwise'              : TOPLEFTCLOCKWISE_TRANS_SUBTYPE,
8065     'topRightClockwise'             : TOPRIGHTCLOCKWISE_TRANS_SUBTYPE,
8066     'bottomRightClockwise'          : BOTTOMRIGHTCLOCKWISE_TRANS_SUBTYPE,
8067     'bottomLeftClockwise'           : BOTTOMLEFTCLOCKWISE_TRANS_SUBTYPE,
8068     'topLeftCounterClockwise'       : TOPLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE,
8069     'topRightCounterClockwise'      : TOPRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE,
8070     'bottomRightCounterClockwise'   : BOTTOMRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE,
8071     'bottomLeftCounterClockwise'    : BOTTOMLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE,
8072     'bottomLeftDiagonal'            : BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE,
8073     'rectangle'                     : RECTANGLE_TRANS_SUBTYPE,
8074     'diamond'                       : DIAMOND_TRANS_SUBTYPE,
8075     'topLeft'                       : TOPLEFT_TRANS_SUBTYPE,
8076     'topRight'                      : TOPRIGHT_TRANS_SUBTYPE,
8077     'bottomRight'                   : BOTTOMRIGHT_TRANS_SUBTYPE,
8078     'bottomLeft'                    : BOTTOMLEFT_TRANS_SUBTYPE,
8079     'topCenter'                     : TOPCENTER_TRANS_SUBTYPE,
8080     'rightCenter'                   : RIGHTCENTER_TRANS_SUBTYPE,
8081     'bottomCenter'                  : BOTTOMCENTER_TRANS_SUBTYPE,
8082     'leftCenter'                    : LEFTCENTER_TRANS_SUBTYPE,
8083     'up'                            : UP_TRANS_SUBTYPE,
8084     'diagonalBottomLeft'            : DIAGONALBOTTOMLEFT_TRANS_SUBTYPE,
8085     'diagonalTopLeft'               : DIAGONALTOPLEFT_TRANS_SUBTYPE,
8086     'verticalLeft'                  : VERTICALLEFT_TRANS_SUBTYPE,
8087     'verticalRight'                 : VERTICALRIGHT_TRANS_SUBTYPE,
8088     'horizontalLeft'                : HORIZONTALLEFT_TRANS_SUBTYPE,
8089     'horizontalRight'               : HORIZONTALRIGHT_TRANS_SUBTYPE,
8090     'doubleBarnDoor'                : DOUBLEBARNDOOR_TRANS_SUBTYPE,
8091     'doubleDiamond'                 : DOUBLEDIAMOND_TRANS_SUBTYPE,
8092     'verticalTopSame'               : VERTICALTOPSAME_TRANS_SUBTYPE,
8093     'verticalBottomSame'            : VERTICALBOTTOMSAME_TRANS_SUBTYPE,
8094     'verticalTopLeftOpposite'       : VERTICALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
8095     'verticalBottomLeftOpposite'    : VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE,
8096     'horizontalLeftSame'            : HORIZONTALLEFTSAME_TRANS_SUBTYPE,
8097     'horizontalRightSame'           : HORIZONTALRIGHTSAME_TRANS_SUBTYPE,
8098     'horizontalTopLeftOpposite'     : HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
8099     'horizontalTopRightOpposite'    : HORIZONTALTOPRIGHTOPPOSITE_TRANS_SUBTYPE,
8100     'diagonalBottomLeftOpposite'    : DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE,
8101     'diagonalTopLeftOpposite'       : DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
8102     'twoBoxTop'                     : TWOBOXTOP_TRANS_SUBTYPE,
8103     'twoBoxBottom'                  : TWOBOXBOTTOM_TRANS_SUBTYPE,
8104     'twoBoxLeft'                    : TWOBOXLEFT_TRANS_SUBTYPE,
8105     'twoBoxRight'                   : TWOBOXRIGHT_TRANS_SUBTYPE,
8106     'fourBoxVertical'               : FOURBOXVERTICAL_TRANS_SUBTYPE,
8107     'fourBoxHorizontal'             : FOURBOXHORIZONTAL_TRANS_SUBTYPE
8110 // Transition Modes
8111 var TRANSITION_MODE_IN  = 1;
8112 var TRANSITION_MODE_OUT = 0;
8114 var aTransitionModeOutMap = [ 'out', 'in' ];
8117 // Transition Reverse Methods
8119 // Ignore direction attribute altogether.
8120 // (If it has no sensible meaning for this transition.)
8121 var REVERSEMETHOD_IGNORE                    = 0;
8122 // Revert by changing the direction of the parameter sweep.
8123 // (From 1->0 instead of 0->1)
8124 var REVERSEMETHOD_INVERT_SWEEP              = 1;
8125 // Revert by subtracting the generated polygon from the target bound rect.
8126 var REVERSEMETHOD_SUBTRACT_POLYGON          = 2;
8127 // Combination of REVERSEMETHOD_INVERT_SWEEP and REVERSEMETHOD_SUBTRACT_POLYGON.
8128 var REVERSEMETHOD_SUBTRACT_AND_INVERT       = 3;
8129 // Reverse by rotating polygon 180 degrees.
8130 var REVERSEMETHOD_ROTATE_180                = 4;
8131 // Reverse by flipping polygon at the y axis.
8132 var REVERSEMETHOD_FLIP_X                    = 5;
8133 // Reverse by flipping polygon at the x axis.
8134 var REVERSEMETHOD_FLIP_Y                    = 6;
8136 // eslint-disable-next-line no-unused-vars
8137 var aReverseMethodOutMap = ['ignore', 'invert sweep', 'subtract polygon',
8138                             'subtract and invert', 'rotate 180', 'flip x', 'flip y'];
8141 // Transition filter info table
8143 var aTransitionInfoTable = {};
8145 // type: fake transition
8146 aTransitionInfoTable[0] = {};
8147 // subtype: default
8148 aTransitionInfoTable[0][0] =
8150     'class' : TRANSITION_INVALID,
8151     'rotationAngle' : 0.0,
8152     'scaleX' : 0.0,
8153     'scaleY' : 0.0,
8154     'reverseMethod' : REVERSEMETHOD_IGNORE,
8155     'outInvertsSweep' : false,
8156     'scaleIsotropically' : false
8159 aTransitionInfoTable[SNAKEWIPE_TRANSITION] = {};
8160 aTransitionInfoTable[SNAKEWIPE_TRANSITION][TOPLEFTVERTICAL_TRANS_SUBTYPE] =
8162     'class' : TRANSITION_CLIP_POLYPOLYGON,
8163     'rotationAngle' : -90.0,
8164     'scaleX' : 1.0,
8165     'scaleY' : 1.0,
8166     'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8167     'outInvertsSweep' : true,
8168     'scaleIsotropically' : false
8170 aTransitionInfoTable[SNAKEWIPE_TRANSITION][TOPLEFTHORIZONTAL_TRANS_SUBTYPE] =
8172     'class' : TRANSITION_CLIP_POLYPOLYGON,
8173     'rotationAngle' : 0.0,
8174     'scaleX' : 1.0,
8175     'scaleY' : 1.0,
8176     'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8177     'outInvertSweep' : true,
8178     'scaleIsotropically' : false
8180 aTransitionInfoTable[SNAKEWIPE_TRANSITION][TOPLEFTDIAGONAL_TRANS_SUBTYPE] =
8182     'class' : TRANSITION_CLIP_POLYPOLYGON,
8183     'rotationAngle' : 0.0,
8184     'scaleX' : 1.0,
8185     'scaleY' : 1.0,
8186     'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8187     'outInvertSweep' : true,
8188     'scaleIsotropically' : false
8190 aTransitionInfoTable[SNAKEWIPE_TRANSITION][TOPRIGHTDIAGONAL_TRANS_SUBTYPE] =
8192     'class' : TRANSITION_CLIP_POLYPOLYGON,
8193     'rotationAngle' : 0.0,
8194     'scaleX' : 1.0,
8195     'scaleY' : 1.0,
8196     'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8197     'outInvertSweep' : true,
8198     'scaleIsotropically' : false
8200 aTransitionInfoTable[SNAKEWIPE_TRANSITION][BOTTOMRIGHTDIAGONAL_TRANS_SUBTYPE] =
8202     'class' : TRANSITION_CLIP_POLYPOLYGON,
8203     'rotationAngle' : 180.0,
8204     'scaleX' : 1.0,
8205     'scaleY' : 1.0,
8206     'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8207     'outInvertSweep' : true,
8208     'scaleIsotropically' : false
8210 aTransitionInfoTable[SNAKEWIPE_TRANSITION][BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE] =
8212     'class' : TRANSITION_CLIP_POLYPOLYGON,
8213     'rotationAngle' : 180.0,
8214     'scaleX' : 1.0,
8215     'scaleY' : 1.0,
8216     'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8217     'outInvertSweep' : true,
8218     'scaleIsotropically' : false
8221 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION] = {};
8222 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][VERTICALTOPSAME_TRANS_SUBTYPE] =
8224     'class' : TRANSITION_CLIP_POLYPOLYGON,
8225     'rotationAngle' : 0.0,
8226     'scaleX' : 1.0,
8227     'scaleY' : 1.0,
8228     'reverseMethod' : REVERSEMETHOD_IGNORE,
8229     'outInvertSweep' : true,
8230     'scaleIsotropically' : false
8232 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][VERTICALBOTTOMSAME_TRANS_SUBTYPE] =
8234     'class' : TRANSITION_CLIP_POLYPOLYGON,
8235     'rotationAngle' : 180.0,
8236     'scaleX' : 1.0,
8237     'scaleY' : 1.0,
8238     'reverseMethod' : REVERSEMETHOD_IGNORE,
8239     'outInvertSweep' : true,
8240     'scaleIsotropically' : false
8242 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][VERTICALTOPLEFTOPPOSITE_TRANS_SUBTYPE] =
8244     'class' : TRANSITION_CLIP_POLYPOLYGON,
8245     'rotationAngle' : 0.0,
8246     'scaleX' : 1.0,
8247     'scaleY' : 1.0,
8248     'reverseMethod' : REVERSEMETHOD_IGNORE,
8249     'outInvertSweep' : true,
8250     'scaleIsotropically' : false
8252 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE] =
8254     'class' : TRANSITION_CLIP_POLYPOLYGON,
8255     'rotationAngle' : 0.0,
8256     'scaleX' : 1.0,
8257     'scaleY' : 1.0,
8258     'reverseMethod' : REVERSEMETHOD_IGNORE,
8259     'outInvertSweep' : true,
8260     'scaleIsotropically' : false
8262 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][HORIZONTALLEFTSAME_TRANS_SUBTYPE] =
8264     'class' : TRANSITION_CLIP_POLYPOLYGON,
8265     'rotationAngle' : -90.0,
8266     'scaleX' : 1.0,
8267     'scaleY' : 1.0,
8268     'reverseMethod' : REVERSEMETHOD_IGNORE,
8269     'outInvertSweep' : true,
8270     'scaleIsotropically' : false
8272 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][HORIZONTALRIGHTSAME_TRANS_SUBTYPE] =
8274     'class' : TRANSITION_CLIP_POLYPOLYGON,
8275     'rotationAngle' : 90.0,
8276     'scaleX' : 1.0,
8277     'scaleY' : 1.0,
8278     'reverseMethod' : REVERSEMETHOD_IGNORE,
8279     'outInvertSweep' : true,
8280     'scaleIsotropically' : false
8282 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE] =
8284     'class' : TRANSITION_CLIP_POLYPOLYGON,
8285     'rotationAngle' : -90.0,
8286     'scaleX' : 1.0,
8287     'scaleY' : 1.0,
8288     'reverseMethod' : REVERSEMETHOD_IGNORE,
8289     'outInvertSweep' : true,
8290     'scaleIsotropically' : false
8292 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][HORIZONTALTOPRIGHTOPPOSITE_TRANS_SUBTYPE] =
8294     'class' : TRANSITION_CLIP_POLYPOLYGON,
8295     'rotationAngle' : -90.0,
8296     'scaleX' : 1.0,
8297     'scaleY' : 1.0,
8298     'reverseMethod' : REVERSEMETHOD_IGNORE,
8299     'outInvertSweep' : true,
8300     'scaleIsotropically' : false
8302 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE] =
8304     'class' : TRANSITION_CLIP_POLYPOLYGON,
8305     'rotationAngle' : 0.0,
8306     'scaleX' : 1.0,
8307     'scaleY' : 1.0,
8308     'reverseMethod' : REVERSEMETHOD_IGNORE,
8309     'outInvertSweep' : true,
8310     'scaleIsotropically' : false
8312 aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE] =
8314     'class' : TRANSITION_CLIP_POLYPOLYGON,
8315     'rotationAngle' : 0.0,
8316     'scaleX' : 1.0,
8317     'scaleY' : 1.0,
8318     'reverseMethod' : REVERSEMETHOD_IGNORE,
8319     'outInvertSweep' : true,
8320     'scaleIsotropically' : false
8323 aTransitionInfoTable[SPIRALWIPE_TRANSITION] = {};
8324 aTransitionInfoTable[SPIRALWIPE_TRANSITION][TOPLEFTCLOCKWISE_TRANS_SUBTYPE] =
8326     'class' : TRANSITION_CLIP_POLYPOLYGON,
8327     'rotationAngle' : 0.0,
8328     'scaleX' : 1.0,
8329     'scaleY' : 1.0,
8330     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8331     'outInvertSweep' : true,
8332     'scaleIsotropically' : false
8334 aTransitionInfoTable[SPIRALWIPE_TRANSITION][TOPRIGHTCLOCKWISE_TRANS_SUBTYPE] =
8336     'class' : TRANSITION_CLIP_POLYPOLYGON,
8337     'rotationAngle' : 90.0,
8338     'scaleX' : 1.0,
8339     'scaleY' : 1.0,
8340     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8341     'outInvertSweep' : true,
8342     'scaleIsotropically' : false
8344 aTransitionInfoTable[SPIRALWIPE_TRANSITION][BOTTOMRIGHTCLOCKWISE_TRANS_SUBTYPE] =
8346     'class' : TRANSITION_CLIP_POLYPOLYGON,
8347     'rotationAngle' : 180.0,
8348     'scaleX' : 1.0,
8349     'scaleY' : 1.0,
8350     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8351     'outInvertSweep' : true,
8352     'scaleIsotropically' : false
8354 aTransitionInfoTable[SPIRALWIPE_TRANSITION][BOTTOMLEFTCLOCKWISE_TRANS_SUBTYPE] =
8356     'class' : TRANSITION_CLIP_POLYPOLYGON,
8357     'rotationAngle' : 270.0,
8358     'scaleX' : 1.0,
8359     'scaleY' : 1.0,
8360     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8361     'outInvertSweep' : true,
8362     'scaleIsotropically' : false
8364 aTransitionInfoTable[SPIRALWIPE_TRANSITION][TOPLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE] =
8366     'class' : TRANSITION_CLIP_POLYPOLYGON,
8367     'rotationAngle' : 90.0,
8368     'scaleX' : 1.0,
8369     'scaleY' : 1.0,
8370     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8371     'outInvertSweep' : true,
8372     'scaleIsotropically' : false
8374 aTransitionInfoTable[SPIRALWIPE_TRANSITION][TOPRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE] =
8376     'class' : TRANSITION_CLIP_POLYPOLYGON,
8377     'rotationAngle' : 180.0,
8378     'scaleX' : 1.0,
8379     'scaleY' : 1.0,
8380     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8381     'outInvertSweep' : true,
8382     'scaleIsotropically' : false
8384 aTransitionInfoTable[SPIRALWIPE_TRANSITION][BOTTOMRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE] =
8386     'class' : TRANSITION_CLIP_POLYPOLYGON,
8387     'rotationAngle' : 270.0,
8388     'scaleX' : 1.0,
8389     'scaleY' : 1.0,
8390     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8391     'outInvertSweep' : true,
8392     'scaleIsotropically' : false
8394 aTransitionInfoTable[SPIRALWIPE_TRANSITION][BOTTOMLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE] =
8396     'class' : TRANSITION_CLIP_POLYPOLYGON,
8397     'rotationAngle' : 0.0,
8398     'scaleX' : 1.0,
8399     'scaleY' : 1.0,
8400     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8401     'outInvertSweep' : true,
8402     'scaleIsotropically' : false
8405 aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION] = {};
8406 aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][TWOBOXTOP_TRANS_SUBTYPE] =
8408     'class' : TRANSITION_CLIP_POLYPOLYGON,
8409     'rotationAngle' : 90.0,
8410     'scaleX' : 1.0,
8411     'scaleY' : 1.0,
8412     'reverseMethod' : REVERSEMETHOD_IGNORE,
8413     'outInvertSweep' : true,
8414     'scaleIsotropically' : false
8416 aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][TWOBOXBOTTOM_TRANS_SUBTYPE] =
8418     'class' : TRANSITION_CLIP_POLYPOLYGON,
8419     'rotationAngle' : -90.0,
8420     'scaleX' : 1.0,
8421     'scaleY' : 1.0,
8422     'reverseMethod' : REVERSEMETHOD_IGNORE,
8423     'outInvertSweep' : true,
8424     'scaleIsotropically' : false
8426 aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][TWOBOXLEFT_TRANS_SUBTYPE] =
8428     'class' : TRANSITION_CLIP_POLYPOLYGON,
8429     'rotationAngle' : 0.0,
8430     'scaleX' : 1.0,
8431     'scaleY' : 1.0,
8432     'reverseMethod' : REVERSEMETHOD_IGNORE,
8433     'outInvertSweep' : true,
8434     'scaleIsotropically' : false
8436 aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][TWOBOXRIGHT_TRANS_SUBTYPE] =
8438     'class' : TRANSITION_CLIP_POLYPOLYGON,
8439     'rotationAngle' : 180.0,
8440     'scaleX' : 1.0,
8441     'scaleY' : 1.0,
8442     'reverseMethod' : REVERSEMETHOD_IGNORE,
8443     'outInvertSweep' : true,
8444     'scaleIsotropically' : false
8446 aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][FOURBOXVERTICAL_TRANS_SUBTYPE] =
8448     'class' : TRANSITION_CLIP_POLYPOLYGON,
8449     'rotationAngle' : 90.0,
8450     'scaleX' : 1.0,
8451     'scaleY' : 1.0,
8452     'reverseMethod' : REVERSEMETHOD_IGNORE,
8453     'outInvertSweep' : true,
8454     'scaleIsotropically' : false
8456 aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][FOURBOXHORIZONTAL_TRANS_SUBTYPE] =
8458     'class' : TRANSITION_CLIP_POLYPOLYGON,
8459     'rotationAngle' : 0.0,
8460     'scaleX' : 1.0,
8461     'scaleY' : 1.0,
8462     'reverseMethod' : REVERSEMETHOD_IGNORE,
8463     'outInvertSweep' : true,
8464     'scaleIsotropically' : false
8467 aTransitionInfoTable[BARNDOORWIPE_TRANSITION] = {};
8468 aTransitionInfoTable[BARNDOORWIPE_TRANSITION][VERTICAL_TRANS_SUBTYPE] =
8470     'class' : TRANSITION_CLIP_POLYPOLYGON,
8471     'rotationAngle': 0.0,
8472     'scaleX': 1.0,
8473     'scaleY': 1.0,
8474     'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
8475     'outInvertsSweep': true,
8476     'scaleIsotropically': false
8478 aTransitionInfoTable[BARNDOORWIPE_TRANSITION][HORIZONTAL_TRANS_SUBTYPE] =
8480     'class' : TRANSITION_CLIP_POLYPOLYGON,
8481     'rotationAngle': 90.0,
8482     'scaleX': 1.0,
8483     'scaleY': 1.0,
8484     'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
8485     'outInvertsSweep': true,
8486     'scaleIsotropically': false
8488 aTransitionInfoTable[BARNDOORWIPE_TRANSITION][DIAGONALBOTTOMLEFT_TRANS_SUBTYPE] =
8490     'class' : TRANSITION_CLIP_POLYPOLYGON,
8491     'rotationAngle': 45.0,
8492     'scaleX': Math.SQRT2,
8493     'scaleY': Math.SQRT2,
8494     'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
8495     'outInvertsSweep': true,
8496     'scaleIsotropically': false
8498 aTransitionInfoTable[BARNDOORWIPE_TRANSITION][DIAGONALTOPLEFT_TRANS_SUBTYPE] =
8500     'class' : TRANSITION_CLIP_POLYPOLYGON,
8501     'rotationAngle': -45.0,
8502     'scaleX': Math.SQRT2,
8503     'scaleY': Math.SQRT2,
8504     'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
8505     'outInvertsSweep': true,
8506     'scaleIsotropically': false
8509 aTransitionInfoTable[MISCDIAGONALWIPE_TRANSITION] = {};
8510 aTransitionInfoTable[MISCDIAGONALWIPE_TRANSITION][DOUBLEBARNDOOR_TRANS_SUBTYPE] =
8512     'class' : TRANSITION_CLIP_POLYPOLYGON,
8513     'rotationAngle': 45.0,
8514     'scaleX': Math.SQRT2,
8515     'scaleY': Math.SQRT2,
8516     'reverseMethod': REVERSEMETHOD_IGNORE,
8517     'outInvertsSweep': true,
8518     'scaleIsotropically': false
8520 aTransitionInfoTable[MISCDIAGONALWIPE_TRANSITION][DOUBLEDIAMOND_TRANS_SUBTYPE] =
8522     'class' : TRANSITION_CLIP_POLYPOLYGON,
8523     'rotationAngle': 0.0,
8524     'scaleX': 1,
8525     'scaleY': 1,
8526     'reverseMethod': REVERSEMETHOD_IGNORE,
8527     'outInvertsSweep': true,
8528     'scaleIsotropically': false
8531 aTransitionInfoTable[IRISWIPE_TRANSITION] = {};
8532 aTransitionInfoTable[IRISWIPE_TRANSITION][RECTANGLE_TRANS_SUBTYPE] =
8534     'class' : TRANSITION_CLIP_POLYPOLYGON,
8535     'rotationAngle': 0.0,
8536     'scaleX': 1.0,
8537     'scaleY': 1.0,
8538     'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
8539     'outInvertsSweep': true,
8540     'scaleIsotropically': false
8543 aTransitionInfoTable[IRISWIPE_TRANSITION][DIAMOND_TRANS_SUBTYPE] =
8545     'class' : TRANSITION_CLIP_POLYPOLYGON,
8546     'rotationAngle': 45.0,
8547     'scaleX': Math.SQRT2,
8548     'scaleY': Math.SQRT2,
8549     'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
8550     'outInvertsSweep': true,
8551     'scaleIsotropically': false
8554 aTransitionInfoTable[ZIGZAGWIPE_TRANSITION] = {};
8555 aTransitionInfoTable[ZIGZAGWIPE_TRANSITION][LEFTTORIGHT_TRANS_SUBTYPE] =
8557     'class' : TRANSITION_CLIP_POLYPOLYGON,
8558     'rotationAngle' : 0.0,
8559     'scaleX' : 1.0,
8560     'scaleY' : 1.0,
8561     'reverseMethod' : REVERSEMETHOD_FLIP_X,
8562     'outInvertsSweep' : true,
8563     'scaleIsotropically' : false
8565 aTransitionInfoTable[ZIGZAGWIPE_TRANSITION][TOPTOBOTTOM_TRANS_SUBTYPE] =
8567     'class' : TRANSITION_CLIP_POLYPOLYGON,
8568     'rotationAngle' : 90.0,
8569     'scaleX' : 1.0,
8570     'scaleY' : 1.0,
8571     'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8572     'outInvertsSweep' : true,
8573     'scaleIsotropically' : false
8576 aTransitionInfoTable[BARNZIGZAGWIPE_TRANSITION] = {};
8577 aTransitionInfoTable[BARNZIGZAGWIPE_TRANSITION][VERTICAL_TRANS_SUBTYPE] =
8579     'class' : TRANSITION_CLIP_POLYPOLYGON,
8580     'rotationAngle' : 0.0,
8581     'scaleX' : 1.0,
8582     'scaleY' : 1.0,
8583     'reverseMethod' : REVERSEMETHOD_IGNORE,
8584     'outInvertsSweep' : true,
8585     'scaleIsotropically' : false
8587 aTransitionInfoTable[BARNZIGZAGWIPE_TRANSITION][HORIZONTAL_TRANS_SUBTYPE] =
8589     'class' : TRANSITION_CLIP_POLYPOLYGON,
8590     'rotationAngle' : 90.0,
8591     'scaleX' : 1.0,
8592     'scaleY' : 1.0,
8593     'reverseMethod' : REVERSEMETHOD_IGNORE,
8594     'outInvertsSweep' : true,
8595     'scaleIsotropically' : false
8598 aTransitionInfoTable[BARWIPE_TRANSITION] = {};
8599 aTransitionInfoTable[BARWIPE_TRANSITION][LEFTTORIGHT_TRANS_SUBTYPE] =
8601     'class' : TRANSITION_CLIP_POLYPOLYGON,
8602     'rotationAngle' : 0.0,
8603     'scaleX' : 1.0,
8604     'scaleY' : 1.0,
8605     'reverseMethod' : REVERSEMETHOD_FLIP_X,
8606     'outInvertsSweep' : false,
8607     'scaleIsotropically' : false
8609 aTransitionInfoTable[BARWIPE_TRANSITION][TOPTOBOTTOM_TRANS_SUBTYPE] =
8611     'class' : TRANSITION_CLIP_POLYPOLYGON,
8612     'rotationAngle' : 90.0,
8613     'scaleX' : 1.0,
8614     'scaleY' : 1.0,
8615     'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8616     'outInvertsSweep' : false,
8617     'scaleIsotropically' : false
8620 aTransitionInfoTable[WATERFALLWIPE_TRANSITION] = {};
8621 aTransitionInfoTable[WATERFALLWIPE_TRANSITION][VERTICALLEFT_TRANS_SUBTYPE] =
8623     'class' : TRANSITION_CLIP_POLYPOLYGON,
8624     'rotationAngle' : 0.0,
8625     'scaleX' : 1.0,
8626     'scaleY' : 1.0,
8627     'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8628     'outInvertsSweep' : true,
8629     'scaleIsotropically' : false
8631 aTransitionInfoTable[WATERFALLWIPE_TRANSITION][VERTICALRIGHT_TRANS_SUBTYPE] =
8633     'class' : TRANSITION_CLIP_POLYPOLYGON,
8634     'rotationAngle' : 0.0,
8635     'scaleX' : 1.0,
8636     'scaleY' : 1.0,
8637     'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8638     'outInvertsSweep' : true,
8639     'scaleIsotropically' : false
8641 aTransitionInfoTable[WATERFALLWIPE_TRANSITION][HORIZONTALLEFT_TRANS_SUBTYPE] =
8643     'class' : TRANSITION_CLIP_POLYPOLYGON,
8644     'rotationAngle' : -90.0,
8645     'scaleX' : 1.0,
8646     'scaleY' : 1.0,
8647     'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8648     'outInvertsSweep' : true,
8649     'scaleIsotropically' : false
8651 aTransitionInfoTable[WATERFALLWIPE_TRANSITION][HORIZONTALRIGHT_TRANS_SUBTYPE] =
8653     'class' : TRANSITION_CLIP_POLYPOLYGON,
8654     'rotationAngle' : 90.0,
8655     'scaleX' : 1.0,
8656     'scaleY' : 1.0,
8657     'reverseMethod' : REVERSEMETHOD_ROTATE_180,
8658     'outInvertsSweep' : true,
8659     'scaleIsotropically' : false
8662 aTransitionInfoTable[BOXWIPE_TRANSITION] = {};
8663 aTransitionInfoTable[BOXWIPE_TRANSITION][TOPLEFT_TRANS_SUBTYPE] =
8665     'class' : TRANSITION_CLIP_POLYPOLYGON,
8666     'rotationAngle' : 0.0,
8667     'scaleX' : 1.0,
8668     'scaleY' : 1.0,
8669     'reverseMethod' : REVERSEMETHOD_IGNORE,
8670     'outInvertsSweep' : true,
8671     'scaleIsotropically' : false
8673 aTransitionInfoTable[BOXWIPE_TRANSITION][TOPRIGHT_TRANS_SUBTYPE] =
8675     'class' : TRANSITION_CLIP_POLYPOLYGON,
8676     'rotationAngle' : 90.0,
8677     'scaleX' : 1.0,
8678     'scaleY' : 1.0,
8679     'reverseMethod' : REVERSEMETHOD_IGNORE,
8680     'outInvertsSweep' : true,
8681     'scaleIsotropically' : false
8683 aTransitionInfoTable[BOXWIPE_TRANSITION][BOTTOMRIGHT_TRANS_SUBTYPE] =
8685     'class' : TRANSITION_CLIP_POLYPOLYGON,
8686     'rotationAngle' : 180.0,
8687     'scaleX' : 1.0,
8688     'scaleY' : 1.0,
8689     'reverseMethod' : REVERSEMETHOD_IGNORE,
8690     'outInvertsSweep' : true,
8691     'scaleIsotropically' : false
8693 aTransitionInfoTable[BOXWIPE_TRANSITION][BOTTOMLEFT_TRANS_SUBTYPE] =
8695     'class' : TRANSITION_CLIP_POLYPOLYGON,
8696     'rotationAngle' : -90.0,
8697     'scaleX' : 1.0,
8698     'scaleY' : 1.0,
8699     'reverseMethod' : REVERSEMETHOD_IGNORE,
8700     'outInvertsSweep' : true,
8701     'scaleIsotropically' : false
8703 aTransitionInfoTable[BOXWIPE_TRANSITION][TOPCENTER_TRANS_SUBTYPE] =
8705     'class' : TRANSITION_CLIP_POLYPOLYGON,
8706     'rotationAngle' : 0.0,
8707     'scaleX' : 1.0,
8708     'scaleY' : 1.0,
8709     'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8710     'outInvertsSweep' : true,
8711     'scaleIsotropically' : false
8713 aTransitionInfoTable[BOXWIPE_TRANSITION][RIGHTCENTER_TRANS_SUBTYPE] =
8715     'class' : TRANSITION_CLIP_POLYPOLYGON,
8716     'rotationAngle' : 90.0,
8717     'scaleX' : 1.0,
8718     'scaleY' : 1.0,
8719     'reverseMethod' : REVERSEMETHOD_FLIP_X,
8720     'outInvertsSweep' : true,
8721     'scaleIsotropically' : false
8723 aTransitionInfoTable[BOXWIPE_TRANSITION][BOTTOMCENTER_TRANS_SUBTYPE] =
8725     'class' : TRANSITION_CLIP_POLYPOLYGON,
8726     'rotationAngle' : 180.0,
8727     'scaleX' : 1.0,
8728     'scaleY' : 1.0,
8729     'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8730     'outInvertsSweep' : true,
8731     'scaleIsotropically' : false
8733 aTransitionInfoTable[BOXWIPE_TRANSITION][LEFTCENTER_TRANS_SUBTYPE] =
8735     'class' : TRANSITION_CLIP_POLYPOLYGON,
8736     'rotationAngle' : -90.0,
8737     'scaleX' : 1.0,
8738     'scaleY' : 1.0,
8739     'reverseMethod' : REVERSEMETHOD_FLIP_X,
8740     'outInvertsSweep' : true,
8741     'scaleIsotropically' : false
8744 aTransitionInfoTable[FOURBOXWIPE_TRANSITION] = {};
8745 aTransitionInfoTable[FOURBOXWIPE_TRANSITION][CORNERSIN_TRANS_SUBTYPE] =
8746 aTransitionInfoTable[FOURBOXWIPE_TRANSITION][CORNERSOUT_TRANS_SUBTYPE] =
8748     'class' : TRANSITION_CLIP_POLYPOLYGON,
8749     'rotationAngle' : 0.0,
8750     'scaleX' : 1.0,
8751     'scaleY' : 1.0,
8752     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8753     'outInvertsSweep' : true,
8754     'scaleIsotropically' : false
8757 aTransitionInfoTable[ELLIPSEWIPE_TRANSITION] = {};
8758 aTransitionInfoTable[ELLIPSEWIPE_TRANSITION][CIRCLE_TRANS_SUBTYPE] =
8760     'class' : TRANSITION_CLIP_POLYPOLYGON,
8761     'rotationAngle' : 0.0,
8762     'scaleX' : 1.0,
8763     'scaleY' : 1.0,
8764     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8765     'outInvertsSweep' : true,
8766     'scaleIsotropically' : true
8768 aTransitionInfoTable[ELLIPSEWIPE_TRANSITION][HORIZONTAL_TRANS_SUBTYPE] =
8770     'class' : TRANSITION_CLIP_POLYPOLYGON,
8771     'rotationAngle' : 0.0,
8772     'scaleX' : 1.0,
8773     'scaleY' : 1.0,
8774     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8775     'outInvertsSweep' : true,
8776     'scaleIsotropically' : false
8778 aTransitionInfoTable[ELLIPSEWIPE_TRANSITION][VERTICAL_TRANS_SUBTYPE] =
8780     'class' : TRANSITION_CLIP_POLYPOLYGON,
8781     'rotationAngle' : 90.0,
8782     'scaleX' : 1.0,
8783     'scaleY' : 1.0,
8784     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8785     'outInvertsSweep' : true,
8786     'scaleIsotropically' : false
8789 aTransitionInfoTable[CLOCKWIPE_TRANSITION] = {};
8790 aTransitionInfoTable[CLOCKWIPE_TRANSITION][CLOCKWISETWELVE_TRANS_SUBTYPE] =
8792     'class' : TRANSITION_CLIP_POLYPOLYGON,
8793     'rotationAngle' : 0.0,
8794     'scaleX' : 1.0,
8795     'scaleY' : 1.0,
8796     'reverseMethod' : REVERSEMETHOD_FLIP_X,
8797     'outInvertsSweep' : true,
8798     'scaleIsotropically' : false
8800 aTransitionInfoTable[CLOCKWIPE_TRANSITION][CLOCKWISETHREE_TRANS_SUBTYPE] =
8802     'class' : TRANSITION_CLIP_POLYPOLYGON,
8803     'rotationAngle' : 90.0,
8804     'scaleX' : 1.0,
8805     'scaleY' : 1.0,
8806     'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8807     'outInvertsSweep' : true,
8808     'scaleIsotropically' : false
8810 aTransitionInfoTable[CLOCKWIPE_TRANSITION][CLOCKWISESIX_TRANS_SUBTYPE] =
8812     'class' : TRANSITION_CLIP_POLYPOLYGON,
8813     'rotationAngle' : 180.0,
8814     'scaleX' : 1.0,
8815     'scaleY' : 1.0,
8816     'reverseMethod' : REVERSEMETHOD_FLIP_X,
8817     'outInvertsSweep' : true,
8818     'scaleIsotropically' : false
8820 aTransitionInfoTable[CLOCKWIPE_TRANSITION][CLOCKWISENINE_TRANS_SUBTYPE] =
8822     'class' : TRANSITION_CLIP_POLYPOLYGON,
8823     'rotationAngle' : 270.0,
8824     'scaleX' : 1.0,
8825     'scaleY' : 1.0,
8826     'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8827     'outInvertsSweep' : true,
8828     'scaleIsotropically' : false
8831 aTransitionInfoTable[VEEWIPE_TRANSITION] = {};
8832 aTransitionInfoTable[VEEWIPE_TRANSITION][DOWN_TRANS_SUBTYPE] =
8834     'class' : TRANSITION_CLIP_POLYPOLYGON,
8835     'rotationAngle' : 0.0,
8836     'scaleX' : 1.0,
8837     'scaleY' : 1.0,
8838     'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8839     'outInvertsSweep' : true,
8840     'scaleIsotropically' : false
8842 aTransitionInfoTable[VEEWIPE_TRANSITION][LEFT_TRANS_SUBTYPE] =
8844     'class' : TRANSITION_CLIP_POLYPOLYGON,
8845     'rotationAngle' : 90.0,
8846     'scaleX' : 1.0,
8847     'scaleY' : 1.0,
8848     'reverseMethod' : REVERSEMETHOD_FLIP_X,
8849     'outInvertsSweep' : true,
8850     'scaleIsotropically' : false
8852 aTransitionInfoTable[VEEWIPE_TRANSITION][UP_TRANS_SUBTYPE] =
8854     'class' : TRANSITION_CLIP_POLYPOLYGON,
8855     'rotationAngle' : 180.0,
8856     'scaleX' : 1.0,
8857     'scaleY' : 1.0,
8858     'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8859     'outInvertsSweep' : true,
8860     'scaleIsotropically' : false
8862 aTransitionInfoTable[VEEWIPE_TRANSITION][RIGHT_TRANS_SUBTYPE] =
8864     'class' : TRANSITION_CLIP_POLYPOLYGON,
8865     'rotationAngle' : -90.0,
8866     'scaleX' : 1.0,
8867     'scaleY' : 1.0,
8868     'reverseMethod' : REVERSEMETHOD_FLIP_X,
8869     'outInvertsSweep' : true,
8870     'scaleIsotropically' : false
8873 aTransitionInfoTable[FANWIPE_TRANSITION] = {};
8874 aTransitionInfoTable[FANWIPE_TRANSITION][CENTERTOP_TRANS_SUBTYPE] =
8876     'class' : TRANSITION_CLIP_POLYPOLYGON,
8877     'rotationAngle' : 0.0,
8878     'scaleX' : 1.0,
8879     'scaleY' : 1.0,
8880     'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8881     'outInvertsSweep' : true,
8882     'scaleIsotropically' : false
8884 aTransitionInfoTable[FANWIPE_TRANSITION][CENTERRIGHT_TRANS_SUBTYPE] =
8886     'class' : TRANSITION_CLIP_POLYPOLYGON,
8887     'rotationAngle' : 90.0,
8888     'scaleX' : 1.0,
8889     'scaleY' : 1.0,
8890     'reverseMethod' : REVERSEMETHOD_FLIP_X,
8891     'outInvertsSweep' : true,
8892     'scaleIsotropically' : false
8894 aTransitionInfoTable[FANWIPE_TRANSITION][TOP_TRANS_SUBTYPE] =
8896     'class' : TRANSITION_CLIP_POLYPOLYGON,
8897     'rotationAngle' : 180.0,
8898     'scaleX' : 1.0,
8899     'scaleY' : 1.0,
8900     'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8901     'outInvertsSweep' : true,
8902     'scaleIsotropically' : false
8904 aTransitionInfoTable[FANWIPE_TRANSITION][RIGHT_TRANS_SUBTYPE] =
8906     'class' : TRANSITION_CLIP_POLYPOLYGON,
8907     'rotationAngle' : -90.0,
8908     'scaleX' : 1.0,
8909     'scaleY' : 1.0,
8910     'reverseMethod' : REVERSEMETHOD_FLIP_X,
8911     'outInvertsSweep' : true,
8912     'scaleIsotropically' : false
8914 aTransitionInfoTable[FANWIPE_TRANSITION][BOTTOM_TRANS_SUBTYPE] =
8916     'class' : TRANSITION_CLIP_POLYPOLYGON,
8917     'rotationAngle' : 180.0,
8918     'scaleX' : 1.0,
8919     'scaleY' : 1.0,
8920     'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8921     'outInvertsSweep' : true,
8922     'scaleIsotropically' : false
8924 aTransitionInfoTable[FANWIPE_TRANSITION][LEFT_TRANS_SUBTYPE] =
8926     'class' : TRANSITION_CLIP_POLYPOLYGON,
8927     'rotationAngle' : 90.0,
8928     'scaleX' : 1.0,
8929     'scaleY' : 1.0,
8930     'reverseMethod' : REVERSEMETHOD_FLIP_X,
8931     'outInvertsSweep' : true,
8932     'scaleIsotropically' : false
8936 aTransitionInfoTable[PINWHEELWIPE_TRANSITION] = {};
8937 aTransitionInfoTable[PINWHEELWIPE_TRANSITION][ONEBLADE_TRANS_SUBTYPE] =
8938 aTransitionInfoTable[PINWHEELWIPE_TRANSITION][TWOBLADEVERTICAL_TRANS_SUBTYPE] =
8939 aTransitionInfoTable[PINWHEELWIPE_TRANSITION][THREEBLADE_TRANS_SUBTYPE] =
8940 aTransitionInfoTable[PINWHEELWIPE_TRANSITION][FOURBLADE_TRANS_SUBTYPE] =
8941 aTransitionInfoTable[PINWHEELWIPE_TRANSITION][EIGHTBLADE_TRANS_SUBTYPE] =
8943     'class' : TRANSITION_CLIP_POLYPOLYGON,
8944     'rotationAngle' : 0.0,
8945     'scaleX' : 1.0,
8946     'scaleY' : 1.0,
8947     'reverseMethod' : REVERSEMETHOD_FLIP_X,
8948     'outInvertsSweep' : true,
8949     'scaleIsotropically' : true
8951 aTransitionInfoTable[PINWHEELWIPE_TRANSITION][TWOBLADEHORIZONTAL_TRANS_SUBTYPE] =
8953     'class' : TRANSITION_CLIP_POLYPOLYGON,
8954     'rotationAngle' : -90.0,
8955     'scaleX' : 1.0,
8956     'scaleY' : 1.0,
8957     'reverseMethod' : REVERSEMETHOD_FLIP_Y,
8958     'outInvertsSweep' : true,
8959     'scaleIsotropically' : true
8962 aTransitionInfoTable[PUSHWIPE_TRANSITION] = {};
8963 aTransitionInfoTable[PUSHWIPE_TRANSITION][FROMLEFT_TRANS_SUBTYPE] =
8964 aTransitionInfoTable[PUSHWIPE_TRANSITION][FROMTOP_TRANS_SUBTYPE] =
8965 aTransitionInfoTable[PUSHWIPE_TRANSITION][FROMRIGHT_TRANS_SUBTYPE] =
8966 aTransitionInfoTable[PUSHWIPE_TRANSITION][FROMBOTTOM_TRANS_SUBTYPE] =
8968     'class' : TRANSITION_SPECIAL,
8969     'rotationAngle' : 0.0,
8970     'scaleX' : 1.0,
8971     'scaleY' : 1.0,
8972     'reverseMethod' : REVERSEMETHOD_IGNORE,
8973     'outInvertsSweep' : true,
8974     'scaleIsotropically' : false
8978 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION] = {};
8979 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISETOP_TRANS_SUBTYPE] =
8981     'class' : TRANSITION_CLIP_POLYPOLYGON,
8982     'rotationAngle' : 0.0,
8983     'scaleX' : 1.0,
8984     'scaleY' : 1.0,
8985     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8986     'outInvertsSweep' : true,
8987     'scaleIsotropically' : false
8989 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISERIGHT_TRANS_SUBTYPE] =
8991     'class' : TRANSITION_CLIP_POLYPOLYGON,
8992     'rotationAngle' : 90.0,
8993     'scaleX' : 1.0,
8994     'scaleY' : 1.0,
8995     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
8996     'outInvertsSweep' : true,
8997     'scaleIsotropically' : false
8999 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISEBOTTOM_TRANS_SUBTYPE] =
9001     'class' : TRANSITION_CLIP_POLYPOLYGON,
9002     'rotationAngle' : 180.0,
9003     'scaleX' : 1.0,
9004     'scaleY' : 1.0,
9005     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9006     'outInvertsSweep' : true,
9007     'scaleIsotropically' : false
9009 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISELEFT_TRANS_SUBTYPE] =
9011     'class' : TRANSITION_CLIP_POLYPOLYGON,
9012     'rotationAngle' : 270.0,
9013     'scaleX' : 1.0,
9014     'scaleY' : 1.0,
9015     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9016     'outInvertsSweep' : true,
9017     'scaleIsotropically' : false
9019 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISETOPLEFT_TRANS_SUBTYPE] =
9021     'class' : TRANSITION_CLIP_POLYPOLYGON,
9022     'rotationAngle' : 0.0,
9023     'scaleX' : 1.0,
9024     'scaleY' : 1.0,
9025     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9026     'outInvertsSweep' : true,
9027     'scaleIsotropically' : false
9029 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][COUNTERCLOCKWISEBOTTOMLEFT_TRANS_SUBTYPE] =
9031     'class' : TRANSITION_CLIP_POLYPOLYGON,
9032     'rotationAngle' : 180.0,
9033     'scaleX' : 1.0,
9034     'scaleY' : 1.0,
9035     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9036     'outInvertsSweep' : true,
9037     'scaleIsotropically' : false
9039 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISEBOTTOMRIGHT_TRANS_SUBTYPE] =
9041     'class' : TRANSITION_CLIP_POLYPOLYGON,
9042     'rotationAngle' : 180.0,
9043     'scaleX' : 1.0,
9044     'scaleY' : 1.0,
9045     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9046     'outInvertsSweep' : true,
9047     'scaleIsotropically' : false
9049 aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][COUNTERCLOCKWISETOPRIGHT_TRANS_SUBTYPE] =
9051     'class' : TRANSITION_CLIP_POLYPOLYGON,
9052     'rotationAngle' : 0.0,
9053     'scaleX' : 1.0,
9054     'scaleY' : 1.0,
9055     'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
9056     'outInvertsSweep' : true,
9057     'scaleIsotropically' : false
9060 aTransitionInfoTable[SLIDEWIPE_TRANSITION] = {};
9061 aTransitionInfoTable[SLIDEWIPE_TRANSITION][FROMLEFT_TRANS_SUBTYPE] =
9062 aTransitionInfoTable[SLIDEWIPE_TRANSITION][FROMTOP_TRANS_SUBTYPE] =
9063 aTransitionInfoTable[SLIDEWIPE_TRANSITION][FROMRIGHT_TRANS_SUBTYPE] =
9064 aTransitionInfoTable[SLIDEWIPE_TRANSITION][FROMBOTTOM_TRANS_SUBTYPE] =
9066     'class' : TRANSITION_SPECIAL,
9067     'rotationAngle' : 0.0,
9068     'scaleX' : 1.0,
9069     'scaleY' : 1.0,
9070     'reverseMethod' : REVERSEMETHOD_IGNORE,
9071     'outInvertsSweep' : true,
9072     'scaleIsotropically' : false
9075 aTransitionInfoTable[FADE_TRANSITION] = {};
9076 aTransitionInfoTable[FADE_TRANSITION][CROSSFADE_TRANS_SUBTYPE] =
9077 aTransitionInfoTable[FADE_TRANSITION][FADETOCOLOR_TRANS_SUBTYPE] =
9078 aTransitionInfoTable[FADE_TRANSITION][FADEFROMCOLOR_TRANS_SUBTYPE] =
9079 aTransitionInfoTable[FADE_TRANSITION][FADEOVERCOLOR_TRANS_SUBTYPE] =
9081     'class' : TRANSITION_SPECIAL,
9082     'rotationAngle' : 0.0,
9083     'scaleX' : 1.0,
9084     'scaleY' : 1.0,
9085     'reverseMethod' : REVERSEMETHOD_IGNORE,
9086     'outInvertsSweep' : true,
9087     'scaleIsotropically' : false
9091 aTransitionInfoTable[RANDOMBARWIPE_TRANSITION] = {};
9092 aTransitionInfoTable[RANDOMBARWIPE_TRANSITION][VERTICAL_TRANS_SUBTYPE] =
9094     'class' : TRANSITION_CLIP_POLYPOLYGON,
9095     'rotationAngle' : 90.0,
9096     'scaleX' : 1.0,
9097     'scaleY' : 1.0,
9098     'reverseMethod' : REVERSEMETHOD_IGNORE,
9099     'outInvertsSweep' : true,
9100     'scaleIsotropically' : false
9102 aTransitionInfoTable[RANDOMBARWIPE_TRANSITION][HORIZONTAL_TRANS_SUBTYPE] =
9104     'class' : TRANSITION_CLIP_POLYPOLYGON,
9105     'rotationAngle' : 0.0,
9106     'scaleX' : 1.0,
9107     'scaleY' : 1.0,
9108     'reverseMethod' : REVERSEMETHOD_IGNORE,
9109     'outInvertsSweep' : true,
9110     'scaleIsotropically' : false
9113 aTransitionInfoTable[CHECKERBOARDWIPE_TRANSITION] = {};
9114 aTransitionInfoTable[CHECKERBOARDWIPE_TRANSITION][DOWN_TRANS_SUBTYPE] =
9116     'class' : TRANSITION_CLIP_POLYPOLYGON,
9117     'rotationAngle' : 90.0,
9118     'scaleX' : 1.0,
9119     'scaleY' : 1.0,
9120     'reverseMethod' : REVERSEMETHOD_FLIP_Y,
9121     'outInvertsSweep' : true,
9122     'scaleIsotropically' : false
9124 aTransitionInfoTable[CHECKERBOARDWIPE_TRANSITION][ACROSS_TRANS_SUBTYPE] =
9126     'class' : TRANSITION_CLIP_POLYPOLYGON,
9127     'rotationAngle' : 0.0,
9128     'scaleX' : 1.0,
9129     'scaleY' : 1.0,
9130     'reverseMethod' : REVERSEMETHOD_FLIP_X,
9131     'outInvertsSweep' : true,
9132     'scaleIsotropically' : false
9135 aTransitionInfoTable[DISSOLVE_TRANSITION] = {};
9136 aTransitionInfoTable[DISSOLVE_TRANSITION][DEFAULT_TRANS_SUBTYPE] =
9138     'class' : TRANSITION_CLIP_POLYPOLYGON,
9139     'rotationAngle' : 0.0,
9140     'scaleX' : 1.0,
9141     'scaleY' : 1.0,
9142     'reverseMethod' : REVERSEMETHOD_IGNORE,
9143     'outInvertsSweep' : true,
9144     'scaleIsotropically' : true
9148 // Transition tables
9150 function createStateTransitionTable()
9152     var aSTT = {};
9154     aSTT[RESTART_MODE_NEVER] = {};
9155     aSTT[RESTART_MODE_WHEN_NOT_ACTIVE] = {};
9156     aSTT[RESTART_MODE_ALWAYS] = {};
9158     // transition table for restart=NEVER, fill=REMOVE
9159     var aTable =
9160     aSTT[RESTART_MODE_NEVER][FILL_MODE_REMOVE] = {};
9161     aTable[INVALID_NODE]        = INVALID_NODE;
9162     aTable[UNRESOLVED_NODE]     = RESOLVED_NODE | ENDED_NODE;
9163     aTable[RESOLVED_NODE]       = ACTIVE_NODE | ENDED_NODE;
9164     aTable[ACTIVE_NODE]         = ENDED_NODE;
9165     aTable[FROZEN_NODE]         = INVALID_NODE;  // this state is unreachable here
9166     aTable[ENDED_NODE]          = ENDED_NODE;    // this state is a sink here (cannot restart)
9168     // transition table for restart=NEVER, fill=FREEZE
9169     aTable =
9170     aSTT[RESTART_MODE_NEVER][FILL_MODE_FREEZE] =
9171     aSTT[RESTART_MODE_NEVER][FILL_MODE_HOLD] =
9172     aSTT[RESTART_MODE_NEVER][FILL_MODE_TRANSITION] = {};
9173     aTable[INVALID_NODE]        = INVALID_NODE;
9174     aTable[UNRESOLVED_NODE]     = RESOLVED_NODE | ENDED_NODE;
9175     aTable[RESOLVED_NODE]       = ACTIVE_NODE | ENDED_NODE;
9176     aTable[ACTIVE_NODE]         = FROZEN_NODE | ENDED_NODE;
9177     aTable[FROZEN_NODE]         = ENDED_NODE;
9178     aTable[ENDED_NODE]          = ENDED_NODE;   // this state is a sink here (cannot restart)
9180     // transition table for restart=WHEN_NOT_ACTIVE, fill=REMOVE
9181     aTable =
9182     aSTT[RESTART_MODE_WHEN_NOT_ACTIVE][FILL_MODE_REMOVE] = {};
9183     aTable[INVALID_NODE]        = INVALID_NODE;
9184     aTable[UNRESOLVED_NODE]     = RESOLVED_NODE | ENDED_NODE;
9185     aTable[RESOLVED_NODE]       = ACTIVE_NODE | ENDED_NODE;
9186     aTable[ACTIVE_NODE]         = ENDED_NODE;
9187     aTable[FROZEN_NODE]         = INVALID_NODE;  // this state is unreachable here
9188     aTable[ENDED_NODE]          = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE;  // restart is possible
9190     // transition table for restart=WHEN_NOT_ACTIVE, fill=FREEZE
9191     aTable =
9192     aSTT[RESTART_MODE_WHEN_NOT_ACTIVE][FILL_MODE_FREEZE] =
9193     aSTT[RESTART_MODE_WHEN_NOT_ACTIVE][FILL_MODE_HOLD] =
9194     aSTT[RESTART_MODE_WHEN_NOT_ACTIVE][FILL_MODE_TRANSITION] = {};
9195     aTable[INVALID_NODE]        = INVALID_NODE;
9196     aTable[UNRESOLVED_NODE]     = RESOLVED_NODE | ENDED_NODE;
9197     aTable[RESOLVED_NODE]       = ACTIVE_NODE | ENDED_NODE;
9198     aTable[ACTIVE_NODE]         = FROZEN_NODE | ENDED_NODE;
9199     aTable[FROZEN_NODE]         = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE;  // restart is possible
9200     aTable[ENDED_NODE]          = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE;  // restart is possible
9202     // transition table for restart=ALWAYS, fill=REMOVE
9203     aTable =
9204     aSTT[RESTART_MODE_ALWAYS][FILL_MODE_REMOVE] = {};
9205     aTable[INVALID_NODE]        = INVALID_NODE;
9206     aTable[UNRESOLVED_NODE]     = RESOLVED_NODE | ENDED_NODE;
9207     aTable[RESOLVED_NODE]       = ACTIVE_NODE | ENDED_NODE;
9208     aTable[ACTIVE_NODE]         = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE;  // restart is possible
9209     aTable[FROZEN_NODE]         = INVALID_NODE;  // this state is unreachable here
9210     aTable[ENDED_NODE]          = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE;  // restart is possible
9212     // transition table for restart=ALWAYS, fill=FREEZE
9213     aTable =
9214     aSTT[RESTART_MODE_ALWAYS][FILL_MODE_FREEZE] =
9215     aSTT[RESTART_MODE_ALWAYS][FILL_MODE_HOLD] =
9216     aSTT[RESTART_MODE_ALWAYS][FILL_MODE_TRANSITION] = {};
9217     aTable[INVALID_NODE]        = INVALID_NODE;
9218     aTable[UNRESOLVED_NODE]     = RESOLVED_NODE | ENDED_NODE;
9219     aTable[RESOLVED_NODE]       = ACTIVE_NODE | ENDED_NODE;
9220     aTable[ACTIVE_NODE]         = RESOLVED_NODE | ACTIVE_NODE | FROZEN_NODE | ENDED_NODE;
9221     aTable[FROZEN_NODE]         = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE;  // restart is possible
9222     aTable[ENDED_NODE]          = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE;  // restart is possible
9224     return aSTT;
9227 var aStateTransitionTable = createStateTransitionTable();
9231 function getTransitionTable( eRestartMode, eFillMode )
9233     // If restart mode has not been resolved we use 'never'.
9234     // Note: RESTART_MODE_DEFAULT == RESTART_MODE_INHERIT.
9235     if( eRestartMode == RESTART_MODE_DEFAULT )
9236     {
9237         log( 'getTransitionTable: unexpected restart mode: ' + eRestartMode
9238                  + '. Used NEVER instead.');
9239         eRestartMode = RESTART_MODE_NEVER;
9240     }
9242     // If fill mode has not been resolved we use 'remove'.
9243     // Note: FILL_MODE_DEFAULT == FILL_MODE_INHERIT
9244     if( eFillMode == FILL_MODE_DEFAULT ||
9245         eFillMode == FILL_MODE_AUTO )
9246     {
9247         eFillMode = FILL_MODE_REMOVE;
9248     }
9250     return aStateTransitionTable[eRestartMode][eFillMode];
9257 // Event Triggers
9258 var EVENT_TRIGGER_UNKNOWN               = 0;
9259 var EVENT_TRIGGER_ON_SLIDE_BEGIN        = 1; // eslint-disable-line no-unused-vars
9260 var EVENT_TRIGGER_ON_SLIDE_END          = 2; // eslint-disable-line no-unused-vars
9261 var EVENT_TRIGGER_BEGIN_EVENT           = 3;
9262 var EVENT_TRIGGER_END_EVENT             = 4;
9263 var EVENT_TRIGGER_ON_CLICK              = 5;
9264 var EVENT_TRIGGER_ON_DBL_CLICK          = 6; // eslint-disable-line no-unused-vars
9265 var EVENT_TRIGGER_ON_MOUSE_ENTER        = 7; // eslint-disable-line no-unused-vars
9266 var EVENT_TRIGGER_ON_MOUSE_LEAVE        = 8; // eslint-disable-line no-unused-vars
9267 var EVENT_TRIGGER_ON_NEXT_EFFECT        = 9;
9268 var EVENT_TRIGGER_ON_PREV_EFFECT        = 10;
9269 var EVENT_TRIGGER_REPEAT                = 11; // eslint-disable-line no-unused-vars
9271 var aEventTriggerOutMap = [ 'unknown', 'slideBegin', 'slideEnd', 'begin', 'end', 'click',
9272                             'doubleClick', 'mouseEnter', 'mouseLeave', 'next', 'previous', 'repeat' ];
9275 function getEventTriggerType( sEventTrigger )
9277     if( sEventTrigger == 'begin' )
9278         return EVENT_TRIGGER_BEGIN_EVENT;
9279     else if( sEventTrigger == 'end' )
9280         return EVENT_TRIGGER_END_EVENT;
9281     else if( sEventTrigger == 'next' )
9282         return EVENT_TRIGGER_ON_NEXT_EFFECT;
9283     else if( sEventTrigger == 'prev' )
9284         return EVENT_TRIGGER_ON_PREV_EFFECT;
9285     else if( sEventTrigger == 'click' )
9286         return EVENT_TRIGGER_ON_CLICK;
9287     else
9288         return EVENT_TRIGGER_UNKNOWN;
9295 // Timing Types
9296 var UNKNOWN_TIMING          = 0;
9297 var OFFSET_TIMING           = 1;
9298 var WALLCLOCK_TIMING        = 2; // eslint-disable-line no-unused-vars
9299 var INDEFINITE_TIMING       = 3;
9300 var EVENT_TIMING            = 4;
9301 var SYNCBASE_TIMING         = 5;
9302 var MEDIA_TIMING            = 6; // eslint-disable-line no-unused-vars
9304 var aTimingTypeOutMap = [ 'unknown', 'offset', 'wallclock', 'indefinite', 'event', 'syncbase', 'media' ];
9307 // Char Codes
9308 var CHARCODE_PLUS       = '+'.charCodeAt(0);
9309 var CHARCODE_MINUS      = '-'.charCodeAt(0);
9310 var CHARCODE_0          = '0'.charCodeAt(0);
9311 var CHARCODE_9          = '9'.charCodeAt(0);
9315 function Timing( aAnimationNode, sTimingAttribute )
9317     this.aAnimationNode = aAnimationNode;     // the node, the timing attribute belongs to
9318     this.sTimingDescription = removeWhiteSpaces( sTimingAttribute );
9319     this.eTimingType = UNKNOWN_TIMING;
9320     this.nOffset = 0.0;                       // in seconds
9321     this.sEventBaseElementId = '';            // the element id for event based timing
9322     this.eEventType = EVENT_TRIGGER_UNKNOWN;  // the event type
9325 Timing.prototype.getAnimationNode = function()
9327     return this.aAnimationNode;
9330 Timing.prototype.getType = function()
9332     return this.eTimingType;
9335 Timing.prototype.getOffset = function()
9337     return this.nOffset;
9340 Timing.prototype.getEventBaseElementId = function()
9342     return this.sEventBaseElementId;
9345 Timing.prototype.getEventType = function()
9347     return this.eEventType;
9350 Timing.prototype.parse = function()
9352     if( !this.sTimingDescription )
9353     {
9354         this.eTimingType = OFFSET_TIMING;
9355         return;
9356     }
9358     if( this.sTimingDescription == 'indefinite' )
9359         this.eTimingType = INDEFINITE_TIMING;
9360     else
9361     {
9362         var nFirstCharCode = this.sTimingDescription.charCodeAt(0);
9363         var bPositiveOffset = !( nFirstCharCode == CHARCODE_MINUS );
9364         if ( ( nFirstCharCode == CHARCODE_PLUS ) ||
9365              ( nFirstCharCode == CHARCODE_MINUS ) ||
9366              ( ( nFirstCharCode >= CHARCODE_0 ) && ( nFirstCharCode <= CHARCODE_9 ) ) )
9367         {
9368             var sClockValue
9369                 = ( ( nFirstCharCode == CHARCODE_PLUS ) || ( nFirstCharCode == CHARCODE_MINUS ) )
9370                     ? this.sTimingDescription.substr( 1 )
9371                     : this.sTimingDescription;
9373             var TimeInSec = Timing.parseClockValue( sClockValue );
9374             if( TimeInSec != undefined )
9375             {
9376                 this.eTimingType = OFFSET_TIMING;
9377                 this.nOffset = bPositiveOffset ? TimeInSec : -TimeInSec;
9378             }
9379         }
9380         else
9381         {
9382             var aTimingSplit = [];
9383             bPositiveOffset = true;
9384             if( this.sTimingDescription.indexOf( '+' ) != -1 )
9385             {
9386                 aTimingSplit = this.sTimingDescription.split( '+' );
9387             }
9388             else if( this.sTimingDescription.indexOf( '-' ) != -1 )
9389             {
9390                 aTimingSplit = this.sTimingDescription.split( '-' );
9391                 bPositiveOffset = false;
9392             }
9393             else
9394             {
9395                 aTimingSplit[0] = this.sTimingDescription;
9396                 aTimingSplit[1] = '';
9397             }
9399             if( aTimingSplit[0].indexOf( '.' ) != -1 )
9400             {
9401                 var aEventSplit = aTimingSplit[0].split( '.' );
9402                 this.sEventBaseElementId = aEventSplit[0];
9403                 this.eEventType = getEventTriggerType( aEventSplit[1] );
9404             }
9405             else
9406             {
9407                 this.eEventType = getEventTriggerType( aTimingSplit[0] );
9408             }
9410             if( this.eEventType == EVENT_TRIGGER_UNKNOWN )
9411                 return;
9413             if( ( this.eEventType == EVENT_TRIGGER_BEGIN_EVENT ) ||
9414                 ( this.eEventType == EVENT_TRIGGER_END_EVENT ) )
9415             {
9416                 this.eTimingType = SYNCBASE_TIMING;
9417             }
9418             else
9419             {
9420                 this.eTimingType = EVENT_TIMING;
9421             }
9423             if( aTimingSplit[1] )
9424             {
9425                 sClockValue = aTimingSplit[1];
9426                 TimeInSec = Timing.parseClockValue( sClockValue );
9427                 if( TimeInSec != undefined )
9428                 {
9429                     this.nOffset = ( bPositiveOffset ) ? TimeInSec : -TimeInSec;
9430                 }
9431                 else
9432                 {
9433                     this.eTimingType = UNKNOWN_TIMING;
9434                 }
9436             }
9437         }
9438     }
9442 Timing.parseClockValue = function( sClockValue )
9444     if( !sClockValue )
9445         return 0.0;
9447     var nTimeInSec = undefined;
9449     var reFullClockValue = /^([0-9]+):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?$/;
9450     var rePartialClockValue = /^([0-5][0-9]):([0-5][0-9])(.[0-9]+)?$/;
9451     var reTimeCountValue = /^([0-9]+)(.[0-9]+)?(h|min|s|ms)?$/;
9453     if( reFullClockValue.test( sClockValue ) )
9454     {
9455         var aClockTimeParts = reFullClockValue.exec( sClockValue );
9457         var nHours = parseInt( aClockTimeParts[1] );
9458         var nMinutes = parseInt( aClockTimeParts[2] );
9459         var nSeconds = parseInt( aClockTimeParts[3] );
9460         if( aClockTimeParts[4] )
9461             nSeconds += parseFloat( aClockTimeParts[4] );
9463         nTimeInSec = ( ( nHours * 60 ) +  nMinutes ) * 60 + nSeconds;
9465     }
9466     else if( rePartialClockValue.test( sClockValue ) )
9467     {
9468         aClockTimeParts = rePartialClockValue.exec( sClockValue );
9470         nMinutes = parseInt( aClockTimeParts[1] );
9471         nSeconds = parseInt( aClockTimeParts[2] );
9472         if( aClockTimeParts[3] )
9473             nSeconds += parseFloat( aClockTimeParts[3] );
9475         nTimeInSec = nMinutes * 60 + nSeconds;
9476     }
9477     else if( reTimeCountValue.test( sClockValue ) )
9478     {
9479         aClockTimeParts = reTimeCountValue.exec( sClockValue );
9481         var nTimeCount = parseInt( aClockTimeParts[1] );
9482         if( aClockTimeParts[2] )
9483             nTimeCount += parseFloat( aClockTimeParts[2] );
9485         if( aClockTimeParts[3] )
9486         {
9487             if( aClockTimeParts[3] == 'h' )
9488             {
9489                 nTimeInSec = nTimeCount * 3600;
9490             }
9491             else if( aClockTimeParts[3] == 'min' )
9492             {
9493                 nTimeInSec = nTimeCount * 60;
9494             }
9495             else if( aClockTimeParts[3] == 's' )
9496             {
9497                 nTimeInSec = nTimeCount;
9498             }
9499             else if( aClockTimeParts[3] == 'ms' )
9500             {
9501                 nTimeInSec = nTimeCount / 1000;
9502             }
9503         }
9504         else
9505         {
9506             nTimeInSec = nTimeCount;
9507         }
9509     }
9511     if( nTimeInSec )
9512         nTimeInSec = parseFloat( nTimeInSec.toFixed( 3 ) );
9513     return nTimeInSec;
9516 Timing.prototype.info = function( bVerbose )
9519     var sInfo = '';
9521     if( bVerbose )
9522     {
9523         sInfo = 'description: ' + this.sTimingDescription + ', ';
9525         sInfo += ', type: ' +  aTimingTypeOutMap[ this.getType() ];
9526         sInfo += ', offset: ' + this.getOffset();
9527         sInfo += ', event base element id: ' + this.getEventBaseElementId();
9528         sInfo += ', timing event type: ' + aEventTriggerOutMap[ this.getEventType() ];
9529     }
9530     else
9531     {
9532         switch( this.getType() )
9533         {
9534             case INDEFINITE_TIMING:
9535                 sInfo += 'indefinite';
9536                 break;
9537             case OFFSET_TIMING:
9538                 sInfo += this.getOffset();
9539                 break;
9540             case EVENT_TIMING:
9541             case SYNCBASE_TIMING:
9542                 if( this.getEventBaseElementId() )
9543                     sInfo += this.getEventBaseElementId() + '.';
9544                 sInfo += aEventTriggerOutMap[ this.getEventType() ];
9545                 if( this.getOffset() )
9546                 {
9547                     if( this.getOffset() > 0 )
9548                         sInfo += '+';
9549                     sInfo += this.getOffset();
9550                 }
9551         }
9552     }
9554     return sInfo;
9560 function Duration( sDurationAttribute )
9562     this.bIndefinite = false;
9563     this.bMedia = false;
9564     this.nValue = undefined;
9565     this.bDefined = false;
9567     if( !sDurationAttribute )
9568         return;
9570     if( sDurationAttribute == 'indefinite' )
9571         this.bIndefinite = true;
9572     else if( sDurationAttribute == 'media' )
9573         this.bMedia = true;
9574     else
9575     {
9576         this.nValue = Timing.parseClockValue( sDurationAttribute );
9577         if( this.nValue <= 0.0 )
9578             this.nValue = 0.001;  // duration must be always greater than 0
9579     }
9580     this.bDefined = true;
9584 Duration.prototype.isSet = function()
9586     return this.bDefined;
9589 Duration.prototype.isIndefinite = function()
9591     return this.bIndefinite;
9594 Duration.prototype.isMedia = function()
9596     return this.bMedia;
9599 Duration.prototype.isValue = function()
9601     return this.nValue != undefined;
9604 Duration.prototype.getValue= function()
9606     return this.nValue;
9609 Duration.prototype.info= function()
9611     var sInfo;
9613     if( this.isIndefinite() )
9614         sInfo = 'indefinite';
9615     else if( this.isMedia() )
9616         sInfo = 'media';
9617     else if( this.getValue() )
9618         sInfo = this.getValue();
9620     return sInfo;
9626 function AnimationNode()
9630 AnimationNode.prototype.init = function() {};
9631 AnimationNode.prototype.resolve = function() {};
9632 AnimationNode.prototype.activate = function() {};
9633 AnimationNode.prototype.deactivate = function() {};
9634 AnimationNode.prototype.end = function() {};
9635 AnimationNode.prototype.getState = function() {};
9636 AnimationNode.prototype.registerDeactivatingListener = function() {};
9637 AnimationNode.prototype.notifyDeactivating = function() {};
9642 function NodeContext( aSlideShowContext )
9644     this.aContext = aSlideShowContext;
9645     this.aAnimationNodeMap = null;
9646     this.aAnimatedElementMap = null;
9647     this.aSourceEventElementMap = null;
9648     this.nStartDelay = 0.0;
9649     this.bFirstRun = undefined;
9650     this.aSlideHeight = HEIGHT;
9651     this.aSlideWidth = WIDTH;
9655 NodeContext.prototype.makeSourceEventElement = function( sId, aEventBaseElem )
9657     if( !aEventBaseElem )
9658     {
9659         log( 'NodeContext.makeSourceEventElement: event base element is not valid' );
9660         return null;
9661     }
9663     if( !this.aContext.aEventMultiplexer )
9664     {
9665         log( 'NodeContext.makeSourceEventElement: event multiplexer not initialized' );
9666         return null;
9667     }
9669     if( !this.aSourceEventElementMap[ sId ] )
9670     {
9671         this.aSourceEventElementMap[ sId ] = new SourceEventElement( sId, aEventBaseElem, this.aContext.aEventMultiplexer );
9672     }
9673     return this.aSourceEventElementMap[ sId ];
9679 function StateTransition( aBaseNode )
9681     this.aNode = aBaseNode;
9682     this.eToState = INVALID_NODE;
9685 StateTransition.prototype.enter = function( eNodeState, bForce )
9687     if( !bForce ) bForce = false;
9689     if( this.eToState != INVALID_NODE )
9690     {
9691         log( 'StateTransition.enter: commit() before enter()ing again!' );
9692         return false;
9693     }
9694     if( !bForce && !this.aNode.isTransition( this.aNode.getState(), eNodeState  ) )
9695         return false;
9697     // recursion detection:
9698     if( ( this.aNode.nCurrentStateTransition & eNodeState ) != 0 )
9699         return false; // already in wanted transition
9701     // mark transition:
9702     this.aNode.nCurrentStateTransition |= eNodeState;
9703     this.eToState = eNodeState;
9704     return true;
9707 StateTransition.prototype.commit = function()
9709     if( this.eToState != INVALID_NODE )
9710     {
9711         this.aNode.eCurrentState = this.eToState;
9712         this.clear();
9713     }
9716 StateTransition.prototype.clear = function()
9718     if( this.eToState != INVALID_NODE )
9719     {
9720         this.aNode.nCurrentStateTransition &= ~this.eToState;
9721         this.eToState = INVALID_NODE;
9722     }
9728 function BaseNode( aAnimElem, aParentNode, aNodeContext )
9730     this.nId = getUniqueId();
9731     this.sClassName = 'BaseNode';
9733     if( !aAnimElem )
9734         log( 'BaseNode(id:' + this.nId + ') constructor: aAnimElem is not valid' );
9736     if( !aNodeContext )
9737         log( 'BaseNode(id:' + this.nId + ') constructor: aNodeContext is not valid' );
9739     if( !aNodeContext.aContext )
9740         log( 'BaseNode(id:' + this.nId + ') constructor: aNodeContext.aContext is not valid' );
9743     this.bIsContainer = false;
9744     this.aElement = aAnimElem;
9745     this.aParentNode = aParentNode;
9746     this.aNodeContext = aNodeContext;
9747     this.aContext = aNodeContext.aContext;
9748     this.nStartDelay = aNodeContext.nStartDelay;
9749     this.eCurrentState = UNRESOLVED_NODE;
9750     this.nCurrentStateTransition = 0;
9751     this.aDeactivatingListenerArray = [];
9752     this.aActivationEvent = null;
9753     this.aDeactivationEvent = null;
9755     this.aBegin = null;
9756     this.aDuration = null;
9757     this.aEnd = null;
9758     this.bMainSequenceRootNode = false;
9759     this.bInteractiveSequenceRootNode = false;
9760     this.eFillMode = FILL_MODE_FREEZE;
9761     this.eRestartMode = RESTART_MODE_NEVER;
9762     this.nReapeatCount = undefined;
9763     this.nAccelerate = 0.0;
9764     this.nDecelerate = 0.0;
9765     this.bAutoReverse = false;
9768 extend( BaseNode, AnimationNode );
9771 BaseNode.prototype.getId = function()
9773     return this.nId;
9776 BaseNode.prototype.parseElement = function()
9778     var aAnimElem = this.aElement;
9780     // id attribute
9781     var sIdAttr = aAnimElem.getAttributeNS( NSS['xml'], 'id' );
9782     // we append the animation node to the Animation Node Map so that it can be retrieved
9783     // by the registerEvent routine for resolving begin values of type 'id.begin', 'id.end'
9784     if( sIdAttr )
9785         this.aNodeContext.aAnimationNodeMap[ sIdAttr ] = this;
9787     // begin attribute
9788     this.aBegin = null;
9789     var sBeginAttr = aAnimElem.getAttributeNS( NSS['smil'], 'begin' );
9790     this.aBegin = new Timing( this, sBeginAttr );
9791     this.aBegin.parse();
9793     // end attribute
9794     this.aEnd = null;
9795     var sEndAttr = aAnimElem.getAttributeNS( NSS['smil'], 'end' );
9796     if( sEndAttr )
9797     {
9798         this.aEnd = new Timing( this, sEndAttr );
9799         this.aEnd.parse();
9800     }
9802     // dur attribute
9803     this.aDuration = null;
9804     var sDurAttr = aAnimElem.getAttributeNS( NSS['smil'], 'dur' );
9805     this.aDuration = new Duration( sDurAttr );
9806     if( !this.aDuration.isSet() )
9807     {
9808         if( this.isContainer() )
9809             this.aDuration = null;
9810         else
9811             this.aDuration = new Duration( 'indefinite' );
9812     }
9814     // fill attribute
9815     var sFillAttr = aAnimElem.getAttributeNS( NSS['smil'], 'fill' );
9816     if( sFillAttr && aFillModeInMap[ sFillAttr ])
9817         this.eFillMode = aFillModeInMap[ sFillAttr ];
9818     else
9819         this.eFillMode = FILL_MODE_DEFAULT;
9821     // restart attribute
9822     var sRestartAttr = aAnimElem.getAttributeNS( NSS['smil'], 'restart' );
9823     if( sRestartAttr && aRestartModeInMap[ sRestartAttr ] )
9824         this.eRestartMode = aRestartModeInMap[ sRestartAttr ];
9825     else
9826         this.eRestartMode = RESTART_MODE_DEFAULT;
9828     // repeatCount attribute
9829     var sRepeatCount = aAnimElem.getAttributeNS( NSS['smil'], 'repeatCount' );
9830     if( !sRepeatCount )
9831         this.nReapeatCount = 1;
9832     else
9833         this.nReapeatCount = parseFloat( sRepeatCount );
9834     if( ( isNaN(this.nReapeatCount) ) && ( sRepeatCount != 'indefinite' ) )
9835         this.nReapeatCount = 1;
9837     // accelerate attribute
9838     this.nAccelerate = 0.0;
9839     var sAccelerateAttr = aAnimElem.getAttributeNS( NSS['smil'], 'accelerate' );
9840     if( sAccelerateAttr )
9841         this.nAccelerate = parseFloat( sAccelerateAttr );
9842     if( isNaN(this.nAccelerate) )
9843         this.nAccelerate = 0.0;
9845     // decelerate attribute
9846     this.nDecelerate = 0.0;
9847     var sDecelerateAttr = aAnimElem.getAttributeNS( NSS['smil'], 'decelerate' );
9848     if( sDecelerateAttr )
9849         this.nDecelerate = parseFloat( sDecelerateAttr );
9850     if( isNaN(this.nDecelerate) )
9851         this.nDecelerate = 0.0;
9853     // autoReverse attribute
9854     this.bAutoreverse = false;
9855     var sAutoReverseAttr = aAnimElem.getAttributeNS( NSS['smil'], 'autoReverse' );
9856     if( sAutoReverseAttr == 'true' )
9857         this.bAutoreverse = true;
9860     // resolve fill value
9861     if( this.eFillMode == FILL_MODE_DEFAULT )
9862         if( this.getParentNode() )
9863             this.eFillMode = this.getParentNode().getFillMode();
9864         else
9865             this.eFillMode = FILL_MODE_AUTO;
9867     if( this.eFillMode ==  FILL_MODE_AUTO ) // see SMIL recommendation document
9868     {
9869         this.eFillMode = ( this.aEnd ||
9870                            ( this.nReapeatCount != 1) ||
9871                            ( this.aDuration && !this.aDuration.isIndefinite() ) )
9872                               ? FILL_MODE_REMOVE
9873                               : FILL_MODE_FREEZE;
9874     }
9876     // resolve restart value
9877     if( this.eRestartMode == RESTART_MODE_DEFAULT )
9878         if( this.getParentNode() )
9879             this.eRestartMode = this.getParentNode().getRestartMode();
9880         else
9881             // SMIL recommendation document says to set it to 'always'
9882             this.eRestartMode = RESTART_MODE_ALWAYS;
9884     // resolve accelerate and decelerate attributes
9885     // from the SMIL recommendation document: if the individual values of the accelerate
9886     // and decelerate attributes are between 0 and 1 and the sum is greater than 1,
9887     // then both the accelerate and decelerate attributes will be ignored and the timed
9888     // element will behave as if neither attribute was specified.
9889     if( ( this.nAccelerate + this.nDecelerate ) > 1.0 )
9890     {
9891         this.nAccelerate = 0.0;
9892         this.nDecelerate = 0.0;
9893     }
9895     this.aStateTransTable = getTransitionTable( this.getRestartMode(), this.getFillMode() );
9897     return true;
9900 BaseNode.prototype.getParentNode = function()
9902     return this.aParentNode;
9905 BaseNode.prototype.init = function()
9907     this.DBG( this.callInfo( 'init' ) );
9908     if( ! this.checkValidNode() )
9909         return false;
9910     if( this.aActivationEvent )
9911         this.aActivationEvent.dispose();
9912     if( this.aDeactivationEvent )
9913         this.aDeactivationEvent.dispose();
9915     this.eCurrentState = UNRESOLVED_NODE;
9917     return this.init_st();
9920 BaseNode.prototype.resolve = function()
9922     if( ! this.checkValidNode() )
9923         return false;
9925     this.DBG( this.callInfo( 'resolve' ) );
9927     if( this.eCurrentState == RESOLVED_NODE )
9928         log( 'BaseNode.resolve: already in RESOLVED state' );
9930     var aStateTrans = new StateTransition( this );
9932     if( aStateTrans.enter( RESOLVED_NODE ) &&
9933         this.isTransition( RESOLVED_NODE, ACTIVE_NODE ) &&
9934         this.resolve_st() )
9935     {
9936         aStateTrans.commit();
9938         if( this.aActivationEvent )
9939         {
9940             this.aActivationEvent.charge();
9941         }
9942         else
9943         {
9944             this.aActivationEvent = makeDelay( bind( this, this.activate ), this.getBegin().getOffset() + this.nStartDelay );
9945         }
9946         registerEvent( this.getId(), this.getBegin(), this.aActivationEvent, this.aNodeContext );
9948         return true;
9949     }
9951     return false;
9954 BaseNode.prototype.activate = function()
9956     if( ! this.checkValidNode() )
9957         return false;
9959     if( this.eCurrentState == ACTIVE_NODE )
9960         log( 'BaseNode.activate: already in ACTIVE state' );
9962     this.DBG( this.callInfo( 'activate' ), getCurrentSystemTime() );
9964     var aStateTrans = new StateTransition( this );
9966     if( aStateTrans.enter( ACTIVE_NODE ) )
9967     {
9968         this.activate_st();
9969         aStateTrans.commit();
9970         if( !this.aContext.aEventMultiplexer )
9971             log( 'BaseNode.activate: this.aContext.aEventMultiplexer is not valid' );
9972         this.aContext.aEventMultiplexer.notifyEvent( EVENT_TRIGGER_BEGIN_EVENT, this.getId() );
9973         return true;
9974     }
9975     return false;
9978 BaseNode.prototype.deactivate = function()
9980     if( this.inStateOrTransition( ENDED_NODE | FROZEN_NODE ) || !this.checkValidNode() )
9981         return;
9983     if( this.isTransition( this.eCurrentState, FROZEN_NODE ) )
9984     {
9985         this.DBG( this.callInfo( 'deactivate' ), getCurrentSystemTime() );
9987         var aStateTrans = new StateTransition( this );
9988         if( aStateTrans.enter( FROZEN_NODE, true /* FORCE */ ) )
9989         {
9990             this.deactivate_st( FROZEN_NODE );
9991             aStateTrans.commit();
9993             this.notifyEndListeners();
9995             if( this.aActivationEvent )
9996                 this.aActivationEvent.dispose();
9997             if( this.aDeactivationEvent )
9998                 this.aDeactivationEvent.dispose();
9999         }
10000     }
10001     else
10002     {
10003         this.end();
10004     }
10005     // state has changed either to FROZEN or ENDED
10008 BaseNode.prototype.end = function()
10010     var bIsFrozenOrInTransitionToFrozen = this.inStateOrTransition( FROZEN_NODE );
10011     if( this.inStateOrTransition( ENDED_NODE ) || !this.checkValidNode() )
10012         return;
10014     if( !(this.isTransition( this.eCurrentState, ENDED_NODE ) ) )
10015         log( 'BaseNode.end: end state not reachable in transition table' );
10017     this.DBG( this.callInfo( 'end' ), getCurrentSystemTime() );
10019     var aStateTrans = new StateTransition( this );
10020     if( aStateTrans.enter( ENDED_NODE, true /* FORCE */ ) )
10021     {
10022         this.deactivate_st( ENDED_NODE );
10023         aStateTrans.commit();
10025         // if is FROZEN or is to be FROZEN, then
10026         // will/already notified deactivating listeners
10027         if( !bIsFrozenOrInTransitionToFrozen )
10028             this.notifyEndListeners();
10030         if( this.aActivationEvent )
10031             this.aActivationEvent.dispose();
10032         if( this.aDeactivationEvent )
10033             this.aDeactivationEvent.dispose();
10034     }
10037 BaseNode.prototype.dispose = function()
10039     if( this.aActivationEvent )
10040         this.aActivationEvent.dispose();
10041     if( this.aDeactivationEvent )
10042         this.aDeactivationEvent.dispose();
10043     this.aDeactivatingListenerArray = [];
10046 BaseNode.prototype.getState = function()
10048     return this.eCurrentState;
10051 BaseNode.prototype.registerDeactivatingListener = function( aNotifiee )
10053     if (! this.checkValidNode())
10054         return false;
10056     if( !aNotifiee )
10057     {
10058         log( 'BaseNode.registerDeactivatingListener(): invalid notifiee' );
10059         return false;
10060     }
10061     this.aDeactivatingListenerArray.push( aNotifiee );
10063     return true;
10066 BaseNode.prototype.notifyDeactivating = function( aNotifier )
10068     assert( ( aNotifier.getState() == FROZEN_NODE ) || ( aNotifier.getState() == ENDED_NODE ),
10069             'BaseNode.notifyDeactivating: Notifier node is neither in FROZEN nor in ENDED state' );
10072 BaseNode.prototype.isMainSequenceRootNode = function()
10074     return this.bMainSequenceRootNode;
10077 BaseNode.prototype.isInteractiveSequenceRootNode = function()
10079     return this.bInteractiveSequenceRootNode;
10082 BaseNode.prototype.makeDeactivationEvent = function( nDelay )
10084     if( this.aDeactivationEvent )
10085     {
10086         this.aDeactivationEvent.charge();
10087     }
10088     else
10089     {
10090         if( typeof( nDelay ) == typeof(0) )
10091             this.aDeactivationEvent = makeDelay( bind( this, this.deactivate ), nDelay );
10092         else
10093             this.aDeactivationEvent = null;
10094     }
10095     return this.aDeactivationEvent;
10098 BaseNode.prototype.scheduleDeactivationEvent = function( aEvent )
10100     this.DBG( this.callInfo( 'scheduleDeactivationEvent' ) );
10102     if( !aEvent )
10103     {
10104         if( this.getDuration() && this.getDuration().isValue() )
10105             aEvent = this.makeDeactivationEvent( this.getDuration().getValue() );
10106     }
10107     if( aEvent )
10108     {
10109         this.aContext.aTimerEventQueue.addEvent( aEvent );
10110     }
10113 BaseNode.prototype.checkValidNode = function()
10115     return ( this.eCurrentState != INVALID_NODE );
10118 BaseNode.prototype.init_st = function()
10120     return true;
10123 BaseNode.prototype.resolve_st = function()
10125     return true;
10128 BaseNode.prototype.activate_st = function()
10130     this.scheduleDeactivationEvent();
10133 BaseNode.prototype.deactivate_st = function( /*aNodeState*/ )
10135     // empty body
10138 BaseNode.prototype.notifyEndListeners = function()
10140     var nDeactivatingListenerCount = this.aDeactivatingListenerArray.length;
10142     for( var i = 0; i < nDeactivatingListenerCount; ++i )
10143     {
10144         this.aDeactivatingListenerArray[i].notifyDeactivating( this );
10145     }
10147     this.aContext.aEventMultiplexer.notifyEvent( EVENT_TRIGGER_END_EVENT, this.getId() );
10148     if( this.getParentNode() && this.getParentNode().isMainSequenceRootNode() )
10149         this.aContext.aEventMultiplexer.notifyNextEffectEndEvent();
10151     if( this.isMainSequenceRootNode() )
10152         this.aContext.aEventMultiplexer.notifyAnimationsEndEvent();
10155 BaseNode.prototype.getContext = function()
10157     return this.aContext;
10160 BaseNode.prototype.isTransition = function( eFromState, eToState )
10162     return ( ( this.aStateTransTable[ eFromState ] & eToState ) != 0 );
10165 BaseNode.prototype.inStateOrTransition = function( nMask )
10167     return ( ( ( this.eCurrentState & nMask ) != 0 ) || ( ( this.nCurrentStateTransition & nMask ) != 0 ) );
10170 BaseNode.prototype.isContainer = function()
10172     return this.bIsContainer;
10175 BaseNode.prototype.getBegin = function()
10177     return this.aBegin;
10180 BaseNode.prototype.getDuration = function()
10182     return this.aDuration;
10185 BaseNode.prototype.getEnd = function()
10187     return this.aEnd;
10190 BaseNode.prototype.getFillMode = function()
10192     return this.eFillMode;
10195 BaseNode.prototype.getRestartMode = function()
10197     return this.eRestartMode;
10200 BaseNode.prototype.getRepeatCount = function()
10202     return this.nReapeatCount;
10205 BaseNode.prototype.getAccelerateValue = function()
10207     return this.nAccelerate;
10210 BaseNode.prototype.getDecelerateValue = function()
10212     return this.nDecelerate;
10215 BaseNode.prototype.isAutoReverseEnabled = function()
10217     return this.bAutoreverse;
10220 BaseNode.prototype.info = function( bVerbose )
10222     var sInfo = 'class name: ' + this.sClassName;
10223     sInfo += ';  element name: ' + this.aElement.localName;
10224     sInfo += ';  id: ' + this.getId();
10225     sInfo += ';  state: ' + getNodeStateName( this.getState() );
10227     if( bVerbose )
10228     {
10229         // is container
10230         sInfo += ';  is container: ' + this.isContainer();
10232         // begin
10233         if( this.getBegin() )
10234             sInfo += ';  begin: ' + this.getBegin().info();
10236         // duration
10237         if( this.getDuration() )
10238             sInfo += ';  dur: ' + this.getDuration().info();
10240         // end
10241         if( this.getEnd() )
10242             sInfo += ';  end: ' + this.getEnd().info();
10244         // fill mode
10245         if( this.getFillMode() )
10246             sInfo += ';  fill: ' + aFillModeOutMap[ this.getFillMode() ];
10248         // restart mode
10249         if( this.getRestartMode() )
10250             sInfo += ';  restart: ' + aRestartModeOutMap[ this.getRestartMode() ];
10252         // repeatCount
10253         if( this.getRepeatCount() && ( this.getRepeatCount() != 1.0 ) )
10254             sInfo += ';  repeatCount: ' + this.getRepeatCount();
10256         // accelerate
10257         if( this.getAccelerateValue() )
10258             sInfo += ';  accelerate: ' + this.getAccelerateValue();
10260         // decelerate
10261         if( this.getDecelerateValue() )
10262             sInfo += ';  decelerate: ' + this.getDecelerateValue();
10264         // auto reverse
10265         if( this.isAutoReverseEnabled() )
10266             sInfo += ';  autoReverse: true';
10268     }
10270     return sInfo;
10273 BaseNode.prototype.callInfo = function( sMethodName )
10275     var sInfo = this.sClassName +
10276                 '( ' + this.getId() +
10277                 ', ' + getNodeStateName( this.getState() ) +
10278                 ' ).' + sMethodName;
10279     return sInfo;
10282 BaseNode.prototype.DBG = function( sMessage, nTime )
10284     ANIMDBG.print( sMessage, nTime );
10290 function AnimationBaseNode( aAnimElem, aParentNode, aNodeContext )
10292     AnimationBaseNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
10294     this.sClassName = 'AnimationBaseNode';
10295     this.bIsContainer = false;
10296     this.aTargetElement = null;
10297     this.bIsTargetTextElement = false;
10298     this.aAnimatedElement = null;
10299     this.aActivity = null;
10301     this.nMinFrameCount = undefined;
10302     this.eAdditiveMode = undefined;
10305 extend( AnimationBaseNode, BaseNode );
10308 AnimationBaseNode.prototype.parseElement = function()
10310     var bRet = AnimationBaseNode.superclass.parseElement.call( this );
10312     var aAnimElem = this.aElement;
10314     // targetElement attribute
10315     this.aTargetElement = null;
10316     var sTargetElementAttr = aAnimElem.getAttributeNS( NSS['smil'], 'targetElement' );
10317     if( sTargetElementAttr )
10318         this.aTargetElement = document.getElementById( sTargetElementAttr );
10320     if( !this.aTargetElement )
10321     {
10322         this.eCurrentState = INVALID_NODE;
10323         log( 'AnimationBaseNode.parseElement: target element not found: ' + sTargetElementAttr );
10324     }
10326     // sub-item attribute for text animated element
10327     var sSubItemAttr = aAnimElem.getAttributeNS( NSS['anim'], 'sub-item' );
10328     this.bIsTargetTextElement = ( sSubItemAttr && ( sSubItemAttr === 'text' ) );
10330     // additive attribute
10331     var sAdditiveAttr = aAnimElem.getAttributeNS( NSS['smil'], 'additive' );
10332     if( sAdditiveAttr && aAddittiveModeInMap[sAdditiveAttr] )
10333         this.eAdditiveMode = aAddittiveModeInMap[sAdditiveAttr];
10334     else
10335         this.eAdditiveMode = ADDITIVE_MODE_REPLACE;
10337     // set up min frame count value;
10338     this.nMinFrameCount = ( this.getDuration().isValue() )
10339             ? ( this.getDuration().getValue() * MINIMUM_FRAMES_PER_SECONDS )
10340             : MINIMUM_FRAMES_PER_SECONDS;
10341     if( this.nMinFrameCount < 1.0 )
10342         this.nMinFrameCount = 1;
10343     else if( this.nMinFrameCount > MINIMUM_FRAMES_PER_SECONDS )
10344         this.nMinFrameCount = MINIMUM_FRAMES_PER_SECONDS;
10347     if( this.aTargetElement )
10348     {
10349         // set up target element initial visibility
10350         if( aAnimElem.getAttributeNS( NSS['smil'], 'attributeName' ) === 'visibility' )
10351         {
10352             if( aAnimElem.getAttributeNS( NSS['smil'], 'to' ) === 'visible' )
10353                 this.aTargetElement.setAttribute( 'visibility', 'hidden' );
10354         }
10356         // create animated element
10357         if( !this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ] )
10358         {
10359             if( this.bIsTargetTextElement )
10360             {
10361                 this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ]
10362                     = new AnimatedTextElement( this.aTargetElement );
10363             }
10364             else
10365             {
10366                 this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ]
10367                     = new AnimatedElement( this.aTargetElement );
10368             }
10369         }
10370         this.aAnimatedElement = this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ];
10372         // set additive mode
10373         this.aAnimatedElement.setAdditiveMode( this.eAdditiveMode );
10374     }
10377     return bRet;
10380 AnimationBaseNode.prototype.init_st = function()
10382     if( this.aActivity )
10383         this.aActivity.activate( makeEvent( bind( this, this.deactivate ) ) );
10384     else
10385         this.aActivity = this.createActivity();
10386     return true;
10389 AnimationBaseNode.prototype.resolve_st = function()
10391     return true;
10394 AnimationBaseNode.prototype.activate_st = function()
10396     if( this.aActivity )
10397     {
10398         this.saveStateOfAnimatedElement();
10399         this.aActivity.setTargets( this.getAnimatedElement() );
10400         if( this.getContext().bIsSkipping  )
10401         {
10402             this.aActivity.end();
10403         }
10404         else
10405         {
10406             this.getContext().aActivityQueue.addActivity( this.aActivity );
10407         }
10408     }
10409     else
10410     {
10411         AnimationBaseNode.superclass.scheduleDeactivationEvent.call( this );
10412     }
10415 AnimationBaseNode.prototype.deactivate_st = function( eDestState )
10417     if( eDestState == FROZEN_NODE )
10418     {
10419         if( this.aActivity )
10420             this.aActivity.end();
10421     }
10422     if( eDestState == ENDED_NODE )
10423     {
10424         if( this.aActivity )
10425             this.aActivity.dispose();
10426         if( ( this.getFillMode() == FILL_MODE_REMOVE ) && this.getAnimatedElement()  )
10427             this.removeEffect();
10428     }
10431 AnimationBaseNode.prototype.createActivity = function()
10433     log( 'AnimationBaseNode.createActivity: abstract method called' );
10436 AnimationBaseNode.prototype.fillActivityParams = function()
10439     // compute duration
10440     var nDuration = 0.001;
10441     if( this.getDuration().isValue() )
10442     {
10443         nDuration = this.getDuration().getValue();
10444     }
10445     else
10446     {
10447         log( 'AnimationBaseNode.fillActivityParams: duration is not a number' );
10448     }
10450     // create and set up activity params
10451     var aActivityParamSet = new ActivityParamSet();
10453     aActivityParamSet.aEndEvent             = makeEvent( bind( this, this.deactivate ) );
10454     aActivityParamSet.aTimerEventQueue      = this.aContext.aTimerEventQueue;
10455     aActivityParamSet.aActivityQueue        = this.aContext.aActivityQueue;
10456     aActivityParamSet.nMinDuration          = nDuration;
10457     aActivityParamSet.nMinNumberOfFrames    = this.getMinFrameCount();
10458     aActivityParamSet.bAutoReverse          = this.isAutoReverseEnabled();
10459     aActivityParamSet.nRepeatCount          = this.getRepeatCount();
10460     aActivityParamSet.nAccelerationFraction = this.getAccelerateValue();
10461     aActivityParamSet.nDecelerationFraction = this.getDecelerateValue();
10462     aActivityParamSet.nSlideWidth           = this.aNodeContext.aSlideWidth;
10463     aActivityParamSet.nSlideHeight          = this.aNodeContext.aSlideHeight;
10465     return aActivityParamSet;
10468 AnimationBaseNode.prototype.hasPendingAnimation = function()
10470     return true;
10473 AnimationBaseNode.prototype.saveStateOfAnimatedElement = function()
10475     this.getAnimatedElement().saveState( this.getId() );
10478 AnimationBaseNode.prototype.removeEffect = function()
10480     this.getAnimatedElement().restoreState( this.getId() );
10483 AnimationBaseNode.prototype.getTargetElement = function()
10485     return this.aTargetElement;
10488 AnimationBaseNode.prototype.getAnimatedElement = function()
10490     return this.aAnimatedElement;
10493 AnimationBaseNode.prototype.dispose= function()
10495     if( this.aActivity )
10496         this.aActivity.dispose();
10498     AnimationBaseNode.superclass.dispose.call( this );
10501 AnimationBaseNode.prototype.getMinFrameCount = function()
10503     return this.nMinFrameCount;
10506 AnimationBaseNode.prototype.getAdditiveMode = function()
10508     return this.eAdditiveMode;
10511 AnimationBaseNode.prototype.info = function( bVerbose )
10513     var sInfo = AnimationBaseNode.superclass.info.call( this, bVerbose );
10515     if( bVerbose )
10516     {
10517         // min frame count
10518         if( this.getMinFrameCount() )
10519             sInfo += ';  min frame count: ' + this.getMinFrameCount();
10521         // additive mode
10522         sInfo += ';  additive: ' + aAddittiveModeOutMap[ this.getAdditiveMode() ];
10524         // target element
10525         if( this.getTargetElement() )
10526         {
10527             var sElemId = this.getTargetElement().getAttribute( 'id' );
10528             sInfo += ';  targetElement: ' +  sElemId;
10529         }
10530     }
10532     return sInfo;
10537 function AnimationBaseNode2( aAnimElem, aParentNode, aNodeContext )
10539     AnimationBaseNode2.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
10541     this.sAttributeName = '';
10542     this.aToValue = null;
10545 extend( AnimationBaseNode2, AnimationBaseNode );
10548 AnimationBaseNode2.prototype.parseElement = function()
10550     var bRet = AnimationBaseNode2.superclass.parseElement.call( this );
10552     var aAnimElem = this.aElement;
10554     // attributeName attribute
10555     this.sAttributeName = aAnimElem.getAttributeNS( NSS['smil'], 'attributeName' );
10556     if( !this.sAttributeName )
10557     {
10558         this.eCurrentState = INVALID_NODE;
10559         log( 'AnimationBaseNode2.parseElement: target attribute name not found: ' + this.sAttributeName );
10560     }
10562     // to attribute
10563     this.aToValue = aAnimElem.getAttributeNS( NSS['smil'], 'to' );
10565     return bRet;
10568 AnimationBaseNode2.prototype.getAttributeName = function()
10570     return this.sAttributeName;
10573 AnimationBaseNode2.prototype.getToValue = function()
10575     return this.aToValue;
10578 AnimationBaseNode2.prototype.info = function( bVerbose )
10580     var sInfo = AnimationBaseNode2.superclass.info.call( this, bVerbose );
10582     if( bVerbose )
10583     {
10584         // attribute name
10585         if( this.getAttributeName() )
10586             sInfo += ';  attributeName: ' + this.getAttributeName();
10588         // To
10589         if( this.getToValue() )
10590             sInfo += ';  to: ' + this.getToValue();
10591     }
10593     return sInfo;
10599 function AnimationBaseNode3( aAnimElem, aParentNode, aNodeContext )
10601     AnimationBaseNode3.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
10603     this.eAccumulate = undefined;
10604     this.eCalcMode = undefined;
10605     this.aFromValue = null;
10606     this.aByValue = null;
10607     this.aKeyTimes = null;
10608     this.aValues = null;
10609     this.aFormula= null;
10611 extend( AnimationBaseNode3, AnimationBaseNode2 );
10614 AnimationBaseNode3.prototype.parseElement = function()
10616     var bRet = AnimationBaseNode3.superclass.parseElement.call( this );
10618     var aAnimElem = this.aElement;
10620     // accumulate attribute
10621     this.eAccumulate = ACCUMULATE_MODE_NONE;
10622     var sAccumulateAttr = aAnimElem.getAttributeNS( NSS['smil'], 'accumulate' );
10623     if( sAccumulateAttr == 'sum' )
10624         this.eAccumulate = ACCUMULATE_MODE_SUM;
10626     // calcMode attribute
10627     this.eCalcMode = CALC_MODE_LINEAR;
10628     var sCalcModeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'calcMode' );
10629     if( sCalcModeAttr && aCalcModeInMap[ sCalcModeAttr ] )
10630         this.eCalcMode = aCalcModeInMap[ sCalcModeAttr ];
10632     // from attribute
10633     this.aFromValue = aAnimElem.getAttributeNS( NSS['smil'], 'from' );
10635     // by attribute
10636     this.aByValue = aAnimElem.getAttributeNS( NSS['smil'], 'by' );
10638     // keyTimes attribute
10639     this.aKeyTimes = [];
10640     var sKeyTimesAttr = aAnimElem.getAttributeNS( NSS['smil'], 'keyTimes' );
10641     sKeyTimesAttr = removeWhiteSpaces( sKeyTimesAttr );
10642     if( sKeyTimesAttr )
10643     {
10644         var aKeyTimes = sKeyTimesAttr.split( ';' );
10645         for( var i = 0; i < aKeyTimes.length; ++i )
10646             this.aKeyTimes.push( parseFloat( aKeyTimes[i] ) );
10647     }
10649     // values attribute
10650     var sValuesAttr = aAnimElem.getAttributeNS( NSS['smil'], 'values' );
10651     if( sValuesAttr )
10652     {
10653         this.aValues = sValuesAttr.split( ';' );
10654     }
10655     else
10656     {
10657         this.aValues = [];
10658     }
10660     // formula attribute
10661     this.aFormula = aAnimElem.getAttributeNS( NSS['anim'], 'formula' );
10663     return bRet;
10666 AnimationBaseNode3.prototype.getAccumulate = function()
10668     return this.eAccumulate;
10671 AnimationBaseNode3.prototype.getCalcMode = function()
10673     return this.eCalcMode;
10676 AnimationBaseNode3.prototype.getFromValue = function()
10678     return this.aFromValue;
10681 AnimationBaseNode3.prototype.getByValue = function()
10683     return this.aByValue;
10686 AnimationBaseNode3.prototype.getKeyTimes = function()
10688     return this.aKeyTimes;
10691 AnimationBaseNode3.prototype.getValues = function()
10693     return this.aValues;
10696 AnimationBaseNode3.prototype.getFormula = function()
10698     return this.aFormula;
10701 AnimationBaseNode3.prototype.info = function( bVerbose )
10703     var sInfo = AnimationBaseNode3.superclass.info.call( this, bVerbose );
10705     if( bVerbose )
10706     {
10707         // accumulate mode
10708         if( this.getAccumulate() )
10709             sInfo += ';  accumulate: ' + aAccumulateModeOutMap[ this.getAccumulate() ];
10711         // calcMode
10712         sInfo += ';  calcMode: ' + aCalcModeOutMap[ this.getCalcMode() ];
10714         // from
10715         if( this.getFromValue() )
10716             sInfo += ';  from: ' + this.getFromValue();
10718         // by
10719         if( this.getByValue() )
10720             sInfo += ';  by: ' + this.getByValue();
10722         // keyTimes
10723         if( this.getKeyTimes().length )
10724             sInfo += ';  keyTimes: ' + this.getKeyTimes().join( ',' );
10726         // values
10727         if( this.getValues().length )
10728             sInfo += ';  values: ' + this.getValues().join( ',' );
10730         // formula
10731         if( this.getFormula() )
10732             sInfo += ';  formula: ' + this.getFormula();
10733     }
10735     return sInfo;
10741 function BaseContainerNode( aAnimElem, aParentNode, aNodeContext )
10743     BaseContainerNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
10745     this.sClassName = 'BaseContainerNode';
10746     this.bIsContainer = true;
10747     this.aChildrenArray = [];
10748     this.nFinishedChildren = 0;
10749     this.bDurationIndefinite = false;
10750     this.nLeftIterations = 1;
10752     this.eImpressNodeType = undefined;
10753     this.ePresetClass =  undefined;
10754     this.ePresetId =  undefined;
10756 extend( BaseContainerNode, BaseNode );
10759 BaseContainerNode.prototype.parseElement= function()
10761     var bRet = BaseContainerNode.superclass.parseElement.call( this );
10763     var aAnimElem = this.aElement;
10765     // node-type attribute
10766     this.eImpressNodeType = IMPRESS_DEFAULT_NODE;
10767     var sNodeTypeAttr = aAnimElem.getAttributeNS( NSS['presentation'], 'node-type' );
10768     if( sNodeTypeAttr && aImpressNodeTypeInMap[ sNodeTypeAttr ] )
10769         this.eImpressNodeType = aImpressNodeTypeInMap[ sNodeTypeAttr ];
10770     this.bMainSequenceRootNode = ( this.eImpressNodeType == IMPRESS_MAIN_SEQUENCE_NODE );
10771     this.bInteractiveSequenceRootNode = ( this.eImpressNodeType == IMPRESS_INTERACTIVE_SEQUENCE_NODE );
10773     // preset-class attribute
10774     this.ePresetClass =  undefined;
10775     var sPresetClassAttr = aAnimElem.getAttributeNS( NSS['presentation'], 'preset-class' );
10776     if( sPresetClassAttr && aPresetClassInMap[ sPresetClassAttr ] )
10777         this.ePresetClass = aPresetClassInMap[ sPresetClassAttr ];
10779     // preset-id attribute
10780     this.ePresetId =  undefined;
10781     var sPresetIdAttr = aAnimElem.getAttributeNS( NSS['presentation'], 'preset-id' );
10782     if( sPresetIdAttr && aPresetIdInMap[ sPresetIdAttr ] )
10783         this.ePresetId = aPresetIdInMap[ sPresetIdAttr ];
10786     // parse children elements
10787     var nChildrenCount = this.aChildrenArray.length;
10788     for( var i = 0; i < nChildrenCount; ++i )
10789     {
10790         this.aChildrenArray[i].parseElement();
10791     }
10794     // resolve duration
10795     this.bDurationIndefinite
10796             = ( !this.getDuration() || this.getDuration().isIndefinite()  ) &&
10797               ( !this.getEnd() || ( this.getEnd().getType() != OFFSET_TIMING ) );
10799     return bRet;
10802 BaseContainerNode.prototype.appendChildNode = function( aAnimationNode )
10804     if( ! this.checkValidNode() )
10805         return ;
10807     if( aAnimationNode.registerDeactivatingListener( this ) )
10808         this.aChildrenArray.push( aAnimationNode );
10811 BaseContainerNode.prototype.removeAllChildrenNodes = function()
10813     this.aChildrenArray = [];
10816 BaseContainerNode.prototype.init_st = function()
10818     this.nLeftIterations = this.getRepeatCount();
10820     return this.init_children();
10823 BaseContainerNode.prototype.init_children = function()
10825     this.nFinishedChildren = 0;
10826     var nChildrenCount = this.aChildrenArray.length;
10827     var nInitChildren = 0;
10828     for( var i = 0; i < nChildrenCount; ++i )
10829     {
10830         if( this.aChildrenArray[i].init() )
10831         {
10832             ++nInitChildren;
10833         }
10834     }
10835     return ( nChildrenCount == nInitChildren );
10839 BaseContainerNode.prototype.deactivate_st = function( eDestState )
10841     this.nLeftIterations = 0;
10842     if( eDestState == FROZEN_NODE )
10843     {
10844         // deactivate all children that are not FROZEN or ENDED:
10845         this.forEachChildNode( mem_fn( 'deactivate' ), ~( FROZEN_NODE | ENDED_NODE ) );
10846     }
10847     else
10848     {
10849         // end all children that are not ENDED:
10850         this.forEachChildNode( mem_fn( 'end' ), ~ENDED_NODE );
10851         if( this.getFillMode() == FILL_MODE_REMOVE )
10852             this.removeEffect();
10853     }
10856 BaseContainerNode.prototype.hasPendingAnimation = function()
10858     var nChildrenCount = this.aChildrenArray.length;
10859     for( var i = 0; i < nChildrenCount; ++i )
10860     {
10861         if( this.aChildrenArray[i].hasPendingAnimation() )
10862             return true;
10863     }
10864     return false;
10867 BaseContainerNode.prototype.activate_st = function()
10869     log( 'BaseContainerNode.activate_st: abstract method called' );
10872 BaseContainerNode.prototype.notifyDeactivating = function( /*aAnimationNode*/ )
10874     log( 'BaseContainerNode.notifyDeactivating: abstract method called' );
10877 BaseContainerNode.prototype.isDurationIndefinite = function()
10879     return this.bDurationIndefinite;
10882 BaseContainerNode.prototype.isChildNode = function( aAnimationNode )
10884     var nChildrenCount = this.aChildrenArray.length;
10885     for( var i = 0; i < nChildrenCount; ++i )
10886     {
10887         if( this.aChildrenArray[i].getId() == aAnimationNode.getId() )
10888             return true;
10889     }
10890     return false;
10893 BaseContainerNode.prototype.notifyDeactivatedChild = function( aChildNode )
10895     assert( ( aChildNode.getState() == FROZEN_NODE ) || ( aChildNode.getState() == ENDED_NODE ),
10896             'BaseContainerNode.notifyDeactivatedChild: passed child node is neither in FROZEN nor in ENDED state' );
10898     assert( this.getState() != INVALID_NODE,
10899             'BaseContainerNode.notifyDeactivatedChild: this node is invalid' );
10901     if( !this.isChildNode( aChildNode ) )
10902     {
10903         log( 'BaseContainerNode.notifyDeactivatedChild: unknown child notifier!' );
10904         return false;
10905     }
10907     var nChildrenCount = this.aChildrenArray.length;
10909     assert( ( this.nFinishedChildren < nChildrenCount ),
10910             'BaseContainerNode.notifyDeactivatedChild: assert(this.nFinishedChildren < nChildrenCount) failed' );
10912     ++this.nFinishedChildren;
10913     var bFinished = ( this.nFinishedChildren >= nChildrenCount );
10915     if( bFinished && this.isDurationIndefinite() )
10916     {
10917         if( this.nLeftIterations >= 1.0 )
10918         {
10919             this.nLeftIterations -= 1.0;
10920         }
10921         if( this.nLeftIterations >= 1.0 )
10922         {
10923             bFinished = false;
10924             var aRepetitionEvent = makeDelay( bind( this, this.repeat ), 0.0 );
10925             this.aContext.aTimerEventQueue.addEvent( aRepetitionEvent );
10926         }
10927         else
10928         {
10929             this.deactivate();
10930         }
10931     }
10933     return bFinished;
10936 BaseContainerNode.prototype.repeat = function()
10938     // end all children that are not ENDED:
10939     this.forEachChildNode( mem_fn( 'end' ), ~ENDED_NODE );
10940     this.removeEffect();
10941     var bInitialized = this.init_children();
10942     if( bInitialized )
10943         this.activate_st();
10944     return bInitialized;
10947 BaseContainerNode.prototype.removeEffect = function()
10949     var nChildrenCount = this.aChildrenArray.length;
10950     if( nChildrenCount == 0 )
10951         return;
10952     // We remove effect in reverse order.
10953     for( var i = nChildrenCount - 1; i >= 0; --i )
10954     {
10955         if( ( this.aChildrenArray[i].getState() & ( FROZEN_NODE | ENDED_NODE ) ) == 0 )
10956         {
10957             log( 'BaseContainerNode.removeEffect: child(id:'
10958                  + this.aChildrenArray[i].getId() + ') is neither frozen nor ended;'
10959                  + ' state: '
10960                  + aTransitionModeOutMap[ this.aChildrenArray[i].getState() ] );
10961             continue;
10962         }
10963         this.aChildrenArray[i].removeEffect();
10964     }
10967 BaseContainerNode.prototype.saveStateOfAnimatedElement = function()
10969     var nChildrenCount = this.aChildrenArray.length;
10970     for( var i = 0; i < nChildrenCount; ++i )
10971     {
10972         this.aChildrenArray[i].saveStateOfAnimatedElement();
10973     }
10976 BaseContainerNode.prototype.forEachChildNode = function( aFunction, eNodeStateMask )
10978     if( !eNodeStateMask )
10979         eNodeStateMask = -1;
10981     var nChildrenCount = this.aChildrenArray.length;
10982     for( var i = 0; i < nChildrenCount; ++i )
10983     {
10984         if( ( eNodeStateMask != -1 ) && ( ( this.aChildrenArray[i].getState() & eNodeStateMask ) == 0 ) )
10985             continue;
10986         aFunction( this.aChildrenArray[i] );
10987     }
10990 BaseContainerNode.prototype.dispose = function()
10992     var nChildrenCount = this.aChildrenArray.length;
10993     for( var i = 0; i < nChildrenCount; ++i )
10994     {
10995         this.aChildrenArray[i].dispose();
10996     }
10998     BaseContainerNode.superclass.dispose.call( this );
11001 BaseContainerNode.prototype.getImpressNodeType = function()
11003     return this.eImpressNodeType;
11006 BaseContainerNode.prototype.info = function( bVerbose )
11008     var sInfo = BaseContainerNode.superclass.info.call( this, bVerbose );
11010     if( bVerbose )
11011     {
11012         // impress node type
11013         if( this.getImpressNodeType() )
11014             sInfo += ';  node-type: ' + aImpressNodeTypeOutMap[ this.getImpressNodeType() ];
11015     }
11017     var nChildrenCount = this.aChildrenArray.length;
11018     for( var i = 0; i < nChildrenCount; ++i )
11019     {
11020         sInfo += '\n';
11021         sInfo += this.aChildrenArray[i].info( bVerbose );
11022     }
11024     return sInfo;
11028 function ParallelTimeContainer( aAnimElem, aParentNode, aNodeContext )
11030     ParallelTimeContainer.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
11032     this.sClassName = 'ParallelTimeContainer';
11034 extend( ParallelTimeContainer, BaseContainerNode );
11037 ParallelTimeContainer.prototype.activate_st = function()
11039     var nChildrenCount = this.aChildrenArray.length;
11040     var nResolvedChildren = 0;
11041     for( var i = 0; i < nChildrenCount; ++i )
11042     {
11043         if( this.aChildrenArray[i].resolve() )
11044         {
11045             ++nResolvedChildren;
11046         }
11047     }
11049     if( nChildrenCount != nResolvedChildren )
11050     {
11051         log( 'ParallelTimeContainer.activate_st: resolving all children failed' );
11052         return;
11053     }
11056     if( this.isDurationIndefinite() && ( nChildrenCount == 0  ) )
11057     {
11058         this.scheduleDeactivationEvent( this.makeDeactivationEvent( 0.0 ) );
11059     }
11060     else
11061     {
11062         this.scheduleDeactivationEvent();
11063     }
11066 ParallelTimeContainer.prototype.notifyDeactivating = function( aAnimationNode )
11068     this.notifyDeactivatedChild( aAnimationNode );
11074 function SequentialTimeContainer( aAnimElem, aParentNode, aNodeContext )
11076     SequentialTimeContainer.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
11078     this.sClassName = 'SequentialTimeContainer';
11079     this.bIsRewinding = false;
11080     this.aCurrentSkipEvent = null;
11081     this.aRewindCurrentEffectEvent = null;
11082     this.aRewindLastEffectEvent = null;
11084 extend( SequentialTimeContainer, BaseContainerNode );
11087 SequentialTimeContainer.prototype.activate_st = function()
11089     var nChildrenCount = this.aChildrenArray.length;
11090     for( ; this.nFinishedChildren < nChildrenCount; ++this.nFinishedChildren )
11091     {
11092         if( this.resolveChild( this.aChildrenArray[ this.nFinishedChildren ] ) )
11093             break;
11094         else
11095             log( 'SequentialTimeContainer.activate_st: resolving child failed!' );
11096     }
11098     if( this.isDurationIndefinite() && ( ( nChildrenCount == 0 ) || ( this.nFinishedChildren >= nChildrenCount ) ) )
11099     {
11100         // deactivate ASAP:
11101         this.scheduleDeactivationEvent( this.makeDeactivationEvent( 0.0 ) );
11102     }
11103     else
11104     {
11105         this.scheduleDeactivationEvent();
11106     }
11109 SequentialTimeContainer.prototype.notifyDeactivating = function( aNotifier )
11111     // If we are rewinding we have not to resolve the next child.
11112     if( this.bIsRewinding )
11113         return;
11115     if( this.notifyDeactivatedChild( aNotifier ) )
11116         return;
11118     assert( this.nFinishedChildren < this.aChildrenArray.length,
11119             'SequentialTimeContainer.notifyDeactivating: assertion (this.nFinishedChildren < this.aChildrenArray.length) failed' );
11121     var aNextChild = this.aChildrenArray[ this.nFinishedChildren ];
11123     assert( aNextChild.getState() == UNRESOLVED_NODE,
11124             'SequentialTimeContainer.notifyDeactivating: assertion (aNextChild.getState == UNRESOLVED_NODE) failed' );
11126     if( !this.resolveChild( aNextChild ) )
11127     {
11128         // could not resolve child - since we risk to
11129         // stall the chain of events here, play it safe
11130         // and deactivate this node (only if we have
11131         // indefinite duration - otherwise, we'll get a
11132         // deactivation event, anyways).
11133         this.deactivate();
11134     }
11137 /** skipEffect
11138  *  Skip the current playing shape effect.
11139  *  Requires: the current node is the main sequence root node.
11141  *  @param aChildNode
11142  *      An animation node representing the root node of the shape effect being
11143  *      played.
11144  */
11145 SequentialTimeContainer.prototype.skipEffect = function( aChildNode )
11147     if( this.isChildNode( aChildNode ) )
11148     {
11149         // First off we end all queued activities.
11150         this.getContext().aActivityQueue.endAll();
11151         // We signal that we are going to skip all subsequent animations by
11152         // setting the bIsSkipping flag to 'true', then all queued events are
11153         // fired immediately. In such a way the correct order of the various
11154         // events that belong to the animation time-line is preserved.
11155         this.getContext().bIsSkipping = true;
11156         this.getContext().aTimerEventQueue.forceEmpty();
11157         this.getContext().bIsSkipping = false;
11158         var aEvent = makeEvent( bind2( aChildNode.deactivate, aChildNode ) );
11159         this.getContext().aTimerEventQueue.addEvent( aEvent );
11160     }
11161     else
11162     {
11163         log( 'SequentialTimeContainer.skipEffect: unknown child: '
11164                  + aChildNode.getId() );
11165     }
11168 /** rewindCurrentEffect
11169  *  Rewind a playing shape effect.
11170  *  Requires: the current node is the main sequence root node.
11172  *  @param aChildNode
11173  *      An animation node representing the root node of the shape effect being
11174  *      played
11175  */
11176 SequentialTimeContainer.prototype.rewindCurrentEffect = function( aChildNode )
11178     if( this.isChildNode( aChildNode ) )
11179     {
11180         assert( !this.bIsRewinding,
11181                 'SequentialTimeContainer.rewindCurrentEffect: is already rewinding.' );
11183         // We signal we are rewinding so the notifyDeactivating method returns
11184         // immediately without increment the finished children counter and
11185         // resolve the next child.
11186         this.bIsRewinding = true;
11187         // First off we end all queued activities.
11188         this.getContext().aActivityQueue.endAll();
11189         // We signal that we are going to skip all subsequent animations by
11190         // setting the bIsSkipping flag to 'true', then all queued events are
11191         // fired immediately. In such a way the correct order of the various
11192         // events that belong to the animation time-line is preserved.
11193         this.getContext().bIsSkipping = true;
11194         this.getContext().aTimerEventQueue.forceEmpty();
11195         this.getContext().bIsSkipping = false;
11196         // We end all new activities appended to the activity queue by
11197         // the fired events.
11198         this.getContext().aActivityQueue.endAll();
11200         // Now we perform a final 'end' and restore the animated shape to
11201         // the state it was before the current effect was applied.
11202         aChildNode.end();
11203         aChildNode.removeEffect();
11204         // Finally we place the child node to the 'unresolved' state and
11205         // resolve it again.
11206         aChildNode.init();
11207         this.resolveChild( aChildNode );
11208         this.notifyRewindedEvent( aChildNode );
11209         this.bIsRewinding = false;
11210     }
11211     else
11212     {
11213         log( 'SequentialTimeContainer.rewindCurrentEffect: unknown child: '
11214                  + aChildNode.getId() );
11215     }
11218 /** rewindLastEffect
11219  *  Rewind the last ended effect.
11220  *  Requires: the current node is the main sequence root node.
11222  *  @param aChildNode
11223  *      An animation node representing the root node of the next shape effect
11224  *      to be played.
11225  */
11226 SequentialTimeContainer.prototype.rewindLastEffect = function( aChildNode )
11228     if( this.isChildNode( aChildNode ) )
11229     {
11230         assert( !this.bIsRewinding,
11231                 'SequentialTimeContainer.rewindLastEffect: is already rewinding.' );
11233         // We signal we are rewinding so the notifyDeactivating method returns
11234         // immediately without increment the finished children counter and
11235         // resolve the next child.
11236         this.bIsRewinding = true;
11237         // We end the current effect.
11238         this.getContext().aTimerEventQueue.forceEmpty();
11239         this.getContext().aActivityQueue.clear();
11240         aChildNode.end();
11241         // Invoking the end method on the current child node that has not yet
11242         // been activated should not lead to any change on the animated shape.
11243         // However for safety we used to call the removeEffect method but
11244         // lately we noticed that when interactive animation sequences are
11245         // involved into the shape effect invoking such a method causes
11246         // some issue.
11247         //aChildNode.removeEffect();
11249         // As we rewind the previous effect we need to decrease the finished
11250         // children counter.
11251         --this.nFinishedChildren;
11252         var aPreviousChildNode = this.aChildrenArray[ this.nFinishedChildren ];
11253         // No need to invoke the end method for the previous child as it is
11254         // already in the ENDED state.
11256         aPreviousChildNode.removeEffect();
11257         // We place the child node to the 'unresolved' state.
11258         aPreviousChildNode.init();
11259         // We need to re-initialize the old current child too, because it is
11260         // in ENDED state now, On the contrary it cannot be resolved again later.
11261         aChildNode.init();
11262         this.resolveChild( aPreviousChildNode );
11263         this.notifyRewindedEvent( aChildNode );
11264         this.bIsRewinding = false;
11265     }
11266     else
11267     {
11268         log( 'SequentialTimeContainer.rewindLastEffect: unknown child: '
11269                  + aChildNode.getId() );
11270     }
11273 /** resolveChild
11274  *  Resolve the passed child.
11275  *  In case this node is a main sequence root node events for skipping and
11276  *  rewinding the effect related to the passed child node are created and
11277  *  registered.
11279  *  @param aChildNode
11280  *      An animation node representing the root node of the next shape effect
11281  *      to be played.
11282  *  @return
11283  *      It returns true if the passed child has been resolved successfully,
11284  *      false otherwise.
11285  */
11286 SequentialTimeContainer.prototype.resolveChild = function( aChildNode )
11288     var bResolved = aChildNode.resolve();
11290     if( bResolved && ( this.isMainSequenceRootNode() || this.isInteractiveSequenceRootNode() ) )
11291     {
11292         if( this.aCurrentSkipEvent )
11293             this.aCurrentSkipEvent.dispose();
11294         this.aCurrentSkipEvent = makeEvent( bind2( SequentialTimeContainer.prototype.skipEffect, this, aChildNode ) );
11296         if( this.aRewindCurrentEffectEvent )
11297             this.aRewindCurrentEffectEvent.dispose();
11298         this.aRewindCurrentEffectEvent = makeEvent( bind2( SequentialTimeContainer.prototype.rewindCurrentEffect, this, aChildNode ) );
11300         if( this.aRewindLastEffectEvent )
11301             this.aRewindLastEffectEvent.dispose();
11302         this.aRewindLastEffectEvent = makeEvent( bind2( SequentialTimeContainer.prototype.rewindLastEffect, this, aChildNode ) );
11304         if( this.isMainSequenceRootNode() )
11305         {
11306             this.aContext.aEventMultiplexer.registerSkipEffectEvent( this.aCurrentSkipEvent );
11307             this.aContext.aEventMultiplexer.registerRewindCurrentEffectEvent( this.aRewindCurrentEffectEvent );
11308             this.aContext.aEventMultiplexer.registerRewindLastEffectEvent( this.aRewindLastEffectEvent );
11309         }
11310         else if( this.isInteractiveSequenceRootNode() )
11311         {
11312             this.aContext.aEventMultiplexer.registerSkipInteractiveEffectEvent( aChildNode.getId(), this.aCurrentSkipEvent );
11313             this.aContext.aEventMultiplexer.registerRewindRunningInteractiveEffectEvent( aChildNode.getId(), this.aRewindCurrentEffectEvent );
11314             this.aContext.aEventMultiplexer.registerRewindEndedInteractiveEffectEvent( aChildNode.getId(), this.aRewindLastEffectEvent );
11315         }
11316     }
11317     return bResolved;
11320 SequentialTimeContainer.prototype.notifyRewindedEvent = function( aChildNode )
11322     if( this.isInteractiveSequenceRootNode() )
11323     {
11324         this.aContext.aEventMultiplexer.notifyRewindedEffectEvent( aChildNode.getId() );
11326         var sId = aChildNode.getBegin().getEventBaseElementId();
11327         if( sId )
11328         {
11329             this.aContext.aEventMultiplexer.notifyRewindedEffectEvent( sId );
11330         }
11331     }
11334 SequentialTimeContainer.prototype.dispose = function()
11336     if( this.aCurrentSkipEvent )
11337         this.aCurrentSkipEvent.dispose();
11339     SequentialTimeContainer.superclass.dispose.call( this );
11345 function PropertyAnimationNode(  aAnimElem, aParentNode, aNodeContext )
11347     PropertyAnimationNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
11349     this.sClassName = 'PropertyAnimationNode';
11351 extend( PropertyAnimationNode, AnimationBaseNode3 );
11354 PropertyAnimationNode.prototype.createActivity = function()
11356     var aActivityParamSet = this.fillActivityParams();
11358     var aAnimation = createPropertyAnimation( this.getAttributeName(),
11359                                               this.getAnimatedElement(),
11360                                               this.aNodeContext.aSlideWidth,
11361                                               this.aNodeContext.aSlideHeight );
11363     var aInterpolator = null;  // createActivity will compute it;
11364     return createActivity( aActivityParamSet, this, aAnimation, aInterpolator );
11370 function AnimationSetNode(  aAnimElem, aParentNode, aNodeContext )
11372     AnimationSetNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
11374     this.sClassName = 'AnimationSetNode';
11376 extend( AnimationSetNode, AnimationBaseNode2 );
11379 AnimationSetNode.prototype.createActivity = function()
11381     var aAnimation = createPropertyAnimation( this.getAttributeName(),
11382                                               this.getAnimatedElement(),
11383                                               this.aNodeContext.aSlideWidth,
11384                                               this.aNodeContext.aSlideHeight );
11386     var aActivityParamSet = this.fillActivityParams();
11388     return new SetActivity( aActivityParamSet, aAnimation, this.getToValue() );
11394 function AnimationColorNode(  aAnimElem, aParentNode, aNodeContext )
11396     AnimationColorNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
11398     this.sClassName = 'AnimationColorNode';
11400     this.eColorInterpolation = undefined;
11401     this.eColorInterpolationDirection = undefined;
11403 extend( AnimationColorNode, AnimationBaseNode3 );
11406 AnimationColorNode.prototype.parseElement = function()
11408     var bRet = AnimationColorNode.superclass.parseElement.call( this );
11410     var aAnimElem = this.aElement;
11412     // color-interpolation attribute
11413     this.eColorInterpolation = COLOR_SPACE_RGB;
11414     var sColorInterpolationAttr = aAnimElem.getAttributeNS( NSS['anim'], 'color-interpolation' );
11415     if( sColorInterpolationAttr && aColorSpaceInMap[ sColorInterpolationAttr ] )
11416         this.eColorInterpolation = aColorSpaceInMap[ sColorInterpolationAttr ];
11418     // color-interpolation-direction attribute
11419     this.eColorInterpolationDirection = CLOCKWISE;
11420     var sColorInterpolationDirectionAttr = aAnimElem.getAttributeNS( NSS['anim'], 'color-interpolation-direction' );
11421     if( sColorInterpolationDirectionAttr && aClockDirectionInMap[ sColorInterpolationDirectionAttr ] )
11422         this.eColorInterpolationDirection = aClockDirectionInMap[ sColorInterpolationDirectionAttr ];
11424     return bRet;
11427 AnimationColorNode.prototype.createActivity = function()
11429     var aActivityParamSet = this.fillActivityParams();
11431     var aAnimation = createPropertyAnimation( this.getAttributeName(),
11432                                               this.getAnimatedElement(),
11433                                               this.aNodeContext.aSlideWidth,
11434                                               this.aNodeContext.aSlideHeight );
11436     var aColorAnimation;
11437     var aInterpolator;
11438     if( this.getColorInterpolation() === COLOR_SPACE_HSL )
11439     {
11440         ANIMDBG.print( 'AnimationColorNode.createActivity: color space hsl'  );
11441         aColorAnimation = new HSLAnimationWrapper( aAnimation );
11442         var aInterpolatorMaker = aInterpolatorHandler.getInterpolator( this.getCalcMode(),
11443                                                                        COLOR_PROPERTY,
11444                                                                        COLOR_SPACE_HSL );
11445         aInterpolator = aInterpolatorMaker( this.getColorInterpolationDirection() );
11446     }
11447     else
11448     {
11449         ANIMDBG.print( 'AnimationColorNode.createActivity: color space rgb'  );
11450         aColorAnimation = aAnimation;
11451         aInterpolator = aInterpolatorHandler.getInterpolator( this.getCalcMode(),
11452                                                               COLOR_PROPERTY,
11453                                                               COLOR_SPACE_RGB );
11454     }
11456     return createActivity( aActivityParamSet, this, aColorAnimation, aInterpolator );
11459 AnimationColorNode.prototype.getColorInterpolation = function()
11461     return this.eColorInterpolation;
11464 AnimationColorNode.prototype.getColorInterpolationDirection = function()
11466     return this.eColorInterpolationDirection;
11469 AnimationColorNode.prototype.info = function( bVerbose )
11471     var sInfo = AnimationColorNode.superclass.info.call( this, bVerbose );
11473     if( bVerbose )
11474     {
11475         // color interpolation
11476         sInfo += ';  color-interpolation: ' + aColorSpaceOutMap[ this.getColorInterpolation() ];
11478         // color interpolation direction
11479         sInfo += ';  color-interpolation-direction: ' + aClockDirectionOutMap[ this.getColorInterpolationDirection() ];
11480     }
11481     return sInfo;
11487 function AnimationTransitionFilterNode(  aAnimElem, aParentNode, aNodeContext )
11489     AnimationTransitionFilterNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
11491     this.sClassName = 'AnimationTransitionFilterNode';
11493     this.eTransitionType = undefined;
11494     this.eTransitionSubType = undefined;
11495     this.bReverseDirection = undefined;
11496     this.eTransitionMode = undefined;
11498 extend( AnimationTransitionFilterNode, AnimationBaseNode );
11501 AnimationTransitionFilterNode.prototype.createActivity = function()
11503     var aActivityParamSet = this.fillActivityParams();
11505     return createShapeTransition( aActivityParamSet,
11506                                   this.getAnimatedElement(),
11507                                   this.aNodeContext.aSlideWidth,
11508                                   this.aNodeContext.aSlideHeight,
11509                                   this );
11512 AnimationTransitionFilterNode.prototype.parseElement = function()
11514     var bRet = AnimationTransitionFilterNode.superclass.parseElement.call( this );
11515     var bIsValidTransition = true;
11517     var aAnimElem = this.aElement;
11519     // type attribute
11520     this.eTransitionType = undefined;
11521     var sTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'type' );
11522     if( sTypeAttr && aTransitionTypeInMap[ sTypeAttr ] )
11523     {
11524         this.eTransitionType = aTransitionTypeInMap[ sTypeAttr ];
11525     }
11526     else
11527     {
11528         bIsValidTransition = false;
11529         log( 'AnimationTransitionFilterNode.parseElement: transition type not valid: ' + sTypeAttr );
11530     }
11532     // subtype attribute
11533     this.eTransitionSubType = undefined;
11534     var sSubTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'subtype' );
11535     if( sSubTypeAttr === null )
11536         sSubTypeAttr = 'default';
11537     if( sSubTypeAttr && ( aTransitionSubtypeInMap[ sSubTypeAttr ] !== undefined  ) )
11538     {
11539         this.eTransitionSubType = aTransitionSubtypeInMap[ sSubTypeAttr ];
11540     }
11541     else
11542     {
11543         bIsValidTransition = false;
11544         log( 'AnimationTransitionFilterNode.parseElement: transition subtype not valid: ' + sSubTypeAttr );
11545     }
11547     // if we do not support the requested transition type we fall back to crossfade transition;
11548     // note: if we do not provide an alternative transition and we set the state of the animation node to 'invalid'
11549     // the animation engine stops itself;
11550     if( !bIsValidTransition )
11551     {
11552         this.eTransitionType = FADE_TRANSITION;
11553         this.eTransitionSubType = CROSSFADE_TRANS_SUBTYPE;
11554         log( 'AnimationTransitionFilterNode.parseElement: in place of the invalid transition a crossfade transition is used' );
11555     }
11557     // direction attribute
11558     this.bReverseDirection = false;
11559     var sDirectionAttr = aAnimElem.getAttributeNS( NSS['smil'], 'direction' );
11560     if( sDirectionAttr == 'reverse' )
11561         this.bReverseDirection = true;
11563     // mode attribute:
11564     this.eTransitionMode = TRANSITION_MODE_IN;
11565     var sModeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'mode' );
11566     if( sModeAttr === 'out' )
11567         this.eTransitionMode = TRANSITION_MODE_OUT;
11569     return bRet;
11572 AnimationTransitionFilterNode.prototype.getTransitionType = function()
11574     return this.eTransitionType;
11577 AnimationTransitionFilterNode.prototype.getTransitionSubType = function()
11579     return this.eTransitionSubType;
11582 AnimationTransitionFilterNode.prototype.getTransitionMode = function()
11584     return this.eTransitionMode;
11587 AnimationTransitionFilterNode.prototype.getReverseDirection = function()
11589     return this.bReverseDirection;
11592 AnimationTransitionFilterNode.prototype.info = function( bVerbose )
11594     var sInfo = AnimationTransitionFilterNode.superclass.info.call( this, bVerbose );
11596     if( bVerbose )
11597     {
11598         // transition type
11599         sInfo += ';  type: ' + getKeyByValue(aTransitionTypeInMap, this.getTransitionType());
11601         // transition subtype
11602         sInfo += ';  subtype: ' + getKeyByValue(aTransitionSubtypeInMap, this.getTransitionSubType());
11604         // transition direction
11605         if( this.getReverseDirection() )
11606             sInfo += ';  direction: reverse';
11607     }
11609     return sInfo;
11614 /**********************************************************************************************
11615  *      Animation Node Factory
11616  **********************************************************************************************/
11619 function createAnimationTree( aRootElement, aNodeContext )
11621     return createAnimationNode( aRootElement, null, aNodeContext );
11627 function createAnimationNode( aElement, aParentNode, aNodeContext )
11629     assert( aElement, 'createAnimationNode: invalid animation element' );
11631     var eAnimationNodeType = getAnimationElementType( aElement );
11633     var aCreatedNode = null;
11634     var aCreatedContainer = null;
11636     switch( eAnimationNodeType )
11637     {
11638         case ANIMATION_NODE_PAR:
11639             aCreatedNode = aCreatedContainer =
11640                 new ParallelTimeContainer( aElement, aParentNode, aNodeContext );
11641             break;
11642         case ANIMATION_NODE_ITERATE:
11643             // map iterate container to ParallelTimeContainer.
11644             // the iterating functionality is to be found
11645             // below, (see method implCreateIteratedNodes)
11646             aCreatedNode = aCreatedContainer =
11647                 new ParallelTimeContainer( aElement, aParentNode, aNodeContext );
11648             break;
11649         case ANIMATION_NODE_SEQ:
11650             aCreatedNode = aCreatedContainer =
11651                 new SequentialTimeContainer( aElement, aParentNode, aNodeContext );
11652             break;
11653         case ANIMATION_NODE_ANIMATE:
11654             aCreatedNode = new PropertyAnimationNode( aElement, aParentNode, aNodeContext );
11655             break;
11656         case ANIMATION_NODE_SET:
11657             aCreatedNode = new AnimationSetNode( aElement, aParentNode, aNodeContext );
11658             break;
11659         case ANIMATION_NODE_ANIMATEMOTION:
11660             //aCreatedNode = new AnimationPathMotionNode( aElement, aParentNode, aNodeContext );
11661             //break;
11662             log( 'createAnimationNode: ANIMATEMOTION not implemented' );
11663             return null;
11664         case ANIMATION_NODE_ANIMATECOLOR:
11665             aCreatedNode = new AnimationColorNode( aElement, aParentNode, aNodeContext );
11666             break;
11667         case ANIMATION_NODE_ANIMATETRANSFORM:
11668             //aCreatedNode = new AnimationTransformNode( aElement, aParentNode, aNodeContext );
11669             //break;
11670             log( 'createAnimationNode: ANIMATETRANSFORM not implemented' );
11671             return null;
11672         case ANIMATION_NODE_TRANSITIONFILTER:
11673             aCreatedNode = new AnimationTransitionFilterNode( aElement, aParentNode, aNodeContext );
11674             break;
11675          case ANIMATION_NODE_AUDIO:
11676             log( 'createAnimationNode: AUDIO not implemented' );
11677             return null;
11678          case ANIMATION_NODE_COMMAND:
11679             log( 'createAnimationNode: COMMAND not implemented' );
11680             return null;
11681         default:
11682             log( 'createAnimationNode: invalid Animation Node Type: ' + eAnimationNodeType );
11683             return null;
11684     }
11686     if( aCreatedContainer )
11687     {
11688         if( eAnimationNodeType == ANIMATION_NODE_ITERATE )
11689         {
11690             createIteratedNodes( aElement, aCreatedContainer, aNodeContext );
11691         }
11692         else
11693         {
11694             var aChildrenArray = getElementChildren( aElement );
11695             for( var i = 0; i < aChildrenArray.length; ++i )
11696             {
11697                 if( !createChildNode( aChildrenArray[i], aCreatedContainer, aNodeContext ) )
11698                 {
11699                     aCreatedContainer.removeAllChildrenNodes();
11700                     break;
11701                 }
11702             }
11703         }
11704     }
11706     return aCreatedNode;
11712 function createChildNode( aElement, aParentNode, aNodeContext )
11714     var aChildNode = createAnimationNode( aElement, aParentNode, aNodeContext );
11716     if( !aChildNode )
11717     {
11718         log( 'createChildNode: child node creation failed' );
11719         return false;
11720     }
11721     else
11722     {
11723         aParentNode.appendChildNode( aChildNode );
11724         return true;
11725     }
11731 function createIteratedNodes( /*aElement, aContainerNode, aNodeContext*/ )
11733     // not implemented
11738 /**********************************************************************************************
11739  *      Animation Factory
11740  **********************************************************************************************/
11742 // makeScaler is used in aAttributeMap:
11743 // eslint-disable-next-line no-unused-vars
11744 function makeScaler( nScale )
11746     if( ( typeof( nScale ) !== typeof( 0 ) ) || !isFinite( nScale ) )
11747     {
11748         log( 'makeScaler: not valid param passed: ' + nScale );
11749         return null;
11750     }
11752     return  function( nValue )
11753             {
11754                 return ( nScale * nValue );
11755             };
11760 // eslint-disable-next-line no-unused-vars
11761 function createPropertyAnimation( sAttrName, aAnimatedElement, nWidth, nHeight )
11763     if( !aAttributeMap[ sAttrName ] )
11764     {
11765         log( 'createPropertyAnimation: attribute is unknown' );
11766         return null;
11767     }
11770     var aFunctorSet = aAttributeMap[ sAttrName ];
11772     var sGetValueMethod =   aFunctorSet.get;
11773     var sSetValueMethod =   aFunctorSet.set;
11775     if( !sGetValueMethod || !sSetValueMethod  )
11776     {
11777         log( 'createPropertyAnimation: attribute is not handled' );
11778         return null;
11779     }
11781     var aGetModifier =  eval( aFunctorSet.getmod );
11782     var aSetModifier =  eval( aFunctorSet.setmod );
11785     return new GenericAnimation( bind( aAnimatedElement, aAnimatedElement[ sGetValueMethod ] ),
11786                                  bind( aAnimatedElement, aAnimatedElement[ sSetValueMethod ] ),
11787                                  aGetModifier,
11788                                  aSetModifier);
11794 /** createShapeTransition
11796  *  @param aActivityParamSet
11797  *     The set of property for the activity to be created.
11798  *  @param aAnimatedElement
11799  *      The element to be animated.
11800  *  @param nSlideWidth
11801  *      The width of a slide.
11802  *  @param nSlideHeight
11803  *      The height of a slide.
11804  *  @param aAnimatedTransitionFilterNode
11805  *      An instance of the AnimationFilterNode that invoked this function.
11806  *  @return {SimpleActivity}
11807  *      A simple activity handling a shape transition.
11808  */
11809 function createShapeTransition( aActivityParamSet, aAnimatedElement,
11810                                 nSlideWidth, nSlideHeight,
11811                                 aAnimatedTransitionFilterNode )
11813     if( !aAnimatedTransitionFilterNode )
11814     {
11815         log( 'createShapeTransition: the animated transition filter node is not valid.' );
11816         return null;
11817     }
11818     var eTransitionType = aAnimatedTransitionFilterNode.getTransitionType();
11819     var eTransitionSubType = aAnimatedTransitionFilterNode.getTransitionSubType();
11820     var bDirectionForward = ! aAnimatedTransitionFilterNode.getReverseDirection();
11821     var bModeIn = ( aAnimatedTransitionFilterNode.getTransitionMode() == FORWARD );
11823     var aTransitionInfo = aTransitionInfoTable[eTransitionType][eTransitionSubType];
11824     var eTransitionClass = aTransitionInfo['class'];
11826     switch( eTransitionClass )
11827     {
11828         default:
11829         case TRANSITION_INVALID:
11830             log( 'createShapeTransition: transition class: TRANSITION_INVALID' );
11831             return null;
11833         case TRANSITION_CLIP_POLYPOLYGON:
11834             var aParametricPolyPolygon
11835                 = createClipPolyPolygon( eTransitionType, eTransitionSubType );
11836             var aClippingAnimation
11837                 = new ClippingAnimation( aParametricPolyPolygon, aTransitionInfo,
11838                                          bDirectionForward, bModeIn );
11839             return new SimpleActivity( aActivityParamSet, aClippingAnimation, true );
11841         case TRANSITION_SPECIAL:
11842             switch( eTransitionType )
11843             {
11844                 // no special transition filter provided
11845                 // we map everything to crossfade
11846                 default:
11847                     var aAnimation
11848                         = createPropertyAnimation( 'opacity',
11849                                                    aAnimatedElement,
11850                                                    nSlideWidth,
11851                                                    nSlideHeight );
11852                     return new SimpleActivity( aActivityParamSet, aAnimation, bModeIn );
11853             }
11854     }
11861 /** Class ClippingAnimation
11862  *  This class performs a shape transition where the effect is achieved by
11863  *  clipping the shape to be animated with a parametric path.
11865  *  @param aParametricPolyPolygon
11866  *      An object handling a <path> element that depends on a parameter.
11867  *  @param aTransitionInfo
11868  *      The set of parameters defining the shape transition to be performed.
11869  *  @param bDirectionForward
11870  *      The direction the shape transition has to be performed.
11871  *  @param bModeIn
11872  *      If true the element to be animated becomes more visible as the transition
11873  *      progress else it becomes less visible.
11874  */
11875 function ClippingAnimation( aParametricPolyPolygon, aTransitionInfo,
11876                             bDirectionForward, bModeIn )
11878     this.aClippingFunctor = new ClippingFunctor( aParametricPolyPolygon,
11879                                                  aTransitionInfo,
11880                                                  bDirectionForward, bModeIn );
11881     this.bAnimationStarted = false;
11884 /** start
11885  *  This method notifies to the element involved in the transition that
11886  *  the animation is starting and creates the <clipPath> element used for
11887  *  the transition.
11889  *  @param aAnimatableElement
11890  *      The element to be animated.
11891  */
11892 ClippingAnimation.prototype.start = function( aAnimatableElement )
11894     assert( aAnimatableElement,
11895             'ClippingAnimation.start: animatable element is not valid' );
11896     this.aAnimatableElement = aAnimatableElement;
11897     this.aAnimatableElement.initClipPath();
11898     this.aAnimatableElement.notifyAnimationStart();
11900     if( !this.bAnimationStarted )
11901         this.bAnimationStarted = true;
11905 /** end
11906  *  The transition clean up is performed here.
11907  */
11908 ClippingAnimation.prototype.end = function()
11910     if( this.bAnimationStarted )
11911     {
11912         this.aAnimatableElement.cleanClipPath();
11913         this.bAnimationStarted = false;
11914         this.aAnimatableElement.notifyAnimationEnd();
11915     }
11918 /** perform
11919  *  This method set the position of the element to be animated according to
11920  *  the passed  time value.
11922  *  @param nValue
11923  *      The time parameter.
11924  */
11925 ClippingAnimation.prototype.perform = function( nValue )
11927     var nWidth = this.aAnimatableElement.aClippingBBox.width;
11928     var nHeight = this.aAnimatableElement.aClippingBBox.height;
11929     var aPolyPolygonElement = this.aClippingFunctor.perform( nValue, nWidth, nHeight );
11930     this.aAnimatableElement.setClipPath( aPolyPolygonElement );
11933 ClippingAnimation.prototype.getUnderlyingValue = function()
11935     return 0.0;
11941 function GenericAnimation( aGetValueFunc, aSetValueFunc, aGetModifier, aSetModifier )
11943     assert( aGetValueFunc && aSetValueFunc,
11944             'GenericAnimation constructor: get value functor and/or set value functor are not valid' );
11946     this.aGetValueFunc = aGetValueFunc;
11947     this.aSetValueFunc = aSetValueFunc;
11948     this.aGetModifier = aGetModifier;
11949     this.aSetModifier = aSetModifier;
11950     this.aAnimatableElement = null;
11951     this.bAnimationStarted = false;
11955 GenericAnimation.prototype.start = function( aAnimatableElement )
11957     assert( aAnimatableElement, 'GenericAnimation.start: animatable element is not valid' );
11959     this.aAnimatableElement = aAnimatableElement;
11960     this.aAnimatableElement.notifyAnimationStart();
11962     if( !this.bAnimationStarted )
11963         this.bAnimationStarted = true;
11966 GenericAnimation.prototype.end = function()
11968     if( this.bAnimationStarted )
11969     {
11970         this.bAnimationStarted = false;
11971         this.aAnimatableElement.notifyAnimationEnd();
11972     }
11975 GenericAnimation.prototype.perform = function( aValue )
11977     if( this.aSetModifier )
11978         aValue = this.aSetModifier( aValue );
11980     this.aSetValueFunc( aValue );
11983 GenericAnimation.prototype.getUnderlyingValue = function()
11985     var aValue = this.aGetValueFunc();
11986     if( this.aGetModifier )
11987         aValue = this.aGetModifier( aValue );
11988     return aValue;
11994 function HSLAnimationWrapper( aColorAnimation )
11996     assert( aColorAnimation,
11997             'HSLAnimationWrapper constructor: invalid color animation delegate' );
11999     this.aAnimation = aColorAnimation;
12003 HSLAnimationWrapper.prototype.start = function( aAnimatableElement )
12005     this.aAnimation.start( aAnimatableElement );
12008 HSLAnimationWrapper.prototype.end = function()
12010     this.aAnimation.end();
12012 HSLAnimationWrapper.prototype.perform = function( aHSLValue )
12014     this.aAnimation.perform( aHSLValue.convertToRGB() );
12017 HSLAnimationWrapper.prototype.getUnderlyingValue = function()
12019     return this.aAnimation.getUnderlyingValue().convertToHSL();
12025 /** Class SlideChangeBase
12026  *  The base abstract class of classes performing slide transitions.
12028  *  @param aLeavingSlide
12029  *      An object of type AnimatedSlide handling the leaving slide.
12030  *  @param aEnteringSlide
12031  *      An object of type AnimatedSlide handling the entering slide.
12032  */
12033 function SlideChangeBase(aLeavingSlide, aEnteringSlide)
12035     this.aLeavingSlide = aLeavingSlide;
12036     this.aEnteringSlide = aEnteringSlide;
12037     this.bIsFinished = false;
12040 /** start
12041  *  The transition initialization is performed here.
12042  */
12043 SlideChangeBase.prototype.start = function()
12047 /** end
12048  *  The transition clean up is performed here.
12049  */
12050 SlideChangeBase.prototype.end = function()
12052     if( this.bIsFinished )
12053         return;
12055     this.aLeavingSlide.hide();
12056     this.aEnteringSlide.reset();
12057     this.aLeavingSlide.reset();
12059     this.bIsFinished = true;
12062 /** perform
12063  *  This method is responsible for performing the slide transition.
12065  *  @param nValue
12066  *      The time parameter.
12067  *  @return {Boolean}
12068  *      If the transition is performed returns tue else returns false.
12069  */
12070 SlideChangeBase.prototype.perform = function( nValue )
12072     if( this.bIsFinished ) return false;
12074     if( this.aLeavingSlide )
12075         this.performOut( nValue );
12077     if( this.aEnteringSlide )
12078         this.performIn( nValue );
12080     return true;
12083 SlideChangeBase.prototype.getUnderlyingValue = function()
12085     return 0.0;
12088 SlideChangeBase.prototype.performIn = function( )
12090     log( 'SlideChangeBase.performIn: abstract method called' );
12093 SlideChangeBase.prototype.performOut = function( )
12095     log( 'SlideChangeBase.performOut: abstract method called' );
12101 /** Class FadingSlideChange
12102  *  This class performs a slide transition by fading out the leaving slide and
12103  *  fading in the entering slide.
12105  *  @param aLeavingSlide
12106  *      An object of type AnimatedSlide handling the leaving slide.
12107  *  @param aEnteringSlide
12108  *      An object of type AnimatedSlide handling the entering slide.
12109  */
12110 function FadingSlideChange( aLeavingSlide, aEnteringSlide )
12112     FadingSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
12113     this.bFirstRun = true;
12115 extend( FadingSlideChange, SlideChangeBase );
12117 /** start
12118  *  This method notifies to the slides involved in the transition the attributes
12119  *  appended to the slide elements for performing the animation.
12120  *  Moreover it sets the entering slide in the initial state and makes the slide
12121  *  visible.
12122  */
12123 FadingSlideChange.prototype.start = function()
12125     FadingSlideChange.superclass.start.call( this );
12126     this.aEnteringSlide.notifyUsedAttribute( 'opacity' );
12127     this.aLeavingSlide.notifyUsedAttribute( 'opacity' );
12128     this.aEnteringSlide.setOpacity( 0.0 );
12129     this.aEnteringSlide.show();
12132 /** performIn
12133  *  This method set the opacity of the entering slide according to the passed
12134  *  time value.
12136  *  @param nT
12137  *      The time parameter.
12138  */
12139 FadingSlideChange.prototype.performIn = function( nT )
12141     this.aEnteringSlide.setOpacity( nT );
12144 /** performOut
12145  *  This method set the opacity of the leaving slide according to the passed
12146  *  time value.
12148  *  @param nT
12149  *      The time parameter.
12150  */
12151 FadingSlideChange.prototype.performOut = function( nT )
12154     this.aLeavingSlide.setOpacity( 1 - nT );
12160 /** Class FadingOverColorSlideChange
12161  *  This class performs a slide transition by fading out the leaving slide to
12162  *  a given color and fading in the entering slide from the same color.
12164  *  @param aLeavingSlide
12165  *      An object of type AnimatedSlide handling the leaving slide.
12166  *  @param aEnteringSlide
12167  *      An object of type AnimatedSlide handling the entering slide.
12168  *  @param sFadeColor
12169  *      A string representing the color the leaving slide fades out to and
12170  *      the entering slide fade in from.
12171  */
12172 function FadingOverColorSlideChange( aLeavingSlide, aEnteringSlide, sFadeColor )
12174     FadingSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
12175     this.sFadeColor = sFadeColor;
12176     if( !this.sFadeColor )
12177     {
12178         log( 'FadingOverColorSlideChange: sFadeColor not valid.' );
12179         this.sFadeColor = '#000000';
12180     }
12181     this.aColorPlaneElement = this.createColorPlaneElement();
12183 extend( FadingOverColorSlideChange, SlideChangeBase );
12185 /** start
12186  *  This method notifies to the slides involved in the transition the attributes
12187  *  appended to the slide elements for performing the animation.
12188  *  Moreover it inserts the color plane element below the leaving slide.
12189  *  Finally it sets the entering slide in the initial state and makes
12190  *  the slide visible.
12191  */
12192 FadingOverColorSlideChange.prototype.start = function()
12194     FadingOverColorSlideChange.superclass.start.call( this );
12195     this.aEnteringSlide.notifyUsedAttribute( 'opacity' );
12196     this.aLeavingSlide.notifyUsedAttribute( 'opacity' );
12197     this.aLeavingSlide.insertBefore( this.aColorPlaneElement );
12198     this.aEnteringSlide.setOpacity( 0.0 );
12199     this.aEnteringSlide.show();
12202 /** end
12203  *  This method removes the color plane element.
12204  */
12205 FadingOverColorSlideChange.prototype.end = function()
12207     FadingOverColorSlideChange.superclass.end.call( this );
12208     this.aLeavingSlide.removeElement( this.aColorPlaneElement );
12211 /** performIn
12212  *  This method set the opacity of the entering slide according to the passed
12213  *  time value.
12215  *  @param nT
12216  *      The time parameter.
12217  */
12218 FadingOverColorSlideChange.prototype.performIn = function( nT )
12220     this.aEnteringSlide.setOpacity( (nT > 0.55) ? 2.0*(nT-0.55) : 0.0 );
12223 /** performOut
12224  *  This method set the opacity of the leaving slide according to the passed
12225  *  time value.
12227  *  @param nT
12228  *      The time parameter.
12229  */
12230 FadingOverColorSlideChange.prototype.performOut = function( nT )
12232     this.aLeavingSlide.setOpacity( (nT > 0.45) ? 0.0 : 2.0*(0.45-nT) );
12235 FadingOverColorSlideChange.prototype.createColorPlaneElement = function()
12237     var aColorPlaneElement = document.createElementNS( NSS['svg'], 'rect' );
12238     aColorPlaneElement.setAttribute( 'width', String( this.aLeavingSlide.getWidth() ) );
12239     aColorPlaneElement.setAttribute( 'height', String( this.aLeavingSlide.getHeight() ) );
12240     aColorPlaneElement.setAttribute( 'fill', this.sFadeColor );
12241     return aColorPlaneElement;
12247 /** Class MovingSlideChange
12248  *  This class performs a slide transition that involves translating the leaving
12249  *  slide and/or the entering one in a given direction.
12251  *  @param aLeavingSlide
12252  *      An object of type AnimatedSlide handling the leaving slide.
12253  *  @param aEnteringSlide
12254  *      An object of type AnimatedSlide handling the entering slide.
12255  *  @param aLeavingDirection
12256  *      A 2D vector object {x, y}.
12257  *  @param aEnteringDirection
12258  *      A 2D vector object {x, y}.
12259  */
12260 function MovingSlideChange( aLeavingSlide, aEnteringSlide,
12261                             aLeavingDirection, aEnteringDirection )
12263     MovingSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
12264     this.aLeavingDirection = aLeavingDirection;
12265     this.aEnteringDirection = aEnteringDirection;
12267 extend( MovingSlideChange, SlideChangeBase );
12269 /** start
12270  *  This method notifies to the slides involved in the transition the attributes
12271  *  appended to the slide elements for performing the animation.
12272  *  Moreover it sets the entering slide in the initial state and makes the slide
12273  *  visible.
12274  */
12275 MovingSlideChange.prototype.start = function()
12277     MovingSlideChange.superclass.start.call( this );
12278     this.aEnteringSlide.notifyUsedAttribute( 'transform' );
12279     this.aLeavingSlide.notifyUsedAttribute( 'transform' );
12280     // Before setting the 'visibility' attribute of the entering slide to 'visible'
12281     // we translate it to the initial position so that it is not really visible
12282     // because it is clipped out.
12283     this.performIn( 0 );
12284     this.aEnteringSlide.show();
12287 /** performIn
12288  *  This method set the position of the entering slide according to the passed
12289  *  time value.
12291  *  @param nT
12292  *      The time parameter.
12293  */
12294 MovingSlideChange.prototype.performIn = function( nT )
12296     var nS = nT - 1;
12297     var dx = nS * this.aEnteringDirection.x * this.aEnteringSlide.getWidth();
12298     var dy = nS * this.aEnteringDirection.y * this.aEnteringSlide.getHeight();
12299     this.aEnteringSlide.translate( dx, dy );
12302 /** performOut
12303  *  This method set the position of the leaving slide according to the passed
12304  *  time value.
12306  *  @param nT
12307  *      The time parameter.
12308  */
12309 MovingSlideChange.prototype.performOut = function( nT )
12311     var dx = nT * this.aLeavingDirection.x * this.aLeavingSlide.getWidth();
12312     var dy = nT * this.aLeavingDirection.y * this.aLeavingSlide.getHeight();
12313     this.aLeavingSlide.translate( dx, dy );
12319 /** Class ClippedSlideChange
12320  *  This class performs a slide transition where the entering slide wipes
12321  *  the leaving one out. The wipe effect is achieved by clipping the entering
12322  *  slide with a parametric path.
12324  *  @param aLeavingSlide
12325  *      An object of type AnimatedSlide handling the leaving slide.
12326  *  @param aEnteringSlide
12327  *      An object of type AnimatedSlide handling the entering slide.
12328  *  @param aParametricPolyPolygon
12329  *      An object handling a <path> element that depends on a parameter.
12330  *  @param aTransitionInfo
12331  *      The set of parameters defining the slide transition to be performed.
12332  *  @param bIsDirectionForward
12333  *      The direction the slide transition has to be performed.
12334  */
12335 function ClippedSlideChange( aLeavingSlide, aEnteringSlide, aParametricPolyPolygon,
12336                              aTransitionInfo, bIsDirectionForward )
12338     ClippedSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
12340     var bIsModeIn = true;
12341     this.aClippingFunctor= new ClippingFunctor( aParametricPolyPolygon, aTransitionInfo,
12342                                                 bIsDirectionForward, bIsModeIn );
12344 extend( ClippedSlideChange, SlideChangeBase );
12346 /** start
12347  *  This method notifies to the slides involved in the transition the attributes
12348  *  appended to the slide elements for performing the animation.
12349  *  Moreover it sets the entering slide in the initial state and makes the slide
12350  *  visible.
12351  */
12352 ClippedSlideChange.prototype.start = function()
12354     ClippedSlideChange.superclass.start.call( this );
12355     this.aEnteringSlide.notifyUsedAttribute( 'clip-path' );
12356     this.performIn( 0 );
12357     this.aEnteringSlide.show();
12360 /** performIn
12361  *  This method set the position of the entering slide according to the passed
12362  *  time value.
12364  *  @param nT
12365  *      The time parameter.
12366  */
12367 ClippedSlideChange.prototype.performIn = function( nT )
12369     var nWidth = this.aEnteringSlide.getWidth();
12370     var nHeight = this.aEnteringSlide.getHeight();
12371     var aPolyPolygonElement = this.aClippingFunctor.perform( nT, nWidth, nHeight );
12372     this.aEnteringSlide.setClipPath( aPolyPolygonElement );
12375 ClippedSlideChange.prototype.performOut = function( )
12377     // empty body
12383 /** Class ClippingFunctor
12384  *  This class is responsible for computing the <path> used for clipping
12385  *  the entering slide in a polypolygon clipping slide transition or the
12386  *  animated shape in a transition filter effect.
12388  *  @param aParametricPolyPolygon
12389  *      An object that handle a <path> element defined in the [0,1]x[0,1]
12390  *      unit square and that depends on a parameter.
12391  *  @param aTransitionInfo
12392  *      The set of parameters defining the slide transition to be performed.
12393  *  @param bIsDirectionForward
12394  *      The direction the slide transition has to be performed.
12395  *  @param bIsModeIn
12396  *      The direction the filter effect has to be performed
12397  */
12398 function ClippingFunctor( aParametricPolyPolygon, aTransitionInfo,
12399                           bIsDirectionForward, bIsModeIn)
12401     this.aParametricPolyPolygon = aParametricPolyPolygon;
12402     this.aStaticTransformation = null;
12403     this.bForwardParameterSweep = true;
12404     this.bSubtractPolygon = false;
12405     this.bScaleIsotropically = aTransitionInfo.scaleIsotropically;
12406     this.bFlip = false;
12408     assert( this.aParametricPolyPolygon,
12409             'ClippingFunctor: parametric polygon is not valid' );
12411     if( aTransitionInfo.rotationAngle != 0.0 ||
12412         aTransitionInfo.scaleX != 1.0 ||  aTransitionInfo.scaleY != 1.0 )
12413     {
12414         // note: operations must be defined in reverse order.
12415         this.aStaticTransformation = SVGIdentityMatrix.translate( 0.5, 0.5 );
12416         if( aTransitionInfo.scaleX != 1.0 ||  aTransitionInfo.scaleY != 1.0 )
12417             this.aStaticTransformation
12418                 = this.aStaticTransformation.scaleNonUniform( aTransitionInfo.scaleX,
12419                                                               aTransitionInfo.scaleY );
12420         if( aTransitionInfo.rotationAngle != 0.0 )
12421             this.aStaticTransformation
12422                 = this.aStaticTransformation.rotate( aTransitionInfo.rotationAngle );
12423         this.aStaticTransformation = this.aStaticTransformation.translate( -0.5, -0.5 );
12424     }
12425     else
12426     {
12427         this.aStaticTransformation = document.documentElement.createSVGMatrix();
12428     }
12430     if( !bIsDirectionForward )
12431     {
12432         var aMatrix = null;
12433         switch( aTransitionInfo.reverseMethod )
12434         {
12435             default:
12436                 log( 'ClippingFunctor: unexpected reverse method.' );
12437                 break;
12438             case REVERSEMETHOD_IGNORE:
12439                 break;
12440             case REVERSEMETHOD_INVERT_SWEEP:
12441                 this.bForwardParameterSweep = !this.bForwardParameterSweep;
12442                 break;
12443             case REVERSEMETHOD_SUBTRACT_POLYGON:
12444                 this.bSubtractPolygon = !this.bSubtractPolygon;
12445                 break;
12446             case REVERSEMETHOD_SUBTRACT_AND_INVERT:
12447                 this.bForwardParameterSweep = !this.bForwardParameterSweep;
12448                 this.bSubtractPolygon = !this.bSubtractPolygon;
12449                 break;
12450             case REVERSEMETHOD_ROTATE_180:
12451                 aMatrix = document.documentElement.createSVGMatrix();
12452                 aMatrix.setToRotationAroundPoint( 0.5, 0.5, 180 );
12453                 this.aStaticTransformation = aMatrix.multiply( this.aStaticTransformation );
12454                 break;
12455             case REVERSEMETHOD_FLIP_X:
12456                 aMatrix = document.documentElement.createSVGMatrix();
12457                 // |-1  0  1 |
12458                 // | 0  1  0 |
12459                 aMatrix.a = -1; aMatrix.e = 1.0;
12460                 this.aStaticTransformation = aMatrix.multiply( this.aStaticTransformation );
12461                 this.bFlip = true;
12462                 break;
12463             case REVERSEMETHOD_FLIP_Y:
12464                 aMatrix = document.documentElement.createSVGMatrix();
12465                 // | 1  0  0 |
12466                 // | 0 -1  1 |
12467                 aMatrix.d = -1; aMatrix.f = 1.0;
12468                 this.aStaticTransformation = aMatrix.multiply( this.aStaticTransformation );
12469                 this.bFlip = true;
12470                 break;
12471         }
12472     }
12474     if( !bIsModeIn )
12475     {
12476         if( aTransitionInfo.outInvertsSweep )
12477         {
12478             this.bForwardParameterSweep = !this.bForwardParameterSweep;
12479         }
12480         else
12481         {
12482             this.bSubtractPolygon = !this.bSubtractPolygon;
12483         }
12484     }
12487 // This path is used when the direction is the reverse one and
12488 // the reverse method type is the subtraction type.
12489 ClippingFunctor.aBoundingPath = document.createElementNS( NSS['svg'], 'path' );
12490 ClippingFunctor.aBoundingPath.setAttribute( 'd', 'M -1 -1 L 2 -1 L 2 2 L -1 2 L -1 -1' );
12492 /** perform
12494  *  @param nT
12495  *      A parameter in [0,1] representing normalized time.
12496  *  @param nWidth
12497  *      The width of the bounding box of the slide/shape to be clipped.
12498  *  @param nHeight
12499  *      The height of the bounding box of the slide/shape to be clipped.
12500  *  @return SVGPathElement
12501  *      A svg <path> element representing the path to be used for the clipping
12502  *      operation.
12503  */
12504 ClippingFunctor.prototype.perform = function( nT, nWidth, nHeight )
12506     var aClipPoly = this.aParametricPolyPolygon.perform( this.bForwardParameterSweep ? nT : (1 - nT) );
12508     // Note: even if the reverse method involves flipping we don't need to
12509     // change the clip-poly orientation because we utilize the 'nonzero'
12510     // clip-rule.
12511     // See: http://www.w3.org/TR/SVG11/painting.html#FillRuleProperty
12513     if( this.bSubtractPolygon )
12514     {
12515         aClipPoly.changeOrientation();
12516         aClipPoly.prependPath( ClippingFunctor.aBoundingPath );
12517     }
12519     var aMatrix;
12520     if( this.bScaleIsotropically )
12521     {
12522         var nScaleFactor = Math.max( nWidth, nHeight );
12523         // note: operations must be defined in reverse order.
12524         aMatrix = SVGIdentityMatrix.translate( -( nScaleFactor - nWidth ) / 2.0,
12525                                                   -( nScaleFactor - nHeight ) / 2.0 );
12526         aMatrix = aMatrix.scale( nScaleFactor );
12527         aMatrix = aMatrix.multiply( this.aStaticTransformation );
12528     }
12529     else
12530     {
12531         aMatrix = SVGIdentityMatrix.scaleNonUniform( nWidth, nHeight );
12532         aMatrix = aMatrix.multiply( this.aStaticTransformation );
12533     }
12535     aClipPoly.matrixTransform( aMatrix );
12537     return aClipPoly;
12543 /** createClipPolyPolygon
12545  *  @param nType
12546  *      An enumerator representing the transition type.
12547  *  @param nSubtype
12548  *      An enumerator representing the transition subtype.
12549  *  @return
12550  *      An object that handles a parametric <path> element.
12551  */
12552 function createClipPolyPolygon( nType, nSubtype )
12554     switch( nType )
12555     {
12556         default:
12557             log( 'createClipPolyPolygon: unknown transition type: ' + nType );
12558             return null;
12559         case BARWIPE_TRANSITION:
12560             return new BarWipePath( 1 );
12561         case FOURBOXWIPE_TRANSITION:
12562             return new FourBoxWipePath( nSubtype === CORNERSOUT_TRANS_SUBTYPE );
12563         case BOXWIPE_TRANSITION:
12564             return new BoxWipePath( nSubtype == LEFTCENTER_TRANS_SUBTYPE ||
12565                                     nSubtype == TOPCENTER_TRANS_SUBTYPE ||
12566                                     nSubtype == RIGHTCENTER_TRANS_SUBTYPE ||
12567                                     nSubtype == BOTTOMCENTER_TRANS_SUBTYPE );
12568         case ELLIPSEWIPE_TRANSITION:
12569             return new EllipseWipePath( nSubtype );
12570         case FANWIPE_TRANSITION:
12571             return new FanWipePath(nSubtype == CENTERTOP_TRANS_SUBTYPE ||
12572                                    nSubtype == CENTERRIGHT_TRANS_SUBTYPE, true, false);
12573         case PINWHEELWIPE_TRANSITION:
12574             var nBlades;
12575             switch( nSubtype )
12576             {
12577                 case ONEBLADE_TRANS_SUBTYPE:
12578                     nBlades = 1;
12579                     break;
12580                 case DEFAULT_TRANS_SUBTYPE:
12581                 case TWOBLADEVERTICAL_TRANS_SUBTYPE:
12582                     nBlades = 2;
12583                     break;
12584                 case TWOBLADEHORIZONTAL_TRANS_SUBTYPE:
12585                     nBlades = 2;
12586                     break;
12587                 case THREEBLADE_TRANS_SUBTYPE:
12588                     nBlades = 3;
12589                     break;
12590                 case FOURBLADE_TRANS_SUBTYPE:
12591                     nBlades = 4;
12592                     break;
12593                 case EIGHTBLADE_TRANS_SUBTYPE:
12594                     nBlades = 8;
12595                     break;
12596                 default:
12597                     log( 'createClipPolyPolygon: unknown subtype: ' + nSubtype );
12598                     return null;
12599             }
12600             return new PinWheelWipePath( nBlades );
12601         case CLOCKWIPE_TRANSITION:
12602             return new ClockWipePath();
12603         case RANDOMBARWIPE_TRANSITION:
12604             return new RandomWipePath( 128, true /* bars */ );
12605         case CHECKERBOARDWIPE_TRANSITION:
12606             return new CheckerBoardWipePath( 10 );
12607         case ZIGZAGWIPE_TRANSITION:
12608             return new ZigZagWipePath( 5 );
12609         case BARNZIGZAGWIPE_TRANSITION:
12610             return new BarnZigZagWipePath( 5 );
12611         case IRISWIPE_TRANSITION:
12612             switch(nSubtype)
12613             {
12614                 case RECTANGLE_TRANS_SUBTYPE:
12615                     return new IrisWipePath(0);
12616                 case DIAMOND_TRANS_SUBTYPE:
12617                     return new IrisWipePath(1);
12618                 default:
12619                     log( 'createClipPolyPolygon: unknown subtype: ' + nSubtype );
12620                     return null;
12621             }
12622         case BARNDOORWIPE_TRANSITION:
12623             return new BarnDoorWipePath(false);
12624         case SINGLESWEEPWIPE_TRANSITION:
12625             return new SweepWipePath(
12626                 // center
12627                 nSubtype == CLOCKWISETOP_TRANS_SUBTYPE ||
12628                 nSubtype == CLOCKWISERIGHT_TRANS_SUBTYPE ||
12629                 nSubtype == CLOCKWISEBOTTOM_TRANS_SUBTYPE ||
12630                 nSubtype == CLOCKWISELEFT_TRANS_SUBTYPE,
12631                 // single
12632                 true,
12633                 // oppositeVertical
12634                 false,
12635                 // flipOnYAxis
12636                 nSubtype == COUNTERCLOCKWISEBOTTOMLEFT_TRANS_SUBTYPE ||
12637                 nSubtype == COUNTERCLOCKWISETOPRIGHT_TRANS_SUBTYPE );
12638         case WATERFALLWIPE_TRANSITION:
12639             return new WaterfallWipePath(128, // flipOnYAxis
12640                                               nSubtype == VERTICALRIGHT_TRANS_SUBTYPE ||
12641                                               nSubtype == HORIZONTALLEFT_TRANS_SUBTYPE);
12642         case MISCDIAGONALWIPE_TRANSITION:
12643             switch(nSubtype) {
12644                 case DOUBLEBARNDOOR_TRANS_SUBTYPE:
12645                     return new BarnDoorWipePath(true /* Doubled */);
12646                 case DOUBLEDIAMOND_TRANS_SUBTYPE:
12647                     return new DoubleDiamondWipePath();
12648                 default:
12649                     log( 'createClipPolyPolygon: unhandled subtype: ' + nSubtype );
12650                     return null;
12651             }
12652         case DISSOLVE_TRANSITION:
12653             return new RandomWipePath( 16 * 16, false /* dissolve */ );
12654         case VEEWIPE_TRANSITION:
12655             return new VeeWipePath();
12656         case SNAKEWIPE_TRANSITION:
12657             return new SnakeWipePath( 8 * 8, // diagonal
12658                                              nSubtype == TOPLEFTDIAGONAL_TRANS_SUBTYPE     ||
12659                                              nSubtype == TOPRIGHTDIAGONAL_TRANS_SUBTYPE    ||
12660                                              nSubtype == BOTTOMRIGHTDIAGONAL_TRANS_SUBTYPE ||
12661                                              nSubtype == BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE   ,
12662                                              // flipOnYAxis
12663                                              nSubtype == TOPLEFTVERTICAL_TRANS_SUBTYPE     ||
12664                                              nSubtype == TOPRIGHTDIAGONAL_TRANS_SUBTYPE    ||
12665                                              nSubtype == BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE
12666                                              );
12667         case PARALLELSNAKESWIPE_TRANSITION:
12668             return new ParallelSnakesWipePath(
12669                 8 * 8, // elements
12670                 // diagonal
12671                 nSubtype == DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
12672                 nSubtype == DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
12673                 // flipOnYAxis
12674                 nSubtype == VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
12675                 nSubtype == HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE  ||
12676                 nSubtype == DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
12677                 // opposite
12678                 nSubtype == VERTICALTOPLEFTOPPOSITE_TRANS_SUBTYPE    ||
12679                 nSubtype == VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
12680                 nSubtype == HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE  ||
12681                 nSubtype == HORIZONTALTOPRIGHTOPPOSITE_TRANS_SUBTYPE ||
12682                 nSubtype == DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
12683                 nSubtype == DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE
12684                 );
12686         case SPIRALWIPE_TRANSITION:
12687             return new SpiralWipePath(
12688                 8 * 8, // elements
12689                 nSubtype == TOPLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE     ||
12690                 nSubtype == TOPRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE    ||
12691                 nSubtype == BOTTOMRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE ||
12692                 nSubtype == BOTTOMLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE );
12694         case BOXSNAKESWIPE_TRANSITION:
12695             return new BoxSnakesWipePath(
12696                 // elements
12697                 8 * 8,
12698                 // fourBox
12699                 nSubtype == FOURBOXVERTICAL_TRANS_SUBTYPE ||
12700                 nSubtype == FOURBOXHORIZONTAL_TRANS_SUBTYPE );
12701     }
12707 function createUnitSquarePath()
12709     var aPath = document.createElementNS( NSS['svg'], 'path' );
12710     var sD = 'M 0 0 L 1 0 L 1 1 L 0 1 L 0 0';
12711     aPath.setAttribute( 'd', sD );
12712     return aPath;
12715 function createEmptyPath()
12717     var aPath = document.createElementNS( NSS['svg'], 'path' );
12718     var sD = 'M 0 0 L 0 0';
12719     aPath.setAttribute( 'd', sD );
12720     return aPath;
12723 function pruneScaleValue( nVal )
12725     if( nVal < 0.0 )
12726         return (nVal < -0.00001 ? nVal : -0.00001);
12727     else
12728         return (nVal > 0.00001 ? nVal : 0.00001);
12732 /** Class BarWipePath
12733  *  This class handles a <path> element that defines a unit square and
12734  *  transforms it accordingly to a parameter in the [0,1] range for performing
12735  *  a left to right barWipe transition.
12737  *  @param nBars
12738  *     The number of bars to be generated.
12739  */
12740 function BarWipePath( nBars /* nBars > 1: blinds effect */ )
12742     this.nBars = nBars;
12743     if( this.nBars === undefined || this.nBars < 1 )
12744         this.nBars = 1;
12745     this.aBasePath = createUnitSquarePath();
12748 /** perform
12750  *  @param nT
12751  *      A parameter in [0,1] representing the width of the generated bars.
12752  *  @return SVGPathElement
12753  *      A svg <path> element representing a multi-bars.
12754  */
12755 BarWipePath.prototype.perform = function( nT )
12758     var aMatrix = SVGIdentityMatrix.scaleNonUniform( pruneScaleValue( nT / this.nBars ), 1.0 );
12760     var aPolyPath = this.aBasePath.cloneNode( true );
12761     aPolyPath.matrixTransform( aMatrix );
12763     if( this.nBars > 1 )
12764     {
12765         var i;
12766         var aTransform;
12767         var aPath;
12768         for( i = this.nBars - 1; i > 0; --i )
12769         {
12770             aTransform = SVGIdentityMatrix.translate( i / this.nBars, 0.0 );
12771             aTransform = aTransform.multiply( aMatrix );
12772             aPath = this.aBasePath.cloneNode( true );
12773             aPath.matrixTransform( aTransform );
12774             aPolyPath.appendPath( aPath );
12775         }
12776     }
12777     return aPolyPath;
12781 /** Class BoxWipePath
12782  *  This class handles a path made up by one square and is utilized for
12783  *  performing BoxWipe transitions.
12785  *  @param bIsTopCentered
12786  *      if true the transition subtype is top centered else not.
12787  */
12788 function BoxWipePath(bIsTopCentered) {
12789     this.bIsTopCentered = bIsTopCentered;
12790     this.aBasePath = createUnitSquarePath();
12793 BoxWipePath.prototype.perform = function( nT ) {
12794     var d = pruneScaleValue(nT);
12795     var aTransform = SVGIdentityMatrix;
12796     if(this.bIsTopCentered) {
12797         aTransform = aTransform.translate(-0.5, 0.0).scale(d, d).translate(0.5, 0.0);
12798     }
12799     else {
12800         aTransform = aTransform.scale(d, d);
12801     }
12802     var aPath = this.aBasePath.cloneNode(true);
12803     aPath.matrixTransform(aTransform);
12804     return aPath;
12807 /* Class SweepWipePath
12810  */
12811 function SweepWipePath(bCenter, bSingle, bOppositeVertical, bFlipOnYAxis) {
12812   this.bCenter = bCenter;
12813   this.bSingle = bSingle;
12814   this.bOppositeVertical = bOppositeVertical;
12815   this.bFlipOnYAxis = bFlipOnYAxis;
12816   this.aBasePath = createUnitSquarePath();
12819 SweepWipePath.prototype.perform = function( nT ) {
12820     nT /= 2.0;
12821     if(!this.bCenter)
12822         nT /= 2.0;
12823     if(!this.bSingle && !this.bOppositeVertical)
12824         nT /= 2.0;
12826     var poly = PinWheelWipePath.calcCenteredClock( nT + 0.25, 1.0 );
12827     var aTransform;
12829     if(this.bCenter) {
12830         aTransform = SVGIdentityMatrix.translate(0.5, 0.0);
12831         poly.matrixTransform(aTransform);
12832     }
12833     var res = poly;
12835     if(!this.bSingle) {
12836         if(this.bOppositeVertical) {
12837             aTransform = SVGIdentityMatrix.scale(1.0, -1.0);
12838             aTransform.translate(0.0, 1.0);
12839             poly.matrixTransform(aTransform);
12840             poly.changeOrientation();
12841         }
12842         else {
12843             aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
12844             aTransform.rotate(Math.PI);
12845             aTransform.translate(0.5, 0.5);
12846             poly.matrixTransform(aTransform);
12847         }
12848         res.appendPath(poly);
12849     }
12850     return this.bFlipOnYAxis ? flipOnYAxis(res) : res;
12853 /** Class FourBoxWipePath
12854  *  This class handles a path made up by four squares and is utilized for
12855  *  performing fourBoxWipe transitions.
12857  *  @param bCornersOut
12858  *      If true the transition subtype is cornersOut else is cornersIn.
12859  */
12860 function FourBoxWipePath( bCornersOut )
12862     this.bCornersOut = bCornersOut;
12863     this.aBasePath = createUnitSquarePath();
12866 FourBoxWipePath.prototype.perform = function( nT )
12868     var aMatrix;
12869     var d = pruneScaleValue( nT / 2.0 );
12871     if( this.bCornersOut )
12872     {
12873         aMatrix = SVGIdentityMatrix.translate( -0.25, -0.25 ).scale( d ).translate( -0.5, -0.5 );
12874     }
12875     else
12876     {
12877         aMatrix = SVGIdentityMatrix.translate( -0.5, -0.5 ).scale( d );
12878     }
12881     var aTransform = aMatrix;
12882     // top left
12883     var aSquare = this.aBasePath.cloneNode( true );
12884     aSquare.matrixTransform( aTransform );
12885     var aPolyPath = aSquare;
12886     // bottom left, flip on x-axis:
12887     aMatrix = SVGIdentityMatrix.flipY();
12888     aTransform = aMatrix.multiply( aTransform );
12889     aSquare = this.aBasePath.cloneNode( true );
12890     aSquare.matrixTransform( aTransform );
12891     aSquare.changeOrientation();
12892     aPolyPath.appendPath( aSquare );
12893     // bottom right, flip on y-axis:
12894     aMatrix = SVGIdentityMatrix.flipX();
12895     aTransform = aMatrix.multiply( aTransform );
12896     aSquare = this.aBasePath.cloneNode( true );
12897     aSquare.matrixTransform( aTransform );
12898     aPolyPath.appendPath( aSquare );
12899     // top right, flip on x-axis:
12900     aMatrix = SVGIdentityMatrix.flipY();
12901     aTransform = aMatrix.multiply( aTransform );
12902     aSquare = this.aBasePath.cloneNode( true );
12903     aSquare.matrixTransform( aTransform );
12904     aSquare.changeOrientation();
12905     aPolyPath.appendPath( aSquare );
12907     // Remind: operations are applied in inverse order
12908     aMatrix = SVGIdentityMatrix.translate( 0.5, 0.5 );
12909     // We enlarge a bit the clip path so we avoid that in reverse direction
12910     // some thin line of the border stroke is visible.
12911     aMatrix = aMatrix.scale( 1.1 );
12912     aPolyPath.matrixTransform( aMatrix );
12914     return aPolyPath;
12920 /** Class EllipseWipePath
12921  *  This class handles a parametric ellipse represented by a path made up of
12922  *  cubic Bezier curve segments that helps in performing the ellipseWipe
12923  *  transition.
12925  *  @param eSubtype
12926  *      The transition subtype.
12927  */
12928 function EllipseWipePath( eSubtype )
12930     this.eSubtype = eSubtype;
12932     // precomputed circle( 0.5, 0.5, SQRT2 / 2 )
12933     var sPathData = 'M 0.5 -0.207107 ' +
12934                     'C 0.687536 -0.207107 0.867392 -0.132608 1 0 ' +
12935                     'C 1.13261 0.132608 1.20711 0.312464 1.20711 0.5 ' +
12936                     'C 1.20711 0.687536 1.13261 0.867392 1 1 ' +
12937                     'C 0.867392 1.13261 0.687536 1.20711 0.5 1.20711 ' +
12938                     'C 0.312464 1.20711 0.132608 1.13261 0 1 ' +
12939                     'C -0.132608 0.867392 -0.207107 0.687536 -0.207107 0.5 ' +
12940                     'C -0.207107 0.312464 -0.132608 0.132608 0 0 ' +
12941                     'C 0.132608 -0.132608 0.312464 -0.207107 0.5 -0.207107';
12943     this.aBasePath = document.createElementNS( NSS['svg'], 'path' );
12944     this.aBasePath.setAttribute( 'd', sPathData );
12947 EllipseWipePath.prototype.perform = function( nT )
12950     var aTransform = SVGIdentityMatrix.translate( 0.5, 0.5 ).scale( nT ).translate( -0.5, -0.5 );
12951     var aEllipse = this.aBasePath.cloneNode( true );
12952     aEllipse.matrixTransform( aTransform );
12954     return aEllipse;
12958  * Class FanWipePath
12960  */
12961 function FanWipePath(bIsCenter, bIsSingle, bIsFanIn) {
12962     this.bCenter = bIsCenter;
12963     this.bSingle = bIsSingle;
12964     this.bFanIn  = bIsFanIn;
12965     this.aBasePath = createUnitSquarePath();
12968 FanWipePath.prototype.perform = function( nT ) {
12969   var res = this.aBasePath.cloneNode(true);
12970   var poly = PinWheelWipePath.calcCenteredClock(
12971           nT / ((this.bCenter && this.bSingle) ? 2.0 : 4.0), 1.0);
12972   res.appendPath(poly);
12973   // flip on y-axis
12974   var aTransform = SVGIdentityMatrix.flipY();
12975   aTransform = aTransform.scaleNonUniform(-1.0, 1.0);
12976   poly.matrixTransform(aTransform);
12977   res.appendPath(poly);
12979   if(this.bCenter) {
12980       aTransform = SVGIdentityMatrix.scaleNonUniform(0.5, 0.5).translate(0.5, 0.5);
12981       res.matrixTransform(aTransform);
12983       if(!this.bSingle)
12984           res.appendPath(flipOnXAxis(res));
12985   }
12986   else {
12987       aTransform = SVGIdentityMatrix.scaleNonUniform(0.5, 1.0).translate(0.5, 1.0);
12988       res.matrixTransform(aTransform);
12989   }
12990   return res;
12994  * Class ClockWipePath
12996  */
12997 function ClockWipePath() { }
12999 ClockWipePath.prototype.perform = function( nT ) {
13000     const aTransform = SVGIdentityMatrix.scaleNonUniform(0.5, 0.5).translate(0.5, 0.5);
13001     var aPolyPath = PinWheelWipePath.calcCenteredClock(nT, 1.0);
13002     aPolyPath.matrixTransform( aTransform );
13004     return aPolyPath;
13007 /** Class PinWheelWipePath
13008  *  This class handles a parametric poly-path that is used for performing
13009  *  a spinWheelWipe transition.
13011  *  @param nBlades
13012  *      Number of blades generated by the transition.
13013  */
13014 function PinWheelWipePath( nBlades )
13016     this.nBlades = nBlades;
13017     if( !this.nBlades || this.nBlades < 1 )
13018         this.nBlades = 1;
13021 PinWheelWipePath.calcCenteredClock = function( nT, nE )
13023     var nMAX_EDGE = 2;
13025     var aTransform = SVGIdentityMatrix.rotate( nT * 360 );
13027     var aPoint = document.documentElement.createSVGPoint();
13028     aPoint.y = -nMAX_EDGE;
13029     aPoint = aPoint.matrixTransform( aTransform );
13031     var sPathData = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
13032     if( nT >= 0.875 )
13033         // L -e -e
13034         sPathData += 'L ' + '-' + nE + ' -' + nE + ' ';
13035     if( nT >= 0.625 )
13036         // L -e e
13037         sPathData += 'L ' + '-' + nE + ' ' + nE + ' ';
13038     if( nT >= 0.375 )
13039         // L e e
13040         sPathData += 'L ' + nE + ' ' + nE + ' ';
13041      if( nT >= 0.125 )
13042         // L e -e
13043         sPathData += 'L ' + nE + ' -' + nE + ' ';
13045     // L 0 -e
13046     sPathData += 'L 0 -' + nE + ' ';
13047     sPathData += 'L 0 0 ';
13048     // Z
13049     sPathData += 'L '  + aPoint.x + ' ' + aPoint.y;
13051     var aPath = document.createElementNS( NSS['svg'], 'path' );
13052     aPath.setAttribute( 'd', sPathData );
13053     return aPath;
13056 PinWheelWipePath.prototype.perform = function( nT )
13058     var aBasePath = PinWheelWipePath.calcCenteredClock( nT / this.nBlades,
13059                                                         2.0 /* max edge when rotating */  );
13061     var aPolyPath = aBasePath.cloneNode( true );
13062     var aPath;
13063     var aRotation;
13064     var i;
13065     for( i = this.nBlades - 1; i > 0; --i )
13066     {
13067         aRotation = SVGIdentityMatrix.rotate( (i * 360) / this.nBlades );
13068         aPath = aBasePath.cloneNode( true );
13069         aPath.matrixTransform( aRotation );
13070         aPolyPath.appendPath( aPath );
13071     }
13073     var aTransform = SVGIdentityMatrix.translate( 0.5, 0.5 ).scale( 0.5 );
13074     aPolyPath.matrixTransform( aTransform );
13076     return aPolyPath;
13079 /** Class BarnDoorWipe
13080   *
13081   * @param doubled
13082   */
13083 function BarnDoorWipePath(doubled) {
13084     this.aBasePath = createUnitSquarePath();
13085     this.doubled   = doubled;
13088 BarnDoorWipePath.prototype.perform = function( nT ) {
13089     if(this.doubled)
13090         nT /= 2.0;
13091     var aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
13092     aTransform = aTransform.scaleNonUniform(pruneScaleValue(nT), 1.0).translate(0.5, 0.5);
13093     var aPath = this.aBasePath.cloneNode(true);
13094     aPath.matrixTransform(aTransform);
13095     var res = aPath;
13097     if(this.doubled) {
13098         aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
13099         aTransform = aTransform.rotate(Math.PI / 2).translate(0.5, 0.5);
13100         aPath.matrixTransform(aTransform);
13101         res.appendPath(aPath);
13102     }
13103     return res;
13106 /** Class WaterfallWipe
13107   *
13108   * @param nElements
13109   *     Number of cells to be used
13110   * @param bFlipOnYAxis
13111   *     Whether to flip on y-axis or not.
13112   */
13113 function WaterfallWipePath(nElements, bFlipOnYAxis) {
13114     this.bFlipOnYAxis = bFlipOnYAxis;
13116     var sqrtElements = Math.floor(Math.sqrt(nElements));
13117     var elementEdge = 1.0/sqrtElements;
13119     var aPath = 'M '+ 0.0 + ' ' + -1.0 + ' ';
13120     for(var pos = sqrtElements; pos--; ) {
13121         var xPos = sqrtElements - pos - 1;
13122         var yPos = pruneScaleValue( ((pos+1) * elementEdge) - 1.0);
13124         aPath += 'L ' + pruneScaleValue(xPos * elementEdge) + ' ' + yPos + ' ';
13125         aPath += 'L ' + pruneScaleValue((xPos+1)*elementEdge) + ' ' + yPos + ' ';
13126     }
13127     aPath += 'L ' + 1.0 + ' ' + -1.0 + ' ';
13128     aPath += 'L ' + 0.0 + ' ' + -1.0 + ' ';
13129     this.aBasePath = document.createElementNS( NSS['svg'], 'path');
13130     this.aBasePath.setAttribute('d', aPath);
13133 WaterfallWipePath.prototype.perform = function( nT ) {
13134     var poly = this.aBasePath.cloneNode(true);
13135     var aTransform = SVGIdentityMatrix.translate(0.0, pruneScaleValue(2.0 * nT));
13136     poly.matrixTransform(aTransform);
13137     var aHead = 'M ' + 0.0 + ' ' + -1.0 + ' ';
13138     var aHeadPath= document.createElementNS( NSS['svg'], 'path');
13139     aHeadPath.setAttribute('d', aHead);
13141     var aTail = 'M ' + 1.0 + ' ' + -1.0 + ' ';
13142     var aTailPath = document.createElementNS( NSS['svg'], 'path');
13143     aTailPath.setAttribute('d', aTail);
13145     poly.prependPath(aHeadPath);
13146     poly.appendPath(aTailPath);
13148     return this.bFlipOnYAxis ? flipOnYAxis(poly) : poly;
13151 /** Class DoubleDiamondWipePath
13153  */
13154 function DoubleDiamondWipePath() { }
13156 DoubleDiamondWipePath.prototype.perform = function( nT ) {
13157     var a = pruneScaleValue(0.25 + (nT * 0.75));
13158     var aPath = 'M ' + (0.5 + a) + ' ' + 0.5 + ' ';
13159     aPath += 'L ' + 0.5 + ' ' + (0.5 - a) + ' ';
13160     aPath += 'L ' + (0.5 - a) + ' ' + 0.5 + ' ';
13161     aPath += 'L ' + 0.5 + ' ' + (0.5 + a) + ' ';
13162     aPath += 'L ' + (0.5 + a) + ' ' + 0.5 + ' ';
13163     var poly = document.createElementNS( NSS['svg'], 'path');
13164     poly.setAttribute('d', aPath);
13165     var res = poly.cloneNode(true);
13167     var b = pruneScaleValue( (1.0 - nT) * 0.25);
13168     aPath = 'M ' + (0.5 + b) + ' ' + 0.5 + ' ';
13169     aPath += 'L ' + 0.5 + ' ' + (0.5 + b) + ' ';
13170     aPath += 'L ' + (0.5 - b) + ' ' + 0.5 + ' ';
13171     aPath += 'L ' + 0.5 + ' ' + (0.5 - b) + ' ';
13172     aPath += 'L ' + (0.5 + b) + ' ' + 0.5 + ' ';
13173     poly = document.createElementNS( NSS['svg'], 'path');
13174     poly.setAttribute('d', aPath);
13175     res.appendPath(poly);
13177     return res;
13180 /** Class Iriswipe
13181   *
13182   * @param unitRect
13183   *
13184   */
13185 function IrisWipePath(unitRect) {
13186     this.unitRect = unitRect;
13187     this.aBasePath = createUnitSquarePath();
13191 /** perform
13192   *
13193   *  @param nT
13194   *      A parameter in [0,1] representing the diamond or rectangle.
13195   *  @return SVGPathElement
13196   *      A svg <path> element representing a transition.
13197   */
13198 IrisWipePath.prototype.perform = function( nT ) {
13199     var d = pruneScaleValue(nT);
13200     var aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
13201     aTransform = aTransform.multiply(SVGIdentityMatrix.scaleNonUniform(d, d).translate(0.5, 0.5));
13202     var aPath = this.aBasePath.cloneNode(true);
13203     aPath.matrixTransform(aTransform);
13204     return aPath;
13208  * Class ZigZagWipePath
13210  * @param nZigs
13212  */
13213 function ZigZagWipePath(nZigs) {
13214     this.zigEdge = 1.0/nZigs;
13215     const d = this.zigEdge;
13216     const d2 = (d / 2.0);
13217     this.aBasePath = 'M ' + (-1.0 - d) + ' ' + -d + ' ';
13218     this.aBasePath += 'L ' + (-1.0 - d) + ' ' + (1.0 + d) + ' ';
13219     this.aBasePath += 'L ' + -d + ' ' + (1.0 + d) + ' ';
13221     for(var pos = (nZigs + 2); pos--; ) {
13222         this.aBasePath += 'L ' + 0.0 + ' ' + ((pos - 1) * d + d2) + ' ';
13223         this.aBasePath += 'L ' + -d + ' ' + (pos - 1) * d + ' ';
13224     }
13225     this.aBasePath += 'L ' + (-1.0 - d) + ' ' + -d + ' ';
13228 ZigZagWipePath.prototype.perform = function( nT ) {
13229     var res = document.createElementNS( NSS['svg'], 'path');
13230     res.setAttribute('d', this.aBasePath);
13231     res.matrixTransform(SVGIdentityMatrix.translate((1.0 + this.zigEdge) * nT, 0.0));
13232     return res;
13236  * Class BarnZigZagWipePath
13238  * @param nZigs
13240  */
13241 function BarnZigZagWipePath( nZigs ) { ZigZagWipePath.call(this, nZigs); }
13243 BarnZigZagWipePath.prototype = Object.create(ZigZagWipePath);
13245 BarnZigZagWipePath.prototype.perform = function( nT ) {
13246     var res = createEmptyPath();
13247     var poly = document.createElementNS( NSS['svg'], 'path');
13248     var aTransform = SVGIdentityMatrix.translate(
13249         ((1.0 + this.zigEdge) * (1.0 - nT)) / 2.0, 0.0);
13250     poly.setAttribute('d', this.aBasePath);
13251     poly.changeOrientation();
13252     poly.matrixTransform(aTransform);
13253     res.appendPath(poly);
13255     aTransform = SVGIdentityMatrix.scale(-1.0, 1.0);
13256     aTransform.translate(1.0, this.zigEdge / 2.0);
13257     poly = document.createElementNS( NSS['svg'], 'path');
13258     poly.setAttribute('d', this.aBasePath);
13259     poly.matrixTransform(aTransform);
13260     res.appendPath(poly);
13262     return res;
13265 /** Class CheckerBoardWipePath
13267  *  @param unitsPerEdge
13268  *     The number of cells (per line and column) in the checker board.
13269  */
13270 function CheckerBoardWipePath( unitsPerEdge )
13272     this.unitsPerEdge = unitsPerEdge;
13273     if( this.unitsPerEdge === undefined || this.unitsPerEdge < 1 )
13274         this.unitsPerEdge = 10;
13275     this.aBasePath = createUnitSquarePath();
13278 /** perform
13280  *  @param nT
13281  *      A parameter in [0,1] representing the width of the generated bars.
13282  *  @return SVGPathElement
13283  *      A svg <path> element representing a multi-bars.
13284  */
13285 CheckerBoardWipePath.prototype.perform = function( nT )
13287     var d = pruneScaleValue(1.0 / this.unitsPerEdge);
13288     var aMatrix = SVGIdentityMatrix.scaleNonUniform(pruneScaleValue( d*2.0*nT ),
13289                                                     pruneScaleValue( d ) );
13291     var aPolyPath = null;
13292     var i, j;
13293     var aTransform;
13294     var aPath;
13295     for ( i = this.unitsPerEdge; i--; )
13296     {
13297         aTransform = SVGIdentityMatrix;
13299         if ((i % 2) == 1) // odd line
13300             aTransform = aTransform.translate( -d, 0.0 );
13302         aTransform = aTransform.multiply( aMatrix );
13304         for ( j = (this.unitsPerEdge / 2) + 1; j--;)
13305         {
13306             aPath = this.aBasePath.cloneNode( true );
13307             aPath.matrixTransform( aTransform );
13308             if (aPolyPath == null) aPolyPath = aPath;
13309             else aPolyPath.appendPath( aPath );
13310             aTransform = SVGIdentityMatrix.translate( d*2.0, 0.0 ).multiply( aTransform );
13311         }
13313         aMatrix = SVGIdentityMatrix.translate( 0.0, d ).multiply( aMatrix ); // next line
13314     }
13316     return aPolyPath;
13321 /** Class RandomWipePath
13323  *  @param nElements
13324  *     The number of bars or cells to be used.
13325  *  @param bRandomBars
13326  *     true: generates a horizontal random bar wipe
13327  *     false: generates a dissolve wipe
13328  */
13329 function RandomWipePath( nElements, bRandomBars )
13331     this.nElements = nElements;
13332     this.aBasePath = createUnitSquarePath();
13333     this.aPositionArray = new Array( nElements );
13334     this.aClipPath = createEmptyPath();
13335     this.nAlreadyAppendedElements = 0;
13337     var fEdgeLength, nPos, aTransform;
13339     if( bRandomBars ) // random bar wipe
13340     {
13341         fEdgeLength = 1.0 / nElements;
13342         for( nPos = 0; nPos < nElements; ++nPos )
13343         {
13344             this.aPositionArray[nPos] = { x: 0.0, y: pruneScaleValue( nPos * fEdgeLength ) }
13345         }
13346         aTransform = SVGIdentityMatrix.scaleNonUniform( 1.0, pruneScaleValue( fEdgeLength ) );
13347     }
13348     else // dissolve wipe
13349     {
13350         var nSqrtElements = Math.round( Math.sqrt( nElements ) );
13351         fEdgeLength = 1.0 / nSqrtElements;
13352         for( nPos = 0; nPos < nElements; ++nPos )
13353         {
13354             this.aPositionArray[nPos] = {
13355                 x: pruneScaleValue( ( nPos % nSqrtElements ) * fEdgeLength ),
13356                 y: pruneScaleValue( ( nPos / nSqrtElements ) * fEdgeLength ) }
13357         }
13358         aTransform = SVGIdentityMatrix.scale( pruneScaleValue( fEdgeLength ) );
13359     }
13360     this.aBasePath.matrixTransform( aTransform );
13362     var nPos1, nPos2;
13363     var tmp;
13364     for( nPos1 = nElements - 1; nPos1 > 0; --nPos1 )
13365     {
13366         nPos2 = getRandomInt( nPos1 + 1 );
13367         tmp = this.aPositionArray[nPos1];
13368         this.aPositionArray[nPos1] = this.aPositionArray[nPos2];
13369         this.aPositionArray[nPos2] = tmp;
13370     }
13373 /** perform
13375  *  @param nT
13376  *      A parameter in [0,1] representing the width of the generated bars or squares.
13377  *  @return SVGPathElement
13378  *      A svg <path> element representing a multi bars or a multi squared cells.
13379  */
13380 RandomWipePath.prototype.perform = function( nT )
13382     var aPolyPath = createEmptyPath();
13383     var aPoint;
13384     var aPath;
13385     var aTransform;
13386     var nElements = Math.round( nT * this.nElements );
13387     if( nElements === 0 )
13388     {
13389         return aPolyPath;
13390     }
13391     // check if we need to reset the clip path
13392     if( this.nAlreadyAppendedElements >= nElements )
13393     {
13394         this.nAlreadyAppendedElements = 0;
13395         this.aClipPath = createEmptyPath();
13396     }
13397     var nPos;
13398     for( nPos = this.nAlreadyAppendedElements; nPos < nElements; ++nPos )
13399     {
13400         aPoint = this.aPositionArray[nPos];
13401         aPath = this.aBasePath.cloneNode( true );
13402         aTransform = SVGIdentityMatrix.translate( aPoint.x, aPoint.y );
13403         aPath.matrixTransform( aTransform );
13404         aPolyPath.appendPath( aPath );
13405     }
13407     this.nAlreadyAppendedElements = nElements;
13408     this.aClipPath.appendPath( aPolyPath );
13410     return this.aClipPath.cloneNode( true );
13413 /** Class SnakeWipeSlide
13415  *  @param nElements
13416  *  @param bDiagonal
13417  *  @param bFlipOnYaxis
13418  */
13419 function SnakeWipePath(nElements, bDiagonal, bflipOnYAxis)
13421     this.sqrtElements = Math.floor(Math.sqrt(nElements));
13422     this.elementEdge  = (1.0 / this.sqrtElements);
13423     this.diagonal     = bDiagonal;
13424     this.flipOnYAxis  = bflipOnYAxis;
13425     this.aBasePath    = createUnitSquarePath();
13428 SnakeWipePath.prototype.calcSnake = function(t)
13430     var aPolyPath = createEmptyPath();
13431     const area   = (t * this.sqrtElements * this.sqrtElements);
13432     const line_  = Math.floor(area) / this.sqrtElements;
13433     const line   = pruneScaleValue(line_ / this.sqrtElements);
13434     const col    = pruneScaleValue((area - (line_ * this.sqrtElements)) / this.sqrtElements);
13436     if(line != 0) {
13437         let aPath = 'M '+ 0.0 + ' ' + 0.0 + ' ';
13438         aPath += 'L ' + 0.0 + ' ' + line + ' ';
13439         aPath += 'L ' + 1.0 + ' ' + line + ' ';
13440         aPath += 'L ' + 1.0 + ' ' + 0.0 + ' ';
13441         aPath += 'L 0 0 ';
13442         let poly = document.createElementNS( NSS['svg'], 'path');
13443         poly.setAttribute('d', aPath);
13444         aPolyPath.appendPath(poly);
13445     }
13446     if(col != 0) {
13447         var offset = 0.0;
13448         if((line_ & 1) == 1) {
13449             // odd line: => right to left
13450             offset = (1.0 - col);
13451         }
13452         let aPath = 'M ' + offset + ' ' + line + ' ';
13453         aPath += 'L '+ offset + ' ' + (line + this.elementEdge) + ' ';
13454         aPath += 'L ' + (offset+col) + ' ' + (line + this.elementEdge) + ' ';
13455         aPath += 'L ' + (offset+col) + ' ' + line + ' ';
13456         aPath += 'L ' + offset + ' ' + line + ' ';
13457         let poly = document.createElementNS( NSS['svg'], 'path');
13458         poly.setAttribute('d', aPath);
13459         aPolyPath.appendPath(poly);
13460     }
13462     return aPolyPath;
13465 SnakeWipePath.prototype.calcHalfDiagonalSnake = function(nT, bIn) {
13466     var res = createEmptyPath();
13468     if(bIn) {
13469         const sqrtArea2 = Math.sqrt(nT * this.sqrtElements * this.sqrtElements);
13470         const edge = pruneScaleValue(sqrtArea2 / this.sqrtElements);
13472         var aPath, aPoint = document.documentElement.createSVGPoint();
13473         if(edge) {
13474             aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
13475             aPoint.y = edge;
13476             aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13477             aPoint.x = edge;
13478             aPoint.y = 0.0;
13479             aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13480             aPoint.x = 0.0;
13481             aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13482             const poly = document.createElementNS( NSS['svg'], 'path');
13483             poly.setAttribute('d', aPath);
13484             res.appendPath(poly);
13485         }
13486         const a = (Math.SQRT1_2 / this.sqrtElements);
13487         const d = (sqrtArea2 - Math.floor(sqrtArea2));
13488         const len = (nT * Math.SQRT1_2 * d);
13489         const height = pruneScaleValue(Math.SQRT1_2 / this.sqrtElements);
13490         aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
13491         aPoint.y = height;
13492         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13493         aPoint.x = len + a;
13494         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13495         aPoint.y = 0.0;
13496         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13497         aPoint.x = 0.0;
13498         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13499         const poly = document.createElementNS( NSS['svg'], 'path');
13500         poly.setAttribute('d', aPath);
13501         let aTransform;
13503         if((Math.floor(sqrtArea2) & 1) == 1) {
13504             // odd line
13505             aTransform = SVGIdentityMatrix.rotate((Math.PI)/2 + (Math.PI)/4);
13506             aTransform.translate(edge + this.elementEdge, 0.0);
13507         }
13508         else {
13509             aTransform = SVGIdentityMatrix.translate(-a, 0.0);
13510             aTransform.rotate(-(Math.PI/4));
13511             aTransform.translate(0.0, edge);
13512         }
13514         poly.matrixTransform(aTransform);
13515         res.appendPath(poly);
13516     }
13517     else { //out
13518         const sqrtArea2 = Math.sqrt(nT * this.sqrtElements * this.sqrtElements);
13519         const edge = pruneScaleValue(Math.floor(sqrtArea2)/this.sqrtElements);
13521         let aPath, aPoint = document.documentElement.createSVGPoint();
13522         if(edge != 0) {
13523             aPoint.y = 1.0;
13524             aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
13525             aPoint.x = edge;
13526             aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13527             aPoint.x = 1.0;
13528             aPoint.y = edge;
13529             aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13530             aPoint.y = 0.0;
13531             aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13532             aPoint.x = 0.0;
13533             aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13534             const poly = document.createElementNS( NSS['svg'], 'path');
13535             poly.setAttribute('d', aPath);
13536             res.appendPath(poly);
13537         }
13538         const a = (Math.SQRT1_2 / this.sqrtElements);
13539         const d = (sqrtArea2 - Math.floor(sqrtArea2));
13540         const len = ((1.0 - nT) * Math.SQRT2 * d);
13541         const height = pruneScaleValue(Math.SQRT1_2 / this.sqrtElements);
13542         aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
13543         aPoint.y = height;
13544         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13545         aPoint.x = len + a;
13546         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13547         aPoint.y = 0.0;
13548         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13549         aPoint.x = 0.0;
13550         aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13551         const poly = document.createElementNS( NSS['svg'], 'path');
13552         poly.setAttribute('d', aPath);
13553         let aTransform;
13555         if((Math.floor(sqrtArea2) & 1) == 1) {
13556             // odd line
13557             aTransform = SVGIdentityMatrix.translate(0.0, -height);
13558             aTransform.rotate(Math.PI/2 + Math.PI/4);
13559             aTransform.translate(1.0, edge);
13560         }
13561         else {
13562             aTransform = SVGIdentityMatrix.rotate(-(Math.PI/4));
13563             aTransform = aTransform.translate(edge, 1.0);
13564         }
13565         poly.matrixTransform(aTransform);
13566         res.appendPath(poly);
13567     }
13568     return res;
13571 SnakeWipePath.prototype.perform = function(nT) {
13572     var res = createEmptyPath();
13573     if(this.diagonal) {
13574         if(nT >= 0.5) {
13575             res.appendPath(this.calcHalfDiagonalSnake(1.0, true));
13576             res.appendPath(this.calcHalfDiagonalSnake(2.0*(nT-0.5), false));
13577         }
13578         else
13579             res.appendPath(this.calcHalfDiagonalSnake(2.0*nT, true));
13580     }
13581     else
13582         res = this.calcSnake(nT);
13584     return this.flipOnYAxis ? flipOnYAxis(res) : res;
13587 /** Class ParallelSnakesWipePath
13588  *  Generates a parallel snakes wipe:
13590  *  @param nElements
13591  *  @param bDiagonal
13592  *  @param bFlipOnYAxis
13593  *  @param bOpposite
13594  */
13595 function ParallelSnakesWipePath(nElements, bDiagonal, bFlipOnYAxis, bOpposite) {
13596     SnakeWipePath.call(this, nElements, bDiagonal, bFlipOnYAxis);
13597     this.bOpposite = bOpposite;
13600 ParallelSnakesWipePath.prototype = Object.create(SnakeWipePath);
13602 ParallelSnakesWipePath.prototype.perform = function( nT ) {
13603     var res = createEmptyPath(), half, aTransform;
13604     if(this.diagonal) {
13605         assert(this.bOpposite);
13606         half = SnakeWipePath.prototype.calcHalfDiagonalSnake.call(this, nT, false);
13607         // flip on x axis and rotate 90 degrees:
13608         aTransform = SVGIdentityMatrix.scale(1, -1);
13609         aTransform.translate(-0.5, 0.5);
13610         aTransform.rotate(Math.PI/2);
13611         aTransform.translate(0.5, 0.5);
13612         half.matrixTransform(aTransform);
13613         half.changeOrientation();
13614         res.appendPath(half);
13616         // rotate 180 degrees:
13617         aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
13618         aTransform.rotate(Math.PI);
13619         aTransform.translate(0.5, 0.5);
13620         half.matrixTransform(aTransform);
13621         res.appendPath(half);
13622     }
13623     else {
13624         half = SnakeWipePath.prototype.calcSnake.call(this, nT / 2.0 );
13625         // rotate 90 degrees
13626         aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
13627         aTransform = aTransform.rotate(Math.PI/2);
13628         aTransform = aTransform.translate(0.5, 0.5);
13629         half.matrixTransform(aTransform);
13630         res.appendPath(flipOnYAxis(half));
13631         res.appendPath(this.bOpposite ? flipOnXAxis(half) : half);
13632     }
13634     return this.flipOnYAxis ? flipOnYAxis(res) : res;
13637 /** SpiralWipePath
13639  *  @param nElements
13640  *      number of elements in the spiral animation
13641  *  @param bFlipOnYAxis
13642  *      boolean value indicating whether to flip on y-axis or not.
13643  */
13644 function SpiralWipePath(nElements, bFlipOnYAxis) {
13645     this.nElements    = nElements;
13646     this.sqrtElements = Math.floor(Math.sqrt(nElements));
13647     this.bFlipOnYAxis = bFlipOnYAxis;
13650 SpiralWipePath.prototype.calcNegSpiral = function( nT ) {
13651     var area  = nT * this.nElements;
13652     var e     = (Math.sqrt(area) / 2.0);
13653     var edge  = Math.floor(e) * 2;
13655     var aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
13656     var edge_ = pruneScaleValue(edge / this.sqrtElements);
13658     aTransform = aTransform.scale(edge_, edge_);
13659     aTransform = aTransform.translate(0.5, 0.5);
13660     var poly = createUnitSquarePath();
13661     poly.matrixTransform(aTransform);
13662     var res = poly.cloneNode(true);
13664     if(1.0 - nT != 0) {
13665         var edge1 = edge + 1;
13666         var len   = Math.floor( (e - edge/2) * edge1 * 4);
13667         var w     = Math.PI / 2;
13669         while(len > 0) {
13670             var alen = Math.min(len, edge1);
13671             len -= alen;
13672             poly = createUnitSquarePath();
13673             aTransform = SVGIdentityMatrix.scale(
13674                             pruneScaleValue( alen / this.sqrtElements ),
13675                             pruneScaleValue( 1.0 / this.sqrtElements ));
13676             aTransform = aTransform.translate(
13677                             - pruneScaleValue( (edge / 2) / this.sqrtElements ),
13678                             pruneScaleValue( (edge / 2) / this.sqrtElements ));
13679             aTransform = aTransform.rotate( w );
13680             w -= Math.PI / 2;
13681             aTransform = aTransform.translate(0.5, 0.5);
13682             poly.matrixTransform(aTransform);
13683             res.appendPath(poly);
13684         }
13685     }
13687     return res;
13690 SpiralWipePath.prototype.perform = function( nT ) {
13691     var res         = createUnitSquarePath();
13692     var innerSpiral = this.calcNegSpiral( 1.0 - nT );
13693     innerSpiral.changeOrientation();
13694     res.appendPath(innerSpiral);
13696     return this.bFlipOnYAxis ? flipOnYAxis(res) : res;
13699 /** Class BoxSnakesWipePath
13700  *  Generates a twoBoxLeft or fourBoxHorizontal wipe:
13702  */
13703 function BoxSnakesWipePath(nElements, bFourBox) {
13704     SpiralWipePath.call(this, nElements);
13705     this.bFourBox = bFourBox;
13708 BoxSnakesWipePath.prototype = Object.create(SpiralWipePath);
13710 BoxSnakesWipePath.prototype.perform = function( nT ) {
13711     var res = createUnitSquarePath(), aTransform;
13712     var innerSpiral = SpiralWipePath.prototype.calcNegSpiral.call(this, 1.0 - nT);
13713     innerSpiral.changeOrientation();
13715     if(this.bFourBox) {
13716         aTransform = SVGIdentityMatrix.scale(0.5, 0.5);
13717         innerSpiral.matrixTransform(aTransform);
13718         res.appendPath(innerSpiral);
13719         res.appendPath(flipOnXAxis(innerSpiral));
13720         innerSpiral = flipOnYAxis(innerSpiral);
13721         res.appendPath(innerSpiral);
13722         res.appendPath(flipOnXAxis(innerSpiral));
13723     }
13724     else {
13725         aTransform = SVGIdentityMatrix.scale(1.0, 0.5);
13726         innerSpiral.matrixTransform(aTransform);
13727         res.appendPath(innerSpiral);
13728         res.appendPath(flipOnXAxis(innerSpiral));
13729     }
13730     return this.bFlipOnYAxis ? flipOnYAxis(res) : res;
13733 /** Class VeeWipePath
13734   *
13735   */
13736 function VeeWipePath() { }
13738 VeeWipePath.prototype.perform = function( nT ) {
13739     const d = pruneScaleValue(2.0 * nT);
13740     var polyPath = 'M ' + 0.0 + ' ' + -1.0 + ' ';
13741     polyPath += 'L ' + 0.0 + ' ' + (d - 1.0) + ' ';
13742     polyPath += 'L ' + 0.5 + ' ' + d + ' ';
13743     polyPath += 'L ' + 1.0 + ' ' + (d - 1.0) + ' ';
13744     polyPath += 'L ' + 1.0 + ' ' + -1.0 + ' ';
13745     polyPath += 'L ' + 0.0 + ' ' + -1.0 + ' ';
13747     var aPolyPolyPath = document.createElementNS( NSS['svg'], 'path');
13748     aPolyPolyPath.setAttribute('d', polyPath);
13749     return aPolyPolyPath;
13753 /** Class AnimatedSlide
13754  *  This class handle a slide element during a slide transition.
13756  *  @param aMetaSlide
13757  *      The MetaSlide object related to the slide element to be handled.
13758  */
13759 function AnimatedSlide( aMetaSlide )
13761     if( !aMetaSlide )
13762     {
13763         log( 'AnimatedSlide constructor: meta slide is not valid' );
13764     }
13766     this.aMetaSlide = aMetaSlide;
13767     this.aSlideElement = this.aMetaSlide.slideElement;
13768     this.sSlideId = this.aMetaSlide.slideId;
13770     this.aUsedAttributeSet = [];
13772     this.aClipPathElement = null;
13773     this.aClipPathContent = null;
13774     this.bIsClipped = false;
13777 /** show
13778  *  Set the visibility property of the slide to 'inherit'
13779  *  and update the master page view.
13780  */
13781 AnimatedSlide.prototype.show = function()
13783     this.aMetaSlide.show();
13786 /** hide
13787  *  Set the visibility property of the slide to 'hidden'.
13788  */
13789 AnimatedSlide.prototype.hide = function()
13791     this.aMetaSlide.hide();
13794 /** notifyUsedAttribute
13795  *  Populate the set of attribute used for the transition.
13797  *  @param sName
13798  *      A string representing an attribute name.
13799  */
13800 AnimatedSlide.prototype.notifyUsedAttribute = function( sName )
13802     if( sName == 'clip-path' )
13803     {
13804         this.initClipPath();
13805         this.bIsClipped = true;
13806     }
13807     else
13808     {
13809         this.aUsedAttributeSet.push( sName );
13810     }
13813 /** reset
13814  *  Remove from the handled slide element any attribute that was appended for
13815  *  performing the transition.
13816  */
13817 AnimatedSlide.prototype.reset = function()
13819     if( this.bIsClipped )
13820     {
13821         this.cleanClipPath();
13822         this.bIsClipped = false;
13823     }
13825     var i;
13826     for( i = 0; i < this.aUsedAttributeSet.length; ++i )
13827     {
13828         var sAttrName = this.aUsedAttributeSet[i];
13829         this.aSlideElement.removeAttribute( sAttrName );
13830     }
13831     this.aUsedAttributeSet = [];
13834 /** initClipPath
13835  *  Create a new clip path element and append it to the clip path def group.
13836  *  Moreover the created <clipPath> element is referenced by the handled slide
13837  *  element.
13838  */
13839 AnimatedSlide.prototype.initClipPath = function()
13841     // We create the clip path element.
13842     this.aClipPathElement = document.createElementNS( NSS['svg'], 'clipPath' );
13844     var sId = 'clip-path-' + this.sSlideId;
13845     this.aClipPathElement.setAttribute( 'id', sId );
13846     this.aClipPathElement.setAttribute( 'clipPathUnits', 'userSpaceOnUse' );
13848     // We create and append a placeholder content.
13849     this.aClipPathContent = document.createElementNS( NSS['svg'], 'path' );
13850     var sPathData = 'M 0 0 h ' + WIDTH + ' v ' + HEIGHT + ' h -' + WIDTH + ' z';
13851     this.aClipPathContent.setAttribute( 'd', sPathData );
13852     this.aClipPathElement.appendChild( this.aClipPathContent );
13854     // We insert it into the svg document.
13855     var aClipPathGroup = theMetaDoc.aClipPathGroup;
13856     aClipPathGroup.appendChild( this.aClipPathElement );
13858     // Finally we set the reference to the created clip path.
13859     // We set it on the parent element because a slide element already
13860     // owns a clip path attribute.
13861     var sRef = 'url(#' + sId + ')';
13862     this.aSlideElement.parentNode.setAttribute( 'clip-path', sRef );
13865 /** cleanClipPath
13866  *  Removes the related <clipPath> element from the <defs> group,
13867  *  and remove the 'clip-path' attribute from the slide element.
13869  */
13870 AnimatedSlide.prototype.cleanClipPath = function()
13872     this.aSlideElement.parentNode.removeAttribute( 'clip-path' );
13874     if( this.aClipPathElement )
13875     {
13876         var aClipPathGroup = theMetaDoc.aClipPathGroup;
13877         aClipPathGroup.removeChild( this.aClipPathElement );
13878         this.aClipPathElement = null;
13879         this.aClipPathContent = null;
13880     }
13883 /** insertBefore
13884  *  Insert an svg element before the handled slide element.
13886  *  @param aElement
13887  *      A svg element.
13888  */
13889 AnimatedSlide.prototype.insertBefore = function( aElement )
13891     if( aElement )
13892     {
13893         this.aSlideElement.parentNode.insertBefore( aElement, this.aSlideElement );
13894     }
13897 /** appendElement
13898  *  Insert an svg element after the handled slide element.
13900  *  @param aElement
13901  *      A svg element.
13902  */
13903 AnimatedSlide.prototype.appendElement = function( aElement )
13905     if( aElement )
13906     {
13907          this.aSlideElement.parentNode.appendChild( aElement );
13908     }
13911 /** removeElement
13912  *  Remove an svg element.
13914  *  @param aElement
13915  *      A svg element.
13916  */
13917 AnimatedSlide.prototype.removeElement = function( aElement )
13919     if( aElement )
13920     {
13921         this.aSlideElement.parentNode.removeChild( aElement );
13922     }
13925 /** getWidth
13927  *  @return {Number}
13928  *      The slide width.
13929  */
13930 AnimatedSlide.prototype.getWidth = function()
13932     return WIDTH;
13935 /** getHeight
13937  *  @return {Number}
13938  *      The slide height.
13939  */
13940 AnimatedSlide.prototype.getHeight = function()
13942     return HEIGHT;
13945 /** setOpacity
13947  *  @param nValue
13948  *      A number in the [0,1] range representing the slide opacity.
13949  */
13950 AnimatedSlide.prototype.setOpacity = function( nValue )
13952     this.aSlideElement.setAttribute( 'opacity', nValue );
13955 /** translate
13956  *  Translate the handled slide.
13958  *  @param nDx
13959  *     A number representing the translation that occurs in the x direction.
13960  *  @param nDy
13961  *     A number representing the translation that occurs in the y direction.
13962  */
13963 AnimatedSlide.prototype.translate = function( nDx, nDy )
13965     var sTransformAttr = 'translate(' + nDx + ',' + nDy + ')';
13966     this.aSlideElement.setAttribute( 'transform', sTransformAttr );
13969 /** setClipPath
13970  *  Replace the current content of the <clipPath> element with the one
13971  *  passed through the parameter.
13973  *  @param aClipPathContent
13974  *      A <g> element representing a <path> element used for clipping.
13975  */
13976 AnimatedSlide.prototype.setClipPath = function( aClipPathContent )
13978     // Earlier we used to replace the current <path> element with the passed one,
13979     // anyway that does not work in IE9, so we replace the 'd' attribute, only.
13980     if( this.aClipPathContent )
13981     {
13982         var sPathData = aClipPathContent.getAttribute( 'd' );
13983         this.aClipPathContent.setAttribute( 'd', sPathData );
13984     }
13989 function AnimatedElement( aElement )
13991     if( !aElement )
13992     {
13993         log( 'AnimatedElement constructor: element is not valid' );
13994     }
13996     this.aSlideShowContext = null;
13998     this.aBaseElement = aElement.cloneNode( true );
13999     this.aActiveElement = aElement;
14000     this.sElementId = this.aActiveElement.getAttribute( 'id' );
14002     this.aBaseBBox = this.aActiveElement.getBBox();
14003     this.nBaseCenterX = this.aBaseBBox.x + this.aBaseBBox.width / 2;
14004     this.nBaseCenterY = this.aBaseBBox.y + this.aBaseBBox.height / 2;
14007     this.aClipPathElement = null;
14008     this.aClipPathContent = null;
14010     this.aPreviousElement = null;
14011     this.aStateSet = {};
14013     this.eAdditiveMode = ADDITIVE_MODE_REPLACE;
14014     this.bIsUpdated = true;
14016     this.aTMatrix = document.documentElement.createSVGMatrix();
14017     this.aCTM = document.documentElement.createSVGMatrix();
14018     this.aICTM = document.documentElement.createSVGMatrix();
14020     this.initElement();
14023 AnimatedElement.prototype.initElement = function()
14025     this.nCenterX = this.nBaseCenterX;
14026     this.nCenterY = this.nBaseCenterY;
14027     this.nScaleFactorX = 1.0;
14028     this.nScaleFactorY = 1.0;
14029     this.nRotationAngle = 0.0;
14031     // add a transform attribute of type matrix
14032     this.aActiveElement.setAttribute( 'transform', makeMatrixString( 1, 0, 0, 1, 0, 0 ) );
14035 /** initClipPath
14036  *  Create a new clip path element and append it to the clip path def group.
14037  *  Moreover the created <clipPath> element is referenced by the handled
14038  *  animated element.
14040  */
14041 AnimatedElement.prototype.initClipPath = function()
14043     // We create the clip path element.
14044     this.aClipPathElement = document.createElementNS( NSS['svg'], 'clipPath' );
14046     var sId = 'clip-path-' + this.sElementId;
14047     this.aClipPathElement.setAttribute( 'id', sId );
14048     this.aClipPathElement.setAttribute( 'clipPathUnits', 'userSpaceOnUse' );
14050     // We create and append a placeholder content.
14051     this.aClipPathContent = document.createElementNS( NSS['svg'], 'path' );
14052     this.aClippingBBox = this.getBBoxWithStroke();
14053     var nWidth = this.aClippingBBox.width;
14054     var nHeight = this.aClippingBBox.height;
14055     var sPathData = 'M ' + this.aClippingBBox.x + ' ' + this.aClippingBBox.y +
14056                     ' h ' + nWidth + ' v ' + nHeight + ' h -' + nWidth + ' z';
14057     this.aClipPathContent.setAttribute( 'd', sPathData );
14058     this.aClipPathElement.appendChild( this.aClipPathContent );
14060     // We insert it into the svg document.
14061     var aClipPathGroup = theMetaDoc.aClipPathGroup;
14062     aClipPathGroup.appendChild( this.aClipPathElement );
14064     // Finally we set the reference to the created clip path.
14065     var sRef = 'url(#' + sId + ')';
14066     this.aActiveElement.setAttribute( 'clip-path', sRef );
14069 /** cleanClipPath
14070  *  Removes the related <clipPath> element from the <defs> group,
14071  *  and remove the 'clip-path' attribute from the animated element.
14073  */
14074 AnimatedElement.prototype.cleanClipPath = function()
14076     this.aActiveElement.removeAttribute( 'clip-path' );
14078     if( this.aClipPathElement )
14079     {
14080         var aClipPathGroup = theMetaDoc.aClipPathGroup;
14081         aClipPathGroup.removeChild( this.aClipPathElement );
14082         this.aClipPathElement = null;
14083         this.aClipPathContent = null;
14084     }
14087 AnimatedElement.prototype.getId = function()
14089     return this.aActiveElement.getAttribute( 'id' );
14092 AnimatedElement.prototype.getAdditiveMode = function()
14094     return this.eAdditiveMode;
14097 AnimatedElement.prototype.setAdditiveMode = function( eAdditiveMode )
14099     this.eAdditiveMode = eAdditiveMode;
14102 AnimatedElement.prototype.setToElement = function( aElement )
14104     if( !aElement )
14105     {
14106         log( 'AnimatedElement(' + this.getId() + ').setToElement: element is not valid' );
14107         return false;
14108     }
14110     var aClone = aElement.cloneNode( true );
14111     this.aPreviousElement = this.aActiveElement.parentNode.replaceChild( aClone, this.aActiveElement );
14112     this.aActiveElement = aClone;
14114     return true;
14117 AnimatedElement.prototype.notifySlideStart = function( aSlideShowContext )
14119     if( !aSlideShowContext )
14120     {
14121         log( 'AnimatedElement.notifySlideStart: slideshow context is not valid' );
14122     }
14123     this.aSlideShowContext = aSlideShowContext;
14125     var aClone = this.aBaseElement.cloneNode( true );
14126     this.aActiveElement.parentNode.replaceChild( aClone, this.aActiveElement );
14127     this.aActiveElement = aClone;
14129     this.initElement();
14130     this.DBG( '.notifySlideStart invoked' );
14133 AnimatedElement.prototype.notifySlideEnd = function()
14135     // empty body
14138 AnimatedElement.prototype.notifyAnimationStart = function()
14140     // empty body
14143 AnimatedElement.prototype.notifyAnimationEnd = function()
14145     // empty body
14148 AnimatedElement.prototype.notifyNextEffectStart = function( /*nEffectIndex*/ )
14150     // empty body
14153 /** saveState
14154  *  Save the state of the managed animated element and append it to aStateSet
14155  *  using the passed animation node id as key.
14157  *  @param nAnimationNodeId
14158  *      A non negative integer representing the unique id of an animation node.
14159  */
14160 AnimatedElement.prototype.saveState = function( nAnimationNodeId )
14162     ANIMDBG.print( 'AnimatedElement(' + this.getId() + ').saveState(' + nAnimationNodeId +')' );
14163     if( !this.aStateSet[ nAnimationNodeId ] )
14164     {
14165         this.aStateSet[ nAnimationNodeId ] = {};
14166     }
14167     var aState = this.aStateSet[ nAnimationNodeId ];
14168     aState.aElement = this.aActiveElement.cloneNode( true );
14169     aState.nCenterX = this.nCenterX;
14170     aState.nCenterY = this.nCenterY;
14171     aState.nScaleFactorX = this.nScaleFactorX;
14172     aState.nScaleFactorY = this.nScaleFactorY;
14173     aState.nRotationAngle = this.nRotationAngle;
14177 /** restoreState
14178  *  Restore the state of the managed animated element to the state with key
14179  *  the passed animation node id.
14181  *  @param nAnimationNodeId
14182  *      A non negative integer representing the unique id of an animation node.
14184  *  @return
14185  *      True if the restoring operation is successful, false otherwise.
14186  */
14187 AnimatedElement.prototype.restoreState = function( nAnimationNodeId )
14189     if( !this.aStateSet[ nAnimationNodeId ] )
14190     {
14191         log( 'AnimatedElement(' + this.getId() + ').restoreState: state '
14192                  +nAnimationNodeId  + ' is not valid' );
14193         return false;
14194     }
14196     ANIMDBG.print( 'AnimatedElement(' + this.getId() + ').restoreState(' + nAnimationNodeId +')' );
14197     var aState = this.aStateSet[ nAnimationNodeId ];
14198     var bRet = this.setToElement( aState.aElement );
14199     if( bRet )
14200     {
14201         this.nCenterX = aState.nCenterX;
14202         this.nCenterY = aState.nCenterY;
14203         this.nScaleFactorX = aState.nScaleFactorX;
14204         this.nScaleFactorY = aState.nScaleFactorY;
14205         this.nRotationAngle = aState.nRotationAngle;
14206     }
14207     return bRet;
14210 AnimatedElement.prototype.getBaseBBox = function()
14212     return this.aBaseBBox;
14215 AnimatedElement.prototype.getBaseCenterX = function()
14217     return this.nBaseCenterX;
14220 AnimatedElement.prototype.getBaseCenterY = function()
14222     return this.nBaseCenterY;
14225 AnimatedElement.prototype.getBBox = function()
14227     return this.aActiveElement.parentNode.getBBox();
14230 AnimatedElement.prototype.getBBoxWithStroke = function()
14232     var aBBox = this.aActiveElement.parentNode.getBBox();
14234     var aChildrenSet = this.aActiveElement.childNodes;
14236     var sStroke, sStrokeWidth;
14237     var nStrokeWidth = 0;
14238     var i;
14239     for( i = 0; i < aChildrenSet.length; ++i )
14240     {
14241         if( ! aChildrenSet[i].getAttribute  )
14242             continue;
14244         sStroke = aChildrenSet[i].getAttribute( 'stroke' );
14245         if( sStroke && sStroke != 'none' )
14246         {
14247             sStrokeWidth = aChildrenSet[i].getAttribute( 'stroke-width' );
14248             var nSW = parseFloat( sStrokeWidth );
14249             if( nSW > nStrokeWidth )
14250                 nStrokeWidth = nSW;
14251         }
14252     }
14254     if( nStrokeWidth == 0 )
14255     {
14256         sStrokeWidth = ROOT_NODE.getAttribute( 'stroke-width' );
14257         nStrokeWidth = parseFloat( sStrokeWidth );
14258     }
14259     if( nStrokeWidth != 0 )
14260     {
14261         // It is hard to clip properly the stroke so we try to enlarge
14262         // the resulting bounding box even more.
14263         nStrokeWidth *= 1.1;
14264         var nHalfStrokeWidth = nStrokeWidth / 2;
14265         var nDoubleStrokeWidth = nStrokeWidth * 2;
14267         // Note: IE10 don't let modify the values of an element BBox.
14268         var aEBBox = document.documentElement.createSVGRect();
14269         aEBBox.x = aBBox.x - nHalfStrokeWidth;
14270         aEBBox.y = aBBox.y - nHalfStrokeWidth;
14271         aEBBox.width = aBBox.width + nDoubleStrokeWidth;
14272         aEBBox.height = aBBox.height + nDoubleStrokeWidth;
14273         aBBox = aEBBox;
14274     }
14275     return aBBox;
14278 /** setClipPath
14279  *  Replace the current content of the <clipPath> element with the one
14280  *  passed through the parameter.
14282  *  @param aClipPathContent
14283  *      A <g> element representing a <path> element used for clipping.
14284  */
14285 AnimatedElement.prototype.setClipPath = function( aClipPathContent )
14287     if( this.aClipPathContent )
14288     {
14289         // We need to translate the clip path to the top left corner of
14290         // the element bounding box.
14291         var aTranslation = SVGIdentityMatrix.translate( this.aClippingBBox.x,
14292                                                         this.aClippingBBox.y);
14293         aClipPathContent.matrixTransform( aTranslation );
14294         var sPathData = aClipPathContent.getAttribute( 'd' );
14295         this.aClipPathContent.setAttribute( 'd', sPathData );
14296     }
14300 AnimatedElement.prototype.getX = function()
14302     return this.nCenterX;
14305 AnimatedElement.prototype.getY = function()
14307     return this.nCenterY;
14310 AnimatedElement.prototype.getWidth = function()
14312     return this.nScaleFactorX * this.getBaseBBox().width;
14315 AnimatedElement.prototype.getHeight = function()
14317     return this.nScaleFactorY * this.getBaseBBox().height;
14320 AnimatedElement.prototype.updateTransformAttribute = function()
14322     this.aTransformAttrList = this.aActiveElement.transform.baseVal;
14323     this.aTransformAttr = this.aTransformAttrList.getItem( 0 );
14324     this.aTransformAttr.setMatrix( this.aTMatrix );
14327 AnimatedElement.prototype.setX = function( nNewCenterX )
14329     if( nNewCenterX === this.nCenterX ) return;
14331     this.aTransformAttrList = this.aActiveElement.transform.baseVal;
14332     this.aTransformAttr = this.aTransformAttrList.getItem( 0 );
14333     this.aTMatrix = this.aTransformAttr.matrix.translate( nNewCenterX - this.nCenterX, 0 );
14334     this.aTransformAttr.setMatrix( this.aTMatrix );
14335     this.nCenterX = nNewCenterX;
14338 AnimatedElement.prototype.setY = function( nNewCenterY )
14340     if( nNewCenterY === this.nCenterY ) return;
14342     this.aTransformAttrList = this.aActiveElement.transform.baseVal;
14343     this.aTransformAttr = this.aTransformAttrList.getItem( 0 );
14344     this.aTMatrix = this.aTransformAttr.matrix.translate( 0, nNewCenterY - this.nCenterY );
14345     this.aTransformAttr.setMatrix( this.aTMatrix );
14346     this.nCenterY = nNewCenterY;
14349 AnimatedElement.prototype.setWidth = function( nNewWidth )
14351     ANIMDBG.print( 'AnimatedElement.setWidth: nNewWidth = ' + nNewWidth );
14352     if( nNewWidth < 0 )
14353     {
14354         log('AnimatedElement(' + this.getId() + ').setWidth: negative height!');
14355         nNewWidth = 0;
14356     }
14358     var nBaseWidth = this.getBaseBBox().width;
14359     var nScaleFactorX = nNewWidth / nBaseWidth;
14361     if( nScaleFactorX < 1e-5 ) nScaleFactorX = 1e-5;
14362     if( nScaleFactorX == this.nScaleFactorX ) return;
14364     this.aTMatrix = document.documentElement.createSVGMatrix()
14365         .translate( this.nCenterX, this.nCenterY )
14366         .rotate(this.nRotationAngle)
14367         .scaleNonUniform( nScaleFactorX, this.nScaleFactorY )
14368         .translate( -this.nBaseCenterX, -this.nBaseCenterY );
14369     this.updateTransformAttribute();
14371     this.nScaleFactorX = nScaleFactorX;
14374 AnimatedElement.prototype.setHeight = function( nNewHeight )
14376     ANIMDBG.print( 'AnimatedElement.setWidth: nNewHeight = ' + nNewHeight );
14377     if( nNewHeight < 0 )
14378     {
14379         log('AnimatedElement(' + this.getId() + ').setWidth: negative height!');
14380         nNewHeight = 0;
14381     }
14383     var nBaseHeight = this.getBaseBBox().height;
14384     var nScaleFactorY = nNewHeight / nBaseHeight;
14386     if( nScaleFactorY < 1e-5 ) nScaleFactorY = 1e-5;
14387     if( nScaleFactorY == this.nScaleFactorY ) return;
14389     this.aTMatrix = document.documentElement.createSVGMatrix()
14390         .translate( this.nCenterX, this.nCenterY )
14391         .rotate(this.nRotationAngle)
14392         .scaleNonUniform( this.nScaleFactorX, nScaleFactorY )
14393         .translate( -this.nBaseCenterX, -this.nBaseCenterY );
14394     this.updateTransformAttribute();
14396     this.nScaleFactorY = nScaleFactorY;
14399 AnimatedElement.prototype.getOpacity = function()
14401     return this.aActiveElement.getAttribute( 'opacity' );
14404 AnimatedElement.prototype.setOpacity = function( nValue )
14406     this.aActiveElement.setAttribute( 'opacity', nValue );
14409 AnimatedElement.prototype.getRotationAngle = function()
14411     return this.nRotationAngle;
14414 AnimatedElement.prototype.setRotationAngle = function( nNewRotAngle )
14416     this.aTMatrix = document.documentElement.createSVGMatrix()
14417         .translate( this.nCenterX, this.nCenterY )
14418         .rotate(nNewRotAngle)
14419         .scaleNonUniform( this.nScaleFactorX, this.nScaleFactorY )
14420         .translate( -this.nBaseCenterX, -this.nBaseCenterY );
14421     this.updateTransformAttribute();
14423     this.nRotationAngle = nNewRotAngle;
14426 AnimatedElement.prototype.getVisibility = function()
14429     var sVisibilityValue = this.aActiveElement.getAttribute( 'visibility' );
14430     if( !sVisibilityValue || ( sVisibilityValue === 'inherit' ) )
14431         return 'visible'; // TODO: look for parent visibility!
14432     else
14433         return sVisibilityValue;
14436 AnimatedElement.prototype.setVisibility = function( sValue )
14438     if( sValue == 'visible' )
14439         sValue = 'inherit';
14440     this.aActiveElement.setAttribute( 'visibility', sValue );
14443 AnimatedElement.prototype.getStrokeStyle = function()
14445     // TODO: getStrokeStyle: implement it
14446     return 'solid';
14449 AnimatedElement.prototype.setStrokeStyle = function( sValue )
14451     ANIMDBG.print( 'AnimatedElement.setStrokeStyle(' + sValue + ')' );
14454 AnimatedElement.prototype.getFillStyle = function()
14456     // TODO: getFillStyle: implement it
14457     return 'solid';
14460 AnimatedElement.prototype.setFillStyle = function( sValue )
14462     ANIMDBG.print( 'AnimatedElement.setFillStyle(' + sValue + ')' );
14465 AnimatedElement.prototype.getFillColor = function()
14467     var aChildSet = getElementChildren( this.aActiveElement );
14468     var sFillColorValue = '';
14469     for( var i = 0; i <  aChildSet.length; ++i )
14470     {
14471         sFillColorValue = aChildSet[i].getAttribute( 'fill' );
14472         if( sFillColorValue && ( sFillColorValue !== 'none' ) )
14473             break;
14474     }
14476     return colorParser( sFillColorValue );
14479 AnimatedElement.prototype.setFillColor = function( aRGBValue )
14481     assert( aRGBValue instanceof RGBColor,
14482             'AnimatedElement.setFillColor: value argument is not an instance of RGBColor' );
14484     var sValue = aRGBValue.toString( true /* clamped values */ );
14485     var aChildSet = getElementChildren( this.aActiveElement );
14487     var sFillColorValue = '';
14488     for( var i = 0; i <  aChildSet.length; ++i )
14489     {
14490         sFillColorValue = aChildSet[i].getAttribute( 'fill' );
14491         if( sFillColorValue && ( sFillColorValue !== 'none' ) )
14492         {
14493             aChildSet[i].setAttribute( 'fill', sValue );
14494         }
14495     }
14498 AnimatedElement.prototype.getStrokeColor = function()
14500     var aChildSet = getElementChildren( this.aActiveElement );
14501     var sStrokeColorValue = '';
14502     for( var i = 0; i <  aChildSet.length; ++i )
14503     {
14504         sStrokeColorValue = aChildSet[i].getAttribute( 'stroke' );
14505         if( sStrokeColorValue && ( sStrokeColorValue !== 'none' ) )
14506             break;
14507     }
14509     return colorParser( sStrokeColorValue );
14512 AnimatedElement.prototype.setStrokeColor = function( aRGBValue )
14514     assert( aRGBValue instanceof RGBColor,
14515             'AnimatedElement.setFillColor: value argument is not an instance of RGBColor' );
14517     var sValue = aRGBValue.toString( true /* clamped values */ );
14518     var aChildSet = getElementChildren( this.aActiveElement );
14520     var sStrokeColorValue = '';
14521     for( var i = 0; i <  aChildSet.length; ++i )
14522     {
14523         sStrokeColorValue = aChildSet[i].getAttribute( 'stroke' );
14524         if( sStrokeColorValue && ( sStrokeColorValue !== 'none' ) )
14525         {
14526             aChildSet[i].setAttribute( 'stroke', sValue );
14527         }
14528     }
14531 AnimatedElement.prototype.getFontColor = function()
14533     // TODO: getFontColor implement it
14534     return new RGBColor( 0, 0, 0 );
14537 AnimatedElement.prototype.setFontColor = function( sValue )
14539     ANIMDBG.print( 'AnimatedElement.setFontColor(' + sValue + ')' );
14542 AnimatedElement.prototype.DBG = function( sMessage, nTime )
14544     aAnimatedElementDebugPrinter.print( 'AnimatedElement(' + this.getId() + ')' + sMessage, nTime );
14548 function AnimatedTextElement( aElement, aEventMultiplexer )
14550     var theDocument = document;
14552     var sTextType = aElement.getAttribute( 'class' );
14553     var bIsListItem = ( sTextType === 'ListItem' );
14554     if( ( sTextType !== 'TextParagraph' ) && !bIsListItem )
14555     {
14556         log( 'AnimatedTextElement: passed element is not a paragraph.' );
14557         return;
14558     }
14559     var aTextShapeElement = aElement.parentNode;
14560     sTextType = aTextShapeElement.getAttribute( 'class' );
14561     if( sTextType !== 'TextShape' )
14562     {
14563         log( 'AnimatedTextElement: element parent is not a text shape.' );
14564         return;
14565     }
14566     var aTextShapeGroup = aTextShapeElement.parentNode;
14567     // We search for the helper group element used for inserting
14568     // the element copy to be animated; if it doesn't exist we create it.
14569     var aAnimatedElementGroup = getElementByClassName( aTextShapeGroup, 'AnimatedElements' );
14570     if( !aAnimatedElementGroup )
14571     {
14572         aAnimatedElementGroup = theDocument.createElementNS( NSS['svg'], 'g' );
14573         aAnimatedElementGroup.setAttribute( 'class', 'AnimatedElements' );
14574         aTextShapeGroup.appendChild( aAnimatedElementGroup );
14575     }
14577     // Create element used on animating
14578     var aAnimatableElement = theDocument.createElementNS( NSS['svg'], 'g' );
14579     var aTextElement = theDocument.createElementNS( NSS['svg'], 'text' );
14580     // Clone paragraph element <tspan>
14581     var aParagraphElement = aElement.cloneNode( true );
14583     // We create a group element for wrapping bullets, bitmaps
14584     // and text decoration
14585     this.aGraphicGroupElement = theDocument.createElementNS( NSS['svg'], 'g' );
14586     this.aGraphicGroupElement.setAttribute( 'class', 'GraphicGroup' );
14588     // In case we are dealing with a list item that utilizes a bullet char
14589     // we need to clone the related bullet char too.
14590     var aBulletCharClone = null;
14591     var aBulletCharElem = null;
14592     var bIsBulletCharStyle =
14593         ( aElement.getAttributeNS( NSS['ooo'], aOOOAttrListItemNumberingType ) === 'bullet-style' );
14594     if( bIsBulletCharStyle )
14595     {
14596         var aBulletCharGroupElem = getElementByClassName( aTextShapeGroup, 'BulletChars' );
14597         if( aBulletCharGroupElem )
14598         {
14599             var aBulletPlaceholderElem = getElementByClassName( aElement, 'BulletPlaceholder' );
14600             if( aBulletPlaceholderElem )
14601             {
14602                 var sId = aBulletPlaceholderElem.getAttribute( 'id' );
14603                 sId = 'bullet-char(' + sId + ')';
14604                 aBulletCharElem = theDocument.getElementById( sId );
14605                 if( aBulletCharElem )
14606                 {
14607                     aBulletCharClone = aBulletCharElem.cloneNode( true );
14608                 }
14609                 else
14610                 {
14611                     log( 'AnimatedTextElement: ' + sId + ' not found.' );
14612                 }
14613             }
14614             else
14615             {
14616                 log( 'AnimatedTextElement: no bullet placeholder found' );
14617             }
14618         }
14619         else
14620         {
14621             log( 'AnimatedTextElement: no bullet char group found' );
14622         }
14623     }
14625     // In case there are embedded bitmaps we need to clone them
14626     var aBitmapElemSet = [];
14627     var aBitmapCloneSet = [];
14628     var aBitmapPlaceholderSet = getElementsByClassName( aElement, 'BitmapPlaceholder' );
14629     var i;
14630     if( aBitmapPlaceholderSet )
14631     {
14632         for( i = 0; i < aBitmapPlaceholderSet.length; ++i )
14633         {
14634             sId = aBitmapPlaceholderSet[i].getAttribute( 'id' );
14635             var sBitmapChecksum = sId.substring( 'bitmap-placeholder'.length + 1, sId.length - 1 );
14636             sId = 'embedded-bitmap(' + sBitmapChecksum + ')';
14637             aBitmapElemSet[i] = theDocument.getElementById( sId );
14638             if( aBitmapElemSet[i] )
14639             {
14640                 aBitmapCloneSet[i] = aBitmapElemSet[i].cloneNode( true );
14641             }
14642             else
14643             {
14644                 log( 'AnimatedTextElement: ' + sId + ' not found.' );
14645             }
14646         }
14647     }
14650     // Change clone element id.
14651     this.sParagraphId = sId = aParagraphElement.getAttribute( 'id' );
14652     aParagraphElement.removeAttribute( 'id' );
14653     aAnimatableElement.setAttribute( 'id', sId +'.a' );
14654     if( aBulletCharClone )
14655         aBulletCharClone.removeAttribute( 'id' );
14656     for( i = 0; i < aBitmapCloneSet.length; ++i )
14657     {
14658         if( aBitmapCloneSet[i] )
14659             aBitmapCloneSet[i].removeAttribute( 'id' );
14660     }
14662     // Set up visibility
14663     var sVisibilityAttr = aElement.getAttribute( 'visibility' );
14664     if( !sVisibilityAttr )
14665         sVisibilityAttr = 'inherit';
14666     aAnimatableElement.setAttribute( 'visibility', sVisibilityAttr );
14667     aParagraphElement.setAttribute( 'visibility', 'inherit' );
14668     this.aGraphicGroupElement.setAttribute( 'visibility', 'inherit' );
14669     if( aBulletCharElem )
14670         aBulletCharElem.setAttribute( 'visibility', 'hidden' );
14671     for( i = 0; i < aBitmapCloneSet.length; ++i )
14672     {
14673         if( aBitmapElemSet[i] )
14674             aBitmapElemSet[i].setAttribute( 'visibility', 'hidden' );
14675     }
14677     // Append each element to its parent.
14678     // <g class='AnimatedElements'>
14679     //   <g>
14680     //     <text>
14681     //       <tspan class='TextParagraph'> ... </tspan>
14682     //     </text>
14683     //     <g class='GraphicGroup'>
14684     //       [<g class='BulletChar'>...</g>]
14685     //       [<g class='EmbeddedBitmap'>...</g>]
14686     //       .
14687     //       .
14688     //       [<g class='EmbeddedBitmap'>...</g>]
14689     //     </g>
14690     //   </g>
14691     // </g>
14693     aTextElement.appendChild( aParagraphElement );
14694     aAnimatableElement.appendChild( aTextElement );
14696     if( aBulletCharClone )
14697         this.aGraphicGroupElement.appendChild( aBulletCharClone );
14698     for( i = 0; i < aBitmapCloneSet.length; ++i )
14699     {
14700         if( aBitmapCloneSet[i] )
14701             this.aGraphicGroupElement.appendChild( aBitmapCloneSet[i] );
14702     }
14703     aAnimatableElement.appendChild( this.aGraphicGroupElement );
14704     aAnimatedElementGroup.appendChild( aAnimatableElement );
14706     this.aParentTextElement = aElement.parentNode;
14707     this.aParagraphElement = aElement;
14708     this.aAnimatedElementGroup = aAnimatedElementGroup;
14709     this.nRunningAnimations = 0;
14711     // we collect all hyperlink ids
14712     this.aHyperlinkIdSet = [];
14713     var aHyperlinkElementSet = getElementsByClassName( this.aParagraphElement, 'UrlField' );
14714     var sHyperlinkId;
14715     for( i = 0; i < aHyperlinkElementSet.length; ++i )
14716     {
14717         sHyperlinkId = aHyperlinkElementSet[i].getAttribute( 'id' );
14718         if( sHyperlinkId )
14719            this.aHyperlinkIdSet.push( sHyperlinkId );
14720         else
14721             log( 'error: AnimatedTextElement constructor: hyperlink element has no id' );
14722     }
14724     AnimatedTextElement.superclass.constructor.call( this, aAnimatableElement, aEventMultiplexer );
14727 extend( AnimatedTextElement, AnimatedElement );
14730 AnimatedTextElement.prototype.setToElement = function( aElement )
14732     var bRet = AnimatedTextElement.superclass.setToElement.call( this, aElement );
14733     if( bRet )
14734     {
14735         this.aGraphicGroupElement = getElementByClassName( this.aActiveElement, 'GraphicGroup' );
14736     }
14737     return ( bRet && this.aGraphicGroupElement );
14740 AnimatedTextElement.prototype.notifySlideStart = function( aSlideShowContext )
14742     DBGLOG( 'AnimatedTextElement.notifySlideStart' );
14743     AnimatedTextElement.superclass.notifySlideStart.call( this, aSlideShowContext );
14744     this.aGraphicGroupElement = getElementByClassName( this.aActiveElement, 'GraphicGroup' );
14745     this.restoreBaseTextParagraph();
14748 AnimatedTextElement.prototype.notifySlideEnd = function()
14750     DBGLOG( 'AnimatedTextElement.notifySlideEnd' );
14751     this.aGraphicGroupElement.setAttribute( 'visibility', 'inherit' );
14754 AnimatedTextElement.prototype.restoreBaseTextParagraph = function()
14756     var aActiveParagraphElement = this.aActiveElement.firstElementChild.firstElementChild;
14757     if( aActiveParagraphElement )
14758     {
14759         var sVisibilityAttr = this.aActiveElement.getAttribute( 'visibility' );
14760         if( !sVisibilityAttr || ( sVisibilityAttr === 'visible' ) )
14761             sVisibilityAttr = 'inherit';
14762         if( sVisibilityAttr === 'inherit' )
14763             this.aGraphicGroupElement.setAttribute( 'visibility', 'visible' );
14764         else
14765             this.aGraphicGroupElement.setAttribute( 'visibility', 'hidden' );
14767         var aParagraphClone = aActiveParagraphElement.cloneNode( true );
14768         aParagraphClone.setAttribute( 'id', this.sParagraphId );
14769         aParagraphClone.setAttribute( 'visibility', sVisibilityAttr );
14770         this.aParentTextElement.replaceChild( aParagraphClone, this.aParagraphElement );
14771         this.aParagraphElement = aParagraphClone;
14774         var aEventMultiplexer = this.aSlideShowContext.aEventMultiplexer;
14775         var aHyperlinkIdSet = this.aHyperlinkIdSet;
14776         var aHyperlinkElementSet = getElementsByClassName( this.aParagraphElement, 'UrlField' );
14777         var i = 0;
14778         for( ; i < aHyperlinkIdSet.length; ++i )
14779         {
14780             aEventMultiplexer.notifyElementChangedEvent( aHyperlinkIdSet[i], aHyperlinkElementSet[i] );
14781         }
14782     }
14783     this.aActiveElement.setAttribute( 'visibility', 'hidden' );
14786 AnimatedTextElement.prototype.notifyAnimationStart = function()
14788     DBGLOG( 'AnimatedTextElement.notifyAnimationStart' );
14789     if( this.nRunningAnimations === 0 )
14790     {
14791         var sVisibilityAttr = this.aParagraphElement.getAttribute( 'visibility' );
14792         if( !sVisibilityAttr )
14793             sVisibilityAttr = 'inherit';
14794         this.aActiveElement.setAttribute( 'visibility', sVisibilityAttr );
14795         this.aGraphicGroupElement.setAttribute( 'visibility', 'inherit' );
14796         this.aParagraphElement.setAttribute( 'visibility', 'hidden' );
14797     }
14798     ++this.nRunningAnimations;
14801 AnimatedTextElement.prototype.notifyAnimationEnd = function()
14803     DBGLOG( 'AnimatedTextElement.notifyAnimationEnd' );
14804     --this.nRunningAnimations;
14805     if( this.nRunningAnimations === 0 )
14806     {
14807         this.restoreBaseTextParagraph();
14808     }
14811 AnimatedTextElement.prototype.saveState = function( nAnimationNodeId )
14813     if( this.nRunningAnimations === 0 )
14814     {
14815         var sVisibilityAttr = this.aParagraphElement.getAttribute( 'visibility' );
14816         this.aActiveElement.setAttribute( 'visibility', sVisibilityAttr );
14817         this.aGraphicGroupElement.setAttribute( 'visibility', 'inherit' );
14818     }
14819     AnimatedTextElement.superclass.saveState.call( this, nAnimationNodeId );
14822 AnimatedTextElement.prototype.restoreState = function( nAnimationNodeId )
14824     var bRet = AnimatedTextElement.superclass.restoreState.call( this, nAnimationNodeId );
14825     if( bRet )
14826         this.restoreBaseTextParagraph();
14827     return bRet;
14833 /** Class SlideTransition
14834  *  This class is responsible for initializing the properties of a slide
14835  *  transition and create the object that actually will perform the transition.
14837  *  @param aAnimationsRootElement
14838  *      The <defs> element wrapping all animations for the related slide.
14839  *  @param aSlideId
14840  *      A string representing a slide id.
14841  */
14842 function SlideTransition( aAnimationsRootElement, aSlideId )
14844     this.sSlideId = aSlideId;
14845     this.bIsValid = false;
14846     this.eTransitionType = undefined;
14847     this.eTransitionSubType = undefined;
14848     this.bReverseDirection = false;
14849     this.eTransitionMode = TRANSITION_MODE_IN;
14850     this.sFadeColor = null;
14851     this.aDuration = null;
14852     this.nMinFrameCount = undefined;
14854     if( aAnimationsRootElement )
14855     {
14856         if( aAnimationsRootElement.firstElementChild &&
14857             ( aAnimationsRootElement.firstElementChild.getAttributeNS( NSS['smil'], 'begin' ) === (this.sSlideId + '.begin') ) )
14858         {
14859             var aTransitionFilterElement = aAnimationsRootElement.firstElementChild.firstElementChild;
14860             if( aTransitionFilterElement && ( aTransitionFilterElement.localName === 'transitionFilter' ) )
14861             {
14862                 this.aElement = aTransitionFilterElement;
14863                 this.parseElement();
14864             }
14865             aAnimationsRootElement.removeChild( aAnimationsRootElement.firstElementChild );
14866         }
14867     }
14870 SlideTransition.prototype.createSlideTransition = function( aLeavingSlide, aEnteringSlide )
14872     if( !this.isValid() )
14873         return null;
14874     if( this.eTransitionType == 0 )
14875         return null;
14877     if( !aEnteringSlide )
14878     {
14879         log( 'SlideTransition.createSlideTransition: invalid entering slide.' );
14880         return null;
14881     }
14883     var aTransitionInfo = aTransitionInfoTable[this.eTransitionType][this.eTransitionSubType];
14884     var eTransitionClass = aTransitionInfo['class'];
14886     switch( eTransitionClass )
14887     {
14888         default:
14889         case TRANSITION_INVALID:
14890             log( 'SlideTransition.createSlideTransition: transition class: TRANSITION_INVALID' );
14891             return null;
14893         case TRANSITION_CLIP_POLYPOLYGON:
14894             var aParametricPolyPolygon
14895                     = createClipPolyPolygon( this.eTransitionType, this.eTransitionSubType );
14896             return new ClippedSlideChange( aLeavingSlide, aEnteringSlide, aParametricPolyPolygon,
14897                                            aTransitionInfo, this.isDirectionForward() );
14899         case TRANSITION_SPECIAL:
14900             switch( this.eTransitionType )
14901             {
14902                 default:
14903                     log( 'SlideTransition.createSlideTransition: ' +
14904                          'transition class: TRANSITION_SPECIAL, ' +
14905                          'unknown transition type: ' + this.eTransitionType );
14906                     return null;
14908                 case PUSHWIPE_TRANSITION:
14909                 {
14910                     var aDirection = null;
14911                     switch( this.eTransitionSubType )
14912                     {
14913                         default:
14914                             log( 'SlideTransition.createSlideTransition: ' +
14915                                  'transition type: PUSHWIPE_TRANSITION, ' +
14916                                  'unknown transition subtype: ' + this.eTransitionSubType );
14917                             return null;
14918                         case FROMTOP_TRANS_SUBTYPE:
14919                             aDirection = { x: 0.0, y: 1.0 };
14920                             break;
14921                         case FROMBOTTOM_TRANS_SUBTYPE:
14922                             aDirection = { x: 0.0, y: -1.0 };
14923                             break;
14924                         case FROMLEFT_TRANS_SUBTYPE:
14925                             aDirection = { x: 1.0, y: 0.0 };
14926                             break;
14927                         case FROMRIGHT_TRANS_SUBTYPE:
14928                             aDirection = { x: -1.0, y: 0.0 };
14929                             break;
14930                     }
14931                     return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aDirection, aDirection );
14932                 }
14934                 case SLIDEWIPE_TRANSITION:
14935                 {
14936                     var aInDirection = null;
14937                     switch( this.eTransitionSubType )
14938                     {
14939                         default:
14940                             log( 'SlideTransition.createSlideTransition: ' +
14941                                  'transition type: SLIDEWIPE_TRANSITION, ' +
14942                                  'unknown transition subtype: ' + this.eTransitionSubType );
14943                             return null;
14944                         case FROMTOP_TRANS_SUBTYPE:
14945                             aInDirection = { x: 0.0, y: 1.0 };
14946                             break;
14947                         case FROMBOTTOM_TRANS_SUBTYPE:
14948                             aInDirection = { x: 0.0, y: -1.0 };
14949                             break;
14950                         case FROMLEFT_TRANS_SUBTYPE:
14951                             aInDirection = { x: 1.0, y: 0.0 };
14952                             break;
14953                         case FROMRIGHT_TRANS_SUBTYPE:
14954                             aInDirection = { x: -1.0, y: 0.0 };
14955                             break;
14956                     }
14957                     var aNoDirection = { x: 0.0, y: 0.0 };
14958                     if( !this.bReverseDirection )
14959                     {
14960                         return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aNoDirection, aInDirection );
14961                     }
14962                     else
14963                     {
14964                         return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aInDirection, aNoDirection );
14965                     }
14966                 }
14968                 case FADE_TRANSITION:
14969                     switch( this.eTransitionSubType )
14970                     {
14971                         default:
14972                             log( 'SlideTransition.createSlideTransition: ' +
14973                                  'transition type: FADE_TRANSITION, ' +
14974                                  'unknown transition subtype: ' + this.eTransitionSubType );
14975                             return null;
14976                         case CROSSFADE_TRANS_SUBTYPE:
14977                             return new FadingSlideChange( aLeavingSlide, aEnteringSlide );
14978                         case FADEOVERCOLOR_TRANS_SUBTYPE:
14979                             return new FadingOverColorSlideChange( aLeavingSlide, aEnteringSlide, this.getFadeColor() );
14980                     }
14981             }
14982     }
14985 SlideTransition.prototype.parseElement = function()
14987     this.bIsValid = true;
14988     var aAnimElem = this.aElement;
14990     // type attribute
14991     this.eTransitionType = undefined;
14992     var sTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'type' );
14993     if( sTypeAttr && aTransitionTypeInMap[ sTypeAttr ] )
14994     {
14995         this.eTransitionType = aTransitionTypeInMap[ sTypeAttr ];
14996     }
14997     else
14998     {
14999         this.bIsValid = false;
15000         log( 'SlideTransition.parseElement: transition type not valid: ' + sTypeAttr );
15001     }
15003     // subtype attribute
15004     this.eTransitionSubType = undefined;
15005     var sSubTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'subtype' );
15006     if( sSubTypeAttr === null )
15007         sSubTypeAttr = 'default';
15008     if( sSubTypeAttr && ( aTransitionSubtypeInMap[ sSubTypeAttr ] !== undefined ) )
15009     {
15010         this.eTransitionSubType = aTransitionSubtypeInMap[ sSubTypeAttr ];
15011     }
15012     else
15013     {
15014         this.bIsValid = false;
15015         log( 'SlideTransition.parseElement: transition subtype not valid: ' + sSubTypeAttr );
15016     }
15018     if( this.bIsValid && aTransitionInfoTable[this.eTransitionType][this.eTransitionSubType] === undefined )
15019     {
15020         this.bIsValid = false;
15021         log( 'SlideTransition.parseElement: transition not valid: type: ' + sTypeAttr + ' subtype: ' + sSubTypeAttr );
15022     }
15024     // direction attribute
15025     this.bReverseDirection = false;
15026     var sDirectionAttr = aAnimElem.getAttributeNS( NSS['smil'], 'direction' );
15027     if( sDirectionAttr == 'reverse' )
15028         this.bReverseDirection = true;
15030     // fade color
15031     this.sFadeColor = null;
15032     if( this.eTransitionType == FADE_TRANSITION &&
15033         ( this.eTransitionSubType == FADEFROMCOLOR_TRANS_SUBTYPE ||
15034           this.eTransitionSubType == FADEOVERCOLOR_TRANS_SUBTYPE ||
15035           this.eTransitionSubType == FADETOCOLOR_TRANS_SUBTYPE ) )
15036     {
15037         var sColorAttr = aAnimElem.getAttributeNS( NSS['smil'], 'fadeColor' );
15038         if( sColorAttr )
15039             this.sFadeColor = sColorAttr;
15040         else
15041             this.sFadeColor='#000000';
15042     }
15045     // dur attribute
15046     this.aDuration = null;
15047     var sDurAttr = aAnimElem.getAttributeNS( NSS['smil'], 'dur' );
15048     this.aDuration = new Duration( sDurAttr );
15049     if( !this.aDuration.isSet() )
15050     {
15051         this.aDuration = new Duration( null ); // duration == 0.0
15052     }
15054     // set up min frame count value;
15055     this.nMinFrameCount = ( this.getDuration().isValue() )
15056         ? ( this.getDuration().getValue() * MINIMUM_FRAMES_PER_SECONDS )
15057         : MINIMUM_FRAMES_PER_SECONDS;
15058     if( this.nMinFrameCount < 1.0 )
15059         this.nMinFrameCount = 1;
15060     else if( this.nMinFrameCount > MINIMUM_FRAMES_PER_SECONDS )
15061         this.nMinFrameCount = MINIMUM_FRAMES_PER_SECONDS;
15065 SlideTransition.prototype.isValid = function()
15067     return this.bIsValid;
15070 SlideTransition.prototype.getTransitionType = function()
15072     return this.eTransitionType;
15075 SlideTransition.prototype.getTransitionSubType = function()
15077     return this.eTransitionSubType;
15080 SlideTransition.prototype.getTransitionMode = function()
15082     return this.eTransitionMode;
15085 SlideTransition.prototype.getFadeColor = function()
15087     return this.sFadeColor;
15090 SlideTransition.prototype.isDirectionForward = function()
15092     return !this.bReverseDirection;
15095 SlideTransition.prototype.getDuration = function()
15097     return this.aDuration;
15100 SlideTransition.prototype.getMinFrameCount = function()
15102     return this.nMinFrameCount;
15105 SlideTransition.prototype.info = function()
15108     var sInfo ='slide transition <' + this.sSlideId + '>: ';
15109     // transition type
15110     sInfo += ';  type: ' + getKeyByValue(aTransitionTypeInMap, this.getTransitionType());
15112     // transition subtype
15113     sInfo += ';  subtype: ' + getKeyByValue(aTransitionSubtypeInMap, this.getTransitionSubType());
15115     // transition direction
15116     if( !this.isDirectionForward() )
15117         sInfo += ';  direction: reverse';
15119     // transition mode
15120     sInfo += '; mode: ' + aTransitionModeOutMap[ this.getTransitionMode() ];
15122     // duration
15123     if( this.getDuration() )
15124         sInfo += '; duration: ' + this.getDuration().info();
15126     return sInfo;
15132 // SlideAnimations
15134 function SlideAnimations( aSlideShowContext )
15136     this.aContext = new NodeContext( aSlideShowContext );
15137     this.aAnimationNodeMap = {};
15138     this.aAnimatedElementMap = {};
15139     this.aSourceEventElementMap = {};
15140     this.aNextEffectEventArray = new NextEffectEventArray();
15141     this.aInteractiveAnimationSequenceMap = {};
15142     this.aEventMultiplexer = new EventMultiplexer( aSlideShowContext.aTimerEventQueue );
15143     this.aRootNode = null;
15144     this.bElementsParsed = false;
15146     this.aContext.aAnimationNodeMap = this.aAnimationNodeMap;
15147     this.aContext.aAnimatedElementMap = this.aAnimatedElementMap;
15148     this.aContext.aSourceEventElementMap = this.aSourceEventElementMap;
15150     // We set up a low priority for the invocation of document.handleClick
15151     // in order to make clicks on shapes, that start interactive animation
15152     // sequence (on click), have an higher priority.
15153     this.aEventMultiplexer.registerMouseClickHandler( document, 100 );
15157 SlideAnimations.prototype.importAnimations = function( aAnimationRootElement )
15159     if( !aAnimationRootElement )
15160         return false;
15162     this.aRootNode = createAnimationTree( aAnimationRootElement, this.aContext );
15164     return ( this.aRootNode ? true : false );
15167 SlideAnimations.prototype.parseElements = function()
15169     if( !this.aRootNode )
15170         return false;
15172     // parse all nodes
15173     if( !this.aRootNode.parseElement() )
15174         return false;
15175     else
15176         this.bElementsParsed = true;
15179 SlideAnimations.prototype.elementsParsed = function()
15181     return this.bElementsParsed;
15184 SlideAnimations.prototype.isFirstRun = function()
15186     return this.aContext.bFirstRun;
15189 SlideAnimations.prototype.isAnimated = function()
15191     if( !this.bElementsParsed )
15192         return false;
15194     return this.aRootNode.hasPendingAnimation();
15197 SlideAnimations.prototype.start = function()
15199     if( !this.bElementsParsed )
15200         return false;
15202     this.chargeSourceEvents();
15203     this.chargeInterAnimEvents();
15205     aSlideShow.setSlideEvents( this.aNextEffectEventArray,
15206                                this.aInteractiveAnimationSequenceMap,
15207                                this.aEventMultiplexer );
15209     if( this.aContext.bFirstRun == undefined )
15210         this.aContext.bFirstRun = true;
15211     else if( this.aContext.bFirstRun )
15212         this.aContext.bFirstRun = false;
15214     // init all nodes
15215     if( !this.aRootNode.init() )
15216         return false;
15218     // resolve root node
15219     return this.aRootNode.resolve();
15222 SlideAnimations.prototype.end = function( bLeftEffectsSkipped )
15224     if( !this.bElementsParsed )
15225         return; // no animations there
15227     // end root node
15228     this.aRootNode.deactivate();
15229     this.aRootNode.end();
15231     if( bLeftEffectsSkipped && this.isFirstRun() )
15232     {
15233         // in case this is the first run and left events have been skipped
15234         // some next effect events for the slide could not be collected
15235         // so the next time we should behave as it was the first run again
15236         this.aContext.bFirstRun = undefined;
15237     }
15238     else if( this.isFirstRun() )
15239     {
15240         this.aContext.bFirstRun = false;
15241     }
15245 SlideAnimations.prototype.dispose = function()
15247     if( this.aRootNode )
15248     {
15249         this.aRootNode.dispose();
15250     }
15253 SlideAnimations.prototype.clearNextEffectEvents = function()
15255     ANIMDBG.print( 'SlideAnimations.clearNextEffectEvents: current slide: ' + nCurSlide );
15256     this.aNextEffectEventArray.clear();
15257     this.aContext.bFirstRun = undefined;
15260 SlideAnimations.prototype.chargeSourceEvents = function()
15262     for( var id in this.aSourceEventElementMap )
15263     {
15264         this.aSourceEventElementMap[id].charge();
15265     }
15268 SlideAnimations.prototype.chargeInterAnimEvents = function()
15270     for( var id in this.aInteractiveAnimationSequenceMap )
15271     {
15272         this.aInteractiveAnimationSequenceMap[id].chargeEvents();
15273     }
15276 /**********************************************************************************************
15277  *      Event classes and helper functions
15278  **********************************************************************************************/
15281 function Event()
15283     this.nId = Event.getUniqueId();
15287 Event.CURR_UNIQUE_ID = 0;
15289 Event.getUniqueId = function()
15291     ++Event.CURR_UNIQUE_ID;
15292     return Event.CURR_UNIQUE_ID;
15295 Event.prototype.getId = function()
15297     return this.nId;
15302 function DelayEvent( aFunctor, nTimeout )
15304     DelayEvent.superclass.constructor.call( this );
15306     this.aFunctor = aFunctor;
15307     this.nTimeout = nTimeout;
15308     this.bWasFired = false;
15310 extend( DelayEvent, Event );
15313 DelayEvent.prototype.fire = function()
15315     assert( this.isCharged(), 'DelayEvent.fire: assertion isCharged failed' );
15317     this.bWasFired = true;
15318     this.aFunctor();
15319     return true;
15322 DelayEvent.prototype.isCharged = function()
15324     return !this.bWasFired;
15327 DelayEvent.prototype.getActivationTime = function( nCurrentTime )
15329     return ( this.nTimeout + nCurrentTime );
15332 DelayEvent.prototype.dispose = function()
15334     // don't clear unconditionally, because it may currently be executed:
15335     if( this.isCharged() )
15336         this.bWasFired = true;
15339 DelayEvent.prototype.charge = function()
15341     if( !this.isCharged() )
15342         this.bWasFired = false;
15347 function WakeupEvent( aTimer, aActivityQueue )
15349     WakeupEvent.superclass.constructor.call( this );
15351     this.aTimer = new ElapsedTime( aTimer );
15352     this.nNextTime = 0.0;
15353     this.aActivity = null;
15354     this.aActivityQueue = aActivityQueue;
15356 extend( WakeupEvent, Event );
15359 WakeupEvent.prototype.clone = function()
15361     var aWakeupEvent = new WakeupEvent( this.aTimer.getTimeBase(), this.aActivityQueue );
15362     aWakeupEvent.nNextTime = this.nNextTime;
15363     aWakeupEvent.aActivity = this.aActivity;
15364     return aWakeupEvent;
15367 WakeupEvent.prototype.dispose = function()
15369     this.aActivity = null;
15372 WakeupEvent.prototype.fire = function()
15374     if( !this.aActivity )
15375         return false;
15377     return this.aActivityQueue.addActivity( this.aActivity );
15380 WakeupEvent.prototype.isCharged = function()
15382     // this event won't expire, we fire every time we're
15383     // re-inserted into the event queue.
15384     return true;
15387 WakeupEvent.prototype.getActivationTime = function( nCurrentTime )
15389     var nElapsedTime = this.aTimer.getElapsedTime();
15391     return Math.max( nCurrentTime, nCurrentTime - nElapsedTime + this.nNextTime );
15394 WakeupEvent.prototype.start = function()
15396     this.aTimer.reset();
15399 WakeupEvent.prototype.setNextTimeout = function( nNextTime )
15401     this.nNextTime = nNextTime;
15404 WakeupEvent.prototype.setActivity = function( aActivity )
15406     this.aActivity = aActivity;
15411 function makeEvent( aFunctor )
15413     return new DelayEvent( aFunctor, 0.0 );
15419 function makeDelay( aFunctor, nTimeout )
15421     return new DelayEvent( aFunctor, nTimeout );
15427 function registerEvent( nNodeId, aTiming, aEvent, aNodeContext )
15429     var aSlideShowContext = aNodeContext.aContext;
15430     var eTimingType = aTiming.getType();
15432     registerEvent.DBG( aTiming );
15434     if( eTimingType == OFFSET_TIMING )
15435     {
15436         aSlideShowContext.aTimerEventQueue.addEvent( aEvent );
15437     }
15438     else if ( aNodeContext.bFirstRun )
15439     {
15440         var aEventMultiplexer = aSlideShowContext.aEventMultiplexer;
15441         if( !aEventMultiplexer )
15442         {
15443             log( 'registerEvent: event multiplexer not initialized' );
15444             return;
15445         }
15446         var aNextEffectEventArray = aSlideShowContext.aNextEffectEventArray;
15447         if( !aNextEffectEventArray )
15448         {
15449             log( 'registerEvent: next effect event array not initialized' );
15450             return;
15451         }
15452         var aInteractiveAnimationSequenceMap =
15453             aSlideShowContext.aInteractiveAnimationSequenceMap;
15454         if( !aInteractiveAnimationSequenceMap )
15455         {
15456             log( 'registerEvent: interactive animation sequence map not initialized' );
15457             return;
15458         }
15460         switch( eTimingType )
15461         {
15462             case EVENT_TIMING:
15463                 var eEventType = aTiming.getEventType();
15464                 var sEventBaseElemId = aTiming.getEventBaseElementId();
15465                 if( sEventBaseElemId )
15466                 {
15467                     var aEventBaseElem = document.getElementById( sEventBaseElemId );
15468                     if( !aEventBaseElem )
15469                     {
15470                         log( 'generateEvent: EVENT_TIMING: event base element not found: ' + sEventBaseElemId );
15471                         return;
15472                     }
15473                     var aSourceEventElement = aNodeContext.makeSourceEventElement( sEventBaseElemId, aEventBaseElem );
15475                     if( !aInteractiveAnimationSequenceMap[ nNodeId ] )
15476                     {
15477                         aInteractiveAnimationSequenceMap[ nNodeId ] = new InteractiveAnimationSequence(nNodeId);
15478                     }
15480                     var bEventRegistered = false;
15481                     switch( eEventType )
15482                     {
15483                         case EVENT_TRIGGER_ON_CLICK:
15484                             aEventMultiplexer.registerEvent( eEventType, aSourceEventElement.getId(), aEvent );
15485                             aEventMultiplexer.registerRewindedEffectHandler( aSourceEventElement.getId(),
15486                                                                              bind2( aSourceEventElement.charge, aSourceEventElement ) );
15487                             bEventRegistered = true;
15488                             break;
15489                         default:
15490                             log( 'generateEvent: not handled event type: ' + eEventType );
15491                     }
15492                     if( bEventRegistered )
15493                     {
15494                         var aStartEvent = aInteractiveAnimationSequenceMap[ nNodeId ].getStartEvent();
15495                         var aEndEvent = aInteractiveAnimationSequenceMap[ nNodeId ].getEndEvent();
15496                         aEventMultiplexer.registerEvent( eEventType, aSourceEventElement.getId(), aStartEvent );
15497                         aEventMultiplexer.registerEvent( EVENT_TRIGGER_END_EVENT, nNodeId, aEndEvent );
15498                         aEventMultiplexer.registerRewindedEffectHandler(
15499                             nNodeId,
15500                             bind2( InteractiveAnimationSequence.prototype.chargeEvents,
15501                                    aInteractiveAnimationSequenceMap[ nNodeId ] )
15502                         );
15503                     }
15504                 }
15505                 else  // no base event element present
15506                 {
15507                     switch( eEventType )
15508                     {
15509                         case EVENT_TRIGGER_ON_NEXT_EFFECT:
15510                             aNextEffectEventArray.appendEvent( aEvent );
15511                             break;
15512                         default:
15513                             log( 'generateEvent: not handled event type: ' + eEventType );
15514                     }
15515                 }
15516                 break;
15517             case SYNCBASE_TIMING:
15518                 eEventType = aTiming.getEventType();
15519                 sEventBaseElemId = aTiming.getEventBaseElementId();
15520                 if( sEventBaseElemId )
15521                 {
15522                     var aAnimationNode = aNodeContext.aAnimationNodeMap[ sEventBaseElemId ];
15523                     if( !aAnimationNode )
15524                     {
15525                         log( 'generateEvent: SYNCBASE_TIMING: event base element not found: ' + sEventBaseElemId );
15526                         return;
15527                     }
15528                     aEventMultiplexer.registerEvent( eEventType, aAnimationNode.getId(), aEvent );
15529                 }
15530                 else
15531                 {
15532                     log( 'generateEvent: SYNCBASE_TIMING: event base element not specified' );
15533                 }
15534                 break;
15535             default:
15536                 log( 'generateEvent: not handled timing type: ' + eTimingType );
15537         }
15538     }
15541 registerEvent.DEBUG = aRegisterEventDebugPrinter.isEnabled();
15543 registerEvent.DBG = function( aTiming, nTime )
15545     if( registerEvent.DEBUG )
15546     {
15547         aRegisterEventDebugPrinter.print( 'registerEvent( timing: ' + aTiming.info() + ' )', nTime );
15548     }
15554 function SourceEventElement( sId, aElement, aEventMultiplexer )
15556     this.sId = sId;
15557     this.aElement = aElement;
15558     this.aEventMultiplexer = aEventMultiplexer;
15560     this.aEventMultiplexer.registerMouseClickHandler( this, 1000 );
15562     this.bClickHandled = false;
15563     this.bIsPointerOver = false;
15564     this.aElement.addEventListener( 'mouseover', bind2( SourceEventElement.prototype.onMouseEnter, this), false );
15565     this.aElement.addEventListener( 'mouseout', bind2( SourceEventElement.prototype.onMouseLeave, this), false );
15568 SourceEventElement.prototype.getId = function()
15570     return this.sId;
15573 SourceEventElement.prototype.onMouseEnter = function()
15575     this.bIsPointerOver = true;
15576     this.setPointerCursor();
15579 SourceEventElement.prototype.onMouseLeave = function()
15581     this.bIsPointerOver = false;
15582     this.setDefaultCursor();
15585 SourceEventElement.prototype.charge = function()
15587     this.bClickHandled = false;
15588     this.setPointerCursor();
15591 SourceEventElement.prototype.handleClick = function( /*aMouseEvent*/ )
15593     if( !this.bIsPointerOver ) return false;
15595     if( this.bClickHandled )
15596         return false;
15598     this.aEventMultiplexer.notifyEvent( EVENT_TRIGGER_ON_CLICK, this.getId() );
15599     aSlideShow.update();
15600     this.bClickHandled = true;
15601     this.setDefaultCursor();
15602     return true;
15605 SourceEventElement.prototype.setPointerCursor = function()
15607     if( this.bClickHandled )
15608         return;
15610     this.aElement.setAttribute( 'style', 'cursor: pointer' );
15613 SourceEventElement.prototype.setDefaultCursor = function()
15615     this.aElement.setAttribute( 'style', 'cursor: default' );
15620 function HyperlinkElement( sId, aEventMultiplexer )
15622     var aElement = document.getElementById( sId );
15623     if( !aElement )
15624     {
15625         log( 'error: HyperlinkElement: no element with id: <' + sId + '> found' );
15626         return;
15627     }
15628     if( !aEventMultiplexer )
15629     {
15630         log( 'AnimatedElement constructor: event multiplexer is not valid' );
15631     }
15633     this.sId = sId;
15634     this.aElement = aElement;
15635     this.aEventMultiplexer = aEventMultiplexer;
15636     this.nTargetSlideIndex = undefined;
15638     this.sURL = getNSAttribute( 'xlink', this.aElement, 'href' );
15639     if( this.sURL )
15640     {
15641         if( this.sURL[0] === '#' )
15642         {
15643             if( this.sURL.substr(1, 5) === 'Slide' )
15644             {
15645                 var sSlideIndex = this.sURL.split( ' ' )[1];
15646                 this.nTargetSlideIndex = parseInt( sSlideIndex ) - 1;
15647             }
15648         }
15650         this.aEventMultiplexer.registerElementChangedHandler( this.sId, bind2( HyperlinkElement.prototype.onElementChanged, this) );
15651         this.aEventMultiplexer.registerMouseClickHandler( this, 1100 );
15653         this.bIsPointerOver = false;
15654         this.mouseEnterHandler = bind2( HyperlinkElement.prototype.onMouseEnter, this);
15655         this.mouseLeaveHandler = bind2( HyperlinkElement.prototype.onMouseLeave, this);
15656         this.aElement.addEventListener( 'mouseover', this.mouseEnterHandler, false );
15657         this.aElement.addEventListener( 'mouseout', this.mouseLeaveHandler, false );
15658     }
15659     else
15660     {
15661         log( 'warning: HyperlinkElement(' + this.sId + '): url is empty' );
15662     }
15665 HyperlinkElement.prototype.onElementChanged = function( aElement )
15667     if( !aElement )
15668     {
15669         log( 'error: HyperlinkElement: passed element is not valid' );
15670         return;
15671     }
15673     if( this.sURL )
15674     {
15675         this.aElement.removeEventListener( 'mouseover', this.mouseEnterHandler, false );
15676         this.aElement.removeEventListener( 'mouseout', this.mouseLeaveHandler, false );
15677         this.aElement = aElement;
15678         this.aElement.addEventListener( 'mouseover', this.mouseEnterHandler, false );
15679         this.aElement.addEventListener( 'mouseout', this.mouseLeaveHandler, false );
15680     }
15683 HyperlinkElement.prototype.onMouseEnter = function()
15685     this.bIsPointerOver = true;
15686     this.setPointerCursor();
15689 HyperlinkElement.prototype.onMouseLeave = function()
15691     this.bIsPointerOver = false;
15692     this.setDefaultCursor();
15695 HyperlinkElement.prototype.handleClick = function( )
15697     if( !this.bIsPointerOver ) return false;
15699     if( this.nTargetSlideIndex !== undefined )
15700     {
15701         aSlideShow.displaySlide( this.nTargetSlideIndex, true );
15702     }
15703     else
15704     {
15705         var aWindowObject = document.defaultView;
15706         if( aWindowObject )
15707         {
15708             aWindowObject.open( this.sURL, this.sId );
15709         }
15710         else
15711         {
15712             log( 'error: HyperlinkElement.handleClick: invalid window object.' );
15713         }
15714     }
15716     return true;
15719 HyperlinkElement.prototype.setPointerCursor = function()
15721     if( this.bClickHandled )
15722         return;
15724     this.aElement.setAttribute( 'style', 'cursor: pointer' );
15727 HyperlinkElement.prototype.setDefaultCursor = function()
15729     this.aElement.setAttribute( 'style', 'cursor: default' );
15734 function InteractiveAnimationSequence( nId )
15736     this.nId = nId;
15737     this.bIsRunning = false;
15738     this.aStartEvent = null;
15739     this.aEndEvent = null;
15742 InteractiveAnimationSequence.prototype.getId = function()
15744     return this.nId;
15747 InteractiveAnimationSequence.prototype.getStartEvent = function()
15749     if( !this.aStartEvent )
15750     {
15751         this.aStartEvent =
15752             makeEvent( bind2( InteractiveAnimationSequence.prototype.start, this ) );
15753     }
15754     return this.aStartEvent;
15757 InteractiveAnimationSequence.prototype.getEndEvent = function()
15759     if( !this.aEndEvent )
15760     {
15761         this.aEndEvent =
15762             makeEvent( bind2( InteractiveAnimationSequence.prototype.end, this ) );
15763     }
15764     return this.aEndEvent;
15767 InteractiveAnimationSequence.prototype.chargeEvents = function()
15769     if( this.aStartEvent )      this.aStartEvent.charge();
15770     if( this.aEndEvent )        this.aEndEvent.charge();
15773 InteractiveAnimationSequence.prototype.isRunning = function()
15775     return this.bIsRunning;
15778 InteractiveAnimationSequence.prototype.start = function()
15780     aSlideShow.notifyInteractiveAnimationSequenceStart( this.getId() );
15781     this.bIsRunning = true;
15784 InteractiveAnimationSequence.prototype.end = function()
15786     aSlideShow.notifyInteractiveAnimationSequenceEnd( this.getId() );
15787     this.bIsRunning = false;
15791 /** class PriorityEntry
15792  *  It provides an entry type for priority queues.
15793  *  Higher is the value of nPriority higher is the priority of the created entry.
15795  *  @param aValue
15796  *      The object to be prioritized.
15797  *  @param nPriority
15798  *      An integral number representing the object priority.
15800  */
15801 function PriorityEntry( aValue, nPriority )
15803     this.aValue = aValue;
15804     this.nPriority = nPriority;
15807 /** EventEntry.compare
15808  *  Compare priority of two entries.
15810  *  @param aLhsEntry
15811  *      An instance of type PriorityEntry.
15812  *  @param aRhsEntry
15813  *      An instance of type PriorityEntry.
15814  *  @return Integer
15815  *      -1 if the left entry has lower priority of the right entry,
15816  *       1 if the left entry has higher priority of the right entry,
15817  *       0 if the two entry have the same priority
15818  */
15819 PriorityEntry.compare = function( aLhsEntry, aRhsEntry )
15821     if ( aLhsEntry.nPriority < aRhsEntry.nPriority )
15822     {
15823         return -1;
15824     }
15825     else if (aLhsEntry.nPriority > aRhsEntry.nPriority)
15826     {
15827         return 1;
15828     }
15829     else
15830     {
15831         return 0;
15832     }
15838 function EventMultiplexer( aTimerEventQueue )
15840     this.nId = EventMultiplexer.getUniqueId();
15841     this.aTimerEventQueue = aTimerEventQueue;
15842     this.aEventMap = {};
15843     this.aAnimationsEndHandler = null;
15844     this.aSkipEffectEndHandlerSet = [];
15845     this.aMouseClickHandlerSet = new PriorityQueue( PriorityEntry.compare );
15846     this.aSkipEffectEvent = null;
15847     this.aRewindCurrentEffectEvent = null;
15848     this.aRewindLastEffectEvent = null;
15849     this.aSkipInteractiveEffectEventSet = {};
15850     this.aRewindRunningInteractiveEffectEventSet = {};
15851     this.aRewindEndedInteractiveEffectEventSet = {};
15852     this.aRewindedEffectHandlerSet = {};
15853     this.aElementChangedHandlerSet = {};
15856 EventMultiplexer.CURR_UNIQUE_ID = 0;
15858 EventMultiplexer.getUniqueId = function()
15860     ++EventMultiplexer.CURR_UNIQUE_ID;
15861     return EventMultiplexer.CURR_UNIQUE_ID;
15864 EventMultiplexer.prototype.getId = function()
15866     return this.nId;
15869 EventMultiplexer.prototype.hasRegisteredMouseClickHandlers = function()
15871     return !this.aMouseClickHandlerSet.isEmpty();
15874 EventMultiplexer.prototype.registerMouseClickHandler = function( aHandler, nPriority )
15876     var aHandlerEntry = new PriorityEntry( aHandler, nPriority );
15877     this.aMouseClickHandlerSet.push( aHandlerEntry );
15880 EventMultiplexer.prototype.notifyMouseClick = function( aMouseEvent )
15882     var aMouseClickHandlerSet = this.aMouseClickHandlerSet.clone();
15883     while( !aMouseClickHandlerSet.isEmpty() )
15884     {
15885         var aHandlerEntry = aMouseClickHandlerSet.top();
15886         aMouseClickHandlerSet.pop();
15887         if( aHandlerEntry.aValue.handleClick( aMouseEvent ) )
15888             break;
15889     }
15892 EventMultiplexer.prototype.registerEvent = function( eEventType, aNotifierId, aEvent )
15894     this.DBG( 'registerEvent', eEventType, aNotifierId );
15895     if( !this.aEventMap[ eEventType ] )
15896     {
15897         this.aEventMap[ eEventType ] = {};
15898     }
15899     if( !this.aEventMap[ eEventType ][ aNotifierId ] )
15900     {
15901         this.aEventMap[ eEventType ][ aNotifierId ] = [];
15902     }
15903     this.aEventMap[ eEventType ][ aNotifierId ].push( aEvent );
15907 EventMultiplexer.prototype.notifyEvent = function( eEventType, aNotifierId )
15909     this.DBG( 'notifyEvent', eEventType, aNotifierId );
15910     if( this.aEventMap[ eEventType ] )
15911     {
15912         if( this.aEventMap[ eEventType ][ aNotifierId ] )
15913         {
15914             var aEventArray = this.aEventMap[ eEventType ][ aNotifierId ];
15915             var nSize = aEventArray.length;
15916             for( var i = 0; i < nSize; ++i )
15917             {
15918                 this.aTimerEventQueue.addEvent( aEventArray[i] );
15919             }
15920         }
15921     }
15924 EventMultiplexer.prototype.registerAnimationsEndHandler = function( aHandler )
15926     this.aAnimationsEndHandler = aHandler;
15929 EventMultiplexer.prototype.notifyAnimationsEndEvent = function()
15931     if( this.aAnimationsEndHandler )
15932         this.aAnimationsEndHandler();
15935 EventMultiplexer.prototype.registerNextEffectEndHandler = function( aHandler )
15937     this.aSkipEffectEndHandlerSet.push( aHandler );
15940 EventMultiplexer.prototype.notifyNextEffectEndEvent = function()
15942     var nSize = this.aSkipEffectEndHandlerSet.length;
15943     for( var i = 0; i < nSize; ++i )
15944     {
15945         (this.aSkipEffectEndHandlerSet[i])();
15946     }
15947     this.aSkipEffectEndHandlerSet = [];
15950 EventMultiplexer.prototype.registerSkipEffectEvent = function( aEvent )
15952     this.aSkipEffectEvent = aEvent;
15955 EventMultiplexer.prototype.notifySkipEffectEvent = function()
15957     if( this.aSkipEffectEvent )
15958     {
15959         this.aTimerEventQueue.addEvent( this.aSkipEffectEvent );
15960         this.aSkipEffectEvent = null;
15961     }
15964 EventMultiplexer.prototype.registerRewindCurrentEffectEvent = function( aEvent )
15966     this.aRewindCurrentEffectEvent = aEvent;
15969 EventMultiplexer.prototype.notifyRewindCurrentEffectEvent = function()
15971     if( this.aRewindCurrentEffectEvent )
15972     {
15973         this.aTimerEventQueue.addEvent( this.aRewindCurrentEffectEvent );
15974         this.aRewindCurrentEffectEvent = null;
15975     }
15978 EventMultiplexer.prototype.registerRewindLastEffectEvent = function( aEvent )
15980     this.aRewindLastEffectEvent = aEvent;
15983 EventMultiplexer.prototype.notifyRewindLastEffectEvent = function()
15985     if( this.aRewindLastEffectEvent )
15986     {
15987         this.aTimerEventQueue.addEvent( this.aRewindLastEffectEvent );
15988         this.aRewindLastEffectEvent = null;
15989     }
15992 EventMultiplexer.prototype.registerSkipInteractiveEffectEvent = function( nNotifierId, aEvent )
15994     this.aSkipInteractiveEffectEventSet[ nNotifierId ] = aEvent;
15997 EventMultiplexer.prototype.notifySkipInteractiveEffectEvent = function( nNotifierId )
15999     if( this.aSkipInteractiveEffectEventSet[ nNotifierId ] )
16000     {
16001         this.aTimerEventQueue.addEvent( this.aSkipInteractiveEffectEventSet[ nNotifierId ] );
16002     }
16005 EventMultiplexer.prototype.registerRewindRunningInteractiveEffectEvent = function( nNotifierId, aEvent )
16007     this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] = aEvent;
16010 EventMultiplexer.prototype.notifyRewindRunningInteractiveEffectEvent = function( nNotifierId )
16012     if( this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] )
16013     {
16014         this.aTimerEventQueue.addEvent( this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] );
16015     }
16018 EventMultiplexer.prototype.registerRewindEndedInteractiveEffectEvent = function( nNotifierId, aEvent )
16020     this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] = aEvent;
16023 EventMultiplexer.prototype.notifyRewindEndedInteractiveEffectEvent = function( nNotifierId )
16025     if( this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] )
16026     {
16027         this.aTimerEventQueue.addEvent( this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] );
16028     }
16031 EventMultiplexer.prototype.registerRewindedEffectHandler = function( aNotifierId, aHandler )
16033     this.aRewindedEffectHandlerSet[ aNotifierId ] = aHandler;
16036 EventMultiplexer.prototype.notifyRewindedEffectEvent = function( aNotifierId )
16038     if( this.aRewindedEffectHandlerSet[ aNotifierId ] )
16039     {
16040         (this.aRewindedEffectHandlerSet[ aNotifierId ])();
16041     }
16044 EventMultiplexer.prototype.registerElementChangedHandler = function( aNotifierId, aHandler )
16046     this.aElementChangedHandlerSet[ aNotifierId ] = aHandler;
16049 EventMultiplexer.prototype.notifyElementChangedEvent = function( aNotifierId, aElement )
16051     if( this.aElementChangedHandlerSet[ aNotifierId ] )
16052     {
16053         (this.aElementChangedHandlerSet[ aNotifierId ])( aElement );
16054     }
16057 EventMultiplexer.DEBUG = aEventMultiplexerDebugPrinter.isEnabled();
16059 EventMultiplexer.prototype.DBG = function( sMethodName, eEventType, aNotifierId, nTime )
16061     if( EventMultiplexer.DEBUG )
16062     {
16063         var sInfo = 'EventMultiplexer.' + sMethodName;
16064         sInfo += '( type: ' + aEventTriggerOutMap[ eEventType ];
16065         sInfo += ', notifier: ' + aNotifierId + ' )';
16066         aEventMultiplexerDebugPrinter.print( sInfo, nTime );
16067     }
16072 /**********************************************************************************************
16073  *      Interpolator Handler and KeyStopLerp
16074  **********************************************************************************************/
16076 var aInterpolatorHandler = {};
16078 aInterpolatorHandler.getInterpolator = function( eCalcMode, eValueType, eValueSubtype )
16080     var bHasSubtype = ( typeof( eValueSubtype ) === typeof( 0 ) );
16082     if( !bHasSubtype && aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ] )
16083     {
16084         return aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ];
16085     }
16086     else if( bHasSubtype && aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ][ eValueSubtype ] )
16087     {
16088         return aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ][ eValueSubtype ];
16089     }
16090     else
16091     {
16092         log( 'aInterpolatorHandler.getInterpolator: not found any valid interpolator for calc mode '
16093              + aCalcModeOutMap[eCalcMode]  + ' and value type ' + aValueTypeOutMap[eValueType]  );
16094         return null;
16095     }
16098 aInterpolatorHandler.aLerpFunctorMap = [];
16099 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_DISCRETE ] = [];
16100 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ] = [];
16103 // interpolators for linear calculation
16105 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ NUMBER_PROPERTY ] =
16106     function ( nFrom, nTo, nT )
16107     {
16108         return ( ( 1.0 - nT )* nFrom + nT * nTo );
16109     };
16111 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ COLOR_PROPERTY ] = [];
16113 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ COLOR_PROPERTY ][ COLOR_SPACE_RGB ] =
16114     function ( nFrom, nTo, nT )
16115     {
16116         return RGBColor.interpolate( nFrom, nTo, nT );
16117     };
16119 // For HSLColor we do not return the interpolator but a function
16120 // that generate the interpolator. The AnimationColorNode is 'aware' of that.
16121 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ COLOR_PROPERTY ][ COLOR_SPACE_HSL ] =
16122     function ( bCCW  )
16123     {
16124         return  function ( nFrom, nTo, nT )
16125                 {
16126                     return HSLColor.interpolate( nFrom, nTo, nT, bCCW );
16127                 };
16128     };
16133 function KeyStopLerp( aValueList )
16135     KeyStopLerp.validateInput( aValueList );
16137     this.aKeyStopList = [];
16138     this.nLastIndex = 0;
16139     this.nKeyStopDistance = aValueList[1] - aValueList[0];
16140     if( this.nKeyStopDistance <= 0 )
16141         this.nKeyStopDistance = 0.001;
16143     for( var i = 0; i < aValueList.length; ++i )
16144         this.aKeyStopList.push( aValueList[i] );
16146     this.nUpperBoundIndex = this.aKeyStopList.length - 2;
16150 KeyStopLerp.validateInput = function( aValueList )
16152     var nSize = aValueList.length;
16153     assert( nSize > 1, 'KeyStopLerp.validateInput: key stop vector must have two entries or more' );
16155     for( var i = 1; i < nSize; ++i )
16156     {
16157         if( aValueList[i-1] > aValueList[i] )
16158             log( 'KeyStopLerp.validateInput: time vector is not sorted in ascending order!' );
16159     }
16162 KeyStopLerp.prototype.reset = function()
16164     KeyStopLerp.validateInput( this.aKeyStopList );
16165     this.nLastIndex = 0;
16166     this.nKeyStopDistance = this.aKeyStopList[1] - this.aKeyStopList[0];
16167     if( this.nKeyStopDistance <= 0 )
16168         this.nKeyStopDistance = 0.001;
16172 KeyStopLerp.prototype.lerp = function( nAlpha )
16174     if( nAlpha > this.aKeyStopList[ this.nLastIndex + 1 ] )
16175     {
16176         do
16177         {
16178             var nIndex = this.nLastIndex + 1;
16179             this.nLastIndex = clamp( nIndex, 0, this.nUpperBoundIndex );
16180             this.nKeyStopDistance = this.aKeyStopList[ this.nLastIndex + 1 ] - this.aKeyStopList[ this.nLastIndex ];
16181         }
16182         while( ( this.nKeyStopDistance <= 0 ) && ( this.nLastIndex < this.nUpperBoundIndex ) );
16183     }
16185     var nRawLerp = ( nAlpha - this.aKeyStopList[ this.nLastIndex ] ) / this.nKeyStopDistance;
16187     nRawLerp = clamp( nRawLerp, 0.0, 1.0 );
16189     var aResult = {};
16190     aResult.nIndex = this.nLastIndex;
16191     aResult.nLerp = nRawLerp;
16193     return aResult;
16196 KeyStopLerp.prototype.lerp_ported = function( nAlpha )
16198     if( ( this.aKeyStopList[ this.nLastIndex ] < nAlpha ) ||
16199         ( this.aKeyStopList[ this.nLastIndex + 1 ] >= nAlpha ) )
16200     {
16201         var i = 0;
16202         for( ; i < this.aKeyStopList.length; ++i )
16203         {
16204             if( this.aKeyStopList[i] >= nAlpha )
16205                 break;
16206         }
16207         if( this.aKeyStopList[i] > nAlpha )
16208             --i;
16209         var nIndex = i - 1;
16210         this.nLastIndex = clamp( nIndex, 0, this.aKeyStopList.length - 2 );
16211     }
16213     var nRawLerp = ( nAlpha - this.aKeyStopList[ this.nLastIndex ] ) /
16214                        ( this.aKeyStopList[ this.nLastIndex+1 ] - this.aKeyStopList[ this.nLastIndex ] );
16216     nRawLerp = clamp( nRawLerp, 0.0, 1.0 );
16218     var aResult = {};
16219     aResult.nIndex = this.nLastIndex;
16220     aResult.nLerp = nRawLerp;
16222     return aResult;
16227 /**********************************************************************************************
16228  *      Operators
16229  **********************************************************************************************/
16231 var aOperatorSetMap = [];
16233 // number operators
16234 aOperatorSetMap[ NUMBER_PROPERTY ] = {};
16236 aOperatorSetMap[ NUMBER_PROPERTY ].equal = function( a, b )
16238     return ( a === b );
16241 aOperatorSetMap[ NUMBER_PROPERTY ].add = function( a, b )
16243     return ( a + b );
16246 aOperatorSetMap[ NUMBER_PROPERTY ].scale = function( k, v )
16248     return ( k * v );
16251 // color operators
16252 aOperatorSetMap[ COLOR_PROPERTY ] = {};
16254 aOperatorSetMap[ COLOR_PROPERTY ].equal = function( a, b )
16256     return a.equal( b );
16259 aOperatorSetMap[ COLOR_PROPERTY ].add = function( a, b )
16261     var c = a.clone();
16262     c.add( b );
16263     return c;
16266 aOperatorSetMap[ COLOR_PROPERTY ].scale = function( k, v )
16268     var r = v.clone();
16269     r.scale( k );
16270     return r;
16273 // enum operators
16274 aOperatorSetMap[ ENUM_PROPERTY ] = {};
16276 aOperatorSetMap[ ENUM_PROPERTY ].equal = function( a, b )
16278     return ( a === b );
16281 aOperatorSetMap[ ENUM_PROPERTY ].add = function( a )
16283     return a;
16286 aOperatorSetMap[ ENUM_PROPERTY ].scale = function( k, v )
16288     return v;
16291 // string operators
16292 aOperatorSetMap[ STRING_PROPERTY ] = aOperatorSetMap[ ENUM_PROPERTY ];
16294 // bool operators
16295 aOperatorSetMap[ BOOL_PROPERTY ] = aOperatorSetMap[ ENUM_PROPERTY ];
16300 /**********************************************************************************************
16301  *      Activity Class Hierarchy
16302  **********************************************************************************************/
16305 function ActivityParamSet()
16307     this.aEndEvent = null;
16308     this.aWakeupEvent = null;
16309     this.aTimerEventQueue = null;
16310     this.aActivityQueue = null;
16311     this.nMinDuration = undefined;
16312     this.nMinNumberOfFrames = MINIMUM_FRAMES_PER_SECONDS;
16313     this.bAutoReverse = false;
16314     this.nRepeatCount = 1.0;
16315     this.nAccelerationFraction = 0.0;
16316     this.nDecelerationFraction = 0.0;
16317     this.nSlideWidth = undefined;
16318     this.nSlideHeight = undefined;
16319     this.aFormula = null;
16320     this.aDiscreteTimes = [];
16324 function AnimationActivity()
16326     this.nId = AnimationActivity.getUniqueId();
16330 AnimationActivity.CURR_UNIQUE_ID = 0;
16332 AnimationActivity.getUniqueId = function()
16334     ++AnimationActivity.CURR_UNIQUE_ID;
16335     return AnimationActivity.CURR_UNIQUE_ID;
16338 AnimationActivity.prototype.getId = function()
16340     return this.nId;
16346 function SetActivity( aCommonParamSet, aAnimation, aToAttr  )
16348     SetActivity.superclass.constructor.call( this );
16350     this.aAnimation = aAnimation;
16351     this.aTargetElement = null;
16352     this.aEndEvent = aCommonParamSet.aEndEvent;
16353     this.aTimerEventQueue = aCommonParamSet.aTimerEventQueue;
16354     this.aToAttr = aToAttr;
16355     this.bIsActive = true;
16357 extend( SetActivity, AnimationActivity );
16360 SetActivity.prototype.activate = function( aEndEvent )
16362     this.aEndEvent = aEndEvent;
16363     this.bIsActive = true;
16366 SetActivity.prototype.dispose = function()
16368     this.bIsActive = false;
16369     if( this.aEndEvent && this.aEndEvent.isCharged() )
16370         this.aEndEvent.dispose();
16373 SetActivity.prototype.calcTimeLag = function()
16375     return 0.0;
16378 SetActivity.prototype.perform = function()
16380     if( !this.isActive() )
16381         return false;
16383     // we're going inactive immediately:
16384     this.bIsActive = false;
16386     if( this.aAnimation && this.aTargetElement )
16387     {
16388         this.aAnimation.start( this.aTargetElement );
16389         this.aAnimation.perform( this.aToAttr );
16390         this.aAnimation.end();
16391     }
16393     if( this.aEndEvent )
16394         this.aTimerEventQueue.addEvent( this.aEndEvent );
16398 SetActivity.prototype.isActive = function()
16400     return this.bIsActive;
16403 SetActivity.prototype.dequeued = function()
16405     // empty body
16408 SetActivity.prototype.end = function()
16410     this.perform();
16413 SetActivity.prototype.setTargets = function( aTargetElement )
16415     assert( aTargetElement, 'SetActivity.setTargets: target element is not valid' );
16416     this.aTargetElement = aTargetElement;
16422 function ActivityBase( aCommonParamSet )
16424     ActivityBase.superclass.constructor.call( this );
16426     this.aTargetElement = null;
16427     this.aEndEvent = aCommonParamSet.aEndEvent;
16428     this.aTimerEventQueue = aCommonParamSet.aTimerEventQueue;
16429     this.nRepeats = aCommonParamSet.nRepeatCount;
16430     this.nAccelerationFraction = aCommonParamSet.nAccelerationFraction;
16431     this.nDecelerationFraction = aCommonParamSet.nDecelerationFraction;
16432     this.bAutoReverse = aCommonParamSet.bAutoReverse;
16434     this.bFirstPerformCall = true;
16435     this.bIsActive = true;
16438 extend( ActivityBase, AnimationActivity );
16441 ActivityBase.prototype.activate = function( aEndEvent )
16443     this.aEndEvent = aEndEvent;
16444     this.bFirstPerformCall = true;
16445     this.bIsActive = true;
16448 ActivityBase.prototype.dispose = function()
16450     // deactivate
16451     this.bIsActive = false;
16453     // dispose event
16454     if( this.aEndEvent )
16455         this.aEndEvent.dispose();
16457     this.aEndEvent = null;
16460 ActivityBase.prototype.perform = function()
16462     // still active?
16463     if( !this.isActive() )
16464         return false; // no, early exit.
16466     assert( !this.bFirstPerformCall, 'ActivityBase.perform: assertion (!this.FirstPerformCall) failed' );
16468     return true;
16471 ActivityBase.prototype.calcTimeLag = function()
16473     // TODO(Q1): implement different init process!
16474     if( this.isActive() && this.bFirstPerformCall )
16475     {
16476         this.bFirstPerformCall = false;
16478         // notify derived classes that we're
16479         // starting now
16480         this.startAnimation();
16481     }
16482     return 0.0;
16485 ActivityBase.prototype.isActive = function()
16487     return this.bIsActive;
16490 ActivityBase.prototype.isDisposed = function()
16492     return ( !this.bIsActive && !this.aEndEvent );
16495 ActivityBase.prototype.dequeued = function()
16497     if( !this.isActive() )
16498         this.endAnimation();
16501 ActivityBase.prototype.setTargets = function( aTargetElement )
16503     assert( aTargetElement, 'ActivityBase.setTargets: target element is not valid' );
16505     this.aTargetElement = aTargetElement;
16508 ActivityBase.prototype.startAnimation = function()
16510     throw ( 'ActivityBase.startAnimation: abstract method invoked' );
16513 ActivityBase.prototype.endAnimation = function()
16515     throw ( 'ActivityBase.endAnimation: abstract method invoked' );
16518 ActivityBase.prototype.endActivity = function()
16520     // this is a regular activity end
16521     this.bIsActive = false;
16523     // Activity is ending, queue event, then
16524     if( this.aEndEvent )
16525         this.aTimerEventQueue.addEvent( this.aEndEvent );
16527     this.aEndEvent = null;
16531 ActivityBase.prototype.calcAcceleratedTime = function( nT )
16533     // Handle acceleration/deceleration
16536     // clamp nT to permissible [0,1] range
16537     nT = clamp( nT, 0.0, 1.0 );
16539     // take acceleration/deceleration into account. if the sum
16540     // of nAccelerationFraction and nDecelerationFraction
16541     // exceeds 1.0, ignore both (that's according to SMIL spec)
16542     if( ( this.nAccelerationFraction > 0.0 || this.nDecelerationFraction > 0.0 ) &&
16543         ( this.nAccelerationFraction + this.nDecelerationFraction <= 1.0 ) )
16544     {
16545         var nC = 1.0 - 0.5*this.nAccelerationFraction - 0.5*this.nDecelerationFraction;
16547         // this variable accumulates the new time value
16548         var nTPrime = 0.0;
16550         if( nT < this.nAccelerationFraction )
16551         {
16552             nTPrime += 0.5 * nT * nT / this.nAccelerationFraction; // partial first interval
16553         }
16554         else
16555         {
16556             nTPrime += 0.5 * this.nAccelerationFraction; // full first interval
16558             if( nT <= ( 1.0 - this.nDecelerationFraction ) )
16559             {
16560                 nTPrime += nT - this.nAccelerationFraction; // partial second interval
16561             }
16562             else
16563             {
16564                 nTPrime += 1.0 - this.nAccelerationFraction - this.nDecelerationFraction; // full second interval
16566                 var nTRelative = nT - 1.0 + this.nDecelerationFraction;
16568                 nTPrime += nTRelative - 0.5*nTRelative*nTRelative / this.nDecelerationFraction;
16569             }
16570         }
16572         // normalize, and assign to work variable
16573         nT = nTPrime / nC;
16575     }
16576     return nT;
16579 ActivityBase.prototype.getEventQueue = function()
16581     return this.aTimerEventQueue;
16584 ActivityBase.prototype.getTargetElement = function()
16586     return this.aTargetElement;
16589 ActivityBase.prototype.isRepeatCountValid = function()
16591     return !!this.nRepeats; // first ! convert to bool
16594 ActivityBase.prototype.getRepeatCount = function()
16596     return this.nRepeats;
16599 ActivityBase.prototype.isAutoReverse = function()
16601     return this.bAutoReverse;
16604 ActivityBase.prototype.end = function()
16606     if( !this.isActive() || this.isDisposed() )
16607         return;
16609     // assure animation is started:
16610     if( this.bFirstPerformCall )
16611     {
16612         this.bFirstPerformCall = false;
16613         // notify derived classes that we're starting now
16614         this.startAnimation();
16615     }
16617     this.performEnd();
16618     this.endAnimation();
16619     this.endActivity();
16622 ActivityBase.prototype.performEnd = function()
16624     throw ( 'ActivityBase.performEnd: abstract method invoked' );
16630 function DiscreteActivityBase( aCommonParamSet )
16632     DiscreteActivityBase.superclass.constructor.call( this, aCommonParamSet );
16634     this.aOriginalWakeupEvent = aCommonParamSet.aWakeupEvent;
16635     this.aOriginalWakeupEvent.setActivity( this );
16636     this.aWakeupEvent = this.aOriginalWakeupEvent;
16637     this.aWakeupEvent = aCommonParamSet.aWakeupEvent;
16638     this.aDiscreteTimes = aCommonParamSet.aDiscreteTimes;
16639     // Simple duration of activity
16640     this.nMinSimpleDuration = aCommonParamSet.nMinDuration;
16641     // Actual number of frames shown until now.
16642     this.nCurrPerformCalls = 0;
16644 extend( DiscreteActivityBase, ActivityBase );
16647 DiscreteActivityBase.prototype.activate = function( aEndElement )
16649     DiscreteActivityBase.superclass.activate.call( this, aEndElement );
16651     this.aWakeupEvent = this.aOriginalWakeupEvent;
16652     this.aWakeupEvent.setNextTimeout( 0 );
16653     this.nCurrPerformCalls = 0;
16656 DiscreteActivityBase.prototype.startAnimation = function()
16658     this.aWakeupEvent.start();
16661 DiscreteActivityBase.prototype.calcFrameIndex = function( nCurrCalls, nVectorSize )
16663     if( this.isAutoReverse() )
16664     {
16665         // every full repeat run consists of one
16666         // forward and one backward traversal.
16667         var nFrameIndex = nCurrCalls % (2 * nVectorSize);
16669         // nFrameIndex values >= nVectorSize belong to
16670         // the backward traversal
16671         if( nFrameIndex >= nVectorSize )
16672             nFrameIndex = 2*nVectorSize - nFrameIndex; // invert sweep
16674         return nFrameIndex;
16675     }
16676     else
16677     {
16678         return nCurrCalls % nVectorSize;
16679     }
16682 DiscreteActivityBase.prototype.calcRepeatCount = function( nCurrCalls, nVectorSize )
16684     if( this.isAutoReverse() )
16685     {
16686         return Math.floor( nCurrCalls / (2*nVectorSize) ); // we've got 2 cycles per repeat
16687     }
16688     else
16689     {
16690         return Math.floor( nCurrCalls / nVectorSize );
16691     }
16694 DiscreteActivityBase.prototype.performDiscreteHook = function( /*nFrame, nRepeatCount*/ )
16696     throw ( 'DiscreteActivityBase.performDiscreteHook: abstract method invoked' );
16699 DiscreteActivityBase.prototype.perform = function()
16701     // call base class, for start() calls and end handling
16702     if( !SimpleContinuousActivityBase.superclass.perform.call( this ) )
16703         return false; // done, we're ended
16705     var nVectorSize = this.aDiscreteTimes.length;
16707     var nFrameIndex = this.calcFrameIndex(this.nCurrPerformCalls, nVectorSize);
16708     var nRepeatCount = this.calcRepeatCount( this.nCurrPerformCalls, nVectorSize );
16709     this.performDiscreteHook( nFrameIndex, nRepeatCount );
16711     // one more frame successfully performed
16712     ++this.nCurrPerformCalls;
16714     // calc currently reached repeat count
16715     var nCurrRepeat = this.nCurrPerformCalls / nVectorSize;
16717     // if auto-reverse is specified, halve the
16718     // effective repeat count, since we pass every
16719     // repeat run twice: once forward, once backward.
16720     if( this.isAutoReverse() )
16721         nCurrRepeat /= 2;
16723     // schedule next frame, if either repeat is indefinite
16724     // (repeat forever), or we've not yet reached the requested
16725     // repeat count
16726     if( !this.isRepeatCountValid() || nCurrRepeat < this.getRepeatCount() )
16727     {
16728         // add wake-up event to queue (modulo vector size, to cope with repeats).
16730         // repeat is handled locally, only apply acceleration/deceleration.
16731         // Scale time vector with simple duration, offset with full repeat
16732         // times.
16734         // Note that calcAcceleratedTime() is only applied to the current repeat's value,
16735         // not to the total resulting time. This is in accordance with the SMIL spec.
16737         nFrameIndex = this.calcFrameIndex(this.nCurrPerformCalls, nVectorSize);
16738         var nCurrentRepeatTime = this.aDiscreteTimes[nFrameIndex];
16739         nRepeatCount = this.calcRepeatCount( this.nCurrPerformCalls, nVectorSize );
16740         var nNextTimeout = this.nMinSimpleDuration * ( nRepeatCount + this.calcAcceleratedTime( nCurrentRepeatTime ) );
16741         this.aWakeupEvent.setNextTimeout( nNextTimeout );
16743         this.getEventQueue().addEvent( this.aWakeupEvent );
16744     }
16745     else
16746     {
16747         // release event reference (relation to wake up event is circular!)
16748         this.aWakeupEvent = null;
16750         // done with this activity
16751         this.endActivity();
16752     }
16754     return false; // remove from queue, will be added back by the wakeup event.
16757 DiscreteActivityBase.prototype.dispose = function()
16759     // dispose event
16760     if( this.aWakeupEvent )
16761         this.aWakeupEvent.dispose();
16763     // release references
16764     this.aWakeupEvent = null;
16766     DiscreteActivityBase.superclass.dispose.call(this);
16772 function SimpleContinuousActivityBase( aCommonParamSet )
16774     SimpleContinuousActivityBase.superclass.constructor.call( this, aCommonParamSet );
16776     // Time elapsed since activity started
16777     this.aTimer = new ElapsedTime( aCommonParamSet.aActivityQueue.getTimer() );
16778     // Simple duration of activity
16779     this.nMinSimpleDuration = aCommonParamSet.nMinDuration;
16780     // Minimal number of frames to show
16781     this.nMinNumberOfFrames = aCommonParamSet.nMinNumberOfFrames;
16782     // Actual number of frames shown until now.
16783     this.nCurrPerformCalls = 0;
16786 extend( SimpleContinuousActivityBase, ActivityBase );
16789 SimpleContinuousActivityBase.prototype.startAnimation = function()
16791     // init timer. We measure animation time only when we're
16792     // actually started.
16793     this.aTimer.reset();
16796 SimpleContinuousActivityBase.prototype.calcTimeLag = function()
16798     SimpleContinuousActivityBase.superclass.calcTimeLag.call( this );
16800     if( !this.isActive() )
16801         return 0.0;
16803     // retrieve locally elapsed time
16804     var nCurrElapsedTime = this.aTimer.getElapsedTime();
16806     // go to great length to ensure a proper animation
16807     // run. Since we don't know how often we will be called
16808     // here, try to spread the animator calls uniquely over
16809     // the [0,1] parameter range. Be aware of the fact that
16810     // perform will be called at least mnMinNumberOfTurns
16811     // times.
16813     // fraction of time elapsed
16814     var nFractionElapsedTime = nCurrElapsedTime / this.nMinSimpleDuration;
16816     // fraction of minimum calls performed
16817     var nFractionRequiredCalls = this.nCurrPerformCalls / this.nMinNumberOfFrames;
16819     // okay, so now, the decision is easy:
16820     //
16821     // If the fraction of time elapsed is smaller than the
16822     // number of calls required to be performed, then we calc
16823     // the position on the animation range according to
16824     // elapsed time. That is, we're so to say ahead of time.
16825     //
16826     // In contrary, if the fraction of time elapsed is larger,
16827     // then we're lagging, and we thus calc the position on
16828     // the animation time line according to the fraction of
16829     // calls performed. Thus, the animation is forced to slow
16830     // down, and take the required minimal number of steps,
16831     // sufficiently equally distributed across the animation
16832     // time line.
16834     if( nFractionElapsedTime < nFractionRequiredCalls )
16835     {
16836         return 0.0;
16837     }
16838     else
16839     {
16840         // lag global time, so all other animations lag, too:
16841         return ( ( nFractionElapsedTime - nFractionRequiredCalls ) * this.nMinSimpleDuration );
16842     }
16845 SimpleContinuousActivityBase.prototype.perform = function()
16847     // call base class, for start() calls and end handling
16848     if( !SimpleContinuousActivityBase.superclass.perform.call( this ) )
16849         return false; // done, we're ended
16851     // get relative animation position
16852     var nCurrElapsedTime = this.aTimer.getElapsedTime();
16853     var nT = nCurrElapsedTime / this.nMinSimpleDuration;
16856     // one of the stop criteria reached?
16858     // will be set to true below, if one of the termination criteria matched.
16859     var bActivityEnding = false;
16861     if( this.isRepeatCountValid() )
16862     {
16863         // Finite duration case
16865         // When we've autoreverse on, the repeat count doubles
16866         var nRepeatCount = this.getRepeatCount();
16867         var nEffectiveRepeat = this.isAutoReverse() ? 2.0 * nRepeatCount : nRepeatCount;
16869         // time (or frame count) elapsed?
16870         if( nEffectiveRepeat <= nT )
16871         {
16872             // Ok done for now. Will not exit right here,
16873             // to give animation the chance to render the last
16874             // frame below
16875             bActivityEnding = true;
16877             // clamp animation to max permissible value
16878             nT = nEffectiveRepeat;
16879         }
16880     }
16883     // need to do auto-reverse?
16885     var nRepeats;
16886     var nRelativeSimpleTime;
16887     // TODO(Q3): Refactor this mess
16888     if( this.isAutoReverse() )
16889     {
16890         // divert active duration into repeat and
16891         // fractional part.
16892         nRepeats = Math.floor( nT );
16893         var nFractionalActiveDuration =  nT - nRepeats;
16895         // for auto-reverse, map ranges [1,2), [3,4), ...
16896         // to ranges [0,1), [1,2), etc.
16897         if( nRepeats % 2 )
16898         {
16899             // we're in an odd range, reverse sweep
16900             nRelativeSimpleTime = 1.0 - nFractionalActiveDuration;
16901         }
16902         else
16903         {
16904             // we're in an even range, pass on as is
16905             nRelativeSimpleTime = nFractionalActiveDuration;
16906         }
16908         // effective repeat count for autoreverse is half of
16909         // the input time's value (each run of an autoreverse
16910         // cycle is half of a repeat)
16911         nRepeats /= 2;
16912     }
16913     else
16914     {
16915         // determine repeat
16917         // calc simple time and number of repeats from nT
16918         // Now, that's easy, since the fractional part of
16919         // nT gives the relative simple time, and the
16920         // integer part the number of full repeats:
16921         nRepeats = Math.floor( nT );
16922         nRelativeSimpleTime = nT - nRepeats;
16924         // clamp repeats to max permissible value (maRepeats.getValue() - 1.0)
16925         if( this.isRepeatCountValid() && ( nRepeats >= this.getRepeatCount() ) )
16926         {
16927             // Note that this code here only gets
16928             // triggered if this.nRepeats is an
16929             // _integer_. Otherwise, nRepeats will never
16930             // reach nor exceed
16931             // maRepeats.getValue(). Thus, the code below
16932             // does not need to handle cases of fractional
16933             // repeats, and can always assume that a full
16934             // animation run has ended (with
16935             // nRelativeSimpleTime = 1.0 for
16936             // non-autoreversed activities).
16938             // with modf, nRelativeSimpleTime will never
16939             // become 1.0, since nRepeats is incremented and
16940             // nRelativeSimpleTime set to 0.0 then.
16941             //
16942             // For the animation to reach its final value,
16943             // nRepeats must although become this.nRepeats - 1.0,
16944             // and nRelativeSimpleTime = 1.0.
16945             nRelativeSimpleTime = 1.0;
16946             nRepeats -= 1.0;
16947         }
16948     }
16951     // actually perform something
16953     this.simplePerform( nRelativeSimpleTime, nRepeats );
16955     // delayed endActivity() call from end condition check
16956     // below. Issued after the simplePerform() call above, to
16957     // give animations the chance to correctly reach the
16958     // animation end value, without spurious bail-outs because
16959     // of isActive() returning false.
16960     if( bActivityEnding )
16961         this.endActivity();
16963     // one more frame successfully performed
16964     ++this.nCurrPerformCalls;
16966     return this.isActive();
16969 SimpleContinuousActivityBase.prototype.simplePerform = function( /*nSimpleTime, nRepeatCount*/ )
16971     throw ( 'SimpleContinuousActivityBase.simplePerform: abstract method invoked' );
16977 function ContinuousKeyTimeActivityBase( aCommonParamSet )
16979     var nSize = aCommonParamSet.aDiscreteTimes.length;
16980     assert( nSize > 1,
16981             'ContinuousKeyTimeActivityBase constructor: assertion (aDiscreteTimes.length > 1) failed' );
16983     assert( aCommonParamSet.aDiscreteTimes[0] == 0.0,
16984             'ContinuousKeyTimeActivityBase constructor: assertion (aDiscreteTimes.front() == 0.0) failed' );
16986     assert( aCommonParamSet.aDiscreteTimes[ nSize - 1 ] <= 1.0,
16987             'ContinuousKeyTimeActivityBase constructor: assertion (aDiscreteTimes.back() <= 1.0) failed' );
16989     ContinuousKeyTimeActivityBase.superclass.constructor.call( this, aCommonParamSet );
16991     this.aLerper = new KeyStopLerp( aCommonParamSet.aDiscreteTimes );
16993 extend( ContinuousKeyTimeActivityBase, SimpleContinuousActivityBase );
16996 ContinuousKeyTimeActivityBase.prototype.activate = function( aEndElement )
16998     ContinuousKeyTimeActivityBase.superclass.activate.call( this, aEndElement );
17000     this.aLerper.reset();
17003 ContinuousKeyTimeActivityBase.prototype.performContinuousHook = function( /*nIndex, nFractionalIndex, nRepeatCount*/ )
17005     throw ( 'ContinuousKeyTimeActivityBase.performContinuousHook: abstract method invoked' );
17008 ContinuousKeyTimeActivityBase.prototype.simplePerform = function( nSimpleTime, nRepeatCount )
17010     var nAlpha = this.calcAcceleratedTime( nSimpleTime );
17012     var aLerpResult = this.aLerper.lerp( nAlpha );
17014     this.performContinuousHook( aLerpResult.nIndex, aLerpResult.nLerp, nRepeatCount );
17020 function ContinuousActivityBase( aCommonParamSet )
17022     ContinuousActivityBase.superclass.constructor.call( this, aCommonParamSet );
17025 extend( ContinuousActivityBase, SimpleContinuousActivityBase );
17028 ContinuousActivityBase.prototype.performContinuousHook = function( /*nModifiedTime, nRepeatCount*/ )
17030     throw ( 'ContinuousActivityBase.performContinuousHook: abstract method invoked' );
17033 ContinuousActivityBase.prototype.simplePerform = function( nSimpleTime, nRepeatCount )
17035     this.performContinuousHook( this.calcAcceleratedTime( nSimpleTime ), nRepeatCount );
17041 function SimpleActivity( aCommonParamSet, aNumberAnimation, eDirection )
17043     assert( ( eDirection == BACKWARD ) || ( eDirection == FORWARD ),
17044             'SimpleActivity constructor: animation direction is not valid' );
17046     assert( aNumberAnimation, 'SimpleActivity constructor: animation object is not valid' );
17048     SimpleActivity.superclass.constructor.call( this, aCommonParamSet );
17050     this.aAnimation = aNumberAnimation;
17051     this.nDirection = ( eDirection == FORWARD ) ? 1.0 : 0.0;
17053 extend( SimpleActivity, ContinuousActivityBase );
17056 SimpleActivity.prototype.startAnimation = function()
17058     if( this.isDisposed() || !this.aAnimation )
17059         return;
17061     ANIMDBG.print( 'SimpleActivity.startAnimation invoked' );
17062     SimpleActivity.superclass.startAnimation.call( this );
17064     // start animation
17065     this.aAnimation.start( this.getTargetElement() );
17068 SimpleActivity.prototype.endAnimation = function()
17070     if( this.aAnimation )
17071         this.aAnimation.end();
17075 SimpleActivity.prototype.performContinuousHook = function( nModifiedTime /*, nRepeatCount*/ )
17077     // nRepeatCount is not used
17079     if( this.isDisposed() || !this.aAnimation )
17080         return;
17082     var nT = 1.0 - this.nDirection + nModifiedTime * ( 2.0*this.nDirection - 1.0 );
17083     this.aAnimation.perform( nT );
17086 SimpleActivity.prototype.performEnd = function()
17088     if( this.aAnimation )
17089         this.aAnimation.perform( this.nDirection );
17095 //  FromToByActivity< BaseType > template class
17098 function FromToByActivityTemplate( BaseType ) // template parameter
17101     function FromToByActivity( aFromValue, aToValue, aByValue,
17102                                aActivityParamSet, aAnimation,
17103                                aInterpolator, aOperatorSet, bAccumulate )
17104     {
17105         assert( aAnimation, 'FromToByActivity constructor: invalid animation object' );
17106         assert( ( aToValue != undefined ) || ( aByValue != undefined ),
17107                 'FromToByActivity constructor: one of aToValue or aByValue must be valid' );
17109         FromToByActivity.superclass.constructor.call( this, aActivityParamSet );
17111         this.aFrom = aFromValue;
17112         this.aTo = aToValue;
17113         this.aBy = aByValue;
17114         this.aStartValue = null;
17115         this.aEndValue = null;
17116         this.aPreviousValue = null;
17117         this.aStartInterpolationValue = null;
17118         this.aAnimation = aAnimation;
17119         this.aInterpolator = aInterpolator;
17120         this.equal = aOperatorSet.equal;
17121         this.add = aOperatorSet.add;
17122         this.scale = aOperatorSet.scale;
17123         this.bDynamicStartValue = false;
17124         this.nIteration = 0;
17125         this.bCumulative = bAccumulate;
17126         this.aFormula = aActivityParamSet.aFormula;
17127     }
17128     extend( FromToByActivity, BaseType );
17130     FromToByActivity.prototype.initAnimatedElement = function()
17131     {
17132         if( this.aAnimation && this.aFrom )
17133         {
17134             var aValue = this.aFormula ? this.aFormula( this.aFrom ) : this.aFrom;
17135             this.aAnimation.perform(aValue);
17136         }
17137     };
17139     FromToByActivity.prototype.startAnimation = function()
17140     {
17141         if( this.isDisposed() || !this.aAnimation  )
17142         {
17143             log( 'FromToByActivity.startAnimation: activity disposed or not valid animation' );
17144             return;
17145         }
17147         FromToByActivity.superclass.startAnimation.call( this );
17149         this.aAnimation.start( this.getTargetElement() );
17152         var aAnimationStartValue = this.aAnimation.getUnderlyingValue();
17154         // first of all, determine general type of
17155         // animation, by inspecting which of the FromToBy values
17156         // are actually valid.
17157         // See http://www.w3.org/TR/smil20/animation.html#AnimationNS-FromToBy
17158         // for a definition
17159         if( this.aFrom )
17160         {
17161             // From-to or From-by animation. According to
17162             // SMIL spec, the To value takes precedence
17163             // over the By value, if both are specified
17164             if( this.aTo )
17165             {
17166                 // From-To animation
17167                 this.aStartValue = this.aFrom;
17168                 this.aEndValue = this.aTo;
17169             }
17170             else if( this.aBy )
17171             {
17172                 // From-By animation
17173                 this.aStartValue = this.aFrom;
17175                 this.aEndValue = this.add( this.aStartValue, this.aBy );
17176             }
17177         }
17178         else
17179         {
17180             this.aStartValue = aAnimationStartValue;
17181             this.aStartInterpolationValue = this.aStartValue;
17183             // By or To animation. According to SMIL spec,
17184             // the To value takes precedence over the By
17185             // value, if both are specified
17186             if( this.aTo )
17187             {
17188                 // To animation
17190                 // According to the SMIL spec
17191                 // (http://www.w3.org/TR/smil20/animation.html#animationNS-ToAnimation),
17192                 // the to animation interpolates between
17193                 // the _running_ underlying value and the to value (as the end value)
17194                 this.bDynamicStartValue = true;
17195                 this.aPreviousValue = this.aStartValue;
17196                 this.aEndValue = this.aTo;
17197             }
17198             else if( this.aBy )
17199             {
17200                 // By animation
17201                 this.aStartValue = aAnimationStartValue;
17203                 this.aEndValue = this.add( this.aStartValue, this.aBy );
17204             }
17205         }
17207         ANIMDBG.print( 'FromToByActivity.startAnimation: aStartValue = ' + this.aStartValue + ', aEndValue = ' + this.aEndValue );
17208     };
17210     FromToByActivity.prototype.endAnimation = function()
17211     {
17212         if( this.aAnimation )
17213             this.aAnimation.end();
17214     };
17216     // perform hook override for ContinuousActivityBase
17217     FromToByActivity.prototype.performContinuousHook = function( nModifiedTime, nRepeatCount )
17218     {
17219         if( this.isDisposed() || !this.aAnimation  )
17220         {
17221             log( 'FromToByActivity.performContinuousHook: activity disposed or not valid animation' );
17222             return;
17223         }
17226         // According to SMIL 3.0 spec 'to' animation if no other (lower priority)
17227         // animations are active or frozen then a simple interpolation is performed.
17228         // That is, the start interpolation value is constant while the animation
17229         // is running, and is equal to the underlying value retrieved when
17230         // the animation start.
17231         // However if another animation is manipulating the underlying value,
17232         // the 'to' animation will initially add to the effect of the lower priority
17233         // animation, and increasingly dominate it as it nears the end of the
17234         // simple duration, eventually overriding it completely.
17235         // That is, each time the underlying value is changed between two
17236         // computations of the animation function the new underlying value is used
17237         // as start value for the interpolation.
17238         // See:
17239         // http://www.w3.org/TR/SMIL3/smil-animation.html#animationNS-ToAnimation
17240         // (Figure 6 - Effect of Additive to animation example)
17241         // Moreover when a 'to' animation is repeated, at each new iteration
17242         // the start interpolation value is reset to the underlying value
17243         // of the animated property when the animation started,
17244         // as it is shown in the example provided by the SMIL 3.0 spec.
17245         // This is exactly as Firefox performs SVG 'to' animations.
17246         if( this.bDynamicStartValue )
17247         {
17248             if( this.nIteration != nRepeatCount )
17249             {
17250                 this.nIteration = nRepeatCount;
17251                 this.aStartInterpolationValue =  this.aStartValue;
17252             }
17253             else
17254             {
17255                 var aActualValue = this.aAnimation.getUnderlyingValue();
17256                 if( !this.equal( aActualValue, this.aPreviousValue ) )
17257                     this.aStartInterpolationValue = aActualValue;
17258             }
17259         }
17261         var aValue = this.aInterpolator( this.aStartInterpolationValue,
17262                                          this.aEndValue, nModifiedTime );
17264         // According to the SMIL spec:
17265         // Because 'to' animation is defined in terms of absolute values of
17266         // the target attribute, cumulative animation is not defined.
17267         if( this.bCumulative && !this.bDynamicStartValue )
17268         {
17269             // aValue = this.aEndValue * nRepeatCount + aValue;
17270             aValue = this.add( this.scale( nRepeatCount, this.aEndValue ), aValue );
17271         }
17273         aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17274         this.aAnimation.perform( aValue );
17276         if( this.bDynamicStartValue )
17277         {
17278             this.aPreviousValue = this.aAnimation.getUnderlyingValue();
17279         }
17281     };
17283     // perform hook override for DiscreteActivityBase
17284     FromToByActivity.prototype.performDiscreteHook = function( /*nFrame, nRepeatCount*/ )
17285     {
17286         if (this.isDisposed() || !this.aAnimation) {
17287             log('FromToByActivity.performDiscreteHook: activity disposed or not valid animation');
17288             return;
17289         }
17290     };
17292     FromToByActivity.prototype.performEnd = function()
17293     {
17294         if( this.aAnimation )
17295         {
17296             var aValue = this.isAutoReverse() ? this.aStartValue : this.aEndValue;
17297             aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17298             this.aAnimation.perform( aValue );
17299         }
17300     };
17302     FromToByActivity.prototype.dispose = function()
17303     {
17304         FromToByActivity.superclass.dispose.call( this );
17305     };
17308     return FromToByActivity;
17312 // FromToByActivity< ContinuousActivityBase > instantiation
17313 var LinearFromToByActivity = instantiate( FromToByActivityTemplate, ContinuousActivityBase );
17314 // FromToByActivity< DiscreteActivityBase > instantiation
17315 var DiscreteFromToByActivity = instantiate( FromToByActivityTemplate, DiscreteActivityBase );
17320 //  ValueListActivity< BaseType > template class
17323 function  ValueListActivityTemplate( BaseType ) // template parameter
17326     function ValueListActivity( aValueList, aActivityParamSet,
17327                                 aAnimation, aInterpolator,
17328                                 aOperatorSet, bAccumulate )
17329     {
17330         assert( aAnimation, 'ValueListActivity constructor: invalid animation object' );
17331         assert( aValueList.length != 0, 'ValueListActivity: value list is empty' );
17333         ValueListActivity.superclass.constructor.call( this, aActivityParamSet );
17335         this.aValueList = aValueList;
17336         this.aAnimation = aAnimation;
17337         this.aInterpolator = aInterpolator;
17338         this.add = aOperatorSet.add;
17339         this.scale = aOperatorSet.scale;
17340         this.bCumulative = bAccumulate;
17341         this.aLastValue = this.aValueList[ this.aValueList.length - 1 ];
17342         this.aFormula = aActivityParamSet.aFormula;
17343     }
17344     extend( ValueListActivity, BaseType );
17346     ValueListActivity.prototype.activate = function( aEndEvent )
17347     {
17348         ValueListActivity.superclass.activate.call( this, aEndEvent );
17349         for( var i = 0; i < this.aValueList.length; ++i )
17350         {
17351             ANIMDBG.print( 'createValueListActivity: value[' + i + '] = ' + this.aValueList[i] );
17352         }
17353     };
17355     ValueListActivity.prototype.initAnimatedElement = function()
17356     {
17357         if( this.aAnimation )
17358         {
17359             var aValue = this.aValueList[0];
17360             aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17361             this.aAnimation.perform(aValue);
17362         }
17363     };
17365     ValueListActivity.prototype.startAnimation = function()
17366     {
17367         if( this.isDisposed() || !this.aAnimation  )
17368         {
17369             log( 'ValueListActivity.startAnimation: activity disposed or not valid animation' );
17370             return;
17371         }
17373         ValueListActivity.superclass.startAnimation.call( this );
17375         this.aAnimation.start( this.getTargetElement() );
17376     };
17378     ValueListActivity.prototype.endAnimation = function()
17379     {
17380         if( this.aAnimation )
17381             this.aAnimation.end();
17382     };
17384     // perform hook override for ContinuousKeyTimeActivityBase base
17385     ValueListActivity.prototype.performContinuousHook = function( nIndex, nFractionalIndex, nRepeatCount )
17386     {
17387         if( this.isDisposed() || !this.aAnimation  )
17388         {
17389             log( 'ValueListActivity.performContinuousHook: activity disposed or not valid animation' );
17390             return;
17391         }
17393         assert( ( nIndex + 1 ) < this.aValueList.length,
17394                 'ValueListActivity.performContinuousHook: assertion (nIndex + 1 < this.aValueList.length) failed' );
17396         // interpolate between nIndex and nIndex+1 values
17398         var aValue = this.aInterpolator( this.aValueList[ nIndex ],
17399                                          this.aValueList[ nIndex+1 ],
17400                                          nFractionalIndex );
17402         if( this.bCumulative )
17403         {
17404             //aValue = aValue + nRepeatCount * this.aLastValue;
17405             aValue = this.add( aValue, this.scale( nRepeatCount, this.aLastValue ) );
17406         }
17408         aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17409         this.aAnimation.perform( aValue );
17410     };
17412     // perform hook override for DiscreteActivityBase base
17413     ValueListActivity.prototype.performDiscreteHook = function( nFrame, nRepeatCount )
17414     {
17415         if( this.isDisposed() || !this.aAnimation  )
17416         {
17417             log( 'ValueListActivity.performDiscreteHook: activity disposed or not valid animation' );
17418             return;
17419         }
17421         assert( nFrame < this.aValueList.length,
17422                'ValueListActivity.performDiscreteHook: assertion ( nFrame < this.aValueList.length) failed' );
17424         // this is discrete, thus no lerp here.
17425         var aValue = this.aValueList[nFrame];
17427         if( this.bCumulative )
17428         {
17429             aValue = this.add( aValue, this.scale( nRepeatCount, this.aLastValue ) );
17430             // for numbers:   aValue = aValue + nRepeatCount * this.aLastValue;
17431             // for enums, bools or strings:   aValue = aValue;
17432         }
17434         aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17435         this.aAnimation.perform( aValue );
17436     };
17438     ValueListActivity.prototype.performEnd = function()
17439     {
17440         if( this.aAnimation )
17441         {
17442             var aValue = this.aFormula ? this.aFormula( this.aLastValue ) : this.aLastValue;
17443             this.aAnimation.perform( aValue );
17444         }
17445     };
17447     ValueListActivity.prototype.dispose = function()
17448     {
17449         ValueListActivity.superclass.dispose.call( this );
17450     };
17453     return ValueListActivity;
17457 //  ValueListActivity< ContinuousKeyTimeActivityBase > instantiation
17458 var LinearValueListActivity = instantiate( ValueListActivityTemplate, ContinuousKeyTimeActivityBase );
17459 //  ValueListActivity< DiscreteActivityBase > instantiation
17460 var DiscreteValueListActivity = instantiate( ValueListActivityTemplate, DiscreteActivityBase );
17464 /**********************************************************************************************
17465  *      Activity Factory
17466  **********************************************************************************************/
17469 function createActivity( aActivityParamSet, aAnimationNode, aAnimation, aInterpolator )
17471     var eCalcMode = aAnimationNode.getCalcMode();
17473     var sAttributeName = aAnimationNode.getAttributeName();
17474     var aAttributeProp = aAttributeMap[ sAttributeName ];
17476     var eValueType = aAttributeProp[ 'type' ];
17477     var eValueSubtype = aAttributeProp[ 'subtype' ];
17479     // do we need to get an interpolator ?
17480     if( ! aInterpolator )
17481     {
17482         aInterpolator = aInterpolatorHandler.getInterpolator( eCalcMode,
17483                                                               eValueType,
17484                                                               eValueSubtype );
17485     }
17487     // is it cumulative ?
17488     var bAccumulate = ( aAnimationNode.getAccumulate() === ACCUMULATE_MODE_SUM )
17489                             && !( eValueType === BOOL_PROPERTY ||
17490                                   eValueType === STRING_PROPERTY ||
17491                                   eValueType === ENUM_PROPERTY );
17493     if( aAnimationNode.getFormula() )
17494     {
17495         var sFormula =  aAnimationNode.getFormula();
17496         var reMath = /abs|sqrt|asin|acos|atan|sin|cos|tan|exp|log|min|max/g;
17497         sFormula = sFormula.replace(reMath, 'Math.$&');
17498         sFormula = sFormula.replace(/pi(?!\w)/g, 'Math.PI');
17499         sFormula = sFormula.replace(/e(?!\w)/g, 'Math.E');
17500         sFormula = sFormula.replace(/\$/g, '__PARAM0__');
17502         var aAnimatedElement = aAnimationNode.getAnimatedElement();
17503         var aBBox = aAnimatedElement.getBaseBBox();
17505         // the following variable are used for evaluating sFormula
17506         /* eslint-disable no-unused-vars */
17507         var width = aBBox.width / aActivityParamSet.nSlideWidth;
17508         var height = aBBox.height / aActivityParamSet.nSlideHeight;
17509         var x = ( aBBox.x + aBBox.width / 2 ) / aActivityParamSet.nSlideWidth;
17510         var y = ( aBBox.y + aBBox.height / 2 ) / aActivityParamSet.nSlideHeight;
17512         aActivityParamSet.aFormula = function( __PARAM0__ ) {
17514             return eval(sFormula);
17515         };
17516         /* eslint-enable no-unused-vars */
17517     }
17519     aActivityParamSet.aDiscreteTimes = aAnimationNode.getKeyTimes();
17521     // do we have a value list ?
17522     var aValueSet = aAnimationNode.getValues();
17523     var nValueSetSize = aValueSet.length;
17525     if( nValueSetSize != 0 )
17526     {
17527         // Value list activity
17529         if( aActivityParamSet.aDiscreteTimes.length == 0 )
17530         {
17531             for( var i = 0; i < nValueSetSize; ++i )
17532                 aActivityParamSet.aDiscreteTimes[i].push( i / nValueSetSize );
17533         }
17535         switch( eCalcMode )
17536         {
17537             case CALC_MODE_DISCRETE:
17538                 aActivityParamSet.aWakeupEvent =
17539                         new WakeupEvent( aActivityParamSet.aTimerEventQueue.getTimer(),
17540                                          aActivityParamSet.aActivityQueue );
17542                 return createValueListActivity( aActivityParamSet,
17543                                                 aAnimationNode,
17544                                                 aAnimation,
17545                                                 aInterpolator,
17546                                                 DiscreteValueListActivity,
17547                                                 bAccumulate,
17548                                                 eValueType );
17550             default:
17551                 log( 'createActivity: unexpected calculation mode: ' + eCalcMode );
17552                 // FALLTHROUGH intended
17553             case CALC_MODE_PACED :
17554             case CALC_MODE_SPLINE :
17555             case CALC_MODE_LINEAR:
17556                 return createValueListActivity( aActivityParamSet,
17557                                                 aAnimationNode,
17558                                                 aAnimation,
17559                                                 aInterpolator,
17560                                                 LinearValueListActivity,
17561                                                 bAccumulate,
17562                                                 eValueType );
17563         }
17564     }
17565     else
17566     {
17567         // FromToBy activity
17568         switch( eCalcMode )
17569         {
17570             case CALC_MODE_DISCRETE:
17571                 log( 'createActivity: discrete calculation case not yet implemented' );
17572                 aActivityParamSet.aWakeupEvent =
17573                         new WakeupEvent( aActivityParamSet.aTimerEventQueue.getTimer(),
17574                                          aActivityParamSet.aActivityQueue );
17575                 return createFromToByActivity(  aActivityParamSet,
17576                                                 aAnimationNode,
17577                                                 aAnimation,
17578                                                 aInterpolator,
17579                                                 DiscreteFromToByActivity,
17580                                                 bAccumulate,
17581                                                 eValueType );
17583             default:
17584                 log( 'createActivity: unexpected calculation mode: ' + eCalcMode );
17585                 // FALLTHROUGH intended
17586             case CALC_MODE_PACED :
17587             case CALC_MODE_SPLINE :
17588             case CALC_MODE_LINEAR:
17589                 return createFromToByActivity(  aActivityParamSet,
17590                                                 aAnimationNode,
17591                                                 aAnimation,
17592                                                 aInterpolator,
17593                                                 LinearFromToByActivity,
17594                                                 bAccumulate,
17595                                                 eValueType );
17596         }
17597     }
17603 function createValueListActivity( aActivityParamSet, aAnimationNode, aAnimation,
17604                                   aInterpolator, ClassTemplateInstance, bAccumulate, eValueType )
17606     var aAnimatedElement = aAnimationNode.getAnimatedElement();
17607     var aOperatorSet = aOperatorSetMap[ eValueType ];
17608     assert( aOperatorSet, 'createValueListActivity: no operator set found' );
17610     var aValueSet = aAnimationNode.getValues();
17612     var aValueList = [];
17614     extractAttributeValues( eValueType,
17615                             aValueList,
17616                             aValueSet,
17617                             aAnimatedElement.getBaseBBox(),
17618                             aActivityParamSet.nSlideWidth,
17619                             aActivityParamSet.nSlideHeight );
17621     for( var i = 0; i < aValueList.length; ++i )
17622     {
17623         ANIMDBG.print( 'createValueListActivity: value[' + i + '] = ' + aValueList[i] );
17624     }
17626     return new ClassTemplateInstance( aValueList, aActivityParamSet, aAnimation,
17627                                       aInterpolator, aOperatorSet, bAccumulate );
17633 function createFromToByActivity( aActivityParamSet, aAnimationNode, aAnimation,
17634                                  aInterpolator, ClassTemplateInstance, bAccumulate, eValueType )
17637     var aAnimatedElement = aAnimationNode.getAnimatedElement();
17638     var aOperatorSet = aOperatorSetMap[ eValueType ];
17639     assert( aOperatorSet, 'createFromToByActivity: no operator set found' );
17641     var aValueSet = [];
17642     aValueSet[0] = aAnimationNode.getFromValue();
17643     aValueSet[1] = aAnimationNode.getToValue();
17644     aValueSet[2] = aAnimationNode.getByValue();
17646     ANIMDBG.print( 'createFromToByActivity: value type: ' + aValueTypeOutMap[eValueType] +
17647                     ', aFrom = ' + aValueSet[0] +
17648                     ', aTo = ' + aValueSet[1] +
17649                     ', aBy = ' + aValueSet[2] );
17651     var aValueList = [];
17653     extractAttributeValues( eValueType,
17654                             aValueList,
17655                             aValueSet,
17656                             aAnimatedElement.getBaseBBox(),
17657                             aActivityParamSet.nSlideWidth,
17658                             aActivityParamSet.nSlideHeight );
17660     ANIMDBG.print( 'createFromToByActivity: ' +
17661                     ', aFrom = ' + aValueList[0] +
17662                     ', aTo = ' + aValueList[1] +
17663                     ', aBy = ' + aValueList[2] );
17665     return new ClassTemplateInstance( aValueList[0], aValueList[1], aValueList[2],
17666                                       aActivityParamSet, aAnimation,
17667                                       aInterpolator, aOperatorSet, bAccumulate );
17672 function extractAttributeValues( eValueType, aValueList, aValueSet, aBBox, nSlideWidth, nSlideHeight )
17674     var i;
17675     switch( eValueType )
17676     {
17677         case NUMBER_PROPERTY :
17678             evalValuesAttribute( aValueList, aValueSet, aBBox, nSlideWidth, nSlideHeight );
17679             break;
17680         case BOOL_PROPERTY :
17681             for( i = 0; i < aValueSet.length; ++i )
17682             {
17683                 var aValue = booleanParser( aValueSet[i] );
17684                 aValueList.push( aValue );
17685             }
17686             break;
17687         case STRING_PROPERTY :
17688             for( i = 0; i < aValueSet.length; ++i )
17689             {
17690                 aValueList.push( aValueSet[i] );
17691             }
17692             break;
17693         case ENUM_PROPERTY :
17694             for( i = 0; i < aValueSet.length; ++i )
17695             {
17696                 aValueList.push( aValueSet[i] );
17697             }
17698             break;
17699         case COLOR_PROPERTY :
17700             for( i = 0; i < aValueSet.length; ++i )
17701             {
17702                 aValue = colorParser( aValueSet[i] );
17703                 aValueList.push( aValue );
17704             }
17705             break;
17706         default:
17707             log( 'createValueListActivity: unexpected value type: ' + eValueType );
17708     }
17713 function evalValuesAttribute( aValueList, aValueSet, aBBox, nSlideWidth, nSlideHeight )
17715     // the following variables are used for evaluating sValue later
17716     /* eslint-disable no-unused-vars */
17717     var width = aBBox.width / nSlideWidth;
17718     var height = aBBox.height / nSlideHeight;
17719     var x = ( aBBox.x + aBBox.width / 2 ) / nSlideWidth;
17720     var y = ( aBBox.y + aBBox.height / 2 ) / nSlideHeight;
17721     /* eslint-enable no-unused-vars */
17723     var reMath = /abs|sqrt|asin|acos|atan|sin|cos|tan|exp|log|min|max/g;
17725     for( var i = 0; i < aValueSet.length; ++i )
17726     {
17727         var sValue = aValueSet[i];
17728         sValue = sValue.replace(reMath, 'Math.$&');
17729         sValue = sValue.replace(/pi(?!\w)/g, 'Math.PI');
17730         sValue = sValue.replace(/e(?!\w)/g, 'Math.E');
17731         var aValue =  eval( sValue );
17732         aValueList.push( aValue );
17733     }
17738 /**********************************************************************************************
17739  *      SlideShow, SlideShowContext and FrameSynchronization
17740  **********************************************************************************************/
17744 // direction of animation, important: not change the values!
17745 var BACKWARD    = 0;
17746 var FORWARD     = 1;
17748 var MAXIMUM_FRAME_COUNT                 = 60;
17749 var MINIMUM_TIMEOUT                     = 1.0 / MAXIMUM_FRAME_COUNT;
17750 var MAXIMUM_TIMEOUT                     = 4.0;
17751 var MINIMUM_FRAMES_PER_SECONDS          = 10;
17752 var PREFERRED_FRAMES_PER_SECONDS        = 50;
17753 var PREFERRED_FRAME_RATE                = 1.0 / PREFERRED_FRAMES_PER_SECONDS;
17756 function Effect( nId )
17758     this.nId = ( typeof( nId ) === typeof( 1 ) ) ? nId : -1;
17759     this.eState = Effect.NOT_STARTED;
17761 Effect.NOT_STARTED = 0;
17762 Effect.PLAYING = 1;
17763 Effect.ENDED = 2;
17765 Effect.prototype.getId = function()
17767     return this.nId;
17770 Effect.prototype.isMainEffect = function()
17772     return ( this.nId === -1 );
17775 Effect.prototype.isPlaying = function()
17777     return ( this.eState === Effect.PLAYING );
17780 Effect.prototype.isEnded = function()
17782     return ( this.eState === Effect.ENDED );
17785 Effect.prototype.start = function()
17787     assert( this.eState === Effect.NOT_STARTED, 'Effect.start: wrong state.' );
17788     this.eState = Effect.PLAYING;
17791 Effect.prototype.end = function()
17793     assert( this.eState === Effect.PLAYING, 'Effect.end: wrong state.' );
17794     this.eState = Effect.ENDED;
17799 function SlideShow()
17801     this.aTimer = new ElapsedTime();
17802     this.aFrameSynchronization = new FrameSynchronization( PREFERRED_FRAME_RATE );
17803     this.aTimerEventQueue = new TimerEventQueue( this.aTimer );
17804     this.aActivityQueue = new ActivityQueue( this.aTimer );
17805     this.aNextEffectEventArray = null;
17806     this.aInteractiveAnimationSequenceMap = null;
17807     this.aEventMultiplexer = null;
17809     this.aContext = new SlideShowContext( this.aTimerEventQueue,
17810                                           this.aEventMultiplexer,
17811                                           this.aNextEffectEventArray,
17812                                           this.aInteractiveAnimationSequenceMap,
17813                                           this.aActivityQueue );
17814     this.bIsIdle = true;
17815     this.bIsEnabled = true;
17816     this.bNoSlideTransition = false;
17817     this.bIsTransitionRunning = false;
17819     this.nCurrentEffect = 0;
17820     this.bIsNextEffectRunning = false;
17821     this.bIsRewinding = false;
17822     this.bIsSkipping = false;
17823     this.bIsSkippingAll = false;
17824     this.nTotalInteractivePlayingEffects = 0;
17825     this.aStartedEffectList = [];
17826     this.aStartedEffectIndexMap = {};
17827     this.aStartedEffectIndexMap[ -1 ] = undefined;
17828     this.automaticAdvanceTimeout = null;
17831 SlideShow.prototype.setSlideEvents = function( aNextEffectEventArray,
17832                                                aInteractiveAnimationSequenceMap,
17833                                                aEventMultiplexer )
17835     if( !aNextEffectEventArray )
17836         log( 'SlideShow.setSlideEvents: aNextEffectEventArray is not valid' );
17838     if( !aInteractiveAnimationSequenceMap )
17839         log( 'SlideShow.setSlideEvents:aInteractiveAnimationSequenceMap  is not valid' );
17841     if( !aEventMultiplexer )
17842         log( 'SlideShow.setSlideEvents: aEventMultiplexer is not valid' );
17844     this.aContext.aNextEffectEventArray = aNextEffectEventArray;
17845     this.aNextEffectEventArray = aNextEffectEventArray;
17846     this.aContext.aInteractiveAnimationSequenceMap = aInteractiveAnimationSequenceMap;
17847     this.aInteractiveAnimationSequenceMap = aInteractiveAnimationSequenceMap;
17848     this.aContext.aEventMultiplexer = aEventMultiplexer;
17849     this.aEventMultiplexer = aEventMultiplexer;
17850     this.nCurrentEffect = 0;
17853 SlideShow.prototype.createSlideTransition = function( aSlideTransitionHandler, aLeavingSlide, aEnteringSlide, aTransitionEndEvent )
17855     if( !aEnteringSlide )
17856     {
17857         log( 'SlideShow.createSlideTransition: entering slide element is not valid.' );
17858         return null;
17859     }
17861     if( this.bNoSlideTransition ) return null;
17863     var aAnimatedLeavingSlide = null;
17864     if( aLeavingSlide )
17865         aAnimatedLeavingSlide = new AnimatedSlide( aLeavingSlide );
17866     var aAnimatedEnteringSlide = new AnimatedSlide( aEnteringSlide );
17868     var aSlideTransition = aSlideTransitionHandler.createSlideTransition( aAnimatedLeavingSlide, aAnimatedEnteringSlide );
17869     if( !aSlideTransition ) return null;
17871     // compute duration
17872     var nDuration = 0.001;
17873     if( aSlideTransitionHandler.getDuration().isValue() )
17874     {
17875         nDuration = aSlideTransitionHandler.getDuration().getValue();
17876     }
17877     else
17878     {
17879         log( 'SlideShow.createSlideTransition: duration is not a number' );
17880     }
17882     var aCommonParameterSet = new ActivityParamSet();
17883     aCommonParameterSet.aEndEvent = aTransitionEndEvent;
17884     aCommonParameterSet.aTimerEventQueue = this.aTimerEventQueue;
17885     aCommonParameterSet.aActivityQueue = this.aActivityQueue;
17886     aCommonParameterSet.nMinDuration = nDuration;
17887     aCommonParameterSet.nMinNumberOfFrames = aSlideTransitionHandler.getMinFrameCount();
17888     aCommonParameterSet.nSlideWidth = WIDTH;
17889     aCommonParameterSet.nSlideHeight = HEIGHT;
17891     return new SimpleActivity( aCommonParameterSet, aSlideTransition, FORWARD );
17895 SlideShow.prototype.isEnabled = function()
17897     return this.bIsEnabled;
17900 SlideShow.prototype.isRunning = function()
17902     return !this.bIsIdle;
17905 SlideShow.prototype.isTransitionPlaying = function()
17907     return this.bIsTransitionRunning;
17910 SlideShow.prototype.isMainEffectPlaying = function()
17912     return this.bIsNextEffectRunning;
17915 SlideShow.prototype.isInteractiveEffectPlaying = function()
17917     return ( this.nTotalInteractivePlayingEffects > 0 );
17920 SlideShow.prototype.isAnyEffectPlaying = function()
17922     return ( this.isMainEffectPlaying() || this.isInteractiveEffectPlaying() );
17925 SlideShow.prototype.hasAnyEffectStarted = function()
17927     return ( this.aStartedEffectList.length > 0 );
17930 SlideShow.prototype.notifyNextEffectStart = function()
17932     assert( !this.bIsNextEffectRunning,
17933             'SlideShow.notifyNextEffectStart: an effect is already started.' );
17934     this.bIsNextEffectRunning = true;
17935     this.aEventMultiplexer.registerNextEffectEndHandler( bind2( SlideShow.prototype.notifyNextEffectEnd, this ) );
17936     var aEffect = new Effect();
17937     aEffect.start();
17938     this.aStartedEffectIndexMap[ -1 ] = this.aStartedEffectList.length;
17939     this.aStartedEffectList.push( aEffect );
17941     var aAnimatedElementMap = theMetaDoc.aMetaSlideSet[nCurSlide].aSlideAnimationsHandler.aAnimatedElementMap;
17942     for( var sId in aAnimatedElementMap )
17943         aAnimatedElementMap[ sId ].notifyNextEffectStart( this.nCurrentEffect );
17946 SlideShow.prototype.notifyNextEffectEnd = function()
17948     assert( this.bIsNextEffectRunning,
17949             'SlideShow.notifyNextEffectEnd: effect already ended.' );
17950     this.bIsNextEffectRunning = false;
17952     this.aStartedEffectList[ this.aStartedEffectIndexMap[ -1 ] ].end();
17953     if( this.automaticAdvanceTimeout !== null )
17954     {
17955         if( this.automaticAdvanceTimeout['rewindedEffect'] === this.nCurrentEffect )
17956         {
17957             this.automaticAdvanceTimeout = null;
17958             this.notifyAnimationsEnd();
17959         }
17960     }
17963 SlideShow.prototype.notifyAnimationsEnd = function()
17965     if( nCurSlide + 1 === theMetaDoc.nNumberOfSlides )
17966         return;
17968     assert (this.automaticAdvanceTimeout === null,
17969         'SlideShow.notifyAnimationsEnd: Timeout already set.')
17971     var nTimeout = Math.ceil(theMetaDoc.aMetaSlideSet[nCurSlide].fDuration * 1000);
17972     if( nTimeout < 0 )
17973         return;
17975     this.automaticAdvanceTimeout = window.setTimeout('switchSlide(1, false)', nTimeout);
17978 SlideShow.prototype.notifySlideStart = function( nNewSlideIndex, nOldSlideIndex )
17980     this.nCurrentEffect = 0;
17981     this.bIsRewinding = false;
17982     this.bIsSkipping = false;
17983     this.bIsSkippingAll = false;
17984     this.nTotalInteractivePlayingEffects = 0;
17985     this.aStartedEffectList = [];
17986     this.aStartedEffectIndexMap = {};
17987     this.aStartedEffectIndexMap[ -1 ] = undefined;
17989     var aAnimatedElementMap;
17990     var sId;
17991     if( nOldSlideIndex !== undefined )
17992     {
17993         aAnimatedElementMap = theMetaDoc.aMetaSlideSet[nOldSlideIndex].aSlideAnimationsHandler.aAnimatedElementMap;
17994         for( sId in aAnimatedElementMap )
17995             aAnimatedElementMap[ sId ].notifySlideEnd();
17996     }
17998     aAnimatedElementMap = theMetaDoc.aMetaSlideSet[nNewSlideIndex].aSlideAnimationsHandler.aAnimatedElementMap;
17999     for( sId in aAnimatedElementMap )
18000         aAnimatedElementMap[ sId ].notifySlideStart( this.aContext );
18003 SlideShow.prototype.notifyTransitionEnd = function( nSlideIndex )
18005     // reset the presentation clip path on the leaving slide
18006     // to the standard one when transition ends
18007     if( theMetaDoc.getCurrentSlide() )
18008     {
18009         var sRef = 'url(#' + aPresentationClipPathId + ')';
18010         theMetaDoc.getCurrentSlide().slideElement.setAttribute('clip-path', sRef);
18011     }
18013     this.bIsTransitionRunning = false;
18014     if( this.bIsRewinding )
18015     {
18016         theMetaDoc.aMetaSlideSet[nSlideIndex].hide();
18017         var nIndex = nCurSlide !== undefined ? nCurSlide : -1;
18018         this.displaySlide( nIndex, true );
18019         this.skipAllEffects();
18020         this.bIsRewinding = false;
18021         return;
18022     }
18024     theMetaDoc.setCurrentSlide(nSlideIndex);
18026     if( this.aSlideViewElement )
18027     {
18028         theMetaDoc.getCurrentSlide().aVisibilityStatusElement.parentNode.removeChild( this.aSlideViewElement );
18029         this.aSlideViewElement = null;
18030     }
18031     if( this.isEnabled() )
18032     {
18033         // clear all queues
18034         this.dispose();
18036         var aCurrentSlide = theMetaDoc.getCurrentSlide();
18037         if( aCurrentSlide.aSlideAnimationsHandler.elementsParsed() )
18038         {
18039                         aCurrentSlide.aSlideAnimationsHandler.start();
18040                         this.aEventMultiplexer.registerAnimationsEndHandler( bind2( SlideShow.prototype.notifyAnimationsEnd, this ) );
18041         }
18042         else
18043             this.notifyAnimationsEnd();
18045         this.update();
18046     }
18047     else
18048         this.notifyAnimationsEnd();
18051 SlideShow.prototype.notifyInteractiveAnimationSequenceStart = function( nNodeId )
18053     ++this.nTotalInteractivePlayingEffects;
18054     var aEffect = new Effect( nNodeId );
18055     aEffect.start();
18056     this.aStartedEffectIndexMap[ nNodeId ] = this.aStartedEffectList.length;
18057     this.aStartedEffectList.push( aEffect );
18060 SlideShow.prototype.notifyInteractiveAnimationSequenceEnd = function( nNodeId )
18062     assert( this.isInteractiveEffectPlaying(),
18063             'SlideShow.notifyInteractiveAnimationSequenceEnd: no interactive effect playing.' );
18065     this.aStartedEffectList[ this.aStartedEffectIndexMap[ nNodeId ] ].end();
18066     --this.nTotalInteractivePlayingEffects;
18069 /** nextEffect
18070  *  Start the next effect belonging to the main animation sequence if any.
18071  *  If there is an already playing effect belonging to any animation sequence
18072  *  it is skipped.
18074  *  @return {Boolean}
18075  *      False if there is no more effect to start, true otherwise.
18076  */
18077 SlideShow.prototype.nextEffect = function()
18079     if( !this.isEnabled() )
18080         return false;
18082     if( this.isTransitionPlaying() )
18083     {
18084         this.skipTransition();
18085         return true;
18086     }
18088     if( this.isAnyEffectPlaying() )
18089     {
18090         this.skipAllPlayingEffects();
18091         return true;
18092     }
18094     if( !this.aNextEffectEventArray )
18095         return false;
18097     if( this.nCurrentEffect >= this.aNextEffectEventArray.size() )
18098         return false;
18100     this.notifyNextEffectStart();
18102     this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
18103     ++this.nCurrentEffect;
18104     this.update();
18105     return true;
18108 /** skipTransition
18109  *  Skip the current playing slide transition.
18110  */
18111 SlideShow.prototype.skipTransition  = function()
18113     if( this.bIsSkipping || this.bIsRewinding )
18114         return;
18116     this.bIsSkipping = true;
18118     this.aActivityQueue.endAll();
18119     this.aTimerEventQueue.forceEmpty();
18120     this.aActivityQueue.endAll();
18121     this.update();
18122     this.bIsSkipping = false;
18125 /** skipAllPlayingEffects
18126  *  Skip all playing effect, independently to which animation sequence they
18127  *  belong.
18129  */
18130 SlideShow.prototype.skipAllPlayingEffects  = function()
18132     if( this.bIsSkipping || this.bIsRewinding )
18133         return true;
18135     this.bIsSkipping = true;
18136     // TODO: The correct order should be based on the left playing time.
18137     for( var i = 0; i < this.aStartedEffectList.length; ++i )
18138     {
18139         var aEffect = this.aStartedEffectList[i];
18140         if( aEffect.isPlaying() )
18141         {
18142             if( aEffect.isMainEffect() )
18143                 this.aEventMultiplexer.notifySkipEffectEvent();
18144             else
18145                 this.aEventMultiplexer.notifySkipInteractiveEffectEvent( aEffect.getId() );
18146         }
18147     }
18148     this.update();
18149     this.bIsSkipping = false;
18150     return true;
18153 /** skipNextEffect
18154  *  Skip the next effect to be played (if any) that belongs to the main
18155  *  animation sequence.
18156  *  Require: no effect is playing.
18158  *  @return {Boolean}
18159  *      False if there is no more effect to skip, true otherwise.
18160  */
18161 SlideShow.prototype.skipNextEffect = function()
18163     if( this.bIsSkipping || this.bIsRewinding )
18164         return true;
18166     assert( !this.isAnyEffectPlaying(),
18167             'SlideShow.skipNextEffect' );
18169     if( !this.aNextEffectEventArray )
18170         return false;
18172     if( this.nCurrentEffect >= this.aNextEffectEventArray.size() )
18173         return false;
18175     this.notifyNextEffectStart();
18177     this.bIsSkipping = true;
18178     this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
18179     this.aEventMultiplexer.notifySkipEffectEvent();
18180     ++this.nCurrentEffect;
18181     this.update();
18182     this.bIsSkipping = false;
18183     return true;
18186 /** skipPlayingOrNextEffect
18187  *  Skip the next effect to be played that belongs to the main animation
18188  *  sequence  or all playing effects.
18190  *  @return {Boolean}
18191  *      False if there is no more effect to skip, true otherwise.
18192  */
18193 SlideShow.prototype.skipPlayingOrNextEffect = function()
18195     if( this.isTransitionPlaying() )
18196     {
18197         this.skipTransition();
18198         return true;
18199     }
18201     if( this.isAnyEffectPlaying() )
18202         return this.skipAllPlayingEffects();
18203     else
18204         return this.skipNextEffect();
18208 /** skipAllEffects
18209  *  Skip all left effects that belongs to the main animation sequence and all
18210  *  playing effects on the current slide.
18212  *  @return {Boolean}
18213  *      True if it already skipping or when it has ended skipping,
18214  *      false if the next slide needs to be displayed.
18215  */
18216 SlideShow.prototype.skipAllEffects = function()
18218     if( this.bIsSkippingAll )
18219         return true;
18221     this.bIsSkippingAll = true;
18223     if( this.isTransitionPlaying() )
18224     {
18225         this.skipTransition();
18226     }
18228     if( this.isAnyEffectPlaying() )
18229     {
18230         this.skipAllPlayingEffects();
18231     }
18232     else if( !this.aNextEffectEventArray
18233                || ( this.nCurrentEffect >= this.aNextEffectEventArray.size() ) )
18234     {
18235         this.bIsSkippingAll = false;
18236         return false;
18237     }
18239     // Pay attention here: a new next effect event is appended to
18240     // aNextEffectEventArray only after the related animation node has been
18241     // resolved, that is only after the animation node related to the previous
18242     // effect has notified to be deactivated to the main sequence time container.
18243     // So you should avoid any optimization here because the size of
18244     // aNextEffectEventArray will going on increasing after every skip action.
18245     while( this.nCurrentEffect < this.aNextEffectEventArray.size() )
18246     {
18247         this.skipNextEffect();
18248     }
18249     this.bIsSkippingAll = false;
18250     return true;
18253 /** rewindTransition
18254  * Rewind the current playing slide transition.
18255  */
18256 SlideShow.prototype.rewindTransition = function()
18258     if( this.bIsSkipping || this.bIsRewinding )
18259     return;
18261     this.bIsRewinding = true;
18262     this.aActivityQueue.endAll();
18263     this.update();
18264     this.bIsRewinding = false;
18267 /** rewindEffect
18268  *  Rewind all the effects started after at least one of the current playing
18269  *  effects. If there is no playing effect, it rewinds the last played one,
18270  *  both in case it belongs to the main or to an interactive animation sequence.
18272  */
18273 SlideShow.prototype.rewindEffect = function()
18275     if( this.bIsSkipping || this.bIsRewinding )
18276         return;
18278         if( this.automaticAdvanceTimeout !== null && !this.automaticAdvanceTimeout['rewindedEffect'] )
18279         {
18280                 window.clearTimeout( this.automaticAdvanceTimeout );
18281                 this.automaticAdvanceTimeout = { 'rewindedEffect': this.nCurrentEffect };
18282         }
18284     if( !this.hasAnyEffectStarted() )
18285     {
18286         this.rewindToPreviousSlide();
18287         return;
18288     }
18290     this.bIsRewinding = true;
18292     var nFirstPlayingEffectIndex = undefined;
18294     var i = 0;
18295     for( ; i < this.aStartedEffectList.length; ++i )
18296     {
18297         var aEffect = this.aStartedEffectList[i];
18298         if( aEffect.isPlaying() )
18299         {
18300             nFirstPlayingEffectIndex = i;
18301             break;
18302         }
18303     }
18305     // There is at least one playing effect.
18306     if( nFirstPlayingEffectIndex !== undefined )
18307     {
18308         i = this.aStartedEffectList.length - 1;
18309         for( ; i >= nFirstPlayingEffectIndex; --i )
18310         {
18311             aEffect = this.aStartedEffectList[i];
18312             if( aEffect.isPlaying() )
18313             {
18314                 if( aEffect.isMainEffect() )
18315                 {
18316                     this.aEventMultiplexer.notifyRewindCurrentEffectEvent();
18317                     if( this.nCurrentEffect > 0 )
18318                         --this.nCurrentEffect;
18319                 }
18320                 else
18321                 {
18322                     this.aEventMultiplexer.notifyRewindRunningInteractiveEffectEvent( aEffect.getId() );
18323                 }
18324             }
18325             else if( aEffect.isEnded() )
18326             {
18327                 if( aEffect.isMainEffect() )
18328                 {
18329                     this.aEventMultiplexer.notifyRewindLastEffectEvent();
18330                     if( this.nCurrentEffect > 0 )
18331                         --this.nCurrentEffect;
18332                 }
18333                 else
18334                 {
18335                     this.aEventMultiplexer.notifyRewindEndedInteractiveEffectEvent( aEffect.getId() );
18336                 }
18337             }
18338         }
18339         this.update();
18341         // Pay attention here: we need to remove all rewinded effects from
18342         // the started effect list only after updating.
18343         i = this.aStartedEffectList.length - 1;
18344         for( ; i >= nFirstPlayingEffectIndex; --i )
18345         {
18346             aEffect = this.aStartedEffectList.pop();
18347             if( !aEffect.isMainEffect() )
18348                 delete this.aStartedEffectIndexMap[ aEffect.getId() ];
18349         }
18350     }
18351     else  // there is no playing effect
18352     {
18353         aEffect = this.aStartedEffectList.pop();
18354         if( !aEffect.isMainEffect() )
18355             delete this.aStartedEffectIndexMap[ aEffect.getId() ];
18356         if( aEffect.isEnded() )  // Well that is almost an assertion.
18357         {
18358             if( aEffect.isMainEffect() )
18359             {
18360                 this.aEventMultiplexer.notifyRewindLastEffectEvent();
18361                 if( this.nCurrentEffect > 0 )
18362                     --this.nCurrentEffect;
18363             }
18364             else
18365             {
18366                 this.aEventMultiplexer.notifyRewindEndedInteractiveEffectEvent( aEffect.getId() );
18367             }
18368         }
18369         this.update();
18370     }
18372     this.bIsRewinding = false;
18375 /** rewindToPreviousSlide
18376  *  Displays the previous slide with all effects, that belong to the main
18377  *  animation sequence, played.
18379  */
18380 SlideShow.prototype.rewindToPreviousSlide = function()
18382     if( this.isTransitionPlaying() )
18383     {
18384         this.rewindTransition();
18385         return;
18386     }
18387     if( this.isAnyEffectPlaying() )
18388         return;
18389     var nNewSlide = nCurSlide - 1;
18390     this.displaySlide( nNewSlide, true );
18391     this.skipAllEffects();
18394 /** rewindAllEffects
18395  *  Rewind all effects already played on the current slide.
18397  */
18398 SlideShow.prototype.rewindAllEffects = function()
18400     if( !this.hasAnyEffectStarted() )
18401     {
18402         this.rewindToPreviousSlide();
18403         return;
18404     }
18406     while( this.hasAnyEffectStarted() )
18407     {
18408         this.rewindEffect();
18409     }
18412 SlideShow.prototype.exitSlideShowInApp = function()
18414     if (window.webkit !== undefined &&
18415         window.webkit.messageHandlers !== undefined &&
18416         window.webkit.messageHandlers.lool !== undefined)
18417         window.webkit.messageHandlers.lool.postMessage('EXITSLIDESHOW', '*');
18420 SlideShow.prototype.displaySlide = function( nNewSlide, bSkipSlideTransition )
18422     var aMetaDoc = theMetaDoc;
18423     var nSlides = aMetaDoc.nNumberOfSlides;
18424     if( nNewSlide < 0 && nSlides > 0 )
18425         nNewSlide = nSlides - 1;
18426     else if( nNewSlide >= nSlides ) {
18427         nNewSlide = 0;
18428         // In the iOS app, exit the slideshow when going past the end.
18429         this.exitSlideShowInApp();
18430     }
18432     if( ( currentMode === INDEX_MODE ) && ( nNewSlide === nCurSlide ) )
18433     {
18434         aMetaDoc.getCurrentSlide().show();
18435         return;
18436     }
18438     if( this.isTransitionPlaying() )
18439     {
18440         this.skipTransition();
18441     }
18443     // handle current slide
18444     var nOldSlide = nCurSlide;
18445     if( nOldSlide !== undefined )
18446     {
18447         var oldMetaSlide = aMetaDoc.aMetaSlideSet[nOldSlide];
18448         if( this.isEnabled() )
18449         {
18450             if( oldMetaSlide.aSlideAnimationsHandler.isAnimated() )
18451             {
18452                 // force end animations
18453                 oldMetaSlide.aSlideAnimationsHandler.end( bSkipSlideTransition );
18455                 // clear all queues
18456                 this.dispose();
18457             }
18458         }
18460         if( this.automaticAdvanceTimeout !== null )
18461         {
18462             window.clearTimeout( this.automaticAdvanceTimeout );
18463             this.automaticAdvanceTimeout = null;
18464         }
18465     }
18467     this.notifySlideStart( nNewSlide, nOldSlide );
18469     if( this.isEnabled() && !bSkipSlideTransition  )
18470     {
18471         // create slide transition and add to activity queue
18472         if ( ( ( nOldSlide !== undefined ) &&
18473                ( ( nNewSlide > nOldSlide ) ||
18474                ( ( nNewSlide == 0) && ( nOldSlide == (aMetaDoc.nNumberOfSlides - 1) ) ) ) ) ||
18475              (  ( nOldSlide === undefined ) &&  ( nNewSlide == 0) )  // for transition on first slide
18476            )
18477         {
18479             var aOldMetaSlide = null;
18480             if( nOldSlide === undefined ) // for transition on first slide
18481             {
18482                 aOldMetaSlide = aMetaDoc.theMetaDummySlide;
18483             }
18484             else
18485             {
18486                 aOldMetaSlide = aMetaDoc.aMetaSlideSet[nOldSlide];
18487             }
18488             var aNewMetaSlide = aMetaDoc.aMetaSlideSet[nNewSlide];
18490             var aSlideTransitionHandler = aNewMetaSlide.aTransitionHandler;
18491             if( aSlideTransitionHandler && aSlideTransitionHandler.isValid() )
18492             {
18493                 // clipPath element used for the leaving slide in order
18494                 // to avoid that slide borders are visible during transition
18495                 var sRef = 'url(#' + aPresentationClipPathShrinkId + ')';
18496                 aOldMetaSlide.slideElement.setAttribute( 'clip-path', sRef );
18498                 // when we switch from the last to the first slide we need to hide the last slide
18499                 // or nobody will see the transition, hence we create a view of the last slide and
18500                 // we place it before the first slide
18501                 if( nOldSlide > nNewSlide )
18502                 {
18503                     this.aSlideViewElement = document.createElementNS( NSS['svg'], 'use' );
18504                     setNSAttribute( 'xlink', this.aSlideViewElement, 'href', '#' + aOldMetaSlide.slideContainerId );
18505                     aNewMetaSlide.aVisibilityStatusElement.parentNode.insertBefore( this.aSlideViewElement, aNewMetaSlide.aVisibilityStatusElement );
18506                     aOldMetaSlide.hide();
18507                 }
18509                 var aLeavingSlide = aOldMetaSlide;
18510                 var aEnteringSlide = aNewMetaSlide;
18511                 var aTransitionEndEvent = makeEvent( bind2( this.notifyTransitionEnd, this, nNewSlide ) );
18513                 var aTransitionActivity =
18514                     this.createSlideTransition( aSlideTransitionHandler, aLeavingSlide,
18515                                                 aEnteringSlide, aTransitionEndEvent );
18517                 if( aTransitionActivity )
18518                 {
18519                     this.bIsTransitionRunning = true;
18520                     this.aActivityQueue.addActivity( aTransitionActivity );
18521                     this.update();
18522                 }
18523                 else
18524                 {
18525                     this.notifyTransitionEnd( nNewSlide );
18526                 }
18527             }
18528             else
18529             {
18530                 this.notifyTransitionEnd( nNewSlide );
18531             }
18532         }
18533         else
18534         {
18535             this.notifyTransitionEnd( nNewSlide );
18536         }
18537     }
18538     else
18539     {
18540         this.notifyTransitionEnd( nNewSlide );
18541     }
18545 SlideShow.prototype.update = function()
18547     this.aTimer.holdTimer();
18549     // process queues
18550     this.aTimerEventQueue.process();
18551     this.aActivityQueue.process();
18553     this.aFrameSynchronization.synchronize();
18555     this.aActivityQueue.processDequeued();
18557     this.aTimer.releaseTimer();
18559     var bActivitiesLeft = ( ! this.aActivityQueue.isEmpty() );
18560     var bTimerEventsLeft = ( ! this.aTimerEventQueue.isEmpty() );
18561     var bEventsLeft = ( bActivitiesLeft || bTimerEventsLeft );
18564     if( bEventsLeft )
18565     {
18566         var nNextTimeout;
18567         if( bActivitiesLeft )
18568         {
18569             nNextTimeout = MINIMUM_TIMEOUT;
18570             this.aFrameSynchronization.activate();
18571         }
18572         else
18573         {
18574             nNextTimeout = this.aTimerEventQueue.nextTimeout();
18575             if( nNextTimeout < MINIMUM_TIMEOUT )
18576                 nNextTimeout = MINIMUM_TIMEOUT;
18577             else if( nNextTimeout > MAXIMUM_TIMEOUT )
18578                 nNextTimeout = MAXIMUM_TIMEOUT;
18579             this.aFrameSynchronization.deactivate();
18580         }
18582         this.bIsIdle = false;
18583         window.setTimeout( 'aSlideShow.update()', nNextTimeout * 1000 );
18584     }
18585     else
18586     {
18587         this.bIsIdle = true;
18588     }
18591 SlideShow.prototype.dispose = function()
18593     // clear all queues
18594     this.aTimerEventQueue.clear();
18595     this.aActivityQueue.clear();
18596     this.aNextEffectEventArray = null;
18597     this.aEventMultiplexer = null;
18600 SlideShow.prototype.getContext = function()
18602     return this.aContext;
18605 // the SlideShow global instance
18606 var aSlideShow = null;
18611 function SlideShowContext( aTimerEventQueue, aEventMultiplexer, aNextEffectEventArray, aInteractiveAnimationSequenceMap, aActivityQueue)
18613     this.aTimerEventQueue = aTimerEventQueue;
18614     this.aEventMultiplexer = aEventMultiplexer;
18615     this.aNextEffectEventArray = aNextEffectEventArray;
18616     this.aInteractiveAnimationSequenceMap = aInteractiveAnimationSequenceMap;
18617     this.aActivityQueue = aActivityQueue;
18618     this.bIsSkipping = false;
18624 function FrameSynchronization( nFrameDuration )
18626     this.nFrameDuration = nFrameDuration;
18627     this.aTimer = new ElapsedTime();
18628     this.nNextFrameTargetTime = 0.0;
18629     this.bIsActive = false;
18631     this.markCurrentFrame();
18635 FrameSynchronization.prototype.markCurrentFrame = function()
18637     this.nNextFrameTargetTime = this.aTimer.getElapsedTime() + this.nFrameDuration;
18640 FrameSynchronization.prototype.synchronize = function()
18642     if( this.bIsActive )
18643     {
18644         // Do busy waiting for now.
18645         while( this.aTimer.getElapsedTime() < this.nNextFrameTargetTime )
18646             ;
18647     }
18649     this.markCurrentFrame();
18653 FrameSynchronization.prototype.activate = function()
18655     this.bIsActive = true;
18658 FrameSynchronization.prototype.deactivate = function()
18660     this.bIsActive = false;
18665 /**********************************************************************************************
18666  *      TimerEventQueue, ActivityQueue and ElapsedTime
18667  **********************************************************************************************/
18670 function NextEffectEventArray()
18672     this.aEventArray = [];
18676 NextEffectEventArray.prototype.size = function()
18678     return this.aEventArray.length;
18681 NextEffectEventArray.prototype.at = function( nIndex )
18683     return this.aEventArray[ nIndex ];
18686 NextEffectEventArray.prototype.appendEvent = function( aEvent )
18688     var nSize = this.size();
18689     for( var i = 0; i < nSize; ++i )
18690     {
18691         if( this.aEventArray[i].getId() == aEvent.getId() )
18692         {
18693             aNextEffectEventArrayDebugPrinter.print( 'NextEffectEventArray.appendEvent: event(' + aEvent.getId() + ') already present' );
18694             return false;
18695         }
18696     }
18697     this.aEventArray.push( aEvent );
18698     aNextEffectEventArrayDebugPrinter.print( 'NextEffectEventArray.appendEvent: event(' + aEvent.getId() + ') appended' );
18699     return true;
18702 NextEffectEventArray.prototype.clear = function( )
18704     this.aEventArray = [];
18710 function TimerEventQueue( aTimer )
18712     this.aTimer = aTimer;
18713     this.aEventSet = new PriorityQueue( EventEntry.compare );
18717 TimerEventQueue.prototype.addEvent = function( aEvent )
18719     this.DBG( 'TimerEventQueue.addEvent event(' + aEvent.getId() + ') appended.' );
18720     if( !aEvent )
18721     {
18722         log( 'TimerEventQueue.addEvent: null event' );
18723         return false;
18724     }
18726     var nTime = aEvent.getActivationTime( this.aTimer.getElapsedTime() );
18727     var aEventEntry = new EventEntry( aEvent, nTime );
18728     this.aEventSet.push( aEventEntry );
18730     return true;
18733 TimerEventQueue.prototype.forceEmpty = function()
18735     this.process_(true);
18739 TimerEventQueue.prototype.process = function()
18741     this.process_(false);
18744 TimerEventQueue.prototype.process_ = function( bFireAllEvents )
18746     var nCurrentTime = this.aTimer.getElapsedTime();
18748     while( !this.isEmpty() && ( bFireAllEvents || ( this.aEventSet.top().nActivationTime <= nCurrentTime ) ) )
18749     {
18750         var aEventEntry = this.aEventSet.top();
18751         this.aEventSet.pop();
18753         var aEvent = aEventEntry.aEvent;
18754         if( aEvent.isCharged() )
18755             aEvent.fire();
18756     }
18759 TimerEventQueue.prototype.isEmpty = function()
18761     return this.aEventSet.isEmpty();
18764 TimerEventQueue.prototype.nextTimeout = function()
18766     var nTimeout = Number.MAX_VALUE;
18767     var nCurrentTime = this.aTimer.getElapsedTime();
18768     if( !this.isEmpty() )
18769         nTimeout = this.aEventSet.top().nActivationTime - nCurrentTime;
18770     return nTimeout;
18773 TimerEventQueue.prototype.clear = function()
18775     this.DBG( 'TimerEventQueue.clear invoked' );
18776     this.aEventSet.clear();
18779 TimerEventQueue.prototype.getTimer = function()
18781     return this.aTimer;
18784 TimerEventQueue.prototype.DBG = function( sMessage, nTime )
18786     aTimerEventQueueDebugPrinter.print( sMessage, nTime );
18790 TimerEventQueue.prototype.insert = function( aEventEntry )
18792     var nHoleIndex = this.aEventSet.length;
18793     var nParent = Math.floor( ( nHoleIndex - 1 ) / 2 );
18795     while( ( nHoleIndex > 0 ) && this.aEventSet[ nParent ].compare( aEventEntry ) )
18796     {
18797         this.aEventSet[ nHoleIndex ] = this.aEventSet[ nParent ];
18798         nHoleIndex = nParent;
18799         nParent = Math.floor( ( nHoleIndex - 1 ) / 2 );
18800     }
18801     this.aEventSet[ nHoleIndex ] = aEventEntry;
18807 function EventEntry( aEvent, nTime )
18809     this.aEvent = aEvent;
18810     this.nActivationTime = nTime;
18814 EventEntry.compare = function( aLhsEventEntry, aRhsEventEntry )
18816     if ( aLhsEventEntry.nActivationTime > aRhsEventEntry.nActivationTime )
18817     {
18818         return -1;
18819     }
18820     else if ( aLhsEventEntry.nActivationTime < aRhsEventEntry.nActivationTime )
18821     {
18822         return 1;
18823     }
18824     else
18825     {
18826         return 0;
18827     }
18833 function ActivityQueue( aTimer )
18835     this.aTimer = aTimer;
18836     this.aCurrentActivityWaitingSet = [];
18837     this.aCurrentActivityReinsertSet = [];
18838     this.aDequeuedActivitySet = [];
18842 ActivityQueue.prototype.dispose = function()
18844     var nSize = this.aCurrentActivityWaitingSet.length;
18845     var i;
18846     for( i = 0; i < nSize; ++i )
18847         this.aCurrentActivityWaitingSet[i].dispose();
18849     nSize = this.aCurrentActivityReinsertSet.length;
18850     for( i = 0; i < nSize; ++i )
18851         this.aCurrentActivityReinsertSet[i].dispose();
18854 ActivityQueue.prototype.addActivity = function( aActivity )
18856     if( !aActivity )
18857     {
18858         log( 'ActivityQueue.addActivity: activity is not valid' );
18859         return false;
18860     }
18862     this.aCurrentActivityWaitingSet.push( aActivity );
18863     aActivityQueueDebugPrinter.print( 'ActivityQueue.addActivity: activity appended' );
18864     return true;
18867 ActivityQueue.prototype.process = function()
18869     var nSize = this.aCurrentActivityWaitingSet.length;
18870     var nLag = 0.0;
18871     for( var i = 0; i < nSize; ++i )
18872     {
18873         nLag = Math.max( nLag,this.aCurrentActivityWaitingSet[i].calcTimeLag()  );
18874     }
18876     if( nLag > 0.0 )
18877         this.aTimer.adjustTimer( -nLag, true );
18880     while( this.aCurrentActivityWaitingSet.length != 0 )
18881     {
18882         var aActivity = this.aCurrentActivityWaitingSet.shift();
18883         var bReinsert = false;
18885         bReinsert = aActivity.perform();
18887         if( bReinsert )
18888         {
18889             this.aCurrentActivityReinsertSet.push( aActivity );
18890         }
18891         else
18892         {
18893             this.aDequeuedActivitySet.push( aActivity );
18894         }
18895     }
18897     if( this.aCurrentActivityReinsertSet.length != 0 )
18898     {
18899         // TODO: optimization, try to swap reference here
18900         this.aCurrentActivityWaitingSet = this.aCurrentActivityReinsertSet;
18901         this.aCurrentActivityReinsertSet = [];
18902     }
18905 ActivityQueue.prototype.processDequeued = function()
18907     // notify all dequeued activities from last round
18908     var nSize = this.aDequeuedActivitySet.length;
18909     for( var i = 0; i < nSize; ++i )
18910         this.aDequeuedActivitySet[i].dequeued();
18912     this.aDequeuedActivitySet = [];
18915 ActivityQueue.prototype.isEmpty = function()
18917     return ( ( this.aCurrentActivityWaitingSet.length == 0 ) &&
18918              ( this.aCurrentActivityReinsertSet.length == 0 ) );
18921 ActivityQueue.prototype.clear = function()
18923     aActivityQueueDebugPrinter.print( 'ActivityQueue.clear invoked' );
18924     var nSize = this.aCurrentActivityWaitingSet.length;
18925     var i;
18926     for( i = 0; i < nSize; ++i )
18927         this.aCurrentActivityWaitingSet[i].dequeued();
18928     this.aCurrentActivityWaitingSet = [];
18930     nSize = this.aCurrentActivityReinsertSet.length;
18931     for( i = 0; i < nSize; ++i )
18932         this.aCurrentActivityReinsertSet[i].dequeued();
18933     this.aCurrentActivityReinsertSet = [];
18936 ActivityQueue.prototype.endAll = function()
18938     aActivityQueueDebugPrinter.print( 'ActivityQueue.endAll invoked' );
18939     var nSize = this.aCurrentActivityWaitingSet.length;
18940     var i;
18941     for( i = 0; i < nSize; ++i )
18942         this.aCurrentActivityWaitingSet[i].end();
18943     this.aCurrentActivityWaitingSet = [];
18945     nSize = this.aCurrentActivityReinsertSet.length;
18946     for( i = 0; i < nSize; ++i )
18947         this.aCurrentActivityReinsertSet[i].end();
18948     this.aCurrentActivityReinsertSet = [];
18951 ActivityQueue.prototype.getTimer = function()
18953     return this.aTimer;
18956 ActivityQueue.prototype.size = function()
18958     return ( this.aCurrentActivityWaitingSet.length +
18959              this.aCurrentActivityReinsertSet.length +
18960              this.aDequeuedActivitySet.length );
18966 function ElapsedTime( aTimeBase )
18968     this.aTimeBase = aTimeBase;
18969     this.nLastQueriedTime = 0.0;
18970     this.nStartTime = this.getCurrentTime();
18971     this.nFrozenTime = 0.0;
18972     this.bInPauseMode = false;
18973     this.bInHoldMode = false;
18977 ElapsedTime.prototype.getTimeBase = function()
18979     return this.aTimeBase;
18982 ElapsedTime.prototype.reset = function()
18984     this.nLastQueriedTime = 0.0;
18985     this.nStartTime = this.getCurrentTime();
18986     this.nFrozenTime = 0.0;
18987     this.bInPauseMode = false;
18988     this.bInHoldMode = false;
18991 ElapsedTime.prototype.getElapsedTime = function()
18993     this.nLastQueriedTime = this.getElapsedTimeImpl();
18994     return this.nLastQueriedTime;
18997 ElapsedTime.prototype.pauseTimer = function()
18999     this.nFrozenTime = this.getElapsedTimeImpl();
19000     this.bInPauseMode = true;
19003 ElapsedTime.prototype.continueTimer = function()
19005     this.bInPauseMode = false;
19007     // stop pausing, time runs again. Note that
19008     // getElapsedTimeImpl() honors hold mode, i.e. a
19009     // continueTimer() in hold mode will preserve the latter
19010     var nPauseDuration = this.getElapsedTimeImpl() - this.nFrozenTime;
19012     // adjust start time, such that subsequent getElapsedTime() calls
19013     // will virtually start from m_fFrozenTime.
19014     this.nStartTime += nPauseDuration;
19017 ElapsedTime.prototype.adjustTimer = function( nOffset, bLimitToLastQueriedTime )
19019     if( bLimitToLastQueriedTime == undefined )
19020         bLimitToLastQueriedTime = true;
19022     // to make getElapsedTime() become _larger_, have to reduce nStartTime.
19023     this.nStartTime -= nOffset;
19025     // also adjust frozen time, this method must _always_ affect the
19026     // value returned by getElapsedTime()!
19027     if( this.bInHoldMode || this.bInPauseMode )
19028         this.nFrozenTime += nOffset;
19031 ElapsedTime.prototype.holdTimer = function()
19033     // when called during hold mode (e.g. more than once per time
19034     // object), the original hold time will be maintained.
19035     this.nFrozenTime = this.getElapsedTimeImpl();
19036     this.bInHoldMode = true;
19039 ElapsedTime.prototype.releaseTimer = function()
19041     this.bInHoldMode = false;
19044 ElapsedTime.prototype.getSystemTime = function()
19046     return ( getCurrentSystemTime() / 1000.0 );
19049 ElapsedTime.prototype.getCurrentTime = function()
19051     var nCurrentTime;
19052     if ( !this.aTimeBase )
19053     {
19054         nCurrentTime = this.getSystemTime();
19055     }
19056     else
19057     {
19058         nCurrentTime = this.aTimeBase.getElapsedTimeImpl();
19059     }
19061     assert( ( typeof( nCurrentTime ) === typeof( 0 ) ) && isFinite( nCurrentTime ),
19062             'ElapsedTime.getCurrentTime: assertion failed: nCurrentTime == ' + nCurrentTime );
19065     return nCurrentTime;
19068 ElapsedTime.prototype.getElapsedTimeImpl = function()
19070     if( this.bInHoldMode || this.bInPauseMode )
19071     {
19072         return this.nFrozenTime;
19073     }
19075     var nCurTime = this.getCurrentTime();
19076     return ( nCurTime - this.nStartTime );
19081 /*****
19082  * @libreofficeend
19084  * Several parts of the above code are the result of the porting,
19085  * started on August 2011, of the C++ code included in the source files
19086  * placed under the folder '/slideshow/source' and subfolders.
19087  * @source https://cgit.freedesktop.org/libreoffice/core/tree/slideshow/source
19089  */
19091 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */