1 /* -*- Mode: JS; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
4 * - Presentation Engine - *
5 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
8 * WARNING: any comment that should not be striped out by the script
9 * generating the C++ header file must start with a '/' and exactly 5 '*'
10 * not striped examples: '/*****', '/***** *'
11 * striped examples: '/** ***' (not contiguous), '/******' (more than 5)
13 * NOTE: This file combines several works, under different
14 * licenses. See the @licstart / @licend sections below.
17 /*! Hammer.JS - v2.0.7 - 2016-04-22
18 * http://hammerjs.github.io/
20 * Copyright (c) 2016 Jorik Tangelder;
21 * Licensed under the MIT license */
22 (function(window, document, exportName, undefined) {
25 var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
26 var TEST_ELEMENT = document.createElement('div');
28 var TYPE_FUNCTION = 'function';
30 var round = Math.round;
35 * set a timeout with a given scope
36 * @param {Function} fn
37 * @param {Number} timeout
38 * @param {Object} context
41 function setTimeoutContext(fn, timeout, context) {
42 return setTimeout(bindFn(fn, context), timeout);
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
51 * @param {Object} [context]
54 function invokeArrayArg(arg, fn, context) {
55 if (Array.isArray(arg)) {
56 each(arg, context[fn], context);
63 * walk objects and arrays
65 * @param {Function} iterator
66 * @param {Object} context
68 function each(obj, iterator, context) {
76 obj.forEach(iterator, context);
77 } else if (obj.length !== undefined) {
79 while (i < obj.length) {
80 iterator.call(context, obj[i], i, obj);
85 obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
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.
97 function deprecate(method, name, message) {
98 var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
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);
107 log.call(window.console, deprecationMessage, stack);
109 return method.apply(this, arguments);
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
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');
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];
141 assign = Object.assign;
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
152 var extend = deprecate(function extend(dest, src, merge) {
153 var keys = Object.keys(src);
155 while (i < keys.length) {
156 if (!merge || (merge && dest[keys[i]] === undefined)) {
157 dest[keys[i]] = src[keys[i]];
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
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]
181 function inherit(child, base, properties) {
182 var baseP = base.prototype,
185 childP = child.prototype = Object.create(baseP);
186 childP.constructor = child;
187 childP._super = baseP;
190 assign(childP, properties);
195 * simple function bind
196 * @param {Function} fn
197 * @param {Object} context
198 * @returns {Function}
200 function bindFn(fn, context) {
201 return function boundFn() {
202 return fn.apply(context, arguments);
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]
213 function boolOrFn(val, args) {
214 if (typeof val == TYPE_FUNCTION) {
215 return val.apply(args ? args[0] || undefined : undefined, args);
221 * use the val2 when val1 is undefined
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
236 function addEventListeners(target, types, handler) {
237 each(splitStr(types), function(type) {
238 target.addEventListener(type, handler, false);
243 * removeEventListener with multiple events at once
244 * @param {EventTarget} target
245 * @param {String} types
246 * @param {Function} handler
248 function removeEventListeners(target, types, handler) {
249 each(splitStr(types), function(type) {
250 target.removeEventListener(type, handler, false);
255 * find if a node is in the given parent
257 * @param {HTMLElement} node
258 * @param {HTMLElement} parent
259 * @return {Boolean} found
261 function hasParent(node, parent) {
263 if (node == parent) {
266 node = node.parentNode;
272 * small indexOf wrapper
273 * @param {String} str
274 * @param {String} find
275 * @returns {Boolean} found
277 function inStr(str, find) {
278 return str.indexOf(find) > -1;
282 * split string on whitespace
283 * @param {String} str
284 * @returns {Array} words
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
293 * @param {String} find
294 * @param {String} [findByKey]
295 * @return {Boolean|Number} false when not found, or the index
297 function inArray(src, find, findByKey) {
298 if (src.indexOf && !findByKey) {
299 return src.indexOf(find);
302 while (i < src.length) {
303 if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
313 * convert array-like objects to real arrays
314 * @param {Object} obj
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}]
328 function uniqueArray(src, key, sort) {
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]);
344 results = results.sort();
346 results = results.sort(function sortUniqueArray(a, b) {
347 return a[key] > b[key];
356 * get the prefixed property
357 * @param {Object} obj
358 * @param {String} property
359 * @returns {String|Undefined} prefixed
361 function prefixed(obj, property) {
362 // tml: Have to check for obj being undefined
363 if (obj === undefined) {
368 var camelProp = property[0].toUpperCase() + property.slice(1);
371 while (i < VENDOR_PREFIXES.length) {
372 prefix = VENDOR_PREFIXES[i];
373 prop = (prefix) ? prefix + camelProp : property;
385 * @returns {number} uniqueId
388 function uniqueId() {
393 * get the window object of an element
394 * @param {HTMLElement} element
395 * @returns {DocumentView|Window}
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;
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
440 function Input(manager, callback) {
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])) {
461 * should handle the inputEvent data and trigger the callback
464 handler: 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);
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);
486 * create new input type manager
487 * called by the Manager constructor
488 * @param {Hammer} manager
491 function createInputInstance(manager) {
493 var inputClass = manager.options.inputClass;
497 } else if (!SUPPORT_TOUCH && SUPPORT_POINTER_EVENTS) {
498 Type = PointerEventInput;
499 } else if (SUPPORT_ONLY_TOUCH) {
501 } else if (!SUPPORT_TOUCH) {
504 Type = TouchMouseInput;
506 return new (Type)(manager, inputHandler);
510 * handle input events
511 * @param {Manager} manager
512 * @param {String} eventType
513 * @param {Object} input
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;
525 manager.session = {};
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);
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
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);
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;
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;
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
611 offset = session.offsetDelta = {
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
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);
638 velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
639 direction = getDirection(deltaX, deltaY);
641 session.lastInterval = input;
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;
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
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
666 while (i < input.pointers.length) {
668 clientX: round(input.pointers[i].clientX),
669 clientY: round(input.pointers[i].clientY)
677 center: getCenter(pointers),
678 deltaX: input.deltaX,
684 * get the center of all the pointers
685 * @param {Array} pointers
686 * @return {Object} center contains `x` and `y` properties
688 function getCenter(pointers) {
689 var pointersLength = pointers.length;
691 // no need to loop when only one touch
692 if (pointersLength === 1) {
694 x: round(pointers[0].clientX),
695 y: round(pointers[0].clientY)
699 var x = 0, y = 0, i = 0;
700 while (i < pointersLength) {
701 x += pointers[i].clientX;
702 y += pointers[i].clientY;
707 x: round(x / pointersLength),
708 y: round(y / pointersLength)
713 * calculate the velocity between two points. unit is in px per ms.
714 * @param {Number} deltaTime
717 * @return {Object} velocity `x` and `y`
719 function getVelocity(deltaTime, x, y) {
721 x: x / deltaTime || 0,
722 y: y / deltaTime || 0
727 * get the direction between two points
730 * @return {Number} direction
732 function getDirection(x, y) {
734 return DIRECTION_NONE;
737 if (abs(x) >= abs(y)) {
738 return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
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
750 function getDistance(p1, p2, props) {
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
764 * @param {Array} [props] containing x and y keys
765 * @return {Number} angle
767 function getAngle(p1, p2, props) {
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
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
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,
803 var MOUSE_ELEMENT_EVENTS = 'mousedown';
804 var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
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, {
822 * handle mouse events
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) {
834 if (eventType & INPUT_MOVE && ev.which !== 1) {
835 eventType = INPUT_END;
838 // mouse must be down
843 if (eventType & INPUT_END) {
844 this.pressed = false;
847 this.callback(this.manager, eventType, {
849 changedPointers: [ev],
850 pointerType: INPUT_TYPE_MOUSE,
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 = {
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
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, {
897 * handle mouse events
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) {
918 storeIndex = store.length - 1;
920 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
921 removePointer = true;
924 // it not found, so the pointer hasn't been down (so it's probably a hover)
925 if (storeIndex < 0) {
929 // update the event in the store
930 store[storeIndex] = ev;
932 this.callback(this.manager, eventType, {
934 changedPointers: [ev],
935 pointerType: pointerType,
940 // remove from the store
941 store.splice(storeIndex, 1);
946 var SINGLE_TOUCH_INPUT_MAP = {
947 touchstart: INPUT_START,
948 touchmove: INPUT_MOVE,
950 touchcancel: INPUT_CANCEL
953 var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
954 var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
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) {
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;
990 this.callback(this.manager, type, {
991 pointers: touches[0],
992 changedPointers: touches[1],
993 pointerType: INPUT_TYPE_TOUCH,
1000 * @this {TouchInput}
1001 * @param {Object} ev
1002 * @param {Number} type flag
1003 * @returns {undefined|Array} [all, changed]
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);
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
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);
1046 this.callback(this.manager, type, {
1047 pointers: touches[0],
1048 changedPointers: touches[1],
1049 pointerType: INPUT_TYPE_TOUCH,
1056 * @this {TouchInput}
1057 * @param {Object} ev
1058 * @param {Number} type flag
1059 * @returns {undefined|Array} [all, changed]
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];
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);
1083 if (type === INPUT_START) {
1085 while (i < targetTouches.length) {
1086 targetIds[targetTouches[i].identifier] = true;
1091 // filter changed touches to only contain touches that exist in the collected target ids
1093 while (i < changedTouches.length) {
1094 if (targetIds[changedTouches[i].identifier]) {
1095 changedTargetTouches.push(changedTouches[i]);
1098 // cleanup removed touches
1099 if (type & (INPUT_END | INPUT_CANCEL)) {
1100 delete targetIds[changedTouches[i].identifier];
1105 if (!changedTargetTouches.length) {
1110 // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
1111 uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
1112 changedTargetTouches
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.
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, {
1142 * handle mouse and touch events
1143 * @param {Hammer} manager
1144 * @param {String} inputEvent
1145 * @param {Object} inputData
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) {
1156 // when we're in a touch event, record touches to de-dupe synthetic mouse event
1158 recordTouches.call(this, inputEvent, inputData);
1159 } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
1163 this.callback(manager, inputEvent, inputData);
1167 * remove the event listeners
1169 destroy: function destroy() {
1170 this.touch.destroy();
1171 this.mouse.destroy();
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);
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);
1197 setTimeout(removeLastTouch, DEDUP_TIMEOUT);
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) {
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();
1227 * sets the touchAction property or uses the js alternative
1228 * @param {Manager} manager
1229 * @param {String} value
1232 function TouchAction(manager, value) {
1233 this.manager = manager;
1237 TouchAction.prototype = {
1239 * set the touchAction value on the element or enable the polyfill
1240 * @param {String} value
1242 set: function(value) {
1243 // find out the touch-action by the event handlers
1244 if (value == TOUCH_ACTION_COMPUTE) {
1245 value = this.compute();
1248 if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
1249 this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
1251 this.actions = value.toLowerCase().trim();
1255 * just re-set the touchAction value
1257 update: function() {
1258 this.set(this.manager.options.touchAction);
1262 * compute the value for the touchAction property based on the recognizer's settings
1263 * @returns {String} value
1265 compute: function() {
1267 each(this.manager.recognizers, function(recognizer) {
1268 if (boolOrFn(recognizer.options.enable, [recognizer])) {
1269 actions = actions.concat(recognizer.getTouchAction());
1272 return cleanTouchActions(actions.join(' '));
1276 * this method is called on each input cycle and provides the preventing of the browser behavior
1277 * @param {Object} input
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();
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];
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) {
1306 if (hasPanX && hasPanY) {
1307 // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
1312 (hasPanY && direction & DIRECTION_HORIZONTAL) ||
1313 (hasPanX && direction & DIRECTION_VERTICAL)) {
1314 return this.preventSrc(srcEvent);
1319 * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
1320 * @param {Object} srcEvent
1322 preventSrc: function(srcEvent) {
1323 this.manager.session.prevented = true;
1324 srcEvent.preventDefault();
1329 * when the touchActions are collected they are not a valid value, so we need to clean things up. *
1330 * @param {String} actions
1333 function cleanTouchActions(actions) {
1335 if (inStr(actions, TOUCH_ACTION_NONE)) {
1336 return TOUCH_ACTION_NONE;
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;
1351 if (hasPanX || hasPanY) {
1352 return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
1356 if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
1357 return TOUCH_ACTION_MANIPULATION;
1360 return TOUCH_ACTION_AUTO;
1363 function getTouchActionProps() {
1364 if (!NATIVE_TOUCH_ACTION) {
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;
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.
1392 * +-----+---------------+
1396 * Failed Cancelled |
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;
1415 * Every recognizer needs to extend from this class.
1417 * @param {Object} options
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 = {
1444 * @param {Object} options
1445 * @return {Recognizer}
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();
1456 * recognize simultaneous with another recognizer.
1457 * @param {Recognizer} otherRecognizer
1458 * @returns {Recognizer} this
1460 recognizeWith: function(otherRecognizer) {
1461 if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
1465 var simultaneous = this.simultaneous;
1466 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1467 if (!simultaneous[otherRecognizer.id]) {
1468 simultaneous[otherRecognizer.id] = otherRecognizer;
1469 otherRecognizer.recognizeWith(this);
1475 * drop the simultaneous link. It doesn't remove the link on the other recognizer.
1476 * @param {Recognizer} otherRecognizer
1477 * @returns {Recognizer} this
1479 dropRecognizeWith: function(otherRecognizer) {
1480 if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
1484 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1485 delete this.simultaneous[otherRecognizer.id];
1490 * recognizer can only run when another is failing
1491 * @param {Recognizer} otherRecognizer
1492 * @returns {Recognizer} this
1494 requireFailure: function(otherRecognizer) {
1495 if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
1499 var requireFail = this.requireFail;
1500 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1501 if (inArray(requireFail, otherRecognizer) === -1) {
1502 requireFail.push(otherRecognizer);
1503 otherRecognizer.requireFailure(this);
1509 * drop the requireFailure link. It does not remove the link on the other recognizer.
1510 * @param {Recognizer} otherRecognizer
1511 * @returns {Recognizer} this
1513 dropRequireFailure: function(otherRecognizer) {
1514 if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
1518 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
1519 var index = inArray(this.requireFail, otherRecognizer);
1521 this.requireFail.splice(index, 1);
1527 * has require failures boolean
1528 * @returns {boolean}
1530 hasRequireFailures: function() {
1531 return this.requireFail.length > 0;
1535 * if the recognizer can recognize simultaneous with another recognizer
1536 * @param {Recognizer} otherRecognizer
1537 * @returns {Boolean}
1539 canRecognizeWith: function(otherRecognizer) {
1540 return !!this.simultaneous[otherRecognizer.id];
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
1548 emit: function(input) {
1550 var state = this.state;
1552 function emit(event) {
1553 self.manager.emit(event, input);
1556 // 'panstart' and 'panmove'
1557 if (state < STATE_ENDED) {
1558 emit(self.options.event + stateStr(state));
1561 emit(self.options.event); // simple 'eventName' events
1563 if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
1564 emit(input.additionalEvent);
1567 // panend and pancancel
1568 if (state >= STATE_ENDED) {
1569 emit(self.options.event + stateStr(state));
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
1579 tryEmit: function(input) {
1580 if (this.canEmit()) {
1581 return this.emit(input);
1583 // it's failing anyway
1584 this.state = STATE_FAILED;
1589 * @returns {boolean}
1591 canEmit: function() {
1593 while (i < this.requireFail.length) {
1594 if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
1603 * update the recognizer
1604 * @param {Object} inputData
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])) {
1614 this.state = STATE_FAILED;
1618 // reset when we've reached the end
1619 if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
1620 this.state = STATE_POSSIBLE;
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);
1633 * return the state of the recognizer
1634 * the actual recognizing happens in this method
1636 * @param {Object} inputData
1637 * @returns {Const} STATE
1639 process: function(inputData) { }, // jshint ignore:line
1642 * return the preferred touch-action
1646 getTouchAction: function() { },
1649 * called when the gesture isn't allowed to recognize
1650 * like when another is being recognized or it is disabled
1653 reset: function() { }
1657 * get a usable string, used as event postfix
1658 * @param {Const} state
1659 * @returns {String} state
1661 function stateStr(state) {
1662 if (state & STATE_CANCELLED) {
1664 } else if (state & STATE_ENDED) {
1666 } else if (state & STATE_CHANGED) {
1668 } else if (state & STATE_BEGAN) {
1675 * direction cons to string
1676 * @param {Const} direction
1679 function directionStr(direction) {
1680 if (direction == DIRECTION_DOWN) {
1682 } else if (direction == DIRECTION_UP) {
1684 } else if (direction == DIRECTION_LEFT) {
1686 } else if (direction == DIRECTION_RIGHT) {
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}
1698 function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
1699 var manager = recognizer.manager;
1701 return manager.get(otherRecognizer);
1703 return otherRecognizer;
1707 * This recognizer is just used as a base for the simple attribute recognizers.
1709 * @extends Recognizer
1711 function AttrRecognizer() {
1712 Recognizer.apply(this, arguments);
1715 inherit(AttrRecognizer, Recognizer, {
1718 * @memberof AttrRecognizer
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
1734 attrTest: function(input) {
1735 var optionPointers = this.options.pointers;
1736 return optionPointers === 0 || input.pointers.length === optionPointers;
1740 * Process the input and return the state for the recognizer
1741 * @memberof AttrRecognizer
1742 * @param {Object} input
1743 * @returns {*} State
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)) {
1761 return state | STATE_CHANGED;
1763 return STATE_FAILED;
1769 * Recognized when the pointer is down and moved in the allowed direction.
1771 * @extends AttrRecognizer
1773 function PanRecognizer() {
1774 AttrRecognizer.apply(this, arguments);
1780 inherit(PanRecognizer, AttrRecognizer, {
1783 * @memberof PanRecognizer
1789 direction: DIRECTION_ALL
1792 getTouchAction: function() {
1793 var direction = this.options.direction;
1795 if (direction & DIRECTION_HORIZONTAL) {
1796 actions.push(TOUCH_ACTION_PAN_Y);
1798 if (direction & DIRECTION_VERTICAL) {
1799 actions.push(TOUCH_ACTION_PAN_X);
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;
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);
1819 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
1820 hasMoved = y != this.pY;
1821 distance = Math.abs(input.deltaY);
1824 input.direction = direction;
1825 return hasMoved && distance > options.threshold && direction & options.direction;
1828 attrTest: function(input) {
1829 return AttrRecognizer.prototype.attrTest.call(this, input) &&
1830 (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
1833 emit: function(input) {
1835 this.pX = input.deltaX;
1836 this.pY = input.deltaY;
1838 var direction = directionStr(input.direction);
1841 input.additionalEvent = this.options.event + direction;
1843 this._super.emit.call(this, input);
1849 * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
1851 * @extends AttrRecognizer
1853 function PinchRecognizer() {
1854 AttrRecognizer.apply(this, arguments);
1857 inherit(PinchRecognizer, AttrRecognizer, {
1860 * @memberof PinchRecognizer
1868 getTouchAction: function() {
1869 return [TOUCH_ACTION_NONE];
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);
1877 emit: function(input) {
1878 if (input.scale !== 1) {
1879 var inOut = input.scale < 1 ? 'in' : 'out';
1880 input.additionalEvent = this.options.event + inOut;
1882 this._super.emit.call(this, input);
1888 * Recognized when the pointer is down for x ms without any movement.
1890 * @extends Recognizer
1892 function PressRecognizer() {
1893 Recognizer.apply(this, arguments);
1899 inherit(PressRecognizer, Recognizer, {
1902 * @memberof PressRecognizer
1907 time: 251, // minimal time of the pointer to be pressed
1908 threshold: 9 // a minimal movement is ok, but keep it low
1911 getTouchAction: function() {
1912 return [TOUCH_ACTION_AUTO];
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)) {
1927 } else if (input.eventType & INPUT_START) {
1929 this._timer = setTimeoutContext(function() {
1930 this.state = STATE_RECOGNIZED;
1932 }, options.time, this);
1933 } else if (input.eventType & INPUT_END) {
1934 return STATE_RECOGNIZED;
1936 return STATE_FAILED;
1940 clearTimeout(this._timer);
1943 emit: function(input) {
1944 if (this.state !== STATE_RECOGNIZED) {
1948 if (input && (input.eventType & INPUT_END)) {
1949 this.manager.emit(this.options.event + 'up', input);
1951 this._input.timeStamp = now();
1952 this.manager.emit(this.options.event, this._input);
1959 * Recognized when two or more pointer are moving in a circular motion.
1961 * @extends AttrRecognizer
1963 function RotateRecognizer() {
1964 AttrRecognizer.apply(this, arguments);
1967 inherit(RotateRecognizer, AttrRecognizer, {
1970 * @memberof RotateRecognizer
1978 getTouchAction: function() {
1979 return [TOUCH_ACTION_NONE];
1982 attrTest: function(input) {
1983 return this._super.attrTest.call(this, input) &&
1984 (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
1990 * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
1992 * @extends AttrRecognizer
1994 function SwipeRecognizer() {
1995 AttrRecognizer.apply(this, arguments);
1998 inherit(SwipeRecognizer, AttrRecognizer, {
2001 * @memberof SwipeRecognizer
2007 direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
2011 getTouchAction: function() {
2012 return PanRecognizer.prototype.getTouchAction.call(this);
2015 attrTest: function(input) {
2016 var direction = this.options.direction;
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;
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;
2034 emit: function(input) {
2035 var direction = directionStr(input.offsetDirection);
2037 this.manager.emit(this.options.event + direction, input);
2040 this.manager.emit(this.options.event, input);
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
2049 * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
2050 * multi-taps being recognized.
2052 * @extends Recognizer
2054 function TapRecognizer() {
2055 Recognizer.apply(this, arguments);
2057 // previous time and center,
2058 // used for tap counting
2060 this.pCenter = false;
2067 inherit(TapRecognizer, Recognizer, {
2070 * @memberof PinchRecognizer
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
2082 getTouchAction: function() {
2083 return [TOUCH_ACTION_MANIPULATION];
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;
2095 if ((input.eventType & INPUT_START) && (this.count === 0)) {
2096 return this.failTimeout();
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();
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) {
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;
2129 this._timer = setTimeoutContext(function() {
2130 this.state = STATE_RECOGNIZED;
2132 }, options.interval, this);
2137 return STATE_FAILED;
2140 failTimeout: function() {
2141 this._timer = setTimeoutContext(function() {
2142 this.state = STATE_FAILED;
2143 }, this.options.interval, this);
2144 return STATE_FAILED;
2148 clearTimeout(this._timer);
2152 if (this.state == STATE_RECOGNIZED) {
2153 this._input.tapCount = this.count;
2154 this.manager.emit(this.options.event, this._input);
2160 * Simple way to create a manager with a default set of recognizers.
2161 * @param {HTMLElement} element
2162 * @param {Object} [options]
2165 function Hammer(element, options) {
2166 options = options || {};
2167 options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);
2168 return new Manager(element, options);
2174 Hammer.VERSION = '2.0.7';
2182 * set if DOM events are being triggered.
2183 * But this is slower and unused by simple implementations, so disabled by default.
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.
2195 touchAction: TOUCH_ACTION_COMPUTE,
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}
2213 * force an input class
2214 * @type {Null|Function}
2220 * Default recognizer setup when calling `Hammer()`
2221 * When creating a new Manager these will be skipped.
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']],
2231 [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
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.
2242 * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
2249 * Disable the Windows Phone grippers when pressing an element.
2253 touchSelect: 'none',
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.
2262 touchCallout: 'none',
2265 * Specifies whether zooming is enabled. Used by IE10>
2269 contentZooming: 'none',
2272 * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
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.
2282 * @default 'rgba(0,0,0,0)'
2284 tapHighlightColor: 'rgba(0,0,0,0)'
2289 var FORCED_STOP = 2;
2293 * @param {HTMLElement} element
2294 * @param {Object} [options]
2297 function Manager(element, options) {
2298 this.options = assign({}, Hammer.defaults, options || {});
2300 this.options.inputTarget = this.options.inputTarget || element;
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]);
2320 Manager.prototype = {
2323 * @param {Object} options
2324 * @returns {Manager}
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();
2333 if (options.inputTarget) {
2334 // Clean up existing event listeners and reinitialize
2335 this.input.destroy();
2336 this.input.target = options.inputTarget;
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]
2348 stop: function(force) {
2349 this.session.stopped = force ? FORCED_STOP : STOP;
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
2358 recognize: function(inputData) {
2359 var session = this.session;
2360 if (session.stopped) {
2364 // run the touch-action polyfill
2365 this.touchAction.preventDefaults(inputData);
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;
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);
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;
2409 * get a recognizer by its event name.
2410 * @param {Recognizer|String} recognizer
2411 * @returns {Recognizer|Null}
2413 get: function(recognizer) {
2414 if (recognizer instanceof Recognizer) {
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];
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}
2433 add: function(recognizer) {
2434 if (invokeArrayArg(recognizer, 'add', this)) {
2439 var existing = this.get(recognizer.options.event);
2441 this.remove(existing);
2444 this.recognizers.push(recognizer);
2445 recognizer.manager = this;
2447 this.touchAction.update();
2452 * remove a recognizer by name or instance
2453 * @param {Recognizer|String} recognizer
2454 * @returns {Manager}
2456 remove: function(recognizer) {
2457 if (invokeArrayArg(recognizer, 'remove', this)) {
2461 recognizer = this.get(recognizer);
2463 // let's make sure this recognizer exists
2465 var recognizers = this.recognizers;
2466 var index = inArray(recognizers, recognizer);
2469 recognizers.splice(index, 1);
2470 this.touchAction.update();
2479 * @param {String} events
2480 * @param {Function} handler
2481 * @returns {EventEmitter} this
2483 on: function(events, handler) {
2484 if (events === undefined) {
2487 if (handler === undefined) {
2491 var handlers = this.handlers;
2492 each(splitStr(events), function(event) {
2493 handlers[event] = handlers[event] || [];
2494 handlers[event].push(handler);
2500 * unbind event, leave emit blank to remove all handlers
2501 * @param {String} events
2502 * @param {Function} [handler]
2503 * @returns {EventEmitter} this
2505 off: function(events, handler) {
2506 if (events === undefined) {
2510 var handlers = this.handlers;
2511 each(splitStr(events), function(event) {
2513 delete handlers[event];
2515 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
2522 * emit event to the listeners
2523 * @param {String} event
2524 * @param {Object} data
2526 emit: function(event, data) {
2527 // we also want to trigger dom events
2528 if (this.options.domEvents) {
2529 triggerDomEvent(event, data);
2532 // no handlers, so skip it all
2533 var handlers = this.handlers[event] && this.handlers[event].slice();
2534 if (!handlers || !handlers.length) {
2539 data.preventDefault = function() {
2540 data.srcEvent.preventDefault();
2544 while (i < handlers.length) {
2551 * destroy the manager and unbinds all events
2552 * it doesn't unbind dom events, that is the user own responsibility
2554 destroy: function() {
2555 this.element && toggleCssProps(this, false);
2559 this.input.destroy();
2560 this.element = null;
2565 * add/remove the css properties as defined in manager.options.cssProps
2566 * @param {Manager} manager
2567 * @param {Boolean} add
2569 function toggleCssProps(manager, add) {
2570 var element = manager.element;
2571 if (!element.style) {
2575 each(manager.options.cssProps, function(value, name) {
2576 prop = prefixed(element.style, name);
2578 manager.oldCssProps[prop] = element.style[prop];
2579 element.style[prop] = value;
2581 element.style[prop] = manager.oldCssProps[prop] || '';
2585 manager.oldCssProps = {};
2591 * @param {String} event
2592 * @param {Object} data
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);
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,
2626 TouchAction: TouchAction,
2628 TouchInput: TouchInput,
2629 MouseInput: MouseInput,
2630 PointerEventInput: PointerEventInput,
2631 TouchMouseInput: TouchMouseInput,
2632 SingleTouchInput: SingleTouchInput,
2634 Recognizer: Recognizer,
2635 AttrRecognizer: AttrRecognizer,
2638 Swipe: SwipeRecognizer,
2639 Pinch: PinchRecognizer,
2640 Rotate: RotateRecognizer,
2641 Press: PressRecognizer,
2643 on: addEventListeners,
2644 off: removeEventListeners,
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) {
2663 } else if (typeof module != 'undefined' && module.exports) {
2664 module.exports = Hammer;
2666 window[exportName] = Hammer;
2669 })(window, document, 'Hammer');
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.
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/.
2702 * You can find the complete source code of the JessyInk project at:
2703 * @source http://code.google.com/p/jessyink/
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.
2718 * The following code is a derivative work of some parts of the JessyInk
2720 * @source http://code.google.com/p/jessyink/
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'
2732 function getElementsByProperty( node, name )
2736 if( node.getAttribute( name ) )
2737 elements.push( node );
2739 for( var counter = 0; counter < node.childNodes.length; ++counter )
2741 if( node.childNodes[counter].nodeType == 1 )
2743 var subElements = getElementsByProperty( node.childNodes[counter], name );
2744 elements = elements.concat( subElements );
2750 /** Event handler for key press.
2752 * @param aEvt the event
2754 function onKeyDown( 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) {
2766 case 'UIKeyInputLeftArrow':
2769 case 'UIKeyInputUpArrow':
2772 case 'UIKeyInputRightArrow':
2775 case 'UIKeyInputDownArrow':
2780 // console.log(' now: ' + code);
2783 if( !processingEffect && keyCodeDictionary[currentMode] && keyCodeDictionary[currentMode][code] )
2785 return keyCodeDictionary[currentMode][code]();
2789 document.onkeypress = onKeyPress;
2793 //Set event handler for key down.
2794 document.onkeydown = onKeyDown;
2796 /** Event handler for key press.
2798 * @param aEvt the event
2800 function onKeyPress( aEvt )
2802 document.onkeypress = null;
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]();
2815 /** Function to supply the default key code dictionary.
2817 * @returns Object default key code dictionary
2819 function getDefaultKeyCodeDictionary()
2821 var keyCodeDict = {};
2823 keyCodeDict[SLIDE_MODE] = {};
2824 keyCodeDict[INDEX_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(); };
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(); };
2878 /** Function to supply the default char code dictionary.
2880 * @returns Object char code dictionary
2882 function getDefaultCharCodeDictionary()
2884 var charCodeDict = {};
2886 charCodeDict[SLIDE_MODE] = {};
2887 charCodeDict[INDEX_MODE] = {};
2890 charCodeDict[SLIDE_MODE]['i']
2891 = function () { return toggleSlideIndex(); };
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 )
2912 aEvt = window.event;
2916 if( aEvt.button == 0 )
2918 else if( aEvt.button == 2 )
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
2934 function slideOnMouseWheel(aEvt)
2939 aEvt = window.event;
2941 if (aEvt.wheelDelta)
2943 delta = aEvt.wheelDelta/120;
2945 else if (aEvt.detail)
2947 delta = -aEvt.detail/3;
2955 if (aEvt.preventDefault)
2956 aEvt.preventDefault();
2958 aEvt.returnValue = false;
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
2969 = function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_WHEEL ); };
2971 /** Function to handle all mouse events.
2974 * @param anAction type of event (e.g. mouse up, mouse wheel)
2976 function mouseHandlerDispatch( aEvt, anAction )
2979 aEvt = window.event;
2983 if ( mouseHandlerDictionary[currentMode] && mouseHandlerDictionary[currentMode][anAction] )
2985 var subRetVal = mouseHandlerDictionary[currentMode][anAction]( aEvt );
2987 if( subRetVal != null && subRetVal != undefined )
2991 if( aEvt.preventDefault && !retVal )
2992 aEvt.preventDefault();
2994 aEvt.returnValue = retVal;
2999 //Set mouse event handler.
3000 document.onmouseup = function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_UP ); };
3003 /** mouseClickHelper
3006 * a mouse click handler
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 )
3020 var aWindowObject = document.defaultView;
3023 var aTextSelection = aWindowObject.getSelection();
3024 var sSelectedText = aTextSelection.toString();
3027 DBGLOG( 'text selection: ' + sSelectedText );
3028 if( sLastSelectedText !== sSelectedText )
3030 bTextHasBeenSelected = true;
3031 sLastSelectedText = sSelectedText;
3035 bTextHasBeenSelected = false;
3039 else if( bTextHasBeenSelected )
3041 bTextHasBeenSelected = false;
3042 sLastSelectedText = '';
3048 log( 'error: HyperlinkElement.handleClick: invalid window object.' );
3052 var aSlideAnimationsHandler = theMetaDoc.aMetaSlideSet[nCurSlide].aSlideAnimationsHandler;
3053 if( aSlideAnimationsHandler )
3055 var aCurrentEventMultiplexer = aSlideAnimationsHandler.aEventMultiplexer;
3056 if( aCurrentEventMultiplexer )
3058 if( aCurrentEventMultiplexer.hasRegisteredMouseClickHandlers() )
3060 return aCurrentEventMultiplexer.notifyMouseClick( aEvt );
3064 return slideOnMouseUp( aEvt );
3068 /** Function to supply the default mouse handler dictionary.
3070 * @returns Object default mouse handler dictionary
3072 function getDefaultMouseHandlerDictionary()
3074 var mouseHandlerDict = {};
3076 mouseHandlerDict[SLIDE_MODE] = {};
3077 mouseHandlerDict[INDEX_MODE] = {};
3080 mouseHandlerDict[SLIDE_MODE][MOUSE_UP]
3083 mouseHandlerDict[SLIDE_MODE][MOUSE_WHEEL]
3084 = function( aEvt ) { return slideOnMouseWheel( aEvt ); };
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.
3104 * indexSetPageSlide(activeSlide);
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;
3118 //if different from kept offset, then record and change the page
3119 if( offset != INDEX_OFFSET )
3121 INDEX_OFFSET = offset;
3122 displayIndex( INDEX_OFFSET );
3125 //set the selected thumbnail and the current slide
3126 theSlideIndexPage.setSelection( nSelectedThumbnailIndex );
3133 * The above code is a derivative work of some parts of the JessyInk project.
3134 * @source http://code.google.com/p/jessyink/
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.
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 ****************************************************************************/
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.
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
3197 function has( name )
3199 return has.cache[name];
3204 has.add = function( name, test )
3206 has.cache[name] = test;
3209 function configureDetectionTools()
3213 log( 'error: configureDetectionTools: configuration failed' );
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);
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);
3240 // Mozilla and firefox
3241 if(dua.indexOf('Gecko') >= 0 && !has('khtml') && !has('webkit')){
3242 has.add('mozilla', tv);
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);
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){
3264 has.add('ie', isIE);
3268 has.add('wii', typeof opera != 'undefined' && opera.wiiremote);
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.)
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.)
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'),
3316 // True if the client runs on Mac
3320 // True if client is iPhone, iPod, or iPad
3323 // isAndroid: Number|undefined
3324 // Version as a Number if client is android browser. undefined otherwise.
3325 isAndroid: has('android'),
3328 // True if client is Wii
3331 // isQuirks: Boolean
3332 // Page is in quirks mode.
3333 isQuirks: has('quirks'),
3336 // True if client is Adobe Air
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
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.
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
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
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 ****************************************************************************/
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.
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
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;
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);
3449 window.SVGPathSegClosePath = function(owningPathSegList) {
3450 window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CLOSEPATH, 'z', owningPathSegList);
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);
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);
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);
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);
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);
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);
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);
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);
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);
3583 this._angle = angle;
3584 this._largeArcFlag = largeArcFlag;
3585 this._sweepFlag = sweepFlag;
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);
3605 this._angle = angle;
3606 this._largeArcFlag = largeArcFlag;
3607 this._sweepFlag = sweepFlag;
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);
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);
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);
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);
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);
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);
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);
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);
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)
3756 measurementElement.pathSegList.removeItem(lastPathSegment);
3757 if (distance > measurementElement.getTotalLength())
3760 } while (lastPathSegment > 0);
3761 return lastPathSegment;
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);
3783 window.SVGPathSegList.prototype.classname = 'SVGPathSegList';
3785 Object.defineProperty(window.SVGPathSegList.prototype, 'numberOfItems', {
3787 this._checkPathSynchronizedToList();
3788 return this._list.length;
3793 // The length property was not specified but was in Firefox 58.
3794 Object.defineProperty(window.SVGPathSegList.prototype, 'length', {
3796 this._checkPathSynchronizedToList();
3797 return this._list.length;
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', {
3806 if (!this._pathSegList)
3807 this._pathSegList = new window.SVGPathSegList(this);
3808 return this._pathSegList;
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());
3824 window.SVGPathSegList.prototype._updateListFromPathMutations = function(mutationRecords) {
3825 if (!this._pathElement)
3827 var hasPathMutations = false;
3828 mutationRecords.forEach(function(record) {
3829 if (record.attributeName == 'd')
3830 hasPathMutations = true;
3832 if (hasPathMutations)
3833 this._list = this._parsePath(this._pathElement.getAttribute('d'));
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);
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();
3848 window.SVGPathSegList.prototype.clear = function() {
3849 this._checkPathSynchronizedToList();
3851 this._list.forEach(function(pathSeg) {
3852 pathSeg._owningPathSegList = null;
3855 this._writeListToPath();
3858 window.SVGPathSegList.prototype.initialize = function(newItem) {
3859 this._checkPathSynchronizedToList();
3861 this._list = [newItem];
3862 newItem._owningPathSegList = this;
3863 this._writeListToPath();
3867 window.SVGPathSegList.prototype._checkValidIndex = function(index) {
3868 if (isNaN(index) || index < 0 || index >= this.numberOfItems)
3869 throw 'INDEX_SIZE_ERR';
3872 window.SVGPathSegList.prototype.getItem = function(index) {
3873 this._checkPathSynchronizedToList();
3875 this._checkValidIndex(index);
3876 return this._list[index];
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();
3889 this._list.splice(index, 0, newItem);
3890 newItem._owningPathSegList = this;
3891 this._writeListToPath();
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();
3902 this._checkValidIndex(index);
3903 this._list[index] = newItem;
3904 newItem._owningPathSegList = this;
3905 this._writeListToPath();
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();
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();
3926 this._list.push(newItem);
3927 newItem._owningPathSegList = this;
3928 // TODO: Optimize this to just append to the existing attribute.
3929 this._writeListToPath();
3933 window.SVGPathSegList.prototype.matrixTransform = function(aSVGMatrix) {
3934 this._checkPathSynchronizedToList();
3936 var nLength = this._list.length;
3937 for( var i = 0; i < nLength; ++i )
3940 var aPathSeg = this._list[i];
3941 switch( aPathSeg.pathSegTypeAsLetter )
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
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
3956 aPathSeg._x = aSVGMatrix.a * nX + aSVGMatrix.c * aPathSeg._y + aSVGMatrix.e;
3957 aPathSeg._y = aSVGMatrix.b * nX + aSVGMatrix.d * aPathSeg._y + aSVGMatrix.f;
3960 log( 'SVGPathSeg.matrixTransform: unexpected path segment type: '
3961 + aPathSeg.pathSegTypeAsLetter );
3965 this._writeListToPath();
3968 window.SVGPathSegList.prototype.changeOrientation = function() {
3969 this._checkPathSynchronizedToList();
3971 var aPathSegList = this._list;
3972 var nLength = aPathSegList.length;
3973 if( nLength == 0 ) return;
3978 var aPathSeg = aPathSegList[0];
3979 if( aPathSeg.pathSegTypeAsLetter == 'M' )
3981 nCurrentX = aPathSeg.x;
3982 nCurrentY = aPathSeg.y;
3983 aPathSegList.shift();
3988 for( i = 0; i < nLength; ++i )
3990 aPathSeg = aPathSegList[i];
3991 switch( aPathSeg.pathSegTypeAsLetter )
3994 var nX = aPathSeg._x1;
3995 aPathSeg._x1 = aPathSeg._x2;
3997 var nY = aPathSeg._y1;
3998 aPathSeg._y1 = aPathSeg._y2;
4000 // fall through intended
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;
4011 log( 'SVGPathSegList.changeOrientation: unexpected path segment type: '
4012 + aPathSeg.pathSegTypeAsLetter );
4017 aPathSegList.reverse();
4019 var aMovePathSeg = new window.SVGPathSegMovetoAbs( this, nCurrentX, nCurrentY );
4020 aPathSegList.unshift( aMovePathSeg );
4022 this._writeListToPath();
4025 window.SVGPathSegList._pathSegArrayAsString = function(pathSegArray) {
4028 pathSegArray.forEach(function(pathSeg) {
4031 string += pathSeg._asPathString();
4033 string += ' ' + pathSeg._asPathString();
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)
4044 var owningPathSegList = this;
4046 var Builder = function() {
4047 this.pathSegList = [];
4050 Builder.prototype.appendSegment = function(pathSeg) {
4051 this.pathSegList.push(pathSeg);
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();
4063 Source.prototype._isCurrentSpace = function() {
4064 var character = this._string[this._currentIndex];
4065 return character <= ' ' && (character == ' ' || character == '\n' || character == '\t' || character == '\r' || character == '\f');
4068 Source.prototype._skipOptionalSpaces = function() {
4069 while (this._currentIndex < this._endIndex && this._isCurrentSpace())
4070 this._currentIndex++;
4071 return this._currentIndex < this._endIndex;
4074 Source.prototype._skipOptionalSpacesOrDelimiter = function() {
4075 if (this._currentIndex < this._endIndex && !this._isCurrentSpace() && this._string.charAt(this._currentIndex) != ',')
4077 if (this._skipOptionalSpaces()) {
4078 if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == ',') {
4079 this._currentIndex++;
4080 this._skipOptionalSpaces();
4083 return this._currentIndex < this._endIndex;
4086 Source.prototype.hasMoreData = function() {
4087 return this._currentIndex < this._endIndex;
4090 Source.prototype.peekSegmentType = function() {
4091 var lookahead = this._string[this._currentIndex];
4092 return this._pathSegTypeFromChar(lookahead);
4095 Source.prototype._pathSegTypeFromChar = function(lookahead) {
4096 switch (lookahead) {
4099 return window.SVGPathSeg.PATHSEG_CLOSEPATH;
4101 return window.SVGPathSeg.PATHSEG_MOVETO_ABS;
4103 return window.SVGPathSeg.PATHSEG_MOVETO_REL;
4105 return window.SVGPathSeg.PATHSEG_LINETO_ABS;
4107 return window.SVGPathSeg.PATHSEG_LINETO_REL;
4109 return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS;
4111 return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL;
4113 return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS;
4115 return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL;
4117 return window.SVGPathSeg.PATHSEG_ARC_ABS;
4119 return window.SVGPathSeg.PATHSEG_ARC_REL;
4121 return window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS;
4123 return window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL;
4125 return window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS;
4127 return window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL;
4129 return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS;
4131 return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL;
4133 return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS;
4135 return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL;
4137 return window.SVGPathSeg.PATHSEG_UNKNOWN;
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;
4150 return window.SVGPathSeg.PATHSEG_UNKNOWN;
4153 Source.prototype.initialCommandIsMoveTo = function() {
4154 // If the path is empty it is still valid, so return true.
4155 if (!this.hasMoreData())
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;
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() {
4172 var startIndex = this._currentIndex;
4174 this._skipOptionalSpaces();
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++;
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+-.].
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;
4196 while (scanIntPartIndex >= startIntPartIndex) {
4197 integer += multiplier * (this._string.charAt(scanIntPartIndex--) - '0');
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')
4209 while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
4211 decimal += (this._string.charAt(this._currentIndex) - '0') / frac;
4212 this._currentIndex += 1;
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++;
4228 // There must be an exponent.
4229 if (this._currentIndex >= this._endIndex || this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9')
4232 while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
4234 exponent += (this._string.charAt(this._currentIndex) - '0');
4235 this._currentIndex++;
4239 var number = integer + decimal;
4243 number *= Math.pow(10, expsign * exponent);
4245 if (startIndex == this._currentIndex)
4248 this._skipOptionalSpacesOrDelimiter();
4253 Source.prototype._parseArcFlag = function() {
4254 if (this._currentIndex >= this._endIndex)
4257 var flagChar = this._string.charAt(this._currentIndex++);
4258 if (flagChar == '0')
4260 else if (flagChar == '1')
4265 this._skipOptionalSpacesOrDelimiter();
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)
4276 command = this._nextCommandHelper(lookahead, this._previousCommand);
4277 if (command == window.SVGPathSeg.PATHSEG_UNKNOWN)
4280 this._currentIndex++;
4283 this._previousCommand = 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);
4334 throw 'Unknown path seg type.'
4338 var builder = new Builder();
4339 var source = new Source(string);
4341 if (!source.initialCommandIsMoveTo())
4343 while (source.hasMoreData()) {
4344 var pathSeg = source.parseSegment();
4347 builder.appendSegment(pathSeg);
4350 return builder.pathSegList;
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
4371 * The following is the license notice for the part of JavaScript code of
4372 * this page included between the '@libreofficestart' and the '@libreofficeend'
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 ************************************************************************/
4399 * The above is the license notice for the part of JavaScript code of
4400 * this page included between the '@libreofficestart' and the '@libreofficeend'
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
4418 window.onload = init;
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';
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.
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.
4476 // Mouse handler actions.
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;
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
4493 var ESCAPE_KEY = 27;
4496 // Visibility Values
4500 var aVisibilityAttributeValue = [ 'hidden', 'visible', 'inherit' ]; // eslint-disable-line no-unused-vars
4501 var aVisibilityValue = { 'hidden' : HIDDEN, 'visible' : VISIBLE, 'inherit' : INHERIT };
4504 var ROOT_NODE = document.getElementsByTagNameNS( NSS['svg'], 'svg' )[0];
4507 var INDEX_COLUMNS_DEFAULT = 3;
4508 var INDEX_OFFSET = 0;
4511 var Detect = configureDetectionTools();
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;
4540 function extend( aSubType, aSuperType )
4542 if (!aSuperType || !aSubType)
4544 alert('extend failed, verify dependencies');
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)
4557 sp.constructor = aSuperType;
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 )
4573 if( TemplateClass.instanceSet[i].base === BaseType )
4574 return TemplateClass.instanceSet[i].instance;
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' ) );
4598 this.right = x + width;
4600 this.bottom = y + height;
4604 * Returns key corresponding to a value in object, null otherwise.
4609 function getKeyByValue(aObj, value) {
4610 for(var key in aObj) {
4611 if(aObj[key] == value)
4617 function log( message )
4619 if( typeof console == 'object' )
4621 // eslint-disable-next-line no-console
4622 console.log( message );
4624 else if( typeof opera == 'object' )
4626 opera.postError( message );
4628 // eslint-disable-next-line no-undef
4629 else if( typeof java == 'object' && typeof java.lang == 'object' )
4631 // eslint-disable-next-line no-undef
4632 java.lang.System.out.println( message );
4636 function getNSAttribute( sNSPrefix, aElem, sAttrName )
4638 if( !aElem ) return null;
4639 if( 'getAttributeNS' in aElem )
4641 return aElem.getAttributeNS( NSS[sNSPrefix], sAttrName );
4645 return aElem.getAttribute( sNSPrefix + ':' + sAttrName );
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 )
4659 aElem.setAttributeNS( NSS[sNSPrefix], sAttrName, aValue );
4664 aElem.setAttribute(sNSPrefix + ':' + sAttrName, aValue );
4669 function getElementsByClassName( aElem, sClassName )
4672 var aElementSet = [];
4673 // not all browsers support the 'getElementsByClassName' method
4674 if( 'getElementsByClassName' in aElem )
4676 aElementSet = aElem.getElementsByClassName( sClassName );
4680 var aElementSetByClassProperty = getElementsByProperty( aElem, 'class' );
4681 for( var i = 0; i < aElementSetByClassProperty.length; ++i )
4683 var sAttrClassName = aElementSetByClassProperty[i].getAttribute( 'class' );
4684 if( sAttrClassName == sClassName )
4686 aElementSet.push( aElementSetByClassProperty[i] );
4693 function getElementByClassName( aElem, sClassName /*, sTagName */)
4695 var aElementSet = getElementsByClassName( aElem, sClassName );
4696 if ( aElementSet.length == 1 )
4697 return aElementSet[0];
4702 function getClassAttribute( aElem )
4705 return aElem.getAttribute( 'class' );
4709 function createElementGroup( aParentElement, aElementList, nFrom, nCount, sGroupClass, sGroupId )
4711 var nTo = nFrom + nCount;
4712 if( nCount < 1 || aElementList.length < nTo )
4714 log( 'createElementGroup: not enough elements available.' );
4717 var firstElement = aElementList[nFrom];
4720 log( 'createElementGroup: element not found.' );
4723 var aGroupElement = document.createElementNS( NSS['svg'], 'g' );
4725 aGroupElement.setAttribute( 'id', sGroupId );
4727 aGroupElement.setAttribute( 'class', sGroupClass );
4728 aParentElement.insertBefore( aGroupElement, firstElement );
4730 for( ; i < nTo; ++i )
4732 aParentElement.removeChild( aElementList[i] );
4733 aGroupElement.appendChild( aElementList[i] );
4737 function initVisibilityProperty( aElement )
4739 var nVisibility = VISIBLE;
4740 var sVisibility = aElement.getAttribute( 'visibility' );
4741 if( sVisibility ) nVisibility = aVisibilityValue[ sVisibility ];
4745 function getSafeIndex( nIndex, nMin, nMax )
4749 else if( nIndex > nMax )
4759 * an integer in [0,nMax[
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() )
4805 var sInfo = 'DBG: ' + sMessage;
4807 sInfo += ' (at: ' + String( nTime / 1000 ) + 's)';
4813 // - Debug Printers -
4814 var aGenericDebugPrinter = new DebugPrinter();
4815 aGenericDebugPrinter.on();
4816 var DBGLOG = bind2( DebugPrinter.prototype.print, aGenericDebugPrinter );
4818 var NAVDBG = new DebugPrinter();
4821 var ANIMDBG = new DebugPrinter();
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
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.
4858 function MetaDocument()
4860 // We look for the svg element that provides the following presentation
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 )
4911 var sMetaSlideId = aOOOElemMetaSlide + '_' + i;
4912 this.aMetaSlideSet.push( new MetaSlide( sMetaSlideId, this ) );
4914 assert( this.aMetaSlideSet.length == this.nNumberOfSlides,
4915 'MetaDocument: aMetaSlideSet.length != nNumberOfSlides.' );
4918 MetaDocument.prototype =
4920 /*** public methods ***/
4925 * The MetaSlide object handling the current slide.
4927 getCurrentSlide : function()
4929 return this.aMetaSlideSet[nCurSlide];
4934 * @param nSlideIndex
4935 * The index of the slide to show.
4937 setCurrentSlide : function( nSlideIndex )
4939 if( nSlideIndex >= 0 && nSlideIndex < this.nNumberOfSlides )
4941 if( nCurSlide !== undefined )
4942 this.aMetaSlideSet[nCurSlide].hide();
4943 this.aMetaSlideSet[nSlideIndex].show();
4944 nCurSlide = nSlideIndex;
4948 log('MetaDocument.setCurrentSlide: slide index out of range: ' + nSlideIndex );
4952 /*** private methods ***/
4954 initSlideAnimationsMap : function()
4956 var aAnimationsSection = document.getElementById( 'presentation-animations' );
4957 if( aAnimationsSection )
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 )
4967 var sSlideId = aAnimationsDefSet[i].getAttributeNS( NSS['ooo'], aOOOAttrSlide );
4968 var aChildSet = getElementChildren( aAnimationsDefSet[i] );
4969 if( sSlideId && ( aChildSet.length === 1 ) )
4971 this.aSlideAnimationsMap[ sSlideId ] = aChildSet[0];
4977 }; // end MetaDocument prototype
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.
4990 * The MetaDocument global object.
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) );
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 )
5077 this.aTransitionHandler = new SlideTransition( this.getSlideAnimationsRoot(), this.slideId );
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 ***/
5103 * Set the visibility property of the slide to 'inherit'
5104 * and update the master page view.
5108 this.updateMasterPageView();
5109 this.aVisibilityStatusElement.setAttribute( 'visibility', 'inherit' );
5113 * Set the visibility property of the slide to 'hidden'.
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.
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 )
5131 this.aMasterPageView = new MasterPageView( this );
5132 this.aMasterPageView.attachToSlide();
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 ) )
5146 this.theMetaDoc.aMasterPageSet[ sMasterPageId ] = new MasterPage( sMasterPageId, this );
5148 // We initialize aTextFieldHandlerSet[ sMasterPageId ] to an empty
5150 this.theMetaDoc.aTextFieldHandlerSet[ sMasterPageId ] = {};
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 );
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 );
5175 nVisibility = aVisibilityValue[ sVisibility ];
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 ] )
5196 var aTextFieldElem = document.getElementById( sTextFieldId );
5197 var sClassName = getClassAttribute( aTextFieldElem );
5198 if( sClassName == 'FixedDateTimeField' )
5200 aTextField = new FixedTextProvider( aTextFieldElem );
5201 this.bIsDateTimeVariable = false;
5203 else if( sClassName == 'VariableDateTimeField' )
5205 aTextField = new CurrentDateTimeProvider( aTextFieldElem );
5206 this.bIsDateTimeVariable = true;
5212 this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ] = aTextField;
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 ] )
5228 var aTextFieldElem = document.getElementById( sTextFieldId );
5229 this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ]
5230 = new FixedTextProvider( aTextFieldElem );
5232 return this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ];
5235 collectTextShapes : function()
5237 var aTextShapeSet = [];
5238 var aTextShapeIndexElem = getElementByClassName( document, 'TextShapeIndex' );
5239 if( aTextShapeIndexElem )
5241 var aIndexEntryList = getElementChildren( aTextShapeIndexElem );
5243 for( i = 0; i < aIndexEntryList.length; ++i )
5245 var sSlideId = getOOOAttribute( aIndexEntryList[i], 'slide' );
5246 if( sSlideId === this.slideId )
5248 var sTextShapeIds = getOOOAttribute( aIndexEntryList[i], 'id-list' );
5251 var aTextShapeIdSet = sTextShapeIds.split( ' ' );
5253 for( j = 0; j < aTextShapeIdSet.length; ++j )
5255 var aTextShapeElem = document.getElementById( aTextShapeIdSet[j] );
5256 if( aTextShapeElem )
5258 aTextShapeSet.push( aTextShapeElem );
5262 log( 'warning: MetaSlide.collectTextShapes: text shape with id <' + aTextShapeIdSet[j] + '> is not valid.' );
5270 return aTextShapeSet;
5273 initHyperlinks : function()
5275 var aHyperlinkSet = {};
5277 for( i = 0; i < this.aTextShapeSet.length; ++i )
5279 if( this.aTextShapeSet[i] )
5281 var aHyperlinkIdList = getElementByClassName( this.aTextShapeSet[i], 'HyperlinkIdList' );
5282 if( aHyperlinkIdList )
5284 var sHyperlinkIds = aHyperlinkIdList.textContent;
5287 var aHyperlinkIdSet = sHyperlinkIds.trim().split( ' ' );
5289 for( j = 0; j < aHyperlinkIdSet.length; ++j )
5291 var sId = aHyperlinkIdSet[j];
5292 aHyperlinkSet[ sId ] = new HyperlinkElement( sId, this.aSlideAnimationsHandler.aEventMultiplexer );
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'>
5321 * <g class='BackgroundObjects'>
5322 * <g class='Date/Time'>
5323 * date/time placeholder
5325 * <g class='Header'>
5326 * header placeholder
5328 * <g class='Footer'>
5329 * footer placeholder
5331 * <g class='Slide_Number'>
5332 * slide number placeholder
5338 * @param sMasterPageId
5339 * A string representing the value of the id attribute of the master page
5340 * element to be handled.
5342 * A meta slide having as master page the one with the passed id.
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 )
5358 this.backgroundId = this.background.getAttribute( 'id' );
5359 this.backgroundVisibility = initVisibilityProperty( this.background );
5363 this.backgroundId = '';
5364 log( 'MasterPage: the background element is not valid.' );
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 )
5372 this.backgroundObjectsId = this.backgroundObjects.getAttribute( 'id' );
5373 this.backgroundObjectsVisibility = initVisibilityProperty( this.backgroundObjects );
5375 if( this.backgroundObjectsVisibility != HIDDEN )
5377 var aBackgroundObjectList = getElementChildren( this.backgroundObjects );
5380 var nSubGroupId = 1;
5383 this.aBackgroundObjectSubGroupIdList = [];
5385 for( ; i < aBackgroundObjectList.length; ++i )
5387 sClass = aBackgroundObjectList[i].getAttribute( 'class' );
5388 if( !sClass || ( ( sClass !== aDateTimeClassName ) && ( sClass !== aFooterClassName )
5389 && ( sClass !== aHeaderClassName ) && ( sClass !== aSlideNumberClassName ) ) )
5394 sId = this.backgroundObjectsId + '.' + nSubGroupId;
5396 this.aBackgroundObjectSubGroupIdList.push( sId );
5402 this.aBackgroundObjectSubGroupIdList.push( sClass );
5405 createElementGroup( this.backgroundObjects, aBackgroundObjectList, nFrom, nCount, 'BackgroundObjectSubgroup', sId );
5412 createElementGroup( this.backgroundObjects, aBackgroundObjectList, nFrom, nCount, 'BackgroundObjectSubgroup', sId );
5418 this.backgroundObjectsId = '';
5419 log( 'MasterPage: the background objects element is not valid.' );
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
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.
5453 * A string representing the value of the class attribute of the text
5454 * field element to be handled.
5456 function PlaceholderShape( aMasterPage, sClassName )
5458 this.masterPage = aMasterPage;
5459 this.className = sClassName;
5461 this.element = null;
5462 this.textElement = null;
5466 /* public methods */
5467 PlaceholderShape.prototype.isValid = function()
5469 return ( this.element && this.textElement );
5472 /* private methods */
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.
5479 PlaceholderShape.prototype.init = function()
5482 var aTextFieldElement = getElementByClassName( this.masterPage.backgroundObjects, this.className );
5483 if( aTextFieldElement )
5485 var aPlaceholderElement = getElementByClassName( aTextFieldElement, 'PlaceholderText' );
5486 if( aPlaceholderElement )
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' );
5496 var aRect = new Rectangle( aSVGRectElem );
5497 var sTextAdjust = getOOOAttribute( aTextFieldElement, aOOOAttrTextAdjust ) || 'left';
5498 var sTextAnchor, sX;
5499 if( sTextAdjust == 'left' )
5501 sTextAnchor = 'start';
5502 sX = String( aRect.left );
5504 else if( sTextAdjust == 'right' )
5506 sTextAnchor = 'end';
5507 sX = String( aRect.right );
5509 else if( sTextAdjust == 'center' )
5511 sTextAnchor = 'middle';
5512 var nMiddle = ( aRect.left + aRect.right ) / 2;
5513 sX = String( parseInt( String( nMiddle ) ) );
5516 aPlaceholderElement.setAttribute( 'text-anchor', sTextAnchor );
5518 aPlaceholderElement.setAttribute( 'x', sX );
5521 // date/time fields were not exported correctly when positioned chars are used
5522 if( this.masterPage.metaSlide.theMetaDoc.bIsUsePositionedChars )
5524 // We remove all text lines but the first one used as placeholder.
5525 var aTextLineGroupElem = aPlaceholderElement.parentNode.parentNode;
5526 if( aTextLineGroupElem )
5528 // Just to be sure it is the element we are looking for.
5529 var sFontFamilyAttr = aTextLineGroupElem.getAttribute( 'font-family' );
5530 if( sFontFamilyAttr )
5532 var aChildSet = getElementChildren( aTextLineGroupElem );
5533 if( aChildSet.length > 1 )
5535 for( ; i < aChildSet.length; ++i )
5537 aTextLineGroupElem.removeChild( aChildSet[i] );
5542 this.element = aTextFieldElement;
5543 this.textElement = aPlaceholderElement;
5548 /** Class MasterPageView
5549 * This class is used to creates a svg element of class MasterPageView and its
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
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
5569 * Sub-elements are present only if they are visible.
5572 * The MetaSlide object managing the slide element that targets
5573 * the master page view element created by an instance of MasterPageView.
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 ***/
5588 * Prepend the master slide view element to the slide element.
5590 MasterPageView.prototype.attachToSlide = function()
5592 if( !this.bIsAttached )
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;
5603 * Remove the master slide view element from the slide element.
5605 MasterPageView.prototype.detachFromSlide = function()
5607 if( this.bIsAttached )
5609 this.aSlideElement.removeChild( this.aMPVElement );
5610 this.bIsAttached = false;
5615 * Update the content of text fields placed on the master page.
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 )
5649 this.aBackgroundElement = theDocument.createElementNS( NSS['svg'], 'use' );
5650 this.aBackgroundElement.setAttribute( 'class', 'Background' );
5651 setNSAttribute( 'xlink', this.aBackgroundElement,
5652 'href', '#' + this.aMasterPage.backgroundId );
5655 aMasterPageViewElement.appendChild( this.aBackgroundElement );
5658 // init the BackgroundObjects element
5659 if( this.aMetaSlide.nAreMasterObjectsVisible )
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;
5676 for( ; i < aBackgroundObjectSubGroupIdList.length; ++i )
5678 sId = aBackgroundObjectSubGroupIdList[i];
5679 if( sId === aSlideNumberClassName )
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] )
5690 this.aSlideNumberFieldHandler =
5691 new SlideNumberFieldHandler( aPlaceholderShapeSet[aSlideNumberClassName],
5692 aTextFieldContentProviderSet[aSlideNumberClassName] );
5693 this.aSlideNumberFieldHandler.update( this.aMetaSlide.nSlideNumber );
5694 this.aSlideNumberFieldHandler.appendTo( this.aBackgroundObjectsElement );
5697 else if( sId === aDateTimeClassName )
5700 if( this.aMetaSlide.nIsDateTimeVisible )
5702 this.aDateTimeFieldHandler =
5703 this.initTextFieldHandler( aDateTimeClassName, aPlaceholderShapeSet,
5704 aTextFieldContentProviderSet, aDefsElement,
5705 aTextFieldHandlerSet, sMasterSlideId );
5708 else if( sId === aFooterClassName )
5711 if( this.aMetaSlide.nIsFooterVisible )
5713 this.aFooterFieldHandler =
5714 this.initTextFieldHandler( aFooterClassName, aPlaceholderShapeSet,
5715 aTextFieldContentProviderSet, aDefsElement,
5716 aTextFieldHandlerSet, sMasterSlideId );
5719 else if( sId === aHeaderClassName )
5722 if( this.aMetaSlide.nIsHeaderVisible )
5724 this.aHeaderFieldHandler =
5725 this.initTextFieldHandler( aHeaderClassName, aPlaceholderShapeSet,
5726 aTextFieldContentProviderSet, aDefsElement,
5727 aTextFieldHandlerSet, sMasterSlideId );
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 );
5739 this.aBackgroundObjectsElement.appendChild( aBackgroundSubGroupElement );
5744 aMasterPageViewElement.appendChild( this.aBackgroundObjectsElement );
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] )
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 ] )
5764 aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ] =
5765 new TextFieldHandler( aPlaceholderShapeSet[sClassName],
5766 aTextFieldContentProviderSet[sClassName] );
5767 aTextFieldHandler = aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ];
5768 aTextFieldHandler.update();
5769 aTextFieldHandler.appendTo( aDefsElement );
5773 aTextFieldHandler = aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ];
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 );
5783 this.aBackgroundObjectsElement.appendChild( aTextFieldElement );
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
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 ***/
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.
5847 TextFieldHandler.prototype.appendTo = function( aParentNode )
5849 if( !this.aTextFieldElement )
5851 log( 'TextFieldHandler.appendTo: aTextFieldElement is not defined' );
5856 log( 'TextFieldHandler.appendTo: parent node is not defined' );
5860 aParentNode.appendChild( this.aTextFieldElement );
5864 * Modify the content of the cloned text field.
5867 * A string representing the new content of the cloned text field.
5869 TextFieldHandler.prototype.setTextContent = function( sText )
5871 if( !this.aTextPlaceholderElement )
5873 log( 'PlaceholderShape.setTextContent: text element is not valid in placeholder of type '
5874 + this.className + ' that belongs to master slide ' + this.masterPage.id );
5877 this.aTextPlaceholderElement.textContent = sText;
5881 * Update the content of the handled text field. The new content is provided
5882 * directly from the TextContentProvider data member.
5884 TextFieldHandler.prototype.update = function()
5886 if( !this.aTextContentProvider )
5887 log('TextFieldHandler.update: text content provider not defined.');
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
5899 * @param aTextContentProvider
5900 * A SlideNumberProvider object to which the actual content updating is
5903 function SlideNumberFieldHandler( aPlaceholderShape, aTextContentProvider )
5905 SlideNumberFieldHandler.superclass.constructor.call( this, aPlaceholderShape, aTextContentProvider );
5907 extend( SlideNumberFieldHandler, TextFieldHandler );
5909 /*** public methods ***/
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.
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.');
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
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.
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.
5959 function FixedTextProvider( aTextFieldContentElement )
5961 FixedTextProvider.superclass.constructor.call( this, aTextFieldContentElement );
5962 this.text = aTextFieldContentElement.textContent;
5964 extend( FixedTextProvider, TextFieldContentProvider );
5966 /*** public methods ***/
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.
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.
5988 function CurrentDateTimeProvider( aTextFieldContentElement )
5990 CurrentDateTimeProvider.superclass.constructor.call( this, aTextFieldContentElement );
5991 this.dateTimeFormat = getOOOAttribute( aTextFieldContentElement, aOOOAttrDateTimeFormat );
5993 extend( CurrentDateTimeProvider, TextFieldContentProvider );
5995 /*** public methods ***/
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.
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();
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.
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
6038 * The page numbering type.
6040 SlideNumberProvider.prototype.getNumberingType = function()
6042 return this.pageNumberingType;
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.
6055 SlideNumberProvider.prototype.update = function( aSlideNumberField, nSlideNumber )
6057 if( nSlideNumber === undefined )
6059 if( nCurSlide === undefined )
6060 nSlideNumber = this.nInitialSlideNumber;
6062 nSlideNumber = nCurSlide + 1;
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
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 )
6123 this.aThumbnailSet[i] = new Thumbnail( this, i );
6124 this.aThumbnailSet[i].updateView();
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' );
6149 * Change the selected thumbnail from the current one to the thumbnail with index nIndex.
6151 * @param nIndex - the thumbnail index
6153 SlideIndexPage.prototype.setSelection = function( nIndex )
6155 nIndex = getSafeIndex( nIndex, 0, this.getTotalThumbnails() - 1 );
6156 if( this.curThumbnailIndex != nIndex )
6158 this.aThumbnailSet[ this.curThumbnailIndex ].unselect();
6159 this.aThumbnailSet[ nIndex ].select();
6160 this.curThumbnailIndex = nIndex;
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
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
6259 for( i = this.totalThumbnails; i < nOldTotalThumbnails; ++i )
6261 this.aThumbnailSet[i].removeElement();
6264 // if we increased the number of used columns we create the needed thumbnail objects
6265 for( i = nOldTotalThumbnails; i < this.totalThumbnails; ++i )
6267 this.aThumbnailSet[i] = new Thumbnail( this, i );
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 )
6291 this.aThumbnailSet[i].updateView();
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
6299 indexSetPageSlide( this.selectedSlideIndex );
6301 ROOT_NODE.unsuspendRedraw( suspendHandle );
6302 ROOT_NODE.forceRedraw();
6306 /** Class Thumbnail **
6307 * This class handles a slide thumbnail.
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 )
6339 this.thumbnailElement.setAttribute( 'display', 'inherit' );
6340 this.visibility = VISIBLE;
6344 Thumbnail.prototype.hide = function()
6346 if( this.visibility == VISIBLE )
6348 this.thumbnailElement.setAttribute( 'display', 'none' );
6349 this.visibility = HIDDEN;
6353 Thumbnail.prototype.select = function()
6355 if( !this.isSelected )
6357 this.borderElement.setAttribute( 'stroke', this.sSelectionBorderColor );
6358 this.isSelected = true;
6362 Thumbnail.prototype.unselect = function()
6364 if( this.isSelected )
6366 this.borderElement.setAttribute( 'stroke', this.sNormalBorderColor );
6367 this.isSelected = false;
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.
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()' );
6395 * This method update the content of the thumbnail view
6397 * @param nIndex - the index of the slide to be shown in the thumbnail
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( ' ' );
6457 Thumbnail.prototype.onMouseOver = function()
6459 if( ( currentMode == INDEX_MODE ) && ( this.container.curThumbnailIndex != this.index ) )
6461 this.container.setSelection( this.index );
6469 /** Initialization function.
6470 * The whole presentation is set-up in this function.
6474 var VIEWBOX = ROOT_NODE.getAttribute('viewBox');
6478 WIDTH = ROOT_NODE.viewBox.animVal.width;
6479 HEIGHT = ROOT_NODE.viewBox.animVal.height;
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);
6494 hammer.on('swiperight', function() {
6495 switchSlide(-1, false);
6497 hammer.get('swipe').set({ direction: Hammer.DIRECTION_ALL });
6498 hammer.on('swipeup', function() {
6499 aSlideShow.exitSlideShowInApp();
6501 hammer.on('swipedown', function() {
6502 aSlideShow.exitSlideShowInApp();
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 )
6519 presentationEngineStop( message );
6520 if (typeof console == 'object')
6521 // eslint-disable-next-line no-console
6523 throw new Error( message );
6527 function dispatchEffects(dir)
6529 // TODO to be implemented
6533 var bRet = aSlideShow.nextEffect();
6537 switchSlide( 1, false );
6542 switchSlide( dir, false );
6546 function skipAllEffects()
6548 var bRet = aSlideShow.skipAllEffects();
6551 switchSlide( 1, true );
6555 function skipEffects(dir)
6559 var bRet = aSlideShow.skipPlayingOrNextEffect();
6563 switchSlide( 1, true );
6568 switchSlide( dir, true );
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
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;
6592 for( var i = offsetNumber; i < nEnd; ++i, ++j )
6594 aThumbnailSet[j].update( i );
6595 aThumbnailSet[j].show();
6597 for( ; j < nTotalThumbnails; ++j )
6599 aThumbnailSet[j].hide();
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.
6609 function toggleSlideIndex()
6611 if( currentMode == SLIDE_MODE )
6614 theMetaDoc.getCurrentSlide().hide();
6616 indexSetPageSlide( nCurSlide );
6617 theSlideIndexPage.show();
6618 currentMode = INDEX_MODE;
6620 else if( currentMode == INDEX_MODE )
6622 theSlideIndexPage.hide();
6623 var nNewSlide = theSlideIndexPage.selectedSlideIndex;
6625 aSlideShow.displaySlide( nNewSlide, true );
6626 currentMode = SLIDE_MODE;
6630 /** Function that exit from the index mode without changing the shown slide
6633 function abandonIndexMode()
6635 theSlideIndexPage.selectedSlideIndex = nCurSlide;
6643 /*********************************************************************************************
6644 *********************************************************************************************
6645 *********************************************************************************************
6647 ***** ANIMATION ENGINE *****
6649 *********************************************************************************************
6650 *********************************************************************************************
6651 *********************************************************************************************/
6661 var CURR_UNIQUE_ID = 0;
6663 function getUniqueId()
6666 return CURR_UNIQUE_ID;
6669 function mem_fn( sMethodName )
6671 return function( aObject )
6673 var aMethod = aObject[ sMethodName ];
6675 aMethod.call( aObject );
6677 log( 'method sMethodName not found' );
6681 function bind( aObject, aMethod )
6685 return aMethod.call( aObject, arguments[0] );
6689 function bind2( aFunction )
6692 log( 'bind2: passed function is not valid.' );
6694 var aBoundArgList = arguments;
6696 var aResultFunction = null;
6698 switch( aBoundArgList.length )
6700 case 1: aResultFunction = function()
6702 return aFunction.call( arguments[0], arguments[1],
6703 arguments[2], arguments[3],
6707 case 2: aResultFunction = function()
6709 return aFunction.call( aBoundArgList[1], arguments[0],
6710 arguments[1], arguments[2],
6714 case 3: aResultFunction = function()
6716 return aFunction.call( aBoundArgList[1], aBoundArgList[2],
6717 arguments[0], arguments[1],
6721 case 4: aResultFunction = function()
6723 return aFunction.call( aBoundArgList[1], aBoundArgList[2],
6724 aBoundArgList[3], arguments[0],
6728 case 5: aResultFunction = function()
6730 return aFunction.call( aBoundArgList[1], aBoundArgList[2],
6731 aBoundArgList[3], aBoundArgList[4],
6736 log( 'bind2: arity not handled.' );
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
6759 * an array that contains all children elements
6761 function getElementChildren( aElement )
6763 var aChildrenArray = [];
6765 var nSize = aElement.childNodes.length;
6767 for( var i = 0; i < nSize; ++i )
6769 if( aElement.childNodes[i].nodeType == 1 )
6770 aChildrenArray.push( aElement.childNodes[i] );
6773 return aChildrenArray;
6776 function removeWhiteSpaces( str )
6782 var aSplitString = str.split( re );
6783 return aSplitString.join('');
6786 function clamp( nValue, nMinimum, nMaximum )
6788 if( nValue < nMinimum )
6792 else if( nValue > nMaximum )
6802 function makeMatrixString( a, b, c, d, e, f )
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 === '.' )
6832 var reFloatNumber = /^[+-]?[0-9]*[.]?[0-9]*$/;
6834 if( reFloatNumber.test( sValue ) )
6835 return parseFloat( sValue );
6840 function booleanParser( sValue )
6842 sValue = sValue.toLowerCase();
6843 if( sValue === 'true' )
6845 else if( sValue === 'false' )
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 )
6859 return new HSLColor( nHue, nSaturation / 100, nLuminance / 100 );
6862 // eslint-disable-next-line no-unused-vars
6863 function rgb( nRed, nGreen, nBlue )
6865 return new RGBColor( nRed / 255, nGreen / 255, nBlue / 255 );
6868 // eslint-disable-next-line no-unused-vars
6869 function prgb( nRed, nGreen, nBlue )
6871 return new RGBColor( nRed / 100, nGreen / 100, nBlue / 100 );
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 ) )
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 );
6909 else if( reHSLPercent.test( sValue ) )
6911 sValue = sValue.replace( '%', '' ).replace( '%', '' );
6912 return eval( sValue );
6914 else if( reRGBInteger.test( sValue ) )
6916 return eval( sValue );
6918 else if( reRGBPercent.test( sValue ) )
6920 sValue = 'p' + sValue.replace( '%', '' ).replace( '%', '' ).replace( '%', '' );
6921 return eval( sValue );
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
6941 this.nGreen = nGreen;
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;
6966 RGBColor.prototype.scale = function( aT )
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;
7000 nSaturation = ( nLuminance > 0.5 ) ?
7001 ( nDelta / ( 2.0 - nMax - nMin) ) :
7002 ( nDelta / ( nMax + nMin ) );
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;
7017 return new HSLColor( nHue, nSaturation, nLuminance );
7021 RGBColor.prototype.toString = function( bClamped )
7026 aRGBColor = RGBColor.clamp( this );
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 );
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
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();
7087 HSLColor.prototype.scale = function( aT )
7090 this.nSaturation *= aT;
7091 this.nLuminance *= aT;
7092 this.normalizeHue();
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 )
7131 return new RGBColor( nLuminance, nLuminance, nLuminance );
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 )
7154 return nValue1 + ( nValue2 - nValue1 ) * nHue / 60.0;
7155 else if( nHue < 180.0 )
7157 else if( nHue < 240.0 )
7158 return ( nValue1 + ( nValue2 - nValue1 ) * ( 240.0 - nHue ) / 60.0 );
7163 HSLColor.interpolate = function( aFrom, aTo, nT, bCCW )
7168 if( aFrom.nHue <= aTo.nHue && !bCCW )
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;
7178 else if( aFrom.nHue > aTo.nHue && bCCW )
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);
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;
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.
7238 * An object of type SVGPathElement to be prepended.
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.
7251 * An object of type SVGPathElement to be appended.
7253 SVGPathElement.prototype.appendPath = function( aPath )
7255 var sPathData = this.getAttribute( 'd' );
7256 sPathData += ( ' ' + aPath.getAttribute( 'd' ) );
7257 this.setAttribute( 'd', sPathData );
7261 * Flips the SVG Path element along y-axis.
7264 * An object of type SVGPathElement to be flipped.
7266 function flipOnYAxis( aPath )
7268 var aPolyPath = aPath.cloneNode(true);
7269 var aTransform = document.documentElement.createSVGMatrix();
7272 aPolyPath.matrixTransform(aTransform);
7277 * Flips the SVG Path element along x-axis
7280 * An object of type SVGPathElement to be flipped
7282 function flipOnXAxis( aPath )
7284 var aPolyPath = aPath.cloneNode(true);
7285 var aTransform = document.documentElement.createSVGMatrix();
7288 aPolyPath.matrixTransform(aTransform);
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.
7299 * An SVGMatrix instance.
7301 SVGPathElement.prototype.matrixTransform = function( aSVGMatrix )
7303 if( SVGPathSegList.prototype.matrixTransform )
7305 this.pathSegList.matrixTransform( aSVGMatrix );
7309 var aPathSegList = this.pathSegList;
7310 var nLength = aPathSegList.numberOfItems;
7312 for( i = 0; i < nLength; ++i )
7314 aPathSegList.getItem( i ).matrixTransform( aSVGMatrix );
7318 /** SVGPathElement.changeOrientation
7319 * Invert the path orientation by inverting the path command list.
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 )
7330 aPathSegList.changeOrientation();
7337 var aPathSeg = aPathSegList.getItem( 0 );
7338 if( aPathSeg.pathSegTypeAsLetter == 'M' )
7340 nCurrentX = aPathSeg.x;
7341 nCurrentY = aPathSeg.y;
7342 aPathSegList.removeItem( 0 );
7347 for( i = 0; i < nLength; ++i )
7349 aPathSeg = aPathSegList.getItem( i );
7350 var aPoint = aPathSeg.changeOrientation( nCurrentX, nCurrentY );
7351 nCurrentX = aPoint.x;
7352 nCurrentY = aPoint.y;
7356 for( i = nLength - 2; i >= 0; --i )
7358 aPathSeg = aPathSegList.removeItem( i );
7359 aPathSegList.appendItem( aPathSeg );
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.
7377 { // Firefox, Google Chrome, Internet Explorer, Safari.
7379 SVGPathSegMovetoAbs.prototype.matrixTransform = function( aSVGMatrix )
7381 SVGPathMatrixTransform( this, aSVGMatrix );
7384 SVGPathSegLinetoAbs.prototype.matrixTransform = function( aSVGMatrix )
7386 SVGPathMatrixTransform( this, aSVGMatrix );
7389 SVGPathSegCurvetoQuadraticAbs.prototype.matrixTransform = function( aSVGMatrix )
7391 SVGPathMatrixTransform( this, aSVGMatrix );
7393 this.x1 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y1 + aSVGMatrix.e;
7394 this.y1 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y1 + aSVGMatrix.f;
7397 SVGPathSegCurvetoCubicAbs.prototype.matrixTransform = function( aSVGMatrix )
7399 SVGPathMatrixTransform( this, aSVGMatrix );
7401 this.x1 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y1 + aSVGMatrix.e;
7402 this.y1 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y1 + aSVGMatrix.f;
7404 this.x2 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y2 + aSVGMatrix.e;
7405 this.y2 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y2 + aSVGMatrix.f;
7409 SVGPathSegMovetoAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7411 var aPoint = { x: this.x, y: this.y };
7417 SVGPathSegLinetoAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7419 var aPoint = { x: this.x, y: this.y };
7425 SVGPathSegCurvetoQuadraticAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7427 var aPoint = { x: this.x, y: this.y };
7433 SVGPathSegCurvetoCubicAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7435 var aPoint = { x: this.x, y: this.y };
7451 if( e.name == 'ReferenceError' )
7453 SVGPathSeg.prototype.matrixTransform = function( aSVGMatrix )
7456 switch( this.pathSegTypeAsLetter )
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
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
7470 SVGPathMatrixTransform( this, aSVGMatrix );
7473 log( 'SVGPathSeg.matrixTransform: unexpected path segment type: '
7474 + this.pathSegTypeAsLetter );
7478 SVGPathSeg.prototype.changeOrientation = function( nCurrentX, nCurrentY )
7480 switch( this.pathSegTypeAsLetter )
7489 // fall through intended
7493 var aPoint = { x: this.x, y: this.y };
7498 log( 'SVGPathSeg.changeOrientation: unexpected path segment type: '
7499 + this.pathSegTypeAsLetter );
7507 function SVGPathMatrixTransform( aPath, aSVGMatrix )
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;
7531 for( i = 0, l = src.length; i < l; ++i )
7535 dest.push( src[i] );
7538 aCopy.aSequence = dest;
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 **********************************************************************************************/
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 ];
7615 return ANIMATION_NODE_CUSTOM;
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 )
7636 case UNRESOLVED_NODE:
7637 return 'UNRESOLVED';
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' ];
7675 var aPresetClassInMap = {};
7679 var aPresetIdInMap = {};
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' ];
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' ];
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' ];
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' ];
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' ];
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' ];
7793 'height': { 'type': NUMBER_PROPERTY,
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,
7810 'getmod': 'makeScaler( 1/nWidth )',
7811 'setmod': 'makeScaler( nWidth)' },
7813 'x': { 'type': NUMBER_PROPERTY,
7816 'getmod': 'makeScaler( 1/nWidth )',
7817 'setmod': 'makeScaler( nWidth)' },
7819 'y': { 'type': NUMBER_PROPERTY,
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.
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.
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
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] = {};
8148 aTransitionInfoTable[0][0] =
8150 'class' : TRANSITION_INVALID,
8151 'rotationAngle' : 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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
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,
9142 'reverseMethod' : REVERSEMETHOD_IGNORE,
9143 'outInvertsSweep' : true,
9144 'scaleIsotropically' : true
9148 // Transition tables
9150 function createStateTransitionTable()
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
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
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
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
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
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
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
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 )
9237 log( 'getTransitionTable: unexpected restart mode: ' + eRestartMode
9238 + '. Used NEVER instead.');
9239 eRestartMode = RESTART_MODE_NEVER;
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 )
9247 eFillMode = FILL_MODE_REMOVE;
9250 return aStateTransitionTable[eRestartMode][eFillMode];
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;
9288 return EVENT_TRIGGER_UNKNOWN;
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' ];
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 )
9354 this.eTimingType = OFFSET_TIMING;
9358 if( this.sTimingDescription == 'indefinite' )
9359 this.eTimingType = INDEFINITE_TIMING;
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 ) ) )
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 )
9376 this.eTimingType = OFFSET_TIMING;
9377 this.nOffset = bPositiveOffset ? TimeInSec : -TimeInSec;
9382 var aTimingSplit = [];
9383 bPositiveOffset = true;
9384 if( this.sTimingDescription.indexOf( '+' ) != -1 )
9386 aTimingSplit = this.sTimingDescription.split( '+' );
9388 else if( this.sTimingDescription.indexOf( '-' ) != -1 )
9390 aTimingSplit = this.sTimingDescription.split( '-' );
9391 bPositiveOffset = false;
9395 aTimingSplit[0] = this.sTimingDescription;
9396 aTimingSplit[1] = '';
9399 if( aTimingSplit[0].indexOf( '.' ) != -1 )
9401 var aEventSplit = aTimingSplit[0].split( '.' );
9402 this.sEventBaseElementId = aEventSplit[0];
9403 this.eEventType = getEventTriggerType( aEventSplit[1] );
9407 this.eEventType = getEventTriggerType( aTimingSplit[0] );
9410 if( this.eEventType == EVENT_TRIGGER_UNKNOWN )
9413 if( ( this.eEventType == EVENT_TRIGGER_BEGIN_EVENT ) ||
9414 ( this.eEventType == EVENT_TRIGGER_END_EVENT ) )
9416 this.eTimingType = SYNCBASE_TIMING;
9420 this.eTimingType = EVENT_TIMING;
9423 if( aTimingSplit[1] )
9425 sClockValue = aTimingSplit[1];
9426 TimeInSec = Timing.parseClockValue( sClockValue );
9427 if( TimeInSec != undefined )
9429 this.nOffset = ( bPositiveOffset ) ? TimeInSec : -TimeInSec;
9433 this.eTimingType = UNKNOWN_TIMING;
9442 Timing.parseClockValue = function( sClockValue )
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 ) )
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;
9466 else if( rePartialClockValue.test( sClockValue ) )
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;
9477 else if( reTimeCountValue.test( sClockValue ) )
9479 aClockTimeParts = reTimeCountValue.exec( sClockValue );
9481 var nTimeCount = parseInt( aClockTimeParts[1] );
9482 if( aClockTimeParts[2] )
9483 nTimeCount += parseFloat( aClockTimeParts[2] );
9485 if( aClockTimeParts[3] )
9487 if( aClockTimeParts[3] == 'h' )
9489 nTimeInSec = nTimeCount * 3600;
9491 else if( aClockTimeParts[3] == 'min' )
9493 nTimeInSec = nTimeCount * 60;
9495 else if( aClockTimeParts[3] == 's' )
9497 nTimeInSec = nTimeCount;
9499 else if( aClockTimeParts[3] == 'ms' )
9501 nTimeInSec = nTimeCount / 1000;
9506 nTimeInSec = nTimeCount;
9512 nTimeInSec = parseFloat( nTimeInSec.toFixed( 3 ) );
9516 Timing.prototype.info = function( bVerbose )
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() ];
9532 switch( this.getType() )
9534 case INDEFINITE_TIMING:
9535 sInfo += 'indefinite';
9538 sInfo += this.getOffset();
9541 case SYNCBASE_TIMING:
9542 if( this.getEventBaseElementId() )
9543 sInfo += this.getEventBaseElementId() + '.';
9544 sInfo += aEventTriggerOutMap[ this.getEventType() ];
9545 if( this.getOffset() )
9547 if( this.getOffset() > 0 )
9549 sInfo += this.getOffset();
9560 function Duration( sDurationAttribute )
9562 this.bIndefinite = false;
9563 this.bMedia = false;
9564 this.nValue = undefined;
9565 this.bDefined = false;
9567 if( !sDurationAttribute )
9570 if( sDurationAttribute == 'indefinite' )
9571 this.bIndefinite = true;
9572 else if( sDurationAttribute == 'media' )
9576 this.nValue = Timing.parseClockValue( sDurationAttribute );
9577 if( this.nValue <= 0.0 )
9578 this.nValue = 0.001; // duration must be always greater than 0
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()
9599 Duration.prototype.isValue = function()
9601 return this.nValue != undefined;
9604 Duration.prototype.getValue= function()
9609 Duration.prototype.info= function()
9613 if( this.isIndefinite() )
9614 sInfo = 'indefinite';
9615 else if( this.isMedia() )
9617 else if( this.getValue() )
9618 sInfo = this.getValue();
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 )
9659 log( 'NodeContext.makeSourceEventElement: event base element is not valid' );
9663 if( !this.aContext.aEventMultiplexer )
9665 log( 'NodeContext.makeSourceEventElement: event multiplexer not initialized' );
9669 if( !this.aSourceEventElementMap[ sId ] )
9671 this.aSourceEventElementMap[ sId ] = new SourceEventElement( sId, aEventBaseElem, this.aContext.aEventMultiplexer );
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 )
9691 log( 'StateTransition.enter: commit() before enter()ing again!' );
9694 if( !bForce && !this.aNode.isTransition( this.aNode.getState(), eNodeState ) )
9697 // recursion detection:
9698 if( ( this.aNode.nCurrentStateTransition & eNodeState ) != 0 )
9699 return false; // already in wanted transition
9702 this.aNode.nCurrentStateTransition |= eNodeState;
9703 this.eToState = eNodeState;
9707 StateTransition.prototype.commit = function()
9709 if( this.eToState != INVALID_NODE )
9711 this.aNode.eCurrentState = this.eToState;
9716 StateTransition.prototype.clear = function()
9718 if( this.eToState != INVALID_NODE )
9720 this.aNode.nCurrentStateTransition &= ~this.eToState;
9721 this.eToState = INVALID_NODE;
9728 function BaseNode( aAnimElem, aParentNode, aNodeContext )
9730 this.nId = getUniqueId();
9731 this.sClassName = 'BaseNode';
9734 log( 'BaseNode(id:' + this.nId + ') constructor: aAnimElem is not valid' );
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;
9756 this.aDuration = 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()
9776 BaseNode.prototype.parseElement = function()
9778 var aAnimElem = this.aElement;
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'
9785 this.aNodeContext.aAnimationNodeMap[ sIdAttr ] = this;
9789 var sBeginAttr = aAnimElem.getAttributeNS( NSS['smil'], 'begin' );
9790 this.aBegin = new Timing( this, sBeginAttr );
9791 this.aBegin.parse();
9795 var sEndAttr = aAnimElem.getAttributeNS( NSS['smil'], 'end' );
9798 this.aEnd = new Timing( this, sEndAttr );
9803 this.aDuration = null;
9804 var sDurAttr = aAnimElem.getAttributeNS( NSS['smil'], 'dur' );
9805 this.aDuration = new Duration( sDurAttr );
9806 if( !this.aDuration.isSet() )
9808 if( this.isContainer() )
9809 this.aDuration = null;
9811 this.aDuration = new Duration( 'indefinite' );
9815 var sFillAttr = aAnimElem.getAttributeNS( NSS['smil'], 'fill' );
9816 if( sFillAttr && aFillModeInMap[ sFillAttr ])
9817 this.eFillMode = aFillModeInMap[ sFillAttr ];
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 ];
9826 this.eRestartMode = RESTART_MODE_DEFAULT;
9828 // repeatCount attribute
9829 var sRepeatCount = aAnimElem.getAttributeNS( NSS['smil'], 'repeatCount' );
9831 this.nReapeatCount = 1;
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();
9865 this.eFillMode = FILL_MODE_AUTO;
9867 if( this.eFillMode == FILL_MODE_AUTO ) // see SMIL recommendation document
9869 this.eFillMode = ( this.aEnd ||
9870 ( this.nReapeatCount != 1) ||
9871 ( this.aDuration && !this.aDuration.isIndefinite() ) )
9876 // resolve restart value
9877 if( this.eRestartMode == RESTART_MODE_DEFAULT )
9878 if( this.getParentNode() )
9879 this.eRestartMode = this.getParentNode().getRestartMode();
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 )
9891 this.nAccelerate = 0.0;
9892 this.nDecelerate = 0.0;
9895 this.aStateTransTable = getTransitionTable( this.getRestartMode(), this.getFillMode() );
9900 BaseNode.prototype.getParentNode = function()
9902 return this.aParentNode;
9905 BaseNode.prototype.init = function()
9907 this.DBG( this.callInfo( 'init' ) );
9908 if( ! this.checkValidNode() )
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() )
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 ) &&
9936 aStateTrans.commit();
9938 if( this.aActivationEvent )
9940 this.aActivationEvent.charge();
9944 this.aActivationEvent = makeDelay( bind( this, this.activate ), this.getBegin().getOffset() + this.nStartDelay );
9946 registerEvent( this.getId(), this.getBegin(), this.aActivationEvent, this.aNodeContext );
9954 BaseNode.prototype.activate = function()
9956 if( ! this.checkValidNode() )
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 ) )
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() );
9978 BaseNode.prototype.deactivate = function()
9980 if( this.inStateOrTransition( ENDED_NODE | FROZEN_NODE ) || !this.checkValidNode() )
9983 if( this.isTransition( this.eCurrentState, FROZEN_NODE ) )
9985 this.DBG( this.callInfo( 'deactivate' ), getCurrentSystemTime() );
9987 var aStateTrans = new StateTransition( this );
9988 if( aStateTrans.enter( FROZEN_NODE, true /* FORCE */ ) )
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();
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() )
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 */ ) )
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();
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())
10058 log( 'BaseNode.registerDeactivatingListener(): invalid notifiee' );
10061 this.aDeactivatingListenerArray.push( aNotifiee );
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 )
10086 this.aDeactivationEvent.charge();
10090 if( typeof( nDelay ) == typeof(0) )
10091 this.aDeactivationEvent = makeDelay( bind( this, this.deactivate ), nDelay );
10093 this.aDeactivationEvent = null;
10095 return this.aDeactivationEvent;
10098 BaseNode.prototype.scheduleDeactivationEvent = function( aEvent )
10100 this.DBG( this.callInfo( 'scheduleDeactivationEvent' ) );
10104 if( this.getDuration() && this.getDuration().isValue() )
10105 aEvent = this.makeDeactivationEvent( this.getDuration().getValue() );
10109 this.aContext.aTimerEventQueue.addEvent( aEvent );
10113 BaseNode.prototype.checkValidNode = function()
10115 return ( this.eCurrentState != INVALID_NODE );
10118 BaseNode.prototype.init_st = function()
10123 BaseNode.prototype.resolve_st = function()
10128 BaseNode.prototype.activate_st = function()
10130 this.scheduleDeactivationEvent();
10133 BaseNode.prototype.deactivate_st = function( /*aNodeState*/ )
10138 BaseNode.prototype.notifyEndListeners = function()
10140 var nDeactivatingListenerCount = this.aDeactivatingListenerArray.length;
10142 for( var i = 0; i < nDeactivatingListenerCount; ++i )
10144 this.aDeactivatingListenerArray[i].notifyDeactivating( this );
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()
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() );
10230 sInfo += '; is container: ' + this.isContainer();
10233 if( this.getBegin() )
10234 sInfo += '; begin: ' + this.getBegin().info();
10237 if( this.getDuration() )
10238 sInfo += '; dur: ' + this.getDuration().info();
10241 if( this.getEnd() )
10242 sInfo += '; end: ' + this.getEnd().info();
10245 if( this.getFillMode() )
10246 sInfo += '; fill: ' + aFillModeOutMap[ this.getFillMode() ];
10249 if( this.getRestartMode() )
10250 sInfo += '; restart: ' + aRestartModeOutMap[ this.getRestartMode() ];
10253 if( this.getRepeatCount() && ( this.getRepeatCount() != 1.0 ) )
10254 sInfo += '; repeatCount: ' + this.getRepeatCount();
10257 if( this.getAccelerateValue() )
10258 sInfo += '; accelerate: ' + this.getAccelerateValue();
10261 if( this.getDecelerateValue() )
10262 sInfo += '; decelerate: ' + this.getDecelerateValue();
10265 if( this.isAutoReverseEnabled() )
10266 sInfo += '; autoReverse: true';
10273 BaseNode.prototype.callInfo = function( sMethodName )
10275 var sInfo = this.sClassName +
10276 '( ' + this.getId() +
10277 ', ' + getNodeStateName( this.getState() ) +
10278 ' ).' + sMethodName;
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 )
10322 this.eCurrentState = INVALID_NODE;
10323 log( 'AnimationBaseNode.parseElement: target element not found: ' + sTargetElementAttr );
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];
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 )
10349 // set up target element initial visibility
10350 if( aAnimElem.getAttributeNS( NSS['smil'], 'attributeName' ) === 'visibility' )
10352 if( aAnimElem.getAttributeNS( NSS['smil'], 'to' ) === 'visible' )
10353 this.aTargetElement.setAttribute( 'visibility', 'hidden' );
10356 // create animated element
10357 if( !this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ] )
10359 if( this.bIsTargetTextElement )
10361 this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ]
10362 = new AnimatedTextElement( this.aTargetElement );
10366 this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ]
10367 = new AnimatedElement( this.aTargetElement );
10370 this.aAnimatedElement = this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ];
10372 // set additive mode
10373 this.aAnimatedElement.setAdditiveMode( this.eAdditiveMode );
10380 AnimationBaseNode.prototype.init_st = function()
10382 if( this.aActivity )
10383 this.aActivity.activate( makeEvent( bind( this, this.deactivate ) ) );
10385 this.aActivity = this.createActivity();
10389 AnimationBaseNode.prototype.resolve_st = function()
10394 AnimationBaseNode.prototype.activate_st = function()
10396 if( this.aActivity )
10398 this.saveStateOfAnimatedElement();
10399 this.aActivity.setTargets( this.getAnimatedElement() );
10400 if( this.getContext().bIsSkipping )
10402 this.aActivity.end();
10406 this.getContext().aActivityQueue.addActivity( this.aActivity );
10411 AnimationBaseNode.superclass.scheduleDeactivationEvent.call( this );
10415 AnimationBaseNode.prototype.deactivate_st = function( eDestState )
10417 if( eDestState == FROZEN_NODE )
10419 if( this.aActivity )
10420 this.aActivity.end();
10422 if( eDestState == ENDED_NODE )
10424 if( this.aActivity )
10425 this.aActivity.dispose();
10426 if( ( this.getFillMode() == FILL_MODE_REMOVE ) && this.getAnimatedElement() )
10427 this.removeEffect();
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() )
10443 nDuration = this.getDuration().getValue();
10447 log( 'AnimationBaseNode.fillActivityParams: duration is not a number' );
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()
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 );
10518 if( this.getMinFrameCount() )
10519 sInfo += '; min frame count: ' + this.getMinFrameCount();
10522 sInfo += '; additive: ' + aAddittiveModeOutMap[ this.getAdditiveMode() ];
10525 if( this.getTargetElement() )
10527 var sElemId = this.getTargetElement().getAttribute( 'id' );
10528 sInfo += '; targetElement: ' + sElemId;
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 )
10558 this.eCurrentState = INVALID_NODE;
10559 log( 'AnimationBaseNode2.parseElement: target attribute name not found: ' + this.sAttributeName );
10563 this.aToValue = aAnimElem.getAttributeNS( NSS['smil'], 'to' );
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 );
10585 if( this.getAttributeName() )
10586 sInfo += '; attributeName: ' + this.getAttributeName();
10589 if( this.getToValue() )
10590 sInfo += '; to: ' + this.getToValue();
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 ];
10633 this.aFromValue = aAnimElem.getAttributeNS( NSS['smil'], 'from' );
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 )
10644 var aKeyTimes = sKeyTimesAttr.split( ';' );
10645 for( var i = 0; i < aKeyTimes.length; ++i )
10646 this.aKeyTimes.push( parseFloat( aKeyTimes[i] ) );
10649 // values attribute
10650 var sValuesAttr = aAnimElem.getAttributeNS( NSS['smil'], 'values' );
10653 this.aValues = sValuesAttr.split( ';' );
10660 // formula attribute
10661 this.aFormula = aAnimElem.getAttributeNS( NSS['anim'], 'formula' );
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 );
10708 if( this.getAccumulate() )
10709 sInfo += '; accumulate: ' + aAccumulateModeOutMap[ this.getAccumulate() ];
10712 sInfo += '; calcMode: ' + aCalcModeOutMap[ this.getCalcMode() ];
10715 if( this.getFromValue() )
10716 sInfo += '; from: ' + this.getFromValue();
10719 if( this.getByValue() )
10720 sInfo += '; by: ' + this.getByValue();
10723 if( this.getKeyTimes().length )
10724 sInfo += '; keyTimes: ' + this.getKeyTimes().join( ',' );
10727 if( this.getValues().length )
10728 sInfo += '; values: ' + this.getValues().join( ',' );
10731 if( this.getFormula() )
10732 sInfo += '; formula: ' + this.getFormula();
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 )
10790 this.aChildrenArray[i].parseElement();
10794 // resolve duration
10795 this.bDurationIndefinite
10796 = ( !this.getDuration() || this.getDuration().isIndefinite() ) &&
10797 ( !this.getEnd() || ( this.getEnd().getType() != OFFSET_TIMING ) );
10802 BaseContainerNode.prototype.appendChildNode = function( aAnimationNode )
10804 if( ! this.checkValidNode() )
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 )
10830 if( this.aChildrenArray[i].init() )
10835 return ( nChildrenCount == nInitChildren );
10839 BaseContainerNode.prototype.deactivate_st = function( eDestState )
10841 this.nLeftIterations = 0;
10842 if( eDestState == FROZEN_NODE )
10844 // deactivate all children that are not FROZEN or ENDED:
10845 this.forEachChildNode( mem_fn( 'deactivate' ), ~( FROZEN_NODE | ENDED_NODE ) );
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();
10856 BaseContainerNode.prototype.hasPendingAnimation = function()
10858 var nChildrenCount = this.aChildrenArray.length;
10859 for( var i = 0; i < nChildrenCount; ++i )
10861 if( this.aChildrenArray[i].hasPendingAnimation() )
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 )
10887 if( this.aChildrenArray[i].getId() == aAnimationNode.getId() )
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 ) )
10903 log( 'BaseContainerNode.notifyDeactivatedChild: unknown child notifier!' );
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() )
10917 if( this.nLeftIterations >= 1.0 )
10919 this.nLeftIterations -= 1.0;
10921 if( this.nLeftIterations >= 1.0 )
10924 var aRepetitionEvent = makeDelay( bind( this, this.repeat ), 0.0 );
10925 this.aContext.aTimerEventQueue.addEvent( aRepetitionEvent );
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();
10943 this.activate_st();
10944 return bInitialized;
10947 BaseContainerNode.prototype.removeEffect = function()
10949 var nChildrenCount = this.aChildrenArray.length;
10950 if( nChildrenCount == 0 )
10952 // We remove effect in reverse order.
10953 for( var i = nChildrenCount - 1; i >= 0; --i )
10955 if( ( this.aChildrenArray[i].getState() & ( FROZEN_NODE | ENDED_NODE ) ) == 0 )
10957 log( 'BaseContainerNode.removeEffect: child(id:'
10958 + this.aChildrenArray[i].getId() + ') is neither frozen nor ended;'
10960 + aTransitionModeOutMap[ this.aChildrenArray[i].getState() ] );
10963 this.aChildrenArray[i].removeEffect();
10967 BaseContainerNode.prototype.saveStateOfAnimatedElement = function()
10969 var nChildrenCount = this.aChildrenArray.length;
10970 for( var i = 0; i < nChildrenCount; ++i )
10972 this.aChildrenArray[i].saveStateOfAnimatedElement();
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 )
10984 if( ( eNodeStateMask != -1 ) && ( ( this.aChildrenArray[i].getState() & eNodeStateMask ) == 0 ) )
10986 aFunction( this.aChildrenArray[i] );
10990 BaseContainerNode.prototype.dispose = function()
10992 var nChildrenCount = this.aChildrenArray.length;
10993 for( var i = 0; i < nChildrenCount; ++i )
10995 this.aChildrenArray[i].dispose();
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 );
11012 // impress node type
11013 if( this.getImpressNodeType() )
11014 sInfo += '; node-type: ' + aImpressNodeTypeOutMap[ this.getImpressNodeType() ];
11017 var nChildrenCount = this.aChildrenArray.length;
11018 for( var i = 0; i < nChildrenCount; ++i )
11021 sInfo += this.aChildrenArray[i].info( bVerbose );
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 )
11043 if( this.aChildrenArray[i].resolve() )
11045 ++nResolvedChildren;
11049 if( nChildrenCount != nResolvedChildren )
11051 log( 'ParallelTimeContainer.activate_st: resolving all children failed' );
11056 if( this.isDurationIndefinite() && ( nChildrenCount == 0 ) )
11058 this.scheduleDeactivationEvent( this.makeDeactivationEvent( 0.0 ) );
11062 this.scheduleDeactivationEvent();
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 )
11092 if( this.resolveChild( this.aChildrenArray[ this.nFinishedChildren ] ) )
11095 log( 'SequentialTimeContainer.activate_st: resolving child failed!' );
11098 if( this.isDurationIndefinite() && ( ( nChildrenCount == 0 ) || ( this.nFinishedChildren >= nChildrenCount ) ) )
11100 // deactivate ASAP:
11101 this.scheduleDeactivationEvent( this.makeDeactivationEvent( 0.0 ) );
11105 this.scheduleDeactivationEvent();
11109 SequentialTimeContainer.prototype.notifyDeactivating = function( aNotifier )
11111 // If we are rewinding we have not to resolve the next child.
11112 if( this.bIsRewinding )
11115 if( this.notifyDeactivatedChild( aNotifier ) )
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 ) )
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).
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
11145 SequentialTimeContainer.prototype.skipEffect = function( aChildNode )
11147 if( this.isChildNode( aChildNode ) )
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 );
11163 log( 'SequentialTimeContainer.skipEffect: unknown child: '
11164 + aChildNode.getId() );
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
11176 SequentialTimeContainer.prototype.rewindCurrentEffect = function( aChildNode )
11178 if( this.isChildNode( aChildNode ) )
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.
11203 aChildNode.removeEffect();
11204 // Finally we place the child node to the 'unresolved' state and
11205 // resolve it again.
11207 this.resolveChild( aChildNode );
11208 this.notifyRewindedEvent( aChildNode );
11209 this.bIsRewinding = false;
11213 log( 'SequentialTimeContainer.rewindCurrentEffect: unknown child: '
11214 + aChildNode.getId() );
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
11226 SequentialTimeContainer.prototype.rewindLastEffect = function( aChildNode )
11228 if( this.isChildNode( aChildNode ) )
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();
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
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.
11262 this.resolveChild( aPreviousChildNode );
11263 this.notifyRewindedEvent( aChildNode );
11264 this.bIsRewinding = false;
11268 log( 'SequentialTimeContainer.rewindLastEffect: unknown child: '
11269 + aChildNode.getId() );
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
11279 * @param aChildNode
11280 * An animation node representing the root node of the next shape effect
11283 * It returns true if the passed child has been resolved successfully,
11286 SequentialTimeContainer.prototype.resolveChild = function( aChildNode )
11288 var bResolved = aChildNode.resolve();
11290 if( bResolved && ( this.isMainSequenceRootNode() || this.isInteractiveSequenceRootNode() ) )
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() )
11306 this.aContext.aEventMultiplexer.registerSkipEffectEvent( this.aCurrentSkipEvent );
11307 this.aContext.aEventMultiplexer.registerRewindCurrentEffectEvent( this.aRewindCurrentEffectEvent );
11308 this.aContext.aEventMultiplexer.registerRewindLastEffectEvent( this.aRewindLastEffectEvent );
11310 else if( this.isInteractiveSequenceRootNode() )
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 );
11320 SequentialTimeContainer.prototype.notifyRewindedEvent = function( aChildNode )
11322 if( this.isInteractiveSequenceRootNode() )
11324 this.aContext.aEventMultiplexer.notifyRewindedEffectEvent( aChildNode.getId() );
11326 var sId = aChildNode.getBegin().getEventBaseElementId();
11329 this.aContext.aEventMultiplexer.notifyRewindedEffectEvent( sId );
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 ];
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;
11438 if( this.getColorInterpolation() === COLOR_SPACE_HSL )
11440 ANIMDBG.print( 'AnimationColorNode.createActivity: color space hsl' );
11441 aColorAnimation = new HSLAnimationWrapper( aAnimation );
11442 var aInterpolatorMaker = aInterpolatorHandler.getInterpolator( this.getCalcMode(),
11445 aInterpolator = aInterpolatorMaker( this.getColorInterpolationDirection() );
11449 ANIMDBG.print( 'AnimationColorNode.createActivity: color space rgb' );
11450 aColorAnimation = aAnimation;
11451 aInterpolator = aInterpolatorHandler.getInterpolator( this.getCalcMode(),
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 );
11475 // color interpolation
11476 sInfo += '; color-interpolation: ' + aColorSpaceOutMap[ this.getColorInterpolation() ];
11478 // color interpolation direction
11479 sInfo += '; color-interpolation-direction: ' + aClockDirectionOutMap[ this.getColorInterpolationDirection() ];
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,
11512 AnimationTransitionFilterNode.prototype.parseElement = function()
11514 var bRet = AnimationTransitionFilterNode.superclass.parseElement.call( this );
11515 var bIsValidTransition = true;
11517 var aAnimElem = this.aElement;
11520 this.eTransitionType = undefined;
11521 var sTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'type' );
11522 if( sTypeAttr && aTransitionTypeInMap[ sTypeAttr ] )
11524 this.eTransitionType = aTransitionTypeInMap[ sTypeAttr ];
11528 bIsValidTransition = false;
11529 log( 'AnimationTransitionFilterNode.parseElement: transition type not valid: ' + sTypeAttr );
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 ) )
11539 this.eTransitionSubType = aTransitionSubtypeInMap[ sSubTypeAttr ];
11543 bIsValidTransition = false;
11544 log( 'AnimationTransitionFilterNode.parseElement: transition subtype not valid: ' + sSubTypeAttr );
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 )
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' );
11557 // direction attribute
11558 this.bReverseDirection = false;
11559 var sDirectionAttr = aAnimElem.getAttributeNS( NSS['smil'], 'direction' );
11560 if( sDirectionAttr == 'reverse' )
11561 this.bReverseDirection = true;
11564 this.eTransitionMode = TRANSITION_MODE_IN;
11565 var sModeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'mode' );
11566 if( sModeAttr === 'out' )
11567 this.eTransitionMode = TRANSITION_MODE_OUT;
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 );
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';
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 )
11638 case ANIMATION_NODE_PAR:
11639 aCreatedNode = aCreatedContainer =
11640 new ParallelTimeContainer( aElement, aParentNode, aNodeContext );
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 );
11649 case ANIMATION_NODE_SEQ:
11650 aCreatedNode = aCreatedContainer =
11651 new SequentialTimeContainer( aElement, aParentNode, aNodeContext );
11653 case ANIMATION_NODE_ANIMATE:
11654 aCreatedNode = new PropertyAnimationNode( aElement, aParentNode, aNodeContext );
11656 case ANIMATION_NODE_SET:
11657 aCreatedNode = new AnimationSetNode( aElement, aParentNode, aNodeContext );
11659 case ANIMATION_NODE_ANIMATEMOTION:
11660 //aCreatedNode = new AnimationPathMotionNode( aElement, aParentNode, aNodeContext );
11662 log( 'createAnimationNode: ANIMATEMOTION not implemented' );
11664 case ANIMATION_NODE_ANIMATECOLOR:
11665 aCreatedNode = new AnimationColorNode( aElement, aParentNode, aNodeContext );
11667 case ANIMATION_NODE_ANIMATETRANSFORM:
11668 //aCreatedNode = new AnimationTransformNode( aElement, aParentNode, aNodeContext );
11670 log( 'createAnimationNode: ANIMATETRANSFORM not implemented' );
11672 case ANIMATION_NODE_TRANSITIONFILTER:
11673 aCreatedNode = new AnimationTransitionFilterNode( aElement, aParentNode, aNodeContext );
11675 case ANIMATION_NODE_AUDIO:
11676 log( 'createAnimationNode: AUDIO not implemented' );
11678 case ANIMATION_NODE_COMMAND:
11679 log( 'createAnimationNode: COMMAND not implemented' );
11682 log( 'createAnimationNode: invalid Animation Node Type: ' + eAnimationNodeType );
11686 if( aCreatedContainer )
11688 if( eAnimationNodeType == ANIMATION_NODE_ITERATE )
11690 createIteratedNodes( aElement, aCreatedContainer, aNodeContext );
11694 var aChildrenArray = getElementChildren( aElement );
11695 for( var i = 0; i < aChildrenArray.length; ++i )
11697 if( !createChildNode( aChildrenArray[i], aCreatedContainer, aNodeContext ) )
11699 aCreatedContainer.removeAllChildrenNodes();
11706 return aCreatedNode;
11712 function createChildNode( aElement, aParentNode, aNodeContext )
11714 var aChildNode = createAnimationNode( aElement, aParentNode, aNodeContext );
11718 log( 'createChildNode: child node creation failed' );
11723 aParentNode.appendChildNode( aChildNode );
11731 function createIteratedNodes( /*aElement, aContainerNode, aNodeContext*/ )
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 ) )
11748 log( 'makeScaler: not valid param passed: ' + nScale );
11752 return function( nValue )
11754 return ( nScale * nValue );
11760 // eslint-disable-next-line no-unused-vars
11761 function createPropertyAnimation( sAttrName, aAnimatedElement, nWidth, nHeight )
11763 if( !aAttributeMap[ sAttrName ] )
11765 log( 'createPropertyAnimation: attribute is unknown' );
11770 var aFunctorSet = aAttributeMap[ sAttrName ];
11772 var sGetValueMethod = aFunctorSet.get;
11773 var sSetValueMethod = aFunctorSet.set;
11775 if( !sGetValueMethod || !sSetValueMethod )
11777 log( 'createPropertyAnimation: attribute is not handled' );
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 ] ),
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.
11809 function createShapeTransition( aActivityParamSet, aAnimatedElement,
11810 nSlideWidth, nSlideHeight,
11811 aAnimatedTransitionFilterNode )
11813 if( !aAnimatedTransitionFilterNode )
11815 log( 'createShapeTransition: the animated transition filter node is not valid.' );
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 )
11829 case TRANSITION_INVALID:
11830 log( 'createShapeTransition: transition class: TRANSITION_INVALID' );
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 )
11844 // no special transition filter provided
11845 // we map everything to crossfade
11848 = createPropertyAnimation( 'opacity',
11852 return new SimpleActivity( aActivityParamSet, aAnimation, bModeIn );
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.
11872 * If true the element to be animated becomes more visible as the transition
11873 * progress else it becomes less visible.
11875 function ClippingAnimation( aParametricPolyPolygon, aTransitionInfo,
11876 bDirectionForward, bModeIn )
11878 this.aClippingFunctor = new ClippingFunctor( aParametricPolyPolygon,
11880 bDirectionForward, bModeIn );
11881 this.bAnimationStarted = false;
11885 * This method notifies to the element involved in the transition that
11886 * the animation is starting and creates the <clipPath> element used for
11889 * @param aAnimatableElement
11890 * The element to be animated.
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;
11906 * The transition clean up is performed here.
11908 ClippingAnimation.prototype.end = function()
11910 if( this.bAnimationStarted )
11912 this.aAnimatableElement.cleanClipPath();
11913 this.bAnimationStarted = false;
11914 this.aAnimatableElement.notifyAnimationEnd();
11919 * This method set the position of the element to be animated according to
11920 * the passed time value.
11923 * The time parameter.
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()
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 )
11970 this.bAnimationStarted = false;
11971 this.aAnimatableElement.notifyAnimationEnd();
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 );
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.
12033 function SlideChangeBase(aLeavingSlide, aEnteringSlide)
12035 this.aLeavingSlide = aLeavingSlide;
12036 this.aEnteringSlide = aEnteringSlide;
12037 this.bIsFinished = false;
12041 * The transition initialization is performed here.
12043 SlideChangeBase.prototype.start = function()
12048 * The transition clean up is performed here.
12050 SlideChangeBase.prototype.end = function()
12052 if( this.bIsFinished )
12055 this.aLeavingSlide.hide();
12056 this.aEnteringSlide.reset();
12057 this.aLeavingSlide.reset();
12059 this.bIsFinished = true;
12063 * This method is responsible for performing the slide transition.
12066 * The time parameter.
12067 * @return {Boolean}
12068 * If the transition is performed returns tue else returns false.
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 );
12083 SlideChangeBase.prototype.getUnderlyingValue = function()
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.
12110 function FadingSlideChange( aLeavingSlide, aEnteringSlide )
12112 FadingSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
12113 this.bFirstRun = true;
12115 extend( FadingSlideChange, SlideChangeBase );
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
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();
12133 * This method set the opacity of the entering slide according to the passed
12137 * The time parameter.
12139 FadingSlideChange.prototype.performIn = function( nT )
12141 this.aEnteringSlide.setOpacity( nT );
12145 * This method set the opacity of the leaving slide according to the passed
12149 * The time parameter.
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.
12172 function FadingOverColorSlideChange( aLeavingSlide, aEnteringSlide, sFadeColor )
12174 FadingSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
12175 this.sFadeColor = sFadeColor;
12176 if( !this.sFadeColor )
12178 log( 'FadingOverColorSlideChange: sFadeColor not valid.' );
12179 this.sFadeColor = '#000000';
12181 this.aColorPlaneElement = this.createColorPlaneElement();
12183 extend( FadingOverColorSlideChange, SlideChangeBase );
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.
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();
12203 * This method removes the color plane element.
12205 FadingOverColorSlideChange.prototype.end = function()
12207 FadingOverColorSlideChange.superclass.end.call( this );
12208 this.aLeavingSlide.removeElement( this.aColorPlaneElement );
12212 * This method set the opacity of the entering slide according to the passed
12216 * The time parameter.
12218 FadingOverColorSlideChange.prototype.performIn = function( nT )
12220 this.aEnteringSlide.setOpacity( (nT > 0.55) ? 2.0*(nT-0.55) : 0.0 );
12224 * This method set the opacity of the leaving slide according to the passed
12228 * The time parameter.
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}.
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 );
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
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();
12288 * This method set the position of the entering slide according to the passed
12292 * The time parameter.
12294 MovingSlideChange.prototype.performIn = function( nT )
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 );
12303 * This method set the position of the leaving slide according to the passed
12307 * The time parameter.
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.
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 );
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
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();
12361 * This method set the position of the entering slide according to the passed
12365 * The time parameter.
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( )
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.
12396 * The direction the filter effect has to be performed
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 )
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 );
12427 this.aStaticTransformation = document.documentElement.createSVGMatrix();
12430 if( !bIsDirectionForward )
12432 var aMatrix = null;
12433 switch( aTransitionInfo.reverseMethod )
12436 log( 'ClippingFunctor: unexpected reverse method.' );
12438 case REVERSEMETHOD_IGNORE:
12440 case REVERSEMETHOD_INVERT_SWEEP:
12441 this.bForwardParameterSweep = !this.bForwardParameterSweep;
12443 case REVERSEMETHOD_SUBTRACT_POLYGON:
12444 this.bSubtractPolygon = !this.bSubtractPolygon;
12446 case REVERSEMETHOD_SUBTRACT_AND_INVERT:
12447 this.bForwardParameterSweep = !this.bForwardParameterSweep;
12448 this.bSubtractPolygon = !this.bSubtractPolygon;
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 );
12455 case REVERSEMETHOD_FLIP_X:
12456 aMatrix = document.documentElement.createSVGMatrix();
12459 aMatrix.a = -1; aMatrix.e = 1.0;
12460 this.aStaticTransformation = aMatrix.multiply( this.aStaticTransformation );
12463 case REVERSEMETHOD_FLIP_Y:
12464 aMatrix = document.documentElement.createSVGMatrix();
12467 aMatrix.d = -1; aMatrix.f = 1.0;
12468 this.aStaticTransformation = aMatrix.multiply( this.aStaticTransformation );
12476 if( aTransitionInfo.outInvertsSweep )
12478 this.bForwardParameterSweep = !this.bForwardParameterSweep;
12482 this.bSubtractPolygon = !this.bSubtractPolygon;
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' );
12495 * A parameter in [0,1] representing normalized time.
12497 * The width of the bounding box of the slide/shape to be clipped.
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
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'
12511 // See: http://www.w3.org/TR/SVG11/painting.html#FillRuleProperty
12513 if( this.bSubtractPolygon )
12515 aClipPoly.changeOrientation();
12516 aClipPoly.prependPath( ClippingFunctor.aBoundingPath );
12520 if( this.bScaleIsotropically )
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 );
12531 aMatrix = SVGIdentityMatrix.scaleNonUniform( nWidth, nHeight );
12532 aMatrix = aMatrix.multiply( this.aStaticTransformation );
12535 aClipPoly.matrixTransform( aMatrix );
12543 /** createClipPolyPolygon
12546 * An enumerator representing the transition type.
12548 * An enumerator representing the transition subtype.
12550 * An object that handles a parametric <path> element.
12552 function createClipPolyPolygon( nType, nSubtype )
12557 log( 'createClipPolyPolygon: unknown transition type: ' + nType );
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:
12577 case ONEBLADE_TRANS_SUBTYPE:
12580 case DEFAULT_TRANS_SUBTYPE:
12581 case TWOBLADEVERTICAL_TRANS_SUBTYPE:
12584 case TWOBLADEHORIZONTAL_TRANS_SUBTYPE:
12587 case THREEBLADE_TRANS_SUBTYPE:
12590 case FOURBLADE_TRANS_SUBTYPE:
12593 case EIGHTBLADE_TRANS_SUBTYPE:
12597 log( 'createClipPolyPolygon: unknown subtype: ' + nSubtype );
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:
12614 case RECTANGLE_TRANS_SUBTYPE:
12615 return new IrisWipePath(0);
12616 case DIAMOND_TRANS_SUBTYPE:
12617 return new IrisWipePath(1);
12619 log( 'createClipPolyPolygon: unknown subtype: ' + nSubtype );
12622 case BARNDOORWIPE_TRANSITION:
12623 return new BarnDoorWipePath(false);
12624 case SINGLESWEEPWIPE_TRANSITION:
12625 return new SweepWipePath(
12627 nSubtype == CLOCKWISETOP_TRANS_SUBTYPE ||
12628 nSubtype == CLOCKWISERIGHT_TRANS_SUBTYPE ||
12629 nSubtype == CLOCKWISEBOTTOM_TRANS_SUBTYPE ||
12630 nSubtype == CLOCKWISELEFT_TRANS_SUBTYPE,
12633 // oppositeVertical
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:
12644 case DOUBLEBARNDOOR_TRANS_SUBTYPE:
12645 return new BarnDoorWipePath(true /* Doubled */);
12646 case DOUBLEDIAMOND_TRANS_SUBTYPE:
12647 return new DoubleDiamondWipePath();
12649 log( 'createClipPolyPolygon: unhandled subtype: ' + nSubtype );
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 ,
12663 nSubtype == TOPLEFTVERTICAL_TRANS_SUBTYPE ||
12664 nSubtype == TOPRIGHTDIAGONAL_TRANS_SUBTYPE ||
12665 nSubtype == BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE
12667 case PARALLELSNAKESWIPE_TRANSITION:
12668 return new ParallelSnakesWipePath(
12671 nSubtype == DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
12672 nSubtype == DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
12674 nSubtype == VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
12675 nSubtype == HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE ||
12676 nSubtype == DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
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
12686 case SPIRALWIPE_TRANSITION:
12687 return new SpiralWipePath(
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(
12699 nSubtype == FOURBOXVERTICAL_TRANS_SUBTYPE ||
12700 nSubtype == FOURBOXHORIZONTAL_TRANS_SUBTYPE );
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 );
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 );
12723 function pruneScaleValue( nVal )
12726 return (nVal < -0.00001 ? nVal : -0.00001);
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.
12738 * The number of bars to be generated.
12740 function BarWipePath( nBars /* nBars > 1: blinds effect */ )
12742 this.nBars = nBars;
12743 if( this.nBars === undefined || this.nBars < 1 )
12745 this.aBasePath = createUnitSquarePath();
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.
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 )
12768 for( i = this.nBars - 1; i > 0; --i )
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 );
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.
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);
12800 aTransform = aTransform.scale(d, d);
12802 var aPath = this.aBasePath.cloneNode(true);
12803 aPath.matrixTransform(aTransform);
12807 /* Class SweepWipePath
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 ) {
12823 if(!this.bSingle && !this.bOppositeVertical)
12826 var poly = PinWheelWipePath.calcCenteredClock( nT + 0.25, 1.0 );
12830 aTransform = SVGIdentityMatrix.translate(0.5, 0.0);
12831 poly.matrixTransform(aTransform);
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();
12843 aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
12844 aTransform.rotate(Math.PI);
12845 aTransform.translate(0.5, 0.5);
12846 poly.matrixTransform(aTransform);
12848 res.appendPath(poly);
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.
12860 function FourBoxWipePath( bCornersOut )
12862 this.bCornersOut = bCornersOut;
12863 this.aBasePath = createUnitSquarePath();
12866 FourBoxWipePath.prototype.perform = function( nT )
12869 var d = pruneScaleValue( nT / 2.0 );
12871 if( this.bCornersOut )
12873 aMatrix = SVGIdentityMatrix.translate( -0.25, -0.25 ).scale( d ).translate( -0.5, -0.5 );
12877 aMatrix = SVGIdentityMatrix.translate( -0.5, -0.5 ).scale( d );
12881 var aTransform = aMatrix;
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 );
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
12926 * The transition subtype.
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 );
12958 * Class FanWipePath
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);
12974 var aTransform = SVGIdentityMatrix.flipY();
12975 aTransform = aTransform.scaleNonUniform(-1.0, 1.0);
12976 poly.matrixTransform(aTransform);
12977 res.appendPath(poly);
12980 aTransform = SVGIdentityMatrix.scaleNonUniform(0.5, 0.5).translate(0.5, 0.5);
12981 res.matrixTransform(aTransform);
12984 res.appendPath(flipOnXAxis(res));
12987 aTransform = SVGIdentityMatrix.scaleNonUniform(0.5, 1.0).translate(0.5, 1.0);
12988 res.matrixTransform(aTransform);
12994 * Class ClockWipePath
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 );
13007 /** Class PinWheelWipePath
13008 * This class handles a parametric poly-path that is used for performing
13009 * a spinWheelWipe transition.
13012 * Number of blades generated by the transition.
13014 function PinWheelWipePath( nBlades )
13016 this.nBlades = nBlades;
13017 if( !this.nBlades || this.nBlades < 1 )
13021 PinWheelWipePath.calcCenteredClock = function( nT, nE )
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 + ' ';
13034 sPathData += 'L ' + '-' + nE + ' -' + nE + ' ';
13037 sPathData += 'L ' + '-' + nE + ' ' + nE + ' ';
13040 sPathData += 'L ' + nE + ' ' + nE + ' ';
13043 sPathData += 'L ' + nE + ' -' + nE + ' ';
13046 sPathData += 'L 0 -' + nE + ' ';
13047 sPathData += 'L 0 0 ';
13049 sPathData += 'L ' + aPoint.x + ' ' + aPoint.y;
13051 var aPath = document.createElementNS( NSS['svg'], 'path' );
13052 aPath.setAttribute( 'd', sPathData );
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 );
13065 for( i = this.nBlades - 1; i > 0; --i )
13067 aRotation = SVGIdentityMatrix.rotate( (i * 360) / this.nBlades );
13068 aPath = aBasePath.cloneNode( true );
13069 aPath.matrixTransform( aRotation );
13070 aPolyPath.appendPath( aPath );
13073 var aTransform = SVGIdentityMatrix.translate( 0.5, 0.5 ).scale( 0.5 );
13074 aPolyPath.matrixTransform( aTransform );
13079 /** Class BarnDoorWipe
13083 function BarnDoorWipePath(doubled) {
13084 this.aBasePath = createUnitSquarePath();
13085 this.doubled = doubled;
13088 BarnDoorWipePath.prototype.perform = function( nT ) {
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);
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);
13106 /** Class WaterfallWipe
13109 * Number of cells to be used
13110 * @param bFlipOnYAxis
13111 * Whether to flip on y-axis or not.
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 + ' ';
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
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);
13185 function IrisWipePath(unitRect) {
13186 this.unitRect = unitRect;
13187 this.aBasePath = createUnitSquarePath();
13194 * A parameter in [0,1] representing the diamond or rectangle.
13195 * @return SVGPathElement
13196 * A svg <path> element representing a transition.
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);
13208 * Class ZigZagWipePath
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 + ' ';
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));
13236 * Class BarnZigZagWipePath
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);
13265 /** Class CheckerBoardWipePath
13267 * @param unitsPerEdge
13268 * The number of cells (per line and column) in the checker board.
13270 function CheckerBoardWipePath( unitsPerEdge )
13272 this.unitsPerEdge = unitsPerEdge;
13273 if( this.unitsPerEdge === undefined || this.unitsPerEdge < 1 )
13274 this.unitsPerEdge = 10;
13275 this.aBasePath = createUnitSquarePath();
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.
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;
13295 for ( i = this.unitsPerEdge; i--; )
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--;)
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 );
13313 aMatrix = SVGIdentityMatrix.translate( 0.0, d ).multiply( aMatrix ); // next line
13321 /** Class RandomWipePath
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
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
13341 fEdgeLength = 1.0 / nElements;
13342 for( nPos = 0; nPos < nElements; ++nPos )
13344 this.aPositionArray[nPos] = { x: 0.0, y: pruneScaleValue( nPos * fEdgeLength ) }
13346 aTransform = SVGIdentityMatrix.scaleNonUniform( 1.0, pruneScaleValue( fEdgeLength ) );
13348 else // dissolve wipe
13350 var nSqrtElements = Math.round( Math.sqrt( nElements ) );
13351 fEdgeLength = 1.0 / nSqrtElements;
13352 for( nPos = 0; nPos < nElements; ++nPos )
13354 this.aPositionArray[nPos] = {
13355 x: pruneScaleValue( ( nPos % nSqrtElements ) * fEdgeLength ),
13356 y: pruneScaleValue( ( nPos / nSqrtElements ) * fEdgeLength ) }
13358 aTransform = SVGIdentityMatrix.scale( pruneScaleValue( fEdgeLength ) );
13360 this.aBasePath.matrixTransform( aTransform );
13364 for( nPos1 = nElements - 1; nPos1 > 0; --nPos1 )
13366 nPos2 = getRandomInt( nPos1 + 1 );
13367 tmp = this.aPositionArray[nPos1];
13368 this.aPositionArray[nPos1] = this.aPositionArray[nPos2];
13369 this.aPositionArray[nPos2] = tmp;
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.
13380 RandomWipePath.prototype.perform = function( nT )
13382 var aPolyPath = createEmptyPath();
13386 var nElements = Math.round( nT * this.nElements );
13387 if( nElements === 0 )
13391 // check if we need to reset the clip path
13392 if( this.nAlreadyAppendedElements >= nElements )
13394 this.nAlreadyAppendedElements = 0;
13395 this.aClipPath = createEmptyPath();
13398 for( nPos = this.nAlreadyAppendedElements; nPos < nElements; ++nPos )
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 );
13407 this.nAlreadyAppendedElements = nElements;
13408 this.aClipPath.appendPath( aPolyPath );
13410 return this.aClipPath.cloneNode( true );
13413 /** Class SnakeWipeSlide
13417 * @param bFlipOnYaxis
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);
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 + ' ';
13442 let poly = document.createElementNS( NSS['svg'], 'path');
13443 poly.setAttribute('d', aPath);
13444 aPolyPath.appendPath(poly);
13448 if((line_ & 1) == 1) {
13449 // odd line: => right to left
13450 offset = (1.0 - col);
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);
13465 SnakeWipePath.prototype.calcHalfDiagonalSnake = function(nT, bIn) {
13466 var res = createEmptyPath();
13469 const sqrtArea2 = Math.sqrt(nT * this.sqrtElements * this.sqrtElements);
13470 const edge = pruneScaleValue(sqrtArea2 / this.sqrtElements);
13472 var aPath, aPoint = document.documentElement.createSVGPoint();
13474 aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
13476 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13479 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13481 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13482 const poly = document.createElementNS( NSS['svg'], 'path');
13483 poly.setAttribute('d', aPath);
13484 res.appendPath(poly);
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 + ' ';
13492 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13493 aPoint.x = len + a;
13494 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13496 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13498 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13499 const poly = document.createElementNS( NSS['svg'], 'path');
13500 poly.setAttribute('d', aPath);
13503 if((Math.floor(sqrtArea2) & 1) == 1) {
13505 aTransform = SVGIdentityMatrix.rotate((Math.PI)/2 + (Math.PI)/4);
13506 aTransform.translate(edge + this.elementEdge, 0.0);
13509 aTransform = SVGIdentityMatrix.translate(-a, 0.0);
13510 aTransform.rotate(-(Math.PI/4));
13511 aTransform.translate(0.0, edge);
13514 poly.matrixTransform(aTransform);
13515 res.appendPath(poly);
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();
13524 aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
13526 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13529 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13531 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13533 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13534 const poly = document.createElementNS( NSS['svg'], 'path');
13535 poly.setAttribute('d', aPath);
13536 res.appendPath(poly);
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 + ' ';
13544 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13545 aPoint.x = len + a;
13546 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13548 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13550 aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
13551 const poly = document.createElementNS( NSS['svg'], 'path');
13552 poly.setAttribute('d', aPath);
13555 if((Math.floor(sqrtArea2) & 1) == 1) {
13557 aTransform = SVGIdentityMatrix.translate(0.0, -height);
13558 aTransform.rotate(Math.PI/2 + Math.PI/4);
13559 aTransform.translate(1.0, edge);
13562 aTransform = SVGIdentityMatrix.rotate(-(Math.PI/4));
13563 aTransform = aTransform.translate(edge, 1.0);
13565 poly.matrixTransform(aTransform);
13566 res.appendPath(poly);
13571 SnakeWipePath.prototype.perform = function(nT) {
13572 var res = createEmptyPath();
13573 if(this.diagonal) {
13575 res.appendPath(this.calcHalfDiagonalSnake(1.0, true));
13576 res.appendPath(this.calcHalfDiagonalSnake(2.0*(nT-0.5), false));
13579 res.appendPath(this.calcHalfDiagonalSnake(2.0*nT, true));
13582 res = this.calcSnake(nT);
13584 return this.flipOnYAxis ? flipOnYAxis(res) : res;
13587 /** Class ParallelSnakesWipePath
13588 * Generates a parallel snakes wipe:
13592 * @param bFlipOnYAxis
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);
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);
13634 return this.flipOnYAxis ? flipOnYAxis(res) : res;
13640 * number of elements in the spiral animation
13641 * @param bFlipOnYAxis
13642 * boolean value indicating whether to flip on y-axis or not.
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;
13670 var alen = Math.min(len, edge1);
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 );
13681 aTransform = aTransform.translate(0.5, 0.5);
13682 poly.matrixTransform(aTransform);
13683 res.appendPath(poly);
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:
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));
13725 aTransform = SVGIdentityMatrix.scale(1.0, 0.5);
13726 innerSpiral.matrixTransform(aTransform);
13727 res.appendPath(innerSpiral);
13728 res.appendPath(flipOnXAxis(innerSpiral));
13730 return this.bFlipOnYAxis ? flipOnYAxis(res) : res;
13733 /** Class VeeWipePath
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.
13759 function AnimatedSlide( aMetaSlide )
13763 log( 'AnimatedSlide constructor: meta slide is not valid' );
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;
13778 * Set the visibility property of the slide to 'inherit'
13779 * and update the master page view.
13781 AnimatedSlide.prototype.show = function()
13783 this.aMetaSlide.show();
13787 * Set the visibility property of the slide to 'hidden'.
13789 AnimatedSlide.prototype.hide = function()
13791 this.aMetaSlide.hide();
13794 /** notifyUsedAttribute
13795 * Populate the set of attribute used for the transition.
13798 * A string representing an attribute name.
13800 AnimatedSlide.prototype.notifyUsedAttribute = function( sName )
13802 if( sName == 'clip-path' )
13804 this.initClipPath();
13805 this.bIsClipped = true;
13809 this.aUsedAttributeSet.push( sName );
13814 * Remove from the handled slide element any attribute that was appended for
13815 * performing the transition.
13817 AnimatedSlide.prototype.reset = function()
13819 if( this.bIsClipped )
13821 this.cleanClipPath();
13822 this.bIsClipped = false;
13826 for( i = 0; i < this.aUsedAttributeSet.length; ++i )
13828 var sAttrName = this.aUsedAttributeSet[i];
13829 this.aSlideElement.removeAttribute( sAttrName );
13831 this.aUsedAttributeSet = [];
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
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 );
13866 * Removes the related <clipPath> element from the <defs> group,
13867 * and remove the 'clip-path' attribute from the slide element.
13870 AnimatedSlide.prototype.cleanClipPath = function()
13872 this.aSlideElement.parentNode.removeAttribute( 'clip-path' );
13874 if( this.aClipPathElement )
13876 var aClipPathGroup = theMetaDoc.aClipPathGroup;
13877 aClipPathGroup.removeChild( this.aClipPathElement );
13878 this.aClipPathElement = null;
13879 this.aClipPathContent = null;
13884 * Insert an svg element before the handled slide element.
13889 AnimatedSlide.prototype.insertBefore = function( aElement )
13893 this.aSlideElement.parentNode.insertBefore( aElement, this.aSlideElement );
13898 * Insert an svg element after the handled slide element.
13903 AnimatedSlide.prototype.appendElement = function( aElement )
13907 this.aSlideElement.parentNode.appendChild( aElement );
13912 * Remove an svg element.
13917 AnimatedSlide.prototype.removeElement = function( aElement )
13921 this.aSlideElement.parentNode.removeChild( aElement );
13930 AnimatedSlide.prototype.getWidth = function()
13938 * The slide height.
13940 AnimatedSlide.prototype.getHeight = function()
13948 * A number in the [0,1] range representing the slide opacity.
13950 AnimatedSlide.prototype.setOpacity = function( nValue )
13952 this.aSlideElement.setAttribute( 'opacity', nValue );
13956 * Translate the handled slide.
13959 * A number representing the translation that occurs in the x direction.
13961 * A number representing the translation that occurs in the y direction.
13963 AnimatedSlide.prototype.translate = function( nDx, nDy )
13965 var sTransformAttr = 'translate(' + nDx + ',' + nDy + ')';
13966 this.aSlideElement.setAttribute( 'transform', sTransformAttr );
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.
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 )
13982 var sPathData = aClipPathContent.getAttribute( 'd' );
13983 this.aClipPathContent.setAttribute( 'd', sPathData );
13989 function AnimatedElement( aElement )
13993 log( 'AnimatedElement constructor: element is not valid' );
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 ) );
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.
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 );
14070 * Removes the related <clipPath> element from the <defs> group,
14071 * and remove the 'clip-path' attribute from the animated element.
14074 AnimatedElement.prototype.cleanClipPath = function()
14076 this.aActiveElement.removeAttribute( 'clip-path' );
14078 if( this.aClipPathElement )
14080 var aClipPathGroup = theMetaDoc.aClipPathGroup;
14081 aClipPathGroup.removeChild( this.aClipPathElement );
14082 this.aClipPathElement = null;
14083 this.aClipPathContent = null;
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 )
14106 log( 'AnimatedElement(' + this.getId() + ').setToElement: element is not valid' );
14110 var aClone = aElement.cloneNode( true );
14111 this.aPreviousElement = this.aActiveElement.parentNode.replaceChild( aClone, this.aActiveElement );
14112 this.aActiveElement = aClone;
14117 AnimatedElement.prototype.notifySlideStart = function( aSlideShowContext )
14119 if( !aSlideShowContext )
14121 log( 'AnimatedElement.notifySlideStart: slideshow context is not valid' );
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()
14138 AnimatedElement.prototype.notifyAnimationStart = function()
14143 AnimatedElement.prototype.notifyAnimationEnd = function()
14148 AnimatedElement.prototype.notifyNextEffectStart = function( /*nEffectIndex*/ )
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.
14160 AnimatedElement.prototype.saveState = function( nAnimationNodeId )
14162 ANIMDBG.print( 'AnimatedElement(' + this.getId() + ').saveState(' + nAnimationNodeId +')' );
14163 if( !this.aStateSet[ nAnimationNodeId ] )
14165 this.aStateSet[ nAnimationNodeId ] = {};
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;
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.
14185 * True if the restoring operation is successful, false otherwise.
14187 AnimatedElement.prototype.restoreState = function( nAnimationNodeId )
14189 if( !this.aStateSet[ nAnimationNodeId ] )
14191 log( 'AnimatedElement(' + this.getId() + ').restoreState: state '
14192 +nAnimationNodeId + ' is not valid' );
14196 ANIMDBG.print( 'AnimatedElement(' + this.getId() + ').restoreState(' + nAnimationNodeId +')' );
14197 var aState = this.aStateSet[ nAnimationNodeId ];
14198 var bRet = this.setToElement( aState.aElement );
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;
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;
14239 for( i = 0; i < aChildrenSet.length; ++i )
14241 if( ! aChildrenSet[i].getAttribute )
14244 sStroke = aChildrenSet[i].getAttribute( 'stroke' );
14245 if( sStroke && sStroke != 'none' )
14247 sStrokeWidth = aChildrenSet[i].getAttribute( 'stroke-width' );
14248 var nSW = parseFloat( sStrokeWidth );
14249 if( nSW > nStrokeWidth )
14250 nStrokeWidth = nSW;
14254 if( nStrokeWidth == 0 )
14256 sStrokeWidth = ROOT_NODE.getAttribute( 'stroke-width' );
14257 nStrokeWidth = parseFloat( sStrokeWidth );
14259 if( nStrokeWidth != 0 )
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;
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.
14285 AnimatedElement.prototype.setClipPath = function( aClipPathContent )
14287 if( this.aClipPathContent )
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 );
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 )
14354 log('AnimatedElement(' + this.getId() + ').setWidth: negative height!');
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 )
14379 log('AnimatedElement(' + this.getId() + ').setWidth: negative height!');
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!
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
14449 AnimatedElement.prototype.setStrokeStyle = function( sValue )
14451 ANIMDBG.print( 'AnimatedElement.setStrokeStyle(' + sValue + ')' );
14454 AnimatedElement.prototype.getFillStyle = function()
14456 // TODO: getFillStyle: implement it
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 )
14471 sFillColorValue = aChildSet[i].getAttribute( 'fill' );
14472 if( sFillColorValue && ( sFillColorValue !== 'none' ) )
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 )
14490 sFillColorValue = aChildSet[i].getAttribute( 'fill' );
14491 if( sFillColorValue && ( sFillColorValue !== 'none' ) )
14493 aChildSet[i].setAttribute( 'fill', sValue );
14498 AnimatedElement.prototype.getStrokeColor = function()
14500 var aChildSet = getElementChildren( this.aActiveElement );
14501 var sStrokeColorValue = '';
14502 for( var i = 0; i < aChildSet.length; ++i )
14504 sStrokeColorValue = aChildSet[i].getAttribute( 'stroke' );
14505 if( sStrokeColorValue && ( sStrokeColorValue !== 'none' ) )
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 )
14523 sStrokeColorValue = aChildSet[i].getAttribute( 'stroke' );
14524 if( sStrokeColorValue && ( sStrokeColorValue !== 'none' ) )
14526 aChildSet[i].setAttribute( 'stroke', sValue );
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 )
14556 log( 'AnimatedTextElement: passed element is not a paragraph.' );
14559 var aTextShapeElement = aElement.parentNode;
14560 sTextType = aTextShapeElement.getAttribute( 'class' );
14561 if( sTextType !== 'TextShape' )
14563 log( 'AnimatedTextElement: element parent is not a text shape.' );
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 )
14572 aAnimatedElementGroup = theDocument.createElementNS( NSS['svg'], 'g' );
14573 aAnimatedElementGroup.setAttribute( 'class', 'AnimatedElements' );
14574 aTextShapeGroup.appendChild( aAnimatedElementGroup );
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 )
14596 var aBulletCharGroupElem = getElementByClassName( aTextShapeGroup, 'BulletChars' );
14597 if( aBulletCharGroupElem )
14599 var aBulletPlaceholderElem = getElementByClassName( aElement, 'BulletPlaceholder' );
14600 if( aBulletPlaceholderElem )
14602 var sId = aBulletPlaceholderElem.getAttribute( 'id' );
14603 sId = 'bullet-char(' + sId + ')';
14604 aBulletCharElem = theDocument.getElementById( sId );
14605 if( aBulletCharElem )
14607 aBulletCharClone = aBulletCharElem.cloneNode( true );
14611 log( 'AnimatedTextElement: ' + sId + ' not found.' );
14616 log( 'AnimatedTextElement: no bullet placeholder found' );
14621 log( 'AnimatedTextElement: no bullet char group found' );
14625 // In case there are embedded bitmaps we need to clone them
14626 var aBitmapElemSet = [];
14627 var aBitmapCloneSet = [];
14628 var aBitmapPlaceholderSet = getElementsByClassName( aElement, 'BitmapPlaceholder' );
14630 if( aBitmapPlaceholderSet )
14632 for( i = 0; i < aBitmapPlaceholderSet.length; ++i )
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] )
14640 aBitmapCloneSet[i] = aBitmapElemSet[i].cloneNode( true );
14644 log( 'AnimatedTextElement: ' + sId + ' not found.' );
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 )
14658 if( aBitmapCloneSet[i] )
14659 aBitmapCloneSet[i].removeAttribute( 'id' );
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 )
14673 if( aBitmapElemSet[i] )
14674 aBitmapElemSet[i].setAttribute( 'visibility', 'hidden' );
14677 // Append each element to its parent.
14678 // <g class='AnimatedElements'>
14681 // <tspan class='TextParagraph'> ... </tspan>
14683 // <g class='GraphicGroup'>
14684 // [<g class='BulletChar'>...</g>]
14685 // [<g class='EmbeddedBitmap'>...</g>]
14688 // [<g class='EmbeddedBitmap'>...</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 )
14700 if( aBitmapCloneSet[i] )
14701 this.aGraphicGroupElement.appendChild( aBitmapCloneSet[i] );
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' );
14715 for( i = 0; i < aHyperlinkElementSet.length; ++i )
14717 sHyperlinkId = aHyperlinkElementSet[i].getAttribute( 'id' );
14719 this.aHyperlinkIdSet.push( sHyperlinkId );
14721 log( 'error: AnimatedTextElement constructor: hyperlink element has no id' );
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 );
14735 this.aGraphicGroupElement = getElementByClassName( this.aActiveElement, 'GraphicGroup' );
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 )
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' );
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' );
14778 for( ; i < aHyperlinkIdSet.length; ++i )
14780 aEventMultiplexer.notifyElementChangedEvent( aHyperlinkIdSet[i], aHyperlinkElementSet[i] );
14783 this.aActiveElement.setAttribute( 'visibility', 'hidden' );
14786 AnimatedTextElement.prototype.notifyAnimationStart = function()
14788 DBGLOG( 'AnimatedTextElement.notifyAnimationStart' );
14789 if( this.nRunningAnimations === 0 )
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' );
14798 ++this.nRunningAnimations;
14801 AnimatedTextElement.prototype.notifyAnimationEnd = function()
14803 DBGLOG( 'AnimatedTextElement.notifyAnimationEnd' );
14804 --this.nRunningAnimations;
14805 if( this.nRunningAnimations === 0 )
14807 this.restoreBaseTextParagraph();
14811 AnimatedTextElement.prototype.saveState = function( nAnimationNodeId )
14813 if( this.nRunningAnimations === 0 )
14815 var sVisibilityAttr = this.aParagraphElement.getAttribute( 'visibility' );
14816 this.aActiveElement.setAttribute( 'visibility', sVisibilityAttr );
14817 this.aGraphicGroupElement.setAttribute( 'visibility', 'inherit' );
14819 AnimatedTextElement.superclass.saveState.call( this, nAnimationNodeId );
14822 AnimatedTextElement.prototype.restoreState = function( nAnimationNodeId )
14824 var bRet = AnimatedTextElement.superclass.restoreState.call( this, nAnimationNodeId );
14826 this.restoreBaseTextParagraph();
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.
14840 * A string representing a slide id.
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 )
14856 if( aAnimationsRootElement.firstElementChild &&
14857 ( aAnimationsRootElement.firstElementChild.getAttributeNS( NSS['smil'], 'begin' ) === (this.sSlideId + '.begin') ) )
14859 var aTransitionFilterElement = aAnimationsRootElement.firstElementChild.firstElementChild;
14860 if( aTransitionFilterElement && ( aTransitionFilterElement.localName === 'transitionFilter' ) )
14862 this.aElement = aTransitionFilterElement;
14863 this.parseElement();
14865 aAnimationsRootElement.removeChild( aAnimationsRootElement.firstElementChild );
14870 SlideTransition.prototype.createSlideTransition = function( aLeavingSlide, aEnteringSlide )
14872 if( !this.isValid() )
14874 if( this.eTransitionType == 0 )
14877 if( !aEnteringSlide )
14879 log( 'SlideTransition.createSlideTransition: invalid entering slide.' );
14883 var aTransitionInfo = aTransitionInfoTable[this.eTransitionType][this.eTransitionSubType];
14884 var eTransitionClass = aTransitionInfo['class'];
14886 switch( eTransitionClass )
14889 case TRANSITION_INVALID:
14890 log( 'SlideTransition.createSlideTransition: transition class: TRANSITION_INVALID' );
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 )
14903 log( 'SlideTransition.createSlideTransition: ' +
14904 'transition class: TRANSITION_SPECIAL, ' +
14905 'unknown transition type: ' + this.eTransitionType );
14908 case PUSHWIPE_TRANSITION:
14910 var aDirection = null;
14911 switch( this.eTransitionSubType )
14914 log( 'SlideTransition.createSlideTransition: ' +
14915 'transition type: PUSHWIPE_TRANSITION, ' +
14916 'unknown transition subtype: ' + this.eTransitionSubType );
14918 case FROMTOP_TRANS_SUBTYPE:
14919 aDirection = { x: 0.0, y: 1.0 };
14921 case FROMBOTTOM_TRANS_SUBTYPE:
14922 aDirection = { x: 0.0, y: -1.0 };
14924 case FROMLEFT_TRANS_SUBTYPE:
14925 aDirection = { x: 1.0, y: 0.0 };
14927 case FROMRIGHT_TRANS_SUBTYPE:
14928 aDirection = { x: -1.0, y: 0.0 };
14931 return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aDirection, aDirection );
14934 case SLIDEWIPE_TRANSITION:
14936 var aInDirection = null;
14937 switch( this.eTransitionSubType )
14940 log( 'SlideTransition.createSlideTransition: ' +
14941 'transition type: SLIDEWIPE_TRANSITION, ' +
14942 'unknown transition subtype: ' + this.eTransitionSubType );
14944 case FROMTOP_TRANS_SUBTYPE:
14945 aInDirection = { x: 0.0, y: 1.0 };
14947 case FROMBOTTOM_TRANS_SUBTYPE:
14948 aInDirection = { x: 0.0, y: -1.0 };
14950 case FROMLEFT_TRANS_SUBTYPE:
14951 aInDirection = { x: 1.0, y: 0.0 };
14953 case FROMRIGHT_TRANS_SUBTYPE:
14954 aInDirection = { x: -1.0, y: 0.0 };
14957 var aNoDirection = { x: 0.0, y: 0.0 };
14958 if( !this.bReverseDirection )
14960 return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aNoDirection, aInDirection );
14964 return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aInDirection, aNoDirection );
14968 case FADE_TRANSITION:
14969 switch( this.eTransitionSubType )
14972 log( 'SlideTransition.createSlideTransition: ' +
14973 'transition type: FADE_TRANSITION, ' +
14974 'unknown transition subtype: ' + this.eTransitionSubType );
14976 case CROSSFADE_TRANS_SUBTYPE:
14977 return new FadingSlideChange( aLeavingSlide, aEnteringSlide );
14978 case FADEOVERCOLOR_TRANS_SUBTYPE:
14979 return new FadingOverColorSlideChange( aLeavingSlide, aEnteringSlide, this.getFadeColor() );
14985 SlideTransition.prototype.parseElement = function()
14987 this.bIsValid = true;
14988 var aAnimElem = this.aElement;
14991 this.eTransitionType = undefined;
14992 var sTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'type' );
14993 if( sTypeAttr && aTransitionTypeInMap[ sTypeAttr ] )
14995 this.eTransitionType = aTransitionTypeInMap[ sTypeAttr ];
14999 this.bIsValid = false;
15000 log( 'SlideTransition.parseElement: transition type not valid: ' + sTypeAttr );
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 ) )
15010 this.eTransitionSubType = aTransitionSubtypeInMap[ sSubTypeAttr ];
15014 this.bIsValid = false;
15015 log( 'SlideTransition.parseElement: transition subtype not valid: ' + sSubTypeAttr );
15018 if( this.bIsValid && aTransitionInfoTable[this.eTransitionType][this.eTransitionSubType] === undefined )
15020 this.bIsValid = false;
15021 log( 'SlideTransition.parseElement: transition not valid: type: ' + sTypeAttr + ' subtype: ' + sSubTypeAttr );
15024 // direction attribute
15025 this.bReverseDirection = false;
15026 var sDirectionAttr = aAnimElem.getAttributeNS( NSS['smil'], 'direction' );
15027 if( sDirectionAttr == 'reverse' )
15028 this.bReverseDirection = true;
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 ) )
15037 var sColorAttr = aAnimElem.getAttributeNS( NSS['smil'], 'fadeColor' );
15039 this.sFadeColor = sColorAttr;
15041 this.sFadeColor='#000000';
15046 this.aDuration = null;
15047 var sDurAttr = aAnimElem.getAttributeNS( NSS['smil'], 'dur' );
15048 this.aDuration = new Duration( sDurAttr );
15049 if( !this.aDuration.isSet() )
15051 this.aDuration = new Duration( null ); // duration == 0.0
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 + '>: ';
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';
15120 sInfo += '; mode: ' + aTransitionModeOutMap[ this.getTransitionMode() ];
15123 if( this.getDuration() )
15124 sInfo += '; duration: ' + this.getDuration().info();
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 )
15162 this.aRootNode = createAnimationTree( aAnimationRootElement, this.aContext );
15164 return ( this.aRootNode ? true : false );
15167 SlideAnimations.prototype.parseElements = function()
15169 if( !this.aRootNode )
15173 if( !this.aRootNode.parseElement() )
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 )
15194 return this.aRootNode.hasPendingAnimation();
15197 SlideAnimations.prototype.start = function()
15199 if( !this.bElementsParsed )
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;
15215 if( !this.aRootNode.init() )
15218 // resolve root node
15219 return this.aRootNode.resolve();
15222 SlideAnimations.prototype.end = function( bLeftEffectsSkipped )
15224 if( !this.bElementsParsed )
15225 return; // no animations there
15228 this.aRootNode.deactivate();
15229 this.aRootNode.end();
15231 if( bLeftEffectsSkipped && this.isFirstRun() )
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;
15238 else if( this.isFirstRun() )
15240 this.aContext.bFirstRun = false;
15245 SlideAnimations.prototype.dispose = function()
15247 if( this.aRootNode )
15249 this.aRootNode.dispose();
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 )
15264 this.aSourceEventElementMap[id].charge();
15268 SlideAnimations.prototype.chargeInterAnimEvents = function()
15270 for( var id in this.aInteractiveAnimationSequenceMap )
15272 this.aInteractiveAnimationSequenceMap[id].chargeEvents();
15276 /**********************************************************************************************
15277 * Event classes and helper functions
15278 **********************************************************************************************/
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()
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;
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 )
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.
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 )
15436 aSlideShowContext.aTimerEventQueue.addEvent( aEvent );
15438 else if ( aNodeContext.bFirstRun )
15440 var aEventMultiplexer = aSlideShowContext.aEventMultiplexer;
15441 if( !aEventMultiplexer )
15443 log( 'registerEvent: event multiplexer not initialized' );
15446 var aNextEffectEventArray = aSlideShowContext.aNextEffectEventArray;
15447 if( !aNextEffectEventArray )
15449 log( 'registerEvent: next effect event array not initialized' );
15452 var aInteractiveAnimationSequenceMap =
15453 aSlideShowContext.aInteractiveAnimationSequenceMap;
15454 if( !aInteractiveAnimationSequenceMap )
15456 log( 'registerEvent: interactive animation sequence map not initialized' );
15460 switch( eTimingType )
15463 var eEventType = aTiming.getEventType();
15464 var sEventBaseElemId = aTiming.getEventBaseElementId();
15465 if( sEventBaseElemId )
15467 var aEventBaseElem = document.getElementById( sEventBaseElemId );
15468 if( !aEventBaseElem )
15470 log( 'generateEvent: EVENT_TIMING: event base element not found: ' + sEventBaseElemId );
15473 var aSourceEventElement = aNodeContext.makeSourceEventElement( sEventBaseElemId, aEventBaseElem );
15475 if( !aInteractiveAnimationSequenceMap[ nNodeId ] )
15477 aInteractiveAnimationSequenceMap[ nNodeId ] = new InteractiveAnimationSequence(nNodeId);
15480 var bEventRegistered = false;
15481 switch( eEventType )
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;
15490 log( 'generateEvent: not handled event type: ' + eEventType );
15492 if( bEventRegistered )
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(
15500 bind2( InteractiveAnimationSequence.prototype.chargeEvents,
15501 aInteractiveAnimationSequenceMap[ nNodeId ] )
15505 else // no base event element present
15507 switch( eEventType )
15509 case EVENT_TRIGGER_ON_NEXT_EFFECT:
15510 aNextEffectEventArray.appendEvent( aEvent );
15513 log( 'generateEvent: not handled event type: ' + eEventType );
15517 case SYNCBASE_TIMING:
15518 eEventType = aTiming.getEventType();
15519 sEventBaseElemId = aTiming.getEventBaseElementId();
15520 if( sEventBaseElemId )
15522 var aAnimationNode = aNodeContext.aAnimationNodeMap[ sEventBaseElemId ];
15523 if( !aAnimationNode )
15525 log( 'generateEvent: SYNCBASE_TIMING: event base element not found: ' + sEventBaseElemId );
15528 aEventMultiplexer.registerEvent( eEventType, aAnimationNode.getId(), aEvent );
15532 log( 'generateEvent: SYNCBASE_TIMING: event base element not specified' );
15536 log( 'generateEvent: not handled timing type: ' + eTimingType );
15541 registerEvent.DEBUG = aRegisterEventDebugPrinter.isEnabled();
15543 registerEvent.DBG = function( aTiming, nTime )
15545 if( registerEvent.DEBUG )
15547 aRegisterEventDebugPrinter.print( 'registerEvent( timing: ' + aTiming.info() + ' )', nTime );
15554 function SourceEventElement( sId, aElement, aEventMultiplexer )
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()
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 )
15598 this.aEventMultiplexer.notifyEvent( EVENT_TRIGGER_ON_CLICK, this.getId() );
15599 aSlideShow.update();
15600 this.bClickHandled = true;
15601 this.setDefaultCursor();
15605 SourceEventElement.prototype.setPointerCursor = function()
15607 if( this.bClickHandled )
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 );
15625 log( 'error: HyperlinkElement: no element with id: <' + sId + '> found' );
15628 if( !aEventMultiplexer )
15630 log( 'AnimatedElement constructor: event multiplexer is not valid' );
15634 this.aElement = aElement;
15635 this.aEventMultiplexer = aEventMultiplexer;
15636 this.nTargetSlideIndex = undefined;
15638 this.sURL = getNSAttribute( 'xlink', this.aElement, 'href' );
15641 if( this.sURL[0] === '#' )
15643 if( this.sURL.substr(1, 5) === 'Slide' )
15645 var sSlideIndex = this.sURL.split( ' ' )[1];
15646 this.nTargetSlideIndex = parseInt( sSlideIndex ) - 1;
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 );
15661 log( 'warning: HyperlinkElement(' + this.sId + '): url is empty' );
15665 HyperlinkElement.prototype.onElementChanged = function( aElement )
15669 log( 'error: HyperlinkElement: passed element is not valid' );
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 );
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 )
15701 aSlideShow.displaySlide( this.nTargetSlideIndex, true );
15705 var aWindowObject = document.defaultView;
15706 if( aWindowObject )
15708 aWindowObject.open( this.sURL, this.sId );
15712 log( 'error: HyperlinkElement.handleClick: invalid window object.' );
15719 HyperlinkElement.prototype.setPointerCursor = function()
15721 if( this.bClickHandled )
15724 this.aElement.setAttribute( 'style', 'cursor: pointer' );
15727 HyperlinkElement.prototype.setDefaultCursor = function()
15729 this.aElement.setAttribute( 'style', 'cursor: default' );
15734 function InteractiveAnimationSequence( nId )
15737 this.bIsRunning = false;
15738 this.aStartEvent = null;
15739 this.aEndEvent = null;
15742 InteractiveAnimationSequence.prototype.getId = function()
15747 InteractiveAnimationSequence.prototype.getStartEvent = function()
15749 if( !this.aStartEvent )
15752 makeEvent( bind2( InteractiveAnimationSequence.prototype.start, this ) );
15754 return this.aStartEvent;
15757 InteractiveAnimationSequence.prototype.getEndEvent = function()
15759 if( !this.aEndEvent )
15762 makeEvent( bind2( InteractiveAnimationSequence.prototype.end, this ) );
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.
15796 * The object to be prioritized.
15798 * An integral number representing the object priority.
15801 function PriorityEntry( aValue, nPriority )
15803 this.aValue = aValue;
15804 this.nPriority = nPriority;
15807 /** EventEntry.compare
15808 * Compare priority of two entries.
15811 * An instance of type PriorityEntry.
15813 * An instance of type PriorityEntry.
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
15819 PriorityEntry.compare = function( aLhsEntry, aRhsEntry )
15821 if ( aLhsEntry.nPriority < aRhsEntry.nPriority )
15825 else if (aLhsEntry.nPriority > aRhsEntry.nPriority)
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()
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() )
15885 var aHandlerEntry = aMouseClickHandlerSet.top();
15886 aMouseClickHandlerSet.pop();
15887 if( aHandlerEntry.aValue.handleClick( aMouseEvent ) )
15892 EventMultiplexer.prototype.registerEvent = function( eEventType, aNotifierId, aEvent )
15894 this.DBG( 'registerEvent', eEventType, aNotifierId );
15895 if( !this.aEventMap[ eEventType ] )
15897 this.aEventMap[ eEventType ] = {};
15899 if( !this.aEventMap[ eEventType ][ aNotifierId ] )
15901 this.aEventMap[ eEventType ][ aNotifierId ] = [];
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 ] )
15912 if( this.aEventMap[ eEventType ][ aNotifierId ] )
15914 var aEventArray = this.aEventMap[ eEventType ][ aNotifierId ];
15915 var nSize = aEventArray.length;
15916 for( var i = 0; i < nSize; ++i )
15918 this.aTimerEventQueue.addEvent( aEventArray[i] );
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 )
15945 (this.aSkipEffectEndHandlerSet[i])();
15947 this.aSkipEffectEndHandlerSet = [];
15950 EventMultiplexer.prototype.registerSkipEffectEvent = function( aEvent )
15952 this.aSkipEffectEvent = aEvent;
15955 EventMultiplexer.prototype.notifySkipEffectEvent = function()
15957 if( this.aSkipEffectEvent )
15959 this.aTimerEventQueue.addEvent( this.aSkipEffectEvent );
15960 this.aSkipEffectEvent = null;
15964 EventMultiplexer.prototype.registerRewindCurrentEffectEvent = function( aEvent )
15966 this.aRewindCurrentEffectEvent = aEvent;
15969 EventMultiplexer.prototype.notifyRewindCurrentEffectEvent = function()
15971 if( this.aRewindCurrentEffectEvent )
15973 this.aTimerEventQueue.addEvent( this.aRewindCurrentEffectEvent );
15974 this.aRewindCurrentEffectEvent = null;
15978 EventMultiplexer.prototype.registerRewindLastEffectEvent = function( aEvent )
15980 this.aRewindLastEffectEvent = aEvent;
15983 EventMultiplexer.prototype.notifyRewindLastEffectEvent = function()
15985 if( this.aRewindLastEffectEvent )
15987 this.aTimerEventQueue.addEvent( this.aRewindLastEffectEvent );
15988 this.aRewindLastEffectEvent = null;
15992 EventMultiplexer.prototype.registerSkipInteractiveEffectEvent = function( nNotifierId, aEvent )
15994 this.aSkipInteractiveEffectEventSet[ nNotifierId ] = aEvent;
15997 EventMultiplexer.prototype.notifySkipInteractiveEffectEvent = function( nNotifierId )
15999 if( this.aSkipInteractiveEffectEventSet[ nNotifierId ] )
16001 this.aTimerEventQueue.addEvent( this.aSkipInteractiveEffectEventSet[ nNotifierId ] );
16005 EventMultiplexer.prototype.registerRewindRunningInteractiveEffectEvent = function( nNotifierId, aEvent )
16007 this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] = aEvent;
16010 EventMultiplexer.prototype.notifyRewindRunningInteractiveEffectEvent = function( nNotifierId )
16012 if( this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] )
16014 this.aTimerEventQueue.addEvent( this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] );
16018 EventMultiplexer.prototype.registerRewindEndedInteractiveEffectEvent = function( nNotifierId, aEvent )
16020 this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] = aEvent;
16023 EventMultiplexer.prototype.notifyRewindEndedInteractiveEffectEvent = function( nNotifierId )
16025 if( this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] )
16027 this.aTimerEventQueue.addEvent( this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] );
16031 EventMultiplexer.prototype.registerRewindedEffectHandler = function( aNotifierId, aHandler )
16033 this.aRewindedEffectHandlerSet[ aNotifierId ] = aHandler;
16036 EventMultiplexer.prototype.notifyRewindedEffectEvent = function( aNotifierId )
16038 if( this.aRewindedEffectHandlerSet[ aNotifierId ] )
16040 (this.aRewindedEffectHandlerSet[ aNotifierId ])();
16044 EventMultiplexer.prototype.registerElementChangedHandler = function( aNotifierId, aHandler )
16046 this.aElementChangedHandlerSet[ aNotifierId ] = aHandler;
16049 EventMultiplexer.prototype.notifyElementChangedEvent = function( aNotifierId, aElement )
16051 if( this.aElementChangedHandlerSet[ aNotifierId ] )
16053 (this.aElementChangedHandlerSet[ aNotifierId ])( aElement );
16057 EventMultiplexer.DEBUG = aEventMultiplexerDebugPrinter.isEnabled();
16059 EventMultiplexer.prototype.DBG = function( sMethodName, eEventType, aNotifierId, nTime )
16061 if( EventMultiplexer.DEBUG )
16063 var sInfo = 'EventMultiplexer.' + sMethodName;
16064 sInfo += '( type: ' + aEventTriggerOutMap[ eEventType ];
16065 sInfo += ', notifier: ' + aNotifierId + ' )';
16066 aEventMultiplexerDebugPrinter.print( sInfo, nTime );
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 ] )
16084 return aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ];
16086 else if( bHasSubtype && aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ][ eValueSubtype ] )
16088 return aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ][ eValueSubtype ];
16092 log( 'aInterpolatorHandler.getInterpolator: not found any valid interpolator for calc mode '
16093 + aCalcModeOutMap[eCalcMode] + ' and value type ' + aValueTypeOutMap[eValueType] );
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 )
16108 return ( ( 1.0 - nT )* nFrom + nT * nTo );
16111 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ COLOR_PROPERTY ] = [];
16113 aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ COLOR_PROPERTY ][ COLOR_SPACE_RGB ] =
16114 function ( nFrom, nTo, nT )
16116 return RGBColor.interpolate( nFrom, nTo, nT );
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 ] =
16124 return function ( nFrom, nTo, nT )
16126 return HSLColor.interpolate( nFrom, nTo, nT, bCCW );
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 )
16157 if( aValueList[i-1] > aValueList[i] )
16158 log( 'KeyStopLerp.validateInput: time vector is not sorted in ascending order!' );
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 ] )
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 ];
16182 while( ( this.nKeyStopDistance <= 0 ) && ( this.nLastIndex < this.nUpperBoundIndex ) );
16185 var nRawLerp = ( nAlpha - this.aKeyStopList[ this.nLastIndex ] ) / this.nKeyStopDistance;
16187 nRawLerp = clamp( nRawLerp, 0.0, 1.0 );
16190 aResult.nIndex = this.nLastIndex;
16191 aResult.nLerp = nRawLerp;
16196 KeyStopLerp.prototype.lerp_ported = function( nAlpha )
16198 if( ( this.aKeyStopList[ this.nLastIndex ] < nAlpha ) ||
16199 ( this.aKeyStopList[ this.nLastIndex + 1 ] >= nAlpha ) )
16202 for( ; i < this.aKeyStopList.length; ++i )
16204 if( this.aKeyStopList[i] >= nAlpha )
16207 if( this.aKeyStopList[i] > nAlpha )
16209 var nIndex = i - 1;
16210 this.nLastIndex = clamp( nIndex, 0, this.aKeyStopList.length - 2 );
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 );
16219 aResult.nIndex = this.nLastIndex;
16220 aResult.nLerp = nRawLerp;
16227 /**********************************************************************************************
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 )
16246 aOperatorSetMap[ NUMBER_PROPERTY ].scale = function( k, v )
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 )
16266 aOperatorSetMap[ COLOR_PROPERTY ].scale = function( k, v )
16274 aOperatorSetMap[ ENUM_PROPERTY ] = {};
16276 aOperatorSetMap[ ENUM_PROPERTY ].equal = function( a, b )
16278 return ( a === b );
16281 aOperatorSetMap[ ENUM_PROPERTY ].add = function( a )
16286 aOperatorSetMap[ ENUM_PROPERTY ].scale = function( k, v )
16291 // string operators
16292 aOperatorSetMap[ STRING_PROPERTY ] = aOperatorSetMap[ ENUM_PROPERTY ];
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()
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()
16378 SetActivity.prototype.perform = function()
16380 if( !this.isActive() )
16383 // we're going inactive immediately:
16384 this.bIsActive = false;
16386 if( this.aAnimation && this.aTargetElement )
16388 this.aAnimation.start( this.aTargetElement );
16389 this.aAnimation.perform( this.aToAttr );
16390 this.aAnimation.end();
16393 if( this.aEndEvent )
16394 this.aTimerEventQueue.addEvent( this.aEndEvent );
16398 SetActivity.prototype.isActive = function()
16400 return this.bIsActive;
16403 SetActivity.prototype.dequeued = function()
16408 SetActivity.prototype.end = function()
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()
16451 this.bIsActive = false;
16454 if( this.aEndEvent )
16455 this.aEndEvent.dispose();
16457 this.aEndEvent = null;
16460 ActivityBase.prototype.perform = function()
16463 if( !this.isActive() )
16464 return false; // no, early exit.
16466 assert( !this.bFirstPerformCall, 'ActivityBase.perform: assertion (!this.FirstPerformCall) failed' );
16471 ActivityBase.prototype.calcTimeLag = function()
16473 // TODO(Q1): implement different init process!
16474 if( this.isActive() && this.bFirstPerformCall )
16476 this.bFirstPerformCall = false;
16478 // notify derived classes that we're
16480 this.startAnimation();
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 ) )
16545 var nC = 1.0 - 0.5*this.nAccelerationFraction - 0.5*this.nDecelerationFraction;
16547 // this variable accumulates the new time value
16550 if( nT < this.nAccelerationFraction )
16552 nTPrime += 0.5 * nT * nT / this.nAccelerationFraction; // partial first interval
16556 nTPrime += 0.5 * this.nAccelerationFraction; // full first interval
16558 if( nT <= ( 1.0 - this.nDecelerationFraction ) )
16560 nTPrime += nT - this.nAccelerationFraction; // partial second interval
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;
16572 // normalize, and assign to work variable
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() )
16609 // assure animation is started:
16610 if( this.bFirstPerformCall )
16612 this.bFirstPerformCall = false;
16613 // notify derived classes that we're starting now
16614 this.startAnimation();
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() )
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;
16678 return nCurrCalls % nVectorSize;
16682 DiscreteActivityBase.prototype.calcRepeatCount = function( nCurrCalls, nVectorSize )
16684 if( this.isAutoReverse() )
16686 return Math.floor( nCurrCalls / (2*nVectorSize) ); // we've got 2 cycles per repeat
16690 return Math.floor( nCurrCalls / nVectorSize );
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() )
16723 // schedule next frame, if either repeat is indefinite
16724 // (repeat forever), or we've not yet reached the requested
16726 if( !this.isRepeatCountValid() || nCurrRepeat < this.getRepeatCount() )
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
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 );
16747 // release event reference (relation to wake up event is circular!)
16748 this.aWakeupEvent = null;
16750 // done with this activity
16751 this.endActivity();
16754 return false; // remove from queue, will be added back by the wakeup event.
16757 DiscreteActivityBase.prototype.dispose = function()
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() )
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
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:
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.
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
16834 if( nFractionElapsedTime < nFractionRequiredCalls )
16840 // lag global time, so all other animations lag, too:
16841 return ( ( nFractionElapsedTime - nFractionRequiredCalls ) * this.nMinSimpleDuration );
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() )
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 )
16872 // Ok done for now. Will not exit right here,
16873 // to give animation the chance to render the last
16875 bActivityEnding = true;
16877 // clamp animation to max permissible value
16878 nT = nEffectiveRepeat;
16883 // need to do auto-reverse?
16886 var nRelativeSimpleTime;
16887 // TODO(Q3): Refactor this mess
16888 if( this.isAutoReverse() )
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.
16899 // we're in an odd range, reverse sweep
16900 nRelativeSimpleTime = 1.0 - nFractionalActiveDuration;
16904 // we're in an even range, pass on as is
16905 nRelativeSimpleTime = nFractionalActiveDuration;
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)
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() ) )
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.
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;
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;
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 )
17061 ANIMDBG.print( 'SimpleActivity.startAnimation invoked' );
17062 SimpleActivity.superclass.startAnimation.call( this );
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 )
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 )
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;
17128 extend( FromToByActivity, BaseType );
17130 FromToByActivity.prototype.initAnimatedElement = function()
17132 if( this.aAnimation && this.aFrom )
17134 var aValue = this.aFormula ? this.aFormula( this.aFrom ) : this.aFrom;
17135 this.aAnimation.perform(aValue);
17139 FromToByActivity.prototype.startAnimation = function()
17141 if( this.isDisposed() || !this.aAnimation )
17143 log( 'FromToByActivity.startAnimation: activity disposed or not valid animation' );
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
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
17166 // From-To animation
17167 this.aStartValue = this.aFrom;
17168 this.aEndValue = this.aTo;
17170 else if( this.aBy )
17172 // From-By animation
17173 this.aStartValue = this.aFrom;
17175 this.aEndValue = this.add( this.aStartValue, this.aBy );
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
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;
17198 else if( this.aBy )
17201 this.aStartValue = aAnimationStartValue;
17203 this.aEndValue = this.add( this.aStartValue, this.aBy );
17207 ANIMDBG.print( 'FromToByActivity.startAnimation: aStartValue = ' + this.aStartValue + ', aEndValue = ' + this.aEndValue );
17210 FromToByActivity.prototype.endAnimation = function()
17212 if( this.aAnimation )
17213 this.aAnimation.end();
17216 // perform hook override for ContinuousActivityBase
17217 FromToByActivity.prototype.performContinuousHook = function( nModifiedTime, nRepeatCount )
17219 if( this.isDisposed() || !this.aAnimation )
17221 log( 'FromToByActivity.performContinuousHook: activity disposed or not valid animation' );
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.
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 )
17248 if( this.nIteration != nRepeatCount )
17250 this.nIteration = nRepeatCount;
17251 this.aStartInterpolationValue = this.aStartValue;
17255 var aActualValue = this.aAnimation.getUnderlyingValue();
17256 if( !this.equal( aActualValue, this.aPreviousValue ) )
17257 this.aStartInterpolationValue = aActualValue;
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 )
17269 // aValue = this.aEndValue * nRepeatCount + aValue;
17270 aValue = this.add( this.scale( nRepeatCount, this.aEndValue ), aValue );
17273 aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17274 this.aAnimation.perform( aValue );
17276 if( this.bDynamicStartValue )
17278 this.aPreviousValue = this.aAnimation.getUnderlyingValue();
17283 // perform hook override for DiscreteActivityBase
17284 FromToByActivity.prototype.performDiscreteHook = function( /*nFrame, nRepeatCount*/ )
17286 if (this.isDisposed() || !this.aAnimation) {
17287 log('FromToByActivity.performDiscreteHook: activity disposed or not valid animation');
17292 FromToByActivity.prototype.performEnd = function()
17294 if( this.aAnimation )
17296 var aValue = this.isAutoReverse() ? this.aStartValue : this.aEndValue;
17297 aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17298 this.aAnimation.perform( aValue );
17302 FromToByActivity.prototype.dispose = function()
17304 FromToByActivity.superclass.dispose.call( this );
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 )
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;
17344 extend( ValueListActivity, BaseType );
17346 ValueListActivity.prototype.activate = function( aEndEvent )
17348 ValueListActivity.superclass.activate.call( this, aEndEvent );
17349 for( var i = 0; i < this.aValueList.length; ++i )
17351 ANIMDBG.print( 'createValueListActivity: value[' + i + '] = ' + this.aValueList[i] );
17355 ValueListActivity.prototype.initAnimatedElement = function()
17357 if( this.aAnimation )
17359 var aValue = this.aValueList[0];
17360 aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17361 this.aAnimation.perform(aValue);
17365 ValueListActivity.prototype.startAnimation = function()
17367 if( this.isDisposed() || !this.aAnimation )
17369 log( 'ValueListActivity.startAnimation: activity disposed or not valid animation' );
17373 ValueListActivity.superclass.startAnimation.call( this );
17375 this.aAnimation.start( this.getTargetElement() );
17378 ValueListActivity.prototype.endAnimation = function()
17380 if( this.aAnimation )
17381 this.aAnimation.end();
17384 // perform hook override for ContinuousKeyTimeActivityBase base
17385 ValueListActivity.prototype.performContinuousHook = function( nIndex, nFractionalIndex, nRepeatCount )
17387 if( this.isDisposed() || !this.aAnimation )
17389 log( 'ValueListActivity.performContinuousHook: activity disposed or not valid animation' );
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 )
17404 //aValue = aValue + nRepeatCount * this.aLastValue;
17405 aValue = this.add( aValue, this.scale( nRepeatCount, this.aLastValue ) );
17408 aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17409 this.aAnimation.perform( aValue );
17412 // perform hook override for DiscreteActivityBase base
17413 ValueListActivity.prototype.performDiscreteHook = function( nFrame, nRepeatCount )
17415 if( this.isDisposed() || !this.aAnimation )
17417 log( 'ValueListActivity.performDiscreteHook: activity disposed or not valid animation' );
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 )
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;
17434 aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
17435 this.aAnimation.perform( aValue );
17438 ValueListActivity.prototype.performEnd = function()
17440 if( this.aAnimation )
17442 var aValue = this.aFormula ? this.aFormula( this.aLastValue ) : this.aLastValue;
17443 this.aAnimation.perform( aValue );
17447 ValueListActivity.prototype.dispose = function()
17449 ValueListActivity.superclass.dispose.call( this );
17453 return ValueListActivity;
17457 // ValueListActivity< ContinuousKeyTimeActivityBase > instantiation
17458 var LinearValueListActivity = instantiate( ValueListActivityTemplate, ContinuousKeyTimeActivityBase );
17459 // ValueListActivity< DiscreteActivityBase > instantiation
17460 var DiscreteValueListActivity = instantiate( ValueListActivityTemplate, DiscreteActivityBase );
17464 /**********************************************************************************************
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 )
17482 aInterpolator = aInterpolatorHandler.getInterpolator( eCalcMode,
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() )
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);
17516 /* eslint-enable no-unused-vars */
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 )
17527 // Value list activity
17529 if( aActivityParamSet.aDiscreteTimes.length == 0 )
17531 for( var i = 0; i < nValueSetSize; ++i )
17532 aActivityParamSet.aDiscreteTimes[i].push( i / nValueSetSize );
17535 switch( eCalcMode )
17537 case CALC_MODE_DISCRETE:
17538 aActivityParamSet.aWakeupEvent =
17539 new WakeupEvent( aActivityParamSet.aTimerEventQueue.getTimer(),
17540 aActivityParamSet.aActivityQueue );
17542 return createValueListActivity( aActivityParamSet,
17546 DiscreteValueListActivity,
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,
17560 LinearValueListActivity,
17567 // FromToBy activity
17568 switch( eCalcMode )
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,
17579 DiscreteFromToByActivity,
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,
17593 LinearFromToByActivity,
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,
17617 aAnimatedElement.getBaseBBox(),
17618 aActivityParamSet.nSlideWidth,
17619 aActivityParamSet.nSlideHeight );
17621 for( var i = 0; i < aValueList.length; ++i )
17623 ANIMDBG.print( 'createValueListActivity: value[' + i + '] = ' + aValueList[i] );
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,
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 )
17675 switch( eValueType )
17677 case NUMBER_PROPERTY :
17678 evalValuesAttribute( aValueList, aValueSet, aBBox, nSlideWidth, nSlideHeight );
17680 case BOOL_PROPERTY :
17681 for( i = 0; i < aValueSet.length; ++i )
17683 var aValue = booleanParser( aValueSet[i] );
17684 aValueList.push( aValue );
17687 case STRING_PROPERTY :
17688 for( i = 0; i < aValueSet.length; ++i )
17690 aValueList.push( aValueSet[i] );
17693 case ENUM_PROPERTY :
17694 for( i = 0; i < aValueSet.length; ++i )
17696 aValueList.push( aValueSet[i] );
17699 case COLOR_PROPERTY :
17700 for( i = 0; i < aValueSet.length; ++i )
17702 aValue = colorParser( aValueSet[i] );
17703 aValueList.push( aValue );
17707 log( 'createValueListActivity: unexpected value type: ' + eValueType );
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 )
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 );
17738 /**********************************************************************************************
17739 * SlideShow, SlideShowContext and FrameSynchronization
17740 **********************************************************************************************/
17744 // direction of animation, important: not change the values!
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;
17765 Effect.prototype.getId = function()
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 )
17857 log( 'SlideShow.createSlideTransition: entering slide element is not valid.' );
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() )
17875 nDuration = aSlideTransitionHandler.getDuration().getValue();
17879 log( 'SlideShow.createSlideTransition: duration is not a number' );
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();
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 )
17955 if( this.automaticAdvanceTimeout['rewindedEffect'] === this.nCurrentEffect )
17957 this.automaticAdvanceTimeout = null;
17958 this.notifyAnimationsEnd();
17963 SlideShow.prototype.notifyAnimationsEnd = function()
17965 if( nCurSlide + 1 === theMetaDoc.nNumberOfSlides )
17968 assert (this.automaticAdvanceTimeout === null,
17969 'SlideShow.notifyAnimationsEnd: Timeout already set.')
17971 var nTimeout = Math.ceil(theMetaDoc.aMetaSlideSet[nCurSlide].fDuration * 1000);
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;
17991 if( nOldSlideIndex !== undefined )
17993 aAnimatedElementMap = theMetaDoc.aMetaSlideSet[nOldSlideIndex].aSlideAnimationsHandler.aAnimatedElementMap;
17994 for( sId in aAnimatedElementMap )
17995 aAnimatedElementMap[ sId ].notifySlideEnd();
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() )
18009 var sRef = 'url(#' + aPresentationClipPathId + ')';
18010 theMetaDoc.getCurrentSlide().slideElement.setAttribute('clip-path', sRef);
18013 this.bIsTransitionRunning = false;
18014 if( this.bIsRewinding )
18016 theMetaDoc.aMetaSlideSet[nSlideIndex].hide();
18017 var nIndex = nCurSlide !== undefined ? nCurSlide : -1;
18018 this.displaySlide( nIndex, true );
18019 this.skipAllEffects();
18020 this.bIsRewinding = false;
18024 theMetaDoc.setCurrentSlide(nSlideIndex);
18026 if( this.aSlideViewElement )
18028 theMetaDoc.getCurrentSlide().aVisibilityStatusElement.parentNode.removeChild( this.aSlideViewElement );
18029 this.aSlideViewElement = null;
18031 if( this.isEnabled() )
18033 // clear all queues
18036 var aCurrentSlide = theMetaDoc.getCurrentSlide();
18037 if( aCurrentSlide.aSlideAnimationsHandler.elementsParsed() )
18039 aCurrentSlide.aSlideAnimationsHandler.start();
18040 this.aEventMultiplexer.registerAnimationsEndHandler( bind2( SlideShow.prototype.notifyAnimationsEnd, this ) );
18043 this.notifyAnimationsEnd();
18048 this.notifyAnimationsEnd();
18051 SlideShow.prototype.notifyInteractiveAnimationSequenceStart = function( nNodeId )
18053 ++this.nTotalInteractivePlayingEffects;
18054 var aEffect = new Effect( nNodeId );
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;
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
18074 * @return {Boolean}
18075 * False if there is no more effect to start, true otherwise.
18077 SlideShow.prototype.nextEffect = function()
18079 if( !this.isEnabled() )
18082 if( this.isTransitionPlaying() )
18084 this.skipTransition();
18088 if( this.isAnyEffectPlaying() )
18090 this.skipAllPlayingEffects();
18094 if( !this.aNextEffectEventArray )
18097 if( this.nCurrentEffect >= this.aNextEffectEventArray.size() )
18100 this.notifyNextEffectStart();
18102 this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
18103 ++this.nCurrentEffect;
18109 * Skip the current playing slide transition.
18111 SlideShow.prototype.skipTransition = function()
18113 if( this.bIsSkipping || this.bIsRewinding )
18116 this.bIsSkipping = true;
18118 this.aActivityQueue.endAll();
18119 this.aTimerEventQueue.forceEmpty();
18120 this.aActivityQueue.endAll();
18122 this.bIsSkipping = false;
18125 /** skipAllPlayingEffects
18126 * Skip all playing effect, independently to which animation sequence they
18130 SlideShow.prototype.skipAllPlayingEffects = function()
18132 if( this.bIsSkipping || this.bIsRewinding )
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 )
18139 var aEffect = this.aStartedEffectList[i];
18140 if( aEffect.isPlaying() )
18142 if( aEffect.isMainEffect() )
18143 this.aEventMultiplexer.notifySkipEffectEvent();
18145 this.aEventMultiplexer.notifySkipInteractiveEffectEvent( aEffect.getId() );
18149 this.bIsSkipping = false;
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.
18161 SlideShow.prototype.skipNextEffect = function()
18163 if( this.bIsSkipping || this.bIsRewinding )
18166 assert( !this.isAnyEffectPlaying(),
18167 'SlideShow.skipNextEffect' );
18169 if( !this.aNextEffectEventArray )
18172 if( this.nCurrentEffect >= this.aNextEffectEventArray.size() )
18175 this.notifyNextEffectStart();
18177 this.bIsSkipping = true;
18178 this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
18179 this.aEventMultiplexer.notifySkipEffectEvent();
18180 ++this.nCurrentEffect;
18182 this.bIsSkipping = false;
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.
18193 SlideShow.prototype.skipPlayingOrNextEffect = function()
18195 if( this.isTransitionPlaying() )
18197 this.skipTransition();
18201 if( this.isAnyEffectPlaying() )
18202 return this.skipAllPlayingEffects();
18204 return this.skipNextEffect();
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.
18216 SlideShow.prototype.skipAllEffects = function()
18218 if( this.bIsSkippingAll )
18221 this.bIsSkippingAll = true;
18223 if( this.isTransitionPlaying() )
18225 this.skipTransition();
18228 if( this.isAnyEffectPlaying() )
18230 this.skipAllPlayingEffects();
18232 else if( !this.aNextEffectEventArray
18233 || ( this.nCurrentEffect >= this.aNextEffectEventArray.size() ) )
18235 this.bIsSkippingAll = false;
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() )
18247 this.skipNextEffect();
18249 this.bIsSkippingAll = false;
18253 /** rewindTransition
18254 * Rewind the current playing slide transition.
18256 SlideShow.prototype.rewindTransition = function()
18258 if( this.bIsSkipping || this.bIsRewinding )
18261 this.bIsRewinding = true;
18262 this.aActivityQueue.endAll();
18264 this.bIsRewinding = false;
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.
18273 SlideShow.prototype.rewindEffect = function()
18275 if( this.bIsSkipping || this.bIsRewinding )
18278 if( this.automaticAdvanceTimeout !== null && !this.automaticAdvanceTimeout['rewindedEffect'] )
18280 window.clearTimeout( this.automaticAdvanceTimeout );
18281 this.automaticAdvanceTimeout = { 'rewindedEffect': this.nCurrentEffect };
18284 if( !this.hasAnyEffectStarted() )
18286 this.rewindToPreviousSlide();
18290 this.bIsRewinding = true;
18292 var nFirstPlayingEffectIndex = undefined;
18295 for( ; i < this.aStartedEffectList.length; ++i )
18297 var aEffect = this.aStartedEffectList[i];
18298 if( aEffect.isPlaying() )
18300 nFirstPlayingEffectIndex = i;
18305 // There is at least one playing effect.
18306 if( nFirstPlayingEffectIndex !== undefined )
18308 i = this.aStartedEffectList.length - 1;
18309 for( ; i >= nFirstPlayingEffectIndex; --i )
18311 aEffect = this.aStartedEffectList[i];
18312 if( aEffect.isPlaying() )
18314 if( aEffect.isMainEffect() )
18316 this.aEventMultiplexer.notifyRewindCurrentEffectEvent();
18317 if( this.nCurrentEffect > 0 )
18318 --this.nCurrentEffect;
18322 this.aEventMultiplexer.notifyRewindRunningInteractiveEffectEvent( aEffect.getId() );
18325 else if( aEffect.isEnded() )
18327 if( aEffect.isMainEffect() )
18329 this.aEventMultiplexer.notifyRewindLastEffectEvent();
18330 if( this.nCurrentEffect > 0 )
18331 --this.nCurrentEffect;
18335 this.aEventMultiplexer.notifyRewindEndedInteractiveEffectEvent( aEffect.getId() );
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 )
18346 aEffect = this.aStartedEffectList.pop();
18347 if( !aEffect.isMainEffect() )
18348 delete this.aStartedEffectIndexMap[ aEffect.getId() ];
18351 else // there is no playing effect
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.
18358 if( aEffect.isMainEffect() )
18360 this.aEventMultiplexer.notifyRewindLastEffectEvent();
18361 if( this.nCurrentEffect > 0 )
18362 --this.nCurrentEffect;
18366 this.aEventMultiplexer.notifyRewindEndedInteractiveEffectEvent( aEffect.getId() );
18372 this.bIsRewinding = false;
18375 /** rewindToPreviousSlide
18376 * Displays the previous slide with all effects, that belong to the main
18377 * animation sequence, played.
18380 SlideShow.prototype.rewindToPreviousSlide = function()
18382 if( this.isTransitionPlaying() )
18384 this.rewindTransition();
18387 if( this.isAnyEffectPlaying() )
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.
18398 SlideShow.prototype.rewindAllEffects = function()
18400 if( !this.hasAnyEffectStarted() )
18402 this.rewindToPreviousSlide();
18406 while( this.hasAnyEffectStarted() )
18408 this.rewindEffect();
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 ) {
18428 // In the iOS app, exit the slideshow when going past the end.
18429 this.exitSlideShowInApp();
18432 if( ( currentMode === INDEX_MODE ) && ( nNewSlide === nCurSlide ) )
18434 aMetaDoc.getCurrentSlide().show();
18438 if( this.isTransitionPlaying() )
18440 this.skipTransition();
18443 // handle current slide
18444 var nOldSlide = nCurSlide;
18445 if( nOldSlide !== undefined )
18447 var oldMetaSlide = aMetaDoc.aMetaSlideSet[nOldSlide];
18448 if( this.isEnabled() )
18450 if( oldMetaSlide.aSlideAnimationsHandler.isAnimated() )
18452 // force end animations
18453 oldMetaSlide.aSlideAnimationsHandler.end( bSkipSlideTransition );
18455 // clear all queues
18460 if( this.automaticAdvanceTimeout !== null )
18462 window.clearTimeout( this.automaticAdvanceTimeout );
18463 this.automaticAdvanceTimeout = null;
18467 this.notifySlideStart( nNewSlide, nOldSlide );
18469 if( this.isEnabled() && !bSkipSlideTransition )
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
18479 var aOldMetaSlide = null;
18480 if( nOldSlide === undefined ) // for transition on first slide
18482 aOldMetaSlide = aMetaDoc.theMetaDummySlide;
18486 aOldMetaSlide = aMetaDoc.aMetaSlideSet[nOldSlide];
18488 var aNewMetaSlide = aMetaDoc.aMetaSlideSet[nNewSlide];
18490 var aSlideTransitionHandler = aNewMetaSlide.aTransitionHandler;
18491 if( aSlideTransitionHandler && aSlideTransitionHandler.isValid() )
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 )
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();
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 )
18519 this.bIsTransitionRunning = true;
18520 this.aActivityQueue.addActivity( aTransitionActivity );
18525 this.notifyTransitionEnd( nNewSlide );
18530 this.notifyTransitionEnd( nNewSlide );
18535 this.notifyTransitionEnd( nNewSlide );
18540 this.notifyTransitionEnd( nNewSlide );
18545 SlideShow.prototype.update = function()
18547 this.aTimer.holdTimer();
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 );
18567 if( bActivitiesLeft )
18569 nNextTimeout = MINIMUM_TIMEOUT;
18570 this.aFrameSynchronization.activate();
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();
18582 this.bIsIdle = false;
18583 window.setTimeout( 'aSlideShow.update()', nNextTimeout * 1000 );
18587 this.bIsIdle = true;
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 )
18644 // Do busy waiting for now.
18645 while( this.aTimer.getElapsedTime() < this.nNextFrameTargetTime )
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 )
18691 if( this.aEventArray[i].getId() == aEvent.getId() )
18693 aNextEffectEventArrayDebugPrinter.print( 'NextEffectEventArray.appendEvent: event(' + aEvent.getId() + ') already present' );
18697 this.aEventArray.push( aEvent );
18698 aNextEffectEventArrayDebugPrinter.print( 'NextEffectEventArray.appendEvent: event(' + aEvent.getId() + ') appended' );
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.' );
18722 log( 'TimerEventQueue.addEvent: null event' );
18726 var nTime = aEvent.getActivationTime( this.aTimer.getElapsedTime() );
18727 var aEventEntry = new EventEntry( aEvent, nTime );
18728 this.aEventSet.push( aEventEntry );
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 ) ) )
18750 var aEventEntry = this.aEventSet.top();
18751 this.aEventSet.pop();
18753 var aEvent = aEventEntry.aEvent;
18754 if( aEvent.isCharged() )
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;
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 ) )
18797 this.aEventSet[ nHoleIndex ] = this.aEventSet[ nParent ];
18798 nHoleIndex = nParent;
18799 nParent = Math.floor( ( nHoleIndex - 1 ) / 2 );
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 )
18820 else if ( aLhsEventEntry.nActivationTime < aRhsEventEntry.nActivationTime )
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;
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 )
18858 log( 'ActivityQueue.addActivity: activity is not valid' );
18862 this.aCurrentActivityWaitingSet.push( aActivity );
18863 aActivityQueueDebugPrinter.print( 'ActivityQueue.addActivity: activity appended' );
18867 ActivityQueue.prototype.process = function()
18869 var nSize = this.aCurrentActivityWaitingSet.length;
18871 for( var i = 0; i < nSize; ++i )
18873 nLag = Math.max( nLag,this.aCurrentActivityWaitingSet[i].calcTimeLag() );
18877 this.aTimer.adjustTimer( -nLag, true );
18880 while( this.aCurrentActivityWaitingSet.length != 0 )
18882 var aActivity = this.aCurrentActivityWaitingSet.shift();
18883 var bReinsert = false;
18885 bReinsert = aActivity.perform();
18889 this.aCurrentActivityReinsertSet.push( aActivity );
18893 this.aDequeuedActivitySet.push( aActivity );
18897 if( this.aCurrentActivityReinsertSet.length != 0 )
18899 // TODO: optimization, try to swap reference here
18900 this.aCurrentActivityWaitingSet = this.aCurrentActivityReinsertSet;
18901 this.aCurrentActivityReinsertSet = [];
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;
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;
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()
19052 if ( !this.aTimeBase )
19054 nCurrentTime = this.getSystemTime();
19058 nCurrentTime = this.aTimeBase.getElapsedTimeImpl();
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 )
19072 return this.nFrozenTime;
19075 var nCurTime = this.getCurrentTime();
19076 return ( nCurTime - this.nStartTime );
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
19091 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */