summaryrefslogtreecommitdiffstats
path: root/filter/source/svg
diff options
context:
space:
mode:
Diffstat (limited to 'filter/source/svg')
-rw-r--r--filter/source/svg/.eslintrc.js250
-rw-r--r--filter/source/svg/gentoken.py60
-rwxr-xr-xfilter/source/svg/js2hxx.py149
-rw-r--r--filter/source/svg/presentation_engine.js19575
-rw-r--r--filter/source/svg/svgexport.cxx2877
-rw-r--r--filter/source/svg/svgfilter.component32
-rw-r--r--filter/source/svg/svgfilter.cxx847
-rw-r--r--filter/source/svg/svgfilter.hxx321
-rw-r--r--filter/source/svg/svgfontexport.cxx321
-rw-r--r--filter/source/svg/svgfontexport.hxx72
-rw-r--r--filter/source/svg/svgwriter.cxx4096
-rw-r--r--filter/source/svg/svgwriter.hxx406
-rw-r--r--filter/source/svg/test/odfserializer.cxx128
-rw-r--r--filter/source/svg/test/odfserializer.hxx33
-rw-r--r--filter/source/svg/tokens.txt403
15 files changed, 29570 insertions, 0 deletions
diff --git a/filter/source/svg/.eslintrc.js b/filter/source/svg/.eslintrc.js
new file mode 100644
index 000000000..8daf37eab
--- /dev/null
+++ b/filter/source/svg/.eslintrc.js
@@ -0,0 +1,250 @@
+module.exports = {
+ "env": {
+ "browser": true,
+ "es6": true
+ },
+ "extends": "eslint:recommended",
+ "rules": {
+ "accessor-pairs": "error",
+ "array-bracket-spacing": "off",
+ "array-callback-return": "error",
+ "arrow-body-style": "error",
+ "arrow-parens": "error",
+ "arrow-spacing": "error",
+ "block-scoped-var": "off",
+ "block-spacing": [
+ "error",
+ "always"
+ ],
+ "brace-style": "off",
+ "callback-return": "error",
+ "camelcase": "off",
+ "capitalized-comments": "off",
+ "class-methods-use-this": "error",
+ "comma-dangle": "error",
+ "comma-spacing": "off",
+ "comma-style": [
+ "error",
+ "last"
+ ],
+ "complexity": "off",
+ "computed-property-spacing": "off",
+ "consistent-return": "off",
+ "consistent-this": "off",
+ "curly": "off",
+ "default-case": "off",
+ "dot-location": [
+ "error",
+ "property"
+ ],
+ "dot-notation": "off",
+ "eol-last": "error",
+ "eqeqeq": "off",
+ "func-call-spacing": "error",
+ "func-name-matching": "error",
+ "func-names": [
+ "error",
+ "never"
+ ],
+ "func-style": "off",
+ "generator-star-spacing": "error",
+ "global-require": "error",
+ "guard-for-in": "off",
+ "handle-callback-err": "error",
+ "id-denylist": "error",
+ "id-length": "off",
+ "id-match": "error",
+ "indent": "off",
+ "init-declarations": "off",
+ "jsx-quotes": "error",
+ "key-spacing": "off",
+ "keyword-spacing": "off",
+ "line-comment-position": "off",
+ "linebreak-style": [
+ "error",
+ "unix"
+ ],
+ "lines-around-comment": "off",
+ "lines-around-directive": "off",
+ "max-depth": "off",
+ "max-len": "off",
+ "max-lines": "off",
+ "max-nested-callbacks": "error",
+ "max-params": "off",
+ "max-statements": "off",
+ "max-statements-per-line": "off",
+ "multiline-ternary": "off",
+ "new-parens": "error",
+ "newline-after-var": "off",
+ "newline-before-return": "off",
+ "newline-per-chained-call": "off",
+ "no-alert": "off",
+ "no-array-constructor": "error",
+ "no-await-in-loop": "error",
+ "no-bitwise": "off",
+ "no-caller": "error",
+ "no-catch-shadow": "error",
+ "no-compare-neg-zero": "error",
+ "no-confusing-arrow": "error",
+ "no-continue": "off",
+ "no-div-regex": "error",
+ "no-duplicate-imports": "error",
+ "no-else-return": "off",
+ "no-empty-function": "off",
+ "no-eq-null": "off",
+ "no-eval": "off",
+ "no-extend-native": "error",
+ "no-extra-bind": "error",
+ "no-extra-label": "error",
+ "no-extra-parens": "off",
+ "no-floating-decimal": "error",
+ "no-implicit-globals": "off",
+ "no-implied-eval": "off",
+ "no-inline-comments": "off",
+ "no-inner-declarations": [
+ "error",
+ "functions"
+ ],
+ "no-invalid-this": "error",
+ "no-iterator": "error",
+ "no-label-var": "error",
+ "no-labels": "error",
+ "no-lone-blocks": "error",
+ "no-lonely-if": "off",
+ "no-loop-func": "error",
+ "no-magic-numbers": "off",
+ "no-mixed-operators": "off",
+ "no-mixed-requires": "error",
+ "no-multi-assign": "off",
+ "no-multi-spaces": "off",
+ "no-multi-str": "error",
+ "no-multiple-empty-lines": "off",
+ "no-native-reassign": "error",
+ "no-negated-condition": "off",
+ "no-negated-in-lhs": "error",
+ "no-nested-ternary": "error",
+ "no-new": "error",
+ "no-new-func": "error",
+ "no-new-object": "error",
+ "no-new-require": "error",
+ "no-new-wrappers": "error",
+ "no-octal-escape": "error",
+ "no-param-reassign": "off",
+ "no-path-concat": "error",
+ "no-plusplus": "off",
+ "no-process-env": "error",
+ "no-process-exit": "error",
+ "no-proto": "error",
+ "no-prototype-builtins": "off",
+ "no-restricted-globals": "error",
+ "no-restricted-imports": "error",
+ "no-restricted-modules": "error",
+ "no-restricted-properties": "error",
+ "no-restricted-syntax": "error",
+ "no-return-assign": [
+ "error",
+ "except-parens"
+ ],
+ "no-return-await": "error",
+ "no-script-url": "error",
+ "no-self-compare": "error",
+ "no-sequences": "error",
+ "no-shadow": "off",
+ "no-shadow-restricted-names": "error",
+ "no-spaced-func": "error",
+ "no-sync": "error",
+ "no-tabs": "off",
+ "no-template-curly-in-string": "error",
+ "no-ternary": "off",
+ "no-throw-literal": "off",
+ "no-trailing-spaces": "off",
+ "no-undef-init": "off",
+ "no-undefined": "off",
+ "no-underscore-dangle": "off",
+ "no-unmodified-loop-condition": "off",
+ "no-unneeded-ternary": "off",
+ "no-unused-expressions": "error",
+ "no-use-before-define": "off",
+ "no-useless-call": "error",
+ "no-useless-computed-key": "error",
+ "no-useless-concat": "off",
+ "no-useless-constructor": "error",
+ "no-useless-escape": "error",
+ "no-useless-rename": "error",
+ "no-useless-return": "off",
+ "no-var": "off",
+ "no-void": "error",
+ "no-warning-comments": "off",
+ "no-whitespace-before-property": "error",
+ "no-with": "error",
+ "nonblock-statement-body-position": [
+ "error",
+ "any"
+ ],
+ "object-curly-newline": "off",
+ "object-curly-spacing": "off",
+ "object-property-newline": [
+ "error",
+ {
+ "allowMultiplePropertiesPerLine": true
+ }
+ ],
+ "object-shorthand": "off",
+ "one-var": "off",
+ "one-var-declaration-per-line": "off",
+ "operator-assignment": "off",
+ "operator-linebreak": "off",
+ "padded-blocks": "off",
+ "prefer-arrow-callback": "off",
+ "prefer-const": "off",
+ "prefer-destructuring": [
+ "error",
+ {
+ "array": false,
+ "object": false
+ }
+ ],
+ "prefer-numeric-literals": "error",
+ "prefer-promise-reject-errors": "error",
+ "prefer-reflect": "off",
+ "prefer-rest-params": "off",
+ "prefer-spread": "error",
+ "prefer-template": "off",
+ "quote-props": "off",
+ "quotes": [
+ "error",
+ "single"
+ ],
+ "radix": [
+ "error",
+ "as-needed"
+ ],
+ "require-await": "error",
+ "require-jsdoc": "off",
+ "rest-spread-spacing": "error",
+ "semi": "off",
+ "semi-spacing": "off",
+ "sort-imports": "error",
+ "sort-keys": "off",
+ "sort-vars": "off",
+ "space-before-blocks": "off",
+ "space-before-function-paren": "off",
+ "space-in-parens": "off",
+ "space-infix-ops": "off",
+ "space-unary-ops": "off",
+ "spaced-comment": "off",
+ "strict": "off",
+ "symbol-description": "error",
+ "template-curly-spacing": "error",
+ "template-tag-spacing": "error",
+ "unicode-bom": [
+ "error",
+ "never"
+ ],
+ "valid-jsdoc": "off",
+ "vars-on-top": "off",
+ "wrap-iife": "error",
+ "wrap-regex": "off",
+ "yield-star-spacing": "error"
+ }
+}; \ No newline at end of file
diff --git a/filter/source/svg/gentoken.py b/filter/source/svg/gentoken.py
new file mode 100644
index 000000000..c78d066d7
--- /dev/null
+++ b/filter/source/svg/gentoken.py
@@ -0,0 +1,60 @@
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+import sys
+
+tokenfile_name = sys.argv[1]
+hxx_name = sys.argv[2]
+gperf_name = sys.argv[3]
+
+gperf_header = r"""%language=C++
+%global-table
+%null-strings
+%struct-type
+struct xmltoken
+{
+ const char *name; sal_Int32 nToken;
+}
+%%
+"""
+
+tokens = {}
+
+with open(tokenfile_name) as tokenfile:
+ for line in tokenfile:
+ line = line.strip()
+ if line:
+ arr = line.split()
+ if len(arr) < 2:
+ t = "XML_" + arr[0]
+ t = t.replace('-', '_').replace('.', '_').replace(':', '_')
+ t = t.replace('+', 'PLUS')
+ arr.append(t)
+ tokens[arr[0]] = arr[1].upper()
+
+hxx = open(hxx_name, 'w')
+gperf = open(gperf_name, 'w')
+
+gperf.write(gperf_header)
+
+hxx.write("#ifndef INCLUDED_AUTOGEN_TOKEN_HXX\n")
+hxx.write("#define INCLUDED_AUTOGEN_TOKEN_HXX\n\n")
+hxx.write("#include <sal/types.h>\n\n" )
+
+i = 0;
+for token in sorted(tokens.keys()):
+ i += 1;
+ hxx.write("const sal_Int32 {} = {};\n".format(tokens[token], i))
+ gperf.write("{},{}\n".format(token, tokens[token]))
+
+gperf.write("%%\n")
+hxx.write("const sal_Int32 XML_TOKEN_COUNT = {};\n".format(i))
+hxx.write("const sal_Int32 XML_TOKEN_INVALID = -1;\n\n")
+hxx.write("#endif\n")
+
+hxx.close()
+gperf.close()
diff --git a/filter/source/svg/js2hxx.py b/filter/source/svg/js2hxx.py
new file mode 100755
index 000000000..38bcab12b
--- /dev/null
+++ b/filter/source/svg/js2hxx.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# Major Contributor(s):
+# Copyright (C) 2011 Marco Cecchetti <mrcekets@gmail.com>
+#
+# All Rights Reserved.
+#
+# For minor contributions see the git repository.
+
+import os, sys
+
+MAX_LINES = 150
+VARIABLE_NAME = 'aSVGScript'
+
+def get_var_decl(n):
+ return 'static const char %s%d[] =' % ( VARIABLE_NAME, n )
+
+script_name = os.path.basename( sys.argv[0] )
+infile_name = sys.argv[1]
+outfile_name = sys.argv[2]
+
+
+# collect input JavaScript file lines
+if( not os.path.isfile( infile_name ) ):
+ print ( '%s: error: file "%s" not found' % ( script_name, infile_name ) )
+ sys.exit( -1 )
+
+infile = open( infile_name, 'r' )
+in_lines = [line.rstrip() for line in infile.readlines()]
+infile.close()
+
+
+valid_lines=[]
+is_multiline_comment = False
+lineNumber = 0
+emptyLineCount = 0
+for line in in_lines:
+ lineNumber += 1
+ index = line.find('"')
+ if( index != -1 ):
+ print ( '%s: warning: processed file contains \'"\' at %d:%d' % ( script_name, lineNumber, index ) )
+
+ sline = line.strip()
+
+ # strip comment lines except multilines comments that begins with one '/' and exactly 5 '*'
+ if( is_multiline_comment and sline.endswith( '*/' ) ):
+ is_multiline_comment = False
+ continue
+
+ if( is_multiline_comment ):
+ continue
+
+ if( sline.startswith( '//' ) ):
+ continue
+
+ if( sline.startswith( '/*' ) and sline.endswith( '*/' ) ):
+ continue
+
+ if( ( sline.startswith( '/*' ) and not sline.startswith( '/*****' ) )
+ or sline.startswith( '/******' ) ):
+ is_multiline_comment = True
+ continue
+
+ # disable any debug printer
+ dline = line.replace( 'NAVDBG.on', 'NAVDBG.off' )
+ dline = dline.replace( 'ANIMDBG.on', 'ANIMDBG.off' )
+ dline = dline.replace( 'DebugPrinter.on', 'DebugPrinter.off' )
+
+ escaped_line = '%s' % dline
+ escaped_line = escaped_line.rstrip().lstrip()
+
+ # no more than 2 consecutive empty lines
+ if( escaped_line == '' ):
+ emptyLineCount += 1
+ else:
+ emptyLineCount = 0
+
+ if( emptyLineCount > 2 ):
+ continue
+
+ # append to some escape sequence another '\'
+ escaped_line = escaped_line.replace( '\\', '\\\\' )
+ escaped_line = escaped_line.replace( '\n', '\\n')
+ escaped_line = escaped_line.replace( '\t', '\\t' )
+
+ valid_lines.append( escaped_line )
+
+
+# compute the number of needed fragments that is of C constant strings
+total_valid_lines = len (valid_lines) + 2
+total_fragments = total_valid_lines / MAX_LINES
+if( ( total_valid_lines % MAX_LINES ) != 0 ):
+ total_fragments += 1
+
+
+
+out_lines = []
+out_lines.append( '' )
+out_lines.append( '#define N_SVGSCRIPT_FRAGMENTS %d' % total_fragments )
+out_lines.append( '' )
+out_lines.append( get_var_decl( 0 ) )
+out_lines.append( '"<![CDATA[\\n\\' )
+i = 2
+fragment = 0
+for line in valid_lines:
+ out_lines.append( line + '\\n\\' )
+ if( i == MAX_LINES ):
+ i = 0
+ fragment += 1
+ out_lines.append( '";' )
+ out_lines.append( '' )
+ out_lines.append( get_var_decl( fragment ) )
+ out_lines.append( '"\\' )
+ i += 1
+
+out_lines.append( ']]>";' )
+out_lines.append( '' )
+
+out_lines.append('static const char * g_SVGScripts[N_SVGSCRIPT_FRAGMENTS] = {')
+for j in range(0, fragment+1):
+ out_lines.append(" %s%d," % (VARIABLE_NAME, j))
+out_lines.append('};')
+
+outfile = open( outfile_name, 'w' )
+if( not os.path.isfile( outfile_name ) ):
+ print ( '%s: error: I cannot create file "%s"' % ( script_name, outfile_name ) )
+ sys.exit( -1 )
+
+
+# C++ header
+header_info = '/* !! This file is auto-generated, do not edit !! */'
+
+outfile.write( header_info +'\n\n' )
+
+for line in out_lines:
+ outfile.write( line + '\n' )
+
+outfile.close()
diff --git a/filter/source/svg/presentation_engine.js b/filter/source/svg/presentation_engine.js
new file mode 100644
index 000000000..6473963f8
--- /dev/null
+++ b/filter/source/svg/presentation_engine.js
@@ -0,0 +1,19575 @@
+/* -*- Mode: JS; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * - Presentation Engine - *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
+
+/**
+ * WARNING: any comment that should not be striped out by the script
+ * generating the C++ header file must start with a '/' and exactly 5 '*'
+ * not striped examples: '/*****', '/***** *'
+ * striped examples: '/** ***' (not contiguous), '/******' (more than 5)
+ *
+ * NOTE: This file combines several works, under different
+ * licenses. See the @licstart / @licend sections below.
+ */
+
+/*! Hammer.JS - v2.0.7 - 2016-04-22
+ * http://hammerjs.github.io/
+ *
+ * Copyright (c) 2016 Jorik Tangelder;
+ * Licensed under the MIT license */
+(function(window, document, exportName, undefined) {
+ 'use strict';
+
+var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
+var TEST_ELEMENT = document.createElement('div');
+
+var TYPE_FUNCTION = 'function';
+
+var round = Math.round;
+var abs = Math.abs;
+var now = Date.now;
+
+/**
+ * polyfill for IE11
+ */
+if (!Math.trunc) {
+ Math.trunc = function (v) {
+ return v < 0 ? Math.ceil(v) : Math.floor(v);
+ };
+}
+
+/**
+ * set a timeout with a given scope
+ * @param {Function} fn
+ * @param {Number} timeout
+ * @param {Object} context
+ * @returns {number}
+ */
+function setTimeoutContext(fn, timeout, context) {
+ return setTimeout(bindFn(fn, context), timeout);
+}
+
+/**
+ * if the argument is an array, we want to execute the fn on each entry
+ * if it aint an array we don't want to do a thing.
+ * this is used by all the methods that accept a single and array argument.
+ * @param {*|Array} arg
+ * @param {String} fn
+ * @param {Object} [context]
+ * @returns {Boolean}
+ */
+function invokeArrayArg(arg, fn, context) {
+ if (Array.isArray(arg)) {
+ each(arg, context[fn], context);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * walk objects and arrays
+ * @param {Object} obj
+ * @param {Function} iterator
+ * @param {Object} context
+ */
+function each(obj, iterator, context) {
+ var i;
+
+ if (!obj) {
+ return;
+ }
+
+ if (obj.forEach) {
+ obj.forEach(iterator, context);
+ } else if (obj.length !== undefined) {
+ i = 0;
+ while (i < obj.length) {
+ iterator.call(context, obj[i], i, obj);
+ i++;
+ }
+ } else {
+ for (i in obj) {
+ obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
+ }
+ }
+}
+
+/**
+ * wrap a method with a deprecation warning and stack trace
+ * @param {Function} method
+ * @param {String} name
+ * @param {String} message
+ * @returns {Function} A new function wrapping the supplied method.
+ */
+function deprecate(method, name, message) {
+ var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
+ return function() {
+ var e = new Error('get-stack-trace');
+ var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
+ .replace(/^\s+at\s+/gm, '')
+ .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
+
+ var log = window.console && (window.console.warn || window.console.log);
+ if (log) {
+ log.call(window.console, deprecationMessage, stack);
+ }
+ return method.apply(this, arguments);
+ };
+}
+
+/**
+ * extend object.
+ * means that properties in dest will be overwritten by the ones in src.
+ * @param {Object} target
+ * @param {...Object} objects_to_assign
+ * @returns {Object} target
+ */
+var assign;
+if (typeof Object.assign !== 'function') {
+ assign = function assign(target) {
+ if (target === undefined || target === null) {
+ throw new TypeError('Cannot convert undefined or null to object');
+ }
+
+ var output = Object(target);
+ for (var index = 1; index < arguments.length; index++) {
+ var source = arguments[index];
+ if (source !== undefined && source !== null) {
+ for (var nextKey in source) {
+ if (source.hasOwnProperty(nextKey)) {
+ output[nextKey] = source[nextKey];
+ }
+ }
+ }
+ }
+ return output;
+ };
+} else {
+ assign = Object.assign;
+}
+
+/**
+ * extend object.
+ * means that properties in dest will be overwritten by the ones in src.
+ * @param {Object} dest
+ * @param {Object} src
+ * @param {Boolean} [merge=false]
+ * @returns {Object} dest
+ */
+var extend = deprecate(function extend(dest, src, merge) {
+ var keys = Object.keys(src);
+ var i = 0;
+ while (i < keys.length) {
+ if (!merge || (merge && dest[keys[i]] === undefined)) {
+ dest[keys[i]] = src[keys[i]];
+ }
+ i++;
+ }
+ return dest;
+}, 'extend', 'Use `assign`.');
+
+/**
+ * merge the values from src in the dest.
+ * means that properties that exist in dest will not be overwritten by src
+ * @param {Object} dest
+ * @param {Object} src
+ * @returns {Object} dest
+ */
+var merge = deprecate(function merge(dest, src) {
+ return extend(dest, src, true);
+}, 'merge', 'Use `assign`.');
+
+/**
+ * simple class inheritance
+ * @param {Function} child
+ * @param {Function} base
+ * @param {Object} [properties]
+ */
+function inherit(child, base, properties) {
+ var baseP = base.prototype,
+ childP;
+
+ childP = child.prototype = Object.create(baseP);
+ childP.constructor = child;
+ childP._super = baseP;
+
+ if (properties) {
+ assign(childP, properties);
+ }
+}
+
+/**
+ * simple function bind
+ * @param {Function} fn
+ * @param {Object} context
+ * @returns {Function}
+ */
+function bindFn(fn, context) {
+ return function boundFn() {
+ return fn.apply(context, arguments);
+ };
+}
+
+/**
+ * let a boolean value also be a function that must return a boolean
+ * this first item in args will be used as the context
+ * @param {Boolean|Function} val
+ * @param {Array} [args]
+ * @returns {Boolean}
+ */
+function boolOrFn(val, args) {
+ if (typeof val == TYPE_FUNCTION) {
+ return val.apply(args ? args[0] || undefined : undefined, args);
+ }
+ return val;
+}
+
+/**
+ * use the val2 when val1 is undefined
+ * @param {*} val1
+ * @param {*} val2
+ * @returns {*}
+ */
+function ifUndefined(val1, val2) {
+ return (val1 === undefined) ? val2 : val1;
+}
+
+/**
+ * addEventListener with multiple events at once
+ * @param {EventTarget} target
+ * @param {String} types
+ * @param {Function} handler
+ */
+function addEventListeners(target, types, handler) {
+ each(splitStr(types), function(type) {
+ target.addEventListener(type, handler, false);
+ });
+}
+
+/**
+ * removeEventListener with multiple events at once
+ * @param {EventTarget} target
+ * @param {String} types
+ * @param {Function} handler
+ */
+function removeEventListeners(target, types, handler) {
+ each(splitStr(types), function(type) {
+ target.removeEventListener(type, handler, false);
+ });
+}
+
+/**
+ * find if a node is in the given parent
+ * @method hasParent
+ * @param {HTMLElement} node
+ * @param {HTMLElement} parent
+ * @return {Boolean} found
+ */
+function hasParent(node, parent) {
+ while (node) {
+ if (node == parent) {
+ return true;
+ }
+ node = node.parentNode;
+ }
+ return false;
+}
+
+/**
+ * small indexOf wrapper
+ * @param {String} str
+ * @param {String} find
+ * @returns {Boolean} found
+ */
+function inStr(str, find) {
+ return str.indexOf(find) > -1;
+}
+
+/**
+ * split string on whitespace
+ * @param {String} str
+ * @returns {Array} words
+ */
+function splitStr(str) {
+ return str.trim().split(/\s+/g);
+}
+
+/**
+ * find if an array contains the object using indexOf or a simple polyFill
+ * @param {Array} src
+ * @param {String} find
+ * @param {String} [findByKey]
+ * @return {Boolean|Number} false when not found, or the index
+ */
+function inArray(src, find, findByKey) {
+ if (src.indexOf && !findByKey) {
+ return src.indexOf(find);
+ } else {
+ var i = 0;
+ while (i < src.length) {
+ if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
+ return i;
+ }
+ i++;
+ }
+ return -1;
+ }
+}
+
+/**
+ * convert array-like objects to real arrays
+ * @param {Object} obj
+ * @returns {Array}
+ */
+function toArray(obj) {
+ return Array.prototype.slice.call(obj, 0);
+}
+
+/**
+ * unique array with objects based on a key (like 'id') or just by the array's value
+ * @param {Array} src [{id:1},{id:2},{id:1}]
+ * @param {String} [key]
+ * @param {Boolean} [sort=False]
+ * @returns {Array} [{id:1},{id:2}]
+ */
+function uniqueArray(src, key, sort) {
+ var results = [];
+ var values = [];
+ var i = 0;
+
+ while (i < src.length) {
+ var val = key ? src[i][key] : src[i];
+ if (inArray(values, val) < 0) {
+ results.push(src[i]);
+ }
+ values[i] = val;
+ i++;
+ }
+
+ if (sort) {
+ if (!key) {
+ results = results.sort();
+ } else {
+ results = results.sort(function sortUniqueArray(a, b) {
+ return a[key] > b[key];
+ });
+ }
+ }
+
+ return results;
+}
+
+/**
+ * get the prefixed property
+ * @param {Object} obj
+ * @param {String} property
+ * @returns {String|Undefined} prefixed
+ */
+function prefixed(obj, property) {
+ // tml: Have to check for obj being undefined
+ if (obj === undefined) {
+ return undefined;
+ }
+
+ var prefix, prop;
+ var camelProp = property[0].toUpperCase() + property.slice(1);
+
+ var i = 0;
+ while (i < VENDOR_PREFIXES.length) {
+ prefix = VENDOR_PREFIXES[i];
+ prop = (prefix) ? prefix + camelProp : property;
+
+ if (prop in obj) {
+ return prop;
+ }
+ i++;
+ }
+ return undefined;
+}
+
+/**
+ * get a unique id
+ * @returns {number} uniqueId
+ */
+var _uniqueId = 1;
+function uniqueId() {
+ return _uniqueId++;
+}
+
+/**
+ * get the window object of an element
+ * @param {HTMLElement} element
+ * @returns {DocumentView|Window}
+ */
+function getWindowForElement(element) {
+ var doc = element.ownerDocument || element;
+ return (doc.defaultView || doc.parentWindow || window);
+}
+
+var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
+
+var SUPPORT_TOUCH = ('ontouchstart' in window);
+var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
+var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
+
+var INPUT_TYPE_TOUCH = 'touch';
+var INPUT_TYPE_PEN = 'pen';
+var INPUT_TYPE_MOUSE = 'mouse';
+var INPUT_TYPE_KINECT = 'kinect';
+
+var COMPUTE_INTERVAL = 25;
+
+var INPUT_START = 1;
+var INPUT_MOVE = 2;
+var INPUT_END = 4;
+var INPUT_CANCEL = 8;
+
+var DIRECTION_NONE = 1;
+var DIRECTION_LEFT = 2;
+var DIRECTION_RIGHT = 4;
+var DIRECTION_UP = 8;
+var DIRECTION_DOWN = 16;
+
+var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
+var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
+var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
+
+var PROPS_XY = ['x', 'y'];
+var PROPS_CLIENT_XY = ['clientX', 'clientY'];
+
+/**
+ * create new input type manager
+ * @param {Manager} manager
+ * @param {Function} callback
+ * @returns {Input}
+ * @constructor
+ */
+function Input(manager, callback) {
+ var self = this;
+ this.manager = manager;
+ this.callback = callback;
+ this.element = manager.element;
+ this.target = manager.options.inputTarget;
+
+ // smaller wrapper around the handler, for the scope and the enabled state of the manager,
+ // so when disabled the input events are completely bypassed.
+ this.domHandler = function(ev) {
+ if (boolOrFn(manager.options.enable, [manager])) {
+ self.handler(ev);
+ }
+ };
+
+ this.init();
+
+}
+
+Input.prototype = {
+ /**
+ * should handle the inputEvent data and trigger the callback
+ * @virtual
+ */
+ handler: function() { },
+
+ /**
+ * bind the events
+ */
+ init: function() {
+ this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
+ this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
+ this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
+ },
+
+ /**
+ * unbind the events
+ */
+ destroy: function() {
+ this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
+ this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
+ this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
+ }
+};
+
+/**
+ * create new input type manager
+ * called by the Manager constructor
+ * @param {Hammer} manager
+ * @returns {Input}
+ */
+function createInputInstance(manager) {
+ var Type;
+ var inputClass = manager.options.inputClass;
+
+ if (inputClass) {
+ Type = inputClass;
+ } else if (!SUPPORT_TOUCH && SUPPORT_POINTER_EVENTS) {
+ Type = PointerEventInput;
+ } else if (SUPPORT_ONLY_TOUCH) {
+ Type = TouchInput;
+ } else if (!SUPPORT_TOUCH) {
+ Type = MouseInput;
+ } else {
+ Type = TouchMouseInput;
+ }
+ return new (Type)(manager, inputHandler);
+}
+
+/**
+ * handle input events
+ * @param {Manager} manager
+ * @param {String} eventType
+ * @param {Object} input
+ */
+function inputHandler(manager, eventType, input) {
+ var pointersLen = input.pointers.length;
+ var changedPointersLen = input.changedPointers.length;
+ var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
+ var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
+
+ input.isFirst = !!isFirst;
+ input.isFinal = !!isFinal;
+
+ if (isFirst) {
+ manager.session = {};
+ }
+
+ // source event is the normalized value of the domEvents
+ // like 'touchstart, mouseup, pointerdown'
+ input.eventType = eventType;
+
+ // compute scale, rotation etc
+ computeInputData(manager, input);
+
+ // emit secret event
+ manager.emit('hammer.input', input);
+
+ manager.recognize(input);
+ manager.session.prevInput = input;
+}
+
+/**
+ * extend the data with some usable properties like scale, rotate, velocity etc
+ * @param {Object} manager
+ * @param {Object} input
+ */
+function computeInputData(manager, input) {
+ var session = manager.session;
+ var pointers = input.pointers;
+ var pointersLength = pointers.length;
+
+ // store the first input to calculate the distance and direction
+ if (!session.firstInput) {
+ session.firstInput = simpleCloneInputData(input);
+ }
+
+ // to compute scale and rotation we need to store the multiple touches
+ if (pointersLength > 1 && !session.firstMultiple) {
+ session.firstMultiple = simpleCloneInputData(input);
+ } else if (pointersLength === 1) {
+ session.firstMultiple = false;
+ }
+
+ var firstInput = session.firstInput;
+ var firstMultiple = session.firstMultiple;
+ var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
+
+ var center = input.center = getCenter(pointers);
+ input.timeStamp = now();
+ input.deltaTime = input.timeStamp - firstInput.timeStamp;
+
+ input.angle = getAngle(offsetCenter, center);
+ input.distance = getDistance(offsetCenter, center);
+
+ computeDeltaXY(session, input);
+ input.offsetDirection = getDirection(input.deltaX, input.deltaY);
+
+ var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
+ input.overallVelocityX = overallVelocity.x;
+ input.overallVelocityY = overallVelocity.y;
+ input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
+
+ input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
+ input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
+
+ input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
+ session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
+
+ computeIntervalInputData(session, input);
+
+ // find the correct target
+ var target = manager.element;
+ if (hasParent(input.srcEvent.target, target)) {
+ target = input.srcEvent.target;
+ }
+ input.target = target;
+}
+
+function computeDeltaXY(session, input) {
+ var center = input.center;
+ var offset = session.offsetDelta || {};
+ var prevDelta = session.prevDelta || {};
+ var prevInput = session.prevInput || {};
+
+ if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
+ prevDelta = session.prevDelta = {
+ x: prevInput.deltaX || 0,
+ y: prevInput.deltaY || 0
+ };
+
+ offset = session.offsetDelta = {
+ x: center.x,
+ y: center.y
+ };
+ }
+
+ input.deltaX = prevDelta.x + (center.x - offset.x);
+ input.deltaY = prevDelta.y + (center.y - offset.y);
+}
+
+/**
+ * velocity is calculated every x ms
+ * @param {Object} session
+ * @param {Object} input
+ */
+function computeIntervalInputData(session, input) {
+ var last = session.lastInterval || input,
+ deltaTime = input.timeStamp - last.timeStamp,
+ velocity, velocityX, velocityY, direction;
+
+ if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
+ var deltaX = input.deltaX - last.deltaX;
+ var deltaY = input.deltaY - last.deltaY;
+
+ var v = getVelocity(deltaTime, deltaX, deltaY);
+ velocityX = v.x;
+ velocityY = v.y;
+ velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
+ direction = getDirection(deltaX, deltaY);
+
+ session.lastInterval = input;
+ } else {
+ // use latest velocity info if it doesn't overtake a minimum period
+ velocity = last.velocity;
+ velocityX = last.velocityX;
+ velocityY = last.velocityY;
+ direction = last.direction;
+ }
+
+ input.velocity = velocity;
+ input.velocityX = velocityX;
+ input.velocityY = velocityY;
+ input.direction = direction;
+}
+
+/**
+ * create a simple clone from the input used for storage of firstInput and firstMultiple
+ * @param {Object} input
+ * @returns {Object} clonedInputData
+ */
+function simpleCloneInputData(input) {
+ // make a simple copy of the pointers because we will get a reference if we don't
+ // we only need clientXY for the calculations
+ var pointers = [];
+ var i = 0;
+ while (i < input.pointers.length) {
+ pointers[i] = {
+ clientX: round(input.pointers[i].clientX),
+ clientY: round(input.pointers[i].clientY)
+ };
+ i++;
+ }
+
+ return {
+ timeStamp: now(),
+ pointers: pointers,
+ center: getCenter(pointers),
+ deltaX: input.deltaX,
+ deltaY: input.deltaY
+ };
+}
+
+/**
+ * get the center of all the pointers
+ * @param {Array} pointers
+ * @return {Object} center contains `x` and `y` properties
+ */
+function getCenter(pointers) {
+ var pointersLength = pointers.length;
+
+ // no need to loop when only one touch
+ if (pointersLength === 1) {
+ return {
+ x: round(pointers[0].clientX),
+ y: round(pointers[0].clientY)
+ };
+ }
+
+ var x = 0, y = 0, i = 0;
+ while (i < pointersLength) {
+ x += pointers[i].clientX;
+ y += pointers[i].clientY;
+ i++;
+ }
+
+ return {
+ x: round(x / pointersLength),
+ y: round(y / pointersLength)
+ };
+}
+
+/**
+ * calculate the velocity between two points. unit is in px per ms.
+ * @param {Number} deltaTime
+ * @param {Number} x
+ * @param {Number} y
+ * @return {Object} velocity `x` and `y`
+ */
+function getVelocity(deltaTime, x, y) {
+ return {
+ x: x / deltaTime || 0,
+ y: y / deltaTime || 0
+ };
+}
+
+/**
+ * get the direction between two points
+ * @param {Number} x
+ * @param {Number} y
+ * @return {Number} direction
+ */
+function getDirection(x, y) {
+ if (x === y) {
+ return DIRECTION_NONE;
+ }
+
+ if (abs(x) >= abs(y)) {
+ return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
+ }
+ return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
+}
+
+/**
+ * calculate the absolute distance between two points
+ * @param {Object} p1 {x, y}
+ * @param {Object} p2 {x, y}
+ * @param {Array} [props] containing x and y keys
+ * @return {Number} distance
+ */
+function getDistance(p1, p2, props) {
+ if (!props) {
+ props = PROPS_XY;
+ }
+ var x = p2[props[0]] - p1[props[0]],
+ y = p2[props[1]] - p1[props[1]];
+
+ return Math.sqrt((x * x) + (y * y));
+}
+
+/**
+ * calculate the angle between two coordinates
+ * @param {Object} p1
+ * @param {Object} p2
+ * @param {Array} [props] containing x and y keys
+ * @return {Number} angle
+ */
+function getAngle(p1, p2, props) {
+ if (!props) {
+ props = PROPS_XY;
+ }
+ var x = p2[props[0]] - p1[props[0]],
+ y = p2[props[1]] - p1[props[1]];
+ return Math.atan2(y, x) * 180 / Math.PI;
+}
+
+/**
+ * calculate the rotation degrees between two pointersets
+ * @param {Array} start array of pointers
+ * @param {Array} end array of pointers
+ * @return {Number} rotation
+ */
+function getRotation(start, end) {
+ return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
+}
+
+/**
+ * calculate the scale factor between two pointersets
+ * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
+ * @param {Array} start array of pointers
+ * @param {Array} end array of pointers
+ * @return {Number} scale
+ */
+function getScale(start, end) {
+ return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
+}
+
+var MOUSE_INPUT_MAP = {
+ mousedown: INPUT_START,
+ mousemove: INPUT_MOVE,
+ mouseup: INPUT_END
+};
+
+var MOUSE_ELEMENT_EVENTS = 'mousedown';
+var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
+
+/**
+ * Mouse events input
+ * @constructor
+ * @extends Input
+ */
+function MouseInput() {
+ this.evEl = MOUSE_ELEMENT_EVENTS;
+ this.evWin = MOUSE_WINDOW_EVENTS;
+
+ this.pressed = false; // mousedown state
+
+ Input.apply(this, arguments);
+}
+
+inherit(MouseInput, Input, {
+ /**
+ * handle mouse events
+ * @param {Object} ev
+ */
+ handler: function MEhandler(ev) {
+ // console.log('==> MouseInput handler');
+ var eventType = MOUSE_INPUT_MAP[ev.type];
+
+ // on start we want to have the left mouse button down
+ if (eventType & INPUT_START && ev.button === 0) {
+ this.pressed = true;
+ }
+
+ if (eventType & INPUT_MOVE && ev.which !== 1) {
+ eventType = INPUT_END;
+ }
+
+ // mouse must be down
+ if (!this.pressed) {
+ return;
+ }
+
+ if (eventType & INPUT_END) {
+ this.pressed = false;
+ }
+
+ this.callback(this.manager, eventType, {
+ pointers: [ev],
+ changedPointers: [ev],
+ pointerType: INPUT_TYPE_MOUSE,
+ srcEvent: ev
+ });
+ }
+});
+
+var POINTER_INPUT_MAP = {
+ pointerdown: INPUT_START,
+ pointermove: INPUT_MOVE,
+ pointerup: INPUT_END,
+ pointercancel: INPUT_CANCEL,
+ pointerout: INPUT_CANCEL
+};
+
+// in IE10 the pointer types is defined as an enum
+var IE10_POINTER_TYPE_ENUM = {
+ 2: INPUT_TYPE_TOUCH,
+ 3: INPUT_TYPE_PEN,
+ 4: INPUT_TYPE_MOUSE,
+ 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
+};
+
+var POINTER_ELEMENT_EVENTS = 'pointerdown';
+var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
+
+// IE10 has prefixed support, and case-sensitive
+if (window.MSPointerEvent && !window.PointerEvent) {
+ POINTER_ELEMENT_EVENTS = 'MSPointerDown';
+ POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
+}
+
+/**
+ * Pointer events input
+ * @constructor
+ * @extends Input
+ */
+function PointerEventInput() {
+ this.evEl = POINTER_ELEMENT_EVENTS;
+ this.evWin = POINTER_WINDOW_EVENTS;
+
+ Input.apply(this, arguments);
+
+ this.store = (this.manager.session.pointerEvents = []);
+}
+
+inherit(PointerEventInput, Input, {
+ /**
+ * handle mouse events
+ * @param {Object} ev
+ */
+ handler: function PEhandler(ev) {
+ // console.log('==> PointerEventInput handler');
+ var store = this.store;
+ var removePointer = false;
+
+ var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
+ var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
+ var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
+
+ var isTouch = (pointerType == INPUT_TYPE_TOUCH);
+
+ // get index of the event in the store
+ var storeIndex = inArray(store, ev.pointerId, 'pointerId');
+
+ // start and mouse must be down
+ if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
+ if (storeIndex < 0) {
+ store.push(ev);
+ storeIndex = store.length - 1;
+ }
+ } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
+ removePointer = true;
+ }
+
+ // it not found, so the pointer hasn't been down (so it's probably a hover)
+ if (storeIndex < 0) {
+ return;
+ }
+
+ // update the event in the store
+ store[storeIndex] = ev;
+
+ this.callback(this.manager, eventType, {
+ pointers: store,
+ changedPointers: [ev],
+ pointerType: pointerType,
+ srcEvent: ev
+ });
+
+ if (removePointer) {
+ // remove from the store
+ store.splice(storeIndex, 1);
+ }
+ }
+});
+
+var SINGLE_TOUCH_INPUT_MAP = {
+ touchstart: INPUT_START,
+ touchmove: INPUT_MOVE,
+ touchend: INPUT_END,
+ touchcancel: INPUT_CANCEL
+};
+
+var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
+var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
+
+/**
+ * Touch events input
+ * @constructor
+ * @extends Input
+ */
+function SingleTouchInput() {
+ this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
+ this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
+ this.started = false;
+
+ Input.apply(this, arguments);
+}
+
+inherit(SingleTouchInput, Input, {
+ handler: function TEhandler(ev) {
+ // console.log('==> SingleTouchInput handler');
+ var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
+
+ // should we handle the touch events?
+ if (type === INPUT_START) {
+ this.started = true;
+ }
+
+ if (!this.started) {
+ return;
+ }
+
+ var touches = normalizeSingleTouches.call(this, ev, type);
+
+ // when done, reset the started state
+ if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
+ this.started = false;
+ }
+
+ this.callback(this.manager, type, {
+ pointers: touches[0],
+ changedPointers: touches[1],
+ pointerType: INPUT_TYPE_TOUCH,
+ srcEvent: ev
+ });
+ }
+});
+
+/**
+ * @this {TouchInput}
+ * @param {Object} ev
+ * @param {Number} type flag
+ * @returns {undefined|Array} [all, changed]
+ */
+function normalizeSingleTouches(ev, type) {
+ var all = toArray(ev.touches);
+ var changed = toArray(ev.changedTouches);
+
+ if (type & (INPUT_END | INPUT_CANCEL)) {
+ all = uniqueArray(all.concat(changed), 'identifier', true);
+ }
+
+ return [all, changed];
+}
+
+var TOUCH_INPUT_MAP = {
+ touchstart: INPUT_START,
+ touchmove: INPUT_MOVE,
+ touchend: INPUT_END,
+ touchcancel: INPUT_CANCEL
+};
+
+var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
+
+/**
+ * Multi-user touch events input
+ * @constructor
+ * @extends Input
+ */
+function TouchInput() {
+ this.evTarget = TOUCH_TARGET_EVENTS;
+ this.targetIds = {};
+
+ Input.apply(this, arguments);
+}
+
+inherit(TouchInput, Input, {
+ handler: function MTEhandler(ev) {
+ // console.log('==> TouchInput handler');
+ var type = TOUCH_INPUT_MAP[ev.type];
+ var touches = getTouches.call(this, ev, type);
+ if (!touches) {
+ return;
+ }
+
+ this.callback(this.manager, type, {
+ pointers: touches[0],
+ changedPointers: touches[1],
+ pointerType: INPUT_TYPE_TOUCH,
+ srcEvent: ev
+ });
+ }
+});
+
+/**
+ * @this {TouchInput}
+ * @param {Object} ev
+ * @param {Number} type flag
+ * @returns {undefined|Array} [all, changed]
+ */
+function getTouches(ev, type) {
+ var allTouches = toArray(ev.touches);
+ var targetIds = this.targetIds;
+
+ // when there is only one touch, the process can be simplified
+ if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
+ targetIds[allTouches[0].identifier] = true;
+ return [allTouches, allTouches];
+ }
+
+ var i,
+ targetTouches,
+ changedTouches = toArray(ev.changedTouches),
+ changedTargetTouches = [],
+ target = this.target;
+
+ // get target touches from touches
+ targetTouches = allTouches.filter(function(touch) {
+ return hasParent(touch.target, target);
+ });
+
+ // collect touches
+ if (type === INPUT_START) {
+ i = 0;
+ while (i < targetTouches.length) {
+ targetIds[targetTouches[i].identifier] = true;
+ i++;
+ }
+ }
+
+ // filter changed touches to only contain touches that exist in the collected target ids
+ i = 0;
+ while (i < changedTouches.length) {
+ if (targetIds[changedTouches[i].identifier]) {
+ changedTargetTouches.push(changedTouches[i]);
+ }
+
+ // cleanup removed touches
+ if (type & (INPUT_END | INPUT_CANCEL)) {
+ delete targetIds[changedTouches[i].identifier];
+ }
+ i++;
+ }
+
+ if (!changedTargetTouches.length) {
+ return;
+ }
+
+ return [
+ // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
+ uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
+ changedTargetTouches
+ ];
+}
+
+/**
+ * Combined touch and mouse input
+ *
+ * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
+ * This because touch devices also emit mouse events while doing a touch.
+ *
+ * @constructor
+ * @extends Input
+ */
+
+var DEDUP_TIMEOUT = 2500;
+var DEDUP_DISTANCE = 25;
+
+function TouchMouseInput() {
+ Input.apply(this, arguments);
+
+ var handler = bindFn(this.handler, this);
+ this.touch = new TouchInput(this.manager, handler);
+ this.mouse = new MouseInput(this.manager, handler);
+
+ this.primaryTouch = null;
+ this.lastTouches = [];
+}
+
+inherit(TouchMouseInput, Input, {
+ /**
+ * handle mouse and touch events
+ * @param {Hammer} manager
+ * @param {String} inputEvent
+ * @param {Object} inputData
+ */
+ handler: function TMEhandler(manager, inputEvent, inputData) {
+ // console.log('==> TouchMouseInput handler');
+ var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
+ isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
+
+ if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
+ return;
+ }
+
+ // when we're in a touch event, record touches to de-dupe synthetic mouse event
+ if (isTouch) {
+ recordTouches.call(this, inputEvent, inputData);
+ } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
+ return;
+ }
+
+ this.callback(manager, inputEvent, inputData);
+ },
+
+ /**
+ * remove the event listeners
+ */
+ destroy: function destroy() {
+ this.touch.destroy();
+ this.mouse.destroy();
+ }
+});
+
+function recordTouches(eventType, eventData) {
+ if (eventType & INPUT_START) {
+ this.primaryTouch = eventData.changedPointers[0].identifier;
+ setLastTouch.call(this, eventData);
+ } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
+ setLastTouch.call(this, eventData);
+ }
+}
+
+function setLastTouch(eventData) {
+ var touch = eventData.changedPointers[0];
+
+ if (touch.identifier === this.primaryTouch) {
+ var lastTouch = {x: touch.clientX, y: touch.clientY};
+ this.lastTouches.push(lastTouch);
+ var lts = this.lastTouches;
+ var removeLastTouch = function() {
+ var i = lts.indexOf(lastTouch);
+ if (i > -1) {
+ lts.splice(i, 1);
+ }
+ };
+ setTimeout(removeLastTouch, DEDUP_TIMEOUT);
+ }
+}
+
+function isSyntheticEvent(eventData) {
+ var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
+ for (var i = 0; i < this.lastTouches.length; i++) {
+ var t = this.lastTouches[i];
+ var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
+ if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
+ return true;
+ }
+ }
+ return false;
+}
+
+var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
+var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
+
+// magical touchAction value
+var TOUCH_ACTION_COMPUTE = 'compute';
+var TOUCH_ACTION_AUTO = 'auto';
+var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
+var TOUCH_ACTION_NONE = 'none';
+var TOUCH_ACTION_PAN_X = 'pan-x';
+var TOUCH_ACTION_PAN_Y = 'pan-y';
+var TOUCH_ACTION_MAP = getTouchActionProps();
+
+/**
+ * Touch Action
+ * sets the touchAction property or uses the js alternative
+ * @param {Manager} manager
+ * @param {String} value
+ * @constructor
+ */
+function TouchAction(manager, value) {
+ this.manager = manager;
+ this.set(value);
+}
+
+TouchAction.prototype = {
+ /**
+ * set the touchAction value on the element or enable the polyfill
+ * @param {String} value
+ */
+ set: function(value) {
+ // find out the touch-action by the event handlers
+ if (value == TOUCH_ACTION_COMPUTE) {
+ value = this.compute();
+ }
+
+ if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
+ this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
+ }
+ this.actions = value.toLowerCase().trim();
+ },
+
+ /**
+ * just re-set the touchAction value
+ */
+ update: function() {
+ this.set(this.manager.options.touchAction);
+ },
+
+ /**
+ * compute the value for the touchAction property based on the recognizer's settings
+ * @returns {String} value
+ */
+ compute: function() {
+ var actions = [];
+ each(this.manager.recognizers, function(recognizer) {
+ if (boolOrFn(recognizer.options.enable, [recognizer])) {
+ actions = actions.concat(recognizer.getTouchAction());
+ }
+ });
+ return cleanTouchActions(actions.join(' '));
+ },
+
+ /**
+ * this method is called on each input cycle and provides the preventing of the browser behavior
+ * @param {Object} input
+ */
+ preventDefaults: function(input) {
+ var srcEvent = input.srcEvent;
+ var direction = input.offsetDirection;
+
+ // if the touch action did prevented once this session
+ if (this.manager.session.prevented) {
+ srcEvent.preventDefault();
+ return;
+ }
+
+ var actions = this.actions;
+ var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
+ var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
+ var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
+
+ if (hasNone) {
+ //do not prevent defaults if this is a tap gesture
+
+ var isTapPointer = input.pointers.length === 1;
+ var isTapMovement = input.distance < 2;
+ var isTapTouchTime = input.deltaTime < 250;
+
+ if (isTapPointer && isTapMovement && isTapTouchTime) {
+ return;
+ }
+ }
+
+ if (hasPanX && hasPanY) {
+ // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
+ return;
+ }
+
+ if (hasNone ||
+ (hasPanY && direction & DIRECTION_HORIZONTAL) ||
+ (hasPanX && direction & DIRECTION_VERTICAL)) {
+ return this.preventSrc(srcEvent);
+ }
+ },
+
+ /**
+ * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
+ * @param {Object} srcEvent
+ */
+ preventSrc: function(srcEvent) {
+ this.manager.session.prevented = true;
+ srcEvent.preventDefault();
+ }
+};
+
+/**
+ * when the touchActions are collected they are not a valid value, so we need to clean things up. *
+ * @param {String} actions
+ * @returns {*}
+ */
+function cleanTouchActions(actions) {
+ // none
+ if (inStr(actions, TOUCH_ACTION_NONE)) {
+ return TOUCH_ACTION_NONE;
+ }
+
+ var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
+ var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
+
+ // if both pan-x and pan-y are set (different recognizers
+ // for different directions, e.g. horizontal pan but vertical swipe?)
+ // we need none (as otherwise with pan-x pan-y combined none of these
+ // recognizers will work, since the browser would handle all panning
+ if (hasPanX && hasPanY) {
+ return TOUCH_ACTION_NONE;
+ }
+
+ // pan-x OR pan-y
+ if (hasPanX || hasPanY) {
+ return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
+ }
+
+ // manipulation
+ if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
+ return TOUCH_ACTION_MANIPULATION;
+ }
+
+ return TOUCH_ACTION_AUTO;
+}
+
+function getTouchActionProps() {
+ if (!NATIVE_TOUCH_ACTION) {
+ return false;
+ }
+ var touchMap = {};
+ var cssSupports = window.CSS && window.CSS.supports;
+ ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
+
+ // If css.supports is not supported but there is native touch-action assume it supports
+ // all values. This is the case for IE 10 and 11.
+ touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
+ });
+ return touchMap;
+}
+
+/**
+ * Recognizer flow explained; *
+ * All recognizers have the initial state of POSSIBLE when an input session starts.
+ * The definition of an input session is from the first input until the last input, with all it's movement in it. *
+ * Example session for mouse-input: mousedown -> mousemove -> mouseup
+ *
+ * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
+ * which determines with state it should be.
+ *
+ * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
+ * POSSIBLE to give it another change on the next cycle.
+ *
+ * Possible
+ * |
+ * +-----+---------------+
+ * | |
+ * +-----+-----+ |
+ * | | |
+ * Failed Cancelled |
+ * +-------+------+
+ * | |
+ * Recognized Began
+ * |
+ * Changed
+ * |
+ * Ended/Recognized
+ */
+var STATE_POSSIBLE = 1;
+var STATE_BEGAN = 2;
+var STATE_CHANGED = 4;
+var STATE_ENDED = 8;
+var STATE_RECOGNIZED = STATE_ENDED;
+var STATE_CANCELLED = 16;
+var STATE_FAILED = 32;
+
+/**
+ * Recognizer
+ * Every recognizer needs to extend from this class.
+ * @constructor
+ * @param {Object} options
+ */
+function Recognizer(options) {
+ this.options = assign({}, this.defaults, options || {});
+
+ this.id = uniqueId();
+
+ this.manager = null;
+
+ // default is enable true
+ this.options.enable = ifUndefined(this.options.enable, true);
+
+ this.state = STATE_POSSIBLE;
+
+ this.simultaneous = {};
+ this.requireFail = [];
+}
+
+Recognizer.prototype = {
+ /**
+ * @virtual
+ * @type {Object}
+ */
+ defaults: {},
+
+ /**
+ * set options
+ * @param {Object} options
+ * @return {Recognizer}
+ */
+ set: function(options) {
+ assign(this.options, options);
+
+ // also update the touchAction, in case something changed about the directions/enabled state
+ this.manager && this.manager.touchAction.update();
+ return this;
+ },
+
+ /**
+ * recognize simultaneous with another recognizer.
+ * @param {Recognizer} otherRecognizer
+ * @returns {Recognizer} this
+ */
+ recognizeWith: function(otherRecognizer) {
+ if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
+ return this;
+ }
+
+ var simultaneous = this.simultaneous;
+ otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
+ if (!simultaneous[otherRecognizer.id]) {
+ simultaneous[otherRecognizer.id] = otherRecognizer;
+ otherRecognizer.recognizeWith(this);
+ }
+ return this;
+ },
+
+ /**
+ * drop the simultaneous link. It doesn't remove the link on the other recognizer.
+ * @param {Recognizer} otherRecognizer
+ * @returns {Recognizer} this
+ */
+ dropRecognizeWith: function(otherRecognizer) {
+ if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
+ return this;
+ }
+
+ otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
+ delete this.simultaneous[otherRecognizer.id];
+ return this;
+ },
+
+ /**
+ * recognizer can only run when another is failing
+ * @param {Recognizer} otherRecognizer
+ * @returns {Recognizer} this
+ */
+ requireFailure: function(otherRecognizer) {
+ if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
+ return this;
+ }
+
+ var requireFail = this.requireFail;
+ otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
+ if (inArray(requireFail, otherRecognizer) === -1) {
+ requireFail.push(otherRecognizer);
+ otherRecognizer.requireFailure(this);
+ }
+ return this;
+ },
+
+ /**
+ * drop the requireFailure link. It does not remove the link on the other recognizer.
+ * @param {Recognizer} otherRecognizer
+ * @returns {Recognizer} this
+ */
+ dropRequireFailure: function(otherRecognizer) {
+ if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
+ return this;
+ }
+
+ otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
+ var index = inArray(this.requireFail, otherRecognizer);
+ if (index > -1) {
+ this.requireFail.splice(index, 1);
+ }
+ return this;
+ },
+
+ /**
+ * has require failures boolean
+ * @returns {boolean}
+ */
+ hasRequireFailures: function() {
+ return this.requireFail.length > 0;
+ },
+
+ /**
+ * if the recognizer can recognize simultaneous with another recognizer
+ * @param {Recognizer} otherRecognizer
+ * @returns {Boolean}
+ */
+ canRecognizeWith: function(otherRecognizer) {
+ return !!this.simultaneous[otherRecognizer.id];
+ },
+
+ /**
+ * You should use `tryEmit` instead of `emit` directly to check
+ * that all the needed recognizers has failed before emitting.
+ * @param {Object} input
+ */
+ emit: function(input) {
+ var self = this;
+ var state = this.state;
+
+ function emit(event) {
+ self.manager.emit(event, input);
+ }
+
+ // 'panstart' and 'panmove'
+ if (state < STATE_ENDED) {
+ emit(self.options.event + stateStr(state));
+ }
+
+ emit(self.options.event); // simple 'eventName' events
+
+ if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
+ emit(input.additionalEvent);
+ }
+
+ // panend and pancancel
+ if (state >= STATE_ENDED) {
+ emit(self.options.event + stateStr(state));
+ }
+ },
+
+ /**
+ * Check that all the require failure recognizers has failed,
+ * if true, it emits a gesture event,
+ * otherwise, setup the state to FAILED.
+ * @param {Object} input
+ */
+ tryEmit: function(input) {
+ if (this.canEmit()) {
+ return this.emit(input);
+ }
+ // it's failing anyway
+ this.state = STATE_FAILED;
+ },
+
+ /**
+ * can we emit?
+ * @returns {boolean}
+ */
+ canEmit: function() {
+ var i = 0;
+ while (i < this.requireFail.length) {
+ if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
+ return false;
+ }
+ i++;
+ }
+ return true;
+ },
+
+ /**
+ * update the recognizer
+ * @param {Object} inputData
+ */
+ recognize: function(inputData) {
+ // make a new copy of the inputData
+ // so we can change the inputData without messing up the other recognizers
+ var inputDataClone = assign({}, inputData);
+
+ // is it enabled and allow recognizing?
+ if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
+ this.reset();
+ this.state = STATE_FAILED;
+ return;
+ }
+
+ // reset when we've reached the end
+ if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
+ this.state = STATE_POSSIBLE;
+ }
+
+ this.state = this.process(inputDataClone);
+
+ // the recognizer has recognized a gesture
+ // so trigger an event
+ if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
+ this.tryEmit(inputDataClone);
+ }
+ },
+
+ /**
+ * return the state of the recognizer
+ * the actual recognizing happens in this method
+ * @virtual
+ * @param {Object} inputData
+ * @returns {Const} STATE
+ */
+ process: function(inputData) { }, // jshint ignore:line
+
+ /**
+ * return the preferred touch-action
+ * @virtual
+ * @returns {Array}
+ */
+ getTouchAction: function() { },
+
+ /**
+ * called when the gesture isn't allowed to recognize
+ * like when another is being recognized or it is disabled
+ * @virtual
+ */
+ reset: function() { }
+};
+
+/**
+ * get a usable string, used as event postfix
+ * @param {Const} state
+ * @returns {String} state
+ */
+function stateStr(state) {
+ if (state & STATE_CANCELLED) {
+ return 'cancel';
+ } else if (state & STATE_ENDED) {
+ return 'end';
+ } else if (state & STATE_CHANGED) {
+ return 'move';
+ } else if (state & STATE_BEGAN) {
+ return 'start';
+ }
+ return '';
+}
+
+/**
+ * direction cons to string
+ * @param {Const} direction
+ * @returns {String}
+ */
+function directionStr(direction) {
+ if (direction == DIRECTION_DOWN) {
+ return 'down';
+ } else if (direction == DIRECTION_UP) {
+ return 'up';
+ } else if (direction == DIRECTION_LEFT) {
+ return 'left';
+ } else if (direction == DIRECTION_RIGHT) {
+ return 'right';
+ }
+ return '';
+}
+
+/**
+ * get a recognizer by name if it is bound to a manager
+ * @param {Recognizer|String} otherRecognizer
+ * @param {Recognizer} recognizer
+ * @returns {Recognizer}
+ */
+function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
+ var manager = recognizer.manager;
+ if (manager) {
+ return manager.get(otherRecognizer);
+ }
+ return otherRecognizer;
+}
+
+/**
+ * This recognizer is just used as a base for the simple attribute recognizers.
+ * @constructor
+ * @extends Recognizer
+ */
+function AttrRecognizer() {
+ Recognizer.apply(this, arguments);
+}
+
+inherit(AttrRecognizer, Recognizer, {
+ /**
+ * @namespace
+ * @memberof AttrRecognizer
+ */
+ defaults: {
+ /**
+ * @type {Number}
+ * @default 1
+ */
+ pointers: 1
+ },
+
+ /**
+ * Used to check if it the recognizer receives valid input, like input.distance > 10.
+ * @memberof AttrRecognizer
+ * @param {Object} input
+ * @returns {Boolean} recognized
+ */
+ attrTest: function(input) {
+ var optionPointers = this.options.pointers;
+ return optionPointers === 0 || input.pointers.length === optionPointers;
+ },
+
+ /**
+ * Process the input and return the state for the recognizer
+ * @memberof AttrRecognizer
+ * @param {Object} input
+ * @returns {*} State
+ */
+ process: function(input) {
+ var state = this.state;
+ var eventType = input.eventType;
+
+ var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
+ var isValid = this.attrTest(input);
+
+ // on cancel input and we've recognized before, return STATE_CANCELLED
+ if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
+ return state | STATE_CANCELLED;
+ } else if (isRecognized || isValid) {
+ if (eventType & INPUT_END) {
+ return state | STATE_ENDED;
+ } else if (!(state & STATE_BEGAN)) {
+ return STATE_BEGAN;
+ }
+ return state | STATE_CHANGED;
+ }
+ return STATE_FAILED;
+ }
+});
+
+/**
+ * Pan
+ * Recognized when the pointer is down and moved in the allowed direction.
+ * @constructor
+ * @extends AttrRecognizer
+ */
+function PanRecognizer() {
+ AttrRecognizer.apply(this, arguments);
+
+ this.pX = null;
+ this.pY = null;
+}
+
+inherit(PanRecognizer, AttrRecognizer, {
+ /**
+ * @namespace
+ * @memberof PanRecognizer
+ */
+ defaults: {
+ event: 'pan',
+ threshold: 10,
+ pointers: 1,
+ direction: DIRECTION_ALL
+ },
+
+ getTouchAction: function() {
+ var direction = this.options.direction;
+ var actions = [];
+ if (direction & DIRECTION_HORIZONTAL) {
+ actions.push(TOUCH_ACTION_PAN_Y);
+ }
+ if (direction & DIRECTION_VERTICAL) {
+ actions.push(TOUCH_ACTION_PAN_X);
+ }
+ return actions;
+ },
+
+ directionTest: function(input) {
+ var options = this.options;
+ var hasMoved = true;
+ var distance = input.distance;
+ var direction = input.direction;
+ var x = input.deltaX;
+ var y = input.deltaY;
+
+ // lock to axis?
+ if (!(direction & options.direction)) {
+ if (options.direction & DIRECTION_HORIZONTAL) {
+ direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
+ hasMoved = x != this.pX;
+ distance = Math.abs(input.deltaX);
+ } else {
+ direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
+ hasMoved = y != this.pY;
+ distance = Math.abs(input.deltaY);
+ }
+ }
+ input.direction = direction;
+ return hasMoved && distance > options.threshold && direction & options.direction;
+ },
+
+ attrTest: function(input) {
+ return AttrRecognizer.prototype.attrTest.call(this, input) &&
+ (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
+ },
+
+ emit: function(input) {
+
+ this.pX = input.deltaX;
+ this.pY = input.deltaY;
+
+ var direction = directionStr(input.direction);
+
+ if (direction) {
+ input.additionalEvent = this.options.event + direction;
+ }
+ this._super.emit.call(this, input);
+ }
+});
+
+/**
+ * Pinch
+ * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
+ * @constructor
+ * @extends AttrRecognizer
+ */
+function PinchRecognizer() {
+ AttrRecognizer.apply(this, arguments);
+}
+
+inherit(PinchRecognizer, AttrRecognizer, {
+ /**
+ * @namespace
+ * @memberof PinchRecognizer
+ */
+ defaults: {
+ event: 'pinch',
+ threshold: 0,
+ pointers: 2
+ },
+
+ getTouchAction: function() {
+ return [TOUCH_ACTION_NONE];
+ },
+
+ attrTest: function(input) {
+ return this._super.attrTest.call(this, input) &&
+ (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
+ },
+
+ emit: function(input) {
+ if (input.scale !== 1) {
+ var inOut = input.scale < 1 ? 'in' : 'out';
+ input.additionalEvent = this.options.event + inOut;
+ }
+ this._super.emit.call(this, input);
+ }
+});
+
+/**
+ * Press
+ * Recognized when the pointer is down for x ms without any movement.
+ * @constructor
+ * @extends Recognizer
+ */
+function PressRecognizer() {
+ Recognizer.apply(this, arguments);
+
+ this._timer = null;
+ this._input = null;
+}
+
+inherit(PressRecognizer, Recognizer, {
+ /**
+ * @namespace
+ * @memberof PressRecognizer
+ */
+ defaults: {
+ event: 'press',
+ pointers: 1,
+ time: 251, // minimal time of the pointer to be pressed
+ threshold: 9 // a minimal movement is ok, but keep it low
+ },
+
+ getTouchAction: function() {
+ return [TOUCH_ACTION_AUTO];
+ },
+
+ process: function(input) {
+ var options = this.options;
+ var validPointers = input.pointers.length === options.pointers;
+ var validMovement = input.distance < options.threshold;
+ var validTime = input.deltaTime > options.time;
+
+ this._input = input;
+
+ // we only allow little movement
+ // and we've reached an end event, so a tap is possible
+ if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
+ this.reset();
+ } else if (input.eventType & INPUT_START) {
+ this.reset();
+ this._timer = setTimeoutContext(function() {
+ this.state = STATE_RECOGNIZED;
+ this.tryEmit();
+ }, options.time, this);
+ } else if (input.eventType & INPUT_END) {
+ return STATE_RECOGNIZED;
+ }
+ return STATE_FAILED;
+ },
+
+ reset: function() {
+ clearTimeout(this._timer);
+ },
+
+ emit: function(input) {
+ if (this.state !== STATE_RECOGNIZED) {
+ return;
+ }
+
+ if (input && (input.eventType & INPUT_END)) {
+ this.manager.emit(this.options.event + 'up', input);
+ } else {
+ this._input.timeStamp = now();
+ this.manager.emit(this.options.event, this._input);
+ }
+ }
+});
+
+/**
+ * Rotate
+ * Recognized when two or more pointer are moving in a circular motion.
+ * @constructor
+ * @extends AttrRecognizer
+ */
+function RotateRecognizer() {
+ AttrRecognizer.apply(this, arguments);
+}
+
+inherit(RotateRecognizer, AttrRecognizer, {
+ /**
+ * @namespace
+ * @memberof RotateRecognizer
+ */
+ defaults: {
+ event: 'rotate',
+ threshold: 0,
+ pointers: 2
+ },
+
+ getTouchAction: function() {
+ return [TOUCH_ACTION_NONE];
+ },
+
+ attrTest: function(input) {
+ return this._super.attrTest.call(this, input) &&
+ (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
+ }
+});
+
+/**
+ * Swipe
+ * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
+ * @constructor
+ * @extends AttrRecognizer
+ */
+function SwipeRecognizer() {
+ AttrRecognizer.apply(this, arguments);
+}
+
+inherit(SwipeRecognizer, AttrRecognizer, {
+ /**
+ * @namespace
+ * @memberof SwipeRecognizer
+ */
+ defaults: {
+ event: 'swipe',
+ threshold: 10,
+ velocity: 0.3,
+ direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
+ pointers: 1
+ },
+
+ getTouchAction: function() {
+ return PanRecognizer.prototype.getTouchAction.call(this);
+ },
+
+ attrTest: function(input) {
+ var direction = this.options.direction;
+ var velocity;
+
+ if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
+ velocity = input.overallVelocity;
+ } else if (direction & DIRECTION_HORIZONTAL) {
+ velocity = input.overallVelocityX;
+ } else if (direction & DIRECTION_VERTICAL) {
+ velocity = input.overallVelocityY;
+ }
+
+ return this._super.attrTest.call(this, input) &&
+ direction & input.offsetDirection &&
+ input.distance > this.options.threshold &&
+ input.maxPointers == this.options.pointers &&
+ abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
+ },
+
+ emit: function(input) {
+ var direction = directionStr(input.offsetDirection);
+ if (direction) {
+ this.manager.emit(this.options.event + direction, input);
+ }
+
+ this.manager.emit(this.options.event, input);
+ }
+});
+
+/**
+ * A tap is recognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
+ * between the given interval and position. The delay option can be used to recognize multi-taps without firing
+ * a single tap.
+ *
+ * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
+ * multi-taps being recognized.
+ * @constructor
+ * @extends Recognizer
+ */
+function TapRecognizer() {
+ Recognizer.apply(this, arguments);
+
+ // previous time and center,
+ // used for tap counting
+ this.pTime = false;
+ this.pCenter = false;
+
+ this._timer = null;
+ this._input = null;
+ this.count = 0;
+}
+
+inherit(TapRecognizer, Recognizer, {
+ /**
+ * @namespace
+ * @memberof PinchRecognizer
+ */
+ defaults: {
+ event: 'tap',
+ pointers: 1,
+ taps: 1,
+ interval: 300, // max time between the multi-tap taps
+ time: 250, // max time of the pointer to be down (like finger on the screen)
+ threshold: 9, // a minimal movement is ok, but keep it low
+ posThreshold: 10 // a multi-tap can be a bit off the initial position
+ },
+
+ getTouchAction: function() {
+ return [TOUCH_ACTION_MANIPULATION];
+ },
+
+ process: function(input) {
+ var options = this.options;
+
+ var validPointers = input.pointers.length === options.pointers;
+ var validMovement = input.distance < options.threshold;
+ var validTouchTime = input.deltaTime < options.time;
+
+ this.reset();
+
+ if ((input.eventType & INPUT_START) && (this.count === 0)) {
+ return this.failTimeout();
+ }
+
+ // we only allow little movement
+ // and we've reached an end event, so a tap is possible
+ if (validMovement && validTouchTime && validPointers) {
+ if (input.eventType != INPUT_END) {
+ return this.failTimeout();
+ }
+
+ var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
+ var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
+
+ this.pTime = input.timeStamp;
+ this.pCenter = input.center;
+
+ if (!validMultiTap || !validInterval) {
+ this.count = 1;
+ } else {
+ this.count += 1;
+ }
+
+ this._input = input;
+
+ // if tap count matches we have recognized it,
+ // else it has begun recognizing...
+ var tapCount = this.count % options.taps;
+ if (tapCount === 0) {
+ // no failing requirements, immediately trigger the tap event
+ // or wait as long as the multitap interval to trigger
+ if (!this.hasRequireFailures()) {
+ return STATE_RECOGNIZED;
+ } else {
+ this._timer = setTimeoutContext(function() {
+ this.state = STATE_RECOGNIZED;
+ this.tryEmit();
+ }, options.interval, this);
+ return STATE_BEGAN;
+ }
+ }
+ }
+ return STATE_FAILED;
+ },
+
+ failTimeout: function() {
+ this._timer = setTimeoutContext(function() {
+ this.state = STATE_FAILED;
+ }, this.options.interval, this);
+ return STATE_FAILED;
+ },
+
+ reset: function() {
+ clearTimeout(this._timer);
+ },
+
+ emit: function() {
+ if (this.state == STATE_RECOGNIZED) {
+ this._input.tapCount = this.count;
+ this.manager.emit(this.options.event, this._input);
+ }
+ }
+});
+
+/**
+ * Simple way to create a manager with a default set of recognizers.
+ * @param {HTMLElement} element
+ * @param {Object} [options]
+ * @constructor
+ */
+function Hammer(element, options) {
+ options = options || {};
+ options.recognizers = ifUndefined(options.recognizers, Hammer.defaults.preset);
+ return new Manager(element, options);
+}
+
+/**
+ * @const {string}
+ */
+Hammer.VERSION = '2.0.7';
+
+/**
+ * default settings
+ * @namespace
+ */
+Hammer.defaults = {
+ /**
+ * set if DOM events are being triggered.
+ * But this is slower and unused by simple implementations, so disabled by default.
+ * @type {Boolean}
+ * @default false
+ */
+ domEvents: false,
+
+ /**
+ * The value for the touchAction property/fallback.
+ * When set to `compute` it will magically set the correct value based on the added recognizers.
+ * @type {String}
+ * @default compute
+ */
+ touchAction: TOUCH_ACTION_COMPUTE,
+
+ /**
+ * @type {Boolean}
+ * @default true
+ */
+ enable: true,
+
+ /**
+ * EXPERIMENTAL FEATURE -- can be removed/changed
+ * Change the parent input target element.
+ * If Null, then it is being set the to main element.
+ * @type {Null|EventTarget}
+ * @default null
+ */
+ inputTarget: null,
+
+ /**
+ * force an input class
+ * @type {Null|Function}
+ * @default null
+ */
+ inputClass: null,
+
+ /**
+ * Default recognizer setup when calling `Hammer()`
+ * When creating a new Manager these will be skipped.
+ * @type {Array}
+ */
+ preset: [
+ // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
+ [RotateRecognizer, {enable: false}],
+ [PinchRecognizer, {enable: false}, ['rotate']],
+ [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
+ [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
+ [TapRecognizer],
+ [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
+ [PressRecognizer]
+ ],
+
+ /**
+ * Some CSS properties can be used to improve the working of Hammer.
+ * Add them to this method and they will be set when creating a new Manager.
+ * @namespace
+ */
+ cssProps: {
+ /**
+ * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
+ * @type {String}
+ * @default 'none'
+ */
+ userSelect: 'none',
+
+ /**
+ * Disable the Windows Phone grippers when pressing an element.
+ * @type {String}
+ * @default 'none'
+ */
+ touchSelect: 'none',
+
+ /**
+ * Disables the default callout shown when you touch and hold a touch target.
+ * On iOS, when you touch and hold a touch target such as a link, Safari displays
+ * a callout containing information about the link. This property allows you to disable that callout.
+ * @type {String}
+ * @default 'none'
+ */
+ touchCallout: 'none',
+
+ /**
+ * Specifies whether zooming is enabled. Used by IE10>
+ * @type {String}
+ * @default 'none'
+ */
+ contentZooming: 'none',
+
+ /**
+ * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
+ * @type {String}
+ * @default 'none'
+ */
+ userDrag: 'none',
+
+ /**
+ * Overrides the highlight color shown when the user taps a link or a JavaScript
+ * clickable element in iOS. This property obeys the alpha value, if specified.
+ * @type {String}
+ * @default 'rgba(0,0,0,0)'
+ */
+ tapHighlightColor: 'rgba(0,0,0,0)'
+ }
+};
+
+var STOP = 1;
+var FORCED_STOP = 2;
+
+/**
+ * Manager
+ * @param {HTMLElement} element
+ * @param {Object} [options]
+ * @constructor
+ */
+function Manager(element, options) {
+ this.options = assign({}, Hammer.defaults, options || {});
+
+ this.options.inputTarget = this.options.inputTarget || element;
+
+ this.handlers = {};
+ this.session = {};
+ this.recognizers = [];
+ this.oldCssProps = {};
+
+ this.element = element;
+ this.input = createInputInstance(this);
+ this.touchAction = new TouchAction(this, this.options.touchAction);
+
+ toggleCssProps(this, true);
+
+ each(this.options.recognizers, function(item) {
+ var recognizer = this.add(new (item[0])(item[1]));
+ item[2] && recognizer.recognizeWith(item[2]);
+ item[3] && recognizer.requireFailure(item[3]);
+ }, this);
+}
+
+Manager.prototype = {
+ /**
+ * set options
+ * @param {Object} options
+ * @returns {Manager}
+ */
+ set: function(options) {
+ assign(this.options, options);
+
+ // Options that need a little more setup
+ if (options.touchAction) {
+ this.touchAction.update();
+ }
+ if (options.inputTarget) {
+ // Clean up existing event listeners and reinitialize
+ this.input.destroy();
+ this.input.target = options.inputTarget;
+ this.input.init();
+ }
+ return this;
+ },
+
+ /**
+ * stop recognizing for this session.
+ * This session will be discarded, when a new [input]start event is fired.
+ * When forced, the recognizer cycle is stopped immediately.
+ * @param {Boolean} [force]
+ */
+ stop: function(force) {
+ this.session.stopped = force ? FORCED_STOP : STOP;
+ },
+
+ /**
+ * run the recognizers!
+ * called by the inputHandler function on every movement of the pointers (touches)
+ * it walks through all the recognizers and tries to detect the gesture that is being made
+ * @param {Object} inputData
+ */
+ recognize: function(inputData) {
+ var session = this.session;
+ if (session.stopped) {
+ return;
+ }
+
+ // run the touch-action polyfill
+ this.touchAction.preventDefaults(inputData);
+
+ var recognizer;
+ var recognizers = this.recognizers;
+
+ // this holds the recognizer that is being recognized.
+ // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
+ // if no recognizer is detecting a thing, it is set to `null`
+ var curRecognizer = session.curRecognizer;
+
+ // reset when the last recognizer is recognized
+ // or when we're in a new session
+ if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
+ curRecognizer = session.curRecognizer = null;
+ }
+
+ var i = 0;
+ while (i < recognizers.length) {
+ recognizer = recognizers[i];
+
+ // find out if we are allowed try to recognize the input for this one.
+ // 1. allow if the session is NOT forced stopped (see the .stop() method)
+ // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
+ // that is being recognized.
+ // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
+ // this can be setup with the `recognizeWith()` method on the recognizer.
+ if (session.stopped !== FORCED_STOP && ( // 1
+ !curRecognizer || recognizer == curRecognizer || // 2
+ recognizer.canRecognizeWith(curRecognizer))) { // 3
+ recognizer.recognize(inputData);
+ } else {
+ recognizer.reset();
+ }
+
+ // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
+ // current active recognizer. but only if we don't already have an active recognizer
+ if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
+ curRecognizer = session.curRecognizer = recognizer;
+ }
+ i++;
+ }
+ },
+
+ /**
+ * get a recognizer by its event name.
+ * @param {Recognizer|String} recognizer
+ * @returns {Recognizer|Null}
+ */
+ get: function(recognizer) {
+ if (recognizer instanceof Recognizer) {
+ return recognizer;
+ }
+
+ var recognizers = this.recognizers;
+ for (var i = 0; i < recognizers.length; i++) {
+ if (recognizers[i].options.event == recognizer) {
+ return recognizers[i];
+ }
+ }
+ return null;
+ },
+
+ /**
+ * add a recognizer to the manager
+ * existing recognizers with the same event name will be removed
+ * @param {Recognizer} recognizer
+ * @returns {Recognizer|Manager}
+ */
+ add: function(recognizer) {
+ if (invokeArrayArg(recognizer, 'add', this)) {
+ return this;
+ }
+
+ // remove existing
+ var existing = this.get(recognizer.options.event);
+ if (existing) {
+ this.remove(existing);
+ }
+
+ this.recognizers.push(recognizer);
+ recognizer.manager = this;
+
+ this.touchAction.update();
+ return recognizer;
+ },
+
+ /**
+ * remove a recognizer by name or instance
+ * @param {Recognizer|String} recognizer
+ * @returns {Manager}
+ */
+ remove: function(recognizer) {
+ if (invokeArrayArg(recognizer, 'remove', this)) {
+ return this;
+ }
+
+ recognizer = this.get(recognizer);
+
+ // let's make sure this recognizer exists
+ if (recognizer) {
+ var recognizers = this.recognizers;
+ var index = inArray(recognizers, recognizer);
+
+ if (index !== -1) {
+ recognizers.splice(index, 1);
+ this.touchAction.update();
+ }
+ }
+
+ return this;
+ },
+
+ /**
+ * bind event
+ * @param {String} events
+ * @param {Function} handler
+ * @returns {EventEmitter} this
+ */
+ on: function(events, handler) {
+ if (events === undefined) {
+ return;
+ }
+ if (handler === undefined) {
+ return;
+ }
+
+ var handlers = this.handlers;
+ each(splitStr(events), function(event) {
+ handlers[event] = handlers[event] || [];
+ handlers[event].push(handler);
+ });
+ return this;
+ },
+
+ /**
+ * unbind event, leave emit blank to remove all handlers
+ * @param {String} events
+ * @param {Function} [handler]
+ * @returns {EventEmitter} this
+ */
+ off: function(events, handler) {
+ if (events === undefined) {
+ return;
+ }
+
+ var handlers = this.handlers;
+ each(splitStr(events), function(event) {
+ if (!handler) {
+ delete handlers[event];
+ } else {
+ handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
+ }
+ });
+ return this;
+ },
+
+ /**
+ * emit event to the listeners
+ * @param {String} event
+ * @param {Object} data
+ */
+ emit: function(event, data) {
+ // we also want to trigger dom events
+ if (this.options.domEvents) {
+ triggerDomEvent(event, data);
+ }
+
+ // no handlers, so skip it all
+ var handlers = this.handlers[event] && this.handlers[event].slice();
+ if (!handlers || !handlers.length) {
+ return;
+ }
+
+ data.type = event;
+ data.preventDefault = function() {
+ data.srcEvent.preventDefault();
+ };
+
+ var i = 0;
+ while (i < handlers.length) {
+ handlers[i](data);
+ i++;
+ }
+ },
+
+ /**
+ * destroy the manager and unbinds all events
+ * it doesn't unbind dom events, that is the user own responsibility
+ */
+ destroy: function() {
+ this.element && toggleCssProps(this, false);
+
+ this.handlers = {};
+ this.session = {};
+ this.input.destroy();
+ this.element = null;
+ }
+};
+
+/**
+ * add/remove the css properties as defined in manager.options.cssProps
+ * @param {Manager} manager
+ * @param {Boolean} add
+ */
+function toggleCssProps(manager, add) {
+ var element = manager.element;
+ if (!element.style) {
+ return;
+ }
+ var prop;
+ each(manager.options.cssProps, function(value, name) {
+ prop = prefixed(element.style, name);
+ if (add) {
+ manager.oldCssProps[prop] = element.style[prop];
+ element.style[prop] = value;
+ } else {
+ element.style[prop] = manager.oldCssProps[prop] || '';
+ }
+ });
+ if (!add) {
+ manager.oldCssProps = {};
+ }
+}
+
+/**
+ * trigger dom event
+ * @param {String} event
+ * @param {Object} data
+ */
+function triggerDomEvent(event, data) {
+ var gestureEvent = document.createEvent('Event');
+ gestureEvent.initEvent(event, true, true);
+ gestureEvent.gesture = data;
+ data.target.dispatchEvent(gestureEvent);
+}
+
+assign(Hammer, {
+ INPUT_START: INPUT_START,
+ INPUT_MOVE: INPUT_MOVE,
+ INPUT_END: INPUT_END,
+ INPUT_CANCEL: INPUT_CANCEL,
+
+ STATE_POSSIBLE: STATE_POSSIBLE,
+ STATE_BEGAN: STATE_BEGAN,
+ STATE_CHANGED: STATE_CHANGED,
+ STATE_ENDED: STATE_ENDED,
+ STATE_RECOGNIZED: STATE_RECOGNIZED,
+ STATE_CANCELLED: STATE_CANCELLED,
+ STATE_FAILED: STATE_FAILED,
+
+ DIRECTION_NONE: DIRECTION_NONE,
+ DIRECTION_LEFT: DIRECTION_LEFT,
+ DIRECTION_RIGHT: DIRECTION_RIGHT,
+ DIRECTION_UP: DIRECTION_UP,
+ DIRECTION_DOWN: DIRECTION_DOWN,
+ DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
+ DIRECTION_VERTICAL: DIRECTION_VERTICAL,
+ DIRECTION_ALL: DIRECTION_ALL,
+
+ Manager: Manager,
+ Input: Input,
+ TouchAction: TouchAction,
+
+ TouchInput: TouchInput,
+ MouseInput: MouseInput,
+ PointerEventInput: PointerEventInput,
+ TouchMouseInput: TouchMouseInput,
+ SingleTouchInput: SingleTouchInput,
+
+ Recognizer: Recognizer,
+ AttrRecognizer: AttrRecognizer,
+ Tap: TapRecognizer,
+ Pan: PanRecognizer,
+ Swipe: SwipeRecognizer,
+ Pinch: PinchRecognizer,
+ Rotate: RotateRecognizer,
+ Press: PressRecognizer,
+
+ on: addEventListeners,
+ off: removeEventListeners,
+ each: each,
+ merge: merge,
+ extend: extend,
+ assign: assign,
+ inherit: inherit,
+ bindFn: bindFn,
+ prefixed: prefixed
+});
+
+// this prevents errors when Hammer is loaded in the presence of an AMD
+// style loader but by script tag, not by the loader.
+var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
+freeGlobal.Hammer = Hammer;
+
+if (typeof define === 'function' && define.amd) {
+ define(function() {
+ return Hammer;
+ });
+} else if (typeof module != 'undefined' && module.exports) {
+ module.exports = Hammer;
+} else {
+ window[exportName] = Hammer;
+}
+
+})(window, document, 'Hammer');
+
+/*****
+ * @licstart
+ *
+ * The following is the license notice for the part of JavaScript code of this
+ * page included between the '@jessyinkstart' and the '@jessyinkend' notes.
+ */
+
+/***** ******************************************************************
+ *
+ * Copyright 2008-2013 Hannes Hochreiner
+ *
+ * The JavaScript code included between the start note '@jessyinkstart'
+ * and the end note '@jessyinkend' is subject to the terms of the Mozilla
+ * Public License, v. 2.0. If a copy of the MPL was not distributed with
+ * this file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Alternatively, you can redistribute and/or that part of this file
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/.
+*/
+
+/*****
+ * You can find the complete source code of the JessyInk project at:
+ * @source http://code.google.com/p/jessyink/
+ */
+
+/*****
+ * @licend
+ *
+ * The above is the license notice for the part of JavaScript code of this
+ * page included between the '@jessyinkstart' and the '@jessyinkend' notes.
+ */
+
+
+
+/*****
+ * @jessyinkstart
+ *
+ * The following code is a derivative work of some parts of the JessyInk
+ * project.
+ * @source http://code.google.com/p/jessyink/
+ */
+
+/** Convenience function to get an element depending on whether it has a
+ * property with a particular name.
+ *
+ * @param node element of the document
+ * @param name attribute name
+ *
+ * @returns Array array containing all the elements of the tree with root
+ * 'node' that own the property 'name'
+ */
+function getElementsByProperty( node, name )
+{
+ var elements = [];
+
+ if( node.getAttribute( name ) )
+ elements.push( node );
+
+ for( var counter = 0; counter < node.childNodes.length; ++counter )
+ {
+ if( node.childNodes[counter].nodeType == 1 )
+ {
+ var subElements = getElementsByProperty( node.childNodes[counter], name );
+ elements = elements.concat( subElements );
+ }
+ }
+ return elements;
+}
+
+/** Event handler for key press.
+ *
+ * @param aEvt the event
+ */
+function onKeyDown( aEvt )
+{
+ if ( !aEvt )
+ aEvt = window.event;
+
+ var code = aEvt.keyCode || aEvt.charCode;
+
+ // console.log('===> onKeyDown: ' + code);
+
+ // Handle arrow keys in iOS WebKit (including Mobile Safari)
+ if (code == 0 && aEvt.key != undefined) {
+ switch (aEvt.key) {
+ case 'UIKeyInputLeftArrow':
+ code = LEFT_KEY;
+ break;
+ case 'UIKeyInputUpArrow':
+ code = UP_KEY;
+ break;
+ case 'UIKeyInputRightArrow':
+ code = RIGHT_KEY;
+ break;
+ case 'UIKeyInputDownArrow':
+ code = DOWN_KEY;
+ break;
+ }
+
+ // console.log(' now: ' + code);
+ }
+
+ if( !processingEffect && keyCodeDictionary[currentMode] && keyCodeDictionary[currentMode][code] )
+ {
+ return keyCodeDictionary[currentMode][code]();
+ }
+ else
+ {
+ document.onkeypress = onKeyPress;
+ return null;
+ }
+}
+//Set event handler for key down.
+document.onkeydown = onKeyDown;
+
+/** Event handler for key press.
+ *
+ * @param aEvt the event
+ */
+function onKeyPress( aEvt )
+{
+ document.onkeypress = null;
+
+ if ( !aEvt )
+ aEvt = window.event;
+
+ var str = String.fromCharCode( aEvt.keyCode || aEvt.charCode );
+
+ if ( !processingEffect && charCodeDictionary[currentMode] && charCodeDictionary[currentMode][str] )
+ return charCodeDictionary[currentMode][str]();
+
+ return null;
+}
+
+/** Function to supply the default key code dictionary.
+ *
+ * @returns Object default key code dictionary
+ */
+function getDefaultKeyCodeDictionary()
+{
+ var keyCodeDict = {};
+
+ keyCodeDict[SLIDE_MODE] = {};
+ keyCodeDict[INDEX_MODE] = {};
+
+ // slide mode
+ keyCodeDict[SLIDE_MODE][LEFT_KEY]
+ = function() { return aSlideShow.rewindEffect(); };
+ keyCodeDict[SLIDE_MODE][RIGHT_KEY]
+ = function() { return dispatchEffects(1); };
+ keyCodeDict[SLIDE_MODE][UP_KEY]
+ = function() { return aSlideShow.rewindEffect(); };
+ keyCodeDict[SLIDE_MODE][DOWN_KEY]
+ = function() { return skipEffects(1); };
+ keyCodeDict[SLIDE_MODE][PAGE_UP_KEY]
+ = function() { return aSlideShow.rewindAllEffects(); };
+ keyCodeDict[SLIDE_MODE][PAGE_DOWN_KEY]
+ = function() { return skipAllEffects(); };
+ keyCodeDict[SLIDE_MODE][HOME_KEY]
+ = function() { return aSlideShow.displaySlide( 0, true ); };
+ keyCodeDict[SLIDE_MODE][END_KEY]
+ = function() { return aSlideShow.displaySlide( theMetaDoc.nNumberOfSlides - 1, true ); };
+ keyCodeDict[SLIDE_MODE][SPACE_KEY]
+ = function() { return dispatchEffects(1); };
+ // The ESC key can't actually be handled on iOS, it seems to be hardcoded to work like the home button? But try anyway.
+ keyCodeDict[SLIDE_MODE][ESCAPE_KEY]
+ = function() { return aSlideShow.exitSlideShowInApp(); };
+ keyCodeDict[SLIDE_MODE][Q_KEY]
+ = function() { return aSlideShow.exitSlideShowInApp(); };
+
+ // index mode
+ keyCodeDict[INDEX_MODE][LEFT_KEY]
+ = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex - 1 ); };
+ keyCodeDict[INDEX_MODE][RIGHT_KEY]
+ = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex + 1 ); };
+ keyCodeDict[INDEX_MODE][UP_KEY]
+ = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex - theSlideIndexPage.indexColumns ); };
+ keyCodeDict[INDEX_MODE][DOWN_KEY]
+ = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex + theSlideIndexPage.indexColumns ); };
+ keyCodeDict[INDEX_MODE][PAGE_UP_KEY]
+ = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex - theSlideIndexPage.getTotalThumbnails() ); };
+ keyCodeDict[INDEX_MODE][PAGE_DOWN_KEY]
+ = function() { return indexSetPageSlide( theSlideIndexPage.selectedSlideIndex + theSlideIndexPage.getTotalThumbnails() ); };
+ keyCodeDict[INDEX_MODE][HOME_KEY]
+ = function() { return indexSetPageSlide( 0 ); };
+ keyCodeDict[INDEX_MODE][END_KEY]
+ = function() { return indexSetPageSlide( theMetaDoc.nNumberOfSlides - 1 ); };
+ keyCodeDict[INDEX_MODE][ENTER_KEY]
+ = function() { return toggleSlideIndex(); };
+ keyCodeDict[INDEX_MODE][SPACE_KEY]
+ = function() { return toggleSlideIndex(); };
+ keyCodeDict[INDEX_MODE][ESCAPE_KEY]
+ = function() { return abandonIndexMode(); };
+
+ return keyCodeDict;
+}
+
+/** Function to supply the default char code dictionary.
+ *
+ * @returns Object char code dictionary
+ */
+function getDefaultCharCodeDictionary()
+{
+ var charCodeDict = {};
+
+ charCodeDict[SLIDE_MODE] = {};
+ charCodeDict[INDEX_MODE] = {};
+
+ // slide mode
+ charCodeDict[SLIDE_MODE]['i']
+ = function () { return toggleSlideIndex(); };
+
+ // index mode
+ charCodeDict[INDEX_MODE]['i']
+ = function () { return toggleSlideIndex(); };
+ charCodeDict[INDEX_MODE]['-']
+ = function () { return theSlideIndexPage.decreaseNumberOfColumns(); };
+ charCodeDict[INDEX_MODE]['=']
+ = function () { return theSlideIndexPage.increaseNumberOfColumns(); };
+ charCodeDict[INDEX_MODE]['+']
+ = function () { return theSlideIndexPage.increaseNumberOfColumns(); };
+ charCodeDict[INDEX_MODE]['0']
+ = function () { return theSlideIndexPage.resetNumberOfColumns(); };
+
+ return charCodeDict;
+}
+
+
+function slideOnMouseUp( aEvt )
+{
+ if (!aEvt)
+ aEvt = window.event;
+
+ var nOffset = 0;
+
+ if( aEvt.button == 0 )
+ nOffset = 1;
+ else if( aEvt.button == 2 )
+ nOffset = -1;
+
+ if( 0 != nOffset )
+ dispatchEffects( nOffset );
+ return true; // the click has been handled
+}
+
+document.handleClick = slideOnMouseUp;
+
+
+/** Event handler for mouse wheel events in slide mode.
+ * based on http://adomas.org/javascript-mouse-wheel/
+ *
+ * @param aEvt the event
+ */
+function slideOnMouseWheel(aEvt)
+{
+ var delta = 0;
+
+ if (!aEvt)
+ aEvt = window.event;
+
+ if (aEvt.wheelDelta)
+ { // IE Opera
+ delta = aEvt.wheelDelta/120;
+ }
+ else if (aEvt.detail)
+ { // MOZ
+ delta = -aEvt.detail/3;
+ }
+
+ if (delta > 0)
+ skipEffects(-1);
+ else if (delta < 0)
+ skipEffects(1);
+
+ if (aEvt.preventDefault)
+ aEvt.preventDefault();
+
+ aEvt.returnValue = false;
+}
+
+//Mozilla
+if( window.addEventListener )
+{
+ window.addEventListener( 'DOMMouseScroll', function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_WHEEL ); }, false );
+}
+
+//Opera Safari OK - may not work in IE
+window.onmousewheel
+ = function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_WHEEL ); };
+
+/** Function to handle all mouse events.
+ *
+ * @param aEvt event
+ * @param anAction type of event (e.g. mouse up, mouse wheel)
+ */
+function mouseHandlerDispatch( aEvt, anAction )
+{
+ if( !aEvt )
+ aEvt = window.event;
+
+ var retVal = true;
+
+ if ( mouseHandlerDictionary[currentMode] && mouseHandlerDictionary[currentMode][anAction] )
+ {
+ var subRetVal = mouseHandlerDictionary[currentMode][anAction]( aEvt );
+
+ if( subRetVal != null && subRetVal != undefined )
+ retVal = subRetVal;
+ }
+
+ if( aEvt.preventDefault && !retVal )
+ aEvt.preventDefault();
+
+ aEvt.returnValue = retVal;
+
+ return retVal;
+}
+
+//Set mouse event handler.
+document.onmouseup = function( aEvt ) { return mouseHandlerDispatch( aEvt, MOUSE_UP ); };
+
+
+/** mouseClickHelper
+ *
+ * @return {Object}
+ * a mouse click handler
+ */
+function mouseClickHelper( aEvt )
+{
+ // In case text is selected we stay on the current slide.
+ // Anyway if we are dealing with Firefox there is an issue:
+ // Firefox supports a naive way of selecting svg text, if you click
+ // on text the current selection is set to the whole text fragment
+ // wrapped by the related <tspan> element.
+ // That means until you click on text you never move to the next slide.
+ // In order to avoid this case we do not test the status of current
+ // selection, when the presentation is running on a mozilla browser.
+ if( !Detect.isMozilla )
+ {
+ var aWindowObject = document.defaultView;
+ if( aWindowObject )
+ {
+ var aTextSelection = aWindowObject.getSelection();
+ var sSelectedText = aTextSelection.toString();
+ if( sSelectedText )
+ {
+ DBGLOG( 'text selection: ' + sSelectedText );
+ if( sLastSelectedText !== sSelectedText )
+ {
+ bTextHasBeenSelected = true;
+ sLastSelectedText = sSelectedText;
+ }
+ else
+ {
+ bTextHasBeenSelected = false;
+ }
+ return null;
+ }
+ else if( bTextHasBeenSelected )
+ {
+ bTextHasBeenSelected = false;
+ sLastSelectedText = '';
+ return null;
+ }
+ }
+ else
+ {
+ log( 'error: HyperlinkElement.handleClick: invalid window object.' );
+ }
+ }
+
+ var aSlideAnimationsHandler = theMetaDoc.aMetaSlideSet[nCurSlide].aSlideAnimationsHandler;
+ if( aSlideAnimationsHandler )
+ {
+ var aCurrentEventMultiplexer = aSlideAnimationsHandler.aEventMultiplexer;
+ if( aCurrentEventMultiplexer )
+ {
+ if( aCurrentEventMultiplexer.hasRegisteredMouseClickHandlers() )
+ {
+ return aCurrentEventMultiplexer.notifyMouseClick( aEvt );
+ }
+ }
+ }
+ return slideOnMouseUp( aEvt );
+}
+
+
+/** Function to supply the default mouse handler dictionary.
+ *
+ * @returns Object default mouse handler dictionary
+ */
+function getDefaultMouseHandlerDictionary()
+{
+ var mouseHandlerDict = {};
+
+ mouseHandlerDict[SLIDE_MODE] = {};
+ mouseHandlerDict[INDEX_MODE] = {};
+
+ // slide mode
+ mouseHandlerDict[SLIDE_MODE][MOUSE_UP]
+ = mouseClickHelper;
+
+ mouseHandlerDict[SLIDE_MODE][MOUSE_WHEEL]
+ = function( aEvt ) { return slideOnMouseWheel( aEvt ); };
+
+ // index mode
+ mouseHandlerDict[INDEX_MODE][MOUSE_UP]
+ = function( ) { return toggleSlideIndex(); };
+
+ return mouseHandlerDict;
+}
+
+/** Function to set the page and active slide in index view.
+ *
+ * @param nIndex index of the active slide
+ *
+ * NOTE: To force a redraw,
+ * set INDEX_OFFSET to -1 before calling indexSetPageSlide().
+ *
+ * This is necessary for zooming (otherwise the index might not
+ * get redrawn) and when switching to index mode.
+ *
+ * INDEX_OFFSET = -1
+ * indexSetPageSlide(activeSlide);
+ */
+function indexSetPageSlide( nIndex )
+{
+ var aMetaSlideSet = theMetaDoc.aMetaSlideSet;
+ nIndex = getSafeIndex( nIndex, 0, aMetaSlideSet.length - 1 );
+
+ //calculate the offset
+ var nSelectedThumbnailIndex = nIndex % theSlideIndexPage.getTotalThumbnails();
+ var offset = nIndex - nSelectedThumbnailIndex;
+
+ if( offset < 0 )
+ offset = 0;
+
+ //if different from kept offset, then record and change the page
+ if( offset != INDEX_OFFSET )
+ {
+ INDEX_OFFSET = offset;
+ displayIndex( INDEX_OFFSET );
+ }
+
+ //set the selected thumbnail and the current slide
+ theSlideIndexPage.setSelection( nSelectedThumbnailIndex );
+}
+
+
+/*****
+ * @jessyinkend
+ *
+ * The above code is a derivative work of some parts of the JessyInk project.
+ * @source http://code.google.com/p/jessyink/
+ */
+
+
+
+
+
+/*****
+ * @licstart
+ *
+ * The following is the license notice for the part of JavaScript code of this
+ * page included between the '@dojostart' and the '@dojoend' notes.
+ */
+
+/***** **********************************************************************
+ *
+ * The 'New' BSD License:
+ * **********************
+ * Copyright (c) 2005-2012, The Dojo Foundation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * * Neither the name of the Dojo Foundation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+
+/*****
+ * @licend
+ *
+ * The above is the license notice for the part of JavaScript code of this
+ * page included between the '@dojostart' and the '@dojoend' notes.
+ */
+
+
+
+/*****
+ * @dojostart
+ *
+ * The following code is a derivative work of some part of the dojox.gfx library.
+ * @source http://svn.dojotoolkit.org/src/dojox/trunk/_base/sniff.js
+ */
+
+function has( name )
+{
+ return has.cache[name];
+}
+
+has.cache = {};
+
+has.add = function( name, test )
+{
+ has.cache[name] = test;
+};
+
+function configureDetectionTools()
+{
+ if( !navigator )
+ {
+ log( 'error: configureDetectionTools: configuration failed' );
+ return null;
+ }
+
+ var n = navigator,
+ dua = n.userAgent,
+ dav = n.appVersion,
+ tv = parseFloat(dav);
+
+ has.add('air', dua.indexOf('AdobeAIR') >= 0);
+ has.add('khtml', dav.indexOf('Konqueror') >= 0 ? tv : undefined);
+ has.add('webkit', parseFloat(dua.split('WebKit/')[1]) || undefined);
+ has.add('chrome', parseFloat(dua.split('Chrome/')[1]) || undefined);
+ has.add('safari', dav.indexOf('Safari')>=0 && !has('chrome') ? parseFloat(dav.split('Version/')[1]) : undefined);
+ has.add('mac', dav.indexOf('Macintosh') >= 0);
+ has.add('quirks', document.compatMode == 'BackCompat');
+ has.add('ios', /iPhone|iPod|iPad/.test(dua));
+ has.add('android', parseFloat(dua.split('Android ')[1]) || undefined);
+
+ if(!has('webkit')){
+ // Opera
+ if(dua.indexOf('Opera') >= 0){
+ // see http://dev.opera.com/articles/view/opera-ua-string-changes and http://www.useragentstring.com/pages/Opera/
+ // 9.8 has both styles; <9.8, 9.9 only old style
+ has.add('opera', tv >= 9.8 ? parseFloat(dua.split('Version/')[1]) || tv : tv);
+ }
+
+ // Mozilla and firefox
+ if(dua.indexOf('Gecko') >= 0 && !has('khtml') && !has('webkit')){
+ has.add('mozilla', tv);
+ }
+ if(has('mozilla')){
+ //We really need to get away from this. Consider a sane isGecko approach for the future.
+ has.add('ff', parseFloat(dua.split('Firefox/')[1] || dua.split('Minefield/')[1]) || undefined);
+ }
+
+ // IE
+ if(document.all && !has('opera')){
+ var isIE = parseFloat(dav.split('MSIE ')[1]) || undefined;
+
+ //In cases where the page has an HTTP header or META tag with
+ //X-UA-Compatible, then it is in emulation mode.
+ //Make sure isIE reflects the desired version.
+ //document.documentMode of 5 means quirks mode.
+ //Only switch the value if documentMode's major version
+ //is different from isIE major version.
+ var mode = document.documentMode;
+ if(mode && mode != 5 && Math.floor(isIE) != mode){
+ isIE = mode;
+ }
+
+ has.add('ie', isIE);
+ }
+
+ // Wii
+ has.add('wii', typeof opera != 'undefined' && opera.wiiremote);
+ }
+
+ var detect =
+ {
+ // isFF: Number|undefined
+ // Version as a Number if client is FireFox. undefined otherwise. Corresponds to
+ // major detected FireFox version (1.5, 2, 3, etc.)
+ isFF: has('ff'),
+
+ // isIE: Number|undefined
+ // Version as a Number if client is MSIE(PC). undefined otherwise. Corresponds to
+ // major detected IE version (6, 7, 8, etc.)
+ isIE: has('ie'),
+
+ // isKhtml: Number|undefined
+ // Version as a Number if client is a KHTML browser. undefined otherwise. Corresponds to major
+ // detected version.
+ isKhtml: has('khtml'),
+
+ // isWebKit: Number|undefined
+ // Version as a Number if client is a WebKit-derived browser (Konqueror,
+ // Safari, Chrome, etc.). undefined otherwise.
+ isWebKit: has('webkit'),
+
+ // isMozilla: Number|undefined
+ // Version as a Number if client is a Mozilla-based browser (Firefox,
+ // SeaMonkey). undefined otherwise. Corresponds to major detected version.
+ isMozilla: has('mozilla'),
+ // isMoz: Number|undefined
+ // Version as a Number if client is a Mozilla-based browser (Firefox,
+ // SeaMonkey). undefined otherwise. Corresponds to major detected version.
+ isMoz: has('mozilla'),
+
+ // isOpera: Number|undefined
+ // Version as a Number if client is Opera. undefined otherwise. Corresponds to
+ // major detected version.
+ isOpera: has('opera'),
+
+ // isSafari: Number|undefined
+ // Version as a Number if client is Safari or iPhone. undefined otherwise.
+ isSafari: has('safari'),
+
+ // isChrome: Number|undefined
+ // Version as a Number if client is Chrome browser. undefined otherwise.
+ isChrome: has('chrome'),
+
+ // isMac: Boolean
+ // True if the client runs on Mac
+ isMac: has('mac'),
+
+ // isIos: Boolean
+ // True if client is iPhone, iPod, or iPad
+ isIos: has('ios'),
+
+ // isAndroid: Number|undefined
+ // Version as a Number if client is android browser. undefined otherwise.
+ isAndroid: has('android'),
+
+ // isWii: Boolean
+ // True if client is Wii
+ isWii: has('wii'),
+
+ // isQuirks: Boolean
+ // Page is in quirks mode.
+ isQuirks: has('quirks'),
+
+ // isAir: Boolean
+ // True if client is Adobe Air
+ isAir: has('air')
+ };
+ return detect;
+}
+
+/*****
+ * @dojoend
+ *
+ * The above code is a derivative work of some part of the dojox.gfx library.
+ * @source http://svn.dojotoolkit.org/src/dojox/trunk/_base/sniff.js
+ */
+
+/*****
+ * @licstart
+ *
+ * The following is the license notice for the part of JavaScript code of this
+ * file included between the '@svgpathstart' and the '@svgpathend' notes.
+ */
+
+/***** **********************************************************************
+ *
+ * Copyright 2015 The Chromium Authors. All rights reserved.
+ *
+ * The Chromium Authors can be found at
+ * http://src.chromium.org/svn/trunk/src/AUTHORS
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+/*****
+ * @licend
+ *
+ * The above is the license notice for the part of JavaScript code of this
+ * file included between the '@svgpathstart' and the '@svgpathend' notes.
+ */
+
+
+/*****
+ * @svgpathstart
+ *
+ * The following code is a derivative work of some part of the SVGPathSeg API.
+ *
+ * This API is a drop-in replacement for the SVGPathSeg and SVGPathSegList APIs that were removed from
+ * SVG2 (https://lists.w3.org/Archives/Public/www-svg/2015Jun/0044.html), including the latest spec
+ * changes which were implemented in Firefox 43 and Chrome 46.
+ *
+ * @source https://github.com/progers/pathseg
+ */
+
+(function() { 'use strict';
+ if (!('SVGPathSeg' in window)) {
+ // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSeg
+ window.SVGPathSeg = function(type, typeAsLetter, owningPathSegList) {
+ this.pathSegType = type;
+ this.pathSegTypeAsLetter = typeAsLetter;
+ this._owningPathSegList = owningPathSegList;
+ }
+
+ window.SVGPathSeg.prototype.classname = 'SVGPathSeg';
+
+ window.SVGPathSeg.PATHSEG_UNKNOWN = 0;
+ window.SVGPathSeg.PATHSEG_CLOSEPATH = 1;
+ window.SVGPathSeg.PATHSEG_MOVETO_ABS = 2;
+ window.SVGPathSeg.PATHSEG_MOVETO_REL = 3;
+ window.SVGPathSeg.PATHSEG_LINETO_ABS = 4;
+ window.SVGPathSeg.PATHSEG_LINETO_REL = 5;
+ window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS = 6;
+ window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL = 7;
+ window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS = 8;
+ window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL = 9;
+ window.SVGPathSeg.PATHSEG_ARC_ABS = 10;
+ window.SVGPathSeg.PATHSEG_ARC_REL = 11;
+ window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS = 12;
+ window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL = 13;
+ window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS = 14;
+ window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL = 15;
+ window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS = 16;
+ window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL = 17;
+ window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS = 18;
+ window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL = 19;
+
+ // Notify owning PathSegList on any changes so they can be synchronized back to the path element.
+ window.SVGPathSeg.prototype._segmentChanged = function() {
+ if (this._owningPathSegList)
+ this._owningPathSegList.segmentChanged(this);
+ }
+
+ window.SVGPathSegClosePath = function(owningPathSegList) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CLOSEPATH, 'z', owningPathSegList);
+ }
+ window.SVGPathSegClosePath.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegClosePath.prototype.toString = function() { return '[object SVGPathSegClosePath]'; }
+ window.SVGPathSegClosePath.prototype._asPathString = function() { return this.pathSegTypeAsLetter; }
+ window.SVGPathSegClosePath.prototype.clone = function() { return new window.SVGPathSegClosePath(undefined); }
+
+ window.SVGPathSegMovetoAbs = function(owningPathSegList, x, y) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_MOVETO_ABS, 'M', owningPathSegList);
+ this._x = x;
+ this._y = y;
+ }
+ window.SVGPathSegMovetoAbs.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegMovetoAbs.prototype.toString = function() { return '[object SVGPathSegMovetoAbs]'; }
+ window.SVGPathSegMovetoAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
+ window.SVGPathSegMovetoAbs.prototype.clone = function() { return new window.SVGPathSegMovetoAbs(undefined, this._x, this._y); }
+ Object.defineProperty(window.SVGPathSegMovetoAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegMovetoAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegMovetoRel = function(owningPathSegList, x, y) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_MOVETO_REL, 'm', owningPathSegList);
+ this._x = x;
+ this._y = y;
+ }
+ window.SVGPathSegMovetoRel.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegMovetoRel.prototype.toString = function() { return '[object SVGPathSegMovetoRel]'; }
+ window.SVGPathSegMovetoRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
+ window.SVGPathSegMovetoRel.prototype.clone = function() { return new window.SVGPathSegMovetoRel(undefined, this._x, this._y); }
+ Object.defineProperty(window.SVGPathSegMovetoRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegMovetoRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegLinetoAbs = function(owningPathSegList, x, y) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_ABS, 'L', owningPathSegList);
+ this._x = x;
+ this._y = y;
+ }
+ window.SVGPathSegLinetoAbs.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegLinetoAbs.prototype.toString = function() { return '[object SVGPathSegLinetoAbs]'; }
+ window.SVGPathSegLinetoAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
+ window.SVGPathSegLinetoAbs.prototype.clone = function() { return new window.SVGPathSegLinetoAbs(undefined, this._x, this._y); }
+ Object.defineProperty(window.SVGPathSegLinetoAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegLinetoAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegLinetoRel = function(owningPathSegList, x, y) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_REL, 'l', owningPathSegList);
+ this._x = x;
+ this._y = y;
+ }
+ window.SVGPathSegLinetoRel.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegLinetoRel.prototype.toString = function() { return '[object SVGPathSegLinetoRel]'; }
+ window.SVGPathSegLinetoRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
+ window.SVGPathSegLinetoRel.prototype.clone = function() { return new window.SVGPathSegLinetoRel(undefined, this._x, this._y); }
+ Object.defineProperty(window.SVGPathSegLinetoRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegLinetoRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegCurvetoCubicAbs = function(owningPathSegList, x, y, x1, y1, x2, y2) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS, 'C', owningPathSegList);
+ this._x = x;
+ this._y = y;
+ this._x1 = x1;
+ this._y1 = y1;
+ this._x2 = x2;
+ this._y2 = y2;
+ }
+ window.SVGPathSegCurvetoCubicAbs.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegCurvetoCubicAbs.prototype.toString = function() { return '[object SVGPathSegCurvetoCubicAbs]'; }
+ window.SVGPathSegCurvetoCubicAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
+ window.SVGPathSegCurvetoCubicAbs.prototype.clone = function() { return new window.SVGPathSegCurvetoCubicAbs(undefined, this._x, this._y, this._x1, this._y1, this._x2, this._y2); }
+ Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'x1', { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'y1', { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'x2', { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype, 'y2', { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegCurvetoCubicRel = function(owningPathSegList, x, y, x1, y1, x2, y2) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL, 'c', owningPathSegList);
+ this._x = x;
+ this._y = y;
+ this._x1 = x1;
+ this._y1 = y1;
+ this._x2 = x2;
+ this._y2 = y2;
+ }
+ window.SVGPathSegCurvetoCubicRel.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegCurvetoCubicRel.prototype.toString = function() { return '[object SVGPathSegCurvetoCubicRel]'; }
+ window.SVGPathSegCurvetoCubicRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
+ window.SVGPathSegCurvetoCubicRel.prototype.clone = function() { return new window.SVGPathSegCurvetoCubicRel(undefined, this._x, this._y, this._x1, this._y1, this._x2, this._y2); }
+ Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'x1', { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'y1', { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'x2', { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype, 'y2', { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegCurvetoQuadraticAbs = function(owningPathSegList, x, y, x1, y1) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS, 'Q', owningPathSegList);
+ this._x = x;
+ this._y = y;
+ this._x1 = x1;
+ this._y1 = y1;
+ }
+ window.SVGPathSegCurvetoQuadraticAbs.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegCurvetoQuadraticAbs.prototype.toString = function() { return '[object SVGPathSegCurvetoQuadraticAbs]'; }
+ window.SVGPathSegCurvetoQuadraticAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x + ' ' + this._y; }
+ window.SVGPathSegCurvetoQuadraticAbs.prototype.clone = function() { return new window.SVGPathSegCurvetoQuadraticAbs(undefined, this._x, this._y, this._x1, this._y1); }
+ Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype, 'x1', { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype, 'y1', { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegCurvetoQuadraticRel = function(owningPathSegList, x, y, x1, y1) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL, 'q', owningPathSegList);
+ this._x = x;
+ this._y = y;
+ this._x1 = x1;
+ this._y1 = y1;
+ }
+ window.SVGPathSegCurvetoQuadraticRel.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegCurvetoQuadraticRel.prototype.toString = function() { return '[object SVGPathSegCurvetoQuadraticRel]'; }
+ window.SVGPathSegCurvetoQuadraticRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x1 + ' ' + this._y1 + ' ' + this._x + ' ' + this._y; }
+ window.SVGPathSegCurvetoQuadraticRel.prototype.clone = function() { return new window.SVGPathSegCurvetoQuadraticRel(undefined, this._x, this._y, this._x1, this._y1); }
+ Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype, 'x1', { get: function() { return this._x1; }, set: function(x1) { this._x1 = x1; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype, 'y1', { get: function() { return this._y1; }, set: function(y1) { this._y1 = y1; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegArcAbs = function(owningPathSegList, x, y, r1, r2, angle, largeArcFlag, sweepFlag) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_ARC_ABS, 'A', owningPathSegList);
+ this._x = x;
+ this._y = y;
+ this._r1 = r1;
+ this._r2 = r2;
+ this._angle = angle;
+ this._largeArcFlag = largeArcFlag;
+ this._sweepFlag = sweepFlag;
+ }
+ window.SVGPathSegArcAbs.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegArcAbs.prototype.toString = function() { return '[object SVGPathSegArcAbs]'; }
+ 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; }
+ window.SVGPathSegArcAbs.prototype.clone = function() { return new window.SVGPathSegArcAbs(undefined, this._x, this._y, this._r1, this._r2, this._angle, this._largeArcFlag, this._sweepFlag); }
+ Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'r1', { get: function() { return this._r1; }, set: function(r1) { this._r1 = r1; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'r2', { get: function() { return this._r2; }, set: function(r2) { this._r2 = r2; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'angle', { get: function() { return this._angle; }, set: function(angle) { this._angle = angle; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'largeArcFlag', { get: function() { return this._largeArcFlag; }, set: function(largeArcFlag) { this._largeArcFlag = largeArcFlag; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegArcAbs.prototype, 'sweepFlag', { get: function() { return this._sweepFlag; }, set: function(sweepFlag) { this._sweepFlag = sweepFlag; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegArcRel = function(owningPathSegList, x, y, r1, r2, angle, largeArcFlag, sweepFlag) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_ARC_REL, 'a', owningPathSegList);
+ this._x = x;
+ this._y = y;
+ this._r1 = r1;
+ this._r2 = r2;
+ this._angle = angle;
+ this._largeArcFlag = largeArcFlag;
+ this._sweepFlag = sweepFlag;
+ }
+ window.SVGPathSegArcRel.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegArcRel.prototype.toString = function() { return '[object SVGPathSegArcRel]'; }
+ 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; }
+ window.SVGPathSegArcRel.prototype.clone = function() { return new window.SVGPathSegArcRel(undefined, this._x, this._y, this._r1, this._r2, this._angle, this._largeArcFlag, this._sweepFlag); }
+ Object.defineProperty(window.SVGPathSegArcRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegArcRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegArcRel.prototype, 'r1', { get: function() { return this._r1; }, set: function(r1) { this._r1 = r1; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegArcRel.prototype, 'r2', { get: function() { return this._r2; }, set: function(r2) { this._r2 = r2; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegArcRel.prototype, 'angle', { get: function() { return this._angle; }, set: function(angle) { this._angle = angle; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegArcRel.prototype, 'largeArcFlag', { get: function() { return this._largeArcFlag; }, set: function(largeArcFlag) { this._largeArcFlag = largeArcFlag; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegArcRel.prototype, 'sweepFlag', { get: function() { return this._sweepFlag; }, set: function(sweepFlag) { this._sweepFlag = sweepFlag; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegLinetoHorizontalAbs = function(owningPathSegList, x) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS, 'H', owningPathSegList);
+ this._x = x;
+ }
+ window.SVGPathSegLinetoHorizontalAbs.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegLinetoHorizontalAbs.prototype.toString = function() { return '[object SVGPathSegLinetoHorizontalAbs]'; }
+ window.SVGPathSegLinetoHorizontalAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x; }
+ window.SVGPathSegLinetoHorizontalAbs.prototype.clone = function() { return new window.SVGPathSegLinetoHorizontalAbs(undefined, this._x); }
+ Object.defineProperty(window.SVGPathSegLinetoHorizontalAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegLinetoHorizontalRel = function(owningPathSegList, x) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL, 'h', owningPathSegList);
+ this._x = x;
+ }
+ window.SVGPathSegLinetoHorizontalRel.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegLinetoHorizontalRel.prototype.toString = function() { return '[object SVGPathSegLinetoHorizontalRel]'; }
+ window.SVGPathSegLinetoHorizontalRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x; }
+ window.SVGPathSegLinetoHorizontalRel.prototype.clone = function() { return new window.SVGPathSegLinetoHorizontalRel(undefined, this._x); }
+ Object.defineProperty(window.SVGPathSegLinetoHorizontalRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegLinetoVerticalAbs = function(owningPathSegList, y) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS, 'V', owningPathSegList);
+ this._y = y;
+ }
+ window.SVGPathSegLinetoVerticalAbs.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegLinetoVerticalAbs.prototype.toString = function() { return '[object SVGPathSegLinetoVerticalAbs]'; }
+ window.SVGPathSegLinetoVerticalAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._y; }
+ window.SVGPathSegLinetoVerticalAbs.prototype.clone = function() { return new window.SVGPathSegLinetoVerticalAbs(undefined, this._y); }
+ Object.defineProperty(window.SVGPathSegLinetoVerticalAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegLinetoVerticalRel = function(owningPathSegList, y) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL, 'v', owningPathSegList);
+ this._y = y;
+ }
+ window.SVGPathSegLinetoVerticalRel.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegLinetoVerticalRel.prototype.toString = function() { return '[object SVGPathSegLinetoVerticalRel]'; }
+ window.SVGPathSegLinetoVerticalRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._y; }
+ window.SVGPathSegLinetoVerticalRel.prototype.clone = function() { return new window.SVGPathSegLinetoVerticalRel(undefined, this._y); }
+ Object.defineProperty(window.SVGPathSegLinetoVerticalRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegCurvetoCubicSmoothAbs = function(owningPathSegList, x, y, x2, y2) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS, 'S', owningPathSegList);
+ this._x = x;
+ this._y = y;
+ this._x2 = x2;
+ this._y2 = y2;
+ }
+ window.SVGPathSegCurvetoCubicSmoothAbs.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegCurvetoCubicSmoothAbs.prototype.toString = function() { return '[object SVGPathSegCurvetoCubicSmoothAbs]'; }
+ window.SVGPathSegCurvetoCubicSmoothAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
+ window.SVGPathSegCurvetoCubicSmoothAbs.prototype.clone = function() { return new window.SVGPathSegCurvetoCubicSmoothAbs(undefined, this._x, this._y, this._x2, this._y2); }
+ Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype, 'x2', { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype, 'y2', { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegCurvetoCubicSmoothRel = function(owningPathSegList, x, y, x2, y2) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL, 's', owningPathSegList);
+ this._x = x;
+ this._y = y;
+ this._x2 = x2;
+ this._y2 = y2;
+ }
+ window.SVGPathSegCurvetoCubicSmoothRel.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegCurvetoCubicSmoothRel.prototype.toString = function() { return '[object SVGPathSegCurvetoCubicSmoothRel]'; }
+ window.SVGPathSegCurvetoCubicSmoothRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x2 + ' ' + this._y2 + ' ' + this._x + ' ' + this._y; }
+ window.SVGPathSegCurvetoCubicSmoothRel.prototype.clone = function() { return new window.SVGPathSegCurvetoCubicSmoothRel(undefined, this._x, this._y, this._x2, this._y2); }
+ Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype, 'x2', { get: function() { return this._x2; }, set: function(x2) { this._x2 = x2; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype, 'y2', { get: function() { return this._y2; }, set: function(y2) { this._y2 = y2; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegCurvetoQuadraticSmoothAbs = function(owningPathSegList, x, y) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS, 'T', owningPathSegList);
+ this._x = x;
+ this._y = y;
+ }
+ window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype.toString = function() { return '[object SVGPathSegCurvetoQuadraticSmoothAbs]'; }
+ window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
+ window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype.clone = function() { return new window.SVGPathSegCurvetoQuadraticSmoothAbs(undefined, this._x, this._y); }
+ Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+
+ window.SVGPathSegCurvetoQuadraticSmoothRel = function(owningPathSegList, x, y) {
+ window.SVGPathSeg.call(this, window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL, 't', owningPathSegList);
+ this._x = x;
+ this._y = y;
+ }
+ window.SVGPathSegCurvetoQuadraticSmoothRel.prototype = Object.create(window.SVGPathSeg.prototype);
+ window.SVGPathSegCurvetoQuadraticSmoothRel.prototype.toString = function() { return '[object SVGPathSegCurvetoQuadraticSmoothRel]'; }
+ window.SVGPathSegCurvetoQuadraticSmoothRel.prototype._asPathString = function() { return this.pathSegTypeAsLetter + ' ' + this._x + ' ' + this._y; }
+ window.SVGPathSegCurvetoQuadraticSmoothRel.prototype.clone = function() { return new window.SVGPathSegCurvetoQuadraticSmoothRel(undefined, this._x, this._y); }
+ Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothRel.prototype, 'x', { get: function() { return this._x; }, set: function(x) { this._x = x; this._segmentChanged(); }, enumerable: true });
+ Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothRel.prototype, 'y', { get: function() { return this._y; }, set: function(y) { this._y = y; this._segmentChanged(); }, enumerable: true });
+
+ // Add createSVGPathSeg* functions to window.SVGPathElement.
+ // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-Interfacewindow.SVGPathElement.
+ window.SVGPathElement.prototype.createSVGPathSegClosePath = function() { return new window.SVGPathSegClosePath(undefined); }
+ window.SVGPathElement.prototype.createSVGPathSegMovetoAbs = function(x, y) { return new window.SVGPathSegMovetoAbs(undefined, x, y); }
+ window.SVGPathElement.prototype.createSVGPathSegMovetoRel = function(x, y) { return new window.SVGPathSegMovetoRel(undefined, x, y); }
+ window.SVGPathElement.prototype.createSVGPathSegLinetoAbs = function(x, y) { return new window.SVGPathSegLinetoAbs(undefined, x, y); }
+ window.SVGPathElement.prototype.createSVGPathSegLinetoRel = function(x, y) { return new window.SVGPathSegLinetoRel(undefined, x, y); }
+ window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicAbs = function(x, y, x1, y1, x2, y2) { return new window.SVGPathSegCurvetoCubicAbs(undefined, x, y, x1, y1, x2, y2); }
+ window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicRel = function(x, y, x1, y1, x2, y2) { return new window.SVGPathSegCurvetoCubicRel(undefined, x, y, x1, y1, x2, y2); }
+ window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticAbs = function(x, y, x1, y1) { return new window.SVGPathSegCurvetoQuadraticAbs(undefined, x, y, x1, y1); }
+ window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticRel = function(x, y, x1, y1) { return new window.SVGPathSegCurvetoQuadraticRel(undefined, x, y, x1, y1); }
+ window.SVGPathElement.prototype.createSVGPathSegArcAbs = function(x, y, r1, r2, angle, largeArcFlag, sweepFlag) { return new window.SVGPathSegArcAbs(undefined, x, y, r1, r2, angle, largeArcFlag, sweepFlag); }
+ window.SVGPathElement.prototype.createSVGPathSegArcRel = function(x, y, r1, r2, angle, largeArcFlag, sweepFlag) { return new window.SVGPathSegArcRel(undefined, x, y, r1, r2, angle, largeArcFlag, sweepFlag); }
+ window.SVGPathElement.prototype.createSVGPathSegLinetoHorizontalAbs = function(x) { return new window.SVGPathSegLinetoHorizontalAbs(undefined, x); }
+ window.SVGPathElement.prototype.createSVGPathSegLinetoHorizontalRel = function(x) { return new window.SVGPathSegLinetoHorizontalRel(undefined, x); }
+ window.SVGPathElement.prototype.createSVGPathSegLinetoVerticalAbs = function(y) { return new window.SVGPathSegLinetoVerticalAbs(undefined, y); }
+ window.SVGPathElement.prototype.createSVGPathSegLinetoVerticalRel = function(y) { return new window.SVGPathSegLinetoVerticalRel(undefined, y); }
+ window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothAbs = function(x, y, x2, y2) { return new window.SVGPathSegCurvetoCubicSmoothAbs(undefined, x, y, x2, y2); }
+ window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothRel = function(x, y, x2, y2) { return new window.SVGPathSegCurvetoCubicSmoothRel(undefined, x, y, x2, y2); }
+ window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothAbs = function(x, y) { return new window.SVGPathSegCurvetoQuadraticSmoothAbs(undefined, x, y); }
+ window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothRel = function(x, y) { return new window.SVGPathSegCurvetoQuadraticSmoothRel(undefined, x, y); }
+
+ if (!('getPathSegAtLength' in window.SVGPathElement.prototype)) {
+ // Add getPathSegAtLength to SVGPathElement.
+ // Spec: https://www.w3.org/TR/SVG11/single-page.html#paths-__svg__SVGPathElement__getPathSegAtLength
+ // This polyfill requires SVGPathElement.getTotalLength to implement the distance-along-a-path algorithm.
+ window.SVGPathElement.prototype.getPathSegAtLength = function(distance) {
+ if (distance === undefined || !isFinite(distance))
+ throw 'Invalid arguments.';
+
+ var measurementElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
+ measurementElement.setAttribute('d', this.getAttribute('d'));
+ var lastPathSegment = measurementElement.pathSegList.numberOfItems - 1;
+
+ // If the path is empty, return 0.
+ if (lastPathSegment <= 0)
+ return 0;
+
+ do {
+ measurementElement.pathSegList.removeItem(lastPathSegment);
+ if (distance > measurementElement.getTotalLength())
+ break;
+ lastPathSegment--;
+ } while (lastPathSegment > 0);
+ return lastPathSegment;
+ }
+ }
+ }
+
+ // Checking for SVGPathSegList in window checks for the case of an implementation without the
+ // SVGPathSegList API.
+ // The second check for appendItem is specific to Firefox 59+ which removed only parts of the
+ // SVGPathSegList API (e.g., appendItem). In this case we need to re-implement the entire API
+ // so the polyfill data (i.e., _list) is used throughout.
+ if (!('SVGPathSegList' in window) || !('appendItem' in window.SVGPathSegList.prototype)) {
+ // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGPathSegList
+ window.SVGPathSegList = function(pathElement) {
+ this._pathElement = pathElement;
+ this._list = this._parsePath(this._pathElement.getAttribute('d'));
+
+ // Use a MutationObserver to catch changes to the path's 'd' attribute.
+ this._mutationObserverConfig = { 'attributes': true, 'attributeFilter': ['d'] };
+ this._pathElementMutationObserver = new MutationObserver(this._updateListFromPathMutations.bind(this));
+ this._pathElementMutationObserver.observe(this._pathElement, this._mutationObserverConfig);
+ }
+
+ window.SVGPathSegList.prototype.classname = 'SVGPathSegList';
+
+ Object.defineProperty(window.SVGPathSegList.prototype, 'numberOfItems', {
+ get: function() {
+ this._checkPathSynchronizedToList();
+ return this._list.length;
+ },
+ enumerable: true
+ });
+
+ // The length property was not specified but was in Firefox 58.
+ Object.defineProperty(window.SVGPathSegList.prototype, 'length', {
+ get: function() {
+ this._checkPathSynchronizedToList();
+ return this._list.length;
+ },
+ enumerable: true
+ });
+
+ // Add the pathSegList accessors to window.SVGPathElement.
+ // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-InterfaceSVGAnimatedPathData
+ Object.defineProperty(window.SVGPathElement.prototype, 'pathSegList', {
+ get: function() {
+ if (!this._pathSegList)
+ this._pathSegList = new window.SVGPathSegList(this);
+ return this._pathSegList;
+ },
+ enumerable: true
+ });
+ // FIXME: The following are not implemented and simply return window.SVGPathElement.pathSegList.
+ Object.defineProperty(window.SVGPathElement.prototype, 'normalizedPathSegList', { get: function() { return this.pathSegList; }, enumerable: true });
+ Object.defineProperty(window.SVGPathElement.prototype, 'animatedPathSegList', { get: function() { return this.pathSegList; }, enumerable: true });
+ Object.defineProperty(window.SVGPathElement.prototype, 'animatedNormalizedPathSegList', { get: function() { return this.pathSegList; }, enumerable: true });
+
+ // Process any pending mutations to the path element and update the list as needed.
+ // This should be the first call of all public functions and is needed because
+ // MutationObservers are not synchronous so we can have pending asynchronous mutations.
+ window.SVGPathSegList.prototype._checkPathSynchronizedToList = function() {
+ this._updateListFromPathMutations(this._pathElementMutationObserver.takeRecords());
+ }
+
+ window.SVGPathSegList.prototype._updateListFromPathMutations = function(mutationRecords) {
+ if (!this._pathElement)
+ return;
+ var hasPathMutations = false;
+ mutationRecords.forEach(function(record) {
+ if (record.attributeName == 'd')
+ hasPathMutations = true;
+ });
+ if (hasPathMutations)
+ this._list = this._parsePath(this._pathElement.getAttribute('d'));
+ }
+
+ // Serialize the list and update the path's 'd' attribute.
+ window.SVGPathSegList.prototype._writeListToPath = function() {
+ this._pathElementMutationObserver.disconnect();
+ this._pathElement.setAttribute('d', window.SVGPathSegList._pathSegArrayAsString(this._list));
+ this._pathElementMutationObserver.observe(this._pathElement, this._mutationObserverConfig);
+ }
+
+ // When a path segment changes the list needs to be synchronized back to the path element.
+ window.SVGPathSegList.prototype.segmentChanged = function(pathSeg) {
+ this._writeListToPath();
+ }
+
+ window.SVGPathSegList.prototype.clear = function() {
+ this._checkPathSynchronizedToList();
+
+ this._list.forEach(function(pathSeg) {
+ pathSeg._owningPathSegList = null;
+ });
+ this._list = [];
+ this._writeListToPath();
+ }
+
+ window.SVGPathSegList.prototype.initialize = function(newItem) {
+ this._checkPathSynchronizedToList();
+
+ this._list = [newItem];
+ newItem._owningPathSegList = this;
+ this._writeListToPath();
+ return newItem;
+ }
+
+ window.SVGPathSegList.prototype._checkValidIndex = function(index) {
+ if (isNaN(index) || index < 0 || index >= this.numberOfItems)
+ throw 'INDEX_SIZE_ERR';
+ }
+
+ window.SVGPathSegList.prototype.getItem = function(index) {
+ this._checkPathSynchronizedToList();
+
+ this._checkValidIndex(index);
+ return this._list[index];
+ }
+
+ window.SVGPathSegList.prototype.insertItemBefore = function(newItem, index) {
+ this._checkPathSynchronizedToList();
+
+ // Spec: If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list.
+ if (index > this.numberOfItems)
+ index = this.numberOfItems;
+ if (newItem._owningPathSegList) {
+ // SVG2 spec says to make a copy.
+ newItem = newItem.clone();
+ }
+ this._list.splice(index, 0, newItem);
+ newItem._owningPathSegList = this;
+ this._writeListToPath();
+ return newItem;
+ }
+
+ window.SVGPathSegList.prototype.replaceItem = function(newItem, index) {
+ this._checkPathSynchronizedToList();
+
+ if (newItem._owningPathSegList) {
+ // SVG2 spec says to make a copy.
+ newItem = newItem.clone();
+ }
+ this._checkValidIndex(index);
+ this._list[index] = newItem;
+ newItem._owningPathSegList = this;
+ this._writeListToPath();
+ return newItem;
+ }
+
+ window.SVGPathSegList.prototype.removeItem = function(index) {
+ this._checkPathSynchronizedToList();
+
+ this._checkValidIndex(index);
+ var item = this._list[index];
+ this._list.splice(index, 1);
+ this._writeListToPath();
+ return item;
+ }
+
+ window.SVGPathSegList.prototype.appendItem = function(newItem) {
+ this._checkPathSynchronizedToList();
+
+ if (newItem._owningPathSegList) {
+ // SVG2 spec says to make a copy.
+ newItem = newItem.clone();
+ }
+ this._list.push(newItem);
+ newItem._owningPathSegList = this;
+ // TODO: Optimize this to just append to the existing attribute.
+ this._writeListToPath();
+ return newItem;
+ };
+
+ window.SVGPathSegList.prototype.matrixTransform = function(aSVGMatrix) {
+ this._checkPathSynchronizedToList();
+
+ var nLength = this._list.length;
+ for( var i = 0; i < nLength; ++i )
+ {
+ var nX;
+ var aPathSeg = this._list[i];
+ switch( aPathSeg.pathSegTypeAsLetter )
+ {
+ case 'C':
+ nX = aPathSeg._x2;
+ aPathSeg._x2 = aSVGMatrix.a * nX + aSVGMatrix.c * aPathSeg._y2 + aSVGMatrix.e;
+ aPathSeg._y2 = aSVGMatrix.b * nX + aSVGMatrix.d * aPathSeg._y2 + aSVGMatrix.f;
+ // fall through intended
+ case 'Q':
+ nX = aPathSeg._x1;
+ aPathSeg._x1 = aSVGMatrix.a * nX + aSVGMatrix.c * aPathSeg._y1 + aSVGMatrix.e;
+ aPathSeg._y1 = aSVGMatrix.b * nX + aSVGMatrix.d * aPathSeg._y1 + aSVGMatrix.f;
+ // fall through intended
+ case 'M':
+ case 'L':
+ nX = aPathSeg._x;
+ aPathSeg._x = aSVGMatrix.a * nX + aSVGMatrix.c * aPathSeg._y + aSVGMatrix.e;
+ aPathSeg._y = aSVGMatrix.b * nX + aSVGMatrix.d * aPathSeg._y + aSVGMatrix.f;
+ break;
+ default:
+ log( 'SVGPathSeg.matrixTransform: unexpected path segment type: '
+ + aPathSeg.pathSegTypeAsLetter );
+ }
+ }
+
+ this._writeListToPath();
+ };
+
+ window.SVGPathSegList.prototype.changeOrientation = function() {
+ this._checkPathSynchronizedToList();
+
+ var aPathSegList = this._list;
+ var nLength = aPathSegList.length;
+ if( nLength == 0 ) return;
+
+ var nCurrentX = 0;
+ var nCurrentY = 0;
+
+ var aPathSeg = aPathSegList[0];
+ if( aPathSeg.pathSegTypeAsLetter == 'M' )
+ {
+ nCurrentX = aPathSeg.x;
+ nCurrentY = aPathSeg.y;
+ aPathSegList.shift();
+ --nLength;
+ }
+
+ var i;
+ for( i = 0; i < nLength; ++i )
+ {
+ aPathSeg = aPathSegList[i];
+ switch( aPathSeg.pathSegTypeAsLetter )
+ {
+ case 'C':
+ var nX = aPathSeg._x1;
+ aPathSeg._x1 = aPathSeg._x2;
+ aPathSeg._x2 = nX;
+ var nY = aPathSeg._y1;
+ aPathSeg._y1 = aPathSeg._y2;
+ aPathSeg._y2 = nY;
+ // fall through intended
+ case 'M':
+ case 'L':
+ case 'Q':
+ var aPoint = { x: aPathSeg._x, y: aPathSeg._y };
+ aPathSeg._x = nCurrentX;
+ aPathSeg._y = nCurrentY;
+ nCurrentX = aPoint.x;
+ nCurrentY = aPoint.y;
+ break;
+ default:
+ log( 'SVGPathSegList.changeOrientation: unexpected path segment type: '
+ + aPathSeg.pathSegTypeAsLetter );
+ }
+
+ }
+
+ aPathSegList.reverse();
+
+ var aMovePathSeg = new window.SVGPathSegMovetoAbs( this, nCurrentX, nCurrentY );
+ aPathSegList.unshift( aMovePathSeg );
+
+ this._writeListToPath();
+ };
+
+ window.SVGPathSegList._pathSegArrayAsString = function(pathSegArray) {
+ var string = '';
+ var first = true;
+ pathSegArray.forEach(function(pathSeg) {
+ if (first) {
+ first = false;
+ string += pathSeg._asPathString();
+ } else {
+ string += ' ' + pathSeg._asPathString();
+ }
+ });
+ return string;
+ }
+
+ // This closely follows SVGPathParser::parsePath from Source/core/svg/SVGPathParser.cpp.
+ window.SVGPathSegList.prototype._parsePath = function(string) {
+ if (!string || string.length == 0)
+ return [];
+
+ var owningPathSegList = this;
+
+ var Builder = function() {
+ this.pathSegList = [];
+ }
+
+ Builder.prototype.appendSegment = function(pathSeg) {
+ this.pathSegList.push(pathSeg);
+ }
+
+ var Source = function(string) {
+ this._string = string;
+ this._currentIndex = 0;
+ this._endIndex = this._string.length;
+ this._previousCommand = window.SVGPathSeg.PATHSEG_UNKNOWN;
+
+ this._skipOptionalSpaces();
+ }
+
+ Source.prototype._isCurrentSpace = function() {
+ var character = this._string[this._currentIndex];
+ return character <= ' ' && (character == ' ' || character == '\n' || character == '\t' || character == '\r' || character == '\f');
+ }
+
+ Source.prototype._skipOptionalSpaces = function() {
+ while (this._currentIndex < this._endIndex && this._isCurrentSpace())
+ this._currentIndex++;
+ return this._currentIndex < this._endIndex;
+ }
+
+ Source.prototype._skipOptionalSpacesOrDelimiter = function() {
+ if (this._currentIndex < this._endIndex && !this._isCurrentSpace() && this._string.charAt(this._currentIndex) != ',')
+ return false;
+ if (this._skipOptionalSpaces()) {
+ if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == ',') {
+ this._currentIndex++;
+ this._skipOptionalSpaces();
+ }
+ }
+ return this._currentIndex < this._endIndex;
+ }
+
+ Source.prototype.hasMoreData = function() {
+ return this._currentIndex < this._endIndex;
+ }
+
+ Source.prototype.peekSegmentType = function() {
+ var lookahead = this._string[this._currentIndex];
+ return this._pathSegTypeFromChar(lookahead);
+ }
+
+ Source.prototype._pathSegTypeFromChar = function(lookahead) {
+ switch (lookahead) {
+ case 'Z':
+ case 'z':
+ return window.SVGPathSeg.PATHSEG_CLOSEPATH;
+ case 'M':
+ return window.SVGPathSeg.PATHSEG_MOVETO_ABS;
+ case 'm':
+ return window.SVGPathSeg.PATHSEG_MOVETO_REL;
+ case 'L':
+ return window.SVGPathSeg.PATHSEG_LINETO_ABS;
+ case 'l':
+ return window.SVGPathSeg.PATHSEG_LINETO_REL;
+ case 'C':
+ return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS;
+ case 'c':
+ return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL;
+ case 'Q':
+ return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS;
+ case 'q':
+ return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL;
+ case 'A':
+ return window.SVGPathSeg.PATHSEG_ARC_ABS;
+ case 'a':
+ return window.SVGPathSeg.PATHSEG_ARC_REL;
+ case 'H':
+ return window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS;
+ case 'h':
+ return window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL;
+ case 'V':
+ return window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS;
+ case 'v':
+ return window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL;
+ case 'S':
+ return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS;
+ case 's':
+ return window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL;
+ case 'T':
+ return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS;
+ case 't':
+ return window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL;
+ default:
+ return window.SVGPathSeg.PATHSEG_UNKNOWN;
+ }
+ }
+
+ Source.prototype._nextCommandHelper = function(lookahead, previousCommand) {
+ // Check for remaining coordinates in the current command.
+ if ((lookahead == '+' || lookahead == '-' || lookahead == '.' || (lookahead >= '0' && lookahead <= '9')) && previousCommand != window.SVGPathSeg.PATHSEG_CLOSEPATH) {
+ if (previousCommand == window.SVGPathSeg.PATHSEG_MOVETO_ABS)
+ return window.SVGPathSeg.PATHSEG_LINETO_ABS;
+ if (previousCommand == window.SVGPathSeg.PATHSEG_MOVETO_REL)
+ return window.SVGPathSeg.PATHSEG_LINETO_REL;
+ return previousCommand;
+ }
+ return window.SVGPathSeg.PATHSEG_UNKNOWN;
+ }
+
+ Source.prototype.initialCommandIsMoveTo = function() {
+ // If the path is empty it is still valid, so return true.
+ if (!this.hasMoreData())
+ return true;
+ var command = this.peekSegmentType();
+ // Path must start with moveTo.
+ return command == window.SVGPathSeg.PATHSEG_MOVETO_ABS || command == window.SVGPathSeg.PATHSEG_MOVETO_REL;
+ }
+
+ // Parse a number from an SVG path. This very closely follows genericParseNumber(...) from Source/core/svg/SVGParserUtilities.cpp.
+ // Spec: http://www.w3.org/TR/SVG11/single-page.html#paths-PathDataBNF
+ Source.prototype._parseNumber = function() {
+ var exponent = 0;
+ var integer = 0;
+ var frac = 1;
+ var decimal = 0;
+ var sign = 1;
+ var expsign = 1;
+
+ var startIndex = this._currentIndex;
+
+ this._skipOptionalSpaces();
+
+ // Read the sign.
+ if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == '+')
+ this._currentIndex++;
+ else if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == '-') {
+ this._currentIndex++;
+ sign = -1;
+ }
+
+ if (this._currentIndex == this._endIndex || ((this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9') && this._string.charAt(this._currentIndex) != '.'))
+ // The first character of a number must be one of [0-9+-.].
+ return undefined;
+
+ // Read the integer part, build right-to-left.
+ var startIntPartIndex = this._currentIndex;
+ while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9')
+ this._currentIndex++; // Advance to first non-digit.
+
+ if (this._currentIndex != startIntPartIndex) {
+ var scanIntPartIndex = this._currentIndex - 1;
+ var multiplier = 1;
+ while (scanIntPartIndex >= startIntPartIndex) {
+ integer += multiplier * (this._string.charAt(scanIntPartIndex--) - '0');
+ multiplier *= 10;
+ }
+ }
+
+ // Read the decimals.
+ if (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) == '.') {
+ this._currentIndex++;
+
+ // There must be a least one digit following the .
+ if (this._currentIndex >= this._endIndex || this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9')
+ return undefined;
+ while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
+ frac *= 10;
+ decimal += (this._string.charAt(this._currentIndex) - '0') / frac;
+ this._currentIndex += 1;
+ }
+ }
+
+ // Read the exponent part.
+ 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')) {
+ this._currentIndex++;
+
+ // Read the sign of the exponent.
+ if (this._string.charAt(this._currentIndex) == '+') {
+ this._currentIndex++;
+ } else if (this._string.charAt(this._currentIndex) == '-') {
+ this._currentIndex++;
+ expsign = -1;
+ }
+
+ // There must be an exponent.
+ if (this._currentIndex >= this._endIndex || this._string.charAt(this._currentIndex) < '0' || this._string.charAt(this._currentIndex) > '9')
+ return undefined;
+
+ while (this._currentIndex < this._endIndex && this._string.charAt(this._currentIndex) >= '0' && this._string.charAt(this._currentIndex) <= '9') {
+ exponent *= 10;
+ exponent += (this._string.charAt(this._currentIndex) - '0');
+ this._currentIndex++;
+ }
+ }
+
+ var number = integer + decimal;
+ number *= sign;
+
+ if (exponent)
+ number *= Math.pow(10, expsign * exponent);
+
+ if (startIndex == this._currentIndex)
+ return undefined;
+
+ this._skipOptionalSpacesOrDelimiter();
+
+ return number;
+ }
+
+ Source.prototype._parseArcFlag = function() {
+ if (this._currentIndex >= this._endIndex)
+ return undefined;
+ var flag = false;
+ var flagChar = this._string.charAt(this._currentIndex++);
+ if (flagChar == '0')
+ flag = false;
+ else if (flagChar == '1')
+ flag = true;
+ else
+ return undefined;
+
+ this._skipOptionalSpacesOrDelimiter();
+ return flag;
+ }
+
+ Source.prototype.parseSegment = function() {
+ var lookahead = this._string[this._currentIndex];
+ var command = this._pathSegTypeFromChar(lookahead);
+ if (command == window.SVGPathSeg.PATHSEG_UNKNOWN) {
+ // Possibly an implicit command. Not allowed if this is the first command.
+ if (this._previousCommand == window.SVGPathSeg.PATHSEG_UNKNOWN)
+ return null;
+ command = this._nextCommandHelper(lookahead, this._previousCommand);
+ if (command == window.SVGPathSeg.PATHSEG_UNKNOWN)
+ return null;
+ } else {
+ this._currentIndex++;
+ }
+
+ this._previousCommand = command;
+
+ switch (command) {
+ case window.SVGPathSeg.PATHSEG_MOVETO_REL:
+ return new window.SVGPathSegMovetoRel(owningPathSegList, this._parseNumber(), this._parseNumber());
+ case window.SVGPathSeg.PATHSEG_MOVETO_ABS:
+ return new window.SVGPathSegMovetoAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
+ case window.SVGPathSeg.PATHSEG_LINETO_REL:
+ return new window.SVGPathSegLinetoRel(owningPathSegList, this._parseNumber(), this._parseNumber());
+ case window.SVGPathSeg.PATHSEG_LINETO_ABS:
+ return new window.SVGPathSegLinetoAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
+ case window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL:
+ return new window.SVGPathSegLinetoHorizontalRel(owningPathSegList, this._parseNumber());
+ case window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS:
+ return new window.SVGPathSegLinetoHorizontalAbs(owningPathSegList, this._parseNumber());
+ case window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL:
+ return new window.SVGPathSegLinetoVerticalRel(owningPathSegList, this._parseNumber());
+ case window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS:
+ return new window.SVGPathSegLinetoVerticalAbs(owningPathSegList, this._parseNumber());
+ case window.SVGPathSeg.PATHSEG_CLOSEPATH:
+ this._skipOptionalSpaces();
+ return new window.SVGPathSegClosePath(owningPathSegList);
+ case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL:
+ var points = {x1: this._parseNumber(), y1: this._parseNumber(), x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
+ return new window.SVGPathSegCurvetoCubicRel(owningPathSegList, points.x, points.y, points.x1, points.y1, points.x2, points.y2);
+ case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS:
+ var points = {x1: this._parseNumber(), y1: this._parseNumber(), x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
+ return new window.SVGPathSegCurvetoCubicAbs(owningPathSegList, points.x, points.y, points.x1, points.y1, points.x2, points.y2);
+ case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL:
+ var points = {x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
+ return new window.SVGPathSegCurvetoCubicSmoothRel(owningPathSegList, points.x, points.y, points.x2, points.y2);
+ case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:
+ var points = {x2: this._parseNumber(), y2: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
+ return new window.SVGPathSegCurvetoCubicSmoothAbs(owningPathSegList, points.x, points.y, points.x2, points.y2);
+ case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL:
+ var points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
+ return new window.SVGPathSegCurvetoQuadraticRel(owningPathSegList, points.x, points.y, points.x1, points.y1);
+ case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS:
+ var points = {x1: this._parseNumber(), y1: this._parseNumber(), x: this._parseNumber(), y: this._parseNumber()};
+ return new window.SVGPathSegCurvetoQuadraticAbs(owningPathSegList, points.x, points.y, points.x1, points.y1);
+ case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:
+ return new window.SVGPathSegCurvetoQuadraticSmoothRel(owningPathSegList, this._parseNumber(), this._parseNumber());
+ case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:
+ return new window.SVGPathSegCurvetoQuadraticSmoothAbs(owningPathSegList, this._parseNumber(), this._parseNumber());
+ case window.SVGPathSeg.PATHSEG_ARC_REL:
+ var points = {x1: this._parseNumber(), y1: this._parseNumber(), arcAngle: this._parseNumber(), arcLarge: this._parseArcFlag(), arcSweep: this._parseArcFlag(), x: this._parseNumber(), y: this._parseNumber()};
+ return new window.SVGPathSegArcRel(owningPathSegList, points.x, points.y, points.x1, points.y1, points.arcAngle, points.arcLarge, points.arcSweep);
+ case window.SVGPathSeg.PATHSEG_ARC_ABS:
+ var points = {x1: this._parseNumber(), y1: this._parseNumber(), arcAngle: this._parseNumber(), arcLarge: this._parseArcFlag(), arcSweep: this._parseArcFlag(), x: this._parseNumber(), y: this._parseNumber()};
+ return new window.SVGPathSegArcAbs(owningPathSegList, points.x, points.y, points.x1, points.y1, points.arcAngle, points.arcLarge, points.arcSweep);
+ default:
+ throw 'Unknown path seg type.'
+ }
+ }
+
+ var builder = new Builder();
+ var source = new Source(string);
+
+ if (!source.initialCommandIsMoveTo())
+ return [];
+ while (source.hasMoreData()) {
+ var pathSeg = source.parseSegment();
+ if (!pathSeg)
+ return [];
+ builder.appendSegment(pathSeg);
+ }
+
+ return builder.pathSegList;
+ }
+ }
+}());
+
+/*****
+ * @svgpathend
+ *
+ * The above code is a derivative work of some part of the SVGPathSeg API.
+ *
+ * This API is a drop-in replacement for the SVGPathSeg and SVGPathSegList APIs that were removed from
+ * SVG2 (https://lists.w3.org/Archives/Public/www-svg/2015Jun/0044.html), including the latest spec
+ * changes which were implemented in Firefox 43 and Chrome 46.
+ *
+ * @source https://github.com/progers/pathseg
+ */
+
+
+/*****
+ * @licstart
+ *
+ * The following is the license notice for the part of JavaScript code of
+ * this page included between the '@libreofficestart' and the '@libreofficeend'
+ * notes.
+ */
+
+/***** ******************************************************************
+ *
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the 'License'); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ *
+ ************************************************************************/
+
+/*****
+ * @licend
+ *
+ * The above is the license notice for the part of JavaScript code of
+ * this page included between the '@libreofficestart' and the '@libreofficeend'
+ * notes.
+ */
+
+
+/*****
+ * @libreofficestart
+ *
+ * Several parts of the following code are the result of the porting,
+ * started on August 2011, of the C++ code included in the source
+ * files placed under the folder '/slideshow/source' and
+ * sub-folders. This got later rebased onto the AL2-licensed versions
+ * of those files in early 2013.
+ * @source https://cgit.freedesktop.org/libreoffice/core/tree/slideshow/source
+ *
+ */
+
+
+window.onload = init;
+
+
+// ooo elements
+var aOOOElemMetaSlides = 'ooo:meta_slides';
+var aOOOElemMetaSlide = 'ooo:meta_slide';
+var aOOOElemTextField = 'ooo:text_field';
+var aPresentationClipPathId = 'presentation_clip_path';
+var aPresentationClipPathShrinkId = 'presentation_clip_path_shrink';
+
+// ooo attributes
+var aOOOAttrNumberOfSlides = 'number-of-slides';
+var aOOOAttrStartSlideNumber= 'start-slide-number';
+var aOOOAttrNumberingType = 'page-numbering-type';
+var aOOOAttrListItemNumberingType= 'numbering-type';
+var aOOOAttrUsePositionedChars = 'use-positioned-chars';
+
+var aOOOAttrSlide = 'slide';
+var aOOOAttrMaster = 'master';
+var aOOOAttrDisplayName = 'display-name';
+var aOOOAttrSlideDuration = 'slide-duration';
+var aOOOAttrHasTransition = 'has-transition';
+var aOOOAttrHasCustomBackground = 'has-custom-background';
+var aOOOAttrBackgroundVisibility = 'background-visibility';
+var aOOOAttrMasterObjectsVisibility = 'master-objects-visibility';
+var aOOOAttrPageNumberVisibility = 'page-number-visibility';
+var aOOOAttrDateTimeVisibility = 'date-time-visibility';
+var aOOOAttrFooterVisibility = 'footer-visibility';
+var aOOOAttrHeaderVisibility = 'header-visibility';
+var aOOOAttrDateTimeField = 'date-time-field';
+var aOOOAttrFooterField = 'footer-field';
+var aOOOAttrHeaderField = 'header-field';
+
+var aOOOAttrDateTimeFormat = 'date-time-format';
+
+var aOOOAttrTextAdjust = 'text-adjust';
+
+// element class names
+var aClipPathGroupClassName = 'ClipPathGroup';
+var aPageClassName = 'Page';
+var aSlideNumberClassName = 'Slide_Number';
+var aDateTimeClassName = 'Date/Time';
+var aFooterClassName = 'Footer';
+var aHeaderClassName = 'Header';
+var aDateClassName = 'Date';
+var aTimeClassName = 'Time';
+var aSlideNameClassName='SlideName';
+
+// Creating a namespace dictionary.
+var NSS = {};
+NSS['svg']='http://www.w3.org/2000/svg';
+NSS['rdf']='http://www.w3.org/1999/02/22-rdf-syntax-ns#';
+NSS['xlink']='http://www.w3.org/1999/xlink';
+NSS['xml']='http://www.w3.org/XML/1998/namespace';
+NSS['ooo'] = 'http://xml.openoffice.org/svg/export';
+NSS['presentation'] = 'http://sun.com/xmlns/staroffice/presentation';
+NSS['smil'] = 'http://www.w3.org/2001/SMIL20/';
+NSS['anim'] = 'urn:oasis:names:tc:opendocument:xmlns:animation:1.0';
+
+// Presentation modes.
+var SLIDE_MODE = 1;
+var INDEX_MODE = 2;
+
+// Mouse handler actions.
+var MOUSE_UP = 1;
+var MOUSE_DOWN = 2; // eslint-disable-line no-unused-vars
+var MOUSE_MOVE = 3; // eslint-disable-line no-unused-vars
+var MOUSE_WHEEL = 4;
+
+// Key-codes.
+var LEFT_KEY = 37; // cursor left keycode
+var UP_KEY = 38; // cursor up keycode
+var RIGHT_KEY = 39; // cursor right keycode
+var DOWN_KEY = 40; // cursor down keycode
+var PAGE_UP_KEY = 33; // page up keycode
+var PAGE_DOWN_KEY = 34; // page down keycode
+var HOME_KEY = 36; // home keycode
+var END_KEY = 35; // end keycode
+var ENTER_KEY = 13;
+var SPACE_KEY = 32;
+var ESCAPE_KEY = 27;
+var Q_KEY = 81;
+
+// Visibility Values
+var HIDDEN = 0;
+var VISIBLE = 1;
+var INHERIT = 2;
+var aVisibilityAttributeValue = [ 'hidden', 'visible', 'inherit' ]; // eslint-disable-line no-unused-vars
+var aVisibilityValue = { 'hidden' : HIDDEN, 'visible' : VISIBLE, 'inherit' : INHERIT };
+
+// Parameters
+var ROOT_NODE = document.getElementsByTagNameNS( NSS['svg'], 'svg' )[0];
+var WIDTH = 0;
+var HEIGHT = 0;
+var INDEX_COLUMNS_DEFAULT = 3;
+var INDEX_OFFSET = 0;
+
+// Initialization.
+var Detect = configureDetectionTools();
+var theMetaDoc;
+var theSlideIndexPage;
+var currentMode = SLIDE_MODE;
+var processingEffect = false;
+var nCurSlide = undefined;
+var bTextHasBeenSelected = false;
+var sLastSelectedText = '';
+
+
+// Initialize char and key code dictionaries.
+var charCodeDictionary = getDefaultCharCodeDictionary();
+var keyCodeDictionary = getDefaultKeyCodeDictionary();
+
+// Initialize mouse handler dictionary.
+var mouseHandlerDictionary = getDefaultMouseHandlerDictionary();
+
+/***************************
+ ** OOP support functions **
+ ***************************/
+
+function object( aObject )
+{
+ var F = function() {};
+ F.prototype = aObject;
+ return new F();
+}
+
+
+function extend( aSubType, aSuperType )
+{
+ if (!aSuperType || !aSubType)
+ {
+ alert('extend failed, verify dependencies');
+ }
+ var OP = Object.prototype;
+ var sp = aSuperType.prototype;
+ var rp = object( sp );
+ aSubType.prototype = rp;
+
+ rp.constructor = aSubType;
+ aSubType.superclass = sp;
+
+ // assign constructor property
+ if (aSuperType != Object && sp.constructor == OP.constructor)
+ {
+ sp.constructor = aSuperType;
+ }
+
+ return aSubType;
+}
+
+
+function instantiate( TemplateClass, BaseType )
+{
+ if( !TemplateClass.instanceSet )
+ TemplateClass.instanceSet = [];
+
+ var nSize = TemplateClass.instanceSet.length;
+
+ for( var i = 0; i < nSize; ++i )
+ {
+ if( TemplateClass.instanceSet[i].base === BaseType )
+ return TemplateClass.instanceSet[i].instance;
+ }
+
+ TemplateClass.instanceSet[ nSize ] = {};
+ TemplateClass.instanceSet[ nSize ].base = BaseType;
+ TemplateClass.instanceSet[ nSize ].instance = TemplateClass( BaseType );
+
+ return TemplateClass.instanceSet[ nSize ].instance;
+}
+
+
+
+/**********************************
+ ** Helper functions and classes **
+ **********************************/
+
+function Rectangle( aSVGRectElem )
+{
+ var x = parseInt( aSVGRectElem.getAttribute( 'x' ) );
+ var y = parseInt( aSVGRectElem.getAttribute( 'y' ) );
+ var width = parseInt( aSVGRectElem.getAttribute( 'width' ) );
+ var height = parseInt( aSVGRectElem.getAttribute( 'height' ) );
+
+ this.left = x;
+ this.right = x + width;
+ this.top = y;
+ this.bottom = y + height;
+}
+
+/*
+ * Returns key corresponding to a value in object, null otherwise.
+ *
+ * @param Object
+ * @param value
+ */
+function getKeyByValue(aObj, value) {
+ for(var key in aObj) {
+ if(aObj[key] == value)
+ return key;
+ }
+ return null;
+}
+
+function log( message )
+{
+ if( typeof console == 'object' )
+ {
+ // eslint-disable-next-line no-console
+ console.log( message );
+ }
+ else if( typeof opera == 'object' )
+ {
+ opera.postError( message );
+ }
+ // eslint-disable-next-line no-undef
+ else if( typeof java == 'object' && typeof java.lang == 'object' )
+ {
+ // eslint-disable-next-line no-undef
+ java.lang.System.out.println( message );
+ }
+}
+
+function getNSAttribute( sNSPrefix, aElem, sAttrName )
+{
+ if( !aElem ) return null;
+ if( 'getAttributeNS' in aElem )
+ {
+ return aElem.getAttributeNS( NSS[sNSPrefix], sAttrName );
+ }
+ else
+ {
+ return aElem.getAttribute( sNSPrefix + ':' + sAttrName );
+ }
+}
+
+function getOOOAttribute( aElem, sAttrName )
+{
+ return getNSAttribute( 'ooo', aElem, sAttrName );
+}
+
+function setNSAttribute( sNSPrefix, aElem, sAttrName, aValue )
+{
+ if( !aElem ) return false;
+ if( 'setAttributeNS' in aElem )
+ {
+ aElem.setAttributeNS( NSS[sNSPrefix], sAttrName, aValue );
+ return true;
+ }
+ else
+ {
+ aElem.setAttribute(sNSPrefix + ':' + sAttrName, aValue );
+ return true;
+ }
+}
+
+function getElementsByClassName( aElem, sClassName )
+{
+
+ var aElementSet = [];
+ // not all browsers support the 'getElementsByClassName' method
+ if( 'getElementsByClassName' in aElem )
+ {
+ aElementSet = aElem.getElementsByClassName( sClassName );
+ }
+ else
+ {
+ var aElementSetByClassProperty = getElementsByProperty( aElem, 'class' );
+ for( var i = 0; i < aElementSetByClassProperty.length; ++i )
+ {
+ var sAttrClassName = aElementSetByClassProperty[i].getAttribute( 'class' );
+ if( sAttrClassName == sClassName )
+ {
+ aElementSet.push( aElementSetByClassProperty[i] );
+ }
+ }
+ }
+ return aElementSet;
+}
+
+function getElementByClassName( aElem, sClassName /*, sTagName */)
+{
+ var aElementSet = getElementsByClassName( aElem, sClassName );
+ if ( aElementSet.length == 1 )
+ return aElementSet[0];
+ else
+ return null;
+}
+
+function getClassAttribute( aElem )
+{
+ if( aElem )
+ return aElem.getAttribute( 'class' );
+ return '';
+}
+
+function createElementGroup( aParentElement, aElementList, nFrom, nCount, sGroupClass, sGroupId )
+{
+ var nTo = nFrom + nCount;
+ if( nCount < 1 || aElementList.length < nTo )
+ {
+ log( 'createElementGroup: not enough elements available.' );
+ return;
+ }
+ var firstElement = aElementList[nFrom];
+ if( !firstElement )
+ {
+ log( 'createElementGroup: element not found.' );
+ return;
+ }
+ var aGroupElement = document.createElementNS( NSS['svg'], 'g' );
+ if( sGroupId )
+ aGroupElement.setAttribute( 'id', sGroupId );
+ if( sGroupClass )
+ aGroupElement.setAttribute( 'class', sGroupClass );
+ aParentElement.insertBefore( aGroupElement, firstElement );
+ var i = nFrom;
+ for( ; i < nTo; ++i )
+ {
+ aParentElement.removeChild( aElementList[i] );
+ aGroupElement.appendChild( aElementList[i] );
+ }
+}
+
+function initVisibilityProperty( aElement )
+{
+ var nVisibility = VISIBLE;
+ var sVisibility = aElement.getAttribute( 'visibility' );
+ if( sVisibility ) nVisibility = aVisibilityValue[ sVisibility ];
+ return nVisibility;
+}
+
+function getSafeIndex( nIndex, nMin, nMax )
+{
+ if( nIndex < nMin )
+ return nMin;
+ else if( nIndex > nMax )
+ return nMax;
+ else
+ return nIndex;
+}
+
+function getUrlParameter(name)
+{
+ name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
+ var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
+ var results = regex.exec(window.location.search);
+ return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
+}
+
+/** getRandomInt
+ *
+ * @param nMax
+ * @returns {number}
+ * an integer in [0,nMax[
+ */
+function getRandomInt( nMax )
+{
+ return Math.floor( Math.random() * nMax );
+}
+
+function isTextFieldElement( aElement ) // eslint-disable-line no-unused-vars
+{
+ var sClassName = aElement.getAttribute( 'class' );
+ return ( sClassName === aSlideNumberClassName ) ||
+ ( sClassName === aFooterClassName ) ||
+ ( sClassName === aHeaderClassName ) ||
+ ( sClassName === aDateTimeClassName );
+}
+
+
+/*********************
+ ** Debug Utilities **
+ *********************/
+
+function DebugPrinter()
+{
+ this.bEnabled = false;
+}
+
+
+DebugPrinter.prototype.on = function()
+{
+ this.bEnabled = true;
+};
+
+DebugPrinter.prototype.off = function()
+{
+ this.bEnabled = false;
+};
+
+DebugPrinter.prototype.isEnabled = function()
+{
+ return this.bEnabled;
+};
+
+DebugPrinter.prototype.print = function( sMessage, nTime )
+{
+ if( this.isEnabled() )
+ {
+ var sInfo = 'DBG: ' + sMessage;
+ if( nTime )
+ sInfo += ' (at: ' + String( nTime / 1000 ) + 's)';
+ log( sInfo );
+ }
+};
+
+
+// - Debug Printers -
+var aGenericDebugPrinter = new DebugPrinter();
+aGenericDebugPrinter.on();
+var DBGLOG = bind2( DebugPrinter.prototype.print, aGenericDebugPrinter );
+
+var NAVDBG = new DebugPrinter();
+NAVDBG.off();
+
+var ANIMDBG = new DebugPrinter();
+ANIMDBG.off();
+
+var aRegisterEventDebugPrinter = new DebugPrinter();
+aRegisterEventDebugPrinter.off();
+
+var aTimerEventQueueDebugPrinter = new DebugPrinter();
+aTimerEventQueueDebugPrinter.off();
+
+var aEventMultiplexerDebugPrinter = new DebugPrinter();
+aEventMultiplexerDebugPrinter.off();
+
+var aNextEffectEventArrayDebugPrinter = new DebugPrinter();
+aNextEffectEventArrayDebugPrinter.off();
+
+var aActivityQueueDebugPrinter = new DebugPrinter();
+aActivityQueueDebugPrinter.off();
+
+var aAnimatedElementDebugPrinter = new DebugPrinter();
+aAnimatedElementDebugPrinter.off();
+
+
+
+
+/************************
+ *** Core Classes ***
+ ************************/
+
+/** Class MetaDocument
+ * This class provides a pool of properties related to the whole presentation.
+ * Moreover it is responsible for:
+ * - initializing the set of MetaSlide objects that handle the meta information
+ * for each slide;
+ * - creating a map with key an id and value the svg element containing
+ * the animations performed on the slide with such an id.
+ *
+ */
+function MetaDocument()
+{
+ // We look for the svg element that provides the following presentation
+ // properties:
+ // - the number of slides in the presentation;
+ // - the type of numbering used in the presentation.
+ // Moreover it wraps svg elements providing meta information on each slide
+ // and svg elements providing content and properties of each text field.
+ var aMetaDocElem = document.getElementById( aOOOElemMetaSlides );
+ assert( aMetaDocElem, 'MetaDocument: the svg element with id:' + aOOOElemMetaSlides + 'is not valid.');
+
+ // We initialize general presentation properties:
+ // - the number of slides in the presentation;
+ this.nNumberOfSlides = parseInt( aMetaDocElem.getAttributeNS( NSS['ooo'], aOOOAttrNumberOfSlides ) );
+ assert( typeof this.nNumberOfSlides == 'number' && this.nNumberOfSlides > 0,
+ 'MetaDocument: number of slides is zero or undefined.' );
+ // - the index of the slide to show when the presentation starts;
+ this.nStartSlideNumber = parseInt( aMetaDocElem.getAttributeNS( NSS['ooo'], aOOOAttrStartSlideNumber ) ) || 0;
+ // - get the parameter StartSlideNumber in the URL for online presentation
+ var aParmStartSlideNumber = getUrlParameter('StartSlideNumber');
+ if (aParmStartSlideNumber !== '')
+ {
+ this.nStartSlideNumber = parseInt(aParmStartSlideNumber);
+ }
+ // - the numbering type used in the presentation, default type is arabic.
+ this.sPageNumberingType = aMetaDocElem.getAttributeNS( NSS['ooo'], aOOOAttrNumberingType ) || 'arabic';
+ // - the way text is exported
+ this.bIsUsePositionedChars = ( aMetaDocElem.getAttributeNS( NSS['ooo'], aOOOAttrUsePositionedChars ) === 'true' );
+
+ // The <defs> element used for wrapping <clipPath>.
+ this.aClipPathGroup = getElementByClassName( ROOT_NODE, aClipPathGroupClassName );
+ assert( this.aClipPathGroup, 'MetaDocument: the clip path group element is not valid.');
+
+ // The <clipPath> element used to clip all slides.
+ this.aPresentationClipPath = document.getElementById( aPresentationClipPathId );
+ assert( this.aPresentationClipPath,
+ 'MetaDocument: the presentation clip path element element is not valid.');
+
+ // The collections for handling properties of each slide, svg elements
+ // related to master pages and content and properties of text fields.
+ this.aMetaSlideSet = [];
+ this.aMasterPageSet = {};
+ this.aTextFieldHandlerSet = {};
+ this.aTextFieldContentProviderSet = [];
+ this.aSlideNumberProvider = new SlideNumberProvider( this.nStartSlideNumber + 1, this.sPageNumberingType );
+ this.aCurrentDateProvider = new CurrentDateTimeProvider( null, '<date>' );
+ this.aCurrentTimeProvider = new CurrentDateTimeProvider( null, '<time>' );
+
+ // We create a map with key an id and value the svg element containing
+ // the animations performed on the slide with such an id.
+ this.bIsAnimated = false;
+ this.aSlideAnimationsMap = {};
+ this.initSlideAnimationsMap();
+
+ // We initialize dummy slide - used as leaving slide for transition on the first slide
+ this.theMetaDummySlide = new MetaSlide( 'ooo:meta_dummy_slide', this );
+
+ // We initialize the set of MetaSlide objects that handle the meta
+ // information for each slide.
+ for( var i = 0; i < this.nNumberOfSlides; ++i )
+ {
+ var sMetaSlideId = aOOOElemMetaSlide + '_' + i;
+ this.aMetaSlideSet.push( new MetaSlide( sMetaSlideId, this ) );
+ }
+ assert( this.aMetaSlideSet.length == this.nNumberOfSlides,
+ 'MetaDocument: aMetaSlideSet.length != nNumberOfSlides.' );
+}
+
+MetaDocument.prototype =
+{
+/*** public methods ***/
+
+/** getCurrentSlide
+ *
+ * @return
+ * The MetaSlide object handling the current slide.
+ */
+getCurrentSlide : function()
+{
+ return this.aMetaSlideSet[nCurSlide];
+},
+
+/** setCurrentSlide
+ *
+ * @param nSlideIndex
+ * The index of the slide to show.
+ */
+setCurrentSlide : function( nSlideIndex )
+{
+ if( nSlideIndex >= 0 && nSlideIndex < this.nNumberOfSlides )
+ {
+ if( nCurSlide !== undefined )
+ this.aMetaSlideSet[nCurSlide].hide();
+ this.aMetaSlideSet[nSlideIndex].show();
+ nCurSlide = nSlideIndex;
+ }
+ else
+ {
+ log('MetaDocument.setCurrentSlide: slide index out of range: ' + nSlideIndex );
+ }
+},
+
+/*** private methods ***/
+
+initSlideAnimationsMap : function()
+{
+ var aAnimationsSection = document.getElementById( 'presentation-animations' );
+ if( aAnimationsSection )
+ {
+ var aAnimationsDefSet = aAnimationsSection.getElementsByTagName( 'defs' );
+
+ // we have at least one slide with animations ?
+ this.bIsAnimated = ( typeof aAnimationsDefSet.length =='number' &&
+ aAnimationsDefSet.length > 0 );
+
+ for( var i = 0; i < aAnimationsDefSet.length; ++i )
+ {
+ var sSlideId = aAnimationsDefSet[i].getAttributeNS( NSS['ooo'], aOOOAttrSlide );
+ var aChildSet = getElementChildren( aAnimationsDefSet[i] );
+ if( sSlideId && ( aChildSet.length === 1 ) )
+ {
+ this.aSlideAnimationsMap[ sSlideId ] = aChildSet[0];
+ }
+ }
+ }
+}
+
+}; // end MetaDocument prototype
+
+/** Class MetaSlide
+ * This class is responsible for:
+ * - parsing and initializing slide properties;
+ * - creating a MasterSlide object that provides direct access to the target
+ * master slide and its sub-elements;
+ * - initializing text field content providers;
+ * - initializing the slide animation handler.
+ *
+ * @param sMetaSlideId
+ * The string representing the id attribute of the meta-slide element.
+ * @param aMetaDoc
+ * The MetaDocument global object.
+ */
+function MetaSlide( sMetaSlideId, aMetaDoc )
+{
+ this.theDocument = document;
+ this.id = sMetaSlideId;
+ this.theMetaDoc = aMetaDoc;
+
+ // We get a reference to the meta-slide element.
+ this.element = this.theDocument.getElementById( this.id );
+ assert( this.element,
+ 'MetaSlide: meta_slide element <' + this.id + '> not found.' );
+
+ // We get a reference to the slide element.
+ this.slideId = this.element.getAttributeNS( NSS['ooo'], aOOOAttrSlide );
+ this.slideElement = this.theDocument.getElementById( this.slideId );
+ assert( this.slideElement,
+ 'MetaSlide: slide element <' + this.slideId + '> not found.' );
+
+ if( this.slideId !== 'dummy_slide' )
+ this.nSlideNumber = parseInt( this.slideId.substr(2) );
+ else
+ this.nSlideNumber= -1;
+
+ this.slideName = this.element.getAttributeNS( NSS['ooo'], aOOOAttrDisplayName );
+
+ // Each slide element is double wrapped by <g> elements.
+ // The outer <g> element is responsible for
+ // the slide element visibility. In fact the visibility attribute has
+ // to be set on the parent of the slide element and not directly on
+ // the slide element. The reason is that in index mode each slide
+ // rendered in a thumbnail view is targeted by a <use> element, however
+ // when the visibility attribute is set directly on the referred slide
+ // element its visibility is not overridden by the visibility attribute
+ // defined by the targeting <use> element. The previous solution was,
+ // when the user switched to index mode, to set up the visibility attribute
+ // of all slides rendered in a thumbnail to 'visible'.
+ // Obviously the slides were not really visible because the grid of
+ // thumbnails was above them, anyway Firefox performance was really bad.
+ // The workaround of setting up the visibility attribute on the slide
+ // parent element let us to make visible a slide in a <use> element
+ // even if the slide parent element visibility is set to 'hidden'.
+ // The inner <g> element is used in order to append some element
+ // before or after the slide, operation that can be needed for some
+ // slide transition (e.g. fade through black). In this way we can
+ // create a view of both the slide and the appended elements that turns out
+ // to be useful for handling transition from the last to the first slide.
+ this.aContainerElement = this.slideElement.parentNode;
+ this.slideContainerId = this.aContainerElement.getAttribute( 'id' );
+ this.aVisibilityStatusElement = this.aContainerElement.parentNode;
+
+ // We get a reference to the draw page element, where all shapes specific
+ // of this slide live.
+ this.pageElement = getElementByClassName( this.slideElement, aPageClassName );
+ assert( this.pageElement,
+ 'MetaSlide: page element <' + this.slideId + '> not found.' );
+
+ // The slide custom background element and its id attribute.
+ this.backgroundElement = getElementByClassName( this.pageElement, 'Background' );
+ if( this.backgroundElement )
+ {
+ this.backgroundId = this.backgroundElement.getAttribute( 'id' );
+ }
+
+ // We initialize text fields
+ this.initPlaceholderElements();
+
+ // We initialize the MasterPage object that provides direct access to
+ // the target master page element.
+ this.masterPage = this.initMasterPage();
+
+ // We check if the slide has a custom background which overrides the one of the targeted master page
+ this.bHasCustomBackground = this.initHasCustomBackground();
+
+ // We initialize visibility properties of the target master page elements.
+ this.nAreMasterObjectsVisible = this.initVisibilityProperty( aOOOAttrMasterObjectsVisibility, VISIBLE );
+ this.nIsBackgroundVisible = this.initVisibilityProperty( aOOOAttrBackgroundVisibility, VISIBLE );
+ this.nIsPageNumberVisible = this.initVisibilityProperty( aOOOAttrPageNumberVisibility, HIDDEN );
+ this.nIsDateTimeVisible = this.initVisibilityProperty( aOOOAttrDateTimeVisibility, VISIBLE );
+ this.nIsFooterVisible = this.initVisibilityProperty( aOOOAttrFooterVisibility, VISIBLE );
+ this.nIsHeaderVisible = this.initVisibilityProperty( aOOOAttrHeaderVisibility, VISIBLE );
+
+ // This property tell us if the date/time field need to be updated
+ // each time the slide is shown. It is initialized in
+ // the initDateTimeFieldContentProvider method.
+ this.bIsDateTimeVariable = undefined;
+
+ // We initialize the objects responsible to provide the content to text field.
+ this.aTextFieldContentProviderSet = {};
+ this.aTextFieldContentProviderSet[aSlideNumberClassName] = this.initSlideNumberFieldContentProvider();
+ this.aTextFieldContentProviderSet[aDateTimeClassName] = this.initDateTimeFieldContentProvider( aOOOAttrDateTimeField );
+ this.aTextFieldContentProviderSet[aFooterClassName] = this.initFixedTextFieldContentProvider( aOOOAttrFooterField );
+ this.aTextFieldContentProviderSet[aHeaderClassName] = this.initFixedTextFieldContentProvider( aOOOAttrHeaderField );
+ this.aTextFieldContentProviderSet[aDateClassName] = this.theMetaDoc.aCurrentDateProvider;
+ this.aTextFieldContentProviderSet[aTimeClassName] = this.theMetaDoc.aCurrentTimeProvider;
+ this.aTextFieldContentProviderSet[aSlideNameClassName] = new FixedTextProvider( this.slideName );
+
+ // We init the slide duration when automatic slide transition is enabled
+ this.fDuration = this.initSlideDuration();
+
+ // We look for slide transition.
+ this.aTransitionHandler = null;
+ this.bHasTransition = this.initHasTransition() || true;
+ if( this.bHasTransition )
+ {
+ this.aTransitionHandler = new SlideTransition( this.getSlideAnimationsRoot(), this.slideId );
+ }
+
+ // We initialize the SlideAnimationsHandler object
+ this.aSlideAnimationsHandler = new SlideAnimations( aSlideShow.getContext() );
+ this.aSlideAnimationsHandler.importAnimations( this.getSlideAnimationsRoot() );
+ this.aSlideAnimationsHandler.parseElements();
+
+ // this statement is used only for debugging
+ // eslint-disable-next-line no-constant-condition
+ if( false && this.aSlideAnimationsHandler.aRootNode )
+ log( this.aSlideAnimationsHandler.aRootNode.info( true ) );
+
+ // We collect text shapes included in this slide .
+ this.aTextShapeSet = this.collectTextShapes();
+
+ // We initialize hyperlinks
+ this.aHyperlinkSet = this.initHyperlinks();
+
+}
+
+MetaSlide.prototype =
+{
+/*** public methods ***/
+
+/** show
+ * Set the visibility property of the slide to 'inherit'
+ * and update the master page view.
+ */
+show : function()
+{
+ this.updateMasterPageView();
+ this.aVisibilityStatusElement.setAttribute( 'visibility', 'inherit' );
+},
+
+/** hide
+ * Set the visibility property of the slide to 'hidden'.
+ */
+hide : function()
+{
+ this.aVisibilityStatusElement.setAttribute( 'visibility', 'hidden' );
+},
+
+/** updateMasterPageView
+ * On first call it creates a master page view element and insert it at
+ * the begin of the slide element. Moreover it updates the text fields
+ * included in the master page view.
+ */
+updateMasterPageView : function()
+{
+ // The master page view element is generated and attached on first time
+ // the slide is shown.
+ if( !this.aMasterPageView )
+ {
+ this.aMasterPageView = new MasterPageView( this );
+ this.aMasterPageView.attachToSlide();
+ }
+ this.aMasterPageView.update();
+},
+
+/*** private methods ***/
+
+// It handles a text field inserted on a slide, not on a master page.
+initPlaceholderElements : function()
+{
+ var aPlaceholderList = getElementsByClassName(this.pageElement , 'PlaceholderText' );
+ var i = 0;
+ for( ; i < aPlaceholderList.length; ++i )
+ {
+ var aPlaceholderElem = aPlaceholderList[i];
+ var sContent = aPlaceholderElem.textContent;
+ if( sContent === '<date>' )
+ aPlaceholderElem.textContent = new Date().toLocaleDateString();
+ else if( sContent === '<time>' )
+ aPlaceholderElem.textContent = new Date().toLocaleTimeString();
+ }
+},
+
+initMasterPage : function()
+{
+ var sMasterPageId = this.element.getAttributeNS( NSS['ooo'], aOOOAttrMaster );
+
+ // Check that the master page handler object has not already been
+ // created by another slide that target the same master page.
+ if( !this.theMetaDoc.aMasterPageSet.hasOwnProperty( sMasterPageId ) )
+ {
+ this.theMetaDoc.aMasterPageSet[ sMasterPageId ] = new MasterPage( sMasterPageId, this );
+
+ // We initialize aTextFieldHandlerSet[ sMasterPageId ] to an empty
+ // collection.
+ this.theMetaDoc.aTextFieldHandlerSet[ sMasterPageId ] = {};
+ }
+ return this.theMetaDoc.aMasterPageSet[ sMasterPageId ];
+},
+
+initSlideDuration : function()
+{
+ var sSlideDuration = this.element.getAttributeNS( NSS['ooo'], aOOOAttrSlideDuration );
+ if( sSlideDuration && sSlideDuration.length > 0 )
+ return parseFloat( sSlideDuration );
+ else
+ return -1;
+},
+
+initHasTransition : function()
+{
+ var sHasTransition = this.element.getAttributeNS( NSS['ooo'], aOOOAttrHasTransition );
+ return ( sHasTransition === 'true' );
+},
+
+initHasCustomBackground : function()
+{
+ var sHasCustomBackground = this.element.getAttributeNS( NSS['ooo'], aOOOAttrHasCustomBackground );
+ return ( sHasCustomBackground === 'true' );
+},
+
+initVisibilityProperty : function( aVisibilityAttribute, nDefaultValue )
+{
+ var nVisibility = nDefaultValue;
+ var sVisibility = getOOOAttribute( this.element, aVisibilityAttribute );
+ if( sVisibility )
+ nVisibility = aVisibilityValue[ sVisibility ];
+ return nVisibility;
+},
+
+initSlideNumberFieldContentProvider : function()
+{
+ return this.theMetaDoc.aSlideNumberProvider;
+},
+
+initDateTimeFieldContentProvider : function( aOOOAttrDateTimeField )
+{
+ var sTextFieldId = getOOOAttribute( this.element, aOOOAttrDateTimeField );
+ if( !sTextFieldId ) return null;
+
+ var nLength = aOOOElemTextField.length + 1;
+ var nIndex = parseInt(sTextFieldId.substring( nLength ) );
+ if( typeof nIndex != 'number') return null;
+
+ if( !this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ] )
+ {
+ var aTextField;
+ var aTextFieldElem = document.getElementById( sTextFieldId );
+ var sClassName = getClassAttribute( aTextFieldElem );
+ if( sClassName == 'FixedDateTimeField' )
+ {
+ aTextField = new FixedTextByElementProvider( aTextFieldElem );
+ this.bIsDateTimeVariable = false;
+ }
+ else if( sClassName == 'VariableDateTimeField' )
+ {
+ aTextField = new CurrentDateTimeProvider( aTextFieldElem );
+ this.bIsDateTimeVariable = true;
+ }
+ else
+ {
+ aTextField = null;
+ }
+ this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ] = aTextField;
+ }
+ return this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ];
+},
+
+initFixedTextFieldContentProvider : function( aOOOAttribute )
+{
+ var sTextFieldId = getOOOAttribute( this.element, aOOOAttribute );
+ if( !sTextFieldId ) return null;
+
+ var nLength = aOOOElemTextField.length + 1;
+ var nIndex = parseInt( sTextFieldId.substring( nLength ) );
+ if( typeof nIndex != 'number') return null;
+
+ if( !this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ] )
+ {
+ var aTextFieldElem = document.getElementById( sTextFieldId );
+ this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ]
+ = new FixedTextByElementProvider( aTextFieldElem );
+ }
+ return this.theMetaDoc.aTextFieldContentProviderSet[ nIndex ];
+},
+
+collectTextShapes : function()
+{
+ var aTextShapeSet = [];
+ var aTextShapeIndexElem = getElementByClassName( document, 'TextShapeIndex' );
+ if( aTextShapeIndexElem )
+ {
+ var aIndexEntryList = getElementChildren( aTextShapeIndexElem );
+ var i;
+ for( i = 0; i < aIndexEntryList.length; ++i )
+ {
+ var sSlideId = getOOOAttribute( aIndexEntryList[i], 'slide' );
+ if( sSlideId === this.slideId )
+ {
+ var sTextShapeIds = getOOOAttribute( aIndexEntryList[i], 'id-list' );
+ if( sTextShapeIds )
+ {
+ var aTextShapeIdSet = sTextShapeIds.split( ' ' );
+ var j;
+ for( j = 0; j < aTextShapeIdSet.length; ++j )
+ {
+ var aTextShapeElem = document.getElementById( aTextShapeIdSet[j] );
+ if( aTextShapeElem )
+ {
+ aTextShapeSet.push( aTextShapeElem );
+ }
+ else
+ {
+ log( 'warning: MetaSlide.collectTextShapes: text shape with id <' + aTextShapeIdSet[j] + '> is not valid.' );
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ return aTextShapeSet;
+},
+
+initHyperlinks : function()
+{
+ var aHyperlinkSet = {};
+ var i;
+ for( i = 0; i < this.aTextShapeSet.length; ++i )
+ {
+ if( this.aTextShapeSet[i] )
+ {
+ var aHyperlinkIdList = getElementByClassName( this.aTextShapeSet[i], 'HyperlinkIdList' );
+ if( aHyperlinkIdList )
+ {
+ var sHyperlinkIds = aHyperlinkIdList.textContent;
+ if( sHyperlinkIds )
+ {
+ var aHyperlinkIdSet = sHyperlinkIds.trim().split( ' ' );
+ var j;
+ for( j = 0; j < aHyperlinkIdSet.length; ++j )
+ {
+ var sId = aHyperlinkIdSet[j];
+ aHyperlinkSet[ sId ] = new HyperlinkElement( sId, this.aSlideAnimationsHandler.aEventMultiplexer );
+ }
+ }
+ }
+ }
+ }
+ return aHyperlinkSet;
+},
+
+getSlideAnimationsRoot : function()
+{
+ return this.theMetaDoc.aSlideAnimationsMap[ this.slideId ];
+}
+
+}; // end MetaSlide prototype
+
+function getTextFieldType ( elem )
+{
+ var sFieldType = null;
+ var sClass = elem.getAttribute('class');
+ if( sClass == 'TextShape' )
+ {
+ var aPlaceholderElement = getElementByClassName( elem, 'PlaceholderText' );
+ if (aPlaceholderElement)
+ {
+ var sContent = aPlaceholderElement.textContent
+ if (sContent === '<number>')
+ sFieldType = aSlideNumberClassName;
+ else if (sContent === '<date>')
+ sFieldType = aDateClassName;
+ else if (sContent === '<time>')
+ sFieldType = aTimeClassName;
+ else if (sContent === '<slide-name>')
+ sFieldType = aSlideNameClassName;
+ }
+ }
+ return sFieldType;
+}
+
+function isTextFieldByClassName ( sClassName )
+{
+ return sClassName === aDateTimeClassName || sClassName === aFooterClassName
+ || sClassName === aHeaderClassName || sClassName.indexOf( aSlideNumberClassName ) == 0
+ || sClassName.indexOf( aDateClassName ) == 0 || sClassName.indexOf( aTimeClassName ) == 0
+ || sClassName.indexOf( aSlideNameClassName ) == 0;
+}
+
+/** Class MasterPage
+ * This class gives direct access to a master page element and to the following
+ * elements included in the master page:
+ * - the background element,
+ * - the background objects group element,
+ * Moreover for each text field element a Placeholder object is created which
+ * manages the text field element itself.
+ *
+ * The master page element structure is the following:
+ * <g class='Master_Slide'>
+ * <g class='Background'>
+ * background image
+ * </g>
+ * <g class='BackgroundObjects'>
+ * <g class='Date/Time'>
+ * date/time placeholder
+ * </g>
+ * <g class='Header'>
+ * header placeholder
+ * </g>
+ * <g class='Footer'>
+ * footer placeholder
+ * </g>
+ * <g class='Slide_Number'>
+ * slide number placeholder
+ * </g>
+ * shapes
+ * </g>
+ * </g>
+ *
+ * @param sMasterPageId
+ * A string representing the value of the id attribute of the master page
+ * element to be handled.
+ * @param aMetaSlide
+ * A meta slide having as master page the one with the passed id.
+ */
+function MasterPage( sMasterPageId, aMetaSlide )
+{
+ this.id = sMasterPageId;
+ this.metaSlide = aMetaSlide;
+
+ // The master page element to be handled.
+ this.element = document.getElementById( this.id );
+ assert( this.element,
+ 'MasterPage: master page element <' + this.id + '> not found.' );
+
+ // The master page background element and its id attribute.
+ this.background = getElementByClassName( this.element, 'Background' );
+ if( this.background )
+ {
+ this.backgroundId = this.background.getAttribute( 'id' );
+ this.backgroundVisibility = initVisibilityProperty( this.background );
+ }
+ else
+ {
+ this.backgroundId = '';
+ log( 'MasterPage: the background element is not valid.' );
+ }
+
+ // The background objects group element that contains every element presents
+ // on the master page except the background element.
+ this.backgroundObjects = getElementByClassName( this.element, 'BackgroundObjects' );
+ this.aBackgroundObjectSubGroupIdList = [];
+ if( this.backgroundObjects )
+ {
+ this.backgroundObjectsId = this.backgroundObjects.getAttribute( 'id' );
+ this.backgroundObjectsVisibility = initVisibilityProperty( this.backgroundObjects );
+
+ if( this.backgroundObjectsVisibility != HIDDEN )
+ {
+ var aBackgroundObjectList = getElementChildren( this.backgroundObjects );
+ var nFrom = 0;
+ var nCount = 0;
+ var nSubGroupId = 1;
+ var sClass;
+ var sId = '';
+ var i = 0;
+ for( ; i < aBackgroundObjectList.length; ++i )
+ {
+ var aObject = aBackgroundObjectList[i];
+ sClass = null;
+ var sFieldType = getTextFieldType( aObject );
+ if( sFieldType && aObject.firstElementChild )
+ {
+ var sObjId = aObject.firstElementChild.getAttribute( 'id' );
+ if( sObjId )
+ {
+ sClass = sFieldType + '.' + sObjId;
+ aObject.setAttribute('class', sClass);
+ }
+ }
+ if( !sClass )
+ {
+ sClass = aBackgroundObjectList[i].getAttribute('class');
+ }
+ if( !sClass || !isTextFieldByClassName( sClass ) )
+ {
+ if( nCount === 0 )
+ {
+ nFrom = i;
+ sId = this.backgroundObjectsId + '.' + nSubGroupId;
+ ++nSubGroupId;
+ this.aBackgroundObjectSubGroupIdList.push( sId );
+ }
+ ++nCount;
+ }
+ else
+ {
+ this.aBackgroundObjectSubGroupIdList.push( sClass );
+ if( nCount !== 0 )
+ {
+ createElementGroup( this.backgroundObjects, aBackgroundObjectList, nFrom, nCount, 'BackgroundObjectSubgroup', sId );
+ nCount = 0;
+ }
+ }
+ }
+ if( nCount !== 0 )
+ {
+ createElementGroup( this.backgroundObjects, aBackgroundObjectList, nFrom, nCount, 'BackgroundObjectSubgroup', sId );
+ }
+ }
+ }
+ else
+ {
+ this.backgroundObjectsId = '';
+ log( 'MasterPage: the background objects element is not valid.' );
+ }
+
+ // We populate the collection of placeholders.
+ this.aPlaceholderShapeSet = {};
+ this.initPlaceholderShapes();
+}
+
+MasterPage.prototype =
+{
+/*** private methods ***/
+
+initPlaceholderShapes : function()
+{
+ var sClassName;
+ var i = 0;
+ for( ; i < this.aBackgroundObjectSubGroupIdList.length; ++i )
+ {
+ sClassName = this.aBackgroundObjectSubGroupIdList[i];
+ if( isTextFieldByClassName( sClassName ) )
+ this.aPlaceholderShapeSet[ sClassName ] = new PlaceholderShape( this, sClassName );
+ }
+}
+
+}; // end MasterPage prototype
+
+/** Class PlaceholderShape
+ * This class provides direct access to a text field element and
+ * to the embedded placeholder element.
+ * Moreover it set up the text adjustment and position for the placeholder
+ * element.
+ * Note: the text field element included in a master page is used only as
+ * a template element, it is cloned for each specific text content
+ * (see the TextFieldContentProvider class and its derived classes).
+ *
+ * @param aMasterPage
+ * The master page object to which the text field to be handled belongs.
+ * @param sClassName
+ * A string representing the value of the class attribute of the text
+ * field element to be handled.
+ */
+function PlaceholderShape( aMasterPage, sClassName )
+{
+ this.masterPage = aMasterPage;
+ this.className = sClassName;
+
+ this.element = null;
+ this.textElement = null;
+ this.init();
+}
+
+/* public methods */
+PlaceholderShape.prototype.isValid = function()
+{
+ return ( this.element && this.textElement );
+};
+
+/* private methods */
+
+/** init
+ * In case a text field element of class type 'className' exists and such
+ * an element embeds a placeholder element, the text adjustment and position
+ * of the placeholder element is set up.
+ */
+PlaceholderShape.prototype.init = function()
+{
+ var aTextFieldElement = getElementByClassName( this.masterPage.backgroundObjects, this.className );
+ if( aTextFieldElement )
+ {
+ var aTextElem = getElementByClassName( aTextFieldElement, 'SVGTextShape' );
+ if( aTextElem )
+ {
+ var aPlaceholderElement = getElementByClassName(aTextElem, 'PlaceholderText');
+ if( aPlaceholderElement )
+ {
+ // SVG 1.1 does not support text wrapping wrt a rectangle.
+ // When a text shape contains a placeholder, setting up the position
+ // of each text line doesn't work since the position is computed
+ // before replacing the placeholder text.
+ // Anyway each text shape has an invisible rectangle that can be
+ // regarded as the text shape bounding box.
+ // We exploit such a feature and the exported text adjust attribute
+ // value in order to set up correctly the position and text
+ // adjustment for the text shape content.
+ // We assume that once the real value has been substituted to
+ // the placeholder the resulting content is no more than a single line.
+ // So we remove from <tspan> elements used for setting up the
+ // position of text lines (class TextPosition) the 'x' and 'y' attribute.
+ // In the general case we would need to implement a function
+ // which is able to compute at which words the text shape content has
+ // to be wrapped.
+ var aSVGRectElem = getElementByClassName( aTextFieldElement, 'BoundingBox' );
+ if( aSVGRectElem )
+ {
+ var aRect = new Rectangle( aSVGRectElem );
+ var sTextAdjust = getOOOAttribute( aTextFieldElement, aOOOAttrTextAdjust );
+ // the bbox of the text shape is indeed a bit larger, there is a bit of internal padding
+ var nMargin = 250; // 1000th mm
+ var sTextAnchor, sX;
+ if( sTextAdjust == 'left' )
+ {
+ sTextAnchor = 'start';
+ sX = String( Math.trunc( aRect.left + nMargin ) );
+ }
+ else if( sTextAdjust == 'right' )
+ {
+ sTextAnchor = 'end';
+ sX = String( Math.trunc( aRect.right - nMargin ) );
+ }
+ else if( sTextAdjust == 'center' )
+ {
+ sTextAnchor = 'middle';
+ var nMiddle = ( aRect.left + aRect.right ) / 2;
+ sX = String( parseInt( String( nMiddle ) ) );
+ }
+ if( sTextAnchor )
+ {
+ aTextElem.setAttribute( 'text-anchor', sTextAnchor );
+ if( sX )
+ aTextElem.setAttribute( 'x', sX );
+
+ var aTSpanElements = getElementsByClassName( aTextElem, 'TextPosition' );
+ if( aTSpanElements )
+ {
+ var i = 0;
+ for( ; i < aTSpanElements.length; ++i )
+ {
+ var aTSpanElem = aTSpanElements[i];
+ aTSpanElem.removeAttribute( 'x' );
+ if( i !== 0 )
+ aTSpanElem.removeAttribute( 'y' );
+ }
+ }
+ }
+ }
+
+ // date/time fields were not exported correctly when positioned chars are used
+ if( this.masterPage.metaSlide.theMetaDoc.bIsUsePositionedChars )
+ {
+ // We remove all text lines but the first one used as placeholder.
+ var aTextLineGroupElem = aPlaceholderElement.parentNode.parentNode;
+ if( aTextLineGroupElem )
+ {
+ // Just to be sure it is the element we are looking for.
+ var sFontFamilyAttr = aTextLineGroupElem.getAttribute( 'font-family' );
+ if( sFontFamilyAttr )
+ {
+ var aChildSet = getElementChildren( aTextLineGroupElem );
+ if( aChildSet.length > 1 )
+ var i = 1;
+ for( ; i < aChildSet.length; ++i )
+ {
+ aTextLineGroupElem.removeChild( aChildSet[i] );
+ }
+ }
+ }
+ }
+ this.textElement = aPlaceholderElement;
+ }
+ }
+ this.element = aTextFieldElement;
+ }
+};
+
+/** Class MasterPageView
+ * This class is used to creates a svg element of class MasterPageView and its
+ * sub-elements.
+ * It is also responsible for updating the content of the included text fields.
+ *
+ * MasterPageView element structure:
+ *
+ * <g class='MasterPageView'>
+ * <use class='Background'> // reference to master page background element
+ * <g class='BackgroundObjects'>
+ * <use class='BackgroundObjectSubGroup'> // reference to the group of shapes on the master page that are below text fields
+ * <g class='Slide_Number'> // a cloned element
+ * ...
+ * </g>
+ * <use class='Date/Time'> // reference to a clone
+ * <use class='Footer'>
+ * <use class='Header'>
+ * <use class='BackgroundObjectSubGroup'> // reference to the group of shapes on the master page that are above text fields
+ * </g>
+ * </g>
+ *
+ * Sub-elements are present only if they are visible.
+ *
+ * @param aMetaSlide
+ * The MetaSlide object managing the slide element that targets
+ * the master page view element created by an instance of MasterPageView.
+ */
+function MasterPageView( aMetaSlide )
+{
+ this.aMetaSlide = aMetaSlide;
+ this.aSlideElement = aMetaSlide.slideElement;
+ this.aPageElement = aMetaSlide.pageElement;
+ this.aMasterPage = aMetaSlide.masterPage;
+ this.aMPVElement = this.createElement();
+ this.bIsAttached = false;
+}
+
+/*** public methods ***/
+
+/** attachToSlide
+ * Prepend the master slide view element to the slide element.
+ */
+MasterPageView.prototype.attachToSlide = function()
+{
+ if( !this.bIsAttached )
+ {
+ var aInsertedElement = this.aSlideElement.insertBefore( this.aMPVElement, this.aPageElement );
+ assert( aInsertedElement === this.aMPVElement,
+ 'MasterPageView.attachToSlide: aInsertedElement != this.aMPVElement' );
+
+ this.bIsAttached = true;
+ }
+};
+
+/** detachFromSlide
+ * Remove the master slide view element from the slide element.
+ */
+MasterPageView.prototype.detachFromSlide = function()
+{
+ if( this.bIsAttached )
+ {
+ this.aSlideElement.removeChild( this.aMPVElement );
+ this.bIsAttached = false;
+ }
+};
+
+/** update
+ * Update the content of text fields placed on the master page.
+ */
+MasterPageView.prototype.update = function()
+{
+ if( this.aDateTimeFieldHandler && this.aMetaSlide.bIsDateTimeVariable )
+ this.aDateTimeFieldHandler.update();
+};
+
+/*** private methods ***/
+
+MasterPageView.prototype.createElement = function()
+{
+ var theDocument = document;
+ var aMasterPageViewElement = theDocument.createElementNS( NSS['svg'], 'g' );
+ assert( aMasterPageViewElement,
+ 'MasterPageView.createElement: failed to create a master page view element.' );
+ aMasterPageViewElement.setAttribute( 'class', 'MasterPageView' );
+
+ // we place a white rect below any else element
+ // that is also a workaround for some kind of slide transition
+ // when the master page is empty
+ var aWhiteRect = theDocument.createElementNS( NSS['svg'], 'rect' );
+ var nWidthExt = WIDTH / 1000;
+ var nHeightExt = HEIGHT / 1000;
+ aWhiteRect.setAttribute( 'x', String( -nWidthExt / 2 ) );
+ aWhiteRect.setAttribute( 'y', String( -nHeightExt / 2 ) );
+ aWhiteRect.setAttribute( 'width', String( WIDTH + nWidthExt ) );
+ aWhiteRect.setAttribute( 'height', String( HEIGHT + nHeightExt ) );
+ aWhiteRect.setAttribute( 'fill', '#FFFFFF' );
+ aMasterPageViewElement.appendChild( aWhiteRect );
+
+ // init the Background element
+ if( this.aMetaSlide.nIsBackgroundVisible )
+ {
+ var nBackgroundId = this.aMetaSlide.bHasCustomBackground ? this.aMetaSlide.backgroundId : this.aMasterPage.backgroundId;
+ this.aBackgroundElement = theDocument.createElementNS( NSS['svg'], 'use' );
+ this.aBackgroundElement.setAttribute( 'class', 'Background' );
+ setNSAttribute( 'xlink', this.aBackgroundElement,
+ 'href', '#' + nBackgroundId );
+
+ // node linking
+ aMasterPageViewElement.appendChild( this.aBackgroundElement );
+ }
+
+ // init the BackgroundObjects element
+ if( this.aMetaSlide.nAreMasterObjectsVisible )
+ {
+ this.aBackgroundObjectsElement = theDocument.createElementNS( NSS['svg'], 'g' );
+ this.aBackgroundObjectsElement.setAttribute( 'class', 'BackgroundObjects' );
+
+ // clone and initialize text field elements
+ var aBackgroundObjectSubGroupIdList = this.aMasterPage.aBackgroundObjectSubGroupIdList;
+ this.aBackgroundSubGroupElementSet = [];
+ var aPlaceholderShapeSet = this.aMasterPage.aPlaceholderShapeSet;
+ var aTextFieldContentProviderSet = this.aMetaSlide.aTextFieldContentProviderSet;
+ // where cloned elements are appended
+ var aDefsElement = this.aMetaSlide.element.parentNode;
+ var aTextFieldHandlerSet = this.aMetaSlide.theMetaDoc.aTextFieldHandlerSet;
+ var sMasterSlideId = this.aMasterPage.id;
+
+ var i = 0;
+ var sId;
+ for( ; i < aBackgroundObjectSubGroupIdList.length; ++i )
+ {
+ sId = aBackgroundObjectSubGroupIdList[i];
+ if( sId.indexOf( aSlideNumberClassName ) == 0 )
+ {
+ // Slide Number Field
+ // The cloned element is appended directly to the field group element
+ // since there is no slide number field content shared between two slide
+ // (because the slide number of two slide is always different).
+ var nIsPageNumberVisible = sId === aSlideNumberClassName ? this.aMetaSlide.nIsPageNumberVisible : true;
+ if( aPlaceholderShapeSet[sId] &&
+ aPlaceholderShapeSet[sId].isValid() &&
+ nIsPageNumberVisible &&
+ aTextFieldContentProviderSet[aSlideNumberClassName] )
+ {
+ var aSlideNumberFieldHandler =
+ new SlideNumberFieldHandler( aPlaceholderShapeSet[sId],
+ aTextFieldContentProviderSet[aSlideNumberClassName] );
+ aSlideNumberFieldHandler.update( this.aMetaSlide.nSlideNumber );
+ aSlideNumberFieldHandler.appendTo( this.aBackgroundObjectsElement );
+ if ( sId === aSlideNumberClassName )
+ this.aSlideNumberFieldHandler = aSlideNumberFieldHandler;
+ }
+ }
+ else if( sId === aDateTimeClassName )
+ {
+ // Date/Time field
+ if( this.aMetaSlide.nIsDateTimeVisible )
+ {
+ this.aDateTimeFieldHandler =
+ this.initTextFieldHandler( aDateTimeClassName, aPlaceholderShapeSet,
+ aTextFieldContentProviderSet, aDefsElement,
+ aTextFieldHandlerSet, sMasterSlideId );
+ }
+ }
+ else if( sId === aFooterClassName )
+ {
+ // Footer Field
+ if( this.aMetaSlide.nIsFooterVisible )
+ {
+ this.aFooterFieldHandler =
+ this.initTextFieldHandler( aFooterClassName, aPlaceholderShapeSet,
+ aTextFieldContentProviderSet, aDefsElement,
+ aTextFieldHandlerSet, sMasterSlideId );
+ }
+ }
+ else if( sId === aHeaderClassName )
+ {
+ // Header Field
+ if( this.aMetaSlide.nIsHeaderVisible )
+ {
+ this.aHeaderFieldHandler =
+ this.initTextFieldHandler( aHeaderClassName, aPlaceholderShapeSet,
+ aTextFieldContentProviderSet, aDefsElement,
+ aTextFieldHandlerSet, sMasterSlideId );
+ }
+ }
+ else if( sId.indexOf( aDateClassName ) == 0
+ || sId.indexOf( aTimeClassName ) == 0
+ || sId.indexOf( aSlideNameClassName ) == 0 )
+ {
+ this.initTextFieldHandler( sId, aPlaceholderShapeSet,
+ aTextFieldContentProviderSet, aDefsElement,
+ aTextFieldHandlerSet, sMasterSlideId );
+ }
+ else
+ {
+ // init BackgroundObjectSubGroup elements
+ var aBackgroundSubGroupElement = theDocument.createElementNS( NSS['svg'], 'use' );
+ aBackgroundSubGroupElement.setAttribute( 'class', 'BackgroundObjectSubGroup' );
+ setNSAttribute( 'xlink', aBackgroundSubGroupElement,
+ 'href', '#' + sId );
+ this.aBackgroundSubGroupElementSet.push( aBackgroundSubGroupElement );
+ // node linking
+ this.aBackgroundObjectsElement.appendChild( aBackgroundSubGroupElement );
+ }
+
+ }
+ // node linking
+ aMasterPageViewElement.appendChild( this.aBackgroundObjectsElement );
+ }
+
+ return aMasterPageViewElement;
+};
+
+MasterPageView.prototype.initTextFieldHandler =
+ function( sId, aPlaceholderShapeSet, aTextFieldContentProviderSet,
+ aDefsElement, aTextFieldHandlerSet, sMasterSlideId )
+{
+ var sRefId = null;
+ var aTextFieldHandler = null;
+ var sClassName = sId.split('.')[0];
+ var aPlaceholderShape = aPlaceholderShapeSet[sId];
+ var aTextFieldContentProvider = aTextFieldContentProviderSet[sClassName];
+ if( aPlaceholderShape && aPlaceholderShape.isValid()
+ && aTextFieldContentProvider )
+ {
+ var sTextFieldContentProviderId = aTextFieldContentProvider.sId;
+ // We create only one single TextFieldHandler object (and so one only
+ // text field clone) per master slide and text content.
+ if ( !aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ] )
+ {
+ aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ] =
+ new TextFieldHandler( aPlaceholderShape,
+ aTextFieldContentProvider );
+ aTextFieldHandler = aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ];
+ aTextFieldHandler.update();
+ aTextFieldHandler.appendTo( aDefsElement );
+ }
+ else
+ {
+ aTextFieldHandler = aTextFieldHandlerSet[ sMasterSlideId ][ sTextFieldContentProviderId ];
+ }
+ sRefId = aTextFieldHandler.sId;
+ }
+ else if( aPlaceholderShape && aPlaceholderShape.element && aPlaceholderShape.element.firstElementChild
+ && !aPlaceholderShape.textElement && !aTextFieldContentProvider )
+ {
+ sRefId = aPlaceholderShape.element.firstElementChild.getAttribute('id');
+ }
+
+ if( sRefId )
+ {
+ // We create a <use> element referring to the cloned text field and
+ // append it to the field group element.
+ var aTextFieldElement = document.createElementNS(NSS['svg'], 'use');
+ aTextFieldElement.setAttribute('class', sClassName);
+ setNSAttribute('xlink', aTextFieldElement,
+ 'href', '#' + sRefId);
+ // node linking
+ this.aBackgroundObjectsElement.appendChild( aTextFieldElement );
+ }
+ return aTextFieldHandler;
+};
+
+/** Class TextFieldHandler
+ * This class clone a text field field of a master page and set up
+ * the content of the cloned element on demand.
+ *
+ * @param aPlaceholderShape
+ * A PlaceholderShape object that provides the text field to be cloned.
+ * @param aTextContentProvider
+ * A TextContentProvider object to which the actual content updating is
+ * demanded.
+ */
+function TextFieldHandler( aPlaceholderShape, aTextContentProvider )
+{
+ this.aPlaceHolderShape = aPlaceholderShape;
+ this.aTextContentProvider = aTextContentProvider;
+ assert( this.aTextContentProvider,
+ 'TextFieldHandler: text content provider not defined.' );
+ this.sId = 'tf' + String( TextFieldHandler.getUniqueId() );
+ // The cloned text field element to be handled.
+ this.aTextFieldElement = null;
+ // The actual <text> element where the field content has to be placed.
+ this.aTextPlaceholderElement = null;
+ this.cloneElement();
+}
+
+/*** private methods ***/
+
+TextFieldHandler.CURR_UNIQUE_ID = 0;
+
+TextFieldHandler.getUniqueId = function()
+{
+ ++TextFieldHandler.CURR_UNIQUE_ID;
+ return TextFieldHandler.CURR_UNIQUE_ID;
+};
+
+TextFieldHandler.prototype.cloneElement = function()
+{
+ assert( this.aPlaceHolderShape && this.aPlaceHolderShape.isValid(),
+ 'TextFieldHandler.cloneElement: placeholder shape is not valid.' );
+ // The cloned text field element.
+ this.aTextFieldElement = this.aPlaceHolderShape.element.cloneNode( true /* deep clone */ );
+ assert( this.aTextFieldElement,
+ 'TextFieldHandler.cloneElement: aTextFieldElement is not defined' );
+ this.aTextFieldElement.setAttribute( 'id', this.sId );
+ // Text field placeholder visibility is always set to 'hidden'.
+ this.aTextFieldElement.removeAttribute( 'visibility' );
+ // The actual <text> element where the field content has to be placed.
+ this.aTextPlaceholderElement = getElementByClassName( this.aTextFieldElement, 'PlaceholderText' );
+ assert( this.aTextPlaceholderElement,
+ 'TextFieldHandler.cloneElement: aTextPlaceholderElement is not defined' );
+};
+
+/*** public methods ***/
+
+/** appendTo
+ * Append the cloned text field element to a svg element.
+ *
+ * @param aParentNode
+ * The svg element to which the cloned text field has to be appended.
+ */
+TextFieldHandler.prototype.appendTo = function( aParentNode )
+{
+ if( !this.aTextFieldElement )
+ {
+ log( 'TextFieldHandler.appendTo: aTextFieldElement is not defined' );
+ return;
+ }
+ if( !aParentNode )
+ {
+ log( 'TextFieldHandler.appendTo: parent node is not defined' );
+ return;
+ }
+
+ aParentNode.appendChild( this.aTextFieldElement );
+};
+
+/** setTextContent
+ * Modify the content of the cloned text field.
+ *
+ * @param sText
+ * A string representing the new content of the cloned text field.
+ */
+TextFieldHandler.prototype.setTextContent = function( sText )
+{
+ if( !this.aTextPlaceholderElement )
+ {
+ log( 'PlaceholderShape.setTextContent: text element is not valid in placeholder of type '
+ + this.className + ' that belongs to master slide ' + this.masterPage.id );
+ return;
+ }
+ this.aTextPlaceholderElement.textContent = sText;
+};
+
+/** update
+ * Update the content of the handled text field. The new content is provided
+ * directly from the TextContentProvider data member.
+ */
+TextFieldHandler.prototype.update = function()
+{
+ if( !this.aTextContentProvider )
+ log('TextFieldHandler.update: text content provider not defined.');
+ else
+ this.aTextContentProvider.update( this );
+};
+
+/** SlideNumberFieldHandler
+ * This class clone the slide number field of a master page and set up
+ * the content of the cloned element on demand.
+ *
+ * @param aPlaceholderShape
+ * A PlaceholderShape object that provides the slide number field
+ * to be cloned.
+ * @param aTextContentProvider
+ * A SlideNumberProvider object to which the actual content updating is
+ * demanded.
+ */
+function SlideNumberFieldHandler( aPlaceholderShape, aTextContentProvider )
+{
+ SlideNumberFieldHandler.superclass.constructor.call( this, aPlaceholderShape, aTextContentProvider );
+}
+extend( SlideNumberFieldHandler, TextFieldHandler );
+
+/*** public methods ***/
+
+/** update
+ * Update the content of the handled slide number field with the passed number.
+ *
+ * @param nPageNumber
+ * The number representing the new content of the slide number field.
+ */
+SlideNumberFieldHandler.prototype.update = function( nPageNumber )
+{
+ // The actual content updating is demanded to the related
+ // SlideNumberProvider instance that have the needed info on
+ // the numbering type.
+ if( !this.aTextContentProvider )
+ log('TextFieldHandler.update: text content provider not defined.');
+ else
+ this.aTextContentProvider.update( this, nPageNumber );
+};
+
+
+/******************************************************************************
+ * Text Field Content Provider Class Hierarchy
+ *
+ * The following classes are responsible to format and set the text content
+ * of text fields.
+ *
+ ******************************************************************************/
+
+/** Class TextFieldContentProvider
+ * This class is the root abstract class of the hierarchy.
+ *
+ * @param aTextFieldContentElement
+ * The svg element that contains the text content for one or more
+ * master slide text field.
+ */
+function TextFieldContentProvider()
+{
+ this.sId = TextFieldContentProvider.getUniqueId();
+}
+
+/*** private methods ***/
+
+TextFieldContentProvider.CURR_UNIQUE_ID = 0;
+
+TextFieldContentProvider.getUniqueId = function()
+{
+ ++TextFieldContentProvider.CURR_UNIQUE_ID;
+ return TextFieldContentProvider.CURR_UNIQUE_ID;
+};
+
+/** Class FixedTextProvider
+ * This class handles text field with a fixed text.
+ * The text content is provided by the 'text' property.
+ *
+ * @param aText
+ * a string containing the text to be substituted.
+ */
+function FixedTextProvider( aText )
+{
+ FixedTextProvider.superclass.constructor.call( this );
+ this.text = aText;
+}
+extend( FixedTextProvider, TextFieldContentProvider );
+
+/*** public methods ***/
+
+/** update
+ * Set up the content of a fixed text field.
+ *
+ * @param aFixedTextField
+ * An object that implement a setTextContent( String ) method in order
+ * to set the content of a given text field.
+ */
+FixedTextProvider.prototype.update = function( aFixedTextField )
+{
+ aFixedTextField.setTextContent( this.text );
+};
+
+/** Class FixedTextByElementProvider
+ * This class handles text field with a fixed text.
+ * The text content is provided by the 'text' property.
+ *
+ * @param aTextFieldContentElement
+ * The svg element that contains the text content for one or more
+ * master slide text field.
+ */
+function FixedTextByElementProvider( aTextFieldContentElement )
+{
+ FixedTextByElementProvider.superclass.constructor.call( this, aTextFieldContentElement.textContent );
+}
+extend( FixedTextByElementProvider, FixedTextProvider );
+
+/** Class CurrentDateTimeProvider
+ * Provide the text content to a date/time field by generating the current
+ * date/time in the format specified by the 'dateTimeFormat' property.
+ *
+ * @param aTextFieldContentElement
+ * The svg element that contains the date/time format for one or more
+ * master slide date/time field.
+ */
+function CurrentDateTimeProvider( aTextFieldContentElement, sDateTimeFormat )
+{
+ CurrentDateTimeProvider.superclass.constructor.call( this, aTextFieldContentElement );
+ if( aTextFieldContentElement )
+ this.dateTimeFormat = getOOOAttribute( aTextFieldContentElement, aOOOAttrDateTimeFormat );
+ else
+ {
+ this.dateTimeFormat = sDateTimeFormat;
+ }
+}
+extend( CurrentDateTimeProvider, TextFieldContentProvider );
+
+/*** public methods ***/
+
+/** update
+ * Set up the content of a variable date/time field.
+ *
+ * @param aDateTimeField
+ * An object that implement a setTextContent( String ) method in order
+ * to set the content of a given text field.
+ */
+CurrentDateTimeProvider.prototype.update = function( aDateTimeField )
+{
+ var sText = this.createDateTimeText();
+ aDateTimeField.setTextContent( sText );
+};
+
+/*** private methods ***/
+
+CurrentDateTimeProvider.prototype.createDateTimeText = function()
+{
+ // TODO handle date/time format
+ var sDate;
+ if( this.dateTimeFormat === '<date>' )
+ sDate = new Date().toLocaleDateString();
+ else if( this.dateTimeFormat === '<time>' )
+ sDate = new Date().toLocaleTimeString();
+ else
+ sDate = new Date().toLocaleDateString();
+ return sDate;
+};
+
+/** Class SlideNumberProvider
+ * Provides the text content to the related text field by generating
+ * the current page number in the given page numbering type.
+ */
+function SlideNumberProvider( nInitialSlideNumber, sPageNumberingType )
+{
+ SlideNumberProvider.superclass.constructor.call( this );
+ this.nInitialSlideNumber = nInitialSlideNumber;
+ this.pageNumberingType = sPageNumberingType;
+
+}
+extend( SlideNumberProvider, TextFieldContentProvider );
+
+/*** public methods ***/
+
+/** getNumberingType
+ *
+ * @return
+ * The page numbering type.
+ */
+SlideNumberProvider.prototype.getNumberingType = function()
+{
+ return this.pageNumberingType;
+};
+
+/** update
+ * Set up the content of a slide number field.
+ *
+ * @param aSlideNumberField
+ * An object that implement a setTextContent( String ) method in order
+ * to set the content of a given text field.
+ * @param nSlideNumber
+ * An integer representing the slide number.
+ */
+
+SlideNumberProvider.prototype.update = function( aSlideNumberField, nSlideNumber )
+{
+ if( nSlideNumber === undefined )
+ {
+ if( nCurSlide === undefined )
+ nSlideNumber = this.nInitialSlideNumber;
+ else
+ nSlideNumber = nCurSlide + 1;
+ }
+ var sText = this.createSlideNumberText( nSlideNumber, this.getNumberingType() );
+ aSlideNumberField.setTextContent( sText );
+};
+
+/*** private methods ***/
+
+SlideNumberProvider.prototype.createSlideNumberText = function( nSlideNumber /*, sNumberingType*/ )
+{
+ // TODO handle page numbering type
+ return String( nSlideNumber );
+};
+
+
+
+
+/********************************
+ ** Slide Index Classes **
+ ********************************/
+
+/** Class SlideIndexPage **
+ * This class is responsible for handling the slide index page
+ */
+function SlideIndexPage()
+{
+ this.pageElementId = 'slide_index';
+ this.pageBgColor = 'rgb(252,252,252)';
+ this.pageElement = this.createPageElement();
+ assert( this.pageElement, 'SlideIndexPage: pageElement is not valid' );
+ this.indexColumns = INDEX_COLUMNS_DEFAULT;
+ this.totalThumbnails = this.indexColumns * this.indexColumns;
+ this.selectedSlideIndex = undefined;
+
+ // set up layout parameters
+ this.xSpacingFactor = 600/28000;
+ this.ySpacingFactor = 450/21000;
+ this.xSpacing = WIDTH * this.xSpacingFactor;
+ this.ySpacing = HEIGHT * this.ySpacingFactor;
+ this.halfBorderWidthFactor = ( 300/28000 ) * ( this.indexColumns / 3 );
+ this.halfBorderWidth = WIDTH * this.halfBorderWidthFactor;
+ this.borderWidth = 2 * this.halfBorderWidth;
+ // the following formula is used to compute the slide shrinking factor:
+ // scaleFactor = ( WIDTH - ( columns + 1 ) * xSpacing ) / ( columns * ( WIDTH + borderWidth ) )
+ // indeed we can divide everything by WIDTH:
+ this.scaleFactor = ( 1 - ( this.indexColumns + 1 ) * this.xSpacingFactor ) /
+ ( this.indexColumns * ( 1 + 2 * this.halfBorderWidthFactor ) );
+
+ // We create a Thumbnail Border and Thumbnail MouseArea rectangle template that will be
+ // used by every Thumbnail. The Mouse Area rectangle is used in order to trigger the
+ // mouseover event properly even when the slide background is hidden.
+ this.thumbnailMouseAreaTemplateId = 'thumbnail_mouse_area';
+ this.thumbnailMouseAreaTemplateElement = null;
+ this.thumbnailBorderTemplateId = 'thumbnail_border';
+ this.thumbnailBorderTemplateElement = null;
+ this.createTemplateElements();
+
+ // Now we create the grid of thumbnails
+ this.aThumbnailSet = new Array( this.totalThumbnails );
+ for( var i = 0; i < this.totalThumbnails; ++i )
+ {
+ this.aThumbnailSet[i] = new Thumbnail( this, i );
+ this.aThumbnailSet[i].updateView();
+ }
+
+ this.curThumbnailIndex = 0;
+}
+
+
+/* public methods */
+SlideIndexPage.prototype.getTotalThumbnails = function()
+{
+ return this.totalThumbnails;
+};
+
+SlideIndexPage.prototype.show = function()
+{
+ this.pageElement.setAttribute( 'display', 'inherit' );
+};
+
+SlideIndexPage.prototype.hide = function()
+{
+ this.pageElement.setAttribute( 'display', 'none' );
+};
+
+/** setSelection
+ *
+ * Change the selected thumbnail from the current one to the thumbnail with index nIndex.
+ *
+ * @param nIndex - the thumbnail index
+ */
+SlideIndexPage.prototype.setSelection = function( nIndex )
+{
+ nIndex = getSafeIndex( nIndex, 0, this.getTotalThumbnails() - 1 );
+ if( this.curThumbnailIndex != nIndex )
+ {
+ this.aThumbnailSet[ this.curThumbnailIndex ].unselect();
+ this.aThumbnailSet[ nIndex ].select();
+ this.curThumbnailIndex = nIndex;
+ }
+ this.selectedSlideIndex = this.aThumbnailSet[ nIndex ].slideIndex;
+};
+
+SlideIndexPage.prototype.createPageElement = function()
+{
+ var aPageElement = document.createElementNS( NSS['svg'], 'g' );
+ aPageElement.setAttribute( 'id', this.pageElementId );
+ aPageElement.setAttribute( 'display', 'none' );
+ aPageElement.setAttribute( 'visibility', 'visible' );
+
+ // the slide index page background
+ var sPageBgColor = this.pageBgColor + ';';
+ var aRectElement = document.createElementNS( NSS['svg'], 'rect' );
+ aRectElement.setAttribute( 'x', 0 );
+ aRectElement.setAttribute( 'y', 0 );
+ aRectElement.setAttribute( 'width', WIDTH );
+ aRectElement.setAttribute( 'height', HEIGHT );
+ aRectElement.setAttribute( 'style', 'stroke:none;fill:' + sPageBgColor );
+
+ aPageElement.appendChild( aRectElement );
+ // The index page is appended after all slide elements
+ // so when it is displayed it covers them all
+ ROOT_NODE.appendChild( aPageElement );
+ return( document.getElementById( this.pageElementId ) );
+};
+
+SlideIndexPage.prototype.createTemplateElements = function()
+{
+ // We define a Rect element as a template of thumbnail border for all slide-thumbnails.
+ // The stroke color is defined individually by each thumbnail according to
+ // its selection status.
+ var aDefsElement = document.createElementNS( NSS['svg'], 'defs' );
+ var aRectElement = document.createElementNS( NSS['svg'], 'rect' );
+ aRectElement.setAttribute( 'id', this.thumbnailBorderTemplateId );
+ aRectElement.setAttribute( 'x', -this.halfBorderWidth );
+ aRectElement.setAttribute( 'y', -this.halfBorderWidth );
+ aRectElement.setAttribute( 'rx', this.halfBorderWidth );
+ aRectElement.setAttribute( 'ry', this.halfBorderWidth );
+ aRectElement.setAttribute( 'width', WIDTH + this.halfBorderWidth );
+ aRectElement.setAttribute( 'height', HEIGHT + this.halfBorderWidth );
+ aRectElement.setAttribute( 'stroke-width', this.borderWidth );
+ aRectElement.setAttribute( 'fill', 'none' );
+ aDefsElement.appendChild( aRectElement );
+
+ // We define a Rect element as a template of mouse area for triggering the mouseover event.
+ // A copy is used by each thumbnail element.
+ aRectElement = document.createElementNS( NSS['svg'], 'rect' );
+ aRectElement.setAttribute( 'id', this.thumbnailMouseAreaTemplateId );
+ aRectElement.setAttribute( 'x', 0 );
+ aRectElement.setAttribute( 'y', 0 );
+ aRectElement.setAttribute( 'width', WIDTH );
+ aRectElement.setAttribute( 'height', HEIGHT );
+ aRectElement.setAttribute( 'fill', this.pageBgColor );
+ aDefsElement.appendChild( aRectElement );
+
+ this.pageElement.appendChild( aDefsElement );
+
+ this.thumbnailMouseAreaTemplateElement = document.getElementById( this.thumbnailMouseAreaTemplateId );
+ this.thumbnailBorderTemplateElement = document.getElementById( this.thumbnailBorderTemplateId );
+};
+
+SlideIndexPage.prototype.decreaseNumberOfColumns = function()
+{
+ this.setNumberOfColumns( this.indexColumns - 1 );
+};
+
+SlideIndexPage.prototype.increaseNumberOfColumns = function()
+{
+ this.setNumberOfColumns( this.indexColumns + 1 );
+};
+
+SlideIndexPage.prototype.resetNumberOfColumns = function()
+{
+ this.setNumberOfColumns( INDEX_COLUMNS_DEFAULT );
+};
+
+/** setNumberOfColumns
+ *
+ * Change the size of the thumbnail grid.
+ *
+ * @param nNumberOfColumns - the new number of columns/rows of the thumbnail grid
+ */
+SlideIndexPage.prototype.setNumberOfColumns = function( nNumberOfColumns )
+{
+ if( this.indexColumns == nNumberOfColumns ) return;
+ if( nNumberOfColumns < 2 || nNumberOfColumns > 6 ) return;
+
+ var suspendHandle = ROOT_NODE.suspendRedraw(500);
+
+ var nOldTotalThumbnails = this.totalThumbnails;
+ this.indexColumns = nNumberOfColumns;
+ this.totalThumbnails = nNumberOfColumns * nNumberOfColumns;
+
+ this.aThumbnailSet[this.curThumbnailIndex].unselect();
+
+ // if we decreased the number of used columns we remove the exceeding thumbnail elements
+ var i;
+ for( i = this.totalThumbnails; i < nOldTotalThumbnails; ++i )
+ {
+ this.aThumbnailSet[i].removeElement();
+ }
+
+ // if we increased the number of used columns we create the needed thumbnail objects
+ for( i = nOldTotalThumbnails; i < this.totalThumbnails; ++i )
+ {
+ this.aThumbnailSet[i] = new Thumbnail( this, i );
+ }
+
+ // we set up layout parameters that depend on the number of columns
+ this.halfBorderWidthFactor = ( 300/28000 ) * ( this.indexColumns / 3 );
+ this.halfBorderWidth = WIDTH * this.halfBorderWidthFactor;
+ this.borderWidth = 2 * this.halfBorderWidth;
+ // scaleFactor = ( WIDTH - ( columns + 1 ) * xSpacing ) / ( columns * ( WIDTH + borderWidth ) )
+ this.scaleFactor = ( 1 - ( this.indexColumns + 1 ) * this.xSpacingFactor ) /
+ ( this.indexColumns * ( 1 + 2 * this.halfBorderWidthFactor ) );
+
+ // update the thumbnail border size
+ var aRectElement = this.thumbnailBorderTemplateElement;
+ aRectElement.setAttribute( 'x', -this.halfBorderWidth );
+ aRectElement.setAttribute( 'y', -this.halfBorderWidth );
+ aRectElement.setAttribute( 'rx', this.halfBorderWidth );
+ aRectElement.setAttribute( 'ry', this.halfBorderWidth );
+ aRectElement.setAttribute( 'width', WIDTH + this.halfBorderWidth );
+ aRectElement.setAttribute( 'height', HEIGHT + this.halfBorderWidth );
+ aRectElement.setAttribute( 'stroke-width', this.borderWidth );
+
+ // now we update the displacement on the index page of each thumbnail (old and new)
+ for( i = 0; i < this.totalThumbnails; ++i )
+ {
+ this.aThumbnailSet[i].updateView();
+ }
+
+ this.curThumbnailIndex = this.selectedSlideIndex % this.totalThumbnails;
+ this.aThumbnailSet[this.curThumbnailIndex].select();
+
+ // needed for forcing the indexSetPageSlide routine to update the INDEX_OFFSET
+ INDEX_OFFSET = -1;
+ indexSetPageSlide( this.selectedSlideIndex );
+
+ ROOT_NODE.unsuspendRedraw( suspendHandle );
+ ROOT_NODE.forceRedraw();
+};
+
+
+/** Class Thumbnail **
+ * This class handles a slide thumbnail.
+ */
+function Thumbnail( aSlideIndexPage, nIndex )
+{
+ this.container = aSlideIndexPage;
+ this.index = nIndex;//= getSafeIndex( nIndex, 0, this.container.getTotalThumbnails() );
+ this.pageElement = this.container.pageElement;
+ this.thumbnailId = 'thumbnail' + this.index;
+ this.thumbnailElement = this.createThumbnailElement();
+ this.slideElement = getElementByClassName( this.thumbnailElement, 'Slide' );
+ this.borderElement = getElementByClassName( this.thumbnailElement, 'Border' );
+ this.mouseAreaElement = getElementByClassName( this.thumbnailElement, 'MouseArea' );
+ this.aTransformSet = new Array( 3 );
+ this.visibility = VISIBLE;
+ this.isSelected = false;
+}
+
+/* static const class member */
+Thumbnail.prototype.sNormalBorderColor = 'rgb(216,216,216)';
+Thumbnail.prototype.sSelectionBorderColor = 'rgb(92,92,255)';
+
+/* public methods */
+Thumbnail.prototype.removeElement = function()
+{
+ if( this.thumbnailElement )
+ this.container.pageElement.removeChild( this.thumbnailElement );
+};
+
+Thumbnail.prototype.show = function()
+{
+ if( this.visibility == HIDDEN )
+ {
+ this.thumbnailElement.setAttribute( 'display', 'inherit' );
+ this.visibility = VISIBLE;
+ }
+};
+
+Thumbnail.prototype.hide = function()
+{
+ if( this.visibility == VISIBLE )
+ {
+ this.thumbnailElement.setAttribute( 'display', 'none' );
+ this.visibility = HIDDEN;
+ }
+};
+
+Thumbnail.prototype.select = function()
+{
+ if( !this.isSelected )
+ {
+ this.borderElement.setAttribute( 'stroke', this.sSelectionBorderColor );
+ this.isSelected = true;
+ }
+};
+
+Thumbnail.prototype.unselect = function()
+{
+ if( this.isSelected )
+ {
+ this.borderElement.setAttribute( 'stroke', this.sNormalBorderColor );
+ this.isSelected = false;
+ }
+};
+
+/** updateView
+ *
+ * This method updates the displacement of the thumbnail on the slide index page,
+ * the value of the row, column coordinates of the thumbnail in the grid, and
+ * the onmouseover property of the thumbnail element.
+ *
+ */
+Thumbnail.prototype.updateView = function()
+{
+ this.column = this.index % this.container.indexColumns;
+ this.row = ( this.index - this.column ) / this.container.indexColumns;
+ this.halfBorderWidth = this.container.halfBorderWidth;
+ this.borderWidth = this.container.borderWidth;
+ this.width = ( WIDTH + this.borderWidth ) * this.container.scaleFactor;
+ this.height = ( HEIGHT + this.borderWidth ) * this.container.scaleFactor;
+ this.aTransformSet[2] = 'translate(' + this.halfBorderWidth + ' ' + this.halfBorderWidth + ')';
+ this.aTransformSet[1] = 'scale(' + this.container.scaleFactor + ')';
+ var sTransformAttrValue = this.computeTransform();
+ this.thumbnailElement.setAttribute( 'transform', sTransformAttrValue );
+ this.mouseAreaElement.setAttribute( 'onmouseover', 'theSlideIndexPage.aThumbnailSet[' + this.index + '].onMouseOver()' );
+};
+
+/** update
+ *
+ * This method update the content of the thumbnail view
+ *
+ * @param nIndex - the index of the slide to be shown in the thumbnail
+ */
+Thumbnail.prototype.update = function( nIndex )
+{
+ if( this.slideIndex == nIndex ) return;
+
+ var aMetaSlide = theMetaDoc.aMetaSlideSet[nIndex];
+ aMetaSlide.updateMasterPageView();
+ setNSAttribute( 'xlink', this.slideElement, 'href', '#' + aMetaSlide.slideId );
+ this.slideIndex = nIndex;
+};
+
+Thumbnail.prototype.clear = function( )
+{
+ setNSAttribute( 'xlink', this.slideElement, 'href', '' );
+};
+
+/* private methods */
+Thumbnail.prototype.createThumbnailElement = function()
+{
+ var aThumbnailElement = document.createElementNS( NSS['svg'], 'g' );
+ aThumbnailElement.setAttribute( 'id', this.thumbnailId );
+ aThumbnailElement.setAttribute( 'display', 'inherit' );
+
+ var aSlideElement = document.createElementNS( NSS['svg'], 'use' );
+ setNSAttribute( 'xlink', aSlideElement, 'href', '' );
+ aSlideElement.setAttribute( 'class', 'Slide' );
+ aThumbnailElement.appendChild( aSlideElement );
+
+ var aMouseAreaElement = document.createElementNS( NSS['svg'], 'use' );
+ setNSAttribute( 'xlink', aMouseAreaElement, 'href', '#' + this.container.thumbnailMouseAreaTemplateId );
+ aMouseAreaElement.setAttribute( 'class', 'MouseArea' );
+ aMouseAreaElement.setAttribute( 'opacity', 0.0 );
+ aThumbnailElement.appendChild( aMouseAreaElement );
+
+ var aBorderElement = document.createElementNS( NSS['svg'], 'use' );
+ setNSAttribute( 'xlink', aBorderElement, 'href', '#' + this.container.thumbnailBorderTemplateId );
+ aBorderElement.setAttribute( 'stroke', this.sNormalBorderColor );
+ aBorderElement.setAttribute( 'class', 'Border' );
+ aThumbnailElement.appendChild( aBorderElement );
+
+ this.container.pageElement.appendChild( aThumbnailElement );
+ return( document.getElementById( this.thumbnailId ) );
+};
+
+Thumbnail.prototype.computeTransform = function()
+{
+ var nXSpacing = this.container.xSpacing;
+ var nYSpacing = this.container.ySpacing;
+
+ var nXOffset = nXSpacing + ( this.width + nXSpacing ) * this.column;
+ var nYOffset = nYSpacing + ( this.height + nYSpacing ) * this.row;
+
+ this.aTransformSet[0] = 'translate(' + nXOffset + ' ' + nYOffset + ')';
+
+ var sTransform = this.aTransformSet.join( ' ' );
+
+ return sTransform;
+};
+
+Thumbnail.prototype.onMouseOver = function()
+{
+ if( ( currentMode == INDEX_MODE ) && ( this.container.curThumbnailIndex != this.index ) )
+ {
+ this.container.setSelection( this.index );
+ }
+};
+
+
+
+
+
+/** Initialization function.
+ * The whole presentation is set-up in this function.
+ */
+function init()
+{
+ var VIEWBOX = ROOT_NODE.getAttribute('viewBox');
+
+ if( VIEWBOX )
+ {
+ WIDTH = ROOT_NODE.viewBox.animVal.width;
+ HEIGHT = ROOT_NODE.viewBox.animVal.height;
+ }
+
+ aSlideShow = new SlideShow();
+ theMetaDoc = new MetaDocument();
+ aSlideShow.bIsEnabled = theMetaDoc.bIsAnimated;
+ theSlideIndexPage = new SlideIndexPage();
+ aSlideShow.displaySlide( theMetaDoc.nStartSlideNumber, false );
+
+ // Allow slide switching with swipe gestures left
+ // and right. Swiping up or down will exit the slideshow.
+ var hammer = new Hammer(ROOT_NODE);
+ hammer.on('swipeleft', function() {
+ switchSlide(1, false);
+ });
+ hammer.on('swiperight', function() {
+ switchSlide(-1, false);
+ });
+ hammer.get('swipe').set({ direction: Hammer.DIRECTION_ALL });
+ hammer.on('swipeup', function() {
+ aSlideShow.exitSlideShowInApp();
+ });
+ hammer.on('swipedown', function() {
+ aSlideShow.exitSlideShowInApp();
+ });
+}
+
+function presentationEngineStop(message)
+{
+ alert( message + '\nThe presentation engine will be stopped' );
+ document.onkeydown = null;
+ document.onkeypress = null;
+ document.onclick = null;
+ window.onmousewheel = null;
+}
+
+function assert( condition, message )
+{
+ if (!condition)
+ {
+ presentationEngineStop( message );
+ if (typeof console == 'object')
+ // eslint-disable-next-line no-console
+ console.trace();
+ throw new Error( message );
+ }
+}
+
+function dispatchEffects(dir)
+{
+ // TODO to be implemented
+
+ if( dir == 1 )
+ {
+ var bRet = aSlideShow.nextEffect();
+
+ if( !bRet )
+ {
+ switchSlide( 1, false );
+ }
+ }
+ else
+ {
+ switchSlide( dir, false );
+ }
+}
+
+function skipAllEffects()
+{
+ var bRet = aSlideShow.skipAllEffects();
+ if( !bRet )
+ {
+ switchSlide( 1, true );
+ }
+}
+
+function skipEffects(dir)
+{
+ if( dir == 1 )
+ {
+ var bRet = aSlideShow.skipPlayingOrNextEffect();
+
+ if( !bRet )
+ {
+ switchSlide( 1, true );
+ }
+ }
+ else
+ {
+ switchSlide( dir, true );
+ }
+}
+
+function switchSlide( nOffset, bSkipTransition )
+{
+ var nNextSlide = nCurSlide + nOffset;
+ aSlideShow.displaySlide( nNextSlide, bSkipTransition );
+}
+
+/** Function to display the index sheet.
+ *
+ * @param offsetNumber offset number
+ */
+function displayIndex( offsetNumber )
+{
+ var aMetaSlideSet = theMetaDoc.aMetaSlideSet;
+ offsetNumber = getSafeIndex( offsetNumber, 0, aMetaSlideSet.length - 1 );
+
+ var nTotalThumbnails = theSlideIndexPage.getTotalThumbnails();
+ var nEnd = Math.min( offsetNumber + nTotalThumbnails, aMetaSlideSet.length);
+
+ var aThumbnailSet = theSlideIndexPage.aThumbnailSet;
+ var j = 0;
+ for( var i = offsetNumber; i < nEnd; ++i, ++j )
+ {
+ aThumbnailSet[j].update( i );
+ aThumbnailSet[j].show();
+ }
+ for( ; j < nTotalThumbnails; ++j )
+ {
+ aThumbnailSet[j].hide();
+ }
+
+ //do we need to save the current offset?
+ if (INDEX_OFFSET != offsetNumber)
+ INDEX_OFFSET = offsetNumber;
+}
+
+/** Function to toggle between index and slide mode.
+ */
+function toggleSlideIndex()
+{
+ if( currentMode == SLIDE_MODE )
+ {
+
+ theMetaDoc.getCurrentSlide().hide();
+ INDEX_OFFSET = -1;
+ indexSetPageSlide( nCurSlide );
+ theSlideIndexPage.show();
+ currentMode = INDEX_MODE;
+ }
+ else if( currentMode == INDEX_MODE )
+ {
+ theSlideIndexPage.hide();
+ var nNewSlide = theSlideIndexPage.selectedSlideIndex;
+
+ aSlideShow.displaySlide( nNewSlide, true );
+ currentMode = SLIDE_MODE;
+ }
+}
+
+/** Function that exit from the index mode without changing the shown slide
+ *
+ */
+function abandonIndexMode()
+{
+ theSlideIndexPage.selectedSlideIndex = nCurSlide;
+ toggleSlideIndex();
+}
+
+
+
+
+
+/*********************************************************************************************
+ *********************************************************************************************
+ *********************************************************************************************
+
+ ***** ANIMATION ENGINE *****
+
+ *********************************************************************************************
+ *********************************************************************************************
+ *********************************************************************************************/
+
+
+
+
+
+
+// helper functions
+
+
+var CURR_UNIQUE_ID = 0;
+
+function getUniqueId()
+{
+ ++CURR_UNIQUE_ID;
+ return CURR_UNIQUE_ID;
+}
+
+function mem_fn( sMethodName )
+{
+ return function( aObject )
+ {
+ var aMethod = aObject[ sMethodName ];
+ if( aMethod )
+ aMethod.call( aObject );
+ else
+ log( 'method sMethodName not found' );
+ };
+}
+
+function bind( aObject, aMethod )
+{
+ return function()
+ {
+ return aMethod.call( aObject, arguments[0] );
+ };
+}
+
+function bind2( aFunction )
+{
+ if( !aFunction )
+ log( 'bind2: passed function is not valid.' );
+
+ var aBoundArgList = arguments;
+
+ var aResultFunction = null;
+
+ switch( aBoundArgList.length )
+ {
+ case 1: aResultFunction = function()
+ {
+ return aFunction.call( arguments[0], arguments[1],
+ arguments[2], arguments[3],
+ arguments[4] );
+ };
+ break;
+ case 2: aResultFunction = function()
+ {
+ return aFunction.call( aBoundArgList[1], arguments[0],
+ arguments[1], arguments[2],
+ arguments[3] );
+ };
+ break;
+ case 3: aResultFunction = function()
+ {
+ return aFunction.call( aBoundArgList[1], aBoundArgList[2],
+ arguments[0], arguments[1],
+ arguments[2] );
+ };
+ break;
+ case 4: aResultFunction = function()
+ {
+ return aFunction.call( aBoundArgList[1], aBoundArgList[2],
+ aBoundArgList[3], arguments[0],
+ arguments[1] );
+ };
+ break;
+ case 5: aResultFunction = function()
+ {
+ return aFunction.call( aBoundArgList[1], aBoundArgList[2],
+ aBoundArgList[3], aBoundArgList[4],
+ arguments[0] );
+ };
+ break;
+ default:
+ log( 'bind2: arity not handled.' );
+ }
+
+ return aResultFunction;
+}
+
+function getCurrentSystemTime()
+{
+ return ( new Date() ).getTime();
+}
+
+// eslint-disable-next-line no-unused-vars
+function getSlideAnimationsRoot( sSlideId )
+{
+ return theMetaDoc.aSlideAnimationsMap[ sSlideId ];
+}
+
+/** This function return an array populated with all children nodes of the
+ * passed element that are elements
+ *
+ * @param aElement any XML element
+ *
+ * @returns Array
+ * an array that contains all children elements
+ */
+function getElementChildren( aElement )
+{
+ var aChildrenArray = [];
+
+ var nSize = aElement.childNodes.length;
+
+ for( var i = 0; i < nSize; ++i )
+ {
+ if( aElement.childNodes[i].nodeType == 1 )
+ aChildrenArray.push( aElement.childNodes[i] );
+ }
+
+ return aChildrenArray;
+}
+
+function removeWhiteSpaces( str )
+{
+ if( !str )
+ return '';
+
+ var re = / */;
+ var aSplitString = str.split( re );
+ return aSplitString.join('');
+}
+
+function clamp( nValue, nMinimum, nMaximum )
+{
+ if( nValue < nMinimum )
+ {
+ return nMinimum;
+ }
+ else if( nValue > nMaximum )
+ {
+ return nMaximum;
+ }
+ else
+ {
+ return nValue;
+ }
+}
+
+function makeMatrixString( a, b, c, d, e, f )
+{
+ var s = 'matrix(';
+ s += a + ', ';
+ s += b + ', ';
+ s += c + ', ';
+ s += d + ', ';
+ s += e + ', ';
+ s += f + ')';
+
+ return s;
+}
+
+// eslint-disable-next-line no-unused-vars
+function matrixToString( aSVGMatrix )
+{
+ return makeMatrixString( aSVGMatrix.a, aSVGMatrix.b, aSVGMatrix.c,
+ aSVGMatrix.d, aSVGMatrix.e, aSVGMatrix.f );
+}
+
+
+
+
+// Attribute Parsers
+
+// eslint-disable-next-line no-unused-vars
+function numberParser( sValue )
+{
+ if( typeof sValue !== 'string' )
+ return undefined;
+ if( sValue === '.' )
+ return undefined;
+ var reFloatNumber = /^[+-]?[0-9]*[.]?[0-9]*$/;
+
+ if( reFloatNumber.test( sValue ) )
+ return parseFloat( sValue );
+ else
+ return undefined;
+}
+
+function booleanParser( sValue )
+{
+ if( typeof sValue !== 'string' )
+ return undefined;
+
+ sValue = sValue.toLowerCase();
+ if( sValue === 'true' )
+ return true;
+ else if( sValue === 'false' )
+ return false;
+ else
+ return undefined;
+}
+
+function colorParser( sValue )
+{
+ if( typeof sValue !== 'string' )
+ return undefined;
+
+ // The following 3 color functions are used in evaluating sValue string
+ // so don't remove them.
+
+ // eslint-disable-next-line no-unused-vars
+ function hsl( nHue, nSaturation, nLuminance )
+ {
+ return new HSLColor( nHue, nSaturation / 100, nLuminance / 100 );
+ }
+
+ // eslint-disable-next-line no-unused-vars
+ function rgb( nRed, nGreen, nBlue )
+ {
+ return new RGBColor( nRed / 255, nGreen / 255, nBlue / 255 );
+ }
+
+ // eslint-disable-next-line no-unused-vars
+ function prgb( nRed, nGreen, nBlue )
+ {
+ return new RGBColor( nRed / 100, nGreen / 100, nBlue / 100 );
+ }
+
+ var sCommaPattern = ' *[,] *';
+ var sIntegerPattern = '[+-]?[0-9]+';
+ var sHexDigitPattern = '[0-9A-Fa-f]';
+
+ var sHexColorPattern = '#(' + sHexDigitPattern + '{2})('
+ + sHexDigitPattern + '{2})('
+ + sHexDigitPattern + '{2})';
+
+ var sRGBIntegerPattern = 'rgb[(] *' + sIntegerPattern + sCommaPattern
+ + sIntegerPattern + sCommaPattern
+ + sIntegerPattern + ' *[)]';
+
+ var sRGBPercentPattern = 'rgb[(] *' + sIntegerPattern + '%' + sCommaPattern
+ + sIntegerPattern + '%' + sCommaPattern
+ + sIntegerPattern + '%' + ' *[)]';
+
+ var sHSLPercentPattern = 'hsl[(] *' + sIntegerPattern + sCommaPattern
+ + sIntegerPattern + '%' + sCommaPattern
+ + sIntegerPattern + '%' + ' *[)]';
+
+ var reHexColor = new RegExp( sHexColorPattern );
+ var reRGBInteger = new RegExp( sRGBIntegerPattern );
+ var reRGBPercent = new RegExp( sRGBPercentPattern );
+ var reHSLPercent = new RegExp( sHSLPercentPattern );
+
+ if( reHexColor.test( sValue ) )
+ {
+ var aRGBTriple = reHexColor.exec( sValue );
+
+ var nRed = parseInt( aRGBTriple[1], 16 ) / 255;
+ var nGreen = parseInt( aRGBTriple[2], 16 ) / 255;
+ var nBlue = parseInt( aRGBTriple[3], 16 ) / 255;
+
+ return new RGBColor( nRed, nGreen, nBlue );
+ }
+ else if( reHSLPercent.test( sValue ) )
+ {
+ sValue = sValue.replace( '%', '' ).replace( '%', '' );
+ return eval( sValue );
+ }
+ else if( reRGBInteger.test( sValue ) )
+ {
+ return eval( sValue );
+ }
+ else if( reRGBPercent.test( sValue ) )
+ {
+ sValue = 'p' + sValue.replace( '%', '' ).replace( '%', '' ).replace( '%', '' );
+ return eval( sValue );
+ }
+ else
+ {
+ return null;
+ }
+}
+
+
+
+/**********************************************************************************************
+ * RGB and HSL color classes
+ **********************************************************************************************/
+
+
+function RGBColor( nRed, nGreen, nBlue )
+{
+ this.eColorSpace = COLOR_SPACE_RGB;
+ // values in the [0,1] range
+ this.nRed = nRed;
+ this.nGreen = nGreen;
+ this.nBlue = nBlue;
+}
+
+
+RGBColor.prototype.clone = function()
+{
+ return new RGBColor( this.nRed, this.nGreen, this.nBlue );
+};
+
+RGBColor.prototype.equal = function( aRGBColor )
+{
+ return ( this.nRed == aRGBColor.nRed ) &&
+ ( this.nGreen == aRGBColor.nGreen ) &&
+ ( this.nBlue == aRGBColor.nBlue );
+};
+
+RGBColor.prototype.add = function( aRGBColor )
+{
+ this.nRed += aRGBColor.nRed;
+ this.nGreen += aRGBColor.nGreen;
+ this.nBlue += aRGBColor.nBlue;
+ return this;
+};
+
+RGBColor.prototype.scale = function( aT )
+{
+ this.nRed *= aT;
+ this.nGreen *= aT;
+ this.nBlue *= aT;
+ return this;
+};
+
+RGBColor.clamp = function( aRGBColor )
+{
+ var aClampedRGBColor = new RGBColor( 0, 0, 0 );
+
+ aClampedRGBColor.nRed = clamp( aRGBColor.nRed, 0.0, 1.0 );
+ aClampedRGBColor.nGreen = clamp( aRGBColor.nGreen, 0.0, 1.0 );
+ aClampedRGBColor.nBlue = clamp( aRGBColor.nBlue, 0.0, 1.0 );
+
+ return aClampedRGBColor;
+};
+
+RGBColor.prototype.convertToHSL = function()
+{
+ var nRed = clamp( this.nRed, 0.0, 1.0 );
+ var nGreen = clamp( this.nGreen, 0.0, 1.0 );
+ var nBlue = clamp( this.nBlue, 0.0, 1.0 );
+
+ var nMax = Math.max( nRed, nGreen, nBlue );
+ var nMin = Math.min( nRed, nGreen, nBlue );
+ var nDelta = nMax - nMin;
+
+ var nLuminance = ( nMax + nMin ) / 2.0;
+ var nSaturation = 0.0;
+ var nHue = 0.0;
+ if( nDelta !== 0 )
+ {
+ nSaturation = ( nLuminance > 0.5 ) ?
+ ( nDelta / ( 2.0 - nMax - nMin) ) :
+ ( nDelta / ( nMax + nMin ) );
+
+ if( nRed == nMax )
+ nHue = ( nGreen - nBlue ) / nDelta;
+ else if( nGreen == nMax )
+ nHue = 2.0 + ( nBlue - nRed ) / nDelta;
+ else if( nBlue == nMax )
+ nHue = 4.0 + ( nRed - nGreen ) / nDelta;
+
+ nHue *= 60.0;
+
+ if( nHue < 0.0 )
+ nHue += 360.0;
+ }
+
+ return new HSLColor( nHue, nSaturation, nLuminance );
+
+};
+
+RGBColor.prototype.toString = function( bClamped )
+{
+ var aRGBColor;
+ if( bClamped )
+ {
+ aRGBColor = RGBColor.clamp( this );
+ }
+ else
+ {
+ aRGBColor = this;
+ }
+
+ var nRed = Math.round( aRGBColor.nRed * 255 );
+ var nGreen = Math.round( aRGBColor.nGreen * 255 );
+ var nBlue = Math.round( aRGBColor.nBlue * 255 );
+
+ return ( 'rgb(' + nRed + ',' + nGreen + ',' + nBlue + ')' );
+};
+
+RGBColor.interpolate = function( aStartRGB , aEndRGB, nT )
+{
+ var aResult = aStartRGB.clone();
+ var aTEndRGB = aEndRGB.clone();
+ aResult.scale( 1.0 - nT );
+ aTEndRGB.scale( nT );
+ aResult.add( aTEndRGB );
+
+ return aResult;
+};
+
+
+
+
+function HSLColor( nHue, nSaturation, nLuminance )
+{
+ this.eColorSpace = COLOR_SPACE_HSL;
+ // Hue is in the [0,360[ range, Saturation and Luminance are in the [0,1] range
+ this.nHue = nHue;
+ this.nSaturation = nSaturation;
+ this.nLuminance = nLuminance;
+
+ this.normalizeHue();
+}
+
+
+HSLColor.prototype.clone = function()
+{
+ return new HSLColor( this.nHue, this.nSaturation, this.nLuminance );
+};
+
+HSLColor.prototype.equal = function( aHSLColor )
+{
+ return ( this.nHue == aHSLColor.nHue ) &&
+ ( this.nSaturation += aHSLColor.nSaturation ) &&
+ ( this.nLuminance += aHSLColor.nLuminance );
+};
+
+HSLColor.prototype.add = function( aHSLColor )
+{
+ this.nHue += aHSLColor.nHue;
+ this.nSaturation += aHSLColor.nSaturation;
+ this.nLuminance += aHSLColor.nLuminance;
+ this.normalizeHue();
+ return this;
+};
+
+HSLColor.prototype.scale = function( aT )
+{
+ this.nHue *= aT;
+ this.nSaturation *= aT;
+ this.nLuminance *= aT;
+ this.normalizeHue();
+ return this;
+};
+
+HSLColor.clamp = function( aHSLColor )
+{
+ var aClampedHSLColor = new HSLColor( 0, 0, 0 );
+
+ aClampedHSLColor.nHue = aHSLColor.nHue % 360;
+ if( aClampedHSLColor.nHue < 0 )
+ aClampedHSLColor.nHue += 360;
+ aClampedHSLColor.nSaturation = clamp( aHSLColor.nSaturation, 0.0, 1.0 );
+ aClampedHSLColor.nLuminance = clamp( aHSLColor.nLuminance, 0.0, 1.0 );
+};
+
+HSLColor.prototype.normalizeHue = function()
+{
+ this.nHue = this.nHue % 360;
+ if( this.nHue < 0 ) this.nHue += 360;
+};
+
+HSLColor.prototype.toString = function()
+{
+ return 'hsl(' + this.nHue.toFixed( 3 ) + ','
+ + this.nSaturation.toFixed( 3 ) + ','
+ + this.nLuminance.toFixed( 3 ) + ')';
+};
+
+HSLColor.prototype.convertToRGB = function()
+{
+
+ var nHue = this.nHue % 360;
+ if( nHue < 0 ) nHue += 360;
+ var nSaturation = clamp( this.nSaturation, 0.0, 1.0 );
+ var nLuminance = clamp( this.nLuminance, 0.0, 1.0 );
+
+
+ if( nSaturation === 0 )
+ {
+ return new RGBColor( nLuminance, nLuminance, nLuminance );
+ }
+
+ var nVal1 = ( nLuminance <= 0.5 ) ?
+ ( nLuminance * (1.0 + nSaturation) ) :
+ ( nLuminance + nSaturation - nLuminance * nSaturation );
+
+ var nVal2 = 2.0 * nLuminance - nVal1;
+
+ var nRed = HSLColor.hsl2rgbHelper( nVal2, nVal1, nHue + 120 );
+ var nGreen = HSLColor.hsl2rgbHelper( nVal2, nVal1, nHue );
+ var nBlue = HSLColor.hsl2rgbHelper( nVal2, nVal1, nHue - 120 );
+
+ return new RGBColor( nRed, nGreen, nBlue );
+};
+
+HSLColor.hsl2rgbHelper = function( nValue1, nValue2, nHue )
+{
+ nHue = nHue % 360;
+ if( nHue < 0 )
+ nHue += 360;
+
+ if( nHue < 60.0 )
+ return nValue1 + ( nValue2 - nValue1 ) * nHue / 60.0;
+ else if( nHue < 180.0 )
+ return nValue2;
+ else if( nHue < 240.0 )
+ return ( nValue1 + ( nValue2 - nValue1 ) * ( 240.0 - nHue ) / 60.0 );
+ else
+ return nValue1;
+};
+
+HSLColor.interpolate = function( aFrom, aTo, nT, bCCW )
+{
+ var nS = 1.0 - nT;
+
+ var nHue = 0.0;
+ if( aFrom.nHue <= aTo.nHue && !bCCW )
+ {
+ // interpolate hue clockwise. That is, hue starts at
+ // high values and ends at low ones. Therefore, we
+ // must 'cross' the 360 degrees and start at low
+ // values again (imagine the hues to lie on the
+ // circle, where values above 360 degrees are mapped
+ // back to [0,360)).
+ nHue = nS * (aFrom.nHue + 360.0) + nT * aTo.nHue;
+ }
+ else if( aFrom.nHue > aTo.nHue && bCCW )
+ {
+ // interpolate hue counter-clockwise. That is, hue
+ // starts at high values and ends at low
+ // ones. Therefore, we must 'cross' the 360 degrees
+ // and start at low values again (imagine the hues to
+ // lie on the circle, where values above 360 degrees
+ // are mapped back to [0,360)).
+ nHue = nS * aFrom.nHue + nT * (aTo.nHue + 360.0);
+ }
+ else
+ {
+ // interpolate hue counter-clockwise. That is, hue
+ // starts at low values and ends at high ones (imagine
+ // the hue value as degrees on a circle, with
+ // increasing values going counter-clockwise)
+ nHue = nS * aFrom.nHue + nT * aTo.nHue;
+ }
+
+ var nSaturation = nS * aFrom.nSaturation + nT * aTo.nSaturation;
+ var nLuminance = nS * aFrom.nLuminance + nT * aTo.nLuminance;
+
+ return new HSLColor( nHue, nSaturation, nLuminance );
+};
+
+
+
+/**********************************************************************************************
+ * SVGMatrix extensions
+ **********************************************************************************************/
+
+var SVGIdentityMatrix = document.documentElement.createSVGMatrix();
+
+SVGMatrix.prototype.setToIdentity = function()
+{
+ this.a = this.d = 1;
+ this.b = this.c = this.d = this.e = 0;
+};
+
+SVGMatrix.prototype.setToRotationAroundPoint = function( nX, nY, nAngle )
+{
+ // convert to radians
+ nAngle = Math.PI * nAngle / 180;
+ var nSin = Math.sin( nAngle );
+ var nCos = Math.cos( nAngle );
+
+ this.a = nCos; this.c = -nSin; this.e = nX * (1 - nCos) + nY * nSin;
+ this.b = nSin; this.d = nCos; this.f = nY * (1 - nCos) - nX * nSin;
+};
+
+
+
+/**********************************************************************************************
+ * SVGPath extensions
+ **********************************************************************************************/
+
+/** SVGPathElement.prependPath
+ * Merge the two path together by inserting the passed path before this one.
+ *
+ * @param aPath
+ * An object of type SVGPathElement to be prepended.
+ */
+SVGPathElement.prototype.prependPath = function( aPath )
+{
+ var sPathData = aPath.getAttribute( 'd' );
+ sPathData += ( ' ' + this.getAttribute( 'd' ) );
+ this.setAttribute( 'd', sPathData );
+};
+
+/** SVGPathElement.appendPath
+ * Merge the two path together by inserting the passed path after this one.
+ *
+ * @param aPath
+ * An object of type SVGPathElement to be appended.
+ */
+SVGPathElement.prototype.appendPath = function( aPath )
+{
+ var sPathData = this.getAttribute( 'd' );
+ sPathData += ( ' ' + aPath.getAttribute( 'd' ) );
+ this.setAttribute( 'd', sPathData );
+};
+
+/** flipOnYAxis
+ * Flips the SVG Path element along y-axis.
+ *
+ * @param aPath
+ * An object of type SVGPathElement to be flipped.
+ */
+function flipOnYAxis( aPath )
+{
+ var aPolyPath = aPath.cloneNode(true);
+ var aTransform = document.documentElement.createSVGMatrix();
+ aTransform.a = -1;
+ aTransform.e = 1;
+ aPolyPath.matrixTransform(aTransform);
+ return aPolyPath;
+}
+
+/** flipOnXAxis
+ * Flips the SVG Path element along x-axis
+ *
+ * @param aPath
+ * An object of type SVGPathElement to be flipped
+ */
+function flipOnXAxis( aPath )
+{
+ var aPolyPath = aPath.cloneNode(true);
+ var aTransform = document.documentElement.createSVGMatrix();
+ aTransform.d = -1;
+ aTransform.f = 1;
+ aPolyPath.matrixTransform(aTransform);
+ return aPolyPath;
+}
+
+/** SVGPathElement.matrixTransform
+ * Apply the transformation defined by the passed matrix to the referenced
+ * svg <path> element.
+ * After the transformation 'this' element is modified in order to reference
+ * the transformed path.
+ *
+ * @param aSVGMatrix
+ * An SVGMatrix instance.
+ */
+SVGPathElement.prototype.matrixTransform = function( aSVGMatrix )
+{
+ if( SVGPathSegList.prototype.matrixTransform )
+ {
+ this.pathSegList.matrixTransform( aSVGMatrix );
+ return;
+ }
+
+ var aPathSegList = this.pathSegList;
+ var nLength = aPathSegList.numberOfItems;
+ var i;
+ for( i = 0; i < nLength; ++i )
+ {
+ aPathSegList.getItem( i ).matrixTransform( aSVGMatrix );
+ }
+};
+
+/** SVGPathElement.changeOrientation
+ * Invert the path orientation by inverting the path command list.
+ *
+ */
+SVGPathElement.prototype.changeOrientation = function()
+{
+ var aPathSegList = this.pathSegList;
+ var nLength = aPathSegList.numberOfItems;
+ if( nLength == 0 ) return;
+
+ if( SVGPathSegList.prototype.changeOrientation )
+ {
+ aPathSegList.changeOrientation();
+ return;
+ }
+
+ var nCurrentX = 0;
+ var nCurrentY = 0;
+
+ var aPathSeg = aPathSegList.getItem( 0 );
+ if( aPathSeg.pathSegTypeAsLetter == 'M' )
+ {
+ nCurrentX = aPathSeg.x;
+ nCurrentY = aPathSeg.y;
+ aPathSegList.removeItem( 0 );
+ --nLength;
+ }
+
+ var i;
+ for( i = 0; i < nLength; ++i )
+ {
+ aPathSeg = aPathSegList.getItem( i );
+ var aPoint = aPathSeg.changeOrientation( nCurrentX, nCurrentY );
+ nCurrentX = aPoint.x;
+ nCurrentY = aPoint.y;
+ }
+
+
+ for( i = nLength - 2; i >= 0; --i )
+ {
+ aPathSeg = aPathSegList.removeItem( i );
+ aPathSegList.appendItem( aPathSeg );
+ }
+
+ var aMovePathSeg = this.createSVGPathSegMovetoAbs( nCurrentX, nCurrentY );
+ aPathSegList.insertItemBefore( aMovePathSeg, 0 );
+
+};
+
+/** matrixTransform and changeOrientation
+ * We implement these methods for each path segment type still present
+ * after the path normalization (M, L, Q, C).
+ *
+ * Note: Opera doesn't have any SVGPathSeg* class and rises an error.
+ * We exploit this fact for providing a different implementation.
+ */
+
+
+try
+{ // Firefox, Google Chrome, Internet Explorer, Safari.
+
+ SVGPathSegMovetoAbs.prototype.matrixTransform = function( aSVGMatrix )
+ {
+ SVGPathMatrixTransform( this, aSVGMatrix );
+ };
+
+ SVGPathSegLinetoAbs.prototype.matrixTransform = function( aSVGMatrix )
+ {
+ SVGPathMatrixTransform( this, aSVGMatrix );
+ };
+
+ SVGPathSegCurvetoQuadraticAbs.prototype.matrixTransform = function( aSVGMatrix )
+ {
+ SVGPathMatrixTransform( this, aSVGMatrix );
+ var nX = this.x1;
+ this.x1 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y1 + aSVGMatrix.e;
+ this.y1 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y1 + aSVGMatrix.f;
+ };
+
+ SVGPathSegCurvetoCubicAbs.prototype.matrixTransform = function( aSVGMatrix )
+ {
+ SVGPathMatrixTransform( this, aSVGMatrix );
+ var nX = this.x1;
+ this.x1 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y1 + aSVGMatrix.e;
+ this.y1 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y1 + aSVGMatrix.f;
+ nX = this.x2;
+ this.x2 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y2 + aSVGMatrix.e;
+ this.y2 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y2 + aSVGMatrix.f;
+ };
+
+
+ SVGPathSegMovetoAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
+ {
+ var aPoint = { x: this.x, y: this.y };
+ this.x = nCurrentX;
+ this.y = nCurrentY;
+ return aPoint;
+ };
+
+ SVGPathSegLinetoAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
+ {
+ var aPoint = { x: this.x, y: this.y };
+ this.x = nCurrentX;
+ this.y = nCurrentY;
+ return aPoint;
+ };
+
+ SVGPathSegCurvetoQuadraticAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
+ {
+ var aPoint = { x: this.x, y: this.y };
+ this.x = nCurrentX;
+ this.y = nCurrentY;
+ return aPoint;
+ };
+
+ SVGPathSegCurvetoCubicAbs.prototype.changeOrientation = function( nCurrentX, nCurrentY )
+ {
+ var aPoint = { x: this.x, y: this.y };
+ this.x = nCurrentX;
+ this.y = nCurrentY;
+ var nX = this.x1;
+ this.x1 = this.x2;
+ this.x2 = nX;
+ var nY = this.y1;
+ this.y1 = this.y2;
+ this.y2 = nY;
+ return aPoint;
+ };
+
+}
+catch( e )
+{ // Opera
+
+ if( e.name == 'ReferenceError' )
+ {
+ SVGPathSeg.prototype.matrixTransform = function( aSVGMatrix )
+ {
+ var nX;
+ switch( this.pathSegTypeAsLetter )
+ {
+ case 'C':
+ nX = this.x2;
+ this.x2 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y2 + aSVGMatrix.e;
+ this.y2 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y2 + aSVGMatrix.f;
+ // fall through intended
+ case 'Q':
+ nX = this.x1;
+ this.x1 = aSVGMatrix.a * nX + aSVGMatrix.c * this.y1 + aSVGMatrix.e;
+ this.y1 = aSVGMatrix.b * nX + aSVGMatrix.d * this.y1 + aSVGMatrix.f;
+ // fall through intended
+ case 'M':
+ case 'L':
+ SVGPathMatrixTransform( this, aSVGMatrix );
+ break;
+ default:
+ log( 'SVGPathSeg.matrixTransform: unexpected path segment type: '
+ + this.pathSegTypeAsLetter );
+ }
+ };
+
+ SVGPathSeg.prototype.changeOrientation = function( nCurrentX, nCurrentY )
+ {
+ switch( this.pathSegTypeAsLetter )
+ {
+ case 'C':
+ var nX = this.x1;
+ this.x1 = this.x2;
+ this.x2 = nX;
+ var nY = this.y1;
+ this.y1 = this.y2;
+ this.y2 = nY;
+ // fall through intended
+ case 'M':
+ case 'L':
+ case 'Q':
+ var aPoint = { x: this.x, y: this.y };
+ this.x = nCurrentX;
+ this.y = nCurrentY;
+ return aPoint;
+ default:
+ log( 'SVGPathSeg.changeOrientation: unexpected path segment type: '
+ + this.pathSegTypeAsLetter );
+ return null;
+ }
+ }
+ }
+ else throw e;
+}
+
+function SVGPathMatrixTransform( aPath, aSVGMatrix )
+{
+ var nX = aPath.x;
+ aPath.x = aSVGMatrix.a * nX + aSVGMatrix.c * aPath.y + aSVGMatrix.e;
+ aPath.y = aSVGMatrix.b * nX + aSVGMatrix.d * aPath.y + aSVGMatrix.f;
+}
+
+
+/**********************************************************************************************
+ * simple PriorityQueue
+ **********************************************************************************************/
+
+function PriorityQueue( aCompareFunc )
+{
+ this.aSequence = [];
+ this.aCompareFunc = aCompareFunc;
+}
+
+PriorityQueue.prototype.clone = function()
+{
+ var aCopy = new PriorityQueue( this.aCompareFunc );
+ var src = this.aSequence;
+ var dest = [];
+ var i, l;
+ for( i = 0, l = src.length; i < l; ++i )
+ {
+ if( i in src )
+ {
+ dest.push( src[i] );
+ }
+ }
+ aCopy.aSequence = dest;
+ return aCopy;
+};
+
+PriorityQueue.prototype.top = function()
+{
+ return this.aSequence[this.aSequence.length - 1];
+};
+
+PriorityQueue.prototype.isEmpty = function()
+{
+ return ( this.aSequence.length === 0 );
+};
+
+PriorityQueue.prototype.push = function( aValue )
+{
+ this.aSequence.unshift( aValue );
+ this.aSequence.sort(this.aCompareFunc);
+};
+
+PriorityQueue.prototype.clear = function()
+{
+ this.aSequence = [];
+};
+
+PriorityQueue.prototype.pop = function()
+{
+ return this.aSequence.pop();
+};
+
+
+
+
+/**********************************************************************************************
+ * AnimationNode Class Hierarchy
+ **********************************************************************************************/
+
+
+
+// Node Types
+var ANIMATION_NODE_CUSTOM = 0;
+var ANIMATION_NODE_PAR = 1;
+var ANIMATION_NODE_SEQ = 2;
+var ANIMATION_NODE_ITERATE = 3;
+var ANIMATION_NODE_ANIMATE = 4;
+var ANIMATION_NODE_SET = 5;
+var ANIMATION_NODE_ANIMATEMOTION = 6;
+var ANIMATION_NODE_ANIMATECOLOR = 7;
+var ANIMATION_NODE_ANIMATETRANSFORM = 8;
+var ANIMATION_NODE_TRANSITIONFILTER = 9;
+var ANIMATION_NODE_AUDIO = 10;
+var ANIMATION_NODE_COMMAND = 11;
+
+var aAnimationNodeTypeInMap = {
+ 'par' : ANIMATION_NODE_PAR,
+ 'seq' : ANIMATION_NODE_SEQ,
+ 'iterate' : ANIMATION_NODE_ITERATE,
+ 'animate' : ANIMATION_NODE_ANIMATE,
+ 'set' : ANIMATION_NODE_SET,
+ 'animatemotion' : ANIMATION_NODE_ANIMATEMOTION,
+ 'animatecolor' : ANIMATION_NODE_ANIMATECOLOR,
+ 'animatetransform' : ANIMATION_NODE_ANIMATETRANSFORM,
+ 'transitionfilter' : ANIMATION_NODE_TRANSITIONFILTER,
+ 'audio' : ANIMATION_NODE_AUDIO,
+ 'command' : ANIMATION_NODE_COMMAND
+};
+
+
+
+
+function getAnimationElementType( aElement )
+{
+ var sName = aElement.localName.toLowerCase();
+
+ if( sName && aAnimationNodeTypeInMap[ sName ] )
+ return aAnimationNodeTypeInMap[ sName ];
+ else
+ return ANIMATION_NODE_CUSTOM;
+}
+
+
+
+
+
+// Node States
+var INVALID_NODE = 0;
+var UNRESOLVED_NODE = 1;
+var RESOLVED_NODE = 2;
+var ACTIVE_NODE = 4;
+var FROZEN_NODE = 8;
+var ENDED_NODE = 16;
+
+function getNodeStateName( eNodeState )
+{
+ switch( eNodeState )
+ {
+ case INVALID_NODE:
+ return 'INVALID';
+ case UNRESOLVED_NODE:
+ return 'UNRESOLVED';
+ case RESOLVED_NODE:
+ return 'RESOLVED';
+ case ACTIVE_NODE:
+ return 'ACTIVE';
+ case FROZEN_NODE:
+ return 'FROZEN';
+ case ENDED_NODE:
+ return 'ENDED';
+ default:
+ return 'UNKNOWN';
+ }
+}
+
+
+// Impress Node Types
+var IMPRESS_DEFAULT_NODE = 0;
+var IMPRESS_ON_CLICK_NODE = 1;
+var IMPRESS_WITH_PREVIOUS_NODE = 2;
+var IMPRESS_AFTER_PREVIOUS_NODE = 3;
+var IMPRESS_MAIN_SEQUENCE_NODE = 4;
+var IMPRESS_TIMING_ROOT_NODE = 5;
+var IMPRESS_INTERACTIVE_SEQUENCE_NODE = 6;
+
+var aImpressNodeTypeInMap = {
+ 'on-click' : IMPRESS_ON_CLICK_NODE,
+ 'with-previous' : IMPRESS_WITH_PREVIOUS_NODE,
+ 'after-previous' : IMPRESS_AFTER_PREVIOUS_NODE,
+ 'main-sequence' : IMPRESS_MAIN_SEQUENCE_NODE,
+ 'timing-root' : IMPRESS_TIMING_ROOT_NODE,
+ 'interactive-sequence' : IMPRESS_INTERACTIVE_SEQUENCE_NODE
+};
+
+var aImpressNodeTypeOutMap = [ 'default', 'on-click', 'with-previous', 'after-previous',
+ 'main-sequence', 'timing-root', 'interactive-sequence' ];
+
+
+// Preset Classes
+var aPresetClassInMap = {};
+
+
+// Preset Ids
+var aPresetIdInMap = {};
+
+
+// Restart Modes
+var RESTART_MODE_DEFAULT = 0;
+var RESTART_MODE_INHERIT = 0; // eslint-disable-line no-unused-vars
+var RESTART_MODE_ALWAYS = 1;
+var RESTART_MODE_WHEN_NOT_ACTIVE = 2;
+var RESTART_MODE_NEVER = 3;
+
+var aRestartModeInMap = {
+ 'inherit' : RESTART_MODE_DEFAULT,
+ 'always' : RESTART_MODE_ALWAYS,
+ 'whenNotActive' : RESTART_MODE_WHEN_NOT_ACTIVE,
+ 'never' : RESTART_MODE_NEVER
+};
+
+var aRestartModeOutMap = [ 'inherit','always', 'whenNotActive', 'never' ];
+
+
+// Fill Modes
+var FILL_MODE_DEFAULT = 0;
+var FILL_MODE_INHERIT = 0; // eslint-disable-line no-unused-vars
+var FILL_MODE_REMOVE = 1;
+var FILL_MODE_FREEZE = 2;
+var FILL_MODE_HOLD = 3;
+var FILL_MODE_TRANSITION = 4;
+var FILL_MODE_AUTO = 5;
+
+var aFillModeInMap = {
+ 'inherit' : FILL_MODE_DEFAULT,
+ 'remove' : FILL_MODE_REMOVE,
+ 'freeze' : FILL_MODE_FREEZE,
+ 'hold' : FILL_MODE_HOLD,
+ 'transition' : FILL_MODE_TRANSITION,
+ 'auto' : FILL_MODE_AUTO
+};
+
+var aFillModeOutMap = [ 'inherit', 'remove', 'freeze', 'hold', 'transition', 'auto' ];
+
+
+// Additive Modes
+var ADDITIVE_MODE_UNKNOWN = 0; // eslint-disable-line no-unused-vars
+var ADDITIVE_MODE_BASE = 1;
+var ADDITIVE_MODE_SUM = 2;
+var ADDITIVE_MODE_REPLACE = 3;
+var ADDITIVE_MODE_MULTIPLY = 4;
+var ADDITIVE_MODE_NONE = 5;
+
+var aAddittiveModeInMap = {
+ 'base' : ADDITIVE_MODE_BASE,
+ 'sum' : ADDITIVE_MODE_SUM,
+ 'replace' : ADDITIVE_MODE_REPLACE,
+ 'multiply' : ADDITIVE_MODE_MULTIPLY,
+ 'none' : ADDITIVE_MODE_NONE
+};
+
+var aAddittiveModeOutMap = [ 'unknown', 'base', 'sum', 'replace', 'multiply', 'none' ];
+
+
+// Accumulate Modes
+var ACCUMULATE_MODE_NONE = 0;
+var ACCUMULATE_MODE_SUM = 1;
+
+var aAccumulateModeOutMap = [ 'none', 'sum' ];
+
+// Calculation Modes
+var CALC_MODE_DISCRETE = 1;
+var CALC_MODE_LINEAR = 2;
+var CALC_MODE_PACED = 3;
+var CALC_MODE_SPLINE = 4;
+
+var aCalcModeInMap = {
+ 'discrete' : CALC_MODE_DISCRETE,
+ 'linear' : CALC_MODE_LINEAR,
+ 'paced' : CALC_MODE_PACED,
+ 'spline' : CALC_MODE_SPLINE
+};
+
+var aCalcModeOutMap = [ 'unknown', 'discrete', 'linear', 'paced', 'spline' ];
+
+
+// Color Spaces
+var COLOR_SPACE_RGB = 0;
+var COLOR_SPACE_HSL = 1;
+
+var aColorSpaceInMap = { 'rgb': COLOR_SPACE_RGB, 'hsl': COLOR_SPACE_HSL };
+
+var aColorSpaceOutMap = [ 'rgb', 'hsl' ];
+
+
+// Clock Directions
+var CLOCKWISE = 0;
+var COUNTERCLOCKWISE = 1;
+
+var aClockDirectionInMap = { 'clockwise': CLOCKWISE, 'counter-clockwise': COUNTERCLOCKWISE };
+
+var aClockDirectionOutMap = [ 'clockwise', 'counter-clockwise' ];
+
+
+// Attribute Value Types
+var UNKNOWN_PROPERTY = 0; // eslint-disable-line no-unused-vars
+var NUMBER_PROPERTY = 1;
+var ENUM_PROPERTY = 2;
+var COLOR_PROPERTY = 3;
+var STRING_PROPERTY = 4;
+var BOOL_PROPERTY = 5;
+var TUPLE_NUMBER_PROPERTY = 6;
+
+var aValueTypeOutMap = [ 'unknown', 'number', 'enum', 'color', 'string', 'boolean' ];
+
+
+// Attribute Map
+var aAttributeMap =
+{
+ 'height': { 'type': NUMBER_PROPERTY,
+ 'get': 'getHeight',
+ 'set': 'setHeight',
+ 'getmod': 'makeScaler( 1/nHeight )',
+ 'setmod': 'makeScaler( nHeight)' },
+
+ 'opacity': { 'type': NUMBER_PROPERTY,
+ 'get': 'getOpacity',
+ 'set': 'setOpacity' },
+
+ 'scale': { 'type': TUPLE_NUMBER_PROPERTY,
+ 'get': 'getSize',
+ 'set': 'setSize' },
+
+ 'translate': { 'type': TUPLE_NUMBER_PROPERTY,
+ 'get': 'getPos',
+ 'set': 'setPos' },
+
+ 'rotate': { 'type': NUMBER_PROPERTY,
+ 'get': 'getRotationAngle',
+ 'set': 'setRotationAngle' },
+
+ 'width': { 'type': NUMBER_PROPERTY,
+ 'get': 'getWidth',
+ 'set': 'setWidth',
+ 'getmod': 'makeScaler( 1/nWidth )',
+ 'setmod': 'makeScaler( nWidth)' },
+
+ 'x': { 'type': NUMBER_PROPERTY,
+ 'get': 'getX',
+ 'set': 'setX',
+ 'getmod': 'makeScaler( 1/nWidth )',
+ 'setmod': 'makeScaler( nWidth)' },
+
+ 'y': { 'type': NUMBER_PROPERTY,
+ 'get': 'getY',
+ 'set': 'setY',
+ 'getmod': 'makeScaler( 1/nHeight )',
+ 'setmod': 'makeScaler( nHeight)' },
+
+ 'fill': { 'type': ENUM_PROPERTY,
+ 'get': 'getFillStyle',
+ 'set': 'setFillStyle' },
+
+ 'stroke': { 'type': ENUM_PROPERTY,
+ 'get': 'getStrokeStyle',
+ 'set': 'setStrokeStyle' },
+
+ 'visibility': { 'type': ENUM_PROPERTY,
+ 'get': 'getVisibility',
+ 'set': 'setVisibility' },
+
+ 'fill-color': { 'type': COLOR_PROPERTY,
+ 'get': 'getFillColor',
+ 'set': 'setFillColor' },
+
+ 'stroke-color': { 'type': COLOR_PROPERTY,
+ 'get': 'getStrokeColor',
+ 'set': 'setStrokeColor' },
+
+ 'color': { 'type': COLOR_PROPERTY,
+ 'get': 'getFontColor',
+ 'set': 'setFontColor' }
+
+};
+
+
+// Transition Classes
+var TRANSITION_INVALID = 0; // Invalid type
+var TRANSITION_CLIP_POLYPOLYGON = 1; // Transition expressed by parametric clip polygon
+var TRANSITION_SPECIAL = 2; // Transition expressed by hand-crafted function
+
+/*
+ * All Transition types should be in sync with aTransitionTypeInMap:
+ * Comments '//' followed by integers represent the transition values in their
+ * C++ implementations.
+ */
+
+// Transition Types
+var BARWIPE_TRANSITION = 1;
+var BOXWIPE_TRANSITION = 2;
+var FOURBOXWIPE_TRANSITION = 3;
+var ELLIPSEWIPE_TRANSITION = 4; // 17
+var CLOCKWIPE_TRANSITION = 5; // 22
+var PINWHEELWIPE_TRANSITION = 6; // 23
+var PUSHWIPE_TRANSITION = 7; // 35
+var SLIDEWIPE_TRANSITION = 8; // 36
+var FADE_TRANSITION = 9; // 37
+var RANDOMBARWIPE_TRANSITION = 10; // 38
+var CHECKERBOARDWIPE_TRANSITION = 11; // 39
+var DISSOLVE_TRANSITION = 12; // 40
+var SNAKEWIPE_TRANSITION = 13; // 30
+var PARALLELSNAKESWIPE_TRANSITION = 14; // 32
+var IRISWIPE_TRANSITION = 15; // 12
+var BARNDOORWIPE_TRANSITION = 16; // 4
+var VEEWIPE_TRANSITION = 17; // 8
+var ZIGZAGWIPE_TRANSITION = 18; // 10
+var BARNZIGZAGWIPE_TRANSITION = 19; // 11
+var FANWIPE_TRANSITION = 20; // 25
+var SINGLESWEEPWIPE_TRANSITION = 21; // 24
+var WATERFALLWIPE_TRANSITION = 22; // 34
+var SPIRALWIPE_TRANSITION = 23; // 31
+var MISCDIAGONALWIPE_TRANSITION = 24; // 7
+var BOXSNAKESWIPE_TRANSITION = 25; // 33
+
+var aTransitionTypeInMap = {
+ 'barWipe' : BARWIPE_TRANSITION,
+ 'boxWipe' : BOXWIPE_TRANSITION,
+ 'barnDoorWipe' : BARNDOORWIPE_TRANSITION,
+ 'fourBoxWipe' : FOURBOXWIPE_TRANSITION,
+ 'ellipseWipe' : ELLIPSEWIPE_TRANSITION,
+ 'clockWipe' : CLOCKWIPE_TRANSITION,
+ 'pinWheelWipe' : PINWHEELWIPE_TRANSITION,
+ 'miscDiagonalWipe' : MISCDIAGONALWIPE_TRANSITION,
+ 'pushWipe' : PUSHWIPE_TRANSITION,
+ 'slideWipe' : SLIDEWIPE_TRANSITION,
+ 'fade' : FADE_TRANSITION,
+ 'fanWipe' : FANWIPE_TRANSITION,
+ 'randomBarWipe' : RANDOMBARWIPE_TRANSITION,
+ 'checkerBoardWipe' : CHECKERBOARDWIPE_TRANSITION,
+ 'dissolve' : DISSOLVE_TRANSITION,
+ 'singleSweepWipe' : SINGLESWEEPWIPE_TRANSITION,
+ 'snakeWipe' : SNAKEWIPE_TRANSITION,
+ 'parallelSnakesWipe': PARALLELSNAKESWIPE_TRANSITION,
+ 'spiralWipe' : SPIRALWIPE_TRANSITION,
+ 'boxSnakesWipe' : BOXSNAKESWIPE_TRANSITION,
+ 'irisWipe' : IRISWIPE_TRANSITION,
+ 'veeWipe' : VEEWIPE_TRANSITION,
+ 'zigZagWipe' : ZIGZAGWIPE_TRANSITION,
+ 'barnZigZagWipe' : BARNZIGZAGWIPE_TRANSITION,
+ 'waterfallWipe' : WATERFALLWIPE_TRANSITION
+};
+
+/*
+ * All Transition subtypes should be in sync with aTransitionSubtypeInMap:
+ * Comments '//' followed by integers represent the transition values in their
+ * C++ implementations.
+ */
+// Transition Subtypes
+var DEFAULT_TRANS_SUBTYPE = 0;
+var LEFTTORIGHT_TRANS_SUBTYPE = 1;
+var TOPTOBOTTOM_TRANS_SUBTYPE = 2;
+var CORNERSIN_TRANS_SUBTYPE = 3; // 11
+var CORNERSOUT_TRANS_SUBTYPE = 4;
+var VERTICAL_TRANS_SUBTYPE = 5;
+var HORIZONTAL_TRANS_SUBTYPE = 6; // 14
+var DOWN_TRANS_SUBTYPE = 7; // 19
+var CIRCLE_TRANS_SUBTYPE = 8; // 27
+var CLOCKWISETWELVE_TRANS_SUBTYPE = 9; // 33
+var CLOCKWISETHREE_TRANS_SUBTYPE = 10;
+var CLOCKWISESIX_TRANS_SUBTYPE = 11;
+var CLOCKWISENINE_TRANS_SUBTYPE = 12;
+var TWOBLADEVERTICAL_TRANS_SUBTYPE = 13;
+var TWOBLADEHORIZONTAL_TRANS_SUBTYPE = 14;
+var FOURBLADE_TRANS_SUBTYPE = 15; // 39
+var FROMLEFT_TRANS_SUBTYPE = 16; // 97
+var FROMTOP_TRANS_SUBTYPE = 17;
+var FROMRIGHT_TRANS_SUBTYPE = 18;
+var FROMBOTTOM_TRANS_SUBTYPE = 19;
+var CROSSFADE_TRANS_SUBTYPE = 20;
+var FADETOCOLOR_TRANS_SUBTYPE = 21;
+var FADEFROMCOLOR_TRANS_SUBTYPE = 22;
+var FADEOVERCOLOR_TRANS_SUBTYPE = 23;
+var THREEBLADE_TRANS_SUBTYPE = 24;
+var EIGHTBLADE_TRANS_SUBTYPE = 25;
+var ONEBLADE_TRANS_SUBTYPE = 26; // 107
+var ACROSS_TRANS_SUBTYPE = 27;
+var TOPLEFTVERTICAL_TRANS_SUBTYPE = 28; // 109
+var TOPLEFTHORIZONTAL_TRANS_SUBTYPE = 29; // 64
+var TOPLEFTDIAGONAL_TRANS_SUBTYPE = 30; // 65
+var TOPRIGHTDIAGONAL_TRANS_SUBTYPE = 31; // 66
+var BOTTOMRIGHTDIAGONAL_TRANS_SUBTYPE = 32; // 67
+var BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE = 33; // 68
+var RECTANGLE_TRANS_SUBTYPE = 34; // 101
+var DIAMOND_TRANS_SUBTYPE = 35; // 102
+var TOPLEFT_TRANS_SUBTYPE = 36; // 3
+var TOPRIGHT_TRANS_SUBTYPE = 37; // 4
+var BOTTOMRIGHT_TRANS_SUBTYPE = 38; // 5
+var BOTTOMLEFT_TRANS_SUBTYPE = 39; // 6
+var TOPCENTER_TRANS_SUBTYPE = 40; // 7
+var RIGHTCENTER_TRANS_SUBTYPE = 41; // 8
+var BOTTOMCENTER_TRANS_SUBTYPE = 42; // 9
+var LEFTCENTER_TRANS_SUBTYPE = 43; // 10
+var LEFT_TRANS_SUBTYPE = 44; // 20
+var UP_TRANS_SUBTYPE = 45; // 21
+var RIGHT_TRANS_SUBTYPE = 46; // 22
+var DIAGONALBOTTOMLEFT_TRANS_SUBTYPE = 47; // 15
+var DIAGONALTOPLEFT_TRANS_SUBTYPE = 48; // 16
+var CENTERTOP_TRANS_SUBTYPE = 49; // 48
+var CENTERRIGHT_TRANS_SUBTYPE = 50; // 49
+var TOP_TRANS_SUBTYPE = 51; // 50
+var BOTTOM_TRANS_SUBTYPE = 52; // 52
+var CLOCKWISETOP_TRANS_SUBTYPE = 53; // 40
+var CLOCKWISERIGHT_TRANS_SUBTYPE = 54; // 41
+var CLOCKWISEBOTTOM_TRANS_SUBTYPE = 55; // 42
+var CLOCKWISELEFT_TRANS_SUBTYPE = 56; // 43
+var CLOCKWISETOPLEFT_TRANS_SUBTYPE = 57; // 44
+var COUNTERCLOCKWISEBOTTOMLEFT_TRANS_SUBTYPE = 58; // 45
+var CLOCKWISEBOTTOMRIGHT_TRANS_SUBTYPE = 59; // 46
+var COUNTERCLOCKWISETOPRIGHT_TRANS_SUBTYPE = 60; // 47
+var VERTICALLEFT_TRANS_SUBTYPE = 61; // 93
+var VERTICALRIGHT_TRANS_SUBTYPE = 62; // 94
+var HORIZONTALLEFT_TRANS_SUBTYPE = 63; // 95
+var HORIZONTALRIGHT_TRANS_SUBTYPE = 64; // 96
+var TOPLEFTCLOCKWISE_TRANS_SUBTYPE = 65; // 69
+var TOPRIGHTCLOCKWISE_TRANS_SUBTYPE = 66; // 70
+var BOTTOMRIGHTCLOCKWISE_TRANS_SUBTYPE = 67; // 71
+var BOTTOMLEFTCLOCKWISE_TRANS_SUBTYPE = 68; // 72
+var TOPLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE = 69; // 73
+var TOPRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE = 70; // 74
+var BOTTOMRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE = 71; // 75
+var BOTTOMLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE = 72; // 76
+var DOUBLEBARNDOOR_TRANS_SUBTYPE = 73; // 17
+var DOUBLEDIAMOND_TRANS_SUBTYPE = 74; // 18
+var VERTICALTOPSAME_TRANS_SUBTYPE = 75; // 77
+var VERTICALBOTTOMSAME_TRANS_SUBTYPE = 76; // 78
+var VERTICALTOPLEFTOPPOSITE_TRANS_SUBTYPE = 77; // 79
+var VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE = 78; // 80
+var HORIZONTALLEFTSAME_TRANS_SUBTYPE = 79; // 81
+var HORIZONTALRIGHTSAME_TRANS_SUBTYPE = 80; // 82
+var HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE = 81; // 83
+var HORIZONTALTOPRIGHTOPPOSITE_TRANS_SUBTYPE = 82; // 84
+var DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE = 83; // 85
+var DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE = 84; // 86
+var TWOBOXTOP_TRANS_SUBTYPE = 85; // 87
+var TWOBOXBOTTOM_TRANS_SUBTYPE = 86; // 88
+var TWOBOXLEFT_TRANS_SUBTYPE = 87; // 89
+var TWOBOXRIGHT_TRANS_SUBTYPE = 88; // 90
+var FOURBOXVERTICAL_TRANS_SUBTYPE = 89; // 91
+var FOURBOXHORIZONTAL_TRANS_SUBTYPE = 90; // 92
+
+var aTransitionSubtypeInMap = {
+ 'default' : DEFAULT_TRANS_SUBTYPE,
+ 'leftToRight' : LEFTTORIGHT_TRANS_SUBTYPE,
+ 'topToBottom' : TOPTOBOTTOM_TRANS_SUBTYPE,
+ 'cornersIn' : CORNERSIN_TRANS_SUBTYPE,
+ 'cornersOut' : CORNERSOUT_TRANS_SUBTYPE,
+ 'vertical' : VERTICAL_TRANS_SUBTYPE,
+ 'centerTop' : CENTERTOP_TRANS_SUBTYPE,
+ 'centerRight' : CENTERRIGHT_TRANS_SUBTYPE,
+ 'top' : TOP_TRANS_SUBTYPE,
+ 'right' : RIGHT_TRANS_SUBTYPE,
+ 'bottom' : BOTTOM_TRANS_SUBTYPE,
+ 'left' : LEFT_TRANS_SUBTYPE,
+ 'horizontal' : HORIZONTAL_TRANS_SUBTYPE,
+ 'down' : DOWN_TRANS_SUBTYPE,
+ 'circle' : CIRCLE_TRANS_SUBTYPE,
+ 'clockwiseTwelve' : CLOCKWISETWELVE_TRANS_SUBTYPE,
+ 'clockwiseThree' : CLOCKWISETHREE_TRANS_SUBTYPE,
+ 'clockwiseSix' : CLOCKWISESIX_TRANS_SUBTYPE,
+ 'clockwiseNine' : CLOCKWISENINE_TRANS_SUBTYPE,
+ 'clockwiseRight' : CLOCKWISERIGHT_TRANS_SUBTYPE,
+ 'clockwiseTop' : CLOCKWISETOP_TRANS_SUBTYPE,
+ 'clockwiseBottom' : CLOCKWISEBOTTOM_TRANS_SUBTYPE,
+ 'clockwiseLeft' : CLOCKWISELEFT_TRANS_SUBTYPE,
+ 'clockwiseTopLeft' : CLOCKWISETOPLEFT_TRANS_SUBTYPE,
+ 'counterClockwiseBottomLeft' : COUNTERCLOCKWISEBOTTOMLEFT_TRANS_SUBTYPE,
+ 'clockwiseBottomRight' : CLOCKWISEBOTTOMRIGHT_TRANS_SUBTYPE,
+ 'counterClockwiseTopRight' : COUNTERCLOCKWISETOPRIGHT_TRANS_SUBTYPE,
+ 'twoBladeVertical' : TWOBLADEVERTICAL_TRANS_SUBTYPE,
+ 'twoBladeHorizontal' : TWOBLADEHORIZONTAL_TRANS_SUBTYPE,
+ 'fourBlade' : FOURBLADE_TRANS_SUBTYPE,
+ 'fromLeft' : FROMLEFT_TRANS_SUBTYPE,
+ 'fromTop' : FROMTOP_TRANS_SUBTYPE,
+ 'fromRight' : FROMRIGHT_TRANS_SUBTYPE,
+ 'fromBottom' : FROMBOTTOM_TRANS_SUBTYPE,
+ 'crossfade' : CROSSFADE_TRANS_SUBTYPE,
+ 'fadeToColor' : FADETOCOLOR_TRANS_SUBTYPE,
+ 'fadeFromColor' : FADEFROMCOLOR_TRANS_SUBTYPE,
+ 'fadeOverColor' : FADEOVERCOLOR_TRANS_SUBTYPE,
+ 'threeBlade' : THREEBLADE_TRANS_SUBTYPE,
+ 'eightBlade' : EIGHTBLADE_TRANS_SUBTYPE,
+ 'oneBlade' : ONEBLADE_TRANS_SUBTYPE,
+ 'across' : ACROSS_TRANS_SUBTYPE,
+ 'topLeftVertical' : TOPLEFTVERTICAL_TRANS_SUBTYPE,
+ 'topLeftHorizontal' : TOPLEFTHORIZONTAL_TRANS_SUBTYPE,
+ 'topLeftDiagonal' : TOPLEFTDIAGONAL_TRANS_SUBTYPE,
+ 'topRightDiagonal' : TOPRIGHTDIAGONAL_TRANS_SUBTYPE,
+ 'bottomRightDiagonal' : BOTTOMRIGHTDIAGONAL_TRANS_SUBTYPE,
+ 'topLeftClockwise' : TOPLEFTCLOCKWISE_TRANS_SUBTYPE,
+ 'topRightClockwise' : TOPRIGHTCLOCKWISE_TRANS_SUBTYPE,
+ 'bottomRightClockwise' : BOTTOMRIGHTCLOCKWISE_TRANS_SUBTYPE,
+ 'bottomLeftClockwise' : BOTTOMLEFTCLOCKWISE_TRANS_SUBTYPE,
+ 'topLeftCounterClockwise' : TOPLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE,
+ 'topRightCounterClockwise' : TOPRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE,
+ 'bottomRightCounterClockwise' : BOTTOMRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE,
+ 'bottomLeftCounterClockwise' : BOTTOMLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE,
+ 'bottomLeftDiagonal' : BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE,
+ 'rectangle' : RECTANGLE_TRANS_SUBTYPE,
+ 'diamond' : DIAMOND_TRANS_SUBTYPE,
+ 'topLeft' : TOPLEFT_TRANS_SUBTYPE,
+ 'topRight' : TOPRIGHT_TRANS_SUBTYPE,
+ 'bottomRight' : BOTTOMRIGHT_TRANS_SUBTYPE,
+ 'bottomLeft' : BOTTOMLEFT_TRANS_SUBTYPE,
+ 'topCenter' : TOPCENTER_TRANS_SUBTYPE,
+ 'rightCenter' : RIGHTCENTER_TRANS_SUBTYPE,
+ 'bottomCenter' : BOTTOMCENTER_TRANS_SUBTYPE,
+ 'leftCenter' : LEFTCENTER_TRANS_SUBTYPE,
+ 'up' : UP_TRANS_SUBTYPE,
+ 'diagonalBottomLeft' : DIAGONALBOTTOMLEFT_TRANS_SUBTYPE,
+ 'diagonalTopLeft' : DIAGONALTOPLEFT_TRANS_SUBTYPE,
+ 'verticalLeft' : VERTICALLEFT_TRANS_SUBTYPE,
+ 'verticalRight' : VERTICALRIGHT_TRANS_SUBTYPE,
+ 'horizontalLeft' : HORIZONTALLEFT_TRANS_SUBTYPE,
+ 'horizontalRight' : HORIZONTALRIGHT_TRANS_SUBTYPE,
+ 'doubleBarnDoor' : DOUBLEBARNDOOR_TRANS_SUBTYPE,
+ 'doubleDiamond' : DOUBLEDIAMOND_TRANS_SUBTYPE,
+ 'verticalTopSame' : VERTICALTOPSAME_TRANS_SUBTYPE,
+ 'verticalBottomSame' : VERTICALBOTTOMSAME_TRANS_SUBTYPE,
+ 'verticalTopLeftOpposite' : VERTICALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
+ 'verticalBottomLeftOpposite' : VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE,
+ 'horizontalLeftSame' : HORIZONTALLEFTSAME_TRANS_SUBTYPE,
+ 'horizontalRightSame' : HORIZONTALRIGHTSAME_TRANS_SUBTYPE,
+ 'horizontalTopLeftOpposite' : HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
+ 'horizontalTopRightOpposite' : HORIZONTALTOPRIGHTOPPOSITE_TRANS_SUBTYPE,
+ 'diagonalBottomLeftOpposite' : DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE,
+ 'diagonalTopLeftOpposite' : DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
+ 'twoBoxTop' : TWOBOXTOP_TRANS_SUBTYPE,
+ 'twoBoxBottom' : TWOBOXBOTTOM_TRANS_SUBTYPE,
+ 'twoBoxLeft' : TWOBOXLEFT_TRANS_SUBTYPE,
+ 'twoBoxRight' : TWOBOXRIGHT_TRANS_SUBTYPE,
+ 'fourBoxVertical' : FOURBOXVERTICAL_TRANS_SUBTYPE,
+ 'fourBoxHorizontal' : FOURBOXHORIZONTAL_TRANS_SUBTYPE
+};
+
+// Transition Modes
+var TRANSITION_MODE_IN = 1;
+var TRANSITION_MODE_OUT = 0;
+
+var aTransitionModeOutMap = [ 'out', 'in' ];
+
+
+// Transition Reverse Methods
+
+// Ignore direction attribute altogether.
+// (If it has no sensible meaning for this transition.)
+var REVERSEMETHOD_IGNORE = 0;
+// Revert by changing the direction of the parameter sweep.
+// (From 1->0 instead of 0->1)
+var REVERSEMETHOD_INVERT_SWEEP = 1;
+// Revert by subtracting the generated polygon from the target bound rect.
+var REVERSEMETHOD_SUBTRACT_POLYGON = 2;
+// Combination of REVERSEMETHOD_INVERT_SWEEP and REVERSEMETHOD_SUBTRACT_POLYGON.
+var REVERSEMETHOD_SUBTRACT_AND_INVERT = 3;
+// Reverse by rotating polygon 180 degrees.
+var REVERSEMETHOD_ROTATE_180 = 4;
+// Reverse by flipping polygon at the y axis.
+var REVERSEMETHOD_FLIP_X = 5;
+// Reverse by flipping polygon at the x axis.
+var REVERSEMETHOD_FLIP_Y = 6;
+
+// eslint-disable-next-line no-unused-vars
+var aReverseMethodOutMap = ['ignore', 'invert sweep', 'subtract polygon',
+ 'subtract and invert', 'rotate 180', 'flip x', 'flip y'];
+
+
+// Transition filter info table
+
+var aTransitionInfoTable = {};
+
+// type: fake transition
+aTransitionInfoTable[0] = {};
+// subtype: default
+aTransitionInfoTable[0][0] =
+{
+ 'class' : TRANSITION_INVALID,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 0.0,
+ 'scaleY' : 0.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep' : false,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[SNAKEWIPE_TRANSITION] = {};
+aTransitionInfoTable[SNAKEWIPE_TRANSITION][TOPLEFTVERTICAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : -90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SNAKEWIPE_TRANSITION][TOPLEFTHORIZONTAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SNAKEWIPE_TRANSITION][TOPLEFTDIAGONAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SNAKEWIPE_TRANSITION][TOPRIGHTDIAGONAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SNAKEWIPE_TRANSITION][BOTTOMRIGHTDIAGONAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SNAKEWIPE_TRANSITION][BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+}
+
+aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION] = {};
+aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][VERTICALTOPSAME_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][VERTICALBOTTOMSAME_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][VERTICALTOPLEFTOPPOSITE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][HORIZONTALLEFTSAME_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : -90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][HORIZONTALRIGHTSAME_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : -90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][HORIZONTALTOPRIGHTOPPOSITE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : -90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[PARALLELSNAKESWIPE_TRANSITION][DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[SPIRALWIPE_TRANSITION] = {};
+aTransitionInfoTable[SPIRALWIPE_TRANSITION][TOPLEFTCLOCKWISE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SPIRALWIPE_TRANSITION][TOPRIGHTCLOCKWISE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SPIRALWIPE_TRANSITION][BOTTOMRIGHTCLOCKWISE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SPIRALWIPE_TRANSITION][BOTTOMLEFTCLOCKWISE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 270.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SPIRALWIPE_TRANSITION][TOPLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SPIRALWIPE_TRANSITION][TOPRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SPIRALWIPE_TRANSITION][BOTTOMRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 270.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SPIRALWIPE_TRANSITION][BOTTOMLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION] = {};
+aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][TWOBOXTOP_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][TWOBOXBOTTOM_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : -90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][TWOBOXLEFT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][TWOBOXRIGHT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][FOURBOXVERTICAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[BOXSNAKESWIPE_TRANSITION][FOURBOXHORIZONTAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[BARNDOORWIPE_TRANSITION] = {};
+aTransitionInfoTable[BARNDOORWIPE_TRANSITION][VERTICAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle': 0.0,
+ 'scaleX': 1.0,
+ 'scaleY': 1.0,
+ 'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep': true,
+ 'scaleIsotropically': false
+};
+aTransitionInfoTable[BARNDOORWIPE_TRANSITION][HORIZONTAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle': 90.0,
+ 'scaleX': 1.0,
+ 'scaleY': 1.0,
+ 'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep': true,
+ 'scaleIsotropically': false
+};
+aTransitionInfoTable[BARNDOORWIPE_TRANSITION][DIAGONALBOTTOMLEFT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle': 45.0,
+ 'scaleX': Math.SQRT2,
+ 'scaleY': Math.SQRT2,
+ 'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep': true,
+ 'scaleIsotropically': false
+};
+aTransitionInfoTable[BARNDOORWIPE_TRANSITION][DIAGONALTOPLEFT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle': -45.0,
+ 'scaleX': Math.SQRT2,
+ 'scaleY': Math.SQRT2,
+ 'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep': true,
+ 'scaleIsotropically': false
+};
+
+aTransitionInfoTable[MISCDIAGONALWIPE_TRANSITION] = {};
+aTransitionInfoTable[MISCDIAGONALWIPE_TRANSITION][DOUBLEBARNDOOR_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle': 45.0,
+ 'scaleX': Math.SQRT2,
+ 'scaleY': Math.SQRT2,
+ 'reverseMethod': REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep': true,
+ 'scaleIsotropically': false
+};
+aTransitionInfoTable[MISCDIAGONALWIPE_TRANSITION][DOUBLEDIAMOND_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle': 0.0,
+ 'scaleX': 1,
+ 'scaleY': 1,
+ 'reverseMethod': REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep': true,
+ 'scaleIsotropically': false
+};
+
+aTransitionInfoTable[IRISWIPE_TRANSITION] = {};
+aTransitionInfoTable[IRISWIPE_TRANSITION][RECTANGLE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle': 0.0,
+ 'scaleX': 1.0,
+ 'scaleY': 1.0,
+ 'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep': true,
+ 'scaleIsotropically': false
+};
+
+aTransitionInfoTable[IRISWIPE_TRANSITION][DIAMOND_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle': 45.0,
+ 'scaleX': Math.SQRT2,
+ 'scaleY': Math.SQRT2,
+ 'reverseMethod': REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep': true,
+ 'scaleIsotropically': false
+};
+
+aTransitionInfoTable[ZIGZAGWIPE_TRANSITION] = {};
+aTransitionInfoTable[ZIGZAGWIPE_TRANSITION][LEFTTORIGHT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_X,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[ZIGZAGWIPE_TRANSITION][TOPTOBOTTOM_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[BARNZIGZAGWIPE_TRANSITION] = {};
+aTransitionInfoTable[BARNZIGZAGWIPE_TRANSITION][VERTICAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[BARNZIGZAGWIPE_TRANSITION][HORIZONTAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[BARWIPE_TRANSITION] = {};
+aTransitionInfoTable[BARWIPE_TRANSITION][LEFTTORIGHT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_X,
+ 'outInvertsSweep' : false,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[BARWIPE_TRANSITION][TOPTOBOTTOM_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
+ 'outInvertsSweep' : false,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[WATERFALLWIPE_TRANSITION] = {};
+aTransitionInfoTable[WATERFALLWIPE_TRANSITION][VERTICALLEFT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[WATERFALLWIPE_TRANSITION][VERTICALRIGHT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[WATERFALLWIPE_TRANSITION][HORIZONTALLEFT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : -90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[WATERFALLWIPE_TRANSITION][HORIZONTALRIGHT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_ROTATE_180,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[BOXWIPE_TRANSITION] = {};
+aTransitionInfoTable[BOXWIPE_TRANSITION][TOPLEFT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[BOXWIPE_TRANSITION][TOPRIGHT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[BOXWIPE_TRANSITION][BOTTOMRIGHT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[BOXWIPE_TRANSITION][BOTTOMLEFT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : -90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[BOXWIPE_TRANSITION][TOPCENTER_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[BOXWIPE_TRANSITION][RIGHTCENTER_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_X,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[BOXWIPE_TRANSITION][BOTTOMCENTER_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[BOXWIPE_TRANSITION][LEFTCENTER_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : -90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_X,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[FOURBOXWIPE_TRANSITION] = {};
+aTransitionInfoTable[FOURBOXWIPE_TRANSITION][CORNERSIN_TRANS_SUBTYPE] =
+aTransitionInfoTable[FOURBOXWIPE_TRANSITION][CORNERSOUT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[ELLIPSEWIPE_TRANSITION] = {};
+aTransitionInfoTable[ELLIPSEWIPE_TRANSITION][CIRCLE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : true
+};
+aTransitionInfoTable[ELLIPSEWIPE_TRANSITION][HORIZONTAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[ELLIPSEWIPE_TRANSITION][VERTICAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[CLOCKWIPE_TRANSITION] = {};
+aTransitionInfoTable[CLOCKWIPE_TRANSITION][CLOCKWISETWELVE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_X,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[CLOCKWIPE_TRANSITION][CLOCKWISETHREE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[CLOCKWIPE_TRANSITION][CLOCKWISESIX_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_X,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[CLOCKWIPE_TRANSITION][CLOCKWISENINE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 270.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[VEEWIPE_TRANSITION] = {};
+aTransitionInfoTable[VEEWIPE_TRANSITION][DOWN_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[VEEWIPE_TRANSITION][LEFT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_X,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[VEEWIPE_TRANSITION][UP_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[VEEWIPE_TRANSITION][RIGHT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : -90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_X,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[FANWIPE_TRANSITION] = {};
+aTransitionInfoTable[FANWIPE_TRANSITION][CENTERTOP_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[FANWIPE_TRANSITION][CENTERRIGHT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_X,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[FANWIPE_TRANSITION][TOP_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[FANWIPE_TRANSITION][RIGHT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : -90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_X,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[FANWIPE_TRANSITION][BOTTOM_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[FANWIPE_TRANSITION][LEFT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_X,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+
+aTransitionInfoTable[PINWHEELWIPE_TRANSITION] = {};
+aTransitionInfoTable[PINWHEELWIPE_TRANSITION][ONEBLADE_TRANS_SUBTYPE] =
+aTransitionInfoTable[PINWHEELWIPE_TRANSITION][TWOBLADEVERTICAL_TRANS_SUBTYPE] =
+aTransitionInfoTable[PINWHEELWIPE_TRANSITION][THREEBLADE_TRANS_SUBTYPE] =
+aTransitionInfoTable[PINWHEELWIPE_TRANSITION][FOURBLADE_TRANS_SUBTYPE] =
+aTransitionInfoTable[PINWHEELWIPE_TRANSITION][EIGHTBLADE_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_X,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : true
+};
+aTransitionInfoTable[PINWHEELWIPE_TRANSITION][TWOBLADEHORIZONTAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : -90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : true
+};
+
+aTransitionInfoTable[PUSHWIPE_TRANSITION] = {};
+aTransitionInfoTable[PUSHWIPE_TRANSITION][FROMLEFT_TRANS_SUBTYPE] =
+aTransitionInfoTable[PUSHWIPE_TRANSITION][FROMTOP_TRANS_SUBTYPE] =
+aTransitionInfoTable[PUSHWIPE_TRANSITION][FROMRIGHT_TRANS_SUBTYPE] =
+aTransitionInfoTable[PUSHWIPE_TRANSITION][FROMBOTTOM_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_SPECIAL,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+
+aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION] = {};
+aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISETOP_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISERIGHT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISEBOTTOM_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISELEFT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 270.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISETOPLEFT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][COUNTERCLOCKWISEBOTTOMLEFT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][CLOCKWISEBOTTOMRIGHT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 180.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[SINGLESWEEPWIPE_TRANSITION][COUNTERCLOCKWISETOPRIGHT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_SUBTRACT_AND_INVERT,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[SLIDEWIPE_TRANSITION] = {};
+aTransitionInfoTable[SLIDEWIPE_TRANSITION][FROMLEFT_TRANS_SUBTYPE] =
+aTransitionInfoTable[SLIDEWIPE_TRANSITION][FROMTOP_TRANS_SUBTYPE] =
+aTransitionInfoTable[SLIDEWIPE_TRANSITION][FROMRIGHT_TRANS_SUBTYPE] =
+aTransitionInfoTable[SLIDEWIPE_TRANSITION][FROMBOTTOM_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_SPECIAL,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[FADE_TRANSITION] = {};
+aTransitionInfoTable[FADE_TRANSITION][CROSSFADE_TRANS_SUBTYPE] =
+aTransitionInfoTable[FADE_TRANSITION][FADETOCOLOR_TRANS_SUBTYPE] =
+aTransitionInfoTable[FADE_TRANSITION][FADEFROMCOLOR_TRANS_SUBTYPE] =
+aTransitionInfoTable[FADE_TRANSITION][FADEOVERCOLOR_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_SPECIAL,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+
+aTransitionInfoTable[RANDOMBARWIPE_TRANSITION] = {};
+aTransitionInfoTable[RANDOMBARWIPE_TRANSITION][VERTICAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[RANDOMBARWIPE_TRANSITION][HORIZONTAL_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[CHECKERBOARDWIPE_TRANSITION] = {};
+aTransitionInfoTable[CHECKERBOARDWIPE_TRANSITION][DOWN_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 90.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_Y,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+aTransitionInfoTable[CHECKERBOARDWIPE_TRANSITION][ACROSS_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_FLIP_X,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : false
+};
+
+aTransitionInfoTable[DISSOLVE_TRANSITION] = {};
+aTransitionInfoTable[DISSOLVE_TRANSITION][DEFAULT_TRANS_SUBTYPE] =
+{
+ 'class' : TRANSITION_CLIP_POLYPOLYGON,
+ 'rotationAngle' : 0.0,
+ 'scaleX' : 1.0,
+ 'scaleY' : 1.0,
+ 'reverseMethod' : REVERSEMETHOD_IGNORE,
+ 'outInvertsSweep' : true,
+ 'scaleIsotropically' : true
+};
+
+
+// Transition tables
+
+function createStateTransitionTable()
+{
+ var aSTT = {};
+
+ aSTT[RESTART_MODE_NEVER] = {};
+ aSTT[RESTART_MODE_WHEN_NOT_ACTIVE] = {};
+ aSTT[RESTART_MODE_ALWAYS] = {};
+
+ // transition table for restart=NEVER, fill=REMOVE
+ var aTable =
+ aSTT[RESTART_MODE_NEVER][FILL_MODE_REMOVE] = {};
+ aTable[INVALID_NODE] = INVALID_NODE;
+ aTable[UNRESOLVED_NODE] = RESOLVED_NODE | ENDED_NODE;
+ aTable[RESOLVED_NODE] = ACTIVE_NODE | ENDED_NODE;
+ aTable[ACTIVE_NODE] = ENDED_NODE;
+ aTable[FROZEN_NODE] = INVALID_NODE; // this state is unreachable here
+ aTable[ENDED_NODE] = ENDED_NODE; // this state is a sink here (cannot restart)
+
+ // transition table for restart=NEVER, fill=FREEZE
+ aTable =
+ aSTT[RESTART_MODE_NEVER][FILL_MODE_FREEZE] =
+ aSTT[RESTART_MODE_NEVER][FILL_MODE_HOLD] =
+ aSTT[RESTART_MODE_NEVER][FILL_MODE_TRANSITION] = {};
+ aTable[INVALID_NODE] = INVALID_NODE;
+ aTable[UNRESOLVED_NODE] = RESOLVED_NODE | ENDED_NODE;
+ aTable[RESOLVED_NODE] = ACTIVE_NODE | ENDED_NODE;
+ aTable[ACTIVE_NODE] = FROZEN_NODE | ENDED_NODE;
+ aTable[FROZEN_NODE] = ENDED_NODE;
+ aTable[ENDED_NODE] = ENDED_NODE; // this state is a sink here (cannot restart)
+
+ // transition table for restart=WHEN_NOT_ACTIVE, fill=REMOVE
+ aTable =
+ aSTT[RESTART_MODE_WHEN_NOT_ACTIVE][FILL_MODE_REMOVE] = {};
+ aTable[INVALID_NODE] = INVALID_NODE;
+ aTable[UNRESOLVED_NODE] = RESOLVED_NODE | ENDED_NODE;
+ aTable[RESOLVED_NODE] = ACTIVE_NODE | ENDED_NODE;
+ aTable[ACTIVE_NODE] = ENDED_NODE;
+ aTable[FROZEN_NODE] = INVALID_NODE; // this state is unreachable here
+ aTable[ENDED_NODE] = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE; // restart is possible
+
+ // transition table for restart=WHEN_NOT_ACTIVE, fill=FREEZE
+ aTable =
+ aSTT[RESTART_MODE_WHEN_NOT_ACTIVE][FILL_MODE_FREEZE] =
+ aSTT[RESTART_MODE_WHEN_NOT_ACTIVE][FILL_MODE_HOLD] =
+ aSTT[RESTART_MODE_WHEN_NOT_ACTIVE][FILL_MODE_TRANSITION] = {};
+ aTable[INVALID_NODE] = INVALID_NODE;
+ aTable[UNRESOLVED_NODE] = RESOLVED_NODE | ENDED_NODE;
+ aTable[RESOLVED_NODE] = ACTIVE_NODE | ENDED_NODE;
+ aTable[ACTIVE_NODE] = FROZEN_NODE | ENDED_NODE;
+ aTable[FROZEN_NODE] = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE; // restart is possible
+ aTable[ENDED_NODE] = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE; // restart is possible
+
+ // transition table for restart=ALWAYS, fill=REMOVE
+ aTable =
+ aSTT[RESTART_MODE_ALWAYS][FILL_MODE_REMOVE] = {};
+ aTable[INVALID_NODE] = INVALID_NODE;
+ aTable[UNRESOLVED_NODE] = RESOLVED_NODE | ENDED_NODE;
+ aTable[RESOLVED_NODE] = ACTIVE_NODE | ENDED_NODE;
+ aTable[ACTIVE_NODE] = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE; // restart is possible
+ aTable[FROZEN_NODE] = INVALID_NODE; // this state is unreachable here
+ aTable[ENDED_NODE] = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE; // restart is possible
+
+ // transition table for restart=ALWAYS, fill=FREEZE
+ aTable =
+ aSTT[RESTART_MODE_ALWAYS][FILL_MODE_FREEZE] =
+ aSTT[RESTART_MODE_ALWAYS][FILL_MODE_HOLD] =
+ aSTT[RESTART_MODE_ALWAYS][FILL_MODE_TRANSITION] = {};
+ aTable[INVALID_NODE] = INVALID_NODE;
+ aTable[UNRESOLVED_NODE] = RESOLVED_NODE | ENDED_NODE;
+ aTable[RESOLVED_NODE] = ACTIVE_NODE | ENDED_NODE;
+ aTable[ACTIVE_NODE] = RESOLVED_NODE | ACTIVE_NODE | FROZEN_NODE | ENDED_NODE;
+ aTable[FROZEN_NODE] = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE; // restart is possible
+ aTable[ENDED_NODE] = RESOLVED_NODE | ACTIVE_NODE | ENDED_NODE; // restart is possible
+
+ return aSTT;
+}
+
+var aStateTransitionTable = createStateTransitionTable();
+
+
+
+function getTransitionTable( eRestartMode, eFillMode )
+{
+ // If restart mode has not been resolved we use 'never'.
+ // Note: RESTART_MODE_DEFAULT == RESTART_MODE_INHERIT.
+ if( eRestartMode == RESTART_MODE_DEFAULT )
+ {
+ log( 'getTransitionTable: unexpected restart mode: ' + eRestartMode
+ + '. Used NEVER instead.');
+ eRestartMode = RESTART_MODE_NEVER;
+ }
+
+ // If fill mode has not been resolved we use 'remove'.
+ // Note: FILL_MODE_DEFAULT == FILL_MODE_INHERIT
+ if( eFillMode == FILL_MODE_DEFAULT ||
+ eFillMode == FILL_MODE_AUTO )
+ {
+ eFillMode = FILL_MODE_REMOVE;
+ }
+
+ return aStateTransitionTable[eRestartMode][eFillMode];
+}
+
+
+
+
+
+// Event Triggers
+var EVENT_TRIGGER_UNKNOWN = 0;
+var EVENT_TRIGGER_ON_SLIDE_BEGIN = 1; // eslint-disable-line no-unused-vars
+var EVENT_TRIGGER_ON_SLIDE_END = 2; // eslint-disable-line no-unused-vars
+var EVENT_TRIGGER_BEGIN_EVENT = 3;
+var EVENT_TRIGGER_END_EVENT = 4;
+var EVENT_TRIGGER_ON_CLICK = 5;
+var EVENT_TRIGGER_ON_DBL_CLICK = 6; // eslint-disable-line no-unused-vars
+var EVENT_TRIGGER_ON_MOUSE_ENTER = 7; // eslint-disable-line no-unused-vars
+var EVENT_TRIGGER_ON_MOUSE_LEAVE = 8; // eslint-disable-line no-unused-vars
+var EVENT_TRIGGER_ON_NEXT_EFFECT = 9;
+var EVENT_TRIGGER_ON_PREV_EFFECT = 10;
+var EVENT_TRIGGER_REPEAT = 11; // eslint-disable-line no-unused-vars
+
+var aEventTriggerOutMap = [ 'unknown', 'slideBegin', 'slideEnd', 'begin', 'end', 'click',
+ 'doubleClick', 'mouseEnter', 'mouseLeave', 'next', 'previous', 'repeat' ];
+
+
+function getEventTriggerType( sEventTrigger )
+{
+ if( sEventTrigger == 'begin' )
+ return EVENT_TRIGGER_BEGIN_EVENT;
+ else if( sEventTrigger == 'end' )
+ return EVENT_TRIGGER_END_EVENT;
+ else if( sEventTrigger == 'next' )
+ return EVENT_TRIGGER_ON_NEXT_EFFECT;
+ else if( sEventTrigger == 'prev' )
+ return EVENT_TRIGGER_ON_PREV_EFFECT;
+ else if( sEventTrigger == 'click' )
+ return EVENT_TRIGGER_ON_CLICK;
+ else
+ return EVENT_TRIGGER_UNKNOWN;
+}
+
+
+
+
+
+// Timing Types
+var UNKNOWN_TIMING = 0;
+var OFFSET_TIMING = 1;
+var WALLCLOCK_TIMING = 2; // eslint-disable-line no-unused-vars
+var INDEFINITE_TIMING = 3;
+var EVENT_TIMING = 4;
+var SYNCBASE_TIMING = 5;
+var MEDIA_TIMING = 6; // eslint-disable-line no-unused-vars
+
+var aTimingTypeOutMap = [ 'unknown', 'offset', 'wallclock', 'indefinite', 'event', 'syncbase', 'media' ];
+
+
+// Char Codes
+var CHARCODE_PLUS = '+'.charCodeAt(0);
+var CHARCODE_MINUS = '-'.charCodeAt(0);
+var CHARCODE_0 = '0'.charCodeAt(0);
+var CHARCODE_9 = '9'.charCodeAt(0);
+
+
+
+function Timing( aAnimationNode, sTimingAttribute )
+{
+ this.aAnimationNode = aAnimationNode; // the node, the timing attribute belongs to
+ this.sTimingDescription = removeWhiteSpaces( sTimingAttribute );
+ this.eTimingType = UNKNOWN_TIMING;
+ this.nOffset = 0.0; // in seconds
+ this.sEventBaseElementId = ''; // the element id for event based timing
+ this.eEventType = EVENT_TRIGGER_UNKNOWN; // the event type
+}
+
+Timing.prototype.getAnimationNode = function()
+{
+ return this.aAnimationNode;
+};
+
+Timing.prototype.getType = function()
+{
+ return this.eTimingType;
+};
+
+Timing.prototype.getOffset = function()
+{
+ return this.nOffset;
+};
+
+Timing.prototype.getEventBaseElementId = function()
+{
+ return this.sEventBaseElementId;
+};
+
+Timing.prototype.getEventType = function()
+{
+ return this.eEventType;
+};
+
+Timing.prototype.parse = function()
+{
+ if( !this.sTimingDescription )
+ {
+ this.eTimingType = OFFSET_TIMING;
+ return;
+ }
+
+ if( this.sTimingDescription == 'indefinite' )
+ this.eTimingType = INDEFINITE_TIMING;
+ else
+ {
+ var nFirstCharCode = this.sTimingDescription.charCodeAt(0);
+ var bPositiveOffset = !( nFirstCharCode == CHARCODE_MINUS );
+ if ( ( nFirstCharCode == CHARCODE_PLUS ) ||
+ ( nFirstCharCode == CHARCODE_MINUS ) ||
+ ( ( nFirstCharCode >= CHARCODE_0 ) && ( nFirstCharCode <= CHARCODE_9 ) ) )
+ {
+ var sClockValue
+ = ( ( nFirstCharCode == CHARCODE_PLUS ) || ( nFirstCharCode == CHARCODE_MINUS ) )
+ ? this.sTimingDescription.substr( 1 )
+ : this.sTimingDescription;
+
+ var TimeInSec = Timing.parseClockValue( sClockValue );
+ if( TimeInSec != undefined )
+ {
+ this.eTimingType = OFFSET_TIMING;
+ this.nOffset = bPositiveOffset ? TimeInSec : -TimeInSec;
+ }
+ }
+ else
+ {
+ var aTimingSplit = [];
+ bPositiveOffset = true;
+ if( this.sTimingDescription.indexOf( '+' ) != -1 )
+ {
+ aTimingSplit = this.sTimingDescription.split( '+' );
+ }
+ else if( this.sTimingDescription.indexOf( '-' ) != -1 )
+ {
+ aTimingSplit = this.sTimingDescription.split( '-' );
+ bPositiveOffset = false;
+ }
+ else
+ {
+ aTimingSplit[0] = this.sTimingDescription;
+ aTimingSplit[1] = '';
+ }
+
+ if( aTimingSplit[0].indexOf( '.' ) != -1 )
+ {
+ var aEventSplit = aTimingSplit[0].split( '.' );
+ this.sEventBaseElementId = aEventSplit[0];
+ this.eEventType = getEventTriggerType( aEventSplit[1] );
+ }
+ else
+ {
+ this.eEventType = getEventTriggerType( aTimingSplit[0] );
+ }
+
+ if( this.eEventType == EVENT_TRIGGER_UNKNOWN )
+ return;
+
+ if( ( this.eEventType == EVENT_TRIGGER_BEGIN_EVENT ) ||
+ ( this.eEventType == EVENT_TRIGGER_END_EVENT ) )
+ {
+ this.eTimingType = SYNCBASE_TIMING;
+ }
+ else
+ {
+ this.eTimingType = EVENT_TIMING;
+ }
+
+ if( aTimingSplit[1] )
+ {
+ sClockValue = aTimingSplit[1];
+ TimeInSec = Timing.parseClockValue( sClockValue );
+ if( TimeInSec != undefined )
+ {
+ this.nOffset = ( bPositiveOffset ) ? TimeInSec : -TimeInSec;
+ }
+ else
+ {
+ this.eTimingType = UNKNOWN_TIMING;
+ }
+
+ }
+ }
+ }
+
+};
+
+Timing.parseClockValue = function( sClockValue )
+{
+ if( !sClockValue )
+ return 0.0;
+
+ var nTimeInSec = undefined;
+
+ var reFullClockValue = /^([0-9]+):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?$/;
+ var rePartialClockValue = /^([0-5][0-9]):([0-5][0-9])(.[0-9]+)?$/;
+ var reTimeCountValue = /^([0-9]+)(.[0-9]+)?(h|min|s|ms)?$/;
+
+ if( reFullClockValue.test( sClockValue ) )
+ {
+ var aClockTimeParts = reFullClockValue.exec( sClockValue );
+
+ var nHours = parseInt( aClockTimeParts[1] );
+ var nMinutes = parseInt( aClockTimeParts[2] );
+ var nSeconds = parseInt( aClockTimeParts[3] );
+ if( aClockTimeParts[4] )
+ nSeconds += parseFloat( aClockTimeParts[4] );
+
+ nTimeInSec = ( ( nHours * 60 ) + nMinutes ) * 60 + nSeconds;
+
+ }
+ else if( rePartialClockValue.test( sClockValue ) )
+ {
+ aClockTimeParts = rePartialClockValue.exec( sClockValue );
+
+ nMinutes = parseInt( aClockTimeParts[1] );
+ nSeconds = parseInt( aClockTimeParts[2] );
+ if( aClockTimeParts[3] )
+ nSeconds += parseFloat( aClockTimeParts[3] );
+
+ nTimeInSec = nMinutes * 60 + nSeconds;
+ }
+ else if( reTimeCountValue.test( sClockValue ) )
+ {
+ aClockTimeParts = reTimeCountValue.exec( sClockValue );
+
+ var nTimeCount = parseInt( aClockTimeParts[1] );
+ if( aClockTimeParts[2] )
+ nTimeCount += parseFloat( aClockTimeParts[2] );
+
+ if( aClockTimeParts[3] )
+ {
+ if( aClockTimeParts[3] == 'h' )
+ {
+ nTimeInSec = nTimeCount * 3600;
+ }
+ else if( aClockTimeParts[3] == 'min' )
+ {
+ nTimeInSec = nTimeCount * 60;
+ }
+ else if( aClockTimeParts[3] == 's' )
+ {
+ nTimeInSec = nTimeCount;
+ }
+ else if( aClockTimeParts[3] == 'ms' )
+ {
+ nTimeInSec = nTimeCount / 1000;
+ }
+ }
+ else
+ {
+ nTimeInSec = nTimeCount;
+ }
+
+ }
+
+ if( nTimeInSec )
+ nTimeInSec = parseFloat( nTimeInSec.toFixed( 3 ) );
+ return nTimeInSec;
+};
+
+Timing.prototype.info = function( bVerbose )
+{
+
+ var sInfo = '';
+
+ if( bVerbose )
+ {
+ sInfo = 'description: ' + this.sTimingDescription + ', ';
+
+ sInfo += ', type: ' + aTimingTypeOutMap[ this.getType() ];
+ sInfo += ', offset: ' + this.getOffset();
+ sInfo += ', event base element id: ' + this.getEventBaseElementId();
+ sInfo += ', timing event type: ' + aEventTriggerOutMap[ this.getEventType() ];
+ }
+ else
+ {
+ switch( this.getType() )
+ {
+ case INDEFINITE_TIMING:
+ sInfo += 'indefinite';
+ break;
+ case OFFSET_TIMING:
+ sInfo += this.getOffset();
+ break;
+ case EVENT_TIMING:
+ case SYNCBASE_TIMING:
+ if( this.getEventBaseElementId() )
+ sInfo += this.getEventBaseElementId() + '.';
+ sInfo += aEventTriggerOutMap[ this.getEventType() ];
+ if( this.getOffset() )
+ {
+ if( this.getOffset() > 0 )
+ sInfo += '+';
+ sInfo += this.getOffset();
+ }
+ }
+ }
+
+ return sInfo;
+};
+
+
+
+
+function Duration( sDurationAttribute )
+{
+ this.bIndefinite = false;
+ this.bMedia = false;
+ this.nValue = undefined;
+ this.bDefined = false;
+
+ if( !sDurationAttribute )
+ return;
+
+ if( sDurationAttribute == 'indefinite' )
+ this.bIndefinite = true;
+ else if( sDurationAttribute == 'media' )
+ this.bMedia = true;
+ else
+ {
+ this.nValue = Timing.parseClockValue( sDurationAttribute );
+ if( this.nValue <= 0.0 )
+ this.nValue = 0.001; // duration must be always greater than 0
+ }
+ this.bDefined = true;
+}
+
+
+Duration.prototype.isSet = function()
+{
+ return this.bDefined;
+};
+
+Duration.prototype.isIndefinite = function()
+{
+ return this.bIndefinite;
+};
+
+Duration.prototype.isMedia = function()
+{
+ return this.bMedia;
+};
+
+Duration.prototype.isValue = function()
+{
+ return this.nValue != undefined;
+};
+
+Duration.prototype.getValue= function()
+{
+ return this.nValue;
+};
+
+Duration.prototype.info= function()
+{
+ var sInfo;
+
+ if( this.isIndefinite() )
+ sInfo = 'indefinite';
+ else if( this.isMedia() )
+ sInfo = 'media';
+ else if( this.getValue() )
+ sInfo = this.getValue();
+
+ return sInfo;
+};
+
+
+
+
+function AnimationNode()
+{
+}
+
+AnimationNode.prototype.init = function() {};
+AnimationNode.prototype.resolve = function() {};
+AnimationNode.prototype.activate = function() {};
+AnimationNode.prototype.deactivate = function() {};
+AnimationNode.prototype.end = function() {};
+AnimationNode.prototype.getState = function() {};
+AnimationNode.prototype.registerDeactivatingListener = function() {};
+AnimationNode.prototype.notifyDeactivating = function() {};
+
+
+
+
+function NodeContext( aSlideShowContext )
+{
+ this.aContext = aSlideShowContext;
+ this.aAnimationNodeMap = null;
+ this.aAnimatedElementMap = null;
+ this.aSourceEventElementMap = null;
+ this.nStartDelay = 0.0;
+ this.bFirstRun = undefined;
+ this.bIsInvalid = false;
+ this.aSlideHeight = HEIGHT;
+ this.aSlideWidth = WIDTH;
+}
+
+
+NodeContext.prototype.makeSourceEventElement = function( sId, aEventBaseElem )
+{
+ if( !aEventBaseElem )
+ {
+ log( 'NodeContext.makeSourceEventElement: event base element is not valid' );
+ return null;
+ }
+
+ if( !this.aContext.aEventMultiplexer )
+ {
+ log( 'NodeContext.makeSourceEventElement: event multiplexer not initialized' );
+ return null;
+ }
+
+ if( !this.aSourceEventElementMap[ sId ] )
+ {
+ this.aSourceEventElementMap[ sId ] = new SourceEventElement( sId, aEventBaseElem, this.aContext.aEventMultiplexer );
+ }
+ return this.aSourceEventElementMap[ sId ];
+};
+
+
+
+
+function StateTransition( aBaseNode )
+{
+ this.aNode = aBaseNode;
+ this.eToState = INVALID_NODE;
+}
+
+StateTransition.prototype.enter = function( eNodeState, bForce )
+{
+ if( !bForce ) bForce = false;
+
+ if( this.eToState != INVALID_NODE )
+ {
+ log( 'StateTransition.enter: commit() before enter()ing again!' );
+ return false;
+ }
+ if( !bForce && !this.aNode.isTransition( this.aNode.getState(), eNodeState ) )
+ return false;
+
+ // recursion detection:
+ if( ( this.aNode.nCurrentStateTransition & eNodeState ) != 0 )
+ return false; // already in wanted transition
+
+ // mark transition:
+ this.aNode.nCurrentStateTransition |= eNodeState;
+ this.eToState = eNodeState;
+ return true;
+};
+
+StateTransition.prototype.commit = function()
+{
+ if( this.eToState != INVALID_NODE )
+ {
+ this.aNode.eCurrentState = this.eToState;
+ this.clear();
+ }
+};
+
+StateTransition.prototype.clear = function()
+{
+ if( this.eToState != INVALID_NODE )
+ {
+ this.aNode.nCurrentStateTransition &= ~this.eToState;
+ this.eToState = INVALID_NODE;
+ }
+};
+
+
+
+
+function BaseNode( aAnimElem, aParentNode, aNodeContext )
+{
+ this.nId = getUniqueId();
+ this.sClassName = 'BaseNode';
+
+ if( !aAnimElem )
+ log( 'BaseNode(id:' + this.nId + ') constructor: aAnimElem is not valid' );
+
+ if( !aNodeContext )
+ log( 'BaseNode(id:' + this.nId + ') constructor: aNodeContext is not valid' );
+
+ if( !aNodeContext.aContext )
+ log( 'BaseNode(id:' + this.nId + ') constructor: aNodeContext.aContext is not valid' );
+
+
+ this.bIsContainer = false;
+ this.aElement = aAnimElem;
+ this.aParentNode = aParentNode;
+ this.aNodeContext = aNodeContext;
+ this.aContext = aNodeContext.aContext;
+ this.nStartDelay = aNodeContext.nStartDelay;
+ this.eCurrentState = UNRESOLVED_NODE;
+ this.nCurrentStateTransition = 0;
+ this.aDeactivatingListenerArray = [];
+ this.aActivationEvent = null;
+ this.aDeactivationEvent = null;
+
+ this.aBegin = null;
+ this.aDuration = null;
+ this.aEnd = null;
+ this.bMainSequenceRootNode = false;
+ this.bInteractiveSequenceRootNode = false;
+ this.eFillMode = FILL_MODE_FREEZE;
+ this.eRestartMode = RESTART_MODE_NEVER;
+ this.nRepeatCount = undefined;
+ this.nAccelerate = 0.0;
+ this.nDecelerate = 0.0;
+ this.bAutoReverse = false;
+
+}
+extend( BaseNode, AnimationNode );
+
+
+BaseNode.prototype.getId = function()
+{
+ return this.nId;
+};
+
+BaseNode.prototype.parseElement = function()
+{
+ var aAnimElem = this.aElement;
+
+ // id attribute
+ var sIdAttr = aAnimElem.getAttributeNS( NSS['xml'], 'id' );
+ // we append the animation node to the Animation Node Map so that it can be retrieved
+ // by the registerEvent routine for resolving begin values of type 'id.begin', 'id.end'
+ if( sIdAttr )
+ this.aNodeContext.aAnimationNodeMap[ sIdAttr ] = this;
+
+ // begin attribute
+ this.aBegin = null;
+ var sBeginAttr = aAnimElem.getAttributeNS( NSS['smil'], 'begin' );
+ this.aBegin = new Timing( this, sBeginAttr );
+ this.aBegin.parse();
+
+ // end attribute
+ this.aEnd = null;
+ var sEndAttr = aAnimElem.getAttributeNS( NSS['smil'], 'end' );
+ if( sEndAttr )
+ {
+ this.aEnd = new Timing( this, sEndAttr );
+ this.aEnd.parse();
+ }
+
+ // dur attribute
+ this.aDuration = null;
+ var sDurAttr = aAnimElem.getAttributeNS( NSS['smil'], 'dur' );
+ this.aDuration = new Duration( sDurAttr );
+ if( !this.aDuration.isSet() )
+ {
+ if( this.isContainer() )
+ this.aDuration = null;
+ else
+ this.aDuration = new Duration( 'indefinite' );
+ }
+
+ // fill attribute
+ var sFillAttr = aAnimElem.getAttributeNS( NSS['smil'], 'fill' );
+ if( sFillAttr && aFillModeInMap[ sFillAttr ])
+ this.eFillMode = aFillModeInMap[ sFillAttr ];
+ else
+ this.eFillMode = FILL_MODE_DEFAULT;
+
+ // restart attribute
+ var sRestartAttr = aAnimElem.getAttributeNS( NSS['smil'], 'restart' );
+ if( sRestartAttr && aRestartModeInMap[ sRestartAttr ] )
+ this.eRestartMode = aRestartModeInMap[ sRestartAttr ];
+ else
+ this.eRestartMode = RESTART_MODE_DEFAULT;
+
+ // repeatCount attribute
+ var sRepeatCount = aAnimElem.getAttributeNS( NSS['smil'], 'repeatCount' );
+ if( !sRepeatCount )
+ this.nRepeatCount = 1;
+ else
+ this.nRepeatCount = parseFloat( sRepeatCount );
+ if( ( isNaN(this.nRepeatCount) ) && ( sRepeatCount != 'indefinite' ) )
+ this.nRepeatCount = 1;
+
+ // accelerate attribute
+ this.nAccelerate = 0.0;
+ var sAccelerateAttr = aAnimElem.getAttributeNS( NSS['smil'], 'accelerate' );
+ if( sAccelerateAttr )
+ this.nAccelerate = parseFloat( sAccelerateAttr );
+ if( isNaN(this.nAccelerate) )
+ this.nAccelerate = 0.0;
+
+ // decelerate attribute
+ this.nDecelerate = 0.0;
+ var sDecelerateAttr = aAnimElem.getAttributeNS( NSS['smil'], 'decelerate' );
+ if( sDecelerateAttr )
+ this.nDecelerate = parseFloat( sDecelerateAttr );
+ if( isNaN(this.nDecelerate) )
+ this.nDecelerate = 0.0;
+
+ // autoReverse attribute
+ this.bAutoreverse = false;
+ var sAutoReverseAttr = aAnimElem.getAttributeNS( NSS['smil'], 'autoReverse' );
+ if( sAutoReverseAttr == 'true' )
+ this.bAutoreverse = true;
+
+
+ // resolve fill value
+ if( this.eFillMode == FILL_MODE_DEFAULT )
+ if( this.getParentNode() )
+ this.eFillMode = this.getParentNode().getFillMode();
+ else
+ this.eFillMode = FILL_MODE_AUTO;
+
+ if( this.eFillMode == FILL_MODE_AUTO ) // see SMIL recommendation document
+ {
+ this.eFillMode = ( this.aEnd ||
+ ( this.nRepeatCount != 1) ||
+ ( this.aDuration && !this.aDuration.isIndefinite() ) )
+ ? FILL_MODE_REMOVE
+ : FILL_MODE_FREEZE;
+ }
+
+ // resolve restart value
+ if( this.eRestartMode == RESTART_MODE_DEFAULT )
+ if( this.getParentNode() )
+ this.eRestartMode = this.getParentNode().getRestartMode();
+ else
+ // SMIL recommendation document says to set it to 'always'
+ this.eRestartMode = RESTART_MODE_ALWAYS;
+
+ // resolve accelerate and decelerate attributes
+ // from the SMIL recommendation document: if the individual values of the accelerate
+ // and decelerate attributes are between 0 and 1 and the sum is greater than 1,
+ // then both the accelerate and decelerate attributes will be ignored and the timed
+ // element will behave as if neither attribute was specified.
+ if( ( this.nAccelerate + this.nDecelerate ) > 1.0 )
+ {
+ this.nAccelerate = 0.0;
+ this.nDecelerate = 0.0;
+ }
+
+ this.aStateTransTable = getTransitionTable( this.getRestartMode(), this.getFillMode() );
+
+ return true;
+};
+
+BaseNode.prototype.getParentNode = function()
+{
+ return this.aParentNode;
+};
+
+BaseNode.prototype.init = function()
+{
+ this.DBG( this.callInfo( 'init' ) );
+ if( ! this.checkValidNode() )
+ return false;
+ if( this.aActivationEvent )
+ this.aActivationEvent.dispose();
+ if( this.aDeactivationEvent )
+ this.aDeactivationEvent.dispose();
+
+ this.eCurrentState = UNRESOLVED_NODE;
+
+ return this.init_st();
+};
+
+BaseNode.prototype.resolve = function()
+{
+ if( this.aNodeContext.bIsInvalid || ! this.checkValidNode() )
+ return false;
+
+ this.DBG( this.callInfo( 'resolve' ) );
+
+ if( this.eCurrentState == RESOLVED_NODE )
+ log( 'BaseNode.resolve: already in RESOLVED state' );
+
+ var aStateTrans = new StateTransition( this );
+
+ if( aStateTrans.enter( RESOLVED_NODE ) &&
+ this.isTransition( RESOLVED_NODE, ACTIVE_NODE ) &&
+ this.resolve_st() )
+ {
+ aStateTrans.commit();
+
+ if( this.aActivationEvent )
+ {
+ this.aActivationEvent.charge();
+ }
+ else
+ {
+ this.aActivationEvent = makeDelay( bind( this, this.activate ), this.getBegin().getOffset() + this.nStartDelay );
+ }
+ registerEvent( this.getId(), this.getBegin(), this.aActivationEvent, this.aNodeContext );
+
+ return true;
+ }
+
+ return false;
+};
+
+BaseNode.prototype.activate = function()
+{
+ if( ! this.checkValidNode() )
+ return false;
+
+ if( this.eCurrentState == ACTIVE_NODE )
+ log( 'BaseNode.activate: already in ACTIVE state' );
+
+ this.DBG( this.callInfo( 'activate' ), getCurrentSystemTime() );
+
+ var aStateTrans = new StateTransition( this );
+
+ if( aStateTrans.enter( ACTIVE_NODE ) )
+ {
+ this.activate_st();
+ aStateTrans.commit();
+ if( !this.aContext.aEventMultiplexer )
+ log( 'BaseNode.activate: this.aContext.aEventMultiplexer is not valid' );
+ this.aContext.aEventMultiplexer.notifyEvent( EVENT_TRIGGER_BEGIN_EVENT, this.getId() );
+ return true;
+ }
+ return false;
+};
+
+BaseNode.prototype.deactivate = function()
+{
+ if( this.inStateOrTransition( ENDED_NODE | FROZEN_NODE ) || !this.checkValidNode() )
+ return;
+
+ if( this.isTransition( this.eCurrentState, FROZEN_NODE ) )
+ {
+ this.DBG( this.callInfo( 'deactivate' ), getCurrentSystemTime() );
+
+ var aStateTrans = new StateTransition( this );
+ if( aStateTrans.enter( FROZEN_NODE, true /* FORCE */ ) )
+ {
+ this.deactivate_st( FROZEN_NODE );
+ aStateTrans.commit();
+
+ this.notifyEndListeners();
+
+ if( this.aActivationEvent )
+ this.aActivationEvent.dispose();
+ if( this.aDeactivationEvent )
+ this.aDeactivationEvent.dispose();
+ }
+ }
+ else
+ {
+ this.end();
+ }
+ // state has changed either to FROZEN or ENDED
+};
+
+BaseNode.prototype.end = function()
+{
+ var bIsFrozenOrInTransitionToFrozen = this.inStateOrTransition( FROZEN_NODE );
+ if( this.inStateOrTransition( ENDED_NODE ) || !this.checkValidNode() )
+ return;
+
+ if( !(this.isTransition( this.eCurrentState, ENDED_NODE ) ) )
+ log( 'BaseNode.end: end state not reachable in transition table' );
+
+ this.DBG( this.callInfo( 'end' ), getCurrentSystemTime() );
+
+ var aStateTrans = new StateTransition( this );
+ if( aStateTrans.enter( ENDED_NODE, true /* FORCE */ ) )
+ {
+ this.deactivate_st( ENDED_NODE );
+ aStateTrans.commit();
+
+ // if is FROZEN or is to be FROZEN, then
+ // will/already notified deactivating listeners
+ if( !bIsFrozenOrInTransitionToFrozen )
+ this.notifyEndListeners();
+
+ if( this.aActivationEvent )
+ this.aActivationEvent.dispose();
+ if( this.aDeactivationEvent )
+ this.aDeactivationEvent.dispose();
+ }
+};
+
+BaseNode.prototype.dispose = function()
+{
+ if( this.aActivationEvent )
+ this.aActivationEvent.dispose();
+ if( this.aDeactivationEvent )
+ this.aDeactivationEvent.dispose();
+ this.aDeactivatingListenerArray = [];
+};
+
+BaseNode.prototype.getState = function()
+{
+ return this.eCurrentState;
+};
+
+BaseNode.prototype.registerDeactivatingListener = function( aNotifiee )
+{
+ if (! this.checkValidNode())
+ return false;
+
+ if( !aNotifiee )
+ {
+ log( 'BaseNode.registerDeactivatingListener(): invalid notifiee' );
+ return false;
+ }
+ this.aDeactivatingListenerArray.push( aNotifiee );
+
+ return true;
+};
+
+BaseNode.prototype.notifyDeactivating = function( aNotifier )
+{
+ assert( ( aNotifier.getState() == FROZEN_NODE ) || ( aNotifier.getState() == ENDED_NODE ),
+ 'BaseNode.notifyDeactivating: Notifier node is neither in FROZEN nor in ENDED state' );
+};
+
+BaseNode.prototype.isMainSequenceRootNode = function()
+{
+ return this.bMainSequenceRootNode;
+};
+
+BaseNode.prototype.isInteractiveSequenceRootNode = function()
+{
+ return this.bInteractiveSequenceRootNode;
+};
+
+BaseNode.prototype.makeDeactivationEvent = function( nDelay )
+{
+ if( this.aDeactivationEvent )
+ {
+ this.aDeactivationEvent.charge();
+ }
+ else
+ {
+ if( typeof( nDelay ) == typeof(0) )
+ this.aDeactivationEvent = makeDelay( bind( this, this.deactivate ), nDelay );
+ else
+ this.aDeactivationEvent = null;
+ }
+ return this.aDeactivationEvent;
+};
+
+BaseNode.prototype.scheduleDeactivationEvent = function( aEvent )
+{
+ this.DBG( this.callInfo( 'scheduleDeactivationEvent' ) );
+
+ if( !aEvent )
+ {
+ if( this.getDuration() && this.getDuration().isValue() )
+ aEvent = this.makeDeactivationEvent( this.getDuration().getValue() );
+ }
+ if( aEvent )
+ {
+ this.aContext.aTimerEventQueue.addEvent( aEvent );
+ }
+};
+
+BaseNode.prototype.checkValidNode = function()
+{
+ return ( this.eCurrentState != INVALID_NODE );
+};
+
+BaseNode.prototype.init_st = function()
+{
+ return true;
+};
+
+BaseNode.prototype.resolve_st = function()
+{
+ return true;
+};
+
+BaseNode.prototype.activate_st = function()
+{
+ this.scheduleDeactivationEvent();
+};
+
+BaseNode.prototype.deactivate_st = function( /*aNodeState*/ )
+{
+ // empty body
+};
+
+BaseNode.prototype.notifyEndListeners = function()
+{
+ var nDeactivatingListenerCount = this.aDeactivatingListenerArray.length;
+
+ for( var i = 0; i < nDeactivatingListenerCount; ++i )
+ {
+ this.aDeactivatingListenerArray[i].notifyDeactivating( this );
+ }
+
+ this.aContext.aEventMultiplexer.notifyEvent( EVENT_TRIGGER_END_EVENT, this.getId() );
+ if( this.getParentNode() && this.getParentNode().isMainSequenceRootNode() )
+ this.aContext.aEventMultiplexer.notifyNextEffectEndEvent();
+
+ if( this.isMainSequenceRootNode() )
+ this.aContext.aEventMultiplexer.notifyAnimationsEndEvent();
+};
+
+BaseNode.prototype.getContext = function()
+{
+ return this.aContext;
+};
+
+BaseNode.prototype.isTransition = function( eFromState, eToState )
+{
+ return ( ( this.aStateTransTable[ eFromState ] & eToState ) != 0 );
+};
+
+BaseNode.prototype.inStateOrTransition = function( nMask )
+{
+ return ( ( ( this.eCurrentState & nMask ) != 0 ) || ( ( this.nCurrentStateTransition & nMask ) != 0 ) );
+};
+
+BaseNode.prototype.isContainer = function()
+{
+ return this.bIsContainer;
+};
+
+BaseNode.prototype.getBegin = function()
+{
+ return this.aBegin;
+};
+
+BaseNode.prototype.getDuration = function()
+{
+ return this.aDuration;
+};
+
+BaseNode.prototype.getEnd = function()
+{
+ return this.aEnd;
+};
+
+BaseNode.prototype.getFillMode = function()
+{
+ return this.eFillMode;
+};
+
+BaseNode.prototype.getRestartMode = function()
+{
+ return this.eRestartMode;
+};
+
+BaseNode.prototype.getRepeatCount = function()
+{
+ return this.nRepeatCount;
+};
+
+BaseNode.prototype.getAccelerateValue = function()
+{
+ return this.nAccelerate;
+};
+
+BaseNode.prototype.getDecelerateValue = function()
+{
+ return this.nDecelerate;
+};
+
+BaseNode.prototype.isAutoReverseEnabled = function()
+{
+ return this.bAutoreverse;
+};
+
+BaseNode.prototype.info = function( bVerbose )
+{
+ var sInfo = 'class name: ' + this.sClassName;
+ sInfo += '; element name: ' + this.aElement.localName;
+ sInfo += '; id: ' + this.getId();
+ sInfo += '; state: ' + getNodeStateName( this.getState() );
+
+ if( bVerbose )
+ {
+ // is container
+ sInfo += '; is container: ' + this.isContainer();
+
+ // begin
+ if( this.getBegin() )
+ sInfo += '; begin: ' + this.getBegin().info();
+
+ // duration
+ if( this.getDuration() )
+ sInfo += '; dur: ' + this.getDuration().info();
+
+ // end
+ if( this.getEnd() )
+ sInfo += '; end: ' + this.getEnd().info();
+
+ // fill mode
+ if( this.getFillMode() )
+ sInfo += '; fill: ' + aFillModeOutMap[ this.getFillMode() ];
+
+ // restart mode
+ if( this.getRestartMode() )
+ sInfo += '; restart: ' + aRestartModeOutMap[ this.getRestartMode() ];
+
+ // repeatCount
+ if( this.getRepeatCount() && ( this.getRepeatCount() != 1.0 ) )
+ sInfo += '; repeatCount: ' + this.getRepeatCount();
+
+ // accelerate
+ if( this.getAccelerateValue() )
+ sInfo += '; accelerate: ' + this.getAccelerateValue();
+
+ // decelerate
+ if( this.getDecelerateValue() )
+ sInfo += '; decelerate: ' + this.getDecelerateValue();
+
+ // auto reverse
+ if( this.isAutoReverseEnabled() )
+ sInfo += '; autoReverse: true';
+
+ }
+
+ return sInfo;
+};
+
+BaseNode.prototype.callInfo = function( sMethodName )
+{
+ var sInfo = this.sClassName +
+ '( ' + this.getId() +
+ ', ' + getNodeStateName( this.getState() ) +
+ ' ).' + sMethodName;
+ return sInfo;
+};
+
+BaseNode.prototype.DBG = function( sMessage, nTime )
+{
+ ANIMDBG.print( sMessage, nTime );
+};
+
+
+
+
+function AnimationBaseNode( aAnimElem, aParentNode, aNodeContext )
+{
+ AnimationBaseNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
+
+ this.sClassName = 'AnimationBaseNode';
+ this.bIsContainer = false;
+ this.aTargetElement = null;
+ this.bIsTargetTextElement = false;
+ this.aAnimatedElement = null;
+ this.aActivity = null;
+
+ this.nMinFrameCount = undefined;
+ this.eAdditiveMode = undefined;
+
+}
+extend( AnimationBaseNode, BaseNode );
+
+
+AnimationBaseNode.prototype.parseElement = function()
+{
+ var bRet = AnimationBaseNode.superclass.parseElement.call( this );
+
+ var aAnimElem = this.aElement;
+
+ // targetElement attribute
+ this.aTargetElement = null;
+ var sTargetElementAttr = aAnimElem.getAttributeNS( NSS['smil'], 'targetElement' );
+ if( sTargetElementAttr )
+ this.aTargetElement = document.getElementById( sTargetElementAttr );
+
+ if( !this.aTargetElement )
+ {
+ this.eCurrentState = INVALID_NODE;
+ log( 'AnimationBaseNode.parseElement: target element not found: ' + sTargetElementAttr );
+ }
+
+ // sub-item attribute for text animated element
+ var sSubItemAttr = aAnimElem.getAttributeNS( NSS['anim'], 'sub-item' );
+ this.bIsTargetTextElement = ( sSubItemAttr && ( sSubItemAttr === 'text' ) );
+
+ // additive attribute
+ var sAdditiveAttr = aAnimElem.getAttributeNS( NSS['smil'], 'additive' );
+ if( sAdditiveAttr && aAddittiveModeInMap[sAdditiveAttr] )
+ this.eAdditiveMode = aAddittiveModeInMap[sAdditiveAttr];
+ else
+ this.eAdditiveMode = ADDITIVE_MODE_REPLACE;
+
+ // set up min frame count value;
+ this.nMinFrameCount = ( this.getDuration().isValue() )
+ ? ( this.getDuration().getValue() * MINIMUM_FRAMES_PER_SECONDS )
+ : MINIMUM_FRAMES_PER_SECONDS;
+ if( this.nMinFrameCount < 1.0 )
+ this.nMinFrameCount = 1;
+ else if( this.nMinFrameCount > MINIMUM_FRAMES_PER_SECONDS )
+ this.nMinFrameCount = MINIMUM_FRAMES_PER_SECONDS;
+
+
+ if( this.aTargetElement )
+ {
+ // set up target element initial visibility
+ if( aAnimElem.getAttributeNS( NSS['smil'], 'attributeName' ) === 'visibility' )
+ {
+ if( aAnimElem.getAttributeNS( NSS['smil'], 'to' ) === 'visible' )
+ this.aTargetElement.setAttribute( 'visibility', 'hidden' );
+ }
+
+ // create animated element
+ if( !this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ] )
+ {
+ if( this.bIsTargetTextElement )
+ {
+ this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ]
+ = new AnimatedTextElement( this.aTargetElement );
+ }
+ else
+ {
+ this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ]
+ = new AnimatedElement( this.aTargetElement );
+ }
+ }
+ this.aAnimatedElement = this.aNodeContext.aAnimatedElementMap[ sTargetElementAttr ];
+
+ // set additive mode
+ this.aAnimatedElement.setAdditiveMode( this.eAdditiveMode );
+ }
+
+
+ return bRet;
+};
+
+AnimationBaseNode.prototype.init_st = function()
+{
+ if( this.aActivity )
+ this.aActivity.activate( makeEvent( bind( this, this.deactivate ) ) );
+ else
+ this.aActivity = this.createActivity();
+ return true;
+};
+
+AnimationBaseNode.prototype.resolve_st = function()
+{
+ return true;
+};
+
+AnimationBaseNode.prototype.activate_st = function()
+{
+ if( this.aActivity )
+ {
+ this.saveStateOfAnimatedElement();
+ this.aActivity.setTargets( this.getAnimatedElement() );
+ if( this.getContext().bIsSkipping )
+ {
+ this.aActivity.end();
+ }
+ else
+ {
+ this.getContext().aActivityQueue.addActivity( this.aActivity );
+ }
+ }
+ else
+ {
+ AnimationBaseNode.superclass.scheduleDeactivationEvent.call( this );
+ }
+};
+
+AnimationBaseNode.prototype.deactivate_st = function( eDestState )
+{
+ if( eDestState == FROZEN_NODE )
+ {
+ if( this.aActivity )
+ this.aActivity.end();
+ }
+ if( eDestState == ENDED_NODE )
+ {
+ if( this.aActivity )
+ this.aActivity.dispose();
+ if( ( this.getFillMode() == FILL_MODE_REMOVE ) && this.getAnimatedElement() )
+ this.removeEffect();
+ }
+};
+
+AnimationBaseNode.prototype.createActivity = function()
+{
+ log( 'AnimationBaseNode.createActivity: abstract method called' );
+};
+
+AnimationBaseNode.prototype.fillActivityParams = function()
+{
+
+ // compute duration
+ var nDuration = 0.001;
+ if( this.getDuration().isValue() )
+ {
+ nDuration = this.getDuration().getValue();
+ }
+ else
+ {
+ log( 'AnimationBaseNode.fillActivityParams: duration is not a number' );
+ }
+
+ // create and set up activity params
+ var aActivityParamSet = new ActivityParamSet();
+
+ aActivityParamSet.aEndEvent = makeEvent( bind( this, this.deactivate ) );
+ aActivityParamSet.aTimerEventQueue = this.aContext.aTimerEventQueue;
+ aActivityParamSet.aActivityQueue = this.aContext.aActivityQueue;
+ aActivityParamSet.nMinDuration = nDuration;
+ aActivityParamSet.nMinNumberOfFrames = this.getMinFrameCount();
+ aActivityParamSet.bAutoReverse = this.isAutoReverseEnabled();
+ aActivityParamSet.nRepeatCount = this.getRepeatCount();
+ aActivityParamSet.nAccelerationFraction = this.getAccelerateValue();
+ aActivityParamSet.nDecelerationFraction = this.getDecelerateValue();
+ aActivityParamSet.nSlideWidth = this.aNodeContext.aSlideWidth;
+ aActivityParamSet.nSlideHeight = this.aNodeContext.aSlideHeight;
+
+ return aActivityParamSet;
+};
+
+AnimationBaseNode.prototype.hasPendingAnimation = function()
+{
+ return true;
+};
+
+AnimationBaseNode.prototype.saveStateOfAnimatedElement = function()
+{
+ this.getAnimatedElement().saveState( this.getId() );
+};
+
+AnimationBaseNode.prototype.removeEffect = function()
+{
+ this.getAnimatedElement().restoreState( this.getId() );
+};
+
+AnimationBaseNode.prototype.getTargetElement = function()
+{
+ return this.aTargetElement;
+};
+
+AnimationBaseNode.prototype.getAnimatedElement = function()
+{
+ return this.aAnimatedElement;
+};
+
+AnimationBaseNode.prototype.dispose= function()
+{
+ if( this.aActivity )
+ this.aActivity.dispose();
+
+ AnimationBaseNode.superclass.dispose.call( this );
+};
+
+AnimationBaseNode.prototype.getMinFrameCount = function()
+{
+ return this.nMinFrameCount;
+};
+
+AnimationBaseNode.prototype.getAdditiveMode = function()
+{
+ return this.eAdditiveMode;
+};
+
+AnimationBaseNode.prototype.info = function( bVerbose )
+{
+ var sInfo = AnimationBaseNode.superclass.info.call( this, bVerbose );
+
+ if( bVerbose )
+ {
+ // min frame count
+ if( this.getMinFrameCount() )
+ sInfo += '; min frame count: ' + this.getMinFrameCount();
+
+ // additive mode
+ sInfo += '; additive: ' + aAddittiveModeOutMap[ this.getAdditiveMode() ];
+
+ // target element
+ if( this.getTargetElement() )
+ {
+ var sElemId = this.getTargetElement().getAttribute( 'id' );
+ sInfo += '; targetElement: ' + sElemId;
+ }
+ }
+
+ return sInfo;
+};
+
+
+
+function AnimationBaseNode2( aAnimElem, aParentNode, aNodeContext )
+{
+ AnimationBaseNode2.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
+
+ this.sAttributeName = '';
+ this.aToValue = null;
+
+}
+extend( AnimationBaseNode2, AnimationBaseNode );
+
+
+AnimationBaseNode2.prototype.parseElement = function()
+{
+ var bRet = AnimationBaseNode2.superclass.parseElement.call( this );
+
+ var aAnimElem = this.aElement;
+
+ // attributeName attribute
+ this.sAttributeName = aAnimElem.getAttributeNS( NSS['smil'], 'attributeName' );
+ if( !this.sAttributeName )
+ {
+ this.eCurrentState = INVALID_NODE;
+ log( 'AnimationBaseNode2.parseElement: target attribute name not found: ' + this.sAttributeName );
+ }
+
+ // to attribute
+ this.aToValue = aAnimElem.getAttributeNS( NSS['smil'], 'to' );
+
+ return bRet;
+};
+
+AnimationBaseNode2.prototype.getAttributeName = function()
+{
+ return this.sAttributeName;
+};
+
+AnimationBaseNode2.prototype.getToValue = function()
+{
+ return this.aToValue;
+};
+
+AnimationBaseNode2.prototype.info = function( bVerbose )
+{
+ var sInfo = AnimationBaseNode2.superclass.info.call( this, bVerbose );
+
+ if( bVerbose )
+ {
+ // attribute name
+ if( this.getAttributeName() )
+ sInfo += '; attributeName: ' + this.getAttributeName();
+
+ // To
+ if( this.getToValue() )
+ sInfo += '; to: ' + this.getToValue();
+ }
+
+ return sInfo;
+};
+
+
+
+
+function AnimationBaseNode3( aAnimElem, aParentNode, aNodeContext )
+{
+ AnimationBaseNode3.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
+
+ this.eAccumulate = undefined;
+ this.eCalcMode = undefined;
+ this.aFromValue = null;
+ this.aByValue = null;
+ this.aKeyTimes = null;
+ this.aValues = null;
+ this.aFormula= null;
+}
+extend( AnimationBaseNode3, AnimationBaseNode2 );
+
+
+AnimationBaseNode3.prototype.parseElement = function()
+{
+ var bRet = AnimationBaseNode3.superclass.parseElement.call( this );
+
+ var aAnimElem = this.aElement;
+
+ // accumulate attribute
+ this.eAccumulate = ACCUMULATE_MODE_NONE;
+ var sAccumulateAttr = aAnimElem.getAttributeNS( NSS['smil'], 'accumulate' );
+ if( sAccumulateAttr == 'sum' )
+ this.eAccumulate = ACCUMULATE_MODE_SUM;
+
+ // calcMode attribute
+ this.eCalcMode = CALC_MODE_LINEAR;
+ var sCalcModeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'calcMode' );
+ if( sCalcModeAttr && aCalcModeInMap[ sCalcModeAttr ] )
+ this.eCalcMode = aCalcModeInMap[ sCalcModeAttr ];
+
+ // from attribute
+ this.aFromValue = aAnimElem.getAttributeNS( NSS['smil'], 'from' );
+
+ // by attribute
+ this.aByValue = aAnimElem.getAttributeNS( NSS['smil'], 'by' );
+
+ // keyTimes attribute
+ this.aKeyTimes = [];
+ var sKeyTimesAttr = aAnimElem.getAttributeNS( NSS['smil'], 'keyTimes' );
+ sKeyTimesAttr = removeWhiteSpaces( sKeyTimesAttr );
+ if( sKeyTimesAttr )
+ {
+ var aKeyTimes = sKeyTimesAttr.split( ';' );
+ for( var i = 0; i < aKeyTimes.length; ++i )
+ this.aKeyTimes.push( parseFloat( aKeyTimes[i] ) );
+ }
+
+ // values attribute
+ var sValuesAttr = aAnimElem.getAttributeNS( NSS['smil'], 'values' );
+ if( sValuesAttr )
+ {
+ this.aValues = sValuesAttr.split( ';' );
+ }
+ else
+ {
+ this.aValues = [];
+ }
+
+ // formula attribute
+ this.aFormula = aAnimElem.getAttributeNS( NSS['anim'], 'formula' );
+
+ return bRet;
+};
+
+AnimationBaseNode3.prototype.getAccumulate = function()
+{
+ return this.eAccumulate;
+};
+
+AnimationBaseNode3.prototype.getCalcMode = function()
+{
+ return this.eCalcMode;
+};
+
+AnimationBaseNode3.prototype.getFromValue = function()
+{
+ return this.aFromValue;
+};
+
+AnimationBaseNode3.prototype.getByValue = function()
+{
+ return this.aByValue;
+};
+
+AnimationBaseNode3.prototype.getKeyTimes = function()
+{
+ return this.aKeyTimes;
+};
+
+AnimationBaseNode3.prototype.getValues = function()
+{
+ return this.aValues;
+};
+
+AnimationBaseNode3.prototype.getFormula = function()
+{
+ return this.aFormula;
+};
+
+AnimationBaseNode3.prototype.info = function( bVerbose )
+{
+ var sInfo = AnimationBaseNode3.superclass.info.call( this, bVerbose );
+
+ if( bVerbose )
+ {
+ // accumulate mode
+ if( this.getAccumulate() )
+ sInfo += '; accumulate: ' + aAccumulateModeOutMap[ this.getAccumulate() ];
+
+ // calcMode
+ sInfo += '; calcMode: ' + aCalcModeOutMap[ this.getCalcMode() ];
+
+ // from
+ if( this.getFromValue() )
+ sInfo += '; from: ' + this.getFromValue();
+
+ // by
+ if( this.getByValue() )
+ sInfo += '; by: ' + this.getByValue();
+
+ // keyTimes
+ if( this.getKeyTimes().length )
+ sInfo += '; keyTimes: ' + this.getKeyTimes().join( ',' );
+
+ // values
+ if( this.getValues().length )
+ sInfo += '; values: ' + this.getValues().join( ',' );
+
+ // formula
+ if( this.getFormula() )
+ sInfo += '; formula: ' + this.getFormula();
+ }
+
+ return sInfo;
+};
+
+
+
+
+function BaseContainerNode( aAnimElem, aParentNode, aNodeContext )
+{
+ BaseContainerNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
+
+ this.sClassName = 'BaseContainerNode';
+ this.bIsContainer = true;
+ this.aChildrenArray = [];
+ this.nFinishedChildren = 0;
+ this.bDurationIndefinite = false;
+ this.nLeftIterations = 1;
+
+ this.eImpressNodeType = undefined;
+ this.ePresetClass = undefined;
+ this.ePresetId = undefined;
+}
+extend( BaseContainerNode, BaseNode );
+
+
+BaseContainerNode.prototype.parseElement= function()
+{
+ var bRet = BaseContainerNode.superclass.parseElement.call( this );
+
+ var aAnimElem = this.aElement;
+
+ // node-type attribute
+ this.eImpressNodeType = IMPRESS_DEFAULT_NODE;
+ var sNodeTypeAttr = aAnimElem.getAttributeNS( NSS['presentation'], 'node-type' );
+ if( sNodeTypeAttr && aImpressNodeTypeInMap[ sNodeTypeAttr ] )
+ this.eImpressNodeType = aImpressNodeTypeInMap[ sNodeTypeAttr ];
+ this.bMainSequenceRootNode = ( this.eImpressNodeType == IMPRESS_MAIN_SEQUENCE_NODE );
+ this.bInteractiveSequenceRootNode = ( this.eImpressNodeType == IMPRESS_INTERACTIVE_SEQUENCE_NODE );
+
+ // preset-class attribute
+ this.ePresetClass = undefined;
+ var sPresetClassAttr = aAnimElem.getAttributeNS( NSS['presentation'], 'preset-class' );
+ if( sPresetClassAttr && aPresetClassInMap[ sPresetClassAttr ] )
+ this.ePresetClass = aPresetClassInMap[ sPresetClassAttr ];
+
+ // preset-id attribute
+ this.ePresetId = undefined;
+ var sPresetIdAttr = aAnimElem.getAttributeNS( NSS['presentation'], 'preset-id' );
+ if( sPresetIdAttr && aPresetIdInMap[ sPresetIdAttr ] )
+ this.ePresetId = aPresetIdInMap[ sPresetIdAttr ];
+
+
+ // parse children elements
+ var nChildrenCount = this.aChildrenArray.length;
+ for( var i = 0; i < nChildrenCount; ++i )
+ {
+ this.aChildrenArray[i].parseElement();
+ }
+
+
+ // resolve duration
+ this.bDurationIndefinite
+ = ( !this.getDuration() || this.getDuration().isIndefinite() ) &&
+ ( !this.getEnd() || ( this.getEnd().getType() != OFFSET_TIMING ) );
+
+ return bRet;
+};
+
+BaseContainerNode.prototype.appendChildNode = function( aAnimationNode )
+{
+ if( ! this.checkValidNode() )
+ return ;
+
+ if( aAnimationNode.registerDeactivatingListener( this ) )
+ this.aChildrenArray.push( aAnimationNode );
+};
+
+BaseContainerNode.prototype.removeAllChildrenNodes = function()
+{
+ this.aChildrenArray = [];
+};
+
+BaseContainerNode.prototype.init_st = function()
+{
+ this.nLeftIterations = this.getRepeatCount();
+
+ return this.init_children();
+};
+
+BaseContainerNode.prototype.init_children = function()
+{
+ this.nFinishedChildren = 0;
+ var nChildrenCount = this.aChildrenArray.length;
+ var nInitChildren = 0;
+ for( var i = 0; i < nChildrenCount; ++i )
+ {
+ if( this.aChildrenArray[i].init() )
+ {
+ ++nInitChildren;
+ }
+ }
+ return ( nChildrenCount == nInitChildren );
+};
+
+
+BaseContainerNode.prototype.deactivate_st = function( eDestState )
+{
+ this.nLeftIterations = 0;
+ if( eDestState == FROZEN_NODE )
+ {
+ // deactivate all children that are not FROZEN or ENDED:
+ this.forEachChildNode( mem_fn( 'deactivate' ), ~( FROZEN_NODE | ENDED_NODE ) );
+ }
+ else
+ {
+ // end all children that are not ENDED:
+ this.forEachChildNode( mem_fn( 'end' ), ~ENDED_NODE );
+ if( this.getFillMode() == FILL_MODE_REMOVE )
+ this.removeEffect();
+ }
+};
+
+BaseContainerNode.prototype.hasPendingAnimation = function()
+{
+ var nChildrenCount = this.aChildrenArray.length;
+ for( var i = 0; i < nChildrenCount; ++i )
+ {
+ if( this.aChildrenArray[i].hasPendingAnimation() )
+ return true;
+ }
+ return false;
+};
+
+BaseContainerNode.prototype.activate_st = function()
+{
+ log( 'BaseContainerNode.activate_st: abstract method called' );
+};
+
+BaseContainerNode.prototype.notifyDeactivating = function( /*aAnimationNode*/ )
+{
+ log( 'BaseContainerNode.notifyDeactivating: abstract method called' );
+};
+
+BaseContainerNode.prototype.isDurationIndefinite = function()
+{
+ return this.bDurationIndefinite;
+};
+
+BaseContainerNode.prototype.isChildNode = function( aAnimationNode )
+{
+ var nChildrenCount = this.aChildrenArray.length;
+ for( var i = 0; i < nChildrenCount; ++i )
+ {
+ if( this.aChildrenArray[i].getId() == aAnimationNode.getId() )
+ return true;
+ }
+ return false;
+};
+
+BaseContainerNode.prototype.notifyDeactivatedChild = function( aChildNode )
+{
+ assert( ( aChildNode.getState() == FROZEN_NODE ) || ( aChildNode.getState() == ENDED_NODE ),
+ 'BaseContainerNode.notifyDeactivatedChild: passed child node is neither in FROZEN nor in ENDED state' );
+
+ assert( this.getState() != INVALID_NODE,
+ 'BaseContainerNode.notifyDeactivatedChild: this node is invalid' );
+
+ if( !this.isChildNode( aChildNode ) )
+ {
+ log( 'BaseContainerNode.notifyDeactivatedChild: unknown child notifier!' );
+ return false;
+ }
+
+ var nChildrenCount = this.aChildrenArray.length;
+
+ assert( ( this.nFinishedChildren < nChildrenCount ),
+ 'BaseContainerNode.notifyDeactivatedChild: assert(this.nFinishedChildren < nChildrenCount) failed' );
+
+ ++this.nFinishedChildren;
+ var bFinished = ( this.nFinishedChildren >= nChildrenCount );
+
+ if( bFinished && this.isDurationIndefinite() )
+ {
+ if( this.nLeftIterations >= 1.0 )
+ {
+ this.nLeftIterations -= 1.0;
+ }
+ if( this.nLeftIterations >= 1.0 )
+ {
+ bFinished = false;
+ var aRepetitionEvent = makeDelay( bind( this, this.repeat ), 0.0 );
+ this.aContext.aTimerEventQueue.addEvent( aRepetitionEvent );
+ }
+ else
+ {
+ this.deactivate();
+ }
+ }
+
+ return bFinished;
+};
+
+BaseContainerNode.prototype.repeat = function()
+{
+ // end all children that are not ENDED:
+ this.forEachChildNode( mem_fn( 'end' ), ~ENDED_NODE );
+ this.removeEffect();
+ var bInitialized = this.init_children();
+ if( bInitialized )
+ this.activate_st();
+ return bInitialized;
+};
+
+BaseContainerNode.prototype.removeEffect = function()
+{
+ var nChildrenCount = this.aChildrenArray.length;
+ if( nChildrenCount == 0 )
+ return;
+ // We remove effect in reverse order.
+ for( var i = nChildrenCount - 1; i >= 0; --i )
+ {
+ if( ( this.aChildrenArray[i].getState() & ( FROZEN_NODE | ENDED_NODE ) ) == 0 )
+ {
+ log( 'BaseContainerNode.removeEffect: child(id:'
+ + this.aChildrenArray[i].getId() + ') is neither frozen nor ended;'
+ + ' state: '
+ + aTransitionModeOutMap[ this.aChildrenArray[i].getState() ] );
+ continue;
+ }
+ this.aChildrenArray[i].removeEffect();
+ }
+};
+
+BaseContainerNode.prototype.saveStateOfAnimatedElement = function()
+{
+ var nChildrenCount = this.aChildrenArray.length;
+ for( var i = 0; i < nChildrenCount; ++i )
+ {
+ this.aChildrenArray[i].saveStateOfAnimatedElement();
+ }
+};
+
+BaseContainerNode.prototype.forEachChildNode = function( aFunction, eNodeStateMask )
+{
+ if( !eNodeStateMask )
+ eNodeStateMask = -1;
+
+ var nChildrenCount = this.aChildrenArray.length;
+ for( var i = 0; i < nChildrenCount; ++i )
+ {
+ if( ( eNodeStateMask != -1 ) && ( ( this.aChildrenArray[i].getState() & eNodeStateMask ) == 0 ) )
+ continue;
+ aFunction( this.aChildrenArray[i] );
+ }
+};
+
+BaseContainerNode.prototype.dispose = function()
+{
+ var nChildrenCount = this.aChildrenArray.length;
+ for( var i = 0; i < nChildrenCount; ++i )
+ {
+ this.aChildrenArray[i].dispose();
+ }
+
+ BaseContainerNode.superclass.dispose.call( this );
+};
+
+BaseContainerNode.prototype.getImpressNodeType = function()
+{
+ return this.eImpressNodeType;
+};
+
+BaseContainerNode.prototype.info = function( bVerbose )
+{
+ var sInfo = BaseContainerNode.superclass.info.call( this, bVerbose );
+
+ if( bVerbose )
+ {
+ // impress node type
+ if( this.getImpressNodeType() )
+ sInfo += '; node-type: ' + aImpressNodeTypeOutMap[ this.getImpressNodeType() ];
+ }
+
+ var nChildrenCount = this.aChildrenArray.length;
+ for( var i = 0; i < nChildrenCount; ++i )
+ {
+ sInfo += '\n';
+ sInfo += this.aChildrenArray[i].info( bVerbose );
+ }
+
+ return sInfo;
+};
+
+
+function ParallelTimeContainer( aAnimElem, aParentNode, aNodeContext )
+{
+ ParallelTimeContainer.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
+
+ this.sClassName = 'ParallelTimeContainer';
+}
+extend( ParallelTimeContainer, BaseContainerNode );
+
+
+ParallelTimeContainer.prototype.activate_st = function()
+{
+ var nChildrenCount = this.aChildrenArray.length;
+ var nResolvedChildren = 0;
+ for( var i = 0; i < nChildrenCount; ++i )
+ {
+ if( this.aChildrenArray[i].resolve() )
+ {
+ ++nResolvedChildren;
+ }
+ }
+
+ if( nChildrenCount != nResolvedChildren )
+ {
+ log( 'ParallelTimeContainer.activate_st: resolving all children failed' );
+ return;
+ }
+
+
+ if( this.isDurationIndefinite() && ( nChildrenCount == 0 ) )
+ {
+ this.scheduleDeactivationEvent( this.makeDeactivationEvent( 0.0 ) );
+ }
+ else
+ {
+ this.scheduleDeactivationEvent();
+ }
+};
+
+ParallelTimeContainer.prototype.notifyDeactivating = function( aAnimationNode )
+{
+ this.notifyDeactivatedChild( aAnimationNode );
+};
+
+
+
+
+function SequentialTimeContainer( aAnimElem, aParentNode, aNodeContext )
+{
+ SequentialTimeContainer.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
+
+ this.sClassName = 'SequentialTimeContainer';
+ this.bIsRewinding = false;
+ this.aCurrentSkipEvent = null;
+ this.aRewindCurrentEffectEvent = null;
+ this.aRewindLastEffectEvent = null;
+}
+extend( SequentialTimeContainer, BaseContainerNode );
+
+
+SequentialTimeContainer.prototype.activate_st = function()
+{
+ var nChildrenCount = this.aChildrenArray.length;
+ for( ; this.nFinishedChildren < nChildrenCount; ++this.nFinishedChildren )
+ {
+ if( this.resolveChild( this.aChildrenArray[ this.nFinishedChildren ] ) )
+ break;
+ else
+ log( 'SequentialTimeContainer.activate_st: resolving child failed!' );
+ }
+
+ if( this.isDurationIndefinite() && ( ( nChildrenCount == 0 ) || ( this.nFinishedChildren >= nChildrenCount ) ) )
+ {
+ // deactivate ASAP:
+ this.scheduleDeactivationEvent( this.makeDeactivationEvent( 0.0 ) );
+ }
+ else
+ {
+ this.scheduleDeactivationEvent();
+ }
+};
+
+SequentialTimeContainer.prototype.notifyDeactivating = function( aNotifier )
+{
+ // If we are rewinding we have not to resolve the next child.
+ if( this.bIsRewinding )
+ return;
+
+ if( this.notifyDeactivatedChild( aNotifier ) )
+ return;
+
+ assert( this.nFinishedChildren < this.aChildrenArray.length,
+ 'SequentialTimeContainer.notifyDeactivating: assertion (this.nFinishedChildren < this.aChildrenArray.length) failed' );
+
+ var aNextChild = this.aChildrenArray[ this.nFinishedChildren ];
+
+ assert( aNextChild.getState() == UNRESOLVED_NODE,
+ 'SequentialTimeContainer.notifyDeactivating: assertion (aNextChild.getState == UNRESOLVED_NODE) failed' );
+
+ if( !this.resolveChild( aNextChild ) )
+ {
+ // could not resolve child - since we risk to
+ // stall the chain of events here, play it safe
+ // and deactivate this node (only if we have
+ // indefinite duration - otherwise, we'll get a
+ // deactivation event, anyways).
+ this.deactivate();
+ }
+};
+
+/** skipEffect
+ * Skip the current playing shape effect.
+ * Requires: the current node is the main sequence root node.
+ *
+ * @param aChildNode
+ * An animation node representing the root node of the shape effect being
+ * played.
+ */
+SequentialTimeContainer.prototype.skipEffect = function( aChildNode )
+{
+ if( this.isChildNode( aChildNode ) )
+ {
+ // First off we end all queued activities.
+ this.getContext().aActivityQueue.endAll();
+ // We signal that we are going to skip all subsequent animations by
+ // setting the bIsSkipping flag to 'true', then all queued events are
+ // fired immediately. In such a way the correct order of the various
+ // events that belong to the animation time-line is preserved.
+ this.getContext().bIsSkipping = true;
+ this.getContext().aTimerEventQueue.forceEmpty();
+ this.getContext().bIsSkipping = false;
+ var aEvent = makeEvent( bind2( aChildNode.deactivate, aChildNode ) );
+ this.getContext().aTimerEventQueue.addEvent( aEvent );
+ }
+ else
+ {
+ log( 'SequentialTimeContainer.skipEffect: unknown child: '
+ + aChildNode.getId() );
+ }
+};
+
+/** rewindCurrentEffect
+ * Rewind a playing shape effect.
+ * Requires: the current node is the main sequence root node.
+ *
+ * @param aChildNode
+ * An animation node representing the root node of the shape effect being
+ * played
+ */
+SequentialTimeContainer.prototype.rewindCurrentEffect = function( aChildNode )
+{
+ if( this.isChildNode( aChildNode ) )
+ {
+ assert( !this.bIsRewinding,
+ 'SequentialTimeContainer.rewindCurrentEffect: is already rewinding.' );
+
+ // We signal we are rewinding so the notifyDeactivating method returns
+ // immediately without increment the finished children counter and
+ // resolve the next child.
+ this.bIsRewinding = true;
+ // First off we end all queued activities.
+ this.getContext().aActivityQueue.endAll();
+ // We signal that we are going to skip all subsequent animations by
+ // setting the bIsSkipping flag to 'true', then all queued events are
+ // fired immediately. In such a way the correct order of the various
+ // events that belong to the animation time-line is preserved.
+ this.getContext().bIsSkipping = true;
+ this.getContext().aTimerEventQueue.forceEmpty();
+ this.getContext().bIsSkipping = false;
+ // We end all new activities appended to the activity queue by
+ // the fired events.
+ this.getContext().aActivityQueue.endAll();
+
+ // Now we perform a final 'end' and restore the animated shape to
+ // the state it was before the current effect was applied.
+ aChildNode.end();
+ aChildNode.removeEffect();
+ // Finally we place the child node to the 'unresolved' state and
+ // resolve it again.
+ aChildNode.init();
+ this.resolveChild( aChildNode );
+ this.notifyRewindedEvent( aChildNode );
+ this.bIsRewinding = false;
+ }
+ else
+ {
+ log( 'SequentialTimeContainer.rewindCurrentEffect: unknown child: '
+ + aChildNode.getId() );
+ }
+};
+
+/** rewindLastEffect
+ * Rewind the last ended effect.
+ * Requires: the current node is the main sequence root node.
+ *
+ * @param aChildNode
+ * An animation node representing the root node of the next shape effect
+ * to be played.
+ */
+SequentialTimeContainer.prototype.rewindLastEffect = function( aChildNode )
+{
+ if( this.isChildNode( aChildNode ) )
+ {
+ assert( !this.bIsRewinding,
+ 'SequentialTimeContainer.rewindLastEffect: is already rewinding.' );
+
+ // We signal we are rewinding so the notifyDeactivating method returns
+ // immediately without increment the finished children counter and
+ // resolve the next child.
+ this.bIsRewinding = true;
+ // We end the current effect.
+ this.getContext().aTimerEventQueue.forceEmpty();
+ this.getContext().aActivityQueue.clear();
+ aChildNode.end();
+ // Invoking the end method on the current child node that has not yet
+ // been activated should not lead to any change on the animated shape.
+ // However for safety we used to call the removeEffect method but
+ // lately we noticed that when interactive animation sequences are
+ // involved into the shape effect invoking such a method causes
+ // some issue.
+ //aChildNode.removeEffect();
+
+ // As we rewind the previous effect we need to decrease the finished
+ // children counter.
+ --this.nFinishedChildren;
+ var aPreviousChildNode = this.aChildrenArray[ this.nFinishedChildren ];
+ // No need to invoke the end method for the previous child as it is
+ // already in the ENDED state.
+
+ aPreviousChildNode.removeEffect();
+ // We place the child node to the 'unresolved' state.
+ aPreviousChildNode.init();
+ // We need to re-initialize the old current child too, because it is
+ // in ENDED state now, On the contrary it cannot be resolved again later.
+ aChildNode.init();
+ this.resolveChild( aPreviousChildNode );
+ this.notifyRewindedEvent( aChildNode );
+ this.bIsRewinding = false;
+ }
+ else
+ {
+ log( 'SequentialTimeContainer.rewindLastEffect: unknown child: '
+ + aChildNode.getId() );
+ }
+};
+
+/** resolveChild
+ * Resolve the passed child.
+ * In case this node is a main sequence root node events for skipping and
+ * rewinding the effect related to the passed child node are created and
+ * registered.
+ *
+ * @param aChildNode
+ * An animation node representing the root node of the next shape effect
+ * to be played.
+ * @return
+ * It returns true if the passed child has been resolved successfully,
+ * false otherwise.
+ */
+SequentialTimeContainer.prototype.resolveChild = function( aChildNode )
+{
+ var bResolved = aChildNode.resolve();
+
+ if( bResolved && ( this.isMainSequenceRootNode() || this.isInteractiveSequenceRootNode() ) )
+ {
+ if( this.aCurrentSkipEvent )
+ this.aCurrentSkipEvent.dispose();
+ this.aCurrentSkipEvent = makeEvent( bind2( SequentialTimeContainer.prototype.skipEffect, this, aChildNode ) );
+
+ if( this.aRewindCurrentEffectEvent )
+ this.aRewindCurrentEffectEvent.dispose();
+ this.aRewindCurrentEffectEvent = makeEvent( bind2( SequentialTimeContainer.prototype.rewindCurrentEffect, this, aChildNode ) );
+
+ if( this.aRewindLastEffectEvent )
+ this.aRewindLastEffectEvent.dispose();
+ this.aRewindLastEffectEvent = makeEvent( bind2( SequentialTimeContainer.prototype.rewindLastEffect, this, aChildNode ) );
+
+ if( this.isMainSequenceRootNode() )
+ {
+ this.aContext.aEventMultiplexer.registerSkipEffectEvent( this.aCurrentSkipEvent );
+ this.aContext.aEventMultiplexer.registerRewindCurrentEffectEvent( this.aRewindCurrentEffectEvent );
+ this.aContext.aEventMultiplexer.registerRewindLastEffectEvent( this.aRewindLastEffectEvent );
+ }
+ else if( this.isInteractiveSequenceRootNode() )
+ {
+ this.aContext.aEventMultiplexer.registerSkipInteractiveEffectEvent( aChildNode.getId(), this.aCurrentSkipEvent );
+ this.aContext.aEventMultiplexer.registerRewindRunningInteractiveEffectEvent( aChildNode.getId(), this.aRewindCurrentEffectEvent );
+ this.aContext.aEventMultiplexer.registerRewindEndedInteractiveEffectEvent( aChildNode.getId(), this.aRewindLastEffectEvent );
+ }
+ }
+ return bResolved;
+};
+
+SequentialTimeContainer.prototype.notifyRewindedEvent = function( aChildNode )
+{
+ if( this.isInteractiveSequenceRootNode() )
+ {
+ this.aContext.aEventMultiplexer.notifyRewindedEffectEvent( aChildNode.getId() );
+
+ var sId = aChildNode.getBegin().getEventBaseElementId();
+ if( sId )
+ {
+ this.aContext.aEventMultiplexer.notifyRewindedEffectEvent( sId );
+ }
+ }
+};
+
+SequentialTimeContainer.prototype.dispose = function()
+{
+ if( this.aCurrentSkipEvent )
+ this.aCurrentSkipEvent.dispose();
+
+ SequentialTimeContainer.superclass.dispose.call( this );
+};
+
+
+
+
+function PropertyAnimationNode( aAnimElem, aParentNode, aNodeContext )
+{
+ PropertyAnimationNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
+
+ this.sClassName = 'PropertyAnimationNode';
+}
+extend( PropertyAnimationNode, AnimationBaseNode3 );
+
+
+PropertyAnimationNode.prototype.createActivity = function()
+{
+ var aActivityParamSet = this.fillActivityParams();
+
+ var aAnimation = createPropertyAnimation( this.getAttributeName(),
+ this.getAnimatedElement(),
+ this.aNodeContext.aSlideWidth,
+ this.aNodeContext.aSlideHeight );
+
+ var aInterpolator = null; // createActivity will compute it;
+ return createActivity( aActivityParamSet, this, aAnimation, aInterpolator );
+};
+
+
+
+
+function isValidTransformation( sType )
+{
+ return ( sType === 'translate' || sType === 'scale' || sType === 'rotate'
+ || sType === 'skewX' || sType === 'skewY' );
+}
+
+function AnimationTransformNode( aAnimElem, aParentNode, aNodeContext )
+{
+ AnimationTransformNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
+
+ this.sClassName = 'AnimationTransformNode';
+}
+extend( AnimationTransformNode, AnimationBaseNode3 );
+
+
+AnimationTransformNode.prototype.parseElement = function()
+{
+ var bRet = AnimationTransformNode.superclass.parseElement.call(this);
+
+ var aAnimElem = this.aElement;
+
+ // transformation type
+ var sTransformType = aAnimElem.getAttribute( 'svg:type' );
+ if( !isValidTransformation( sTransformType ) )
+ {
+ this.eCurrentState = INVALID_NODE;
+ log( 'AnimationTransformNode.parseElement: transformation type not found: ' + sTransformType );
+ }
+ else
+ {
+ this.sAttributeName = sTransformType;
+ }
+
+ return bRet;
+}
+
+AnimationTransformNode.prototype.createActivity = function()
+{
+ var aActivityParamSet = this.fillActivityParams();
+ var aAnimation;
+
+ if( this.getAttributeName() === 'scale' || this.getAttributeName() === 'translate' )
+ {
+ aAnimation = createPairPropertyAnimation( this.getAttributeName(),
+ this.getAnimatedElement(),
+ this.aNodeContext.aSlideWidth,
+ this.aNodeContext.aSlideHeight );
+
+ }
+ else
+ {
+ aAnimation = createPropertyAnimation( this.getAttributeName(),
+ this.getAnimatedElement(),
+ this.aNodeContext.aSlideWidth,
+ this.aNodeContext.aSlideHeight );
+ }
+
+ var aInterpolator = null; // createActivity will compute it;
+ return createActivity( aActivityParamSet, this, aAnimation, aInterpolator );
+};
+
+
+
+
+function AnimationSetNode( aAnimElem, aParentNode, aNodeContext )
+{
+ AnimationSetNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
+
+ this.sClassName = 'AnimationSetNode';
+}
+extend( AnimationSetNode, AnimationBaseNode2 );
+
+
+AnimationSetNode.prototype.createActivity = function()
+{
+ var aAnimation = createPropertyAnimation( this.getAttributeName(),
+ this.getAnimatedElement(),
+ this.aNodeContext.aSlideWidth,
+ this.aNodeContext.aSlideHeight );
+
+ var aActivityParamSet = this.fillActivityParams();
+
+ return new SetActivity( aActivityParamSet, aAnimation, this.getToValue() );
+};
+
+
+
+
+function AnimationColorNode( aAnimElem, aParentNode, aNodeContext )
+{
+ AnimationColorNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
+
+ this.sClassName = 'AnimationColorNode';
+
+ this.eColorInterpolation = undefined;
+ this.eColorInterpolationDirection = undefined;
+}
+extend( AnimationColorNode, AnimationBaseNode3 );
+
+
+AnimationColorNode.prototype.parseElement = function()
+{
+ var bRet = AnimationColorNode.superclass.parseElement.call( this );
+
+ var aAnimElem = this.aElement;
+
+ // color-interpolation attribute
+ this.eColorInterpolation = COLOR_SPACE_RGB;
+ var sColorInterpolationAttr = aAnimElem.getAttributeNS( NSS['anim'], 'color-interpolation' );
+ if( sColorInterpolationAttr && aColorSpaceInMap[ sColorInterpolationAttr ] )
+ this.eColorInterpolation = aColorSpaceInMap[ sColorInterpolationAttr ];
+
+ // color-interpolation-direction attribute
+ this.eColorInterpolationDirection = CLOCKWISE;
+ var sColorInterpolationDirectionAttr = aAnimElem.getAttributeNS( NSS['anim'], 'color-interpolation-direction' );
+ if( sColorInterpolationDirectionAttr && aClockDirectionInMap[ sColorInterpolationDirectionAttr ] )
+ this.eColorInterpolationDirection = aClockDirectionInMap[ sColorInterpolationDirectionAttr ];
+
+ return bRet;
+};
+
+AnimationColorNode.prototype.createActivity = function()
+{
+ var aActivityParamSet = this.fillActivityParams();
+
+ var aAnimation = createPropertyAnimation( this.getAttributeName(),
+ this.getAnimatedElement(),
+ this.aNodeContext.aSlideWidth,
+ this.aNodeContext.aSlideHeight );
+
+ var aColorAnimation;
+ var aInterpolator;
+ if( this.getColorInterpolation() === COLOR_SPACE_HSL )
+ {
+ ANIMDBG.print( 'AnimationColorNode.createActivity: color space hsl' );
+ aColorAnimation = new HSLAnimationWrapper( aAnimation );
+ var aInterpolatorMaker = aInterpolatorHandler.getInterpolator( this.getCalcMode(),
+ COLOR_PROPERTY,
+ COLOR_SPACE_HSL );
+ aInterpolator = aInterpolatorMaker( this.getColorInterpolationDirection() );
+ }
+ else
+ {
+ ANIMDBG.print( 'AnimationColorNode.createActivity: color space rgb' );
+ aColorAnimation = aAnimation;
+ aInterpolator = aInterpolatorHandler.getInterpolator( this.getCalcMode(),
+ COLOR_PROPERTY,
+ COLOR_SPACE_RGB );
+ }
+
+ return createActivity( aActivityParamSet, this, aColorAnimation, aInterpolator );
+};
+
+AnimationColorNode.prototype.getColorInterpolation = function()
+{
+ return this.eColorInterpolation;
+};
+
+AnimationColorNode.prototype.getColorInterpolationDirection = function()
+{
+ return this.eColorInterpolationDirection;
+};
+
+AnimationColorNode.prototype.info = function( bVerbose )
+{
+ var sInfo = AnimationColorNode.superclass.info.call( this, bVerbose );
+
+ if( bVerbose )
+ {
+ // color interpolation
+ sInfo += '; color-interpolation: ' + aColorSpaceOutMap[ this.getColorInterpolation() ];
+
+ // color interpolation direction
+ sInfo += '; color-interpolation-direction: ' + aClockDirectionOutMap[ this.getColorInterpolationDirection() ];
+ }
+ return sInfo;
+};
+
+
+
+
+function AnimationTransitionFilterNode( aAnimElem, aParentNode, aNodeContext )
+{
+ AnimationTransitionFilterNode.superclass.constructor.call( this, aAnimElem, aParentNode, aNodeContext );
+
+ this.sClassName = 'AnimationTransitionFilterNode';
+
+ this.eTransitionType = undefined;
+ this.eTransitionSubType = undefined;
+ this.bReverseDirection = undefined;
+ this.eTransitionMode = undefined;
+}
+extend( AnimationTransitionFilterNode, AnimationBaseNode );
+
+
+AnimationTransitionFilterNode.prototype.createActivity = function()
+{
+ var aActivityParamSet = this.fillActivityParams();
+
+ return createShapeTransition( aActivityParamSet,
+ this.getAnimatedElement(),
+ this.aNodeContext.aSlideWidth,
+ this.aNodeContext.aSlideHeight,
+ this );
+};
+
+AnimationTransitionFilterNode.prototype.parseElement = function()
+{
+ var bRet = AnimationTransitionFilterNode.superclass.parseElement.call( this );
+ var bIsValidTransition = true;
+
+ var aAnimElem = this.aElement;
+
+ // type attribute
+ this.eTransitionType = undefined;
+ var sTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'type' );
+ if( sTypeAttr && aTransitionTypeInMap[ sTypeAttr ] )
+ {
+ this.eTransitionType = aTransitionTypeInMap[ sTypeAttr ];
+ }
+ else
+ {
+ bIsValidTransition = false;
+ log( 'AnimationTransitionFilterNode.parseElement: transition type not valid: ' + sTypeAttr );
+ }
+
+ // subtype attribute
+ this.eTransitionSubType = undefined;
+ var sSubTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'subtype' );
+ if( sSubTypeAttr === null )
+ sSubTypeAttr = 'default';
+ if( sSubTypeAttr && ( aTransitionSubtypeInMap[ sSubTypeAttr ] !== undefined ) )
+ {
+ this.eTransitionSubType = aTransitionSubtypeInMap[ sSubTypeAttr ];
+ }
+ else
+ {
+ bIsValidTransition = false;
+ log( 'AnimationTransitionFilterNode.parseElement: transition subtype not valid: ' + sSubTypeAttr );
+ }
+
+ // if we do not support the requested transition type we fall back to crossfade transition;
+ // note: if we do not provide an alternative transition and we set the state of the animation node to 'invalid'
+ // the animation engine stops itself;
+ if( !bIsValidTransition )
+ {
+ this.eTransitionType = FADE_TRANSITION;
+ this.eTransitionSubType = CROSSFADE_TRANS_SUBTYPE;
+ log( 'AnimationTransitionFilterNode.parseElement: in place of the invalid transition a crossfade transition is used' );
+ }
+
+ // direction attribute
+ this.bReverseDirection = false;
+ var sDirectionAttr = aAnimElem.getAttributeNS( NSS['smil'], 'direction' );
+ if( sDirectionAttr == 'reverse' )
+ this.bReverseDirection = true;
+
+ // mode attribute:
+ this.eTransitionMode = TRANSITION_MODE_IN;
+ var sModeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'mode' );
+ if( sModeAttr === 'out' )
+ this.eTransitionMode = TRANSITION_MODE_OUT;
+
+ return bRet;
+};
+
+AnimationTransitionFilterNode.prototype.getTransitionType = function()
+{
+ return this.eTransitionType;
+};
+
+AnimationTransitionFilterNode.prototype.getTransitionSubType = function()
+{
+ return this.eTransitionSubType;
+};
+
+AnimationTransitionFilterNode.prototype.getTransitionMode = function()
+{
+ return this.eTransitionMode;
+};
+
+AnimationTransitionFilterNode.prototype.getReverseDirection = function()
+{
+ return this.bReverseDirection;
+};
+
+AnimationTransitionFilterNode.prototype.info = function( bVerbose )
+{
+ var sInfo = AnimationTransitionFilterNode.superclass.info.call( this, bVerbose );
+
+ if( bVerbose )
+ {
+ // transition type
+ sInfo += '; type: ' + getKeyByValue(aTransitionTypeInMap, this.getTransitionType());
+
+ // transition subtype
+ sInfo += '; subtype: ' + getKeyByValue(aTransitionSubtypeInMap, this.getTransitionSubType());
+
+ // transition direction
+ if( this.getReverseDirection() )
+ sInfo += '; direction: reverse';
+ }
+
+ return sInfo;
+};
+
+
+
+/**********************************************************************************************
+ * Animation Node Factory
+ **********************************************************************************************/
+
+
+function createAnimationTree( aRootElement, aNodeContext )
+{
+ return createAnimationNode( aRootElement, null, aNodeContext );
+}
+
+
+
+
+function createAnimationNode( aElement, aParentNode, aNodeContext )
+{
+ assert( aElement, 'createAnimationNode: invalid animation element' );
+
+ var eAnimationNodeType = getAnimationElementType( aElement );
+
+ var aCreatedNode = null;
+ var aCreatedContainer = null;
+
+ switch( eAnimationNodeType )
+ {
+ case ANIMATION_NODE_PAR:
+ aCreatedNode = aCreatedContainer =
+ new ParallelTimeContainer( aElement, aParentNode, aNodeContext );
+ break;
+ case ANIMATION_NODE_ITERATE:
+ // map iterate container to ParallelTimeContainer.
+ // the iterating functionality is to be found
+ // below, (see method implCreateIteratedNodes)
+ aCreatedNode = aCreatedContainer =
+ new ParallelTimeContainer( aElement, aParentNode, aNodeContext );
+ break;
+ case ANIMATION_NODE_SEQ:
+ aCreatedNode = aCreatedContainer =
+ new SequentialTimeContainer( aElement, aParentNode, aNodeContext );
+ break;
+ case ANIMATION_NODE_ANIMATE:
+ aCreatedNode = new PropertyAnimationNode( aElement, aParentNode, aNodeContext );
+ break;
+ case ANIMATION_NODE_SET:
+ aCreatedNode = new AnimationSetNode( aElement, aParentNode, aNodeContext );
+ break;
+ case ANIMATION_NODE_ANIMATEMOTION:
+ //aCreatedNode = new AnimationPathMotionNode( aElement, aParentNode, aNodeContext );
+ //break;
+ log( 'createAnimationNode: ANIMATEMOTION not implemented' );
+ return null;
+ case ANIMATION_NODE_ANIMATECOLOR:
+ aCreatedNode = new AnimationColorNode( aElement, aParentNode, aNodeContext );
+ break;
+ case ANIMATION_NODE_ANIMATETRANSFORM:
+ aCreatedNode = new AnimationTransformNode( aElement, aParentNode, aNodeContext );
+ break;
+ case ANIMATION_NODE_TRANSITIONFILTER:
+ aCreatedNode = new AnimationTransitionFilterNode( aElement, aParentNode, aNodeContext );
+ break;
+ case ANIMATION_NODE_AUDIO:
+ log( 'createAnimationNode: AUDIO not implemented' );
+ return null;
+ case ANIMATION_NODE_COMMAND:
+ log( 'createAnimationNode: COMMAND not implemented' );
+ return null;
+ default:
+ log( 'createAnimationNode: invalid Animation Node Type: ' + eAnimationNodeType );
+ return null;
+ }
+
+ if( aCreatedContainer )
+ {
+ if( eAnimationNodeType == ANIMATION_NODE_ITERATE )
+ {
+ createIteratedNodes( aElement, aCreatedContainer, aNodeContext );
+ }
+ else
+ {
+ var aChildrenArray = getElementChildren( aElement );
+ for( var i = 0; i < aChildrenArray.length; ++i )
+ {
+ if( !createChildNode( aChildrenArray[i], aCreatedContainer, aNodeContext ) )
+ {
+ aCreatedContainer.removeAllChildrenNodes();
+ break;
+ }
+ }
+ }
+ }
+
+ return aCreatedNode;
+}
+
+
+
+
+function createChildNode( aElement, aParentNode, aNodeContext )
+{
+ var aChildNode = createAnimationNode( aElement, aParentNode, aNodeContext );
+
+ if( !aChildNode )
+ {
+ log( 'createChildNode: child node creation failed' );
+ return false;
+ }
+ else
+ {
+ aParentNode.appendChildNode( aChildNode );
+ return true;
+ }
+}
+
+
+
+
+function createIteratedNodes( /*aElement, aContainerNode, aNodeContext*/ )
+{
+ // not implemented
+}
+
+
+
+/**********************************************************************************************
+ * Animation Factory
+ **********************************************************************************************/
+
+// makeScaler is used in aAttributeMap:
+// eslint-disable-next-line no-unused-vars
+function makeScaler( nScale )
+{
+ if( ( typeof( nScale ) !== typeof( 0 ) ) || !isFinite( nScale ) )
+ {
+ log( 'makeScaler: not valid param passed: ' + nScale );
+ return null;
+ }
+
+ return function( nValue )
+ {
+ return ( nScale * nValue );
+ };
+}
+
+
+
+// eslint-disable-next-line no-unused-vars
+function createPropertyAnimation( sAttrName, aAnimatedElement, nWidth, nHeight )
+{
+ if( !aAttributeMap[ sAttrName ] )
+ {
+ log( 'createPropertyAnimation: attribute is unknown' );
+ return null;
+ }
+
+
+ var aFunctorSet = aAttributeMap[ sAttrName ];
+
+ var sGetValueMethod = aFunctorSet.get;
+ var sSetValueMethod = aFunctorSet.set;
+
+ if( !sGetValueMethod || !sSetValueMethod )
+ {
+ log( 'createPropertyAnimation: attribute is not handled' );
+ return null;
+ }
+
+ var aGetModifier = eval( aFunctorSet.getmod );
+ var aSetModifier = eval( aFunctorSet.setmod );
+
+
+ return new GenericAnimation( bind( aAnimatedElement, aAnimatedElement[ sGetValueMethod ] ),
+ bind( aAnimatedElement, aAnimatedElement[ sSetValueMethod ] ),
+ aGetModifier,
+ aSetModifier);
+}
+
+
+
+
+function createPairPropertyAnimation( sTransformType, aAnimatedElement, nWidth, nHeight )
+{
+ var aFunctorSet = aAttributeMap[ sTransformType ];
+ var sGetValueMethod = aFunctorSet.get;
+ var sSetValueMethod = aFunctorSet.set;
+
+ var aDefaultValue = [];
+ var aSizeReference = [];
+ if( sTransformType === 'scale' )
+ {
+ aDefaultValue[0] = aSizeReference[0] = aAnimatedElement.getBaseBBox().width;
+ aDefaultValue[1] = aSizeReference[1] = aAnimatedElement.getBaseBBox().height;
+ }
+ else if( sTransformType === 'translate' )
+ {
+ aDefaultValue[0] = aAnimatedElement.getBaseCenterX();
+ aDefaultValue[1] = aAnimatedElement.getBaseCenterY();
+ aSizeReference[0] = nWidth;
+ aSizeReference[1] = nHeight;
+ }
+ else
+ {
+ log( 'createPairPropertyAnimation: transform type is not handled' );
+ return null;
+ }
+
+ return new TupleAnimation( bind( aAnimatedElement, aAnimatedElement[ sGetValueMethod ] ),
+ bind( aAnimatedElement, aAnimatedElement[ sSetValueMethod ] ),
+ aDefaultValue,
+ aSizeReference );
+}
+
+
+
+
+/** createShapeTransition
+ *
+ * @param aActivityParamSet
+ * The set of property for the activity to be created.
+ * @param aAnimatedElement
+ * The element to be animated.
+ * @param nSlideWidth
+ * The width of a slide.
+ * @param nSlideHeight
+ * The height of a slide.
+ * @param aAnimatedTransitionFilterNode
+ * An instance of the AnimationFilterNode that invoked this function.
+ * @return {SimpleActivity}
+ * A simple activity handling a shape transition.
+ */
+function createShapeTransition( aActivityParamSet, aAnimatedElement,
+ nSlideWidth, nSlideHeight,
+ aAnimatedTransitionFilterNode )
+{
+ if( !aAnimatedTransitionFilterNode )
+ {
+ log( 'createShapeTransition: the animated transition filter node is not valid.' );
+ return null;
+ }
+ var eTransitionType = aAnimatedTransitionFilterNode.getTransitionType();
+ var eTransitionSubType = aAnimatedTransitionFilterNode.getTransitionSubType();
+ var bDirectionForward = ! aAnimatedTransitionFilterNode.getReverseDirection();
+ var bModeIn = ( aAnimatedTransitionFilterNode.getTransitionMode() == FORWARD );
+
+ var aTransitionInfo = aTransitionInfoTable[eTransitionType][eTransitionSubType];
+ var eTransitionClass = aTransitionInfo['class'];
+
+ switch( eTransitionClass )
+ {
+ default:
+ case TRANSITION_INVALID:
+ log( 'createShapeTransition: transition class: TRANSITION_INVALID' );
+ return null;
+
+ case TRANSITION_CLIP_POLYPOLYGON:
+ var aParametricPolyPolygon
+ = createClipPolyPolygon( eTransitionType, eTransitionSubType );
+ var aClippingAnimation
+ = new ClippingAnimation( aParametricPolyPolygon, aTransitionInfo,
+ bDirectionForward, bModeIn );
+ return new SimpleActivity( aActivityParamSet, aClippingAnimation, true );
+
+ case TRANSITION_SPECIAL:
+ switch( eTransitionType )
+ {
+ // no special transition filter provided
+ // we map everything to crossfade
+ default:
+ var aAnimation
+ = createPropertyAnimation( 'opacity',
+ aAnimatedElement,
+ nSlideWidth,
+ nSlideHeight );
+ return new SimpleActivity( aActivityParamSet, aAnimation, bModeIn );
+ }
+ }
+
+}
+
+
+
+
+/** Class ClippingAnimation
+ * This class performs a shape transition where the effect is achieved by
+ * clipping the shape to be animated with a parametric path.
+ *
+ * @param aParametricPolyPolygon
+ * An object handling a <path> element that depends on a parameter.
+ * @param aTransitionInfo
+ * The set of parameters defining the shape transition to be performed.
+ * @param bDirectionForward
+ * The direction the shape transition has to be performed.
+ * @param bModeIn
+ * If true the element to be animated becomes more visible as the transition
+ * progress else it becomes less visible.
+ */
+function ClippingAnimation( aParametricPolyPolygon, aTransitionInfo,
+ bDirectionForward, bModeIn )
+{
+ this.aClippingFunctor = new ClippingFunctor( aParametricPolyPolygon,
+ aTransitionInfo,
+ bDirectionForward, bModeIn );
+ this.bAnimationStarted = false;
+}
+
+/** start
+ * This method notifies to the element involved in the transition that
+ * the animation is starting and creates the <clipPath> element used for
+ * the transition.
+ *
+ * @param aAnimatableElement
+ * The element to be animated.
+ */
+ClippingAnimation.prototype.start = function( aAnimatableElement )
+{
+ assert( aAnimatableElement,
+ 'ClippingAnimation.start: animatable element is not valid' );
+ this.aAnimatableElement = aAnimatableElement;
+ this.aAnimatableElement.initClipPath();
+ this.aAnimatableElement.notifyAnimationStart();
+
+ if( !this.bAnimationStarted )
+ this.bAnimationStarted = true;
+
+};
+
+/** end
+ * The transition clean up is performed here.
+ */
+ClippingAnimation.prototype.end = function()
+{
+ if( this.bAnimationStarted )
+ {
+ this.aAnimatableElement.cleanClipPath();
+ this.bAnimationStarted = false;
+ this.aAnimatableElement.notifyAnimationEnd();
+ }
+};
+
+/** perform
+ * This method set the position of the element to be animated according to
+ * the passed time value.
+ *
+ * @param nValue
+ * The time parameter.
+ */
+ClippingAnimation.prototype.perform = function( nValue )
+{
+ var nWidth = this.aAnimatableElement.aClippingBBox.width;
+ var nHeight = this.aAnimatableElement.aClippingBBox.height;
+ var aPolyPolygonElement = this.aClippingFunctor.perform( nValue, nWidth, nHeight );
+ this.aAnimatableElement.setClipPath( aPolyPolygonElement );
+};
+
+ClippingAnimation.prototype.getUnderlyingValue = function()
+{
+ return 0.0;
+};
+
+
+
+
+function GenericAnimation( aGetValueFunc, aSetValueFunc, aGetModifier, aSetModifier )
+{
+ assert( aGetValueFunc && aSetValueFunc,
+ 'GenericAnimation constructor: get value functor and/or set value functor are not valid' );
+
+ this.aGetValueFunc = aGetValueFunc;
+ this.aSetValueFunc = aSetValueFunc;
+ this.aGetModifier = aGetModifier;
+ this.aSetModifier = aSetModifier;
+ this.aAnimatableElement = null;
+ this.bAnimationStarted = false;
+}
+
+
+GenericAnimation.prototype.start = function( aAnimatableElement )
+{
+ assert( aAnimatableElement, 'GenericAnimation.start: animatable element is not valid' );
+
+ this.aAnimatableElement = aAnimatableElement;
+ this.aAnimatableElement.notifyAnimationStart();
+
+ if( !this.bAnimationStarted )
+ this.bAnimationStarted = true;
+};
+
+GenericAnimation.prototype.end = function()
+{
+ if( this.bAnimationStarted )
+ {
+ this.bAnimationStarted = false;
+ this.aAnimatableElement.notifyAnimationEnd();
+ }
+};
+
+GenericAnimation.prototype.perform = function( aValue )
+{
+ if( this.aSetModifier )
+ aValue = this.aSetModifier( aValue );
+
+ this.aSetValueFunc( aValue );
+};
+
+GenericAnimation.prototype.getUnderlyingValue = function()
+{
+ var aValue = this.aGetValueFunc();
+ if( this.aGetModifier )
+ aValue = this.aGetModifier( aValue );
+ return aValue;
+};
+
+
+
+function TupleAnimation( aGetValueFunc, aSetValueFunc, aDefaultValue, aReferenceSize )
+{
+ TupleAnimation.superclass.constructor.call( this, aGetValueFunc, aSetValueFunc );
+ assert( aDefaultValue && aReferenceSize,
+ 'TupleAnimation constructor: default value functor and/or reference size are not valid' );
+
+ this.aDefaultValue = aDefaultValue;
+ this.aReferenceSize = aReferenceSize;
+}
+extend( TupleAnimation, GenericAnimation );
+
+TupleAnimation.prototype.perform = function( aNormValue )
+{
+ assert(aNormValue.length === this.aReferenceSize.length);
+
+ var aValue = [];
+ for( var i = 0; i < aNormValue.length; ++i )
+ {
+ aValue.push( aNormValue[i] * this.aReferenceSize[i] );
+ }
+
+ this.aSetValueFunc( aValue );
+};
+
+TupleAnimation.prototype.getUnderlyingValue = function()
+{
+ var aValue = this.aGetValueFunc();
+ assert(aValue.length === this.aReferenceSize.length);
+
+ var aNormValue = [];
+ for( var i = 0; i < aValue.length; ++i )
+ {
+ aNormValue.push( aValue[i] / this.aReferenceSize[i] );
+ }
+
+ return aNormValue;
+};
+
+
+
+function HSLAnimationWrapper( aColorAnimation )
+{
+ assert( aColorAnimation,
+ 'HSLAnimationWrapper constructor: invalid color animation delegate' );
+
+ this.aAnimation = aColorAnimation;
+}
+
+
+HSLAnimationWrapper.prototype.start = function( aAnimatableElement )
+{
+ this.aAnimation.start( aAnimatableElement );
+};
+
+HSLAnimationWrapper.prototype.end = function()
+{
+ this.aAnimation.end();
+};
+HSLAnimationWrapper.prototype.perform = function( aHSLValue )
+{
+ this.aAnimation.perform( aHSLValue.convertToRGB() );
+};
+
+HSLAnimationWrapper.prototype.getUnderlyingValue = function()
+{
+ return this.aAnimation.getUnderlyingValue().convertToHSL();
+};
+
+
+
+
+/** Class SlideChangeBase
+ * The base abstract class of classes performing slide transitions.
+ *
+ * @param aLeavingSlide
+ * An object of type AnimatedSlide handling the leaving slide.
+ * @param aEnteringSlide
+ * An object of type AnimatedSlide handling the entering slide.
+ */
+function SlideChangeBase(aLeavingSlide, aEnteringSlide)
+{
+ this.aLeavingSlide = aLeavingSlide;
+ this.aEnteringSlide = aEnteringSlide;
+ this.bIsFinished = false;
+}
+
+/** start
+ * The transition initialization is performed here.
+ */
+SlideChangeBase.prototype.start = function()
+{
+};
+
+/** end
+ * The transition clean up is performed here.
+ */
+SlideChangeBase.prototype.end = function()
+{
+ if( this.bIsFinished )
+ return;
+
+ this.aLeavingSlide.hide();
+ this.aEnteringSlide.reset();
+ this.aLeavingSlide.reset();
+
+ this.bIsFinished = true;
+};
+
+/** perform
+ * This method is responsible for performing the slide transition.
+ *
+ * @param nValue
+ * The time parameter.
+ * @return {Boolean}
+ * If the transition is performed returns tue else returns false.
+ */
+SlideChangeBase.prototype.perform = function( nValue )
+{
+ if( this.bIsFinished ) return false;
+
+ if( this.aLeavingSlide )
+ this.performOut( nValue );
+
+ if( this.aEnteringSlide )
+ this.performIn( nValue );
+
+ return true;
+};
+
+SlideChangeBase.prototype.getUnderlyingValue = function()
+{
+ return 0.0;
+};
+
+SlideChangeBase.prototype.performIn = function( )
+{
+ log( 'SlideChangeBase.performIn: abstract method called' );
+};
+
+SlideChangeBase.prototype.performOut = function( )
+{
+ log( 'SlideChangeBase.performOut: abstract method called' );
+};
+
+
+
+
+/** Class FadingSlideChange
+ * This class performs a slide transition by fading out the leaving slide and
+ * fading in the entering slide.
+ *
+ * @param aLeavingSlide
+ * An object of type AnimatedSlide handling the leaving slide.
+ * @param aEnteringSlide
+ * An object of type AnimatedSlide handling the entering slide.
+ */
+function FadingSlideChange( aLeavingSlide, aEnteringSlide )
+{
+ FadingSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
+ this.bFirstRun = true;
+}
+extend( FadingSlideChange, SlideChangeBase );
+
+/** start
+ * This method notifies to the slides involved in the transition the attributes
+ * appended to the slide elements for performing the animation.
+ * Moreover it sets the entering slide in the initial state and makes the slide
+ * visible.
+ */
+FadingSlideChange.prototype.start = function()
+{
+ FadingSlideChange.superclass.start.call( this );
+ this.aEnteringSlide.notifyUsedAttribute( 'opacity' );
+ this.aLeavingSlide.notifyUsedAttribute( 'opacity' );
+ this.aEnteringSlide.setOpacity( 0.0 );
+ this.aEnteringSlide.show();
+};
+
+/** performIn
+ * This method set the opacity of the entering slide according to the passed
+ * time value.
+ *
+ * @param nT
+ * The time parameter.
+ */
+FadingSlideChange.prototype.performIn = function( nT )
+{
+ this.aEnteringSlide.setOpacity( nT );
+};
+
+/** performOut
+ * This method set the opacity of the leaving slide according to the passed
+ * time value.
+ *
+ * @param nT
+ * The time parameter.
+ */
+FadingSlideChange.prototype.performOut = function( nT )
+{
+
+ this.aLeavingSlide.setOpacity( 1 - nT );
+};
+
+
+
+
+/** Class FadingOverColorSlideChange
+ * This class performs a slide transition by fading out the leaving slide to
+ * a given color and fading in the entering slide from the same color.
+ *
+ * @param aLeavingSlide
+ * An object of type AnimatedSlide handling the leaving slide.
+ * @param aEnteringSlide
+ * An object of type AnimatedSlide handling the entering slide.
+ * @param sFadeColor
+ * A string representing the color the leaving slide fades out to and
+ * the entering slide fade in from.
+ */
+function FadingOverColorSlideChange( aLeavingSlide, aEnteringSlide, sFadeColor )
+{
+ FadingSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
+ this.sFadeColor = sFadeColor;
+ if( !this.sFadeColor )
+ {
+ log( 'FadingOverColorSlideChange: sFadeColor not valid.' );
+ this.sFadeColor = '#000000';
+ }
+ this.aColorPlaneElement = this.createColorPlaneElement();
+}
+extend( FadingOverColorSlideChange, SlideChangeBase );
+
+/** start
+ * This method notifies to the slides involved in the transition the attributes
+ * appended to the slide elements for performing the animation.
+ * Moreover it inserts the color plane element below the leaving slide.
+ * Finally it sets the entering slide in the initial state and makes
+ * the slide visible.
+ */
+FadingOverColorSlideChange.prototype.start = function()
+{
+ FadingOverColorSlideChange.superclass.start.call( this );
+ this.aEnteringSlide.notifyUsedAttribute( 'opacity' );
+ this.aLeavingSlide.notifyUsedAttribute( 'opacity' );
+ this.aLeavingSlide.insertBefore( this.aColorPlaneElement );
+ this.aEnteringSlide.setOpacity( 0.0 );
+ this.aEnteringSlide.show();
+};
+
+/** end
+ * This method removes the color plane element.
+ */
+FadingOverColorSlideChange.prototype.end = function()
+{
+ FadingOverColorSlideChange.superclass.end.call( this );
+ this.aLeavingSlide.removeElement( this.aColorPlaneElement );
+};
+
+/** performIn
+ * This method set the opacity of the entering slide according to the passed
+ * time value.
+ *
+ * @param nT
+ * The time parameter.
+ */
+FadingOverColorSlideChange.prototype.performIn = function( nT )
+{
+ this.aEnteringSlide.setOpacity( (nT > 0.55) ? 2.0*(nT-0.55) : 0.0 );
+};
+
+/** performOut
+ * This method set the opacity of the leaving slide according to the passed
+ * time value.
+ *
+ * @param nT
+ * The time parameter.
+ */
+FadingOverColorSlideChange.prototype.performOut = function( nT )
+{
+ this.aLeavingSlide.setOpacity( (nT > 0.45) ? 0.0 : 2.0*(0.45-nT) );
+};
+
+FadingOverColorSlideChange.prototype.createColorPlaneElement = function()
+{
+ var aColorPlaneElement = document.createElementNS( NSS['svg'], 'rect' );
+ aColorPlaneElement.setAttribute( 'width', String( this.aLeavingSlide.getWidth() ) );
+ aColorPlaneElement.setAttribute( 'height', String( this.aLeavingSlide.getHeight() ) );
+ aColorPlaneElement.setAttribute( 'fill', this.sFadeColor );
+ return aColorPlaneElement;
+};
+
+
+
+
+/** Class MovingSlideChange
+ * This class performs a slide transition that involves translating the leaving
+ * slide and/or the entering one in a given direction.
+ *
+ * @param aLeavingSlide
+ * An object of type AnimatedSlide handling the leaving slide.
+ * @param aEnteringSlide
+ * An object of type AnimatedSlide handling the entering slide.
+ * @param aLeavingDirection
+ * A 2D vector object {x, y}.
+ * @param aEnteringDirection
+ * A 2D vector object {x, y}.
+ */
+function MovingSlideChange( aLeavingSlide, aEnteringSlide,
+ aLeavingDirection, aEnteringDirection )
+{
+ MovingSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
+ this.aLeavingDirection = aLeavingDirection;
+ this.aEnteringDirection = aEnteringDirection;
+}
+extend( MovingSlideChange, SlideChangeBase );
+
+/** start
+ * This method notifies to the slides involved in the transition the attributes
+ * appended to the slide elements for performing the animation.
+ * Moreover it sets the entering slide in the initial state and makes the slide
+ * visible.
+ */
+MovingSlideChange.prototype.start = function()
+{
+ MovingSlideChange.superclass.start.call( this );
+ this.aEnteringSlide.notifyUsedAttribute( 'transform' );
+ this.aLeavingSlide.notifyUsedAttribute( 'transform' );
+ // Before setting the 'visibility' attribute of the entering slide to 'visible'
+ // we translate it to the initial position so that it is not really visible
+ // because it is clipped out.
+ this.performIn( 0 );
+ this.aEnteringSlide.show();
+};
+
+/** performIn
+ * This method set the position of the entering slide according to the passed
+ * time value.
+ *
+ * @param nT
+ * The time parameter.
+ */
+MovingSlideChange.prototype.performIn = function( nT )
+{
+ var nS = nT - 1;
+ var dx = nS * this.aEnteringDirection.x * this.aEnteringSlide.getWidth();
+ var dy = nS * this.aEnteringDirection.y * this.aEnteringSlide.getHeight();
+ this.aEnteringSlide.translate( dx, dy );
+};
+
+/** performOut
+ * This method set the position of the leaving slide according to the passed
+ * time value.
+ *
+ * @param nT
+ * The time parameter.
+ */
+MovingSlideChange.prototype.performOut = function( nT )
+{
+ var dx = nT * this.aLeavingDirection.x * this.aLeavingSlide.getWidth();
+ var dy = nT * this.aLeavingDirection.y * this.aLeavingSlide.getHeight();
+ this.aLeavingSlide.translate( dx, dy );
+};
+
+
+
+
+/** Class ClippedSlideChange
+ * This class performs a slide transition where the entering slide wipes
+ * the leaving one out. The wipe effect is achieved by clipping the entering
+ * slide with a parametric path.
+ *
+ * @param aLeavingSlide
+ * An object of type AnimatedSlide handling the leaving slide.
+ * @param aEnteringSlide
+ * An object of type AnimatedSlide handling the entering slide.
+ * @param aParametricPolyPolygon
+ * An object handling a <path> element that depends on a parameter.
+ * @param aTransitionInfo
+ * The set of parameters defining the slide transition to be performed.
+ * @param bIsDirectionForward
+ * The direction the slide transition has to be performed.
+ */
+function ClippedSlideChange( aLeavingSlide, aEnteringSlide, aParametricPolyPolygon,
+ aTransitionInfo, bIsDirectionForward )
+{
+ ClippedSlideChange.superclass.constructor.call( this, aLeavingSlide, aEnteringSlide );
+
+ var bIsModeIn = true;
+ this.aClippingFunctor= new ClippingFunctor( aParametricPolyPolygon, aTransitionInfo,
+ bIsDirectionForward, bIsModeIn );
+}
+extend( ClippedSlideChange, SlideChangeBase );
+
+/** start
+ * This method notifies to the slides involved in the transition the attributes
+ * appended to the slide elements for performing the animation.
+ * Moreover it sets the entering slide in the initial state and makes the slide
+ * visible.
+ */
+ClippedSlideChange.prototype.start = function()
+{
+ ClippedSlideChange.superclass.start.call( this );
+ this.aEnteringSlide.notifyUsedAttribute( 'clip-path' );
+ this.performIn( 0 );
+ this.aEnteringSlide.show();
+};
+
+/** performIn
+ * This method set the position of the entering slide according to the passed
+ * time value.
+ *
+ * @param nT
+ * The time parameter.
+ */
+ClippedSlideChange.prototype.performIn = function( nT )
+{
+ var nWidth = this.aEnteringSlide.getWidth();
+ var nHeight = this.aEnteringSlide.getHeight();
+ var aPolyPolygonElement = this.aClippingFunctor.perform( nT, nWidth, nHeight );
+ this.aEnteringSlide.setClipPath( aPolyPolygonElement );
+};
+
+ClippedSlideChange.prototype.performOut = function( )
+{
+ // empty body
+};
+
+
+
+
+/** Class ClippingFunctor
+ * This class is responsible for computing the <path> used for clipping
+ * the entering slide in a polypolygon clipping slide transition or the
+ * animated shape in a transition filter effect.
+ *
+ * @param aParametricPolyPolygon
+ * An object that handle a <path> element defined in the [0,1]x[0,1]
+ * unit square and that depends on a parameter.
+ * @param aTransitionInfo
+ * The set of parameters defining the slide transition to be performed.
+ * @param bIsDirectionForward
+ * The direction the slide transition has to be performed.
+ * @param bIsModeIn
+ * The direction the filter effect has to be performed
+ */
+function ClippingFunctor( aParametricPolyPolygon, aTransitionInfo,
+ bIsDirectionForward, bIsModeIn)
+{
+ this.aParametricPolyPolygon = aParametricPolyPolygon;
+ this.aStaticTransformation = null;
+ this.bForwardParameterSweep = true;
+ this.bSubtractPolygon = false;
+ this.bScaleIsotropically = aTransitionInfo.scaleIsotropically;
+ this.bFlip = false;
+
+ assert( this.aParametricPolyPolygon,
+ 'ClippingFunctor: parametric polygon is not valid' );
+
+ if( aTransitionInfo.rotationAngle != 0.0 ||
+ aTransitionInfo.scaleX != 1.0 || aTransitionInfo.scaleY != 1.0 )
+ {
+ // note: operations must be defined in reverse order.
+ this.aStaticTransformation = SVGIdentityMatrix.translate( 0.5, 0.5 );
+ if( aTransitionInfo.scaleX != 1.0 || aTransitionInfo.scaleY != 1.0 )
+ this.aStaticTransformation
+ = this.aStaticTransformation.scaleNonUniform( aTransitionInfo.scaleX,
+ aTransitionInfo.scaleY );
+ if( aTransitionInfo.rotationAngle != 0.0 )
+ this.aStaticTransformation
+ = this.aStaticTransformation.rotate( aTransitionInfo.rotationAngle );
+ this.aStaticTransformation = this.aStaticTransformation.translate( -0.5, -0.5 );
+ }
+ else
+ {
+ this.aStaticTransformation = document.documentElement.createSVGMatrix();
+ }
+
+ if( !bIsDirectionForward )
+ {
+ var aMatrix = null;
+ switch( aTransitionInfo.reverseMethod )
+ {
+ default:
+ log( 'ClippingFunctor: unexpected reverse method.' );
+ break;
+ case REVERSEMETHOD_IGNORE:
+ break;
+ case REVERSEMETHOD_INVERT_SWEEP:
+ this.bForwardParameterSweep = !this.bForwardParameterSweep;
+ break;
+ case REVERSEMETHOD_SUBTRACT_POLYGON:
+ this.bSubtractPolygon = !this.bSubtractPolygon;
+ break;
+ case REVERSEMETHOD_SUBTRACT_AND_INVERT:
+ this.bForwardParameterSweep = !this.bForwardParameterSweep;
+ this.bSubtractPolygon = !this.bSubtractPolygon;
+ break;
+ case REVERSEMETHOD_ROTATE_180:
+ aMatrix = document.documentElement.createSVGMatrix();
+ aMatrix.setToRotationAroundPoint( 0.5, 0.5, 180 );
+ this.aStaticTransformation = aMatrix.multiply( this.aStaticTransformation );
+ break;
+ case REVERSEMETHOD_FLIP_X:
+ aMatrix = document.documentElement.createSVGMatrix();
+ // |-1 0 1 |
+ // | 0 1 0 |
+ aMatrix.a = -1; aMatrix.e = 1.0;
+ this.aStaticTransformation = aMatrix.multiply( this.aStaticTransformation );
+ this.bFlip = true;
+ break;
+ case REVERSEMETHOD_FLIP_Y:
+ aMatrix = document.documentElement.createSVGMatrix();
+ // | 1 0 0 |
+ // | 0 -1 1 |
+ aMatrix.d = -1; aMatrix.f = 1.0;
+ this.aStaticTransformation = aMatrix.multiply( this.aStaticTransformation );
+ this.bFlip = true;
+ break;
+ }
+ }
+
+ if( !bIsModeIn )
+ {
+ if( aTransitionInfo.outInvertsSweep )
+ {
+ this.bForwardParameterSweep = !this.bForwardParameterSweep;
+ }
+ else
+ {
+ this.bSubtractPolygon = !this.bSubtractPolygon;
+ }
+ }
+}
+
+// This path is used when the direction is the reverse one and
+// the reverse method type is the subtraction type.
+ClippingFunctor.aBoundingPath = document.createElementNS( NSS['svg'], 'path' );
+ClippingFunctor.aBoundingPath.setAttribute( 'd', 'M -1 -1 L 2 -1 L 2 2 L -1 2 L -1 -1' );
+
+/** perform
+ *
+ * @param nT
+ * A parameter in [0,1] representing normalized time.
+ * @param nWidth
+ * The width of the bounding box of the slide/shape to be clipped.
+ * @param nHeight
+ * The height of the bounding box of the slide/shape to be clipped.
+ * @return SVGPathElement
+ * A svg <path> element representing the path to be used for the clipping
+ * operation.
+ */
+ClippingFunctor.prototype.perform = function( nT, nWidth, nHeight )
+{
+ var aClipPoly = this.aParametricPolyPolygon.perform( this.bForwardParameterSweep ? nT : (1 - nT) );
+
+ // Note: even if the reverse method involves flipping we don't need to
+ // change the clip-poly orientation because we utilize the 'nonzero'
+ // clip-rule.
+ // See: http://www.w3.org/TR/SVG11/painting.html#FillRuleProperty
+
+ if( this.bSubtractPolygon )
+ {
+ aClipPoly.changeOrientation();
+ aClipPoly.prependPath( ClippingFunctor.aBoundingPath );
+ }
+
+ var aMatrix;
+ if( this.bScaleIsotropically )
+ {
+ var nScaleFactor = Math.max( nWidth, nHeight );
+ // note: operations must be defined in reverse order.
+ aMatrix = SVGIdentityMatrix.translate( -( nScaleFactor - nWidth ) / 2.0,
+ -( nScaleFactor - nHeight ) / 2.0 );
+ aMatrix = aMatrix.scale( nScaleFactor );
+ aMatrix = aMatrix.multiply( this.aStaticTransformation );
+ }
+ else
+ {
+ aMatrix = SVGIdentityMatrix.scaleNonUniform( nWidth, nHeight );
+ aMatrix = aMatrix.multiply( this.aStaticTransformation );
+ }
+
+ aClipPoly.matrixTransform( aMatrix );
+
+ return aClipPoly;
+};
+
+
+
+
+/** createClipPolyPolygon
+ *
+ * @param nType
+ * An enumerator representing the transition type.
+ * @param nSubtype
+ * An enumerator representing the transition subtype.
+ * @return
+ * An object that handles a parametric <path> element.
+ */
+function createClipPolyPolygon( nType, nSubtype )
+{
+ switch( nType )
+ {
+ default:
+ log( 'createClipPolyPolygon: unknown transition type: ' + nType );
+ return null;
+ case BARWIPE_TRANSITION:
+ return new BarWipePath( 1 );
+ case FOURBOXWIPE_TRANSITION:
+ return new FourBoxWipePath( nSubtype === CORNERSOUT_TRANS_SUBTYPE );
+ case BOXWIPE_TRANSITION:
+ return new BoxWipePath( nSubtype == LEFTCENTER_TRANS_SUBTYPE ||
+ nSubtype == TOPCENTER_TRANS_SUBTYPE ||
+ nSubtype == RIGHTCENTER_TRANS_SUBTYPE ||
+ nSubtype == BOTTOMCENTER_TRANS_SUBTYPE );
+ case ELLIPSEWIPE_TRANSITION:
+ return new EllipseWipePath( nSubtype );
+ case FANWIPE_TRANSITION:
+ return new FanWipePath(nSubtype == CENTERTOP_TRANS_SUBTYPE ||
+ nSubtype == CENTERRIGHT_TRANS_SUBTYPE, true, false);
+ case PINWHEELWIPE_TRANSITION:
+ var nBlades;
+ switch( nSubtype )
+ {
+ case ONEBLADE_TRANS_SUBTYPE:
+ nBlades = 1;
+ break;
+ case DEFAULT_TRANS_SUBTYPE:
+ case TWOBLADEVERTICAL_TRANS_SUBTYPE:
+ nBlades = 2;
+ break;
+ case TWOBLADEHORIZONTAL_TRANS_SUBTYPE:
+ nBlades = 2;
+ break;
+ case THREEBLADE_TRANS_SUBTYPE:
+ nBlades = 3;
+ break;
+ case FOURBLADE_TRANS_SUBTYPE:
+ nBlades = 4;
+ break;
+ case EIGHTBLADE_TRANS_SUBTYPE:
+ nBlades = 8;
+ break;
+ default:
+ log( 'createClipPolyPolygon: unknown subtype: ' + nSubtype );
+ return null;
+ }
+ return new PinWheelWipePath( nBlades );
+ case CLOCKWIPE_TRANSITION:
+ return new ClockWipePath();
+ case RANDOMBARWIPE_TRANSITION:
+ return new RandomWipePath( 128, true /* bars */ );
+ case CHECKERBOARDWIPE_TRANSITION:
+ return new CheckerBoardWipePath( 10 );
+ case ZIGZAGWIPE_TRANSITION:
+ return new ZigZagWipePath( 5 );
+ case BARNZIGZAGWIPE_TRANSITION:
+ return new BarnZigZagWipePath( 5 );
+ case IRISWIPE_TRANSITION:
+ switch(nSubtype)
+ {
+ case RECTANGLE_TRANS_SUBTYPE:
+ return new IrisWipePath(0);
+ case DIAMOND_TRANS_SUBTYPE:
+ return new IrisWipePath(1);
+ default:
+ log( 'createClipPolyPolygon: unknown subtype: ' + nSubtype );
+ return null;
+ }
+ case BARNDOORWIPE_TRANSITION:
+ return new BarnDoorWipePath(false);
+ case SINGLESWEEPWIPE_TRANSITION:
+ return new SweepWipePath(
+ // center
+ nSubtype == CLOCKWISETOP_TRANS_SUBTYPE ||
+ nSubtype == CLOCKWISERIGHT_TRANS_SUBTYPE ||
+ nSubtype == CLOCKWISEBOTTOM_TRANS_SUBTYPE ||
+ nSubtype == CLOCKWISELEFT_TRANS_SUBTYPE,
+ // single
+ true,
+ // oppositeVertical
+ false,
+ // flipOnYAxis
+ nSubtype == COUNTERCLOCKWISEBOTTOMLEFT_TRANS_SUBTYPE ||
+ nSubtype == COUNTERCLOCKWISETOPRIGHT_TRANS_SUBTYPE );
+ case WATERFALLWIPE_TRANSITION:
+ return new WaterfallWipePath(128, // flipOnYAxis
+ nSubtype == VERTICALRIGHT_TRANS_SUBTYPE ||
+ nSubtype == HORIZONTALLEFT_TRANS_SUBTYPE);
+ case MISCDIAGONALWIPE_TRANSITION:
+ switch(nSubtype) {
+ case DOUBLEBARNDOOR_TRANS_SUBTYPE:
+ return new BarnDoorWipePath(true /* Doubled */);
+ case DOUBLEDIAMOND_TRANS_SUBTYPE:
+ return new DoubleDiamondWipePath();
+ default:
+ log( 'createClipPolyPolygon: unhandled subtype: ' + nSubtype );
+ return null;
+ }
+ case DISSOLVE_TRANSITION:
+ return new RandomWipePath( 16 * 16, false /* dissolve */ );
+ case VEEWIPE_TRANSITION:
+ return new VeeWipePath();
+ case SNAKEWIPE_TRANSITION:
+ return new SnakeWipePath( 8 * 8, // diagonal
+ nSubtype == TOPLEFTDIAGONAL_TRANS_SUBTYPE ||
+ nSubtype == TOPRIGHTDIAGONAL_TRANS_SUBTYPE ||
+ nSubtype == BOTTOMRIGHTDIAGONAL_TRANS_SUBTYPE ||
+ nSubtype == BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE ,
+ // flipOnYAxis
+ nSubtype == TOPLEFTVERTICAL_TRANS_SUBTYPE ||
+ nSubtype == TOPRIGHTDIAGONAL_TRANS_SUBTYPE ||
+ nSubtype == BOTTOMLEFTDIAGONAL_TRANS_SUBTYPE
+ );
+ case PARALLELSNAKESWIPE_TRANSITION:
+ return new ParallelSnakesWipePath(
+ 8 * 8, // elements
+ // diagonal
+ nSubtype == DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
+ nSubtype == DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
+ // flipOnYAxis
+ nSubtype == VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
+ nSubtype == HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE ||
+ nSubtype == DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE,
+ // opposite
+ nSubtype == VERTICALTOPLEFTOPPOSITE_TRANS_SUBTYPE ||
+ nSubtype == VERTICALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
+ nSubtype == HORIZONTALTOPLEFTOPPOSITE_TRANS_SUBTYPE ||
+ nSubtype == HORIZONTALTOPRIGHTOPPOSITE_TRANS_SUBTYPE ||
+ nSubtype == DIAGONALBOTTOMLEFTOPPOSITE_TRANS_SUBTYPE ||
+ nSubtype == DIAGONALTOPLEFTOPPOSITE_TRANS_SUBTYPE
+ );
+
+ case SPIRALWIPE_TRANSITION:
+ return new SpiralWipePath(
+ 8 * 8, // elements
+ nSubtype == TOPLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE ||
+ nSubtype == TOPRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE ||
+ nSubtype == BOTTOMRIGHTCOUNTERCLOCKWISE_TRANS_SUBTYPE ||
+ nSubtype == BOTTOMLEFTCOUNTERCLOCKWISE_TRANS_SUBTYPE );
+
+ case BOXSNAKESWIPE_TRANSITION:
+ return new BoxSnakesWipePath(
+ // elements
+ 8 * 8,
+ // fourBox
+ nSubtype == FOURBOXVERTICAL_TRANS_SUBTYPE ||
+ nSubtype == FOURBOXHORIZONTAL_TRANS_SUBTYPE );
+ }
+}
+
+
+
+
+function createUnitSquarePath()
+{
+ var aPath = document.createElementNS( NSS['svg'], 'path' );
+ var sD = 'M 0 0 L 1 0 L 1 1 L 0 1 L 0 0';
+ aPath.setAttribute( 'd', sD );
+ return aPath;
+}
+
+function createEmptyPath()
+{
+ var aPath = document.createElementNS( NSS['svg'], 'path' );
+ var sD = 'M 0 0 L 0 0';
+ aPath.setAttribute( 'd', sD );
+ return aPath;
+}
+
+function pruneScaleValue( nVal )
+{
+ if( nVal < 0.0 )
+ return (nVal < -0.00001 ? nVal : -0.00001);
+ else
+ return (nVal > 0.00001 ? nVal : 0.00001);
+}
+
+
+/** Class BarWipePath
+ * This class handles a <path> element that defines a unit square and
+ * transforms it accordingly to a parameter in the [0,1] range for performing
+ * a left to right barWipe transition.
+ *
+ * @param nBars
+ * The number of bars to be generated.
+ */
+function BarWipePath( nBars /* nBars > 1: blinds effect */ )
+{
+ this.nBars = nBars;
+ if( this.nBars === undefined || this.nBars < 1 )
+ this.nBars = 1;
+ this.aBasePath = createUnitSquarePath();
+}
+
+/** perform
+ *
+ * @param nT
+ * A parameter in [0,1] representing the width of the generated bars.
+ * @return SVGPathElement
+ * A svg <path> element representing a multi-bars.
+ */
+BarWipePath.prototype.perform = function( nT )
+{
+
+ var aMatrix = SVGIdentityMatrix.scaleNonUniform( pruneScaleValue( nT / this.nBars ), 1.0 );
+
+ var aPolyPath = this.aBasePath.cloneNode( true );
+ aPolyPath.matrixTransform( aMatrix );
+
+ if( this.nBars > 1 )
+ {
+ var i;
+ var aTransform;
+ var aPath;
+ for( i = this.nBars - 1; i > 0; --i )
+ {
+ aTransform = SVGIdentityMatrix.translate( i / this.nBars, 0.0 );
+ aTransform = aTransform.multiply( aMatrix );
+ aPath = this.aBasePath.cloneNode( true );
+ aPath.matrixTransform( aTransform );
+ aPolyPath.appendPath( aPath );
+ }
+ }
+ return aPolyPath;
+};
+
+
+/** Class BoxWipePath
+ * This class handles a path made up by one square and is utilized for
+ * performing BoxWipe transitions.
+ *
+ * @param bIsTopCentered
+ * if true the transition subtype is top centered else not.
+ */
+function BoxWipePath(bIsTopCentered) {
+ this.bIsTopCentered = bIsTopCentered;
+ this.aBasePath = createUnitSquarePath();
+}
+
+BoxWipePath.prototype.perform = function( nT ) {
+ var d = pruneScaleValue(nT);
+ var aTransform = SVGIdentityMatrix;
+ if(this.bIsTopCentered) {
+ aTransform = aTransform.translate(-0.5, 0.0).scale(d, d).translate(0.5, 0.0);
+ }
+ else {
+ aTransform = aTransform.scale(d, d);
+ }
+ var aPath = this.aBasePath.cloneNode(true);
+ aPath.matrixTransform(aTransform);
+ return aPath;
+}
+
+/* Class SweepWipePath
+ *
+ *
+ */
+function SweepWipePath(bCenter, bSingle, bOppositeVertical, bFlipOnYAxis) {
+ this.bCenter = bCenter;
+ this.bSingle = bSingle;
+ this.bOppositeVertical = bOppositeVertical;
+ this.bFlipOnYAxis = bFlipOnYAxis;
+ this.aBasePath = createUnitSquarePath();
+}
+
+SweepWipePath.prototype.perform = function( nT ) {
+ nT /= 2.0;
+ if(!this.bCenter)
+ nT /= 2.0;
+ if(!this.bSingle && !this.bOppositeVertical)
+ nT /= 2.0;
+
+ var poly = PinWheelWipePath.calcCenteredClock( nT + 0.25, 1.0 );
+ var aTransform;
+
+ if(this.bCenter) {
+ aTransform = SVGIdentityMatrix.translate(0.5, 0.0);
+ poly.matrixTransform(aTransform);
+ }
+ var res = poly;
+
+ if(!this.bSingle) {
+ if(this.bOppositeVertical) {
+ aTransform = SVGIdentityMatrix.scale(1.0, -1.0);
+ aTransform.translate(0.0, 1.0);
+ poly.matrixTransform(aTransform);
+ poly.changeOrientation();
+ }
+ else {
+ aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
+ aTransform.rotate(Math.PI);
+ aTransform.translate(0.5, 0.5);
+ poly.matrixTransform(aTransform);
+ }
+ res.appendPath(poly);
+ }
+ return this.bFlipOnYAxis ? flipOnYAxis(res) : res;
+}
+
+/** Class FourBoxWipePath
+ * This class handles a path made up by four squares and is utilized for
+ * performing fourBoxWipe transitions.
+ *
+ * @param bCornersOut
+ * If true the transition subtype is cornersOut else is cornersIn.
+ */
+function FourBoxWipePath( bCornersOut )
+{
+ this.bCornersOut = bCornersOut;
+ this.aBasePath = createUnitSquarePath();
+}
+
+FourBoxWipePath.prototype.perform = function( nT )
+{
+ var aMatrix;
+ var d = pruneScaleValue( nT / 2.0 );
+
+ if( this.bCornersOut )
+ {
+ aMatrix = SVGIdentityMatrix.translate( -0.25, -0.25 ).scale( d ).translate( -0.5, -0.5 );
+ }
+ else
+ {
+ aMatrix = SVGIdentityMatrix.translate( -0.5, -0.5 ).scale( d );
+ }
+
+
+ var aTransform = aMatrix;
+ // top left
+ var aSquare = this.aBasePath.cloneNode( true );
+ aSquare.matrixTransform( aTransform );
+ var aPolyPath = aSquare;
+ // bottom left, flip on x-axis:
+ aMatrix = SVGIdentityMatrix.flipY();
+ aTransform = aMatrix.multiply( aTransform );
+ aSquare = this.aBasePath.cloneNode( true );
+ aSquare.matrixTransform( aTransform );
+ aSquare.changeOrientation();
+ aPolyPath.appendPath( aSquare );
+ // bottom right, flip on y-axis:
+ aMatrix = SVGIdentityMatrix.flipX();
+ aTransform = aMatrix.multiply( aTransform );
+ aSquare = this.aBasePath.cloneNode( true );
+ aSquare.matrixTransform( aTransform );
+ aPolyPath.appendPath( aSquare );
+ // top right, flip on x-axis:
+ aMatrix = SVGIdentityMatrix.flipY();
+ aTransform = aMatrix.multiply( aTransform );
+ aSquare = this.aBasePath.cloneNode( true );
+ aSquare.matrixTransform( aTransform );
+ aSquare.changeOrientation();
+ aPolyPath.appendPath( aSquare );
+
+ // Remind: operations are applied in inverse order
+ aMatrix = SVGIdentityMatrix.translate( 0.5, 0.5 );
+ // We enlarge a bit the clip path so we avoid that in reverse direction
+ // some thin line of the border stroke is visible.
+ aMatrix = aMatrix.scale( 1.1 );
+ aPolyPath.matrixTransform( aMatrix );
+
+ return aPolyPath;
+};
+
+
+
+
+/** Class EllipseWipePath
+ * This class handles a parametric ellipse represented by a path made up of
+ * cubic Bezier curve segments that helps in performing the ellipseWipe
+ * transition.
+ *
+ * @param eSubtype
+ * The transition subtype.
+ */
+function EllipseWipePath( eSubtype )
+{
+ this.eSubtype = eSubtype;
+
+ // precomputed circle( 0.5, 0.5, SQRT2 / 2 )
+ var sPathData = 'M 0.5 -0.207107 ' +
+ 'C 0.687536 -0.207107 0.867392 -0.132608 1 0 ' +
+ 'C 1.13261 0.132608 1.20711 0.312464 1.20711 0.5 ' +
+ 'C 1.20711 0.687536 1.13261 0.867392 1 1 ' +
+ 'C 0.867392 1.13261 0.687536 1.20711 0.5 1.20711 ' +
+ 'C 0.312464 1.20711 0.132608 1.13261 0 1 ' +
+ 'C -0.132608 0.867392 -0.207107 0.687536 -0.207107 0.5 ' +
+ 'C -0.207107 0.312464 -0.132608 0.132608 0 0 ' +
+ 'C 0.132608 -0.132608 0.312464 -0.207107 0.5 -0.207107';
+
+ this.aBasePath = document.createElementNS( NSS['svg'], 'path' );
+ this.aBasePath.setAttribute( 'd', sPathData );
+}
+
+EllipseWipePath.prototype.perform = function( nT )
+{
+
+ var aTransform = SVGIdentityMatrix.translate( 0.5, 0.5 ).scale( nT ).translate( -0.5, -0.5 );
+ var aEllipse = this.aBasePath.cloneNode( true );
+ aEllipse.matrixTransform( aTransform );
+
+ return aEllipse;
+};
+
+/*
+ * Class FanWipePath
+ *
+ */
+function FanWipePath(bIsCenter, bIsSingle, bIsFanIn) {
+ this.bCenter = bIsCenter;
+ this.bSingle = bIsSingle;
+ this.bFanIn = bIsFanIn;
+ this.aBasePath = createUnitSquarePath();
+}
+
+FanWipePath.prototype.perform = function( nT ) {
+ var res = this.aBasePath.cloneNode(true);
+ var poly = PinWheelWipePath.calcCenteredClock(
+ nT / ((this.bCenter && this.bSingle) ? 2.0 : 4.0), 1.0);
+ res.appendPath(poly);
+ // flip on y-axis
+ var aTransform = SVGIdentityMatrix.flipY();
+ aTransform = aTransform.scaleNonUniform(-1.0, 1.0);
+ poly.matrixTransform(aTransform);
+ res.appendPath(poly);
+
+ if(this.bCenter) {
+ aTransform = SVGIdentityMatrix.scaleNonUniform(0.5, 0.5).translate(0.5, 0.5);
+ res.matrixTransform(aTransform);
+
+ if(!this.bSingle)
+ res.appendPath(flipOnXAxis(res));
+ }
+ else {
+ aTransform = SVGIdentityMatrix.scaleNonUniform(0.5, 1.0).translate(0.5, 1.0);
+ res.matrixTransform(aTransform);
+ }
+ return res;
+}
+
+/**
+ * Class ClockWipePath
+ *
+ */
+function ClockWipePath() { }
+
+ClockWipePath.prototype.perform = function( nT ) {
+ const aTransform = SVGIdentityMatrix.scaleNonUniform(0.5, 0.5).translate(0.5, 0.5);
+ var aPolyPath = PinWheelWipePath.calcCenteredClock(nT, 1.0);
+ aPolyPath.matrixTransform( aTransform );
+
+ return aPolyPath;
+}
+
+/** Class PinWheelWipePath
+ * This class handles a parametric poly-path that is used for performing
+ * a spinWheelWipe transition.
+ *
+ * @param nBlades
+ * Number of blades generated by the transition.
+ */
+function PinWheelWipePath( nBlades )
+{
+ this.nBlades = nBlades;
+ if( !this.nBlades || this.nBlades < 1 )
+ this.nBlades = 1;
+}
+
+PinWheelWipePath.calcCenteredClock = function( nT, nE )
+{
+ var nMAX_EDGE = 2;
+
+ var aTransform = SVGIdentityMatrix.rotate( nT * 360 );
+
+ var aPoint = document.documentElement.createSVGPoint();
+ aPoint.y = -nMAX_EDGE;
+ aPoint = aPoint.matrixTransform( aTransform );
+
+ var sPathData = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
+ if( nT >= 0.875 )
+ // L -e -e
+ sPathData += 'L ' + '-' + nE + ' -' + nE + ' ';
+ if( nT >= 0.625 )
+ // L -e e
+ sPathData += 'L ' + '-' + nE + ' ' + nE + ' ';
+ if( nT >= 0.375 )
+ // L e e
+ sPathData += 'L ' + nE + ' ' + nE + ' ';
+ if( nT >= 0.125 )
+ // L e -e
+ sPathData += 'L ' + nE + ' -' + nE + ' ';
+
+ // L 0 -e
+ sPathData += 'L 0 -' + nE + ' ';
+ sPathData += 'L 0 0 ';
+ // Z
+ sPathData += 'L ' + aPoint.x + ' ' + aPoint.y;
+
+ var aPath = document.createElementNS( NSS['svg'], 'path' );
+ aPath.setAttribute( 'd', sPathData );
+ return aPath;
+};
+
+PinWheelWipePath.prototype.perform = function( nT )
+{
+ var aBasePath = PinWheelWipePath.calcCenteredClock( nT / this.nBlades,
+ 2.0 /* max edge when rotating */ );
+
+ var aPolyPath = aBasePath.cloneNode( true );
+ var aPath;
+ var aRotation;
+ var i;
+ for( i = this.nBlades - 1; i > 0; --i )
+ {
+ aRotation = SVGIdentityMatrix.rotate( (i * 360) / this.nBlades );
+ aPath = aBasePath.cloneNode( true );
+ aPath.matrixTransform( aRotation );
+ aPolyPath.appendPath( aPath );
+ }
+
+ var aTransform = SVGIdentityMatrix.translate( 0.5, 0.5 ).scale( 0.5 );
+ aPolyPath.matrixTransform( aTransform );
+
+ return aPolyPath;
+};
+
+/** Class BarnDoorWipe
+ *
+ * @param doubled
+ */
+function BarnDoorWipePath(doubled) {
+ this.aBasePath = createUnitSquarePath();
+ this.doubled = doubled;
+}
+
+BarnDoorWipePath.prototype.perform = function( nT ) {
+ if(this.doubled)
+ nT /= 2.0;
+ var aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
+ aTransform = aTransform.scaleNonUniform(pruneScaleValue(nT), 1.0).translate(0.5, 0.5);
+ var aPath = this.aBasePath.cloneNode(true);
+ aPath.matrixTransform(aTransform);
+ var res = aPath;
+
+ if(this.doubled) {
+ aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
+ aTransform = aTransform.rotate(Math.PI / 2).translate(0.5, 0.5);
+ aPath.matrixTransform(aTransform);
+ res.appendPath(aPath);
+ }
+ return res;
+}
+
+/** Class WaterfallWipe
+ *
+ * @param nElements
+ * Number of cells to be used
+ * @param bFlipOnYAxis
+ * Whether to flip on y-axis or not.
+ */
+function WaterfallWipePath(nElements, bFlipOnYAxis) {
+ this.bFlipOnYAxis = bFlipOnYAxis;
+
+ var sqrtElements = Math.floor(Math.sqrt(nElements));
+ var elementEdge = 1.0/sqrtElements;
+
+ var aPath = 'M '+ 0.0 + ' ' + -1.0 + ' ';
+ for(var pos = sqrtElements; pos--; ) {
+ var xPos = sqrtElements - pos - 1;
+ var yPos = pruneScaleValue( ((pos+1) * elementEdge) - 1.0);
+
+ aPath += 'L ' + pruneScaleValue(xPos * elementEdge) + ' ' + yPos + ' ';
+ aPath += 'L ' + pruneScaleValue((xPos+1)*elementEdge) + ' ' + yPos + ' ';
+ }
+ aPath += 'L ' + 1.0 + ' ' + -1.0 + ' ';
+ aPath += 'L ' + 0.0 + ' ' + -1.0 + ' ';
+ this.aBasePath = document.createElementNS( NSS['svg'], 'path');
+ this.aBasePath.setAttribute('d', aPath);
+}
+
+WaterfallWipePath.prototype.perform = function( nT ) {
+ var poly = this.aBasePath.cloneNode(true);
+ var aTransform = SVGIdentityMatrix.translate(0.0, pruneScaleValue(2.0 * nT));
+ poly.matrixTransform(aTransform);
+ var aHead = 'M ' + 0.0 + ' ' + -1.0 + ' ';
+ var aHeadPath= document.createElementNS( NSS['svg'], 'path');
+ aHeadPath.setAttribute('d', aHead);
+
+ var aTail = 'M ' + 1.0 + ' ' + -1.0 + ' ';
+ var aTailPath = document.createElementNS( NSS['svg'], 'path');
+ aTailPath.setAttribute('d', aTail);
+
+ poly.prependPath(aHeadPath);
+ poly.appendPath(aTailPath);
+
+ return this.bFlipOnYAxis ? flipOnYAxis(poly) : poly;
+}
+
+/** Class DoubleDiamondWipePath
+ *
+ */
+function DoubleDiamondWipePath() { }
+
+DoubleDiamondWipePath.prototype.perform = function( nT ) {
+ var a = pruneScaleValue(0.25 + (nT * 0.75));
+ var aPath = 'M ' + (0.5 + a) + ' ' + 0.5 + ' ';
+ aPath += 'L ' + 0.5 + ' ' + (0.5 - a) + ' ';
+ aPath += 'L ' + (0.5 - a) + ' ' + 0.5 + ' ';
+ aPath += 'L ' + 0.5 + ' ' + (0.5 + a) + ' ';
+ aPath += 'L ' + (0.5 + a) + ' ' + 0.5 + ' ';
+ var poly = document.createElementNS( NSS['svg'], 'path');
+ poly.setAttribute('d', aPath);
+ var res = poly.cloneNode(true);
+
+ var b = pruneScaleValue( (1.0 - nT) * 0.25);
+ aPath = 'M ' + (0.5 + b) + ' ' + 0.5 + ' ';
+ aPath += 'L ' + 0.5 + ' ' + (0.5 + b) + ' ';
+ aPath += 'L ' + (0.5 - b) + ' ' + 0.5 + ' ';
+ aPath += 'L ' + 0.5 + ' ' + (0.5 - b) + ' ';
+ aPath += 'L ' + (0.5 + b) + ' ' + 0.5 + ' ';
+ poly = document.createElementNS( NSS['svg'], 'path');
+ poly.setAttribute('d', aPath);
+ res.appendPath(poly);
+
+ return res;
+}
+
+/** Class Iriswipe
+ *
+ * @param unitRect
+ *
+ */
+function IrisWipePath(unitRect) {
+ this.unitRect = unitRect;
+ this.aBasePath = createUnitSquarePath();
+}
+
+
+/** perform
+ *
+ * @param nT
+ * A parameter in [0,1] representing the diamond or rectangle.
+ * @return SVGPathElement
+ * A svg <path> element representing a transition.
+ */
+IrisWipePath.prototype.perform = function( nT ) {
+ var d = pruneScaleValue(nT);
+ var aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
+ aTransform = aTransform.multiply(SVGIdentityMatrix.scaleNonUniform(d, d).translate(0.5, 0.5));
+ var aPath = this.aBasePath.cloneNode(true);
+ aPath.matrixTransform(aTransform);
+ return aPath;
+}
+
+/**
+ * Class ZigZagWipePath
+ *
+ * @param nZigs
+ *
+ */
+function ZigZagWipePath(nZigs) {
+ this.zigEdge = 1.0/nZigs;
+ const d = this.zigEdge;
+ const d2 = (d / 2.0);
+ this.aBasePath = 'M ' + (-1.0 - d) + ' ' + -d + ' ';
+ this.aBasePath += 'L ' + (-1.0 - d) + ' ' + (1.0 + d) + ' ';
+ this.aBasePath += 'L ' + -d + ' ' + (1.0 + d) + ' ';
+
+ for(var pos = (nZigs + 2); pos--; ) {
+ this.aBasePath += 'L ' + 0.0 + ' ' + ((pos - 1) * d + d2) + ' ';
+ this.aBasePath += 'L ' + -d + ' ' + (pos - 1) * d + ' ';
+ }
+ this.aBasePath += 'L ' + (-1.0 - d) + ' ' + -d + ' ';
+}
+
+ZigZagWipePath.prototype.perform = function( nT ) {
+ var res = document.createElementNS( NSS['svg'], 'path');
+ res.setAttribute('d', this.aBasePath);
+ res.matrixTransform(SVGIdentityMatrix.translate((1.0 + this.zigEdge) * nT, 0.0));
+ return res;
+}
+
+/*
+ * Class BarnZigZagWipePath
+ *
+ * @param nZigs
+ *
+ */
+function BarnZigZagWipePath( nZigs ) { ZigZagWipePath.call(this, nZigs); }
+
+BarnZigZagWipePath.prototype = Object.create(ZigZagWipePath);
+
+BarnZigZagWipePath.prototype.perform = function( nT ) {
+ var res = createEmptyPath();
+ var poly = document.createElementNS( NSS['svg'], 'path');
+ var aTransform = SVGIdentityMatrix.translate(
+ ((1.0 + this.zigEdge) * (1.0 - nT)) / 2.0, 0.0);
+ poly.setAttribute('d', this.aBasePath);
+ poly.changeOrientation();
+ poly.matrixTransform(aTransform);
+ res.appendPath(poly);
+
+ aTransform = SVGIdentityMatrix.scale(-1.0, 1.0);
+ aTransform.translate(1.0, this.zigEdge / 2.0);
+ poly = document.createElementNS( NSS['svg'], 'path');
+ poly.setAttribute('d', this.aBasePath);
+ poly.matrixTransform(aTransform);
+ res.appendPath(poly);
+
+ return res;
+}
+
+/** Class CheckerBoardWipePath
+ *
+ * @param unitsPerEdge
+ * The number of cells (per line and column) in the checker board.
+ */
+function CheckerBoardWipePath( unitsPerEdge )
+{
+ this.unitsPerEdge = unitsPerEdge;
+ if( this.unitsPerEdge === undefined || this.unitsPerEdge < 1 )
+ this.unitsPerEdge = 10;
+ this.aBasePath = createUnitSquarePath();
+}
+
+/** perform
+ *
+ * @param nT
+ * A parameter in [0,1] representing the width of the generated bars.
+ * @return SVGPathElement
+ * A svg <path> element representing a multi-bars.
+ */
+CheckerBoardWipePath.prototype.perform = function( nT )
+{
+ var d = pruneScaleValue(1.0 / this.unitsPerEdge);
+ var aMatrix = SVGIdentityMatrix.scaleNonUniform(pruneScaleValue( d*2.0*nT ),
+ pruneScaleValue( d ) );
+
+ var aPolyPath = null;
+ var i, j;
+ var aTransform;
+ var aPath;
+ for ( i = this.unitsPerEdge; i--; )
+ {
+ aTransform = SVGIdentityMatrix;
+
+ if ((i % 2) == 1) // odd line
+ aTransform = aTransform.translate( -d, 0.0 );
+
+ aTransform = aTransform.multiply( aMatrix );
+
+ for ( j = (this.unitsPerEdge / 2) + 1; j--;)
+ {
+ aPath = this.aBasePath.cloneNode( true );
+ aPath.matrixTransform( aTransform );
+ if (aPolyPath == null) aPolyPath = aPath;
+ else aPolyPath.appendPath( aPath );
+ aTransform = SVGIdentityMatrix.translate( d*2.0, 0.0 ).multiply( aTransform );
+ }
+
+ aMatrix = SVGIdentityMatrix.translate( 0.0, d ).multiply( aMatrix ); // next line
+ }
+
+ return aPolyPath;
+};
+
+
+
+/** Class RandomWipePath
+ *
+ * @param nElements
+ * The number of bars or cells to be used.
+ * @param bRandomBars
+ * true: generates a horizontal random bar wipe
+ * false: generates a dissolve wipe
+ */
+function RandomWipePath( nElements, bRandomBars )
+{
+ this.nElements = nElements;
+ this.aBasePath = createUnitSquarePath();
+ this.aPositionArray = new Array( nElements );
+ this.aClipPath = createEmptyPath();
+ this.nAlreadyAppendedElements = 0;
+
+ var fEdgeLength, nPos, aTransform;
+
+ if( bRandomBars ) // random bar wipe
+ {
+ fEdgeLength = 1.0 / nElements;
+ for( nPos = 0; nPos < nElements; ++nPos )
+ {
+ this.aPositionArray[nPos] = { x: 0.0, y: pruneScaleValue( nPos * fEdgeLength ) }
+ }
+ aTransform = SVGIdentityMatrix.scaleNonUniform( 1.0, pruneScaleValue( fEdgeLength ) );
+ }
+ else // dissolve wipe
+ {
+ var nSqrtElements = Math.round( Math.sqrt( nElements ) );
+ fEdgeLength = 1.0 / nSqrtElements;
+ for( nPos = 0; nPos < nElements; ++nPos )
+ {
+ this.aPositionArray[nPos] = {
+ x: pruneScaleValue( ( nPos % nSqrtElements ) * fEdgeLength ),
+ y: pruneScaleValue( ( nPos / nSqrtElements ) * fEdgeLength ) }
+ }
+ aTransform = SVGIdentityMatrix.scale( pruneScaleValue( fEdgeLength ) );
+ }
+ this.aBasePath.matrixTransform( aTransform );
+
+ var nPos1, nPos2;
+ var tmp;
+ for( nPos1 = nElements - 1; nPos1 > 0; --nPos1 )
+ {
+ nPos2 = getRandomInt( nPos1 + 1 );
+ tmp = this.aPositionArray[nPos1];
+ this.aPositionArray[nPos1] = this.aPositionArray[nPos2];
+ this.aPositionArray[nPos2] = tmp;
+ }
+}
+
+/** perform
+ *
+ * @param nT
+ * A parameter in [0,1] representing the width of the generated bars or squares.
+ * @return SVGPathElement
+ * A svg <path> element representing a multi bars or a multi squared cells.
+ */
+RandomWipePath.prototype.perform = function( nT )
+{
+ var aPolyPath = createEmptyPath();
+ var aPoint;
+ var aPath;
+ var aTransform;
+ var nElements = Math.round( nT * this.nElements );
+ if( nElements === 0 )
+ {
+ return aPolyPath;
+ }
+ // check if we need to reset the clip path
+ if( this.nAlreadyAppendedElements >= nElements )
+ {
+ this.nAlreadyAppendedElements = 0;
+ this.aClipPath = createEmptyPath();
+ }
+ var nPos;
+ for( nPos = this.nAlreadyAppendedElements; nPos < nElements; ++nPos )
+ {
+ aPoint = this.aPositionArray[nPos];
+ aPath = this.aBasePath.cloneNode( true );
+ aTransform = SVGIdentityMatrix.translate( aPoint.x, aPoint.y );
+ aPath.matrixTransform( aTransform );
+ aPolyPath.appendPath( aPath );
+ }
+
+ this.nAlreadyAppendedElements = nElements;
+ this.aClipPath.appendPath( aPolyPath );
+
+ return this.aClipPath.cloneNode( true );
+};
+
+/** Class SnakeWipeSlide
+ *
+ * @param nElements
+ * @param bDiagonal
+ * @param bFlipOnYaxis
+ */
+function SnakeWipePath(nElements, bDiagonal, bflipOnYAxis)
+{
+ this.sqrtElements = Math.floor(Math.sqrt(nElements));
+ this.elementEdge = (1.0 / this.sqrtElements);
+ this.diagonal = bDiagonal;
+ this.flipOnYAxis = bflipOnYAxis;
+ this.aBasePath = createUnitSquarePath();
+}
+
+SnakeWipePath.prototype.calcSnake = function(t)
+{
+ var aPolyPath = createEmptyPath();
+ const area = (t * this.sqrtElements * this.sqrtElements);
+ const line_ = Math.floor(area) / this.sqrtElements;
+ const line = pruneScaleValue(line_ / this.sqrtElements);
+ const col = pruneScaleValue((area - (line_ * this.sqrtElements)) / this.sqrtElements);
+
+ if(line != 0) {
+ let aPath = 'M '+ 0.0 + ' ' + 0.0 + ' ';
+ aPath += 'L ' + 0.0 + ' ' + line + ' ';
+ aPath += 'L ' + 1.0 + ' ' + line + ' ';
+ aPath += 'L ' + 1.0 + ' ' + 0.0 + ' ';
+ aPath += 'L 0 0 ';
+ let poly = document.createElementNS( NSS['svg'], 'path');
+ poly.setAttribute('d', aPath);
+ aPolyPath.appendPath(poly);
+ }
+ if(col != 0) {
+ var offset = 0.0;
+ if((line_ & 1) == 1) {
+ // odd line: => right to left
+ offset = (1.0 - col);
+ }
+ let aPath = 'M ' + offset + ' ' + line + ' ';
+ aPath += 'L '+ offset + ' ' + (line + this.elementEdge) + ' ';
+ aPath += 'L ' + (offset+col) + ' ' + (line + this.elementEdge) + ' ';
+ aPath += 'L ' + (offset+col) + ' ' + line + ' ';
+ aPath += 'L ' + offset + ' ' + line + ' ';
+ let poly = document.createElementNS( NSS['svg'], 'path');
+ poly.setAttribute('d', aPath);
+ aPolyPath.appendPath(poly);
+ }
+
+ return aPolyPath;
+}
+
+SnakeWipePath.prototype.calcHalfDiagonalSnake = function(nT, bIn) {
+ var res = createEmptyPath();
+
+ if(bIn) {
+ const sqrtArea2 = Math.sqrt(nT * this.sqrtElements * this.sqrtElements);
+ const edge = pruneScaleValue(sqrtArea2 / this.sqrtElements);
+
+ var aPath, aPoint = document.documentElement.createSVGPoint();
+ if(edge) {
+ aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.y = edge;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.x = edge;
+ aPoint.y = 0.0;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.x = 0.0;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ const poly = document.createElementNS( NSS['svg'], 'path');
+ poly.setAttribute('d', aPath);
+ res.appendPath(poly);
+ }
+ const a = (Math.SQRT1_2 / this.sqrtElements);
+ const d = (sqrtArea2 - Math.floor(sqrtArea2));
+ const len = (nT * Math.SQRT1_2 * d);
+ const height = pruneScaleValue(Math.SQRT1_2 / this.sqrtElements);
+ aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.y = height;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.x = len + a;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.y = 0.0;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.x = 0.0;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ const poly = document.createElementNS( NSS['svg'], 'path');
+ poly.setAttribute('d', aPath);
+ let aTransform;
+
+ if((Math.floor(sqrtArea2) & 1) == 1) {
+ // odd line
+ aTransform = SVGIdentityMatrix.rotate((Math.PI)/2 + (Math.PI)/4);
+ aTransform.translate(edge + this.elementEdge, 0.0);
+ }
+ else {
+ aTransform = SVGIdentityMatrix.translate(-a, 0.0);
+ aTransform.rotate(-(Math.PI/4));
+ aTransform.translate(0.0, edge);
+ }
+
+ poly.matrixTransform(aTransform);
+ res.appendPath(poly);
+ }
+ else { //out
+ const sqrtArea2 = Math.sqrt(nT * this.sqrtElements * this.sqrtElements);
+ const edge = pruneScaleValue(Math.floor(sqrtArea2)/this.sqrtElements);
+
+ let aPath, aPoint = document.documentElement.createSVGPoint();
+ if(edge != 0) {
+ aPoint.y = 1.0;
+ aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.x = edge;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.x = 1.0;
+ aPoint.y = edge;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.y = 0.0;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.x = 0.0;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ const poly = document.createElementNS( NSS['svg'], 'path');
+ poly.setAttribute('d', aPath);
+ res.appendPath(poly);
+ }
+ const a = (Math.SQRT1_2 / this.sqrtElements);
+ const d = (sqrtArea2 - Math.floor(sqrtArea2));
+ const len = ((1.0 - nT) * Math.SQRT2 * d);
+ const height = pruneScaleValue(Math.SQRT1_2 / this.sqrtElements);
+ aPath = 'M ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.y = height;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.x = len + a;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.y = 0.0;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ aPoint.x = 0.0;
+ aPath += 'L ' + aPoint.x + ' ' + aPoint.y + ' ';
+ const poly = document.createElementNS( NSS['svg'], 'path');
+ poly.setAttribute('d', aPath);
+ let aTransform;
+
+ if((Math.floor(sqrtArea2) & 1) == 1) {
+ // odd line
+ aTransform = SVGIdentityMatrix.translate(0.0, -height);
+ aTransform.rotate(Math.PI/2 + Math.PI/4);
+ aTransform.translate(1.0, edge);
+ }
+ else {
+ aTransform = SVGIdentityMatrix.rotate(-(Math.PI/4));
+ aTransform = aTransform.translate(edge, 1.0);
+ }
+ poly.matrixTransform(aTransform);
+ res.appendPath(poly);
+ }
+ return res;
+}
+
+SnakeWipePath.prototype.perform = function(nT) {
+ var res = createEmptyPath();
+ if(this.diagonal) {
+ if(nT >= 0.5) {
+ res.appendPath(this.calcHalfDiagonalSnake(1.0, true));
+ res.appendPath(this.calcHalfDiagonalSnake(2.0*(nT-0.5), false));
+ }
+ else
+ res.appendPath(this.calcHalfDiagonalSnake(2.0*nT, true));
+ }
+ else
+ res = this.calcSnake(nT);
+
+ return this.flipOnYAxis ? flipOnYAxis(res) : res;
+}
+
+/** Class ParallelSnakesWipePath
+ * Generates a parallel snakes wipe:
+ *
+ * @param nElements
+ * @param bDiagonal
+ * @param bFlipOnYAxis
+ * @param bOpposite
+ */
+function ParallelSnakesWipePath(nElements, bDiagonal, bFlipOnYAxis, bOpposite) {
+ SnakeWipePath.call(this, nElements, bDiagonal, bFlipOnYAxis);
+ this.bOpposite = bOpposite;
+}
+
+ParallelSnakesWipePath.prototype = Object.create(SnakeWipePath);
+
+ParallelSnakesWipePath.prototype.perform = function( nT ) {
+ var res = createEmptyPath(), half, aTransform;
+ if(this.diagonal) {
+ assert(this.bOpposite);
+ half = SnakeWipePath.prototype.calcHalfDiagonalSnake.call(this, nT, false);
+ // flip on x axis and rotate 90 degrees:
+ aTransform = SVGIdentityMatrix.scale(1, -1);
+ aTransform.translate(-0.5, 0.5);
+ aTransform.rotate(Math.PI/2);
+ aTransform.translate(0.5, 0.5);
+ half.matrixTransform(aTransform);
+ half.changeOrientation();
+ res.appendPath(half);
+
+ // rotate 180 degrees:
+ aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
+ aTransform.rotate(Math.PI);
+ aTransform.translate(0.5, 0.5);
+ half.matrixTransform(aTransform);
+ res.appendPath(half);
+ }
+ else {
+ half = SnakeWipePath.prototype.calcSnake.call(this, nT / 2.0 );
+ // rotate 90 degrees
+ aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
+ aTransform = aTransform.rotate(Math.PI/2);
+ aTransform = aTransform.translate(0.5, 0.5);
+ half.matrixTransform(aTransform);
+ res.appendPath(flipOnYAxis(half));
+ res.appendPath(this.bOpposite ? flipOnXAxis(half) : half);
+ }
+
+ return this.flipOnYAxis ? flipOnYAxis(res) : res;
+}
+
+/** SpiralWipePath
+ *
+ * @param nElements
+ * number of elements in the spiral animation
+ * @param bFlipOnYAxis
+ * boolean value indicating whether to flip on y-axis or not.
+ */
+function SpiralWipePath(nElements, bFlipOnYAxis) {
+ this.nElements = nElements;
+ this.sqrtElements = Math.floor(Math.sqrt(nElements));
+ this.bFlipOnYAxis = bFlipOnYAxis;
+}
+
+SpiralWipePath.prototype.calcNegSpiral = function( nT ) {
+ var area = nT * this.nElements;
+ var e = (Math.sqrt(area) / 2.0);
+ var edge = Math.floor(e) * 2;
+
+ var aTransform = SVGIdentityMatrix.translate(-0.5, -0.5);
+ var edge_ = pruneScaleValue(edge / this.sqrtElements);
+
+ aTransform = aTransform.scale(edge_, edge_);
+ aTransform = aTransform.translate(0.5, 0.5);
+ var poly = createUnitSquarePath();
+ poly.matrixTransform(aTransform);
+ var res = poly.cloneNode(true);
+
+ if(1.0 - nT != 0) {
+ var edge1 = edge + 1;
+ var len = Math.floor( (e - edge/2) * edge1 * 4);
+ var w = Math.PI / 2;
+
+ while(len > 0) {
+ var alen = Math.min(len, edge1);
+ len -= alen;
+ poly = createUnitSquarePath();
+ aTransform = SVGIdentityMatrix.scale(
+ pruneScaleValue( alen / this.sqrtElements ),
+ pruneScaleValue( 1.0 / this.sqrtElements ));
+ aTransform = aTransform.translate(
+ - pruneScaleValue( (edge / 2) / this.sqrtElements ),
+ pruneScaleValue( (edge / 2) / this.sqrtElements ));
+ aTransform = aTransform.rotate( w );
+ w -= Math.PI / 2;
+ aTransform = aTransform.translate(0.5, 0.5);
+ poly.matrixTransform(aTransform);
+ res.appendPath(poly);
+ }
+ }
+
+ return res;
+}
+
+SpiralWipePath.prototype.perform = function( nT ) {
+ var res = createUnitSquarePath();
+ var innerSpiral = this.calcNegSpiral( 1.0 - nT );
+ innerSpiral.changeOrientation();
+ res.appendPath(innerSpiral);
+
+ return this.bFlipOnYAxis ? flipOnYAxis(res) : res;
+}
+
+/** Class BoxSnakesWipePath
+ * Generates a twoBoxLeft or fourBoxHorizontal wipe:
+ *
+ */
+function BoxSnakesWipePath(nElements, bFourBox) {
+ SpiralWipePath.call(this, nElements);
+ this.bFourBox = bFourBox;
+}
+
+BoxSnakesWipePath.prototype = Object.create(SpiralWipePath);
+
+BoxSnakesWipePath.prototype.perform = function( nT ) {
+ var res = createUnitSquarePath(), aTransform;
+ var innerSpiral = SpiralWipePath.prototype.calcNegSpiral.call(this, 1.0 - nT);
+ innerSpiral.changeOrientation();
+
+ if(this.bFourBox) {
+ aTransform = SVGIdentityMatrix.scale(0.5, 0.5);
+ innerSpiral.matrixTransform(aTransform);
+ res.appendPath(innerSpiral);
+ res.appendPath(flipOnXAxis(innerSpiral));
+ innerSpiral = flipOnYAxis(innerSpiral);
+ res.appendPath(innerSpiral);
+ res.appendPath(flipOnXAxis(innerSpiral));
+ }
+ else {
+ aTransform = SVGIdentityMatrix.scale(1.0, 0.5);
+ innerSpiral.matrixTransform(aTransform);
+ res.appendPath(innerSpiral);
+ res.appendPath(flipOnXAxis(innerSpiral));
+ }
+ return this.bFlipOnYAxis ? flipOnYAxis(res) : res;
+}
+
+/** Class VeeWipePath
+ *
+ */
+function VeeWipePath() { }
+
+VeeWipePath.prototype.perform = function( nT ) {
+ const d = pruneScaleValue(2.0 * nT);
+ var polyPath = 'M ' + 0.0 + ' ' + -1.0 + ' ';
+ polyPath += 'L ' + 0.0 + ' ' + (d - 1.0) + ' ';
+ polyPath += 'L ' + 0.5 + ' ' + d + ' ';
+ polyPath += 'L ' + 1.0 + ' ' + (d - 1.0) + ' ';
+ polyPath += 'L ' + 1.0 + ' ' + -1.0 + ' ';
+ polyPath += 'L ' + 0.0 + ' ' + -1.0 + ' ';
+
+ var aPolyPolyPath = document.createElementNS( NSS['svg'], 'path');
+ aPolyPolyPath.setAttribute('d', polyPath);
+ return aPolyPolyPath;
+}
+
+
+/** Class AnimatedSlide
+ * This class handle a slide element during a slide transition.
+ *
+ * @param aMetaSlide
+ * The MetaSlide object related to the slide element to be handled.
+ */
+function AnimatedSlide( aMetaSlide )
+{
+ if( !aMetaSlide )
+ {
+ log( 'AnimatedSlide constructor: meta slide is not valid' );
+ }
+
+ this.aMetaSlide = aMetaSlide;
+ this.aSlideElement = this.aMetaSlide.slideElement;
+ this.sSlideId = this.aMetaSlide.slideId;
+
+ this.aUsedAttributeSet = [];
+
+ this.aClipPathElement = null;
+ this.aClipPathContent = null;
+ this.bIsClipped = false;
+}
+
+/** show
+ * Set the visibility property of the slide to 'inherit'
+ * and update the master page view.
+ */
+AnimatedSlide.prototype.show = function()
+{
+ this.aMetaSlide.show();
+};
+
+/** hide
+ * Set the visibility property of the slide to 'hidden'.
+ */
+AnimatedSlide.prototype.hide = function()
+{
+ this.aMetaSlide.hide();
+};
+
+/** notifyUsedAttribute
+ * Populate the set of attribute used for the transition.
+ *
+ * @param sName
+ * A string representing an attribute name.
+ */
+AnimatedSlide.prototype.notifyUsedAttribute = function( sName )
+{
+ if( sName == 'clip-path' )
+ {
+ this.initClipPath();
+ this.bIsClipped = true;
+ }
+ else
+ {
+ this.aUsedAttributeSet.push( sName );
+ }
+};
+
+/** reset
+ * Remove from the handled slide element any attribute that was appended for
+ * performing the transition.
+ */
+AnimatedSlide.prototype.reset = function()
+{
+ if( this.bIsClipped )
+ {
+ this.cleanClipPath();
+ this.bIsClipped = false;
+ }
+
+ var i;
+ for( i = 0; i < this.aUsedAttributeSet.length; ++i )
+ {
+ var sAttrName = this.aUsedAttributeSet[i];
+ this.aSlideElement.removeAttribute( sAttrName );
+ }
+ this.aUsedAttributeSet = [];
+};
+
+/** initClipPath
+ * Create a new clip path element and append it to the clip path def group.
+ * Moreover the created <clipPath> element is referenced by the handled slide
+ * element.
+ */
+AnimatedSlide.prototype.initClipPath = function()
+{
+ // We create the clip path element.
+ this.aClipPathElement = document.createElementNS( NSS['svg'], 'clipPath' );
+
+ var sId = 'clip-path-' + this.sSlideId;
+ this.aClipPathElement.setAttribute( 'id', sId );
+ this.aClipPathElement.setAttribute( 'clipPathUnits', 'userSpaceOnUse' );
+
+ // We create and append a placeholder content.
+ this.aClipPathContent = document.createElementNS( NSS['svg'], 'path' );
+ var sPathData = 'M 0 0 h ' + WIDTH + ' v ' + HEIGHT + ' h -' + WIDTH + ' z';
+ this.aClipPathContent.setAttribute( 'd', sPathData );
+ this.aClipPathElement.appendChild( this.aClipPathContent );
+
+ // We insert it into the svg document.
+ var aClipPathGroup = theMetaDoc.aClipPathGroup;
+ aClipPathGroup.appendChild( this.aClipPathElement );
+
+ // Finally we set the reference to the created clip path.
+ // We set it on the parent element because a slide element already
+ // owns a clip path attribute.
+ var sRef = 'url(#' + sId + ')';
+ this.aSlideElement.parentNode.setAttribute( 'clip-path', sRef );
+};
+
+/** cleanClipPath
+ * Removes the related <clipPath> element from the <defs> group,
+ * and remove the 'clip-path' attribute from the slide element.
+ *
+ */
+AnimatedSlide.prototype.cleanClipPath = function()
+{
+ this.aSlideElement.parentNode.removeAttribute( 'clip-path' );
+
+ if( this.aClipPathElement )
+ {
+ var aClipPathGroup = theMetaDoc.aClipPathGroup;
+ aClipPathGroup.removeChild( this.aClipPathElement );
+ this.aClipPathElement = null;
+ this.aClipPathContent = null;
+ }
+};
+
+/** insertBefore
+ * Insert an svg element before the handled slide element.
+ *
+ * @param aElement
+ * A svg element.
+ */
+AnimatedSlide.prototype.insertBefore = function( aElement )
+{
+ if( aElement )
+ {
+ this.aSlideElement.parentNode.insertBefore( aElement, this.aSlideElement );
+ }
+};
+
+/** appendElement
+ * Insert an svg element after the handled slide element.
+ *
+ * @param aElement
+ * A svg element.
+ */
+AnimatedSlide.prototype.appendElement = function( aElement )
+{
+ if( aElement )
+ {
+ this.aSlideElement.parentNode.appendChild( aElement );
+ }
+};
+
+/** removeElement
+ * Remove an svg element.
+ *
+ * @param aElement
+ * A svg element.
+ */
+AnimatedSlide.prototype.removeElement = function( aElement )
+{
+ if( aElement )
+ {
+ this.aSlideElement.parentNode.removeChild( aElement );
+ }
+};
+
+/** getWidth
+ *
+ * @return {Number}
+ * The slide width.
+ */
+AnimatedSlide.prototype.getWidth = function()
+{
+ return WIDTH;
+};
+
+/** getHeight
+ *
+ * @return {Number}
+ * The slide height.
+ */
+AnimatedSlide.prototype.getHeight = function()
+{
+ return HEIGHT;
+};
+
+/** setOpacity
+ *
+ * @param nValue
+ * A number in the [0,1] range representing the slide opacity.
+ */
+AnimatedSlide.prototype.setOpacity = function( nValue )
+{
+ this.aSlideElement.setAttribute( 'opacity', nValue );
+};
+
+/** translate
+ * Translate the handled slide.
+ *
+ * @param nDx
+ * A number representing the translation that occurs in the x direction.
+ * @param nDy
+ * A number representing the translation that occurs in the y direction.
+ */
+AnimatedSlide.prototype.translate = function( nDx, nDy )
+{
+ var sTransformAttr = 'translate(' + nDx + ',' + nDy + ')';
+ this.aSlideElement.setAttribute( 'transform', sTransformAttr );
+};
+
+/** setClipPath
+ * Replace the current content of the <clipPath> element with the one
+ * passed through the parameter.
+ *
+ * @param aClipPathContent
+ * A <g> element representing a <path> element used for clipping.
+ */
+AnimatedSlide.prototype.setClipPath = function( aClipPathContent )
+{
+ // Earlier we used to replace the current <path> element with the passed one,
+ // anyway that does not work in IE9, so we replace the 'd' attribute, only.
+ if( this.aClipPathContent )
+ {
+ var sPathData = aClipPathContent.getAttribute( 'd' );
+ this.aClipPathContent.setAttribute( 'd', sPathData );
+ }
+};
+
+
+
+function AnimatedElement( aElement )
+{
+ if( !aElement )
+ {
+ log( 'AnimatedElement constructor: element is not valid' );
+ }
+
+ this.aSlideShowContext = null;
+
+ this.aBaseElement = aElement.cloneNode( true );
+ this.aActiveElement = aElement;
+ this.sElementId = this.aActiveElement.getAttribute( 'id' );
+
+ this.aBaseBBox = this.aActiveElement.getBBox();
+ this.nBaseCenterX = this.aBaseBBox.x + this.aBaseBBox.width / 2;
+ this.nBaseCenterY = this.aBaseBBox.y + this.aBaseBBox.height / 2;
+
+
+ this.aClipPathElement = null;
+ this.aClipPathContent = null;
+
+ this.aPreviousElement = null;
+ this.aStateSet = {};
+
+ this.eAdditiveMode = ADDITIVE_MODE_REPLACE;
+ this.bIsUpdated = true;
+
+ this.aTMatrix = document.documentElement.createSVGMatrix();
+ this.aCTM = document.documentElement.createSVGMatrix();
+ this.aICTM = document.documentElement.createSVGMatrix();
+
+ this.initElement();
+}
+
+AnimatedElement.prototype.initElement = function()
+{
+ this.nCenterX = this.nBaseCenterX;
+ this.nCenterY = this.nBaseCenterY;
+ this.nScaleFactorX = 1.0;
+ this.nScaleFactorY = 1.0;
+ this.nRotationAngle = 0.0;
+
+ // add a transform attribute of type matrix
+ this.aActiveElement.setAttribute( 'transform', makeMatrixString( 1, 0, 0, 1, 0, 0 ) );
+};
+
+/** initClipPath
+ * Create a new clip path element and append it to the clip path def group.
+ * Moreover the created <clipPath> element is referenced by the handled
+ * animated element.
+ *
+ */
+AnimatedElement.prototype.initClipPath = function()
+{
+ // We create the clip path element.
+ this.aClipPathElement = document.createElementNS( NSS['svg'], 'clipPath' );
+
+ var sId = 'clip-path-' + this.sElementId;
+ this.aClipPathElement.setAttribute( 'id', sId );
+ this.aClipPathElement.setAttribute( 'clipPathUnits', 'userSpaceOnUse' );
+
+ // We create and append a placeholder content.
+ this.aClipPathContent = document.createElementNS( NSS['svg'], 'path' );
+ this.aClippingBBox = this.getBBoxWithStroke();
+ var nWidth = this.aClippingBBox.width;
+ var nHeight = this.aClippingBBox.height;
+ var sPathData = 'M ' + this.aClippingBBox.x + ' ' + this.aClippingBBox.y +
+ ' h ' + nWidth + ' v ' + nHeight + ' h -' + nWidth + ' z';
+ this.aClipPathContent.setAttribute( 'd', sPathData );
+ this.aClipPathElement.appendChild( this.aClipPathContent );
+
+ // We insert it into the svg document.
+ var aClipPathGroup = theMetaDoc.aClipPathGroup;
+ aClipPathGroup.appendChild( this.aClipPathElement );
+
+ // Finally we set the reference to the created clip path.
+ var sRef = 'url(#' + sId + ')';
+ this.aActiveElement.setAttribute( 'clip-path', sRef );
+};
+
+/** cleanClipPath
+ * Removes the related <clipPath> element from the <defs> group,
+ * and remove the 'clip-path' attribute from the animated element.
+ *
+ */
+AnimatedElement.prototype.cleanClipPath = function()
+{
+ this.aActiveElement.removeAttribute( 'clip-path' );
+
+ if( this.aClipPathElement )
+ {
+ var aClipPathGroup = theMetaDoc.aClipPathGroup;
+ aClipPathGroup.removeChild( this.aClipPathElement );
+ this.aClipPathElement = null;
+ this.aClipPathContent = null;
+ }
+};
+
+AnimatedElement.prototype.getId = function()
+{
+ return this.aActiveElement.getAttribute( 'id' );
+};
+
+AnimatedElement.prototype.getAdditiveMode = function()
+{
+ return this.eAdditiveMode;
+};
+
+AnimatedElement.prototype.setAdditiveMode = function( eAdditiveMode )
+{
+ this.eAdditiveMode = eAdditiveMode;
+};
+
+AnimatedElement.prototype.setToElement = function( aElement )
+{
+ if( !aElement )
+ {
+ log( 'AnimatedElement(' + this.getId() + ').setToElement: element is not valid' );
+ return false;
+ }
+
+ var aClone = aElement.cloneNode( true );
+ this.aPreviousElement = this.aActiveElement.parentNode.replaceChild( aClone, this.aActiveElement );
+ this.aActiveElement = aClone;
+
+ return true;
+};
+
+AnimatedElement.prototype.notifySlideStart = function( aSlideShowContext )
+{
+ if( !aSlideShowContext )
+ {
+ log( 'AnimatedElement.notifySlideStart: slideshow context is not valid' );
+ }
+ this.aSlideShowContext = aSlideShowContext;
+
+ var aClone = this.aBaseElement.cloneNode( true );
+ this.aActiveElement.parentNode.replaceChild( aClone, this.aActiveElement );
+ this.aActiveElement = aClone;
+
+ this.initElement();
+ this.DBG( '.notifySlideStart invoked' );
+};
+
+AnimatedElement.prototype.notifySlideEnd = function()
+{
+ // empty body
+};
+
+AnimatedElement.prototype.notifyAnimationStart = function()
+{
+ // empty body
+};
+
+AnimatedElement.prototype.notifyAnimationEnd = function()
+{
+ // empty body
+};
+
+AnimatedElement.prototype.notifyNextEffectStart = function( /*nEffectIndex*/ )
+{
+ // empty body
+};
+
+/** saveState
+ * Save the state of the managed animated element and append it to aStateSet
+ * using the passed animation node id as key.
+ *
+ * @param nAnimationNodeId
+ * A non negative integer representing the unique id of an animation node.
+ */
+AnimatedElement.prototype.saveState = function( nAnimationNodeId )
+{
+ ANIMDBG.print( 'AnimatedElement(' + this.getId() + ').saveState(' + nAnimationNodeId +')' );
+ if( !this.aStateSet[ nAnimationNodeId ] )
+ {
+ this.aStateSet[ nAnimationNodeId ] = {};
+ }
+ var aState = this.aStateSet[ nAnimationNodeId ];
+ aState.aElement = this.aActiveElement.cloneNode( true );
+ aState.nCenterX = this.nCenterX;
+ aState.nCenterY = this.nCenterY;
+ aState.nScaleFactorX = this.nScaleFactorX;
+ aState.nScaleFactorY = this.nScaleFactorY;
+ aState.nRotationAngle = this.nRotationAngle;
+
+};
+
+/** restoreState
+ * Restore the state of the managed animated element to the state with key
+ * the passed animation node id.
+ *
+ * @param nAnimationNodeId
+ * A non negative integer representing the unique id of an animation node.
+ *
+ * @return
+ * True if the restoring operation is successful, false otherwise.
+ */
+AnimatedElement.prototype.restoreState = function( nAnimationNodeId )
+{
+ if( !this.aStateSet[ nAnimationNodeId ] )
+ {
+ log( 'AnimatedElement(' + this.getId() + ').restoreState: state '
+ +nAnimationNodeId + ' is not valid' );
+ return false;
+ }
+
+ ANIMDBG.print( 'AnimatedElement(' + this.getId() + ').restoreState(' + nAnimationNodeId +')' );
+ var aState = this.aStateSet[ nAnimationNodeId ];
+ var bRet = this.setToElement( aState.aElement );
+ if( bRet )
+ {
+ this.nCenterX = aState.nCenterX;
+ this.nCenterY = aState.nCenterY;
+ this.nScaleFactorX = aState.nScaleFactorX;
+ this.nScaleFactorY = aState.nScaleFactorY;
+ this.nRotationAngle = aState.nRotationAngle;
+ }
+ return bRet;
+};
+
+AnimatedElement.prototype.getBaseBBox = function()
+{
+ return this.aBaseBBox;
+};
+
+AnimatedElement.prototype.getBaseCenterX = function()
+{
+ return this.nBaseCenterX;
+};
+
+AnimatedElement.prototype.getBaseCenterY = function()
+{
+ return this.nBaseCenterY;
+};
+
+AnimatedElement.prototype.getBBox = function()
+{
+ return this.aActiveElement.parentNode.getBBox();
+};
+
+AnimatedElement.prototype.getBBoxWithStroke = function()
+{
+ var aBBox = this.aActiveElement.parentNode.getBBox();
+
+ var aChildrenSet = this.aActiveElement.childNodes;
+
+ var sStroke, sStrokeWidth;
+ var nStrokeWidth = 0;
+ var i;
+ for( i = 0; i < aChildrenSet.length; ++i )
+ {
+ if( ! aChildrenSet[i].getAttribute )
+ continue;
+
+ sStroke = aChildrenSet[i].getAttribute( 'stroke' );
+ if( sStroke && sStroke != 'none' )
+ {
+ sStrokeWidth = aChildrenSet[i].getAttribute( 'stroke-width' );
+ var nSW = parseFloat( sStrokeWidth );
+ if( nSW > nStrokeWidth )
+ nStrokeWidth = nSW;
+ }
+ }
+
+ if( nStrokeWidth == 0 )
+ {
+ sStrokeWidth = ROOT_NODE.getAttribute( 'stroke-width' );
+ nStrokeWidth = parseFloat( sStrokeWidth );
+ }
+ if( nStrokeWidth != 0 )
+ {
+ // It is hard to clip properly the stroke so we try to enlarge
+ // the resulting bounding box even more.
+ nStrokeWidth *= 1.1;
+ var nHalfStrokeWidth = nStrokeWidth / 2;
+ var nDoubleStrokeWidth = nStrokeWidth * 2;
+
+ // Note: IE10 don't let modify the values of an element BBox.
+ var aEBBox = document.documentElement.createSVGRect();
+ aEBBox.x = aBBox.x - nHalfStrokeWidth;
+ aEBBox.y = aBBox.y - nHalfStrokeWidth;
+ aEBBox.width = aBBox.width + nDoubleStrokeWidth;
+ aEBBox.height = aBBox.height + nDoubleStrokeWidth;
+ aBBox = aEBBox;
+ }
+ return aBBox;
+};
+
+/** setClipPath
+ * Replace the current content of the <clipPath> element with the one
+ * passed through the parameter.
+ *
+ * @param aClipPathContent
+ * A <g> element representing a <path> element used for clipping.
+ */
+AnimatedElement.prototype.setClipPath = function( aClipPathContent )
+{
+ if( this.aClipPathContent )
+ {
+ // We need to translate the clip path to the top left corner of
+ // the element bounding box.
+ var aTranslation = SVGIdentityMatrix.translate( this.aClippingBBox.x,
+ this.aClippingBBox.y);
+ aClipPathContent.matrixTransform( aTranslation );
+ var sPathData = aClipPathContent.getAttribute( 'd' );
+ this.aClipPathContent.setAttribute( 'd', sPathData );
+ }
+};
+
+
+AnimatedElement.prototype.getX = function()
+{
+ return this.nCenterX;
+};
+
+AnimatedElement.prototype.getY = function()
+{
+ return this.nCenterY;
+};
+
+AnimatedElement.prototype.getPos = function()
+{
+ return [this.getX(), this.getY()];
+};
+
+AnimatedElement.prototype.getWidth = function()
+{
+ return this.nScaleFactorX * this.getBaseBBox().width;
+};
+
+AnimatedElement.prototype.getHeight = function()
+{
+ return this.nScaleFactorY * this.getBaseBBox().height;
+};
+
+AnimatedElement.prototype.getSize = function()
+{
+ return [this.getWidth(), this.getHeight()];
+};
+
+AnimatedElement.prototype.updateTransformAttribute = function()
+{
+ this.aTransformAttrList = this.aActiveElement.transform.baseVal;
+ this.aTransformAttr = this.aTransformAttrList.getItem( 0 );
+ this.aTransformAttr.setMatrix( this.aTMatrix );
+};
+
+AnimatedElement.prototype.setX = function( nNewCenterX )
+{
+ if( nNewCenterX === this.nCenterX ) return;
+
+ this.aTransformAttrList = this.aActiveElement.transform.baseVal;
+ this.aTransformAttr = this.aTransformAttrList.getItem( 0 );
+ this.aTMatrix = this.aTransformAttr.matrix.translate( nNewCenterX - this.nCenterX, 0 );
+ this.aTransformAttr.setMatrix( this.aTMatrix );
+ this.nCenterX = nNewCenterX;
+};
+
+AnimatedElement.prototype.setY = function( nNewCenterY )
+{
+ if( nNewCenterY === this.nCenterY ) return;
+
+ this.aTransformAttrList = this.aActiveElement.transform.baseVal;
+ this.aTransformAttr = this.aTransformAttrList.getItem( 0 );
+ this.aTMatrix = this.aTransformAttr.matrix.translate( 0, nNewCenterY - this.nCenterY );
+ this.aTransformAttr.setMatrix( this.aTMatrix );
+ this.nCenterY = nNewCenterY;
+};
+
+AnimatedElement.prototype.setPos = function( aNewPos )
+{
+ var nNewCenterX = aNewPos[0];
+ var nNewCenterY = aNewPos[1];
+
+ if( nNewCenterX === this.nCenterX && nNewCenterY === this.nCenterY ) return;
+
+ this.aTransformAttrList = this.aActiveElement.transform.baseVal;
+ this.aTransformAttr = this.aTransformAttrList.getItem( 0 );
+ this.aTMatrix = this.aTransformAttr.matrix.translate( nNewCenterX - this.nCenterX, nNewCenterY - this.nCenterY );
+ this.aTransformAttr.setMatrix( this.aTMatrix );
+ this.nCenterX = nNewCenterX;
+ this.nCenterY = nNewCenterY;
+};
+
+AnimatedElement.prototype.setWidth = function( nNewWidth )
+{
+ ANIMDBG.print( 'AnimatedElement.setWidth: nNewWidth = ' + nNewWidth );
+ if( nNewWidth < 0 )
+ {
+ log('AnimatedElement(' + this.getId() + ').setWidth: negative width!');
+ nNewWidth = 0;
+ }
+
+ var nBaseWidth = this.getBaseBBox().width;
+ var nScaleFactorX = nNewWidth / nBaseWidth;
+
+ if( nScaleFactorX < 1e-5 ) nScaleFactorX = 1e-5;
+ if( nScaleFactorX == this.nScaleFactorX ) return;
+
+ this.aTMatrix = document.documentElement.createSVGMatrix()
+ .translate( this.nCenterX, this.nCenterY )
+ .rotate(this.nRotationAngle)
+ .scaleNonUniform( nScaleFactorX, this.nScaleFactorY )
+ .translate( -this.nBaseCenterX, -this.nBaseCenterY );
+ this.updateTransformAttribute();
+
+ this.nScaleFactorX = nScaleFactorX;
+};
+
+AnimatedElement.prototype.setHeight = function( nNewHeight )
+{
+ ANIMDBG.print( 'AnimatedElement.setWidth: nNewHeight = ' + nNewHeight );
+ if( nNewHeight < 0 )
+ {
+ log('AnimatedElement(' + this.getId() + ').setWidth: negative height!');
+ nNewHeight = 0;
+ }
+
+ var nBaseHeight = this.getBaseBBox().height;
+ var nScaleFactorY = nNewHeight / nBaseHeight;
+
+ if( nScaleFactorY < 1e-5 ) nScaleFactorY = 1e-5;
+ if( nScaleFactorY == this.nScaleFactorY ) return;
+
+ this.aTMatrix = document.documentElement.createSVGMatrix()
+ .translate( this.nCenterX, this.nCenterY )
+ .rotate(this.nRotationAngle)
+ .scaleNonUniform( this.nScaleFactorX, nScaleFactorY )
+ .translate( -this.nBaseCenterX, -this.nBaseCenterY );
+ this.updateTransformAttribute();
+
+ this.nScaleFactorY = nScaleFactorY;
+};
+
+AnimatedElement.prototype.setSize= function( aNewSize )
+{
+ var nNewWidth = aNewSize[0];
+ var nNewHeight = aNewSize[1];
+ ANIMDBG.print( 'AnimatedElement.setSize: = [' + nNewWidth + ',' + nNewHeight + ']');
+ if( nNewWidth < 0 )
+ {
+ log('AnimatedElement(' + this.getId() + ').setSize: negative width!');
+ nNewWidth = 0;
+ }
+ if( nNewHeight < 0 )
+ {
+ log('AnimatedElement(' + this.getId() + ').setSize: negative height!');
+ nNewHeight = 0;
+ }
+
+ var nBaseWidth = this.getBaseBBox().width;
+ var nScaleFactorX = nNewWidth / nBaseWidth;
+ if( nScaleFactorX < 1e-5 ) nScaleFactorX = 1e-5;
+
+ var nBaseHeight = this.getBaseBBox().height;
+ var nScaleFactorY = nNewHeight / nBaseHeight;
+ if( nScaleFactorY < 1e-5 ) nScaleFactorY = 1e-5;
+
+ if( nScaleFactorX == this.nScaleFactorX && nScaleFactorY == this.nScaleFactorY ) return;
+
+ this.aTMatrix = document.documentElement.createSVGMatrix()
+ .translate( this.nCenterX, this.nCenterY )
+ .rotate(this.nRotationAngle)
+ .scaleNonUniform( nScaleFactorX, nScaleFactorY )
+ .translate( -this.nBaseCenterX, -this.nBaseCenterY );
+ this.updateTransformAttribute();
+
+ this.nScaleFactorX = nScaleFactorX;
+ this.nScaleFactorY = nScaleFactorY;
+};
+
+AnimatedElement.prototype.getOpacity = function()
+{
+ return this.aActiveElement.getAttribute( 'opacity' );
+};
+
+AnimatedElement.prototype.setOpacity = function( nValue )
+{
+ this.aActiveElement.setAttribute( 'opacity', nValue );
+};
+
+AnimatedElement.prototype.getRotationAngle = function()
+{
+ return this.nRotationAngle;
+};
+
+AnimatedElement.prototype.setRotationAngle = function( nNewRotAngle )
+{
+ this.aTMatrix = document.documentElement.createSVGMatrix()
+ .translate( this.nCenterX, this.nCenterY )
+ .rotate(nNewRotAngle)
+ .scaleNonUniform( this.nScaleFactorX, this.nScaleFactorY )
+ .translate( -this.nBaseCenterX, -this.nBaseCenterY );
+ this.updateTransformAttribute();
+
+ this.nRotationAngle = nNewRotAngle;
+};
+
+AnimatedElement.prototype.getVisibility = function()
+{
+
+ var sVisibilityValue = this.aActiveElement.getAttribute( 'visibility' );
+ if( !sVisibilityValue || ( sVisibilityValue === 'inherit' ) )
+ return 'visible'; // TODO: look for parent visibility!
+ else
+ return sVisibilityValue;
+};
+
+AnimatedElement.prototype.setVisibility = function( sValue )
+{
+ if( sValue == 'visible' )
+ sValue = 'inherit';
+ this.aActiveElement.setAttribute( 'visibility', sValue );
+};
+
+AnimatedElement.prototype.getStrokeStyle = function()
+{
+ // TODO: getStrokeStyle: implement it
+ return 'solid';
+};
+
+AnimatedElement.prototype.setStrokeStyle = function( sValue )
+{
+ ANIMDBG.print( 'AnimatedElement.setStrokeStyle(' + sValue + ')' );
+};
+
+AnimatedElement.prototype.getFillStyle = function()
+{
+ // TODO: getFillStyle: implement it
+ return 'solid';
+};
+
+AnimatedElement.prototype.setFillStyle = function( sValue )
+{
+ ANIMDBG.print( 'AnimatedElement.setFillStyle(' + sValue + ')' );
+};
+
+AnimatedElement.prototype.getFillColor = function()
+{
+ var aChildSet = getElementChildren( this.aActiveElement );
+ var sFillColorValue = '';
+ for( var i = 0; i < aChildSet.length; ++i )
+ {
+ sFillColorValue = aChildSet[i].getAttribute( 'fill' );
+ if( sFillColorValue && ( sFillColorValue !== 'none' ) )
+ break;
+ }
+
+ return colorParser( sFillColorValue );
+};
+
+AnimatedElement.prototype.setFillColor = function( aRGBValue )
+{
+ assert( aRGBValue instanceof RGBColor,
+ 'AnimatedElement.setFillColor: value argument is not an instance of RGBColor' );
+
+ var sValue = aRGBValue.toString( true /* clamped values */ );
+ var aChildSet = getElementChildren( this.aActiveElement );
+
+ var sFillColorValue = '';
+ for( var i = 0; i < aChildSet.length; ++i )
+ {
+ sFillColorValue = aChildSet[i].getAttribute( 'fill' );
+ if( sFillColorValue && ( sFillColorValue !== 'none' ) )
+ {
+ aChildSet[i].setAttribute( 'fill', sValue );
+ }
+ }
+};
+
+AnimatedElement.prototype.getStrokeColor = function()
+{
+ var aChildSet = getElementChildren( this.aActiveElement );
+ var sStrokeColorValue = '';
+ for( var i = 0; i < aChildSet.length; ++i )
+ {
+ sStrokeColorValue = aChildSet[i].getAttribute( 'stroke' );
+ if( sStrokeColorValue && ( sStrokeColorValue !== 'none' ) )
+ break;
+ }
+
+ return colorParser( sStrokeColorValue );
+};
+
+AnimatedElement.prototype.setStrokeColor = function( aRGBValue )
+{
+ assert( aRGBValue instanceof RGBColor,
+ 'AnimatedElement.setFillColor: value argument is not an instance of RGBColor' );
+
+ var sValue = aRGBValue.toString( true /* clamped values */ );
+ var aChildSet = getElementChildren( this.aActiveElement );
+
+ var sStrokeColorValue = '';
+ for( var i = 0; i < aChildSet.length; ++i )
+ {
+ sStrokeColorValue = aChildSet[i].getAttribute( 'stroke' );
+ if( sStrokeColorValue && ( sStrokeColorValue !== 'none' ) )
+ {
+ aChildSet[i].setAttribute( 'stroke', sValue );
+ }
+ }
+};
+
+AnimatedElement.prototype.getFontColor = function()
+{
+ // TODO: getFontColor implement it
+ return new RGBColor( 0, 0, 0 );
+};
+
+AnimatedElement.prototype.setFontColor = function( sValue )
+{
+ ANIMDBG.print( 'AnimatedElement.setFontColor(' + sValue + ')' );
+};
+
+AnimatedElement.prototype.DBG = function( sMessage, nTime )
+{
+ aAnimatedElementDebugPrinter.print( 'AnimatedElement(' + this.getId() + ')' + sMessage, nTime );
+};
+
+
+function AnimatedTextElement( aElement, aEventMultiplexer )
+{
+ var theDocument = document;
+
+ var sTextType = aElement.getAttribute( 'class' );
+ var bIsListItem = ( sTextType === 'ListItem' );
+ if( ( sTextType !== 'TextParagraph' ) && !bIsListItem )
+ {
+ log( 'AnimatedTextElement: passed element is not a paragraph.' );
+ return;
+ }
+ var aTextShapeElement = aElement.parentNode;
+ sTextType = aTextShapeElement.getAttribute( 'class' );
+ if( sTextType !== 'SVGTextShape' )
+ {
+ log( 'AnimatedTextElement: element parent is not a text shape.' );
+ return;
+ }
+ var aTextShapeGroup = aTextShapeElement.parentNode;
+ // We search for the helper group element used for inserting
+ // the element copy to be animated; if it doesn't exist we create it.
+ var aAnimatedElementGroup = getElementByClassName( aTextShapeGroup, 'AnimatedElements' );
+ if( !aAnimatedElementGroup )
+ {
+ aAnimatedElementGroup = theDocument.createElementNS( NSS['svg'], 'g' );
+ aAnimatedElementGroup.setAttribute( 'class', 'AnimatedElements' );
+ aTextShapeGroup.appendChild( aAnimatedElementGroup );
+ }
+
+ // Create element used on animating
+ var aAnimatableElement = theDocument.createElementNS( NSS['svg'], 'g' );
+ var aTextElement = theDocument.createElementNS( NSS['svg'], 'text' );
+ // Clone paragraph element <tspan>
+ var aParagraphElement = aElement.cloneNode( true );
+
+ // We create a group element for wrapping bullets, bitmaps
+ // and text decoration
+ this.aGraphicGroupElement = theDocument.createElementNS( NSS['svg'], 'g' );
+ this.aGraphicGroupElement.setAttribute( 'class', 'GraphicGroup' );
+
+ // In case we are dealing with a list item that utilizes a bullet char
+ // we need to clone the related bullet char too.
+ var aBulletCharClone = null;
+ var aBulletCharElem = null;
+ var bIsBulletCharStyle =
+ ( aElement.getAttributeNS( NSS['ooo'], aOOOAttrListItemNumberingType ) === 'bullet-style' );
+ if( bIsBulletCharStyle )
+ {
+ var aBulletCharGroupElem = getElementByClassName( aTextShapeGroup, 'BulletChars' );
+ if( aBulletCharGroupElem )
+ {
+ var aBulletPlaceholderElem = getElementByClassName( aElement, 'BulletPlaceholder' );
+ if( aBulletPlaceholderElem )
+ {
+ var sId = aBulletPlaceholderElem.getAttribute( 'id' );
+ sId = 'bullet-char(' + sId + ')';
+ aBulletCharElem = theDocument.getElementById( sId );
+ if( aBulletCharElem )
+ {
+ aBulletCharClone = aBulletCharElem.cloneNode( true );
+ }
+ else
+ {
+ log( 'AnimatedTextElement: ' + sId + ' not found.' );
+ }
+ }
+ else
+ {
+ log( 'AnimatedTextElement: no bullet placeholder found' );
+ }
+ }
+ else
+ {
+ log( 'AnimatedTextElement: no bullet char group found' );
+ }
+ }
+
+ // In case there are embedded bitmaps we need to clone them
+ var aBitmapElemSet = [];
+ var aBitmapCloneSet = [];
+ var aBitmapPlaceholderSet = getElementsByClassName( aElement, 'BitmapPlaceholder' );
+ var i;
+ if( aBitmapPlaceholderSet )
+ {
+ for( i = 0; i < aBitmapPlaceholderSet.length; ++i )
+ {
+ sId = aBitmapPlaceholderSet[i].getAttribute( 'id' );
+ var sBitmapChecksum = sId.substring( 'bitmap-placeholder'.length + 1, sId.length - 1 );
+ sId = 'embedded-bitmap(' + sBitmapChecksum + ')';
+ aBitmapElemSet[i] = theDocument.getElementById( sId );
+ if( aBitmapElemSet[i] )
+ {
+ aBitmapCloneSet[i] = aBitmapElemSet[i].cloneNode( true );
+ }
+ else
+ {
+ log( 'AnimatedTextElement: ' + sId + ' not found.' );
+ }
+ }
+ }
+
+
+ // Change clone element id.
+ this.sParagraphId = sId = aParagraphElement.getAttribute( 'id' );
+ aParagraphElement.removeAttribute( 'id' );
+ aAnimatableElement.setAttribute( 'id', sId +'.a' );
+ if( aBulletCharClone )
+ aBulletCharClone.removeAttribute( 'id' );
+ for( i = 0; i < aBitmapCloneSet.length; ++i )
+ {
+ if( aBitmapCloneSet[i] )
+ aBitmapCloneSet[i].removeAttribute( 'id' );
+ }
+
+ // Set up visibility
+ var sVisibilityAttr = aElement.getAttribute( 'visibility' );
+ if( !sVisibilityAttr )
+ sVisibilityAttr = 'inherit';
+ aAnimatableElement.setAttribute( 'visibility', sVisibilityAttr );
+ aParagraphElement.setAttribute( 'visibility', 'inherit' );
+ this.aGraphicGroupElement.setAttribute( 'visibility', 'inherit' );
+ if( aBulletCharElem )
+ aBulletCharElem.setAttribute( 'visibility', 'hidden' );
+ for( i = 0; i < aBitmapCloneSet.length; ++i )
+ {
+ if( aBitmapElemSet[i] )
+ aBitmapElemSet[i].setAttribute( 'visibility', 'hidden' );
+ }
+
+ // Append each element to its parent.
+ // <g class='AnimatedElements'>
+ // <g>
+ // <text>
+ // <tspan class='TextParagraph'> ... </tspan>
+ // </text>
+ // <g class='GraphicGroup'>
+ // [<g class='BulletChar'>...</g>]
+ // [<g class='EmbeddedBitmap'>...</g>]
+ // .
+ // .
+ // [<g class='EmbeddedBitmap'>...</g>]
+ // </g>
+ // </g>
+ // </g>
+
+ aTextElement.appendChild( aParagraphElement );
+ aAnimatableElement.appendChild( aTextElement );
+
+ if( aBulletCharClone )
+ this.aGraphicGroupElement.appendChild( aBulletCharClone );
+ for( i = 0; i < aBitmapCloneSet.length; ++i )
+ {
+ if( aBitmapCloneSet[i] )
+ this.aGraphicGroupElement.appendChild( aBitmapCloneSet[i] );
+ }
+ aAnimatableElement.appendChild( this.aGraphicGroupElement );
+ aAnimatedElementGroup.appendChild( aAnimatableElement );
+
+ this.aParentTextElement = aElement.parentNode;
+ this.aParagraphElement = aElement;
+ this.aAnimatedElementGroup = aAnimatedElementGroup;
+ this.nRunningAnimations = 0;
+
+ // we collect all hyperlink ids
+ this.aHyperlinkIdSet = [];
+ var aHyperlinkElementSet = getElementsByClassName( this.aParagraphElement, 'UrlField' );
+ var sHyperlinkId;
+ for( i = 0; i < aHyperlinkElementSet.length; ++i )
+ {
+ sHyperlinkId = aHyperlinkElementSet[i].getAttribute( 'id' );
+ if( sHyperlinkId )
+ this.aHyperlinkIdSet.push( sHyperlinkId );
+ else
+ log( 'error: AnimatedTextElement constructor: hyperlink element has no id' );
+ }
+
+ AnimatedTextElement.superclass.constructor.call( this, aAnimatableElement, aEventMultiplexer );
+
+}
+extend( AnimatedTextElement, AnimatedElement );
+
+
+AnimatedTextElement.prototype.setToElement = function( aElement )
+{
+ var bRet = AnimatedTextElement.superclass.setToElement.call( this, aElement );
+ if( bRet )
+ {
+ this.aGraphicGroupElement = getElementByClassName( this.aActiveElement, 'GraphicGroup' );
+ }
+ return ( bRet && this.aGraphicGroupElement );
+};
+
+AnimatedTextElement.prototype.notifySlideStart = function( aSlideShowContext )
+{
+ DBGLOG( 'AnimatedTextElement.notifySlideStart' );
+ AnimatedTextElement.superclass.notifySlideStart.call( this, aSlideShowContext );
+ this.aGraphicGroupElement = getElementByClassName( this.aActiveElement, 'GraphicGroup' );
+ this.restoreBaseTextParagraph();
+};
+
+AnimatedTextElement.prototype.notifySlideEnd = function()
+{
+ DBGLOG( 'AnimatedTextElement.notifySlideEnd' );
+ this.aGraphicGroupElement.setAttribute( 'visibility', 'inherit' );
+};
+
+AnimatedTextElement.prototype.restoreBaseTextParagraph = function()
+{
+ var aActiveParagraphElement = this.aActiveElement.firstElementChild.firstElementChild;
+ if( aActiveParagraphElement )
+ {
+ var sVisibilityAttr = this.aActiveElement.getAttribute( 'visibility' );
+ if( !sVisibilityAttr || ( sVisibilityAttr === 'visible' ) )
+ sVisibilityAttr = 'inherit';
+ if( sVisibilityAttr === 'inherit' )
+ this.aGraphicGroupElement.setAttribute( 'visibility', 'visible' );
+ else
+ this.aGraphicGroupElement.setAttribute( 'visibility', 'hidden' );
+
+ var aParagraphClone = aActiveParagraphElement.cloneNode( true );
+ aParagraphClone.setAttribute( 'id', this.sParagraphId );
+ aParagraphClone.setAttribute( 'visibility', sVisibilityAttr );
+ this.aParentTextElement.replaceChild( aParagraphClone, this.aParagraphElement );
+ this.aParagraphElement = aParagraphClone;
+
+
+ var aEventMultiplexer = this.aSlideShowContext.aEventMultiplexer;
+ var aHyperlinkIdSet = this.aHyperlinkIdSet;
+ var aHyperlinkElementSet = getElementsByClassName( this.aParagraphElement, 'UrlField' );
+ var i = 0;
+ for( ; i < aHyperlinkIdSet.length; ++i )
+ {
+ aEventMultiplexer.notifyElementChangedEvent( aHyperlinkIdSet[i], aHyperlinkElementSet[i] );
+ }
+ }
+ this.aActiveElement.setAttribute( 'visibility', 'hidden' );
+};
+
+AnimatedTextElement.prototype.notifyAnimationStart = function()
+{
+ DBGLOG( 'AnimatedTextElement.notifyAnimationStart' );
+ if( this.nRunningAnimations === 0 )
+ {
+ var sVisibilityAttr = this.aParagraphElement.getAttribute( 'visibility' );
+ if( !sVisibilityAttr )
+ sVisibilityAttr = 'inherit';
+ this.aActiveElement.setAttribute( 'visibility', sVisibilityAttr );
+ this.aGraphicGroupElement.setAttribute( 'visibility', 'inherit' );
+ this.aParagraphElement.setAttribute( 'visibility', 'hidden' );
+ }
+ ++this.nRunningAnimations;
+};
+
+AnimatedTextElement.prototype.notifyAnimationEnd = function()
+{
+ DBGLOG( 'AnimatedTextElement.notifyAnimationEnd' );
+ --this.nRunningAnimations;
+ if( this.nRunningAnimations === 0 )
+ {
+ this.restoreBaseTextParagraph();
+ }
+};
+
+AnimatedTextElement.prototype.saveState = function( nAnimationNodeId )
+{
+ if( this.nRunningAnimations === 0 )
+ {
+ var sVisibilityAttr = this.aParagraphElement.getAttribute( 'visibility' );
+ this.aActiveElement.setAttribute( 'visibility', sVisibilityAttr );
+ this.aGraphicGroupElement.setAttribute( 'visibility', 'inherit' );
+ }
+ AnimatedTextElement.superclass.saveState.call( this, nAnimationNodeId );
+};
+
+AnimatedTextElement.prototype.restoreState = function( nAnimationNodeId )
+{
+ var bRet = AnimatedTextElement.superclass.restoreState.call( this, nAnimationNodeId );
+ if( bRet )
+ this.restoreBaseTextParagraph();
+ return bRet;
+};
+
+
+
+
+/** Class SlideTransition
+ * This class is responsible for initializing the properties of a slide
+ * transition and create the object that actually will perform the transition.
+ *
+ * @param aAnimationsRootElement
+ * The <defs> element wrapping all animations for the related slide.
+ * @param aSlideId
+ * A string representing a slide id.
+ */
+function SlideTransition( aAnimationsRootElement, aSlideId )
+{
+ this.sSlideId = aSlideId;
+ this.bIsValid = false;
+ this.eTransitionType = undefined;
+ this.eTransitionSubType = undefined;
+ this.bReverseDirection = false;
+ this.eTransitionMode = TRANSITION_MODE_IN;
+ this.sFadeColor = null;
+ this.aDuration = null;
+ this.nMinFrameCount = undefined;
+
+ if( aAnimationsRootElement )
+ {
+ if( aAnimationsRootElement.firstElementChild &&
+ ( aAnimationsRootElement.firstElementChild.getAttributeNS( NSS['smil'], 'begin' ) === (this.sSlideId + '.begin') ) )
+ {
+ var aTransitionFilterElement = aAnimationsRootElement.firstElementChild.firstElementChild;
+ if( aTransitionFilterElement && ( aTransitionFilterElement.localName === 'transitionFilter' ) )
+ {
+ this.aElement = aTransitionFilterElement;
+ this.parseElement();
+ }
+ aAnimationsRootElement.removeChild( aAnimationsRootElement.firstElementChild );
+ }
+ }
+}
+
+SlideTransition.prototype.createSlideTransition = function( aLeavingSlide, aEnteringSlide )
+{
+ if( !this.isValid() )
+ return null;
+ if( this.eTransitionType == 0 )
+ return null;
+
+ if( !aEnteringSlide )
+ {
+ log( 'SlideTransition.createSlideTransition: invalid entering slide.' );
+ return null;
+ }
+
+ var aTransitionInfo = aTransitionInfoTable[this.eTransitionType][this.eTransitionSubType];
+ var eTransitionClass = aTransitionInfo['class'];
+
+ switch( eTransitionClass )
+ {
+ default:
+ case TRANSITION_INVALID:
+ log( 'SlideTransition.createSlideTransition: transition class: TRANSITION_INVALID' );
+ return null;
+
+ case TRANSITION_CLIP_POLYPOLYGON:
+ var aParametricPolyPolygon
+ = createClipPolyPolygon( this.eTransitionType, this.eTransitionSubType );
+ return new ClippedSlideChange( aLeavingSlide, aEnteringSlide, aParametricPolyPolygon,
+ aTransitionInfo, this.isDirectionForward() );
+
+ case TRANSITION_SPECIAL:
+ switch( this.eTransitionType )
+ {
+ default:
+ log( 'SlideTransition.createSlideTransition: ' +
+ 'transition class: TRANSITION_SPECIAL, ' +
+ 'unknown transition type: ' + this.eTransitionType );
+ return null;
+
+ case PUSHWIPE_TRANSITION:
+ {
+ var aDirection = null;
+ switch( this.eTransitionSubType )
+ {
+ default:
+ log( 'SlideTransition.createSlideTransition: ' +
+ 'transition type: PUSHWIPE_TRANSITION, ' +
+ 'unknown transition subtype: ' + this.eTransitionSubType );
+ return null;
+ case FROMTOP_TRANS_SUBTYPE:
+ aDirection = { x: 0.0, y: 1.0 };
+ break;
+ case FROMBOTTOM_TRANS_SUBTYPE:
+ aDirection = { x: 0.0, y: -1.0 };
+ break;
+ case FROMLEFT_TRANS_SUBTYPE:
+ aDirection = { x: 1.0, y: 0.0 };
+ break;
+ case FROMRIGHT_TRANS_SUBTYPE:
+ aDirection = { x: -1.0, y: 0.0 };
+ break;
+ }
+ return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aDirection, aDirection );
+ }
+
+ case SLIDEWIPE_TRANSITION:
+ {
+ var aInDirection = null;
+ switch( this.eTransitionSubType )
+ {
+ default:
+ log( 'SlideTransition.createSlideTransition: ' +
+ 'transition type: SLIDEWIPE_TRANSITION, ' +
+ 'unknown transition subtype: ' + this.eTransitionSubType );
+ return null;
+ case FROMTOP_TRANS_SUBTYPE:
+ aInDirection = { x: 0.0, y: 1.0 };
+ break;
+ case FROMBOTTOM_TRANS_SUBTYPE:
+ aInDirection = { x: 0.0, y: -1.0 };
+ break;
+ case FROMLEFT_TRANS_SUBTYPE:
+ aInDirection = { x: 1.0, y: 0.0 };
+ break;
+ case FROMRIGHT_TRANS_SUBTYPE:
+ aInDirection = { x: -1.0, y: 0.0 };
+ break;
+ }
+ var aNoDirection = { x: 0.0, y: 0.0 };
+ if( !this.bReverseDirection )
+ {
+ return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aNoDirection, aInDirection );
+ }
+ else
+ {
+ return new MovingSlideChange( aLeavingSlide, aEnteringSlide, aInDirection, aNoDirection );
+ }
+ }
+
+ case FADE_TRANSITION:
+ switch( this.eTransitionSubType )
+ {
+ default:
+ log( 'SlideTransition.createSlideTransition: ' +
+ 'transition type: FADE_TRANSITION, ' +
+ 'unknown transition subtype: ' + this.eTransitionSubType );
+ return null;
+ case CROSSFADE_TRANS_SUBTYPE:
+ return new FadingSlideChange( aLeavingSlide, aEnteringSlide );
+ case FADEOVERCOLOR_TRANS_SUBTYPE:
+ return new FadingOverColorSlideChange( aLeavingSlide, aEnteringSlide, this.getFadeColor() );
+ }
+ }
+ }
+};
+
+SlideTransition.prototype.parseElement = function()
+{
+ this.bIsValid = true;
+ var aAnimElem = this.aElement;
+
+ // type attribute
+ this.eTransitionType = undefined;
+ var sTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'type' );
+ if( sTypeAttr && aTransitionTypeInMap[ sTypeAttr ] )
+ {
+ this.eTransitionType = aTransitionTypeInMap[ sTypeAttr ];
+ }
+ else
+ {
+ this.bIsValid = false;
+ log( 'SlideTransition.parseElement: transition type not valid: ' + sTypeAttr );
+ }
+
+ // subtype attribute
+ this.eTransitionSubType = undefined;
+ var sSubTypeAttr = aAnimElem.getAttributeNS( NSS['smil'], 'subtype' );
+ if( sSubTypeAttr === null )
+ sSubTypeAttr = 'default';
+ if( sSubTypeAttr && ( aTransitionSubtypeInMap[ sSubTypeAttr ] !== undefined ) )
+ {
+ this.eTransitionSubType = aTransitionSubtypeInMap[ sSubTypeAttr ];
+ }
+ else
+ {
+ this.bIsValid = false;
+ log( 'SlideTransition.parseElement: transition subtype not valid: ' + sSubTypeAttr );
+ }
+
+ if( this.bIsValid && aTransitionInfoTable[this.eTransitionType][this.eTransitionSubType] === undefined )
+ {
+ this.bIsValid = false;
+ log( 'SlideTransition.parseElement: transition not valid: type: ' + sTypeAttr + ' subtype: ' + sSubTypeAttr );
+ }
+
+ // direction attribute
+ this.bReverseDirection = false;
+ var sDirectionAttr = aAnimElem.getAttributeNS( NSS['smil'], 'direction' );
+ if( sDirectionAttr == 'reverse' )
+ this.bReverseDirection = true;
+
+ // fade color
+ this.sFadeColor = null;
+ if( this.eTransitionType == FADE_TRANSITION &&
+ ( this.eTransitionSubType == FADEFROMCOLOR_TRANS_SUBTYPE ||
+ this.eTransitionSubType == FADEOVERCOLOR_TRANS_SUBTYPE ||
+ this.eTransitionSubType == FADETOCOLOR_TRANS_SUBTYPE ) )
+ {
+ var sColorAttr = aAnimElem.getAttributeNS( NSS['smil'], 'fadeColor' );
+ if( sColorAttr )
+ this.sFadeColor = sColorAttr;
+ else
+ this.sFadeColor='#000000';
+ }
+
+
+ // dur attribute
+ this.aDuration = null;
+ var sDurAttr = aAnimElem.getAttributeNS( NSS['smil'], 'dur' );
+ this.aDuration = new Duration( sDurAttr );
+ if( !this.aDuration.isSet() )
+ {
+ this.aDuration = new Duration( null ); // duration == 0.0
+ }
+
+ // set up min frame count value;
+ this.nMinFrameCount = ( this.getDuration().isValue() )
+ ? ( this.getDuration().getValue() * MINIMUM_FRAMES_PER_SECONDS )
+ : MINIMUM_FRAMES_PER_SECONDS;
+ if( this.nMinFrameCount < 1.0 )
+ this.nMinFrameCount = 1;
+ else if( this.nMinFrameCount > MINIMUM_FRAMES_PER_SECONDS )
+ this.nMinFrameCount = MINIMUM_FRAMES_PER_SECONDS;
+
+};
+
+SlideTransition.prototype.isValid = function()
+{
+ return this.bIsValid;
+};
+
+SlideTransition.prototype.getTransitionType = function()
+{
+ return this.eTransitionType;
+};
+
+SlideTransition.prototype.getTransitionSubType = function()
+{
+ return this.eTransitionSubType;
+};
+
+SlideTransition.prototype.getTransitionMode = function()
+{
+ return this.eTransitionMode;
+};
+
+SlideTransition.prototype.getFadeColor = function()
+{
+ return this.sFadeColor;
+};
+
+SlideTransition.prototype.isDirectionForward = function()
+{
+ return !this.bReverseDirection;
+};
+
+SlideTransition.prototype.getDuration = function()
+{
+ return this.aDuration;
+};
+
+SlideTransition.prototype.getMinFrameCount = function()
+{
+ return this.nMinFrameCount;
+};
+
+SlideTransition.prototype.info = function()
+{
+
+ var sInfo ='slide transition <' + this.sSlideId + '>: ';
+ // transition type
+ sInfo += '; type: ' + getKeyByValue(aTransitionTypeInMap, this.getTransitionType());
+
+ // transition subtype
+ sInfo += '; subtype: ' + getKeyByValue(aTransitionSubtypeInMap, this.getTransitionSubType());
+
+ // transition direction
+ if( !this.isDirectionForward() )
+ sInfo += '; direction: reverse';
+
+ // transition mode
+ sInfo += '; mode: ' + aTransitionModeOutMap[ this.getTransitionMode() ];
+
+ // duration
+ if( this.getDuration() )
+ sInfo += '; duration: ' + this.getDuration().info();
+
+ return sInfo;
+};
+
+
+
+
+// SlideAnimations
+
+function SlideAnimations( aSlideShowContext )
+{
+ this.aContext = new NodeContext( aSlideShowContext );
+ this.aAnimationNodeMap = {};
+ this.aAnimatedElementMap = {};
+ this.aSourceEventElementMap = {};
+ this.aNextEffectEventArray = new NextEffectEventArray();
+ this.aInteractiveAnimationSequenceMap = {};
+ this.aEventMultiplexer = new EventMultiplexer( aSlideShowContext.aTimerEventQueue );
+ this.aRootNode = null;
+ this.bElementsParsed = false;
+
+ this.aContext.aAnimationNodeMap = this.aAnimationNodeMap;
+ this.aContext.aAnimatedElementMap = this.aAnimatedElementMap;
+ this.aContext.aSourceEventElementMap = this.aSourceEventElementMap;
+
+ // We set up a low priority for the invocation of document.handleClick
+ // in order to make clicks on shapes, that start interactive animation
+ // sequence (on click), have an higher priority.
+ this.aEventMultiplexer.registerMouseClickHandler( document, 100 );
+}
+
+
+SlideAnimations.prototype.importAnimations = function( aAnimationRootElement )
+{
+ if( !aAnimationRootElement )
+ return false;
+
+ this.aRootNode = createAnimationTree( aAnimationRootElement, this.aContext );
+
+ return ( this.aRootNode ? true : false );
+};
+
+SlideAnimations.prototype.parseElements = function()
+{
+ if( !this.aRootNode )
+ return false;
+
+ // parse all nodes
+ if( !this.aRootNode.parseElement() )
+ return false;
+ else
+ this.bElementsParsed = true;
+};
+
+SlideAnimations.prototype.elementsParsed = function()
+{
+ return this.bElementsParsed;
+};
+
+SlideAnimations.prototype.isFirstRun = function()
+{
+ return this.aContext.bFirstRun;
+};
+
+SlideAnimations.prototype.isAnimated = function()
+{
+ if( !this.bElementsParsed )
+ return false;
+
+ return this.aRootNode.hasPendingAnimation();
+};
+
+SlideAnimations.prototype.start = function()
+{
+ if( !this.bElementsParsed )
+ return false;
+
+ this.chargeSourceEvents();
+ this.chargeInterAnimEvents();
+
+ aSlideShow.setSlideEvents( this.aNextEffectEventArray,
+ this.aInteractiveAnimationSequenceMap,
+ this.aEventMultiplexer );
+
+ if( this.aContext.bFirstRun == undefined )
+ this.aContext.bFirstRun = true;
+ else if( this.aContext.bFirstRun )
+ this.aContext.bFirstRun = false;
+
+ // init all nodes
+ this.aContext.bIsInvalid = !this.aRootNode.init();
+ if( this.aContext.bIsInvalid )
+ return false;
+
+ // resolve root node
+ return this.aRootNode.resolve();
+};
+
+SlideAnimations.prototype.end = function( bLeftEffectsSkipped )
+{
+ if( !this.bElementsParsed )
+ return; // no animations there
+
+ // end root node
+ this.aRootNode.deactivate();
+ this.aRootNode.end();
+
+ if( bLeftEffectsSkipped && this.isFirstRun() )
+ {
+ // in case this is the first run and left events have been skipped
+ // some next effect events for the slide could not be collected
+ // so the next time we should behave as it was the first run again
+ this.aContext.bFirstRun = undefined;
+ }
+ else if( this.isFirstRun() )
+ {
+ this.aContext.bFirstRun = false;
+ }
+
+ this.aContext.bIsInvalid = false;
+};
+
+SlideAnimations.prototype.dispose = function()
+{
+ if( this.aRootNode )
+ {
+ this.aRootNode.dispose();
+ }
+};
+
+SlideAnimations.prototype.clearNextEffectEvents = function()
+{
+ ANIMDBG.print( 'SlideAnimations.clearNextEffectEvents: current slide: ' + nCurSlide );
+ this.aNextEffectEventArray.clear();
+ this.aContext.bFirstRun = undefined;
+};
+
+SlideAnimations.prototype.chargeSourceEvents = function()
+{
+ for( var id in this.aSourceEventElementMap )
+ {
+ this.aSourceEventElementMap[id].charge();
+ }
+};
+
+SlideAnimations.prototype.chargeInterAnimEvents = function()
+{
+ for( var id in this.aInteractiveAnimationSequenceMap )
+ {
+ this.aInteractiveAnimationSequenceMap[id].chargeEvents();
+ }
+};
+
+/**********************************************************************************************
+ * Event classes and helper functions
+ **********************************************************************************************/
+
+
+function Event()
+{
+ this.nId = Event.getUniqueId();
+}
+
+
+Event.CURR_UNIQUE_ID = 0;
+
+Event.getUniqueId = function()
+{
+ ++Event.CURR_UNIQUE_ID;
+ return Event.CURR_UNIQUE_ID;
+};
+
+Event.prototype.getId = function()
+{
+ return this.nId;
+};
+
+
+
+function DelayEvent( aFunctor, nTimeout )
+{
+ DelayEvent.superclass.constructor.call( this );
+
+ this.aFunctor = aFunctor;
+ this.nTimeout = nTimeout;
+ this.bWasFired = false;
+}
+extend( DelayEvent, Event );
+
+
+DelayEvent.prototype.fire = function()
+{
+ assert( this.isCharged(), 'DelayEvent.fire: assertion isCharged failed' );
+
+ this.bWasFired = true;
+ this.aFunctor();
+ return true;
+};
+
+DelayEvent.prototype.isCharged = function()
+{
+ return !this.bWasFired;
+};
+
+DelayEvent.prototype.getActivationTime = function( nCurrentTime )
+{
+ return ( this.nTimeout + nCurrentTime );
+};
+
+DelayEvent.prototype.dispose = function()
+{
+ // don't clear unconditionally, because it may currently be executed:
+ if( this.isCharged() )
+ this.bWasFired = true;
+};
+
+DelayEvent.prototype.charge = function()
+{
+ if( !this.isCharged() )
+ this.bWasFired = false;
+};
+
+
+
+function WakeupEvent( aTimer, aActivityQueue )
+{
+ WakeupEvent.superclass.constructor.call( this );
+
+ this.aTimer = new ElapsedTime( aTimer );
+ this.nNextTime = 0.0;
+ this.aActivity = null;
+ this.aActivityQueue = aActivityQueue;
+}
+extend( WakeupEvent, Event );
+
+
+WakeupEvent.prototype.clone = function()
+{
+ var aWakeupEvent = new WakeupEvent( this.aTimer.getTimeBase(), this.aActivityQueue );
+ aWakeupEvent.nNextTime = this.nNextTime;
+ aWakeupEvent.aActivity = this.aActivity;
+ return aWakeupEvent;
+};
+
+WakeupEvent.prototype.dispose = function()
+{
+ this.aActivity = null;
+};
+
+WakeupEvent.prototype.fire = function()
+{
+ if( !this.aActivity )
+ return false;
+
+ return this.aActivityQueue.addActivity( this.aActivity );
+};
+
+WakeupEvent.prototype.isCharged = function()
+{
+ // this event won't expire, we fire every time we're
+ // re-inserted into the event queue.
+ return true;
+};
+
+WakeupEvent.prototype.getActivationTime = function( nCurrentTime )
+{
+ var nElapsedTime = this.aTimer.getElapsedTime();
+
+ return Math.max( nCurrentTime, nCurrentTime - nElapsedTime + this.nNextTime );
+};
+
+WakeupEvent.prototype.start = function()
+{
+ this.aTimer.reset();
+};
+
+WakeupEvent.prototype.setNextTimeout = function( nNextTime )
+{
+ this.nNextTime = nNextTime;
+};
+
+WakeupEvent.prototype.setActivity = function( aActivity )
+{
+ this.aActivity = aActivity;
+};
+
+
+
+function makeEvent( aFunctor )
+{
+ return new DelayEvent( aFunctor, 0.0 );
+}
+
+
+
+
+function makeDelay( aFunctor, nTimeout )
+{
+ return new DelayEvent( aFunctor, nTimeout );
+}
+
+
+
+
+function registerEvent( nNodeId, aTiming, aEvent, aNodeContext )
+{
+ var aSlideShowContext = aNodeContext.aContext;
+ var eTimingType = aTiming.getType();
+
+ registerEvent.DBG( aTiming );
+
+ if( eTimingType == OFFSET_TIMING )
+ {
+ aSlideShowContext.aTimerEventQueue.addEvent( aEvent );
+ }
+ else if ( aNodeContext.bFirstRun )
+ {
+ var aEventMultiplexer = aSlideShowContext.aEventMultiplexer;
+ if( !aEventMultiplexer )
+ {
+ log( 'registerEvent: event multiplexer not initialized' );
+ return;
+ }
+ var aNextEffectEventArray = aSlideShowContext.aNextEffectEventArray;
+ if( !aNextEffectEventArray )
+ {
+ log( 'registerEvent: next effect event array not initialized' );
+ return;
+ }
+ var aInteractiveAnimationSequenceMap =
+ aSlideShowContext.aInteractiveAnimationSequenceMap;
+ if( !aInteractiveAnimationSequenceMap )
+ {
+ log( 'registerEvent: interactive animation sequence map not initialized' );
+ return;
+ }
+
+ switch( eTimingType )
+ {
+ case EVENT_TIMING:
+ var eEventType = aTiming.getEventType();
+ var sEventBaseElemId = aTiming.getEventBaseElementId();
+ if( sEventBaseElemId )
+ {
+ var aEventBaseElem = document.getElementById( sEventBaseElemId );
+ if( !aEventBaseElem )
+ {
+ log( 'generateEvent: EVENT_TIMING: event base element not found: ' + sEventBaseElemId );
+ return;
+ }
+ var aSourceEventElement = aNodeContext.makeSourceEventElement( sEventBaseElemId, aEventBaseElem );
+
+ if( !aInteractiveAnimationSequenceMap[ nNodeId ] )
+ {
+ aInteractiveAnimationSequenceMap[ nNodeId ] = new InteractiveAnimationSequence(nNodeId);
+ }
+
+ var bEventRegistered = false;
+ switch( eEventType )
+ {
+ case EVENT_TRIGGER_ON_CLICK:
+ aEventMultiplexer.registerEvent( eEventType, aSourceEventElement.getId(), aEvent );
+ aEventMultiplexer.registerRewindedEffectHandler( aSourceEventElement.getId(),
+ bind2( aSourceEventElement.charge, aSourceEventElement ) );
+ bEventRegistered = true;
+ break;
+ default:
+ log( 'generateEvent: not handled event type: ' + eEventType );
+ }
+ if( bEventRegistered )
+ {
+ var aStartEvent = aInteractiveAnimationSequenceMap[ nNodeId ].getStartEvent();
+ var aEndEvent = aInteractiveAnimationSequenceMap[ nNodeId ].getEndEvent();
+ aEventMultiplexer.registerEvent( eEventType, aSourceEventElement.getId(), aStartEvent );
+ aEventMultiplexer.registerEvent( EVENT_TRIGGER_END_EVENT, nNodeId, aEndEvent );
+ aEventMultiplexer.registerRewindedEffectHandler(
+ nNodeId,
+ bind2( InteractiveAnimationSequence.prototype.chargeEvents,
+ aInteractiveAnimationSequenceMap[ nNodeId ] )
+ );
+ }
+ }
+ else // no base event element present
+ {
+ switch( eEventType )
+ {
+ case EVENT_TRIGGER_ON_NEXT_EFFECT:
+ aNextEffectEventArray.appendEvent( aEvent );
+ break;
+ default:
+ log( 'generateEvent: not handled event type: ' + eEventType );
+ }
+ }
+ break;
+ case SYNCBASE_TIMING:
+ eEventType = aTiming.getEventType();
+ sEventBaseElemId = aTiming.getEventBaseElementId();
+ if( sEventBaseElemId )
+ {
+ var aAnimationNode = aNodeContext.aAnimationNodeMap[ sEventBaseElemId ];
+ if( !aAnimationNode )
+ {
+ log( 'generateEvent: SYNCBASE_TIMING: event base element not found: ' + sEventBaseElemId );
+ return;
+ }
+ aEventMultiplexer.registerEvent( eEventType, aAnimationNode.getId(), aEvent );
+ }
+ else
+ {
+ log( 'generateEvent: SYNCBASE_TIMING: event base element not specified' );
+ }
+ break;
+ default:
+ log( 'generateEvent: not handled timing type: ' + eTimingType );
+ }
+ }
+}
+
+registerEvent.DEBUG = aRegisterEventDebugPrinter.isEnabled();
+
+registerEvent.DBG = function( aTiming, nTime )
+{
+ if( registerEvent.DEBUG )
+ {
+ aRegisterEventDebugPrinter.print( 'registerEvent( timing: ' + aTiming.info() + ' )', nTime );
+ }
+};
+
+
+
+
+function SourceEventElement( sId, aElement, aEventMultiplexer )
+{
+ this.sId = sId;
+ this.aElement = aElement;
+ this.aEventMultiplexer = aEventMultiplexer;
+
+ this.aEventMultiplexer.registerMouseClickHandler( this, 1000 );
+
+ this.bClickHandled = false;
+ this.bIsPointerOver = false;
+ this.aElement.addEventListener( 'mouseover', bind2( SourceEventElement.prototype.onMouseEnter, this), false );
+ this.aElement.addEventListener( 'mouseout', bind2( SourceEventElement.prototype.onMouseLeave, this), false );
+}
+
+SourceEventElement.prototype.getId = function()
+{
+ return this.sId;
+};
+
+SourceEventElement.prototype.onMouseEnter = function()
+{
+ this.bIsPointerOver = true;
+ this.setPointerCursor();
+};
+
+SourceEventElement.prototype.onMouseLeave = function()
+{
+ this.bIsPointerOver = false;
+ this.setDefaultCursor();
+};
+
+SourceEventElement.prototype.charge = function()
+{
+ this.bClickHandled = false;
+ this.setPointerCursor();
+};
+
+SourceEventElement.prototype.handleClick = function( /*aMouseEvent*/ )
+{
+ if( !this.bIsPointerOver ) return false;
+
+ if( this.bClickHandled )
+ return false;
+
+ this.aEventMultiplexer.notifyEvent( EVENT_TRIGGER_ON_CLICK, this.getId() );
+ aSlideShow.update();
+ this.bClickHandled = true;
+ this.setDefaultCursor();
+ return true;
+};
+
+SourceEventElement.prototype.setPointerCursor = function()
+{
+ if( this.bClickHandled )
+ return;
+
+ this.aElement.setAttribute( 'style', 'cursor: pointer' );
+};
+
+SourceEventElement.prototype.setDefaultCursor = function()
+{
+ this.aElement.setAttribute( 'style', 'cursor: default' );
+};
+
+
+
+function HyperlinkElement( sId, aEventMultiplexer )
+{
+ var aElement = document.getElementById( sId );
+ if( !aElement )
+ {
+ log( 'error: HyperlinkElement: no element with id: <' + sId + '> found' );
+ return;
+ }
+ if( !aEventMultiplexer )
+ {
+ log( 'AnimatedElement constructor: event multiplexer is not valid' );
+ }
+
+ this.sId = sId;
+ this.aElement = aElement;
+ this.aEventMultiplexer = aEventMultiplexer;
+ this.nTargetSlideIndex = undefined;
+
+ this.sURL = getNSAttribute( 'xlink', this.aElement, 'href' );
+ if( this.sURL )
+ {
+ if( this.sURL[0] === '#' )
+ {
+ if( this.sURL.substr(1, 5) === 'Slide' )
+ {
+ var sSlideIndex = this.sURL.split( ' ' )[1];
+ this.nTargetSlideIndex = parseInt( sSlideIndex ) - 1;
+ }
+ }
+
+ this.aEventMultiplexer.registerElementChangedHandler( this.sId, bind2( HyperlinkElement.prototype.onElementChanged, this) );
+ this.aEventMultiplexer.registerMouseClickHandler( this, 1100 );
+
+ this.bIsPointerOver = false;
+ this.mouseEnterHandler = bind2( HyperlinkElement.prototype.onMouseEnter, this);
+ this.mouseLeaveHandler = bind2( HyperlinkElement.prototype.onMouseLeave, this);
+ this.aElement.addEventListener( 'mouseover', this.mouseEnterHandler, false );
+ this.aElement.addEventListener( 'mouseout', this.mouseLeaveHandler, false );
+ }
+ else
+ {
+ log( 'warning: HyperlinkElement(' + this.sId + '): url is empty' );
+ }
+}
+
+HyperlinkElement.prototype.onElementChanged = function( aElement )
+{
+ if( !aElement )
+ {
+ log( 'error: HyperlinkElement: passed element is not valid' );
+ return;
+ }
+
+ if( this.sURL )
+ {
+ this.aElement.removeEventListener( 'mouseover', this.mouseEnterHandler, false );
+ this.aElement.removeEventListener( 'mouseout', this.mouseLeaveHandler, false );
+ this.aElement = aElement;
+ this.aElement.addEventListener( 'mouseover', this.mouseEnterHandler, false );
+ this.aElement.addEventListener( 'mouseout', this.mouseLeaveHandler, false );
+ }
+};
+
+HyperlinkElement.prototype.onMouseEnter = function()
+{
+ this.bIsPointerOver = true;
+ this.setPointerCursor();
+};
+
+HyperlinkElement.prototype.onMouseLeave = function()
+{
+ this.bIsPointerOver = false;
+ this.setDefaultCursor();
+};
+
+HyperlinkElement.prototype.handleClick = function( )
+{
+ if( !this.bIsPointerOver ) return false;
+
+ if( this.nTargetSlideIndex !== undefined )
+ {
+ aSlideShow.displaySlide( this.nTargetSlideIndex, true );
+ }
+ else
+ {
+ var aWindowObject = document.defaultView;
+ if( aWindowObject )
+ {
+ aWindowObject.open( this.sURL, this.sId );
+ }
+ else
+ {
+ log( 'error: HyperlinkElement.handleClick: invalid window object.' );
+ }
+ }
+
+ return true;
+};
+
+HyperlinkElement.prototype.setPointerCursor = function()
+{
+ if( this.bClickHandled )
+ return;
+
+ this.aElement.setAttribute( 'style', 'cursor: pointer' );
+};
+
+HyperlinkElement.prototype.setDefaultCursor = function()
+{
+ this.aElement.setAttribute( 'style', 'cursor: default' );
+};
+
+
+
+function InteractiveAnimationSequence( nId )
+{
+ this.nId = nId;
+ this.bIsRunning = false;
+ this.aStartEvent = null;
+ this.aEndEvent = null;
+}
+
+InteractiveAnimationSequence.prototype.getId = function()
+{
+ return this.nId;
+};
+
+InteractiveAnimationSequence.prototype.getStartEvent = function()
+{
+ if( !this.aStartEvent )
+ {
+ this.aStartEvent =
+ makeEvent( bind2( InteractiveAnimationSequence.prototype.start, this ) );
+ }
+ return this.aStartEvent;
+};
+
+InteractiveAnimationSequence.prototype.getEndEvent = function()
+{
+ if( !this.aEndEvent )
+ {
+ this.aEndEvent =
+ makeEvent( bind2( InteractiveAnimationSequence.prototype.end, this ) );
+ }
+ return this.aEndEvent;
+};
+
+InteractiveAnimationSequence.prototype.chargeEvents = function()
+{
+ if( this.aStartEvent ) this.aStartEvent.charge();
+ if( this.aEndEvent ) this.aEndEvent.charge();
+};
+
+InteractiveAnimationSequence.prototype.isRunning = function()
+{
+ return this.bIsRunning;
+};
+
+InteractiveAnimationSequence.prototype.start = function()
+{
+ aSlideShow.notifyInteractiveAnimationSequenceStart( this.getId() );
+ this.bIsRunning = true;
+};
+
+InteractiveAnimationSequence.prototype.end = function()
+{
+ aSlideShow.notifyInteractiveAnimationSequenceEnd( this.getId() );
+ this.bIsRunning = false;
+};
+
+
+/** class PriorityEntry
+ * It provides an entry type for priority queues.
+ * Higher is the value of nPriority higher is the priority of the created entry.
+ *
+ * @param aValue
+ * The object to be prioritized.
+ * @param nPriority
+ * An integral number representing the object priority.
+ *
+ */
+function PriorityEntry( aValue, nPriority )
+{
+ this.aValue = aValue;
+ this.nPriority = nPriority;
+}
+
+/** EventEntry.compare
+ * Compare priority of two entries.
+ *
+ * @param aLhsEntry
+ * An instance of type PriorityEntry.
+ * @param aRhsEntry
+ * An instance of type PriorityEntry.
+ * @return Integer
+ * -1 if the left entry has lower priority of the right entry,
+ * 1 if the left entry has higher priority of the right entry,
+ * 0 if the two entry have the same priority
+ */
+PriorityEntry.compare = function( aLhsEntry, aRhsEntry )
+{
+ if ( aLhsEntry.nPriority < aRhsEntry.nPriority )
+ {
+ return -1;
+ }
+ else if (aLhsEntry.nPriority > aRhsEntry.nPriority)
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+};
+
+
+
+
+function EventMultiplexer( aTimerEventQueue )
+{
+ this.nId = EventMultiplexer.getUniqueId();
+ this.aTimerEventQueue = aTimerEventQueue;
+ this.aEventMap = {};
+ this.aAnimationsEndHandler = null;
+ this.aSkipEffectEndHandlerSet = [];
+ this.aMouseClickHandlerSet = new PriorityQueue( PriorityEntry.compare );
+ this.aSkipEffectEvent = null;
+ this.aRewindCurrentEffectEvent = null;
+ this.aRewindLastEffectEvent = null;
+ this.aSkipInteractiveEffectEventSet = {};
+ this.aRewindRunningInteractiveEffectEventSet = {};
+ this.aRewindEndedInteractiveEffectEventSet = {};
+ this.aRewindedEffectHandlerSet = {};
+ this.aElementChangedHandlerSet = {};
+}
+
+EventMultiplexer.CURR_UNIQUE_ID = 0;
+
+EventMultiplexer.getUniqueId = function()
+{
+ ++EventMultiplexer.CURR_UNIQUE_ID;
+ return EventMultiplexer.CURR_UNIQUE_ID;
+};
+
+EventMultiplexer.prototype.getId = function()
+{
+ return this.nId;
+};
+
+EventMultiplexer.prototype.hasRegisteredMouseClickHandlers = function()
+{
+ return !this.aMouseClickHandlerSet.isEmpty();
+};
+
+EventMultiplexer.prototype.registerMouseClickHandler = function( aHandler, nPriority )
+{
+ var aHandlerEntry = new PriorityEntry( aHandler, nPriority );
+ this.aMouseClickHandlerSet.push( aHandlerEntry );
+};
+
+EventMultiplexer.prototype.notifyMouseClick = function( aMouseEvent )
+{
+ var aMouseClickHandlerSet = this.aMouseClickHandlerSet.clone();
+ while( !aMouseClickHandlerSet.isEmpty() )
+ {
+ var aHandlerEntry = aMouseClickHandlerSet.top();
+ aMouseClickHandlerSet.pop();
+ if( aHandlerEntry.aValue.handleClick( aMouseEvent ) )
+ break;
+ }
+};
+
+EventMultiplexer.prototype.registerEvent = function( eEventType, aNotifierId, aEvent )
+{
+ this.DBG( 'registerEvent', eEventType, aNotifierId );
+ if( !this.aEventMap[ eEventType ] )
+ {
+ this.aEventMap[ eEventType ] = {};
+ }
+ if( !this.aEventMap[ eEventType ][ aNotifierId ] )
+ {
+ this.aEventMap[ eEventType ][ aNotifierId ] = [];
+ }
+ this.aEventMap[ eEventType ][ aNotifierId ].push( aEvent );
+};
+
+
+EventMultiplexer.prototype.notifyEvent = function( eEventType, aNotifierId )
+{
+ this.DBG( 'notifyEvent', eEventType, aNotifierId );
+ if( this.aEventMap[ eEventType ] )
+ {
+ if( this.aEventMap[ eEventType ][ aNotifierId ] )
+ {
+ var aEventArray = this.aEventMap[ eEventType ][ aNotifierId ];
+ var nSize = aEventArray.length;
+ for( var i = 0; i < nSize; ++i )
+ {
+ this.aTimerEventQueue.addEvent( aEventArray[i] );
+ }
+ }
+ }
+};
+
+EventMultiplexer.prototype.registerAnimationsEndHandler = function( aHandler )
+{
+ this.aAnimationsEndHandler = aHandler;
+};
+
+EventMultiplexer.prototype.notifyAnimationsEndEvent = function()
+{
+ if( this.aAnimationsEndHandler )
+ this.aAnimationsEndHandler();
+};
+
+EventMultiplexer.prototype.registerNextEffectEndHandler = function( aHandler )
+{
+ this.aSkipEffectEndHandlerSet.push( aHandler );
+};
+
+EventMultiplexer.prototype.notifyNextEffectEndEvent = function()
+{
+ var nSize = this.aSkipEffectEndHandlerSet.length;
+ for( var i = 0; i < nSize; ++i )
+ {
+ (this.aSkipEffectEndHandlerSet[i])();
+ }
+ this.aSkipEffectEndHandlerSet = [];
+};
+
+EventMultiplexer.prototype.registerSkipEffectEvent = function( aEvent )
+{
+ this.aSkipEffectEvent = aEvent;
+};
+
+EventMultiplexer.prototype.notifySkipEffectEvent = function()
+{
+ if( this.aSkipEffectEvent )
+ {
+ this.aTimerEventQueue.addEvent( this.aSkipEffectEvent );
+ this.aSkipEffectEvent = null;
+ }
+};
+
+EventMultiplexer.prototype.registerRewindCurrentEffectEvent = function( aEvent )
+{
+ this.aRewindCurrentEffectEvent = aEvent;
+};
+
+EventMultiplexer.prototype.notifyRewindCurrentEffectEvent = function()
+{
+ if( this.aRewindCurrentEffectEvent )
+ {
+ this.aTimerEventQueue.addEvent( this.aRewindCurrentEffectEvent );
+ this.aRewindCurrentEffectEvent = null;
+ }
+};
+
+EventMultiplexer.prototype.registerRewindLastEffectEvent = function( aEvent )
+{
+ this.aRewindLastEffectEvent = aEvent;
+};
+
+EventMultiplexer.prototype.notifyRewindLastEffectEvent = function()
+{
+ if( this.aRewindLastEffectEvent )
+ {
+ this.aTimerEventQueue.addEvent( this.aRewindLastEffectEvent );
+ this.aRewindLastEffectEvent = null;
+ }
+};
+
+EventMultiplexer.prototype.registerSkipInteractiveEffectEvent = function( nNotifierId, aEvent )
+{
+ this.aSkipInteractiveEffectEventSet[ nNotifierId ] = aEvent;
+};
+
+EventMultiplexer.prototype.notifySkipInteractiveEffectEvent = function( nNotifierId )
+{
+ if( this.aSkipInteractiveEffectEventSet[ nNotifierId ] )
+ {
+ this.aTimerEventQueue.addEvent( this.aSkipInteractiveEffectEventSet[ nNotifierId ] );
+ }
+};
+
+EventMultiplexer.prototype.registerRewindRunningInteractiveEffectEvent = function( nNotifierId, aEvent )
+{
+ this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] = aEvent;
+};
+
+EventMultiplexer.prototype.notifyRewindRunningInteractiveEffectEvent = function( nNotifierId )
+{
+ if( this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] )
+ {
+ this.aTimerEventQueue.addEvent( this.aRewindRunningInteractiveEffectEventSet[ nNotifierId ] );
+ }
+};
+
+EventMultiplexer.prototype.registerRewindEndedInteractiveEffectEvent = function( nNotifierId, aEvent )
+{
+ this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] = aEvent;
+};
+
+EventMultiplexer.prototype.notifyRewindEndedInteractiveEffectEvent = function( nNotifierId )
+{
+ if( this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] )
+ {
+ this.aTimerEventQueue.addEvent( this.aRewindEndedInteractiveEffectEventSet[ nNotifierId ] );
+ }
+};
+
+EventMultiplexer.prototype.registerRewindedEffectHandler = function( aNotifierId, aHandler )
+{
+ this.aRewindedEffectHandlerSet[ aNotifierId ] = aHandler;
+};
+
+EventMultiplexer.prototype.notifyRewindedEffectEvent = function( aNotifierId )
+{
+ if( this.aRewindedEffectHandlerSet[ aNotifierId ] )
+ {
+ (this.aRewindedEffectHandlerSet[ aNotifierId ])();
+ }
+};
+
+EventMultiplexer.prototype.registerElementChangedHandler = function( aNotifierId, aHandler )
+{
+ this.aElementChangedHandlerSet[ aNotifierId ] = aHandler;
+};
+
+EventMultiplexer.prototype.notifyElementChangedEvent = function( aNotifierId, aElement )
+{
+ if( this.aElementChangedHandlerSet[ aNotifierId ] )
+ {
+ (this.aElementChangedHandlerSet[ aNotifierId ])( aElement );
+ }
+};
+
+EventMultiplexer.DEBUG = aEventMultiplexerDebugPrinter.isEnabled();
+
+EventMultiplexer.prototype.DBG = function( sMethodName, eEventType, aNotifierId, nTime )
+{
+ if( EventMultiplexer.DEBUG )
+ {
+ var sInfo = 'EventMultiplexer.' + sMethodName;
+ sInfo += '( type: ' + aEventTriggerOutMap[ eEventType ];
+ sInfo += ', notifier: ' + aNotifierId + ' )';
+ aEventMultiplexerDebugPrinter.print( sInfo, nTime );
+ }
+};
+
+
+
+/**********************************************************************************************
+ * Interpolator Handler and KeyStopLerp
+ **********************************************************************************************/
+
+var aInterpolatorHandler = {};
+
+aInterpolatorHandler.getInterpolator = function( eCalcMode, eValueType, eValueSubtype )
+{
+ var bHasSubtype = ( typeof( eValueSubtype ) === typeof( 0 ) );
+
+ if( !bHasSubtype && aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ] )
+ {
+ return aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ];
+ }
+ else if( bHasSubtype && aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ][ eValueSubtype ] )
+ {
+ return aInterpolatorHandler.aLerpFunctorMap[ eCalcMode ][ eValueType ][ eValueSubtype ];
+ }
+ else
+ {
+ log( 'aInterpolatorHandler.getInterpolator: not found any valid interpolator for calc mode '
+ + aCalcModeOutMap[eCalcMode] + ' and value type ' + aValueTypeOutMap[eValueType] );
+ return null;
+ }
+};
+
+aInterpolatorHandler.aLerpFunctorMap = [];
+aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_DISCRETE ] = [];
+aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ] = [];
+
+
+// interpolators for linear calculation
+
+aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ NUMBER_PROPERTY ] =
+ function ( nFrom, nTo, nT )
+ {
+ return ( ( 1.0 - nT )* nFrom + nT * nTo );
+ };
+
+aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ COLOR_PROPERTY ] = [];
+
+aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ COLOR_PROPERTY ][ COLOR_SPACE_RGB ] =
+ function ( nFrom, nTo, nT )
+ {
+ return RGBColor.interpolate( nFrom, nTo, nT );
+ };
+
+// For HSLColor we do not return the interpolator but a function
+// that generate the interpolator. The AnimationColorNode is 'aware' of that.
+aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ COLOR_PROPERTY ][ COLOR_SPACE_HSL ] =
+ function ( bCCW )
+ {
+ return function ( nFrom, nTo, nT )
+ {
+ return HSLColor.interpolate( nFrom, nTo, nT, bCCW );
+ };
+ };
+
+aInterpolatorHandler.aLerpFunctorMap[ CALC_MODE_LINEAR ][ TUPLE_NUMBER_PROPERTY ] =
+ function ( aFrom, aTo, nT )
+ {
+ var aRes = [];
+ for( var i = 0; i < aFrom.length; ++i )
+ {
+ aRes.push( ( 1.0 - nT )* aFrom[i] + nT * aTo[i] );
+ }
+ return aRes;
+ };
+
+
+
+
+function KeyStopLerp( aValueList )
+{
+ KeyStopLerp.validateInput( aValueList );
+
+ this.aKeyStopList = [];
+ this.nLastIndex = 0;
+ this.nKeyStopDistance = aValueList[1] - aValueList[0];
+ if( this.nKeyStopDistance <= 0 )
+ this.nKeyStopDistance = 0.001;
+
+ for( var i = 0; i < aValueList.length; ++i )
+ this.aKeyStopList.push( aValueList[i] );
+
+ this.nUpperBoundIndex = this.aKeyStopList.length - 2;
+}
+
+
+KeyStopLerp.validateInput = function( aValueList )
+{
+ var nSize = aValueList.length;
+ assert( nSize > 1, 'KeyStopLerp.validateInput: key stop vector must have two entries or more' );
+
+ for( var i = 1; i < nSize; ++i )
+ {
+ if( aValueList[i-1] > aValueList[i] )
+ log( 'KeyStopLerp.validateInput: time vector is not sorted in ascending order!' );
+ }
+};
+
+KeyStopLerp.prototype.reset = function()
+{
+ KeyStopLerp.validateInput( this.aKeyStopList );
+ this.nLastIndex = 0;
+ this.nKeyStopDistance = this.aKeyStopList[1] - this.aKeyStopList[0];
+ if( this.nKeyStopDistance <= 0 )
+ this.nKeyStopDistance = 0.001;
+
+};
+
+KeyStopLerp.prototype.lerp = function( nAlpha )
+{
+ if( nAlpha > this.aKeyStopList[ this.nLastIndex + 1 ] )
+ {
+ do
+ {
+ var nIndex = this.nLastIndex + 1;
+ this.nLastIndex = clamp( nIndex, 0, this.nUpperBoundIndex );
+ this.nKeyStopDistance = this.aKeyStopList[ this.nLastIndex + 1 ] - this.aKeyStopList[ this.nLastIndex ];
+ }
+ while( ( this.nKeyStopDistance <= 0 ) && ( this.nLastIndex < this.nUpperBoundIndex ) );
+ }
+
+ var nRawLerp = ( nAlpha - this.aKeyStopList[ this.nLastIndex ] ) / this.nKeyStopDistance;
+
+ nRawLerp = clamp( nRawLerp, 0.0, 1.0 );
+
+ var aResult = {};
+ aResult.nIndex = this.nLastIndex;
+ aResult.nLerp = nRawLerp;
+
+ return aResult;
+};
+
+KeyStopLerp.prototype.lerp_ported = function( nAlpha )
+{
+ if( ( this.aKeyStopList[ this.nLastIndex ] < nAlpha ) ||
+ ( this.aKeyStopList[ this.nLastIndex + 1 ] >= nAlpha ) )
+ {
+ var i = 0;
+ for( ; i < this.aKeyStopList.length; ++i )
+ {
+ if( this.aKeyStopList[i] >= nAlpha )
+ break;
+ }
+ if( this.aKeyStopList[i] > nAlpha )
+ --i;
+ var nIndex = i - 1;
+ this.nLastIndex = clamp( nIndex, 0, this.aKeyStopList.length - 2 );
+ }
+
+ var nRawLerp = ( nAlpha - this.aKeyStopList[ this.nLastIndex ] ) /
+ ( this.aKeyStopList[ this.nLastIndex+1 ] - this.aKeyStopList[ this.nLastIndex ] );
+
+ nRawLerp = clamp( nRawLerp, 0.0, 1.0 );
+
+ var aResult = {};
+ aResult.nIndex = this.nLastIndex;
+ aResult.nLerp = nRawLerp;
+
+ return aResult;
+};
+
+
+
+/**********************************************************************************************
+ * Operators
+ **********************************************************************************************/
+
+var aOperatorSetMap = [];
+
+// number operators
+aOperatorSetMap[ NUMBER_PROPERTY ] = {};
+
+aOperatorSetMap[ NUMBER_PROPERTY ].equal = function( a, b )
+{
+ return ( a === b );
+};
+
+aOperatorSetMap[ NUMBER_PROPERTY ].add = function( a, b )
+{
+ return ( a + b );
+};
+
+aOperatorSetMap[ NUMBER_PROPERTY ].scale = function( k, v )
+{
+ return ( k * v );
+};
+
+// color operators
+aOperatorSetMap[ COLOR_PROPERTY ] = {};
+
+aOperatorSetMap[ COLOR_PROPERTY ].equal = function( a, b )
+{
+ return a.equal( b );
+};
+
+aOperatorSetMap[ COLOR_PROPERTY ].add = function( a, b )
+{
+ var c = a.clone();
+ c.add( b );
+ return c;
+};
+
+aOperatorSetMap[ COLOR_PROPERTY ].scale = function( k, v )
+{
+ var r = v.clone();
+ r.scale( k );
+ return r;
+};
+
+// enum operators
+aOperatorSetMap[ ENUM_PROPERTY ] = {};
+
+aOperatorSetMap[ ENUM_PROPERTY ].equal = function( a, b )
+{
+ return ( a === b );
+};
+
+aOperatorSetMap[ ENUM_PROPERTY ].add = function( a )
+{
+ return a;
+};
+
+aOperatorSetMap[ ENUM_PROPERTY ].scale = function( k, v )
+{
+ return v;
+};
+
+// string operators
+aOperatorSetMap[ STRING_PROPERTY ] = aOperatorSetMap[ ENUM_PROPERTY ];
+
+// bool operators
+aOperatorSetMap[ BOOL_PROPERTY ] = aOperatorSetMap[ ENUM_PROPERTY ];
+
+// tuple number operators
+aOperatorSetMap[ TUPLE_NUMBER_PROPERTY ] = {};
+
+aOperatorSetMap[ TUPLE_NUMBER_PROPERTY ].equal = function( a, b )
+{
+ assert( a.length === b.length, 'Tuples length mismatch.' );
+ return ( a.toString() === b.toString() );
+};
+
+aOperatorSetMap[ TUPLE_NUMBER_PROPERTY ].add = function( a, b )
+{
+ assert( a.length === b.length, 'Tuples length mismatch.' );
+ var r = [];
+ for( var i = 0; i < a.length; ++i )
+ {
+ r.push(a[i] + b[i]);
+ }
+ return r;
+};
+
+aOperatorSetMap[ TUPLE_NUMBER_PROPERTY ].scale = function( k, v )
+{
+ var r = [];
+ for( var i = 0; i < v.length; ++i )
+ {
+ r.push(k * v[i]);
+ }
+ return r;
+};
+
+
+
+
+/**********************************************************************************************
+ * Activity Class Hierarchy
+ **********************************************************************************************/
+
+
+function ActivityParamSet()
+{
+ this.aEndEvent = null;
+ this.aWakeupEvent = null;
+ this.aTimerEventQueue = null;
+ this.aActivityQueue = null;
+ this.nMinDuration = undefined;
+ this.nMinNumberOfFrames = MINIMUM_FRAMES_PER_SECONDS;
+ this.bAutoReverse = false;
+ this.nRepeatCount = 1.0;
+ this.nAccelerationFraction = 0.0;
+ this.nDecelerationFraction = 0.0;
+ this.nSlideWidth = undefined;
+ this.nSlideHeight = undefined;
+ this.aFormula = null;
+ this.aDiscreteTimes = [];
+}
+
+
+function AnimationActivity()
+{
+ this.nId = AnimationActivity.getUniqueId();
+}
+
+
+AnimationActivity.CURR_UNIQUE_ID = 0;
+
+AnimationActivity.getUniqueId = function()
+{
+ ++AnimationActivity.CURR_UNIQUE_ID;
+ return AnimationActivity.CURR_UNIQUE_ID;
+};
+
+AnimationActivity.prototype.getId = function()
+{
+ return this.nId;
+};
+
+
+
+
+function SetActivity( aCommonParamSet, aAnimation, aToAttr )
+{
+ SetActivity.superclass.constructor.call( this );
+
+ this.aAnimation = aAnimation;
+ this.aTargetElement = null;
+ this.aEndEvent = aCommonParamSet.aEndEvent;
+ this.aTimerEventQueue = aCommonParamSet.aTimerEventQueue;
+ this.aToAttr = aToAttr;
+ this.bIsActive = true;
+}
+extend( SetActivity, AnimationActivity );
+
+
+SetActivity.prototype.activate = function( aEndEvent )
+{
+ this.aEndEvent = aEndEvent;
+ this.bIsActive = true;
+};
+
+SetActivity.prototype.dispose = function()
+{
+ this.bIsActive = false;
+ if( this.aEndEvent && this.aEndEvent.isCharged() )
+ this.aEndEvent.dispose();
+};
+
+SetActivity.prototype.calcTimeLag = function()
+{
+ return 0.0;
+};
+
+SetActivity.prototype.perform = function()
+{
+ if( !this.isActive() )
+ return false;
+
+ // we're going inactive immediately:
+ this.bIsActive = false;
+
+ if( this.aAnimation && this.aTargetElement )
+ {
+ this.aAnimation.start( this.aTargetElement );
+ this.aAnimation.perform( this.aToAttr );
+ this.aAnimation.end();
+ }
+
+ if( this.aEndEvent )
+ this.aTimerEventQueue.addEvent( this.aEndEvent );
+
+};
+
+SetActivity.prototype.isActive = function()
+{
+ return this.bIsActive;
+};
+
+SetActivity.prototype.dequeued = function()
+{
+ // empty body
+};
+
+SetActivity.prototype.end = function()
+{
+ this.perform();
+};
+
+SetActivity.prototype.setTargets = function( aTargetElement )
+{
+ assert( aTargetElement, 'SetActivity.setTargets: target element is not valid' );
+ this.aTargetElement = aTargetElement;
+};
+
+
+
+
+function ActivityBase( aCommonParamSet )
+{
+ ActivityBase.superclass.constructor.call( this );
+
+ this.aTargetElement = null;
+ this.aEndEvent = aCommonParamSet.aEndEvent;
+ this.aTimerEventQueue = aCommonParamSet.aTimerEventQueue;
+ this.nRepeats = aCommonParamSet.nRepeatCount;
+ this.nAccelerationFraction = aCommonParamSet.nAccelerationFraction;
+ this.nDecelerationFraction = aCommonParamSet.nDecelerationFraction;
+ this.bAutoReverse = aCommonParamSet.bAutoReverse;
+
+ this.bFirstPerformCall = true;
+ this.bIsActive = true;
+
+}
+extend( ActivityBase, AnimationActivity );
+
+
+ActivityBase.prototype.activate = function( aEndEvent )
+{
+ this.aEndEvent = aEndEvent;
+ this.bFirstPerformCall = true;
+ this.bIsActive = true;
+};
+
+ActivityBase.prototype.dispose = function()
+{
+ // deactivate
+ this.bIsActive = false;
+
+ // dispose event
+ if( this.aEndEvent )
+ this.aEndEvent.dispose();
+
+ this.aEndEvent = null;
+};
+
+ActivityBase.prototype.perform = function()
+{
+ // still active?
+ if( !this.isActive() )
+ return false; // no, early exit.
+
+ assert( !this.bFirstPerformCall, 'ActivityBase.perform: assertion (!this.FirstPerformCall) failed' );
+
+ return true;
+};
+
+ActivityBase.prototype.calcTimeLag = function()
+{
+ // TODO(Q1): implement different init process!
+ if( this.isActive() && this.bFirstPerformCall )
+ {
+ this.bFirstPerformCall = false;
+
+ // notify derived classes that we're
+ // starting now
+ this.startAnimation();
+ }
+ return 0.0;
+};
+
+ActivityBase.prototype.isActive = function()
+{
+ return this.bIsActive;
+};
+
+ActivityBase.prototype.isDisposed = function()
+{
+ return ( !this.bIsActive && !this.aEndEvent );
+};
+
+ActivityBase.prototype.dequeued = function()
+{
+ if( !this.isActive() )
+ this.endAnimation();
+};
+
+ActivityBase.prototype.setTargets = function( aTargetElement )
+{
+ assert( aTargetElement, 'ActivityBase.setTargets: target element is not valid' );
+
+ this.aTargetElement = aTargetElement;
+};
+
+ActivityBase.prototype.startAnimation = function()
+{
+ throw ( 'ActivityBase.startAnimation: abstract method invoked' );
+};
+
+ActivityBase.prototype.endAnimation = function()
+{
+ throw ( 'ActivityBase.endAnimation: abstract method invoked' );
+};
+
+ActivityBase.prototype.endActivity = function()
+{
+ // this is a regular activity end
+ this.bIsActive = false;
+
+ // Activity is ending, queue event, then
+ if( this.aEndEvent )
+ this.aTimerEventQueue.addEvent( this.aEndEvent );
+
+ this.aEndEvent = null;
+
+};
+
+ActivityBase.prototype.calcAcceleratedTime = function( nT )
+{
+ // Handle acceleration/deceleration
+
+
+ // clamp nT to permissible [0,1] range
+ nT = clamp( nT, 0.0, 1.0 );
+
+ // take acceleration/deceleration into account. if the sum
+ // of nAccelerationFraction and nDecelerationFraction
+ // exceeds 1.0, ignore both (that's according to SMIL spec)
+ if( ( this.nAccelerationFraction > 0.0 || this.nDecelerationFraction > 0.0 ) &&
+ ( this.nAccelerationFraction + this.nDecelerationFraction <= 1.0 ) )
+ {
+ var nC = 1.0 - 0.5*this.nAccelerationFraction - 0.5*this.nDecelerationFraction;
+
+ // this variable accumulates the new time value
+ var nTPrime = 0.0;
+
+ if( nT < this.nAccelerationFraction )
+ {
+ nTPrime += 0.5 * nT * nT / this.nAccelerationFraction; // partial first interval
+ }
+ else
+ {
+ nTPrime += 0.5 * this.nAccelerationFraction; // full first interval
+
+ if( nT <= ( 1.0 - this.nDecelerationFraction ) )
+ {
+ nTPrime += nT - this.nAccelerationFraction; // partial second interval
+ }
+ else
+ {
+ nTPrime += 1.0 - this.nAccelerationFraction - this.nDecelerationFraction; // full second interval
+
+ var nTRelative = nT - 1.0 + this.nDecelerationFraction;
+
+ nTPrime += nTRelative - 0.5*nTRelative*nTRelative / this.nDecelerationFraction;
+ }
+ }
+
+ // normalize, and assign to work variable
+ nT = nTPrime / nC;
+
+ }
+ return nT;
+};
+
+ActivityBase.prototype.getEventQueue = function()
+{
+ return this.aTimerEventQueue;
+};
+
+ActivityBase.prototype.getTargetElement = function()
+{
+ return this.aTargetElement;
+};
+
+ActivityBase.prototype.isRepeatCountValid = function()
+{
+ return !!this.nRepeats; // first ! convert to bool
+};
+
+ActivityBase.prototype.getRepeatCount = function()
+{
+ return this.nRepeats;
+};
+
+ActivityBase.prototype.isAutoReverse = function()
+{
+ return this.bAutoReverse;
+};
+
+ActivityBase.prototype.end = function()
+{
+ if( !this.isActive() || this.isDisposed() )
+ return;
+
+ // assure animation is started:
+ if( this.bFirstPerformCall )
+ {
+ this.bFirstPerformCall = false;
+ // notify derived classes that we're starting now
+ this.startAnimation();
+ }
+
+ this.performEnd();
+ this.endAnimation();
+ this.endActivity();
+};
+
+ActivityBase.prototype.performEnd = function()
+{
+ throw ( 'ActivityBase.performEnd: abstract method invoked' );
+};
+
+
+
+
+function DiscreteActivityBase( aCommonParamSet )
+{
+ DiscreteActivityBase.superclass.constructor.call( this, aCommonParamSet );
+
+ this.aOriginalWakeupEvent = aCommonParamSet.aWakeupEvent;
+ this.aOriginalWakeupEvent.setActivity( this );
+ this.aWakeupEvent = this.aOriginalWakeupEvent;
+ this.aWakeupEvent = aCommonParamSet.aWakeupEvent;
+ this.aDiscreteTimes = aCommonParamSet.aDiscreteTimes;
+ // Simple duration of activity
+ this.nMinSimpleDuration = aCommonParamSet.nMinDuration;
+ // Actual number of frames shown until now.
+ this.nCurrPerformCalls = 0;
+}
+extend( DiscreteActivityBase, ActivityBase );
+
+
+DiscreteActivityBase.prototype.activate = function( aEndElement )
+{
+ DiscreteActivityBase.superclass.activate.call( this, aEndElement );
+
+ this.aWakeupEvent = this.aOriginalWakeupEvent;
+ this.aWakeupEvent.setNextTimeout( 0 );
+ this.nCurrPerformCalls = 0;
+};
+
+DiscreteActivityBase.prototype.startAnimation = function()
+{
+ this.aWakeupEvent.start();
+};
+
+DiscreteActivityBase.prototype.calcFrameIndex = function( nCurrCalls, nVectorSize )
+{
+ if( this.isAutoReverse() )
+ {
+ // every full repeat run consists of one
+ // forward and one backward traversal.
+ var nFrameIndex = nCurrCalls % (2 * nVectorSize);
+
+ // nFrameIndex values >= nVectorSize belong to
+ // the backward traversal
+ if( nFrameIndex >= nVectorSize )
+ nFrameIndex = 2*nVectorSize - nFrameIndex; // invert sweep
+
+ return nFrameIndex;
+ }
+ else
+ {
+ return nCurrCalls % nVectorSize;
+ }
+};
+
+DiscreteActivityBase.prototype.calcRepeatCount = function( nCurrCalls, nVectorSize )
+{
+ if( this.isAutoReverse() )
+ {
+ return Math.floor( nCurrCalls / (2*nVectorSize) ); // we've got 2 cycles per repeat
+ }
+ else
+ {
+ return Math.floor( nCurrCalls / nVectorSize );
+ }
+};
+
+DiscreteActivityBase.prototype.performDiscreteHook = function( /*nFrame, nRepeatCount*/ )
+{
+ throw ( 'DiscreteActivityBase.performDiscreteHook: abstract method invoked' );
+};
+
+DiscreteActivityBase.prototype.perform = function()
+{
+ // call base class, for start() calls and end handling
+ if( !SimpleContinuousActivityBase.superclass.perform.call( this ) )
+ return false; // done, we're ended
+
+ var nVectorSize = this.aDiscreteTimes.length;
+
+ var nFrameIndex = this.calcFrameIndex(this.nCurrPerformCalls, nVectorSize);
+ var nRepeatCount = this.calcRepeatCount( this.nCurrPerformCalls, nVectorSize );
+ this.performDiscreteHook( nFrameIndex, nRepeatCount );
+
+ // one more frame successfully performed
+ ++this.nCurrPerformCalls;
+
+ // calc currently reached repeat count
+ var nCurrRepeat = this.nCurrPerformCalls / nVectorSize;
+
+ // if auto-reverse is specified, halve the
+ // effective repeat count, since we pass every
+ // repeat run twice: once forward, once backward.
+ if( this.isAutoReverse() )
+ nCurrRepeat /= 2;
+
+ // schedule next frame, if either repeat is indefinite
+ // (repeat forever), or we've not yet reached the requested
+ // repeat count
+ if( !this.isRepeatCountValid() || nCurrRepeat < this.getRepeatCount() )
+ {
+ // add wake-up event to queue (modulo vector size, to cope with repeats).
+
+ // repeat is handled locally, only apply acceleration/deceleration.
+ // Scale time vector with simple duration, offset with full repeat
+ // times.
+
+ // Note that calcAcceleratedTime() is only applied to the current repeat's value,
+ // not to the total resulting time. This is in accordance with the SMIL spec.
+
+ nFrameIndex = this.calcFrameIndex(this.nCurrPerformCalls, nVectorSize);
+ var nCurrentRepeatTime = this.aDiscreteTimes[nFrameIndex];
+ nRepeatCount = this.calcRepeatCount( this.nCurrPerformCalls, nVectorSize );
+ var nNextTimeout = this.nMinSimpleDuration * ( nRepeatCount + this.calcAcceleratedTime( nCurrentRepeatTime ) );
+ this.aWakeupEvent.setNextTimeout( nNextTimeout );
+
+ this.getEventQueue().addEvent( this.aWakeupEvent );
+ }
+ else
+ {
+ // release event reference (relation to wake up event is circular!)
+ this.aWakeupEvent = null;
+
+ // done with this activity
+ this.endActivity();
+ }
+
+ return false; // remove from queue, will be added back by the wakeup event.
+};
+
+DiscreteActivityBase.prototype.dispose = function()
+{
+ // dispose event
+ if( this.aWakeupEvent )
+ this.aWakeupEvent.dispose();
+
+ // release references
+ this.aWakeupEvent = null;
+
+ DiscreteActivityBase.superclass.dispose.call(this);
+};
+
+
+
+
+function SimpleContinuousActivityBase( aCommonParamSet )
+{
+ SimpleContinuousActivityBase.superclass.constructor.call( this, aCommonParamSet );
+
+ // Time elapsed since activity started
+ this.aTimer = new ElapsedTime( aCommonParamSet.aActivityQueue.getTimer() );
+ // Simple duration of activity
+ this.nMinSimpleDuration = aCommonParamSet.nMinDuration;
+ // Minimal number of frames to show
+ this.nMinNumberOfFrames = aCommonParamSet.nMinNumberOfFrames;
+ // Actual number of frames shown until now.
+ this.nCurrPerformCalls = 0;
+
+}
+extend( SimpleContinuousActivityBase, ActivityBase );
+
+
+SimpleContinuousActivityBase.prototype.startAnimation = function()
+{
+ // init timer. We measure animation time only when we're
+ // actually started.
+ this.aTimer.reset();
+};
+
+SimpleContinuousActivityBase.prototype.calcTimeLag = function()
+{
+ SimpleContinuousActivityBase.superclass.calcTimeLag.call( this );
+
+ if( !this.isActive() )
+ return 0.0;
+
+ // retrieve locally elapsed time
+ var nCurrElapsedTime = this.aTimer.getElapsedTime();
+
+ // go to great length to ensure a proper animation
+ // run. Since we don't know how often we will be called
+ // here, try to spread the animator calls uniquely over
+ // the [0,1] parameter range. Be aware of the fact that
+ // perform will be called at least mnMinNumberOfTurns
+ // times.
+
+ // fraction of time elapsed
+ var nFractionElapsedTime = nCurrElapsedTime / this.nMinSimpleDuration;
+
+ // fraction of minimum calls performed
+ var nFractionRequiredCalls = this.nCurrPerformCalls / this.nMinNumberOfFrames;
+
+ // okay, so now, the decision is easy:
+ //
+ // If the fraction of time elapsed is smaller than the
+ // number of calls required to be performed, then we calc
+ // the position on the animation range according to
+ // elapsed time. That is, we're so to say ahead of time.
+ //
+ // In contrary, if the fraction of time elapsed is larger,
+ // then we're lagging, and we thus calc the position on
+ // the animation time line according to the fraction of
+ // calls performed. Thus, the animation is forced to slow
+ // down, and take the required minimal number of steps,
+ // sufficiently equally distributed across the animation
+ // time line.
+
+ if( nFractionElapsedTime < nFractionRequiredCalls )
+ {
+ return 0.0;
+ }
+ else
+ {
+ // lag global time, so all other animations lag, too:
+ return ( ( nFractionElapsedTime - nFractionRequiredCalls ) * this.nMinSimpleDuration );
+ }
+};
+
+SimpleContinuousActivityBase.prototype.perform = function()
+{
+ // call base class, for start() calls and end handling
+ if( !SimpleContinuousActivityBase.superclass.perform.call( this ) )
+ return false; // done, we're ended
+
+ // get relative animation position
+ var nCurrElapsedTime = this.aTimer.getElapsedTime();
+ var nT = nCurrElapsedTime / this.nMinSimpleDuration;
+
+
+ // one of the stop criteria reached?
+
+ // will be set to true below, if one of the termination criteria matched.
+ var bActivityEnding = false;
+
+ if( this.isRepeatCountValid() )
+ {
+ // Finite duration case
+
+ // When we've autoreverse on, the repeat count doubles
+ var nRepeatCount = this.getRepeatCount();
+ var nEffectiveRepeat = this.isAutoReverse() ? 2.0 * nRepeatCount : nRepeatCount;
+
+ // time (or frame count) elapsed?
+ if( nEffectiveRepeat <= nT )
+ {
+ // Ok done for now. Will not exit right here,
+ // to give animation the chance to render the last
+ // frame below
+ bActivityEnding = true;
+
+ // clamp animation to max permissible value
+ nT = nEffectiveRepeat;
+ }
+ }
+
+
+ // need to do auto-reverse?
+
+ var nRepeats;
+ var nRelativeSimpleTime;
+ // TODO(Q3): Refactor this mess
+ if( this.isAutoReverse() )
+ {
+ // divert active duration into repeat and
+ // fractional part.
+ nRepeats = Math.floor( nT );
+ var nFractionalActiveDuration = nT - nRepeats;
+
+ // for auto-reverse, map ranges [1,2), [3,4), ...
+ // to ranges [0,1), [1,2), etc.
+ if( nRepeats % 2 )
+ {
+ // we're in an odd range, reverse sweep
+ nRelativeSimpleTime = 1.0 - nFractionalActiveDuration;
+ }
+ else
+ {
+ // we're in an even range, pass on as is
+ nRelativeSimpleTime = nFractionalActiveDuration;
+ }
+
+ // effective repeat count for autoreverse is half of
+ // the input time's value (each run of an autoreverse
+ // cycle is half of a repeat)
+ nRepeats /= 2;
+ }
+ else
+ {
+ // determine repeat
+
+ // calc simple time and number of repeats from nT
+ // Now, that's easy, since the fractional part of
+ // nT gives the relative simple time, and the
+ // integer part the number of full repeats:
+ nRepeats = Math.floor( nT );
+ nRelativeSimpleTime = nT - nRepeats;
+
+ // clamp repeats to max permissible value (maRepeats.getValue() - 1.0)
+ if( this.isRepeatCountValid() && ( nRepeats >= this.getRepeatCount() ) )
+ {
+ // Note that this code here only gets
+ // triggered if this.nRepeats is an
+ // _integer_. Otherwise, nRepeats will never
+ // reach nor exceed
+ // maRepeats.getValue(). Thus, the code below
+ // does not need to handle cases of fractional
+ // repeats, and can always assume that a full
+ // animation run has ended (with
+ // nRelativeSimpleTime = 1.0 for
+ // non-autoreversed activities).
+
+ // with modf, nRelativeSimpleTime will never
+ // become 1.0, since nRepeats is incremented and
+ // nRelativeSimpleTime set to 0.0 then.
+ //
+ // For the animation to reach its final value,
+ // nRepeats must although become this.nRepeats - 1.0,
+ // and nRelativeSimpleTime = 1.0.
+ nRelativeSimpleTime = 1.0;
+ nRepeats -= 1.0;
+ }
+ }
+
+
+ // actually perform something
+
+ this.simplePerform( nRelativeSimpleTime, nRepeats );
+
+ // delayed endActivity() call from end condition check
+ // below. Issued after the simplePerform() call above, to
+ // give animations the chance to correctly reach the
+ // animation end value, without spurious bail-outs because
+ // of isActive() returning false.
+ if( bActivityEnding )
+ this.endActivity();
+
+ // one more frame successfully performed
+ ++this.nCurrPerformCalls;
+
+ return this.isActive();
+};
+
+SimpleContinuousActivityBase.prototype.simplePerform = function( /*nSimpleTime, nRepeatCount*/ )
+{
+ throw ( 'SimpleContinuousActivityBase.simplePerform: abstract method invoked' );
+};
+
+
+
+
+function ContinuousKeyTimeActivityBase( aCommonParamSet )
+{
+ var nSize = aCommonParamSet.aDiscreteTimes.length;
+ assert( nSize > 1,
+ 'ContinuousKeyTimeActivityBase constructor: assertion (aDiscreteTimes.length > 1) failed' );
+
+ assert( aCommonParamSet.aDiscreteTimes[0] == 0.0,
+ 'ContinuousKeyTimeActivityBase constructor: assertion (aDiscreteTimes.front() == 0.0) failed' );
+
+ assert( aCommonParamSet.aDiscreteTimes[ nSize - 1 ] <= 1.0,
+ 'ContinuousKeyTimeActivityBase constructor: assertion (aDiscreteTimes.back() <= 1.0) failed' );
+
+ ContinuousKeyTimeActivityBase.superclass.constructor.call( this, aCommonParamSet );
+
+ this.aLerper = new KeyStopLerp( aCommonParamSet.aDiscreteTimes );
+}
+extend( ContinuousKeyTimeActivityBase, SimpleContinuousActivityBase );
+
+
+ContinuousKeyTimeActivityBase.prototype.activate = function( aEndElement )
+{
+ ContinuousKeyTimeActivityBase.superclass.activate.call( this, aEndElement );
+
+ this.aLerper.reset();
+};
+
+ContinuousKeyTimeActivityBase.prototype.performContinuousHook = function( /*nIndex, nFractionalIndex, nRepeatCount*/ )
+{
+ throw ( 'ContinuousKeyTimeActivityBase.performContinuousHook: abstract method invoked' );
+};
+
+ContinuousKeyTimeActivityBase.prototype.simplePerform = function( nSimpleTime, nRepeatCount )
+{
+ var nAlpha = this.calcAcceleratedTime( nSimpleTime );
+
+ var aLerpResult = this.aLerper.lerp( nAlpha );
+
+ this.performContinuousHook( aLerpResult.nIndex, aLerpResult.nLerp, nRepeatCount );
+};
+
+
+
+
+function ContinuousActivityBase( aCommonParamSet )
+{
+ ContinuousActivityBase.superclass.constructor.call( this, aCommonParamSet );
+
+}
+extend( ContinuousActivityBase, SimpleContinuousActivityBase );
+
+
+ContinuousActivityBase.prototype.performContinuousHook = function( /*nModifiedTime, nRepeatCount*/ )
+{
+ throw ( 'ContinuousActivityBase.performContinuousHook: abstract method invoked' );
+};
+
+ContinuousActivityBase.prototype.simplePerform = function( nSimpleTime, nRepeatCount )
+{
+ this.performContinuousHook( this.calcAcceleratedTime( nSimpleTime ), nRepeatCount );
+};
+
+
+
+
+function SimpleActivity( aCommonParamSet, aNumberAnimation, eDirection )
+{
+ assert( ( eDirection == BACKWARD ) || ( eDirection == FORWARD ),
+ 'SimpleActivity constructor: animation direction is not valid' );
+
+ assert( aNumberAnimation, 'SimpleActivity constructor: animation object is not valid' );
+
+ SimpleActivity.superclass.constructor.call( this, aCommonParamSet );
+
+ this.aAnimation = aNumberAnimation;
+ this.nDirection = ( eDirection == FORWARD ) ? 1.0 : 0.0;
+}
+extend( SimpleActivity, ContinuousActivityBase );
+
+
+SimpleActivity.prototype.startAnimation = function()
+{
+ if( this.isDisposed() || !this.aAnimation )
+ return;
+
+ ANIMDBG.print( 'SimpleActivity.startAnimation invoked' );
+ SimpleActivity.superclass.startAnimation.call( this );
+
+ // start animation
+ this.aAnimation.start( this.getTargetElement() );
+};
+
+SimpleActivity.prototype.endAnimation = function()
+{
+ if( this.aAnimation )
+ this.aAnimation.end();
+
+};
+
+SimpleActivity.prototype.performContinuousHook = function( nModifiedTime /*, nRepeatCount*/ )
+{
+ // nRepeatCount is not used
+
+ if( this.isDisposed() || !this.aAnimation )
+ return;
+
+ var nT = 1.0 - this.nDirection + nModifiedTime * ( 2.0*this.nDirection - 1.0 );
+ this.aAnimation.perform( nT );
+};
+
+SimpleActivity.prototype.performEnd = function()
+{
+ if( this.aAnimation )
+ this.aAnimation.perform( this.nDirection );
+};
+
+
+
+
+// FromToByActivity< BaseType > template class
+
+
+function FromToByActivityTemplate( BaseType ) // template parameter
+{
+
+ function FromToByActivity( aFromValue, aToValue, aByValue,
+ aActivityParamSet, aAnimation,
+ aInterpolator, aOperatorSet, bAccumulate )
+ {
+ assert( aAnimation, 'FromToByActivity constructor: invalid animation object' );
+ assert( ( aToValue != undefined ) || ( aByValue != undefined ),
+ 'FromToByActivity constructor: one of aToValue or aByValue must be valid' );
+
+ FromToByActivity.superclass.constructor.call( this, aActivityParamSet );
+
+ this.aFrom = aFromValue;
+ this.aTo = aToValue;
+ this.aBy = aByValue;
+ this.aStartValue = null;
+ this.aEndValue = null;
+ this.aPreviousValue = null;
+ this.aStartInterpolationValue = null;
+ this.aAnimation = aAnimation;
+ this.aInterpolator = aInterpolator;
+ this.equal = aOperatorSet.equal;
+ this.add = aOperatorSet.add;
+ this.scale = aOperatorSet.scale;
+ this.bDynamicStartValue = false;
+ this.nIteration = 0;
+ this.bCumulative = bAccumulate;
+ this.aFormula = aActivityParamSet.aFormula;
+ }
+ extend( FromToByActivity, BaseType );
+
+ FromToByActivity.prototype.initAnimatedElement = function()
+ {
+ if( this.aAnimation && this.aFrom )
+ {
+ var aValue = this.aFormula ? this.aFormula( this.aFrom ) : this.aFrom;
+ this.aAnimation.perform(aValue);
+ }
+ };
+
+ FromToByActivity.prototype.startAnimation = function()
+ {
+ if( this.isDisposed() || !this.aAnimation )
+ {
+ log( 'FromToByActivity.startAnimation: activity disposed or not valid animation' );
+ return;
+ }
+
+ FromToByActivity.superclass.startAnimation.call( this );
+
+ this.aAnimation.start( this.getTargetElement() );
+
+
+ var aAnimationStartValue = this.aAnimation.getUnderlyingValue();
+
+ // first of all, determine general type of
+ // animation, by inspecting which of the FromToBy values
+ // are actually valid.
+ // See http://www.w3.org/TR/smil20/animation.html#AnimationNS-FromToBy
+ // for a definition
+ if( this.aFrom )
+ {
+ // From-to or From-by animation. According to
+ // SMIL spec, the To value takes precedence
+ // over the By value, if both are specified
+ if( this.aTo )
+ {
+ // From-To animation
+ this.aStartValue = this.aFrom;
+ this.aEndValue = this.aTo;
+ }
+ else if( this.aBy )
+ {
+ // From-By animation
+ this.aStartValue = this.aFrom;
+
+ this.aEndValue = this.add( this.aStartValue, this.aBy );
+ }
+ }
+ else
+ {
+ this.aStartValue = aAnimationStartValue;
+ this.aStartInterpolationValue = this.aStartValue;
+
+ // By or To animation. According to SMIL spec,
+ // the To value takes precedence over the By
+ // value, if both are specified
+ if( this.aTo )
+ {
+ // To animation
+
+ // According to the SMIL spec
+ // (http://www.w3.org/TR/smil20/animation.html#animationNS-ToAnimation),
+ // the to animation interpolates between
+ // the _running_ underlying value and the to value (as the end value)
+ this.bDynamicStartValue = true;
+ this.aPreviousValue = this.aStartValue;
+ this.aEndValue = this.aTo;
+ }
+ else if( this.aBy )
+ {
+ // By animation
+ this.aStartValue = aAnimationStartValue;
+
+ this.aEndValue = this.add( this.aStartValue, this.aBy );
+ }
+ }
+
+ ANIMDBG.print( 'FromToByActivity.startAnimation: aStartValue = ' + this.aStartValue + ', aEndValue = ' + this.aEndValue );
+ };
+
+ FromToByActivity.prototype.endAnimation = function()
+ {
+ if( this.aAnimation )
+ this.aAnimation.end();
+ };
+
+ // perform hook override for ContinuousActivityBase
+ FromToByActivity.prototype.performContinuousHook = function( nModifiedTime, nRepeatCount )
+ {
+ if( this.isDisposed() || !this.aAnimation )
+ {
+ log( 'FromToByActivity.performContinuousHook: activity disposed or not valid animation' );
+ return;
+ }
+
+
+ // According to SMIL 3.0 spec 'to' animation if no other (lower priority)
+ // animations are active or frozen then a simple interpolation is performed.
+ // That is, the start interpolation value is constant while the animation
+ // is running, and is equal to the underlying value retrieved when
+ // the animation start.
+ // However if another animation is manipulating the underlying value,
+ // the 'to' animation will initially add to the effect of the lower priority
+ // animation, and increasingly dominate it as it nears the end of the
+ // simple duration, eventually overriding it completely.
+ // That is, each time the underlying value is changed between two
+ // computations of the animation function the new underlying value is used
+ // as start value for the interpolation.
+ // See:
+ // http://www.w3.org/TR/SMIL3/smil-animation.html#animationNS-ToAnimation
+ // (Figure 6 - Effect of Additive to animation example)
+ // Moreover when a 'to' animation is repeated, at each new iteration
+ // the start interpolation value is reset to the underlying value
+ // of the animated property when the animation started,
+ // as it is shown in the example provided by the SMIL 3.0 spec.
+ // This is exactly as Firefox performs SVG 'to' animations.
+ if( this.bDynamicStartValue )
+ {
+ if( this.nIteration != nRepeatCount )
+ {
+ this.nIteration = nRepeatCount;
+ this.aStartInterpolationValue = this.aStartValue;
+ }
+ else
+ {
+ var aActualValue = this.aAnimation.getUnderlyingValue();
+ if( !this.equal( aActualValue, this.aPreviousValue ) )
+ this.aStartInterpolationValue = aActualValue;
+ }
+ }
+
+ var aValue = this.aInterpolator( this.aStartInterpolationValue,
+ this.aEndValue, nModifiedTime );
+
+ // According to the SMIL spec:
+ // Because 'to' animation is defined in terms of absolute values of
+ // the target attribute, cumulative animation is not defined.
+ if( this.bCumulative && !this.bDynamicStartValue )
+ {
+ // aValue = this.aEndValue * nRepeatCount + aValue;
+ aValue = this.add( this.scale( nRepeatCount, this.aEndValue ), aValue );
+ }
+
+ aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
+ this.aAnimation.perform( aValue );
+
+ if( this.bDynamicStartValue )
+ {
+ this.aPreviousValue = this.aAnimation.getUnderlyingValue();
+ }
+
+ };
+
+ // perform hook override for DiscreteActivityBase
+ FromToByActivity.prototype.performDiscreteHook = function( /*nFrame, nRepeatCount*/ )
+ {
+ if (this.isDisposed() || !this.aAnimation) {
+ log('FromToByActivity.performDiscreteHook: activity disposed or not valid animation');
+ return;
+ }
+ };
+
+ FromToByActivity.prototype.performEnd = function()
+ {
+ if( this.aAnimation )
+ {
+ var aValue = this.isAutoReverse() ? this.aStartValue : this.aEndValue;
+ aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
+ this.aAnimation.perform( aValue );
+ }
+ };
+
+ FromToByActivity.prototype.dispose = function()
+ {
+ FromToByActivity.superclass.dispose.call( this );
+ };
+
+
+ return FromToByActivity;
+}
+
+
+// FromToByActivity< ContinuousActivityBase > instantiation
+var LinearFromToByActivity = instantiate( FromToByActivityTemplate, ContinuousActivityBase );
+// FromToByActivity< DiscreteActivityBase > instantiation
+var DiscreteFromToByActivity = instantiate( FromToByActivityTemplate, DiscreteActivityBase );
+
+
+
+
+// ValueListActivity< BaseType > template class
+
+
+function ValueListActivityTemplate( BaseType ) // template parameter
+{
+
+ function ValueListActivity( aValueList, aActivityParamSet,
+ aAnimation, aInterpolator,
+ aOperatorSet, bAccumulate )
+ {
+ assert( aAnimation, 'ValueListActivity constructor: invalid animation object' );
+ assert( aValueList.length != 0, 'ValueListActivity: value list is empty' );
+
+ ValueListActivity.superclass.constructor.call( this, aActivityParamSet );
+
+ this.aValueList = aValueList;
+ this.aAnimation = aAnimation;
+ this.aInterpolator = aInterpolator;
+ this.add = aOperatorSet.add;
+ this.scale = aOperatorSet.scale;
+ this.bCumulative = bAccumulate;
+ this.aLastValue = this.aValueList[ this.aValueList.length - 1 ];
+ this.aFormula = aActivityParamSet.aFormula;
+ }
+ extend( ValueListActivity, BaseType );
+
+ ValueListActivity.prototype.activate = function( aEndEvent )
+ {
+ ValueListActivity.superclass.activate.call( this, aEndEvent );
+ for( var i = 0; i < this.aValueList.length; ++i )
+ {
+ ANIMDBG.print( 'createValueListActivity: value[' + i + '] = ' + this.aValueList[i] );
+ }
+ };
+
+ ValueListActivity.prototype.initAnimatedElement = function()
+ {
+ if( this.aAnimation )
+ {
+ var aValue = this.aValueList[0];
+ aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
+ this.aAnimation.perform(aValue);
+ }
+ };
+
+ ValueListActivity.prototype.startAnimation = function()
+ {
+ if( this.isDisposed() || !this.aAnimation )
+ {
+ log( 'ValueListActivity.startAnimation: activity disposed or not valid animation' );
+ return;
+ }
+
+ ValueListActivity.superclass.startAnimation.call( this );
+
+ this.aAnimation.start( this.getTargetElement() );
+ };
+
+ ValueListActivity.prototype.endAnimation = function()
+ {
+ if( this.aAnimation )
+ this.aAnimation.end();
+ };
+
+ // perform hook override for ContinuousKeyTimeActivityBase base
+ ValueListActivity.prototype.performContinuousHook = function( nIndex, nFractionalIndex, nRepeatCount )
+ {
+ if( this.isDisposed() || !this.aAnimation )
+ {
+ log( 'ValueListActivity.performContinuousHook: activity disposed or not valid animation' );
+ return;
+ }
+
+ assert( ( nIndex + 1 ) < this.aValueList.length,
+ 'ValueListActivity.performContinuousHook: assertion (nIndex + 1 < this.aValueList.length) failed' );
+
+ // interpolate between nIndex and nIndex+1 values
+
+ var aValue = this.aInterpolator( this.aValueList[ nIndex ],
+ this.aValueList[ nIndex+1 ],
+ nFractionalIndex );
+
+ if( this.bCumulative )
+ {
+ //aValue = aValue + nRepeatCount * this.aLastValue;
+ aValue = this.add( aValue, this.scale( nRepeatCount, this.aLastValue ) );
+ }
+
+ aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
+ this.aAnimation.perform( aValue );
+ };
+
+ // perform hook override for DiscreteActivityBase base
+ ValueListActivity.prototype.performDiscreteHook = function( nFrame, nRepeatCount )
+ {
+ if( this.isDisposed() || !this.aAnimation )
+ {
+ log( 'ValueListActivity.performDiscreteHook: activity disposed or not valid animation' );
+ return;
+ }
+
+ assert( nFrame < this.aValueList.length,
+ 'ValueListActivity.performDiscreteHook: assertion ( nFrame < this.aValueList.length) failed' );
+
+ // this is discrete, thus no lerp here.
+ var aValue = this.aValueList[nFrame];
+
+ if( this.bCumulative )
+ {
+ aValue = this.add( aValue, this.scale( nRepeatCount, this.aLastValue ) );
+ // for numbers: aValue = aValue + nRepeatCount * this.aLastValue;
+ // for enums, bools or strings: aValue = aValue;
+ }
+
+ aValue = this.aFormula ? this.aFormula( aValue ) : aValue;
+ this.aAnimation.perform( aValue );
+ };
+
+ ValueListActivity.prototype.performEnd = function()
+ {
+ if( this.aAnimation )
+ {
+ var aValue = this.aFormula ? this.aFormula( this.aLastValue ) : this.aLastValue;
+ this.aAnimation.perform( aValue );
+ }
+ };
+
+ ValueListActivity.prototype.dispose = function()
+ {
+ ValueListActivity.superclass.dispose.call( this );
+ };
+
+
+ return ValueListActivity;
+}
+
+
+// ValueListActivity< ContinuousKeyTimeActivityBase > instantiation
+var LinearValueListActivity = instantiate( ValueListActivityTemplate, ContinuousKeyTimeActivityBase );
+// ValueListActivity< DiscreteActivityBase > instantiation
+var DiscreteValueListActivity = instantiate( ValueListActivityTemplate, DiscreteActivityBase );
+
+
+
+/**********************************************************************************************
+ * Activity Factory
+ **********************************************************************************************/
+
+
+function createActivity( aActivityParamSet, aAnimationNode, aAnimation, aInterpolator )
+{
+ var eCalcMode = aAnimationNode.getCalcMode();
+
+ var sAttributeName = aAnimationNode.getAttributeName();
+ var aAttributeProp = aAttributeMap[ sAttributeName ];
+
+ var eValueType = aAttributeProp[ 'type' ];
+ var eValueSubtype = aAttributeProp[ 'subtype' ];
+
+ // do we need to get an interpolator ?
+ if( ! aInterpolator )
+ {
+ aInterpolator = aInterpolatorHandler.getInterpolator( eCalcMode,
+ eValueType,
+ eValueSubtype );
+ }
+
+ // is it cumulative ?
+ var bAccumulate = ( aAnimationNode.getAccumulate() === ACCUMULATE_MODE_SUM )
+ && !( eValueType === BOOL_PROPERTY ||
+ eValueType === STRING_PROPERTY ||
+ eValueType === ENUM_PROPERTY );
+
+ if( aAnimationNode.getFormula() )
+ {
+ var sFormula = aAnimationNode.getFormula();
+ var reMath = /abs|sqrt|asin|acos|atan|sin|cos|tan|exp|log|min|max/g;
+ sFormula = sFormula.replace(reMath, 'Math.$&');
+ sFormula = sFormula.replace(/pi(?!\w)/g, 'Math.PI');
+ sFormula = sFormula.replace(/e(?!\w)/g, 'Math.E');
+ sFormula = sFormula.replace(/\$/g, '__PARAM0__');
+
+ var aAnimatedElement = aAnimationNode.getAnimatedElement();
+ var aBBox = aAnimatedElement.getBaseBBox();
+
+ // the following variable are used for evaluating sFormula
+ /* eslint-disable no-unused-vars */
+ var width = aBBox.width / aActivityParamSet.nSlideWidth;
+ var height = aBBox.height / aActivityParamSet.nSlideHeight;
+ var x = ( aBBox.x + aBBox.width / 2 ) / aActivityParamSet.nSlideWidth;
+ var y = ( aBBox.y + aBBox.height / 2 ) / aActivityParamSet.nSlideHeight;
+
+ aActivityParamSet.aFormula = function( __PARAM0__ ) {
+
+ return eval(sFormula);
+ };
+ /* eslint-enable no-unused-vars */
+ }
+
+ aActivityParamSet.aDiscreteTimes = aAnimationNode.getKeyTimes();
+
+ // do we have a value list ?
+ var aValueSet = aAnimationNode.getValues();
+ var nValueSetSize = aValueSet.length;
+
+ if( nValueSetSize != 0 )
+ {
+ // Value list activity
+
+ if( aActivityParamSet.aDiscreteTimes.length == 0 )
+ {
+ for( var i = 0; i < nValueSetSize; ++i )
+ aActivityParamSet.aDiscreteTimes[i].push( i / nValueSetSize );
+ }
+
+ switch( eCalcMode )
+ {
+ case CALC_MODE_DISCRETE:
+ aActivityParamSet.aWakeupEvent =
+ new WakeupEvent( aActivityParamSet.aTimerEventQueue.getTimer(),
+ aActivityParamSet.aActivityQueue );
+
+ return createValueListActivity( aActivityParamSet,
+ aAnimationNode,
+ aAnimation,
+ aInterpolator,
+ DiscreteValueListActivity,
+ bAccumulate,
+ eValueType );
+
+ default:
+ log( 'createActivity: unexpected calculation mode: ' + eCalcMode );
+ // FALLTHROUGH intended
+ case CALC_MODE_PACED :
+ case CALC_MODE_SPLINE :
+ case CALC_MODE_LINEAR:
+ return createValueListActivity( aActivityParamSet,
+ aAnimationNode,
+ aAnimation,
+ aInterpolator,
+ LinearValueListActivity,
+ bAccumulate,
+ eValueType );
+ }
+ }
+ else
+ {
+ // FromToBy activity
+ switch( eCalcMode )
+ {
+ case CALC_MODE_DISCRETE:
+ log( 'createActivity: discrete calculation case not yet implemented' );
+ aActivityParamSet.aWakeupEvent =
+ new WakeupEvent( aActivityParamSet.aTimerEventQueue.getTimer(),
+ aActivityParamSet.aActivityQueue );
+ return createFromToByActivity( aActivityParamSet,
+ aAnimationNode,
+ aAnimation,
+ aInterpolator,
+ DiscreteFromToByActivity,
+ bAccumulate,
+ eValueType );
+
+ default:
+ log( 'createActivity: unexpected calculation mode: ' + eCalcMode );
+ // FALLTHROUGH intended
+ case CALC_MODE_PACED :
+ case CALC_MODE_SPLINE :
+ case CALC_MODE_LINEAR:
+ return createFromToByActivity( aActivityParamSet,
+ aAnimationNode,
+ aAnimation,
+ aInterpolator,
+ LinearFromToByActivity,
+ bAccumulate,
+ eValueType );
+ }
+ }
+}
+
+
+
+
+function createValueListActivity( aActivityParamSet, aAnimationNode, aAnimation,
+ aInterpolator, ClassTemplateInstance, bAccumulate, eValueType )
+{
+ var aAnimatedElement = aAnimationNode.getAnimatedElement();
+ var aOperatorSet = aOperatorSetMap[ eValueType ];
+ assert( aOperatorSet, 'createValueListActivity: no operator set found' );
+
+ var aValueSet = aAnimationNode.getValues();
+
+ var aValueList = [];
+
+ extractAttributeValues( eValueType,
+ aValueList,
+ aValueSet,
+ aAnimatedElement.getBaseBBox(),
+ aActivityParamSet.nSlideWidth,
+ aActivityParamSet.nSlideHeight );
+
+ for( var i = 0; i < aValueList.length; ++i )
+ {
+ ANIMDBG.print( 'createValueListActivity: value[' + i + '] = ' + aValueList[i] );
+ }
+
+ return new ClassTemplateInstance( aValueList, aActivityParamSet, aAnimation,
+ aInterpolator, aOperatorSet, bAccumulate );
+}
+
+
+
+
+function createFromToByActivity( aActivityParamSet, aAnimationNode, aAnimation,
+ aInterpolator, ClassTemplateInstance, bAccumulate, eValueType )
+{
+
+ var aAnimatedElement = aAnimationNode.getAnimatedElement();
+ var aOperatorSet = aOperatorSetMap[ eValueType ];
+ assert( aOperatorSet, 'createFromToByActivity: no operator set found' );
+
+ var aValueSet = [];
+ aValueSet[0] = aAnimationNode.getFromValue();
+ aValueSet[1] = aAnimationNode.getToValue();
+ aValueSet[2] = aAnimationNode.getByValue();
+
+ ANIMDBG.print( 'createFromToByActivity: value type: ' + aValueTypeOutMap[eValueType] +
+ ', aFrom = ' + aValueSet[0] +
+ ', aTo = ' + aValueSet[1] +
+ ', aBy = ' + aValueSet[2] );
+
+ var aValueList = [];
+
+ extractAttributeValues( eValueType,
+ aValueList,
+ aValueSet,
+ aAnimatedElement.getBaseBBox(),
+ aActivityParamSet.nSlideWidth,
+ aActivityParamSet.nSlideHeight );
+
+ ANIMDBG.print( 'createFromToByActivity: ' +
+ ', aFrom = ' + aValueList[0] +
+ ', aTo = ' + aValueList[1] +
+ ', aBy = ' + aValueList[2] );
+
+ return new ClassTemplateInstance( aValueList[0], aValueList[1], aValueList[2],
+ aActivityParamSet, aAnimation,
+ aInterpolator, aOperatorSet, bAccumulate );
+}
+
+
+
+function extractAttributeValues( eValueType, aValueList, aValueSet, aBBox, nSlideWidth, nSlideHeight )
+{
+ var i;
+ switch( eValueType )
+ {
+ case NUMBER_PROPERTY :
+ evalValuesAttribute( aValueList, aValueSet, aBBox, nSlideWidth, nSlideHeight );
+ break;
+ case BOOL_PROPERTY :
+ for( i = 0; i < aValueSet.length; ++i )
+ {
+ var aValue = booleanParser( aValueSet[i] );
+ aValueList.push( aValue );
+ }
+ break;
+ case STRING_PROPERTY :
+ for( i = 0; i < aValueSet.length; ++i )
+ {
+ aValueList.push( aValueSet[i] );
+ }
+ break;
+ case ENUM_PROPERTY :
+ for( i = 0; i < aValueSet.length; ++i )
+ {
+ aValueList.push( aValueSet[i] );
+ }
+ break;
+ case COLOR_PROPERTY :
+ for( i = 0; i < aValueSet.length; ++i )
+ {
+ aValue = colorParser( aValueSet[i] );
+ aValueList.push( aValue );
+ }
+ break;
+ case TUPLE_NUMBER_PROPERTY :
+ for( i = 0; i < aValueSet.length; ++i )
+ {
+ if( typeof aValueSet[i] === 'string' )
+ {
+ var aTuple = aValueSet[i].split(',');
+ aValue = [];
+ evalValuesAttribute(aValue, aTuple, aBBox, nSlideWidth, nSlideHeight);
+ aValueList.push(aValue);
+ }
+ else
+ {
+ aValueList.push( undefined );
+ }
+ }
+ break;
+ default:
+ log( 'createValueListActivity: unexpected value type: ' + eValueType );
+ }
+
+}
+
+
+function evalValuesAttribute( aValueList, aValueSet, aBBox, nSlideWidth, nSlideHeight )
+{
+ // the following variables are used for evaluating sValue later
+ /* eslint-disable no-unused-vars */
+ var width = aBBox.width / nSlideWidth;
+ var height = aBBox.height / nSlideHeight;
+ var x = ( aBBox.x + aBBox.width / 2 ) / nSlideWidth;
+ var y = ( aBBox.y + aBBox.height / 2 ) / nSlideHeight;
+ /* eslint-enable no-unused-vars */
+
+ var reMath = /abs|sqrt|asin|acos|atan|sin|cos|tan|exp|log|min|max/g;
+
+ for( var i = 0; i < aValueSet.length; ++i )
+ {
+ var sValue = aValueSet[i];
+ if(sValue)
+ {
+ sValue = sValue.replace(reMath, 'Math.$&');
+ sValue = sValue.replace(/pi(?!\w)/g, 'Math.PI');
+ sValue = sValue.replace(/e(?!\w)/g, 'Math.E');
+ }
+ var aValue = eval( sValue );
+ aValueList.push( aValue );
+ }
+}
+
+
+
+/**********************************************************************************************
+ * SlideShow, SlideShowContext and FrameSynchronization
+ **********************************************************************************************/
+
+
+
+// direction of animation, important: not change the values!
+var BACKWARD = 0;
+var FORWARD = 1;
+
+var MAXIMUM_FRAME_COUNT = 60;
+var MINIMUM_TIMEOUT = 1.0 / MAXIMUM_FRAME_COUNT;
+var MAXIMUM_TIMEOUT = 4.0;
+var MINIMUM_FRAMES_PER_SECONDS = 10;
+var PREFERRED_FRAMES_PER_SECONDS = 50;
+var PREFERRED_FRAME_RATE = 1.0 / PREFERRED_FRAMES_PER_SECONDS;
+
+
+function Effect( nId )
+{
+ this.nId = ( typeof( nId ) === typeof( 1 ) ) ? nId : -1;
+ this.eState = Effect.NOT_STARTED;
+}
+Effect.NOT_STARTED = 0;
+Effect.PLAYING = 1;
+Effect.ENDED = 2;
+
+Effect.prototype.getId = function()
+{
+ return this.nId;
+};
+
+Effect.prototype.isMainEffect = function()
+{
+ return ( this.nId === -1 );
+};
+
+Effect.prototype.isPlaying = function()
+{
+ return ( this.eState === Effect.PLAYING );
+};
+
+Effect.prototype.isEnded = function()
+{
+ return ( this.eState === Effect.ENDED );
+};
+
+Effect.prototype.start = function()
+{
+ assert( this.eState === Effect.NOT_STARTED, 'Effect.start: wrong state.' );
+ this.eState = Effect.PLAYING;
+};
+
+Effect.prototype.end = function()
+{
+ assert( this.eState === Effect.PLAYING, 'Effect.end: wrong state.' );
+ this.eState = Effect.ENDED;
+};
+
+
+
+function SlideShow()
+{
+ this.aTimer = new ElapsedTime();
+ this.aFrameSynchronization = new FrameSynchronization( PREFERRED_FRAME_RATE );
+ this.aTimerEventQueue = new TimerEventQueue( this.aTimer );
+ this.aActivityQueue = new ActivityQueue( this.aTimer );
+ this.aNextEffectEventArray = null;
+ this.aInteractiveAnimationSequenceMap = null;
+ this.aEventMultiplexer = null;
+
+ this.aContext = new SlideShowContext( this.aTimerEventQueue,
+ this.aEventMultiplexer,
+ this.aNextEffectEventArray,
+ this.aInteractiveAnimationSequenceMap,
+ this.aActivityQueue );
+ this.bIsIdle = true;
+ this.bIsEnabled = true;
+ this.bNoSlideTransition = false;
+ this.bIsTransitionRunning = false;
+
+ this.nCurrentEffect = 0;
+ this.bIsNextEffectRunning = false;
+ this.bIsRewinding = false;
+ this.bIsSkipping = false;
+ this.bIsSkippingAll = false;
+ this.nTotalInteractivePlayingEffects = 0;
+ this.aStartedEffectList = [];
+ this.aStartedEffectIndexMap = {};
+ this.aStartedEffectIndexMap[ -1 ] = undefined;
+ this.automaticAdvanceTimeout = null;
+}
+
+SlideShow.prototype.setSlideEvents = function( aNextEffectEventArray,
+ aInteractiveAnimationSequenceMap,
+ aEventMultiplexer )
+{
+ if( !aNextEffectEventArray )
+ log( 'SlideShow.setSlideEvents: aNextEffectEventArray is not valid' );
+
+ if( !aInteractiveAnimationSequenceMap )
+ log( 'SlideShow.setSlideEvents:aInteractiveAnimationSequenceMap is not valid' );
+
+ if( !aEventMultiplexer )
+ log( 'SlideShow.setSlideEvents: aEventMultiplexer is not valid' );
+
+ this.aContext.aNextEffectEventArray = aNextEffectEventArray;
+ this.aNextEffectEventArray = aNextEffectEventArray;
+ this.aContext.aInteractiveAnimationSequenceMap = aInteractiveAnimationSequenceMap;
+ this.aInteractiveAnimationSequenceMap = aInteractiveAnimationSequenceMap;
+ this.aContext.aEventMultiplexer = aEventMultiplexer;
+ this.aEventMultiplexer = aEventMultiplexer;
+ this.nCurrentEffect = 0;
+};
+
+SlideShow.prototype.createSlideTransition = function( aSlideTransitionHandler, aLeavingSlide, aEnteringSlide, aTransitionEndEvent )
+{
+ if( !aEnteringSlide )
+ {
+ log( 'SlideShow.createSlideTransition: entering slide element is not valid.' );
+ return null;
+ }
+
+ if( this.bNoSlideTransition ) return null;
+
+ var aAnimatedLeavingSlide = null;
+ if( aLeavingSlide )
+ aAnimatedLeavingSlide = new AnimatedSlide( aLeavingSlide );
+ var aAnimatedEnteringSlide = new AnimatedSlide( aEnteringSlide );
+
+ var aSlideTransition = aSlideTransitionHandler.createSlideTransition( aAnimatedLeavingSlide, aAnimatedEnteringSlide );
+ if( !aSlideTransition ) return null;
+
+ // compute duration
+ var nDuration = 0.001;
+ if( aSlideTransitionHandler.getDuration().isValue() )
+ {
+ nDuration = aSlideTransitionHandler.getDuration().getValue();
+ }
+ else
+ {
+ log( 'SlideShow.createSlideTransition: duration is not a number' );
+ }
+
+ var aCommonParameterSet = new ActivityParamSet();
+ aCommonParameterSet.aEndEvent = aTransitionEndEvent;
+ aCommonParameterSet.aTimerEventQueue = this.aTimerEventQueue;
+ aCommonParameterSet.aActivityQueue = this.aActivityQueue;
+ aCommonParameterSet.nMinDuration = nDuration;
+ aCommonParameterSet.nMinNumberOfFrames = aSlideTransitionHandler.getMinFrameCount();
+ aCommonParameterSet.nSlideWidth = WIDTH;
+ aCommonParameterSet.nSlideHeight = HEIGHT;
+
+ return new SimpleActivity( aCommonParameterSet, aSlideTransition, FORWARD );
+
+};
+
+SlideShow.prototype.isEnabled = function()
+{
+ return this.bIsEnabled;
+};
+
+SlideShow.prototype.isRunning = function()
+{
+ return !this.bIsIdle;
+};
+
+SlideShow.prototype.isTransitionPlaying = function()
+{
+ return this.bIsTransitionRunning;
+};
+
+SlideShow.prototype.isMainEffectPlaying = function()
+{
+ return this.bIsNextEffectRunning;
+};
+
+SlideShow.prototype.isInteractiveEffectPlaying = function()
+{
+ return ( this.nTotalInteractivePlayingEffects > 0 );
+};
+
+SlideShow.prototype.isAnyEffectPlaying = function()
+{
+ return ( this.isMainEffectPlaying() || this.isInteractiveEffectPlaying() );
+};
+
+SlideShow.prototype.hasAnyEffectStarted = function()
+{
+ return ( this.aStartedEffectList.length > 0 );
+};
+
+SlideShow.prototype.notifyNextEffectStart = function()
+{
+ assert( !this.bIsNextEffectRunning,
+ 'SlideShow.notifyNextEffectStart: an effect is already started.' );
+ this.bIsNextEffectRunning = true;
+ this.aEventMultiplexer.registerNextEffectEndHandler( bind2( SlideShow.prototype.notifyNextEffectEnd, this ) );
+ var aEffect = new Effect();
+ aEffect.start();
+ this.aStartedEffectIndexMap[ -1 ] = this.aStartedEffectList.length;
+ this.aStartedEffectList.push( aEffect );
+
+ var aAnimatedElementMap = theMetaDoc.aMetaSlideSet[nCurSlide].aSlideAnimationsHandler.aAnimatedElementMap;
+ for( var sId in aAnimatedElementMap )
+ aAnimatedElementMap[ sId ].notifyNextEffectStart( this.nCurrentEffect );
+};
+
+SlideShow.prototype.notifyNextEffectEnd = function()
+{
+ assert( this.bIsNextEffectRunning,
+ 'SlideShow.notifyNextEffectEnd: effect already ended.' );
+ this.bIsNextEffectRunning = false;
+
+ this.aStartedEffectList[ this.aStartedEffectIndexMap[ -1 ] ].end();
+ if( this.automaticAdvanceTimeout !== null )
+ {
+ if( this.automaticAdvanceTimeout['rewindedEffect'] === this.nCurrentEffect )
+ {
+ this.automaticAdvanceTimeout = null;
+ this.notifyAnimationsEnd();
+ }
+ }
+};
+
+SlideShow.prototype.notifyAnimationsEnd = function()
+{
+ if( nCurSlide + 1 === theMetaDoc.nNumberOfSlides )
+ return;
+
+ assert (this.automaticAdvanceTimeout === null,
+ 'SlideShow.notifyAnimationsEnd: Timeout already set.')
+
+ var nTimeout = Math.ceil(theMetaDoc.aMetaSlideSet[nCurSlide].fDuration * 1000);
+ if( nTimeout < 0 )
+ return;
+
+ this.automaticAdvanceTimeout = window.setTimeout('switchSlide(1, false)', nTimeout);
+};
+
+SlideShow.prototype.notifySlideStart = function( nNewSlideIndex, nOldSlideIndex )
+{
+ this.nCurrentEffect = 0;
+ this.bIsRewinding = false;
+ this.bIsSkipping = false;
+ this.bIsSkippingAll = false;
+ this.nTotalInteractivePlayingEffects = 0;
+ this.aStartedEffectList = [];
+ this.aStartedEffectIndexMap = {};
+ this.aStartedEffectIndexMap[ -1 ] = undefined;
+
+ var aAnimatedElementMap;
+ var sId;
+ if( nOldSlideIndex !== undefined )
+ {
+ aAnimatedElementMap = theMetaDoc.aMetaSlideSet[nOldSlideIndex].aSlideAnimationsHandler.aAnimatedElementMap;
+ for( sId in aAnimatedElementMap )
+ aAnimatedElementMap[ sId ].notifySlideEnd();
+ }
+
+ aAnimatedElementMap = theMetaDoc.aMetaSlideSet[nNewSlideIndex].aSlideAnimationsHandler.aAnimatedElementMap;
+ for( sId in aAnimatedElementMap )
+ aAnimatedElementMap[ sId ].notifySlideStart( this.aContext );
+};
+
+SlideShow.prototype.notifyTransitionEnd = function( nSlideIndex )
+{
+ // reset the presentation clip path on the leaving slide
+ // to the standard one when transition ends
+ if( theMetaDoc.getCurrentSlide() )
+ {
+ var sRef = 'url(#' + aPresentationClipPathId + ')';
+ theMetaDoc.getCurrentSlide().slideElement.setAttribute('clip-path', sRef);
+ }
+
+ this.bIsTransitionRunning = false;
+ if( this.bIsRewinding )
+ {
+ theMetaDoc.aMetaSlideSet[nSlideIndex].hide();
+ var nIndex = nCurSlide !== undefined ? nCurSlide : -1;
+ this.displaySlide( nIndex, true );
+ this.skipAllEffects();
+ this.bIsRewinding = false;
+ return;
+ }
+
+ theMetaDoc.setCurrentSlide(nSlideIndex);
+
+ if( this.aSlideViewElement )
+ {
+ theMetaDoc.getCurrentSlide().aVisibilityStatusElement.parentNode.removeChild( this.aSlideViewElement );
+ this.aSlideViewElement = null;
+ }
+ if( this.isEnabled() )
+ {
+ // clear all queues
+ this.dispose();
+
+ var aCurrentSlide = theMetaDoc.getCurrentSlide();
+ if( aCurrentSlide.aSlideAnimationsHandler.elementsParsed() )
+ {
+ aCurrentSlide.aSlideAnimationsHandler.start();
+ this.aEventMultiplexer.registerAnimationsEndHandler( bind2( SlideShow.prototype.notifyAnimationsEnd, this ) );
+ }
+ else
+ this.notifyAnimationsEnd();
+
+ this.update();
+ }
+ else
+ this.notifyAnimationsEnd();
+};
+
+SlideShow.prototype.notifyInteractiveAnimationSequenceStart = function( nNodeId )
+{
+ ++this.nTotalInteractivePlayingEffects;
+ var aEffect = new Effect( nNodeId );
+ aEffect.start();
+ this.aStartedEffectIndexMap[ nNodeId ] = this.aStartedEffectList.length;
+ this.aStartedEffectList.push( aEffect );
+};
+
+SlideShow.prototype.notifyInteractiveAnimationSequenceEnd = function( nNodeId )
+{
+ assert( this.isInteractiveEffectPlaying(),
+ 'SlideShow.notifyInteractiveAnimationSequenceEnd: no interactive effect playing.' );
+
+ this.aStartedEffectList[ this.aStartedEffectIndexMap[ nNodeId ] ].end();
+ --this.nTotalInteractivePlayingEffects;
+};
+
+/** nextEffect
+ * Start the next effect belonging to the main animation sequence if any.
+ * If there is an already playing effect belonging to any animation sequence
+ * it is skipped.
+ *
+ * @return {Boolean}
+ * False if there is no more effect to start, true otherwise.
+ */
+SlideShow.prototype.nextEffect = function()
+{
+ if( !this.isEnabled() )
+ return false;
+
+ if( this.isTransitionPlaying() )
+ {
+ this.skipTransition();
+ return true;
+ }
+
+ if( this.isAnyEffectPlaying() )
+ {
+ this.skipAllPlayingEffects();
+ return true;
+ }
+
+ if( !this.aNextEffectEventArray )
+ return false;
+
+ if( this.nCurrentEffect >= this.aNextEffectEventArray.size() )
+ return false;
+
+ this.notifyNextEffectStart();
+
+ this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
+ ++this.nCurrentEffect;
+ this.update();
+ return true;
+};
+
+/** skipTransition
+ * Skip the current playing slide transition.
+ */
+SlideShow.prototype.skipTransition = function()
+{
+ if( this.bIsSkipping || this.bIsRewinding )
+ return;
+
+ this.bIsSkipping = true;
+
+ this.aActivityQueue.endAll();
+ this.aTimerEventQueue.forceEmpty();
+ this.aActivityQueue.endAll();
+ this.update();
+ this.bIsSkipping = false;
+};
+
+/** skipAllPlayingEffects
+ * Skip all playing effect, independently to which animation sequence they
+ * belong.
+ *
+ */
+SlideShow.prototype.skipAllPlayingEffects = function()
+{
+ if( this.bIsSkipping || this.bIsRewinding )
+ return true;
+
+ this.bIsSkipping = true;
+ // TODO: The correct order should be based on the left playing time.
+ for( var i = 0; i < this.aStartedEffectList.length; ++i )
+ {
+ var aEffect = this.aStartedEffectList[i];
+ if( aEffect.isPlaying() )
+ {
+ if( aEffect.isMainEffect() )
+ this.aEventMultiplexer.notifySkipEffectEvent();
+ else
+ this.aEventMultiplexer.notifySkipInteractiveEffectEvent( aEffect.getId() );
+ }
+ }
+ this.update();
+ this.bIsSkipping = false;
+ return true;
+};
+
+/** skipNextEffect
+ * Skip the next effect to be played (if any) that belongs to the main
+ * animation sequence.
+ * Require: no effect is playing.
+ *
+ * @return {Boolean}
+ * False if there is no more effect to skip, true otherwise.
+ */
+SlideShow.prototype.skipNextEffect = function()
+{
+ if( this.bIsSkipping || this.bIsRewinding )
+ return true;
+
+ assert( !this.isAnyEffectPlaying(),
+ 'SlideShow.skipNextEffect' );
+
+ if( !this.aNextEffectEventArray )
+ return false;
+
+ if( this.nCurrentEffect >= this.aNextEffectEventArray.size() )
+ return false;
+
+ this.notifyNextEffectStart();
+
+ this.bIsSkipping = true;
+ this.aNextEffectEventArray.at( this.nCurrentEffect ).fire();
+ this.aEventMultiplexer.notifySkipEffectEvent();
+ ++this.nCurrentEffect;
+ this.update();
+ this.bIsSkipping = false;
+ return true;
+};
+
+/** skipPlayingOrNextEffect
+ * Skip the next effect to be played that belongs to the main animation
+ * sequence or all playing effects.
+ *
+ * @return {Boolean}
+ * False if there is no more effect to skip, true otherwise.
+ */
+SlideShow.prototype.skipPlayingOrNextEffect = function()
+{
+ if( this.isTransitionPlaying() )
+ {
+ this.skipTransition();
+ return true;
+ }
+
+ if( this.isAnyEffectPlaying() )
+ return this.skipAllPlayingEffects();
+ else
+ return this.skipNextEffect();
+};
+
+
+/** skipAllEffects
+ * Skip all left effects that belongs to the main animation sequence and all
+ * playing effects on the current slide.
+ *
+ * @return {Boolean}
+ * True if it already skipping or when it has ended skipping,
+ * false if the next slide needs to be displayed.
+ */
+SlideShow.prototype.skipAllEffects = function()
+{
+ if( this.bIsSkippingAll )
+ return true;
+
+ this.bIsSkippingAll = true;
+
+ if( this.isTransitionPlaying() )
+ {
+ this.skipTransition();
+ }
+
+ if( this.isAnyEffectPlaying() )
+ {
+ this.skipAllPlayingEffects();
+ }
+ else if( !this.aNextEffectEventArray
+ || ( this.nCurrentEffect >= this.aNextEffectEventArray.size() ) )
+ {
+ this.bIsSkippingAll = false;
+ return false;
+ }
+
+ // Pay attention here: a new next effect event is appended to
+ // aNextEffectEventArray only after the related animation node has been
+ // resolved, that is only after the animation node related to the previous
+ // effect has notified to be deactivated to the main sequence time container.
+ // So you should avoid any optimization here because the size of
+ // aNextEffectEventArray will going on increasing after every skip action.
+ while( this.nCurrentEffect < this.aNextEffectEventArray.size() )
+ {
+ this.skipNextEffect();
+ }
+ this.bIsSkippingAll = false;
+ return true;
+};
+
+/** rewindTransition
+ * Rewind the current playing slide transition.
+ */
+SlideShow.prototype.rewindTransition = function()
+{
+ if( this.bIsSkipping || this.bIsRewinding )
+ return;
+
+ this.bIsRewinding = true;
+ this.aActivityQueue.endAll();
+ this.update();
+ this.bIsRewinding = false;
+};
+
+/** rewindEffect
+ * Rewind all the effects started after at least one of the current playing
+ * effects. If there is no playing effect, it rewinds the last played one,
+ * both in case it belongs to the main or to an interactive animation sequence.
+ *
+ */
+SlideShow.prototype.rewindEffect = function()
+{
+ if( this.bIsSkipping || this.bIsRewinding )
+ return;
+
+ if( this.automaticAdvanceTimeout !== null && !this.automaticAdvanceTimeout['rewindedEffect'] )
+ {
+ window.clearTimeout( this.automaticAdvanceTimeout );
+ this.automaticAdvanceTimeout = { 'rewindedEffect': this.nCurrentEffect };
+ }
+
+ if( !this.hasAnyEffectStarted() )
+ {
+ this.rewindToPreviousSlide();
+ return;
+ }
+
+ this.bIsRewinding = true;
+
+ var nFirstPlayingEffectIndex = undefined;
+
+ var i = 0;
+ for( ; i < this.aStartedEffectList.length; ++i )
+ {
+ var aEffect = this.aStartedEffectList[i];
+ if( aEffect.isPlaying() )
+ {
+ nFirstPlayingEffectIndex = i;
+ break;
+ }
+ }
+
+ // There is at least one playing effect.
+ if( nFirstPlayingEffectIndex !== undefined )
+ {
+ i = this.aStartedEffectList.length - 1;
+ for( ; i >= nFirstPlayingEffectIndex; --i )
+ {
+ aEffect = this.aStartedEffectList[i];
+ if( aEffect.isPlaying() )
+ {
+ if( aEffect.isMainEffect() )
+ {
+ this.aEventMultiplexer.notifyRewindCurrentEffectEvent();
+ if( this.nCurrentEffect > 0 )
+ --this.nCurrentEffect;
+ }
+ else
+ {
+ this.aEventMultiplexer.notifyRewindRunningInteractiveEffectEvent( aEffect.getId() );
+ }
+ }
+ else if( aEffect.isEnded() )
+ {
+ if( aEffect.isMainEffect() )
+ {
+ this.aEventMultiplexer.notifyRewindLastEffectEvent();
+ if( this.nCurrentEffect > 0 )
+ --this.nCurrentEffect;
+ }
+ else
+ {
+ this.aEventMultiplexer.notifyRewindEndedInteractiveEffectEvent( aEffect.getId() );
+ }
+ }
+ }
+ this.update();
+
+ // Pay attention here: we need to remove all rewinded effects from
+ // the started effect list only after updating.
+ i = this.aStartedEffectList.length - 1;
+ for( ; i >= nFirstPlayingEffectIndex; --i )
+ {
+ aEffect = this.aStartedEffectList.pop();
+ if( !aEffect.isMainEffect() )
+ delete this.aStartedEffectIndexMap[ aEffect.getId() ];
+ }
+ }
+ else // there is no playing effect
+ {
+ aEffect = this.aStartedEffectList.pop();
+ if( !aEffect.isMainEffect() )
+ delete this.aStartedEffectIndexMap[ aEffect.getId() ];
+ if( aEffect.isEnded() ) // Well that is almost an assertion.
+ {
+ if( aEffect.isMainEffect() )
+ {
+ this.aEventMultiplexer.notifyRewindLastEffectEvent();
+ if( this.nCurrentEffect > 0 )
+ --this.nCurrentEffect;
+ }
+ else
+ {
+ this.aEventMultiplexer.notifyRewindEndedInteractiveEffectEvent( aEffect.getId() );
+ }
+ }
+ this.update();
+ }
+
+ this.bIsRewinding = false;
+};
+
+/** rewindToPreviousSlide
+ * Displays the previous slide with all effects, that belong to the main
+ * animation sequence, played.
+ *
+ */
+SlideShow.prototype.rewindToPreviousSlide = function()
+{
+ if( this.isTransitionPlaying() )
+ {
+ this.rewindTransition();
+ return;
+ }
+ if( this.isAnyEffectPlaying() )
+ return;
+ var nNewSlide = nCurSlide - 1;
+ this.displaySlide( nNewSlide, true );
+ this.skipAllEffects();
+};
+
+/** rewindAllEffects
+ * Rewind all effects already played on the current slide.
+ *
+ */
+SlideShow.prototype.rewindAllEffects = function()
+{
+ if( !this.hasAnyEffectStarted() )
+ {
+ this.rewindToPreviousSlide();
+ return;
+ }
+
+ while( this.hasAnyEffectStarted() )
+ {
+ this.rewindEffect();
+ }
+};
+
+SlideShow.prototype.exitSlideShowInApp = function()
+{
+ if (window.webkit !== undefined &&
+ window.webkit.messageHandlers !== undefined &&
+ window.webkit.messageHandlers.cool !== undefined)
+ window.webkit.messageHandlers.cool.postMessage('EXITSLIDESHOW', '*');
+}
+
+SlideShow.prototype.displaySlide = function( nNewSlide, bSkipSlideTransition )
+{
+ var aMetaDoc = theMetaDoc;
+ var nSlides = aMetaDoc.nNumberOfSlides;
+ if( nNewSlide < 0 && nSlides > 0 )
+ nNewSlide = nSlides - 1;
+ else if( nNewSlide >= nSlides ) {
+ nNewSlide = 0;
+ // In the iOS app, exit the slideshow when going past the end.
+ this.exitSlideShowInApp();
+ }
+
+ if( ( currentMode === INDEX_MODE ) && ( nNewSlide === nCurSlide ) )
+ {
+ aMetaDoc.getCurrentSlide().show();
+ return;
+ }
+
+ if( this.isTransitionPlaying() )
+ {
+ this.skipTransition();
+ }
+
+ // handle current slide
+ var nOldSlide = nCurSlide;
+ if( nOldSlide !== undefined )
+ {
+ var oldMetaSlide = aMetaDoc.aMetaSlideSet[nOldSlide];
+ if( this.isEnabled() )
+ {
+ if( oldMetaSlide.aSlideAnimationsHandler.isAnimated() )
+ {
+ // force end animations
+ oldMetaSlide.aSlideAnimationsHandler.end( bSkipSlideTransition );
+
+ // clear all queues
+ this.dispose();
+ }
+ }
+
+ if( this.automaticAdvanceTimeout !== null )
+ {
+ window.clearTimeout( this.automaticAdvanceTimeout );
+ this.automaticAdvanceTimeout = null;
+ }
+ }
+
+ this.notifySlideStart( nNewSlide, nOldSlide );
+
+ if( this.isEnabled() && !bSkipSlideTransition )
+ {
+ // create slide transition and add to activity queue
+ if ( ( ( nOldSlide !== undefined ) &&
+ ( ( nNewSlide > nOldSlide ) ||
+ ( ( nNewSlide == 0) && ( nOldSlide == (aMetaDoc.nNumberOfSlides - 1) ) ) ) ) ||
+ ( ( nOldSlide === undefined ) && ( nNewSlide == 0) ) // for transition on first slide
+ )
+ {
+
+ var aOldMetaSlide = null;
+ if( nOldSlide === undefined ) // for transition on first slide
+ {
+ aOldMetaSlide = aMetaDoc.theMetaDummySlide;
+ }
+ else
+ {
+ aOldMetaSlide = aMetaDoc.aMetaSlideSet[nOldSlide];
+ }
+ var aNewMetaSlide = aMetaDoc.aMetaSlideSet[nNewSlide];
+
+ var aSlideTransitionHandler = aNewMetaSlide.aTransitionHandler;
+ if( aSlideTransitionHandler && aSlideTransitionHandler.isValid() )
+ {
+ // clipPath element used for the leaving slide in order
+ // to avoid that slide borders are visible during transition
+ var sRef = 'url(#' + aPresentationClipPathShrinkId + ')';
+ aOldMetaSlide.slideElement.setAttribute( 'clip-path', sRef );
+
+ // when we switch from the last to the first slide we need to hide the last slide
+ // or nobody will see the transition, hence we create a view of the last slide and
+ // we place it before the first slide
+ if( nOldSlide > nNewSlide )
+ {
+ this.aSlideViewElement = document.createElementNS( NSS['svg'], 'use' );
+ setNSAttribute( 'xlink', this.aSlideViewElement, 'href', '#' + aOldMetaSlide.slideContainerId );
+ aNewMetaSlide.aVisibilityStatusElement.parentNode.insertBefore( this.aSlideViewElement, aNewMetaSlide.aVisibilityStatusElement );
+ aOldMetaSlide.hide();
+ }
+
+ var aLeavingSlide = aOldMetaSlide;
+ var aEnteringSlide = aNewMetaSlide;
+ var aTransitionEndEvent = makeEvent( bind2( this.notifyTransitionEnd, this, nNewSlide ) );
+
+ var aTransitionActivity =
+ this.createSlideTransition( aSlideTransitionHandler, aLeavingSlide,
+ aEnteringSlide, aTransitionEndEvent );
+
+ if( aTransitionActivity )
+ {
+ this.bIsTransitionRunning = true;
+ this.aActivityQueue.addActivity( aTransitionActivity );
+ this.update();
+ }
+ else
+ {
+ this.notifyTransitionEnd( nNewSlide );
+ }
+ }
+ else
+ {
+ this.notifyTransitionEnd( nNewSlide );
+ }
+ }
+ else
+ {
+ this.notifyTransitionEnd( nNewSlide );
+ }
+ }
+ else
+ {
+ this.notifyTransitionEnd( nNewSlide );
+ }
+
+};
+
+SlideShow.prototype.update = function()
+{
+ this.aTimer.holdTimer();
+
+ // process queues
+ this.aTimerEventQueue.process();
+ this.aActivityQueue.process();
+
+ this.aFrameSynchronization.synchronize();
+
+ this.aActivityQueue.processDequeued();
+
+ this.aTimer.releaseTimer();
+
+ var bActivitiesLeft = ( ! this.aActivityQueue.isEmpty() );
+ var bTimerEventsLeft = ( ! this.aTimerEventQueue.isEmpty() );
+ var bEventsLeft = ( bActivitiesLeft || bTimerEventsLeft );
+
+
+ if( bEventsLeft )
+ {
+ var nNextTimeout;
+ if( bActivitiesLeft )
+ {
+ nNextTimeout = MINIMUM_TIMEOUT;
+ this.aFrameSynchronization.activate();
+ }
+ else
+ {
+ nNextTimeout = this.aTimerEventQueue.nextTimeout();
+ if( nNextTimeout < MINIMUM_TIMEOUT )
+ nNextTimeout = MINIMUM_TIMEOUT;
+ else if( nNextTimeout > MAXIMUM_TIMEOUT )
+ nNextTimeout = MAXIMUM_TIMEOUT;
+ this.aFrameSynchronization.deactivate();
+ }
+
+ this.bIsIdle = false;
+ window.setTimeout( 'aSlideShow.update()', nNextTimeout * 1000 );
+ }
+ else
+ {
+ this.bIsIdle = true;
+ }
+};
+
+SlideShow.prototype.dispose = function()
+{
+ // clear all queues
+ this.aTimerEventQueue.clear();
+ this.aActivityQueue.clear();
+ this.aNextEffectEventArray = null;
+ this.aEventMultiplexer = null;
+};
+
+SlideShow.prototype.getContext = function()
+{
+ return this.aContext;
+};
+
+// the SlideShow global instance
+var aSlideShow = null;
+
+
+
+
+function SlideShowContext( aTimerEventQueue, aEventMultiplexer, aNextEffectEventArray, aInteractiveAnimationSequenceMap, aActivityQueue)
+{
+ this.aTimerEventQueue = aTimerEventQueue;
+ this.aEventMultiplexer = aEventMultiplexer;
+ this.aNextEffectEventArray = aNextEffectEventArray;
+ this.aInteractiveAnimationSequenceMap = aInteractiveAnimationSequenceMap;
+ this.aActivityQueue = aActivityQueue;
+ this.bIsSkipping = false;
+}
+
+
+
+
+function FrameSynchronization( nFrameDuration )
+{
+ this.nFrameDuration = nFrameDuration;
+ this.aTimer = new ElapsedTime();
+ this.nNextFrameTargetTime = 0.0;
+ this.bIsActive = false;
+
+ this.markCurrentFrame();
+}
+
+
+FrameSynchronization.prototype.markCurrentFrame = function()
+{
+ this.nNextFrameTargetTime = this.aTimer.getElapsedTime() + this.nFrameDuration;
+};
+
+FrameSynchronization.prototype.synchronize = function()
+{
+ if( this.bIsActive )
+ {
+ // Do busy waiting for now.
+ while( this.aTimer.getElapsedTime() < this.nNextFrameTargetTime )
+ ;
+ }
+
+ this.markCurrentFrame();
+
+};
+
+FrameSynchronization.prototype.activate = function()
+{
+ this.bIsActive = true;
+};
+
+FrameSynchronization.prototype.deactivate = function()
+{
+ this.bIsActive = false;
+};
+
+
+
+/**********************************************************************************************
+ * TimerEventQueue, ActivityQueue and ElapsedTime
+ **********************************************************************************************/
+
+
+function NextEffectEventArray()
+{
+ this.aEventArray = [];
+}
+
+
+NextEffectEventArray.prototype.size = function()
+{
+ return this.aEventArray.length;
+};
+
+NextEffectEventArray.prototype.at = function( nIndex )
+{
+ return this.aEventArray[ nIndex ];
+};
+
+NextEffectEventArray.prototype.appendEvent = function( aEvent )
+{
+ var nSize = this.size();
+ for( var i = 0; i < nSize; ++i )
+ {
+ if( this.aEventArray[i].getId() == aEvent.getId() )
+ {
+ aNextEffectEventArrayDebugPrinter.print( 'NextEffectEventArray.appendEvent: event(' + aEvent.getId() + ') already present' );
+ return false;
+ }
+ }
+ this.aEventArray.push( aEvent );
+ aNextEffectEventArrayDebugPrinter.print( 'NextEffectEventArray.appendEvent: event(' + aEvent.getId() + ') appended' );
+ return true;
+};
+
+NextEffectEventArray.prototype.clear = function( )
+{
+ this.aEventArray = [];
+};
+
+
+
+
+function TimerEventQueue( aTimer )
+{
+ this.aTimer = aTimer;
+ this.aEventSet = new PriorityQueue( EventEntry.compare );
+}
+
+
+TimerEventQueue.prototype.addEvent = function( aEvent )
+{
+ this.DBG( 'TimerEventQueue.addEvent event(' + aEvent.getId() + ') appended.' );
+ if( !aEvent )
+ {
+ log( 'TimerEventQueue.addEvent: null event' );
+ return false;
+ }
+
+ var nTime = aEvent.getActivationTime( this.aTimer.getElapsedTime() );
+ var aEventEntry = new EventEntry( aEvent, nTime );
+ this.aEventSet.push( aEventEntry );
+
+ return true;
+};
+
+TimerEventQueue.prototype.forceEmpty = function()
+{
+ this.process_(true);
+};
+
+
+TimerEventQueue.prototype.process = function()
+{
+ this.process_(false);
+};
+
+TimerEventQueue.prototype.process_ = function( bFireAllEvents )
+{
+ var nCurrentTime = this.aTimer.getElapsedTime();
+
+ while( !this.isEmpty() && ( bFireAllEvents || ( this.aEventSet.top().nActivationTime <= nCurrentTime ) ) )
+ {
+ var aEventEntry = this.aEventSet.top();
+ this.aEventSet.pop();
+
+ var aEvent = aEventEntry.aEvent;
+ if( aEvent.isCharged() )
+ aEvent.fire();
+ }
+};
+
+TimerEventQueue.prototype.isEmpty = function()
+{
+ return this.aEventSet.isEmpty();
+};
+
+TimerEventQueue.prototype.nextTimeout = function()
+{
+ var nTimeout = Number.MAX_VALUE;
+ var nCurrentTime = this.aTimer.getElapsedTime();
+ if( !this.isEmpty() )
+ nTimeout = this.aEventSet.top().nActivationTime - nCurrentTime;
+ return nTimeout;
+};
+
+TimerEventQueue.prototype.clear = function()
+{
+ this.DBG( 'TimerEventQueue.clear invoked' );
+ this.aEventSet.clear();
+};
+
+TimerEventQueue.prototype.getTimer = function()
+{
+ return this.aTimer;
+};
+
+TimerEventQueue.prototype.DBG = function( sMessage, nTime )
+{
+ aTimerEventQueueDebugPrinter.print( sMessage, nTime );
+};
+
+
+TimerEventQueue.prototype.insert = function( aEventEntry )
+{
+ var nHoleIndex = this.aEventSet.length;
+ var nParent = Math.floor( ( nHoleIndex - 1 ) / 2 );
+
+ while( ( nHoleIndex > 0 ) && this.aEventSet[ nParent ].compare( aEventEntry ) )
+ {
+ this.aEventSet[ nHoleIndex ] = this.aEventSet[ nParent ];
+ nHoleIndex = nParent;
+ nParent = Math.floor( ( nHoleIndex - 1 ) / 2 );
+ }
+ this.aEventSet[ nHoleIndex ] = aEventEntry;
+};
+
+
+
+
+function EventEntry( aEvent, nTime )
+{
+ this.aEvent = aEvent;
+ this.nActivationTime = nTime;
+}
+
+
+EventEntry.compare = function( aLhsEventEntry, aRhsEventEntry )
+{
+ if ( aLhsEventEntry.nActivationTime > aRhsEventEntry.nActivationTime )
+ {
+ return -1;
+ }
+ else if ( aLhsEventEntry.nActivationTime < aRhsEventEntry.nActivationTime )
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+};
+
+
+
+
+function ActivityQueue( aTimer )
+{
+ this.aTimer = aTimer;
+ this.aCurrentActivityWaitingSet = [];
+ this.aCurrentActivityReinsertSet = [];
+ this.aDequeuedActivitySet = [];
+}
+
+
+ActivityQueue.prototype.dispose = function()
+{
+ var nSize = this.aCurrentActivityWaitingSet.length;
+ var i;
+ for( i = 0; i < nSize; ++i )
+ this.aCurrentActivityWaitingSet[i].dispose();
+
+ nSize = this.aCurrentActivityReinsertSet.length;
+ for( i = 0; i < nSize; ++i )
+ this.aCurrentActivityReinsertSet[i].dispose();
+};
+
+ActivityQueue.prototype.addActivity = function( aActivity )
+{
+ if( !aActivity )
+ {
+ log( 'ActivityQueue.addActivity: activity is not valid' );
+ return false;
+ }
+
+ this.aCurrentActivityWaitingSet.push( aActivity );
+ aActivityQueueDebugPrinter.print( 'ActivityQueue.addActivity: activity appended' );
+ return true;
+};
+
+ActivityQueue.prototype.process = function()
+{
+ var nSize = this.aCurrentActivityWaitingSet.length;
+ var nLag = 0.0;
+ for( var i = 0; i < nSize; ++i )
+ {
+ nLag = Math.max( nLag,this.aCurrentActivityWaitingSet[i].calcTimeLag() );
+ }
+
+ if( nLag > 0.0 )
+ this.aTimer.adjustTimer( -nLag, true );
+
+
+ while( this.aCurrentActivityWaitingSet.length != 0 )
+ {
+ var aActivity = this.aCurrentActivityWaitingSet.shift();
+ var bReinsert = false;
+
+ bReinsert = aActivity.perform();
+
+ if( bReinsert )
+ {
+ this.aCurrentActivityReinsertSet.push( aActivity );
+ }
+ else
+ {
+ this.aDequeuedActivitySet.push( aActivity );
+ }
+ }
+
+ if( this.aCurrentActivityReinsertSet.length != 0 )
+ {
+ // TODO: optimization, try to swap reference here
+ this.aCurrentActivityWaitingSet = this.aCurrentActivityReinsertSet;
+ this.aCurrentActivityReinsertSet = [];
+ }
+};
+
+ActivityQueue.prototype.processDequeued = function()
+{
+ // notify all dequeued activities from last round
+ var nSize = this.aDequeuedActivitySet.length;
+ for( var i = 0; i < nSize; ++i )
+ this.aDequeuedActivitySet[i].dequeued();
+
+ this.aDequeuedActivitySet = [];
+};
+
+ActivityQueue.prototype.isEmpty = function()
+{
+ return ( ( this.aCurrentActivityWaitingSet.length == 0 ) &&
+ ( this.aCurrentActivityReinsertSet.length == 0 ) );
+};
+
+ActivityQueue.prototype.clear = function()
+{
+ aActivityQueueDebugPrinter.print( 'ActivityQueue.clear invoked' );
+ var nSize = this.aCurrentActivityWaitingSet.length;
+ var i;
+ for( i = 0; i < nSize; ++i )
+ this.aCurrentActivityWaitingSet[i].dequeued();
+ this.aCurrentActivityWaitingSet = [];
+
+ nSize = this.aCurrentActivityReinsertSet.length;
+ for( i = 0; i < nSize; ++i )
+ this.aCurrentActivityReinsertSet[i].dequeued();
+ this.aCurrentActivityReinsertSet = [];
+};
+
+ActivityQueue.prototype.endAll = function()
+{
+ aActivityQueueDebugPrinter.print( 'ActivityQueue.endAll invoked' );
+ var nSize = this.aCurrentActivityWaitingSet.length;
+ var i;
+ for( i = 0; i < nSize; ++i )
+ this.aCurrentActivityWaitingSet[i].end();
+ this.aCurrentActivityWaitingSet = [];
+
+ nSize = this.aCurrentActivityReinsertSet.length;
+ for( i = 0; i < nSize; ++i )
+ this.aCurrentActivityReinsertSet[i].end();
+ this.aCurrentActivityReinsertSet = [];
+};
+
+ActivityQueue.prototype.getTimer = function()
+{
+ return this.aTimer;
+};
+
+ActivityQueue.prototype.size = function()
+{
+ return ( this.aCurrentActivityWaitingSet.length +
+ this.aCurrentActivityReinsertSet.length +
+ this.aDequeuedActivitySet.length );
+};
+
+
+
+
+function ElapsedTime( aTimeBase )
+{
+ this.aTimeBase = aTimeBase;
+ this.nLastQueriedTime = 0.0;
+ this.nStartTime = this.getCurrentTime();
+ this.nFrozenTime = 0.0;
+ this.bInPauseMode = false;
+ this.bInHoldMode = false;
+}
+
+
+ElapsedTime.prototype.getTimeBase = function()
+{
+ return this.aTimeBase;
+};
+
+ElapsedTime.prototype.reset = function()
+{
+ this.nLastQueriedTime = 0.0;
+ this.nStartTime = this.getCurrentTime();
+ this.nFrozenTime = 0.0;
+ this.bInPauseMode = false;
+ this.bInHoldMode = false;
+};
+
+ElapsedTime.prototype.getElapsedTime = function()
+{
+ this.nLastQueriedTime = this.getElapsedTimeImpl();
+ return this.nLastQueriedTime;
+};
+
+ElapsedTime.prototype.pauseTimer = function()
+{
+ this.nFrozenTime = this.getElapsedTimeImpl();
+ this.bInPauseMode = true;
+};
+
+ElapsedTime.prototype.continueTimer = function()
+{
+ this.bInPauseMode = false;
+
+ // stop pausing, time runs again. Note that
+ // getElapsedTimeImpl() honors hold mode, i.e. a
+ // continueTimer() in hold mode will preserve the latter
+ var nPauseDuration = this.getElapsedTimeImpl() - this.nFrozenTime;
+
+ // adjust start time, such that subsequent getElapsedTime() calls
+ // will virtually start from m_fFrozenTime.
+ this.nStartTime += nPauseDuration;
+};
+
+ElapsedTime.prototype.adjustTimer = function( nOffset, bLimitToLastQueriedTime )
+{
+ if( bLimitToLastQueriedTime == undefined )
+ bLimitToLastQueriedTime = true;
+
+ // to make getElapsedTime() become _larger_, have to reduce nStartTime.
+ this.nStartTime -= nOffset;
+
+ // also adjust frozen time, this method must _always_ affect the
+ // value returned by getElapsedTime()!
+ if( this.bInHoldMode || this.bInPauseMode )
+ this.nFrozenTime += nOffset;
+};
+
+ElapsedTime.prototype.holdTimer = function()
+{
+ // when called during hold mode (e.g. more than once per time
+ // object), the original hold time will be maintained.
+ this.nFrozenTime = this.getElapsedTimeImpl();
+ this.bInHoldMode = true;
+};
+
+ElapsedTime.prototype.releaseTimer = function()
+{
+ this.bInHoldMode = false;
+};
+
+ElapsedTime.prototype.getSystemTime = function()
+{
+ return ( getCurrentSystemTime() / 1000.0 );
+};
+
+ElapsedTime.prototype.getCurrentTime = function()
+{
+ var nCurrentTime;
+ if ( !this.aTimeBase )
+ {
+ nCurrentTime = this.getSystemTime();
+ }
+ else
+ {
+ nCurrentTime = this.aTimeBase.getElapsedTimeImpl();
+ }
+
+ assert( ( typeof( nCurrentTime ) === typeof( 0 ) ) && isFinite( nCurrentTime ),
+ 'ElapsedTime.getCurrentTime: assertion failed: nCurrentTime == ' + nCurrentTime );
+
+
+ return nCurrentTime;
+};
+
+ElapsedTime.prototype.getElapsedTimeImpl = function()
+{
+ if( this.bInHoldMode || this.bInPauseMode )
+ {
+ return this.nFrozenTime;
+ }
+
+ var nCurTime = this.getCurrentTime();
+ return ( nCurTime - this.nStartTime );
+};
+
+
+
+/*****
+ * @libreofficeend
+ *
+ * Several parts of the above code are the result of the porting,
+ * started on August 2011, of the C++ code included in the source files
+ * placed under the folder '/slideshow/source' and subfolders.
+ * @source https://cgit.freedesktop.org/libreoffice/core/tree/slideshow/source
+ *
+ */
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/filter/source/svg/svgexport.cxx b/filter/source/svg/svgexport.cxx
new file mode 100644
index 000000000..c21b041b1
--- /dev/null
+++ b/filter/source/svg/svgexport.cxx
@@ -0,0 +1,2877 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "svgwriter.hxx"
+#include "svgfontexport.hxx"
+#include "svgfilter.hxx"
+#include <svgscript.hxx>
+
+#include <com/sun/star/awt/Rectangle.hpp>
+#include <com/sun/star/animations/XAnimationNodeSupplier.hpp>
+#include <com/sun/star/container/XEnumerationAccess.hpp>
+#include <com/sun/star/drawing/FillStyle.hpp>
+#include <com/sun/star/drawing/XMasterPageTarget.hpp>
+#include <com/sun/star/drawing/GraphicExportFilter.hpp>
+#include <com/sun/star/presentation/XPresentationSupplier.hpp>
+#include <com/sun/star/style/ParagraphAdjust.hpp>
+#include <com/sun/star/text/textfield/Type.hpp>
+#include <com/sun/star/util/MeasureUnit.hpp>
+#include <com/sun/star/xml/sax/Writer.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/ShapeCollection.hpp>
+
+#include <rtl/bootstrap.hxx>
+#include <svx/unopage.hxx>
+#include <svx/svdpage.hxx>
+#include <svx/svdoutl.hxx>
+#include <editeng/outliner.hxx>
+#include <editeng/flditem.hxx>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+#include <i18nlangtag/lang.h>
+#include <svl/numformat.hxx>
+#include <tools/debug.hxx>
+#include <tools/urlobj.hxx>
+#include <unotools/streamwrap.hxx>
+#include <unotools/tempfile.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <vcl/filter/SvmReader.hxx>
+#include <xmloff/unointerfacetouniqueidentifiermapper.hxx>
+#include <xmloff/namespacemap.hxx>
+#include <xmloff/xmlnamespace.hxx>
+#include <xmloff/xmltoken.hxx>
+#include <xmloff/animationexport.hxx>
+#include <svx/svdograf.hxx>
+#include <svx/svdmodel.hxx>
+#include <svx/svdxcgv.hxx>
+#include <sal/log.hxx>
+#include <tools/diagnose_ex.h>
+
+#include <memory>
+#include <string_view>
+
+using namespace css::animations;
+using namespace css::presentation;
+using namespace ::com::sun::star::graphic;
+using namespace ::com::sun::star::geometry;
+using namespace ::com::sun::star;
+using namespace ::xmloff::token;
+
+// - ooo elements and attributes -
+
+#define NSPREFIX u"ooo:"
+
+constexpr OUStringLiteral SVG_PROP_TINYPROFILE = u"TinyMode";
+constexpr OUStringLiteral SVG_PROP_DTDSTRING = u"DTDString";
+constexpr OUStringLiteral SVG_PROP_EMBEDFONTS = u"EmbedFonts";
+constexpr OUStringLiteral SVG_PROP_NATIVEDECORATION = u"UseNativeTextDecoration";
+constexpr OUStringLiteral SVG_PROP_OPACITY = u"Opacity";
+constexpr OUStringLiteral SVG_PROP_POSITIONED_CHARACTERS = u"UsePositionedCharacters";
+
+// ooo xml elements
+constexpr OUStringLiteral aOOOElemTextField = NSPREFIX "text_field";
+
+
+// ooo xml attributes for meta_slide
+constexpr OUStringLiteral aOOOAttrSlide = NSPREFIX "slide";
+constexpr OUStringLiteral aOOOAttrMaster = NSPREFIX "master";
+constexpr OUStringLiteral aOOOAttrHasCustomBackground = NSPREFIX "has-custom-background";
+constexpr OUStringLiteral aOOOAttrDisplayName = NSPREFIX "display-name";
+constexpr OUStringLiteral aOOOAttrBackgroundVisibility = NSPREFIX "background-visibility";
+constexpr OUStringLiteral aOOOAttrMasterObjectsVisibility = NSPREFIX "master-objects-visibility";
+constexpr OUStringLiteral aOOOAttrSlideDuration = NSPREFIX "slide-duration";
+constexpr OUStringLiteral aOOOAttrDateTimeField = NSPREFIX "date-time-field";
+constexpr OUStringLiteral aOOOAttrFooterField = NSPREFIX "footer-field";
+constexpr OUStringLiteral aOOOAttrHasTransition = NSPREFIX "has-transition";
+
+// ooo xml attributes for pages and shapes
+constexpr OUStringLiteral aOOOAttrName = NSPREFIX "name";
+
+constexpr OUStringLiteral constSvgNamespace = u"http://www.w3.org/2000/svg";
+
+
+/** Text Field Class Hierarchy
+ This is a set of classes for exporting text field meta info.
+ */
+
+namespace {
+
+class TextField
+{
+protected:
+ SVGFilter::ObjectSet mMasterPageSet;
+public:
+ TextField() = default;
+ TextField(TextField const &) = default;
+ TextField(TextField &&) = default;
+ TextField & operator =(TextField const &) = default;
+ TextField & operator =(TextField &&) = default;
+
+ virtual OUString getClassName() const
+ {
+ return "TextField";
+ }
+ virtual bool equalTo( const TextField & aTextField ) const = 0;
+ virtual void growCharSet( SVGFilter::UCharSetMapMap & aTextFieldCharSets ) const = 0;
+ virtual void elementExport( SVGExport* pSVGExport ) const
+ {
+ pSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", getClassName() );
+ }
+ void insertMasterPage( const Reference< css::drawing::XDrawPage>& xMasterPage )
+ {
+ mMasterPageSet.insert( xMasterPage );
+ }
+ virtual ~TextField() {}
+protected:
+ void implGrowCharSet( SVGFilter::UCharSetMapMap & aTextFieldCharSets, const OUString& sText, const OUString& sTextFieldId ) const
+ {
+ const sal_Unicode * ustr = sText.getStr();
+ sal_Int32 nLength = sText.getLength();
+ for (auto const& masterPage : mMasterPageSet)
+ {
+ const Reference< XInterface > & xMasterPage = masterPage;
+ for( sal_Int32 i = 0; i < nLength; ++i )
+ {
+ aTextFieldCharSets[ xMasterPage ][ sTextFieldId ].insert( ustr[i] );
+ }
+ }
+ }
+};
+
+
+class FixedTextField : public TextField
+{
+public:
+ OUString text;
+
+ virtual OUString getClassName() const override
+ {
+ return "FixedTextField";
+ }
+ virtual bool equalTo( const TextField & aTextField ) const override
+ {
+ if( const FixedTextField* aFixedTextField = dynamic_cast< const FixedTextField* >( &aTextField ) )
+ {
+ return ( text == aFixedTextField->text );
+ }
+ return false;
+ }
+ virtual void elementExport( SVGExport* pSVGExport ) const override
+ {
+ TextField::elementExport( pSVGExport );
+ SvXMLElementExport aExp( *pSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+ pSVGExport->GetDocHandler()->characters( text );
+ }
+};
+
+
+class FixedDateTimeField : public FixedTextField
+{
+public:
+ FixedDateTimeField() {}
+ virtual OUString getClassName() const override
+ {
+ return "FixedDateTimeField";
+ }
+ virtual void growCharSet( SVGFilter::UCharSetMapMap & aTextFieldCharSets ) const override
+ {
+ implGrowCharSet( aTextFieldCharSets, text, aOOOAttrDateTimeField );
+ }
+};
+
+
+class FooterField : public FixedTextField
+{
+public:
+ FooterField() {}
+ virtual OUString getClassName() const override
+ {
+ return "FooterField";
+ }
+ virtual void growCharSet( SVGFilter::UCharSetMapMap & aTextFieldCharSets ) const override
+ {
+ static constexpr OUStringLiteral sFieldId = aOOOAttrFooterField;
+ implGrowCharSet( aTextFieldCharSets, text, sFieldId );
+ }
+};
+
+
+class VariableTextField : public TextField
+{
+public:
+ virtual OUString getClassName() const override
+ {
+ return "VariableTextField";
+ }
+};
+
+
+class VariableDateTimeField : public VariableTextField
+{
+public:
+ sal_Int32 format;
+
+ VariableDateTimeField()
+ : format(0)
+ {
+ }
+ virtual OUString getClassName() const override
+ {
+ return "VariableDateTimeField";
+ }
+ virtual bool equalTo( const TextField & aTextField ) const override
+ {
+ if( const VariableDateTimeField* aField = dynamic_cast< const VariableDateTimeField* >( &aTextField ) )
+ {
+ return ( format == aField->format );
+ }
+ return false;
+ }
+ virtual void elementExport( SVGExport* pSVGExport ) const override
+ {
+ VariableTextField::elementExport( pSVGExport );
+ OUString sDateFormat, sTimeFormat;
+ SvxDateFormat eDateFormat = static_cast<SvxDateFormat>( format & 0x0f );
+ if( eDateFormat != SvxDateFormat::AppDefault )
+ {
+ switch( eDateFormat )
+ {
+ case SvxDateFormat::StdSmall:
+ case SvxDateFormat::A: // 13.02.96
+ sDateFormat.clear();
+ break;
+ case SvxDateFormat::C: // 13.Feb 1996
+ sDateFormat.clear();
+ break;
+ case SvxDateFormat::D: // 13.February 1996
+ sDateFormat.clear();
+ break;
+ case SvxDateFormat::E: // Tue, 13.February 1996
+ sDateFormat.clear();
+ break;
+ case SvxDateFormat::StdBig:
+ case SvxDateFormat::F: // Tuesday, 13.February 1996
+ sDateFormat.clear();
+ break;
+ // default case
+ case SvxDateFormat::B: // 13.02.1996
+ default:
+ sDateFormat.clear();
+ break;
+ }
+ }
+
+ SvxTimeFormat eTimeFormat = static_cast<SvxTimeFormat>( ( format >> 4 ) & 0x0f );
+ if( eTimeFormat != SvxTimeFormat::AppDefault )
+ {
+ switch( eTimeFormat )
+ {
+ case SvxTimeFormat::HH24_MM_SS: // 13:49:38
+ sTimeFormat.clear();
+ break;
+ case SvxTimeFormat::HH12_MM_AMPM: // 01:49 PM
+ case SvxTimeFormat::HH12_MM:
+ sTimeFormat.clear();
+ break;
+ case SvxTimeFormat::HH12_MM_SS_AMPM: // 01:49:38 PM
+ case SvxTimeFormat::HH12_MM_SS:
+ sTimeFormat.clear();
+ break;
+ // default case
+ case SvxTimeFormat::HH24_MM: // 13:49
+ default:
+ sTimeFormat.clear();
+ break;
+ }
+ }
+
+ OUString sDateTimeFormat = sDateFormat + " " + sTimeFormat;
+
+ pSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "date-time-format", sDateTimeFormat );
+ SvXMLElementExport aExp( *pSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+ }
+ virtual void growCharSet( SVGFilter::UCharSetMapMap & aTextFieldCharSets ) const override
+ {
+ // we use the unicode char set in an improper way: we put in the date/time format
+ // in order to pass it to the CalcFieldValue method
+ static const OUString sFieldId = aOOOAttrDateTimeField + "-variable";
+ for (auto const& masterPage : mMasterPageSet)
+ {
+ aTextFieldCharSets[ masterPage ][ sFieldId ].insert( static_cast<sal_Unicode>( format ) );
+ }
+ }
+};
+
+
+bool operator==( const TextField & aLhsTextField, const TextField & aRhsTextField )
+{
+ return aLhsTextField.equalTo( aRhsTextField );
+}
+
+}
+
+SVGExport::SVGExport(
+ const css::uno::Reference< css::uno::XComponentContext >& rContext,
+ const Reference< XDocumentHandler >& rxHandler,
+ const Sequence< PropertyValue >& rFilterData )
+ : SvXMLExport( rContext, "",
+ util::MeasureUnit::MM_100TH,
+ xmloff::token::XML_TOKEN_INVALID,
+ SvXMLExportFlags::META|SvXMLExportFlags::PRETTY )
+{
+ SetDocHandler( rxHandler );
+ GetDocHandler()->startDocument();
+
+ // initializing filter settings from filter data
+ comphelper::SequenceAsHashMap aFilterDataHashMap = rFilterData;
+
+ // TinyProfile
+ mbIsUseTinyProfile = aFilterDataHashMap.getUnpackedValueOrDefault(SVG_PROP_TINYPROFILE, false);
+
+ // DTD string
+ mbIsUseDTDString = aFilterDataHashMap.getUnpackedValueOrDefault(SVG_PROP_DTDSTRING, true);
+
+ // Font Embedding
+ comphelper::SequenceAsHashMap::const_iterator iter = aFilterDataHashMap.find(SVG_PROP_EMBEDFONTS);
+ if(iter==aFilterDataHashMap.end())
+ {
+ OUString v;
+ mbIsEmbedFonts = !rtl::Bootstrap::get("SVG_DISABLE_FONT_EMBEDDING", v);
+ }
+ else
+ {
+ if(!(iter->second >>= mbIsEmbedFonts))
+ mbIsEmbedFonts = false;
+ }
+
+ // Native Decoration
+ mbIsUseNativeTextDecoration = !mbIsUseTinyProfile && aFilterDataHashMap.getUnpackedValueOrDefault(SVG_PROP_NATIVEDECORATION, true);
+
+ // Tiny Opacity (supported from SVG Tiny 1.2)
+ mbIsUseOpacity = aFilterDataHashMap.getUnpackedValueOrDefault(SVG_PROP_OPACITY, true);
+
+ // Positioned Characters (The old method)
+ mbIsUsePositionedCharacters = aFilterDataHashMap.getUnpackedValueOrDefault(SVG_PROP_POSITIONED_CHARACTERS, false);
+
+ // add namespaces
+ GetNamespaceMap_().Add(
+ GetXMLToken(XML_NP_PRESENTATION),
+ GetXMLToken(XML_N_PRESENTATION),
+ XML_NAMESPACE_PRESENTATION);
+
+ GetNamespaceMap_().Add(
+ GetXMLToken(XML_NP_SMIL),
+ GetXMLToken(XML_N_SMIL_COMPAT),
+ XML_NAMESPACE_SMIL);
+
+ GetNamespaceMap_().Add(
+ GetXMLToken(XML_NP_ANIMATION),
+ GetXMLToken(XML_N_ANIMATION),
+ XML_NAMESPACE_ANIMATION);
+}
+
+SVGExport::~SVGExport()
+{
+ GetDocHandler()->endDocument();
+}
+
+ObjectRepresentation::ObjectRepresentation()
+{
+}
+
+ObjectRepresentation::ObjectRepresentation( const Reference< XInterface >& rxObject,
+ const GDIMetaFile& rMtf ) :
+ mxObject( rxObject ),
+ mxMtf( new GDIMetaFile( rMtf ) )
+{
+}
+
+ObjectRepresentation::ObjectRepresentation( const ObjectRepresentation& rPresentation ) :
+ mxObject( rPresentation.mxObject ),
+ mxMtf( rPresentation.mxMtf ? new GDIMetaFile( *rPresentation.mxMtf ) : nullptr )
+{
+}
+
+ObjectRepresentation& ObjectRepresentation::operator=( const ObjectRepresentation& rPresentation )
+{
+ // Check for self-assignment
+ if (this == &rPresentation)
+ return *this;
+
+ mxObject = rPresentation.mxObject;
+ mxMtf.reset(rPresentation.mxMtf ? new GDIMetaFile(*rPresentation.mxMtf) : nullptr);
+
+ return *this;
+}
+
+
+namespace
+{
+
+BitmapChecksum GetBitmapChecksum( const MetaAction* pAction )
+{
+ if( !pAction )
+ {
+ OSL_FAIL( "GetBitmapChecksum: passed MetaAction pointer is null." );
+ return 0;
+ }
+
+ BitmapChecksum nChecksum = 0;
+ const MetaActionType nType = pAction->GetType();
+
+ switch( nType )
+ {
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+ // The conversion to BitmapEx is needed since a Bitmap object is
+ // converted to BitmapEx before passing it to SVGActionWriter::ImplWriteBmp
+ // where the checksum is checked for matching.
+ nChecksum = BitmapEx( pA->GetBitmap() ).GetChecksum();
+ }
+ break;
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+ nChecksum = pA->GetBitmapEx().GetChecksum();
+ }
+ break;
+ default: break;
+ }
+ return nChecksum;
+}
+
+MetaAction* CreateMetaBitmapAction( const MetaAction* pAction, const Point& rPt, const Size& rSz )
+{
+ if( !pAction )
+ {
+ OSL_FAIL( "CreateMetaBitmapAction: passed MetaAction pointer is null." );
+ return nullptr;
+ }
+
+ MetaAction* pResAction = nullptr;
+ const MetaActionType nType = pAction->GetType();
+ switch( nType )
+ {
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+ pResAction = new MetaBmpScaleAction( rPt, rSz, pA->GetBitmap() );
+ }
+ break;
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+ pResAction = new MetaBmpExScaleAction( rPt, rSz, pA->GetBitmapEx() );
+ }
+ break;
+ default: break;
+ }
+ return pResAction;
+}
+
+void MetaBitmapActionGetPoint( const MetaAction* pAction, Point& rPt )
+{
+ if( !pAction )
+ {
+ OSL_FAIL( "MetaBitmapActionGetPoint: passed MetaAction pointer is null." );
+ return;
+ }
+ const MetaActionType nType = pAction->GetType();
+ switch( nType )
+ {
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+ rPt = pA->GetPoint();
+ }
+ break;
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+ rPt = pA->GetPoint();
+ }
+ break;
+ default: break;
+ }
+}
+
+void MetaBitmapActionGetSize( const MetaAction* pAction, Size& rSz )
+{
+ if( !pAction )
+ {
+ OSL_FAIL( "MetaBitmapActionGetSize: passed MetaAction pointer is null." );
+ return;
+ }
+ const MetaActionType nType = pAction->GetType();
+ switch( nType )
+ {
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+ rSz = pA->GetSize();
+ }
+ break;
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+ rSz = pA->GetSize();
+ }
+ break;
+ default: break;
+ }
+}
+
+void MetaBitmapActionGetOrigSize( const MetaAction* pAction, Size& rSz )
+{
+ if( !pAction )
+ {
+ OSL_FAIL( "MetaBitmapActionGetOrigSize: passed MetaAction pointer is null." );
+ return;
+ }
+
+ const MetaActionType nType = pAction->GetType();
+ MapMode aSourceMode( MapUnit::MapPixel );
+ MapMode aTargetMode( MapUnit::Map100thMM );
+
+ switch( nType )
+ {
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+ const Bitmap& rBitmap = pA->GetBitmap();
+ rSz = rBitmap.GetSizePixel();
+ }
+ break;
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+ const BitmapEx& rBitmap = pA->GetBitmapEx();
+ rSz = rBitmap.GetSizePixel();
+ }
+ break;
+ default: break;
+ }
+ rSz = OutputDevice::LogicToLogic( rSz, aSourceMode, aTargetMode );
+}
+
+OUString getPatternIdForTiledBackground( std::u16string_view sSlideId, BitmapChecksum nChecksum )
+{
+ return OUString::Concat("bg-pattern.") + sSlideId + "." + OUString::number( nChecksum );
+}
+
+OUString getIdForTiledBackground( std::u16string_view sSlideId, BitmapChecksum nChecksum )
+{
+ return OUString::Concat("bg-") + sSlideId + "." + OUString::number( nChecksum );
+}
+
+} // end anonymous namespace
+
+size_t HashBitmap::operator()( const ObjectRepresentation& rObjRep ) const
+{
+ const GDIMetaFile& aMtf = rObjRep.GetRepresentation();
+ if( aMtf.GetActionSize() == 1 )
+ {
+ return static_cast< size_t >( GetBitmapChecksum( aMtf.GetAction( 0 ) ) );
+ }
+ else
+ {
+ OSL_FAIL( "HashBitmap: metafile should have a single action." );
+ return 0;
+ }
+}
+
+
+bool EqualityBitmap::operator()( const ObjectRepresentation& rObjRep1,
+ const ObjectRepresentation& rObjRep2 ) const
+{
+ const GDIMetaFile& aMtf1 = rObjRep1.GetRepresentation();
+ const GDIMetaFile& aMtf2 = rObjRep2.GetRepresentation();
+ if( aMtf1.GetActionSize() == 1 && aMtf2.GetActionSize() == 1 )
+ {
+ BitmapChecksum nChecksum1 = GetBitmapChecksum( aMtf1.GetAction( 0 ) );
+ BitmapChecksum nChecksum2 = GetBitmapChecksum( aMtf2.GetAction( 0 ) );
+ return ( nChecksum1 == nChecksum2 );
+ }
+ else
+ {
+ OSL_FAIL( "EqualityBitmap: metafile should have a single action." );
+ return false;
+ }
+}
+
+
+bool SVGFilter::implExport( const Sequence< PropertyValue >& rDescriptor )
+{
+ Reference< XOutputStream > xOStm;
+ std::unique_ptr<SvStream> pOStm;
+ sal_Int32 nLength = rDescriptor.getLength();
+ const PropertyValue* pValue = rDescriptor.getConstArray();
+
+ maFilterData.realloc( 0 );
+
+ for ( sal_Int32 i = 0 ; i < nLength; ++i)
+ {
+ if ( pValue[ i ].Name == "OutputStream" )
+ pValue[ i ].Value >>= xOStm;
+ else if ( pValue[ i ].Name == "FileName" )
+ {
+ OUString aFileName;
+
+ pValue[ i ].Value >>= aFileName;
+ pOStm = ::utl::UcbStreamHelper::CreateStream( aFileName, StreamMode::WRITE | StreamMode::TRUNC );
+
+ if( pOStm )
+ xOStm.set( new ::utl::OOutputStreamWrapper ( *pOStm ) );
+ }
+ else if ( pValue[ i ].Name == "FilterData" )
+ {
+ pValue[ i ].Value >>= maFilterData;
+ }
+ }
+
+ if(mbWriterFilter || mbCalcFilter)
+ return implExportWriterOrCalc(xOStm);
+
+ return implExportImpressOrDraw(xOStm);
+}
+
+bool SVGFilter::implExportImpressOrDraw( const Reference< XOutputStream >& rxOStm)
+{
+ Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ) ;
+ bool bRet = false;
+
+ if( rxOStm.is() )
+ {
+ if( !mSelectedPages.empty() && !mMasterPageTargets.empty() )
+ {
+ Reference< XDocumentHandler > xDocHandler = implCreateExportDocumentHandler( rxOStm );
+
+ if( xDocHandler.is() )
+ {
+ mbPresentation = Reference< XPresentationSupplier >( mxSrcDoc, UNO_QUERY ).is();
+ mpObjects = new ObjectMap;
+
+ // mpSVGExport = new SVGExport( xDocHandler );
+ mpSVGExport = new SVGExport( xContext, xDocHandler, maFilterData );
+
+ // create an id for each draw page
+ for( const auto& rPage : mSelectedPages )
+ implRegisterInterface( rPage );
+
+ // create an id for each master page
+ for(const uno::Reference<drawing::XDrawPage> & mMasterPageTarget : mMasterPageTargets)
+ implRegisterInterface( mMasterPageTarget );
+
+ SdrModel* pSdrModel(nullptr);
+
+ try
+ {
+ mxDefaultPage = mSelectedPages[0];
+
+ if( mxDefaultPage.is() )
+ {
+ SvxDrawPage* pSvxDrawPage = comphelper::getFromUnoTunnel<SvxDrawPage>( mxDefaultPage );
+
+ if( pSvxDrawPage )
+ {
+ mpDefaultSdrPage = pSvxDrawPage->GetSdrPage();
+ pSdrModel = &mpDefaultSdrPage->getSdrModelFromSdrPage();
+ SdrOutliner& rOutl = pSdrModel->GetDrawOutliner();
+
+ maOldFieldHdl = rOutl.GetCalcFieldValueHdl();
+ maNewFieldHdl = LINK(this, SVGFilter, CalcFieldHdl);
+ rOutl.SetCalcFieldValueHdl(maNewFieldHdl);
+ }
+
+ bRet = implExportDocument();
+ }
+ }
+ catch( ... )
+ {
+ delete mpSVGDoc;
+ mpSVGDoc = nullptr;
+ SAL_WARN("filter.svg", "Exception caught");
+ }
+
+ if( nullptr != pSdrModel )
+ {
+ // fdo#62682 The maNewFieldHdl can end up getting copied
+ // into various other outliners which live past this
+ // method, so get the full list of outliners and restore
+ // the maOldFieldHdl for all that have ended up using
+ // maNewFieldHdl
+ std::vector<SdrOutliner*> aOutliners(pSdrModel->GetActiveOutliners());
+ for (auto const& outliner : aOutliners)
+ {
+ if (maNewFieldHdl == outliner->GetCalcFieldValueHdl())
+ outliner->SetCalcFieldValueHdl(maOldFieldHdl);
+ }
+ }
+
+ delete mpSVGWriter;
+ mpSVGWriter = nullptr;
+ mpSVGExport = nullptr; // pointed object is released by xSVGExport dtor at the end of this scope
+ delete mpSVGFontExport;
+ mpSVGFontExport = nullptr;
+ delete mpObjects;
+ mpObjects = nullptr;
+ mbPresentation = false;
+ }
+ }
+ }
+ return bRet;
+}
+
+
+bool SVGFilter::implExportWriterOrCalc( const Reference< XOutputStream >& rxOStm )
+{
+ Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ) ;
+ bool bRet = false;
+
+ if( rxOStm.is() )
+ {
+ Reference< XDocumentHandler > xDocHandler = implCreateExportDocumentHandler( rxOStm );
+
+ if( xDocHandler.is() )
+ {
+ mpObjects = new ObjectMap;
+
+ // mpSVGExport = new SVGExport( xDocHandler );
+ mpSVGExport = new SVGExport( xContext, xDocHandler, maFilterData );
+
+ try
+ {
+ mxDefaultPage = mSelectedPages[0];
+ bRet = implExportDocument();
+ }
+ catch( ... )
+ {
+ TOOLS_WARN_EXCEPTION( "filter.svg", "" );
+ delete mpSVGDoc;
+ mpSVGDoc = nullptr;
+ }
+
+ delete mpSVGWriter;
+ mpSVGWriter = nullptr;
+ mpSVGExport = nullptr; // pointed object is released by xSVGExport dtor at the end of this scope
+ delete mpSVGFontExport;
+ mpSVGFontExport = nullptr;
+ delete mpObjects;
+ mpObjects = nullptr;
+ }
+ }
+ return bRet;
+}
+
+bool SVGFilter::implExportWriterTextGraphic( const Reference< view::XSelectionSupplier >& xSelectionSupplier )
+{
+ Any selection = xSelectionSupplier->getSelection();
+ uno::Reference<lang::XServiceInfo> xSelection;
+ selection >>= xSelection;
+ if (!xSelection || !xSelection->supportsService("com.sun.star.text.TextGraphicObject"))
+ return true;
+
+ uno::Reference<beans::XPropertySet> xPropertySet(xSelection, uno::UNO_QUERY);
+
+ uno::Reference<graphic::XGraphic> xOriginalGraphic;
+ xPropertySet->getPropertyValue("Graphic") >>= xOriginalGraphic;
+ const Graphic aOriginalGraphic(xOriginalGraphic);
+
+ uno::Reference<graphic::XGraphic> xTransformedGraphic;
+ xPropertySet->getPropertyValue(
+ mbIsPreview ? OUString("GraphicPreview") : OUString("TransformedGraphic"))
+ >>= xTransformedGraphic;
+
+ if (!xTransformedGraphic.is())
+ return false;
+ const Graphic aTransformedGraphic(xTransformedGraphic);
+ bool bSameGraphic = aTransformedGraphic == aOriginalGraphic ||
+ aOriginalGraphic.GetChecksum() == aTransformedGraphic.GetChecksum();
+ const Graphic aGraphic = bSameGraphic ? aOriginalGraphic : aTransformedGraphic;
+ uno::Reference<graphic::XGraphic> xGraphic = bSameGraphic ? xOriginalGraphic : xTransformedGraphic;
+
+ // Calculate size from Graphic
+ Point aPos( OutputDevice::LogicToLogic(aGraphic.GetPrefMapMode().GetOrigin(), aGraphic.GetPrefMapMode(), MapMode(MapUnit::Map100thMM)) );
+ Size aSize( OutputDevice::LogicToLogic(aGraphic.GetPrefSize(), aGraphic.GetPrefMapMode(), MapMode(MapUnit::Map100thMM)) );
+
+ assert(mSelectedPages.size() == 1);
+ SvxDrawPage* pSvxDrawPage(comphelper::getFromUnoTunnel<SvxDrawPage>(mSelectedPages[0]));
+ if(pSvxDrawPage == nullptr || pSvxDrawPage->GetSdrPage() == nullptr)
+ return false;
+
+ SdrGrafObj* pGraphicObj = new SdrGrafObj(pSvxDrawPage->GetSdrPage()->getSdrModelFromSdrPage(), aGraphic, tools::Rectangle( aPos, aSize ));
+ uno::Reference< drawing::XShape > xShape = GetXShapeForSdrObject(pGraphicObj);
+ uno::Reference< XPropertySet > xShapePropSet(xShape, uno::UNO_QUERY);
+ xShapePropSet->setPropertyValue("Graphic", uno::Any(xGraphic));
+
+ maShapeSelection = drawing::ShapeCollection::create(comphelper::getProcessComponentContext());
+ maShapeSelection->add(xShape);
+
+ return true;
+}
+
+
+Reference< XWriter > SVGFilter::implCreateExportDocumentHandler( const Reference< XOutputStream >& rxOStm )
+{
+ Reference< XWriter > xSaxWriter;
+
+ if( rxOStm.is() )
+ {
+ xSaxWriter = Writer::create( ::comphelper::getProcessComponentContext() );
+ xSaxWriter->setOutputStream( rxOStm );
+ }
+
+ return xSaxWriter;
+}
+
+
+bool SVGFilter::implLookForFirstVisiblePage()
+{
+ sal_Int32 nCurPage = 0, nLastPage = mSelectedPages.size() - 1;
+
+ if(!mbPresentation || mbSinglePage)
+ {
+ mnVisiblePage = nCurPage;
+ }
+
+ while( ( nCurPage <= nLastPage ) && ( -1 == mnVisiblePage ) )
+ {
+ const Reference< css::drawing::XDrawPage > & xDrawPage = mSelectedPages[nCurPage];
+
+ if( xDrawPage.is() )
+ {
+ Reference< XPropertySet > xPropSet( xDrawPage, UNO_QUERY );
+
+ if( xPropSet.is() )
+ {
+ bool bVisible = false;
+
+ if( ( xPropSet->getPropertyValue( "Visible" ) >>= bVisible ) && bVisible )
+ {
+ mnVisiblePage = nCurPage;
+ }
+ }
+ }
+ ++nCurPage;
+ }
+
+ return ( mnVisiblePage != -1 );
+}
+
+
+bool SVGFilter::implExportDocument()
+{
+ sal_Int32 nDocX = 0, nDocY = 0; // #i124608#
+ sal_Int32 nDocWidth = 0, nDocHeight = 0;
+ bool bRet = false;
+ sal_Int32 nLastPage = mSelectedPages.size() - 1;
+
+ mbSinglePage = (nLastPage == 0);
+ mnVisiblePage = -1;
+
+ const Reference< XPropertySet > xDefaultPagePropertySet( mxDefaultPage, UNO_QUERY );
+
+ // #i124608#
+ mbExportShapeSelection = mbSinglePage && maShapeSelection.is() && maShapeSelection->getCount();
+
+ if(xDefaultPagePropertySet.is())
+ {
+ xDefaultPagePropertySet->getPropertyValue( "Width" ) >>= nDocWidth;
+ xDefaultPagePropertySet->getPropertyValue( "Height" ) >>= nDocHeight;
+ }
+
+ if(mbExportShapeSelection)
+ {
+ // #i124608# create BoundRange and set nDocX, nDocY, nDocWidth and nDocHeight
+ basegfx::B2DRange aShapeRange;
+
+ for(sal_Int32 a(0); a < maShapeSelection->getCount(); a++)
+ {
+ Reference< css::drawing::XShape > xShapeCandidate;
+ if((maShapeSelection->getByIndex(a) >>= xShapeCandidate) && xShapeCandidate.is())
+ {
+ Reference< XPropertySet > xShapePropSet( xShapeCandidate, UNO_QUERY );
+ css::awt::Rectangle aBoundRect;
+ if( xShapePropSet.is() && ( xShapePropSet->getPropertyValue( "BoundRect" ) >>= aBoundRect ))
+ {
+ aShapeRange.expand(basegfx::B2DTuple(aBoundRect.X, aBoundRect.Y));
+ aShapeRange.expand(basegfx::B2DTuple(aBoundRect.X + aBoundRect.Width,
+ aBoundRect.Y + aBoundRect.Height));
+ }
+ }
+ }
+
+ if(!aShapeRange.isEmpty())
+ {
+ nDocX = basegfx::fround(aShapeRange.getMinX());
+ nDocY = basegfx::fround(aShapeRange.getMinY());
+ nDocWidth = basegfx::fround(aShapeRange.getWidth());
+ nDocHeight = basegfx::fround(aShapeRange.getHeight());
+ }
+ }
+
+ if(mbWriterFilter || mbCalcFilter)
+ implExportDocumentHeaderWriterOrCalc(nDocX, nDocY, nDocWidth, nDocHeight);
+ else
+ implExportDocumentHeaderImpressOrDraw(nDocX, nDocY, nDocWidth, nDocHeight);
+
+
+ if( implLookForFirstVisiblePage() ) // OK! We found at least one visible page.
+ {
+ if( mbPresentation && !mbExportShapeSelection )
+ {
+ implGenerateMetaData();
+ implExportAnimations();
+ }
+ else
+ {
+ implGetPagePropSet( mSelectedPages[0] );
+ }
+
+ // Create the (Shape, GDIMetaFile) map
+ if( implCreateObjects() )
+ {
+ ::std::vector< ObjectRepresentation > aObjects( mpObjects->size() );
+ sal_uInt32 nPos = 0;
+
+ for (auto const& elem : *mpObjects)
+ {
+ aObjects[ nPos++ ] = elem.second;
+ }
+
+ mpSVGFontExport = new SVGFontExport( *mpSVGExport, std::move(aObjects) );
+ mpSVGWriter = new SVGActionWriter( *mpSVGExport, *mpSVGFontExport );
+
+ if( mpSVGExport->IsEmbedFonts() )
+ {
+ mpSVGFontExport->EmbedFonts();
+ }
+ if( !mpSVGExport->IsUsePositionedCharacters() )
+ {
+ implExportTextShapeIndex();
+ implEmbedBulletGlyphs();
+ implExportTextEmbeddedBitmaps();
+ implExportBackgroundBitmaps();
+ mpSVGWriter->SetEmbeddedBitmapRefs( &maBitmapActionMap );
+ implExportTiledBackground();
+ }
+ if( mbIsPreview )
+ mpSVGWriter->SetPreviewMode();
+
+ // #i124608# export a given object selection, so no MasterPage export at all
+ if (!mbExportShapeSelection)
+ implExportMasterPages( mMasterPageTargets, 0, mMasterPageTargets.size() - 1 );
+ implExportDrawPages( mSelectedPages, 0, nLastPage );
+
+ if( mbPresentation && !mbExportShapeSelection )
+ {
+ implGenerateScript();
+ }
+
+ delete mpSVGDoc;
+ mpSVGDoc = nullptr;
+ bRet = true;
+ }
+ }
+
+ return bRet;
+}
+
+void SVGFilter::implExportDocumentHeaderImpressOrDraw(sal_Int32 nDocX, sal_Int32 nDocY,
+ sal_Int32 nDocWidth, sal_Int32 nDocHeight)
+{
+ const Reference< XExtendedDocumentHandler > xExtDocHandler( mpSVGExport->GetDocHandler(), UNO_QUERY );
+ if( xExtDocHandler.is() && !mpSVGExport->IsUseTinyProfile() )
+ {
+ xExtDocHandler->unknown( SVG_DTD_STRING );
+ }
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "version", "1.2" );
+
+ if( mpSVGExport->IsUseTinyProfile() )
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "baseProfile", "tiny" );
+
+ // The following if block means that the slide size is not adapted
+ // to the size of the browser window, moreover the slide is top left aligned
+ // instead of centered:
+ OUString aAttr;
+ if( !mbPresentation )
+ {
+ aAttr = OUString::number( nDocWidth * 0.01 ) + "mm";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", aAttr );
+
+ aAttr = OUString::number( nDocHeight * 0.01 ) + "mm";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", aAttr );
+ }
+
+ // #i124608# set viewBox explicitly to the exported content
+ if (mbExportShapeSelection)
+ {
+ aAttr = OUString::number(nDocX) + " " + OUString::number(nDocY) + " ";
+ }
+ else
+ {
+ aAttr = "0 0 ";
+ }
+
+ aAttr += OUString::number(nDocWidth) + " " + OUString::number(nDocHeight);
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "viewBox", aAttr );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "preserveAspectRatio", "xMidYMid" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "fill-rule", "evenodd" );
+
+ // standard line width is based on 1 pixel on a 90 DPI device (0.28222mmm)
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "stroke-width", OUString::number( 28.222 ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "stroke-linejoin", "round" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns", constSvgNamespace );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:ooo", "http://xml.openoffice.org/svg/export" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:xlink", "http://www.w3.org/1999/xlink" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:presentation", "http://sun.com/xmlns/staroffice/presentation" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:smil", "http://www.w3.org/2001/SMIL20/" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:anim", "urn:oasis:names:tc:opendocument:xmlns:animation:1.0" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xml:space", "preserve" );
+
+ mpSVGDoc = new SvXMLElementExport( *mpSVGExport, XML_NAMESPACE_NONE, "svg", true, true );
+
+ // Create a ClipPath element that will be used for cutting bitmaps and other elements that could exceed the page margins.
+ if(mbExportShapeSelection)
+ return;
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "ClipPathGroup" );
+ SvXMLElementExport aDefsElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true );
+ {
+ msClipPathId = "presentation_clip_path";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", msClipPathId );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "clipPathUnits", "userSpaceOnUse" );
+ SvXMLElementExport aClipPathElem( *mpSVGExport, XML_NAMESPACE_NONE, "clipPath", true, true );
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "x", OUString::number( nDocX ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "y", OUString::number( nDocY ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", OUString::number( nDocWidth ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", OUString::number( nDocHeight ) );
+ SvXMLElementExport aRectElem( *mpSVGExport, XML_NAMESPACE_NONE, "rect", true, true );
+ }
+ }
+ // Create a ClipPath element applied to the leaving slide in order
+ // to avoid that slide borders are visible during transition
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", "presentation_clip_path_shrink" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "clipPathUnits", "userSpaceOnUse" );
+ SvXMLElementExport aClipPathElem( *mpSVGExport, XML_NAMESPACE_NONE, "clipPath", true, true );
+ {
+ sal_Int32 nDocWidthExt = nDocWidth / 500;
+ sal_Int32 nDocHeightExt = nDocHeight / 500;
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "x", OUString::number( nDocX + nDocWidthExt / 2 ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "y", OUString::number( nDocY + nDocHeightExt / 2) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", OUString::number( nDocWidth - nDocWidthExt ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", OUString::number( nDocHeight - nDocHeightExt ) );
+ SvXMLElementExport aRectElem( *mpSVGExport, XML_NAMESPACE_NONE, "rect", true, true );
+ }
+ }
+}
+
+void SVGFilter::implExportDocumentHeaderWriterOrCalc(sal_Int32 nDocX, sal_Int32 nDocY,
+ sal_Int32 nDocWidth, sal_Int32 nDocHeight)
+{
+ OUString aAttr;
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "version", "1.2" );
+
+ aAttr = OUString::number( nDocWidth * 0.01 ) + "mm";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", aAttr );
+
+ aAttr = OUString::number( nDocHeight * 0.01 ) + "mm";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", aAttr );
+
+ aAttr = OUString::number(nDocX) + " " + OUString::number(nDocY) + " " +
+ OUString::number(nDocWidth) + " " + OUString::number(nDocHeight);
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "viewBox", aAttr );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "preserveAspectRatio", "xMidYMid" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "fill-rule", "evenodd" );
+
+ // standard line width is based on 1 pixel on a 90 DPI device (0.28222mmm)
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "stroke-width", OUString::number( 28.222 ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "stroke-linejoin", "round" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns", constSvgNamespace );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:ooo", "http://xml.openoffice.org/svg/export" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:xlink", "http://www.w3.org/1999/xlink" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:smil", "urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xml:space", "preserve" );
+
+ mpSVGDoc = new SvXMLElementExport( *mpSVGExport, XML_NAMESPACE_NONE, "svg", true, true );
+}
+
+/// Append aField to aFieldSet if it is not already present in the set and create the field id sFieldId
+template< typename TextFieldType >
+static OUString implGenerateFieldId( std::vector< std::unique_ptr<TextField> > & aFieldSet,
+ const TextFieldType & aField,
+ std::u16string_view sOOOElemField,
+ const Reference< css::drawing::XDrawPage >& xMasterPage )
+{
+ bool bFound = false;
+ sal_Int32 i;
+ sal_Int32 nSize = aFieldSet.size();
+ for( i = 0; i < nSize; ++i )
+ {
+ if( *(aFieldSet[i]) == aField )
+ {
+ bFound = true;
+ break;
+ }
+ }
+ OUString sFieldId(OUString::Concat(sOOOElemField) + "_");
+ if( !bFound )
+ {
+ aFieldSet.emplace_back( new TextFieldType( aField ) );
+ }
+ aFieldSet[i]->insertMasterPage( xMasterPage );
+ sFieldId += OUString::number( i );
+ return sFieldId;
+}
+
+
+void SVGFilter::implGenerateMetaData()
+{
+ sal_Int32 nCount = mSelectedPages.size();
+ if( nCount == 0 )
+ return;
+
+ // we wrap all meta presentation info into a svg:defs element
+ SvXMLElementExport aDefsElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true );
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", NSPREFIX "meta_slides" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "number-of-slides", OUString::number( nCount ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "start-slide-number", OUString::number( mnVisiblePage ) );
+
+ if( mpSVGExport->IsUsePositionedCharacters() )
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "use-positioned-chars", "true" );
+ }
+
+ // Add a (global) Page Numbering Type attribute for the document
+ // NOTE: at present pSdrModel->GetPageNumType() returns always css::style::NumberingType::ARABIC
+ // so the following code fragment is pretty useless
+ sal_Int32 nPageNumberingType = css::style::NumberingType::ARABIC;
+ SvxDrawPage* pSvxDrawPage = comphelper::getFromUnoTunnel<SvxDrawPage>( mSelectedPages[0] );
+ if( pSvxDrawPage )
+ {
+ SdrPage* pSdrPage = pSvxDrawPage->GetSdrPage();
+ SdrModel& rSdrModel(pSdrPage->getSdrModelFromSdrPage());
+ nPageNumberingType = rSdrModel.GetPageNumType();
+
+ // That is used by CalcFieldHdl method.
+ mVisiblePagePropSet.nPageNumberingType = nPageNumberingType;
+ }
+ if( nPageNumberingType != css::style::NumberingType::NUMBER_NONE )
+ {
+ OUString sNumberingType;
+ switch( nPageNumberingType )
+ {
+ case css::style::NumberingType::CHARS_UPPER_LETTER:
+ sNumberingType = "alpha-upper";
+ break;
+ case css::style::NumberingType::CHARS_LOWER_LETTER:
+ sNumberingType = "alpha-lower";
+ break;
+ case css::style::NumberingType::ROMAN_UPPER:
+ sNumberingType = "roman-upper";
+ break;
+ case css::style::NumberingType::ROMAN_LOWER:
+ sNumberingType = "roman-lower";
+ break;
+ case css::style::NumberingType::ARABIC:
+ // arabic numbering type is the default, so we do not append any attribute for it
+ default:
+ // in case the numbering type is not handled we fall back on arabic numbering
+ break;
+ }
+ if( !sNumberingType.isEmpty() )
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "page-numbering-type", sNumberingType );
+ }
+
+
+ {
+ SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+ const OUString aId( NSPREFIX "meta_slide" );
+ const OUString aElemTextFieldId( aOOOElemTextField );
+ std::vector< std::unique_ptr<TextField> > aFieldSet;
+
+ // dummy slide - used as leaving slide for transition on the first slide
+ if( mbPresentation )
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", NSPREFIX "meta_dummy_slide" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrSlide, "dummy-slide" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrMaster, "dummy-master-page" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrBackgroundVisibility, "hidden" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrMasterObjectsVisibility, "hidden" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrHasTransition, "false" );
+ SvXMLElementExport aMetaDummySlideElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+ }
+
+ for( sal_Int32 i = 0; i < nCount; ++i )
+ {
+ const Reference< css::drawing::XDrawPage > & xDrawPage = mSelectedPages[i];
+ Reference< css::drawing::XMasterPageTarget > xMasterPageTarget( xDrawPage, UNO_QUERY );
+ if (!xMasterPageTarget.is())
+ continue;
+ Reference< css::drawing::XDrawPage > xMasterPage = xMasterPageTarget->getMasterPage();
+ OUString aSlideId(aId + "_" + OUString::number( i ));
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", aSlideId );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrSlide, implGetValidIDFromInterface( xDrawPage ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrMaster, implGetValidIDFromInterface( xMasterPage ) );
+
+
+ if( mbPresentation )
+ {
+ Reference< XPropertySet > xPropSet( xDrawPage, UNO_QUERY );
+
+ if( xPropSet.is() )
+ {
+ OUString sDisplayName;
+ if (xPropSet->getPropertyValue("LinkDisplayName") >>= sDisplayName)
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrDisplayName, sDisplayName);
+ }
+
+ bool bBackgroundVisibility = true; // default: visible
+ bool bBackgroundObjectsVisibility = true; // default: visible
+
+ FixedDateTimeField aFixedDateTimeField;
+ VariableDateTimeField aVariableDateTimeField;
+ FooterField aFooterField;
+
+ // check if the slide has a custom background which overlaps the master page background
+ Reference< XPropertySet > xBackground;
+ xPropSet->getPropertyValue( "Background" ) >>= xBackground;
+ if( xBackground.is() )
+ {
+ drawing::FillStyle aFillStyle;
+ bool assigned = ( xBackground->getPropertyValue( "FillStyle" ) >>= aFillStyle );
+ // has a custom background ?
+ if( assigned && aFillStyle != drawing::FillStyle_NONE )
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrHasCustomBackground, "true" );
+ }
+
+ xPropSet->getPropertyValue( "IsBackgroundVisible" ) >>= bBackgroundVisibility;
+ // in case the attribute is set to its default value it is not appended to the meta-slide element
+ if( !bBackgroundVisibility ) // visibility default value: 'visible'
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrBackgroundVisibility, "hidden" );
+
+ // Page Number, Date/Time, Footer and Header Fields are regarded as background objects.
+ // So bBackgroundObjectsVisibility overrides visibility of master page text fields.
+ xPropSet->getPropertyValue( "IsBackgroundObjectsVisible" ) >>= bBackgroundObjectsVisibility;
+ if( bBackgroundObjectsVisibility ) // visibility default value: 'visible'
+ {
+ bool bPageNumberVisibility = false; // default: hidden
+ bool bDateTimeVisibility = true; // default: visible
+ bool bFooterVisibility = true; // default: visible
+
+ // Page Number Field
+ xPropSet->getPropertyValue( "IsPageNumberVisible" ) >>= bPageNumberVisibility;
+ bPageNumberVisibility = bPageNumberVisibility && ( nPageNumberingType != css::style::NumberingType::NUMBER_NONE );
+ if( bPageNumberVisibility ) // visibility default value: 'hidden'
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "page-number-visibility", "visible" );
+ }
+
+ // Date/Time Field
+ xPropSet->getPropertyValue( "IsDateTimeVisible" ) >>= bDateTimeVisibility;
+ if( bDateTimeVisibility ) // visibility default value: 'visible'
+ {
+ bool bDateTimeFixed = true; // default: fixed
+ xPropSet->getPropertyValue( "IsDateTimeFixed" ) >>= bDateTimeFixed;
+ if( bDateTimeFixed ) // we are interested only in the field text not in the date/time format
+ {
+ xPropSet->getPropertyValue( "DateTimeText" ) >>= aFixedDateTimeField.text;
+ if( !aFixedDateTimeField.text.isEmpty() )
+ {
+ OUString sFieldId = implGenerateFieldId( aFieldSet, aFixedDateTimeField, aElemTextFieldId, xMasterPage );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrDateTimeField, sFieldId );
+ }
+ }
+ else // the inverse applies: we are interested only in the date/time format not in the field text
+ {
+ xPropSet->getPropertyValue( "DateTimeFormat" ) >>= aVariableDateTimeField.format;
+ OUString sFieldId = implGenerateFieldId( aFieldSet, aVariableDateTimeField, aElemTextFieldId, xMasterPage );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrDateTimeField, sFieldId );
+ }
+ }
+ else
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "date-time-visibility", "hidden" );
+ }
+
+ // Footer Field
+ xPropSet->getPropertyValue( "IsFooterVisible" ) >>= bFooterVisibility;
+ if( bFooterVisibility ) // visibility default value: 'visible'
+ {
+ xPropSet->getPropertyValue( "FooterText" ) >>= aFooterField.text;
+ if( !aFooterField.text.isEmpty() )
+ {
+ OUString sFieldId = implGenerateFieldId( aFieldSet, aFooterField, aElemTextFieldId, xMasterPage );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrFooterField, sFieldId );
+ }
+ }
+ else
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "footer-visibility", "hidden" );
+ }
+ }
+ else
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrMasterObjectsVisibility, "hidden" );
+ }
+
+ sal_Int32 nChange(0);
+
+ if( xPropSet->getPropertySetInfo()->hasPropertyByName( "Change" ) &&
+ (xPropSet->getPropertyValue( "Change" ) >>= nChange ) && nChange == 1 )
+ {
+ double fSlideDuration(0);
+ if( xPropSet->getPropertySetInfo()->hasPropertyByName( "HighResDuration" ) &&
+ (xPropSet->getPropertyValue( "HighResDuration" ) >>= fSlideDuration) )
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrSlideDuration, OUString::number(fSlideDuration) );
+ }
+ }
+ // We look for a slide transition.
+ // Transition properties are exported together with animations.
+ sal_Int16 nTransitionType(0);
+ if( xPropSet->getPropertySetInfo()->hasPropertyByName( "TransitionType" ) &&
+ (xPropSet->getPropertyValue( "TransitionType" ) >>= nTransitionType) )
+ {
+ sal_Int16 nTransitionSubType(0);
+ if( xPropSet->getPropertyValue( "TransitionSubtype" ) >>= nTransitionSubType )
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrHasTransition, "true" );
+ }
+ }
+
+ }
+ }
+
+ {
+ SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+ } // when the aExp2 destructor is called the </g> tag is appended to the output file
+ }
+
+ // export text field elements
+ if( mbPresentation )
+ {
+ for( sal_Int32 i = 0, nSize = aFieldSet.size(); i < nSize; ++i )
+ {
+ OUString sElemId = OUString::Concat(aOOOElemTextField) + "_" + OUString::number( i );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sElemId );
+ aFieldSet[i]->elementExport( mpSVGExport.get() );
+ }
+ if( mpSVGExport->IsEmbedFonts() && mpSVGExport->IsUsePositionedCharacters() )
+ {
+ for(const std::unique_ptr<TextField>& i : aFieldSet)
+ {
+ i->growCharSet( mTextFieldCharSets );
+ }
+ }
+ }
+
+ // text fields are used only for generating meta info so we don't need them anymore
+ aFieldSet.clear();
+ }
+}
+
+
+void SVGFilter::implExportAnimations()
+{
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", "presentation-animations" );
+ SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true );
+
+ for(const uno::Reference<drawing::XDrawPage> & mSelectedPage : mSelectedPages)
+ {
+ Reference< XPropertySet > xProps( mSelectedPage, UNO_QUERY );
+
+ if( xProps.is() && xProps->getPropertySetInfo()->hasPropertyByName( "TransitionType" ) )
+ {
+ sal_Int16 nTransition = 0;
+ xProps->getPropertyValue( "TransitionType" ) >>= nTransition;
+ // we have a slide transition ?
+ bool bHasEffects = ( nTransition != 0 );
+
+ Reference< XAnimationNodeSupplier > xAnimNodeSupplier( mSelectedPage, UNO_QUERY );
+ if( xAnimNodeSupplier.is() )
+ {
+ Reference< XAnimationNode > xRootNode = xAnimNodeSupplier->getAnimationNode();
+ if( xRootNode.is() )
+ {
+ if( !bHasEffects )
+ {
+ // first check if there are no animations
+ Reference< XEnumerationAccess > xEnumerationAccess( xRootNode, UNO_QUERY_THROW );
+ Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
+ if( xEnumeration->hasMoreElements() )
+ {
+ // first child node may be an empty main sequence, check this
+ Reference< XAnimationNode > xMainNode( xEnumeration->nextElement(), UNO_QUERY_THROW );
+ Reference< XEnumerationAccess > xMainEnumerationAccess( xMainNode, UNO_QUERY_THROW );
+ Reference< XEnumeration > xMainEnumeration( xMainEnumerationAccess->createEnumeration(), UNO_SET_THROW );
+
+ // only export if the main sequence is not empty or if there are additional
+ // trigger sequences
+ bHasEffects = xMainEnumeration->hasMoreElements() || xEnumeration->hasMoreElements();
+ }
+ }
+ if( bHasEffects )
+ {
+ OUString sId = implGetValidIDFromInterface( mSelectedPage );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrSlide, sId );
+ sId += "-animations";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sId );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Animations" );
+ SvXMLElementExport aDefsElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true );
+
+ rtl::Reference< xmloff::AnimationsExporter > xAnimationsExporter = new xmloff::AnimationsExporter( *mpSVGExport, xProps );
+ xAnimationsExporter->prepare( xRootNode );
+ xAnimationsExporter->exportAnimations( xRootNode );
+ }
+ }
+ }
+ }
+ }
+}
+
+
+void SVGFilter::implExportTextShapeIndex()
+{
+ if(mbExportShapeSelection)
+ return;
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "TextShapeIndex" );
+ SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true );
+
+ sal_Int32 nCount = mSelectedPages.size();
+ for( sal_Int32 i = 0; i < nCount; ++i )
+ {
+ const Reference< css::drawing::XDrawPage > & xDrawPage = mSelectedPages[i];
+ if( mTextShapeIdListMap.find( xDrawPage ) != mTextShapeIdListMap.end() )
+ {
+ OUString sTextShapeIdList = mTextShapeIdListMap[xDrawPage].trim();
+
+ const OUString& rPageId = implGetValidIDFromInterface( Reference<XInterface>(xDrawPage, UNO_QUERY) );
+ if( !rPageId.isEmpty() && !sTextShapeIdList.isEmpty() )
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrSlide, rPageId );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "id-list", sTextShapeIdList );
+ SvXMLElementExport aGElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+ }
+ }
+ }
+}
+
+
+void SVGFilter::implEmbedBulletGlyphs()
+{
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "EmbeddedBulletChars" );
+ SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true );
+
+ OUString sPathData = "M 580,1141 L 1163,571 580,0 -4,571 580,1141 Z";
+ implEmbedBulletGlyph( 57356, sPathData );
+ sPathData = "M 8,1128 L 1137,1128 1137,0 8,0 8,1128 Z";
+ implEmbedBulletGlyph( 57354, sPathData );
+ sPathData = "M 174,0 L 602,739 174,1481 1456,739 174,0 Z M 1358,739 L 309,1346 659,739 1358,739 Z";
+ implEmbedBulletGlyph( 10146, sPathData );
+ sPathData = "M 2015,739 L 1276,0 717,0 1260,543 174,543 174,936 1260,936 717,1481 1274,1481 2015,739 Z";
+ implEmbedBulletGlyph( 10132, sPathData );
+ sPathData = "M 0,-2 C -7,14 -16,27 -25,37 L 356,567 C 262,823 215,952 215,954 215,979 228,992 255,992 264,992 276,990 289,987 310,991 331,999 354,1012 L 381,999 492,748 772,1049 836,1024 860,1049 C 881,1039 901,1025 922,1006 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 C 774,196 753,168 711,139 L 727,119 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 C 142,-110 111,-127 72,-127 30,-127 9,-110 8,-76 1,-67 -2,-52 -2,-32 -2,-23 -1,-13 0,-2 Z";
+ implEmbedBulletGlyph( 10007, sPathData );
+ sPathData = "M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 41,549 55,616 82,672 116,743 169,778 240,778 293,778 328,747 346,684 L 369,508 C 377,444 397,411 428,410 L 1163,1116 C 1174,1127 1196,1133 1229,1133 1271,1133 1292,1118 1292,1087 L 1292,965 C 1292,929 1282,901 1262,881 L 442,47 C 390,-6 338,-33 285,-33 Z";
+ implEmbedBulletGlyph( 10004, sPathData );
+ sPathData = "M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 223,773 276,916 383,1023 489,1130 632,1184 813,1184 992,1184 1136,1130 1245,1023 1353,916 1407,772 1407,592 1407,412 1353,268 1245,161 1136,54 992,0 813,0 Z";
+ implEmbedBulletGlyph( 9679, sPathData );
+ sPathData = "M 346,457 C 273,457 209,483 155,535 101,586 74,649 74,723 74,796 101,859 155,911 209,963 273,989 346,989 419,989 480,963 531,910 582,859 608,796 608,723 608,648 583,586 532,535 482,483 420,457 346,457 Z";
+ implEmbedBulletGlyph( 8226, sPathData );
+ sPathData = "M -4,459 L 1135,459 1135,606 -4,606 -4,459 Z";
+ implEmbedBulletGlyph( 8211, sPathData );
+ sPathData = "M 173,740 C 173,903 231,1043 346,1159 462,1274 601,1332 765,1332 928,1332 1067,1274 1183,1159 1299,1043 1357,903 1357,740 1357,577 1299,437 1183,322 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z";
+ implEmbedBulletGlyph( 61548, sPathData );
+}
+
+
+void SVGFilter::implEmbedBulletGlyph( sal_Unicode cBullet, const OUString & sPathData )
+{
+ OUString sId = "bullet-char-template-" + OUString::number( static_cast<sal_Int32>(cBullet) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sId );
+
+ OUString sFactor = OUString::number( 1.0 / 2048 );
+ OUString sTransform = "scale(" + sFactor + ",-" + sFactor + ")";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "transform", sTransform );
+
+ SvXMLElementExport aGElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "d", sPathData );
+ SvXMLElementExport aPathElem( *mpSVGExport, XML_NAMESPACE_NONE, "path", true, true );
+
+ mpSVGExport->SetEmbeddedBulletGlyph(cBullet);
+}
+
+void SVGFilter::implExportBackgroundBitmaps()
+{
+ if (maBitmapActionMap.empty())
+ return;
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "BackgroundBitmaps" );
+ SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true );
+
+ OUString sId;
+ for( const auto& rItem : maBitmapActionMap )
+ {
+ BitmapChecksum nChecksum = rItem.first;
+ const GDIMetaFile& aEmbeddedBitmapMtf = *(rItem.second);
+ MetaAction* pBitmapAction = aEmbeddedBitmapMtf.GetAction( 0 );
+ if( pBitmapAction )
+ {
+ sId = "bitmap(" + OUString::number( nChecksum ) + ")";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sId );
+
+ const Point aPos; // (0, 0)
+ const Size aSize = aEmbeddedBitmapMtf.GetPrefSize();
+ mpSVGWriter->WriteMetaFile( aPos, aSize, aEmbeddedBitmapMtf, 0xffffffff );
+ }
+ }
+}
+
+void SVGFilter::implExportTiledBackground()
+{
+ if( maPatterProps.empty() )
+ return;
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "BackgroundPatterns" );
+ SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true );
+
+ for( const auto& [ rSlideId, rData ] : maPatterProps )
+ {
+ auto aBitmapActionIt = maBitmapActionMap.find( rData.aBitmapChecksum );
+ if( aBitmapActionIt != maBitmapActionMap.end() )
+ {
+ // pattern element attributes
+ const OUString sPatternId = getPatternIdForTiledBackground( rSlideId, rData.aBitmapChecksum );
+ // <pattern> <use>
+ {
+ // pattern element attributes
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sPatternId );
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "x", OUString::number( rData.aPos.X() ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "y", OUString::number( rData.aPos.Y() ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", OUString::number( rData.aSize.Width() ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", OUString::number( rData.aSize.Height() ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "patternUnits", "userSpaceOnUse" );
+
+ SvXMLElementExport aPatternElem( *mpSVGExport, XML_NAMESPACE_NONE, "pattern", true, true );
+
+ // use element attributes
+ const Size& aOrigSize = aBitmapActionIt->second->GetPrefSize();
+ OUString sTransform;
+ Fraction aFractionX( rData.aSize.Width(), aOrigSize.Width() );
+ Fraction aFractionY( rData.aSize.Height(), aOrigSize.Height() );
+ double scaleX = rtl_math_round( double(aFractionX), 3, rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected );
+ double scaleY = rtl_math_round( double(aFractionY), 3, rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected );
+ if( !rtl_math_approxEqual( scaleX, 1.0 ) || !rtl_math_approxEqual( scaleY, 1.0 ) )
+ sTransform += " scale(" + OUString::number( double(aFractionX) ) + ", " + OUString::number( double(aFractionY) ) + ")";
+
+ if( !sTransform.isEmpty() )
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "transform", sTransform );
+
+ // referenced bitmap
+ OUString sRefId = "#bitmap(" + OUString::number( rData.aBitmapChecksum ) + ")";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xlink:href", sRefId );
+
+ SvXMLElementExport aUseElem( *mpSVGExport, XML_NAMESPACE_NONE, "use", true, true );
+ } // </use> </pattern>
+
+ // <g> <rect>
+ {
+ // group
+ const OUString sBgId = getIdForTiledBackground( rSlideId, rData.aBitmapChecksum );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sBgId );
+
+ SvXMLElementExport aGroupElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+
+ // rectangle
+ const OUString sUrl = "url(#" + sPatternId + ")";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "x", "0" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "y", "0" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", OUString::number( rData.aSlideSize.Width() ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", OUString::number( rData.aSlideSize.Height() ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "stroke", "none" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "fill", sUrl );
+
+ SvXMLElementExport aRectElem( *mpSVGExport, XML_NAMESPACE_NONE, "rect", true, true );
+ } // </g> </rect>
+ }
+ }
+}
+
+/** SVGFilter::implExportTextEmbeddedBitmaps
+ We export bitmaps embedded into text shapes, such as those used by list
+ items with image style, only once in a specific defs element.
+ */
+void SVGFilter::implExportTextEmbeddedBitmaps()
+{
+ if (mEmbeddedBitmapActionSet.empty())
+ return;
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "TextEmbeddedBitmaps" );
+ SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true );
+
+ OUString sId;
+
+ for (auto const& embeddedBitmapAction : mEmbeddedBitmapActionSet)
+ {
+ const GDIMetaFile& aMtf = embeddedBitmapAction.GetRepresentation();
+
+ if( aMtf.GetActionSize() == 1 )
+ {
+ MetaAction* pAction = aMtf.GetAction( 0 );
+ if( pAction )
+ {
+ BitmapChecksum nId = GetBitmapChecksum( pAction );
+ sId = "bitmap(" + OUString::number( nId ) + ")";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sId );
+
+ const Reference< XInterface >& rxShape = embeddedBitmapAction.GetObject();
+ Reference< XPropertySet > xShapePropSet( rxShape, UNO_QUERY );
+ css::awt::Rectangle aBoundRect;
+ if( xShapePropSet.is() && ( xShapePropSet->getPropertyValue( "BoundRect" ) >>= aBoundRect ) )
+ {
+ // Origin of the coordinate device must be (0,0).
+ const Point aTopLeft;
+ const Size aSize( aBoundRect.Width, aBoundRect.Height );
+
+ Point aPt;
+ MetaBitmapActionGetPoint( pAction, aPt );
+ // The image must be exported with x, y attribute set to 0,
+ // on the contrary when referenced by a <use> element,
+ // specifying the wanted position, they will result
+ // misplaced.
+ pAction->Move( -aPt.X(), -aPt.Y() );
+ mpSVGWriter->WriteMetaFile( aTopLeft, aSize, aMtf, 0xffffffff );
+ // We reset to the original values so that when the <use>
+ // element is created the x, y attributes are correct.
+ pAction->Move( aPt.X(), aPt.Y() );
+ }
+ else
+ {
+ OSL_FAIL( "implExportTextEmbeddedBitmaps: no shape bounding box." );
+ return;
+ }
+ }
+ else
+ {
+ OSL_FAIL( "implExportTextEmbeddedBitmaps: metafile should have MetaBmpExScaleAction only." );
+ return;
+ }
+ }
+ else
+ {
+ OSL_FAIL( "implExportTextEmbeddedBitmaps: metafile should have a single action." );
+ return;
+ }
+ }
+}
+
+void SVGFilter::implGenerateScript()
+{
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "type", "text/ecmascript" );
+
+ {
+ SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, "script", true, true );
+ Reference< XExtendedDocumentHandler > xExtDocHandler( mpSVGExport->GetDocHandler(), UNO_QUERY );
+
+ if( xExtDocHandler.is() )
+ {
+ for (size_t i = 0; i < N_SVGSCRIPT_FRAGMENTS; ++i)
+ {
+ xExtDocHandler->unknown(OUString::createFromAscii(g_SVGScripts[i]));
+ }
+ }
+ }
+}
+
+
+Any SVGFilter::implSafeGetPagePropSet( const OUString & sPropertyName,
+ const Reference< XPropertySet > & rxPropSet,
+ const Reference< XPropertySetInfo > & rxPropSetInfo )
+{
+ Any result;
+ if( rxPropSetInfo->hasPropertyByName( sPropertyName ) )
+ {
+ result = rxPropSet->getPropertyValue( sPropertyName );
+ }
+ return result;
+}
+
+
+/** We collect info on master page elements visibility, and placeholder text shape content.
+ This method is used when exporting a single page as implGenerateMetaData is not invoked.
+ */
+void SVGFilter::implGetPagePropSet( const Reference< css::drawing::XDrawPage > & rxPage )
+{
+ mVisiblePagePropSet.bIsBackgroundVisible = true;
+ mVisiblePagePropSet.bAreBackgroundObjectsVisible = true;
+ mVisiblePagePropSet.bIsPageNumberFieldVisible = false;
+ mVisiblePagePropSet.bIsHeaderFieldVisible = false;
+ mVisiblePagePropSet.bIsFooterFieldVisible = true;
+ mVisiblePagePropSet.bIsDateTimeFieldVisible = true;
+ mVisiblePagePropSet.bIsDateTimeFieldFixed = true;
+ mVisiblePagePropSet.nDateTimeFormat = SvxDateFormat::B;
+ mVisiblePagePropSet.nPageNumberingType = css::style::NumberingType::ARABIC;
+
+ // We collect info on master page elements visibility, and placeholder text shape content.
+ Reference< XPropertySet > xPropSet( rxPage, UNO_QUERY );
+ if( !xPropSet.is() )
+ return;
+
+ Reference< XPropertySetInfo > xPropSetInfo( xPropSet->getPropertySetInfo() );
+ if( !xPropSetInfo.is() )
+ return;
+
+ implSafeGetPagePropSet( "IsBackgroundVisible", xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsBackgroundVisible;
+ implSafeGetPagePropSet( "IsBackgroundObjectsVisible", xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bAreBackgroundObjectsVisible;
+ implSafeGetPagePropSet( "IsPageNumberVisible", xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsPageNumberFieldVisible;
+ implSafeGetPagePropSet( "IsHeaderVisible", xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsHeaderFieldVisible;
+ implSafeGetPagePropSet( "IsFooterVisible", xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsFooterFieldVisible;
+ implSafeGetPagePropSet( "IsDateTimeVisible", xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsDateTimeFieldVisible;
+
+ implSafeGetPagePropSet( "IsDateTimeFixed", xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsDateTimeFieldFixed;
+ sal_Int32 nTmp;
+ if (implSafeGetPagePropSet( "DateTimeFormat", xPropSet, xPropSetInfo ) >>= nTmp)
+ mVisiblePagePropSet.nDateTimeFormat = static_cast<SvxDateFormat>(nTmp);
+
+ if( mVisiblePagePropSet.bIsPageNumberFieldVisible )
+ {
+ SvxDrawPage* pSvxDrawPage = comphelper::getFromUnoTunnel<SvxDrawPage>( rxPage );
+ if( pSvxDrawPage )
+ {
+ SdrPage* pSdrPage = pSvxDrawPage->GetSdrPage();
+ SdrModel& rSdrModel(pSdrPage->getSdrModelFromSdrPage());
+ mVisiblePagePropSet.nPageNumberingType = rSdrModel.GetPageNumType();
+ }
+ }
+}
+
+
+bool SVGFilter::implExportMasterPages( const std::vector< Reference< css::drawing::XDrawPage > > & rxPages,
+ sal_Int32 nFirstPage, sal_Int32 nLastPage )
+{
+ DBG_ASSERT( nFirstPage <= nLastPage,
+ "SVGFilter::implExportMasterPages: nFirstPage > nLastPage" );
+
+ // When the exported slides are more than one we wrap master page elements
+ // with a svg <defs> element.
+ OUString aContainerTag = (!mbPresentation) ? OUString( "g" ) : OUString( "defs" );
+ SvXMLElementExport aContainerElement( *mpSVGExport, XML_NAMESPACE_NONE, aContainerTag, true, true );
+
+ // dummy slide - used as leaving slide for transition on the first slide
+ if( mbPresentation )
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", "dummy-master-page" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrName, "dummy-master-page" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Master_Slide" );
+ SvXMLElementExport aMasterSlideElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", "bg-dummy-master-page" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Background" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "visibility", "hidden" );
+ SvXMLElementExport aBackgroundElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+ }
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", "bo-dummy-master-page" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "BackgroundObjects" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "visibility", "hidden" );
+ SvXMLElementExport aBackgroundObjectElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+ }
+ }
+
+ bool bRet = false;
+ for( sal_Int32 i = nFirstPage; i <= nLastPage; ++i )
+ {
+ if( rxPages[i].is() )
+ {
+ // add id attribute
+ const OUString & sPageId = implGetValidIDFromInterface( rxPages[i] );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sPageId );
+
+ bRet = implExportPage( sPageId, rxPages[i], rxPages[i], true /* is a master page */ ) || bRet;
+ }
+ }
+ return bRet;
+}
+
+
+void SVGFilter::implExportDrawPages( const std::vector< Reference< css::drawing::XDrawPage > > & rxPages,
+ sal_Int32 nFirstPage, sal_Int32 nLastPage )
+{
+ DBG_ASSERT( nFirstPage <= nLastPage,
+ "SVGFilter::implExportDrawPages: nFirstPage > nLastPage" );
+
+ // dummy slide - used as leaving slide for transition on the first slide
+ if( mbPresentation && !mbExportShapeSelection)
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "DummySlide" );
+ SvXMLElementExport aDummySlideElement( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+ {
+ SvXMLElementExport aGElement( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", "dummy-slide" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Slide" );
+ OUString sClipPathAttrValue = "url(#" + msClipPathId + ")";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "clip-path", sClipPathAttrValue );
+ SvXMLElementExport aSlideElement( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrName, "dummy-page" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Page" );
+ SvXMLElementExport aPageElement( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+ }
+ }
+ }
+ }
+
+ if(!mbExportShapeSelection)
+ {
+ // We wrap all slide in a group element with class name "SlideGroup".
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "SlideGroup" );
+ SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+
+ for( sal_Int32 i = nFirstPage; i <= nLastPage; ++i )
+ {
+ Reference< css::drawing::XShapes > xShapes;
+
+ if (mbExportShapeSelection)
+ {
+ // #i124608# export a given object selection
+ xShapes = maShapeSelection;
+ }
+ else
+ {
+ xShapes = rxPages[i];
+ }
+
+ if( xShapes.is() )
+ {
+ // Insert the <g> open tag related to the svg element for
+ // handling a slide visibility.
+ // In case the exported slides are more than one the initial
+ // visibility of each slide is set to 'hidden'.
+ if( mbPresentation )
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "visibility", "hidden" );
+ }
+ SvXMLElementExport aGElement( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+
+
+ {
+ // Insert a further inner the <g> open tag for handling elements
+ // inserted before or after a slide: that is used for some
+ // when switching from the last to the first slide.
+ const OUString & sPageId = implGetValidIDFromInterface( rxPages[i] );
+ OUString sContainerId = "container-" + sPageId;
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sContainerId );
+ SvXMLElementExport aContainerExp( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+
+ {
+ // add id attribute
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sPageId );
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Slide" );
+
+ // Adding a clip path to each exported slide , so in case
+ // bitmaps or other elements exceed the slide margins, they are
+ // trimmed, even when they are shown inside a thumbnail view.
+ OUString sClipPathAttrValue = "url(#" + msClipPathId + ")";
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "clip-path", sClipPathAttrValue );
+
+ SvXMLElementExport aSlideElement( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+
+ implExportPage( sPageId, rxPages[i], xShapes, false /* is not a master page */ );
+ }
+ } // append the </g> closing tag related to inserted elements
+ } // append the </g> closing tag related to the svg element handling the slide visibility
+ }
+ }
+ else
+ {
+ assert(maShapeSelection.is());
+ assert(rxPages.size() == 1);
+
+ const OUString & sPageId = implGetValidIDFromInterface( rxPages[0] );
+ implExportPage( sPageId, rxPages[0], maShapeSelection, false /* is not a master page */ );
+ }
+}
+
+
+bool SVGFilter::implExportPage( std::u16string_view sPageId,
+ const Reference< css::drawing::XDrawPage > & rxPage,
+ const Reference< css::drawing::XShapes > & xShapes,
+ bool bMaster )
+{
+ bool bRet = false;
+
+ {
+ OUString sPageName = implGetInterfaceName( rxPage );
+ if( mbPresentation && !sPageName.isEmpty() )
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrName, sPageName );
+
+ {
+ Reference< XExtendedDocumentHandler > xExtDocHandler( mpSVGExport->GetDocHandler(), UNO_QUERY );
+
+ if( xExtDocHandler.is() )
+ {
+ OUString aDesc;
+
+ if( bMaster )
+ aDesc = "Master_Slide";
+ else
+ aDesc = "Page";
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", aDesc );
+ }
+ }
+
+ // insert the <g> open tag related to the DrawPage/MasterPage
+ SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+
+ // In case the page has a background object we append it .
+ if( mpObjects->find( rxPage ) != mpObjects->end() )
+ {
+ const GDIMetaFile& rMtf = (*mpObjects)[ rxPage ].GetRepresentation();
+ if( rMtf.GetActionSize() )
+ {
+ // If this is not a master page wrap the slide custom background
+ // by a <defs> element.
+ // Slide custom background, if any, is referenced at a different position
+ // in order to not overlap background objects.
+ std::unique_ptr<SvXMLElementExport> xDefsExp;
+ if (!bMaster) // insert the <defs> open tag related to the slide background
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "SlideBackground" );
+ xDefsExp.reset( new SvXMLElementExport( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true ) );
+ }
+ {
+ // background id = "bg-" + page id
+ OUString sBackgroundId = OUString::Concat("bg-") + sPageId;
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sBackgroundId );
+
+ // At present (LibreOffice 3.4.0) the 'IsBackgroundVisible' property is not handled
+ // by Impress; anyway we handle this property as referring only to the visibility
+ // of the master page background. So if a slide has its own background object,
+ // the visibility of such a background object is always inherited from the visibility
+ // of the parent slide regardless of the value of the 'IsBackgroundVisible' property.
+ // This means that we need to set up the visibility attribute only for the background
+ // element of a master page.
+ if( !mbPresentation && bMaster )
+ {
+ if( !mVisiblePagePropSet.bIsBackgroundVisible )
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "visibility", "hidden" );
+ }
+ }
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Background" );
+
+ // insert the <g> open tag related to the Background
+ SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+
+ // append all elements that make up the Background
+ const Point aNullPt;
+ mpSVGWriter->WriteMetaFile( aNullPt, rMtf.GetPrefSize(), rMtf, SVGWRITER_WRITE_FILL );
+ } // insert the </g> closing tag related to the Background
+
+ } // insert the </defs> closing tag related to the slide background
+ }
+
+ // In case we are dealing with a master page we need to group all its shapes
+ // into a group element, this group will make up the so named "background objects"
+ if( bMaster )
+ {
+ // background objects id = "bo-" + page id
+ OUString sBackgroundObjectsId = OUString::Concat("bo-") + sPageId;
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sBackgroundObjectsId );
+ if( !mbPresentation )
+ {
+ if( !mVisiblePagePropSet.bAreBackgroundObjectsVisible )
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "visibility", "hidden" );
+ }
+ }
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "BackgroundObjects" );
+
+ // insert the <g> open tag related to the Background Objects
+ SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+
+ // append all shapes that make up the Master Slide
+ bRet = implExportShapes( xShapes, true ) || bRet;
+ } // append the </g> closing tag related to the Background Objects
+ else
+ {
+ // append all shapes that make up the Slide
+ bRet = implExportShapes( xShapes, false ) || bRet;
+ }
+ } // append the </g> closing tag related to the Slide/Master_Slide
+
+ return bRet;
+}
+
+
+bool SVGFilter::implExportShapes( const Reference< css::drawing::XShapes >& rxShapes, bool bMaster )
+{
+ Reference< css::drawing::XShape > xShape;
+ bool bRet = false;
+
+ for( sal_Int32 i = 0, nCount = rxShapes->getCount(); i < nCount; ++i )
+ {
+ if( ( rxShapes->getByIndex( i ) >>= xShape ) && xShape.is() )
+ bRet = implExportShape( xShape, bMaster ) || bRet;
+
+ xShape = nullptr;
+ }
+
+ return bRet;
+}
+
+
+bool SVGFilter::implExportShape( const Reference< css::drawing::XShape >& rxShape, bool bMaster )
+{
+ Reference< XPropertySet > xShapePropSet( rxShape, UNO_QUERY );
+ bool bRet = false;
+
+ if( xShapePropSet.is() )
+ {
+ const OUString aShapeType( rxShape->getShapeType() );
+ bool bHideObj = false;
+
+ if( mbPresentation )
+ {
+ xShapePropSet->getPropertyValue( "IsEmptyPresentationObject" ) >>= bHideObj;
+ }
+
+ OUString aShapeClass = implGetClassFromShape( rxShape );
+ if( bMaster )
+ {
+ if( aShapeClass == "TitleText" || aShapeClass == "Outline" )
+ bHideObj = true;
+ }
+
+ if( !bHideObj )
+ {
+ if( aShapeType.lastIndexOf( "drawing.GroupShape" ) != -1 )
+ {
+ Reference< css::drawing::XShapes > xShapes( rxShape, UNO_QUERY );
+
+ if( xShapes.is() )
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Group" );
+ const OUString& rShapeId = implGetValidIDFromInterface( Reference<XInterface>(rxShape, UNO_QUERY) );
+ if( !rShapeId.isEmpty() )
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", rShapeId );
+ }
+ SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+
+ bRet = implExportShapes( xShapes, bMaster );
+ }
+ }
+
+ if( !bRet && mpObjects->find( rxShape ) != mpObjects->end() )
+ {
+ css::awt::Rectangle aBoundRect;
+ const GDIMetaFile& rMtf = (*mpObjects)[ rxShape ].GetRepresentation();
+
+ xShapePropSet->getPropertyValue( "BoundRect" ) >>= aBoundRect;
+
+ const Point aTopLeft( aBoundRect.X, aBoundRect.Y );
+ const Size aSize( aBoundRect.Width, aBoundRect.Height );
+
+ if( rMtf.GetActionSize() )
+ { // for text field shapes we set up text-adjust attributes
+ // and set visibility to hidden
+ OUString aElementId;
+
+ if( mbPresentation )
+ {
+ bool bIsPageNumber = ( aShapeClass == "Slide_Number" );
+ bool bIsFooter = ( aShapeClass == "Footer" );
+ bool bIsDateTime = ( aShapeClass == "Date/Time" );
+ bool bTextField = bIsPageNumber || bIsFooter || bIsDateTime;
+ if( bTextField )
+ {
+ // to notify to the SVGActionWriter::ImplWriteActions method
+ // that we are dealing with a placeholder shape
+ aElementId = sPlaceholderTag;
+
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "visibility", "hidden" );
+ }
+
+ if( bTextField || ( aShapeClass == "TextShape" ) )
+ {
+ sal_uInt16 nTextAdjust = sal_uInt16(ParagraphAdjust_LEFT);
+ OUString sTextAdjust;
+ xShapePropSet->getPropertyValue( "ParaAdjust" ) >>= nTextAdjust;
+
+ switch( static_cast<ParagraphAdjust>(nTextAdjust) )
+ {
+ case ParagraphAdjust_LEFT:
+ sTextAdjust = "left";
+ break;
+ case ParagraphAdjust_CENTER:
+ sTextAdjust = "center";
+ break;
+ case ParagraphAdjust_RIGHT:
+ sTextAdjust = "right";
+ break;
+ default:
+ break;
+ }
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "text-adjust", sTextAdjust );
+ }
+ }
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", aShapeClass );
+ SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+
+ Reference< XExtendedDocumentHandler > xExtDocHandler( mpSVGExport->GetDocHandler(), UNO_QUERY );
+
+ OUString aTitle;
+ xShapePropSet->getPropertyValue( "Title" ) >>= aTitle;
+ if( !aTitle.isEmpty() )
+ {
+ SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, "title", true, true );
+ xExtDocHandler->characters( aTitle );
+ }
+
+ OUString aDescription;
+ xShapePropSet->getPropertyValue( "Description" ) >>= aDescription;
+ if( !aDescription.isEmpty() )
+ {
+ SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, "desc", true, true );
+ xExtDocHandler->characters( aDescription );
+ }
+
+
+ const OUString& rShapeId = implGetValidIDFromInterface( Reference<XInterface>(rxShape, UNO_QUERY) );
+ if( !rShapeId.isEmpty() )
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", rShapeId );
+ }
+
+ const GDIMetaFile* pEmbeddedBitmapsMtf = nullptr;
+ if( mEmbeddedBitmapActionMap.find( rxShape ) != mEmbeddedBitmapActionMap.end() )
+ {
+ pEmbeddedBitmapsMtf = &( mEmbeddedBitmapActionMap[ rxShape ].GetRepresentation() );
+ }
+
+ {
+ OUString aBookmark;
+ Reference<XPropertySetInfo> xShapePropSetInfo = xShapePropSet->getPropertySetInfo();
+ if(xShapePropSetInfo->hasPropertyByName("Bookmark"))
+ {
+ xShapePropSet->getPropertyValue( "Bookmark" ) >>= aBookmark;
+ }
+
+ SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true );
+
+ // export the shape bounding box
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "BoundingBox" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "stroke", "none" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "fill", "none" );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "x", OUString::number( aBoundRect.X ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "y", OUString::number( aBoundRect.Y ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", OUString::number( aBoundRect.Width ) );
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", OUString::number( aBoundRect.Height ) );
+ SvXMLElementExport aBB( *mpSVGExport, XML_NAMESPACE_NONE, "rect", true, true );
+ }
+
+ if( !aBookmark.isEmpty() )
+ {
+ INetURLObject aINetURLObject(aBookmark);
+ if (!aINetURLObject.HasError()
+ && aINetURLObject.GetProtocol() != INetProtocol::Javascript)
+ {
+ mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xlink:href", aBookmark);
+ SvXMLElementExport alinkA( *mpSVGExport, XML_NAMESPACE_NONE, "a", true, true );
+ mpSVGWriter->WriteMetaFile( aTopLeft, aSize, rMtf,
+ 0xffffffff,
+ aElementId,
+ &rxShape,
+ pEmbeddedBitmapsMtf );
+ }
+ }
+ else
+ {
+ mpSVGWriter->WriteMetaFile( aTopLeft, aSize, rMtf,
+ 0xffffffff,
+ aElementId,
+ &rxShape,
+ pEmbeddedBitmapsMtf );
+ }
+ }
+ }
+
+ bRet = true;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+
+bool SVGFilter::implCreateObjects()
+{
+ if (mbExportShapeSelection)
+ {
+ // #i124608# export a given object selection
+ if (!mSelectedPages.empty() && mSelectedPages[0].is())
+ {
+ implCreateObjectsFromShapes(mSelectedPages[0], maShapeSelection);
+ return true;
+ }
+ return false;
+ }
+
+ sal_Int32 i, nCount;
+
+ for( i = 0, nCount = mMasterPageTargets.size(); i < nCount; ++i )
+ {
+ const Reference< css::drawing::XDrawPage > & xMasterPage = mMasterPageTargets[i];
+
+ if( xMasterPage.is() )
+ {
+ mCreateOjectsCurrentMasterPage = xMasterPage;
+ implCreateObjectsFromBackground( xMasterPage );
+
+ if( xMasterPage.is() )
+ implCreateObjectsFromShapes( xMasterPage, xMasterPage );
+ }
+ }
+
+ for( i = 0, nCount = mSelectedPages.size(); i < nCount; ++i )
+ {
+ const Reference< css::drawing::XDrawPage > & xDrawPage = mSelectedPages[i];
+
+ if( xDrawPage.is() )
+ {
+ // TODO complete the implementation for exporting custom background for each slide
+ // implementation status:
+ // - hatch stroke color is set to 'none' so the hatch is not visible, why?
+ // - gradient look is not really awesome, too few colors are used;
+ // - gradient and hatch are not exported only once
+ // and then referenced in case more than one slide uses them.
+ Reference< XPropertySet > xPropSet( xDrawPage, UNO_QUERY );
+ if( xPropSet.is() )
+ {
+ Reference< XPropertySet > xBackground;
+ xPropSet->getPropertyValue( "Background" ) >>= xBackground;
+ if( xBackground.is() )
+ {
+ drawing::FillStyle aFillStyle;
+ bool assigned = ( xBackground->getPropertyValue( "FillStyle" ) >>= aFillStyle );
+ if( assigned && aFillStyle != drawing::FillStyle_NONE )
+ {
+ implCreateObjectsFromBackground( xDrawPage );
+ }
+ }
+ }
+ implCreateObjectsFromShapes( xDrawPage, xDrawPage );
+ }
+ }
+ return true;
+}
+
+
+bool SVGFilter::implCreateObjectsFromShapes( const Reference< css::drawing::XDrawPage > & rxPage, const Reference< css::drawing::XShapes >& rxShapes )
+{
+ Reference< css::drawing::XShape > xShape;
+ bool bRet = false;
+
+ for( sal_Int32 i = 0, nCount = rxShapes->getCount(); i < nCount; ++i )
+ {
+ if( ( rxShapes->getByIndex( i ) >>= xShape ) && xShape.is() )
+ bRet = implCreateObjectsFromShape( rxPage, xShape ) || bRet;
+
+ xShape = nullptr;
+ }
+
+ return bRet;
+}
+
+
+bool SVGFilter::implCreateObjectsFromShape( const Reference< css::drawing::XDrawPage > & rxPage, const Reference< css::drawing::XShape >& rxShape )
+{
+ bool bRet = false;
+ if( rxShape->getShapeType().lastIndexOf( "drawing.GroupShape" ) != -1 )
+ {
+ Reference< css::drawing::XShapes > xShapes( rxShape, UNO_QUERY );
+
+ if( xShapes.is() )
+ bRet = implCreateObjectsFromShapes( rxPage, xShapes );
+ }
+ else
+ {
+ SdrObject* pObj = SdrObject::getSdrObjectFromXShape(rxShape);
+
+ if( pObj )
+ {
+ Graphic aGraphic(SdrExchangeView::GetObjGraphic(*pObj));
+
+ // Writer graphic shapes are handled differently
+ if( mbWriterFilter && aGraphic.GetType() == GraphicType::NONE )
+ {
+ if (rxShape->getShapeType() == "com.sun.star.drawing.GraphicObjectShape")
+ {
+ uno::Reference<beans::XPropertySet> xPropertySet(rxShape, uno::UNO_QUERY);
+ uno::Reference<graphic::XGraphic> xGraphic;
+ xPropertySet->getPropertyValue("Graphic") >>= xGraphic;
+
+ if (!xGraphic.is())
+ return false;
+
+ aGraphic = Graphic(xGraphic);
+ }
+ }
+
+ if( aGraphic.GetType() != GraphicType::NONE )
+ {
+ if( aGraphic.GetType() == GraphicType::Bitmap )
+ {
+ GDIMetaFile aMtf;
+ const Point aNullPt;
+ const Size aSize( pObj->GetCurrentBoundRect().GetSize() );
+
+ aMtf.AddAction( new MetaBmpExScaleAction( aNullPt, aSize, aGraphic.GetBitmapEx() ) );
+ aMtf.SetPrefSize( aSize );
+ aMtf.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+
+ (*mpObjects)[ rxShape ] = ObjectRepresentation( rxShape, aMtf );
+ }
+ else
+ {
+ if( aGraphic.GetGDIMetaFile().GetActionSize() )
+ {
+ Reference< XText > xText( rxShape, UNO_QUERY );
+ bool bIsTextShape = xText.is();
+
+ if( !mpSVGExport->IsUsePositionedCharacters() && bIsTextShape )
+ {
+ Reference< XPropertySet > xShapePropSet( rxShape, UNO_QUERY );
+
+ if( xShapePropSet.is() )
+ {
+ bool bHideObj = false;
+
+ if( mbPresentation )
+ {
+ xShapePropSet->getPropertyValue( "IsEmptyPresentationObject" ) >>= bHideObj;
+ }
+
+ if( !bHideObj )
+ {
+ // We create a map of text shape ids.
+ implRegisterInterface( rxShape );
+ const OUString& rShapeId = implGetValidIDFromInterface( Reference<XInterface>(rxShape, UNO_QUERY) );
+ if( !rShapeId.isEmpty() )
+ {
+ mTextShapeIdListMap[rxPage] += rShapeId;
+ mTextShapeIdListMap[rxPage] += " ";
+ }
+
+ // We create a set of bitmaps embedded into text shape.
+ GDIMetaFile aMtf;
+ const Size aSize( pObj->GetCurrentBoundRect().GetSize() );
+ MetaAction* pAction;
+ bool bIsTextShapeStarted = false;
+ const GDIMetaFile& rMtf = aGraphic.GetGDIMetaFile();
+ size_t nCount = rMtf.GetActionSize();
+ for( size_t nCurAction = 0; nCurAction < nCount; ++nCurAction )
+ {
+ pAction = rMtf.GetAction( nCurAction );
+ const MetaActionType nType = pAction->GetType();
+
+ if( nType == MetaActionType::COMMENT )
+ {
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
+ if( pA->GetComment().equalsIgnoreAsciiCase("XTEXT_PAINTSHAPE_BEGIN") )
+ {
+ bIsTextShapeStarted = true;
+ }
+ else if( pA->GetComment().equalsIgnoreAsciiCase( "XTEXT_PAINTSHAPE_END" ) )
+ {
+ bIsTextShapeStarted = false;
+ }
+ }
+ if( bIsTextShapeStarted && ( nType == MetaActionType::BMPSCALE || nType == MetaActionType::BMPEXSCALE ) )
+ {
+ GDIMetaFile aEmbeddedBitmapMtf;
+ aEmbeddedBitmapMtf.AddAction( pAction );
+ aEmbeddedBitmapMtf.SetPrefSize( aSize );
+ aEmbeddedBitmapMtf.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ mEmbeddedBitmapActionSet.insert( ObjectRepresentation( rxShape, aEmbeddedBitmapMtf ) );
+ aMtf.AddAction( pAction );
+ }
+ }
+ aMtf.SetPrefSize( aSize );
+ aMtf.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ mEmbeddedBitmapActionMap[ rxShape ] = ObjectRepresentation( rxShape, aMtf );
+ }
+ }
+ }
+ }
+ (*mpObjects)[ rxShape ] = ObjectRepresentation( rxShape, aGraphic.GetGDIMetaFile() );
+ }
+ bRet = true;
+ }
+ }
+ }
+
+ return bRet;
+}
+
+
+void SVGFilter::implCreateObjectsFromBackground( const Reference< css::drawing::XDrawPage >& rxDrawPage )
+{
+ Reference< css::drawing::XGraphicExportFilter > xExporter = drawing::GraphicExportFilter::create( mxContext );
+
+ GDIMetaFile aMtf;
+
+ utl::TempFile aFile;
+ aFile.EnableKillingFile();
+
+ Sequence< PropertyValue > aDescriptor{
+ comphelper::makePropertyValue("FilterName", OUString( "SVM" )),
+ comphelper::makePropertyValue("URL", aFile.GetURL()),
+ comphelper::makePropertyValue("ExportOnlyBackground", true)
+ };
+
+ xExporter->setSourceDocument( Reference< XComponent >( rxDrawPage, UNO_QUERY ) );
+ xExporter->filter( aDescriptor );
+ SvmReader aReader( *aFile.GetStream( StreamMode::READ ) );
+ aReader.Read( aMtf );
+
+ bool bIsBitmap = false;
+ bool bIsTiled = false;
+
+ // look for background type
+ Reference< XPropertySet > xPropSet( rxDrawPage, UNO_QUERY );
+ if( xPropSet.is() )
+ {
+ Reference< XPropertySet > xBackground;
+ xPropSet->getPropertyValue( "Background" ) >>= xBackground;
+ if( xBackground.is() )
+ {
+ drawing::FillStyle aFillStyle;
+ if( xBackground->getPropertyValue( "FillStyle" ) >>= aFillStyle )
+ {
+ if( aFillStyle == drawing::FillStyle::FillStyle_BITMAP )
+ {
+ bIsBitmap = true;
+ xBackground->getPropertyValue( "FillBitmapTile" ) >>= bIsTiled;
+
+ // we do not handle tiled background with a row or column offset
+ sal_Int32 nFillBitmapOffsetX = 0, nFillBitmapOffsetY = 0;
+ xBackground->getPropertyValue( "FillBitmapOffsetX" ) >>= nFillBitmapOffsetX;
+ xBackground->getPropertyValue( "FillBitmapOffsetY" ) >>= nFillBitmapOffsetY;
+ bIsTiled = bIsTiled && ( nFillBitmapOffsetX == 0 && nFillBitmapOffsetY == 0 );
+ }
+ }
+ }
+ }
+
+ if( !bIsBitmap )
+ {
+ (*mpObjects)[ rxDrawPage ] = ObjectRepresentation( rxDrawPage, aMtf );
+ return;
+ }
+
+ GDIMetaFile aTiledMtf;
+ bool bBitmapFound = false;
+ MetaAction* pAction;
+ size_t nCount = aMtf.GetActionSize();
+ for( size_t nCurAction = 0; nCurAction < nCount; ++nCurAction )
+ {
+ pAction = aMtf.GetAction( nCurAction );
+ const MetaActionType nType = pAction->GetType();
+
+ // collect bitmap
+ if( nType == MetaActionType::BMPSCALE || nType == MetaActionType::BMPEXSCALE )
+ {
+ if( bBitmapFound )
+ continue;
+ bBitmapFound = true; // the subsequent bitmaps are still the same just translated
+
+ BitmapChecksum nChecksum = GetBitmapChecksum( pAction );
+ if( maBitmapActionMap.find( nChecksum ) == maBitmapActionMap.end() )
+ {
+ Point aPos; // (0, 0)
+ Size aSize;
+ MetaBitmapActionGetOrigSize( pAction, aSize );
+ MetaAction* pBitmapAction = CreateMetaBitmapAction( pAction, aPos, aSize );
+ if( pBitmapAction )
+ {
+ GDIMetaFile* pEmbeddedBitmapMtf = new GDIMetaFile();
+ pEmbeddedBitmapMtf->AddAction( pBitmapAction );
+ pEmbeddedBitmapMtf->SetPrefSize( aSize );
+ pEmbeddedBitmapMtf->SetPrefMapMode( MapMode(MapUnit::Map100thMM) );
+
+ maBitmapActionMap[ nChecksum ].reset( pEmbeddedBitmapMtf );
+ }
+ }
+
+ if( bIsTiled )
+ {
+ // collect data for <pattern> and <rect>
+ const OUString & sPageId = implGetValidIDFromInterface( rxDrawPage );
+ Point aPos;
+ MetaBitmapActionGetPoint( pAction, aPos );
+ Size aSize;
+ MetaBitmapActionGetSize( pAction, aSize );
+
+ sal_Int32 nSlideWidth = 0, nSlideHeight = 0;
+ xPropSet->getPropertyValue( "Width" ) >>= nSlideWidth;
+ xPropSet->getPropertyValue( "Height" ) >>= nSlideHeight;
+
+ maPatterProps[ sPageId ] = { nChecksum, aPos, aSize, { nSlideWidth, nSlideHeight } };
+
+ // create meta comment action that is used to exporting
+ // a <use> element which points to the group element representing the background
+ const OUString sBgId = getIdForTiledBackground( sPageId, nChecksum );
+ OString sComment = sTiledBackgroundTag + " " + sBgId.toUtf8();
+ MetaCommentAction* pCommentAction = new MetaCommentAction( sComment );
+ if( pCommentAction )
+ aTiledMtf.AddAction( pCommentAction );
+ }
+ }
+ else if( bIsTiled && nType != MetaActionType::CLIPREGION )
+ {
+ aTiledMtf.AddAction( pAction );
+ }
+ }
+
+ (*mpObjects)[ rxDrawPage ] = ObjectRepresentation( rxDrawPage, bIsTiled ? aTiledMtf : aMtf );
+}
+
+OUString SVGFilter::implGetClassFromShape( const Reference< css::drawing::XShape >& rxShape )
+{
+ OUString aRet;
+ const OUString aShapeType( rxShape->getShapeType() );
+
+ if( aShapeType.lastIndexOf( "drawing.GroupShape" ) != -1 )
+ aRet = "Group";
+ else if( aShapeType.lastIndexOf( "drawing.GraphicObjectShape" ) != -1 )
+ aRet = "Graphic";
+ else if( aShapeType.lastIndexOf( "drawing.OLE2Shape" ) != -1 )
+ aRet = "OLE2";
+ else if( aShapeType.lastIndexOf( "drawing.TextShape" ) != -1 )
+ aRet = "TextShape";
+ else if( aShapeType.lastIndexOf( "presentation.HeaderShape" ) != -1 )
+ aRet = "Header";
+ else if( aShapeType.lastIndexOf( "presentation.FooterShape" ) != -1 )
+ aRet = "Footer";
+ else if( aShapeType.lastIndexOf( "presentation.DateTimeShape" ) != -1 )
+ aRet = "Date/Time";
+ else if( aShapeType.lastIndexOf( "presentation.SlideNumberShape" ) != -1 )
+ aRet = "Slide_Number";
+ else if( aShapeType.lastIndexOf( "presentation.TitleTextShape" ) != -1 )
+ aRet = "TitleText";
+ else if( aShapeType.lastIndexOf( "presentation.OutlinerShape" ) != -1 )
+ aRet = "Outline";
+ else
+ aRet = aShapeType;
+
+ return aRet;
+}
+
+
+void SVGFilter::implRegisterInterface( const Reference< XInterface >& rxIf )
+{
+ if( rxIf.is() )
+ mpSVGExport->getInterfaceToIdentifierMapper().registerReference( rxIf );
+}
+
+
+const OUString & SVGFilter::implGetValidIDFromInterface( const Reference< XInterface >& rxIf )
+{
+ return mpSVGExport->getInterfaceToIdentifierMapper().getIdentifier( rxIf );
+}
+
+
+OUString SVGFilter::implGetInterfaceName( const Reference< XInterface >& rxIf )
+{
+ Reference< XNamed > xNamed( rxIf, UNO_QUERY );
+ OUString aRet;
+ if( xNamed.is() )
+ {
+ aRet = xNamed->getName().replace( ' ', '_' );
+ }
+ return aRet;
+}
+
+
+IMPL_LINK( SVGFilter, CalcFieldHdl, EditFieldInfo*, pInfo, void )
+{
+ bool bFieldProcessed = false;
+ if( pInfo && mbPresentation )
+ {
+ bFieldProcessed = true;
+ if( mpSVGExport->IsEmbedFonts() && mpSVGExport->IsUsePositionedCharacters() )
+ {
+ // to notify to the SVGActionWriter::ImplWriteText method
+ // that we are dealing with a placeholder shape
+ OUStringBuffer aRepresentation(sPlaceholderTag);
+
+ if( !mCreateOjectsCurrentMasterPage.is() )
+ {
+ OSL_FAIL( "error: !mCreateOjectsCurrentMasterPage.is()" );
+ return;
+ }
+ bool bHasCharSetMap = mTextFieldCharSets.find( mCreateOjectsCurrentMasterPage ) != mTextFieldCharSets.end();
+
+ static constexpr OUStringLiteral aHeaderId( NSPREFIX "header-field" );
+ static constexpr OUStringLiteral aFooterId( aOOOAttrFooterField );
+ static constexpr OUStringLiteral aDateTimeId( aOOOAttrDateTimeField );
+ static const OUString aVariableDateTimeId( aOOOAttrDateTimeField + "-variable" );
+
+ const UCharSet * pCharSet = nullptr;
+ UCharSetMap * pCharSetMap = nullptr;
+ if( bHasCharSetMap )
+ {
+ pCharSetMap = &( mTextFieldCharSets[ mCreateOjectsCurrentMasterPage ] );
+ }
+ const SvxFieldData* pField = pInfo->GetField().GetField();
+ if( bHasCharSetMap && ( pField->GetClassId() == text::textfield::Type::PRESENTATION_HEADER ) && ( pCharSetMap->find( aHeaderId ) != pCharSetMap->end() ) )
+ {
+ pCharSet = &( (*pCharSetMap)[ aHeaderId ] );
+ }
+ else if( bHasCharSetMap && ( pField->GetClassId() == text::textfield::Type::PRESENTATION_FOOTER ) && ( pCharSetMap->find( aFooterId ) != pCharSetMap->end() ) )
+ {
+ pCharSet = &( (*pCharSetMap)[ aFooterId ] );
+ }
+ else if( pField->GetClassId() == text::textfield::Type::PRESENTATION_DATE_TIME )
+ {
+ if( bHasCharSetMap && ( pCharSetMap->find( aDateTimeId ) != pCharSetMap->end() ) )
+ {
+ pCharSet = &( (*pCharSetMap)[ aDateTimeId ] );
+ }
+ if( bHasCharSetMap && ( pCharSetMap->find( aVariableDateTimeId ) != pCharSetMap->end() ) && !(*pCharSetMap)[ aVariableDateTimeId ].empty() )
+ {
+ SvxDateFormat eDateFormat = SvxDateFormat::B, eCurDateFormat;
+ const UCharSet & aCharSet = (*pCharSetMap)[ aVariableDateTimeId ];
+ // we look for the most verbose date format
+ for (auto const& elem : aCharSet)
+ {
+ eCurDateFormat = static_cast<SvxDateFormat>( static_cast<int>(elem) & 0x0f );
+ switch( eDateFormat )
+ {
+ case SvxDateFormat::StdSmall:
+ case SvxDateFormat::A: // 13.02.96
+ case SvxDateFormat::B: // 13.02.1996
+ switch( eCurDateFormat )
+ {
+ case SvxDateFormat::C: // 13.Feb 1996
+ case SvxDateFormat::D: // 13.February 1996
+ case SvxDateFormat::E: // Tue, 13.February 1996
+ case SvxDateFormat::StdBig:
+ case SvxDateFormat::F: // Tuesday, 13.February 1996
+ eDateFormat = eCurDateFormat;
+ break;
+ default:
+ break;
+ }
+ break;
+ case SvxDateFormat::C: // 13.Feb 1996
+ case SvxDateFormat::D: // 13.February 1996
+ switch( eCurDateFormat )
+ {
+ case SvxDateFormat::E: // Tue, 13.February 1996
+ case SvxDateFormat::StdBig:
+ case SvxDateFormat::F: // Tuesday, 13.February 1996
+ eDateFormat = eCurDateFormat;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ // Independently of the date format, we always put all these characters by default.
+ // They should be enough to cover every time format.
+ aRepresentation.append( "0123456789.:/-APM" );
+
+ if( eDateFormat != SvxDateFormat::AppDefault )
+ {
+ OUStringBuffer sDate;
+ LanguageType eLang = pInfo->GetOutliner()->GetLanguage( pInfo->GetPara(), pInfo->GetPos() );
+ SvNumberFormatter * pNumberFormatter = new SvNumberFormatter( ::comphelper::getProcessComponentContext(), LANGUAGE_SYSTEM );
+ // We always collect the characters obtained by using the SvxDateFormat::B (as: 13.02.1996)
+ // so we are sure to include any unusual day|month|year separator.
+ Date aDate( 1, 1, 1996 );
+ sDate.append( SvxDateField::GetFormatted( aDate, SvxDateFormat::B, *pNumberFormatter, eLang ) );
+ switch( eDateFormat )
+ {
+ case SvxDateFormat::E: // Tue, 13.February 1996
+ case SvxDateFormat::StdBig:
+ case SvxDateFormat::F: // Tuesday, 13.February 1996
+ for( sal_uInt16 i = 1; i <= 7; ++i ) // we get all days in a week
+ {
+ aDate.SetDay( i );
+ sDate.append( SvxDateField::GetFormatted( aDate, eDateFormat, *pNumberFormatter, eLang ) );
+ }
+ [[fallthrough]]; // We need months too!
+ case SvxDateFormat::C: // 13.Feb 1996
+ case SvxDateFormat::D: // 13.February 1996
+ for( sal_uInt16 i = 1; i <= 12; ++i ) // we get all months in a year
+ {
+ aDate.SetMonth( i );
+ sDate.append( SvxDateField::GetFormatted( aDate, eDateFormat, *pNumberFormatter, eLang ) );
+ }
+ break;
+ // coverity[dead_error_begin] - following conditions exist to avoid compiler warning
+ case SvxDateFormat::StdSmall:
+ case SvxDateFormat::A: // 13.02.96
+ case SvxDateFormat::B: // 13.02.1996
+ default:
+ // nothing to do here, we always collect the characters needed for these cases.
+ break;
+ }
+ aRepresentation.append( sDate );
+ }
+ }
+ }
+ else if( pField->GetClassId() == text::textfield::Type::PAGE )
+ {
+ switch( mVisiblePagePropSet.nPageNumberingType )
+ {
+ case css::style::NumberingType::CHARS_UPPER_LETTER:
+ aRepresentation.append( "QWERTYUIOPASDFGHJKLZXCVBNM" );
+ break;
+ case css::style::NumberingType::CHARS_LOWER_LETTER:
+ aRepresentation.append( "qwertyuiopasdfghjklzxcvbnm" );
+ break;
+ case css::style::NumberingType::ROMAN_UPPER:
+ aRepresentation.append( "IVXLCDM" );
+ break;
+ case css::style::NumberingType::ROMAN_LOWER:
+ aRepresentation.append( "ivxlcdm" );
+ break;
+ // arabic numbering type is the default
+ case css::style::NumberingType::ARABIC:
+ // in case the numbering type is not handled we fall back on arabic numbering
+ default:
+ aRepresentation.append( "0123456789" );
+ break;
+ }
+ }
+ else
+ {
+ bFieldProcessed = false;
+ }
+ if( bFieldProcessed )
+ {
+ if( pCharSet != nullptr )
+ {
+ for (auto const& elem : *pCharSet)
+ {
+ aRepresentation.append(elem);
+ }
+ }
+ pInfo->SetRepresentation( aRepresentation.makeStringAndClear() );
+ }
+ }
+ else
+ {
+ bFieldProcessed = false;
+ }
+
+ }
+ if (!bFieldProcessed)
+ maOldFieldHdl.Call( pInfo );
+}
+
+
+void SVGExport::writeMtf( const GDIMetaFile& rMtf )
+{
+ const Size aSize( OutputDevice::LogicToLogic(rMtf.GetPrefSize(), rMtf.GetPrefMapMode(), MapMode(MapUnit::MapMM)) );
+ OUString aAttr;
+ Reference< XExtendedDocumentHandler> xExtDocHandler( GetDocHandler(), UNO_QUERY );
+
+ if( xExtDocHandler.is() && IsUseDTDString() )
+ xExtDocHandler->unknown( SVG_DTD_STRING );
+
+ aAttr = OUString::number( aSize.Width() ) + "mm";
+ AddAttribute( XML_NAMESPACE_NONE, "width", aAttr );
+
+ aAttr = OUString::number( aSize.Height() ) + "mm";
+ AddAttribute( XML_NAMESPACE_NONE, "height", aAttr );
+
+ aAttr = "0 0 " + OUString::number( aSize.Width() * 100 ) + " " +
+ OUString::number( aSize.Height() * 100 );
+ AddAttribute( XML_NAMESPACE_NONE, "viewBox", aAttr );
+
+ AddAttribute( XML_NAMESPACE_NONE, "version", "1.1" );
+
+ if( IsUseTinyProfile() )
+ AddAttribute( XML_NAMESPACE_NONE, "baseProfile", "tiny" );
+
+ AddAttribute( XML_NAMESPACE_NONE, "xmlns", constSvgNamespace );
+ // For <image xlink:href="...">.
+ AddAttribute(XML_NAMESPACE_XMLNS, "xlink", "http://www.w3.org/1999/xlink");
+ AddAttribute( XML_NAMESPACE_NONE, "stroke-width", OUString::number( 28.222 ) );
+ AddAttribute( XML_NAMESPACE_NONE, "stroke-linejoin", "round" );
+ AddAttribute( XML_NAMESPACE_NONE, "xml:space", "preserve" );
+
+ {
+ SvXMLElementExport aSVG( *this, XML_NAMESPACE_NONE, "svg", true, true );
+
+ std::vector< ObjectRepresentation > aObjects;
+
+ aObjects.emplace_back( Reference< XInterface >(), rMtf );
+ SVGFontExport aSVGFontExport( *this, std::move(aObjects) );
+
+ Point aPoint100thmm( OutputDevice::LogicToLogic(rMtf.GetPrefMapMode().GetOrigin(), rMtf.GetPrefMapMode(), MapMode(MapUnit::Map100thMM)) );
+ Size aSize100thmm( OutputDevice::LogicToLogic(rMtf.GetPrefSize(), rMtf.GetPrefMapMode(), MapMode(MapUnit::Map100thMM)) );
+
+ SVGActionWriter aWriter( *this, aSVGFontExport );
+ aWriter.WriteMetaFile( aPoint100thmm, aSize100thmm, rMtf,
+ SVGWRITER_WRITE_FILL | SVGWRITER_WRITE_TEXT );
+ }
+}
+
+void SVGExport::SetEmbeddedBulletGlyph(sal_Unicode cBullet)
+{
+ maEmbeddedBulletGlyphs.insert(cBullet);
+}
+
+bool SVGExport::IsEmbeddedBulletGlyph(sal_Unicode cBullet) const
+{
+ auto it = maEmbeddedBulletGlyphs.find(cBullet);
+ return it != maEmbeddedBulletGlyphs.end();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/filter/source/svg/svgfilter.component b/filter/source/svg/svgfilter.component
new file mode 100644
index 000000000..479748f38
--- /dev/null
+++ b/filter/source/svg/svgfilter.component
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ -->
+
+<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@"
+ xmlns="http://openoffice.org/2010/uno-components">
+ <implementation name="com.sun.star.comp.Draw.SVGFilter"
+ constructor="filter_SVGFilter_get_implementation">
+ <service name="com.sun.star.document.ImportFilter"/>
+ <service name="com.sun.star.document.ExportFilter"/>
+ <service name="com.sun.star.document.ExtendedTypeDetection"/>
+ </implementation>
+ <implementation name="com.sun.star.comp.Draw.SVGWriter"
+ constructor="filter_SVGWriter_get_implementation">
+ <service name="com.sun.star.svg.SVGWriter"/>
+ </implementation>
+</component>
diff --git a/filter/source/svg/svgfilter.cxx b/filter/source/svg/svgfilter.cxx
new file mode 100644
index 000000000..4538e5021
--- /dev/null
+++ b/filter/source/svg/svgfilter.cxx
@@ -0,0 +1,847 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <cstdio>
+
+#include <comphelper/lok.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <com/sun/star/drawing/XDrawPage.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/XDrawView.hpp>
+#include <com/sun/star/drawing/XMasterPagesSupplier.hpp>
+#include <com/sun/star/drawing/XMasterPageTarget.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/XController.hpp>
+#include <com/sun/star/io/IOException.hpp>
+#include <com/sun/star/view/XSelectionSupplier.hpp>
+#include <com/sun/star/drawing/framework/XControllerManager.hpp>
+#include <com/sun/star/drawing/framework/XConfigurationController.hpp>
+#include <com/sun/star/drawing/framework/XConfiguration.hpp>
+#include <com/sun/star/drawing/framework/AnchorBindingMode.hpp>
+#include <com/sun/star/drawing/framework/XResourceId.hpp>
+#include <com/sun/star/drawing/framework/XResource.hpp>
+
+#include <unotools/mediadescriptor.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <tools/debug.hxx>
+#include <tools/diagnose_ex.h>
+#include <tools/zcodec.hxx>
+
+#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
+#include <drawinglayer/primitive2d/Primitive2DContainer.hxx>
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+
+#include "svgfilter.hxx"
+
+#include <svx/unopage.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/window.hxx>
+#include <svx/svdmodel.hxx>
+#include <svx/svdpage.hxx>
+#include <svx/svdograf.hxx>
+#include <svl/itempool.hxx>
+
+#include <memory>
+
+using namespace ::com::sun::star;
+
+namespace
+{
+ constexpr OUStringLiteral constFilterNameDraw = u"svg_Scalable_Vector_Graphics_Draw";
+ constexpr OUStringLiteral constFilterName = u"svg_Scalable_Vector_Graphics";
+}
+
+SVGFilter::SVGFilter( const Reference< XComponentContext >& rxCtx ) :
+ mxContext( rxCtx ),
+ mpSVGDoc( nullptr ),
+ mpSVGFontExport( nullptr ),
+ mpSVGWriter( nullptr ),
+ mbSinglePage( false ),
+ mnVisiblePage( -1 ),
+ mpObjects( nullptr ),
+ mbExportShapeSelection(false),
+ mbIsPreview(false),
+ mbWriterFilter(false),
+ mbCalcFilter(false),
+ mbImpressFilter(false),
+ mpDefaultSdrPage( nullptr ),
+ mbPresentation( false )
+{
+}
+
+SVGFilter::~SVGFilter()
+{
+ DBG_ASSERT( mpSVGDoc == nullptr, "mpSVGDoc not destroyed" );
+ DBG_ASSERT( mpSVGExport == nullptr, "mpSVGExport not destroyed" );
+ DBG_ASSERT( mpSVGFontExport == nullptr, "mpSVGFontExport not destroyed" );
+ DBG_ASSERT( mpSVGWriter == nullptr, "mpSVGWriter not destroyed" );
+ DBG_ASSERT( mpObjects == nullptr, "mpObjects not destroyed" );
+}
+
+sal_Bool SAL_CALL SVGFilter::filter( const Sequence< PropertyValue >& rDescriptor )
+{
+ mbWriterFilter = false;
+ mbCalcFilter = false;
+ mbImpressFilter = false;
+
+ if(mxDstDoc.is()) // Import works for Impress / draw only
+ return filterImpressOrDraw(rDescriptor);
+
+ if(!mxSrcDoc)
+ return false;
+
+ for (const PropertyValue& rProp : rDescriptor)
+ {
+ if (rProp.Name == "IsPreview")
+ {
+ rProp.Value >>= mbIsPreview;
+ break;
+ }
+ }
+
+ for (const PropertyValue& rProp : rDescriptor)
+ {
+ if (rProp.Name == "FilterName")
+ {
+ OUString sFilterName;
+ rProp.Value >>= sFilterName;
+ if(sFilterName == "impress_svg_Export")
+ {
+ mbImpressFilter = true;
+ return filterImpressOrDraw(rDescriptor);
+ }
+ else if(sFilterName == "writer_svg_Export")
+ {
+ mbWriterFilter = true;
+ return filterWriterOrCalc(rDescriptor);
+ }
+ else if(sFilterName == "calc_svg_Export")
+ {
+ mbCalcFilter = true;
+ return filterWriterOrCalc(rDescriptor);
+ }
+ break;
+ }
+ }
+ return filterImpressOrDraw(rDescriptor);
+}
+
+bool SVGFilter::filterImpressOrDraw( const Sequence< PropertyValue >& rDescriptor )
+{
+ SolarMutexGuard aGuard;
+ vcl::Window* pFocusWindow(Application::GetFocusWindow());
+ bool bRet(false);
+
+ if(pFocusWindow)
+ {
+ pFocusWindow->EnterWait();
+ }
+
+ if(mxDstDoc.is())
+ {
+ // Import. Use an endless loop to have easy exits for error handling
+ while(true)
+ {
+ // use MediaDescriptor to get needed data out of Sequence< PropertyValue >
+ utl::MediaDescriptor aMediaDescriptor(rDescriptor);
+ uno::Reference<io::XInputStream> xInputStream;
+
+ xInputStream.set(aMediaDescriptor[utl::MediaDescriptor::PROP_INPUTSTREAM], UNO_QUERY);
+
+ if(!xInputStream.is())
+ {
+ // we need the InputStream
+ break;
+ }
+
+ // get the DrawPagesSupplier
+ uno::Reference< drawing::XDrawPagesSupplier > xDrawPagesSupplier( mxDstDoc, uno::UNO_QUERY );
+
+ if(!xDrawPagesSupplier.is())
+ {
+ // we need the DrawPagesSupplier
+ break;
+ }
+
+ // get the DrawPages
+ uno::Reference< drawing::XDrawPages > xDrawPages = xDrawPagesSupplier->getDrawPages();
+
+ if(!xDrawPages.is())
+ {
+ // we need the DrawPages
+ break;
+ }
+
+ // check DrawPageCount (there should be one by default)
+ sal_Int32 nDrawPageCount(xDrawPages->getCount());
+
+ if(0 == nDrawPageCount)
+ {
+ // at least one DrawPage should be there - we need that
+ break;
+ }
+
+ // get that DrawPage
+ uno::Reference< drawing::XDrawPage > xDrawPage( xDrawPages->getByIndex(0), uno::UNO_QUERY );
+
+ if(!xDrawPage.is())
+ {
+ // we need that DrawPage
+ break;
+ }
+
+ // get that DrawPage's UNO API implementation
+ SvxDrawPage* pSvxDrawPage(comphelper::getFromUnoTunnel<SvxDrawPage>(xDrawPage));
+
+ if(nullptr == pSvxDrawPage || nullptr == pSvxDrawPage->GetSdrPage())
+ {
+ // we need a SvxDrawPage
+ break;
+ }
+
+ // get the SvStream to work with
+ std::unique_ptr< SvStream > aStream(utl::UcbStreamHelper::CreateStream(xInputStream, true));
+
+ if (!aStream)
+ {
+ // we need the SvStream
+ break;
+ }
+
+ // create a GraphicFilter and load the SVG (format already known, thus *could*
+ // be handed over to ImportGraphic - but detection is fast).
+ // As a bonus, zipped data is already detected and handled there
+ GraphicFilter aGraphicFilter;
+ Graphic aGraphic;
+ const ErrCode nGraphicFilterErrorCode(
+ aGraphicFilter.ImportGraphic(aGraphic, u"", *aStream));
+
+ if(ERRCODE_NONE != nGraphicFilterErrorCode)
+ {
+ // SVG import error, cannot continue
+ break;
+ }
+
+ // get the GraphicPrefSize early to check if we have any content
+ // (the SVG may contain nothing and/or just <g visibility="hidden"> stuff...)
+ const Size aGraphicPrefSize(aGraphic.GetPrefSize());
+
+ if(0 == aGraphicPrefSize.Width() || 0 == aGraphicPrefSize.Height())
+ {
+ // SVG has no displayable content, stop import.
+ // Also possible would be to get the sequence< Primitives >
+ // from aGraphic and check if it is empty.
+ // Possibility to set some error message here to tell
+ // the user what/why loading went wrong, but I do not
+ // know how this could be done here
+ break;
+ }
+
+ // tdf#118232 Get the sequence of primitives and check if geometry is completely
+ // hidden. If so, there is no need to add a SdrObject at all
+ auto const & rVectorGraphicData(aGraphic.getVectorGraphicData());
+ bool bContainsNoGeometry(false);
+
+ if(bool(rVectorGraphicData) && VectorGraphicDataType::Svg == rVectorGraphicData->getType())
+ {
+ const drawinglayer::primitive2d::Primitive2DContainer aContainer(rVectorGraphicData->getPrimitive2DSequence());
+
+ if(!aContainer.empty())
+ {
+ bool bAllAreHiddenGeometry(true);
+
+ for(const auto& rCandidate : aContainer)
+ {
+ if(rCandidate && PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D != rCandidate->getPrimitive2DID())
+ {
+ bAllAreHiddenGeometry = false;
+ break;
+ }
+ }
+
+ if(bAllAreHiddenGeometry)
+ {
+ bContainsNoGeometry = true;
+ }
+ }
+ }
+
+ // create a SdrModel-GraphicObject to insert to page
+ SdrPage* pTargetSdrPage(pSvxDrawPage->GetSdrPage());
+ std::unique_ptr< SdrGrafObj, SdrObjectFreeOp > aNewSdrGrafObj;
+
+ // tdf#118232 only add an SdrGrafObj when we have Geometry
+ if(!bContainsNoGeometry)
+ {
+ aNewSdrGrafObj.reset(
+ new SdrGrafObj(
+ pTargetSdrPage->getSdrModelFromSdrPage(),
+ aGraphic));
+ }
+
+ // Evtl. adapt the GraphicPrefSize to target-MapMode of target-Model
+ // (should be 100thmm here, but just stay safe by doing the conversion)
+ const MapMode aGraphicPrefMapMode(aGraphic.GetPrefMapMode());
+ const MapUnit eDestUnit(pTargetSdrPage->getSdrModelFromSdrPage().GetItemPool().GetMetric(0));
+ const MapUnit eSrcUnit(aGraphicPrefMapMode.GetMapUnit());
+ Size aGraphicSize(aGraphicPrefSize);
+
+ if (eDestUnit != eSrcUnit)
+ {
+ aGraphicSize = Size(
+ OutputDevice::LogicToLogic(aGraphicSize.Width(), eSrcUnit, eDestUnit),
+ OutputDevice::LogicToLogic(aGraphicSize.Height(), eSrcUnit, eDestUnit));
+ }
+
+ // Based on GraphicSize, set size of Page. Do not forget to adapt PageBorders,
+ // but interpret them relative to PageSize so that they do not 'explode/shrink'
+ // in comparison. Use a common scaling factor for hor/ver to not get
+ // asynchronous border distances, though. All in all this will adapt borders
+ // nicely and is based on office-defaults for standard-page-border-sizes.
+ const Size aPageSize(pTargetSdrPage->GetSize());
+ const double fBorderRelation((
+ static_cast< double >(pTargetSdrPage->GetLeftBorder()) / aPageSize.Width() +
+ static_cast< double >(pTargetSdrPage->GetRightBorder()) / aPageSize.Width() +
+ static_cast< double >(pTargetSdrPage->GetUpperBorder()) / aPageSize.Height() +
+ static_cast< double >(pTargetSdrPage->GetLowerBorder()) / aPageSize.Height()) / 4.0);
+ const tools::Long nAllBorder(basegfx::fround((aGraphicSize.Width() + aGraphicSize.Height()) * fBorderRelation * 0.5));
+
+ // Adapt PageSize and Border stuff. To get all MasterPages and PresObjs
+ // correctly adapted, do not just use
+ // pTargetSdrPage->SetBorder(...) and
+ // pTargetSdrPage->SetSize(...),
+ // but ::adaptSizeAndBorderForAllPages
+ // Do use original Size and borders to get as close to original
+ // as possible for better turn-arounds.
+ pTargetSdrPage->getSdrModelFromSdrPage().adaptSizeAndBorderForAllPages(
+ Size(
+ aGraphicSize.Width(),
+ aGraphicSize.Height()),
+ nAllBorder,
+ nAllBorder,
+ nAllBorder,
+ nAllBorder);
+
+ // tdf#118232 set pos/size at SdrGraphicObj - use zero position for
+ // better turn-around results
+ if(!bContainsNoGeometry)
+ {
+ aNewSdrGrafObj->SetSnapRect(
+ tools::Rectangle(
+ Point(0, 0),
+ aGraphicSize));
+
+ // insert to page (owner change of SdrGrafObj)
+ pTargetSdrPage->InsertObject(aNewSdrGrafObj.release());
+ }
+
+ // done - set positive result now
+ bRet = true;
+
+ // always leave helper endless loop
+ break;
+ }
+ }
+ else if( mxSrcDoc.is() )
+ {
+ // #i124608# detect selection
+ bool bSelectionOnly = false;
+ bool bGotSelection = false;
+
+ // when using LibreOfficeKit, default to exporting everything (-1)
+ bool bPageProvided = comphelper::LibreOfficeKit::isActive();
+ sal_Int32 nPageToExport = -1;
+
+ for( const PropertyValue& rProp : rDescriptor )
+ {
+ if (rProp.Name == "SelectionOnly")
+ {
+ // #i124608# extract single selection wanted from dialog return values
+ rProp.Value >>= bSelectionOnly;
+ bPageProvided = false;
+ }
+ else if (rProp.Name == "PagePos")
+ {
+ rProp.Value >>= nPageToExport;
+ bPageProvided = true;
+ }
+ }
+
+ uno::Reference<frame::XDesktop2> xDesktop(frame::Desktop::create(mxContext));
+ uno::Reference<frame::XController > xController;
+ if (xDesktop->getCurrentFrame().is() && !bPageProvided) // Manage headless case
+ {
+ uno::Reference<frame::XFrame> xFrame(xDesktop->getCurrentFrame(), uno::UNO_SET_THROW);
+ xController.set(xFrame->getController(), uno::UNO_SET_THROW);
+ uno::Reference<drawing::XDrawView> xDrawView(xController, uno::UNO_QUERY_THROW);
+ uno::Reference<drawing::framework::XControllerManager> xManager(xController, uno::UNO_QUERY_THROW);
+ uno::Reference<drawing::framework::XConfigurationController> xConfigController(xManager->getConfigurationController());
+
+ // which view configuration are we in?
+ //
+ // * traverse Impress resources to find slide preview pane, grab selection from there
+ // * otherwise, fallback to current slide
+ //
+ const uno::Sequence<uno::Reference<drawing::framework::XResourceId> > aResIds(
+ xConfigController->getCurrentConfiguration()->getResources(
+ uno::Reference<drawing::framework::XResourceId>(),
+ "",
+ drawing::framework::AnchorBindingMode_INDIRECT));
+
+ for( const uno::Reference<drawing::framework::XResourceId>& rResId : aResIds )
+ {
+ // can we somehow obtain the slidesorter from the Impress framework?
+ if( rResId->getResourceURL() == "private:resource/view/SlideSorter" )
+ {
+ // got it, grab current selection from there
+ uno::Reference<drawing::framework::XResource> xRes(
+ xConfigController->getResource(rResId));
+
+ uno::Reference< view::XSelectionSupplier > xSelectionSupplier(
+ xRes,
+ uno::UNO_QUERY );
+ if( xSelectionSupplier.is() )
+ {
+ uno::Any aSelection = xSelectionSupplier->getSelection();
+ if( aSelection.hasValue() )
+ {
+ Sequence< Reference< XInterface > > aSelectedPageSequence;
+ aSelection >>= aSelectedPageSequence;
+ mSelectedPages.resize( aSelectedPageSequence.getLength() );
+ for( size_t j=0; j<mSelectedPages.size(); ++j )
+ {
+ uno::Reference< drawing::XDrawPage > xDrawPage( aSelectedPageSequence[j],
+ uno::UNO_QUERY );
+ mSelectedPages[j] = xDrawPage;
+ }
+
+ // and stop looping. It is likely not getting better
+ break;
+ }
+ }
+ }
+ }
+
+ if( mSelectedPages.empty() )
+ {
+ // apparently failed to clean selection - fallback to current page
+ mSelectedPages.resize( 1 );
+ mSelectedPages[0] = xDrawView->getCurrentPage();
+ }
+ }
+
+ /*
+ * Export all slides, or requested "PagePos"
+ */
+ if( mSelectedPages.empty() )
+ {
+ uno::Reference< drawing::XMasterPagesSupplier > xMasterPagesSupplier( mxSrcDoc, uno::UNO_QUERY );
+ uno::Reference< drawing::XDrawPagesSupplier > xDrawPagesSupplier( mxSrcDoc, uno::UNO_QUERY );
+
+ if( xMasterPagesSupplier.is() && xDrawPagesSupplier.is() )
+ {
+ uno::Reference< drawing::XDrawPages > xMasterPages = xMasterPagesSupplier->getMasterPages();
+ uno::Reference< drawing::XDrawPages > xDrawPages = xDrawPagesSupplier->getDrawPages();
+ if( xMasterPages.is() && xMasterPages->getCount() &&
+ xDrawPages.is() && xDrawPages->getCount() )
+ {
+ sal_Int32 nDPCount = xDrawPages->getCount();
+
+ mSelectedPages.resize( nPageToExport != -1 ? 1 : nDPCount );
+ sal_Int32 i;
+ for( i = 0; i < nDPCount; ++i )
+ {
+ if( nPageToExport != -1 && nPageToExport == i )
+ {
+ uno::Reference< drawing::XDrawPage > xDrawPage( xDrawPages->getByIndex( i ), uno::UNO_QUERY );
+ mSelectedPages[0] = xDrawPage;
+ }
+ else
+ {
+ uno::Reference< drawing::XDrawPage > xDrawPage( xDrawPages->getByIndex( i ), uno::UNO_QUERY );
+ mSelectedPages[i] = xDrawPage;
+ }
+ }
+ }
+ }
+ }
+
+ if (bSelectionOnly)
+ {
+ // #i124608# when selection only is wanted, get the current object selection
+ // from the DrawView
+ Reference< view::XSelectionSupplier > xSelection (xController, UNO_QUERY);
+
+ if (xSelection.is())
+ {
+ bGotSelection
+ = ( xSelection->getSelection() >>= maShapeSelection );
+ }
+ }
+
+ if(bSelectionOnly && bGotSelection && 0 == maShapeSelection->getCount())
+ {
+ // #i124608# export selection, got maShapeSelection but no shape selected -> nothing
+ // to export, we are done (maybe return true, but a hint that nothing was done
+ // may be useful; it may have happened by error)
+ bRet = false;
+ }
+ else
+ {
+ /*
+ * We get all master page that are targeted by at least one draw page.
+ * The master page are put in an unordered set.
+ */
+ ObjectSet aMasterPageTargetSet;
+ for(const uno::Reference<drawing::XDrawPage> & mSelectedPage : mSelectedPages)
+ {
+ uno::Reference< drawing::XMasterPageTarget > xMasterPageTarget( mSelectedPage, uno::UNO_QUERY );
+ if( xMasterPageTarget.is() )
+ {
+ aMasterPageTargetSet.insert( xMasterPageTarget->getMasterPage() );
+ }
+ }
+ // Later we move them to an uno::Sequence so we can get them by index
+ mMasterPageTargets.resize( aMasterPageTargetSet.size() );
+ sal_Int32 i = 0;
+ for (auto const& masterPageTarget : aMasterPageTargetSet)
+ {
+ uno::Reference< drawing::XDrawPage > xMasterPage( masterPageTarget, uno::UNO_QUERY );
+ mMasterPageTargets[i++] = xMasterPage;
+ }
+
+ bRet = implExport( rDescriptor );
+ }
+ }
+ else
+ bRet = false;
+
+ if( pFocusWindow )
+ pFocusWindow->LeaveWait();
+
+ return bRet;
+}
+
+bool SVGFilter::filterWriterOrCalc( const Sequence< PropertyValue >& rDescriptor )
+{
+ bool bSelectionOnly = false;
+
+ for (const PropertyValue& rProp : rDescriptor)
+ {
+ if (rProp.Name == "SelectionOnly")
+ {
+ rProp.Value >>= bSelectionOnly;
+ break;
+ }
+ }
+
+ if(!bSelectionOnly) // For Writer only the selection-only mode is supported
+ return false;
+
+ uno::Reference<frame::XDesktop2> xDesktop(frame::Desktop::create(mxContext));
+ uno::Reference<frame::XController > xController;
+ if (xDesktop->getCurrentFrame().is())
+ {
+ uno::Reference<frame::XFrame> xFrame(xDesktop->getCurrentFrame(), uno::UNO_SET_THROW);
+ xController.set(xFrame->getController(), uno::UNO_SET_THROW);
+ }
+
+ Reference< view::XSelectionSupplier > xSelection (xController, UNO_QUERY);
+ if (!xSelection.is())
+ return false;
+
+ // Select only one draw page
+ uno::Reference< drawing::XDrawPagesSupplier > xDrawPagesSupplier( mxSrcDoc, uno::UNO_QUERY );
+ uno::Reference<drawing::XDrawPages> xDrawPages = xDrawPagesSupplier->getDrawPages();
+ uno::Reference< drawing::XDrawPage > xDrawPage( xDrawPages->getByIndex(0), uno::UNO_QUERY );
+ mSelectedPages.resize( 1 );
+ mSelectedPages[0] = xDrawPage;
+
+ bool bGotSelection = xSelection->getSelection() >>= maShapeSelection;
+
+ if (!bGotSelection)
+ {
+ if (mbWriterFilter)
+ {
+ // For Writer we might have a non-shape graphic
+ bGotSelection = implExportWriterTextGraphic(xSelection);
+ }
+ if (!bGotSelection)
+ return false;
+ }
+
+ return implExport( rDescriptor );
+}
+
+void SAL_CALL SVGFilter::cancel( )
+{
+}
+
+void SAL_CALL SVGFilter::setSourceDocument( const Reference< XComponent >& xDoc )
+{
+ mxSrcDoc = xDoc;
+}
+
+void SAL_CALL SVGFilter::setTargetDocument( const Reference< XComponent >& xDoc )
+{
+ mxDstDoc = xDoc;
+}
+
+namespace {
+
+// There is already another SVG-Type_Detector, see
+// vcl/source/filter/graphicfilter.cxx ("DOCTYPE svg"),
+// but since these start from different preconditions it is not
+// easy to unify these. For now, use this local helper.
+class SVGFileInfo
+{
+private:
+ const uno::Reference<io::XInputStream>& mxInput;
+ uno::Sequence< sal_Int8 > mnFirstBytes;
+ sal_Int32 mnFirstBytesSize;
+ sal_Int32 mnFirstRead;
+ bool mbProcessed;
+ bool mbIsSVG;
+
+ bool impCheckForMagic(
+ const sal_Int8* pMagic,
+ const sal_Int32 nMagicSize)
+ {
+ const sal_Int8* pBuffer(mnFirstBytes.getConstArray());
+ return std::search(
+ pBuffer,
+ pBuffer + mnFirstRead,
+ pMagic,
+ pMagic + nMagicSize) != pBuffer + mnFirstRead;
+ }
+
+ void impEnsureProcessed()
+ {
+ if(mbProcessed)
+ {
+ return;
+ }
+
+ mbProcessed = true;
+
+ if(!mxInput.is())
+ {
+ return;
+ }
+
+ if(0 == mnFirstBytesSize)
+ {
+ return;
+ }
+
+ mnFirstBytes.realloc(mnFirstBytesSize);
+
+ if(mnFirstBytesSize != mnFirstBytes.getLength())
+ {
+ return;
+ }
+
+ std::unique_ptr< SvStream > aStream(utl::UcbStreamHelper::CreateStream(mxInput, true));
+
+ if (!aStream)
+ {
+ return;
+ }
+
+ const sal_uInt64 nStreamLen(aStream->remainingSize());
+
+ if(aStream->GetError())
+ {
+ return;
+ }
+
+ mnFirstRead = aStream->ReadBytes(
+ &mnFirstBytes.getArray()[0],
+ std::min(nStreamLen, static_cast<sal_uInt64>(mnFirstBytesSize)));
+
+ if(aStream->GetError())
+ {
+ return;
+ }
+
+ // check if it is gzipped -> svgz
+ if (mnFirstBytes[0] == 0x1F && static_cast<sal_uInt8>(mnFirstBytes[1]) == 0x8B)
+ {
+ ZCodec aCodec;
+
+ aCodec.BeginCompression(ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true);
+ mnFirstRead = aCodec.Read(
+ *aStream,
+ reinterpret_cast< sal_uInt8* >(mnFirstBytes.getArray()),
+ mnFirstBytesSize);
+ aCodec.EndCompression();
+
+ if (mnFirstRead < 0)
+ return;
+ }
+
+ if(!mbIsSVG)
+ {
+ const sal_Int8 aMagic[] = {'<', 's', 'v', 'g'};
+ const sal_Int32 nMagicSize(SAL_N_ELEMENTS(aMagic));
+
+ mbIsSVG = impCheckForMagic(aMagic, nMagicSize);
+ }
+
+ if(!mbIsSVG)
+ {
+ const sal_Int8 aMagic[] = {'D', 'O', 'C', 'T', 'Y', 'P', 'E', ' ', 's', 'v', 'g'};
+ const sal_Int32 nMagicSize(SAL_N_ELEMENTS(aMagic));
+
+ mbIsSVG = impCheckForMagic(aMagic, nMagicSize);
+ }
+
+ return;
+ }
+
+public:
+ SVGFileInfo(
+ const uno::Reference<io::XInputStream>& xInput)
+ : mxInput(xInput),
+ mnFirstBytesSize(2048),
+ mnFirstRead(0),
+ mbProcessed(false),
+ mbIsSVG(false)
+ {
+ // For the default buffer size: Use not too big
+ // (not more than 16K), but also not too small
+ // (not less than 1/2K), see comments at
+ // ImpPeekGraphicFormat, SVG section.
+ // I remember these cases and it *can* happen
+ // that SVGs have quite massive comments in their
+ // headings (!)
+ // Limit to plausible sizes, also for security reasons
+ mnFirstBytesSize = std::min(sal_Int32(512), mnFirstBytesSize);
+ mnFirstBytesSize = std::max(sal_Int32(16384), mnFirstBytesSize);
+ }
+
+ bool isSVG()
+ {
+ impEnsureProcessed();
+
+ return mbIsSVG;
+ }
+
+ bool isOwnFormat()
+ {
+ impEnsureProcessed();
+
+ if(mbIsSVG)
+ {
+ // xmlns:ooo
+ const sal_Int8 aMagic[] = {'x', 'm', 'l', 'n', 's', ':', 'o', 'o', 'o'};
+ const sal_Int32 nMagicSize(SAL_N_ELEMENTS(aMagic));
+
+ return impCheckForMagic(aMagic, nMagicSize);
+ }
+
+ return false;
+ }
+
+ bool isImpress()
+ {
+ impEnsureProcessed();
+
+ if(mbIsSVG)
+ {
+ // ooo:meta_slides
+ const sal_Int8 aMagic[] = {'o', 'o', 'o', ':', 'm', 'e', 't', 'a', '_', 's', 'l', 'i', 'd', 'e', 's'};
+ const sal_Int32 nMagicSize(SAL_N_ELEMENTS(aMagic));
+
+ return impCheckForMagic(aMagic, nMagicSize);
+ }
+
+ return false;
+ }
+};
+
+}
+
+OUString SAL_CALL SVGFilter::detect(Sequence<PropertyValue>& rDescriptor)
+{
+ utl::MediaDescriptor aMediaDescriptor(rDescriptor);
+ uno::Reference<io::XInputStream> xInput(aMediaDescriptor[utl::MediaDescriptor::PROP_INPUTSTREAM], UNO_QUERY);
+ OUString aRetval;
+
+ if (!xInput.is())
+ {
+ return aRetval;
+ }
+
+ try
+ {
+ SVGFileInfo aSVGFileInfo(xInput);
+
+ if(aSVGFileInfo.isSVG())
+ {
+ // We have SVG - set default document format to Draw
+ aRetval = OUString(constFilterNameDraw);
+
+ if(aSVGFileInfo.isOwnFormat())
+ {
+ // it's a file that was written/exported by LO
+ if(aSVGFileInfo.isImpress())
+ {
+ // it was written by Impress export. Set document
+ // format for import to Impress
+ aRetval = OUString(constFilterName);
+ }
+ }
+ }
+ }
+ catch (css::io::IOException &)
+ {
+ TOOLS_WARN_EXCEPTION("filter.svg", "");
+ }
+
+ return aRetval;
+}
+
+// XServiceInfo
+sal_Bool SVGFilter::supportsService(const OUString& sServiceName)
+{
+ return cppu::supportsService(this, sServiceName);
+}
+OUString SVGFilter::getImplementationName()
+{
+ return "com.sun.star.comp.Draw.SVGFilter";
+}
+css::uno::Sequence< OUString > SVGFilter::getSupportedServiceNames()
+{
+ return { "com.sun.star.document.ImportFilter",
+ "com.sun.star.document.ExportFilter",
+ "com.sun.star.document.ExtendedTypeDetection" };
+}
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+filter_SVGFilter_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new SVGFilter(context));
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/filter/source/svg/svgfilter.hxx b/filter/source/svg/svgfilter.hxx
new file mode 100644
index 000000000..93e14ec41
--- /dev/null
+++ b/filter/source/svg/svgfilter.hxx
@@ -0,0 +1,321 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <com/sun/star/document/XFilter.hpp>
+#include <com/sun/star/document/XImporter.hpp>
+#include <com/sun/star/document/XExporter.hpp>
+#include <com/sun/star/document/XExtendedFilterDetection.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/xml/sax/XWriter.hpp>
+#include <com/sun/star/view/XSelectionSupplier.hpp>
+
+#include <editeng/flditem.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/svapp.hxx>
+#include <svx/unoapi.hxx>
+#include <xmloff/xmlexp.hxx>
+#include <cstdio>
+#include <string_view>
+#include <unordered_set>
+#include <unordered_map>
+
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::document;
+using namespace ::com::sun::star::io;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::xml::sax;
+
+#define SVG_EXPORT_ALLPAGES ((sal_Int32)-1)
+
+
+// Placeholder tag used into the ImplWriteActions method to filter text placeholder fields
+inline constexpr OUStringLiteral sPlaceholderTag = u"<[:isPlaceholder:]>";
+// This tag is used for exporting a slide background made of tiled bitmaps
+inline constexpr OStringLiteral sTiledBackgroundTag = "SLIDE_BACKGROUND";
+
+class SVGExport : public SvXMLExport
+{
+ bool mbIsUseTinyProfile;
+ bool mbIsUseDTDString;
+ bool mbIsEmbedFonts;
+ bool mbIsUseOpacity;
+ bool mbIsUseNativeTextDecoration;
+ bool mbIsUsePositionedCharacters;
+ std::set<sal_Unicode> maEmbeddedBulletGlyphs;
+
+public:
+
+ SVGExport( const css::uno::Reference< css::uno::XComponentContext >& rContext,
+ const Reference< XDocumentHandler >& rxHandler,
+ const Sequence< PropertyValue >& rFilterData );
+
+ virtual ~SVGExport() override;
+
+ bool IsUseTinyProfile() const { return mbIsUseTinyProfile; };
+ bool IsUseDTDString() const { return mbIsUseDTDString; };
+ bool IsEmbedFonts() const { return mbIsEmbedFonts; };
+ bool IsUseOpacity() const { return mbIsUseOpacity; };
+ bool IsUseNativeTextDecoration() const { return mbIsUseNativeTextDecoration; };
+ bool IsUsePositionedCharacters() const { return mbIsUsePositionedCharacters; };
+
+ void writeMtf( const GDIMetaFile& rMtf );
+
+ void SetEmbeddedBulletGlyph(sal_Unicode cBullet);
+ bool IsEmbeddedBulletGlyph(sal_Unicode cBullet) const;
+
+protected:
+
+ virtual void ExportStyles_( bool /* bUsed */ ) override {}
+ virtual void ExportAutoStyles_() override {}
+ virtual void ExportContent_() override {}
+ virtual void ExportMasterStyles_() override {}
+ virtual ErrCode exportDoc( enum ::xmloff::token::XMLTokenEnum /* eClass */ ) override { return ERRCODE_NONE; }
+};
+
+class ObjectRepresentation
+{
+private:
+
+ Reference< XInterface > mxObject;
+ std::unique_ptr<GDIMetaFile> mxMtf;
+
+public:
+ ObjectRepresentation();
+ ObjectRepresentation(const Reference< XInterface >& rxIf,
+ const GDIMetaFile& rMtf);
+ ObjectRepresentation(const ObjectRepresentation& rPresentation);
+
+ ObjectRepresentation& operator=(const ObjectRepresentation& rPresentation);
+
+ const Reference< XInterface >& GetObject() const { return mxObject; }
+ bool HasRepresentation() const { return static_cast<bool>(mxMtf); }
+ const GDIMetaFile& GetRepresentation() const { return *mxMtf; }
+};
+
+struct PagePropertySet
+{
+ bool bIsBackgroundVisible;
+ bool bAreBackgroundObjectsVisible;
+ bool bIsPageNumberFieldVisible;
+ bool bIsDateTimeFieldVisible;
+ bool bIsFooterFieldVisible;
+ bool bIsHeaderFieldVisible;
+ sal_Int32 nPageNumberingType;
+ bool bIsDateTimeFieldFixed;
+ SvxDateFormat nDateTimeFormat;
+ PagePropertySet()
+ : bIsBackgroundVisible(false)
+ , bAreBackgroundObjectsVisible(false)
+ , bIsPageNumberFieldVisible(false)
+ , bIsDateTimeFieldVisible(false)
+ , bIsFooterFieldVisible(false)
+ , bIsHeaderFieldVisible(false)
+ , nPageNumberingType(0)
+ , bIsDateTimeFieldFixed(false)
+ , nDateTimeFormat(SvxDateFormat::AppDefault)
+ {
+ }
+};
+
+struct HashUChar
+{
+ size_t operator()( const sal_Unicode uchar ) const { return static_cast< size_t >( uchar ); }
+};
+
+struct HashBitmap
+{
+ size_t operator()( const ObjectRepresentation& rObjRep ) const;
+};
+
+struct EqualityBitmap
+{
+ bool operator()( const ObjectRepresentation& rObjRep1,
+ const ObjectRepresentation& rObjRep2 ) const;
+};
+
+// This must match the same type definition in svgwriter.hxx
+typedef std::unordered_map< BitmapChecksum, std::unique_ptr< GDIMetaFile > > MetaBitmapActionMap;
+
+struct PatternData
+{
+ BitmapChecksum aBitmapChecksum;
+ Point aPos;
+ Size aSize;
+ Size aSlideSize;
+};
+typedef std::map<OUString, PatternData> PatternPropertySet;
+
+class SVGFontExport;
+class SVGActionWriter;
+class EditFieldInfo;
+
+class SVGFilter : public cppu::WeakImplHelper < XFilter,
+ XImporter,
+ XExporter,
+ XExtendedFilterDetection, XServiceInfo >
+{
+public:
+ typedef std::unordered_map< Reference< XInterface >, ObjectRepresentation > ObjectMap;
+ typedef std::unordered_set< Reference< XInterface > > ObjectSet;
+
+ typedef std::unordered_set< sal_Unicode, HashUChar > UCharSet;
+ typedef std::unordered_map< OUString, UCharSet > UCharSetMap;
+ typedef std::unordered_map< Reference< XInterface >, UCharSetMap > UCharSetMapMap;
+
+ typedef std::unordered_set< ObjectRepresentation, HashBitmap, EqualityBitmap > MetaBitmapActionSet;
+
+private:
+
+ /// Generally use members
+
+ Reference< XComponentContext > mxContext;
+ SvXMLElementExport* mpSVGDoc;
+ rtl::Reference<SVGExport> mpSVGExport;
+ SVGFontExport* mpSVGFontExport;
+ SVGActionWriter* mpSVGWriter;
+ bool mbSinglePage;
+ sal_Int32 mnVisiblePage;
+ ObjectMap* mpObjects;
+ Reference< XComponent > mxSrcDoc;
+ Reference< XComponent > mxDstDoc;
+ // #i124608# explicit ShapeSelection for export when export of the selection is wanted
+ Reference< css::drawing::XShapes > maShapeSelection;
+ bool mbExportShapeSelection;
+ Sequence< PropertyValue > maFilterData;
+ Reference< css::drawing::XDrawPage > mxDefaultPage;
+ std::vector< Reference< css::drawing::XDrawPage > > mSelectedPages;
+ bool mbIsPreview;
+
+ bool mbWriterFilter;
+ bool mbCalcFilter;
+ bool mbImpressFilter;
+
+
+ /// Impress / draw only members
+
+ SdrPage* mpDefaultSdrPage;
+ bool mbPresentation;
+ PagePropertySet mVisiblePagePropSet;
+ OUString msClipPathId;
+ UCharSetMapMap mTextFieldCharSets;
+ Reference< XInterface > mCreateOjectsCurrentMasterPage;
+ std::unordered_map< Reference< XInterface >, OUString >
+ mTextShapeIdListMap;
+ MetaBitmapActionSet mEmbeddedBitmapActionSet;
+ ObjectMap mEmbeddedBitmapActionMap;
+ MetaBitmapActionMap maBitmapActionMap;
+ PatternPropertySet maPatterProps;
+ std::vector< Reference< css::drawing::XDrawPage > > mMasterPageTargets;
+
+ Link<EditFieldInfo*,void> maOldFieldHdl;
+ Link<EditFieldInfo*,void> maNewFieldHdl;
+
+ /// @throws css::uno::RuntimeException
+ bool implExport( const Sequence< PropertyValue >& rDescriptor );
+ bool implExportImpressOrDraw( const Reference< XOutputStream >& rxOStm );
+ bool implExportWriterOrCalc( const Reference< XOutputStream >& rxOStm );
+ bool implExportWriterTextGraphic( const Reference< view::XSelectionSupplier >& xSelectionSupplier );
+
+ static Reference< XWriter > implCreateExportDocumentHandler( const Reference< XOutputStream >& rxOStm );
+
+ void implGetPagePropSet( const Reference< css::drawing::XDrawPage > & rxPage );
+ void implGenerateMetaData();
+ void implExportTextShapeIndex();
+ void implEmbedBulletGlyphs();
+ void implEmbedBulletGlyph( sal_Unicode cBullet, const OUString & sPathData );
+ void implExportTextEmbeddedBitmaps();
+ void implExportBackgroundBitmaps();
+ void implExportTiledBackground();
+ void implGenerateScript();
+
+ bool implExportDocument();
+ void implExportDocumentHeaderImpressOrDraw(sal_Int32 nDocX, sal_Int32 nDocY,
+ sal_Int32 nDocWidth, sal_Int32 nDocHeight);
+ void implExportDocumentHeaderWriterOrCalc(sal_Int32 nDocX, sal_Int32 nDocY,
+ sal_Int32 nDocWidth, sal_Int32 nDocHeight);
+ void implExportAnimations();
+
+ bool implExportMasterPages( const std::vector< Reference< css::drawing::XDrawPage > >& rxPages,
+ sal_Int32 nFirstPage, sal_Int32 nLastPage );
+ void implExportDrawPages( const std::vector< Reference< css::drawing::XDrawPage > >& rxPages,
+ sal_Int32 nFirstPage, sal_Int32 nLastPage );
+ bool implExportPage( std::u16string_view sPageId,
+ const Reference< css::drawing::XDrawPage > & rxPage,
+ const Reference< css::drawing::XShapes > & xShapes,
+ bool bMaster );
+
+ bool implExportShapes( const Reference< css::drawing::XShapes >& rxShapes,
+ bool bMaster );
+ bool implExportShape( const Reference< css::drawing::XShape >& rxShape,
+ bool bMaster );
+
+ bool implCreateObjects();
+ bool implCreateObjectsFromShapes( const Reference< css::drawing::XDrawPage > & rxPage, const Reference< css::drawing::XShapes >& rxShapes );
+ bool implCreateObjectsFromShape( const Reference< css::drawing::XDrawPage > & rxPage, const Reference< css::drawing::XShape >& rxShape );
+ void implCreateObjectsFromBackground( const Reference< css::drawing::XDrawPage >& rxMasterPage );
+
+ static OUString implGetClassFromShape( const Reference< css::drawing::XShape >& rxShape );
+ void implRegisterInterface( const Reference< XInterface >& rxIf );
+ const OUString & implGetValidIDFromInterface( const Reference< XInterface >& rxIf );
+ static OUString implGetInterfaceName( const Reference< XInterface >& rxIf );
+ bool implLookForFirstVisiblePage();
+ static Any implSafeGetPagePropSet( const OUString & sPropertyName,
+ const Reference< XPropertySet > & rxPropSet,
+ const Reference< XPropertySetInfo > & rxPropSetInfo );
+ DECL_LINK( CalcFieldHdl, EditFieldInfo*, void );
+
+ bool filterImpressOrDraw( const Sequence< PropertyValue >& rDescriptor );
+ bool filterWriterOrCalc( const Sequence< PropertyValue >& rDescriptor );
+
+protected:
+
+ // XFilter
+ virtual sal_Bool SAL_CALL filter( const Sequence< PropertyValue >& rDescriptor ) override;
+ virtual void SAL_CALL cancel( ) override;
+
+ // XImporter
+ virtual void SAL_CALL setTargetDocument( const Reference< XComponent >& xDoc ) override;
+
+ // XExporter
+ virtual void SAL_CALL setSourceDocument( const Reference< XComponent >& xDoc ) override;
+
+ // XExtendedFilterDetection
+ virtual OUString SAL_CALL detect( Sequence< PropertyValue >& io_rDescriptor ) override;
+
+public:
+
+ explicit SVGFilter( const Reference< XComponentContext >& rxCtx );
+ virtual ~SVGFilter() override;
+
+ // XServiceInfo
+ virtual sal_Bool SAL_CALL supportsService(const OUString& sServiceName) override;
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/filter/source/svg/svgfontexport.cxx b/filter/source/svg/svgfontexport.cxx
new file mode 100644
index 000000000..7e75d65e6
--- /dev/null
+++ b/filter/source/svg/svgfontexport.cxx
@@ -0,0 +1,321 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include "svgfontexport.hxx"
+#include "svgfilter.hxx"
+#include "svgwriter.hxx"
+
+#include <vcl/unohelp.hxx>
+#include <vcl/font.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/settings.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <xmloff/namespacemap.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+
+const sal_Int32 nFontEM = 2048;
+
+
+SVGFontExport::SVGFontExport( SVGExport& rExport, ::std::vector< ObjectRepresentation >&& rObjects ) :
+ mrExport( rExport ),
+ maObjects( std::move(rObjects) ),
+ mnCurFontId( 0 )
+{
+}
+
+
+SVGFontExport::~SVGFontExport()
+{
+}
+
+
+SVGFontExport::GlyphSet& SVGFontExport::implGetGlyphSet( const vcl::Font& rFont )
+{
+ FontWeight eWeight( WEIGHT_NORMAL );
+ FontItalic eItalic( ITALIC_NONE );
+ const OUString& aFontName( rFont.GetFamilyName() );
+
+ switch( rFont.GetWeight() )
+ {
+ case WEIGHT_BOLD:
+ case WEIGHT_ULTRABOLD:
+ case WEIGHT_BLACK:
+ eWeight = WEIGHT_BOLD;
+ break;
+
+ default:
+ break;
+ }
+
+ if( rFont.GetItalic() != ITALIC_NONE )
+ eItalic = ITALIC_NORMAL;
+
+ return( maGlyphTree[ aFontName.getToken( 0, ';' ) ][ eWeight ][ eItalic ] );
+}
+
+
+void SVGFontExport::implCollectGlyphs()
+{
+ ScopedVclPtrInstance< VirtualDevice > pVDev;
+
+ pVDev->EnableOutput( false );
+
+ for (auto const& elem : maObjects)
+ {
+ if( elem.HasRepresentation() )
+ {
+ const GDIMetaFile& rMtf = elem.GetRepresentation();
+
+ pVDev->Push();
+
+ for( size_t i = 0, nCount = rMtf.GetActionSize(); i < nCount; ++i )
+ {
+ OUString aText;
+ MetaAction* pAction = rMtf.GetAction( i );
+ const MetaActionType nType = pAction->GetType();
+
+ switch( nType )
+ {
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction);
+ sal_Int32 aLength=std::min( pA->GetText().getLength(), pA->GetLen() );
+ aText = pA->GetText().copy( pA->GetIndex(), aLength );
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
+ aText = pA->GetText();
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction);
+ sal_Int32 aLength=std::min( pA->GetText().getLength(), pA->GetLen() );
+ aText = pA->GetText().copy( pA->GetIndex(), aLength );
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction);
+ sal_Int32 aLength=std::min( pA->GetText().getLength(), pA->GetLen() );
+ aText = pA->GetText().copy( pA->GetIndex(), aLength );
+ }
+ break;
+
+ default:
+ pAction->Execute( pVDev );
+ break;
+ }
+
+ if( !aText.isEmpty() )
+ {
+ GlyphSet& rGlyphSet = implGetGlyphSet( pVDev->GetFont() );
+ css::uno::Reference< css::i18n::XBreakIterator > xBI(
+ vcl::unohelper::CreateBreakIterator() );
+
+ if( xBI.is() )
+ {
+ const css::lang::Locale& rLocale = Application::GetSettings().GetLanguageTag().getLocale();
+ sal_Int32 nCurPos = 0, nLastPos = -1;
+
+ while( ( nCurPos < aText.getLength() ) && ( nCurPos > nLastPos ) )
+ {
+ sal_Int32 nCount2 = 1;
+
+ nLastPos = nCurPos;
+ nCurPos = xBI->nextCharacters( aText, nCurPos, rLocale,
+ css::i18n::CharacterIteratorMode::SKIPCELL,
+ nCount2, nCount2 );
+
+ rGlyphSet.insert( aText.copy( nLastPos, nCurPos - nLastPos ) );
+ }
+ }
+ else
+ {
+ const sal_Unicode* pStr = aText.getStr();
+
+ for( sal_uInt32 k = 0, nLen = aText.getLength(); k < nLen; ++k )
+ rGlyphSet.insert( OUString( pStr[ k ] ) );
+ }
+ }
+ }
+
+ pVDev->Pop();
+ }
+ }
+}
+
+
+void SVGFontExport::implEmbedFont( const vcl::Font& rFont )
+{
+ if( !mrExport.IsEmbedFonts() )
+ return;
+
+ GlyphSet& rGlyphSet = implGetGlyphSet( rFont );
+
+ if( rGlyphSet.empty() )
+ return;
+
+ {
+ SvXMLElementExport aExp( mrExport, XML_NAMESPACE_NONE, "defs", true, true );
+ OUString aCurIdStr( "EmbeddedFont_" );
+ OUString aUnitsPerEM( OUString::number( nFontEM ) );
+ ScopedVclPtrInstance< VirtualDevice > pVDev;
+ vcl::Font aFont( rFont );
+
+ aFont.SetFontSize( Size( 0, nFontEM ) );
+ aFont.SetAlignment( ALIGN_BASELINE );
+
+ pVDev->SetMapMode(MapMode(MapUnit::Map100thMM));
+ pVDev->SetFont( aFont );
+
+ aCurIdStr += OUString::number( ++mnCurFontId );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", aCurIdStr );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "horiz-adv-x", aUnitsPerEM );
+
+ {
+ SvXMLElementExport aExp2( mrExport, XML_NAMESPACE_NONE, "font", true, true );
+ OUString aFontWeight;
+ OUString aFontStyle;
+ const Size aSize( nFontEM, nFontEM );
+
+ // Font Weight
+ if( aFont.GetWeight() != WEIGHT_NORMAL )
+ aFontWeight = "bold";
+ else
+ aFontWeight = "normal";
+
+ // Font Italic
+ if( aFont.GetItalic() != ITALIC_NONE )
+ aFontStyle = "italic";
+ else
+ aFontStyle = "normal";
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "font-family", GetMappedFontName( rFont.GetFamilyName() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "units-per-em", aUnitsPerEM );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "font-weight", aFontWeight );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "font-style", aFontStyle );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "ascent", OUString::number( pVDev->GetFontMetric().GetAscent() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "descent", OUString::number( pVDev->GetFontMetric().GetDescent() ) );
+
+ {
+ SvXMLElementExport aExp3( mrExport, XML_NAMESPACE_NONE, "font-face", true, true );
+ }
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "horiz-adv-x", OUString::number( aSize.Width() ) );
+
+ {
+ const Point aPos;
+ const tools::PolyPolygon aMissingGlyphPolyPoly( tools::Rectangle( aPos, aSize ) );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "d", SVGActionWriter::GetPathString( aMissingGlyphPolyPoly, false ) );
+
+ {
+ SvXMLElementExport aExp4( mrExport, XML_NAMESPACE_NONE, "missing-glyph", true, true );
+ }
+ }
+ for (auto const& glyph : rGlyphSet)
+ {
+ implEmbedGlyph( *pVDev, glyph);
+ }
+ }
+ }
+}
+
+
+void SVGFontExport::implEmbedGlyph( OutputDevice const & rOut, const OUString& rCellStr )
+{
+ tools::PolyPolygon aPolyPoly;
+ const sal_Unicode nSpace = ' ';
+
+ if( !rOut.GetTextOutline( aPolyPoly, rCellStr ) )
+ return;
+
+ tools::Rectangle aBoundRect;
+
+ aPolyPoly.Scale( 1.0, -1.0 );
+
+ if( !rOut.GetTextBoundRect( aBoundRect, rCellStr ) )
+ aBoundRect = tools::Rectangle( Point( 0, 0 ), Size( rOut.GetTextWidth( rCellStr ), 0 ) );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "unicode", rCellStr );
+
+ if( rCellStr[ 0 ] == nSpace && rCellStr.getLength() == 1 )
+ aBoundRect = tools::Rectangle( Point( 0, 0 ), Size( rOut.GetTextWidth( OUString(' ') ), 0 ) );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "horiz-adv-x", OUString::number( aBoundRect.GetWidth() ) );
+
+ const OUString aPathString( SVGActionWriter::GetPathString( aPolyPoly, false ) );
+ if( !aPathString.isEmpty() )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "d", aPathString );
+ }
+
+ {
+ SvXMLElementExport aExp( mrExport, XML_NAMESPACE_NONE, "glyph", true, true );
+ }
+}
+
+
+void SVGFontExport::EmbedFonts()
+{
+ implCollectGlyphs();
+
+ for (auto const& glyph : maGlyphTree)
+ {
+ const FontWeightMap& rFontWeightMap = glyph.second;
+ for (auto const& fontWeight : rFontWeightMap)
+ {
+ const FontItalicMap& rFontItalicMap = fontWeight.second;
+ for (auto const& fontItalic : rFontItalicMap)
+ {
+ vcl::Font aFont;
+
+ aFont.SetFamilyName( glyph.first );
+ aFont.SetWeight( fontWeight.first );
+ aFont.SetItalic( fontItalic.first );
+
+ implEmbedFont( aFont );
+ }
+ }
+ }
+}
+
+
+OUString SVGFontExport::GetMappedFontName( std::u16string_view rFontName ) const
+{
+ OUString aRet( o3tl::getToken(rFontName, 0, ';' ) );
+
+ if( mnCurFontId )
+ aRet += " embedded";
+
+ return aRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/filter/source/svg/svgfontexport.hxx b/filter/source/svg/svgfontexport.hxx
new file mode 100644
index 000000000..259196f9e
--- /dev/null
+++ b/filter/source/svg/svgfontexport.hxx
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <functional>
+#include <map>
+#include <set>
+#include <vector>
+
+#include <rtl/ustring.hxx>
+#include <tools/fontenum.hxx>
+
+
+class ObjectRepresentation;
+class SVGExport;
+namespace vcl { class Font; }
+class OutputDevice;
+
+#ifdef _MSC_VER
+#pragma warning (disable : 4503) // FontNameMap gives decorated name length exceeded
+#endif
+
+class SVGFontExport
+{
+ typedef ::std::vector< ObjectRepresentation > ObjectVector;
+ typedef ::std::set< OUString, ::std::greater< OUString > > GlyphSet;
+ typedef ::std::map< FontItalic, GlyphSet > FontItalicMap;
+ typedef ::std::map< FontWeight, FontItalicMap > FontWeightMap;
+ typedef ::std::map< OUString, FontWeightMap > FontNameMap;
+ typedef FontNameMap GlyphTree;
+
+private:
+
+ SVGExport& mrExport;
+ GlyphTree maGlyphTree;
+ ObjectVector maObjects;
+ sal_Int32 mnCurFontId;
+
+ GlyphSet& implGetGlyphSet( const vcl::Font& rFont );
+ void implCollectGlyphs();
+ void implEmbedFont( const vcl::Font& rFont );
+ void implEmbedGlyph( OutputDevice const & rOut, const OUString& rCellStr );
+
+public:
+
+ SVGFontExport( SVGExport& rExport, ::std::vector< ObjectRepresentation >&& rObjects );
+ ~SVGFontExport();
+
+ void EmbedFonts();
+ OUString GetMappedFontName( std::u16string_view rFontName ) const;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx
new file mode 100644
index 000000000..3a3fc8a5c
--- /dev/null
+++ b/filter/source/svg/svgwriter.cxx
@@ -0,0 +1,4096 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "svgfilter.hxx"
+#include "svgfontexport.hxx"
+#include "svgwriter.hxx"
+
+#include <comphelper/base64.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <sal/log.hxx>
+#include <vcl/unohelp.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/filter/SvmWriter.hxx>
+#include <tools/fract.hxx>
+#include <tools/helpers.hxx>
+#include <tools/stream.hxx>
+#include <xmloff/namespacemap.hxx>
+#include <xmloff/unointerfacetouniqueidentifiermapper.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <com/sun/star/container/XEnumerationAccess.hpp>
+#include <com/sun/star/container/XIndexReplace.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <com/sun/star/style/NumberingType.hpp>
+#include <com/sun/star/text/XTextField.hpp>
+
+#include <memory>
+
+
+constexpr OUStringLiteral aPrefixClipPathId = u"clip_path_";
+
+constexpr OUStringLiteral aXMLElemG = u"g";
+constexpr OUStringLiteral aXMLElemDefs = u"defs";
+constexpr OUStringLiteral aXMLElemText = u"text";
+constexpr OUStringLiteral aXMLElemTspan = u"tspan";
+constexpr OUStringLiteral aXMLElemLinearGradient = u"linearGradient";
+constexpr OUStringLiteral aXMLElemStop = u"stop";
+
+constexpr OUStringLiteral aXMLAttrTransform = u"transform";
+constexpr OUStringLiteral aXMLAttrStyle = u"style";
+constexpr OUStringLiteral aXMLAttrId = u"id";
+constexpr OUStringLiteral aXMLAttrX = u"x";
+constexpr OUStringLiteral aXMLAttrY = u"y";
+constexpr OUStringLiteral aXMLAttrX1 = u"x1";
+constexpr OUStringLiteral aXMLAttrY1 = u"y1";
+constexpr OUStringLiteral aXMLAttrX2 = u"x2";
+constexpr OUStringLiteral aXMLAttrY2 = u"y2";
+constexpr OUStringLiteral aXMLAttrCX = u"cx";
+constexpr OUStringLiteral aXMLAttrCY = u"cy";
+constexpr OUStringLiteral aXMLAttrRX = u"rx";
+constexpr OUStringLiteral aXMLAttrRY = u"ry";
+constexpr OUStringLiteral aXMLAttrWidth = u"width";
+constexpr OUStringLiteral aXMLAttrHeight = u"height";
+constexpr OUStringLiteral aXMLAttrStrokeWidth = u"stroke-width";
+constexpr OUStringLiteral aXMLAttrFill = u"fill";
+constexpr OUStringLiteral aXMLAttrFontFamily = u"font-family";
+constexpr OUStringLiteral aXMLAttrFontSize = u"font-size";
+constexpr OUStringLiteral aXMLAttrFontStyle = u"font-style";
+constexpr OUStringLiteral aXMLAttrFontWeight = u"font-weight";
+constexpr OUStringLiteral aXMLAttrTextDecoration = u"text-decoration";
+constexpr OUStringLiteral aXMLAttrXLinkHRef = u"xlink:href";
+constexpr OUStringLiteral aXMLAttrGradientUnits = u"gradientUnits";
+constexpr OUStringLiteral aXMLAttrOffset = u"offset";
+constexpr OUStringLiteral aXMLAttrStopColor = u"stop-color";
+constexpr OUStringLiteral aXMLAttrStrokeLinejoin = u"stroke-linejoin";
+constexpr OUStringLiteral aXMLAttrStrokeLinecap = u"stroke-linecap";
+
+
+vcl::PushFlags SVGContextHandler::getPushFlags() const
+{
+ if (maStateStack.empty())
+ return vcl::PushFlags::NONE;
+
+ const PartialState& rPartialState = maStateStack.top();
+ return rPartialState.meFlags;
+}
+
+SVGState& SVGContextHandler::getCurrentState()
+{
+ return maCurrentState;
+}
+
+void SVGContextHandler::pushState( vcl::PushFlags eFlags )
+{
+ PartialState aPartialState;
+ aPartialState.meFlags = eFlags;
+
+ if (eFlags & vcl::PushFlags::FONT)
+ {
+ aPartialState.setFont( maCurrentState.aFont );
+ }
+
+ if (eFlags & vcl::PushFlags::CLIPREGION)
+ {
+ aPartialState.mnRegionClipPathId = maCurrentState.nRegionClipPathId;
+ }
+
+ maStateStack.push( std::move(aPartialState) );
+}
+
+void SVGContextHandler::popState()
+{
+ if (maStateStack.empty())
+ return;
+
+ const PartialState& rPartialState = maStateStack.top();
+ vcl::PushFlags eFlags = rPartialState.meFlags;
+
+ if (eFlags & vcl::PushFlags::FONT)
+ {
+ maCurrentState.aFont = rPartialState.getFont( vcl::Font() );
+ }
+
+ if (eFlags & vcl::PushFlags::CLIPREGION)
+ {
+ maCurrentState.nRegionClipPathId = rPartialState.mnRegionClipPathId;
+ }
+
+ maStateStack.pop();
+}
+
+SVGAttributeWriter::SVGAttributeWriter( SVGExport& rExport, SVGFontExport& rFontExport, SVGState& rCurState )
+ : mrExport( rExport )
+ , mrFontExport( rFontExport )
+ , mrCurrentState( rCurState )
+{
+}
+
+
+SVGAttributeWriter::~SVGAttributeWriter()
+{
+}
+
+
+double SVGAttributeWriter::ImplRound( double fValue )
+{
+ return floor( fValue * pow( 10.0, 3 ) + 0.5 ) / pow( 10.0, 3 );
+}
+
+
+void SVGAttributeWriter::ImplGetColorStr( const Color& rColor, OUString& rColorStr )
+{
+ if( rColor.GetAlpha() == 0 )
+ rColorStr = "none";
+ else
+ {
+ rColorStr = "rgb(" + OUString::number(rColor.GetRed()) + "," + OUString::number(rColor.GetGreen()) +
+ "," + OUString::number(rColor.GetBlue()) + ")";
+ }
+}
+
+
+void SVGAttributeWriter::AddColorAttr( const OUString& pColorAttrName,
+ const OUString& pColorOpacityAttrName,
+ const Color& rColor )
+{
+ OUString aColor, aColorOpacity;
+
+ ImplGetColorStr( rColor, aColor );
+
+ if( rColor.GetAlpha() < 255 && rColor.GetAlpha() > 0 )
+ aColorOpacity = OUString::number( ImplRound( rColor.GetAlpha() / 255.0 ) );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, pColorAttrName, aColor );
+
+ if( !aColorOpacity.isEmpty() && mrExport.IsUseOpacity() )
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, pColorOpacityAttrName, aColorOpacity );
+}
+
+
+void SVGAttributeWriter::AddPaintAttr( const Color& rLineColor, const Color& rFillColor,
+ const tools::Rectangle* pObjBoundRect, const Gradient* pFillGradient )
+{
+ // Fill
+ if( pObjBoundRect && pFillGradient )
+ {
+ OUString aGradientId;
+
+ AddGradientDef( *pObjBoundRect, *pFillGradient, aGradientId );
+
+ if( !aGradientId.isEmpty() )
+ {
+ OUString aGradientURL = "url(#" + aGradientId + ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFill, aGradientURL );
+ }
+ }
+ else
+ AddColorAttr( aXMLAttrFill, "fill-opacity", rFillColor );
+
+ // Stroke
+ AddColorAttr( "stroke", "stroke-opacity", rLineColor );
+}
+
+
+void SVGAttributeWriter::AddGradientDef( const tools::Rectangle& rObjRect, const Gradient& rGradient, OUString& rGradientId )
+{
+ if( rObjRect.GetWidth() && rObjRect.GetHeight() &&
+ ( rGradient.GetStyle() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial ||
+ rGradient.GetStyle() == GradientStyle::Radial || rGradient.GetStyle() == GradientStyle::Elliptical ) )
+ {
+ SvXMLElementExport aDesc( mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true );
+ Color aStartColor( rGradient.GetStartColor() ), aEndColor( rGradient.GetEndColor() );
+ Degree10 nAngle = rGradient.GetAngle() % 3600_deg10;
+ Point aObjRectCenter( rObjRect.Center() );
+ tools::Polygon aPoly( rObjRect );
+ static sal_Int32 nCurGradientId = 1;
+
+ aPoly.Rotate( aObjRectCenter, nAngle );
+ tools::Rectangle aRect( aPoly.GetBoundRect() );
+
+ // adjust start/end colors with intensities
+ aStartColor.SetRed( static_cast<sal_uInt8>( ( aStartColor.GetRed() * rGradient.GetStartIntensity() ) / 100 ) );
+ aStartColor.SetGreen( static_cast<sal_uInt8>( ( aStartColor.GetGreen() * rGradient.GetStartIntensity() ) / 100 ) );
+ aStartColor.SetBlue( static_cast<sal_uInt8>( ( aStartColor.GetBlue() * rGradient.GetStartIntensity() ) / 100 ) );
+
+ aEndColor.SetRed( static_cast<sal_uInt8>( ( aEndColor.GetRed() * rGradient.GetEndIntensity() ) / 100 ) );
+ aEndColor.SetGreen( static_cast<sal_uInt8>( ( aEndColor.GetGreen() * rGradient.GetEndIntensity() ) / 100 ) );
+ aEndColor.SetBlue( static_cast<sal_uInt8>( ( aEndColor.GetBlue() * rGradient.GetEndIntensity() ) / 100 ) );
+
+ rGradientId = "Gradient_" + OUString::number( nCurGradientId++ );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, rGradientId );
+
+ {
+ std::unique_ptr< SvXMLElementExport > apGradient;
+ OUString aColorStr;
+
+ if( rGradient.GetStyle() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial )
+ {
+ tools::Polygon aLinePoly( 2 );
+
+ aLinePoly[ 0 ] = Point( aObjRectCenter.X(), aRect.Top() );
+ aLinePoly[ 1 ] = Point( aObjRectCenter.X(), aRect.Bottom() );
+
+ aLinePoly.Rotate( aObjRectCenter, nAngle );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrGradientUnits, "userSpaceOnUse" );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX1, OUString::number( aLinePoly[ 0 ].X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY1, OUString::number( aLinePoly[ 0 ].Y() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX2, OUString::number( aLinePoly[ 1 ].X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY2, OUString::number( aLinePoly[ 1 ].Y() ) );
+
+ apGradient.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemLinearGradient, true, true ) );
+
+ // write stop values
+ double fBorder = static_cast< double >( rGradient.GetBorder() ) *
+ ( ( rGradient.GetStyle() == GradientStyle::Axial ) ? 0.005 : 0.01 );
+
+ ImplGetColorStr( ( rGradient.GetStyle() == GradientStyle::Axial ) ? aEndColor : aStartColor, aColorStr );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( fBorder ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr );
+
+ {
+ SvXMLElementExport aDesc2( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true );
+ }
+
+ if( rGradient.GetStyle() == GradientStyle::Axial )
+ {
+ ImplGetColorStr( aStartColor, aColorStr );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( 0.5 ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr );
+
+ {
+ SvXMLElementExport aDesc3( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true );
+ }
+ }
+
+ if( rGradient.GetStyle() != GradientStyle::Axial )
+ fBorder = 0.0;
+
+ ImplGetColorStr( aEndColor, aColorStr );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( ImplRound( 1.0 - fBorder ) ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr );
+
+ {
+ SvXMLElementExport aDesc4( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true );
+ }
+ }
+ else
+ {
+ const double fCenterX = rObjRect.Left() + rObjRect.GetWidth() * rGradient.GetOfsX() * 0.01;
+ const double fCenterY = rObjRect.Top() + rObjRect.GetHeight() * rGradient.GetOfsY() * 0.01;
+ const double fRadius = std::hypot(rObjRect.GetWidth(), rObjRect.GetHeight()) * 0.5;
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrGradientUnits, "userSpaceOnUse" );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrCX, OUString::number( ImplRound( fCenterX ) ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrCY, OUString::number( ImplRound( fCenterY ) ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "r", OUString::number( ImplRound( fRadius ) ) );
+
+ apGradient.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, "radialGradient", true, true ) );
+
+ // write stop values
+ ImplGetColorStr( aEndColor, aColorStr );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( 0.0 ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr );
+
+ {
+ SvXMLElementExport aDesc5( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true );
+ }
+
+ ImplGetColorStr( aStartColor, aColorStr );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset,
+ OUString::number( ImplRound( 1.0 - rGradient.GetBorder() * 0.01 ) ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr );
+
+ {
+ SvXMLElementExport aDesc6( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true );
+ }
+ }
+ }
+ }
+ else
+ rGradientId.clear();
+}
+
+
+void SVGAttributeWriter::SetFontAttr( const vcl::Font& rFont )
+{
+ vcl::Font& rCurFont = mrCurrentState.aFont;
+
+ if( rFont == rCurFont )
+ return;
+
+ OUString aFontStyle;
+ sal_Int32 nFontWeight;
+
+ rCurFont = rFont;
+
+ // Font Family
+ setFontFamily();
+
+ // Font Size
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontSize,
+ OUString::number( rFont.GetFontHeight() ) + "px" );
+
+ // Font Style
+ if( rFont.GetItalic() != ITALIC_NONE )
+ {
+ if( rFont.GetItalic() == ITALIC_OBLIQUE )
+ aFontStyle = "oblique";
+ else
+ aFontStyle = "italic";
+ }
+ else
+ aFontStyle = "normal";
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontStyle, aFontStyle );
+
+ // Font Weight
+ switch( rFont.GetWeight() )
+ {
+ case WEIGHT_THIN: nFontWeight = 100; break;
+ case WEIGHT_ULTRALIGHT: nFontWeight = 200; break;
+ case WEIGHT_LIGHT: nFontWeight = 300; break;
+ case WEIGHT_SEMILIGHT: nFontWeight = 400; break;
+ case WEIGHT_NORMAL: nFontWeight = 400; break;
+ case WEIGHT_MEDIUM: nFontWeight = 500; break;
+ case WEIGHT_SEMIBOLD: nFontWeight = 600; break;
+ case WEIGHT_BOLD: nFontWeight = 700; break;
+ case WEIGHT_ULTRABOLD: nFontWeight = 800; break;
+ case WEIGHT_BLACK: nFontWeight = 900; break;
+ default: nFontWeight = 400; break;
+ }
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontWeight, OUString::number( nFontWeight ) );
+
+ if( mrExport.IsUseNativeTextDecoration() )
+ {
+ OUString aTextDecoration;
+ if( rFont.GetUnderline() != LINESTYLE_NONE || rFont.GetStrikeout() != STRIKEOUT_NONE )
+ {
+ if( rFont.GetUnderline() != LINESTYLE_NONE )
+ aTextDecoration = "underline ";
+
+ if( rFont.GetStrikeout() != STRIKEOUT_NONE )
+ aTextDecoration += "line-through ";
+ }
+ else
+ aTextDecoration = "none";
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTextDecoration, aTextDecoration );
+ }
+
+ startFontSettings();
+}
+
+
+void SVGAttributeWriter::startFontSettings()
+{
+ endFontSettings();
+ if( mrExport.IsUsePositionedCharacters() )
+ {
+ mpElemFont.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true ) );
+ }
+ else
+ {
+ mpElemFont.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, true, true ) );
+ }
+}
+
+
+void SVGAttributeWriter::endFontSettings()
+{
+ mpElemFont.reset();
+}
+
+
+void SVGAttributeWriter::setFontFamily()
+{
+ vcl::Font& rCurFont = mrCurrentState.aFont;
+
+ if( mrExport.IsUsePositionedCharacters() )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontFamily, mrFontExport.GetMappedFontName( rCurFont.GetFamilyName() ) );
+ }
+ else
+ {
+ const OUString& rsFontName = rCurFont.GetFamilyName();
+ OUString sFontFamily( rsFontName.getToken( 0, ';' ) );
+ FontPitch ePitch = rCurFont.GetPitch();
+ if( ePitch == PITCH_FIXED )
+ {
+ sFontFamily += ", monospace";
+ }
+ else
+ {
+ FontFamily eFamily = rCurFont.GetFamilyType();
+ if( eFamily == FAMILY_ROMAN )
+ sFontFamily += ", serif";
+ else if( eFamily == FAMILY_SWISS )
+ sFontFamily += ", sans-serif";
+ }
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontFamily, sFontFamily );
+ }
+}
+
+SVGTextWriter::SVGTextWriter(SVGExport& rExport, SVGAttributeWriter& rAttributeWriter,
+ SVGActionWriter& rActionWriter)
+: mrExport( rExport ),
+ mrAttributeWriter( rAttributeWriter ),
+ mrActionWriter(rActionWriter),
+ mpVDev( nullptr ),
+ mbIsTextShapeStarted( false ),
+ mpTextEmbeddedBitmapMtf( nullptr ),
+ mpTargetMapMode( nullptr ),
+ mnLeftTextPortionLength( 0 ),
+ maTextPos(0,0),
+ mnTextWidth(0),
+ mbPositioningNeeded( false ),
+ mbIsNewListItem( false ),
+ meNumberingType(0),
+ mcBulletChar(0),
+ mbIsListLevelStyleImage( false ),
+ mbLineBreak( false ),
+ mbIsURLField( false ),
+ mbIsPlaceholderShape( false )
+{
+}
+
+
+SVGTextWriter::~SVGTextWriter()
+{
+ endTextParagraph();
+}
+
+
+void SVGTextWriter::implRegisterInterface( const Reference< XInterface >& rxIf )
+{
+ if( rxIf.is() )
+ mrExport.getInterfaceToIdentifierMapper().registerReference( rxIf );
+}
+
+
+const OUString & SVGTextWriter::implGetValidIDFromInterface( const Reference< XInterface >& rxIf )
+{
+ return mrExport.getInterfaceToIdentifierMapper().getIdentifier( rxIf );
+}
+
+
+void SVGTextWriter::implMap( const Size& rSz, Size& rDstSz ) const
+{
+ if( mpVDev && mpTargetMapMode )
+ rDstSz = OutputDevice::LogicToLogic( rSz, mpVDev->GetMapMode(), *mpTargetMapMode );
+ else
+ OSL_FAIL( "SVGTextWriter::implMap: invalid virtual device or map mode." );
+}
+
+
+void SVGTextWriter::implMap( const Point& rPt, Point& rDstPt ) const
+{
+ if( mpVDev && mpTargetMapMode )
+ rDstPt = OutputDevice::LogicToLogic( rPt, mpVDev->GetMapMode(), *mpTargetMapMode );
+ else
+ OSL_FAIL( "SVGTextWriter::implMap: invalid virtual device or map mode." );
+}
+
+
+void SVGTextWriter::implSetCurrentFont()
+{
+ if( mpVDev )
+ {
+ maCurrentFont = mpVDev->GetFont();
+ Size aSz;
+
+ implMap( Size( 0, maCurrentFont.GetFontHeight() ), aSz );
+
+ maCurrentFont.SetFontHeight( aSz.Height() );
+ }
+ else
+ {
+ OSL_FAIL( "SVGTextWriter::implSetCorrectFontHeight: invalid virtual device." );
+ }
+}
+
+
+template< typename SubType >
+bool SVGTextWriter::implGetTextPosition( const MetaAction* pAction, Point& raPos, bool& rbEmpty )
+{
+ const SubType* pA = static_cast<const SubType*>(pAction);
+ sal_uInt16 nLength = pA->GetLen();
+ rbEmpty = ( nLength == 0 );
+ if( !rbEmpty )
+ {
+ raPos = pA->GetPoint();
+ return true;
+ }
+ return false;
+}
+
+
+template<>
+bool SVGTextWriter::implGetTextPosition<MetaTextRectAction>( const MetaAction* pAction, Point& raPos, bool& rbEmpty )
+{
+ const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
+ sal_uInt16 nLength = pA->GetText().getLength();
+ rbEmpty = ( nLength == 0 );
+ if( !rbEmpty )
+ {
+ raPos = pA->GetRect().TopLeft();
+ return true;
+ }
+ return false;
+}
+
+
+template< typename SubType >
+bool SVGTextWriter::implGetTextPositionFromBitmap( const MetaAction* pAction, Point& raPos, bool& rbEmpty )
+{
+ const SubType* pA = static_cast<const SubType*>(pAction);
+ raPos = pA->GetPoint();
+ rbEmpty = false;
+ return true;
+}
+
+
+/** setTextPosition
+ * Set the start position of the next line of text. In case no text is found
+ * the current action index is updated to the index value we reached while
+ * searching for text.
+ *
+ * @returns {sal_Int32}
+ * -2 if no text found and end of line is reached
+ * -1 if no text found and end of paragraph is reached
+ * 0 if no text found and end of text shape is reached
+ * 1 if text found!
+ */
+sal_Int32 SVGTextWriter::setTextPosition(const GDIMetaFile& rMtf, size_t& nCurAction,
+ sal_uInt32 nWriteFlags)
+{
+ Point aPos;
+ size_t nCount = rMtf.GetActionSize();
+ bool bEOL = false;
+ bool bEOP = false;
+ bool bETS = false;
+ bool bConfigured = false;
+ bool bEmpty = true;
+
+ size_t nActionIndex = nCurAction + 1;
+ for( ; nActionIndex < nCount; ++nActionIndex )
+ {
+ const MetaAction* pAction = rMtf.GetAction( nActionIndex );
+ const MetaActionType nType = pAction->GetType();
+
+ switch( nType )
+ {
+ case MetaActionType::TEXT:
+ {
+ bConfigured = implGetTextPosition<MetaTextAction>( pAction, aPos, bEmpty );
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ bConfigured = implGetTextPosition<MetaTextRectAction>( pAction, aPos, bEmpty );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ bConfigured = implGetTextPosition<MetaTextArrayAction>( pAction, aPos, bEmpty );
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ const MetaFloatTransparentAction* pA
+ = static_cast<const MetaFloatTransparentAction*>(pAction);
+ GDIMetaFile aTmpMtf(pA->GetGDIMetaFile());
+ size_t nTmpAction = 0;
+ if (setTextPosition(aTmpMtf, nTmpAction, nWriteFlags) == 1)
+ {
+ // Text is found in the inner metafile.
+ bConfigured = true;
+
+ // nTextFound == 1 is only possible if the inner setTextPosition() had bEmpty ==
+ // false, adjust our bEmpty accordingly.
+ bEmpty = false;
+
+ mrActionWriter.StartMask(pA->GetPoint(), pA->GetSize(), pA->GetGradient(),
+ nWriteFlags, &maTextOpacity);
+ }
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ bConfigured = implGetTextPosition<MetaStretchTextAction>( pAction, aPos, bEmpty );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ bConfigured = implGetTextPositionFromBitmap<MetaBmpScaleAction>( pAction, aPos, bEmpty );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ bConfigured = implGetTextPositionFromBitmap<MetaBmpExScaleAction>( pAction, aPos, bEmpty );
+ }
+ break;
+
+ // If we reach the end of the current line, paragraph or text shape
+ // without finding any text we stop searching
+ case MetaActionType::COMMENT:
+ {
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
+ const OString& rsComment = pA->GetComment();
+ if( rsComment.equalsIgnoreAsciiCase( "XTEXT_EOL" ) )
+ {
+ bEOL = true;
+ }
+ else if( rsComment.equalsIgnoreAsciiCase( "XTEXT_EOP" ) )
+ {
+ bEOP = true;
+
+ OUString sContent;
+ while( nextTextPortion() )
+ {
+ sContent = mrCurrentTextPortion->getString();
+ if( sContent.isEmpty() )
+ {
+ continue;
+ }
+ else
+ {
+ if( sContent == "\n" )
+ mbLineBreak = true;
+ }
+ }
+ if( nextParagraph() )
+ {
+ while( nextTextPortion() )
+ {
+ sContent = mrCurrentTextPortion->getString();
+ if( sContent.isEmpty() )
+ {
+ continue;
+ }
+ else
+ {
+ if( sContent == "\n" )
+ mbLineBreak = true;
+ }
+ }
+ }
+ }
+ else if( rsComment.equalsIgnoreAsciiCase( "XTEXT_PAINTSHAPE_END" ) )
+ {
+ bETS = true;
+ }
+ }
+ break;
+ default: break;
+ }
+ if( bConfigured || bEOL || bEOP || bETS ) break;
+ }
+ implMap( aPos, maTextPos );
+
+ if( bEmpty )
+ {
+ nCurAction = nActionIndex;
+ return ( bEOL ? -2 : ( bEOP ? -1 : 0 ) );
+ }
+ else
+ {
+ return 1;
+ }
+}
+
+
+void SVGTextWriter::setTextProperties( const GDIMetaFile& rMtf, size_t nCurAction )
+{
+ size_t nCount = rMtf.GetActionSize();
+ bool bEOP = false;
+ bool bConfigured = false;
+ for( size_t nActionIndex = nCurAction + 1; nActionIndex < nCount; ++nActionIndex )
+ {
+ const MetaAction* pAction = rMtf.GetAction( nActionIndex );
+ const MetaActionType nType = pAction->GetType();
+ switch( nType )
+ {
+ case MetaActionType::TEXTLINECOLOR:
+ case MetaActionType::TEXTFILLCOLOR:
+ case MetaActionType::TEXTCOLOR:
+ case MetaActionType::TEXTALIGN:
+ case MetaActionType::FONT:
+ case MetaActionType::LAYOUTMODE:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( mpVDev );
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction);
+ if( pA->GetLen() > 2 )
+ bConfigured = true;
+ }
+ break;
+ case MetaActionType::TEXTRECT:
+ {
+ const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
+ if( pA->GetText().getLength() > 2 )
+ bConfigured = true;
+ }
+ break;
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction);
+ if( pA->GetLen() > 2 )
+ bConfigured = true;
+ }
+ break;
+ case MetaActionType::STRETCHTEXT:
+ {
+ const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction);
+ if( pA->GetLen() > 2 )
+ bConfigured = true;
+ }
+ break;
+ // If we reach the end of the paragraph without finding any text
+ // we stop searching
+ case MetaActionType::COMMENT:
+ {
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
+ const OString& rsComment = pA->GetComment();
+ if( rsComment.equalsIgnoreAsciiCase( "XTEXT_EOP" ) )
+ {
+ bEOP = true;
+ }
+ }
+ break;
+ default: break;
+ }
+ if( bConfigured || bEOP ) break;
+ }
+}
+
+
+void SVGTextWriter::addFontAttributes( bool bIsTextContainer )
+{
+ implSetCurrentFont();
+
+ if( maCurrentFont == maParentFont )
+ return;
+
+ const OUString& rsCurFontName = maCurrentFont.GetFamilyName();
+ tools::Long nCurFontSize = maCurrentFont.GetFontHeight();
+ FontItalic eCurFontItalic = maCurrentFont.GetItalic();
+ FontWeight eCurFontWeight = maCurrentFont.GetWeight();
+
+ const OUString& rsParFontName = maParentFont.GetFamilyName();
+ tools::Long nParFontSize = maParentFont.GetFontHeight();
+ FontItalic eParFontItalic = maParentFont.GetItalic();
+ FontWeight eParFontWeight = maParentFont.GetWeight();
+
+
+ // Font Family
+ if( rsCurFontName != rsParFontName )
+ {
+ implSetFontFamily();
+ }
+
+ // Font Size
+ if( nCurFontSize != nParFontSize )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontSize,
+ OUString::number( nCurFontSize ) + "px" );
+ }
+
+ // Font Style
+ if( eCurFontItalic != eParFontItalic )
+ {
+ OUString sFontStyle;
+ if( eCurFontItalic != ITALIC_NONE )
+ {
+ if( eCurFontItalic == ITALIC_OBLIQUE )
+ sFontStyle = "oblique";
+ else
+ sFontStyle = "italic";
+ }
+ else
+ {
+ sFontStyle = "normal";
+ }
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontStyle, sFontStyle );
+ }
+
+ // Font Weight
+ if( eCurFontWeight != eParFontWeight )
+ {
+ sal_Int32 nFontWeight;
+ switch( eCurFontWeight )
+ {
+ case WEIGHT_THIN: nFontWeight = 100; break;
+ case WEIGHT_ULTRALIGHT: nFontWeight = 200; break;
+ case WEIGHT_LIGHT: nFontWeight = 300; break;
+ case WEIGHT_SEMILIGHT: nFontWeight = 400; break;
+ case WEIGHT_NORMAL: nFontWeight = 400; break;
+ case WEIGHT_MEDIUM: nFontWeight = 500; break;
+ case WEIGHT_SEMIBOLD: nFontWeight = 600; break;
+ case WEIGHT_BOLD: nFontWeight = 700; break;
+ case WEIGHT_ULTRABOLD: nFontWeight = 800; break;
+ case WEIGHT_BLACK: nFontWeight = 900; break;
+ default: nFontWeight = 400; break;
+ }
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontWeight, OUString::number( nFontWeight ) );
+ }
+
+
+ if( mrExport.IsUseNativeTextDecoration() )
+ {
+ FontLineStyle eCurFontLineStyle = maCurrentFont.GetUnderline();
+ FontStrikeout eCurFontStrikeout = maCurrentFont.GetStrikeout();
+
+ FontLineStyle eParFontLineStyle = maParentFont.GetUnderline();
+ FontStrikeout eParFontStrikeout = maParentFont.GetStrikeout();
+
+ OUString sTextDecoration;
+ bool bIsDecorationChanged = false;
+ if( eCurFontLineStyle != eParFontLineStyle )
+ {
+ if( eCurFontLineStyle != LINESTYLE_NONE )
+ sTextDecoration = "underline";
+ bIsDecorationChanged = true;
+ }
+ if( eCurFontStrikeout != eParFontStrikeout )
+ {
+ if( eCurFontStrikeout != STRIKEOUT_NONE )
+ {
+ if( !sTextDecoration.isEmpty() )
+ sTextDecoration += " ";
+ sTextDecoration += "line-through";
+ }
+ bIsDecorationChanged = true;
+ }
+
+ if( !sTextDecoration.isEmpty() )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTextDecoration, sTextDecoration );
+ }
+ else if( bIsDecorationChanged )
+ {
+ sTextDecoration = "none";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTextDecoration, sTextDecoration );
+ }
+ }
+
+ if( bIsTextContainer )
+ maParentFont = maCurrentFont;
+}
+
+
+void SVGTextWriter::implSetFontFamily()
+{
+ const OUString& rsFontName = maCurrentFont.GetFamilyName();
+ OUString sFontFamily( rsFontName.getToken( 0, ';' ) );
+ FontPitch ePitch = maCurrentFont.GetPitch();
+ if( ePitch == PITCH_FIXED )
+ {
+ sFontFamily += ", monospace";
+ }
+ else
+ {
+ FontFamily eFamily = maCurrentFont.GetFamilyType();
+ if( eFamily == FAMILY_ROMAN )
+ sFontFamily += ", serif";
+ else if( eFamily == FAMILY_SWISS )
+ sFontFamily += ", sans-serif";
+ }
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontFamily, sFontFamily );
+}
+
+
+void SVGTextWriter::createParagraphEnumeration()
+{
+ if( mrTextShape.is() )
+ {
+ msShapeId = implGetValidIDFromInterface( Reference<XInterface>(mrTextShape, UNO_QUERY) );
+
+ Reference< XEnumerationAccess > xEnumerationAccess( mrTextShape, UNO_QUERY_THROW );
+ Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
+ if( xEnumeration.is() )
+ {
+ mrParagraphEnumeration.set( xEnumeration );
+ }
+ else
+ {
+ OSL_FAIL( "SVGTextWriter::createParagraphEnumeration: no valid xEnumeration interface found." );
+ }
+ }
+ else
+ {
+ OSL_FAIL( "SVGTextWriter::createParagraphEnumeration: no valid XText interface found." );
+ }
+}
+
+
+bool SVGTextWriter::nextParagraph()
+{
+ mrTextPortionEnumeration.clear();
+ mrCurrentTextParagraph.clear();
+ mbIsNewListItem = false;
+ mbIsListLevelStyleImage = false;
+
+ if( !mrParagraphEnumeration || !mrParagraphEnumeration->hasMoreElements() )
+ return false;
+
+ Reference < XTextContent > xTextContent( mrParagraphEnumeration->nextElement(), UNO_QUERY_THROW );
+ if( xTextContent.is() )
+ {
+ Reference< XServiceInfo > xServiceInfo( xTextContent, UNO_QUERY_THROW );
+#if OSL_DEBUG_LEVEL > 0
+ OUString sInfo;
+#endif
+ if( xServiceInfo->supportsService( "com.sun.star.text.Paragraph" ) )
+ {
+ mrCurrentTextParagraph.set( xTextContent );
+ Reference< XPropertySet > xPropSet( xTextContent, UNO_QUERY_THROW );
+ Reference< XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
+ if( xPropSetInfo->hasPropertyByName( "NumberingLevel" ) )
+ {
+ sal_Int16 nListLevel = 0;
+ if( xPropSet->getPropertyValue( "NumberingLevel" ) >>= nListLevel )
+ {
+ mbIsNewListItem = true;
+#if OSL_DEBUG_LEVEL > 0
+ sInfo = "NumberingLevel: " + OUString::number( nListLevel );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "style", sInfo );
+#endif
+ Reference< XIndexReplace > xNumRules;
+ if( xPropSetInfo->hasPropertyByName( "NumberingRules" ) )
+ {
+ xPropSet->getPropertyValue( "NumberingRules" ) >>= xNumRules;
+ }
+ if( xNumRules.is() && ( nListLevel < xNumRules->getCount() ) )
+ {
+ bool bIsNumbered = true;
+ OUString sNumberingIsNumber("NumberingIsNumber");
+ if( xPropSetInfo->hasPropertyByName( sNumberingIsNumber ) )
+ {
+ if( !(xPropSet->getPropertyValue( sNumberingIsNumber ) >>= bIsNumbered ) )
+ {
+ OSL_FAIL( "numbered paragraph without number info" );
+ bIsNumbered = false;
+ }
+#if OSL_DEBUG_LEVEL > 0
+ if( bIsNumbered )
+ {
+ sInfo = "true";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "is-numbered", sInfo );
+ }
+#endif
+ }
+ mbIsNewListItem = bIsNumbered;
+
+ if( bIsNumbered )
+ {
+ Sequence<PropertyValue> aProps;
+ if( xNumRules->getByIndex( nListLevel ) >>= aProps )
+ {
+ sal_Int16 eType = NumberingType::CHAR_SPECIAL;
+ sal_Unicode cBullet = 0xf095;
+ const sal_Int32 nCount = aProps.getLength();
+ const PropertyValue* pPropArray = aProps.getConstArray();
+ for( sal_Int32 i = 0; i < nCount; ++i )
+ {
+ const PropertyValue& rProp = pPropArray[i];
+ if( rProp.Name == "NumberingType" )
+ {
+ rProp.Value >>= eType;
+ }
+ else if( rProp.Name == "BulletChar" )
+ {
+ OUString sValue;
+ rProp.Value >>= sValue;
+ if( !sValue.isEmpty() )
+ {
+ cBullet = sValue[0];
+ }
+ }
+ }
+ meNumberingType = eType;
+ mbIsListLevelStyleImage = ( NumberingType::BITMAP == meNumberingType );
+ if( NumberingType::CHAR_SPECIAL == meNumberingType )
+ {
+ if( cBullet )
+ {
+ if( cBullet < ' ' )
+ {
+ cBullet = 0xF000 + 149;
+ }
+ mcBulletChar = cBullet;
+#if OSL_DEBUG_LEVEL > 0
+ sInfo = OUString::number( static_cast<sal_Int32>(cBullet) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "bullet-char", sInfo );
+#endif
+ }
+
+ }
+ }
+ }
+ }
+
+ }
+ }
+
+ Reference< XEnumerationAccess > xEnumerationAccess( xTextContent, UNO_QUERY_THROW );
+ Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
+ if( xEnumeration.is() && xEnumeration->hasMoreElements() )
+ {
+ mrTextPortionEnumeration.set( xEnumeration );
+ }
+#if OSL_DEBUG_LEVEL > 0
+ sInfo = "Paragraph";
+#endif
+ }
+ else if( xServiceInfo->supportsService( "com.sun.star.text.Table" ) )
+ {
+ OSL_FAIL( "SVGTextWriter::nextParagraph: text tables are not handled." );
+#if OSL_DEBUG_LEVEL > 0
+ sInfo = "Table";
+#endif
+ }
+ else
+ {
+ OSL_FAIL( "SVGTextWriter::nextParagraph: Unknown text content." );
+ return false;
+ }
+#if OSL_DEBUG_LEVEL > 0
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", sInfo );
+ SvXMLElementExport aParaElem( mrExport, XML_NAMESPACE_NONE, "desc", mbIWS, mbIWS );
+#endif
+ }
+ else
+ {
+ OSL_FAIL( "SVGTextWriter::nextParagraph: no XServiceInfo interface available for text content." );
+ return false;
+ }
+
+ const OUString& rParagraphId = implGetValidIDFromInterface( Reference<XInterface>(xTextContent, UNO_QUERY) );
+ if( !rParagraphId.isEmpty() )
+ {
+ // if there is id for empty paragraph we need to create a empty text paragraph
+ Reference < XTextRange > xRange( xTextContent, UNO_QUERY_THROW );
+ if ( xRange.is() && xRange->getString().isEmpty() )
+ {
+ endTextParagraph();
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "TextParagraph" );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", rParagraphId );
+ mpTextParagraphElem.reset(new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ));
+ }
+ else
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", rParagraphId );
+ }
+ }
+ return true;
+}
+
+
+bool SVGTextWriter::nextTextPortion()
+{
+ mrCurrentTextPortion.clear();
+ mbIsURLField = false;
+ if( !mrTextPortionEnumeration || !mrTextPortionEnumeration->hasMoreElements() )
+ return false;
+
+ mbIsPlaceholderShape = false;
+ Reference< XPropertySet > xPortionPropSet( mrTextPortionEnumeration->nextElement(), UNO_QUERY );
+ Reference< XPropertySetInfo > xPortionPropInfo( xPortionPropSet->getPropertySetInfo() );
+ Reference < XTextRange > xPortionTextRange( xPortionPropSet, UNO_QUERY);
+ if( !xPortionPropSet || !xPortionPropInfo
+ || !xPortionPropInfo->hasPropertyByName( "TextPortionType" ) )
+ return true;
+
+#if OSL_DEBUG_LEVEL > 0
+ OUString sInfo;
+ OUString sPortionType;
+ if( xPortionPropSet->getPropertyValue( "TextPortionType" ) >>= sPortionType )
+ {
+ sInfo = "type: " + sPortionType + "; ";
+ }
+#endif
+ msPageCount = "";
+ msDateTimeType = "";
+ msTextFieldType = "";
+ if( xPortionTextRange.is() )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ sInfo += "content: " + xPortionTextRange->getString() + "; ";
+#endif
+ mrCurrentTextPortion.set( xPortionTextRange );
+
+ Reference < XPropertySet > xRangePropSet( xPortionTextRange, UNO_QUERY );
+ if( xRangePropSet.is() && xRangePropSet->getPropertySetInfo()->hasPropertyByName( "TextField" ) )
+ {
+ Reference < XTextField > xTextField( xRangePropSet->getPropertyValue( "TextField" ), UNO_QUERY );
+ if( xTextField.is() )
+ {
+ static const OUStringLiteral sServicePrefix(u"com.sun.star.text.textfield.");
+ static const OUStringLiteral sPresentationServicePrefix(u"com.sun.star.presentation.TextField.");
+
+ Reference< XServiceInfo > xService( xTextField, UNO_QUERY );
+ const Sequence< OUString > aServices = xService->getSupportedServiceNames();
+
+ const OUString* pNames = aServices.getConstArray();
+ sal_Int32 nCount = aServices.getLength();
+
+ OUString sFieldName; // service name postfix of current field
+
+ // search for TextField service name
+ while( nCount-- )
+ {
+ if ( pNames->matchIgnoreAsciiCase( sServicePrefix ) )
+ {
+ // TextField found => postfix is field type!
+ sFieldName = pNames->copy( sServicePrefix.getLength() );
+ break;
+ }
+ else if( pNames->startsWith( sPresentationServicePrefix ) )
+ {
+ // TextField found => postfix is field type!
+ sFieldName = pNames->copy( sPresentationServicePrefix.getLength() );
+ break;
+ }
+
+ ++pNames;
+ }
+
+ msTextFieldType = sFieldName;
+#if OSL_DEBUG_LEVEL > 0
+ sInfo += "text field type: " + sFieldName + "; content: " + xTextField->getPresentation( /* show command: */ false ) + "; ";
+#endif
+ // This case handle Date or Time text field inserted by the user
+ // on both page/master page. It doesn't handle the standard Date/Time field.
+ if( sFieldName == "DateTime" )
+ {
+ Reference<XPropertySet> xTextFieldPropSet(xTextField, UNO_QUERY);
+ if( xTextFieldPropSet.is() )
+ {
+ Reference<XPropertySetInfo> xPropSetInfo = xTextFieldPropSet->getPropertySetInfo();
+ if( xPropSetInfo.is() )
+ {
+ // The standard Date/Time field has no property.
+ // Trying to get a property value on such field would cause a runtime exception.
+ // So the hasPropertyByName check is needed.
+ bool bIsFixed = true;
+ if( xPropSetInfo->hasPropertyByName("IsFixed") && ( ( xTextFieldPropSet->getPropertyValue( "IsFixed" ) ) >>= bIsFixed ) && !bIsFixed )
+ {
+ bool bIsDate = true;
+ if( xPropSetInfo->hasPropertyByName("IsDate") && ( ( xTextFieldPropSet->getPropertyValue( "IsDate" ) ) >>= bIsDate ) )
+ {
+ msDateTimeType = OUString::createFromAscii( bIsDate ? "<date>" : "<time>" );
+ }
+ }
+ }
+ }
+ }
+ if( sFieldName == "DateTime" || sFieldName == "Header"
+ || sFieldName == "Footer" || sFieldName == "PageNumber"
+ || sFieldName == "PageName" )
+ {
+ mbIsPlaceholderShape = true;
+ }
+ else if (sFieldName == "PageCount")
+ {
+ msPageCount = xTextField->getPresentation( /* show command: */ false );
+ }
+ else
+ {
+ mbIsURLField = sFieldName == "URL";
+
+ if( mbIsURLField )
+ {
+ Reference<XPropertySet> xTextFieldPropSet(xTextField, UNO_QUERY);
+ if( xTextFieldPropSet.is() )
+ {
+ OUString sURL;
+ if( ( xTextFieldPropSet->getPropertyValue( sFieldName ) ) >>= sURL )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ sInfo += "url: " + mrExport.GetRelativeReference( sURL );
+#endif
+ msUrl = mrExport.GetRelativeReference( sURL );
+ if( !msUrl.isEmpty() )
+ {
+ implRegisterInterface( xPortionTextRange );
+
+ const OUString& rTextPortionId = implGetValidIDFromInterface( Reference<XInterface>(xPortionTextRange, UNO_QUERY) );
+ if( !rTextPortionId.isEmpty() )
+ {
+ msHyperlinkIdList += rTextPortionId + " ";
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+#if OSL_DEBUG_LEVEL > 0
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "TextPortion" );
+ SvXMLElementExport aPortionElem( mrExport, XML_NAMESPACE_NONE, "desc", mbIWS, mbIWS );
+ mrExport.GetDocHandler()->characters( sInfo );
+#endif
+ return true;
+}
+
+
+void SVGTextWriter::startTextShape()
+{
+ if( mpTextShapeElem )
+ {
+ OSL_FAIL( "SVGTextWriter::startTextShape: text shape already defined." );
+ }
+
+ {
+ mbIsTextShapeStarted = true;
+ maParentFont = vcl::Font();
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "SVGTextShape" );
+
+ // if text is rotated, set transform matrix at text element
+ const vcl::Font& rFont = mpVDev->GetFont();
+ if( rFont.GetOrientation() )
+ {
+ Point aRot( maTextPos );
+ OUString aTransform = "rotate(" +
+ OUString::number( rFont.GetOrientation().get() * -0.1 ) + " " +
+ OUString::number( aRot.X() ) + " " +
+ OUString::number( aRot.Y() ) + ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTransform, aTransform );
+ }
+
+ mpTextShapeElem.reset(new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemText, true, mbIWS ));
+ startTextParagraph();
+ }
+}
+
+
+void SVGTextWriter::endTextShape()
+{
+ endTextParagraph();
+ mrTextShape.clear();
+ mrParagraphEnumeration.clear();
+ mrCurrentTextParagraph.clear();
+ mpTextShapeElem.reset();
+ maTextOpacity.clear();
+ mbIsTextShapeStarted = false;
+ // these need to be invoked after the <text> element has been closed
+ implExportHyperlinkIds();
+ implWriteBulletChars();
+ implWriteEmbeddedBitmaps();
+
+}
+
+
+void SVGTextWriter::startTextParagraph()
+{
+ endTextParagraph();
+ nextParagraph();
+ if( mbIsNewListItem )
+ {
+ OUString sNumberingType;
+ switch( meNumberingType )
+ {
+ case NumberingType::CHAR_SPECIAL:
+ sNumberingType = "bullet-style";
+ break;
+ case NumberingType::BITMAP:
+ sNumberingType = "image-style";
+ break;
+ default:
+ sNumberingType = "number-style";
+ break;
+ }
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "ooo:numbering-type", sNumberingType );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "ListItem" );
+ }
+ else
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "TextParagraph" );
+ }
+ maParentFont = vcl::Font();
+ addFontAttributes( /* isTexTContainer: */ true );
+ mpTextParagraphElem.reset(new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ));
+
+ if( !mbIsListLevelStyleImage )
+ {
+ mbPositioningNeeded = true;
+ }
+}
+
+
+void SVGTextWriter::endTextParagraph()
+{
+ mrCurrentTextPortion.clear();
+ endTextPosition();
+ mbIsNewListItem = false;
+ mbIsListLevelStyleImage = false;
+ mbPositioningNeeded = false;
+ mpTextParagraphElem.reset();
+}
+
+
+void SVGTextWriter::startTextPosition( bool bExportX, bool bExportY )
+{
+ endTextPosition();
+ mnTextWidth = 0;
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "TextPosition" );
+ if( bExportX )
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( maTextPos.X() ) );
+ if( bExportY )
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( maTextPos.Y() ) );
+
+ mpTextPositionElem.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ) );
+}
+
+
+void SVGTextWriter::endTextPosition()
+{
+ mpTextPositionElem.reset();
+}
+
+bool SVGTextWriter::hasTextOpacity() const { return !maTextOpacity.isEmpty(); }
+
+void SVGTextWriter::implExportHyperlinkIds()
+{
+ if( !msHyperlinkIdList.isEmpty() )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "HyperlinkIdList" );
+ SvXMLElementExport aDescElem( mrExport, XML_NAMESPACE_NONE, "desc", true, false );
+ mrExport.GetDocHandler()->characters( msHyperlinkIdList.trim() );
+ msHyperlinkIdList.clear();
+ }
+}
+
+
+void SVGTextWriter::implWriteBulletChars()
+{
+ if( maBulletListItemMap.empty() )
+ return;
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "BulletChars" );
+ SvXMLElementExport aGroupElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ OUString sId, sPosition, sScaling, sRefId;
+ for (auto const& bulletListItem : maBulletListItemMap)
+ {
+ // <g id="?" > (used by animations)
+ // As id we use the id of the text portion placeholder with prefix
+ // bullet-char-*
+ sId = "bullet-char-" + bulletListItem.first;
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", sId );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "BulletChar" );
+ SvXMLElementExport aBulletCharElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ // <g transform="translate(x,y)" >
+ {
+ const BulletListItemInfo& rInfo = bulletListItem.second;
+
+ // Add positioning attribute through a translation
+ sPosition = "translate(" +
+ OUString::number( rInfo.aPos.X() ) +
+ "," + OUString::number( rInfo.aPos.Y() ) + ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "transform", sPosition );
+
+ mrAttributeWriter.AddPaintAttr( COL_TRANSPARENT, rInfo.aColor );
+
+ SvXMLElementExport aPositioningElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ if (mrExport.IsEmbeddedBulletGlyph(rInfo.cBulletChar))
+ {
+ // <use transform="scale(font-size)" xlink:ref="/" >
+ // Add size attribute through a scaling
+ sScaling = "scale(" + OUString::number( rInfo.aFont.GetFontHeight() ) +
+ "," + OUString::number( rInfo.aFont.GetFontHeight() )+ ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "transform", sScaling );
+
+ // Add ref attribute
+ sRefId = "#bullet-char-template-" +
+ OUString::number( rInfo.cBulletChar );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId );
+
+ SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true );
+ }
+ else
+ {
+ // <path d="...">
+ tools::PolyPolygon aPolyPolygon;
+ OUString aStr(rInfo.cBulletChar);
+ mpVDev->Push(vcl::PushFlags::FONT);
+ mpVDev->SetFont(rInfo.aFont);
+ if (mpVDev->GetTextOutline(aPolyPolygon, aStr))
+ {
+ OUString aPathString(SVGActionWriter::GetPathString(aPolyPolygon, false));
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, "d", aPathString);
+ SvXMLElementExport aPath(mrExport, XML_NAMESPACE_NONE, "path", true, true);
+ }
+ mpVDev->Pop();
+ }
+ } // close aPositioningElem
+ }
+
+ // clear the map
+ maBulletListItemMap.clear();
+}
+
+
+template< typename MetaBitmapActionType >
+void SVGTextWriter::writeBitmapPlaceholder( const MetaBitmapActionType* pAction )
+{
+ // text position element
+ const Point& rPos = pAction->GetPoint();
+ implMap( rPos, maTextPos );
+ startTextPosition();
+ mbPositioningNeeded = true;
+ if( mbIsNewListItem )
+ {
+ mbIsNewListItem = false;
+ mbIsListLevelStyleImage = false;
+ }
+
+ // bitmap placeholder element
+ BitmapChecksum nId = SVGActionWriter::GetChecksum( pAction );
+ OUString sId = "bitmap-placeholder(" + msShapeId + "." +
+ OUString::number( nId ) + ")";
+
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", sId );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "BitmapPlaceholder" );
+ SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
+ }
+ endTextPosition();
+}
+
+
+void SVGTextWriter::implWriteEmbeddedBitmaps()
+{
+ if( !(mpTextEmbeddedBitmapMtf && mpTextEmbeddedBitmapMtf->GetActionSize()) )
+ return;
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "EmbeddedBitmaps" );
+ SvXMLElementExport aEmbBitmapGroupElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ const GDIMetaFile& rMtf = *mpTextEmbeddedBitmapMtf;
+
+ BitmapChecksum nId, nChecksum = 0;
+ Point aPt;
+ Size aSz;
+ size_t nCount = rMtf.GetActionSize();
+ for( size_t nCurAction = 0; nCurAction < nCount; nCurAction++ )
+ {
+
+ const MetaAction* pAction = rMtf.GetAction( nCurAction );
+ const MetaActionType nType = pAction->GetType();
+
+ switch( nType )
+ {
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+ // The conversion to BitmapEx is needed since at the point
+ // where the bitmap is actually exported a Bitmap object is
+ // converted to BitmapEx before computing the checksum used
+ // to generate the <image> element id.
+ // (See GetBitmapChecksum in svgexport.cxx)
+ nChecksum = BitmapEx( pA->GetBitmap() ).GetChecksum();
+ aPt = pA->GetPoint();
+ aSz = pA->GetSize();
+ }
+ break;
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+ nChecksum = pA->GetBitmapEx().GetChecksum();
+ aPt = pA->GetPoint();
+ aSz = pA->GetSize();
+ }
+ break;
+ default: break;
+ }
+
+ // <g id="?" > (used by animations)
+ {
+ // embedded bitmap id
+ nId = SVGActionWriter::GetChecksum( pAction );
+ OUString sId = "embedded-bitmap(" + msShapeId + "." + OUString::number( nId ) + ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", sId );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "EmbeddedBitmap" );
+
+ SvXMLElementExport aEmbBitmapElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ // <use x="?" y="?" xlink:ref="?" >
+ {
+ // referenced bitmap template
+ OUString sRefId = "#bitmap(" + OUString::number( nChecksum ) + ")";
+
+ Point aPoint;
+ Size aSize;
+ implMap( aPt, aPoint );
+ implMap( aSz, aSize );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aPoint.X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPoint.Y() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId );
+
+ SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true );
+ }
+ } // close aEmbBitmapElem
+ }
+}
+
+
+void SVGTextWriter::writeTextPortion( const Point& rPos,
+ const OUString& rText )
+{
+ if( rText.isEmpty() )
+ return;
+
+ bool bStandAloneTextPortion = false;
+ if( !isTextShapeStarted() )
+ {
+ bStandAloneTextPortion = true;
+ startTextShape();
+ }
+
+ mbLineBreak = false;
+
+ if( !mbIsNewListItem || mbIsListLevelStyleImage )
+ {
+ bool bNotSync = true;
+ OUString sContent;
+ sal_Int32 nStartPos;
+ while( bNotSync )
+ {
+ if( mnLeftTextPortionLength <= 0 || !mrCurrentTextPortion.is() )
+ {
+ if( !nextTextPortion() )
+ break;
+ else
+ {
+ sContent = mrCurrentTextPortion->getString();
+ if( mbIsURLField && sContent.isEmpty() )
+ {
+ Reference < XPropertySet > xPropSet( mrCurrentTextPortion, UNO_QUERY );
+ Reference < XTextField > xTextField( xPropSet->getPropertyValue( "TextField" ), UNO_QUERY );
+ sContent = xTextField->getPresentation( /* show command: */ false );
+ if( sContent.isEmpty() )
+ OSL_FAIL( "SVGTextWriter::writeTextPortion: content of URL TextField is empty." );
+ }
+ mnLeftTextPortionLength = sContent.getLength();
+ }
+ }
+ else
+ {
+ sContent = mrCurrentTextPortion->getString();
+ }
+
+ nStartPos = sContent.getLength() - mnLeftTextPortionLength;
+ if( nStartPos < 0 ) nStartPos = 0;
+ mnLeftTextPortionLength -= rText.getLength();
+
+ if( sContent.isEmpty() )
+ continue;
+ if( sContent == "\n" )
+ mbLineBreak = true;
+ else if (sContent == "\t")
+ {
+ // Need to emit position for the next text portion after a tab, otherwise the tab
+ // would appear as if it has 0 width.
+ mbPositioningNeeded = true;
+ }
+ if( sContent.match( rText, nStartPos ) )
+ bNotSync = false;
+ }
+ }
+
+ assert(mpVDev); //invalid virtual device
+
+#if 0
+ const FontMetric aMetric( mpVDev->GetFontMetric() );
+
+ bool bTextSpecial = aMetric.IsShadow() || aMetric.IsOutline() || (aMetric.GetRelief() != FontRelief::NONE);
+
+ if( true || !bTextSpecial )
+ {
+ implWriteTextPortion( rPos, rText, mpVDev->GetTextColor() );
+ }
+ else
+ {
+ // to be implemented
+ }
+#else
+ implWriteTextPortion( rPos, rText, mpVDev->GetTextColor() );
+#endif
+
+ if( bStandAloneTextPortion )
+ {
+ endTextShape();
+ }
+}
+
+
+void SVGTextWriter::implWriteTextPortion( const Point& rPos,
+ const OUString& rText,
+ Color aTextColor )
+{
+ Point aPos;
+ Point aBaseLinePos( rPos );
+ const FontMetric aMetric( mpVDev->GetFontMetric() );
+ const vcl::Font& rFont = mpVDev->GetFont();
+
+ if( rFont.GetAlignment() == ALIGN_TOP )
+ aBaseLinePos.AdjustY(aMetric.GetAscent() );
+ else if( rFont.GetAlignment() == ALIGN_BOTTOM )
+ aBaseLinePos.AdjustY( -(aMetric.GetDescent()) );
+
+ implMap( rPos, aPos );
+
+ if( mbPositioningNeeded )
+ {
+ mbPositioningNeeded = false;
+ maTextPos.setX( aPos.X() );
+ maTextPos.setY( aPos.Y() );
+ startTextPosition();
+ }
+ else if( maTextPos.Y() != aPos.Y() )
+ {
+ // In case the text position moved backward we could have a line break
+ // so we end the current line and start a new one.
+ if( mbLineBreak || ( ( maTextPos.X() + mnTextWidth ) > aPos.X() ) )
+ {
+ mbLineBreak = false;
+ maTextPos.setX( aPos.X() );
+ maTextPos.setY( aPos.Y() );
+ startTextPosition();
+ }
+ else // superscript, subscript, list item numbering
+ {
+ maTextPos.setY( aPos.Y() );
+ startTextPosition( false /* do not export x attribute */ );
+ }
+ }
+ // we are dealing with a bullet, so set up this for the next text portion
+ if( mbIsNewListItem )
+ {
+ mbIsNewListItem = false;
+ mbPositioningNeeded = true;
+
+ if( meNumberingType == NumberingType::CHAR_SPECIAL )
+ {
+ // Create an id for the current text portion
+ implRegisterInterface( mrCurrentTextParagraph );
+
+ // Add the needed info to the BulletListItemMap
+ OUString sId = implGetValidIDFromInterface( Reference<XInterface>(mrCurrentTextParagraph, UNO_QUERY) );
+ if( !sId.isEmpty() )
+ {
+ sId += ".bp";
+ BulletListItemInfo& aBulletListItemInfo = maBulletListItemMap[ sId ];
+ aBulletListItemInfo.aFont = rFont;
+ aBulletListItemInfo.aColor = aTextColor;
+ aBulletListItemInfo.aPos = maTextPos;
+ aBulletListItemInfo.cBulletChar = mcBulletChar;
+
+ // Make this text portion a bullet placeholder
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", sId );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "BulletPlaceholder" );
+ SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
+ return;
+ }
+ }
+ }
+
+ const OUString& rTextPortionId = implGetValidIDFromInterface( Reference<XInterface>(mrCurrentTextPortion, UNO_QUERY) );
+ if( !rTextPortionId.isEmpty() )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", rTextPortionId );
+ }
+
+ if( mbIsPlaceholderShape )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "PlaceholderText" );
+ }
+
+ addFontAttributes( /* isTexTContainer: */ false );
+
+ if (!maTextOpacity.isEmpty())
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, "fill-opacity", maTextOpacity);
+ }
+
+ mrAttributeWriter.AddPaintAttr( COL_TRANSPARENT, aTextColor );
+
+ // <a> tag for link should be the innermost tag, inside <tspan>
+ if( !mbIsPlaceholderShape && mbIsURLField && !msUrl.isEmpty() )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "UrlField" );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, msUrl );
+
+ SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, msUrl );
+ {
+ SvXMLElementExport aSVGAElem( mrExport, XML_NAMESPACE_NONE, "a", mbIWS, mbIWS );
+ mrExport.GetDocHandler()->characters( rText );
+ }
+ }
+ else if ( !msPageCount.isEmpty() )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "PageCount" );
+ SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
+ mrExport.GetDocHandler()->characters( msPageCount );
+ }
+ // This case handle Date or Time text field inserted by the user
+ // on both page/master page. It doesn't handle the standard Date/Time field.
+ else if ( !msDateTimeType.isEmpty() )
+ {
+ SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
+ mrExport.GetDocHandler()->characters( msDateTimeType );
+ }
+ else if( mbIsPlaceholderShape && rText.startsWith("<") && rText.endsWith(">") )
+ {
+ OUString sContent;
+ if( msTextFieldType == "PageNumber" )
+ sContent = "<number>";
+ else if( msTextFieldType == "PageName" )
+ sContent = "<slide-name>";
+ else
+ sContent = rText;
+
+ SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
+ mrExport.GetDocHandler()->characters( sContent );
+ }
+ else
+ {
+ // Without the following attribute Google Chrome does not render leading spaces
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "style", "white-space: pre" );
+
+ SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
+ mrExport.GetDocHandler()->characters( rText );
+ }
+
+ mnTextWidth += mpVDev->GetTextWidth( rText );
+}
+
+
+SVGActionWriter::SVGActionWriter( SVGExport& rExport, SVGFontExport& rFontExport ) :
+ mnCurGradientId( 1 ),
+ mnCurMaskId( 1 ),
+ mnCurPatternId( 1 ),
+ mnCurClipPathId( 1 ),
+ mrExport( rExport ),
+ maContextHandler(),
+ mrCurrentState( maContextHandler.getCurrentState() ),
+ maAttributeWriter( rExport, rFontExport, mrCurrentState ),
+ maTextWriter(rExport, maAttributeWriter, *this),
+ mpVDev(VclPtr<VirtualDevice>::Create()),
+ mbClipAttrChanged( false ),
+ mbIsPlaceholderShape( false ),
+ mpEmbeddedBitmapsMap( nullptr ),
+ mbIsPreview( false )
+{
+ mpVDev->EnableOutput( false );
+ maTargetMapMode = MapMode(MapUnit::Map100thMM);
+ maTextWriter.setVirtualDevice( mpVDev, maTargetMapMode );
+}
+
+
+SVGActionWriter::~SVGActionWriter()
+{
+ mpVDev.disposeAndClear();
+}
+
+
+tools::Long SVGActionWriter::ImplMap( sal_Int32 nVal ) const
+{
+ Size aSz( nVal, nVal );
+
+ return ImplMap( aSz, aSz ).Width();
+}
+
+
+Point& SVGActionWriter::ImplMap( const Point& rPt, Point& rDstPt ) const
+{
+ rDstPt = OutputDevice::LogicToLogic( rPt, mpVDev->GetMapMode(), maTargetMapMode );
+ return rDstPt;
+}
+
+
+Size& SVGActionWriter::ImplMap( const Size& rSz, Size& rDstSz ) const
+{
+ rDstSz = OutputDevice::LogicToLogic( rSz, mpVDev->GetMapMode(), maTargetMapMode );
+ return rDstSz;
+}
+
+
+void SVGActionWriter::ImplMap( const tools::Rectangle& rRect, tools::Rectangle& rDstRect ) const
+{
+ Point aTL( rRect.TopLeft() );
+ Size aSz( rRect.GetSize() );
+
+ rDstRect = tools::Rectangle( ImplMap( aTL, aTL ), ImplMap( aSz, aSz ) );
+}
+
+
+tools::Polygon& SVGActionWriter::ImplMap( const tools::Polygon& rPoly, tools::Polygon& rDstPoly ) const
+{
+ rDstPoly = tools::Polygon( rPoly.GetSize() );
+
+ for( sal_uInt16 i = 0, nSize = rPoly.GetSize(); i < nSize; ++i )
+ {
+ ImplMap( rPoly[ i ], rDstPoly[ i ] );
+ rDstPoly.SetFlags( i, rPoly.GetFlags( i ) );
+ }
+
+ return rDstPoly;
+}
+
+
+tools::PolyPolygon& SVGActionWriter::ImplMap( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rDstPolyPoly ) const
+{
+ tools::Polygon aPoly;
+
+ rDstPolyPoly = tools::PolyPolygon();
+
+ for( sal_uInt16 i = 0, nCount = rPolyPoly.Count(); i < nCount; ++i )
+ {
+ rDstPolyPoly.Insert( ImplMap( rPolyPoly[ i ], aPoly ) );
+ }
+
+ return rDstPolyPoly;
+}
+
+
+OUString SVGActionWriter::GetPathString( const tools::PolyPolygon& rPolyPoly, bool bLine )
+{
+ OUStringBuffer aPathData;
+ static const OUStringLiteral aBlank( u" " );
+ static const OUStringLiteral aComma( u"," );
+ Point aPolyPoint;
+
+ for( tools::Long i = 0, nCount = rPolyPoly.Count(); i < nCount; i++ )
+ {
+ const tools::Polygon& rPoly = rPolyPoly[ static_cast<sal_uInt16>(i) ];
+ sal_uInt16 n = 1, nSize = rPoly.GetSize();
+
+ if( nSize > 1 )
+ {
+ aPolyPoint = rPoly[ 0 ];
+ aPathData.append("M " +
+ OUString::number( aPolyPoint.X() ) +
+ aComma +
+ OUString::number( aPolyPoint.Y() ));
+
+ char nCurrentMode = 0;
+ const bool bClose(!bLine || rPoly[0] == rPoly[nSize - 1]);
+ while( n < nSize )
+ {
+ aPathData.append(aBlank);
+
+ if ( ( rPoly.GetFlags( n ) == PolyFlags::Control ) && ( ( n + 2 ) < nSize ) )
+ {
+ if ( nCurrentMode != 'C' )
+ {
+ nCurrentMode = 'C';
+ aPathData.append("C ");
+ }
+ for ( int j = 0; j < 3; j++ )
+ {
+ if ( j )
+ aPathData.append(aBlank);
+
+ aPolyPoint = rPoly[ n++ ];
+ aPathData.append( OUString::number(aPolyPoint.X()) +
+ aComma +
+ OUString::number( aPolyPoint.Y() ) );
+ }
+ }
+ else
+ {
+ if ( nCurrentMode != 'L' )
+ {
+ nCurrentMode = 'L';
+ aPathData.append("L ");
+ }
+
+ aPolyPoint = rPoly[ n++ ];
+ aPathData.append( OUString::number(aPolyPoint.X()) +
+ aComma +
+ OUString::number(aPolyPoint.Y()) );
+ }
+ }
+
+ if(bClose)
+ aPathData.append(" Z");
+
+ if( i < ( nCount - 1 ) )
+ aPathData.append(aBlank);
+ }
+ }
+
+ return aPathData.makeStringAndClear();
+}
+
+BitmapChecksum SVGActionWriter::GetChecksum( const MetaAction* pAction )
+{
+ GDIMetaFile aMtf;
+ MetaAction* pA = const_cast<MetaAction*>(pAction);
+ aMtf.AddAction( pA );
+ return SvmWriter::GetChecksum( aMtf );
+}
+
+void SVGActionWriter::SetEmbeddedBitmapRefs( const MetaBitmapActionMap* pEmbeddedBitmapsMap )
+{
+ if (pEmbeddedBitmapsMap)
+ mpEmbeddedBitmapsMap = pEmbeddedBitmapsMap;
+ else
+ OSL_FAIL( "SVGActionWriter::SetEmbeddedBitmapRefs: passed pointer is null" );
+}
+
+void SVGActionWriter::ImplWriteLine( const Point& rPt1, const Point& rPt2,
+ const Color* pLineColor )
+{
+ Point aPt1, aPt2;
+
+ ImplMap( rPt1, aPt1 );
+ ImplMap( rPt2, aPt2 );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX1, OUString::number( aPt1.X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY1, OUString::number( aPt1.Y() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX2, OUString::number( aPt2.X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY2, OUString::number( aPt2.Y() ) );
+
+ if( pLineColor )
+ {
+ // !!! mrExport.AddAttribute( XML_NAMESPACE_NONE, ... )
+ OSL_FAIL( "SVGActionWriter::ImplWriteLine: Line color not implemented" );
+ }
+
+ {
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "line", true, true );
+ }
+}
+
+
+void SVGActionWriter::ImplWriteRect( const tools::Rectangle& rRect, tools::Long nRadX, tools::Long nRadY )
+{
+ tools::Rectangle aRect;
+
+ ImplMap( rRect, aRect );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aRect.Left() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aRect.Top() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrWidth, OUString::number( aRect.GetWidth() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrHeight, OUString::number( aRect.GetHeight() ) );
+
+ if( nRadX )
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrRX, OUString::number( ImplMap( nRadX ) ) );
+
+ if( nRadY )
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrRY, OUString::number( ImplMap( nRadY ) ) );
+
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "rect", true, true );
+}
+
+
+void SVGActionWriter::ImplWriteEllipse( const Point& rCenter, tools::Long nRadX, tools::Long nRadY )
+{
+ Point aCenter;
+
+ ImplMap( rCenter, aCenter );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrCX, OUString::number( aCenter.X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrCY, OUString::number( aCenter.Y() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrRX, OUString::number( ImplMap( nRadX ) ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrRY, OUString::number( ImplMap( nRadY ) ) );
+
+ {
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "ellipse", true, true );
+ }
+}
+
+
+void SVGActionWriter::ImplAddLineAttr( const LineInfo &rAttrs )
+{
+ if ( rAttrs.IsDefault() )
+ return;
+
+ sal_Int32 nStrokeWidth = ImplMap( rAttrs.GetWidth() );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStrokeWidth,
+ OUString::number( nStrokeWidth ) );
+ // support for LineJoint
+ switch(rAttrs.GetLineJoin())
+ {
+ case basegfx::B2DLineJoin::NONE:
+ case basegfx::B2DLineJoin::Miter:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "miter");
+ break;
+ }
+ case basegfx::B2DLineJoin::Bevel:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "bevel");
+ break;
+ }
+ case basegfx::B2DLineJoin::Round:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "round");
+ break;
+ }
+ }
+
+ // support for LineCap
+ switch(rAttrs.GetLineCap())
+ {
+ default: /* css::drawing::LineCap_BUTT */
+ {
+ // butt is Svg default, so no need to write until the exporter might write styles.
+ // If this happens, activate here
+ // mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "butt");
+ break;
+ }
+ case css::drawing::LineCap_ROUND:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "round");
+ break;
+ }
+ case css::drawing::LineCap_SQUARE:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "square");
+ break;
+ }
+ }
+
+}
+
+
+void SVGActionWriter::ImplWritePolyPolygon( const tools::PolyPolygon& rPolyPoly, bool bLineOnly,
+ bool bApplyMapping )
+{
+ tools::PolyPolygon aPolyPoly;
+
+ if( bApplyMapping )
+ ImplMap( rPolyPoly, aPolyPoly );
+ else
+ aPolyPoly = rPolyPoly;
+
+ // add path data attribute
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "d", GetPathString( aPolyPoly, bLineOnly ) );
+
+ {
+ // write polyline/polygon element
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "path", true, true );
+ }
+}
+
+
+void SVGActionWriter::ImplWriteShape( const SVGShapeDescriptor& rShape )
+{
+ tools::PolyPolygon aPolyPoly;
+
+ ImplMap( rShape.maShapePolyPoly, aPolyPoly );
+
+ const bool bLineOnly
+ = (rShape.maShapeFillColor == COL_TRANSPARENT) && (!rShape.mapShapeGradient);
+ tools::Rectangle aBoundRect( aPolyPoly.GetBoundRect() );
+
+ maAttributeWriter.AddPaintAttr( rShape.maShapeLineColor, rShape.maShapeFillColor, &aBoundRect, rShape.mapShapeGradient.get() );
+
+ if( !rShape.maId.isEmpty() )
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, rShape.maId );
+
+ if( rShape.mnStrokeWidth )
+ {
+ sal_Int32 nStrokeWidth = ImplMap( rShape.mnStrokeWidth );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStrokeWidth, OUString::number( nStrokeWidth ) );
+ }
+
+ // support for LineJoin
+ switch(rShape.maLineJoin)
+ {
+ case basegfx::B2DLineJoin::NONE:
+ case basegfx::B2DLineJoin::Miter:
+ {
+ // miter is Svg default, so no need to write until the exporter might write styles.
+ // If this happens, activate here
+ // mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "miter");
+ break;
+ }
+ case basegfx::B2DLineJoin::Bevel:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "bevel");
+ break;
+ }
+ case basegfx::B2DLineJoin::Round:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "round");
+ break;
+ }
+ }
+
+ // support for LineCap
+ switch(rShape.maLineCap)
+ {
+ default: /* css::drawing::LineCap_BUTT */
+ {
+ // butt is Svg default, so no need to write until the exporter might write styles.
+ // If this happens, activate here
+ // mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "butt");
+ break;
+ }
+ case css::drawing::LineCap_ROUND:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "round");
+ break;
+ }
+ case css::drawing::LineCap_SQUARE:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "square");
+ break;
+ }
+ }
+
+ if( !rShape.maDashArray.empty() )
+ {
+ OUStringBuffer aDashArrayStr;
+
+ for( size_t k = 0; k < rShape.maDashArray.size(); ++k )
+ {
+ const sal_Int32 nDash = ImplMap( FRound( rShape.maDashArray[ k ] ) );
+
+ if( k )
+ aDashArrayStr.append(",");
+
+ aDashArrayStr.append( nDash );
+ }
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "stroke-dasharray", aDashArrayStr.makeStringAndClear() );
+ }
+
+ ImplWritePolyPolygon( aPolyPoly, bLineOnly, false );
+}
+
+
+
+void SVGActionWriter::ImplCreateClipPathDef( const tools::PolyPolygon& rPolyPoly )
+{
+ OUString aClipPathId = aPrefixClipPathId + OUString::number( mnCurClipPathId++ );
+
+ SvXMLElementExport aElemDefs( mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true );
+
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, aClipPathId );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "clipPathUnits", "userSpaceOnUse" );
+ SvXMLElementExport aElemClipPath( mrExport, XML_NAMESPACE_NONE, "clipPath", true, true );
+
+ ImplWritePolyPolygon(rPolyPoly, false);
+ }
+}
+
+void SVGActionWriter::ImplStartClipRegion(sal_Int32 nClipPathId)
+{
+ assert(!mpCurrentClipRegionElem);
+
+ if (nClipPathId == 0)
+ return;
+
+ OUString aUrl = OUString::Concat("url(#") + aPrefixClipPathId + OUString::number( nClipPathId ) + ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "clip-path", aUrl );
+ mpCurrentClipRegionElem.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true ) );
+}
+
+void SVGActionWriter::ImplEndClipRegion()
+{
+ if (mpCurrentClipRegionElem)
+ {
+ mpCurrentClipRegionElem.reset();
+ }
+}
+
+void SVGActionWriter::ImplWriteClipPath( const tools::PolyPolygon& rPolyPoly )
+{
+ ImplEndClipRegion();
+
+ if( rPolyPoly.Count() == 0 )
+ return;
+
+ ImplCreateClipPathDef(rPolyPoly);
+ mrCurrentState.nRegionClipPathId = mnCurClipPathId - 1;
+ ImplStartClipRegion( mrCurrentState.nRegionClipPathId );
+}
+
+void SVGActionWriter::ImplWritePattern( const tools::PolyPolygon& rPolyPoly,
+ const Hatch* pHatch,
+ const Gradient* pGradient,
+ sal_uInt32 nWriteFlags )
+{
+ if( !rPolyPoly.Count() )
+ return;
+
+ SvXMLElementExport aElemG( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ OUString aPatternId = "pattern" + OUString::number( mnCurPatternId++ );
+
+ {
+ SvXMLElementExport aElemDefs( mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, aPatternId );
+
+ tools::Rectangle aRect;
+ ImplMap( rPolyPoly.GetBoundRect(), aRect );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aRect.Left() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aRect.Top() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrWidth, OUString::number( aRect.GetWidth() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrHeight, OUString::number( aRect.GetHeight() ) );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "patternUnits", OUString( "userSpaceOnUse") );
+
+ {
+ SvXMLElementExport aElemPattern( mrExport, XML_NAMESPACE_NONE, "pattern", true, true );
+
+ // The origin of a pattern is positioned at (aRect.Left(), aRect.Top()).
+ // So we need to adjust the pattern coordinate.
+ OUString aTransform = "translate(" +
+ OUString::number( -aRect.Left() ) +
+ "," + OUString::number( -aRect.Top() ) +
+ ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTransform, aTransform );
+
+ {
+ SvXMLElementExport aElemG2( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ GDIMetaFile aTmpMtf;
+ if( pHatch )
+ {
+ mpVDev->AddHatchActions( rPolyPoly, *pHatch, aTmpMtf );
+ }
+ else if ( pGradient )
+ {
+ Gradient aGradient(*pGradient);
+ aGradient.AddGradientActions( rPolyPoly.GetBoundRect(), aTmpMtf );
+ }
+
+ ImplWriteActions( aTmpMtf, nWriteFlags, "" );
+ }
+ }
+ }
+
+ OUString aPatternStyle = "fill:url(#" + aPatternId + ")";
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStyle, aPatternStyle );
+ ImplWritePolyPolygon( rPolyPoly, false );
+}
+
+
+void SVGActionWriter::ImplWriteGradientEx( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient,
+ sal_uInt32 nWriteFlags)
+{
+ if ( rGradient.GetStyle() == GradientStyle::Linear ||
+ rGradient.GetStyle() == GradientStyle::Axial )
+ {
+ ImplWriteGradientLinear( rPolyPoly, rGradient );
+ }
+ else
+ {
+ ImplWritePattern( rPolyPoly, nullptr, &rGradient, nWriteFlags );
+ }
+}
+
+
+void SVGActionWriter::ImplWriteGradientLinear( const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGradient )
+{
+ if( !rPolyPoly.Count() )
+ return;
+
+ SvXMLElementExport aElemG( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ OUString aGradientId = "gradient" + OUString::number( mnCurGradientId++ );
+
+ {
+ SvXMLElementExport aElemDefs( mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, aGradientId );
+ {
+ tools::Rectangle aTmpRect, aRect;
+ Point aTmpCenter, aCenter;
+
+ rGradient.GetBoundRect( rPolyPoly.GetBoundRect(), aTmpRect, aTmpCenter );
+ ImplMap( aTmpRect, aRect );
+ ImplMap( aTmpCenter, aCenter );
+ const Degree10 nAngle = rGradient.GetAngle() % 3600_deg10;
+
+ tools::Polygon aPoly( 2 );
+ // Setting x value of a gradient vector to rotation center to
+ // place a gradient vector in a target polygon.
+ // This would help editing it in SVG editors like inkscape.
+ aPoly[ 0 ].setX( aCenter.X() );
+ aPoly[ 1 ].setX( aCenter.X() );
+ aPoly[ 0 ].setY( aRect.Top() );
+ aPoly[ 1 ].setY( aRect.Bottom() );
+ aPoly.Rotate( aCenter, nAngle );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX1, OUString::number( aPoly[ 0 ].X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY1, OUString::number( aPoly[ 0 ].Y() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX2, OUString::number( aPoly[ 1 ].X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY2, OUString::number( aPoly[ 1 ].Y() ) );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrGradientUnits,
+ OUString( "userSpaceOnUse" ) );
+ }
+
+ {
+ SvXMLElementExport aElemLinearGradient( mrExport, XML_NAMESPACE_NONE, aXMLElemLinearGradient, true, true );
+
+ const Color aStartColor = ImplGetColorWithIntensity( rGradient.GetStartColor(), rGradient.GetStartIntensity() );
+ const Color aEndColor = ImplGetColorWithIntensity( rGradient.GetEndColor(), rGradient.GetEndIntensity() );
+ double fBorderOffset = rGradient.GetBorder() / 100.0;
+ const sal_uInt16 nSteps = rGradient.GetSteps();
+ if( rGradient.GetStyle() == GradientStyle::Linear )
+ {
+ // Emulate non-smooth gradient
+ if( 0 < nSteps && nSteps < 100 )
+ {
+ double fOffsetStep = ( 1.0 - fBorderOffset ) / static_cast<double>(nSteps);
+ for( sal_uInt16 i = 0; i < nSteps; i++ ) {
+ Color aColor = ImplGetGradientColor( aStartColor, aEndColor, i / static_cast<double>(nSteps) );
+ ImplWriteGradientStop( aColor, fBorderOffset + ( i + 1 ) * fOffsetStep );
+ aColor = ImplGetGradientColor( aStartColor, aEndColor, ( i + 1 ) / static_cast<double>(nSteps) );
+ ImplWriteGradientStop( aColor, fBorderOffset + ( i + 1 ) * fOffsetStep );
+ }
+ }
+ else
+ {
+ ImplWriteGradientStop( aStartColor, fBorderOffset );
+ ImplWriteGradientStop( aEndColor, 1.0 );
+ }
+ }
+ else
+ {
+ fBorderOffset /= 2;
+ // Emulate non-smooth gradient
+ if( 0 < nSteps && nSteps < 100 )
+ {
+ double fOffsetStep = ( 0.5 - fBorderOffset ) / static_cast<double>(nSteps);
+ // Upper half
+ for( sal_uInt16 i = 0; i < nSteps; i++ )
+ {
+ Color aColor = ImplGetGradientColor( aEndColor, aStartColor, i / static_cast<double>(nSteps) );
+ ImplWriteGradientStop( aColor, fBorderOffset + i * fOffsetStep );
+ aColor = ImplGetGradientColor( aEndColor, aStartColor, (i + 1 ) / static_cast<double>(nSteps) );
+ ImplWriteGradientStop( aColor, fBorderOffset + i * fOffsetStep );
+ }
+ // Lower half
+ for( sal_uInt16 i = 0; i < nSteps; i++ )
+ {
+ Color aColor = ImplGetGradientColor( aStartColor, aEndColor, i / static_cast<double>(nSteps) );
+ ImplWriteGradientStop( aColor, 0.5 + (i + 1) * fOffsetStep );
+ aColor = ImplGetGradientColor( aStartColor, aEndColor, (i + 1 ) / static_cast<double>(nSteps) );
+ ImplWriteGradientStop( aColor, 0.5 + (i + 1) * fOffsetStep );
+ }
+ }
+ else
+ {
+ ImplWriteGradientStop( aEndColor, fBorderOffset );
+ ImplWriteGradientStop( aStartColor, 0.5 );
+ ImplWriteGradientStop( aEndColor, 1.0 - fBorderOffset );
+ }
+ }
+ }
+ }
+
+ OUString aGradientStyle = "fill:url(#" + aGradientId + ")";
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStyle, aGradientStyle );
+ ImplWritePolyPolygon( rPolyPoly, false );
+}
+
+
+void SVGActionWriter::ImplWriteGradientStop( const Color& rColor, double fOffset )
+{
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( fOffset ) );
+
+ OUString aStyle, aColor;
+ aStyle += "stop-color:";
+ SVGAttributeWriter::ImplGetColorStr ( rColor, aColor );
+ aStyle += aColor;
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStyle, aStyle );
+ {
+ SvXMLElementExport aElemStartStop( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true );
+ }
+}
+
+
+Color SVGActionWriter::ImplGetColorWithIntensity( const Color& rColor,
+ sal_uInt16 nIntensity )
+{
+ sal_uInt8 nNewRed = static_cast<sal_uInt8>( static_cast<tools::Long>(rColor.GetRed()) * nIntensity / 100 );
+ sal_uInt8 nNewGreen = static_cast<sal_uInt8>( static_cast<tools::Long>(rColor.GetGreen()) * nIntensity / 100 );
+ sal_uInt8 nNewBlue = static_cast<sal_uInt8>( static_cast<tools::Long>(rColor.GetBlue()) * nIntensity / 100 );
+ return Color( nNewRed, nNewGreen, nNewBlue);
+}
+
+
+Color SVGActionWriter::ImplGetGradientColor( const Color& rStartColor,
+ const Color& rEndColor,
+ double fOffset )
+{
+ tools::Long nRedStep = rEndColor.GetRed() - rStartColor.GetRed();
+ tools::Long nNewRed = rStartColor.GetRed() + static_cast<tools::Long>( nRedStep * fOffset );
+ nNewRed = ( nNewRed < 0 ) ? 0 : ( nNewRed > 0xFF) ? 0xFF : nNewRed;
+
+ tools::Long nGreenStep = rEndColor.GetGreen() - rStartColor.GetGreen();
+ tools::Long nNewGreen = rStartColor.GetGreen() + static_cast<tools::Long>( nGreenStep * fOffset );
+ nNewGreen = ( nNewGreen < 0 ) ? 0 : ( nNewGreen > 0xFF) ? 0xFF : nNewGreen;
+
+ tools::Long nBlueStep = rEndColor.GetBlue() - rStartColor.GetBlue();
+ tools::Long nNewBlue = rStartColor.GetBlue() + static_cast<tools::Long>( nBlueStep * fOffset );
+ nNewBlue = ( nNewBlue < 0 ) ? 0 : ( nNewBlue > 0xFF) ? 0xFF : nNewBlue;
+
+ return Color( static_cast<sal_uInt8>(nNewRed), static_cast<sal_uInt8>(nNewGreen), static_cast<sal_uInt8>(nNewBlue) );
+}
+
+void SVGActionWriter::StartMask(const Point& rDestPt, const Size& rDestSize,
+ const Gradient& rGradient, sal_uInt32 nWriteFlags,
+ OUString* pTextFillOpacity)
+{
+ OUString aStyle;
+ if (rGradient.GetStartColor() == rGradient.GetEndColor())
+ {
+ // Special case: constant alpha value.
+ const Color& rColor = rGradient.GetStartColor();
+ const double fOpacity = 1.0 - static_cast<double>(rColor.GetLuminance()) / 255;
+ if (pTextFillOpacity)
+ {
+ // Don't write anything, return what is a value suitable for <tspan fill-opacity="...">.
+ *pTextFillOpacity = OUString::number(fOpacity);
+ return;
+ }
+ else
+ {
+ aStyle = "opacity: " + OUString::number(fOpacity);
+ }
+ }
+ else
+ {
+ OUString aMaskId = "mask" + OUString::number(mnCurMaskId++);
+
+ {
+ SvXMLElementExport aElemDefs(mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true);
+
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrId, aMaskId);
+ {
+ SvXMLElementExport aElemMask(mrExport, XML_NAMESPACE_NONE, "mask", true, true);
+
+ const tools::PolyPolygon aPolyPolygon(tools::PolyPolygon(tools::Rectangle(rDestPt, rDestSize)));
+ Gradient aGradient(rGradient);
+
+ // swap gradient stops to adopt SVG mask
+ Color aTmpColor(aGradient.GetStartColor());
+ sal_uInt16 nTmpIntensity(aGradient.GetStartIntensity());
+ aGradient.SetStartColor(aGradient.GetEndColor());
+ aGradient.SetStartIntensity(aGradient.GetEndIntensity());
+ aGradient.SetEndColor(aTmpColor);
+ aGradient.SetEndIntensity(nTmpIntensity);
+
+ ImplWriteGradientEx(aPolyPolygon, aGradient, nWriteFlags);
+ }
+ }
+
+ aStyle = "mask:url(#" + aMaskId + ")";
+ }
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStyle, aStyle);
+}
+
+void SVGActionWriter::ImplWriteMask(GDIMetaFile& rMtf, const Point& rDestPt, const Size& rDestSize,
+ const Gradient& rGradient, sal_uInt32 nWriteFlags)
+{
+ Point aSrcPt(rMtf.GetPrefMapMode().GetOrigin());
+ const Size aSrcSize(rMtf.GetPrefSize());
+ const double fScaleX
+ = aSrcSize.Width() ? static_cast<double>(rDestSize.Width()) / aSrcSize.Width() : 1.0;
+ const double fScaleY
+ = aSrcSize.Height() ? static_cast<double>(rDestSize.Height()) / aSrcSize.Height() : 1.0;
+ tools::Long nMoveX, nMoveY;
+
+ if (fScaleX != 1.0 || fScaleY != 1.0)
+ {
+ rMtf.Scale(fScaleX, fScaleY);
+ aSrcPt.setX(FRound(aSrcPt.X() * fScaleX));
+ aSrcPt.setY(FRound(aSrcPt.Y() * fScaleY));
+ }
+
+ nMoveX = rDestPt.X() - aSrcPt.X();
+ nMoveY = rDestPt.Y() - aSrcPt.Y();
+
+ if (nMoveX || nMoveY)
+ rMtf.Move(nMoveX, nMoveY);
+
+ {
+ std::unique_ptr<SvXMLElementExport> pElemG;
+ if (!maTextWriter.hasTextOpacity())
+ {
+ StartMask(rDestPt, rDestSize, rGradient, nWriteFlags);
+ pElemG.reset(
+ new SvXMLElementExport(mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true));
+ }
+
+ mpVDev->Push();
+ ImplWriteActions( rMtf, nWriteFlags, "" );
+ mpVDev->Pop();
+ }
+}
+
+
+void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText,
+ o3tl::span<const sal_Int32> pDXArray, tools::Long nWidth )
+{
+ const FontMetric aMetric( mpVDev->GetFontMetric() );
+
+ bool bTextSpecial = aMetric.IsShadow() || aMetric.IsOutline() || (aMetric.GetRelief() != FontRelief::NONE);
+
+ if( !bTextSpecial )
+ {
+ ImplWriteText( rPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ }
+ else
+ {
+ if( aMetric.GetRelief() != FontRelief::NONE )
+ {
+ Color aReliefColor( COL_LIGHTGRAY );
+ Color aTextColor( mpVDev->GetTextColor() );
+
+ if ( aTextColor == COL_BLACK )
+ aTextColor = COL_WHITE;
+
+ // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct
+ if (aTextColor == COL_WHITE)
+ aReliefColor = COL_BLACK;
+
+
+ Point aPos( rPos );
+ Point aOffset( 6, 6 );
+
+ if ( aMetric.GetRelief() == FontRelief::Engraved )
+ {
+ aPos -= aOffset;
+ }
+ else
+ {
+ aPos += aOffset;
+ }
+
+ ImplWriteText( aPos, rText, pDXArray, nWidth, aReliefColor );
+ ImplWriteText( rPos, rText, pDXArray, nWidth, aTextColor );
+ }
+ else
+ {
+ if( aMetric.IsShadow() )
+ {
+ tools::Long nOff = 1 + ((aMetric.GetLineHeight()-24)/24);
+ if ( aMetric.IsOutline() )
+ nOff += 6;
+
+ Color aTextColor( mpVDev->GetTextColor() );
+ Color aShadowColor( COL_BLACK );
+
+ if ( (aTextColor == COL_BLACK) || (aTextColor.GetLuminance() < 8) )
+ aShadowColor = COL_LIGHTGRAY;
+
+ Point aPos( rPos );
+ aPos += Point( nOff, nOff );
+ ImplWriteText( aPos, rText, pDXArray, nWidth, aShadowColor );
+
+ if( !aMetric.IsOutline() )
+ {
+ ImplWriteText( rPos, rText, pDXArray, nWidth, aTextColor );
+ }
+ }
+
+ if( aMetric.IsOutline() )
+ {
+ Point aPos = rPos + Point( -6, -6 );
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ aPos = rPos + Point( +6, +6);
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ aPos = rPos + Point( -6, +0);
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ aPos = rPos + Point( -6, +6);
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ aPos = rPos + Point( +0, +6);
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ aPos = rPos + Point( +0, -6);
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ aPos = rPos + Point( +6, -1);
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ aPos = rPos + Point( +6, +0);
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+
+ ImplWriteText( rPos, rText, pDXArray, nWidth, COL_WHITE );
+ }
+ }
+ }
+}
+
+
+void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText,
+ o3tl::span<const sal_Int32> pDXArray, tools::Long nWidth,
+ Color aTextColor )
+{
+ sal_Int32 nLen = rText.getLength();
+ Size aNormSize;
+ Point aPos;
+ Point aBaseLinePos( rPos );
+ const FontMetric aMetric( mpVDev->GetFontMetric() );
+ const vcl::Font& rFont = mpVDev->GetFont();
+
+ if( rFont.GetAlignment() == ALIGN_TOP )
+ aBaseLinePos.AdjustY(aMetric.GetAscent() );
+ else if( rFont.GetAlignment() == ALIGN_BOTTOM )
+ aBaseLinePos.AdjustY( -(aMetric.GetDescent()) );
+
+ ImplMap( rPos, aPos );
+
+ std::vector<sal_Int32> aTmpArray(nLen);
+ // get text sizes
+ if( !pDXArray.empty() )
+ {
+ aNormSize = Size( mpVDev->GetTextWidth( rText ), 0 );
+ memcpy(aTmpArray.data(), pDXArray.data(), nLen * sizeof(sal_Int32));
+ }
+ else
+ {
+ aNormSize = Size( mpVDev->GetTextArray( rText, &aTmpArray ), 0 );
+ }
+ sal_Int32* pDX = aTmpArray.data();
+
+ // if text is rotated, set transform matrix at new g element
+ if( rFont.GetOrientation() )
+ {
+ Point aRot( aPos );
+ OUString aTransform = "rotate(" +
+ OUString::number( rFont.GetOrientation().get() * -0.1 ) + " " +
+ OUString::number( aRot.X() ) + " " +
+ OUString::number( aRot.Y() ) + ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTransform, aTransform );
+ }
+
+
+ maAttributeWriter.AddPaintAttr( COL_TRANSPARENT, aTextColor );
+
+ // for each line of text there should be at least one group element
+ SvXMLElementExport aSVGGElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, false );
+
+ bool bIsPlaceholderField = false;
+
+ if( mbIsPlaceholderShape )
+ {
+ bIsPlaceholderField = rText.match( sPlaceholderTag );
+ // for a placeholder text field we export only one <text> svg element
+ if( bIsPlaceholderField )
+ {
+ OUString sCleanTextContent;
+ static const sal_Int32 nFrom = sPlaceholderTag.getLength();
+ if( rText.getLength() > nFrom )
+ {
+ sCleanTextContent = rText.copy( nFrom );
+ }
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "PlaceholderText" );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aPos.X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPos.Y() ) );
+ {
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, aXMLElemText, true, false );
+ // At least for the single slide case we need really to export placeholder text
+ mrExport.GetDocHandler()->characters( sCleanTextContent );
+ }
+ }
+ }
+
+ if( !bIsPlaceholderField )
+ {
+ if( nLen > 1 )
+ {
+ aNormSize.setWidth( pDX[ nLen - 2 ] + mpVDev->GetTextWidth( OUString(rText[nLen - 1]) ) );
+
+ if( nWidth && aNormSize.Width() && ( nWidth != aNormSize.Width() ) )
+ {
+ tools::Long i;
+ const double fFactor = static_cast<double>(nWidth) / aNormSize.Width();
+
+ for( i = 0; i < ( nLen - 1 ); i++ )
+ pDX[ i ] = FRound( pDX[ i ] * fFactor );
+ }
+ else
+ {
+ css::uno::Reference< css::i18n::XBreakIterator > xBI( vcl::unohelper::CreateBreakIterator() );
+ const css::lang::Locale& rLocale = Application::GetSettings().GetLanguageTag().getLocale();
+ sal_Int32 nCurPos = 0, nLastPos = 0, nX = aPos.X();
+
+ // write single glyphs at absolute text positions
+ for( bool bCont = true; bCont; )
+ {
+ sal_Int32 nCount = 1;
+
+ nLastPos = nCurPos;
+ nCurPos = xBI->nextCharacters( rText, nCurPos, rLocale,
+ css::i18n::CharacterIteratorMode::SKIPCELL,
+ nCount, nCount );
+
+ nCount = nCurPos - nLastPos;
+ bCont = ( nCurPos < rText.getLength() ) && nCount;
+
+ if( nCount )
+ {
+ const OUString aGlyph( rText.copy( nLastPos, nCount ) );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( nX ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPos.Y() ) );
+
+ {
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, aXMLElemText, true, false );
+ mrExport.GetDocHandler()->characters( aGlyph );
+ }
+
+ if( bCont )
+ {
+ // #118796# do NOT access pDXArray, it may be zero (!)
+ sal_Int32 nDXWidth = pDX[ nCurPos - 1 ];
+ nDXWidth = ImplMap( nDXWidth );
+ nX = aPos.X() + nDXWidth;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aPos.X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPos.Y() ) );
+
+ {
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, aXMLElemText, true, false );
+ mrExport.GetDocHandler()->characters( rText );
+ }
+ }
+ }
+
+
+ if( mrExport.IsUseNativeTextDecoration() )
+ return;
+
+ if( rFont.GetStrikeout() == STRIKEOUT_NONE && rFont.GetUnderline() == LINESTYLE_NONE )
+ return;
+
+ tools::Polygon aPoly( 4 );
+ const tools::Long nLineHeight = std::max<tools::Long>( FRound( aMetric.GetLineHeight() * 0.05 ), 1 );
+
+ if( rFont.GetStrikeout() )
+ {
+ const tools::Long nYLinePos = aBaseLinePos.Y() - FRound( aMetric.GetAscent() * 0.26 );
+
+ aPoly[ 0 ].setX( aBaseLinePos.X() ); aPoly[ 0 ].setY( nYLinePos - ( nLineHeight >> 1 ) );
+ aPoly[ 1 ].setX( aBaseLinePos.X() + aNormSize.Width() - 1 ); aPoly[ 1 ].setY( aPoly[ 0 ].Y() );
+ aPoly[ 2 ].setX( aPoly[ 1 ].X() ); aPoly[ 2 ].setY( aPoly[ 0 ].Y() + nLineHeight - 1 );
+ aPoly[ 3 ].setX( aPoly[ 0 ].X() ); aPoly[ 3 ].setY( aPoly[ 2 ].Y() );
+
+ ImplWritePolyPolygon( aPoly, false );
+ }
+
+ if( rFont.GetUnderline() )
+ {
+ const tools::Long nYLinePos = aBaseLinePos.Y() + ( nLineHeight << 1 );
+
+ aPoly[ 0 ].setX( aBaseLinePos.X() ); aPoly[ 0 ].setY( nYLinePos - ( nLineHeight >> 1 ) );
+ aPoly[ 1 ].setX( aBaseLinePos.X() + aNormSize.Width() - 1 ); aPoly[ 1 ].setY( aPoly[ 0 ].Y() );
+ aPoly[ 2 ].setX( aPoly[ 1 ].X() ); aPoly[ 2 ].setY( aPoly[ 0 ].Y() + nLineHeight - 1 );
+ aPoly[ 3 ].setX( aPoly[ 0 ].X() ); aPoly[ 3 ].setY( aPoly[ 2 ].Y() );
+
+ ImplWritePolyPolygon( aPoly, false );
+ }
+}
+
+namespace
+{
+void GetGraphicFromXShape(const css::uno::Reference<css::drawing::XShape>* pShape, Graphic& rGraphic)
+{
+ if (!pShape)
+ {
+ return;
+ }
+
+ uno::Reference<beans::XPropertySet> xPropertySet(*pShape, uno::UNO_QUERY);
+ if (!xPropertySet.is())
+ {
+ return;
+ }
+
+ uno::Reference<graphic::XGraphic> xGraphic;
+ if (xPropertySet->getPropertySetInfo()->hasPropertyByName("Graphic"))
+ {
+ xPropertySet->getPropertyValue("Graphic") >>= xGraphic;
+ }
+ rGraphic= Graphic(xGraphic);
+}
+}
+
+void SVGActionWriter::ImplWriteBmp( const BitmapEx& rBmpEx,
+ const Point& rPt, const Size& rSz,
+ const Point& rSrcPt, const Size& rSrcSz,
+ const css::uno::Reference<css::drawing::XShape>* pShape )
+{
+ if( rBmpEx.IsEmpty() )
+ return;
+ if( mpEmbeddedBitmapsMap && !mpEmbeddedBitmapsMap->empty())
+ {
+ BitmapChecksum nChecksum = rBmpEx.GetChecksum();
+ if( mpEmbeddedBitmapsMap->find( nChecksum ) != mpEmbeddedBitmapsMap->end() )
+ {
+ // <use transform="translate(?) scale(?)" xlink:ref="?" >
+ OUString sTransform;
+
+ Point aPoint;
+ ImplMap( rPt, aPoint );
+ if( aPoint.X() != 0 || aPoint.Y() != 0 )
+ sTransform = "translate(" + OUString::number( aPoint.X() ) + ", " + OUString::number( aPoint.Y() ) + ")";
+
+ Size aSize;
+ ImplMap( rSz, aSize );
+
+ MapMode aSourceMode( MapUnit::MapPixel );
+ Size aPrefSize = OutputDevice::LogicToLogic( rSrcSz, aSourceMode, maTargetMapMode );
+ Fraction aFractionX( aSize.Width(), aPrefSize.Width() );
+ Fraction aFractionY( aSize.Height(), aPrefSize.Height() );
+ double scaleX = rtl_math_round( double(aFractionX), 3, rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected );
+ double scaleY = rtl_math_round( double(aFractionY), 3, rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected );
+ if( !rtl_math_approxEqual( scaleX, 1.0 ) || !rtl_math_approxEqual( scaleY, 1.0 ) )
+ sTransform += " scale(" + OUString::number( double(aFractionX) ) + ", " + OUString::number( double(aFractionY) ) + ")";
+
+ if( !sTransform.isEmpty() )
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTransform, sTransform );
+
+ // referenced bitmap template
+ OUString sRefId = "#bitmap(" + OUString::number( nChecksum ) + ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId );
+
+ SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true );
+ return;
+ }
+ }
+
+ BitmapEx aBmpEx( rBmpEx );
+ const tools::Rectangle aBmpRect( Point(), rBmpEx.GetSizePixel() );
+ const tools::Rectangle aSrcRect( rSrcPt, rSrcSz );
+
+ if( aSrcRect != aBmpRect )
+ aBmpEx.Crop( aSrcRect );
+
+ if( aBmpEx.IsEmpty() )
+ return;
+
+ SvMemoryStream aOStm( 65535, 65535 );
+
+ bool bCached = false;
+ Graphic aGraphic;
+ bool bJPG = false;
+ if (pShape)
+ {
+ GetGraphicFromXShape(pShape, aGraphic);
+ if (aGraphic.GetType() == GraphicType::Bitmap)
+ {
+ const BitmapEx& rGraphicBitmap = aGraphic.GetBitmapExRef();
+ if (rGraphicBitmap.GetChecksum() == rBmpEx.GetChecksum())
+ {
+ bool bPNG = false;
+ GfxLink aGfxLink = aGraphic.GetGfxLink();
+ if (aGfxLink.GetType() == GfxLinkType::NativePng)
+ {
+ bPNG = true;
+ }
+ else if (aGfxLink.GetType() == GfxLinkType::NativeJpg)
+ {
+ bJPG = true;
+ }
+ if (bPNG || bJPG)
+ {
+ aOStm.WriteBytes(aGfxLink.GetData(), aGfxLink.GetDataSize());
+ bCached = true;
+ }
+ }
+ }
+ }
+
+ const BitmapEx* pBitmap = &rBmpEx;
+ std::unique_ptr<BitmapEx> pNewBitmap;
+
+ // for preview we generate downscaled images (1280x720 max)
+ if (mbIsPreview)
+ {
+ Size aSize = rBmpEx.GetSizePixel();
+ double fX = static_cast<double>(aSize.getWidth()) / 1280;
+ double fY = static_cast<double>(aSize.getHeight()) / 720;
+ double fFactor = fX > fY ? fX : fY;
+ if (fFactor > 1.0)
+ {
+ aSize.setWidth(aSize.getWidth() / fFactor);
+ aSize.setHeight(aSize.getHeight() / fFactor);
+ pNewBitmap = std::make_unique<BitmapEx>(rBmpEx);
+ pNewBitmap->Scale(aSize);
+ pBitmap = pNewBitmap.get();
+ }
+ }
+
+ if( !(bCached || GraphicConverter::Export( aOStm, *pBitmap, ConvertDataFormat::PNG ) == ERRCODE_NONE) )
+ return;
+
+ Point aPt;
+ Size aSz;
+ Sequence< sal_Int8 > aSeq( static_cast<sal_Int8 const *>(aOStm.GetData()), aOStm.Tell() );
+ OUStringBuffer aBuffer;
+ if (bJPG)
+ {
+ aBuffer.append("data:image/jpeg;base64,");
+ }
+ else
+ {
+ aBuffer.append("data:image/png;base64,");
+ }
+ ::comphelper::Base64::encode( aBuffer, aSeq );
+
+ ImplMap( rPt, aPt );
+ ImplMap( rSz, aSz );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aPt.X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPt.Y() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrWidth, OUString::number( aSz.Width() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrHeight, OUString::number( aSz.Height() ) );
+
+ // the image must be scaled to aSz in a non-uniform way
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "preserveAspectRatio", "none" );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, aBuffer.makeStringAndClear() );
+ {
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "image", true, true );
+ }
+}
+
+
+void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf,
+ sal_uInt32 nWriteFlags,
+ const OUString& aElementId,
+ const Reference< css::drawing::XShape >* pxShape,
+ const GDIMetaFile* pTextEmbeddedBitmapMtf )
+{
+ // need a counter for the actions written per shape to avoid double ID
+ // generation
+ sal_Int32 nEntryCount(0);
+
+ bool bUseElementId = !aElementId.isEmpty();
+
+#if OSL_DEBUG_LEVEL > 0
+ bool bIsTextShape = false;
+ if( !mrExport.IsUsePositionedCharacters() && pxShape
+ && Reference< XText >( *pxShape, UNO_QUERY ).is() )
+ {
+ bIsTextShape = true;
+ }
+#endif
+ mbIsPlaceholderShape = false;
+ if( bUseElementId && ( aElementId == sPlaceholderTag ) )
+ {
+ mbIsPlaceholderShape = true;
+ // since we utilize aElementId in an improper way we reset the boolean
+ // control variable bUseElementId to false before to go on
+ bUseElementId = false;
+ }
+
+ for( size_t nCurAction = 0, nCount = rMtf.GetActionSize(); nCurAction < nCount; nCurAction++ )
+ {
+ const MetaAction* pAction = rMtf.GetAction( nCurAction );
+ const MetaActionType nType = pAction->GetType();
+
+#if OSL_DEBUG_LEVEL > 0
+ if( bIsTextShape )
+ {
+ try
+ {
+ SvXMLElementExport aElem( mrExport,
+ XML_NAMESPACE_NONE, "desc", false, false );
+ OUStringBuffer sType(OUString::number(static_cast<sal_uInt16>(nType)));
+ if (pAction && (nType == MetaActionType::COMMENT))
+ {
+ sType.append(": ");
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
+ OString sComment = pA->GetComment();
+ if (!sComment.isEmpty())
+ {
+ sType.append(OStringToOUString(
+ sComment, RTL_TEXTENCODING_UTF8));
+ }
+ if (sComment.equalsIgnoreAsciiCase("FIELD_SEQ_BEGIN"))
+ {
+ sal_uInt8 const*const pData = pA->GetData();
+ if (pData && (pA->GetDataSize()))
+ {
+ sal_uInt16 sz = static_cast<sal_uInt16>((pA->GetDataSize()) / 2);
+ if (sz)
+ {
+ sType.append("; ");
+ sType.append(
+ reinterpret_cast<sal_Unicode const*>(pData),
+ sz);
+ }
+ }
+ }
+ }
+ if (sType.getLength())
+ {
+ mrExport.GetDocHandler()->characters(
+ sType.makeStringAndClear());
+ }
+ }
+ catch( ... )
+ {
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
+ SAL_WARN( "filter.svg", pA->GetComment() );
+ }
+
+ }
+#endif
+ switch( nType )
+ {
+ case MetaActionType::PIXEL:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaPixelAction* pA = static_cast<const MetaPixelAction*>(pAction);
+
+ maAttributeWriter.AddPaintAttr( pA->GetColor(), pA->GetColor() );
+ ImplWriteLine( pA->GetPoint(), pA->GetPoint(), &pA->GetColor() );
+ }
+ }
+ break;
+
+ case MetaActionType::POINT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaPointAction* pA = static_cast<const MetaPointAction*>(pAction);
+
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetLineColor() );
+ ImplWriteLine( pA->GetPoint(), pA->GetPoint() );
+ }
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaLineAction* pA = static_cast<const MetaLineAction*>(pAction);
+
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetLineColor() );
+ ImplWriteLine( pA->GetStartPoint(), pA->GetEndPoint() );
+ }
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() );
+ ImplWriteRect( static_cast<const MetaRectAction*>(pAction)->GetRect() );
+ }
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pAction);
+
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() );
+ ImplWriteRect( pA->GetRect(), pA->GetHorzRound(), pA->GetVertRound() );
+ }
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() );
+ ImplWriteEllipse( rRect.Center(), rRect.GetWidth() >> 1, rRect.GetHeight() >> 1 );
+ }
+ }
+ break;
+
+ case MetaActionType::ARC:
+ case MetaActionType::PIE:
+ case MetaActionType::CHORD:
+ case MetaActionType::POLYGON:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ tools::Polygon aPoly;
+
+ switch( nType )
+ {
+ case MetaActionType::ARC:
+ {
+ const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction);
+ aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Arc );
+ }
+ break;
+
+ case MetaActionType::PIE:
+ {
+ const MetaPieAction* pA = static_cast<const MetaPieAction*>(pAction);
+ aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Pie );
+ }
+ break;
+
+ case MetaActionType::CHORD:
+ {
+ const MetaChordAction* pA = static_cast<const MetaChordAction*>(pAction);
+ aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Chord );
+ }
+ break;
+
+ case MetaActionType::POLYGON:
+ aPoly = static_cast<const MetaPolygonAction*>(pAction)->GetPolygon();
+ break;
+ default: break;
+ }
+
+ if( aPoly.GetSize() )
+ {
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() );
+ ImplWritePolyPolygon( aPoly, false );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pAction);
+ const tools::Polygon& rPoly = pA->GetPolygon();
+
+ if( rPoly.GetSize() )
+ {
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), COL_TRANSPARENT );
+ ImplAddLineAttr( pA->GetLineInfo() );
+ ImplWritePolyPolygon( rPoly, true );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaPolyPolygonAction* pA = static_cast<const MetaPolyPolygonAction*>(pAction);
+ const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon();
+
+ if( rPolyPoly.Count() )
+ {
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() );
+ ImplWritePolyPolygon( rPolyPoly, false );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pAction);
+ const tools::Polygon aRectPoly( pA->GetRect() );
+ const tools::PolyPolygon aRectPolyPoly( aRectPoly );
+
+ ImplWriteGradientEx( aRectPolyPoly, pA->GetGradient(), nWriteFlags );
+ }
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaGradientExAction* pA = static_cast<const MetaGradientExAction*>(pAction);
+ ImplWriteGradientEx( pA->GetPolyPolygon(), pA->GetGradient(), nWriteFlags );
+ }
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pAction);
+ ImplWritePattern( pA->GetPolyPolygon(), &pA->GetHatch(), nullptr, nWriteFlags );
+ }
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaTransparentAction* pA = static_cast<const MetaTransparentAction*>(pAction);
+ const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon();
+
+ if( rPolyPoly.Count() )
+ {
+ Color aNewLineColor( mpVDev->GetLineColor() ), aNewFillColor( mpVDev->GetFillColor() );
+
+ // tdf#149800 do not change transparency of fully transparent
+ // i.e. invisible line, because it makes it visible,
+ // resulting an extra line behind the normal shape line
+ if ( aNewLineColor.GetAlpha() > 0 )
+ aNewLineColor.SetAlpha( 255 - sal::static_int_cast<sal_uInt8>( FRound( pA->GetTransparence() * 2.55 ) ) );
+ aNewFillColor.SetAlpha( 255 - sal::static_int_cast<sal_uInt8>( FRound( pA->GetTransparence() * 2.55 ) ) );
+
+ maAttributeWriter.AddPaintAttr( aNewLineColor, aNewFillColor );
+ ImplWritePolyPolygon( rPolyPoly, false );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pAction);
+ GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() );
+ ImplWriteMask( aTmpMtf, pA->GetPoint(), pA->GetSize(),
+ pA->GetGradient(), nWriteFlags );
+ }
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction);
+ const GDIMetaFile& aGDIMetaFile( pA->GetSubstitute() );
+ bool bFound = false;
+
+ for( sal_uInt32 k = 0, nCount2 = aGDIMetaFile.GetActionSize(); ( k < nCount2 ) && !bFound; ++k )
+ {
+ const MetaAction* pSubstAct = aGDIMetaFile.GetAction( k );
+
+ if( pSubstAct->GetType() == MetaActionType::BMPSCALE )
+ {
+ bFound = true;
+ const MetaBmpScaleAction* pBmpScaleAction = static_cast<const MetaBmpScaleAction*>(pSubstAct);
+ ImplWriteBmp( BitmapEx(pBmpScaleAction->GetBitmap()),
+ pA->GetPoint(), pA->GetSize(),
+ Point(), pBmpScaleAction->GetBitmap().GetSizePixel(), pxShape );
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::COMMENT:
+ {
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
+
+ if( ( pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN") ) &&
+ ( nWriteFlags & SVGWRITER_WRITE_FILL ) )
+ {
+ const MetaGradientExAction* pGradAction = nullptr;
+ bool bDone = false;
+
+ while( !bDone && ( ++nCurAction < nCount ) )
+ {
+ pAction = rMtf.GetAction( nCurAction );
+
+ if( pAction->GetType() == MetaActionType::GRADIENTEX )
+ pGradAction = static_cast<const MetaGradientExAction*>(pAction);
+ else if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>( pAction )->GetComment().
+ equalsIgnoreAsciiCase("XGRAD_SEQ_END") ) )
+ {
+ bDone = true;
+ }
+ }
+
+ if( pGradAction )
+ ImplWriteGradientEx( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), nWriteFlags );
+ }
+ else if( ( pA->GetComment().equalsIgnoreAsciiCase("XPATHFILL_SEQ_BEGIN") ) &&
+ ( nWriteFlags & SVGWRITER_WRITE_FILL ) && !( nWriteFlags & SVGWRITER_NO_SHAPE_COMMENTS ) &&
+ pA->GetDataSize() )
+ {
+ // write open shape in every case
+ if (mapCurShape)
+ {
+ ImplWriteShape( *mapCurShape );
+ mapCurShape.reset();
+ }
+
+ SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ );
+ SvtGraphicFill aFill;
+
+ ReadSvtGraphicFill( aMemStm, aFill );
+
+ bool bGradient = SvtGraphicFill::fillGradient == aFill.getFillType() &&
+ ( SvtGraphicFill::GradientType::Linear == aFill.getGradientType() ||
+ SvtGraphicFill::GradientType::Radial == aFill.getGradientType() );
+ bool bSkip = ( SvtGraphicFill::fillSolid == aFill.getFillType() || bGradient );
+
+ if( bSkip )
+ {
+ tools::PolyPolygon aShapePolyPoly;
+
+ aFill.getPath( aShapePolyPoly );
+
+ if( aShapePolyPoly.Count() )
+ {
+ mapCurShape.reset( new SVGShapeDescriptor );
+
+ if( bUseElementId )
+ {
+ mapCurShape->maId = aElementId + "_" + OUString::number(nEntryCount++);
+ }
+
+ mapCurShape->maShapePolyPoly = aShapePolyPoly;
+ mapCurShape->maShapeFillColor = aFill.getFillColor();
+ mapCurShape->maShapeFillColor.SetAlpha( 255 - static_cast<sal_uInt8>(FRound( 255.0 * aFill.getTransparency() )) );
+
+ if( bGradient )
+ {
+ // step through following actions until the first Gradient/GradientEx action is found
+ while (!mapCurShape->mapShapeGradient && bSkip
+ && (++nCurAction < nCount))
+ {
+ pAction = rMtf.GetAction( nCurAction );
+
+ if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>(pAction)->GetComment().
+ equalsIgnoreAsciiCase("XPATHFILL_SEQ_END") ) )
+ {
+ bSkip = false;
+ }
+ else if( pAction->GetType() == MetaActionType::GRADIENTEX )
+ {
+ mapCurShape->mapShapeGradient.reset( new Gradient(
+ static_cast< const MetaGradientExAction* >( pAction )->GetGradient() ) );
+ }
+ else if( pAction->GetType() == MetaActionType::GRADIENT )
+ {
+ mapCurShape->mapShapeGradient.reset( new Gradient(
+ static_cast< const MetaGradientAction* >( pAction )->GetGradient() ) );
+ }
+ }
+ }
+ }
+ else
+ bSkip = false;
+ }
+
+ // skip rest of comment
+ while( bSkip && ( ++nCurAction < nCount ) )
+ {
+ pAction = rMtf.GetAction( nCurAction );
+
+ if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>( pAction )->GetComment().
+ equalsIgnoreAsciiCase("XPATHFILL_SEQ_END") ) )
+ {
+ bSkip = false;
+ }
+ }
+ }
+ else if( ( pA->GetComment().equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_BEGIN") ) &&
+ ( nWriteFlags & SVGWRITER_WRITE_FILL ) && !( nWriteFlags & SVGWRITER_NO_SHAPE_COMMENTS ) &&
+ pA->GetDataSize() )
+ {
+ SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ );
+ SvtGraphicStroke aStroke;
+ tools::PolyPolygon aStartArrow, aEndArrow;
+
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+ aStroke.getStartArrow( aStartArrow );
+ aStroke.getEndArrow( aEndArrow );
+
+ // Currently no support for strokes with start/end arrow(s)
+ // added that support
+ tools::Polygon aPoly;
+
+ aStroke.getPath(aPoly);
+
+ if (mapCurShape)
+ {
+ if(1 != mapCurShape->maShapePolyPoly.Count()
+ || !mapCurShape->maShapePolyPoly[0].IsEqual(aPoly))
+ {
+ // this path action is not covering the same path than the already existing
+ // fill polypolygon, so write out the fill polygon
+ ImplWriteShape( *mapCurShape );
+ mapCurShape.reset();
+ }
+ }
+
+ if (!mapCurShape)
+ {
+
+ mapCurShape.reset( new SVGShapeDescriptor );
+
+ if( bUseElementId )
+ {
+ mapCurShape->maId = aElementId + "_" + OUString::number(nEntryCount++);
+ }
+
+ mapCurShape->maShapePolyPoly = aPoly;
+ }
+
+ mapCurShape->maShapeLineColor = mpVDev->GetLineColor();
+ mapCurShape->maShapeLineColor.SetAlpha( 255 - static_cast<sal_uInt8>(FRound( aStroke.getTransparency() * 255.0 )) );
+ mapCurShape->mnStrokeWidth = FRound( aStroke.getStrokeWidth() );
+ aStroke.getDashArray( mapCurShape->maDashArray );
+
+ // added support for LineJoin
+ switch(aStroke.getJoinType())
+ {
+ default: /* SvtGraphicStroke::joinMiter, SvtGraphicStroke::joinNone */
+ {
+ mapCurShape->maLineJoin = basegfx::B2DLineJoin::Miter;
+ break;
+ }
+ case SvtGraphicStroke::joinRound:
+ {
+ mapCurShape->maLineJoin = basegfx::B2DLineJoin::Round;
+ break;
+ }
+ case SvtGraphicStroke::joinBevel:
+ {
+ mapCurShape->maLineJoin = basegfx::B2DLineJoin::Bevel;
+ break;
+ }
+ }
+
+ // added support for LineCap
+ switch(aStroke.getCapType())
+ {
+ default: /* SvtGraphicStroke::capButt */
+ {
+ mapCurShape->maLineCap = css::drawing::LineCap_BUTT;
+ break;
+ }
+ case SvtGraphicStroke::capRound:
+ {
+ mapCurShape->maLineCap = css::drawing::LineCap_ROUND;
+ break;
+ }
+ case SvtGraphicStroke::capSquare:
+ {
+ mapCurShape->maLineCap = css::drawing::LineCap_SQUARE;
+ break;
+ }
+ }
+
+ if (mapCurShape->maShapePolyPoly.Count() && (aStartArrow.Count() || aEndArrow.Count()))
+ {
+ ImplWriteShape( *mapCurShape );
+
+ mapCurShape->maShapeFillColor = mapCurShape->maShapeLineColor;
+ mapCurShape->maShapeLineColor = COL_TRANSPARENT;
+ mapCurShape->mnStrokeWidth = 0;
+ mapCurShape->maDashArray.clear();
+ mapCurShape->maLineJoin = basegfx::B2DLineJoin::Miter;
+ mapCurShape->maLineCap = css::drawing::LineCap_BUTT;
+
+ if(aStartArrow.Count())
+ {
+ mapCurShape->maShapePolyPoly = aStartArrow;
+
+ if( bUseElementId ) // #i124825# aElementId is optional, may be zero
+ {
+ mapCurShape->maId = aElementId + "_" + OUString::number(nEntryCount++);
+ }
+
+ ImplWriteShape( *mapCurShape );
+ }
+
+ if(aEndArrow.Count())
+ {
+ mapCurShape->maShapePolyPoly = aEndArrow;
+
+ if( bUseElementId ) // #i124825# aElementId is optional, may be zero
+ {
+ mapCurShape->maId = aElementId + "_" + OUString::number(nEntryCount++);
+ }
+
+ ImplWriteShape( *mapCurShape );
+ }
+
+ mapCurShape.reset();
+ }
+
+ // write open shape in every case
+ if (mapCurShape)
+ {
+ ImplWriteShape( *mapCurShape );
+ mapCurShape.reset();
+ }
+
+ // skip rest of comment
+ bool bSkip = true;
+
+ while( bSkip && ( ++nCurAction < nCount ) )
+ {
+ pAction = rMtf.GetAction( nCurAction );
+
+ if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>(pAction)->GetComment().
+ equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_END") ) )
+ {
+ bSkip = false;
+ }
+ }
+ }
+ else if( !mrExport.IsUsePositionedCharacters() && ( nWriteFlags & SVGWRITER_WRITE_TEXT ) )
+ {
+ if( pA->GetComment().equalsIgnoreAsciiCase( "XTEXT_PAINTSHAPE_BEGIN" ) )
+ {
+ if( pxShape )
+ {
+ Reference< XText > xText( *pxShape, UNO_QUERY );
+ if( xText.is() )
+ maTextWriter.setTextShape( xText, pTextEmbeddedBitmapMtf );
+ }
+ maTextWriter.createParagraphEnumeration();
+ {
+ // nTextFound == -1 => no text found
+ // nTextFound == 0 => no text found and end of text shape reached
+ // nTextFound == 1 => text found!
+ sal_Int32 nTextFound = -1;
+ while( ( nTextFound < 0 ) && ( nCurAction < nCount ) )
+ {
+ nTextFound
+ = maTextWriter.setTextPosition(rMtf, nCurAction, nWriteFlags);
+ }
+ // We found some text in the current text shape.
+ if( nTextFound > 0 )
+ {
+ maTextWriter.setTextProperties( rMtf, nCurAction );
+ maTextWriter.startTextShape();
+ }
+ // We reached the end of the current text shape
+ // without finding any text. So we need to go back
+ // by one action in order to handle the
+ // XTEXT_PAINTSHAPE_END action because on the next
+ // loop the nCurAction is incremented by one.
+ else
+ {
+ --nCurAction;
+ }
+ }
+ }
+ else if( pA->GetComment().equalsIgnoreAsciiCase( "XTEXT_PAINTSHAPE_END" ) )
+ {
+ maTextWriter.endTextShape();
+ }
+ else if( pA->GetComment().equalsIgnoreAsciiCase( "XTEXT_EOP" ) )
+ {
+ const MetaAction* pNextAction = rMtf.GetAction( nCurAction + 1 );
+ if( !( ( pNextAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>(pNextAction)->GetComment().equalsIgnoreAsciiCase("XTEXT_PAINTSHAPE_END") ) ))
+ {
+ // nTextFound == -1 => no text found and end of paragraph reached
+ // nTextFound == 0 => no text found and end of text shape reached
+ // nTextFound == 1 => text found!
+ sal_Int32 nTextFound = -1;
+ while( ( nTextFound < 0 ) && ( nCurAction < nCount ) )
+ {
+ nTextFound
+ = maTextWriter.setTextPosition(rMtf, nCurAction, nWriteFlags);
+ }
+ // We found a paragraph with some text in the
+ // current text shape.
+ if( nTextFound > 0 )
+ {
+ maTextWriter.setTextProperties( rMtf, nCurAction );
+ maTextWriter.startTextParagraph();
+ }
+ // We reached the end of the current text shape
+ // without finding any text. So we need to go back
+ // by one action in order to handle the
+ // XTEXT_PAINTSHAPE_END action because on the next
+ // loop the nCurAction is incremented by one.
+ else
+ {
+ --nCurAction;
+ }
+
+ }
+ }
+ else if( pA->GetComment().equalsIgnoreAsciiCase( "XTEXT_EOL" ) )
+ {
+ const MetaAction* pNextAction = rMtf.GetAction( nCurAction + 1 );
+ if( !( ( pNextAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>(pNextAction)->GetComment().equalsIgnoreAsciiCase("XTEXT_EOP") ) ) )
+ {
+ // nTextFound == -2 => no text found and end of line reached
+ // nTextFound == -1 => no text found and end of paragraph reached
+ // nTextFound == 1 => text found!
+ sal_Int32 nTextFound = -2;
+ while( ( nTextFound < -1 ) && ( nCurAction < nCount ) )
+ {
+ nTextFound
+ = maTextWriter.setTextPosition(rMtf, nCurAction, nWriteFlags);
+ }
+ // We found a line with some text in the current
+ // paragraph.
+ if( nTextFound > 0 )
+ {
+ maTextWriter.startTextPosition();
+ }
+ // We reached the end of the current paragraph
+ // without finding any text. So we need to go back
+ // by one action in order to handle the XTEXT_EOP
+ // action because on the next loop the nCurAction is
+ // incremented by one.
+ else
+ {
+ --nCurAction;
+ }
+ }
+ }
+ }
+ else if( pA->GetComment().startsWithIgnoreAsciiCase( sTiledBackgroundTag ) )
+ {
+ // In the tile case the background is rendered through a rectangle
+ // filled by exploiting an exported pattern element.
+ // Both the pattern and the rectangle are embedded in a <defs> element.
+ // The comment content has the following format: "SLIDE_BACKGROUND <background-id>"
+ const OString& sComment = pA->GetComment();
+ OUString sRefId = "#" + OUString::fromUtf8( o3tl::getToken(sComment, 1, ' ') );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId );
+
+ SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true );
+ }
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaBmpAction* pA = static_cast<const MetaBmpAction*>(pAction);
+
+ ImplWriteBmp( BitmapEx(pA->GetBitmap()),
+ pA->GetPoint(), mpVDev->PixelToLogic( pA->GetBitmap().GetSizePixel() ),
+ Point(), pA->GetBitmap().GetSizePixel(), pxShape );
+ }
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+
+ // Bitmaps embedded into text shapes are collected and exported elsewhere.
+ if( maTextWriter.isTextShapeStarted() )
+ {
+ maTextWriter.writeBitmapPlaceholder( pA );
+ }
+ else
+ {
+ ImplWriteBmp( BitmapEx(pA->GetBitmap()),
+ pA->GetPoint(), pA->GetSize(),
+ Point(), pA->GetBitmap().GetSizePixel(), pxShape );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pAction);
+
+ ImplWriteBmp( BitmapEx(pA->GetBitmap()),
+ pA->GetDestPoint(), pA->GetDestSize(),
+ pA->GetSrcPoint(), pA->GetSrcSize(), pxShape );
+ }
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaBmpExAction* pA = static_cast<const MetaBmpExAction*>(pAction);
+
+ ImplWriteBmp( pA->GetBitmapEx(),
+ pA->GetPoint(), mpVDev->PixelToLogic( pA->GetBitmapEx().GetSizePixel() ),
+ Point(), pA->GetBitmapEx().GetSizePixel(), pxShape );
+ }
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+
+ // Bitmaps embedded into text shapes are collected and exported elsewhere.
+ if( maTextWriter.isTextShapeStarted() )
+ {
+ maTextWriter.writeBitmapPlaceholder( pA );
+ }
+ else
+ {
+ ImplWriteBmp( pA->GetBitmapEx(),
+ pA->GetPoint(), pA->GetSize(),
+ Point(), pA->GetBitmapEx().GetSizePixel(), pxShape );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction);
+
+ ImplWriteBmp( pA->GetBitmapEx(),
+ pA->GetDestPoint(), pA->GetDestSize(),
+ pA->GetSrcPoint(), pA->GetSrcSize(), pxShape );
+ }
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_TEXT )
+ {
+ const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction);
+ sal_Int32 aLength = std::min( pA->GetText().getLength(), pA->GetLen() );
+ const OUString aText = pA->GetText().copy( pA->GetIndex(), aLength );
+
+ if( !aText.isEmpty() )
+ {
+ if( mrExport.IsUsePositionedCharacters() )
+ {
+ vcl::Font aFont = ImplSetCorrectFontHeight();
+ maAttributeWriter.SetFontAttr( aFont );
+ ImplWriteText( pA->GetPoint(), aText, {}, 0 );
+ }
+ else
+ {
+ maTextWriter.writeTextPortion( pA->GetPoint(), aText );
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_TEXT )
+ {
+ const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
+
+ if (!pA->GetText().isEmpty())
+ {
+ if( mrExport.IsUsePositionedCharacters() )
+ {
+ vcl::Font aFont = ImplSetCorrectFontHeight();
+ maAttributeWriter.SetFontAttr( aFont );
+ ImplWriteText( pA->GetRect().TopLeft(), pA->GetText(), {}, 0 );
+ }
+ maTextWriter.writeTextPortion( pA->GetRect().TopLeft(), pA->GetText() );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_TEXT )
+ {
+ const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction);
+ sal_Int32 aLength = std::min( pA->GetText().getLength(), pA->GetLen() );
+ const OUString aText = pA->GetText().copy( pA->GetIndex(), aLength );
+
+ if( !aText.isEmpty() )
+ {
+ if( mrExport.IsUsePositionedCharacters() )
+ {
+ vcl::Font aFont = ImplSetCorrectFontHeight();
+ maAttributeWriter.SetFontAttr( aFont );
+ ImplWriteText( pA->GetPoint(), aText, pA->GetDXArray(), 0 );
+ }
+ else
+ {
+ maTextWriter.writeTextPortion( pA->GetPoint(), aText );
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_TEXT )
+ {
+ const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction);
+ sal_Int32 aLength = std::min( pA->GetText().getLength(), pA->GetLen() );
+ const OUString aText = pA->GetText().copy( pA->GetIndex(), aLength );
+
+ if( !aText.isEmpty() )
+ {
+ if( mrExport.IsUsePositionedCharacters() )
+ {
+ vcl::Font aFont = ImplSetCorrectFontHeight();
+ maAttributeWriter.SetFontAttr( aFont );
+ ImplWriteText( pA->GetPoint(), aText, {}, pA->GetWidth() );
+ }
+ else
+ {
+ maTextWriter.writeTextPortion( pA->GetPoint(), aText );
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ case MetaActionType::ISECTRECTCLIPREGION:
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ case MetaActionType::MOVECLIPREGION:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( mpVDev );
+ const vcl::Region& rClipRegion = mpVDev->GetActiveClipRegion();
+ ImplWriteClipPath( rClipRegion.GetAsPolyPolygon() );
+
+ mbClipAttrChanged = true;
+ }
+ break;
+
+ case MetaActionType::PUSH:
+ {
+ const MetaPushAction* pA = static_cast<const MetaPushAction*>(pAction);
+ vcl::PushFlags mnFlags = pA->GetFlags();
+
+ const_cast<MetaAction*>(pAction)->Execute( mpVDev );
+
+ maContextHandler.pushState( mnFlags );
+ }
+ break;
+
+ case MetaActionType::POP:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( mpVDev );
+
+ vcl::PushFlags mnFlags = maContextHandler.getPushFlags();
+
+ maContextHandler.popState();
+
+ if( mnFlags & vcl::PushFlags::CLIPREGION )
+ {
+ ImplEndClipRegion();
+ ImplStartClipRegion( mrCurrentState.nRegionClipPathId );
+ }
+ }
+ break;
+
+ case MetaActionType::REFPOINT:
+ case MetaActionType::MAPMODE:
+ case MetaActionType::LINECOLOR:
+ case MetaActionType::FILLCOLOR:
+ case MetaActionType::TEXTLINECOLOR:
+ case MetaActionType::TEXTFILLCOLOR:
+ case MetaActionType::TEXTCOLOR:
+ case MetaActionType::TEXTALIGN:
+ case MetaActionType::FONT:
+ case MetaActionType::LAYOUTMODE:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( mpVDev );
+ }
+ break;
+
+ case MetaActionType::RASTEROP:
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ case MetaActionType::WALLPAPER:
+ case MetaActionType::TEXTLINE:
+ case MetaActionType::TEXTLANGUAGE:
+ {
+ // !!! >>> we don't want to support these actions
+ }
+ break;
+
+ default:
+ SAL_WARN("filter.svg", "SVGActionWriter::ImplWriteActions: unsupported MetaAction # " << sal_Int32(nType));
+ break;
+ }
+ }
+}
+
+
+vcl::Font SVGActionWriter::ImplSetCorrectFontHeight() const
+{
+ vcl::Font aFont( mpVDev->GetFont() );
+ Size aSz;
+
+ ImplMap( Size( 0, aFont.GetFontHeight() ), aSz );
+
+ aFont.SetFontHeight( aSz.Height() );
+
+ return aFont;
+}
+
+
+void SVGActionWriter::WriteMetaFile( const Point& rPos100thmm,
+ const Size& rSize100thmm,
+ const GDIMetaFile& rMtf,
+ sal_uInt32 nWriteFlags,
+ const OUString& aElementId,
+ const Reference< css::drawing::XShape >* pXShape,
+ const GDIMetaFile* pTextEmbeddedBitmapMtf )
+{
+ MapMode aMapMode( rMtf.GetPrefMapMode() );
+ Size aPrefSize( rMtf.GetPrefSize() );
+ Fraction aFractionX( aMapMode.GetScaleX() );
+ Fraction aFractionY( aMapMode.GetScaleY() );
+
+ mpVDev->Push();
+
+ Size aSize( OutputDevice::LogicToLogic(rSize100thmm, MapMode(MapUnit::Map100thMM), aMapMode) );
+ aFractionX *= Fraction( aSize.Width(), aPrefSize.Width() );
+ aMapMode.SetScaleX( aFractionX );
+ aFractionY *= Fraction( aSize.Height(), aPrefSize.Height() );
+ aMapMode.SetScaleY( aFractionY );
+
+ Point aOffset( OutputDevice::LogicToLogic(rPos100thmm, MapMode(MapUnit::Map100thMM), aMapMode ) );
+ aOffset += aMapMode.GetOrigin();
+ aMapMode.SetOrigin( aOffset );
+
+ mpVDev->SetMapMode( aMapMode );
+
+ mapCurShape.reset();
+
+ ImplWriteActions( rMtf, nWriteFlags, aElementId, pXShape, pTextEmbeddedBitmapMtf );
+ maTextWriter.endTextParagraph();
+ ImplEndClipRegion();
+
+ // draw open shape that doesn't have a border
+ if (mapCurShape)
+ {
+ ImplWriteShape( *mapCurShape );
+ mapCurShape.reset();
+ }
+
+ mpVDev->Pop();
+}
+
+
+SVGWriter::SVGWriter( const Sequence<Any>& args, const Reference< XComponentContext >& rxCtx )
+ : mxContext(rxCtx)
+{
+ if(args.getLength()==1)
+ args[0]>>=maFilterData;
+}
+
+
+SVGWriter::~SVGWriter()
+{
+}
+
+
+void SAL_CALL SVGWriter::write( const Reference<XDocumentHandler>& rxDocHandler,
+ const Sequence<sal_Int8>& rMtfSeq )
+{
+ SvMemoryStream aMemStm( const_cast<sal_Int8 *>(rMtfSeq.getConstArray()), rMtfSeq.getLength(), StreamMode::READ );
+ GDIMetaFile aMtf;
+
+ SvmReader aReader( aMemStm );
+ aReader.Read( aMtf );
+
+ rtl::Reference<SVGExport> pWriter(new SVGExport( mxContext, rxDocHandler, maFilterData ));
+ pWriter->writeMtf( aMtf );
+}
+
+// XServiceInfo
+sal_Bool SVGWriter::supportsService(const OUString& sServiceName)
+{
+ return cppu::supportsService(this, sServiceName);
+}
+OUString SVGWriter::getImplementationName()
+{
+ return "com.sun.star.comp.Draw.SVGWriter";
+}
+css::uno::Sequence< OUString > SVGWriter::getSupportedServiceNames()
+{
+ return { "com.sun.star.svg.SVGWriter" };
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+filter_SVGWriter_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args)
+{
+ return cppu::acquire(new SVGWriter(args, context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/filter/source/svg/svgwriter.hxx b/filter/source/svg/svgwriter.hxx
new file mode 100644
index 000000000..d48d68bde
--- /dev/null
+++ b/filter/source/svg/svgwriter.hxx
@@ -0,0 +1,406 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <cppuhelper/implbase.hxx>
+#include <rtl/ustring.hxx>
+#include <osl/diagnose.h>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/graphictools.hxx>
+#include <xmloff/xmlexp.hxx>
+
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/container/XEnumeration.hpp>
+#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/text/XText.hpp>
+#include <com/sun/star/text/XTextContent.hpp>
+#include <com/sun/star/text/XTextRange.hpp>
+#include <com/sun/star/svg/XSVGWriter.hpp>
+
+#include <memory>
+#include <stack>
+#include <unordered_map>
+
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::container;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::text;
+using namespace ::com::sun::star::style;
+using namespace ::com::sun::star::svg;
+using namespace ::com::sun::star::xml::sax;
+
+inline constexpr OUStringLiteral SVG_DTD_STRING = u"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">";
+
+#define SVGWRITER_WRITE_FILL 0x00000001
+#define SVGWRITER_WRITE_TEXT 0x00000002
+#define SVGWRITER_NO_SHAPE_COMMENTS 0x01000000
+
+// This must match the same type definition in svgexport.hxx
+typedef std::unordered_map< BitmapChecksum, std::unique_ptr< GDIMetaFile > > MetaBitmapActionMap;
+
+struct SVGState
+{
+ vcl::Font aFont;
+// Color aLineColor;
+// Color aFillColor;
+// basegfx::B2DLineJoin aLineJoin;
+// com::sun::star::drawing::LineCap aLineCap;
+ sal_Int32 nRegionClipPathId;
+
+ SVGState()
+ : aFont()
+ , nRegionClipPathId( 0 )
+ {}
+};
+// - PartialState -
+
+struct PartialState
+{
+ vcl::PushFlags meFlags;
+ ::std::optional<vcl::Font> mupFont;
+ sal_Int32 mnRegionClipPathId;
+
+ const vcl::Font& getFont( const vcl::Font& rDefaultFont ) const
+ { return mupFont ? *mupFont : rDefaultFont; }
+
+ void setFont( const vcl::Font& rFont )
+ { mupFont = rFont; }
+
+ PartialState()
+ : meFlags( vcl::PushFlags::NONE )
+ , mupFont()
+ , mnRegionClipPathId( 0 )
+ {}
+
+ PartialState(PartialState&& aPartialState) noexcept
+ : meFlags( aPartialState.meFlags )
+ , mupFont( std::move( aPartialState.mupFont ) )
+ , mnRegionClipPathId( aPartialState.mnRegionClipPathId )
+ {
+ aPartialState.meFlags = vcl::PushFlags::NONE;
+ aPartialState.mnRegionClipPathId = 0;
+ }
+};
+
+
+// - SVGContextHandler -
+
+class SVGContextHandler
+{
+private:
+ ::std::stack<PartialState> maStateStack;
+ SVGState maCurrentState;
+
+public:
+ vcl::PushFlags getPushFlags() const;
+ SVGState& getCurrentState();
+ void pushState( vcl::PushFlags eFlags );
+ void popState();
+};
+
+
+// - SVGAttributeWriter -
+
+class SVGActionWriter;
+class SVGExport;
+class SVGFontExport;
+
+
+class SVGAttributeWriter final
+{
+private:
+
+ SVGExport& mrExport;
+ SVGFontExport& mrFontExport;
+ SVGState& mrCurrentState;
+ std::unique_ptr<SvXMLElementExport> mpElemFont;
+
+
+ static double ImplRound( double fVal );
+
+public:
+
+ SVGAttributeWriter( SVGExport& rExport, SVGFontExport& rFontExport, SVGState& rCurState );
+ ~SVGAttributeWriter();
+
+ void AddColorAttr( const OUString& pColorAttrName, const OUString& pColorOpacityAttrName, const Color& rColor );
+ void AddGradientDef( const tools::Rectangle& rObjRect,const Gradient& rGradient, OUString& rGradientId );
+ void AddPaintAttr( const Color& rLineColor, const Color& rFillColor,
+ const tools::Rectangle* pObjBoundRect = nullptr, const Gradient* pFillGradient = nullptr );
+
+ void SetFontAttr( const vcl::Font& rFont );
+ void startFontSettings();
+ void endFontSettings();
+ void setFontFamily();
+
+ static void ImplGetColorStr( const Color& rColor, OUString& rColorStr );
+};
+
+struct SVGShapeDescriptor
+{
+ tools::PolyPolygon maShapePolyPoly;
+ Color maShapeFillColor;
+ Color maShapeLineColor;
+ sal_Int32 mnStrokeWidth;
+ SvtGraphicStroke::DashArray maDashArray;
+ ::std::unique_ptr< Gradient > mapShapeGradient;
+ OUString maId;
+ basegfx::B2DLineJoin maLineJoin;
+ css::drawing::LineCap maLineCap;
+
+
+ SVGShapeDescriptor() :
+ maShapeFillColor( COL_TRANSPARENT ),
+ maShapeLineColor( COL_TRANSPARENT ),
+ mnStrokeWidth( 0 ),
+ maLineJoin(basegfx::B2DLineJoin::Miter), // miter is Svg 'stroke-linejoin' default
+ maLineCap(css::drawing::LineCap_BUTT) // butt is Svg 'stroke-linecap' default
+ {
+ }
+};
+
+
+class SVGAttributeWriter;
+class SVGExport;
+class GDIMetaFile;
+
+
+struct BulletListItemInfo
+{
+ vcl::Font aFont;
+ Color aColor;
+ Point aPos;
+ sal_Unicode cBulletChar;
+};
+
+
+class SVGTextWriter final
+{
+ private:
+ SVGExport& mrExport;
+ SVGAttributeWriter& mrAttributeWriter;
+ SVGActionWriter& mrActionWriter;
+ VclPtr<VirtualDevice> mpVDev;
+ bool mbIsTextShapeStarted;
+ Reference<XText> mrTextShape;
+ OUString msShapeId;
+ Reference<XEnumeration> mrParagraphEnumeration;
+ Reference<XTextContent> mrCurrentTextParagraph;
+ Reference<XEnumeration> mrTextPortionEnumeration;
+ Reference<XTextRange> mrCurrentTextPortion;
+ const GDIMetaFile* mpTextEmbeddedBitmapMtf;
+ MapMode* mpTargetMapMode;
+ std::unique_ptr<SvXMLElementExport> mpTextShapeElem;
+ std::unique_ptr<SvXMLElementExport> mpTextParagraphElem;
+ std::unique_ptr<SvXMLElementExport> mpTextPositionElem;
+ OUString maTextOpacity;
+ sal_Int32 mnLeftTextPortionLength;
+ Point maTextPos;
+ tools::Long mnTextWidth;
+ bool mbPositioningNeeded;
+ bool mbIsNewListItem;
+ sal_Int16 meNumberingType;
+ sal_Unicode mcBulletChar;
+ std::unordered_map< OUString, BulletListItemInfo > maBulletListItemMap;
+ bool mbIsListLevelStyleImage;
+ bool mbLineBreak;
+ bool mbIsURLField;
+ OUString msUrl;
+ OUString msHyperlinkIdList;
+ OUString msPageCount;
+ OUString msDateTimeType;
+ OUString msTextFieldType;
+ bool mbIsPlaceholderShape;
+ static const bool mbIWS = false;
+ vcl::Font maCurrentFont;
+ vcl::Font maParentFont;
+
+ public:
+ explicit SVGTextWriter(SVGExport& rExport, SVGAttributeWriter& rAttributeWriter,
+ SVGActionWriter& mrActionWriter);
+ ~SVGTextWriter();
+
+ sal_Int32 setTextPosition(const GDIMetaFile& rMtf, size_t& nCurAction,
+ sal_uInt32 nWriteFlags);
+ void setTextProperties( const GDIMetaFile& rMtf, size_t nCurAction );
+ void addFontAttributes( bool bIsTextContainer );
+
+ void createParagraphEnumeration();
+ bool nextParagraph();
+ bool nextTextPortion();
+
+ bool isTextShapeStarted() const { return mbIsTextShapeStarted; }
+ void startTextShape();
+ void endTextShape();
+ void startTextParagraph();
+ void endTextParagraph();
+ void startTextPosition( bool bExportX = true, bool bExportY = true);
+ void endTextPosition();
+ bool hasTextOpacity() const;
+ void implExportHyperlinkIds();
+ void implWriteBulletChars();
+ template< typename MetaBitmapActionType >
+ void writeBitmapPlaceholder( const MetaBitmapActionType* pAction );
+ void implWriteEmbeddedBitmaps();
+ void writeTextPortion( const Point& rPos, const OUString& rText );
+ void implWriteTextPortion( const Point& rPos, const OUString& rText,
+ Color aTextColor );
+
+ void setVirtualDevice( VirtualDevice* pVDev, MapMode& rTargetMapMode )
+ {
+ if( !pVDev )
+ OSL_FAIL( "SVGTextWriter::setVirtualDevice: invalid virtual device." );
+ mpVDev = pVDev;
+ mpTargetMapMode = &rTargetMapMode;
+ }
+
+ void setTextShape( const Reference<XText>& rxText,
+ const GDIMetaFile* pTextEmbeddedBitmapMtf )
+ {
+ mrTextShape.set( rxText );
+ mpTextEmbeddedBitmapMtf = pTextEmbeddedBitmapMtf;
+ }
+
+ private:
+ void implMap( const Size& rSz, Size& rDstSz ) const;
+ void implMap( const Point& rPt, Point& rDstPt ) const;
+ void implSetCurrentFont();
+ void implSetFontFamily();
+
+ template< typename SubType >
+ bool implGetTextPosition( const MetaAction* pAction, Point& raPos, bool& bEmpty );
+ template< typename SubType >
+ bool implGetTextPositionFromBitmap( const MetaAction* pAction, Point& raPos, bool& rbEmpty );
+
+ void implRegisterInterface( const Reference< XInterface >& rxIf );
+ const OUString & implGetValidIDFromInterface( const Reference< XInterface >& rxIf );
+};
+
+
+class SVGActionWriter final
+{
+private:
+
+ sal_Int32 mnCurGradientId;
+ sal_Int32 mnCurMaskId;
+ sal_Int32 mnCurPatternId;
+ sal_Int32 mnCurClipPathId;
+ ::std::unique_ptr< SvXMLElementExport > mpCurrentClipRegionElem;
+ ::std::unique_ptr< SVGShapeDescriptor > mapCurShape;
+ SVGExport& mrExport;
+ SVGContextHandler maContextHandler;
+ SVGState& mrCurrentState;
+ SVGAttributeWriter maAttributeWriter;
+ SVGTextWriter maTextWriter;
+ VclPtr<VirtualDevice> mpVDev;
+ MapMode maTargetMapMode;
+ bool mbClipAttrChanged;
+ bool mbIsPlaceholderShape;
+ const MetaBitmapActionMap* mpEmbeddedBitmapsMap;
+ bool mbIsPreview;
+
+
+ tools::Long ImplMap( sal_Int32 nVal ) const;
+ Point& ImplMap( const Point& rPt, Point& rDstPt ) const;
+ Size& ImplMap( const Size& rSz, Size& rDstSz ) const;
+ void ImplMap( const tools::Rectangle& rRect, tools::Rectangle& rDstRect ) const;
+ tools::Polygon& ImplMap( const tools::Polygon& rPoly, tools::Polygon& rDstPoly ) const;
+ tools::PolyPolygon& ImplMap( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rDstPolyPoly ) const;
+
+ void ImplWriteLine( const Point& rPt1, const Point& rPt2, const Color* pLineColor = nullptr );
+ void ImplWriteRect( const tools::Rectangle& rRect, tools::Long nRadX = 0, tools::Long nRadY = 0 );
+ void ImplWriteEllipse( const Point& rCenter, tools::Long nRadX, tools::Long nRadY );
+ void ImplWritePattern( const tools::PolyPolygon& rPolyPoly, const Hatch* pHatch, const Gradient* pGradient, sal_uInt32 nWriteFlags );
+ void ImplAddLineAttr( const LineInfo &rAttrs );
+ void ImplWritePolyPolygon( const tools::PolyPolygon& rPolyPoly, bool bLineOnly,
+ bool bApplyMapping = true );
+ void ImplWriteShape( const SVGShapeDescriptor& rShape );
+ void ImplCreateClipPathDef( const tools::PolyPolygon& rPolyPoly );
+ void ImplStartClipRegion(sal_Int32 nClipPathId);
+ void ImplEndClipRegion();
+ void ImplWriteClipPath( const tools::PolyPolygon& rPolyPoly );
+ void ImplWriteGradientEx( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, sal_uInt32 nWriteFlags);
+ void ImplWriteGradientLinear( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient );
+ void ImplWriteGradientStop( const Color& rColor, double fOffset );
+ static Color ImplGetColorWithIntensity( const Color& rColor, sal_uInt16 nIntensity );
+ static Color ImplGetGradientColor( const Color& rStartColor, const Color& rEndColor, double fOffset );
+ void ImplWriteMask( GDIMetaFile& rMtf, const Point& rDestPt, const Size& rDestSize, const Gradient& rGradient, sal_uInt32 nWriteFlags );
+ void ImplWriteText( const Point& rPos, const OUString& rText, o3tl::span<const sal_Int32> pDXArray, tools::Long nWidth );
+ void ImplWriteText( const Point& rPos, const OUString& rText, o3tl::span<const sal_Int32> pDXArray, tools::Long nWidth, Color aTextColor );
+ void ImplWriteBmp( const BitmapEx& rBmpEx, const Point& rPt, const Size& rSz, const Point& rSrcPt, const Size& rSrcSz, const css::uno::Reference<css::drawing::XShape>* pShape);
+
+ void ImplWriteActions( const GDIMetaFile& rMtf,
+ sal_uInt32 nWriteFlags,
+ const OUString& aElementId,
+ const Reference< css::drawing::XShape >* pXShape = nullptr,
+ const GDIMetaFile* pTextEmbeddedBitmapMtf = nullptr );
+
+ vcl::Font ImplSetCorrectFontHeight() const;
+
+public:
+
+ static OUString GetPathString( const tools::PolyPolygon& rPolyPoly, bool bLine );
+ static BitmapChecksum GetChecksum( const MetaAction* pAction );
+
+public:
+ SVGActionWriter( SVGExport& rExport, SVGFontExport& rFontExport );
+ ~SVGActionWriter();
+
+ void WriteMetaFile( const Point& rPos100thmm,
+ const Size& rSize100thmm,
+ const GDIMetaFile& rMtf,
+ sal_uInt32 nWriteFlags,
+ const OUString& aElementId = "",
+ const Reference< css::drawing::XShape >* pXShape = nullptr,
+ const GDIMetaFile* pTextEmbeddedBitmapMtf = nullptr );
+
+ void SetEmbeddedBitmapRefs( const MetaBitmapActionMap* pEmbeddedBitmapsMap );
+ void StartMask(const Point& rDestPt, const Size& rDestSize, const Gradient& rGradient,
+ sal_uInt32 nWriteFlags, OUString* pTextStyle = nullptr);
+ void SetPreviewMode(bool bState = true) { mbIsPreview = bState; }
+};
+
+
+class SVGWriter : public cppu::WeakImplHelper< XSVGWriter, XServiceInfo >
+{
+private:
+ Reference< XComponentContext > mxContext;
+ Sequence< css::beans::PropertyValue > maFilterData;
+
+public:
+ explicit SVGWriter( const Sequence<Any>& args,
+ const Reference< XComponentContext >& rxCtx );
+ virtual ~SVGWriter() override;
+
+ // XSVGWriter
+ virtual void SAL_CALL write( const Reference<XDocumentHandler>& rxDocHandler,
+ const Sequence<sal_Int8>& rMtfSeq ) override;
+
+ // XServiceInfo
+ virtual sal_Bool SAL_CALL supportsService(const OUString& sServiceName) override;
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/filter/source/svg/test/odfserializer.cxx b/filter/source/svg/test/odfserializer.cxx
new file mode 100644
index 000000000..cc3e7b30a
--- /dev/null
+++ b/filter/source/svg/test/odfserializer.cxx
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "odfserializer.hxx"
+#include <osl/diagnose.h>
+#include <rtl/ustrbuf.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/basemutex.hxx>
+#include <com/sun/star/uno/Sequence.hxx>
+
+using namespace ::com::sun::star;
+
+namespace svgi
+{
+
+typedef ::cppu::WeakComponentImplHelper<
+ css::xml::sax::XDocumentHandler> ODFSerializerBase;
+
+class ODFSerializer : private cppu::BaseMutex,
+ public ODFSerializerBase
+{
+public:
+ explicit ODFSerializer(const uno::Reference<io::XOutputStream>& xOut) :
+ ODFSerializerBase(m_aMutex),
+ m_xOutStream(xOut),
+ m_aLineFeed(1),
+ m_aBuf()
+ {
+ m_aLineFeed[0] = '\n';
+ }
+ ODFSerializer(const ODFSerializer&) = delete;
+ ODFSerializer& operator=(const ODFSerializer&) = delete;
+
+ virtual void SAL_CALL startDocument( ) override;
+ virtual void SAL_CALL endDocument( ) override;
+ virtual void SAL_CALL startElement( const OUString& aName, const uno::Reference< xml::sax::XAttributeList >& xAttribs ) override;
+ virtual void SAL_CALL endElement( const OUString& aName ) override;
+ virtual void SAL_CALL characters( const OUString& aChars ) override;
+ virtual void SAL_CALL ignorableWhitespace( const OUString& aWhitespaces ) override;
+ virtual void SAL_CALL processingInstruction( const OUString& aTarget, const OUString& aData ) override;
+ virtual void SAL_CALL setDocumentLocator( const uno::Reference< xml::sax::XLocator >& xLocator ) override;
+
+private:
+ uno::Reference<io::XOutputStream> m_xOutStream;
+ uno::Sequence<sal_Int8> m_aLineFeed;
+ uno::Sequence<sal_Int8> m_aBuf;
+};
+
+void SAL_CALL ODFSerializer::startDocument( )
+{
+ OSL_PRECOND(m_xOutStream.is(), "ODFSerializer(): invalid output stream");
+
+ OUStringBuffer aElement;
+ aElement.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+ characters(aElement.makeStringAndClear());
+}
+
+void SAL_CALL ODFSerializer::endDocument()
+{}
+
+void SAL_CALL ODFSerializer::startElement( const OUString& aName,
+ const uno::Reference< xml::sax::XAttributeList >& xAttribs )
+{
+ OUStringBuffer aElement("<" + aName + " ");
+
+ const sal_Int16 nLen=xAttribs->getLength();
+ for( sal_Int16 i=0; i<nLen; ++i )
+ aElement.append(xAttribs->getNameByIndex(i) + "=\"" +
+ xAttribs->getValueByIndex(i) + "\" ");
+
+ characters(aElement.makeStringAndClear() + ">");
+}
+
+void SAL_CALL ODFSerializer::endElement( const OUString& aName )
+{
+ characters("</" + aName + ">");
+}
+
+void SAL_CALL ODFSerializer::characters( const OUString& aChars )
+{
+ const OString aStr = OUStringToOString(aChars, RTL_TEXTENCODING_UTF8);
+ const sal_Int32 nLen( aStr.getLength() );
+ m_aBuf.realloc( nLen );
+ const char* pStr = aStr.getStr();
+ std::copy(pStr,pStr+nLen,m_aBuf.getArray());
+
+ m_xOutStream->writeBytes(m_aBuf);
+ // TODO(F1): Make pretty printing configurable
+ m_xOutStream->writeBytes(m_aLineFeed);
+}
+
+void SAL_CALL ODFSerializer::ignorableWhitespace( const OUString& aWhitespaces )
+{
+ // TODO(F1): Make pretty printing configurable
+ characters(aWhitespaces);
+}
+
+void SAL_CALL ODFSerializer::processingInstruction( const OUString&,
+ const OUString& )
+{}
+
+void SAL_CALL ODFSerializer::setDocumentLocator( const uno::Reference< xml::sax::XLocator >& )
+{}
+
+uno::Reference< xml::sax::XDocumentHandler> createSerializer(const uno::Reference<io::XOutputStream>& xOut )
+{
+ return uno::Reference<xml::sax::XDocumentHandler>(new ODFSerializer(xOut));
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/filter/source/svg/test/odfserializer.hxx b/filter/source/svg/test/odfserializer.hxx
new file mode 100644
index 000000000..2724c9f0a
--- /dev/null
+++ b/filter/source/svg/test/odfserializer.hxx
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/xml/sax/XDocumentHandler.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+
+namespace svgi
+{
+/// Creates a XDocumentHandler that serializes directly to an XOutputStream
+css::uno::Reference<css::xml::sax::XDocumentHandler>
+createSerializer(const css::uno::Reference<css::io::XOutputStream>&);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/filter/source/svg/tokens.txt b/filter/source/svg/tokens.txt
new file mode 100644
index 000000000..1211e691f
--- /dev/null
+++ b/filter/source/svg/tokens.txt
@@ -0,0 +1,403 @@
+#######################################
+#
+# elements (SVG Tiny 1.2)
+#
+#######################################
+a
+animate
+animateColor
+animateMotion
+animateTransform
+animation
+audio
+circle
+defs
+desc
+discard
+ellipse
+font
+font-face
+font-face-src
+font-face-uri
+foreignObject
+g
+glyph
+handler
+hkern
+image
+line
+linearGradient
+listener
+metadata
+missing-glyph
+mpath
+path
+polygon
+polyline
+prefetch
+radialGradient
+rect
+script
+set
+solidColor
+stop
+svg
+switch
+tbreak
+text
+textArea
+title
+tspan
+use
+video
+#######################################
+#
+# properties (SVG Tiny 1.2)
+#
+#######################################
+audio-level
+color
+color-rendering
+display
+display-align
+fill
+fill-opacity
+fill-rule
+font-family
+font-size
+font-style
+font-variant
+font-weight
+image-rendering
+line-increment
+opacity
+pointer-events
+shape-rendering
+solid-color
+solid-opacity
+stop-color
+stop-opacity
+stroke
+stroke-dasharray
+stroke-dashoffset
+stroke-linecap
+stroke-linejoin
+stroke-miterlimit
+stroke-opacity
+stroke-width
+text-align
+text-anchor
+text-rendering
+vector-effect
+viewport-fill
+viewport-fill-opacity
+visibility
+#######################################
+#
+# attributes (SVG Tiny 1.2)
+#
+#######################################
+accent-height
+accumulate
+additive
+alphabetic
+arabic-form
+ascent
+attributeName
+attributeType
+bandwidth
+baseProfile
+bbox
+begin
+by
+calcMode
+cap-height
+class
+contentScriptType
+cx
+cy
+d
+defaultAction
+descent
+dur
+editable
+end
+event
+externalResourcesRequired
+fill
+focusHighlight
+focusable
+font-family
+font-stretch
+font-style
+font-variant
+font-weight
+from
+fx
+fy
+g1
+g2
+glyph-name
+gradientTransform
+gradientUnits
+handler
+hanging
+height
+height
+horiz-adv-x
+horiz-origin-x
+id
+ideographic
+initialVisibility
+k
+keyPoints
+keySplines
+keyTimes
+lang
+mathematical
+max
+mediaCharacterEncoding
+mediaContentEncodings
+mediaSize
+mediaTime
+min
+nav-down
+nav-down-left
+nav-down-right
+nav-left
+nav-next
+nav-prev
+nav-right
+nav-up
+nav-up-left
+nav-up-right
+observer
+offset
+origin
+overlay
+overline-position
+overline-thickness
+panose-1
+path
+pathLength
+phase
+playbackOrder
+points
+preserveAspectRatio
+propagate
+r
+repeatCount
+repeatDur
+requiredExtensions
+requiredFeatures
+requiredFonts
+requiredFormats
+restart
+rotate
+rx
+ry
+slope
+snapshotTime
+stemh
+stemv
+strikethrough-position
+strikethrough-thickness
+style
+syncBehavior
+syncBehaviorDefault
+syncMaster
+syncTolerance
+syncToleranceDefault
+systemLanguage
+target
+timelineBegin
+to
+transform
+transformBehavior
+type
+u1
+u2
+underline-position
+underline-thickness
+unicode
+unicode-range
+units-per-em
+values
+version
+viewBox
+width
+widths
+x
+x-height
+x1
+x2
+actuate
+arcrole
+href
+role
+show
+base
+space
+y
+y1
+y2
+zoomAndPan
+#######################################
+#
+# colour values
+#
+#######################################
+aliceblue
+antiquewhite
+aqua
+aquamarine
+azure
+beige
+bisque
+black
+blanchedalmond
+blue
+blueviolet
+brown
+burlywood
+cadetblue
+chartreuse
+chocolate
+coral
+cornflowerblue
+cornsilk
+crimson
+cyan
+darkblue
+darkcyan
+darkgoldenrod
+darkgray
+darkgreen
+darkgrey
+darkkhaki
+darkmagenta
+darkolivegreen
+darkorange
+darkorchid
+darkred
+darksalmon
+darkseagreen
+darkslateblue
+darkslategray
+darkslategrey
+darkturquoise
+darkviolet
+deeppink
+deepskyblue
+dimgray
+dimgrey
+dodgerblue
+firebrick
+floralwhite
+forestgreen
+fuchsia
+gainsboro
+ghostwhite
+gold
+goldenrod
+gray
+grey
+green
+greenyellow
+honeydew
+hotpink
+indianred
+indigo
+ivory
+khaki
+lavender
+lavenderblush
+lawngreen
+lemonchiffon
+lightblue
+lightcoral
+lightcyan
+lightgoldenrodyellow
+lightgray
+lightgreen
+lightgrey
+lightpink
+lightsalmon
+lightseagreen
+lightskyblue
+lightslategray
+lightslategrey
+lightsteelblue
+lightyellow
+lime
+limegreen
+linen
+magenta
+maroon
+mediumaquamarine
+mediumblue
+mediumorchid
+mediumpurple
+mediumseagreen
+mediumslateblue
+mediumspringgreen
+mediumturquoise
+mediumvioletred
+midnightblue
+mintcream
+mistyrose
+moccasin
+navajowhite
+navy
+oldlace
+olive
+olivedrab
+orange
+orangered
+orchid
+palegoldenrod
+palegreen
+paleturquoise
+palevioletred
+papayawhip
+peachpuff
+peru
+pink
+plum
+powderblue
+purple
+red
+rosybrown
+royalblue
+saddlebrown
+salmon
+sandybrown
+seagreen
+seashell
+sienna
+silver
+skyblue
+slateblue
+slategray
+slategrey
+snow
+springgreen
+steelblue
+tan
+teal
+thistle
+tomato
+turquoise
+violet
+wheat
+white
+whitesmoke
+yellow
+yellowgreen
+#######################################
+#
+# Gradient units values
+#
+#######################################
+userSpaceOnUse
+objectBoundingBox