1<!-- Created with Inkscape (http://www.inkscape.org/) --><svg xmlns:ns1="https://launchpad.net/jessyink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="1000" height="1000" viewBox="0 0 1000 1000" version="1.1" id="svg8" inkscape:version="0.92.4 5da689c313, 2019-01-14" sodipodi:docname="test.svg">
2  <defs id="defs33">
3    <marker inkscape:stockid="Arrow2Lstart" orient="auto" refY="0.0" refX="0.0" id="Arrow2Lstart" style="overflow:visible" inkscape:isstock="true">
4      <path id="path859" style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1" d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z " transform="scale(1.1) translate(1,0)"/>
5    </marker>
6  </defs>
7  <sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" inkscape:window-height="1017" id="base" showgrid="true" inkscape:snap-text-baseline="true" inkscape:zoom="0.6675088" inkscape:cx="202.7349" inkscape:cy="481.16986" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="webslicer-layer">
8    <inkscape:grid type="xygrid" id="grid27" spacingx="10" spacingy="10" empspacing="10" color="#8080ff" opacity="0.25098039" empcolor="#0000ff" empopacity="0.25098039"/>
9  </sodipodi:namedview>
10  <g inkscape:groupmode="layer" id="webslicer-layer" inkscape:label="Slide3" style="display:inline">
11    <rect height="201.49811" width="248.69112" y="353.92661" x="651.3089" id="slicerect1" style="opacity:0.5;fill:#ff0000;stroke-width:1.11927199">
12      <desc id="desc51">format: png
13dpi: 96
14layout-disposition: bg-el-norepeat
15layout-position-anchor: tl</desc>
16    </rect>
17  </g>
18  <g inkscape:groupmode="layer" id="layer2" inkscape:label="Slide2" style="display:inline">
19    <circle style="display:inline;fill:#000080;stroke:none" id="c1" cx="150" cy="450" r="50" inkscape:label="#path3736"/>
20    <ellipse style="display:inline;fill:none;stroke:#ff0000;stroke-width:16" id="c2" cx="400" cy="450" rx="100" ry="50" inkscape:label="#path3738"/>
21    <path style="display:inline;fill:#ffff00;stroke:#008000;stroke-width:16" id="c3" sodipodi:type="arc" sodipodi:cx="700" sodipodi:cy="450" sodipodi:rx="100" sodipodi:ry="50" sodipodi:start="0.59013865" sodipodi:end="5.6484511" d="m 783.08635,477.82381 a 100,50 0 0 1 -111.09848,20.17442 100,50 0 0 1 -71.96301,-46.88343 100,50 0 0 1 67.71127,-48.44091 100,50 0 0 1 112.7868,17.67793 L 700,450 Z" inkscape:label="#path3740"/>
22    <path style="display:inline;fill:none;stroke:#000000;stroke-width:10;marker-start:url(#Arrow2Lstart)" d="M 100,600 200,700 300,600 400,700" id="p1" inkscape:connector-curvature="0" inkscape:label="#path3746"/>
23    <path style="display:inline;fill:none;stroke:#000000;stroke-width:10" d="m 500,600 c 0,0 0,100 100,100 100,0 0,-100 100,-100 100,0 100,100 100,100" id="p2" inkscape:connector-curvature="0" inkscape:label="#path3748"/>
24    <path sodipodi:type="star" style="display:inline;fill:#ffff00;stroke:#008000;stroke-width:10" id="s1" sodipodi:sides="5" sodipodi:cx="189.03001" sodipodi:cy="847.93945" sodipodi:r1="69.364868" sodipodi:r2="34.682434" sodipodi:arg1="0.63598373" sodipodi:arg2="1.2643023" inkscape:flatsided="false" inkscape:rounded="0" inkscape:randomized="0" d="m 244.8332,889.14005 -45.33887,-8.13446 -32.40428,32.73753 -6.27415,-45.63352 -41.14872,-20.70184 41.46124,-20.06861 6.97297,-45.53197 31.89861,33.23044 45.45824,-7.43847 -21.74681,40.60615 z" inkscape:transform-center-x="6.4673011" inkscape:transform-center-y="-0.16430137"/>
25    <use style="display:inline" x="0" y="0" xlink:href="#s1" inkscape:transform-center-x="6.4673011" inkscape:transform-center-y="-0.16430137" id="u1" transform="translate(200,2.9962152)" width="100%" height="100%" inkscape:label="#use3808"/>
26  </g>
27  <g inkscape:label="Slide1" inkscape:groupmode="layer" id="layer1" style="display:inline">
28    <text xml:space="preserve" style="font-size:14.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke:none" x="100" y="100" id="t1" inkscape:label="#text12"><tspan sodipodi:role="line" id="tspan10" x="100" y="100">Hello World</tspan></text>
29    <flowRoot xml:space="preserve" id="t4" style="font-size:40px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke:none" transform="matrix(0.26458333,0,0,0.26458333,372.02961,28.904505)" inkscape:label="#flowRoot14"><flowRegion id="flowRegion16"><rect id="rect18" width="264.5675" height="262.16187" x="105.71429" y="219.66254"/></flowRegion><flowPara id="flowPara20" style="font-size:55.43307114px">flow text which wraps</flowPara></flowRoot>    <text xml:space="preserve" style="font-size:14.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke:none" x="200" y="100" id="t2" inkscape:label="#text3727"><tspan sodipodi:role="line" id="tspan3725" x="200" y="100" style="font-size:14.66666698px">UPPER</tspan></text>
30    <text xml:space="preserve" style="font-size:10.58333302px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke:none" x="300" y="100" id="t3" inkscape:label="#text3735"><tspan sodipodi:role="line" id="tspan3733" x="300" y="100" style="font-size:14.66666698px">Multi line</tspan><tspan sodipodi:role="line" x="300" y="118.33334" id="tspan3737" style="font-size:14.66666698px">text</tspan><tspan sodipodi:role="line" x="300" y="136.66667" id="tspan3739" style="font-size:14.66666698px">FOO</tspan></text>
31    <g id="t5" transform="translate(445.71038,-129.64807)" inkscape:label="#g3772">
32      <text id="text3762" y="229.64807" x="54.289616" style="font-size:10.58333302px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke:none" xml:space="preserve"><tspan y="229.64807" x="54.289616" id="tspan3760" sodipodi:role="line" style="font-size:14.66666698px">Grouped</tspan></text>
33      <text id="text3766" y="259.64807" x="54.289619" style="font-size:14.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke:none" xml:space="preserve"><tspan y="259.64807" x="54.289619" id="tspan3764" sodipodi:role="line" style="font-size:14.66666698px">text</tspan></text>
34    </g>
35    <rect style="fill:#000080;stroke:none" id="r1" width="100" height="100" x="100" y="200" inkscape:label="#rect3732"/>
36    <rect style="fill:none;stroke:#ff0000;stroke-width:16" id="r2" width="200" height="100" x="300" y="200" inkscape:label="#rect3734"/>
37    <rect style="fill:#ffff00;stroke:#008000;stroke-width:16" id="r3" width="200" height="100" x="600" y="200" ry="38.950798" rx="44.943226" inkscape:label="#rect3744"/>
38  </g>
39  <svg:script id="JessyInk" ns1:version="1.5.5">// Copyright 2008, 2009 Hannes Hochreiner
40// This program is free software: you can redistribute it and/or modify
41// it under the terms of the GNU General Public License as published by
42// the Free Software Foundation, either version 3 of the License, or
43// (at your option) any later version.
44//
45// This program is distributed in the hope that it will be useful,
46// but WITHOUT ANY WARRANTY; without even the implied warranty of
47// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
48// GNU General Public License for more details.
49//
50// You should have received a copy of the GNU General Public License
51// along with this program.  If not, see http://www.gnu.org/licenses/.
52
53// Set onload event handler.
54window.onload = jessyInkInit;
55
56// Creating a namespace dictionary. The standard Inkscape namespaces are taken from inkex.py.
57var NSS = new Object();
58NSS['sodipodi']='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd';
59NSS['cc']='http://web.resource.org/cc/';
60NSS['svg']='http://www.w3.org/2000/svg';
61NSS['dc']='http://purl.org/dc/elements/1.1/';
62NSS['rdf']='http://www.w3.org/1999/02/22-rdf-syntax-ns#';
63NSS['inkscape']='http://www.inkscape.org/namespaces/inkscape';
64NSS['xlink']='http://www.w3.org/1999/xlink';
65NSS['xml']='http://www.w3.org/XML/1998/namespace';
66NSS['jessyink']='https://launchpad.net/jessyink';
67
68// Keycodes.
69var LEFT_KEY = 37; // cursor left keycode
70var UP_KEY = 38; // cursor up keycode
71var RIGHT_KEY = 39; // cursor right keycode
72var DOWN_KEY = 40; // cursor down keycode
73var PAGE_UP_KEY = 33; // page up keycode
74var PAGE_DOWN_KEY = 34; // page down keycode
75var HOME_KEY = 36; // home keycode
76var END_KEY = 35; // end keycode
77var ENTER_KEY = 13; // next slide
78var SPACE_KEY = 32;
79var ESCAPE_KEY = 27;
80
81// Presentation modes.
82var SLIDE_MODE = 1;
83var INDEX_MODE = 2;
84var DRAWING_MODE = 3;
85
86// Mouse handler actions.
87var MOUSE_UP = 1;
88var MOUSE_DOWN = 2;
89var MOUSE_MOVE = 3;
90var MOUSE_WHEEL = 4;
91
92// Parameters.
93var ROOT_NODE = document.getElementsByTagNameNS(NSS["svg"], "svg")[0];
94var HEIGHT = 0;
95var WIDTH = 0;
96var INDEX_COLUMNS_DEFAULT = 4;
97var INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT;
98var INDEX_OFFSET = 0;
99var STATE_START = -1;
100var STATE_END = -2;
101var BACKGROUND_COLOR = null;
102var slides = new Array();
103
104// Initialisation.
105var currentMode = SLIDE_MODE;
106var masterSlide = null;
107var activeSlide = 0;
108var activeEffect = 0;
109var timeStep = 30; // 40 ms equal 25 frames per second.
110var lastFrameTime = null;
111var processingEffect = false;
112var transCounter = 0;
113var effectArray = 0;
114var defaultTransitionInDict = new Object();
115defaultTransitionInDict["name"] = "appear";
116var defaultTransitionOutDict = new Object();
117defaultTransitionOutDict["name"] = "appear";
118var jessyInkInitialised = false;
119
120// Initialise char and key code dictionaries.
121var charCodeDictionary = getDefaultCharCodeDictionary();
122var keyCodeDictionary = getDefaultKeyCodeDictionary();
123
124// Initialise mouse handler dictionary.
125var mouseHandlerDictionary = getDefaultMouseHandlerDictionary();
126
127var progress_bar_visible = false;
128var timer_elapsed = 0;
129var timer_start = timer_elapsed;
130var timer_duration = 15; // 15 minutes
131
132var history_counter = 0;
133var history_original_elements = new Array();
134var history_presentation_elements = new Array();
135
136var mouse_original_path = null;
137var mouse_presentation_path = null;
138var mouse_last_x = -1;
139var mouse_last_y = -1;
140var mouse_min_dist_sqr = 3 * 3;
141var path_colour = "red";
142var path_width_default = 3;
143var path_width = path_width_default;
144var path_paint_width = path_width;
145
146var number_of_added_slides = 0;
147
148/** Initialisation function.
149 *  The whole presentation is set-up in this function.
150 */
151function jessyInkInit()
152{
153    // Make sure we only execute this code once. Double execution can occur if the onload event handler is set
154    // in the main svg tag as well (as was recommended in earlier versions). Executing this function twice does
155    // not lead to any problems, but it takes more time.
156    if (jessyInkInitialised)
157        return;
158
159    // Making the presentation scalable.
160    var VIEWBOX = ROOT_NODE.getAttribute("viewBox");
161
162    if (VIEWBOX)
163    {
164        WIDTH = ROOT_NODE.viewBox.animVal.width;
165        HEIGHT = ROOT_NODE.viewBox.animVal.height;
166    }
167    else
168    {
169        HEIGHT = parseFloat(ROOT_NODE.getAttribute("height"));
170        WIDTH = parseFloat(ROOT_NODE.getAttribute("width"));
171        ROOT_NODE.setAttribute("viewBox", "0 0 " + WIDTH + " " + HEIGHT);
172    }
173
174    ROOT_NODE.setAttribute("width", "100%");
175    ROOT_NODE.setAttribute("height", "100%");
176
177    // Setting the background color.
178    var namedViews = document.getElementsByTagNameNS(NSS["sodipodi"], "namedview");
179
180    for (var counter = 0; counter &lt; namedViews.length; counter++)
181    {
182        if (namedViews[counter].hasAttribute("id") &amp;&amp; namedViews[counter].hasAttribute("pagecolor"))
183        {
184            if (namedViews[counter].getAttribute("id") == "base")
185            {
186                BACKGROUND_COLOR = namedViews[counter].getAttribute("pagecolor");
187                var newAttribute = "background-color:" + BACKGROUND_COLOR + ";";
188
189                if (ROOT_NODE.hasAttribute("style"))
190                    newAttribute += ROOT_NODE.getAttribute("style");
191
192                ROOT_NODE.setAttribute("style", newAttribute);
193            }
194        }
195    }
196
197    // Defining clip-path.
198    var defsNodes = document.getElementsByTagNameNS(NSS["svg"], "defs");
199
200    if (defsNodes.length &gt; 0)
201    {
202        var existingClipPath = document.getElementById("jessyInkSlideClipPath");
203
204        if (!existingClipPath)
205        {
206            var rectNode = document.createElementNS(NSS["svg"], "rect");
207            var clipPath = document.createElementNS(NSS["svg"], "clipPath");
208
209            rectNode.setAttribute("x", 0);
210            rectNode.setAttribute("y", 0);
211            rectNode.setAttribute("width", WIDTH);
212            rectNode.setAttribute("height", HEIGHT);
213
214            clipPath.setAttribute("id", "jessyInkSlideClipPath");
215            clipPath.setAttribute("clipPathUnits", "userSpaceOnUse");
216
217            clipPath.appendChild(rectNode);
218            defsNodes[0].appendChild(clipPath);
219        }
220    }
221
222    // Making a list of the slide and finding the master slide.
223    var nodes = document.getElementsByTagNameNS(NSS["svg"], "g");
224    var tempSlides = new Array();
225    var existingJessyInkPresentationLayer = null;
226
227    for (var counter = 0; counter &lt; nodes.length; counter++)
228    {
229        if (nodes[counter].getAttributeNS(NSS["inkscape"], "groupmode") &amp;&amp; (nodes[counter].getAttributeNS(NSS["inkscape"], "groupmode") == "layer"))
230        {
231            if (nodes[counter].getAttributeNS(NSS["inkscape"], "label") &amp;&amp; nodes[counter].getAttributeNS(NSS["jessyink"], "masterSlide") == "masterSlide")
232                masterSlide = nodes[counter];
233            else if (nodes[counter].getAttributeNS(NSS["inkscape"], "label") &amp;&amp; nodes[counter].getAttributeNS(NSS["jessyink"], "presentationLayer") == "presentationLayer")
234                existingJessyInkPresentationLayer = nodes[counter];
235            else
236                tempSlides.push(nodes[counter].getAttribute("id"));
237        }
238        else if (nodes[counter].getAttributeNS(NSS['jessyink'], 'element'))
239        {
240            handleElement(nodes[counter]);
241        }
242    }
243
244    // Hide master slide set default transitions.
245    if (masterSlide)
246    {
247        masterSlide.style.display = "none";
248
249        if (masterSlide.hasAttributeNS(NSS["jessyink"], "transitionIn"))
250            defaultTransitionInDict = propStrToDict(masterSlide.getAttributeNS(NSS["jessyink"], "transitionIn"));
251
252        if (masterSlide.hasAttributeNS(NSS["jessyink"], "transitionOut"))
253            defaultTransitionOutDict = propStrToDict(masterSlide.getAttributeNS(NSS["jessyink"], "transitionOut"));
254    }
255
256    if (existingJessyInkPresentationLayer != null)
257    {
258        existingJessyInkPresentationLayer.parentNode.removeChild(existingJessyInkPresentationLayer);
259    }
260
261    // Set start slide.
262    var hashObj = new LocationHash(window.location.hash);
263
264    activeSlide = hashObj.slideNumber;
265    activeEffect = hashObj.effectNumber;
266
267    if (activeSlide &lt; 0)
268        activeSlide = 0;
269    else if (activeSlide &gt;= tempSlides.length)
270        activeSlide = tempSlides.length - 1;
271
272    var originalNode = document.getElementById(tempSlides[counter]);
273
274    var JessyInkPresentationLayer = document.createElementNS(NSS["svg"], "g");
275    JessyInkPresentationLayer.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
276    JessyInkPresentationLayer.setAttributeNS(NSS["inkscape"], "label", "JessyInk Presentation Layer");
277    JessyInkPresentationLayer.setAttributeNS(NSS["jessyink"], "presentationLayer", "presentationLayer");
278    JessyInkPresentationLayer.setAttribute("id", "jessyink_presentation_layer");
279    JessyInkPresentationLayer.style.display = "inherit";
280    ROOT_NODE.appendChild(JessyInkPresentationLayer);
281
282    // Gathering all the information about the transitions and effects of the slides, set the background
283    // from the master slide and substitute the auto-texts.
284    for (var counter = 0; counter &lt; tempSlides.length; counter++)
285    {
286        var originalNode = document.getElementById(tempSlides[counter]);
287        originalNode.style.display = "none";
288        var node = suffixNodeIds(originalNode.cloneNode(true), "_" + counter);
289        JessyInkPresentationLayer.appendChild(node);
290        slides[counter] = new Object();
291        slides[counter]["original_element"] = originalNode;
292        slides[counter]["element"] = node;
293
294        // Set build in transition.
295        slides[counter]["transitionIn"] = new Object();
296
297        var dict;
298
299        if (node.hasAttributeNS(NSS["jessyink"], "transitionIn"))
300            dict = propStrToDict(node.getAttributeNS(NSS["jessyink"], "transitionIn"));
301        else
302            dict = defaultTransitionInDict;
303
304        slides[counter]["transitionIn"]["name"] = dict["name"];
305        slides[counter]["transitionIn"]["options"] = new Object();
306
307        for (key in dict)
308            if (key != "name")
309                slides[counter]["transitionIn"]["options"][key] = dict[key];
310
311        // Set build out transition.
312        slides[counter]["transitionOut"] = new Object();
313
314        if (node.hasAttributeNS(NSS["jessyink"], "transitionOut"))
315            dict = propStrToDict(node.getAttributeNS(NSS["jessyink"], "transitionOut"));
316        else
317            dict = defaultTransitionOutDict;
318
319        slides[counter]["transitionOut"]["name"] = dict["name"];
320        slides[counter]["transitionOut"]["options"] = new Object();
321
322        for (key in dict)
323            if (key != "name")
324                slides[counter]["transitionOut"]["options"][key] = dict[key];
325
326        // Copy master slide content.
327        if (masterSlide)
328        {
329            var clonedNode = suffixNodeIds(masterSlide.cloneNode(true), "_" + counter);
330            clonedNode.removeAttributeNS(NSS["inkscape"], "groupmode");
331            clonedNode.removeAttributeNS(NSS["inkscape"], "label");
332            clonedNode.style.display = "inherit";
333
334            node.insertBefore(clonedNode, node.firstChild);
335        }
336
337        // Setting clip path.
338        node.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
339
340        // Substitute auto texts.
341        substituteAutoTexts(node, node.getAttributeNS(NSS["inkscape"], "label"), counter + 1, tempSlides.length);
342
343        node.removeAttributeNS(NSS["inkscape"], "groupmode");
344        node.removeAttributeNS(NSS["inkscape"], "label");
345
346        // Set effects.
347        var tempEffects = new Array();
348        var groups = new Object();
349
350        for (var IOCounter = 0; IOCounter &lt;= 1; IOCounter++)
351        {
352            var propName = "";
353            var dir = 0;
354
355            if (IOCounter == 0)
356            {
357                propName = "effectIn";
358                dir = 1;
359            }
360            else if (IOCounter == 1)
361            {
362                propName = "effectOut";
363                dir = -1;
364            }
365
366            var effects = getElementsByPropertyNS(node, NSS["jessyink"], propName);
367
368            for (var effectCounter = 0; effectCounter &lt; effects.length; effectCounter++)
369            {
370                var element = document.getElementById(effects[effectCounter]);
371                var dict = propStrToDict(element.getAttributeNS(NSS["jessyink"], propName));
372
373                // Put every element that has an effect associated with it, into its own group.
374                // Unless of course, we already put it into its own group.
375                if (!(groups[element.id]))
376                {
377                    var newGroup = document.createElementNS(NSS["svg"], "g");
378
379                    element.parentNode.insertBefore(newGroup, element);
380                    newGroup.appendChild(element.parentNode.removeChild(element));
381                    groups[element.id] = newGroup;
382                }
383
384                var effectDict = new Object();
385
386                effectDict["effect"] = dict["name"];
387                effectDict["dir"] = dir;
388                effectDict["element"] = groups[element.id];
389
390                for (var option in dict)
391                {
392                    if ((option != "name") &amp;&amp; (option != "order"))
393                    {
394                        if (!effectDict["options"])
395                            effectDict["options"] = new Object();
396
397                        effectDict["options"][option] = dict[option];
398                    }
399                }
400
401                if (!tempEffects[dict["order"]])
402                    tempEffects[dict["order"]] = new Array();
403
404                tempEffects[dict["order"]][tempEffects[dict["order"]].length] = effectDict;
405            }
406        }
407
408        // Make invisible, but keep in rendering tree to ensure that bounding box can be calculated.
409        node.setAttribute("opacity",0);
410        node.style.display = "inherit";
411
412        // Create a transform group.
413        var transformGroup = document.createElementNS(NSS["svg"], "g");
414
415        // Add content to transform group.
416        while (node.firstChild)
417            transformGroup.appendChild(node.firstChild);
418
419        // Transfer the transform attribute from the node to the transform group.
420        if (node.getAttribute("transform"))
421        {
422            transformGroup.setAttribute("transform", node.getAttribute("transform"));
423            node.removeAttribute("transform");
424        }
425
426        // Create a view group.
427        var viewGroup = document.createElementNS(NSS["svg"], "g");
428
429        viewGroup.appendChild(transformGroup);
430        slides[counter]["viewGroup"] = node.appendChild(viewGroup);
431
432        // Insert background.
433        if (BACKGROUND_COLOR != null)
434        {
435            var rectNode = document.createElementNS(NSS["svg"], "rect");
436
437            rectNode.setAttribute("x", 0);
438            rectNode.setAttribute("y", 0);
439            rectNode.setAttribute("width", WIDTH);
440            rectNode.setAttribute("height", HEIGHT);
441            rectNode.setAttribute("id", "jessyInkBackground" + counter);
442            rectNode.setAttribute("fill", BACKGROUND_COLOR);
443
444            slides[counter]["viewGroup"].insertBefore(rectNode, slides[counter]["viewGroup"].firstChild);
445        }
446
447        // Set views.
448        var tempViews = new Array();
449        var views = getElementsByPropertyNS(node, NSS["jessyink"], "view");
450        var matrixOld = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1);
451
452        // Set initial view even if there are no other views.
453        slides[counter]["viewGroup"].setAttribute("transform", matrixOld.toAttribute());
454        slides[counter].initialView = matrixOld.toAttribute();
455
456        for (var viewCounter = 0; viewCounter &lt; views.length; viewCounter++)
457        {
458            var element = document.getElementById(views[viewCounter]);
459            var dict = propStrToDict(element.getAttributeNS(NSS["jessyink"], "view"));
460
461            if (dict["order"] == 0)
462            {
463                matrixOld = pointMatrixToTransformation(rectToMatrix(element)).mult((new matrixSVG()).fromSVGMatrix(slides[counter].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(element.parentNode.getScreenCTM())).inv());
464                slides[counter].initialView = matrixOld.toAttribute();
465            }
466            else
467            {
468                var effectDict = new Object();
469
470                effectDict["effect"] = dict["name"];
471                effectDict["dir"] = 1;
472                effectDict["element"] = slides[counter]["viewGroup"];
473                effectDict["order"] = dict["order"];
474
475                for (var option in dict)
476                {
477                    if ((option != "name") &amp;&amp; (option != "order"))
478                    {
479                        if (!effectDict["options"])
480                            effectDict["options"] = new Object();
481
482                        effectDict["options"][option] = dict[option];
483                    }
484                }
485
486                effectDict["options"]["matrixNew"] = pointMatrixToTransformation(rectToMatrix(element)).mult((new matrixSVG()).fromSVGMatrix(slides[counter].viewGroup.getScreenCTM()).inv().mult((new matrixSVG()).fromSVGMatrix(element.parentNode.getScreenCTM())).inv());
487
488                tempViews[dict["order"]] = effectDict;
489            }
490
491            // Remove element.
492            element.parentNode.removeChild(element);
493        }
494
495        // Consolidate view array and append it to the effect array.
496        if (tempViews.length &gt; 0)
497        {
498            for (var viewCounter = 0; viewCounter &lt; tempViews.length; viewCounter++)
499            {
500                if (tempViews[viewCounter])
501                {
502                    tempViews[viewCounter]["options"]["matrixOld"] = matrixOld;
503                    matrixOld = tempViews[viewCounter]["options"]["matrixNew"];
504
505                    if (!tempEffects[tempViews[viewCounter]["order"]])
506                        tempEffects[tempViews[viewCounter]["order"]] = new Array();
507
508                    tempEffects[tempViews[viewCounter]["order"]][tempEffects[tempViews[viewCounter]["order"]].length] = tempViews[viewCounter];
509                }
510            }
511        }
512
513        // Set consolidated effect array.
514        if (tempEffects.length &gt; 0)
515        {
516            slides[counter]["effects"] = new Array();
517
518            for (var effectCounter = 0; effectCounter &lt; tempEffects.length; effectCounter++)
519            {
520                if (tempEffects[effectCounter])
521                    slides[counter]["effects"][slides[counter]["effects"].length] = tempEffects[effectCounter];
522            }
523        }
524
525        node.setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) &amp;&amp; ( activeSlide != " + counter + ")) { indexSetActiveSlide(" + counter + "); };");
526
527        // Set visibility for initial state.
528        if (counter == activeSlide)
529        {
530            node.style.display = "inherit";
531            node.setAttribute("opacity",1);
532        }
533        else
534        {
535            node.style.display = "none";
536            node.setAttribute("opacity",0);
537        }
538    }
539
540    // Set key handler.
541    var jessyInkObjects = document.getElementsByTagNameNS(NSS["svg"], "g");
542
543    for (var counter = 0; counter &lt; jessyInkObjects.length; counter++)
544    {
545        var elem = jessyInkObjects[counter];
546
547        if (elem.getAttributeNS(NSS["jessyink"], "customKeyBindings"))
548        {
549            if (elem.getCustomKeyBindings != undefined)
550                keyCodeDictionary = elem.getCustomKeyBindings();
551
552            if (elem.getCustomCharBindings != undefined)
553                charCodeDictionary = elem.getCustomCharBindings();
554        }
555    }
556
557    // Set mouse handler.
558    var jessyInkMouseHandler = document.getElementsByTagNameNS(NSS["jessyink"], "mousehandler");
559
560    for (var counter = 0; counter &lt; jessyInkMouseHandler.length; counter++)
561    {
562        var elem = jessyInkMouseHandler[counter];
563
564        if (elem.getMouseHandler != undefined)
565        {
566            var tempDict = elem.getMouseHandler();
567
568            for (mode in tempDict)
569            {
570                if (!mouseHandlerDictionary[mode])
571                    mouseHandlerDictionary[mode] = new Object();
572
573                for (handler in tempDict[mode])
574                    mouseHandlerDictionary[mode][handler] = tempDict[mode][handler];
575            }
576        }
577    }
578
579    // Check effect number.
580    if ((activeEffect &lt; 0) || (!slides[activeSlide].effects))
581    {
582        activeEffect = 0;
583    }
584    else if (activeEffect &gt; slides[activeSlide].effects.length)
585    {
586        activeEffect = slides[activeSlide].effects.length;
587    }
588
589    createProgressBar(JessyInkPresentationLayer);
590    hideProgressBar();
591    setProgressBarValue(activeSlide);
592    setTimeIndicatorValue(0);
593    setInterval("updateTimer()", 1000);
594    setSlideToState(activeSlide, activeEffect);
595    jessyInkInitialised = true;
596}
597
598/** Function to substitute the auto-texts.
599 *
600 *  @param node the node
601 *  @param slideName name of the slide the node is on
602 *  @param slideNumber number of the slide the node is on
603 *  @param numberOfSlides number of slides in the presentation
604 */
605function substituteAutoTexts(node, slideName, slideNumber, numberOfSlides)
606{
607    var texts = node.getElementsByTagNameNS(NSS["svg"], "tspan");
608
609    for (var textCounter = 0; textCounter &lt; texts.length; textCounter++)
610    {
611        if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "slideNumber")
612            texts[textCounter].firstChild.nodeValue = slideNumber;
613        else if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "numberOfSlides")
614            texts[textCounter].firstChild.nodeValue = numberOfSlides;
615        else if (texts[textCounter].getAttributeNS(NSS["jessyink"], "autoText") == "slideTitle")
616            texts[textCounter].firstChild.nodeValue = slideName;
617    }
618}
619
620/** Convenience function to get an element depending on whether it has a property with a particular name.
621 *    This function emulates some dearly missed XPath functionality.
622 *
623 *  @param node the node
624 *  @param namespace namespace of the attribute
625 *  @param name attribute name
626 */
627function getElementsByPropertyNS(node, namespace, name)
628{
629    var elems = new Array();
630
631    if (node.getAttributeNS(namespace, name))
632        elems.push(node.getAttribute("id"));
633
634    for (var counter = 0; counter &lt; node.childNodes.length; counter++)
635    {
636        if (node.childNodes[counter].nodeType == 1)
637            elems = elems.concat(getElementsByPropertyNS(node.childNodes[counter], namespace, name));
638    }
639
640    return elems;
641}
642
643/** Function to dispatch the next effect, if there is none left, change the slide.
644 *
645 *  @param dir direction of the change (1 = forwards, -1 = backwards)
646 */
647function dispatchEffects(dir)
648{
649    if (slides[activeSlide]["effects"] &amp;&amp; (((dir == 1) &amp;&amp; (activeEffect &lt; slides[activeSlide]["effects"].length)) || ((dir == -1) &amp;&amp; (activeEffect &gt; 0))))
650    {
651        processingEffect = true;
652
653        if (dir == 1)
654        {
655            effectArray = slides[activeSlide]["effects"][activeEffect];
656            activeEffect += dir;
657        }
658        else if (dir == -1)
659        {
660            activeEffect += dir;
661            effectArray = slides[activeSlide]["effects"][activeEffect];
662        }
663
664        transCounter = 0;
665        startTime = (new Date()).getTime();
666        lastFrameTime = null;
667        effect(dir);
668    }
669    else if (((dir == 1) &amp;&amp; (activeSlide &lt; (slides.length - 1))) || (((dir == -1) &amp;&amp; (activeSlide &gt; 0))))
670    {
671        changeSlide(dir);
672    }
673}
674
675/** Function to skip effects and directly either put the slide into start or end state or change slides.
676 *
677 *  @param dir direction of the change (1 = forwards, -1 = backwards)
678 */
679function skipEffects(dir)
680{
681    if (slides[activeSlide]["effects"] &amp;&amp; (((dir == 1) &amp;&amp; (activeEffect &lt; slides[activeSlide]["effects"].length)) || ((dir == -1) &amp;&amp; (activeEffect &gt; 0))))
682    {
683        processingEffect = true;
684
685        if (slides[activeSlide]["effects"] &amp;&amp; (dir == 1))
686            activeEffect = slides[activeSlide]["effects"].length;
687        else
688            activeEffect = 0;
689
690        if (dir == 1)
691            setSlideToState(activeSlide, STATE_END);
692        else
693            setSlideToState(activeSlide, STATE_START);
694
695        processingEffect = false;
696    }
697    else if (((dir == 1) &amp;&amp; (activeSlide &lt; (slides.length - 1))) || (((dir == -1) &amp;&amp; (activeSlide &gt; 0))))
698    {
699        changeSlide(dir);
700    }
701}
702
703/** Function to change between slides.
704 *
705 *  @param dir direction (1 = forwards, -1 = backwards)
706 */
707function changeSlide(dir)
708{
709    processingEffect = true;
710    effectArray = new Array();
711
712    effectArray[0] = new Object();
713    if (dir == 1)
714    {
715        effectArray[0]["effect"] = slides[activeSlide]["transitionOut"]["name"];
716        effectArray[0]["options"] = slides[activeSlide]["transitionOut"]["options"];
717        effectArray[0]["dir"] = -1;
718    }
719    else if (dir == -1)
720    {
721        effectArray[0]["effect"] = slides[activeSlide]["transitionIn"]["name"];
722        effectArray[0]["options"] = slides[activeSlide]["transitionIn"]["options"];
723        effectArray[0]["dir"] = 1;
724    }
725    effectArray[0]["element"] = slides[activeSlide]["element"];
726
727    activeSlide += dir;
728    setProgressBarValue(activeSlide);
729
730    effectArray[1] = new Object();
731
732    if (dir == 1)
733    {
734        effectArray[1]["effect"] = slides[activeSlide]["transitionIn"]["name"];
735        effectArray[1]["options"] = slides[activeSlide]["transitionIn"]["options"];
736        effectArray[1]["dir"] = 1;
737    }
738    else if (dir == -1)
739    {
740        effectArray[1]["effect"] = slides[activeSlide]["transitionOut"]["name"];
741        effectArray[1]["options"] = slides[activeSlide]["transitionOut"]["options"];
742        effectArray[1]["dir"] = -1;
743    }
744
745    effectArray[1]["element"] = slides[activeSlide]["element"];
746
747    if (slides[activeSlide]["effects"] &amp;&amp; (dir == -1))
748        activeEffect = slides[activeSlide]["effects"].length;
749    else
750        activeEffect = 0;
751
752    if (dir == -1)
753        setSlideToState(activeSlide, STATE_END);
754    else
755        setSlideToState(activeSlide, STATE_START);
756
757    transCounter = 0;
758    startTime = (new Date()).getTime();
759    lastFrameTime = null;
760    effect(dir);
761}
762
763/** Function to toggle between index and slide mode.
764*/
765function toggleSlideIndex()
766{
767    var suspendHandle = ROOT_NODE.suspendRedraw(500);
768
769    if (currentMode == SLIDE_MODE)
770    {
771        hideProgressBar();
772        INDEX_OFFSET = -1;
773        indexSetPageSlide(activeSlide);
774        currentMode = INDEX_MODE;
775    }
776    else if (currentMode == INDEX_MODE)
777    {
778        for (var counter = 0; counter &lt; slides.length; counter++)
779        {
780            slides[counter]["element"].setAttribute("transform","scale(1)");
781
782            if (counter == activeSlide)
783            {
784                slides[counter]["element"].style.display = "inherit";
785                slides[counter]["element"].setAttribute("opacity",1);
786                activeEffect = 0;
787            }
788            else
789            {
790                slides[counter]["element"].setAttribute("opacity",0);
791                slides[counter]["element"].style.display = "none";
792            }
793        }
794        currentMode = SLIDE_MODE;
795        setSlideToState(activeSlide, STATE_START);
796        setProgressBarValue(activeSlide);
797
798        if (progress_bar_visible)
799        {
800            showProgressBar();
801        }
802    }
803
804    ROOT_NODE.unsuspendRedraw(suspendHandle);
805    ROOT_NODE.forceRedraw();
806}
807
808/** Function to run an effect.
809 *
810 *  @param dir direction in which to play the effect (1 = forwards, -1 = backwards)
811 */
812function effect(dir)
813{
814    var done = true;
815
816    var suspendHandle = ROOT_NODE.suspendRedraw(200);
817
818    for (var counter = 0; counter &lt; effectArray.length; counter++)
819    {
820        if (effectArray[counter]["effect"] == "fade")
821            done &amp;= fade(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
822        else if (effectArray[counter]["effect"] == "appear")
823            done &amp;= appear(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
824        else if (effectArray[counter]["effect"] == "pop")
825            done &amp;= pop(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
826        else if (effectArray[counter]["effect"] == "view")
827            done &amp;= view(parseInt(effectArray[counter]["dir"]) * dir, effectArray[counter]["element"], transCounter, effectArray[counter]["options"]);
828    }
829
830    ROOT_NODE.unsuspendRedraw(suspendHandle);
831    ROOT_NODE.forceRedraw();
832
833    if (!done)
834    {
835        var currentTime = (new Date()).getTime();
836        var timeDiff = 1;
837
838        transCounter = currentTime - startTime;
839
840        if (lastFrameTime != null)
841        {
842            timeDiff = timeStep - (currentTime - lastFrameTime);
843
844            if (timeDiff &lt;= 0)
845                timeDiff = 1;
846        }
847
848        lastFrameTime = currentTime;
849
850        window.setTimeout("effect(" + dir + ")", timeDiff);
851    }
852    else
853    {
854        window.location.hash = (activeSlide + 1) + '_' + activeEffect;
855        processingEffect = false;
856    }
857}
858
859/** Function to display the index sheet.
860 *
861 *  @param offsetNumber offset number
862 */
863function displayIndex(offsetNumber)
864{
865    var offsetX = 0;
866    var offsetY = 0;
867
868    if (offsetNumber &lt; 0)
869        offsetNumber = 0;
870    else if (offsetNumber &gt;= slides.length)
871        offsetNumber = slides.length - 1;
872
873    for (var counter = 0; counter &lt; slides.length; counter++)
874    {
875        if ((counter &lt; offsetNumber) || (counter &gt; offsetNumber + INDEX_COLUMNS * INDEX_COLUMNS - 1))
876        {
877            slides[counter]["element"].setAttribute("opacity",0);
878            slides[counter]["element"].style.display = "none";
879        }
880        else
881        {
882            offsetX = ((counter - offsetNumber) % INDEX_COLUMNS) * WIDTH;
883            offsetY = Math.floor((counter - offsetNumber) / INDEX_COLUMNS) * HEIGHT;
884
885            slides[counter]["element"].setAttribute("transform","scale("+1/INDEX_COLUMNS+") translate("+offsetX+","+offsetY+")");
886            slides[counter]["element"].style.display = "inherit";
887            slides[counter]["element"].setAttribute("opacity",0.5);
888        }
889
890        setSlideToState(counter, STATE_END);
891    }
892
893    //do we need to save the current offset?
894    if (INDEX_OFFSET != offsetNumber)
895        INDEX_OFFSET = offsetNumber;
896}
897
898/** Function to set the active slide in the slide view.
899 *
900 *  @param nbr index of the active slide
901 */
902function slideSetActiveSlide(nbr)
903{
904    if (nbr &gt;= slides.length)
905        nbr = slides.length - 1;
906    else if (nbr &lt; 0)
907        nbr = 0;
908
909    slides[activeSlide]["element"].setAttribute("opacity",0);
910    slides[activeSlide]["element"].style.display = "none";
911
912    activeSlide = parseInt(nbr);
913
914    setSlideToState(activeSlide, STATE_START);
915    slides[activeSlide]["element"].style.display = "inherit";
916    slides[activeSlide]["element"].setAttribute("opacity",1);
917
918    activeEffect = 0;
919    setProgressBarValue(nbr);
920}
921
922/** Function to set the active slide in the index view.
923 *
924 *  @param nbr index of the active slide
925 */
926function indexSetActiveSlide(nbr)
927{
928    if (nbr &gt;= slides.length)
929        nbr = slides.length - 1;
930    else if (nbr &lt; 0)
931        nbr = 0;
932
933    slides[activeSlide]["element"].setAttribute("opacity",0.5);
934
935    activeSlide = parseInt(nbr);
936    window.location.hash = (activeSlide + 1) + '_0';
937
938    slides[activeSlide]["element"].setAttribute("opacity",1);
939}
940
941/** Function to set the page and active slide in index view.
942 *
943 *  @param nbr index of the active slide
944 *
945 *  NOTE: To force a redraw,
946 *  set INDEX_OFFSET to -1 before calling indexSetPageSlide().
947 *
948 *  This is necessary for zooming (otherwise the index might not
949 *  get redrawn) and when switching to index mode.
950 *
951 *  INDEX_OFFSET = -1
952 *  indexSetPageSlide(activeSlide);
953 */
954function indexSetPageSlide(nbr)
955{
956    if (nbr &gt;= slides.length)
957        nbr = slides.length - 1;
958    else if (nbr &lt; 0)
959        nbr = 0;
960
961    //calculate the offset
962    var offset = nbr - nbr % (INDEX_COLUMNS * INDEX_COLUMNS);
963
964    if (offset &lt; 0)
965        offset = 0;
966
967    //if different from kept offset, then record and change the page
968    if (offset != INDEX_OFFSET)
969    {
970        INDEX_OFFSET = offset;
971        displayIndex(INDEX_OFFSET);
972    }
973
974    //set the active slide
975    indexSetActiveSlide(nbr);
976}
977
978/** Event handler for key press.
979 *
980 *  @param e the event
981 */
982function keydown(e)
983{
984    if (!e)
985        e = window.event;
986
987    code = e.keyCode || e.charCode;
988
989    if (!processingEffect &amp;&amp; keyCodeDictionary[currentMode] &amp;&amp; keyCodeDictionary[currentMode][code])
990        return keyCodeDictionary[currentMode][code]();
991    else
992        document.onkeypress = keypress;
993}
994// Set event handler for key down.
995document.onkeydown = keydown;
996
997/** Event handler for key press.
998 *
999 *  @param e the event
1000 */
1001function keypress(e)
1002{
1003    document.onkeypress = null;
1004
1005    if (!e)
1006        e = window.event;
1007
1008    str = String.fromCharCode(e.keyCode || e.charCode);
1009
1010    if (!processingEffect &amp;&amp; charCodeDictionary[currentMode] &amp;&amp; charCodeDictionary[currentMode][str])
1011        return charCodeDictionary[currentMode][str]();
1012}
1013
1014/** Function to supply the default char code dictionary.
1015 *
1016 * @returns default char code dictionary
1017 */
1018function getDefaultCharCodeDictionary()
1019{
1020    var charCodeDict = new Object();
1021
1022    charCodeDict[SLIDE_MODE] = new Object();
1023    charCodeDict[INDEX_MODE] = new Object();
1024    charCodeDict[DRAWING_MODE] = new Object();
1025
1026    charCodeDict[SLIDE_MODE]["i"] = function () { return toggleSlideIndex(); };
1027    charCodeDict[SLIDE_MODE]["d"] = function () { return slideSwitchToDrawingMode(); };
1028    charCodeDict[SLIDE_MODE]["D"] = function () { return slideQueryDuration(); };
1029    charCodeDict[SLIDE_MODE]["n"] = function () { return slideAddSlide(activeSlide); };
1030    charCodeDict[SLIDE_MODE]["p"] = function () { return slideToggleProgressBarVisibility(); };
1031    charCodeDict[SLIDE_MODE]["t"] = function () { return slideResetTimer(); };
1032    charCodeDict[SLIDE_MODE]["e"] = function () { return slideUpdateExportLayer(); };
1033
1034    charCodeDict[DRAWING_MODE]["d"] = function () { return drawingSwitchToSlideMode(); };
1035    charCodeDict[DRAWING_MODE]["0"] = function () { return drawingResetPathWidth(); };
1036    charCodeDict[DRAWING_MODE]["1"] = function () { return drawingSetPathWidth(1.0); };
1037    charCodeDict[DRAWING_MODE]["3"] = function () { return drawingSetPathWidth(3.0); };
1038    charCodeDict[DRAWING_MODE]["5"] = function () { return drawingSetPathWidth(5.0); };
1039    charCodeDict[DRAWING_MODE]["7"] = function () { return drawingSetPathWidth(7.0); };
1040    charCodeDict[DRAWING_MODE]["9"] = function () { return drawingSetPathWidth(9.0); };
1041    charCodeDict[DRAWING_MODE]["b"] = function () { return drawingSetPathColour("blue"); };
1042    charCodeDict[DRAWING_MODE]["c"] = function () { return drawingSetPathColour("cyan"); };
1043    charCodeDict[DRAWING_MODE]["g"] = function () { return drawingSetPathColour("green"); };
1044    charCodeDict[DRAWING_MODE]["k"] = function () { return drawingSetPathColour("black"); };
1045    charCodeDict[DRAWING_MODE]["m"] = function () { return drawingSetPathColour("magenta"); };
1046    charCodeDict[DRAWING_MODE]["o"] = function () { return drawingSetPathColour("orange"); };
1047    charCodeDict[DRAWING_MODE]["r"] = function () { return drawingSetPathColour("red"); };
1048    charCodeDict[DRAWING_MODE]["w"] = function () { return drawingSetPathColour("white"); };
1049    charCodeDict[DRAWING_MODE]["y"] = function () { return drawingSetPathColour("yellow"); };
1050    charCodeDict[DRAWING_MODE]["z"] = function () { return drawingUndo(); };
1051
1052    charCodeDict[INDEX_MODE]["i"] = function () { return toggleSlideIndex(); };
1053    charCodeDict[INDEX_MODE]["-"] = function () { return indexDecreaseNumberOfColumns(); };
1054    charCodeDict[INDEX_MODE]["="] = function () { return indexIncreaseNumberOfColumns(); };
1055    charCodeDict[INDEX_MODE]["+"] = function () { return indexIncreaseNumberOfColumns(); };
1056    charCodeDict[INDEX_MODE]["0"] = function () { return indexResetNumberOfColumns(); };
1057
1058    return charCodeDict;
1059}
1060
1061/** Function to supply the default key code dictionary.
1062 *
1063 * @returns default key code dictionary
1064 */
1065function getDefaultKeyCodeDictionary()
1066{
1067    var keyCodeDict = new Object();
1068
1069    keyCodeDict[SLIDE_MODE] = new Object();
1070    keyCodeDict[INDEX_MODE] = new Object();
1071    keyCodeDict[DRAWING_MODE] = new Object();
1072
1073    keyCodeDict[SLIDE_MODE][LEFT_KEY] = function() { return dispatchEffects(-1); };
1074    keyCodeDict[SLIDE_MODE][RIGHT_KEY] = function() { return dispatchEffects(1); };
1075    keyCodeDict[SLIDE_MODE][UP_KEY] = function() { return skipEffects(-1); };
1076    keyCodeDict[SLIDE_MODE][DOWN_KEY] = function() { return skipEffects(1); };
1077    keyCodeDict[SLIDE_MODE][PAGE_UP_KEY] = function() { return dispatchEffects(-1); };
1078    keyCodeDict[SLIDE_MODE][PAGE_DOWN_KEY] = function() { return dispatchEffects(1); };
1079    keyCodeDict[SLIDE_MODE][HOME_KEY] = function() { return slideSetActiveSlide(0); };
1080    keyCodeDict[SLIDE_MODE][END_KEY] = function() { return slideSetActiveSlide(slides.length - 1); };
1081    keyCodeDict[SLIDE_MODE][SPACE_KEY] = function() { return dispatchEffects(1); };
1082
1083    keyCodeDict[INDEX_MODE][LEFT_KEY] = function() { return indexSetPageSlide(activeSlide - 1); };
1084    keyCodeDict[INDEX_MODE][RIGHT_KEY] = function() { return indexSetPageSlide(activeSlide + 1); };
1085    keyCodeDict[INDEX_MODE][UP_KEY] = function() { return indexSetPageSlide(activeSlide - INDEX_COLUMNS); };
1086    keyCodeDict[INDEX_MODE][DOWN_KEY] = function() { return indexSetPageSlide(activeSlide + INDEX_COLUMNS); };
1087    keyCodeDict[INDEX_MODE][PAGE_UP_KEY] = function() { return indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS); };
1088    keyCodeDict[INDEX_MODE][PAGE_DOWN_KEY] = function() { return indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS); };
1089    keyCodeDict[INDEX_MODE][HOME_KEY] = function() { return indexSetPageSlide(0); };
1090    keyCodeDict[INDEX_MODE][END_KEY] = function() { return indexSetPageSlide(slides.length - 1); };
1091    keyCodeDict[INDEX_MODE][ENTER_KEY] = function() { return toggleSlideIndex(); };
1092
1093    keyCodeDict[DRAWING_MODE][ESCAPE_KEY] = function () { return drawingSwitchToSlideMode(); };
1094
1095    return keyCodeDict;
1096}
1097
1098/** Function to handle all mouse events.
1099 *
1100 *    @param    evnt    event
1101 *    @param    action    type of event (e.g. mouse up, mouse wheel)
1102 */
1103function mouseHandlerDispatch(evnt, action)
1104{
1105    if (!evnt)
1106        evnt = window.event;
1107
1108    var retVal = true;
1109
1110    if (!processingEffect &amp;&amp; mouseHandlerDictionary[currentMode] &amp;&amp; mouseHandlerDictionary[currentMode][action])
1111    {
1112        var subRetVal = mouseHandlerDictionary[currentMode][action](evnt);
1113
1114        if (subRetVal != null &amp;&amp; subRetVal != undefined)
1115            retVal = subRetVal;
1116    }
1117
1118    if (evnt.preventDefault &amp;&amp; !retVal)
1119        evnt.preventDefault();
1120
1121    evnt.returnValue = retVal;
1122
1123    return retVal;
1124}
1125
1126// Set mouse event handler.
1127document.onmousedown = function(e) { return mouseHandlerDispatch(e, MOUSE_DOWN); };
1128document.onmouseup = function(e) { return mouseHandlerDispatch(e, MOUSE_UP); };
1129document.onmousemove = function(e) { return mouseHandlerDispatch(e, MOUSE_MOVE); };
1130
1131// Moz
1132if (window.addEventListener)
1133{
1134    window.addEventListener('DOMMouseScroll', function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); }, false);
1135}
1136
1137// Opera Safari OK - may not work in IE
1138window.onmousewheel = function(e) { return mouseHandlerDispatch(e, MOUSE_WHEEL); };
1139
1140/** Function to supply the default mouse handler dictionary.
1141 *
1142 * @returns default mouse handler dictionary
1143 */
1144function getDefaultMouseHandlerDictionary()
1145{
1146    var mouseHandlerDict = new Object();
1147
1148    mouseHandlerDict[SLIDE_MODE] = new Object();
1149    mouseHandlerDict[INDEX_MODE] = new Object();
1150    mouseHandlerDict[DRAWING_MODE] = new Object();
1151
1152    mouseHandlerDict[SLIDE_MODE][MOUSE_DOWN] = function(evnt) { return dispatchEffects(1); };
1153    mouseHandlerDict[SLIDE_MODE][MOUSE_WHEEL] = function(evnt) { return slideMousewheel(evnt); };
1154
1155    mouseHandlerDict[INDEX_MODE][MOUSE_DOWN] = function(evnt) { return toggleSlideIndex(); };
1156
1157    mouseHandlerDict[DRAWING_MODE][MOUSE_DOWN] = function(evnt) { return drawingMousedown(evnt); };
1158    mouseHandlerDict[DRAWING_MODE][MOUSE_UP] = function(evnt) { return drawingMouseup(evnt); };
1159    mouseHandlerDict[DRAWING_MODE][MOUSE_MOVE] = function(evnt) { return drawingMousemove(evnt); };
1160
1161    return mouseHandlerDict;
1162}
1163
1164/** Function to switch from slide mode to drawing mode.
1165*/
1166function slideSwitchToDrawingMode()
1167{
1168    currentMode = DRAWING_MODE;
1169
1170    var tempDict;
1171
1172    if (ROOT_NODE.hasAttribute("style"))
1173        tempDict = propStrToDict(ROOT_NODE.getAttribute("style"));
1174    else
1175        tempDict = new Object();
1176
1177    tempDict["cursor"] = "crosshair";
1178    ROOT_NODE.setAttribute("style", dictToPropStr(tempDict));
1179}
1180
1181/** Function to switch from drawing mode to slide mode.
1182*/
1183function drawingSwitchToSlideMode()
1184{
1185    currentMode = SLIDE_MODE;
1186
1187    var tempDict;
1188
1189    if (ROOT_NODE.hasAttribute("style"))
1190        tempDict = propStrToDict(ROOT_NODE.getAttribute("style"));
1191    else
1192        tempDict = new Object();
1193
1194    tempDict["cursor"] = "auto";
1195    ROOT_NODE.setAttribute("style", dictToPropStr(tempDict));
1196}
1197
1198/** Function to decrease the number of columns in index mode.
1199*/
1200function indexDecreaseNumberOfColumns()
1201{
1202    if (INDEX_COLUMNS &gt;= 3)
1203    {
1204        INDEX_COLUMNS -= 1;
1205        INDEX_OFFSET = -1
1206            indexSetPageSlide(activeSlide);
1207    }
1208}
1209
1210/** Function to increase the number of columns in index mode.
1211*/
1212function indexIncreaseNumberOfColumns()
1213{
1214    if (INDEX_COLUMNS &lt; 7)
1215    {
1216        INDEX_COLUMNS += 1;
1217        INDEX_OFFSET = -1
1218            indexSetPageSlide(activeSlide);
1219    }
1220}
1221
1222/** Function to reset the number of columns in index mode.
1223*/
1224function indexResetNumberOfColumns()
1225{
1226    if (INDEX_COLUMNS != INDEX_COLUMNS_DEFAULT)
1227    {
1228        INDEX_COLUMNS = INDEX_COLUMNS_DEFAULT;
1229        INDEX_OFFSET = -1
1230            indexSetPageSlide(activeSlide);
1231    }
1232}
1233
1234/** Function to reset path width in drawing mode.
1235*/
1236function drawingResetPathWidth()
1237{
1238    path_width = path_width_default;
1239    set_path_paint_width();
1240}
1241
1242/** Function to set path width in drawing mode.
1243 *
1244 * @param width new path width
1245 */
1246function drawingSetPathWidth(width)
1247{
1248    path_width = width;
1249    set_path_paint_width();
1250}
1251
1252/** Function to set path colour in drawing mode.
1253 *
1254 * @param colour new path colour
1255 */
1256function drawingSetPathColour(colour)
1257{
1258    path_colour = colour;
1259}
1260
1261/** Function to query the duration of the presentation from the user in slide mode.
1262*/
1263function slideQueryDuration()
1264{
1265    var new_duration = prompt("Length of presentation in minutes?", timer_duration);
1266
1267    if ((new_duration != null) &amp;&amp; (new_duration != ''))
1268    {
1269        timer_duration = new_duration;
1270    }
1271
1272    updateTimer();
1273}
1274
1275/** Function to add new slide in slide mode.
1276 *
1277 * @param afterSlide after which slide to insert the new one
1278 */
1279function slideAddSlide(afterSlide)
1280{
1281    addSlide(afterSlide);
1282    slideSetActiveSlide(afterSlide + 1);
1283    updateTimer();
1284}
1285
1286/** Function to toggle the visibility of the progress bar in slide mode.
1287*/
1288function slideToggleProgressBarVisibility()
1289{
1290    if (progress_bar_visible)
1291    {
1292        progress_bar_visible = false;
1293        hideProgressBar();
1294    }
1295    else
1296    {
1297        progress_bar_visible = true;
1298        showProgressBar();
1299    }
1300}
1301
1302/** Function to reset the timer in slide mode.
1303*/
1304function slideResetTimer()
1305{
1306    timer_start = timer_elapsed;
1307    updateTimer();
1308}
1309
1310/** Convenience function to pad a string with zero in front up to a certain length.
1311 */
1312function padString(str, len)
1313{
1314    var outStr = str;
1315
1316    while (outStr.length &lt; len)
1317    {
1318        outStr = '0' + outStr;
1319    }
1320
1321    return outStr;
1322}
1323
1324/** Function to update the export layer.
1325 */
1326function slideUpdateExportLayer()
1327{
1328    // Suspend redraw since we are going to mess with the slides.
1329    var suspendHandle = ROOT_NODE.suspendRedraw(2000);
1330
1331    var tmpActiveSlide = activeSlide;
1332    var tmpActiveEffect = activeEffect;
1333    var exportedLayers = new Array();
1334
1335    for (var counterSlides = 0; counterSlides &lt; slides.length; counterSlides++)
1336    {
1337        var exportNode;
1338
1339        setSlideToState(counterSlides, STATE_START);
1340
1341        var maxEffect = 0;
1342
1343        if (slides[counterSlides].effects)
1344        {
1345            maxEffect = slides[counterSlides].effects.length;
1346        }
1347
1348        exportNode = slides[counterSlides].element.cloneNode(true);
1349        exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
1350        exportNode.setAttributeNS(NSS["inkscape"], "label", "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString("0", maxEffect.toString().length));
1351
1352        exportedLayers.push(exportNode);
1353
1354        if (slides[counterSlides]["effects"])
1355        {
1356            for (var counter = 0; counter &lt; slides[counterSlides]["effects"].length; counter++)
1357            {
1358                for (var subCounter = 0; subCounter &lt; slides[counterSlides]["effects"][counter].length; subCounter++)
1359                {
1360                    var effect = slides[counterSlides]["effects"][counter][subCounter];
1361                    if (effect["effect"] == "fade")
1362                        fade(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);
1363                    else if (effect["effect"] == "appear")
1364                        appear(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);
1365                    else if (effect["effect"] == "pop")
1366                        pop(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);
1367                    else if (effect["effect"] == "view")
1368                        view(parseInt(effect["dir"]), effect["element"], STATE_END, effect["options"]);
1369                }
1370
1371                var layerName = "slide_" + padString((counterSlides + 1).toString(), slides.length.toString().length) + "_effect_" + padString((counter + 1).toString(), maxEffect.toString().length);
1372                exportNode = slides[counterSlides].element.cloneNode(true);
1373                exportNode.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
1374                exportNode.setAttributeNS(NSS["inkscape"], "label", layerName);
1375                exportNode.setAttribute("id", layerName);
1376
1377                exportedLayers.push(exportNode);
1378            }
1379        }
1380    }
1381
1382    activeSlide = tmpActiveSlide;
1383    activeEffect = tmpActiveEffect;
1384    setSlideToState(activeSlide, activeEffect);
1385
1386    // Copy image.
1387    var newDoc = document.documentElement.cloneNode(true);
1388
1389    // Delete viewbox form new imag and set width and height.
1390    newDoc.removeAttribute('viewbox');
1391    newDoc.setAttribute('width', WIDTH);
1392    newDoc.setAttribute('height', HEIGHT);
1393
1394    // Delete all layers and script elements.
1395    var nodesToBeRemoved = new Array();
1396
1397    for (var childCounter = 0; childCounter &lt;  newDoc.childNodes.length; childCounter++)
1398    {
1399        var child = newDoc.childNodes[childCounter];
1400
1401        if (child.nodeType == 1)
1402        {
1403            if ((child.nodeName.toUpperCase() == 'G') || (child.nodeName.toUpperCase() == 'SCRIPT'))
1404            {
1405                nodesToBeRemoved.push(child);
1406            }
1407        }
1408    }
1409
1410    for (var ndCounter = 0; ndCounter &lt; nodesToBeRemoved.length; ndCounter++)
1411    {
1412        var nd = nodesToBeRemoved[ndCounter];
1413
1414        // Before removing the node, check whether it contains any definitions.
1415        var defs = nd.getElementsByTagNameNS(NSS["svg"], "defs");
1416
1417        for (var defsCounter = 0; defsCounter &lt; defs.length; defsCounter++)
1418        {
1419            if (defs[defsCounter].id)
1420            {
1421                newDoc.appendChild(defs[defsCounter].cloneNode(true));
1422            }
1423        }
1424
1425        // Remove node.
1426        nd.parentNode.removeChild(nd);
1427    }
1428
1429    // Set current layer.
1430    if (exportedLayers[0])
1431    {
1432        var namedView;
1433
1434        for (var nodeCounter = 0; nodeCounter &lt; newDoc.childNodes.length; nodeCounter++)
1435        {
1436            if ((newDoc.childNodes[nodeCounter].nodeType == 1) &amp;&amp; (newDoc.childNodes[nodeCounter].getAttribute('id') == 'base'))
1437            {
1438                namedView = newDoc.childNodes[nodeCounter];
1439            }
1440        }
1441
1442        if (namedView)
1443        {
1444            namedView.setAttributeNS(NSS['inkscape'], 'current-layer', exportedLayers[0].getAttributeNS(NSS['inkscape'], 'label'));
1445        }
1446    }
1447
1448    // Add exported layers.
1449    while (exportedLayers.length &gt; 0)
1450    {
1451        var nd = exportedLayers.pop();
1452
1453        nd.setAttribute("opacity",1);
1454        nd.style.display = "inherit";
1455
1456        newDoc.appendChild(nd);
1457    }
1458
1459    // Serialise the new document.
1460  window.location = 'data:application/svg+xml;base64;charset=utf-8,' + window.btoa(unescape(encodeURIComponent((new XMLSerializer()).serializeToString(newDoc))));
1461
1462    // Unsuspend redraw.
1463    ROOT_NODE.unsuspendRedraw(suspendHandle);
1464    ROOT_NODE.forceRedraw();
1465}
1466
1467/** Function to undo last drawing operation.
1468*/
1469function drawingUndo()
1470{
1471    mouse_presentation_path = null;
1472    mouse_original_path = null;
1473
1474    if (history_presentation_elements.length &gt; 0)
1475    {
1476        var p = history_presentation_elements.pop();
1477        var parent = p.parentNode.removeChild(p);
1478
1479        p = history_original_elements.pop();
1480        parent = p.parentNode.removeChild(p);
1481    }
1482}
1483
1484/** Event handler for mouse down in drawing mode.
1485 *
1486 *  @param e the event
1487 */
1488function drawingMousedown(e)
1489{
1490    var value = 0;
1491
1492    if (e.button)
1493        value = e.button;
1494    else if (e.which)
1495        value = e.which;
1496
1497    if (value == 1)
1498    {
1499        history_counter++;
1500
1501        var p = calcCoord(e);
1502
1503        mouse_last_x = e.clientX;
1504        mouse_last_y = e.clientY;
1505        mouse_original_path = document.createElementNS(NSS["svg"], "path");
1506        mouse_original_path.setAttribute("stroke", path_colour);
1507        mouse_original_path.setAttribute("stroke-width", path_paint_width);
1508        mouse_original_path.setAttribute("fill", "none");
1509        mouse_original_path.setAttribute("id", "path " + Date());
1510        mouse_original_path.setAttribute("d", "M" + p.x + "," + p.y);
1511        slides[activeSlide]["original_element"].appendChild(mouse_original_path);
1512        history_original_elements.push(mouse_original_path);
1513
1514        mouse_presentation_path = document.createElementNS(NSS["svg"], "path");
1515        mouse_presentation_path.setAttribute("stroke", path_colour);
1516        mouse_presentation_path.setAttribute("stroke-width", path_paint_width);
1517        mouse_presentation_path.setAttribute("fill", "none");
1518        mouse_presentation_path.setAttribute("id", "path " + Date() + " presentation copy");
1519        mouse_presentation_path.setAttribute("d", "M" + p.x + "," + p.y);
1520
1521        if (slides[activeSlide]["viewGroup"])
1522            slides[activeSlide]["viewGroup"].appendChild(mouse_presentation_path);
1523        else
1524            slides[activeSlide]["element"].appendChild(mouse_presentation_path);
1525
1526        history_presentation_elements.push(mouse_presentation_path);
1527
1528        return false;
1529    }
1530
1531    return true;
1532}
1533
1534/** Event handler for mouse up in drawing mode.
1535 *
1536 *  @param e the event
1537 */
1538function drawingMouseup(e)
1539{
1540    if(!e)
1541        e = window.event;
1542
1543    if (mouse_presentation_path != null)
1544    {
1545        var p = calcCoord(e);
1546        var d = mouse_presentation_path.getAttribute("d");
1547        d += " L" + p.x + "," + p.y;
1548        mouse_presentation_path.setAttribute("d", d);
1549        mouse_presentation_path = null;
1550        mouse_original_path.setAttribute("d", d);
1551        mouse_original_path = null;
1552
1553        return false;
1554    }
1555
1556    return true;
1557}
1558
1559/** Event handler for mouse move in drawing mode.
1560 *
1561 *  @param e the event
1562 */
1563function drawingMousemove(e)
1564{
1565    if(!e)
1566        e = window.event;
1567
1568    var dist = (mouse_last_x - e.clientX) * (mouse_last_x - e.clientX) + (mouse_last_y - e.clientY) * (mouse_last_y - e.clientY);
1569
1570    if (mouse_presentation_path == null)
1571    {
1572        return true;
1573    }
1574
1575    if (dist &gt;= mouse_min_dist_sqr)
1576    {
1577        var p = calcCoord(e);
1578        var d = mouse_presentation_path.getAttribute("d");
1579        d += " L" + p.x + "," + p.y;
1580        mouse_presentation_path.setAttribute("d", d);
1581        mouse_original_path.setAttribute("d", d);
1582        mouse_last_x = e.clientX;
1583        mouse_last_y = e.clientY;
1584    }
1585
1586    return false;
1587}
1588
1589/** Event handler for mouse wheel events in slide mode.
1590 *  based on http://adomas.org/javascript-mouse-wheel/
1591 *
1592 *  @param e the event
1593 */
1594function slideMousewheel(e)
1595{
1596    var delta = 0;
1597
1598    if (!e)
1599        e = window.event;
1600
1601    if (e.wheelDelta)
1602    { // IE Opera
1603        delta = e.wheelDelta/120;
1604    }
1605    else if (e.detail)
1606    { // MOZ
1607        delta = -e.detail/3;
1608    }
1609
1610    if (delta &gt; 0)
1611        skipEffects(-1);
1612    else if (delta &lt; 0)
1613        skipEffects(1);
1614
1615    if (e.preventDefault)
1616        e.preventDefault();
1617
1618    e.returnValue = false;
1619}
1620
1621/** Event handler for mouse wheel events in index mode.
1622 *  based on http://adomas.org/javascript-mouse-wheel/
1623 *
1624 *  @param e the event
1625 */
1626function indexMousewheel(e)
1627{
1628    var delta = 0;
1629
1630    if (!e)
1631        e = window.event;
1632
1633    if (e.wheelDelta)
1634    { // IE Opera
1635        delta = e.wheelDelta/120;
1636    }
1637    else if (e.detail)
1638    { // MOZ
1639        delta = -e.detail/3;
1640    }
1641
1642    if (delta &gt; 0)
1643        indexSetPageSlide(activeSlide - INDEX_COLUMNS * INDEX_COLUMNS);
1644    else if (delta &lt; 0)
1645        indexSetPageSlide(activeSlide + INDEX_COLUMNS * INDEX_COLUMNS);
1646
1647    if (e.preventDefault)
1648        e.preventDefault();
1649
1650    e.returnValue = false;
1651}
1652
1653/** Function to set the path paint width.
1654*/
1655function set_path_paint_width()
1656{
1657    var svgPoint1 = document.documentElement.createSVGPoint();
1658    var svgPoint2 = document.documentElement.createSVGPoint();
1659
1660    svgPoint1.x = 0.0;
1661    svgPoint1.y = 0.0;
1662    svgPoint2.x = 1.0;
1663    svgPoint2.y = 0.0;
1664
1665    var matrix = slides[activeSlide]["element"].getTransformToElement(ROOT_NODE);
1666
1667    if (slides[activeSlide]["viewGroup"])
1668        matrix = slides[activeSlide]["viewGroup"].getTransformToElement(ROOT_NODE);
1669
1670    svgPoint1 = svgPoint1.matrixTransform(matrix);
1671    svgPoint2 = svgPoint2.matrixTransform(matrix);
1672
1673    path_paint_width = path_width / Math.sqrt((svgPoint2.x - svgPoint1.x) * (svgPoint2.x - svgPoint1.x) + (svgPoint2.y - svgPoint1.y) * (svgPoint2.y - svgPoint1.y));
1674}
1675
1676/** The view effect.
1677 *
1678 *  @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1679 *  @param element the element the effect should be applied to
1680 *  @param time the time that has elapsed since the beginning of the effect
1681 *  @param options a dictionary with additional options (e.g. length of the effect); for the view effect the options need to contain the old and the new matrix.
1682 */
1683function view(dir, element, time, options)
1684{
1685    var length = 250;
1686    var fraction;
1687
1688    if (!options["matrixInitial"])
1689    {
1690        var tempString = slides[activeSlide]["viewGroup"].getAttribute("transform");
1691
1692        if (tempString)
1693            options["matrixInitial"] = (new matrixSVG()).fromAttribute(tempString);
1694        else
1695            options["matrixInitial"] = (new matrixSVG()).fromSVGElements(1, 0, 0, 1, 0, 0);
1696    }
1697
1698    if ((time == STATE_END) || (time == STATE_START))
1699        fraction = 1;
1700    else
1701    {
1702        if (options &amp;&amp; options["length"])
1703            length = options["length"];
1704
1705        fraction = time / length;
1706    }
1707
1708    if (dir == 1)
1709    {
1710        if (fraction &lt;= 0)
1711        {
1712            element.setAttribute("transform", options["matrixInitial"].toAttribute());
1713        }
1714        else if (fraction &gt;= 1)
1715        {
1716            element.setAttribute("transform", options["matrixNew"].toAttribute());
1717
1718            set_path_paint_width();
1719
1720            options["matrixInitial"] = null;
1721            return true;
1722        }
1723        else
1724        {
1725            element.setAttribute("transform", options["matrixInitial"].mix(options["matrixNew"], fraction).toAttribute());
1726        }
1727    }
1728    else if (dir == -1)
1729    {
1730        if (fraction &lt;= 0)
1731        {
1732            element.setAttribute("transform", options["matrixInitial"].toAttribute());
1733        }
1734        else if (fraction &gt;= 1)
1735        {
1736            element.setAttribute("transform", options["matrixOld"].toAttribute());
1737            set_path_paint_width();
1738
1739            options["matrixInitial"] = null;
1740            return true;
1741        }
1742        else
1743        {
1744            element.setAttribute("transform", options["matrixInitial"].mix(options["matrixOld"], fraction).toAttribute());
1745        }
1746    }
1747
1748    return false;
1749}
1750
1751/** The fade effect.
1752 *
1753 *  @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1754 *  @param element the element the effect should be applied to
1755 *  @param time the time that has elapsed since the beginning of the effect
1756 *  @param options a dictionary with additional options (e.g. length of the effect)
1757 */
1758function fade(dir, element, time, options)
1759{
1760    var length = 250;
1761    var fraction;
1762
1763    if ((time == STATE_END) || (time == STATE_START))
1764        fraction = 1;
1765    else
1766    {
1767        if (options &amp;&amp; options["length"])
1768            length = options["length"];
1769
1770        fraction = time / length;
1771    }
1772
1773    if (dir == 1)
1774    {
1775        if (fraction &lt;= 0)
1776        {
1777            element.style.display = "none";
1778            element.setAttribute("opacity", 0);
1779        }
1780        else if (fraction &gt;= 1)
1781        {
1782            element.style.display = "inherit";
1783            element.setAttribute("opacity", 1);
1784            return true;
1785        }
1786        else
1787        {
1788            element.style.display = "inherit";
1789            element.setAttribute("opacity", fraction);
1790        }
1791    }
1792    else if (dir == -1)
1793    {
1794        if (fraction &lt;= 0)
1795        {
1796            element.style.display = "inherit";
1797            element.setAttribute("opacity", 1);
1798        }
1799        else if (fraction &gt;= 1)
1800        {
1801            element.setAttribute("opacity", 0);
1802            element.style.display = "none";
1803            return true;
1804        }
1805        else
1806        {
1807            element.style.display = "inherit";
1808            element.setAttribute("opacity", 1 - fraction);
1809        }
1810    }
1811    return false;
1812}
1813
1814/** The appear effect.
1815 *
1816 *  @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1817 *  @param element the element the effect should be applied to
1818 *  @param time the time that has elapsed since the beginning of the effect
1819 *  @param options a dictionary with additional options (e.g. length of the effect)
1820 */
1821function appear(dir, element, time, options)
1822{
1823    if (dir == 1)
1824    {
1825        element.style.display = "inherit";
1826        element.setAttribute("opacity",1);
1827    }
1828    else if (dir == -1)
1829    {
1830        element.style.display = "none";
1831        element.setAttribute("opacity",0);
1832    }
1833    return true;
1834}
1835
1836/** The pop effect.
1837 *
1838 *  @param dir direction the effect should be played (1 = forwards, -1 = backwards)
1839 *  @param element the element the effect should be applied to
1840 *  @param time the time that has elapsed since the beginning of the effect
1841 *  @param options a dictionary with additional options (e.g. length of the effect)
1842 */
1843function pop(dir, element, time, options)
1844{
1845    var length = 500;
1846    var fraction;
1847
1848    if ((time == STATE_END) || (time == STATE_START))
1849        fraction = 1;
1850    else
1851    {
1852        if (options &amp;&amp; options["length"])
1853            length = options["length"];
1854
1855        fraction = time / length;
1856    }
1857
1858    if (dir == 1)
1859    {
1860        if (fraction &lt;= 0)
1861        {
1862            element.setAttribute("opacity", 0);
1863            element.setAttribute("transform", "scale(0)");
1864            element.style.display = "none";
1865        }
1866        else if (fraction &gt;= 1)
1867        {
1868            element.setAttribute("opacity", 1);
1869            element.removeAttribute("transform");
1870            element.style.display = "inherit";
1871            return true;
1872        }
1873        else
1874        {
1875            element.style.display = "inherit";
1876            var opacityFraction = fraction * 3;
1877            if (opacityFraction &gt; 1)
1878                opacityFraction = 1;
1879            element.setAttribute("opacity", opacityFraction);
1880            var offsetX = WIDTH * (1.0 - fraction) / 2.0;
1881            var offsetY = HEIGHT * (1.0 - fraction) / 2.0;
1882            element.setAttribute("transform", "translate(" + offsetX + "," + offsetY + ") scale(" + fraction + ")");
1883        }
1884    }
1885    else if (dir == -1)
1886    {
1887        if (fraction &lt;= 0)
1888        {
1889            element.setAttribute("opacity", 1);
1890            element.setAttribute("transform", "scale(1)");
1891            element.style.display = "inherit";
1892        }
1893        else if (fraction &gt;= 1)
1894        {
1895            element.setAttribute("opacity", 0);
1896            element.removeAttribute("transform");
1897            element.style.display = "none";
1898            return true;
1899        }
1900        else
1901        {
1902            element.setAttribute("opacity", 1 - fraction);
1903            element.setAttribute("transform", "scale(" + 1 - fraction + ")");
1904            element.style.display = "inherit";
1905        }
1906    }
1907    return false;
1908}
1909
1910/** Function to set a slide either to the start or the end state.
1911 *
1912 *  @param slide the slide to use
1913 *  @param state the state into which the slide should be set
1914 */
1915function setSlideToState(slide, state)
1916{
1917    slides[slide]["viewGroup"].setAttribute("transform", slides[slide].initialView);
1918
1919    if (slides[slide]["effects"])
1920    {
1921        if (state == STATE_END)
1922        {
1923            for (var counter = 0; counter &lt; slides[slide]["effects"].length; counter++)
1924            {
1925                for (var subCounter = 0; subCounter &lt; slides[slide]["effects"][counter].length; subCounter++)
1926                {
1927                    var effect = slides[slide]["effects"][counter][subCounter];
1928                    if (effect["effect"] == "fade")
1929                        fade(effect["dir"], effect["element"], STATE_END, effect["options"]);
1930                    else if (effect["effect"] == "appear")
1931                        appear(effect["dir"], effect["element"], STATE_END, effect["options"]);
1932                    else if (effect["effect"] == "pop")
1933                        pop(effect["dir"], effect["element"], STATE_END, effect["options"]);
1934                    else if (effect["effect"] == "view")
1935                        view(effect["dir"], effect["element"], STATE_END, effect["options"]);
1936                }
1937            }
1938        }
1939        else if (state == STATE_START)
1940        {
1941            for (var counter = slides[slide]["effects"].length - 1; counter &gt;= 0; counter--)
1942            {
1943                for (var subCounter = 0; subCounter &lt; slides[slide]["effects"][counter].length; subCounter++)
1944                {
1945                    var effect = slides[slide]["effects"][counter][subCounter];
1946                    if (effect["effect"] == "fade")
1947                        fade(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);
1948                    else if (effect["effect"] == "appear")
1949                        appear(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);
1950                    else if (effect["effect"] == "pop")
1951                        pop(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);
1952                    else if (effect["effect"] == "view")
1953                        view(parseInt(effect["dir"]) * -1, effect["element"], STATE_START, effect["options"]);
1954                }
1955            }
1956        }
1957        else
1958        {
1959            setSlideToState(slide, STATE_START);
1960
1961            for (var counter = 0; counter &lt; slides[slide]["effects"].length &amp;&amp; counter &lt; state; counter++)
1962            {
1963                for (var subCounter = 0; subCounter &lt; slides[slide]["effects"][counter].length; subCounter++)
1964                {
1965                    var effect = slides[slide]["effects"][counter][subCounter];
1966                    if (effect["effect"] == "fade")
1967                        fade(effect["dir"], effect["element"], STATE_END, effect["options"]);
1968                    else if (effect["effect"] == "appear")
1969                        appear(effect["dir"], effect["element"], STATE_END, effect["options"]);
1970                    else if (effect["effect"] == "pop")
1971                        pop(effect["dir"], effect["element"], STATE_END, effect["options"]);
1972                    else if (effect["effect"] == "view")
1973                        view(effect["dir"], effect["element"], STATE_END, effect["options"]);
1974                }
1975            }
1976        }
1977    }
1978
1979    window.location.hash = (activeSlide + 1) + '_' + activeEffect;
1980}
1981
1982/** Convenience function to translate a attribute string into a dictionary.
1983 *
1984 *    @param str the attribute string
1985 *  @return a dictionary
1986 *  @see dictToPropStr
1987 */
1988function propStrToDict(str)
1989{
1990    var list = str.split(";");
1991    var obj = new Object();
1992
1993    for (var counter = 0; counter &lt; list.length; counter++)
1994    {
1995        var subStr = list[counter];
1996        var subList = subStr.split(":");
1997        if (subList.length == 2)
1998        {
1999            obj[subList[0]] = subList[1];
2000        }
2001    }
2002
2003    return obj;
2004}
2005
2006/** Convenience function to translate a dictionary into a string that can be used as an attribute.
2007 *
2008 *  @param dict the dictionary to convert
2009 *  @return a string that can be used as an attribute
2010 *  @see propStrToDict
2011 */
2012function dictToPropStr(dict)
2013{
2014    var str = "";
2015
2016    for (var key in dict)
2017    {
2018        str += key + ":" + dict[key] + ";";
2019    }
2020
2021    return str;
2022}
2023
2024/** Sub-function to add a suffix to the ids of the node and all its children.
2025 *
2026 *    @param node the node to change
2027 *    @param suffix the suffix to add
2028 *    @param replace dictionary of replaced ids
2029 *  @see suffixNodeIds
2030 */
2031function suffixNoneIds_sub(node, suffix, replace)
2032{
2033    if (node.nodeType == 1)
2034    {
2035        if (node.getAttribute("id"))
2036        {
2037            var id = node.getAttribute("id")
2038                replace["#" + id] = id + suffix;
2039            node.setAttribute("id", id + suffix);
2040        }
2041
2042        if ((node.nodeName == "use") &amp;&amp; (node.getAttributeNS(NSS["xlink"], "href")) &amp;&amp; (replace[node.getAttribute(NSS["xlink"], "href")]))
2043            node.setAttribute(NSS["xlink"], "href", node.getAttribute(NSS["xlink"], "href") + suffix);
2044
2045        if (node.childNodes)
2046        {
2047            for (var counter = 0; counter &lt; node.childNodes.length; counter++)
2048                suffixNoneIds_sub(node.childNodes[counter], suffix, replace);
2049        }
2050    }
2051}
2052
2053/** Function to add a suffix to the ids of the node and all its children.
2054 *
2055 *    @param node the node to change
2056 *    @param suffix the suffix to add
2057 *  @return the changed node
2058 *  @see suffixNodeIds_sub
2059 */
2060function suffixNodeIds(node, suffix)
2061{
2062    var replace = new Object();
2063
2064    suffixNoneIds_sub(node, suffix, replace);
2065
2066    return node;
2067}
2068
2069/** Function to build a progress bar.
2070 *
2071 *  @param parent node to attach the progress bar to
2072 */
2073function createProgressBar(parent_node)
2074{
2075    var g = document.createElementNS(NSS["svg"], "g");
2076    g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
2077    g.setAttribute("id", "layer_progress_bar");
2078    g.setAttribute("style", "display: none;");
2079
2080    var rect_progress_bar = document.createElementNS(NSS["svg"], "rect");
2081    rect_progress_bar.setAttribute("style", "marker: none; fill: rgb(128, 128, 128); stroke: none;");
2082    rect_progress_bar.setAttribute("id", "rect_progress_bar");
2083    rect_progress_bar.setAttribute("x", 0);
2084    rect_progress_bar.setAttribute("y", 0.99 * HEIGHT);
2085    rect_progress_bar.setAttribute("width", 0);
2086    rect_progress_bar.setAttribute("height", 0.01 * HEIGHT);
2087    g.appendChild(rect_progress_bar);
2088
2089    var circle_timer_indicator = document.createElementNS(NSS["svg"], "circle");
2090    circle_timer_indicator.setAttribute("style", "marker: none; fill: rgb(255, 0, 0); stroke: none;");
2091    circle_timer_indicator.setAttribute("id", "circle_timer_indicator");
2092    circle_timer_indicator.setAttribute("cx", 0.005 * HEIGHT);
2093    circle_timer_indicator.setAttribute("cy", 0.995 * HEIGHT);
2094    circle_timer_indicator.setAttribute("r", 0.005 * HEIGHT);
2095    g.appendChild(circle_timer_indicator);
2096
2097    parent_node.appendChild(g);
2098}
2099
2100/** Function to hide the progress bar.
2101 *
2102 */
2103function hideProgressBar()
2104{
2105    var progress_bar = document.getElementById("layer_progress_bar");
2106
2107    if (!progress_bar)
2108    {
2109        return;
2110    }
2111
2112    progress_bar.setAttribute("style", "display: none;");
2113}
2114
2115/** Function to show the progress bar.
2116 *
2117 */
2118function showProgressBar()
2119{
2120    var progress_bar = document.getElementById("layer_progress_bar");
2121
2122    if (!progress_bar)
2123    {
2124        return;
2125    }
2126
2127    progress_bar.setAttribute("style", "display: inherit;");
2128}
2129
2130/** Set progress bar value.
2131 *
2132 *    @param value the current slide number
2133 *
2134 */
2135function setProgressBarValue(value)
2136{
2137    var rect_progress_bar = document.getElementById("rect_progress_bar");
2138
2139    if (!rect_progress_bar)
2140    {
2141        return;
2142    }
2143
2144    if (value &lt; 1)
2145    {
2146        // First slide, assumed to be the title of the presentation
2147        var x = 0;
2148        var w = 0.01 * HEIGHT;
2149    }
2150    else if (value &gt;= slides.length - 1)
2151    {
2152        // Last slide, assumed to be the end of the presentation
2153        var x = WIDTH - 0.01 * HEIGHT;
2154        var w = 0.01 * HEIGHT;
2155    }
2156    else
2157    {
2158        value -= 1;
2159        value /= (slides.length - 2);
2160
2161        var x = WIDTH * value;
2162        var w = WIDTH / (slides.length - 2);
2163    }
2164
2165    rect_progress_bar.setAttribute("x", x);
2166    rect_progress_bar.setAttribute("width", w);
2167}
2168
2169/** Set time indicator.
2170 *
2171 *    @param value the percentage of time elapse so far between 0.0 and 1.0
2172 *
2173 */
2174function setTimeIndicatorValue(value)
2175{
2176    var circle_timer_indicator = document.getElementById("circle_timer_indicator");
2177
2178    if (!circle_timer_indicator)
2179    {
2180        return;
2181    }
2182
2183    if (value &lt; 0.0)
2184    {
2185        value = 0.0;
2186    }
2187
2188    if (value &gt; 1.0)
2189    {
2190        value = 1.0;
2191    }
2192
2193    var cx = (WIDTH - 0.01 * HEIGHT) * value + 0.005 * HEIGHT;
2194    circle_timer_indicator.setAttribute("cx", cx);
2195}
2196
2197/** Update timer.
2198 *
2199 */
2200function updateTimer()
2201{
2202    timer_elapsed += 1;
2203    setTimeIndicatorValue((timer_elapsed - timer_start) / (60 * timer_duration));
2204}
2205
2206/** Convert screen coordinates to document coordinates.
2207 *
2208 *  @param e event with screen coordinates
2209 *
2210 *  @return coordinates in SVG file coordinate system
2211 */
2212function calcCoord(e)
2213{
2214    var svgPoint = document.documentElement.createSVGPoint();
2215    svgPoint.x = e.clientX + window.pageXOffset;
2216    svgPoint.y = e.clientY + window.pageYOffset;
2217
2218    var matrix = slides[activeSlide]["element"].getScreenCTM();
2219
2220    if (slides[activeSlide]["viewGroup"])
2221        matrix = slides[activeSlide]["viewGroup"].getScreenCTM();
2222
2223    svgPoint = svgPoint.matrixTransform(matrix.inverse());
2224    return svgPoint;
2225}
2226
2227/** Add slide.
2228 *
2229 *    @param after_slide after which slide the new slide should be inserted into the presentation
2230 */
2231function addSlide(after_slide)
2232{
2233    number_of_added_slides++;
2234
2235    var g = document.createElementNS(NSS["svg"], "g");
2236    g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
2237    g.setAttribute("id", "Whiteboard " + Date() + " presentation copy");
2238    g.setAttribute("style", "display: none;");
2239
2240    var new_slide = new Object();
2241    new_slide["element"] = g;
2242
2243    // Set build in transition.
2244    new_slide["transitionIn"] = new Object();
2245    var dict = defaultTransitionInDict;
2246    new_slide["transitionIn"]["name"] = dict["name"];
2247    new_slide["transitionIn"]["options"] = new Object();
2248
2249    for (key in dict)
2250        if (key != "name")
2251            new_slide["transitionIn"]["options"][key] = dict[key];
2252
2253    // Set build out transition.
2254    new_slide["transitionOut"] = new Object();
2255    dict = defaultTransitionOutDict;
2256    new_slide["transitionOut"]["name"] = dict["name"];
2257    new_slide["transitionOut"]["options"] = new Object();
2258
2259    for (key in dict)
2260        if (key != "name")
2261            new_slide["transitionOut"]["options"][key] = dict[key];
2262
2263    // Copy master slide content.
2264    if (masterSlide)
2265    {
2266        var clonedNode = suffixNodeIds(masterSlide.cloneNode(true), "_" + Date() + " presentation_copy");
2267        clonedNode.removeAttributeNS(NSS["inkscape"], "groupmode");
2268        clonedNode.removeAttributeNS(NSS["inkscape"], "label");
2269        clonedNode.style.display = "inherit";
2270
2271        g.appendChild(clonedNode);
2272    }
2273
2274    // Substitute auto texts.
2275    substituteAutoTexts(g, "Whiteboard " + number_of_added_slides, "W" + number_of_added_slides, slides.length);
2276
2277    g.setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) &amp;&amp; ( activeSlide != " + (after_slide + 1) + ")) { indexSetActiveSlide(" + (after_slide + 1) + "); };");
2278
2279    // Create a transform group.
2280    var transformGroup = document.createElementNS(NSS["svg"], "g");
2281
2282    // Add content to transform group.
2283    while (g.firstChild)
2284        transformGroup.appendChild(g.firstChild);
2285
2286    // Transfer the transform attribute from the node to the transform group.
2287    if (g.getAttribute("transform"))
2288    {
2289        transformGroup.setAttribute("transform", g.getAttribute("transform"));
2290        g.removeAttribute("transform");
2291    }
2292
2293    // Create a view group.
2294    var viewGroup = document.createElementNS(NSS["svg"], "g");
2295
2296    viewGroup.appendChild(transformGroup);
2297    new_slide["viewGroup"] = g.appendChild(viewGroup);
2298
2299    // Insert background.
2300    if (BACKGROUND_COLOR != null)
2301    {
2302        var rectNode = document.createElementNS(NSS["svg"], "rect");
2303
2304        rectNode.setAttribute("x", 0);
2305        rectNode.setAttribute("y", 0);
2306        rectNode.setAttribute("width", WIDTH);
2307        rectNode.setAttribute("height", HEIGHT);
2308        rectNode.setAttribute("id", "jessyInkBackground" + Date());
2309        rectNode.setAttribute("fill", BACKGROUND_COLOR);
2310
2311        new_slide["viewGroup"].insertBefore(rectNode, new_slide["viewGroup"].firstChild);
2312    }
2313
2314    // Set initial view even if there are no other views.
2315    var matrixOld = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1);
2316
2317    new_slide["viewGroup"].setAttribute("transform", matrixOld.toAttribute());
2318    new_slide.initialView = matrixOld.toAttribute();
2319
2320    // Insert slide
2321    var node = slides[after_slide]["element"];
2322    var next_node = node.nextSibling;
2323    var parent_node = node.parentNode;
2324
2325    if (next_node)
2326    {
2327        parent_node.insertBefore(g, next_node);
2328    }
2329    else
2330    {
2331        parent_node.appendChild(g);
2332    }
2333
2334    g = document.createElementNS(NSS["svg"], "g");
2335    g.setAttributeNS(NSS["inkscape"], "groupmode", "layer");
2336    g.setAttributeNS(NSS["inkscape"], "label", "Whiteboard " + number_of_added_slides);
2337    g.setAttribute("clip-path", "url(#jessyInkSlideClipPath)");
2338    g.setAttribute("id", "Whiteboard " + Date());
2339    g.setAttribute("style", "display: none;");
2340
2341    new_slide["original_element"] = g;
2342
2343    node = slides[after_slide]["original_element"];
2344    next_node = node.nextSibling;
2345    parent_node = node.parentNode;
2346
2347    if (next_node)
2348    {
2349        parent_node.insertBefore(g, next_node);
2350    }
2351    else
2352    {
2353        parent_node.appendChild(g);
2354    }
2355
2356    before_new_slide = slides.slice(0, after_slide + 1);
2357    after_new_slide = slides.slice(after_slide + 1);
2358    slides = before_new_slide.concat(new_slide, after_new_slide);
2359
2360    //resetting the counter attributes on the slides that follow the new slide...
2361    for (var counter = after_slide+2; counter &lt; slides.length; counter++)
2362    {
2363        slides[counter]["element"].setAttribute("onmouseover", "if ((currentMode == INDEX_MODE) &amp;&amp; ( activeSlide != " + counter + ")) { indexSetActiveSlide(" + counter + "); };");
2364    }
2365}
2366
2367/** Convenience function to obtain a transformation matrix from a point matrix.
2368 *
2369 *    @param mPoints Point matrix.
2370 *    @return A transformation matrix.
2371 */
2372function pointMatrixToTransformation(mPoints)
2373{
2374    mPointsOld = (new matrixSVG()).fromElements(0, WIDTH, WIDTH, 0, 0, HEIGHT, 1, 1, 1);
2375
2376    return mPointsOld.mult(mPoints.inv());
2377}
2378
2379/** Convenience function to obtain a matrix with three corners of a rectangle.
2380 *
2381 *    @param rect an svg rectangle
2382 *    @return a matrixSVG containing three corners of the rectangle
2383 */
2384function rectToMatrix(rect)
2385{
2386    rectWidth = rect.getBBox().width;
2387    rectHeight = rect.getBBox().height;
2388    rectX = rect.getBBox().x;
2389    rectY = rect.getBBox().y;
2390    rectXcorr = 0;
2391    rectYcorr = 0;
2392
2393    scaleX = WIDTH / rectWidth;
2394    scaleY = HEIGHT / rectHeight;
2395
2396    if (scaleX &gt; scaleY)
2397    {
2398        scaleX = scaleY;
2399        rectXcorr -= (WIDTH / scaleX - rectWidth) / 2;
2400        rectWidth = WIDTH / scaleX;
2401    }
2402    else
2403    {
2404        scaleY = scaleX;
2405        rectYcorr -= (HEIGHT / scaleY - rectHeight) / 2;
2406        rectHeight = HEIGHT / scaleY;
2407    }
2408
2409    if (rect.transform.baseVal.numberOfItems &lt; 1)
2410    {
2411        mRectTrans = (new matrixSVG()).fromElements(1, 0, 0, 0, 1, 0, 0, 0, 1);
2412    }
2413    else
2414    {
2415        mRectTrans = (new matrixSVG()).fromSVGMatrix(rect.transform.baseVal.consolidate().matrix);
2416    }
2417
2418    newBasePoints = (new matrixSVG()).fromElements(rectX, rectX, rectX, rectY, rectY, rectY, 1, 1, 1);
2419    newVectors = (new matrixSVG()).fromElements(rectXcorr, rectXcorr + rectWidth, rectXcorr + rectWidth, rectYcorr, rectYcorr, rectYcorr + rectHeight, 0, 0, 0);
2420
2421    return mRectTrans.mult(newBasePoints.add(newVectors));
2422}
2423
2424/** Function to handle JessyInk elements.
2425 *
2426 *    @param    node    Element node.
2427 */
2428function handleElement(node)
2429{
2430    if (node.getAttributeNS(NSS['jessyink'], 'element') == 'core.video')
2431    {
2432        var url;
2433        var width;
2434        var height;
2435        var x;
2436        var y;
2437        var transform;
2438
2439        var tspans = node.getElementsByTagNameNS("http://www.w3.org/2000/svg", "tspan");
2440
2441        for (var tspanCounter = 0; tspanCounter &lt; tspans.length; tspanCounter++)
2442        {
2443            if (tspans[tspanCounter].getAttributeNS("https://launchpad.net/jessyink", "video") == "url")
2444            {
2445                url = tspans[tspanCounter].firstChild.nodeValue;
2446            }
2447        }
2448
2449        var rects = node.getElementsByTagNameNS("http://www.w3.org/2000/svg", "rect");
2450
2451        for (var rectCounter = 0; rectCounter &lt; rects.length; rectCounter++)
2452        {
2453            if (rects[rectCounter].getAttributeNS("https://launchpad.net/jessyink", "video") == "rect")
2454            {
2455                x = rects[rectCounter].getAttribute("x");
2456                y = rects[rectCounter].getAttribute("y");
2457                width = rects[rectCounter].getAttribute("width");
2458                height = rects[rectCounter].getAttribute("height");
2459                transform = rects[rectCounter].getAttribute("transform");
2460            }
2461        }
2462
2463        for (var childCounter = 0; childCounter &lt; node.childNodes.length; childCounter++)
2464        {
2465            if (node.childNodes[childCounter].nodeType == 1)
2466            {
2467                if (node.childNodes[childCounter].style)
2468                {
2469                    node.childNodes[childCounter].style.display = 'none';
2470                }
2471                else
2472                {
2473                    node.childNodes[childCounter].setAttribute("style", "display: none;");
2474                }
2475            }
2476        }
2477
2478        var foreignNode = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
2479        foreignNode.setAttribute("x", x);
2480        foreignNode.setAttribute("y", y);
2481        foreignNode.setAttribute("width", width);
2482        foreignNode.setAttribute("height", height);
2483        foreignNode.setAttribute("transform", transform);
2484
2485        var videoNode = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
2486        videoNode.setAttribute("src", url);
2487
2488        foreignNode.appendChild(videoNode);
2489        node.appendChild(foreignNode);
2490    }
2491}
2492
2493/** Class processing the location hash.
2494 *
2495 *    @param str location hash
2496 */
2497function LocationHash(str)
2498{
2499    this.slideNumber = 0;
2500    this.effectNumber = 0;
2501
2502    str = str.substr(1, str.length - 1);
2503
2504    var parts = str.split('_');
2505
2506    // Try to extract slide number.
2507    if (parts.length &gt;= 1)
2508    {
2509        try
2510        {
2511            var slideNumber = parseInt(parts[0]);
2512
2513            if (!isNaN(slideNumber))
2514            {
2515                this.slideNumber = slideNumber - 1;
2516            }
2517        }
2518        catch (e)
2519        {
2520        }
2521    }
2522
2523    // Try to extract effect number.
2524    if (parts.length &gt;= 2)
2525    {
2526        try
2527        {
2528            var effectNumber = parseInt(parts[1]);
2529
2530            if (!isNaN(effectNumber))
2531            {
2532                this.effectNumber = effectNumber;
2533            }
2534        }
2535        catch (e)
2536        {
2537        }
2538    }
2539}
2540
2541/** Class representing an svg matrix.
2542*/
2543function matrixSVG()
2544{
2545    this.e11 = 0; // a
2546    this.e12 = 0; // c
2547    this.e13 = 0; // e
2548    this.e21 = 0; // b
2549    this.e22 = 0; // d
2550    this.e23 = 0; // f
2551    this.e31 = 0;
2552    this.e32 = 0;
2553    this.e33 = 0;
2554}
2555
2556/** Constructor function.
2557 *
2558 *    @param a element a (i.e. 1, 1) as described in the svg standard.
2559 *    @param b element b (i.e. 2, 1) as described in the svg standard.
2560 *    @param c element c (i.e. 1, 2) as described in the svg standard.
2561 *    @param d element d (i.e. 2, 2) as described in the svg standard.
2562 *    @param e element e (i.e. 1, 3) as described in the svg standard.
2563 *    @param f element f (i.e. 2, 3) as described in the svg standard.
2564 */
2565matrixSVG.prototype.fromSVGElements = function(a, b, c, d, e, f)
2566{
2567    this.e11 = a;
2568    this.e12 = c;
2569    this.e13 = e;
2570    this.e21 = b;
2571    this.e22 = d;
2572    this.e23 = f;
2573    this.e31 = 0;
2574    this.e32 = 0;
2575    this.e33 = 1;
2576
2577    return this;
2578}
2579
2580/** Constructor function.
2581 *
2582 *    @param matrix an svg matrix as described in the svg standard.
2583 */
2584matrixSVG.prototype.fromSVGMatrix = function(m)
2585{
2586    this.e11 = m.a;
2587    this.e12 = m.c;
2588    this.e13 = m.e;
2589    this.e21 = m.b;
2590    this.e22 = m.d;
2591    this.e23 = m.f;
2592    this.e31 = 0;
2593    this.e32 = 0;
2594    this.e33 = 1;
2595
2596    return this;
2597}
2598
2599/** Constructor function.
2600 *
2601 *    @param e11 element 1, 1 of the matrix.
2602 *    @param e12 element 1, 2 of the matrix.
2603 *    @param e13 element 1, 3 of the matrix.
2604 *    @param e21 element 2, 1 of the matrix.
2605 *    @param e22 element 2, 2 of the matrix.
2606 *    @param e23 element 2, 3 of the matrix.
2607 *    @param e31 element 3, 1 of the matrix.
2608 *    @param e32 element 3, 2 of the matrix.
2609 *    @param e33 element 3, 3 of the matrix.
2610 */
2611matrixSVG.prototype.fromElements = function(e11, e12, e13, e21, e22, e23, e31, e32, e33)
2612{
2613    this.e11 = e11;
2614    this.e12 = e12;
2615    this.e13 = e13;
2616    this.e21 = e21;
2617    this.e22 = e22;
2618    this.e23 = e23;
2619    this.e31 = e31;
2620    this.e32 = e32;
2621    this.e33 = e33;
2622
2623    return this;
2624}
2625
2626/** Constructor function.
2627 *
2628 *    @param attrString string value of the "transform" attribute (currently only "matrix" is accepted)
2629 */
2630matrixSVG.prototype.fromAttribute = function(attrString)
2631{
2632    str = attrString.substr(7, attrString.length - 8);
2633
2634    str = str.trim();
2635
2636    strArray = str.split(",");
2637
2638    // Opera does not use commas to separate the values of the matrix, only spaces.
2639    if (strArray.length != 6)
2640        strArray = str.split(" ");
2641
2642    this.e11 = parseFloat(strArray[0]);
2643    this.e21 = parseFloat(strArray[1]);
2644    this.e31 = 0;
2645    this.e12 = parseFloat(strArray[2]);
2646    this.e22 = parseFloat(strArray[3]);
2647    this.e32 = 0;
2648    this.e13 = parseFloat(strArray[4]);
2649    this.e23 = parseFloat(strArray[5]);
2650    this.e33 = 1;
2651
2652    return this;
2653}
2654
2655/** Output function
2656 *
2657 *    @return a string that can be used as the "transform" attribute.
2658 */
2659matrixSVG.prototype.toAttribute = function()
2660{
2661    return "matrix(" + this.e11 + ", " + this.e21 + ", " + this.e12 + ", " + this.e22 + ", " + this.e13 + ", " + this.e23 + ")";
2662}
2663
2664/** Matrix nversion.
2665 *
2666 *    @return the inverse of the matrix
2667 */
2668matrixSVG.prototype.inv = function()
2669{
2670    out = new matrixSVG();
2671
2672    det = this.e11 * (this.e33 * this.e22 - this.e32 * this.e23) - this.e21 * (this.e33 * this.e12 - this.e32 * this.e13) + this.e31 * (this.e23 * this.e12 - this.e22 * this.e13);
2673
2674    out.e11 =  (this.e33 * this.e22 - this.e32 * this.e23) / det;
2675    out.e12 = -(this.e33 * this.e12 - this.e32 * this.e13) / det;
2676    out.e13 =  (this.e23 * this.e12 - this.e22 * this.e13) / det;
2677    out.e21 = -(this.e33 * this.e21 - this.e31 * this.e23) / det;
2678    out.e22 =  (this.e33 * this.e11 - this.e31 * this.e13) / det;
2679    out.e23 = -(this.e23 * this.e11 - this.e21 * this.e13) / det;
2680    out.e31 =  (this.e32 * this.e21 - this.e31 * this.e22) / det;
2681    out.e32 = -(this.e32 * this.e11 - this.e31 * this.e12) / det;
2682    out.e33 =  (this.e22 * this.e11 - this.e21 * this.e12) / det;
2683
2684    return out;
2685}
2686
2687/** Matrix multiplication.
2688 *
2689 *    @param op another svg matrix
2690 *    @return this * op
2691 */
2692matrixSVG.prototype.mult = function(op)
2693{
2694    out = new matrixSVG();
2695
2696    out.e11 = this.e11 * op.e11 + this.e12 * op.e21 + this.e13 * op.e31;
2697    out.e12 = this.e11 * op.e12 + this.e12 * op.e22 + this.e13 * op.e32;
2698    out.e13 = this.e11 * op.e13 + this.e12 * op.e23 + this.e13 * op.e33;
2699    out.e21 = this.e21 * op.e11 + this.e22 * op.e21 + this.e23 * op.e31;
2700    out.e22 = this.e21 * op.e12 + this.e22 * op.e22 + this.e23 * op.e32;
2701    out.e23 = this.e21 * op.e13 + this.e22 * op.e23 + this.e23 * op.e33;
2702    out.e31 = this.e31 * op.e11 + this.e32 * op.e21 + this.e33 * op.e31;
2703    out.e32 = this.e31 * op.e12 + this.e32 * op.e22 + this.e33 * op.e32;
2704    out.e33 = this.e31 * op.e13 + this.e32 * op.e23 + this.e33 * op.e33;
2705
2706    return out;
2707}
2708
2709/** Matrix addition.
2710 *
2711 *    @param op another svg matrix
2712 *    @return this + op
2713 */
2714matrixSVG.prototype.add = function(op)
2715{
2716    out = new matrixSVG();
2717
2718    out.e11 = this.e11 + op.e11;
2719    out.e12 = this.e12 + op.e12;
2720    out.e13 = this.e13 + op.e13;
2721    out.e21 = this.e21 + op.e21;
2722    out.e22 = this.e22 + op.e22;
2723    out.e23 = this.e23 + op.e23;
2724    out.e31 = this.e31 + op.e31;
2725    out.e32 = this.e32 + op.e32;
2726    out.e33 = this.e33 + op.e33;
2727
2728    return out;
2729}
2730
2731/** Matrix mixing.
2732 *
2733 *    @param op another svg matrix
2734 *    @parma contribOp contribution of the other matrix (0 &lt;= contribOp &lt;= 1)
2735 *    @return (1 - contribOp) * this + contribOp * op
2736 */
2737matrixSVG.prototype.mix = function(op, contribOp)
2738{
2739    contribThis = 1.0 - contribOp;
2740    out = new matrixSVG();
2741
2742    out.e11 = contribThis * this.e11 + contribOp * op.e11;
2743    out.e12 = contribThis * this.e12 + contribOp * op.e12;
2744    out.e13 = contribThis * this.e13 + contribOp * op.e13;
2745    out.e21 = contribThis * this.e21 + contribOp * op.e21;
2746    out.e22 = contribThis * this.e22 + contribOp * op.e22;
2747    out.e23 = contribThis * this.e23 + contribOp * op.e23;
2748    out.e31 = contribThis * this.e31 + contribOp * op.e31;
2749    out.e32 = contribThis * this.e32 + contribOp * op.e32;
2750    out.e33 = contribThis * this.e33 + contribOp * op.e33;
2751
2752    return out;
2753}
2754
2755/** Trimming function for strings.
2756*/
2757String.prototype.trim = function()
2758{
2759    return this.replace(/^\s+|\s+$/g, '');
2760}
2761
2762/** SVGElement.getTransformToElement polyfill */
2763SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(elem) {
2764    return elem.getScreenCTM().inverse().multiply(this.getScreenCTM());
2765};
2766</svg:script></svg>
2767