Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/gallery.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/js/vendor
diff options
context:
space:
mode:
authorOlivier Paroz <github@oparoz.com>2015-10-11 20:55:40 +0300
committerOlivier Paroz <github@oparoz.com>2015-10-11 20:55:40 +0300
commitf3d18e0421faf33ae06f51cde33a3538f63cce76 (patch)
tree85a1c751d3152d090511434279d8a4d8b8bbab77 /js/vendor
parent50c2ca346962e8841a0a57f95fc22a988d4c6eb9 (diff)
Include readme, license and uncompressed files for all JS libraries
Fixes #419
Diffstat (limited to 'js/vendor')
-rw-r--r--js/vendor/bigshot/LICENSE.txt202
-rw-r--r--js/vendor/bigshot/README.txt64
-rw-r--r--js/vendor/bigshot/bigshot.js9717
-rw-r--r--js/vendor/commonmark/LICENSE115
-rw-r--r--js/vendor/commonmark/README.md326
-rw-r--r--js/vendor/commonmark/dist/commonmark.js3214
-rw-r--r--js/vendor/dompurify/LICENSE378
-rw-r--r--js/vendor/dompurify/README.md185
-rw-r--r--js/vendor/eventsource-polyfill/LICENSE21
-rw-r--r--js/vendor/eventsource-polyfill/README.md75
-rw-r--r--js/vendor/eventsource-polyfill/dist/eventsource.js621
-rw-r--r--js/vendor/image-scale/LICENSE20
-rw-r--r--js/vendor/image-scale/README.md210
-rw-r--r--js/vendor/image-scale/image-scale.js494
14 files changed, 15642 insertions, 0 deletions
diff --git a/js/vendor/bigshot/LICENSE.txt b/js/vendor/bigshot/LICENSE.txt
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/js/vendor/bigshot/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/js/vendor/bigshot/README.txt b/js/vendor/bigshot/README.txt
new file mode 100644
index 00000000..f6b5a646
--- /dev/null
+++ b/js/vendor/bigshot/README.txt
@@ -0,0 +1,64 @@
+Bigshot
+=======
+
+Legal
+-----
+Please see individual files for specific licenses. The Bigshot
+library itself is licensed under the Apache License version 2.0,
+but some runtime dependencies are MIT, MPL or BSD-like.
+
+
+Dependencies
+------------
+For 2D zoomable images, Bigshot has no dependencies.
+
+For VR panoramas using WebGL (the bigshot.VRPanorama class),
+Bigshot has a required dependency on Sylvester and glUtils -
+MIT and MPL, respectively. For debugging of WebGL rendering
+contexts, Bigshot requires the webgl-debug library, which
+is released under a BSD-like license (see the Chromium project).
+
+
+Files
+-----
+
+LICENSE.txt
+ - The Apache License, version 2.0.
+
+bigshot.jar
+ - Java JAR file with the MakeImagePyramid tool.
+ Execute:
+ java -jar bigshot.jar
+ for help.
+
+bigshot.js
+ - Uncompressed JavaScript library
+
+bigshot-compressed.js
+ - The Bigshot library, compressed using the YUI Compressor
+
+bigshot-full-compressed.js
+ - The Bigshot library, with required WebGL dependencies for
+ VR Panoramas, compressed using the YUI Compressor
+
+bigshot-full-and-optional-compressed.js
+ - The Bigshot library, with required and optional WebGL
+ dependencies for VR Panoramas, compressed using the YUI
+ Compressor
+
+bigshot.php
+ - PHP script to serve image data from .bigshot archives instead
+ of from a folder structure
+
+doc/js/
+ - JsDoc documentation for the JavaScript library
+
+
+Tutorial
+--------
+
+Please see:
+
+ http://code.google.com/p/bigshot/wiki/Tutorial
+
+For a nicely formatted tutorial. \ No newline at end of file
diff --git a/js/vendor/bigshot/bigshot.js b/js/vendor/bigshot/bigshot.js
new file mode 100644
index 00000000..ba83fc72
--- /dev/null
+++ b/js/vendor/bigshot/bigshot.js
@@ -0,0 +1,9717 @@
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+if (!self["bigshot"]) {
+ /**
+ * @namespace Bigshot namespace.
+ *
+ * Bigshot is a toolkit for zoomable images and VR panoramas.
+ *
+ * <h3>Zoomable Images</h3>
+ *
+ * <p>The two classes that are needed for zoomable images are:
+ *
+ * <ul>
+ * <li>{@link bigshot.Image}: The main class for making zoomable images. See the class docs
+ * for a tutorial.
+ * <li>{@link bigshot.ImageParameters}: Parameters for zoomable images.
+ * <li>{@link bigshot.SimpleImage}: A class for making simple zoomable images that don't
+ * require the generation of an image pyramid.. See the class docs for a tutorial.
+ * </ul>
+ *
+ * For hotspots, see:
+ *
+ * <ul>
+ * <li>{@link bigshot.HotspotLayer}
+ * <li>{@link bigshot.Hotspot}
+ * <li>{@link bigshot.LabeledHotspot}
+ * <li>{@link bigshot.LinkHotspot}
+ * </ul>
+ *
+ * <h3>VR Panoramas</h3>
+ *
+ * <p>The two classes that are needed for zoomable VR panoramas (requires WebGL) are:
+ *
+ * <ul>
+ * <li>{@link bigshot.VRPanorama}: The main class for making VR panoramas. See the class docs
+ * for a tutorial.
+ * <li>{@link bigshot.VRPanoramaParameters}: Parameters for VR panoramas.
+ * </ul>
+ *
+ * For hotspots, see:
+ *
+ * <ul>
+ * <li>{@link bigshot.VRHotspot}
+ * <li>{@link bigshot.VRRectangleHotspot}
+ * <li>{@link bigshot.VRPointHotspot}
+ * </ul>
+ */
+ bigshot = {};
+
+ /*
+ * This is supposed to be processed using a minimalhttpd.IncludeProcessor
+ * during development. The files must be listed in dependency order.
+ */
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This class has no constructor, it is created as an object literal.
+ * @name bigshot.HomogeneousPoint3D
+ * @class A 3d homogenous point.
+ * @property {number} x the x-coordinate
+ * @property {number} y the y-coordinate
+ * @property {number} z the z-coordinate
+ * @property {number} w the w-coordinate
+ */
+
+/**
+ * This class has no constructor, it is created as an object literal.
+ * @name bigshot.Point3D
+ * @class A 3d point.
+ * @property {number} x the x-coordinate
+ * @property {number} y the y-coordinate
+ * @property {number} z the z-coordinate
+ */
+
+/**
+ * This class has no constructor, it is created as an object literal.
+ * @name bigshot.Point2D
+ * @class A 2d point.
+ * @property {number} x the x-coordinate
+ * @property {number} y the y-coordinate
+ */
+
+/**
+ * This class has no constructor, it is created as an object literal.
+ * @name bigshot.Rotation
+ * @class A rotation specified as a yaw-pitch-roll triplet.
+ * @property {number} y the rotation around the yaw (y) axis
+ * @property {number} p the rotation around the pitch (x) axis
+ * @property {number} r the rotation around the roll (z) axis
+ */
+
+
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @class Object-oriented support functions, used to make JavaScript
+ * a bit more palatable to a Java-head.
+ */
+bigshot.Object = {
+ /**
+ * Extends a base class with a derived class.
+ *
+ * @param {Function} derived the derived-class
+ * @param {Function} base the base-class
+ */
+ extend : function (derived, base) {
+ for (var k in base.prototype) {
+ if (derived.prototype[k]) {
+ derived.prototype[k]._super = base.prototype[k];
+ } else {
+ derived.prototype[k] = base.prototype[k];
+ }
+ }
+ },
+
+ /**
+ * Resolves a name relative to <code>self</code>.
+ *
+ * @param {String} name the name to resolve
+ * @type {Object}
+ */
+ resolve : function (name) {
+ var c = name.split (".");
+ var clazz = self;
+ for (var i = 0; i < c.length; ++i) {
+ clazz = clazz[c[i]];
+ }
+ return clazz;
+ },
+
+ validate : function (clazzName, iface) {
+ },
+
+ /**
+ * Utility function to show an object's fields in a message box.
+ *
+ * @param {Object} o the object
+ */
+ alertr : function (o) {
+ var sb = "";
+ for (var k in o) {
+ sb += k + ":" + o[k] + "\n";
+ }
+ alert (sb);
+ },
+
+ /**
+ * Utility function to show an object's fields in the console log.
+ *
+ * @param {Object} o the object
+ */
+ logr : function (o) {
+ var sb = "";
+ for (var k in o) {
+ sb += k + ":" + o[k] + "\n";
+ }
+ if (console) {
+ console.log (sb);
+ }
+ }
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new browser helper object.
+ *
+ * @class Encapsulates common browser functions for cross-browser portability
+ * and convenience.
+ */
+bigshot.Browser = function () {
+ this.requestAnimationFrameFunction =
+ window.requestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback, element) { return setTimeout (callback, 0); };
+}
+
+bigshot.Browser.prototype = {
+ /**
+ * Removes all children from an element.
+ *
+ * @public
+ * @param {HTMLElement} element the element whose children are to be removed.
+ */
+ removeAllChildren : function (element) {
+ element.innerHTML = "";
+ /*
+ if (element.children.length > 0) {
+ for (var i = element.children.length - 1; i >= 0; --i) {
+ element.removeChild (element.children[i]);
+ }
+ }
+ */
+ },
+
+ /**
+ * Thunk to implement a faked "mouseenter" event.
+ * @private
+ */
+ mouseEnter : function (_fn) {
+ var isAChildOf = this.isAChildOf;
+ return function(_evt)
+ {
+ var relTarget = _evt.relatedTarget;
+ if (this === relTarget || isAChildOf (this, relTarget))
+ { return; }
+
+ _fn.call (this, _evt);
+ }
+ },
+
+ isAChildOf : function (_parent, _child) {
+ if (_parent === _child) { return false; }
+ while (_child && _child !== _parent)
+ { _child = _child.parentNode; }
+
+ return _child === _parent;
+ },
+
+ /**
+ * Unregisters a listener from an element.
+ *
+ * @param {HTMLElement} elem the element
+ * @param {String} eventName the event name ("click", "mouseover", etc.)
+ * @param {function(e)} fn the callback function to detach
+ * @param {boolean} useCapture specifies if we should unregister a listener from the capture chain.
+ */
+ unregisterListener : function (elem, eventName, fn, useCapture) {
+ if (typeof (elem.removeEventListener) != 'undefined') {
+ elem.removeEventListener (eventName, fn, useCapture);
+ } else if (typeof (elem.detachEvent) != 'undefined') {
+ elem.detachEvent('on' + eventName, fn);
+ }
+ },
+
+ /**
+ * Registers a listener to an element.
+ *
+ * @param {HTMLElement} elem the element
+ * @param {String} eventName the event name ("click", "mouseover", etc.)
+ * @param {function(e)} fn the callback function to attach
+ * @param {boolean} useCapture specifies if we want to initiate capture.
+ * See <a href="https://developer.mozilla.org/en/DOM/element.addEventListener">element.addEventListener</a>
+ * on MDN for an explanation.
+ */
+ registerListener : function (_elem, _evtName, _fn, _useCapture) {
+ if (typeof _elem.addEventListener != 'undefined')
+ {
+ if (_evtName === 'mouseenter')
+ { _elem.addEventListener('mouseover', this.mouseEnter(_fn), _useCapture); }
+ else if (_evtName === 'mouseleave')
+ { _elem.addEventListener('mouseout', this.mouseEnter(_fn), _useCapture); }
+ else
+ { _elem.addEventListener(_evtName, _fn, _useCapture); }
+ }
+ else if (typeof _elem.attachEvent != 'undefined')
+ {
+ _elem.attachEvent('on' + _evtName, _fn);
+ }
+ else
+ {
+ _elem['on' + _evtName] = _fn;
+ }
+ },
+
+ /**
+ * Stops an event from bubbling.
+ *
+ * @param {Event} eventObject the event object
+ */
+ stopEventBubbling : function (eventObject) {
+ if (eventObject) {
+ if (eventObject.stopPropagation) {
+ eventObject.stopPropagation ();
+ } else {
+ eventObject.cancelBubble = true;
+ }
+ }
+ },
+
+ /**
+ * Creates a callback function that simply stops the event from bubbling.
+ *
+ * @example
+ * var browser = new bigshot.Browser ();
+ * browser.registerListener (element,
+ * "mousedown",
+ * browser.stopEventBubblingHandler (),
+ * false);
+ * @type function(event)
+ * @return a new function that can be used to stop an event from bubbling
+ */
+ stopEventBubblingHandler : function () {
+ var that = this;
+ return function (event) {
+ that.stopEventBubbling (event);
+ return false;
+ };
+ },
+
+ /**
+ * Stops bubbling for all mouse events on the element.
+ *
+ * @param {HTMLElement} element the element
+ */
+ stopMouseEventBubbling : function (element) {
+ this.registerListener (element, "mousedown", this.stopEventBubblingHandler (), false);
+ this.registerListener (element, "mouseup", this.stopEventBubblingHandler (), false);
+ this.registerListener (element, "mousemove", this.stopEventBubblingHandler (), false);
+ },
+
+ /**
+ * Returns the size in pixels of the element
+ *
+ * @param {HTMLElement} obj the element
+ * @return a size object with two integer members, w and h, for width and height respectively.
+ */
+ getElementSize : function (obj) {
+ var size = {};
+ if (obj.clientWidth) {
+ size.w = obj.clientWidth;
+ }
+ if (obj.clientHeight) {
+ size.h = obj.clientHeight;
+ }
+ return size;
+ },
+
+ /**
+ * Returns true if the browser is scaling the window, such as on Mobile Safari.
+ * The method used here is far from perfect, but it catches the most important use case:
+ * If we are running on an iDevice and the page is zoomed out.
+ */
+ browserIsViewporting : function () {
+ if (window.innerWidth <= screen.width) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ /**
+ * Returns the device pixel scale, which is equal to the number of device
+ * pixels each css pixel corresponds to. Used to render the proper level of detail
+ * on mobile devices, especially when zoomed out and more detailed textures are
+ * simply wasted.
+ *
+ * @returns The number of device pixels each css pixel corresponds to.
+ * For example, if the browser is zoomed out to 50% and a div with <code>width</code>
+ * set to <code>100px</code> occupies 50 physical pixels, the function will return
+ * <code>0.5</code>.
+ * @type number
+ */
+ getDevicePixelScale : function () {
+ if (this.browserIsViewporting ()) {
+ return screen.width / window.innerWidth;
+ } else {
+ return 1.0;
+ }
+ },
+
+ /**
+ * Requests an animation frame, if the API is supported
+ * on the browser. If not, a <code>setTimeout</code> with
+ * a timeout of zero is used.
+ *
+ * @param {function()} callback the animation frame render function
+ * @param {HTMLElement} element the element to use when requesting an
+ * animation frame
+ */
+ requestAnimationFrame : function (callback, element) {
+ var raff = this.requestAnimationFrameFunction;
+ raff (callback, element);
+ },
+
+ /**
+ * Returns the position in pixels of the element relative
+ * to the top left corner of the document.
+ *
+ * @param {HTMLElement} obj the element
+ * @return a position object with two integer members, x and y.
+ */
+ getElementPosition : function (obj) {
+ var position = new Object();
+ position.x = 0;
+ position.y = 0;
+
+ var o = obj;
+ while (o) {
+ position.x += o.offsetLeft;
+ position.y += o.offsetTop;
+ if (o.clientLeft) {
+ position.x += o.clientLeft;
+ }
+ if (o.clientTop) {
+ position.y += o.clientTop;
+ }
+
+ if (o.x) {
+ position.x += o.x;
+ }
+ if (o.y) {
+ position.y += o.y;
+ }
+ o = o.offsetParent;
+ }
+ return position;
+ },
+
+ /**
+ * Creates an XMLHttpRequest object.
+ *
+ * @type XMLHttpRequest
+ * @returns a XMLHttpRequest object.
+ */
+ createXMLHttpRequest : function () {
+ try {
+ return new ActiveXObject("Msxml2.XMLHTTP");
+ } catch (e) {
+ }
+
+ try {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ } catch (e) {
+ }
+
+ try {
+ return new XMLHttpRequest();
+ } catch(e) {
+ }
+
+ alert("XMLHttpRequest not supported");
+
+ return null;
+ },
+
+ /**
+ * Creates an opacity transition from opaque to transparent.
+ * If CSS transitions aren't supported, the element is
+ * immediately made transparent without a transition.
+ *
+ * @param {HTMLElement} element the element to fade out
+ * @param {function()} onComplete function to call when
+ * the transition is complete.
+ */
+ makeOpacityTransition : function (element, onComplete) {
+ if (element.style.WebkitTransitionProperty != undefined) {
+ element.style.opacity = 1.0;
+ element.style.WebkitTransitionProperty = "opacity";
+ element.style.WebkitTransitionTimingFunction = "linear";
+ element.style.WebkitTransitionDuration = "1s";
+ setTimeout (function () {
+ element.addEventListener ("webkitTransitionEnd", function () {
+ onComplete ();
+ });
+ element.style.opacity = 0.0;
+ }, 0);
+ } else {
+ element.style.opacity = 0.0;
+ onComplete ();
+ }
+ }
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates an event dispatcher.
+ *
+ * @class Base class for objects that dispatch events.
+ */
+bigshot.EventDispatcher = function () {
+ /**
+ * The event listeners. Each key-value pair in the map is
+ * an event name and an <code>Array</code> of listeners.
+ *
+ * @type Object
+ */
+ this.eventListeners = {};
+}
+
+bigshot.EventDispatcher.prototype = {
+ /**
+ * Adds an event listener to the specified event.
+ *
+ * @example
+ * image.addEventListener ("click", function (event) { ... });
+ *
+ * @param {String} eventName the name of the event to add a listener for
+ * @param {Function} handler function that is invoked with an event object
+ * when the event is fired
+ */
+ addEventListener : function (eventName, handler) {
+ if (this.eventListeners[eventName] == undefined) {
+ this.eventListeners[eventName] = new Array ();
+ }
+ this.eventListeners[eventName].push (handler);
+ },
+
+ /**
+ * Removes an event listener.
+ * @param {String} eventName the name of the event to remove a listener for
+ * @param {Function} handler the handler to remove
+ */
+ removeEventListener : function (eventName, handler) {
+ if (this.eventListeners[eventName] != undefined) {
+ var el = this.eventListeners[eventName];
+ for (var i = 0; i < el.length; ++i) {
+ if (el[i] === listener) {
+ el.splice (i, 1);
+ if (el.length == 0) {
+ delete this.eventListeners[eventName];
+ }
+ break;
+ }
+ }
+ }
+ },
+
+ /**
+ * Fires an event.
+ *
+ * @param {String} eventName the name of the event to fire
+ * @param {bigshot.Event} eventObject the event object to pass to the handlers
+ */
+ fireEvent : function (eventName, eventObject) {
+ if (this.eventListeners[eventName] != undefined) {
+ var el = this.eventListeners[eventName];
+ for (var i = 0; i < el.length; ++i) {
+ el[i](eventObject);
+ }
+ }
+ }
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates an event.
+ *
+ * @class Base class for events. The interface is supposed to be as similar to
+ * standard DOM events as possible.
+ * @param {Object} data a data object whose fields will be used to set the
+ * corresponding fields of the event object.
+ */
+bigshot.Event = function (data) {
+
+ /**
+ * Indicates whether the event bubbles.
+ * @default false
+ * @type boolean
+ */
+ this.bubbles = false;
+
+ /**
+ * Indicates whether the event is cancelable.
+ * @default false
+ * @type boolean
+ */
+ this.cancelable = false;
+
+ /**
+ * The current target of the event
+ * @default null
+ */
+ this.currentTarget = null;
+
+ /**
+ * Set if the preventDefault method has been called.
+ * @default false
+ * @type boolean
+ */
+ this.defaultPrevented = false;
+
+ /**
+ * The target to which the event is dispatched.
+ * @default null
+ */
+ this.target = null;
+
+ /**
+ * The time the event was created, in milliseconds since the epoch.
+ * @default the current time, as given by <code>new Date ().getTime ()</code>
+ * @type number
+ */
+ this.timeStamp = new Date ().getTime ();
+
+ /**
+ * The event type.
+ * @default null
+ * @type String
+ */
+ this.type = null;
+
+ /**
+ * Flag indicating origin of event.
+ * @default false
+ * @type boolean
+ */
+ this.isTrusted = false;
+
+ for (var k in data) {
+ this[k] = data[k];
+ }
+}
+
+bigshot.Event.prototype = {
+ /**
+ * Prevents default handling of the event.
+ */
+ preventDefault : function () {
+ this.defaultPrevented = true;
+ }
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * Creates a new instance of the cached resource. May return
+ * null, in which case that value is cached. The function
+ * may be called multiple times, but a corresponding call to
+ * the dispose function will always occur inbetween.
+ * @name bigshot.TimedWeakReference.Create
+ * @function
+ */
+
+/**
+ * Disposes a of the cached resource.
+ * @name bigshot.TimedWeakReference.Dispose
+ * @function
+ * @param {Object} resource the resource that was created
+ * by the create function
+ */
+
+/**
+ * Creates a new instance.
+ *
+ * @class Caches a lazy-created resource for a given time before
+ * disposing it.
+ *
+ * @param {bigshot.TimedWeakReference.Create} create a function that creates the
+ * held resource. May be called multiple times, but not without a call to
+ * dispose inbetween.
+ * @param {bigshot.TimedWeakReference.Dispose} dispose a function that disposes the
+ * resource created by create.
+ * @param {int} interval the polling interval in milliseconds. If the last
+ * access time is further back than one interval, the held resource is
+ * disposed (and will be re-created
+ * on the next call to get).
+ */
+bigshot.TimedWeakReference = function (create, dispose, interval) {
+ this.object = null;
+ this.hasObject = false;
+ this.fnCreate = create;
+ this.fnDispose = dispose;
+ this.lastAccess = new Date ().getTime ();
+ this.hasTimer = false;
+ this.interval = interval;
+};
+
+bigshot.TimedWeakReference.prototype = {
+ /**
+ * Disposes of this instance. The resource is disposed.
+ */
+ dispose : function () {
+ this.clear ();
+ },
+
+ /**
+ * Gets the resource. The resource is created if needed.
+ * The last access time is updated.
+ */
+ get : function () {
+ if (!this.hasObject) {
+ this.hasObject = true;
+ this.object = this.fnCreate ();
+ this.startTimer ();
+ }
+ this.lastAccess = new Date ().getTime ();
+ return this.object;
+ },
+
+ /**
+ * Forcibly disposes the held resource, if any.
+ */
+ clear : function () {
+ if (this.hasObject) {
+ this.hasObject = false;
+ this.fnDispose (this.object);
+ this.object = null;
+ this.stopTimer ();
+ }
+ },
+
+ /**
+ * Stops the polling timer if it is running.
+ * @private
+ */
+ stopTimer : function () {
+ if (this.hasTimer) {
+ clearTimeout (this.timerId);
+ this.hasTimer = false;
+ }
+ },
+
+ /**
+ * Starts the polling timer if it isn't already running.
+ * @private
+ */
+ startTimer : function () {
+ if (!this.hasTimer) {
+ var that = this;
+ this.hasTimer = true;
+ this.timerId = setTimeout (function () {
+ that.hasTimer = false;
+ that.update ();
+ }, this.interval);
+ }
+ },
+
+ /**
+ * Disposes of the held resource if it hasn't been
+ * accessed in {@link #interval} milliseconds.
+ * @private
+ */
+ update : function () {
+ if (this.hasObject) {
+ var now = new Date ().getTime ();
+ if (now - this.lastAccess > this.interval) {
+ this.clear ();
+ } else {
+ this.startTimer ();
+ }
+ }
+ }
+}
+
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates an image event.
+ *
+ * @class Base class for events dispatched by bigshot.ImageBase.
+ * @param {Object} data a data object whose fields will be used to set the
+ * corresponding fields of the event object.
+ * @extends bigshot.Event
+ * @see bigshot.ImageBase
+ */
+bigshot.ImageEvent = function (data) {
+ bigshot.Event.call (this, data);
+}
+
+/**
+ * The image X coordinate of the event, if any.
+ *
+ * @name bigshot.ImageEvent#imageX
+ * @field
+ * @type number
+ */
+
+/**
+ * The image Y coordinate of the event, if any.
+ *
+ * @name bigshot.ImageEvent#imageY
+ * @field
+ * @type number
+ */
+
+/**
+ * The client X coordinate of the event, if any.
+ *
+ * @name bigshot.ImageEvent#clientX
+ * @field
+ * @type number
+ */
+
+/**
+ * The client Y coordinate of the event, if any.
+ *
+ * @name bigshot.ImageEvent#clientY
+ * @field
+ * @type number
+ */
+
+/**
+ * The local X coordinate of the event, if any.
+ *
+ * @name bigshot.ImageEvent#localX
+ * @field
+ * @type number
+ */
+
+/**
+ * The local Y coordinate of the event, if any.
+ *
+ * @name bigshot.ImageEvent#localY
+ * @field
+ * @type number
+ */
+
+
+bigshot.ImageEvent.prototype = {
+};
+
+bigshot.Object.extend (bigshot.ImageEvent, bigshot.Event);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates an image event.
+ *
+ * @class Base class for events dispatched by bigshot.VRPanorama.
+ * @param {Object} data a data object whose fields will be used to set the
+ * corresponding fields of the event object.
+ * @extends bigshot.Event
+ * @see bigshot.VRPanorama
+ */
+bigshot.VREvent = function (data) {
+ bigshot.Event.call (this, data);
+}
+
+/**
+ * The yaw coordinate of the event, if any.
+ *
+ * @name bigshot.VREvent#yaw
+ * @field
+ * @type number
+ */
+
+/**
+ * The pitch coordinate of the event, if any.
+ *
+ * @name bigshot.VREvent#pitch
+ * @field
+ * @type number
+ */
+
+/**
+ * The client X coordinate of the event, if any.
+ *
+ * @name bigshot.VREvent#clientX
+ * @field
+ * @type number
+ */
+
+/**
+ * The client Y coordinate of the event, if any.
+ *
+ * @name bigshot.VREvent#clientY
+ * @field
+ * @type number
+ */
+
+/**
+ * The local X coordinate of the event, if any.
+ *
+ * @name bigshot.VREvent#localX
+ * @field
+ * @type number
+ */
+
+/**
+ * The local Y coordinate of the event, if any.
+ *
+ * @name bigshot.VREvent#localY
+ * @field
+ * @type number
+ */
+
+/**
+ * A x,y,z triplet specifying a 3D ray from the viewer in the direction the
+ * event took place. The same as the yaw and pitch fields, but in Cartesian
+ * coordinates.
+ *
+ * @name bigshot.VREvent#ray
+ * @field
+ * @type xyz-triplet
+ */
+
+
+bigshot.VREvent.prototype = {
+};
+
+bigshot.Object.extend (bigshot.VREvent, bigshot.Event);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new full-screen handler for an element.
+ *
+ * @class A utility class for making an element "full screen", or as close to that
+ * as browser security allows. If the browser supports the <code>requestFullScreen</code>
+ * API - as standard or as <code>moz</code>- or <code>webkit</code>- extensions,
+ * this will be used.
+ *
+ * @param {HTMLElement} container the element that is to be made full screen
+ */
+bigshot.FullScreen = function (container) {
+ this.container = container;
+
+ this.isFullScreen = false;
+ this.savedBodyStyle = null;
+ this.savedParent = null;
+ this.savedSize = null;
+ this.expanderDiv = null;
+ this.restoreSize = false;
+
+ this.onCloseHandlers = new Array ();
+ this.onResizeHandlers = new Array ();
+
+ var findFunc = function (el, list) {
+ for (var i = 0; i < list.length; ++i) {
+ if (el[list[i]]) {
+ return list[i];
+ }
+ }
+ return null;
+ };
+
+ this.requestFullScreen = findFunc (container, ["requestFullScreen", "mozRequestFullScreen", "webkitRequestFullScreen"]);
+ this.cancelFullScreen = findFunc (document, ["cancelFullScreen", "mozCancelFullScreen", "webkitCancelFullScreen"]);
+
+ this.restoreSize = this.requestFullScreen != null;
+}
+
+bigshot.FullScreen.prototype = {
+ browser : new bigshot.Browser (),
+
+ getRootElement : function () {
+ return this.div;
+ },
+
+ /**
+ * Adds a function that will run when exiting full screen mode.
+ *
+ * @param {function()} onClose the function to call
+ */
+ addOnClose : function (onClose) {
+ this.onCloseHandlers.push (onClose);
+ },
+
+ /**
+ * Notifies all <code>onClose</code> handlers.
+ *
+ * @private
+ */
+ onClose : function () {
+ for (var i = 0; i < this.onCloseHandlers.length; ++i) {
+ this.onCloseHandlers[i] ();
+ }
+ },
+
+ /**
+ * Adds a function that will run when the element is resized.
+ *
+ * @param {function()} onResize the function to call
+ */
+ addOnResize : function (onResize) {
+ this.onResizeHandlers.push (onResize);
+ },
+
+ /**
+ * Notifies all resize handlers.
+ *
+ * @private
+ */
+ onResize : function () {
+ for (var i = 0; i < this.onResizeHandlers.length; ++i) {
+ this.onResizeHandlers[i] ();
+ }
+ },
+
+ /**
+ * Begins full screen mode.
+ */
+ open : function () {
+ this.isFullScreen = true;
+
+ if (this.requestFullScreen) {
+ return this.openRequestFullScreen ();
+ } else {
+ return this.openCompat ();
+ }
+ },
+
+ /**
+ * Makes the element really full screen using the <code>requestFullScreen</code>
+ * API.
+ *
+ * @private
+ */
+ openRequestFullScreen : function () {
+ this.savedSize = {
+ width : this.container.style.width,
+ height : this.container.style.height
+ };
+
+ this.container.style.width = "100%";
+ this.container.style.height = "100%";
+
+ var that = this;
+
+ if (this.requestFullScreen == "mozRequestFullScreen") {
+ /**
+ * @private
+ */
+ var errFun = function () {
+ that.container.removeEventListener ("mozfullscreenerror", errFun);
+ that.isFullScreen = false;
+ that.exitFullScreenHandler ();
+ that.onClose ();
+ };
+ this.container.addEventListener ("mozfullscreenerror", errFun);
+
+ /**
+ * @private
+ */
+ var changeFun = function () {
+ if (document.mozFullScreenElement !== that.container) {
+ document.removeEventListener ("mozfullscreenchange", changeFun);
+ that.exitFullScreenHandler ();
+ } else {
+ that.onResize ();
+ }
+ };
+ document.addEventListener ("mozfullscreenchange", changeFun);
+ } else {
+ /**
+ * @private
+ */
+ var changeFun = function () {
+ if (document.webkitCurrentFullScreenElement !== that.container) {
+ that.container.removeEventListener ("webkitfullscreenchange", changeFun);
+ that.exitFullScreenHandler ();
+ } else {
+ that.onResize ();
+ }
+ };
+ this.container.addEventListener ("webkitfullscreenchange", changeFun);
+ }
+
+ this.exitFullScreenHandler = function () {
+ if (that.isFullScreen) {
+ that.isFullScreen = false;
+ document[that.cancelFullScreen]();
+ if (that.restoreSize) {
+ that.container.style.width = that.savedSize.width;
+ that.container.style.height = that.savedSize.height;
+ }
+ that.onResize ();
+ that.onClose ();
+ }
+ };
+ this.container[this.requestFullScreen]();
+ },
+
+ /**
+ * Makes the element "full screen" in older browsers by covering the browser's client area.
+ *
+ * @private
+ */
+ openCompat : function () {
+ this.savedParent = this.container.parentNode;
+
+ this.savedSize = {
+ width : this.container.style.width,
+ height : this.container.style.height
+ };
+ this.savedBodyStyle = document.body.style.cssText;
+
+ document.body.style.overflow = "hidden";
+
+ this.expanderDiv = document.createElement ("div");
+ this.expanderDiv.style.position = "absolute";
+ this.expanderDiv.style.top = "0px";
+ this.expanderDiv.style.left = "0px";
+ this.expanderDiv.style.width = Math.max (window.innerWidth, document.documentElement.clientWidth) + "px";
+ this.expanderDiv.style.height = Math.max (window.innerHeight, document.documentElement.clientHeight) + "px";
+
+ document.body.appendChild (this.expanderDiv);
+
+ this.div = document.createElement ("div");
+ this.div.style.position = "fixed";
+ this.div.style.top = window.pageYOffset + "px";
+ this.div.style.left = window.pageXOffset + "px";
+
+ this.div.style.width = window.innerWidth + "px";
+ this.div.style.height = window.innerHeight + "px";
+ this.div.style.zIndex = 9998;
+
+ this.div.appendChild (this.container);
+
+ //this.container.style.width = window.innerWidth + "px";
+ //this.container.style.height = window.innerHeight + "px";
+
+ document.body.appendChild (this.div);
+
+ var that = this;
+ var resizeHandler = function (e) {
+ setTimeout (function () {
+ that.div.style.width = window.innerWidth + "px";
+ that.div.style.height = window.innerHeight + "px";
+ setTimeout (function () {
+ that.onResize ();
+ }, 1);
+ }, 1);
+ };
+
+
+ var rotationHandler = function (e) {
+ that.expanderDiv.style.width = Math.max (window.innerWidth, document.documentElement.clientWidth) + "px";
+ that.expanderDiv.style.height = Math.max (window.innerHeight, document.documentElement.clientHeight) + "px";
+ setTimeout (function () {
+ that.div.style.top = window.pageYOffset + "px";
+ that.div.style.left = window.pageXOffset + "px";
+ that.div.style.width = window.innerWidth + "px";
+ that.div.style.height = window.innerHeight + "px";
+ setTimeout (function () {
+ that.onResize ();
+ }, 1);
+ }, 1);
+ };
+
+ var escHandler = function (e) {
+ if (e.keyCode == 27) {
+ that.exitFullScreenHandler ();
+ }
+ };
+
+ this.exitFullScreenHandler = function () {
+ that.isFullScreen = false;
+ that.browser.unregisterListener (document, "keydown", escHandler);
+ that.browser.unregisterListener (window, "resize", resizeHandler);
+ that.browser.unregisterListener (document.body, "orientationchange", rotationHandler);
+ if (that.restoreSize) {
+ that.container.style.width = that.savedSize.width;
+ that.container.style.height = that.savedSize.height;
+ }
+
+ document.body.style.cssText = that.savedBodyStyle;
+
+ that.savedParent.appendChild (that.container);
+ document.body.removeChild (that.div);
+ document.body.removeChild (that.expanderDiv);
+
+ that.onResize ();
+ that.onClose ();
+ setTimeout (function () {
+ that.onResize ();
+ }, 1);
+ };
+
+ this.browser.registerListener (document, "keydown", escHandler, false);
+ this.browser.registerListener (window, "resize", resizeHandler, false);
+ this.browser.registerListener (document.body, "orientationchange", rotationHandler, false);
+
+ this.onResize ();
+
+ return this.exitFullScreenHandler;
+ },
+
+ /**
+ * Ends full screen mode.
+ */
+ close : function () {
+ this.exitFullScreenHandler ();
+ }
+};
+
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @class Loads image and XML data.
+ */
+bigshot.DataLoader = function () {
+}
+
+bigshot.DataLoader.prototype = {
+ /**
+ * Loads an image.
+ *
+ * @param {String} url the url to load
+ * @param {function(success,img)} onloaded called on complete
+ */
+ loadImage : function (url, onloaded) {},
+
+ /**
+ * Loads XML data.
+ *
+ * @param {String} url the url to load
+ * @param {boolean} async use async request
+ * @param {function(success,xml)} [onloaded] called on complete for async requests
+ * @return the xml for synchronous calls
+ */
+ loadXml : function (url, async, onloaded) {}
+}
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new data loader.
+ *
+ * @param {int} [maxRetries=0] the maximum number of times to retry requests
+ * @param {String} [crossOrigin] the CORS crossOrigin parameter to use when loading images
+ * @class Data loader using standard browser functions.
+ * @augments bigshot.DataLoader
+ */
+bigshot.DefaultDataLoader = function (maxRetries, crossOrigin) {
+ this.maxRetries = maxRetries;
+ this.crossOrigin = crossOrigin;
+
+ if (!this.maxRetries) {
+ this.maxRetries = 0;
+ }
+}
+
+bigshot.DefaultDataLoader.prototype = {
+ browser : new bigshot.Browser (),
+
+ loadImage : function (url, onloaded) {
+ var tile = document.createElement ("img");
+ tile.retries = 0;
+ if (this.crossOrigin != null) {
+ tile.crossOrigin = this.crossOrigin;
+ }
+ var that = this;
+ this.browser.registerListener (tile, "load", function () {
+ if (onloaded) {
+ onloaded (tile);
+ }
+ }, false);
+ this.browser.registerListener (tile, "error", function () {
+ tile.retries++;
+ if (tile.retries <= that.maxRetries) {
+ setTimeout (function () {
+ tile.src = url;
+ }, tile.retries * 1000);
+ } else {
+ if (onloaded) {
+ onloaded (null);
+ }
+ }
+ }, false);
+ tile.src = url;
+ return tile;
+ },
+
+ loadXml : function (url, synchronous, onloaded) {
+ for (var tries = 0; tries <= this.maxRetries; ++tries) {
+ var req = this.browser.createXMLHttpRequest ();
+
+ req.open("GET", url, false);
+ req.send(null);
+ if(req.status == 200) {
+ var xml = req.responseXML;
+ if (xml != null) {
+ if (onloaded) {
+ onloaded (xml);
+ }
+ return xml;
+ }
+ }
+
+ if (tries == that.maxRetries) {
+ if (onloaded) {
+ onloaded (null);
+ }
+ return null;
+ }
+ }
+ }
+}
+
+bigshot.Object.validate ("bigshot.DefaultDataLoader", bigshot.DataLoader);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @class Data loader using standard browser functions that maintains
+ * an in-memory cache of everything loaded.
+ * @augments bigshot.DataLoader
+ */
+bigshot.CachingDataLoader = function () {
+ this.cache = {};
+ this.requested = {};
+ this.requestedTiles = {};
+}
+
+bigshot.CachingDataLoader.prototype = {
+
+ browser : new bigshot.Browser (),
+
+ loadImage : function (url, onloaded) {
+ if (this.cache[url]) {
+ if (onloaded) {
+ onloaded (this.cache[url]);
+ }
+ return this.cache[url];
+ } else if (this.requested[url]) {
+ if (onloaded) {
+ this.requested[url].push (onloaded);
+ }
+ return this.requestedTiles[url];
+ } else {
+ var that = this;
+ this.requested[url] = new Array ();
+ if (onloaded) {
+ this.requested[url].push (onloaded);
+ }
+
+ var tile = document.createElement ("img");
+ this.requestedTiles[url] = tile;
+ this.browser.registerListener (tile, "load", function () {
+ var listeners = that.requested[url];
+ delete that.requested[url];
+ delete that.requestedTiles[url];
+ that.cache[url] = tile;
+
+ for (var i = 0; i < listeners.length; ++i) {
+ listeners[i] (tile);
+ }
+ }, false);
+ tile.src = url;
+ return tile;
+ }
+ },
+
+ loadXml : function (url, async, onloaded) {
+ if (this.cache[url]) {
+ if (onloaded) {
+ onloaded (this.cache[url]);
+ }
+ return this.cache[url];
+ } else if (this.requested[url] && async) {
+ if (onloaded) {
+ this.requested[url].push (onloaded);
+ }
+ } else {
+ var req = this.browser.createXMLHttpRequest ();
+
+ if (!this.requested[url]) {
+ this.requested[url] = new Array ();
+ }
+
+ if (async) {
+ if (onloaded) {
+ this.requested[url].push (onloaded);
+ }
+ }
+
+ var that = this;
+ var finishRequest = function () {
+ if (that.requested[url]) {
+ var xml = null;
+ if(req.status == 200) {
+ xml = req.responseXML;
+ }
+ var listeners = that.requested[url];
+ delete that.requested[url];
+ that.cache[url] = xml
+
+ for (var i = 0; i < listeners.length; ++i) {
+ listeners[i](xml);
+ }
+ }
+ return xml;
+ };
+
+ if (async) {
+ req.onreadystatechange = function () {
+ if (req.readyState == 4) {
+ finishRequest ();
+ }
+ };
+ req.open("GET", url, true);
+ req.send ();
+ } else {
+ req.open("GET", url, false);
+ req.send ();
+ return finishRequest ();
+ }
+ }
+ }
+}
+
+bigshot.Object.validate ("bigshot.CachingDataLoader", bigshot.DataLoader);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new hotspot instance.
+ *
+ * @class Base class for hotspots in a {@link bigshot.HotspotLayer}. See {@link bigshot.HotspotLayer} for
+ * examples.
+ *
+ * @param {number} x x-coordinate of the top-left corner, given in full image pixels
+ * @param {number} y y-coordinate of the top-left corner, given in full image pixels
+ * @param {number} w width of the hotspot, given in full image pixels
+ * @param {number} h height of the hotspot, given in full image pixels
+ * @see bigshot.HotspotLayer
+ * @see bigshot.LabeledHotspot
+ * @see bigshot.LinkHotspot
+ * @constructor
+ */
+bigshot.Hotspot = function (x, y, w, h) {
+ var element = document.createElement ("div");
+ element.style.position = "absolute";
+ element.style.overflow = "visible";
+
+ this.element = element;
+ this.x = x;
+ this.y = y;
+ this.w = w;
+ this.h = h;
+}
+
+bigshot.Hotspot.prototype = {
+
+ browser : new bigshot.Browser (),
+
+ /**
+ * Lays out the hotspot in the viewport.
+ *
+ * @name bigshot.Hotspot#layout
+ * @param x0 x-coordinate of top-left corner of the full image in css pixels
+ * @param y0 y-coordinate of top-left corner of the full image in css pixels
+ * @param zoomFactor the zoom factor.
+ * @function
+ */
+ layout : function (x0, y0, zoomFactor) {
+ var sx = this.x * zoomFactor + x0;
+ var sy = this.y * zoomFactor + y0;
+ var sw = this.w * zoomFactor;
+ var sh = this.h * zoomFactor;
+ this.element.style.top = sy + "px";
+ this.element.style.left = sx + "px";
+ this.element.style.width = sw + "px";
+ this.element.style.height = sh + "px";
+ },
+
+ /**
+ * Returns the HTMLDivElement used to show the hotspot.
+ * Clients can access this element in order to style it.
+ *
+ * @type HTMLDivElement
+ */
+ getElement : function () {
+ return this.element;
+ }
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new labeled hotspot instance.
+ *
+ * @class A point hotspot consisting of an image.
+ *
+ * @see bigshot.HotspotLayer
+ * @param {number} x x-coordinate of the center corner, given in full image pixels
+ * @param {number} y y-coordinate of the center corner, given in full image pixels
+ * @param {number} w width of the hotspot, given in screen pixels
+ * @param {number} h height of the hotspot, given in screen pixels
+ * @param {number} xo x-offset, given in screen pixels
+ * @param {number} yo y-offset, given in screen pixels
+ * @param {HTMLElement} element the HTML element to position
+ * @param {String} [imageUrl] the image to use as hotspot sprite
+ * @augments bigshot.Hotspot
+ */
+bigshot.PointHotspot = function (x, y, w, h, xo, yo, imageUrl) {
+ bigshot.Hotspot.call (this, x, y, w, h);
+ this.xo = xo;
+ this.yo = yo;
+
+ if (imageUrl) {
+ var el = this.getElement ();
+ el.style.backgroundImage = "url('" + imageUrl + "')";
+ el.style.backgroundRepeat = "no-repeat";
+ }
+}
+
+bigshot.PointHotspot.prototype = {
+ /**
+ * Returns the label element.
+ *
+ * @type HTMLDivElement
+ */
+ getLabel : function () {
+ return this.label;
+ },
+
+ layout : function (x0, y0, zoomFactor) {
+ var sx = this.x * zoomFactor + x0 + this.xo;
+ var sy = this.y * zoomFactor + y0 + this.yo;
+ this.element.style.top = sy + "px";
+ this.element.style.left = sx + "px";
+ this.element.style.width = this.w + "px";
+ this.element.style.height = this.h + "px";
+ }
+};
+
+bigshot.Object.extend (bigshot.PointHotspot, bigshot.Hotspot);
+
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Abstract interface description for a Layer.
+ *
+ * @class Abstract interface description for a layer.
+ */
+bigshot.Layer = function () {
+}
+
+bigshot.Layer.prototype = {
+ /**
+ * Returns the layer container.
+ *
+ * @type HTMLDivElement
+ */
+ getContainer : function () {},
+
+ /**
+ * Sets the maximum number of image tiles that will be visible in the image.
+ *
+ * @param {int} x the number of tiles horizontally
+ * @param {int} y the number of tiles vertically
+ */
+ setMaxTiles : function (x, y) {},
+
+ /**
+ * Called when the image's viewport is resized.
+ *
+ * @param {int} w the new width of the viewport, in css pixels
+ * @param {int} h the new height of the viewport, in css pixels
+ */
+ resize : function (w, h) {},
+
+ /**
+ * Lays out the layer.
+ *
+ * @param {number} zoom the zoom level, adjusted for texture stretching
+ * @param {number} x0 the x-coordinate of the top-left corner of the top-left tile in css pixels
+ * @param {number} y0 the y-coordinate of the top-left corner of the top-left tile in css pixels
+ * @param {number} tx0 column number (starting at zero) of the top-left tile
+ * @param {number} ty0 row number (starting at zero) of the top-left tile
+ * @param {number} size the {@link bigshot.ImageParameters#tileSize} - width of each
+ * image tile in pixels - of the image
+ * @param {number} stride offset (vertical and horizontal) from the top-left corner
+ * of a tile to the next tile's top-left corner.
+ * @param {number} opacity the opacity of the layer as a CSS opacity value.
+ */
+ layout : function (zoom, x0, y0, tx0, ty0, size, stride, opacity) {}
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new labeled hotspot instance.
+ *
+ * @class A hotspot with a label under it. The label element can be accessed using
+ * the getLabel method and styled as any HTMLElement. See {@link bigshot.HotspotLayer} for
+ * examples.
+ *
+ * @see bigshot.HotspotLayer
+ * @param {number} x x-coordinate of the top-left corner, given in full image pixels
+ * @param {number} y y-coordinate of the top-left corner, given in full image pixels
+ * @param {number} w width of the hotspot, given in full image pixels
+ * @param {number} h height of the hotspot, given in full image pixels
+ * @param {String} labelText text of the label
+ * @augments bigshot.Hotspot
+ */
+bigshot.LabeledHotspot = function (x, y, w, h, labelText) {
+ bigshot.Hotspot.call (this, x, y, w, h);
+
+ this.label = document.createElement ("div");
+ this.label.style.position = "relative";
+ this.label.style.display = "inline-block";
+
+ this.getElement ().appendChild (this.label);
+ this.label.innerHTML = labelText;
+ this.labelSize = this.browser.getElementSize (this.label);
+}
+
+bigshot.LabeledHotspot.prototype = {
+ /**
+ * Returns the label element.
+ *
+ * @type HTMLDivElement
+ */
+ getLabel : function () {
+ return this.label;
+ },
+
+ layout : function (x0, y0, zoomFactor) {
+ this.layout._super.call (this, x0, y0, zoomFactor);
+ var sw = this.w * zoomFactor;
+ var sh = this.h * zoomFactor;
+ this.label.style.top = (sh + 4) + "px";
+ this.label.style.left = ((sw - this.labelSize.w) / 2) + "px";
+ }
+};
+
+bigshot.Object.extend (bigshot.LabeledHotspot, bigshot.Hotspot);
+
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new link-hotspot instance.
+ *
+ * @class A labeled hotspot that takes the user to another
+ * location when it is clicked on. See {@link bigshot.HotspotLayer} for
+ * examples.
+ *
+ * @see bigshot.HotspotLayer
+ * @param {number} x x-coordinate of the top-left corner, given in full image pixels
+ * @param {number} y y-coordinate of the top-left corner, given in full image pixels
+ * @param {number} w width of the hotspot, given in full image pixels
+ * @param {number} h height of the hotspot, given in full image pixels
+ * @param {String} labelText text of the label
+ * @param {String} url url to go to on click
+ * @augments bigshot.LabeledHotspot
+ * @constructor
+ */
+bigshot.LinkHotspot = function (x, y, w, h, labelText, url) {
+ bigshot.LabeledHotspot.call (this, x, y, w, h, labelText);
+ this.browser.registerListener (this.getElement (), "click", function () {
+ document.location.href = url;
+ });
+};
+
+bigshot.Object.extend (bigshot.LinkHotspot, bigshot.LabeledHotspot);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new hotspot layer. The layer must be added to the image using
+ * {@link bigshot.ImageBase#addLayer}.
+ *
+ * @class A hotspot layer.
+ * @example
+ * var image = new bigshot.Image (...);
+ * var hotspotLayer = new bigshot.HotspotLayer (image);
+ * var hotspot = new bigshot.LinkHotspot (100, 100, 200, 100,
+ * "Bigshot on Google Code",
+ * "http://code.google.com/p/bigshot/");
+ *
+ * // Style the hotspot a bit
+ * hotspot.getElement ().className = "hotspot";
+ * hotspot.getLabel ().className = "label";
+ *
+ * hotspotLayer.addHotspot (hotspot);
+ *
+ * image.addLayer (hotspotLayer);
+ *
+ * @param {bigshot.ImageBase} image the image this hotspot layer will be part of
+ * @augments bigshot.Layer
+ * @constructor
+ */
+bigshot.HotspotLayer = function (image) {
+ this.image = image;
+ this.hotspots = new Array ();
+ this.browser = new bigshot.Browser ();
+ this.container = image.createLayerContainer ();
+ this.parentContainer = image.getContainer ();
+ this.resize (0, 0);
+}
+
+bigshot.HotspotLayer.prototype = {
+
+ getContainer : function () {
+ return this.container;
+ },
+
+ resize : function (w, h) {
+ this.container.style.width = this.parentContainer.clientWidth + "px";
+ this.container.style.height = this.parentContainer.clientHeight + "px";
+ },
+
+ layout : function (zoom, x0, y0, tx0, ty0, size, stride, opacity) {
+ var zoomFactor = Math.pow (2, this.image.getZoom ());
+ x0 -= stride * tx0;
+ y0 -= stride * ty0;
+ for (var i = 0; i < this.hotspots.length; ++i) {
+ this.hotspots[i].layout (x0, y0, zoomFactor);
+ }
+ },
+
+ setMaxTiles : function (mtx, mty) {
+ },
+
+ /**
+ * Adds a hotspot to the layer.
+ *
+ * @param {bigshot.Hotspot} hotspot the hotspot to add.
+ */
+ addHotspot : function (hotspot) {
+ this.container.appendChild (hotspot.getElement ());
+ this.hotspots.push (hotspot);
+ }
+}
+
+bigshot.Object.validate ("bigshot.HotspotLayer", bigshot.Layer);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new image layer.
+ *
+ * @param {bigshot.ImageBase} image the image that this layer is part of
+ * @param {bigshot.ImageParameters} parameters the associated image parameters
+ * @param {number} w the current width in css pixels of the viewport
+ * @param {number} h the current height in css pixels of the viewport
+ * @param {bigshot.ImageTileCache} itc the tile cache to use
+ * @class A tiled, zoomable image layer.
+ * @constructor
+ */
+bigshot.TileLayer = function (image, parameters, w, h, itc) {
+ this.rows = new Array ();
+ this.browser = new bigshot.Browser ();
+ this.container = image.createLayerContainer ();
+ this.parentContainer = image.getContainer ();
+ this.parameters = parameters;
+ this.w = w;
+ this.h = h;
+ this.imageTileCache = itc;
+
+ this.resize (w, h);
+ return this;
+}
+
+bigshot.TileLayer.prototype = {
+ getContainer : function () {
+ return this.container;
+ },
+
+ resize : function (w, h) {
+ this.container.style.width = this.parentContainer.clientWidth + "px";
+ this.container.style.height = this.parentContainer.clientHeight + "px";
+ this.pixelWidth = this.parentContainer.clientWidth;
+ this.pixelHeight = this.parentContainer.clientHeight;
+ this.w = w;
+ this.h = h;
+ this.rows = new Array ();
+ this.browser.removeAllChildren (this.container);
+ for (var r = 0; r < h; ++r) {
+ var row = new Array ();
+ for (var c = 0; c < w; ++c) {
+ var tileAnchor = document.createElement ("div");
+ tileAnchor.style.position = "absolute";
+ tileAnchor.style.overflow = "hidden";
+ tileAnchor.style.width = this.container.clientWidth + "px";
+ tileAnchor.style.height = this.container.clientHeight + "px";
+
+ var tile = document.createElement ("div");
+ tile.style.position = "relative";
+ tile.style.border = "hidden";
+ tile.style.visibility = "hidden";
+ tile.bigshotData = {
+ visible : false
+ };
+ row.push (tile);
+ this.container.appendChild (tileAnchor);
+ tileAnchor.appendChild (tile);
+ }
+ this.rows.push (row);
+ }
+ },
+
+ layout : function (zoom, x0, y0, tx0, ty0, size, stride, opacity) {
+ zoom = Math.min (0, Math.ceil (zoom));
+
+ this.imageTileCache.resetUsed ();
+ var y = y0;
+
+ var visible = 0;
+ for (var r = 0; r < this.h; ++r) {
+ var x = x0;
+ for (var c = 0; c < this.w; ++c) {
+ var tile = this.rows[r][c];
+ var bigshotData = tile.bigshotData;
+ if (x + size < 0 || x > this.pixelWidth || y + size < 0 || y > this.pixelHeight) {
+ if (bigshotData.visible) {
+ bigshotData.visible = false;
+ tile.style.visibility = "hidden";
+ }
+ } else {
+ visible++;
+ tile.style.left = x + "px";
+ tile.style.top = y + "px";
+ tile.style.width = size + "px";
+ tile.style.height = size + "px";
+ tile.style.opacity = opacity;
+ if (!bigshotData.visible) {
+ bigshotData.visible = true;
+ tile.style.visibility = "visible";
+ }
+ var tx = c + tx0;
+ var ty = r + ty0;
+ if (this.parameters.wrapX) {
+ if (tx < 0 || tx >= this.imageTileCache.maxTileX) {
+ tx = (tx + this.imageTileCache.maxTileX) % this.imageTileCache.maxTileX;
+ }
+ }
+
+ if (this.parameters.wrapY) {
+ if (ty < 0 || ty >= this.imageTileCache.maxTileY) {
+ ty = (ty + this.imageTileCache.maxTileY) % this.imageTileCache.maxTileY;
+ }
+ }
+
+ var imageKey = tx + "_" + ty + "_" + zoom;
+ var isOutside = tx < 0 || tx >= this.imageTileCache.maxTileX || ty < 0 || ty >= this.imageTileCache.maxTileY;
+ if (isOutside) {
+ if (!bigshotData.isOutside) {
+ var image = this.imageTileCache.getImage (tx, ty, zoom);
+
+ this.browser.removeAllChildren (tile);
+ tile.appendChild (image);
+ bigshotData.image = image;
+ }
+ bigshotData.isOutside = true;
+ bigshotData.imageKey = "EMPTY";
+ bigshotData.image.style.width = size + "px";
+ bigshotData.image.style.height = size + "px";
+ } else {
+ var image = this.imageTileCache.getImage (tx, ty, zoom);
+
+ bigshotData.isOutside = false;
+
+ if (bigshotData.imageKey !== imageKey || bigshotData.isPartial) {
+ this.browser.removeAllChildren (tile);
+ tile.appendChild (image);
+ bigshotData.image = image;
+ bigshotData.imageKey = imageKey;
+ bigshotData.isPartial = image.isPartial;
+ }
+ bigshotData.image.style.width = size + "px";
+ bigshotData.image.style.height = size + "px";
+
+ }
+ }
+ x += stride;
+ }
+ y += stride;
+ }
+ },
+
+ setMaxTiles : function (mtx, mty) {
+ this.imageTileCache.setMaxTiles (mtx, mty);
+ }
+};
+
+bigshot.Object.validate ("bigshot.TileLayer", bigshot.Layer);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new, empty, LRUMap instance.
+ *
+ * @class Implementation of a Least-Recently-Used cache map.
+ * Used by the ImageTileCache to keep track of cache entries.
+ * @constructor
+ */
+bigshot.LRUMap = function () {
+ /**
+ * Key to last-accessed time mapping.
+ *
+ * @type Object
+ */
+ this.keyToTime = {};
+
+ /**
+ * Current time counter. Incremented for each access of
+ * a key in the map.
+ * @type int
+ */
+ this.counter = 0;
+
+ /**
+ * Current size of the map.
+ * @type int
+ */
+ this.size = 0;
+}
+
+bigshot.LRUMap.prototype = {
+ /**
+ * Marks access to an item, represented by its key in the map.
+ * The key's last-accessed time is updated to the current time
+ * and the current time is incremented by one step.
+ *
+ * @param {String} key the key associated with the accessed item
+ */
+ access : function (key) {
+ this.remove (key);
+ this.keyToTime[key] = this.counter;
+ ++this.counter;
+ ++this.size;
+ },
+
+ /**
+ * Removes a key from the map.
+ *
+ * @param {String} key the key to remove
+ * @returns true iff the key existed in the map.
+ * @type boolean
+ */
+ remove : function (key) {
+ if (this.keyToTime[key]) {
+ delete this.keyToTime[key];
+ --this.size;
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Returns the current number of keys in the map.
+ * @type int
+ */
+ getSize : function () {
+ return this.size;
+ },
+
+ /**
+ * Returns the key in the map with the lowest
+ * last-accessed time. This is done as a linear
+ * search through the map. It could be done much
+ * faster with a sorted map, but unless this becomes
+ * a bottleneck it is just not worth the effort.
+ * @type String
+ */
+ leastUsed : function () {
+ var least = this.counter + 1;
+ var leastKey = null;
+ for (var k in this.keyToTime) {
+ if (this.keyToTime[k] < least) {
+ least = this.keyToTime[k];
+ leastKey = k;
+ }
+ }
+ return leastKey;
+ }
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new cache instance.
+ *
+ * @class Tile cache for the {@link bigshot.TileLayer}.
+ * @constructor
+ */
+bigshot.ImageTileCache = function (onLoaded, onCacheInit, parameters) {
+ var that = this;
+
+ this.parameters = parameters;
+
+ /**
+ * Reduced-resolution preview of the full image.
+ * Loaded from the "poster" image created by
+ * MakeImagePyramid
+ *
+ * @private
+ * @type HTMLImageElement
+ */
+ this.fullImage = null;
+ parameters.dataLoader.loadImage (parameters.fileSystem.getPosterFilename (), function (tile) {
+ that.fullImage = tile;
+ if (onCacheInit) {
+ onCacheInit ();
+ }
+ });
+
+ /**
+ * Maximum number of tiles in the cache.
+ * @private
+ * @type int
+ */
+ this.maxCacheSize = 512;
+ this.maxTileX = 0;
+ this.maxTileY = 0;
+ this.cachedImages = {};
+ this.requestedImages = {};
+ this.usedImages = {};
+ this.lastOnLoadFiredAt = 0;
+ this.imageRequests = 0;
+ this.lruMap = new bigshot.LRUMap ();
+ this.onLoaded = onLoaded;
+ this.browser = new bigshot.Browser ();
+ this.partialImageSize = parameters.tileSize / 4;
+ this.POSTER_ZOOM_LEVEL = Math.log (parameters.posterSize / Math.max (parameters.width, parameters.height)) / Math.log (2);
+}
+
+bigshot.ImageTileCache.prototype = {
+ resetUsed : function () {
+ this.usedImages = {};
+ },
+
+ setMaxTiles : function (mtx, mty) {
+ this.maxTileX = mtx;
+ this.maxTileY = mty;
+ },
+
+ getPartialImage : function (tileX, tileY, zoomLevel) {
+ var img = this.getPartialImageFromDownsampled (tileX, tileY, zoomLevel, 0, 0, this.parameters.tileSize, this.parameters.tileSize);
+ if (img == null) {
+ img = this.getPartialImageFromPoster (tileX, tileY, zoomLevel);
+ }
+ return img;
+ },
+
+ getPartialImageFromPoster : function (tileX, tileY, zoomLevel) {
+ if (this.fullImage && this.fullImage.complete) {
+ var posterScale = this.fullImage.width / this.parameters.width;
+ var tileSizeAtZoom = posterScale * this.parameters.tileSize / Math.pow (2, zoomLevel);
+
+ x0 = Math.floor (tileSizeAtZoom * tileX);
+ y0 = Math.floor (tileSizeAtZoom * tileY);
+ w = Math.floor (tileSizeAtZoom);
+ h = Math.floor (tileSizeAtZoom);
+
+ return this.createPartialImage (this.fullImage, this.fullImage.width, x0, y0, w, h);
+ } else {
+ return null;
+ }
+ },
+
+ createPartialImage : function (sourceImage, expectedSourceImageSize, x0, y0, w, h) {
+ var canvas = document.createElement ("canvas");
+ if (!canvas["width"]) {
+ return null;
+ }
+ canvas.width = this.partialImageSize;
+ canvas.height = this.partialImageSize;
+ var ctx = canvas.getContext('2d');
+
+ var scale = sourceImage.width / expectedSourceImageSize;
+
+ var sx = Math.floor (x0 * scale);
+ var sy = Math.floor (y0 * scale);
+ var dw = this.partialImageSize;
+ var dh = this.partialImageSize;
+
+ w *= scale;
+ if (sx + w >= sourceImage.width) {
+ var w0 = w;
+ w = sourceImage.width - sx;
+ dw *= w / w0;
+ }
+
+ h *= scale;
+ if (sy + h >= sourceImage.height) {
+ var h0 = h;
+ h = sourceImage.height - sy;
+ dh *= h / h0;
+ }
+
+ try {
+ ctx.drawImage (sourceImage, sx, sy, w, h, -0.1, -0.1, dw + 0.2, dh + 0.2);
+ } catch (e) {
+ // DOM INDEX error on iPad.
+ return null;
+ }
+
+ return canvas;
+ },
+
+ getPartialImageFromDownsampled : function (tileX, tileY, zoomLevel, x0, y0, w, h) {
+ // Give up if the poster image has higher resolution.
+ if (zoomLevel < this.POSTER_ZOOM_LEVEL || zoomLevel < this.parameters.minZoom) {
+ return null;
+ }
+
+ var key = this.getImageKey (tileX, tileY, zoomLevel);
+ var sourceImage = this.cachedImages[key];
+
+ if (sourceImage == null) {
+ this.requestImage (tileX, tileY, zoomLevel);
+ }
+
+ if (sourceImage) {
+ return this.createPartialImage (sourceImage, this.parameters.tileSize, x0, y0, w, h);
+ } else {
+ w /= 2;
+ h /= 2;
+ x0 /= 2;
+ y0 /= 2;
+ if ((tileX % 2) == 1) {
+ x0 += this.parameters.tileSize / 2;
+ }
+ if ((tileY % 2) == 1) {
+ y0 += this.parameters.tileSize / 2;
+ }
+ tileX = Math.floor (tileX / 2);
+ tileY = Math.floor (tileY / 2);
+ --zoomLevel;
+ return this.getPartialImageFromDownsampled (tileX, tileY, zoomLevel, x0, y0, w, h);
+ }
+ },
+
+ getEmptyImage : function () {
+ var tile = document.createElement ("img");
+ if (this.parameters.emptyImage) {
+ tile.src = this.parameters.emptyImage;
+ } else {
+ tile.src = "data:image/gif,GIF89a%01%00%01%00%80%00%00%00%00%00%FF%FF%FF!%F9%04%00%00%00%00%00%2C%00%00%00%00%01%00%01%00%00%02%02D%01%00%3B";
+ }
+ return tile;
+ },
+
+ getImage : function (tileX, tileY, zoomLevel) {
+ if (tileX < 0 || tileY < 0 || tileX >= this.maxTileX || tileY >= this.maxTileY) {
+ return this.getEmptyImage ();
+ }
+
+ var key = this.getImageKey (tileX, tileY, zoomLevel);
+ this.lruMap.access (key);
+
+ if (this.cachedImages[key]) {
+ if (this.usedImages[key]) {
+ var tile = this.parameters.dataLoader.loadImage (this.getImageFilename (tileX, tileY, zoomLevel));
+ tile.isPartial = false;
+ return tile;
+ } else {
+ this.usedImages[key] = true;
+ var img = this.cachedImages[key];
+ return img;
+ }
+ } else {
+ this.requestImage (tileX, tileY, zoomLevel);
+ var img = this.getPartialImage (tileX, tileY, zoomLevel);
+ if (img != null) {
+ img.isPartial = true;
+ this.cachedImages[key] = img;
+ } else {
+ img = this.getEmptyImage ();
+ if (img != null) {
+ img.isPartial = true;
+ }
+ }
+ return img;
+ }
+ },
+
+ requestImage : function (tileX, tileY, zoomLevel) {
+ var key = this.getImageKey (tileX, tileY, zoomLevel);
+ if (!this.requestedImages[key]) {
+ this.imageRequests++;
+ var that = this;
+ this.requestedImages[key] = true;
+ this.parameters.dataLoader.loadImage (this.getImageFilename (tileX, tileY, zoomLevel), function (tile) {
+ delete that.requestedImages[key];
+ that.imageRequests--;
+ tile.isPartial = false;
+ that.cachedImages[key] = tile;
+ that.fireOnLoad ();
+ });
+ }
+ },
+
+ /**
+ * Fires the onload event, if it hasn't been fired for at least 50 ms
+ */
+ fireOnLoad : function () {
+ var now = new Date();
+ if (this.imageRequests == 0 || now.getTime () > (this.lastOnLoadFiredAt + 50)) {
+ this.purgeCache ();
+ this.lastOnLoadFiredAt = now.getTime ();
+ this.onLoaded ();
+ }
+ },
+
+ /**
+ * Removes the least-recently used objects from the cache,
+ * if the size of the cache exceeds the maximum cache size.
+ * A maximum of four objects will be removed per call.
+ *
+ * @private
+ */
+ purgeCache : function () {
+ for (var i = 0; i < 4; ++i) {
+ if (this.lruMap.getSize () > this.maxCacheSize) {
+ var leastUsed = this.lruMap.leastUsed ();
+ this.lruMap.remove (leastUsed);
+ delete this.cachedImages[leastUsed];
+ }
+ }
+ },
+
+ getImageKey : function (tileX, tileY, zoomLevel) {
+ return "I" + tileX + "_" + tileY + "_" + zoomLevel;
+ },
+
+ getImageFilename : function (tileX, tileY, zoomLevel) {
+ var f = this.parameters.fileSystem.getImageFilename (tileX, tileY, zoomLevel);
+ return f;
+ }
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new image parameter object and populates it with default values for
+ * all values not explicitly given.
+ *
+ * @class ImageParameters parameter object.
+ * You need not set any fields that can be read from the image descriptor that
+ * MakeImagePyramid creates. See the {@link bigshot.Image} documentation for
+ * required parameters.
+ *
+ * <p>Usage:
+ *
+ * @example
+ * var bsi = new bigshot.Image (
+ * new bigshot.ImageParameters ({
+ * basePath : "/bigshot.php?file=myshot.bigshot",
+ * fileSystemType : "archive",
+ * container : document.getElementById ("bigshot_div")
+ * }));
+ *
+ * @param values named parameter map, see the fields below for parameter names and types.
+ * @see bigshot.Image
+ */
+bigshot.ImageParameters = function (values) {
+ /**
+ * Size of low resolution preview image along the longest image
+ * dimension. The preview is assumed to have the same aspect
+ * ratio as the full image (specified by width and height).
+ *
+ * @default <i>Optional</i> set by MakeImagePyramid and loaded from descriptor
+ * @type int
+ * @public
+ */
+ this.posterSize = 0;
+
+ /**
+ * Url for the image tile to show while the tile is loading and no
+ * low-resolution preview is available.
+ *
+ * @default <code>null</code>, which results in an all-black image
+ * @type String
+ * @public
+ */
+ this.emptyImage = null;
+
+ /**
+ * Suffix to append to the tile filenames. Typically <code>".jpg"</code> or
+ * <code>".png"</code>.
+ *
+ * @default <i>Optional</i> set by MakeImagePyramid and loaded from descriptor
+ * @type String
+ */
+ this.suffix = null;
+
+ /**
+ * The width of the full image; in pixels.
+ *
+ * @default <i>Optional</i> set by MakeImagePyramid and loaded from descriptor
+ * @type int
+ */
+ this.width = 0;
+
+ /**
+ * The height of the full image; in pixels.
+ *
+ * @default <i>Optional</i> set by MakeImagePyramid and loaded from descriptor
+ * @type int
+ */
+ this.height = 0;
+
+ /**
+ * For {@link bigshot.Image} and {@link bigshot.SimpleImage}, the <code>div</code>
+ * to use as a container for the image.
+ *
+ * @type HTMLDivElement
+ */
+ this.container = null;
+
+ /**
+ * The minimum zoom value. Zoom values are specified as a magnification; where
+ * 2<sup>n</sup> is the magnification and n is the zoom value. So a zoom value of
+ * 2 means a 4x magnification of the full image. -3 means showing an image that
+ * is a eighth (1/8 or 1/2<sup>3</sup>) of the full size.
+ *
+ * @type number
+ * @default <i>Optional</i> set by MakeImagePyramid and loaded from descriptor
+ */
+ this.minZoom = 0.0;
+
+ /**
+ * The maximum zoom value. Zoom values are specified as a magnification; where
+ * 2<sup>n</sup> is the magnification and n is the zoom value. So a zoom value of
+ * 2 means a 4x magnification of the full image. -3 means showing an image that
+ * is a eighth (1/8 or 1/2<sup>3</sup>) of the full size.
+ *
+ * @type number
+ * @default 0.0
+ */
+ this.maxZoom = 0.0;
+
+ /**
+ * Size of one tile in pixels.
+ *
+ * @type int
+ * @default <i>Optional</i> set by MakeImagePyramid and loaded from descriptor
+ */
+ this.tileSize = 0;
+
+ /**
+ * Tile overlap. Not implemented.
+ *
+ * @type int
+ * @default <i>Optional</i> set by MakeImagePyramid and loaded from descriptor
+ */
+ this.overlap = 0;
+
+ /**
+ * Flag indicating that the image should wrap horizontally. The image wraps on tile
+ * boundaries; so in order to get a seamless wrap at zoom level -n; the image width must
+ * be evenly divisible by <code>tileSize * 2^n</code>. Set the minZoom value appropriately.
+ *
+ * @type boolean
+ * @default false
+ */
+ this.wrapX = false;
+
+ /**
+ * Flag indicating that the image should wrap vertically. The image wraps on tile
+ * boundaries; so in order to get a seamless wrap at zoom level -n; the image height must
+ * be evenly divisible by <code>tileSize * 2^n</code>. Set the minZoom value appropriately.
+ *
+ * @type boolean
+ * @default false
+ */
+ this.wrapY = false;
+
+ /**
+ * Base path for the image. This is filesystem dependent; but for the two most common cases
+ * the following should be set
+ *
+ * <ul>
+ * <li><b>archive</b>= The basePath is <code>"&lt;path&gt;/bigshot.php?file=&lt;path-to-bigshot-archive-relative-to-bigshot.php&gt;"</code>;
+ * for example; <code>"/bigshot.php?file=images/bigshot-sample.bigshot"</code>.
+ * <li><b>folder</b>= The basePath is <code>"&lt;path-to-image-folder&gt;"</code>;
+ * for example; <code>"/images/bigshot-sample"</code>.
+ * </ul>
+ *
+ * @type String
+ */
+ this.basePath = null;
+
+ /**
+ * The file system type. Used to create a filesystem instance unless
+ * the fileSystem field is set. Possible values are <code>"archive"</code>,
+ * <code>"folder"</code> or <code>"dzi"</code>.
+ *
+ * @type String
+ * @default "folder"
+ */
+ this.fileSystemType = "folder";
+
+ /**
+ * A reference to a filesystem implementation. If set; it overrides the
+ * fileSystemType field.
+ *
+ * @default set depending on value of bigshot.ImageParameters.fileSystemType
+ * @type bigshot.FileSystem
+ */
+ this.fileSystem = null;
+
+ /**
+ * Object used to load data files.
+ *
+ * @default bigshot.DefaultDataLoader
+ * @type bigshot.DataLoader
+ */
+ this.dataLoader = new bigshot.DefaultDataLoader ();
+
+ /**
+ * Enable the touch-friendly ui. The touch-friendly UI splits the viewport into
+ * three click-sensitive regions:
+ * <p style="text-align:center"><img src="../images/touch-ui.png"/></p>
+ *
+ * <p>Clicking (or tapping with a finger) on the outer region causes the viewport to zoom out.
+ * Clicking anywhere within the middle, "pan", region centers the image on the spot clicked.
+ * Finally, clicking in the center hotspot will center the image on the spot clicked and zoom
+ * in half a zoom level.
+ *
+ * <p>As before, you can drag to pan anywhere.
+ *
+ * <p>If you have navigation tools for mouse users that hover over the image container, it
+ * is recommended that any click events on them are kept from bubbling, otherwise the click
+ * will propagate to the touch ui. One way is to use the
+ * {@link bigshot.Browser#stopMouseEventBubbling} method:
+ *
+ * @example
+ * var browser = new bigshot.Browser ();
+ * browser.stopMouseEventBubbling (document.getElementById ("myBigshotControlDiv"));
+ *
+ * @see bigshot.ImageBase#showTouchUI
+ *
+ * @type boolean
+ * @default true
+ * @deprecated Bigshot supports all common touch-gestures.
+ */
+ this.touchUI = false;
+
+ /**
+ * Lets you "fling" the image.
+ *
+ * @type boolean
+ * @default true
+ */
+ this.fling = true;
+
+ /**
+ * The maximum amount that a tile will be stretched until we try to show
+ * the next more detailed level.
+ *
+ * @type float
+ * @default 1.0
+ */
+ this.maxTextureMagnification = 1.0;
+
+ if (values) {
+ for (var k in values) {
+ this[k] = values[k];
+ }
+ }
+
+ this.merge = function (values, overwrite) {
+ for (var k in values) {
+ if (overwrite || !this[k]) {
+ this[k] = values[k];
+ }
+ }
+ }
+ return this;
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * Sets up base image functionality.
+ *
+ * @param {bigshot.ImageParameters} parameters the image parameters
+ * @class Base class for image viewers.
+ * @extends bigshot.EventDispatcher
+ */
+bigshot.ImageBase = function (parameters) {
+ // Base class init
+ bigshot.EventDispatcher.call (this);
+
+ this.parameters = parameters;
+ this.flying = 0;
+ this.container = parameters.container;
+ this.x = parameters.width / 2.0;
+ this.y = parameters.height / 2.0;
+ this.zoom = 0.0;
+ this.width = parameters.width;
+ this.height = parameters.height;
+ this.minZoom = parameters.minZoom;
+ this.maxZoom = parameters.maxZoom;
+ this.tileSize = parameters.tileSize;
+ this.overlap = 0;
+ this.imageTileCache = null;
+
+ this.dragStart = null;
+ this.dragged = false;
+
+ this.layers = new Array ();
+
+ this.fullScreenHandler = null;
+ this.currentGesture = null;
+
+ var that = this;
+ this.onresizeHandler = function (e) {
+ that.onresize ();
+ }
+
+ /**
+ * Helper function to consume events.
+ * @private
+ */
+ var consumeEvent = function (event) {
+ if (event.preventDefault) {
+ event.preventDefault ();
+ }
+ return false;
+ };
+
+ /**
+ * Helper function to translate touch events to mouse-like events.
+ * @private
+ */
+ var translateEvent = function (event) {
+ if (event.clientX) {
+ return event;
+ } else {
+ return {
+ clientX : event.changedTouches[0].clientX,
+ clientY : event.changedTouches[0].clientY,
+ changedTouches : event.changedTouches
+ };
+ };
+ };
+
+ this.setupLayers ();
+
+ this.resize ();
+
+ this.allListeners = {
+ "DOMMouseScroll" : function (e) {
+ that.mouseWheel (e);
+ return consumeEvent (e);
+ },
+ "mousewheel" : function (e) {
+ that.mouseWheel (e);
+ return consumeEvent (e);
+ },
+ "dblclick" : function (e) {
+ that.mouseDoubleClick (e);
+ return consumeEvent (e);
+ },
+ "mousedown" : function (e) {
+ that.dragMouseDown (e);
+ return consumeEvent (e);
+ },
+ "gesturestart" : function (e) {
+ that.gestureStart (e);
+ return consumeEvent (e);
+ },
+ "gesturechange" : function (e) {
+ that.gestureChange (e);
+ return consumeEvent (e);
+ },
+ "gestureend" : function (e) {
+ that.gestureEnd (e);
+ return consumeEvent (e);
+ },
+ "touchstart" : function (e) {
+ that.dragMouseDown (translateEvent (e));
+ return consumeEvent (e);
+ },
+ "mouseup" : function (e) {
+ that.dragMouseUp (e);
+ return consumeEvent (e);
+ },
+ "touchend" : function (e) {
+ that.dragMouseUp (translateEvent (e));
+ return consumeEvent (e);
+ },
+ "mousemove" : function (e) {
+ that.dragMouseMove (e);
+ return consumeEvent (e);
+ },
+ "mouseout" : function (e) {
+ //that.dragMouseUp (e);
+ return consumeEvent (e);
+ },
+ "touchmove" : function (e) {
+ that.dragMouseMove (translateEvent (e));
+ return consumeEvent (e);
+ }
+ };
+
+ this.addEventListeners ();
+ this.browser.registerListener (window, 'resize', that.onresizeHandler, false);
+ this.zoomToFit ();
+}
+
+bigshot.ImageBase.prototype = {
+ /**
+ * Browser helper and compatibility functions.
+ *
+ * @private
+ * @type bigshot.Browser
+ */
+ browser : new bigshot.Browser (),
+
+ /**
+ * Adds all event listeners to the container object.
+ * @private
+ */
+ addEventListeners : function () {
+ for (var k in this.allListeners) {
+ this.browser.registerListener (this.container, k, this.allListeners[k], false);
+ }
+ },
+
+ /**
+ * Removes all event listeners from the container object.
+ * @private
+ */
+ removeEventListeners : function () {
+ for (var k in this.allListeners) {
+ this.browser.unregisterListener (this.container, k, this.allListeners[k], false);
+ }
+ },
+
+ /**
+ * Sets up the initial layers of the image. Override in subclass.
+ */
+ setupLayers : function () {
+ },
+
+ /**
+ * Returns the base 2 logarithm of the maximum texture stretching, allowing for device pixel scaling.
+ * @type number
+ * @private
+ */
+ getTextureStretch : function () {
+ var ts = Math.log (this.parameters.maxTextureMagnification / this.browser.getDevicePixelScale ()) / Math.LN2;
+ return ts;
+ },
+
+ /**
+ * Constrains the x and y coordinates to allowed values
+ * @param {number} x the initial x coordinate
+ * @param {number} y the initial y coordinate
+ * @return {number} .x the constrained x coordinate
+ * @return {number} .y the constrained y coordinate
+ */
+ clampXY : function (x, y) {
+ var viewportWidth = this.container.clientWidth;
+ var viewportHeight = this.container.clientHeight;
+
+ var realZoomFactor = Math.pow (2, this.zoom);
+ /*
+ Constrain X and Y
+ */
+ var viewportWidthInImagePixels = viewportWidth / realZoomFactor;
+ var viewportHeightInImagePixels = viewportHeight / realZoomFactor;
+
+ var constrain = function (viewportSizeInImagePixels, imageSizeInImagePixels, p) {
+ var min = viewportSizeInImagePixels / 2;
+ min = Math.min (imageSizeInImagePixels / 2, min);
+ if (p < min) {
+ p = min;
+ }
+
+ var max = imageSizeInImagePixels - viewportSizeInImagePixels / 2;
+ max = Math.max (imageSizeInImagePixels / 2, max);
+ if (p > max) {
+ p = max;
+ }
+ return p;
+ };
+
+ var o = {};
+ if (x != null) {
+ o.x = constrain (viewportWidthInImagePixels, this.width, x);
+ }
+
+ if (y != null) {
+ o.y = constrain (viewportHeightInImagePixels, this.height, y);
+ }
+
+ return o;
+ },
+
+ /**
+ * Lays out all layers according to the current
+ * x, y and zoom values.
+ *
+ * @public
+ */
+ layout : function () {
+ var viewportWidth = this.container.clientWidth;
+ var viewportHeight = this.container.clientHeight;
+
+ var zoomWithStretch = Math.min (this.maxZoom, Math.max (this.zoom - this.getTextureStretch (), this.minZoom));
+
+ var zoomLevel = Math.min (0, Math.ceil (zoomWithStretch));
+ var zoomFactor = Math.pow (2, zoomLevel);
+
+ var clamped = this.clampXY (this.x, this.y);
+
+ if (!this.parameters.wrapY) {
+ this.y = clamped.y;
+ }
+
+ if (!this.parameters.wrapX) {
+ this.x = clamped.x;
+ }
+
+ var tileWidthInRealPixels = this.tileSize / zoomFactor;
+
+ var fractionalZoomFactor = Math.pow (2, this.zoom - zoomLevel);
+ var tileDisplayWidth = this.tileSize * fractionalZoomFactor;
+
+ var widthInTiles = this.width / tileWidthInRealPixels;
+ var heightInTiles = this.height / tileWidthInRealPixels;
+ var centerInTilesX = this.x / tileWidthInRealPixels;
+ var centerInTilesY = this.y / tileWidthInRealPixels;
+
+ var topLeftInTilesX = centerInTilesX - (viewportWidth / 2) / tileDisplayWidth;
+ var topLeftInTilesY = centerInTilesY - (viewportHeight / 2) / tileDisplayWidth;
+
+ var topLeftTileX = Math.floor (topLeftInTilesX);
+ var topLeftTileY = Math.floor (topLeftInTilesY);
+ var topLeftTileXoffset = Math.round ((topLeftInTilesX - topLeftTileX) * tileDisplayWidth);
+ var topLeftTileYoffset = Math.round ((topLeftInTilesY - topLeftTileY) * tileDisplayWidth);
+
+ for (var i = 0; i < this.layers.length; ++i) {
+ this.layers[i].layout (
+ zoomWithStretch,
+ -topLeftTileXoffset - tileDisplayWidth, -topLeftTileYoffset - tileDisplayWidth,
+ topLeftTileX - 1, topLeftTileY - 1,
+ Math.ceil (tileDisplayWidth), Math.ceil (tileDisplayWidth),
+ 1.0);
+ }
+ },
+
+ /**
+ * Resizes the layers of this image.
+ *
+ * @public
+ */
+ resize : function () {
+ var tilesW = Math.ceil (2 * this.container.clientWidth / this.tileSize) + 2;
+ var tilesH = Math.ceil (2 * this.container.clientHeight / this.tileSize) + 2;
+ for (var i = 0; i < this.layers.length; ++i) {
+ this.layers[i].resize (tilesW, tilesH);
+ }
+ },
+
+ /**
+ * Creates a HTML div container for a layer. This method
+ * is called by the layer's constructor to obtain a
+ * container.
+ *
+ * @public
+ * @type HTMLDivElement
+ */
+ createLayerContainer : function () {
+ var layerContainer = document.createElement ("div");
+ layerContainer.style.position = "absolute";
+ layerContainer.style.overflow = "hidden";
+ return layerContainer;
+ },
+
+ /**
+ * Returns the div element used as viewport.
+ *
+ * @public
+ * @type HTMLDivElement
+ */
+ getContainer : function () {
+ return this.container;
+ },
+
+ /**
+ * Adds a new layer to the image.
+ *
+ * @public
+ * @see bigshot.HotspotLayer for usage example
+ * @param {bigshot.Layer} layer the layer to add.
+ */
+ addLayer : function (layer) {
+ this.container.appendChild (layer.getContainer ());
+ this.layers.push (layer);
+ },
+
+ /**
+ * Clamps the zoom value to be between minZoom and maxZoom.
+ *
+ * @param {number} zoom the zoom value
+ * @type number
+ */
+ clampZoom : function (zoom) {
+ return Math.min (this.maxZoom, Math.max (zoom, this.minZoom));
+ },
+
+ /**
+ * Sets the current zoom value.
+ *
+ * @private
+ * @param {number} zoom the zoom value.
+ * @param {boolean} [layout] trigger a viewport update after setting. Defaults to <code>false</code>.
+ */
+ setZoom : function (zoom, updateViewport) {
+ this.zoom = this.clampZoom (zoom);
+ var zoomLevel = Math.ceil (this.zoom - this.getTextureStretch ());
+ var zoomFactor = Math.pow (2, zoomLevel);
+ var maxTileX = Math.ceil (zoomFactor * this.width / this.tileSize);
+ var maxTileY = Math.ceil (zoomFactor * this.height / this.tileSize);
+ for (var i = 0; i < this.layers.length; ++i) {
+ this.layers[i].setMaxTiles (maxTileX, maxTileY);
+ }
+ if (updateViewport) {
+ this.layout ();
+ }
+ },
+
+ /**
+ * Sets the maximum zoom value. The maximum magnification (of the full-size image)
+ * is 2<sup>maxZoom</sup>. Set to 0.0 to avoid pixelation.
+ *
+ * @public
+ * @param {number} maxZoom the maximum zoom value
+ */
+ setMaxZoom : function (maxZoom) {
+ this.maxZoom = maxZoom;
+ },
+
+ /**
+ * Gets the maximum zoom value. The maximum magnification (of the full-size image)
+ * is 2<sup>maxZoom</sup>.
+ *
+ * @public
+ * @type number
+ */
+ getMaxZoom : function () {
+ return this.maxZoom;
+ },
+
+ /**
+ * Sets the minimum zoom value. The minimum magnification (of the full-size image)
+ * is 2<sup>minZoom</sup>, so a minZoom of <code>-3</code> means that the smallest
+ * image shown will be one-eighth of the full-size image.
+ *
+ * @public
+ * @param {number} minZoom the minimum zoom value for this image
+ */
+ setMinZoom : function (minZoom) {
+ this.minZoom = minZoom;
+ },
+
+ /**
+ * Gets the minimum zoom value. The minimum magnification (of the full-size image)
+ * is 2<sup>minZoom</sup>, so a minZoom of <code>-3</code> means that the smallest
+ * image shown will be one-eighth of the full-size image.
+ *
+ * @public
+ * @type number
+ */
+ getMinZoom : function () {
+ return this.minZoom;
+ },
+
+ /**
+ * Adjusts a coordinate so that the center of zoom
+ * remains constant during zooming operations. The
+ * method is intended to be called twice, once for x
+ * and once for y. The <code>current</code> and
+ * <code>centerOfZoom</code> values will be the current
+ * and the center for the x and y, respectively.
+ *
+ * @example
+ * this.x = this.adjustCoordinateForZoom (this.x, zoomCenterX, oldZoom, newZoom);
+ * this.y = this.adjustCoordinateForZoom (this.y, zoomCenterY, oldZoom, newZoom);
+ *
+ * @param {number} current the current value of the coordinate
+ * @param {number} centerOfZoom the center of zoom along the coordinate axis
+ * @param {number} oldZoom the old zoom value
+ * @param {number} oldZoom the new zoom value
+ * @type number
+ * @returns the new value for the coordinate
+ */
+ adjustCoordinateForZoom : function (current, centerOfZoom, oldZoom, newZoom) {
+ var zoomRatio = Math.pow (2, oldZoom) / Math.pow (2, newZoom);
+ return centerOfZoom + (current - centerOfZoom) * zoomRatio;
+ },
+
+ /**
+ * Begins a potential drag event.
+ *
+ * @private
+ */
+ gestureStart : function (event) {
+ this.currentGesture = {
+ startZoom : this.zoom,
+ scale : event.scale
+ };
+ },
+
+ /**
+ * Ends a gesture.
+ *
+ * @param {Event} event the <code>gestureend</code> event
+ * @private
+ */
+ gestureEnd : function (event) {
+ this.currentGesture = null;
+ if (this.dragStart) {
+ this.dragStart.hadGesture = true;
+ }
+ },
+
+ /**
+ * Adjusts the zoom level based on the scale property of the
+ * gesture.
+ *
+ * @private
+ */
+ gestureChange : function (event) {
+ if (this.currentGesture) {
+ if (this.dragStart) {
+ this.dragStart.hadGesture = true;
+ }
+
+ var newZoom = this.clampZoom (this.currentGesture.startZoom + Math.log (event.scale) / Math.log (2));
+ var oldZoom = this.getZoom ();
+ if (this.currentGesture.clientX !== undefined && this.currentGesture.clientY !== undefined) {
+ var centerOfZoom = this.clientToImage (this.currentGesture.clientX, this.currentGesture.clientY);
+
+ var nx = this.adjustCoordinateForZoom (this.x, centerOfZoom.x, oldZoom, newZoom);
+ var ny = this.adjustCoordinateForZoom (this.y, centerOfZoom.y, oldZoom, newZoom);
+
+ this.moveTo (nx, ny, newZoom);
+ } else {
+ this.setZoom (newZoom);
+ this.layout ();
+ }
+ }
+ },
+
+ /**
+ * Begins a potential drag event.
+ *
+ * @private
+ */
+ dragMouseDown : function (event) {
+ this.dragStart = {
+ x : event.clientX,
+ y : event.clientY
+ };
+ this.dragLast = {
+ clientX : event.clientX,
+ clientY : event.clientY,
+ dx : 0,
+ dy : 0,
+ dt : 1000000,
+ time : new Date ().getTime ()
+ };
+ this.dragged = false;
+ },
+
+ /**
+ * Handles a mouse drag event by panning the image.
+ * Also sets the dragged flag to indicate that the
+ * following <code>click</code> event should be ignored.
+ * @private
+ */
+ dragMouseMove : function (event) {
+ if (this.currentGesture != null && event.changedTouches != null && event.changedTouches.length > 0) {
+ var cx = 0;
+ var cy = 0;
+ for (var i = 0; i < event.changedTouches.length; ++i) {
+ cx += event.changedTouches[i].clientX;
+ cy += event.changedTouches[i].clientY;
+ }
+ this.currentGesture.clientX = cx / event.changedTouches.length;
+ this.currentGesture.clientY = cy / event.changedTouches.length;
+ }
+
+ if (this.currentGesture == null && this.dragStart != null) {
+ var delta = {
+ x : event.clientX - this.dragStart.x,
+ y : event.clientY - this.dragStart.y
+ };
+ if (delta.x != 0 || delta.y != 0) {
+ this.dragged = true;
+ }
+ var zoomFactor = Math.pow (2, this.zoom);
+ var realX = delta.x / zoomFactor;
+ var realY = delta.y / zoomFactor;
+
+ this.dragStart = {
+ x : event.clientX,
+ y : event.clientY
+ };
+
+ var dt = new Date ().getTime () - this.dragLast.time;
+ if (dt > 20) {
+ this.dragLast = {
+ dx : this.dragLast.clientX - event.clientX,
+ dy : this.dragLast.clientY - event.clientY,
+ dt : dt,
+ clientX : event.clientX,
+ clientY : event.clientY,
+ time : new Date ().getTime ()
+ };
+ }
+
+ this.moveTo (this.x - realX, this.y - realY);
+ }
+ },
+
+ /**
+ * Ends a drag event by freeing the associated structures.
+ * @private
+ */
+ dragMouseUp : function (event) {
+ if (this.currentGesture == null && !this.dragStart.hadGesture && this.dragStart != null) {
+ this.dragStart = null;
+ if (!this.dragged) {
+ this.mouseClick (event);
+ } else {
+ var scale = Math.pow (2, this.zoom);
+ var dx = this.dragLast.dx / scale;
+ var dy = this.dragLast.dy / scale;
+ var ds = Math.sqrt (dx * dx + dy * dy);
+ var dt = this.dragLast.dt;
+ var dtb = new Date ().getTime () - this.dragLast.time;
+ this.dragLast = null;
+
+ var v = dt > 0 ? (ds / dt) : 0;
+ if (v > 0.05 && dtb < 250 && dt > 20 && this.parameters.fling) {
+ var t0 = new Date ().getTime ();
+
+ dx /= dt;
+ dy /= dt;
+
+ this.flyTo (this.x + dx * 250, this.y + dy * 250, this.zoom);
+ }
+ }
+ }
+ },
+
+ /**
+ * Mouse double-click handler. Pans to the clicked point and
+ * zooms in half a zoom level (approx 40%).
+ * @private
+ */
+ mouseDoubleClick : function (event) {
+ var eventData = this.createImageEventData ({
+ type : "dblclick",
+ clientX : event.clientX,
+ clientY : event.clientY
+ });
+ this.fireEvent ("dblclick", eventData);
+ if (!eventData.defaultPrevented) {
+ this.flyTo (eventData.imageX, eventData.imageY, this.zoom + 0.5);
+ }
+ },
+
+ /**
+ * Returns the current zoom level.
+ *
+ * @public
+ * @type number
+ */
+ getZoom : function () {
+ return this.zoom;
+ },
+
+ /**
+ * Stops any current flyTo operation and sets the current position.
+ *
+ * @param [x] the new x-coordinate
+ * @param [y] the new y-coordinate
+ * @param [zoom] the new zoom level
+ * @param [updateViewport=true] updates the viewport
+ * @public
+ */
+ moveTo : function (x, y, zoom, updateViewport) {
+ this.stopFlying ();
+
+ if (x != null || y != null) {
+ this.setPosition (x, y, false);
+ }
+ if (zoom != null) {
+ this.setZoom (zoom, false);
+ }
+ if (updateViewport == undefined || updateViewport == true) {
+ this.layout ();
+ }
+ },
+
+ /**
+ * Sets the current position.
+ *
+ * @param [x] the new x-coordinate
+ * @param [y] the new y-coordinate
+ * @param [updateViewport=true] if the viewport should be updated
+ * @private
+ */
+ setPosition : function (x, y, updateViewport) {
+ var clamped = this.clampXY (x, y);
+
+ if (x != null) {
+ if (this.parameters.wrapX) {
+ if (x < 0 || x >= this.width) {
+ x = (x + this.width) % this.width;
+ }
+ } else {
+ x = clamped.x;
+ }
+ this.x = Math.max (0, Math.min (this.width, x));
+ }
+
+ if (y != null) {
+ if (this.parameters.wrapY) {
+ if (y < 0 || y >= this.height) {
+ y = (y + this.height) % this.height;
+ }
+ } else {
+ y = clamped.y;
+ }
+ this.y = Math.max (0, Math.min (this.height, y));
+ }
+
+ if (updateViewport != false) {
+ this.layout ();
+ }
+ },
+
+ /**
+ * Helper function for calculating zoom levels.
+ *
+ * @public
+ * @returns the zoom level at which the given number of full-image pixels
+ * occupy the given number of screen pixels.
+ * @param {number} imageDimension the image dimension in full-image pixels
+ * @param {number} containerDimension the container dimension in screen pixels
+ * @type number
+ */
+ fitZoom : function (imageDimension, containerDimension) {
+ var scale = containerDimension / imageDimension;
+ return Math.log (scale) / Math.LN2;
+ },
+
+ /**
+ * Returns the maximum zoom level at which the full image
+ * is visible in the viewport.
+ * @public
+ * @type number
+ */
+ getZoomToFitValue : function () {
+ return Math.min (
+ this.fitZoom (this.parameters.width, this.container.clientWidth),
+ this.fitZoom (this.parameters.height, this.container.clientHeight));
+ },
+
+ /**
+ * Returns the zoom level at which the image fills the whole
+ * viewport.
+ * @public
+ * @type number
+ */
+ getZoomToFillValue : function () {
+ return Math.max (
+ this.fitZoom (this.parameters.width, this.container.clientWidth),
+ this.fitZoom (this.parameters.height, this.container.clientHeight));
+ },
+
+ /**
+ * Adjust the zoom level to fit the image in the viewport.
+ * @public
+ */
+ zoomToFit : function () {
+ this.moveTo (null, null, this.getZoomToFitValue ());
+ },
+
+ /**
+ * Adjust the zoom level to fit the image in the viewport.
+ * @public
+ */
+ zoomToFill : function () {
+ this.moveTo (null, null, this.getZoomToFillValue ());
+ },
+
+ /**
+ * Adjust the zoom level to fit the
+ * image height in the viewport.
+ * @public
+ */
+ zoomToFitHeight : function () {
+ this.moveTo (null, null, this.fitZoom (this.parameters.height, this.container.clientHeight));
+ },
+
+ /**
+ * Adjust the zoom level to fit the
+ * image width in the viewport.
+ * @public
+ */
+ zoomToFitWidth : function () {
+ this.moveTo (null, null, this.fitZoom (this.parameters.width, this.container.clientWidth));
+ },
+
+ /**
+ * Smoothly adjust the zoom level to fit the
+ * image height in the viewport.
+ * @public
+ */
+ flyZoomToFitHeight : function () {
+ this.flyTo (null, this.parameters.height / 2, this.fitZoom (this.parameters.height, this.container.clientHeight));
+ },
+
+ /**
+ * Smoothly adjust the zoom level to fit the
+ * image width in the viewport.
+ * @public
+ */
+ flyZoomToFitWidth : function () {
+ this.flyTo (this.parameters.width / 2, null, this.fitZoom (this.parameters.width, this.container.clientWidth));
+ },
+
+ /**
+ * Smoothly adjust the zoom level to fit the
+ * full image in the viewport.
+ * @public
+ */
+ flyZoomToFit : function () {
+ this.flyTo (this.parameters.width / 2, this.parameters.height / 2, this.getZoomToFitValue ());
+ },
+
+ /**
+ * Converts client-relative screen coordinates to image coordinates.
+ *
+ * @param {number} clientX the client x-coordinate
+ * @param {number} clientY the client y-coordinate
+ *
+ * @returns {number} .x the image x-coordinate
+ * @returns {number} .y the image y-coordinate
+ * @type Object
+ */
+ clientToImage : function (clientX, clientY) {
+ var zoomFactor = Math.pow (2, this.zoom);
+ return {
+ x : (clientX - this.container.clientWidth / 2) / zoomFactor + this.x,
+ y : (clientY - this.container.clientHeight / 2) / zoomFactor + this.y
+ };
+ },
+
+ /**
+ * Handles mouse wheel actions.
+ * @private
+ */
+ mouseWheelHandler : function (delta, event) {
+ var zoomDelta = false;
+ if (delta > 0) {
+ zoomDelta = 0.5;
+ } else if (delta < 0) {
+ zoomDelta = -0.5;
+ }
+
+ if (zoomDelta) {
+ var centerOfZoom = this.clientToImage (event.clientX, event.clientY);
+ var newZoom = Math.min (this.maxZoom, Math.max (this.getZoom () + zoomDelta, this.minZoom));
+
+ var nx = this.adjustCoordinateForZoom (this.x, centerOfZoom.x, this.getZoom (), newZoom);
+ var ny = this.adjustCoordinateForZoom (this.y, centerOfZoom.y, this.getZoom (), newZoom);
+
+ this.flyTo (nx, ny, newZoom, true);
+ }
+ },
+
+ /**
+ * Translates mouse wheel events.
+ * @private
+ */
+ mouseWheel : function (event){
+ var delta = 0;
+ if (!event) /* For IE. */
+ event = window.event;
+ if (event.wheelDelta) { /* IE/Opera. */
+ delta = event.wheelDelta / 120;
+ /*
+ * In Opera 9, delta differs in sign as compared to IE.
+ */
+ if (window.opera)
+ delta = -delta;
+ } else if (event.detail) { /* Mozilla case. */
+ /*
+ * In Mozilla, sign of delta is different than in IE.
+ * Also, delta is multiple of 3.
+ */
+ delta = -event.detail;
+ }
+
+ /*
+ * If delta is nonzero, handle it.
+ * Basically, delta is now positive if wheel was scrolled up,
+ * and negative, if wheel was scrolled down.
+ */
+ if (delta) {
+ this.mouseWheelHandler (delta, event);
+ }
+
+ /*
+ * Prevent default actions caused by mouse wheel.
+ * That might be ugly, but we handle scrolls somehow
+ * anyway, so don't bother here..
+ */
+ if (event.preventDefault) {
+ event.preventDefault ();
+ }
+ event.returnValue = false;
+ },
+
+ /**
+ * Triggers a right-sizing of all layers.
+ * Called on window resize via the {@link bigshot.ImageBase#onresizeHandler} stub.
+ * @public
+ */
+ onresize : function () {
+ this.resize ();
+ this.layout ();
+ },
+
+ /**
+ * Returns the current x-coordinate, which is the full-image x coordinate
+ * in the center of the viewport.
+ * @public
+ * @type number
+ */
+ getX : function () {
+ return this.x;
+ },
+
+ /**
+ * Returns the current y-coordinate, which is the full-image x coordinate
+ * in the center of the viewport.
+ * @public
+ * @type number
+ */
+ getY : function () {
+ return this.y;
+ },
+
+ /**
+ * Interrupts the current {@link #flyTo}, if one is active.
+ * @public
+ */
+ stopFlying : function () {
+ this.flying++;
+ },
+
+ /**
+ * Smoothly flies to the specified position.
+ *
+ * @public
+ * @param {number} [x=current x] the new x-coordinate
+ * @param {number} [y=current y] the new y-coordinate
+ * @param {number} [zoom=current zoom] the new zoom level
+ * @param {boolean} [uniformApproach=false] if true, uses the same interpolation curve for x, y and zoom.
+ */
+ flyTo : function (x, y, zoom, uniformApproach) {
+ var that = this;
+
+ x = x != null ? x : this.x;
+ y = y != null ? y : this.y;
+ zoom = zoom != null ? zoom : this.zoom;
+ uniformApproach = uniformApproach != null ? uniformApproach : false;
+
+ var startX = this.x;
+ var startY = this.y;
+ var startZoom = this.zoom;
+
+ var clamped = this.clampXY (x, y);
+ var targetX = this.parameters.wrapX ? x : clamped.x;
+ var targetY = this.parameters.wrapY ? y : clamped.y;
+ var targetZoom = Math.min (this.maxZoom, Math.max (zoom, this.minZoom));
+
+ this.flying++;
+ var flyingAtStart = this.flying;
+
+ var t0 = new Date ().getTime ();
+
+ var approach = function (start, target, dt, step, linear) {
+ var delta = (target - start);
+
+ var diff = - delta * Math.pow (2, -dt * step);
+
+ var lin = dt * linear;
+ if (delta < 0) {
+ diff = Math.max (0, diff - lin);
+ } else {
+ diff = Math.min (0, diff + lin);
+ }
+
+ return target + diff;
+ };
+
+
+ var iter = function () {
+ if (that.flying == flyingAtStart) {
+ var dt = (new Date ().getTime () - t0) / 1000;
+
+ var nx = approach (startX, targetX, dt, uniformApproach ? 10 : 4, uniformApproach ? 0.2 : 1.0);
+ var ny = approach (startY, targetY, dt, uniformApproach ? 10 : 4, uniformApproach ? 0.2 : 1.0);
+ var nz = approach (startZoom, targetZoom, dt, 10, 0.2);
+ var done = true;
+
+ var zoomFactor = Math.min (Math.pow (2, that.getZoom ()), 1);
+
+ if (Math.abs (nx - targetX) < (0.5 * zoomFactor)) {
+ nx = targetX;
+ } else {
+ done = false;
+ }
+ if (Math.abs (ny - targetY) < (0.5 * zoomFactor)) {
+ ny = targetY;
+ } else {
+ done = false;
+ }
+ if (Math.abs (nz - targetZoom) < 0.02) {
+ nz = targetZoom;
+ } else {
+ done = false;
+ }
+ that.setPosition (nx, ny, false);
+ that.setZoom (nz, false);
+ that.layout ();
+ if (!done) {
+ that.browser.requestAnimationFrame (iter, that.container);
+ }
+ };
+ }
+ this.browser.requestAnimationFrame (iter, this.container);
+ },
+
+ /**
+ * Returns the maximum zoom level at which a rectangle with the given dimensions
+ * fit into the viewport.
+ *
+ * @public
+ * @param {number} w the width of the rectangle, given in full-image pixels
+ * @param {number} h the height of the rectangle, given in full-image pixels
+ * @type number
+ * @returns the zoom level that will precisely fit the given rectangle
+ */
+ rectVisibleAtZoomLevel : function (w, h) {
+ return Math.min (
+ this.fitZoom (w, this.container.clientWidth),
+ this.fitZoom (h, this.container.clientHeight));
+ },
+
+ /**
+ * Returns the base size in screen pixels of the two zoom touch areas.
+ * The zoom out border will be getTouchAreaBaseSize() pixels wide,
+ * and the center zoom in hotspot will be 2*getTouchAreaBaseSize() pixels wide
+ * and tall.
+ * @deprecated
+ * @type number
+ * @public
+ */
+ getTouchAreaBaseSize : function () {
+ var averageSize = ((this.container.clientWidth + this.container.clientHeight) / 2) * 0.2;
+ return Math.min (averageSize, Math.min (this.container.clientWidth, this.container.clientHeight) / 6);
+ },
+
+ /**
+ * Creates a new {@link bigshot.ImageEvent} using the supplied data object,
+ * transforming the client x- and y-coordinates to local and image coordinates.
+ * The returned event object will have the {@link bigshot.ImageEvent#localX},
+ * {@link bigshot.ImageEvent#localY}, {@link bigshot.ImageEvent#imageX},
+ * {@link bigshot.ImageEvent#imageY}, {@link bigshot.Event#target} and
+ * {@link bigshot.Event#currentTarget} fields set.
+ *
+ * @param {Object} data data object with initial values for the event object
+ * @param {number} data.clientX the clientX of the event
+ * @param {number} data.clientY the clientY of the event
+ * @returns the new event object
+ * @type bigshot.ImageEvent
+ */
+ createImageEventData : function (data) {
+ var elementPos = this.browser.getElementPosition (this.container);
+ data.localX = data.clientX - elementPos.x;
+ data.localY = data.clientY - elementPos.y;
+
+ var scale = Math.pow (2, this.zoom);
+
+ data.imageX = (data.localX - this.container.clientWidth / 2) / scale + this.x;
+ data.imageY = (data.localY - this.container.clientHeight / 2) / scale + this.y;
+
+ data.target = this;
+ data.currentTarget = this;
+
+ return new bigshot.ImageEvent (data);
+ },
+
+ /**
+ * Handles mouse click events. If the touch UI is active,
+ * we'll pan and/or zoom, as appropriate. If not, we just ignore
+ * the event.
+ * @private
+ */
+ mouseClick : function (event) {
+ var eventData = this.createImageEventData ({
+ type : "click",
+ clientX : event.clientX,
+ clientY : event.clientY
+ });
+ this.fireEvent ("click", eventData);
+ /*
+ if (!eventData.defaultPrevented) {
+ if (!this.parameters.touchUI) {
+ return;
+ }
+ if (this.dragged) {
+ return;
+ }
+
+ var zoomOutBorderSize = this.getTouchAreaBaseSize ();
+ var zoomInHotspotSize = this.getTouchAreaBaseSize ();
+
+ if (Math.abs (clickPos.x) > (this.container.clientWidth / 2 - zoomOutBorderSize) || Math.abs (clickPos.y) > (this.container.clientHeight / 2 - zoomOutBorderSize)) {
+ this.flyTo (this.x, this.y, this.zoom - 0.5);
+ } else {
+ var newZoom = this.zoom;
+ if (Math.abs (clickPos.x) < zoomInHotspotSize && Math.abs (clickPos.y) < zoomInHotspotSize) {
+ newZoom += 0.5;
+ }
+ var scale = Math.pow (2, this.zoom);
+ clickPos.x /= scale;
+ clickPos.y /= scale;
+ this.flyTo (this.x + clickPos.x, this.y + clickPos.y, newZoom);
+ }
+ }
+ */
+ },
+
+ /**
+ * Briefly shows the touch ui zones. See the {@link bigshot.ImageParameters#touchUI}
+ * documentation for an explanation of the touch ui.
+ *
+ * @public
+ * @deprecated All common touch gestures are supported by default.
+ * @see bigshot.ImageParameters#touchUI
+ * @param {int} [delay] milliseconds before fading out
+ * @param {int} [fadeOut] milliseconds to fade out the zone overlays in
+ */
+ showTouchUI : function (delay, fadeOut) {
+ if (!delay) {
+ delay = 2500;
+ }
+ if (!fadeOut) {
+ fadeOut = 1000;
+ }
+
+ var zoomOutBorderSize = this.getTouchAreaBaseSize ();
+ var zoomInHotspotSize = this.getTouchAreaBaseSize ();
+ var centerX = this.container.clientWidth / 2;
+ var centerY = this.container.clientHeight / 2;
+
+ var frameDiv = document.createElement ("div");
+ frameDiv.style.position = "absolute";
+ frameDiv.style.zIndex = "9999";
+ frameDiv.style.opacity = 0.9;
+ frameDiv.style.width = this.container.clientWidth + "px";
+ frameDiv.style.height = this.container.clientHeight + "px";
+
+ var centerSpotAnchor = document.createElement ("div");
+ centerSpotAnchor.style.position = "absolute";
+
+ var centerSpot = document.createElement ("div");
+ centerSpot.style.position = "relative";
+ centerSpot.style.background = "black";
+ centerSpot.style.textAlign = "center";
+ centerSpot.style.top = (centerY - zoomInHotspotSize) + "px";
+ centerSpot.style.left = (centerX - zoomInHotspotSize) + "px";
+ centerSpot.style.width = (2 * zoomInHotspotSize) + "px";
+ centerSpot.style.height = (2 * zoomInHotspotSize) + "px";
+
+ frameDiv.appendChild (centerSpotAnchor);
+ centerSpotAnchor.appendChild (centerSpot);
+ centerSpot.innerHTML = "<span style='display:inline-box; position:relative; vertical-align:middle; font-size: 20pt; top: 10pt; color:white'>ZOOM IN</span>";
+
+ var zoomOutBorderAnchor = document.createElement ("div");
+ zoomOutBorderAnchor.style.position = "absolute";
+
+ var zoomOutBorder = document.createElement ("div");
+ zoomOutBorder.style.position = "relative";
+ zoomOutBorder.style.border = zoomOutBorderSize + "px solid black";
+ zoomOutBorder.style.top = "0px";
+ zoomOutBorder.style.left = "0px";
+ zoomOutBorder.style.textAlign = "center";
+ zoomOutBorder.style.width = this.container.clientWidth + "px";
+ zoomOutBorder.style.height = this.container.clientHeight + "px";
+ zoomOutBorder.style.MozBoxSizing =
+ zoomOutBorder.style.boxSizing =
+ zoomOutBorder.style.WebkitBoxSizing =
+ "border-box";
+
+ zoomOutBorder.innerHTML = "<span style='position:relative; font-size: 20pt; top: -25pt; color:white'>ZOOM OUT</span>";
+
+ zoomOutBorderAnchor.appendChild (zoomOutBorder);
+ frameDiv.appendChild (zoomOutBorderAnchor);
+
+ this.container.appendChild (frameDiv);
+
+ var that = this;
+ var opacity = 0.9;
+ var fadeOutSteps = fadeOut / 50;
+ if (fadeOutSteps < 1) {
+ fadeOutSteps = 1;
+ }
+ var iter = function () {
+ opacity = opacity - (0.9 / fadeOutSteps);
+ if (opacity < 0.0) {
+ that.container.removeChild (frameDiv);
+ } else {
+ frameDiv.style.opacity = opacity;
+ setTimeout (iter, 50);
+ }
+ };
+ setTimeout (iter, delay);
+ },
+
+ /**
+ * Forces exit from full screen mode, if we're there.
+ * @public
+ */
+ exitFullScreen : function () {
+ if (this.fullScreenHandler) {
+ this.removeEventListeners ();
+ this.fullScreenHandler.close ();
+ this.addEventListeners ();
+ this.fullScreenHandler = null;
+ return;
+ }
+ },
+
+ /**
+ * Maximizes the image to cover the browser viewport.
+ * The container div is removed from its parent node upon entering
+ * full screen mode. When leaving full screen mode, the container
+ * is appended to its old parent node. To avoid rearranging the
+ * nodes, wrap the container in an extra div.
+ *
+ * <p>For unknown reasons (probably security), browsers will
+ * not let you open a window that covers the entire screen.
+ * Even when specifying "fullscreen=yes", all you get is a window
+ * that has a title bar and only covers the desktop (not any task
+ * bars or the like). For now, this is the best that I can do,
+ * but should the situation change I'll update this to be
+ * full-screen<i>-ier</i>.
+ * @public
+ */
+ fullScreen : function (onClose) {
+ if (this.fullScreenHandler) {
+ return;
+ }
+
+ var message = document.createElement ("div");
+ message.style.position = "absolute";
+ message.style.fontSize = "16pt";
+ message.style.top = "128px";
+ message.style.width = "100%";
+ message.style.color = "white";
+ message.style.padding = "16px";
+ message.style.zIndex = "9999";
+ message.style.textAlign = "center";
+ message.style.opacity = "0.75";
+ message.innerHTML = "<span style='border-radius: 16px; -moz-border-radius: 16px; padding: 16px; padding-left: 32px; padding-right: 32px; background:black'>Press Esc to exit full screen mode.</span>";
+
+ var that = this;
+
+ this.fullScreenHandler = new bigshot.FullScreen (this.container);
+ this.fullScreenHandler.restoreSize = true;
+
+ this.fullScreenHandler.addOnResize (function () {
+ if (that.fullScreenHandler && that.fullScreenHandler.isFullScreen) {
+ that.container.style.width = window.innerWidth + "px";
+ that.container.style.height = window.innerHeight + "px";
+ }
+ that.onresize ();
+ });
+
+ this.fullScreenHandler.addOnClose (function () {
+ if (message.parentNode) {
+ try {
+ div.removeChild (message);
+ } catch (x) {
+ }
+ }
+ that.fullScreenHandler = null;
+ });
+
+ if (onClose) {
+ this.fullScreenHandler.addOnClose (function () {
+ onClose ();
+ });
+ }
+
+ this.removeEventListeners ();
+ this.fullScreenHandler.open ();
+ this.addEventListeners ();
+ if (this.fullScreenHandler.getRootElement ()) {
+ this.fullScreenHandler.getRootElement ().appendChild (message);
+
+ setTimeout (function () {
+ var opacity = 0.75;
+ var iter = function () {
+ opacity -= 0.02;
+ if (message.parentNode) {
+ if (opacity <= 0) {
+ try {
+ div.removeChild (message);
+ } catch (x) {}
+ } else {
+ message.style.opacity = opacity;
+ setTimeout (iter, 20);
+ }
+ }
+ };
+ setTimeout (iter, 20);
+ }, 3500);
+ }
+
+ return function () {
+ that.fullScreenHandler.close ();
+ };
+ },
+
+ /**
+ * Unregisters event handlers and other page-level hooks. The client need not call this
+ * method unless bigshot images are created and removed from the page
+ * dynamically. In that case, this method must be called when the client wishes to
+ * free the resources allocated by the image. Otherwise the browser will garbage-collect
+ * all resources automatically.
+ * @public
+ */
+ dispose : function () {
+ this.browser.unregisterListener (window, "resize", this.onresizeHandler, false);
+ this.removeEventListeners ();
+ }
+};
+
+/**
+ * Fired when the user double-clicks on the image
+ *
+ * @name bigshot.ImageBase#dblclick
+ * @event
+ * @param {bigshot.ImageEvent} event the event object
+ */
+
+/**
+ * Fired when the user clicks on (but does not drag) the image
+ *
+ * @name bigshot.ImageBase#click
+ * @event
+ * @param {bigshot.ImageEvent} event the event object
+ */
+
+bigshot.Object.extend (bigshot.ImageBase, bigshot.EventDispatcher);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * Creates a new tiled image viewer. (Note: See {@link bigshot.ImageBase#dispose} for important information.)
+ *
+ * @example
+ * var bsi = new bigshot.Image (
+ * new bigshot.ImageParameters ({
+ * basePath : "/bigshot.php?file=myshot.bigshot",
+ * fileSystemType : "archive",
+ * container : document.getElementById ("bigshot_div")
+ * }));
+ *
+ * @param {bigshot.ImageParameters} parameters the image parameters. Required fields are: <code>basePath</code> and <code>container</code>.
+ * If you intend to use the archive filesystem, you need to set the <code>fileSystemType</code> to <code>"archive"</code>
+ * as well.
+ * @see bigshot.ImageBase#dispose
+ * @class A tiled, zoomable image viewer.
+ *
+ * <h3 id="creating-a-wrapping-image">Creating a Wrapping Image</h3>
+ *
+ * <p>If you have set the wrapX or wrapY parameters in the {@link bigshot.ImageParameters}, the
+ * image must be an integer multiple of the tile size at the desired minimum zoom level, otherwise
+ * there will be a gap at the wrap point:
+ *
+ * <p>The way to figure out the proper input size is this:
+ *
+ * <ol>
+ * <li><p>Decide on a tile size and call this <i>tileSize</i>.</p></li>
+ * <li><p>Decide on a minimum integer zoom level, and call this <i>minZoom</i>.</p></li>
+ * <li><p>Compute <i>tileSize * 2<sup>-minZoom</sup></i>, call this <i>S</i>.</p></li>
+ * <li><p>The source image size along the wrapped axis must be evenly divisible by <i>S</i>.</p></li>
+ * </ol>
+ *
+ * <p>An example:</p>
+ *
+ * <ol>
+ * <li><p>I have an image that is 23148x3242 pixels.</p></li>
+ * <li><p>I chose 256x256 pixel tiles: <i>tileSize = 256</i>.</p></li>
+ * <li><p>When displaying the image, I want the user to be able to zoom out so that the
+ * whole image is less than or equal to 600 pixels tall. Since the image is 3242 pixels
+ * tall originally, I will need a <i>minZoom</i> of -3. A <i>minZoom</i> of -2 would only let me
+ * zoom out to 1/4 (2<sup>-2</sup>), or an image that is 810 pixels tall. A <i>minZoom</i> of -3, however lets me
+ * zoom out to 1/8 (2<sup>-3</sup>), or an image that is 405 pixels tall. Thus: <i>minZoom = -3</i></p></li>
+ * <li><p>Computing <i>S</i> gives: <i>S = 256 * 2<sup>3</sup> = 256 * 8 = 2048</i></p></li>
+ * <li><p>I want it to wrap along the X axis. Therefore I may have to adjust the width,
+ * currently 23148 pixels.</p></li>
+ * <li><p>Rounding 23148 down to the nearest multiple of 2048 gives 22528. (23148 divided by 2048 is 11.3, and 11 times 2048 is 22528.)</p></li>
+ * <li><p>I will shrink my source image to be 22528 pixels wide before building the image pyramid,
+ * and I will set the <code>minZoom</code> parameter to -3 in the {@link bigshot.ImageParameters} when creating
+ * the image. (I will also set <code>wrapX</code> to <code>true</code>.)</p></li>
+ * </ol>
+ *
+ * @augments bigshot.ImageBase
+ */
+bigshot.Image = function (parameters) {
+ bigshot.setupFileSystem (parameters);
+ parameters.merge (parameters.fileSystem.getDescriptor (), false);
+
+ bigshot.ImageBase.call (this, parameters);
+}
+
+bigshot.Image.prototype = {
+ setupLayers : function () {
+ var that = this;
+ this.thisTileCache = new bigshot.ImageTileCache (function () {
+ that.layout ();
+ }, null, this.parameters);
+
+ this.addLayer (
+ new bigshot.TileLayer (this, this.parameters, 0, 0, this.thisTileCache)
+ );
+ }
+};
+
+bigshot.Object.extend (bigshot.Image, bigshot.ImageBase);
+
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new HTML element layer. The layer must be added to the image using
+ * {@link bigshot.ImageBase#addLayer}.
+ *
+ * @class A layer consisting of a single HTML element that is moved and scaled to cover
+ * the layer.
+ * @example
+ * var image = new bigshot.Image (...);
+ * image.addLayer (
+ * new bigshot.HTMLElementLayer (this, this.imgElement, this.parameters.width, this.parameters.height)
+ * );
+ * @param {bigshot.ImageBase} image the image this hotspot layer will be part of
+ * @param {HTMLElement} element the element to present in this layer
+ * @param {int} width the width, in image pixels (display size at zoom level 0), of the HTML element
+ * @param {int} height the height, in image pixels (display size at zoom level 0), of the HTML element
+ * @augments bigshot.Layer
+ */
+bigshot.HTMLElementLayer = function (image, element, width, height) {
+ this.hotspots = new Array ();
+ this.browser = new bigshot.Browser ();
+ this.image = image;
+ this.container = image.createLayerContainer ();
+ this.parentContainer = image.getContainer ();
+ this.element = element;
+ this.parentContainer.appendChild (element);
+ this.w = width;
+ this.h = height;
+ this.resize (0, 0);
+}
+
+bigshot.HTMLElementLayer.prototype = {
+
+ getContainer : function () {
+ return this.container;
+ },
+
+ resize : function (w, h) {
+ this.container.style.width = this.parentContainer.clientWidth + "px";
+ this.container.style.height = this.parentContainer.clientHeight + "px";
+ },
+
+ layout : function (zoom, x0, y0, tx0, ty0, size, stride, opacity) {
+ var zoomFactor = Math.pow (2, this.image.getZoom ());
+ x0 -= stride * tx0;
+ y0 -= stride * ty0;
+
+ this.element.style.top = y0 + "px";
+ this.element.style.left = x0 + "px";
+ this.element.style.width = (this.w * zoomFactor) + "px";
+ this.element.style.height = (this.h * zoomFactor) + "px";
+ },
+
+ setMaxTiles : function (mtx, mty) {
+ }
+}
+
+bigshot.Object.validate ("bigshot.HTMLElementLayer", bigshot.Layer);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new HTML element layer. The layer must be added to the image using
+ * {@link bigshot.ImageBase#addLayer}.
+ *
+ * @class A layer consisting of a single HTML element that is moved and scaled to cover
+ * the layer.
+ * @example
+ * var image = new bigshot.Image (...);
+ * image.addLayer (
+ * new bigshot.HTMLElementLayer (this, this.imgElement, this.parameters.width, this.parameters.height)
+ * );
+ * @param {bigshot.ImageBase} image the image this hotspot layer will be part of
+ * @param {HTMLElement} element the element to present in this layer
+ * @param {int} width the width, in image pixels (display size at zoom level 0), of the HTML element
+ * @param {int} height the height, in image pixels (display size at zoom level 0), of the HTML element
+ * @augments bigshot.Layer
+ */
+bigshot.HTMLDivElementLayer = function (image, element, width, height, wrapX, wrapY) {
+ this.wrapX = wrapX;
+ this.wrapY = wrapY;
+ this.hotspots = new Array ();
+ this.browser = new bigshot.Browser ();
+ this.image = image;
+ this.container = image.createLayerContainer ();
+ this.parentContainer = image.getContainer ();
+ this.element = element;
+ this.parentContainer.appendChild (element);
+ this.w = width;
+ this.h = height;
+ this.resize (0, 0);
+}
+
+bigshot.HTMLDivElementLayer.prototype = {
+
+ getContainer : function () {
+ return this.container;
+ },
+
+ resize : function (w, h) {
+ this.container.style.width = this.parentContainer.clientWidth + "px";
+ this.container.style.height = this.parentContainer.clientHeight + "px";
+ },
+
+ layout : function (zoom, x0, y0, tx0, ty0, size, stride, opacity) {
+ var zoomFactor = Math.pow (2, this.image.getZoom ());
+ x0 -= stride * tx0;
+ y0 -= stride * ty0;
+
+ var imW = (this.w * zoomFactor);
+ var imH = (this.h * zoomFactor);
+
+ this.element.style.backgroundSize = imW + "px " + imH + "px";
+
+ var bposX = "0px";
+ var bposY = "0px";
+
+ if (this.wrapY) {
+ this.element.style.top = "0px";
+ this.element.style.height = (this.parentContainer.clientHeight) + "px";
+ bposY = y0 + "px";
+ } else {
+ this.element.style.top = y0 + "px";
+ this.element.style.height = imH + "px";
+ }
+
+ if (this.wrapX) {
+ this.element.style.left = "0px";
+ this.element.style.width = (this.parentContainer.clientWidth) + "px";
+ bposX = x0 + "px";
+ } else {
+ this.element.style.left = x0 + "px";
+ this.element.style.width = imW + "px";
+ }
+
+ this.element.style.backgroundPosition = bposX + " " + bposY;
+ },
+
+ setMaxTiles : function (mtx, mty) {
+ }
+}
+
+bigshot.Object.validate ("bigshot.HTMLDivElementLayer", bigshot.Layer);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * Creates a new image viewer. (Note: See {@link bigshot.SimpleImage#dispose} for important information.)
+ *
+ * @example
+ * var bsi = new bigshot.SimpleImage (
+ * new bigshot.ImageParameters ({
+ * basePath : "myimage.jpg",
+ * width : 681,
+ * height : 1024,
+ * container : document.getElementById ("bigshot_div")
+ * }));
+ *
+ * @param {bigshot.ImageParameters} parameters the image parameters. Required fields are: <code>container</code>.
+ * If the <code>imgElement</code> parameter is not given, then <code>basePath</code>, <code>width</code> and <code>height</code> are also required. The
+ * following parameters are not supported and should be left as defaults: <code>fileSystem</code>, <code>fileSystemType</code>,
+ * <code>maxTextureMagnification</code> and <code>tileSize</code>. <code>wrapX</code> and <code>wrapY</code> may only be used if the imgElement is <b>not</b>
+ * set.
+ *
+ * @param {HTMLImageElement} [imgElement] an img element to use. The element should have <code>style.position = "absolute"</code>.
+ * @see bigshot.ImageBase#dispose
+ * @class A zoomable image viewer.
+ * @augments bigshot.ImageBase
+ */
+bigshot.SimpleImage = function (parameters, imgElement) {
+ parameters.merge ({
+ fileSystem : null,
+ fileSystemType : "simple",
+ maxTextureMagnification : 1.0,
+ tileSize : 1024
+ }, true);
+
+ if (imgElement) {
+ parameters.merge ({
+ width : imgElement.width,
+ height : imgElement.height
+ });
+ this.imgElement = imgElement;
+ } else {
+ if (parameters.width == 0 || parameters.height == 0) {
+ throw new Error ("No imgElement and missing width or height in ImageParameters");
+ }
+ }
+ bigshot.setupFileSystem (parameters);
+
+ bigshot.ImageBase.call (this, parameters);
+}
+
+bigshot.SimpleImage.prototype = {
+ setupLayers : function () {
+ if (!this.imgElement) {
+ /*
+ this.imgElement = document.createElement ("img");
+ this.imgElement.src = this.parameters.basePath;
+ this.imgElement.style.position = "absolute";
+ */
+ this.imgElement = document.createElement ("div");
+ this.imgElement.style.backgroundImage = "url('" + this.parameters.basePath + "')";
+ this.imgElement.style.position = "absolute";
+ if (!this.parameters.wrapX && !this.parameters.wrapY) {
+ this.imgElement.style.backgroundRepeat = "no-repeat";
+ } else if (this.parameters.wrapX && !this.parameters.wrapY) {
+ this.imgElement.style.backgroundRepeat = "repeat-x";
+ } else if (!this.parameters.wrapX && this.parameters.wrapY) {
+ this.imgElement.style.backgroundRepeat = "repeat-y";
+ } else if (this.parameters.wrapX && this.parameters.wrapY) {
+ this.imgElement.style.backgroundRepeat = "repeat";
+ }
+ }
+
+ this.addLayer (
+ new bigshot.HTMLDivElementLayer (this, this.imgElement, this.parameters.width, this.parameters.height, this.parameters.wrapX, this.parameters.wrapY)
+ );
+ }
+};
+
+bigshot.Object.extend (bigshot.SimpleImage, bigshot.ImageBase);
+
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Abstract filesystem definition.
+ *
+ * @class Abstract filesystem definition.
+ */
+bigshot.FileSystem = function () {
+}
+
+bigshot.FileSystem.prototype = {
+ /**
+ * Returns the URL filename for the given filesystem entry.
+ *
+ * @param {String} name the entry name
+ */
+ getFilename : function (name) {},
+
+ /**
+ * Returns the entry filename for the given tile.
+ *
+ * @param {int} tileX the column of the tile
+ * @param {int} tileY the row of the tile
+ * @param {int} zoomLevel the zoom level
+ */
+ getImageFilename : function (tileX, tileY, zoomLevel) {},
+
+ /**
+ * Sets an optional prefix that is prepended, along with a forward
+ * slash ("/"), to all names.
+ *
+ * @param {String} prefix the prefix
+ */
+ setPrefix : function (prefix) {},
+
+ /**
+ * Returns an image descriptor object from the descriptor file.
+ *
+ * @return a descriptor object
+ */
+ getDescriptor : function () {},
+
+ /**
+ * Returns the poster URL filename. For Bigshot images this is
+ * typically the URL corresponding to the entry "poster.jpg",
+ * but for other filesystems it can be different.
+ */
+ getPosterFilename : function () {}
+};
+
+/**
+ * Sets up a filesystem instance in the given parameters object, if none exist.
+ * If the {@link bigshot.ImageParameters#fileSystem} member isn't set, the
+ * {@link bigshot.ImageParameters#fileSystemType} member is used to create a new
+ * {@link bigshot.FileSystem} instance and set it.
+ *
+ * @param {bigshot.ImageParameters or bigshot.VRPanoramaParameters or bigshot.ImageCarouselPanoramaParameters} parameters the parameters object to populate
+ */
+bigshot.setupFileSystem = function (parameters) {
+ if (!parameters.fileSystem) {
+ if (parameters.fileSystemType == "archive") {
+ parameters.fileSystem = new bigshot.ArchiveFileSystem (parameters);
+ } else if (parameters.fileSystemType == "dzi") {
+ parameters.fileSystem = new bigshot.DeepZoomImageFileSystem (parameters);
+ } else if (parameters.fileSystemType == "simple") {
+ parameters.fileSystem = new bigshot.SimpleFileSystem (parameters);
+ } else {
+ parameters.fileSystem = new bigshot.FolderFileSystem (parameters);
+ }
+ }
+}
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new instance of a filesystem adapter for the SimpleImage class.
+ *
+ * @class Filesystem adapter for bigshot.SimpleImage. This class is not
+ * supposed to be used outside of the {@link bigshot.SimpleImage} class.
+ * @param {bigshot.ImageParameters} parameters the associated image parameters
+ * @augments bigshot.FileSystem
+ * @see bigshot.SimpleImage
+ */
+bigshot.SimpleFileSystem = function (parameters) {
+ this.parameters = parameters;
+};
+
+
+bigshot.SimpleFileSystem.prototype = {
+ getDescriptor : function () {
+ return {};
+ },
+
+ getPosterFilename : function () {
+ return null;
+ },
+
+ getFilename : function (name) {
+ return null;
+ },
+
+ getImageFilename : function (tileX, tileY, zoomLevel) {
+ return null;
+ },
+
+ getPrefix : function () {
+ return "";
+ },
+
+ setPrefix : function (prefix) {
+ this.prefix = prefix;
+ }
+}
+
+bigshot.Object.validate ("bigshot.SimpleFileSystem", bigshot.FileSystem);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new instance of a folder-based filesystem adapter.
+ *
+ * @augments bigshot.FileSystem
+ * @class Folder-based filesystem.
+ * @param {bigshot.ImageParameters|bigshot.VRPanoramaParameters} parameters the associated image parameters
+ * @constructor
+ */
+bigshot.FolderFileSystem = function (parameters) {
+ this.prefix = null;
+ this.suffix = "";
+ this.parameters = parameters;
+}
+
+
+bigshot.FolderFileSystem.prototype = {
+ getDescriptor : function () {
+ this.browser = new bigshot.Browser ();
+ var req = this.browser.createXMLHttpRequest ();
+
+ req.open("GET", this.getFilename ("descriptor"), false);
+ req.send(null);
+ var descriptor = {};
+ if(req.status == 200) {
+ var substrings = req.responseText.split (":");
+ for (var i = 0; i < substrings.length; i += 2) {
+ if (substrings[i] == "suffix") {
+ descriptor[substrings[i]] = substrings[i + 1];
+ } else {
+ descriptor[substrings[i]] = parseInt (substrings[i + 1]);
+ }
+ }
+ this.suffix = descriptor.suffix;
+ return descriptor;
+ } else {
+ throw new Error ("Unable to find descriptor.");
+ }
+ },
+
+ getPosterFilename : function () {
+ return this.getFilename ("poster" + this.suffix);
+ },
+
+ setPrefix : function (prefix) {
+ this.prefix = prefix;
+ },
+
+ getPrefix : function () {
+ if (this.prefix) {
+ return this.prefix + "/";
+ } else {
+ return "";
+ }
+ },
+
+ getFilename : function (name) {
+ return this.parameters.basePath + "/" + this.getPrefix () + name;
+ },
+
+ getImageFilename : function (tileX, tileY, zoomLevel) {
+ var key = (-zoomLevel) + "/" + tileX + "_" + tileY + this.suffix;
+ return this.getFilename (key);
+ }
+};
+
+bigshot.Object.validate ("bigshot.FolderFileSystem", bigshot.FileSystem);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new instance of a Deep Zoom Image folder-based filesystem adapter.
+ *
+ * @augments bigshot.FileSystem
+ * @class A Deep Zoom Image filesystem.
+ * @param {bigshot.ImageParameters|bigshot.VRPanoramaParameters} parameters the associated image parameters
+ * @constructor
+ */
+bigshot.DeepZoomImageFileSystem = function (parameters) {
+ this.prefix = "";
+ this.suffix = "";
+
+ this.DZ_NAMESPACE = "http://schemas.microsoft.com/deepzoom/2009";
+ this.fullZoomLevel = 0;
+ this.posterName = "";
+ this.parameters = parameters;
+}
+
+bigshot.DeepZoomImageFileSystem.prototype = {
+ getDescriptor : function () {
+ var descriptor = {};
+
+ var xml = this.parameters.dataLoader.loadXml (this.parameters.basePath + this.prefix + ".xml", false);
+ var image = xml.getElementsByTagName ("Image")[0];
+ var size = xml.getElementsByTagName ("Size")[0];
+ descriptor.width = parseInt (size.getAttribute ("Width"));
+ descriptor.height = parseInt (size.getAttribute ("Height"));
+ descriptor.tileSize = parseInt (image.getAttribute ("TileSize"));
+ descriptor.overlap = parseInt (image.getAttribute ("Overlap"));
+ descriptor.suffix = "." + image.getAttribute ("Format")
+ descriptor.posterSize = descriptor.tileSize;
+
+ this.suffix = descriptor.suffix;
+ this.fullZoomLevel = Math.ceil (Math.log (Math.max (descriptor.width, descriptor.height)) / Math.LN2);
+
+ descriptor.minZoom = -this.fullZoomLevel;
+ var posterZoomLevel = Math.ceil (Math.log (descriptor.tileSize) / Math.LN2);
+ this.posterName = this.getImageFilename (0, 0, posterZoomLevel - this.fullZoomLevel);
+ return descriptor;
+ },
+
+ setPrefix : function (prefix) {
+ this.prefix = prefix;
+ },
+
+ getPosterFilename : function () {
+ return this.posterName;
+ },
+
+ getFilename : function (name) {
+ return this.parameters.basePath + this.prefix + "/" + name;
+ },
+
+ getImageFilename : function (tileX, tileY, zoomLevel) {
+ var dziZoomLevel = this.fullZoomLevel + zoomLevel;
+ var key = dziZoomLevel + "/" + tileX + "_" + tileY + this.suffix;
+ return this.getFilename (key);
+ }
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new instance of a <code>.bigshot</code> archive filesystem adapter.
+ *
+ * @class Bigshot archive filesystem.
+ * @param {bigshot.ImageParameters|bigshot.VRPanoramaParameters} parameters the associated image parameters
+ * @augments bigshot.FileSystem
+ * @constructor
+ */
+bigshot.ArchiveFileSystem = function (parameters) {
+ this.indexSize = 0;
+ this.offset = 0;
+ this.index = {};
+ this.prefix = "";
+ this.suffix = "";
+ this.parameters = parameters;
+
+ var browser = new bigshot.Browser ();
+ var req = browser.createXMLHttpRequest ();
+ req.open("GET", this.parameters.basePath + "&start=0&length=24&type=text/plain", false);
+ req.send(null);
+ if(req.status == 200) {
+ if (req.responseText.substring (0, 7) != "BIGSHOT") {
+ alert ("\"" + this.parameters.basePath + "\" is not a valid bigshot file");
+ return;
+ }
+ this.indexSize = parseInt (req.responseText.substring (8), 16);
+ this.offset = this.indexSize + 24;
+
+ req.open("GET", this.parameters.basePath + "&type=text/plain&start=24&length=" + this.indexSize, false);
+ req.send(null);
+ if(req.status == 200) {
+ var substrings = req.responseText.split (":");
+ for (var i = 0; i < substrings.length; i += 3) {
+ this.index[substrings[i]] = {
+ start : parseInt (substrings[i + 1]) + this.offset,
+ length : parseInt (substrings[i + 2])
+ };
+ }
+ } else {
+ alert ("The index of \"" + this.parameters.basePath + "\" could not be loaded: " + req.status);
+ }
+ } else {
+ alert ("The header of \"" + this.parameters.basePath + "\" could not be loaded: " + req.status);
+ }
+};
+
+
+bigshot.ArchiveFileSystem.prototype = {
+ getDescriptor : function () {
+ this.browser = new bigshot.Browser ();
+ var req = this.browser.createXMLHttpRequest ();
+
+ req.open("GET", this.getFilename ("descriptor"), false);
+ req.send(null);
+ var descriptor = {};
+ if(req.status == 200) {
+ var substrings = req.responseText.split (":");
+ for (var i = 0; i < substrings.length; i += 2) {
+ if (substrings[i] == "suffix") {
+ descriptor[substrings[i]] = substrings[i + 1];
+ } else {
+ descriptor[substrings[i]] = parseInt (substrings[i + 1]);
+ }
+ }
+ this.suffix = descriptor.suffix;
+ return descriptor;
+ } else {
+ throw new Error ("Unable to find descriptor.");
+ }
+ },
+
+ getPosterFilename : function () {
+ return this.getFilename ("poster" + this.suffix);
+ },
+
+ getFilename : function (name) {
+ name = this.getPrefix () + name;
+ if (!this.index[name] && console) {
+ console.log ("Can't find " + name);
+ }
+ var f = this.parameters.basePath + "&start=" + this.index[name].start + "&length=" + this.index[name].length;
+ if (name.substring (name.length - 4) == ".jpg") {
+ f = f + "&type=image/jpeg";
+ } else if (name.substring (name.length - 4) == ".png") {
+ f = f + "&type=image/png";
+ } else {
+ f = f + "&type=text/plain";
+ }
+ return f;
+ },
+
+ getImageFilename : function (tileX, tileY, zoomLevel) {
+ var key = (-zoomLevel) + "/" + tileX + "_" + tileY + this.suffix;
+ return this.getFilename (key);
+ },
+
+ getPrefix : function () {
+ if (this.prefix) {
+ return this.prefix + "/";
+ } else {
+ return "";
+ }
+ },
+
+ setPrefix : function (prefix) {
+ this.prefix = prefix;
+ }
+}
+
+bigshot.Object.validate ("bigshot.ArchiveFileSystem", bigshot.FileSystem);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @class Abstract base class.
+ */
+bigshot.VRTileCache = function () {
+}
+
+bigshot.VRTileCache.prototype = {
+ /**
+ * Returns the texture object for the given tile-x, tile-y and zoom level.
+ * The return type is dependent on the renderer. The WebGL renderer, for example
+ * uses a tile cache that returns WebGL textures, while the CSS3D renderer
+ * returns HTML img or canvas elements.
+ */
+ getTexture : function (tileX, tileY, zoomLevel) {},
+
+ /**
+ * Purges the cache of old entries.
+ *
+ * @type void
+ */
+ purge : function () {},
+
+ /**
+ * Disposes the cache and all its entries.
+ *
+ * @type void
+ */
+ dispose : function () {}
+}
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @class A VR tile cache backed by a {@link bigshot.ImageTileCache}.
+ * @augments bigshot.VRTileCache
+ */
+bigshot.ImageVRTileCache = function (onloaded, onCacheInit, parameters) {
+ this.imageTileCache = new bigshot.ImageTileCache (onloaded, onCacheInit, parameters);
+
+ // Keep the imageTileCache from wrapping around.
+ this.imageTileCache.setMaxTiles (999999, 999999);
+}
+
+bigshot.ImageVRTileCache.prototype = {
+ getTexture : function (tileX, tileY, zoomLevel) {
+ var res = this.imageTileCache.getImage (tileX, tileY, zoomLevel);
+ return res;
+ },
+
+ purge : function () {
+ this.imageTileCache.resetUsed ();
+ },
+
+ dispose : function () {
+
+ }
+}
+
+bigshot.Object.validate ("bigshot.ImageVRTileCache", bigshot.VRTileCache);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new cache instance.
+ *
+ * @class Tile texture cache for a {@link bigshot.VRFace}.
+ * @augments bigshot.VRTileCache
+ * @param {function()} onLoaded function that is called whenever a texture tile has been loaded
+ * @param {function()} onCacheInit function that is called when the texture cache is fully initialized
+ * @param {bigshot.VRPanoramaParameters} parameters image parameters
+ * @param {bigshot.WebGL} _webGl WebGL instance to use
+ */
+bigshot.TextureTileCache = function (onLoaded, onCacheInit, parameters, _webGl) {
+ this.parameters = parameters;
+ this.webGl = _webGl;
+
+ /**
+ * Reduced-resolution preview of the full image.
+ * Loaded from the "poster" image created by
+ * MakeImagePyramid
+ *
+ * @private
+ * @type HTMLImageElement
+ */
+ this.fullImage = parameters.dataLoader.loadImage (parameters.fileSystem.getPosterFilename (), onCacheInit);
+
+ /**
+ * Maximum number of WebGL textures in the cache. This is the
+ * "L1" cache.
+ *
+ * @private
+ * @type int
+ */
+ this.maxTextureCacheSize = 512;
+
+ /**
+ * Maximum number of HTMLImageElement images in the cache. This is the
+ * "L2" cache.
+ *
+ * @private
+ * @type int
+ */
+ this.maxImageCacheSize = 2048;
+ this.cachedTextures = {};
+ this.cachedImages = {};
+ this.requestedImages = {};
+ this.lastOnLoadFiredAt = 0;
+ this.imageRequests = 0;
+ this.partialImageSize = parameters.tileSize / 8;
+ this.imageLruMap = new bigshot.LRUMap ();
+ this.textureLruMap = new bigshot.LRUMap ();
+ this.onLoaded = onLoaded;
+ this.browser = new bigshot.Browser ();
+ this.disposed = false;
+}
+
+bigshot.TextureTileCache.prototype = {
+
+ getPartialTexture : function (tileX, tileY, zoomLevel) {
+ if (this.fullImage.complete) {
+ var canvas = document.createElement ("canvas");
+ if (!canvas["width"]) {
+ return null;
+ }
+ canvas.width = this.partialImageSize;
+ canvas.height = this.partialImageSize;
+ var ctx = canvas.getContext ("2d");
+
+ var posterScale = this.parameters.posterSize / Math.max (this.parameters.width, this.parameters.height);
+
+ var posterWidth = Math.floor (posterScale * this.parameters.width);
+ var posterHeight = Math.floor (posterScale * this.parameters.height);
+
+ var tileSizeAtZoom = posterScale * (this.parameters.tileSize - this.parameters.overlap) / Math.pow (2, zoomLevel);
+ var sx = Math.floor (tileSizeAtZoom * tileX);
+ var sy = Math.floor (tileSizeAtZoom * tileY);
+ var sw = Math.floor (tileSizeAtZoom);
+ var sh = Math.floor (tileSizeAtZoom);
+ var dw = this.partialImageSize + 2;
+ var dh = this.partialImageSize + 2;
+
+ if (sx + sw > posterWidth) {
+ sw = posterWidth - sx;
+ dw = this.partialImageSize * (sw / Math.floor (tileSizeAtZoom));
+ }
+ if (sy + sh > posterHeight) {
+ sh = posterHeight - sy;
+ dh = this.partialImageSize * (sh / Math.floor (tileSizeAtZoom));
+ }
+
+ ctx.drawImage (this.fullImage, sx, sy, sw, sh, -1, -1, dw, dh);
+
+ return this.webGl.createImageTextureFromImage (canvas, this.parameters.textureMinFilter, this.parameters.textureMagFilter);
+ } else {
+ return null;
+ }
+ },
+
+ setCachedTexture : function (key, newTexture) {
+ if (this.cachedTextures[key] != null) {
+ this.webGl.deleteTexture (this.cachedTextures[key]);
+ }
+ this.cachedTextures[key] = newTexture;
+ },
+
+ getTexture : function (tileX, tileY, zoomLevel) {
+ var key = this.getImageKey (tileX, tileY, zoomLevel);
+ this.textureLruMap.access (key);
+ this.imageLruMap.access (key);
+
+ if (this.cachedTextures[key]) {
+ return this.cachedTextures[key];
+ } else if (this.cachedImages[key]) {
+ this.setCachedTexture (key, this.webGl.createImageTextureFromImage (this.cachedImages[key], this.parameters.textureMinFilter, this.parameters.textureMagFilter));
+ return this.cachedTextures[key];
+ } else {
+ this.requestImage (tileX, tileY, zoomLevel);
+ var partial = this.getPartialTexture (tileX, tileY, zoomLevel);
+ if (partial) {
+ this.setCachedTexture (key, partial);
+ }
+ return partial;
+ }
+ },
+
+ requestImage : function (tileX, tileY, zoomLevel) {
+ var key = this.getImageKey (tileX, tileY, zoomLevel);
+ if (!this.requestedImages[key]) {
+ this.imageRequests++;
+ var that = this;
+ this.parameters.dataLoader.loadImage (this.getImageFilename (tileX, tileY, zoomLevel), function (tile) {
+ if (that.disposed) {
+ return;
+ }
+ that.cachedImages[key] = tile;
+ that.setCachedTexture (key, that.webGl.createImageTextureFromImage (tile, that.parameters.textureMinFilter, that.parameters.textureMagFilter));
+ delete that.requestedImages[key];
+ that.imageRequests--;
+ var now = new Date();
+ if (that.imageRequests == 0 || now.getTime () > (that.lastOnLoadFiredAt + 50)) {
+ that.lastOnLoadFiredAt = now.getTime ();
+ that.onLoaded ();
+ }
+ });
+ this.requestedImages[key] = true;
+ }
+ },
+
+ purge : function () {
+ var that = this;
+ this.purgeCache (this.textureLruMap, this.cachedTextures, this.maxTextureCacheSize, function (leastUsedKey) {
+ that.webGl.deleteTexture (that.cachedTextures[leastUsedKey]);
+ });
+ this.purgeCache (this.imageLruMap, this.cachedImages, this.maxImageCacheSize, function (leastUsedKey) {
+ });
+ },
+
+ purgeCache : function (lruMap, cache, maxCacheSize, onEvict) {
+ for (var i = 0; i < 64; ++i) {
+ if (lruMap.getSize () > maxCacheSize) {
+ var leastUsed = lruMap.leastUsed ();
+ lruMap.remove (leastUsed);
+ if (onEvict) {
+ onEvict (leastUsed);
+ }
+ delete cache[leastUsed];
+ } else {
+ break;
+ }
+ }
+ },
+
+ getImageKey : function (tileX, tileY, zoomLevel) {
+ return "I" + tileX + "_" + tileY + "_" + zoomLevel;
+ },
+
+ getImageFilename : function (tileX, tileY, zoomLevel) {
+ var f = this.parameters.fileSystem.getImageFilename (tileX, tileY, zoomLevel);
+ return f;
+ },
+
+ dispose : function () {
+ this.disposed = true;
+ for (var k in this.cachedTextures) {
+ this.webGl.deleteTexture (this.cachedTextures[k]);
+ }
+ }
+};
+
+
+bigshot.Object.validate ("bigshot.TextureTileCache", bigshot.VRTileCache);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new VR cube face.
+ *
+ * @class a VR cube face. The {@link bigshot.VRPanorama} instance holds
+ * six of these.
+ *
+ * @param {bigshot.VRPanorama} owner the VR panorama this face is part of.
+ * @param {String} key the identifier for the face. "f" is front, "b" is back, "u" is
+ * up, "d" is down, "l" is left and "r" is right.
+ * @param {bigshot.Point3D} topLeft_ the top-left corner of the quad.
+ * @param {number} width_ the length of the sides of the face, expressed in multiples of u and v.
+ * @param {bigshot.Point3D} u basis vector going from the top left corner along the top edge of the face
+ * @param {bigshot.Point3D} v basis vector going from the top left corner along the left edge of the face
+ */
+bigshot.VRFace = function (owner, key, topLeft_, width_, u, v, onLoaded) {
+ var that = this;
+ this.owner = owner;
+ this.key = key;
+ this.topLeft = topLeft_;
+ this.width = width_;
+ this.u = u;
+ this.v = v;
+ this.updated = false;
+ this.parameters = new Object ();
+
+ for (var k in this.owner.getParameters ()) {
+ this.parameters[k] = this.owner.getParameters ()[k];
+ }
+
+ bigshot.setupFileSystem (this.parameters);
+ this.parameters.fileSystem.setPrefix ("face_" + key);
+ this.parameters.merge (this.parameters.fileSystem.getDescriptor (), false);
+
+
+ /**
+ * Texture cache.
+ *
+ * @private
+ */
+ this.tileCache = owner.renderer.createTileCache (function () {
+ that.updated = true;
+ owner.renderUpdated (bigshot.VRPanorama.ONRENDER_TEXTURE_UPDATE);
+ }, onLoaded, this.parameters);
+
+ this.fullSize = this.parameters.width;
+ this.overlap = this.parameters.overlap;
+ this.tileSize = this.parameters.tileSize;
+
+ this.minDivisions = 0;
+ var fullZoom = Math.log (this.fullSize - this.overlap) / Math.LN2;
+ var singleTile = Math.log (this.tileSize - this.overlap) / Math.LN2;
+ this.maxDivisions = Math.floor (fullZoom - singleTile);
+ this.maxTesselation = this.parameters.maxTesselation >= 0 ? this.parameters.maxTesselation : this.maxDivisions;
+}
+
+bigshot.VRFace.prototype = {
+ browser : new bigshot.Browser (),
+
+ dispose : function () {
+ this.tileCache.dispose ();
+ },
+
+ /**
+ * Utility function to do a multiply-and-add of a 3d point.
+ *
+ * @private
+ * @param p {bigshot.Point3D} the point to multiply
+ * @param m {number} the number to multiply the elements of p with
+ * @param a {bigshot.Point3D} the point to add
+ * @return p * m + a
+ */
+ pt3dMultAdd : function (p, m, a) {
+ return {
+ x : p.x * m + a.x,
+ y : p.y * m + a.y,
+ z : p.z * m + a.z
+ };
+ },
+
+ /**
+ * Utility function to do an element-wise multiply of a 3d point.
+ *
+ * @private
+ * @param p {bigshot.Point3D} the point to multiply
+ * @param m {number} the number to multiply the elements of p with
+ * @return p * m
+ */
+ pt3dMult : function (p, m) {
+ return {
+ x : p.x * m,
+ y : p.y * m,
+ z : p.z * m
+ };
+ },
+
+ /**
+ * Creates a textured quad.
+ *
+ * @private
+ */
+ generateFace : function (scene, topLeft, width, tx, ty, divisions) {
+ width *= this.tileSize / (this.tileSize - this.overlap);
+ var texture = this.tileCache.getTexture (tx, ty, -this.maxDivisions + divisions);
+ scene.addQuad (this.owner.renderer.createTexturedQuad (
+ topLeft,
+ this.pt3dMult (this.u, width),
+ this.pt3dMult (this.v, width),
+ texture
+ )
+ );
+ },
+
+ VISIBLE_NONE : 0,
+ VISIBLE_SOME : 1,
+ VISIBLE_ALL : 2,
+
+ /**
+ * Tests whether the point is in the axis-aligned rectangle.
+ *
+ * @private
+ * @param point the point
+ * @param min top left corner of the rectangle
+ * @param max bottom right corner of the rectangle
+ */
+ pointInRect : function (point, min, max) {
+ return (point.x >= min.x && point.y >= min.y && point.x < max.x && point.y < max.y);
+ },
+
+ /**
+ * Intersects a quadrilateral with the view frustum.
+ * The test is a simple rectangle intersection of the AABB of
+ * the transformed quad with the viewport.
+ *
+ * @private
+ * @return VISIBLE_NONE, VISIBLE_SOME or VISIBLE_ALL
+ */
+ intersectWithView : function intersectWithView (transformed) {
+ var numNull = 0;
+ var tf = [];
+ var tfl = transformed.length;
+ for (var i = 0; i < tfl; ++i) {
+ if (transformed[i] == null) {
+ numNull++;
+ } else {
+ tf.push (transformed[i]);
+ }
+ }
+ if (numNull == 4) {
+ return this.VISIBLE_NONE;
+ }
+
+ var minX = tf[0].x;
+ var minY = tf[0].y;
+
+ var maxX = minX;
+ var maxY = minY;
+
+ var viewMinX = 0;
+ var viewMinY = 0;
+
+ var viewMaxX = this.viewportWidth;
+ var viewMaxY = this.viewportHeight;
+
+ var pointsInViewport = 0;
+ var tl = tf.length;
+ for (var i = 1; i < tl; ++i) {
+ var tix = tf[i].x;
+ var tiy = tf[i].y;
+
+ minX = minX < tix ? minX : tix;
+ minY = minY < tiy ? minY : tiy;
+
+
+ maxX = maxX > tix ? maxX : tix;
+ maxY = maxY > tiy ? maxY : tiy;
+ }
+
+ var iminX = minX > viewMinX ? minX : viewMinX;
+ var iminY = minY > viewMinY ? minY : viewMinY;
+
+ var imaxX = maxX < viewMaxX ? maxX : viewMaxX;
+ var imaxY = maxY < viewMaxY ? maxY : viewMaxY;
+
+ if (iminX <= imaxX && iminY <= imaxY) {
+ return this.VISIBLE_SOME;
+ }
+
+ return this.VISIBLE_NONE;
+ },
+
+ /**
+ * Quick and dirty computation of the on-screen distance in pixels
+ * between two 2d points. We use the max of the x and y differences.
+ * In case a point is null (that is, it's not on the screen), we
+ * return an arbitrarily high number.
+ *
+ * @private
+ */
+ screenDistance : function screenDistance (p0, p1) {
+ if (p0 == null || p1 == null) {
+ return 0;
+ }
+ return Math.max (Math.abs (p0.x - p1.x), Math.abs (p0.y - p1.y));
+ },
+
+ transformToScreen : function transformToScreen (v) {
+ return this.owner.renderer.transformToScreen (v);
+ },
+
+ /**
+ * Optionally subdivides a quad into fourn new quads, depending on the
+ * position and on-screen size of the quad.
+ *
+ * @private
+ * @param {bigshot.WebGLTexturedQuadScene} scene the scene to add quads to
+ * @param {bigshot.Point3D} topLeft the top left corner of this quad
+ * @param {number} width the sides of the quad, expressed in multiples of u and v
+ * @param {int} divisions the current number of divisions done (increases by one for each
+ * split-in-four).
+ * @param {int} tx the tile column this face is in
+ * @param {int} ty the tile row this face is in
+ */
+ generateSubdivisionFace : function generateSubdivisionFace (scene, topLeft, width, divisions, tx, ty, transformed) {
+ if (!transformed) {
+ transformed = new Array (4);
+ transformed[0] = this.transformToScreen (topLeft);
+ var topRight = this.pt3dMultAdd (this.u, width, topLeft);
+ transformed[1] = this.transformToScreen (topRight);
+
+ var bottomLeft = this.pt3dMultAdd (this.v, width, topLeft);
+ transformed[3] = this.transformToScreen (bottomLeft);
+
+ var bottomRight = this.pt3dMultAdd (this.v, width, topRight);
+ transformed[2] = this.transformToScreen (bottomRight);
+ };
+
+ var numVisible = this.intersectWithView (transformed);
+
+ if (numVisible == this.VISIBLE_NONE) {
+ return;
+ }
+
+ var dmax = 0;
+ for (var i = 0; i < transformed.length; ++i) {
+ var next = (i + 1) % 4;
+ dmax = Math.max (this.screenDistance (transformed[i], transformed[next]), dmax);
+ }
+
+ // Convert the distance to physical pixels
+ dmax *= this.owner.browser.getDevicePixelScale ();
+
+ if (divisions < this.minDivisions
+ ||
+ (
+ (
+ dmax > this.owner.maxTextureMagnification * (this.tileSize - this.overlap)
+ ) && divisions < this.maxDivisions && divisions < this.maxTesselation
+ )
+ ) {
+ var center = this.pt3dMultAdd ({x: this.u.x + this.v.x, y: this.u.y + this.v.y, z: this.u.z + this.v.z }, width / 2, topLeft);
+ var midTop = this.pt3dMultAdd (this.u, width / 2, topLeft);
+ var midLeft = this.pt3dMultAdd (this.v, width / 2, topLeft);
+
+ var tCenter = this.transformToScreen (center);
+ var tMidLeft = this.transformToScreen (midLeft);
+ var tMidTop = this.transformToScreen (midTop);
+ var tMidRight = this.transformToScreen (this.pt3dMultAdd (this.u, width, midLeft));
+ var tMidBottom = this.transformToScreen (this.pt3dMultAdd (this.v, width, midTop));
+
+ this.generateSubdivisionFace (scene, topLeft, width / 2, divisions + 1, tx * 2, ty * 2, [transformed[0], tMidTop, tCenter, tMidLeft]);
+ this.generateSubdivisionFace (scene, midTop, width / 2, divisions + 1, tx * 2 + 1, ty * 2, [tMidTop, transformed[1], tMidRight, tCenter]);
+ this.generateSubdivisionFace (scene, midLeft, width / 2, divisions + 1, tx * 2, ty * 2 + 1, [tMidLeft, tCenter, tMidBottom, transformed[3]]);
+ this.generateSubdivisionFace (scene, center, width / 2, divisions + 1, tx * 2 + 1, ty * 2 + 1, [tCenter, tMidRight, transformed[2], tMidBottom]);
+ } else {
+ this.generateFace (scene, topLeft, width, tx, ty, divisions);
+ }
+ },
+
+ /**
+ * Tests if the face has had any updated texture
+ * notifications from the tile cache.
+ *
+ * @public
+ */
+ isUpdated : function () {
+ return this.updated;
+ },
+
+ /**
+ * Renders this face into a scene.
+ *
+ * @public
+ * @param {bigshot.WebGLTexturedQuadScene} scene the scene to render into
+ */
+ render : function (scene) {
+ this.updated = false;
+ this.viewportWidth = this.owner.renderer.getViewportWidth ();
+ this.viewportHeight = this.owner.renderer.getViewportHeight ();
+ this.generateSubdivisionFace (scene, this.topLeft, this.width, 0, 0, 0);
+ },
+
+ /**
+ * Performs post-render cleanup.
+ */
+ endRender : function () {
+ this.tileCache.purge ();
+ }
+}
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @class WebGL utility functions.
+ */
+bigshot.WebGLUtil = {
+ /**
+ * Flag indicating whether we want to wrap the WebGL context in a
+ * WebGLDebugUtils.makeDebugContext. Defaults to false.
+ *
+ * @type boolean
+ * @public
+ */
+ debug : false,
+
+ /**
+ * List of context identifiers WebGL may be accessed via.
+ *
+ * @type String[]
+ * @private
+ */
+ contextNames : ["webgl", "experimental-webgl"],
+
+ /**
+ * Utility function for creating a context given a canvas and
+ * a context identifier.
+ * @type WebGLRenderingContext
+ * @private
+ */
+ createContext0 : function (canvas, context) {
+ var gl = this.debug
+ ?
+ WebGLDebugUtils.makeDebugContext(canvas.getContext(context))
+ :
+ canvas.getContext (context);
+ return gl;
+ },
+
+ /**
+ * Creates a WebGL context for the given canvas, if possible.
+ *
+ * @public
+ * @type WebGLRenderingContext
+ * @param {HTMLCanvasElement} canvas the canvas
+ * @return The WebGL context
+ * @throws {Error} If WebGL isn't supported.
+ */
+ createContext : function (canvas) {
+ for (var i = 0; i < this.contextNames.length; ++i) {
+ try {
+ var gl = this.createContext0 (canvas, this.contextNames[i]);
+ if (gl) {
+ return gl;
+ }
+ } catch (e) {
+ }
+ }
+ throw new Error ("Could not initialize WebGL.");
+ },
+
+ /**
+ * Tests whether WebGL is supported.
+ *
+ * @type boolean
+ * @public
+ * @return true If WebGL is supported, false otherwise.
+ */
+ isWebGLSupported : function () {
+ var canvas = document.createElement ("canvas");
+ if (!canvas["width"]) {
+ // Not even canvas support
+ return false;
+ }
+
+ try {
+ this.createContext (canvas);
+ return true;
+ } catch (e) {
+ // No WebGL support
+ return false;
+ }
+ }
+}
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new transformation stack, initialized to the identity transform.
+ *
+ * @class A 3D transformation stack.
+ */
+bigshot.TransformStack = function () {
+ /**
+ * The current transform matrix.
+ *
+ * @type Matrix
+ */
+ this.mvMatrix = null;
+
+ /**
+ * The object-to-world transform matrix stack.
+ *
+ * @type Matrix[]
+ */
+ this.mvMatrixStack = [];
+
+ this.reset ();
+}
+
+bigshot.TransformStack.prototype = {
+ /**
+ * Pushes the current world transform onto the stack
+ * and returns a new, identical one.
+ *
+ * @return the new world transform matrix
+ * @param {Matrix} [matrix] the new world transform.
+ * If omitted, the current is used
+ * @type Matrix
+ */
+ push : function (matrix) {
+ if (matrix) {
+ this.mvMatrixStack.push (matrix.dup());
+ this.mvMatrix = matrix.dup();
+ return mvMatrix;
+ } else {
+ this.mvMatrixStack.push (this.mvMatrix.dup());
+ return mvMatrix;
+ }
+ },
+
+ /**
+ * Pops the last-pushed world transform off the stack, thereby restoring it.
+ *
+ * @type Matrix
+ * @return the previously-pushed matrix
+ */
+ pop : function () {
+ if (this.mvMatrixStack.length == 0) {
+ throw new Error ("Invalid popMatrix!");
+ }
+ this.mvMatrix = this.mvMatrixStack.pop();
+ return mvMatrix;
+ },
+
+ /**
+ * Resets the world transform to the identity transform.
+ */
+ reset : function () {
+ this.mvMatrix = Matrix.I(4);
+ },
+
+ /**
+ * Multiplies the current world transform with a matrix.
+ *
+ * @param {Matrix} matrix the matrix to multiply with
+ */
+ multiply : function (matrix) {
+ this.mvMatrix = matrix.x (this.mvMatrix);
+ },
+
+ /**
+ * Adds a translation to the world transform matrix.
+ *
+ * @param {bigshot.Point3D} vector the translation vector
+ */
+ translate : function (vector) {
+ var m = Matrix.Translation($V([vector.x, vector.y, vector.z])).ensure4x4 ();
+ this.multiply (m);
+ },
+
+ /**
+ * Adds a rotation to the world transform matrix.
+ *
+ * @param {number} ang the angle in degrees to rotate
+ * @param {bigshot.Point3D} vector the rotation vector
+ */
+ rotate : function (ang, vector) {
+ var arad = ang * Math.PI / 180.0;
+ var m = Matrix.Rotation(arad, $V([vector.x, vector.y, vector.z])).ensure4x4 ();
+ this.multiply (m);
+ },
+
+ /**
+ * Adds a rotation around the x-axis to the world transform matrix.
+ *
+ * @param {number} ang the angle in degrees to rotate
+ */
+ rotateX : function (ang) {
+ this.rotate (ang, { x : 1, y : 0, z : 0 });
+ },
+
+ /**
+ * Adds a rotation around the y-axis to the world transform matrix.
+ *
+ * @param {number} ang the angle in degrees to rotate
+ */
+ rotateY : function (ang) {
+ this.rotate (ang, { x : 0, y : 1, z : 0 });
+ },
+
+ /**
+ * Adds a rotation around the z-axis to the world transform matrix.
+ *
+ * @param {number} ang the angle in degrees to rotate
+ */
+ rotateZ : function (ang) {
+ this.rotate (ang, { x : 0, y : 0, z : 1 });
+ },
+
+ /**
+ * Multiplies the current matrix with a
+ * perspective transformation matrix.
+ *
+ * @param {number} fovy vertical field of view
+ * @param {number} aspect viewport aspect ratio
+ * @param {number} znear near image plane
+ * @param {number} zfar far image plane
+ */
+ perspective : function (fovy, aspect, znear, zfar) {
+ var m = makePerspective (fovy, aspect, znear, zfar);
+ this.multiply (m);
+ },
+
+ matrix : function () {
+ return this.mvMatrix;
+ }
+}
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new WebGL wrapper instance.
+ *
+ * @class WebGL wrapper for common {@link bigshot.VRPanorama} uses.
+ * @param {HTMLCanvasElement} canvas_ the canvas
+ * @see #onresize()
+ */
+bigshot.WebGL = function (canvas_) {
+ /**
+ * The html canvas element we'll be rendering in.
+ *
+ * @type HTMLCanvasElement
+ */
+ this.canvas = canvas_;
+
+ /**
+ * Our WebGL context.
+ *
+ * @type WebGLRenderingContext
+ */
+ this.gl = bigshot.WebGLUtil.createContext (this.canvas);
+
+ /**
+ * The current object-to-world transform matrix.
+ *
+ * @type bigshot.TransformStack
+ */
+ this.mvMatrix = new bigshot.TransformStack ();
+
+ /**
+ * The current perspective transform matrix.
+ *
+ * @type bigshot.TransformStack
+ */
+ this.pMatrix = new bigshot.TransformStack ();
+
+ /**
+ * The current shader program.
+ */
+ this.shaderProgram = null;
+
+ this.onresize ();
+}
+
+bigshot.WebGL.prototype = {
+ /**
+ * Must be called when the canvas element is resized.
+ *
+ * @public
+ */
+ onresize : function () {
+ this.gl.viewportWidth = this.canvas.width;
+ this.gl.viewportHeight = this.canvas.height;
+ },
+
+ /**
+ * Fragment shader. Taken from the "Learning WebGL" lessons:
+ * http://learningwebgl.com/blog/?p=571
+ */
+ fragmentShader :
+ "#ifdef GL_ES\n" +
+ " precision highp float;\n" +
+ "#endif\n" +
+ "\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "\n" +
+ "uniform sampler2D uSampler;\n" +
+ "\n" +
+ "void main(void) {\n" +
+ " gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));\n" +
+ "}\n",
+
+ /**
+ * Vertex shader. Taken from the "Learning WebGL" lessons:
+ * http://learningwebgl.com/blog/?p=571
+ */
+ vertexShader :
+ "attribute vec3 aVertexPosition;\n" +
+ "attribute vec2 aTextureCoord;\n" +
+ "\n" +
+ "uniform mat4 uMVMatrix;\n" +
+ "uniform mat4 uPMatrix;\n" +
+ "\n" +
+ "varying vec2 vTextureCoord;\n" +
+ "\n" +
+ "void main(void) {\n" +
+ " gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);\n" +
+ " vTextureCoord = aTextureCoord;\n" +
+ "}",
+
+ /**
+ * Creates a new shader.
+ *
+ * @type WebGLShader
+ * @param {String} source the source code
+ * @param {int} type the shader type, one of WebGLRenderingContext.FRAGMENT_SHADER or
+ * WebGLRenderingContext.VERTEX_SHADER
+ */
+ createShader : function (source, type) {
+ var shader = this.gl.createShader (type);
+ this.gl.shaderSource (shader, source);
+ this.gl.compileShader (shader);
+
+ if (!this.gl.getShaderParameter (shader, this.gl.COMPILE_STATUS)) {
+ alert (this.gl.getShaderInfoLog (shader));
+ return null;
+ }
+
+ return shader;
+ },
+
+ /**
+ * Creates a new fragment shader.
+ *
+ * @type WebGLShader
+ * @param {String} source the source code
+ */
+ createFragmentShader : function (source) {
+ return this.createShader (source, this.gl.FRAGMENT_SHADER);
+ },
+
+ /**
+ * Creates a new vertex shader.
+ *
+ * @type WebGLShader
+ * @param {String} source the source code
+ */
+ createVertexShader : function (source) {
+ return this.createShader (source, this.gl.VERTEX_SHADER);
+ },
+
+ /**
+ * Initializes the shaders.
+ */
+ initShaders : function () {
+ this.shaderProgram = this.gl.createProgram ();
+ this.gl.attachShader (this.shaderProgram, this.createVertexShader (this.vertexShader));
+ this.gl.attachShader (this.shaderProgram, this.createFragmentShader (this.fragmentShader));
+ this.gl.linkProgram (this.shaderProgram);
+
+ if (!this.gl.getProgramParameter (this.shaderProgram, this.gl.LINK_STATUS)) {
+ throw new Error ("Could not initialise shaders");
+ return;
+ }
+
+ this.gl.useProgram (this.shaderProgram);
+
+ this.shaderProgram.vertexPositionAttribute = this.gl.getAttribLocation (this.shaderProgram, "aVertexPosition");
+ this.gl.enableVertexAttribArray (this.shaderProgram.vertexPositionAttribute);
+
+ this.shaderProgram.textureCoordAttribute = this.gl.getAttribLocation (this.shaderProgram, "aTextureCoord");
+ this.gl.enableVertexAttribArray (this.shaderProgram.textureCoordAttribute);
+
+ this.shaderProgram.pMatrixUniform = this.gl.getUniformLocation(this.shaderProgram, "uPMatrix");
+ this.shaderProgram.mvMatrixUniform = this.gl.getUniformLocation(this.shaderProgram, "uMVMatrix");
+ this.shaderProgram.samplerUniform = this.gl.getUniformLocation(this.shaderProgram, "uSampler");
+ },
+
+
+ /**
+ * Sets the matrix parameters ("uniforms", since the variables are declared as uniform) in the shaders.
+ */
+ setMatrixUniforms : function () {
+ this.gl.uniformMatrix4fv (this.shaderProgram.pMatrixUniform, false, new Float32Array(this.pMatrix.matrix().flatten()));
+ this.gl.uniformMatrix4fv (this.shaderProgram.mvMatrixUniform, false, new Float32Array(this.mvMatrix.matrix().flatten()));
+ },
+
+ /**
+ * Creates a texture from an image.
+ *
+ * @param {HTMLImageElement or HTMLCanvasElement} image the image
+ * @type WebGLTexture
+ * @return An initialized texture
+ */
+ createImageTextureFromImage : function (image, minFilter, magFilter) {
+ var texture = this.gl.createTexture();
+ this.handleImageTextureLoaded (this, texture, image, minFilter, magFilter);
+ return texture;
+ },
+
+ /**
+ * Creates a texture from a source url.
+ *
+ * @param {String} source the URL of the image
+ * @return WebGLTexture
+ */
+ createImageTextureFromSource : function (source, minFilter, magFilter) {
+ var image = new Image();
+ var texture = this.gl.createTexture();
+
+ var that = this;
+ image.onload = function () {
+ that.handleImageTextureLoaded (that, texture, image, minFilter, magFilter);
+ }
+
+ image.src = source;
+
+ return texture;
+ },
+
+ /**
+ * Uploads the image data to the texture memory. Called when the texture image
+ * has finished loading.
+ *
+ * @private
+ */
+ handleImageTextureLoaded : function (that, texture, image, minFilter, magFilter) {
+ that.gl.bindTexture (that.gl.TEXTURE_2D, texture);
+ that.gl.texImage2D (that.gl.TEXTURE_2D, 0, that.gl.RGBA, that.gl.RGBA, that.gl.UNSIGNED_BYTE, image);
+ that.gl.texParameteri (that.gl.TEXTURE_2D, that.gl.TEXTURE_MAG_FILTER, magFilter ? magFilter : that.gl.NEAREST);
+ that.gl.texParameteri (that.gl.TEXTURE_2D, that.gl.TEXTURE_MIN_FILTER, minFilter ? minFilter : that.gl.NEAREST);
+ that.gl.texParameteri (that.gl.TEXTURE_2D, that.gl.TEXTURE_WRAP_S, that.gl.CLAMP_TO_EDGE);
+ that.gl.texParameteri (that.gl.TEXTURE_2D, that.gl.TEXTURE_WRAP_T, that.gl.CLAMP_TO_EDGE);
+ if (minFilter == that.gl.NEAREST_MIPMAP_NEAREST
+ || minFilter == that.gl.LINEAR_MIPMAP_NEAREST
+ || minFilter == that.gl.NEAREST_MIPMAP_LINEAR
+ || minFilter == that.gl.LINEAR_MIPMAP_LINEAR) {
+ that.gl.generateMipmap(that.gl.TEXTURE_2D);
+ }
+
+ that.gl.bindTexture (that.gl.TEXTURE_2D, null);
+ },
+
+ deleteTexture : function (texture) {
+ this.gl.deleteTexture (texture);
+ },
+
+ dispose : function () {
+ delete this.canvas;
+ delete this.gl;
+ }
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @class Abstract base for 3d rendering system.
+ */
+bigshot.VRRenderer = function () {
+}
+
+bigshot.VRRenderer.prototype = {
+ /**
+ * Creates a new {@link bigshot.VRTileCache}, appropriate for the rendering system.
+ *
+ * @param {function()} onloaded function that is called whenever a texture tile has been loaded
+ * @param {function()} onCacheInit function that is called when the texture cache is fully initialized
+ * @param {bigshot.VRPanoramaParameters} parameters the parameters for the panorama
+ */
+ createTileCache : function (onloaded, onCacheInit, parameters) {},
+
+ /**
+ * Creates a bigshot.TexturedQuadScene.
+ */
+ createTexturedQuadScene : function () {},
+
+ /**
+ * Creates a bigshot.TexturedQuad.
+ *
+ * @param {bigshot.Point3D} p the top-left corner of the quad
+ * @param {bigshot.Point3D} u a vector going along the top edge of the quad
+ * @param {bigshot.Point3D} v a vector going down the left edge of the quad
+ * @param {Object} texture a texture to use for the quad. The texture type may vary among different
+ * VRRenderer implementations. The VRTileCache that is created using the createTileCache method will
+ * supply the correct type.
+ */
+ createTexturedQuad : function (p, u, v, texture) {},
+
+ /**
+ * Returns the viewport width, in pixels.
+ *
+ * @type int
+ */
+ getViewportWidth : function () {},
+
+ /**
+ * Returns the viewport height, in pixels.
+ *
+ * @type int
+ */
+ getViewportHeight : function () {},
+
+ /**
+ * Transforms a vector to world coordinates.
+ *
+ * @param {bigshot.Point3D} v the view-space point to transform
+ */
+ transformToWorld : function (v) {},
+
+ /**
+ * Transforms a world vector to screen coordinates.
+ *
+ * @param {bigshot.Point3D} worldVector the world-space point to transform
+ */
+ transformWorldToScreen : function (worldVector) {},
+
+ /**
+ * Transforms a 3D vector to screen coordinates.
+ *
+ * @param {bigshot.Point3D} vector the vector to transform.
+ * If it is already in homogenous coordinates (4-element array)
+ * the transformation is faster. Otherwise it will be converted.
+ */
+ transformToScreen : function (vector) {},
+
+ /**
+ * Disposes the renderer and associated resources.
+ */
+ dispose : function () {},
+
+ /**
+ * Called to begin a render.
+ *
+ * @param {bigshot.Rotation} rotation the rotation of the viewer
+ * @param {number} fov the vertical field of view, in degrees
+ * @param {bigshot.Point3D} translation the position of the viewer in world space
+ * @param {bigshot.Rotation} rotationOffsets the rotation to apply to the VR cube
+ * before the viewer rotation is applied
+ */
+ beginRender : function (rotation, fov, translation, rotationOffsets) {},
+
+ /**
+ * Called to end a render.
+ */
+ endRender : function () {},
+
+ /**
+ * Called by client code to notify the renderer that the viewport has been resized.
+ */
+ onresize : function () {},
+
+ /**
+ * Resizes the viewport.
+ *
+ * @param {int} w the new width of the viewport, in pixels
+ * @param {int} h the new height of the viewport, in pixels
+ */
+ resize : function (w, h) {},
+
+ /**
+ * Gets the container element for the renderer. This is used
+ * when calling the requestAnimationFrame API.
+ */
+ getElement : function () {}
+}
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @class Abstract VR renderer base class.
+ */
+bigshot.AbstractVRRenderer = function () {
+}
+
+bigshot.AbstractVRRenderer.prototype = {
+ /**
+ * Transforms a vector to world coordinates.
+ *
+ * @param {bigshot.Point3D} vector the vector to transform
+ */
+ transformToWorld : function transformToWorld (vector) {
+ var world = this.mvMatrix.matrix ().xPoint3Dhom1 (vector);
+
+ return world;
+ },
+
+ /**
+ * Transforms a world vector to screen coordinates.
+ *
+ * @param {bigshot.Point3D} world the world-vector to transform
+ */
+ transformWorldToScreen : function transformWorldToScreen (world) {
+ if (world.z > 0) {
+ return null;
+ }
+
+ var screen = this.pMatrix.matrix ().xPoint3Dhom (world);
+ if (Math.abs (screen.w) < Sylvester.precision) {
+ return null;
+ }
+
+ var sx = screen.x;
+ var sy = screen.y;
+ var sz = screen.z;
+ var vw = this.getViewportWidth ();
+ var vh = this.getViewportHeight ();
+
+ var r = {
+ x: (vw / 2) * sx / sz + vw / 2,
+ y: - (vh / 2) * sy / sz + vh / 2
+ };
+ return r;
+ },
+
+ /**
+ * Transforms a vector to screen coordinates.
+ *
+ * @param {bigshot.Point3D} vector the vector to transform
+ * @return the transformed vector, or null if the vector is nearer than the near-z plane.
+ */
+ transformToScreen : function transformToScreen (vector) {
+ var sel = this.mvpMatrix.xPoint3Dhom (vector);
+
+ if (sel.z < 0) {
+ return null;
+ }
+
+ var sz = sel.w;
+
+ if (Math.abs (sel.w) < Sylvester.precision) {
+ return null;
+ }
+
+ var sx = sel.x;
+ var sy = sel.y;
+ var vw = this.getViewportWidth ();
+ var vh = this.getViewportHeight ();
+
+ var r = {
+ x: (vw / 2) * sx / sz + vw / 2,
+ y: - (vh / 2) * sy / sz + vh / 2
+ };
+
+ return r;
+ }
+}
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @class CSS 3D Transform-based renderer.
+ * @param {HTMLElement} _container the HTML container element for the render viewport
+ *
+ * @augments bigshot.VRRenderer
+ */
+bigshot.CSS3DVRRenderer = function (_container) {
+ this.container = _container;
+ this.canvasOrigin = document.createElement ("div");
+
+ this.canvasOrigin.style.WebkitTransformOrigin = "0px 0px 0px";
+ this.canvasOrigin.style.WebkitTransformStyle = "preserve-3d";
+ this.canvasOrigin.style.WebkitPerspective= "600px";
+
+ this.canvasOrigin.style.position = "relative";
+ this.canvasOrigin.style.left = "50%";
+ this.canvasOrigin.style.top = "50%";
+
+ this.container.appendChild (this.canvasOrigin);
+
+ this.viewport = document.createElement ("div");
+ this.viewport.style.WebkitTransformOrigin = "0px 0px 0px";
+ this.viewport.style.WebkitTransformStyle = "preserve-3d";
+ this.canvasOrigin.appendChild (this.viewport);
+
+ this.world = document.createElement ("div");
+ this.world.style.WebkitTransformOrigin = "0px 0px 0px";
+ this.world.style.WebkitTransformStyle = "preserve-3d";
+ this.viewport.appendChild (this.world);
+
+ this.browser.removeAllChildren (this.world);
+
+ this.view = null;
+
+ this.mvMatrix = new bigshot.TransformStack ();
+
+ this.yaw = 0;
+ this.pitch = 0;
+ this.fov = 0;
+ this.pMatrix = new bigshot.TransformStack ();
+
+ this.onresize = function () {
+ };
+
+ this.viewportSize = null;
+};
+
+bigshot.CSS3DVRRenderer.prototype = {
+ browser : new bigshot.Browser (),
+
+ dispose : function () {
+
+ },
+
+ createTileCache : function (onloaded, onCacheInit, parameters) {
+ return new bigshot.ImageVRTileCache (onloaded, onCacheInit, parameters);
+ },
+
+ createTexturedQuadScene : function () {
+ return new bigshot.CSS3DTexturedQuadScene (this.world, 128, this.view);
+ },
+
+ createTexturedQuad : function (p, u, v, texture) {
+ return new bigshot.CSS3DTexturedQuad (p, u, v, texture);
+ },
+
+ getElement : function () {
+ return this.container;
+ },
+
+ supportsUpdate : function () {
+ return false;
+ },
+
+ getViewportWidth : function () {
+ if (this.viewportSize) {
+ return this.viewportSize.w;
+ }
+ return this.browser.getElementSize (this.container).w;
+ },
+
+ getViewportHeight : function () {
+ if (this.viewportSize) {
+ return this.viewportSize.h;
+ }
+ return this.browser.getElementSize (this.container).h;
+ },
+
+ onresize : function () {
+ },
+
+ resize : function (w, h) {
+ if (this.container.style.width != "") {
+ this.container.style.width = w + "px";
+ }
+ if (this.container.style.height != "") {
+ this.container.style.height = h + "px";
+ }
+ },
+
+ beginRender : function (rotation, fov, translation, rotationOffsets) {
+ this.viewportSize = this.browser.getElementSize (this.container);
+
+ this.yaw = rotation.y;
+ this.pitch = rotation.p;
+ this.fov = fov;
+
+ var halfFovInRad = 0.5 * fov * Math.PI / 180;
+ var halfHeight = this.getViewportHeight () / 2;
+ var perspectiveDistance = halfHeight / Math.tan (halfFovInRad);
+
+ this.mvMatrix.reset ();
+
+ this.view = translation;
+ this.mvMatrix.translate (this.view);
+
+
+ this.mvMatrix.rotateZ (rotationOffsets.r);
+ this.mvMatrix.rotateX (rotationOffsets.p);
+ this.mvMatrix.rotateY (rotationOffsets.y);
+
+ this.mvMatrix.rotateY (this.yaw);
+ this.mvMatrix.rotateX (this.pitch);
+
+
+ this.pMatrix.reset ();
+ this.pMatrix.perspective (this.fov, this.getViewportWidth () / this.getViewportHeight (), 0.1, 100.0);
+
+ this.mvpMatrix = this.pMatrix.matrix ().multiply (this.mvMatrix.matrix ());
+
+ this.canvasOrigin.style.WebkitPerspective= perspectiveDistance + "px";
+
+ for (var i = this.world.children.length - 1; i >= 0; --i) {
+ this.world.children[i].inWorld = 1;
+ }
+
+ this.world.style.WebkitTransform =
+ "rotate3d(1,0,0," + (-rotation.p) + "deg) " +
+ "rotate3d(0,1,0," + rotation.y + "deg) " +
+ "rotate3d(0,1,0," + (rotationOffsets.y) + "deg) " +
+ "rotate3d(1,0,0," + (-rotationOffsets.p) + "deg) " +
+ "rotate3d(0,0,1," + (-rotationOffsets.r) + "deg) ";
+ this.world.style.WebkitTransformStyle = "preserve-3d";
+ this.world.style.WebKitBackfaceVisibility = "hidden";
+
+ this.viewport.style.WebkitTransform =
+ "translateZ(" + perspectiveDistance + "px)";
+ },
+
+ endRender : function () {
+ for (var i = this.world.children.length - 1; i >= 0; --i) {
+ var child = this.world.children[i];
+ if (!child.inWorld || child.inWorld != 2) {
+ delete child.inWorld;
+ this.world.removeChild (child);
+ }
+ }
+
+ this.viewportSize = null;
+ }
+};
+
+bigshot.Object.extend (bigshot.CSS3DVRRenderer, bigshot.AbstractVRRenderer);
+bigshot.Object.validate ("bigshot.CSS3DVRRenderer", bigshot.VRRenderer);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a textured quad object.
+ *
+ * @class An abstraction for textured quads. Used in the
+ * {@link bigshot.CSS3DTexturedQuadScene}.
+ *
+ * @param {bigshot.Point3D} p the top-left corner of the quad
+ * @param {bigshot.Point3D} u vector pointing from p along the top edge of the quad
+ * @param {bigshot.Point3D} v vector pointing from p along the left edge of the quad
+ * @param {HTMLImageElement} the image to use.
+ */
+bigshot.CSS3DTexturedQuad = function (p, u, v, image) {
+ this.p = p;
+ this.u = u;
+ this.v = v;
+ this.image = image;
+}
+
+bigshot.CSS3DTexturedQuad.prototype = {
+ /**
+ * Computes the cross product of two vectors.
+ *
+ * @param {bigshot.Point3D} a the first vector
+ * @param {bigshot.Point3D} b the second vector
+ * @type bigshot.Point3D
+ * @return the cross product
+ */
+ crossProduct : function crossProduct (a, b) {
+ return {
+ x : a.y*b.z-a.z*b.y,
+ y : a.z*b.x-a.x*b.z,
+ z : a.x*b.y-a.y*b.x
+ };
+ },
+
+ /**
+ * Stringifies a vector as the x, y, and z components
+ * separated by commas.
+ *
+ * @param {bigshot.Point3D} u the vector
+ * @type String
+ * @return the stringified vector
+ */
+ vecToStr : function vecToStr (u) {
+ return (u.x) + "," + (u.y) + "," + (u.z);
+ },
+
+ /**
+ * Creates a CSS3D matrix3d transform from
+ * an origin point and two basis vectors
+ *
+ * @param {bigshot.Point3D} tl the top left corner
+ * @param {bigshot.Point3D} u the vector pointing along the top edge
+ * @param {bigshot.Point3D} y the vector pointing down the left edge
+ * @type String
+ * @return the matrix3d statement
+ */
+ quadTransform : function quadTransform (tl, u, v) {
+ var w = this.crossProduct (u, v);
+ var res =
+ "matrix3d(" +
+ this.vecToStr (u) + ",0," +
+ this.vecToStr (v) + ",0," +
+ this.vecToStr (w) + ",0," +
+ this.vecToStr (tl) + ",1)";
+ return res;
+ },
+
+ /**
+ * Computes the norm of a vector.
+ *
+ * @param {bigshot.Point3D} vec the vector
+ */
+ norm : function norm (vec) {
+ return Math.sqrt (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);
+ },
+
+ /**
+ * Renders the quad.
+ *
+ * @param {HTMLElement} world the world element
+ * @param {number} scale the scale factor to apply to world space to get CSS pixel distances
+ * @param {bigshot.Point3D} view the viewer position in world space
+ */
+ render : function render (world, scale, view) {
+ var s = scale / (this.image.width - 1);
+ var ps = scale * 1.0;
+ var p = this.p;
+ var u = this.u;
+ var v = this.v;
+
+ this.image.style.position = "absolute";
+ if (!this.image.inWorld || this.image.inWorld != 1) {
+ world.appendChild (this.image);
+ }
+ this.image.inWorld = 2;
+ this.image.style.WebkitTransformOrigin = "0px 0px 0px";
+ this.image.style.WebkitTransform =
+ this.quadTransform ({
+ x : (p.x + view.x) * ps,
+ y : (-p.y + view.y) * ps,
+ z : (p.z + view.z) * ps
+ }, {
+ x : u.x * s,
+ y : -u.y * s,
+ z : u.z * s
+ }, {
+ x : v.x * s,
+ y : -v.y * s,
+ z : v.z * s
+ });
+ }
+}
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a textured quad scene.
+ *
+ * @param {HTMLElement} world element used as container for
+ * the world coordinate system.
+ * @param {number} scale the scaling factor to use to avoid
+ * numeric errors.
+ * @param {bigshot.Point3D} view the 3d-coordinates of the viewer
+ *
+ * @class A scene consisting of a number of quads, all with
+ * a unique texture. Used by the {@link bigshot.VRPanorama} to render the VR cube.
+ *
+ * @see bigshot.CSS3DTexturedQuad
+ */
+bigshot.CSS3DTexturedQuadScene = function (world, scale, view) {
+ this.quads = new Array ();
+ this.world = world;
+ this.scale = scale;
+ this.view = view;
+}
+
+bigshot.CSS3DTexturedQuadScene.prototype = {
+ /**
+ * Adds a new quad to the scene.
+ *
+ * @param {bigshot.TexturedQuad} quad the quad to add to the scene
+ */
+ addQuad : function (quad) {
+ this.quads.push (quad);
+ },
+
+ /**
+ * Renders all quads.
+ */
+ render : function () {
+ for (var i = 0; i < this.quads.length; ++i) {
+ this.quads[i].render (this.world, this.scale, this.view);
+ }
+ }
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @class Abstract base for textured quad scenes.
+ */
+bigshot.TexturedQuadScene = function () {
+}
+
+bigshot.TexturedQuadScene.prototype = {
+ /**
+ * Adds a quad to the scene.
+ */
+ addQuad : function (quad) {},
+
+ /**
+ * Renders the scene.
+ */
+ render : function () {}
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @class WebGL renderer.
+ */
+bigshot.WebGLVRRenderer = function (container) {
+ this.container = container;
+
+ this.canvas = document.createElement ("canvas");
+ this.canvas.width = 480;
+ this.canvas.height = 480;
+ this.canvas.style.position = "absolute";
+ this.container.appendChild (this.canvas);
+
+ this.webGl = new bigshot.WebGL (this.canvas);
+ this.webGl.initShaders ();
+ this.webGl.gl.clearColor(0.0, 0.0, 0.0, 1.0);
+ this.webGl.gl.blendFunc (this.webGl.gl.ONE, this.webGl.gl.ZERO);
+ this.webGl.gl.enable (this.webGl.gl.BLEND);
+ this.webGl.gl.disable (this.webGl.gl.DEPTH_TEST);
+ this.webGl.gl.clearDepth (1.0);
+
+ var that = this;
+ this.buffers = new bigshot.TimedWeakReference (function () {
+ return that.setupBuffers ();
+ }, function (heldObject) {
+ that.disposeBuffers (heldObject);
+ }, 1000);
+}
+
+bigshot.WebGLVRRenderer.prototype = {
+ createTileCache : function (onloaded, onCacheInit, parameters) {
+ return new bigshot.TextureTileCache (onloaded, onCacheInit, parameters, this.webGl);
+ },
+
+ createTexturedQuadScene : function () {
+ return new bigshot.WebGLTexturedQuadScene (this.webGl, this.buffers);
+ },
+
+ setupBuffers : function () {
+ var vertexPositionBuffer = this.webGl.gl.createBuffer();
+
+ var textureCoordBuffer = this.webGl.gl.createBuffer();
+ this.webGl.gl.bindBuffer(this.webGl.gl.ARRAY_BUFFER, textureCoordBuffer);
+ var textureCoords = [
+ // Front face
+ 0.0, 0.0,
+ 1.0, 0.0,
+ 1.0, 1.0,
+ 0.0, 1.0
+ ];
+ this.webGl.gl.bufferData (this.webGl.gl.ARRAY_BUFFER, new Float32Array (textureCoords), this.webGl.gl.STATIC_DRAW);
+
+ var vertexIndexBuffer = this.webGl.gl.createBuffer();
+ this.webGl.gl.bindBuffer(this.webGl.gl.ELEMENT_ARRAY_BUFFER, vertexIndexBuffer);
+ var vertexIndexes = [
+ 0, 2, 1,
+ 0, 3, 2
+ ];
+ this.webGl.gl.bufferData(this.webGl.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array (vertexIndexes), this.webGl.gl.STATIC_DRAW);
+
+ this.webGl.gl.bindBuffer(this.webGl.gl.ARRAY_BUFFER, textureCoordBuffer);
+ this.webGl.gl.vertexAttribPointer(this.webGl.shaderProgram.textureCoordAttribute, 2, this.webGl.gl.FLOAT, false, 0, 0);
+
+ this.webGl.gl.bindBuffer(this.webGl.gl.ARRAY_BUFFER, vertexPositionBuffer);
+ this.webGl.gl.vertexAttribPointer(this.webGl.shaderProgram.vertexPositionAttribute, 3, this.webGl.gl.FLOAT, false, 0, 0);
+
+ return {
+ vertexPositionBuffer : vertexPositionBuffer,
+ textureCoordBuffer : textureCoordBuffer,
+ vertexIndexBuffer : vertexIndexBuffer
+ };
+ },
+
+ dispose : function () {
+ this.buffers.dispose ();
+ this.container.removeChild (this.canvas);
+ delete this.canvas;
+ this.webGl.dispose ();
+ delete this.webGl;
+ },
+
+ disposeBuffers : function (buffers) {
+ this.webGl.gl.deleteBuffer (buffers.vertexPositionBuffer);
+ this.webGl.gl.deleteBuffer (buffers.vertexIndexBuffer);
+ this.webGl.gl.deleteBuffer (buffers.textureCoordBuffer);
+ },
+
+ getElement : function () {
+ return this.canvas;
+ },
+
+ supportsUpdate : function () {
+ return false;
+ },
+
+ createTexturedQuad : function (p, u, v, texture) {
+ return new bigshot.WebGLTexturedQuad (p, u, v, texture);
+ },
+
+ getViewportWidth : function () {
+ return this.webGl.gl.viewportWidth;
+ },
+
+ getViewportHeight : function () {
+ return this.webGl.gl.viewportHeight;
+ },
+
+ beginRender : function (rotation, fov, translation, rotationOffsets) {
+ this.webGl.gl.viewport (0, 0, this.webGl.gl.viewportWidth, this.webGl.gl.viewportHeight);
+
+ this.webGl.pMatrix.reset ();
+ this.webGl.pMatrix.perspective (fov, this.webGl.gl.viewportWidth / this.webGl.gl.viewportHeight, 0.1, 100.0);
+
+ this.webGl.mvMatrix.reset ();
+ this.webGl.mvMatrix.translate (translation);
+ this.webGl.mvMatrix.rotateZ (rotationOffsets.r);
+ this.webGl.mvMatrix.rotateX (rotationOffsets.p);
+ this.webGl.mvMatrix.rotateY (rotationOffsets.y);
+ this.webGl.mvMatrix.rotateY (rotation.y);
+ this.webGl.mvMatrix.rotateX (rotation.p);
+
+ this.mvMatrix = this.webGl.mvMatrix;
+ this.pMatrix = this.webGl.pMatrix;
+ this.mvpMatrix = this.pMatrix.matrix ().multiply (this.mvMatrix.matrix ());
+ },
+
+ endRender : function () {
+
+ },
+
+ resize : function (w, h) {
+ this.canvas.width = w;
+ this.canvas.height = h;
+ if (this.container.style.width != "") {
+ this.container.style.width = w + "px";
+ }
+ if (this.container.style.height != "") {
+ this.container.style.height = h + "px";
+ }
+ },
+
+ onresize : function () {
+ this.webGl.onresize ();
+ }
+}
+
+bigshot.Object.extend (bigshot.WebGLVRRenderer, bigshot.AbstractVRRenderer);
+bigshot.Object.validate ("bigshot.WebGLVRRenderer", bigshot.VRRenderer);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @class Abstract base for textured quads.
+ */
+bigshot.TexturedQuad = function () {
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a textured quad object.
+ *
+ * @class An abstraction for textured quads. Used in the
+ * {@link bigshot.WebGLTexturedQuadScene}.
+ *
+ * @param {bigshot.Point3D} p the top-left corner of the quad
+ * @param {bigshot.Point3D} u vector pointing from p along the top edge of the quad
+ * @param {bigshot.Point3D} v vector pointing from p along the left edge of the quad
+ * @param {WebGLTexture} the texture to use.
+ */
+bigshot.WebGLTexturedQuad = function (p, u, v, texture) {
+ this.p = p;
+ this.u = u;
+ this.v = v;
+ this.texture = texture;
+}
+
+bigshot.WebGLTexturedQuad.prototype = {
+
+ /**
+ * Renders the quad using the given {@link bigshot.WebGL} instance.
+ * Currently creates, fills, draws with and then deletes three buffers -
+ * not very efficient, but works.
+ *
+ * @param {bigshot.WebGL} webGl the WebGL wrapper instance to use for rendering.
+ */
+ render : function (webGl, vertexPositionBuffer, textureCoordBuffer, vertexIndexBuffer) {
+ webGl.gl.bindBuffer(webGl.gl.ARRAY_BUFFER, vertexPositionBuffer);
+ var vertices = [
+ this.p.x, this.p.y, this.p.z,
+ this.p.x + this.u.x, this.p.y + this.u.y, this.p.z + this.u.z,
+ this.p.x + this.u.x + this.v.x, this.p.y + this.u.y + this.v.y, this.p.z + this.u.z + this.v.z,
+ this.p.x + this.v.x, this.p.y + this.v.y, this.p.z + this.v.z
+ ];
+ webGl.gl.bufferData(webGl.gl.ARRAY_BUFFER, new Float32Array (vertices), webGl.gl.STATIC_DRAW);
+
+ webGl.gl.activeTexture(webGl.gl.TEXTURE0);
+ webGl.gl.bindTexture(webGl.gl.TEXTURE_2D, this.texture);
+ webGl.gl.uniform1i(webGl.shaderProgram.samplerUniform, 0);
+
+ webGl.gl.bindBuffer(webGl.gl.ELEMENT_ARRAY_BUFFER, vertexIndexBuffer);
+ webGl.gl.drawElements(webGl.gl.TRIANGLES, 6, webGl.gl.UNSIGNED_SHORT, 0);
+
+ webGl.gl.bindTexture(webGl.gl.TEXTURE_2D, null);
+ }
+}
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a textured quad scene.
+ *
+ * @param {bigshot.WebGL} webGl the webGl instance to use for rendering.
+ *
+ * @class A "scene" consisting of a number of quads, all with
+ * a unique texture. Used by the {@link bigshot.VRPanorama} to render the VR cube.
+ *
+ * @see bigshot.WebGLTexturedQuad
+ */
+bigshot.WebGLTexturedQuadScene = function (webGl, buffers) {
+ this.quads = new Array ();
+ this.webGl = webGl;
+ this.buffers = buffers;
+}
+
+bigshot.WebGLTexturedQuadScene.prototype = {
+ /**
+ * Adds a new quad to the scene.
+ */
+ addQuad : function (quad) {
+ this.quads.push (quad);
+ },
+
+ /**
+ * Renders all quads.
+ */
+ render : function () {
+ var b = this.buffers.get ();
+ var vertexPositionBuffer = b.vertexPositionBuffer;
+ var textureCoordBuffer = b.textureCoordBuffer;
+ var vertexIndexBuffer = b.vertexIndexBuffer;
+
+ this.webGl.setMatrixUniforms();
+
+ for (var i = 0; i < this.quads.length; ++i) {
+ this.quads[i].render (this.webGl, vertexPositionBuffer, textureCoordBuffer, vertexIndexBuffer);
+ }
+ }
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new VR panorama parameter object and populates it with default values for
+ * all values not explicitly given.
+ *
+ * @class VRPanoramaParameters parameter object.
+ * You need not set any fields that can be read from the image descriptor that
+ * MakeImagePyramid creates. See the {@link bigshot.VRPanorama}
+ * documentation for required parameters.
+ *
+ * <p>Usage:
+ *
+ * @example
+ * var bvr = new bigshot.VRPanorama (
+ * new bigshot.VRPanoramaParameters ({
+ * basePath : "/bigshot.php?file=myvr.bigshot",
+ * fileSystemType : "archive",
+ * container : document.getElementById ("bigshot_canvas")
+ * }));
+ * @param values named parameter map, see the fields below for parameter names and types.
+ * @see bigshot.VRPanorama
+ */
+bigshot.VRPanoramaParameters = function (values) {
+ /**
+ * Size of low resolution preview image along the longest image
+ * dimension. The preview is assumed to have the same aspect
+ * ratio as the full image (specified by width and height).
+ *
+ * @default <i>Optional</i> set by MakeImagePyramid and loaded from descriptor
+ * @type int
+ * @public
+ */
+ this.posterSize = 0;
+
+ /**
+ * Url for the image tile to show while the tile is loading and no
+ * low-resolution preview is available.
+ *
+ * @default <code>null</code>, which results in an all-black image
+ * @type String
+ * @public
+ */
+ this.emptyImage = null;
+
+ /**
+ * Suffix to append to the tile filenames. Typically <code>".jpg"</code> or
+ * <code>".png"</code>.
+ *
+ * @default <i>Optional</i> set by MakeImagePyramid and loaded from descriptor
+ * @type String
+ */
+ this.suffix = null;
+
+ /**
+ * The width of the full image; in pixels.
+ *
+ * @default <i>Optional</i> set by MakeImagePyramid and loaded from descriptor
+ * @type int
+ */
+ this.width = 0;
+
+ /**
+ * The height of the full image; in pixels.
+ *
+ * @default <i>Optional</i> set by MakeImagePyramid and loaded from descriptor
+ * @type int
+ */
+ this.height = 0;
+
+ /**
+ * For {@link bigshot.VRPanorama}, the {@code div} to render into.
+ *
+ * @type HTMLDivElement
+ */
+ this.container = null;
+
+ /**
+ * The maximum number of times to split a cube face into four quads.
+ *
+ * @type int
+ * @default <i>Optional</i> set by MakeImagePyramid and loaded from descriptor
+ */
+ this.maxTesselation = -1;
+
+ /**
+ * Size of one tile in pixels.
+ *
+ * @type int
+ * @default <i>Optional</i> set by MakeImagePyramid and loaded from descriptor
+ */
+ this.tileSize = 0;
+
+ /**
+ * Tile overlap. Not implemented.
+ *
+ * @type int
+ * @default <i>Optional</i> set by MakeImagePyramid and loaded from descriptor
+ */
+ this.overlap = 0;
+
+ /**
+ * Base path for the image. This is filesystem dependent; but for the two most common cases
+ * the following should be set
+ *
+ * <ul>
+ * <li><b>archive</b>= The basePath is <code>"&lt;path&gt;/bigshot.php?file=&lt;path-to-bigshot-archive-relative-to-bigshot.php&gt;"</code>;
+ * for example; <code>"/bigshot.php?file=images/bigshot-sample.bigshot"</code>.
+ * <li><b>folder</b>= The basePath is <code>"&lt;path-to-image-folder&gt;"</code>;
+ * for example; <code>"/images/bigshot-sample"</code>.
+ * </ul>
+ *
+ * @type String
+ */
+ this.basePath = null;
+
+ /**
+ * The file system type. Used to create a filesystem instance unless
+ * the fileSystem field is set. Possible values are <code>"archive"</code>,
+ * <code>"folder"</code> or <code>"dzi"</code>.
+ *
+ * @type String
+ * @default "folder"
+ */
+ this.fileSystemType = "folder";
+
+ /**
+ * A reference to a filesystem implementation. If set; it overrides the
+ * fileSystemType field.
+ *
+ * @default set depending on value of bigshot.VRPanoramaParameters#fileSystemType
+ * @type bigshot.FileSystem
+ */
+ this.fileSystem = null;
+
+ /**
+ * Object used to load data files.
+ *
+ * @default bigshot.DefaultDataLoader
+ * @type bigshot.DataLoader
+ */
+ this.dataLoader = new bigshot.DefaultDataLoader ();
+
+ /**
+ * The maximum magnification for the texture tiles making up the VR cube.
+ * Used for level-of-detail tesselation.
+ * A value of 1.0 means that textures will never be stretched (one texture pixel will
+ * always be at most one screen pixel), unless there is no more detailed texture available.
+ * A value of 2.0 means that textures may be stretched at most 2x (one texture pixel
+ * will always be at most 2x2 screen pixels)
+ * The bigger the value, the less texture data is required, but quality suffers.
+ *
+ * @type number
+ * @default 1.0
+ */
+ this.maxTextureMagnification = 1.0;
+
+ /**
+ * The WebGL texture filter to use for magnifying textures.
+ * Possible values are all values valid for <code>TEXTURE_MAG_FILTER</code>.
+ * <code>null</code> means <code>NEAREST</code>.
+ *
+ * @default null / NEAREST.
+ */
+ this.textureMagFilter = null;
+
+ /**
+ * The WebGL texture filter to use for supersampling (minifying) textures.
+ * Possible values are all values valid for <code>TEXTURE_MIN_FILTER</code>.
+ * <code>null</code> means <code>NEAREST</code>.
+ *
+ * @default null / NEAREST.
+ */
+ this.textureMinFilter = null;
+
+ /**
+ * Minimum vertical field of view in degrees.
+ *
+ * @default 2.0
+ * @type number
+ */
+ this.minFov = 2.0;
+
+ /**
+ * Maximum vertical field of view in degrees.
+ *
+ * @default 90.0
+ * @type number
+ */
+ this.maxFov = 90;
+
+ /**
+ * Minimum pitch in degrees.
+ *
+ * @default -90
+ * @type number
+ */
+ this.minPitch = -90;
+
+ /**
+ * Maximum pitch in degrees.
+ *
+ * @default 90.0
+ * @type number
+ */
+ this.maxPitch = 90;
+
+ /**
+ * Minimum yaw in degrees. The number is interpreted modulo 360.
+ * The default value, -360, is just to make sure that we won't accidentally
+ * trip it. If the number is set to something in the interval 0-360,
+ * the autoRotate function will pan back and forth.
+ *
+ * @default -360
+ * @type number
+ */
+ this.minYaw = -360;
+
+ /**
+ * Maximum yaw in degrees. The number is interpreted modulo 360.
+ * The default value, 720, is just to make sure that we won't accidentally
+ * trip it. If the number is set to something in the interval 0-360,
+ * the autoRotate function will pan back and forth.
+ *
+ * @default 720.0
+ * @type number
+ */
+ this.maxYaw = 720;
+
+ /**
+ * Transform offset for yaw.
+ * @default 0.0
+ * @type number
+ */
+ this.yawOffset = 0.0;
+
+ /**
+ * Transform offset for pitch.
+ * @default 0.0
+ * @type number
+ */
+ this.pitchOffset = 0.0;
+
+ /**
+ * Transform offset for roll.
+ * @default 0.0
+ * @type number
+ */
+ this.rollOffset = 0.0;
+
+ /**
+ * Function to call when all six cube faces have loaded the base texture level
+ * and can be rendered.
+ *
+ * @type function()
+ * @default null
+ */
+ this.onload = null;
+
+ /**
+ * The rendering back end to use.
+ * Values are "css" and "webgl".
+ *
+ * @type String
+ * @default null
+ */
+ this.renderer = null;
+
+ /**
+ * Controls whether the panorama can be "flung" by quickly dragging and releasing.
+ *
+ * @type boolean
+ * @default true
+ */
+ this.fling = true;
+
+ /**
+ * Controls the decay of the "flinging" animation. The fling animation decays
+ * as 2^(-flingScale * t) where t is the time in milliseconds since the animation started.
+ * For the animation to decay to half-speed in X seconds,
+ * flingScale should then be set to 1 / (X*1000).
+ *
+ * @type float
+ * @default 0.004
+ */
+ this.flingScale = 0.004;
+
+ if (values) {
+ for (var k in values) {
+ this[k] = values[k];
+ }
+ }
+
+ this.merge = function (values, overwrite) {
+ for (var k in values) {
+ if (overwrite || !this[k]) {
+ this[k] = values[k];
+ }
+ }
+ }
+ return this;
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new VR panorama in a canvas. <b>Requires WebGL or CSS3D support.</b>
+ * (Note: See {@link bigshot.VRPanorama#dispose} for important information.)
+ *
+ * <h3 id="creating-a-cubemap">Creating a Cube Map</h3>
+ *
+ * <p>The panorama consists of six image pyramids, one for each face of the VR cube.
+ * Due to restrictions in WebGL, each texture tile must have a power-of-two (POT) size -
+ * that is, 2, 4, ..., 128, 256, etc. Furthermore, due to the way the faces are tesselated
+ * the largest image must consist of POT x POT tiles. The final restriction is that the
+ * tiles must overlap for good seamless results.
+ *
+ * <p>The MakeImagePyramid has some sensible defaults built-in. If you just use the
+ * command line:
+ *
+ * <code><pre>
+ * java -jar bigshot.jar input.jpg temp/dzi \
+ * --preset dzi-cubemap \
+ * --format folders
+ * </pre></code>
+ *
+ * <p>You will get 2034 pixels per face, and a tile size of 256 pixels with 2 pixels
+ * overlap. If you don't like that, you can use the <code>overlap</code>, <code>face-size</code>
+ * and <code>tile-size</code> parameters. Let's take these one by one:
+ *
+ * <ul>
+ * <li><p><code>overlap</code>: Overlap defines how much tiles should overlap, just to avoid
+ * seams in the rendered results caused by finite numeric precision. The default is <b>2</b>, which
+ * I've found works great for me.</p></li>
+ * <li><p><code>tile-size</code>: First you need to decide what POT size the output should be.
+ * Then subtract the overlap value. For example, if you set overlap to 1, <code>tile-size</code>
+ * could be 127, 255, 511, or any 2<sup>n</sup>-1 value.</p></li>
+ * <li><p><code>face-size</code>: Finally, we decide on a size for the full cube face. This should be
+ * tile-size * 2<sup>n</sup>. Let's say we set n=3, which makes each face 8x8 tiles at the most zoomed-in
+ * level. For a tile-size of 255, then, face-size is 255*2<sup>3</sup> = 255*8 = <b>2040</b>.</p></li>
+ * </ul>
+ *
+ * <p>A command line for the hypothetical scenario above would be:
+ *
+ * <code><pre>
+ * java -jar bigshot.jar input.jpg temp/dzi \
+ * --preset dzi-cubemap \
+ * --overlap 1 \
+ * --tile-size 255 \
+ * --face-size 2040 \
+ * --format folders
+ * </pre></code>
+ *
+ * <p>If your tile size numbers don't add up, you'll get a warning like:
+ *
+ * <code><pre>
+ * WARNING: Resulting image tile size (tile-size + overlap) is not a power of two: 255
+ * </pre></code>
+ *
+ * <p>If your face size don't add up, you'll get another warning:
+ *
+ * <code><pre>
+ * WARNING: face-size is not an even multiple of tile-size: 2040 % 254 != 0
+ * </pre></code>
+ *
+ * <h3 id="integration-with-saladoplayer">Integration With SaladoPlayer</h3>
+ *
+ * <p><a href="http://panozona.com/wiki/">SaladoPlayer</a> is a cool
+ * Flash-based VR panorama viewer that can display Deep Zoom Images.
+ * It can be used as a fallback for Bigshot for browsers that don't
+ * support WebGL.
+ *
+ * <p>Since Bigshot can use a Deep Zoom Image (DZI) via a {@link bigshot.DeepZoomImageFileSystem}
+ * adapter, the common file format is DZI. There are two cases: The first is
+ * when the DZI is served up as a folder structure, the second when
+ * we pack the DZI into a Bigshot archive and serve it using bigshot.php.
+ *
+ * <h4>Serving DZI as Folders</h4>
+ *
+ * <p>This is an easy one. First, we generate the required DZIs:
+ *
+ * <code><pre>
+ * java -jar bigshot.jar input.jpg temp/dzi \
+ * --preset dzi-cubemap \
+ * --format folders
+ * </pre></code>
+ *
+ * <p>We'll assume that we have the six DZI folders in "temp/dzi", and that
+ * they have "face_" as a common prefix (which is what Bigshot's MakeImagePyramid
+ * outputs). So we have, for example, "temp/dzi/face_f.xml" and the tiles for face_f
+ * in "temp/dzi/face_f/". Set up Bigshot like this:
+ *
+ * <code><pre>
+ * bvr = new bigshot.VRPanorama (
+ * new bigshot.VRPanoramaParameters ({
+ * container : document.getElementById ("canvas"),
+ * basePath : "temp/dzi",
+ * fileSystemType : "dzi"
+ * }));
+ * </pre></code>
+ *
+ * <p>SaladoPlayer uses an XML config file, which in this case will
+ * look something like this:
+ *
+ * <code><pre>
+ * &lt;SaladoPlayer>
+ * &lt;global debug="false" firstPanorama="pano"/>
+ * &lt;panoramas>
+ * &lt;panorama id="pano" path="temp/dzi/face_f.xml"/>
+ * &lt;/panoramas>
+ * &lt;/SaladoPlayer>
+ * </pre></code>
+ *
+ * <h4>Serving DZI as Archive</h4>
+ *
+ * <p>This one is a bit more difficult. First we create a DZI as a bigshot archive:
+ *
+ * <code><pre>
+ * java -jar bigshot.jar input.jpg temp/dzi.bigshot \
+ * --preset dzi-cubemap \
+ * --format archive
+ * </pre></code>
+ *
+ * <p>We'll assume that we have our Bigshot archive at
+ * "temp/dzi.bigshot". For this we will use the "entry" parameter of bigshot.php
+ * to serve up the right files:
+ *
+ * <code><pre>
+ * bvr = new bigshot.VRPanorama (
+ * new bigshot.VRPanoramaParameters ({
+ * container : document.getElementById ("canvas"),
+ * basePath : "/bigshot.php?file=temp/dzi.bigshot&entry=",
+ * fileSystemType : "dzi"
+ * }));
+ * </pre></code>
+ *
+ * <p>SaladoPlayer uses an XML config file, which in this case will
+ * look something like this:
+ *
+ * <code><pre>
+ * &lt;SaladoPlayer>
+ * &lt;global debug="false" firstPanorama="pano"/>
+ * &lt;panoramas>
+ * &lt;panorama id="pano" path="/bigshot.php?file=dzi.bigshot&amp;amp;entry=face_f.xml"/>
+ * &lt;/panoramas>
+ * &lt;/SaladoPlayer>
+ * </pre></code>
+ *
+ * <h3>Usage example:</h3>
+ * @example
+ * var bvr = new bigshot.VRPanorama (
+ * new bigshot.VRPanoramaParameters ({
+ * basePath : "/bigshot.php?file=myvr.bigshot",
+ * fileSystemType : "archive",
+ * container : document.getElementById ("bigshot_canvas")
+ * }));
+ * @class A cube-map VR panorama.
+ * @extends bigshot.EventDispatcher
+ *
+ * @param {bigshot.VRPanoramaParameters} parameters the panorama parameters.
+ *
+ * @see bigshot.VRPanoramaParameters
+ */
+bigshot.VRPanorama = function (parameters) {
+ bigshot.EventDispatcher.call (this);
+
+ var that = this;
+
+ this.parameters = parameters;
+ this.maxTextureMagnification = parameters.maxTextureMagnification;
+ this.container = parameters.container;
+ this.browser = new bigshot.Browser ();
+ this.dragStart = null;
+ this.dragDistance = 0;
+ this.hotspots = [];
+ this.disposed = false;
+
+ this.transformOffsets = {
+ y : parameters.yawOffset,
+ p : parameters.pitchOffset,
+ r : parameters.rollOffset
+ };
+
+ /**
+ * Current camera state.
+ * @private
+ */
+ this.state = {
+ rotation : {
+ /**
+ * Pitch in degrees.
+ * @type float
+ * @private
+ */
+ p : 0.0,
+
+ /**
+ * Yaw in degrees.
+ * @type float
+ * @private
+ */
+ y : 0.0,
+
+ r : 0
+ },
+
+ /**
+ * Field of view (vertical) in degrees.
+ * @type float
+ * @private
+ */
+ fov : 45,
+
+ translation : {
+ /**
+ * Translation along X-axis.
+ * @private
+ * @type float
+ */
+ x : 0.0,
+
+ /**
+ * Translation along Y-axis.
+ * @private
+ * @type float
+ */
+ y : 0.0,
+
+ /**
+ * Translation along Z-axis.
+ * @private
+ * @type float
+ */
+ z : 0.0
+ }
+ };
+
+ /**
+ * Renderer wrapper.
+ * @private
+ * @type bigshot.VRRenderer
+ */
+ this.renderer = null;
+ if (this.parameters.renderer) {
+ if (this.parameters.renderer == "css") {
+ this.renderer = new bigshot.CSS3DVRRenderer (this.container);
+ } else if (this.parameters.renderer == "webgl") {
+ this.renderer = new bigshot.WebGLVRRenderer (this.container)
+ } else {
+ throw new Error ("Unknown renderer: " + this.parameters.renderer);
+ }
+ } else {
+ this.renderer =
+ bigshot.WebGLUtil.isWebGLSupported () ?
+ new bigshot.WebGLVRRenderer (this.container)
+ :
+ new bigshot.CSS3DVRRenderer (this.container);
+ }
+
+ /**
+ * List of render listeners to call at the start and end of each render.
+ *
+ * @private
+ */
+ this.renderListeners = new Array ();
+
+ this.renderables = new Array ();
+
+ /**
+ * Current value of the idle counter.
+ *
+ * @private
+ */
+ this.idleCounter = 0;
+
+ /**
+ * Maximum value of the idle counter before any idle events start,
+ * such as autorotation.
+ *
+ * @private
+ */
+ this.maxIdleCounter = -1;
+
+
+ /**
+ * Integer acting as a "permit". When the smoothRotate function
+ * is called, the current value is incremented and saved. If the number changes
+ * that particular call to smoothRotate stops. This way we avoid
+ * having multiple smoothRotate rotations going in parallel.
+ * @private
+ * @type int
+ */
+ this.smoothrotatePermit = 0;
+
+ /**
+ * Helper function to consume events.
+ * @private
+ */
+ var consumeEvent = function (event) {
+ if (event.preventDefault) {
+ event.preventDefault ();
+ }
+ return false;
+ };
+
+ /**
+ * Full screen handler.
+ *
+ * @private
+ */
+ this.fullScreenHandler = null;
+
+ this.renderAsapPermitTaken = false;
+
+ /**
+ * An element to use as reference when resizing the canvas element.
+ * If non-null, any onresize() calls will result in the canvas being
+ * resized to the size of this element.
+ *
+ * @private
+ */
+ this.sizeContainer = null;
+
+ /**
+ * The six cube faces.
+ *
+ * @type bigshot.VRFace[]
+ * @private
+ */
+ var facesInit = {
+ facesLeft : 6,
+ faceLoaded : function () {
+ this.facesLeft--;
+ if (this.facesLeft == 0) {
+ if (that.parameters.onload) {
+ that.parameters.onload ();
+ }
+ }
+ }
+ };
+ var onFaceLoad = function () {
+ facesInit.faceLoaded ()
+ };
+
+ this.vrFaces = new Array ();
+ this.vrFaces[0] = new bigshot.VRFace (this, "f", {x:-1, y:1, z:-1}, 2.0, {x:1, y:0, z:0}, {x:0, y:-1, z:0}, onFaceLoad);
+ this.vrFaces[1] = new bigshot.VRFace (this, "b", {x:1, y:1, z:1}, 2.0, {x:-1, y:0, z:0}, {x:0, y:-1, z:0}, onFaceLoad);
+ this.vrFaces[2] = new bigshot.VRFace (this, "l", {x:-1, y:1, z:1}, 2.0, {x:0, y:0, z:-1}, {x:0, y:-1, z:0}, onFaceLoad);
+ this.vrFaces[3] = new bigshot.VRFace (this, "r", {x:1, y:1, z:-1}, 2.0, {x:0, y:0, z:1}, {x:0, y:-1, z:0}, onFaceLoad);
+ this.vrFaces[4] = new bigshot.VRFace (this, "u", {x:-1, y:1, z:1}, 2.0, {x:1, y:0, z:0}, {x:0, y:0, z:-1}, onFaceLoad);
+ this.vrFaces[5] = new bigshot.VRFace (this, "d", {x:-1, y:-1, z:-1}, 2.0, {x:1, y:0, z:0}, {x:0, y:0, z:1}, onFaceLoad);
+
+ /**
+ * Helper function to translate touch events to mouse-like events.
+ * @private
+ */
+ var translateEvent = function (event) {
+ if (event.clientX) {
+ return event;
+ } else {
+ return {
+ clientX : event.changedTouches[0].clientX,
+ clientY : event.changedTouches[0].clientY
+ };
+ };
+ };
+
+ this.lastTouchStartAt = -1;
+
+ this.allListeners = {
+ "mousedown" : function (e) {
+ that.smoothRotate ();
+ that.resetIdle ();
+ that.dragMouseDown (e);
+ return consumeEvent (e);
+ },
+ "mouseup" : function (e) {
+ that.resetIdle ();
+ that.dragMouseUp (e);
+ return consumeEvent (e);
+ },
+ "mousemove" : function (e) {
+ that.resetIdle ();
+ that.dragMouseMove (e);
+ return consumeEvent (e);
+ },
+ "gesturestart" : function (e) {
+ that.gestureStart (e);
+ return consumeEvent (e);
+ },
+ "gesturechange" : function (e) {
+ that.gestureChange (e);
+ return consumeEvent (e);
+ },
+ "gestureend" : function (e) {
+ that.gestureEnd (e);
+ return consumeEvent (e);
+ },
+
+ "DOMMouseScroll" : function (e) {
+ that.resetIdle ();
+ that.mouseWheel (e);
+ return consumeEvent (e);
+ },
+ "mousewheel" : function (e) {
+ that.resetIdle ();
+ that.mouseWheel (e);
+ return consumeEvent (e);
+ },
+ "dblclick" : function (e) {
+ that.mouseDoubleClick (e);
+ return consumeEvent (e);
+ },
+
+ "touchstart" : function (e) {
+ that.smoothRotate ();
+ that.lastTouchStartAt = new Date ().getTime ();
+ that.resetIdle ();
+ that.dragMouseDown (translateEvent (e));
+ return consumeEvent (e);
+ },
+ "touchend" : function (e) {
+ that.resetIdle ();
+ var handled = that.dragMouseUp (translateEvent (e));
+ if (!handled && (that.lastTouchStartAt > new Date().getTime() - 350)) {
+ that.mouseDoubleClick (translateEvent (e));
+ }
+ that.lastTouchStartAt = -1;
+ return consumeEvent (e);
+ },
+ "touchmove" : function (e) {
+ if (that.dragDistance > 24) {
+ that.lastTouchStartAt = -1;
+ }
+ that.resetIdle ();
+ that.dragMouseMove (translateEvent (e));
+ return consumeEvent (e);
+ }
+ };
+ this.addEventListeners ();
+
+ /**
+ * Stub function to call onresize on this instance.
+ *
+ * @private
+ */
+ this.onresizeHandler = function (e) {
+ that.onresize ();
+ };
+
+ this.browser.registerListener (window, 'resize', this.onresizeHandler, false);
+ this.browser.registerListener (document.body, 'orientationchange', this.onresizeHandler, false);
+
+ this.setPitch (0.0);
+ this.setYaw (0.0);
+ this.setFov (45.0);
+}
+
+/*
+ * Statics
+ */
+
+/**
+ * When the mouse is pressed and dragged, the camera rotates
+ * proportionally to the length of the dragging.
+ *
+ * @constant
+ * @public
+ * @static
+ */
+bigshot.VRPanorama.DRAG_GRAB = "grab";
+
+/**
+ * When the mouse is pressed and dragged, the camera continuously
+ * rotates with a speed that is proportional to the length of the
+ * dragging.
+ *
+ * @constant
+ * @public
+ * @static
+ */
+bigshot.VRPanorama.DRAG_PAN = "pan";
+
+/**
+ * @name bigshot.VRPanorama.RenderState
+ * @class The state the renderer is in when a {@link bigshot.VRPanorama.RenderListener} is called.
+ *
+ * @see bigshot.VRPanorama.ONRENDER_BEGIN
+ * @see bigshot.VRPanorama.ONRENDER_END
+ */
+
+/**
+ * A RenderListener state parameter value used at the start of each render.
+ *
+ * @constant
+ * @public
+ * @static
+ * @type bigshot.VRPanorama.RenderState
+ */
+bigshot.VRPanorama.ONRENDER_BEGIN = 0;
+
+/**
+ * A RenderListener state parameter value used at the end of each render.
+ *
+ * @constant
+ * @public
+ * @static
+ * @type bigshot.VRPanorama.RenderState
+ */
+bigshot.VRPanorama.ONRENDER_END = 1;
+
+/**
+ * A RenderListener cause parameter indicating that a previously requested
+ * texture has loaded and a render is forced. The data parameter is not used.
+ *
+ * @constant
+ * @public
+ * @static
+ * @param {bigshot.VRPanorama.RenderCause}
+ */
+bigshot.VRPanorama.ONRENDER_TEXTURE_UPDATE = 0;
+
+/**
+ * @name bigshot.VRPanorama.RenderCause
+ * @class The reason why the {@link bigshot.VRPanorama} is being rendered.
+ * Due to the events outside of the panorama, the VR panorama may be forced to
+ * re-render itself. When this happens, the {@link bigshot.VRPanorama.RenderListener}s
+ * receive a constant indicating the cause of the rendering.
+ *
+ * @see bigshot.VRPanorama.ONRENDER_TEXTURE_UPDATE
+ */
+
+/**
+ * Specification for functions passed to {@link bigshot.VRPanorama#addRenderListener}.
+ *
+ * @name bigshot.VRPanorama.RenderListener
+ * @function
+ * @param {bigshot.VRPanorama.RenderState} state The state of the renderer. Can be {@link bigshot.VRPanorama.ONRENDER_BEGIN} or {@link bigshot.VRPanorama.ONRENDER_END}
+ * @param {bigshot.VRPanorama.RenderCause} [cause] The reason for rendering the scene. Can be undefined or {@link bigshot.VRPanorama.ONRENDER_TEXTURE_UPDATE}
+ * @param {Object} [data] An optional data object that is dependent on the cause. See the documentation
+ * for the different causes.
+ */
+
+/**
+ * Specification for functions passed to {@link bigshot.VRPanorama#addRenderable}.
+ *
+ * @name bigshot.VRPanorama.Renderable
+ * @function
+ * @param {bigshot.VRRenderer} renderer The renderer object to use.
+ * @param {bigshot.TexturedQuadScene} scene The scene to render into.
+ */
+
+/** */
+bigshot.VRPanorama.prototype = {
+ /**
+ * Adds a hotstpot.
+ *
+ * @param {bigshot.VRHotspot} hs the hotspot to add
+ */
+ addHotspot : function (hs) {
+ this.hotspots.push (hs);
+ },
+
+ /**
+ * Returns the {@link bigshot.VRPanoramaParameters} object used by this instance.
+ *
+ * @type bigshot.VRPanoramaParameters
+ */
+ getParameters : function () {
+ return this.parameters;
+ },
+
+ /**
+ * Sets the view translation.
+ *
+ * @param x translation of the viewer along the X axis
+ * @param y translation of the viewer along the Y axis
+ * @param z translation of the viewer along the Z axis
+ */
+ setTranslation : function (x, y, z) {
+ this.state.translation.x = x;
+ this.state.translation.y = y;
+ this.state.translation.z = z;
+ },
+
+ /**
+ * Returns the current view translation as an x-y-z triplet.
+ *
+ * @returns {number} x translation of the viewer along the X axis
+ * @returns {number} y translation of the viewer along the Y axis
+ * @returns {number} z translation of the viewer along the Z axis
+ */
+ getTranslation : function () {
+ return this.state.translation;
+ },
+
+ /**
+ * Sets the field of view.
+ *
+ * @param {number} fov the vertical field of view, in degrees
+ */
+ setFov : function (fov) {
+ fov = Math.min (this.parameters.maxFov, fov);
+ fov = Math.max (this.parameters.minFov, fov);
+ this.state.fov = fov;
+ },
+
+ /**
+ * Gets the field of view.
+ *
+ * @return {number} the vertical field of view, in degrees
+ */
+ getFov : function () {
+ return this.state.fov;
+ },
+
+ /**
+ * Returns the angle (yaw, pitch) for a given pixel coordinate.
+ *
+ * @param {number} x the x-coordinate of the pixel, measured in pixels
+ * from the left edge of the panorama.
+ * @param {number} y the y-coordinate of the pixel, measured in pixels
+ * from the top edge of the panorama.
+ * @return {number} .yaw the yaw angle of the pixel (0 &lt;= yaw &lt; 360)
+ * @return {number} .pitch the pitch angle of the pixel (-180 &lt;= pitch &lt;= 180)
+ *
+ * @example
+ * var container = ...; // an HTML element
+ * var pano = ...; // a bigshot.VRPanorama
+ * ...
+ * container.addEventListener ("click", function (e) {
+ * var clickX = e.clientX - container.offsetX;
+ * var clickY = e.clientY - container.offsetY;
+ * var polar = pano.screenToPolar (clickX, clickY);
+ * alert ("You clicked at: " +
+ * "Yaw: " + polar.yaw +
+ * " Pitch: " + polar.pitch);
+ * });
+ */
+ screenToPolar : function (x, y) {
+ var dray = this.screenToRayDelta (x, y);
+ var ray = $V([dray.x, dray.y, dray.z, 1.0]);
+
+ ray = Matrix.RotationX (this.getPitch () * Math.PI / 180.0).ensure4x4 ().x (ray);
+ ray = Matrix.RotationY (-this.getYaw () * Math.PI / 180.0).ensure4x4 ().x (ray);
+
+ var dx = ray.e(1);
+ var dy = ray.e(2);
+ var dz = ray.e(3);
+
+ var dxz = Math.sqrt (dx * dx + dz * dz);
+
+ var dyaw = Math.atan2 (dx, -dz) * 180 / Math.PI;
+ var dpitch = Math.atan2 (dy, dxz) * 180 / Math.PI;
+
+ var res = {};
+ res.yaw = (dyaw + 360) % 360.0;
+ res.pitch = dpitch;
+
+ return res;
+ },
+
+ /**
+ * Restricts the pitch value to be between the minPitch and maxPitch parameters.
+ *
+ * @param {number} p the pitch value
+ * @returns the constrained pitch value.
+ */
+ snapPitch : function (p) {
+ p = Math.min (this.parameters.maxPitch, p);
+ p = Math.max (this.parameters.minPitch, p);
+ return p;
+ },
+
+ /**
+ * Sets the current camera pitch.
+ *
+ * @param {number} p the pitch, in degrees
+ */
+ setPitch : function (p) {
+ this.state.rotation.p = this.snapPitch (p);
+ },
+
+ /**
+ * Subtraction mod 360, sort of...
+ *
+ * @private
+ * @returns the angular distance with smallest magnitude to add to p0 to get to p1 % 360
+ */
+ circleDistance : function (p0, p1) {
+ if (p1 > p0) {
+ // p1 is somewhere clockwise to p0
+ var d1 = (p1 - p0); // move clockwise
+ var d2 = ((p1 - 360) - p0); // move counterclockwise, first -p0 to get to 0, then p1 - 360.
+ return Math.abs (d1) < Math.abs (d2) ? d1 : d2;
+ } else {
+ // p1 is somewhere counterclockwise to p0
+ var d1 = (p1 - p0); // move counterclockwise
+ var d2 = (360 - p0) + p1; // move clockwise, first (360-p= to get to 0, then another p1 degrees
+ return Math.abs (d1) < Math.abs (d2) ? d1 : d2;
+ }
+ },
+
+ /**
+ * Subtraction mod 360, sort of...
+ *
+ * @private
+ */
+ circleSnapTo : function (p, p1, p2) {
+ var d1 = this.circleDistance (p, p1);
+ var d2 = this.circleDistance (p, p2);
+ return Math.abs (d1) < Math.abs (d2) ? p1 : p2;
+ },
+
+ /**
+ * Constrains a yaw value to the required minimum and maximum values.
+ *
+ * @private
+ */
+ snapYaw : function (y) {
+ y %= 360;
+ if (y < 0) {
+ y += 360;
+ }
+ if (this.parameters.minYaw < this.parameters.maxYaw) {
+ if (y > this.parameters.maxYaw || y < this.parameters.minYaw) {
+ y = circleSnapTo (y, this.parameters.minYaw, this.parameters.maxYaw);
+ }
+ } else {
+ // The only time when minYaw > maxYaw is when the interval
+ // contains the 0 angle.
+ if (y > this.parameters.minYaw) {
+ // ok, we're somewhere between minYaw and 0.0
+ } else if (y > this.parameters.maxYaw) {
+ // we're somewhere between maxYaw and minYaw
+ // (but on the wrong side).
+ // figure out the nearest point and snap to it
+ y = circleSnapTo (y, this.parameters.minYaw, this.parameters.maxYaw);
+ } else {
+ // ok, we're somewhere between 0.0 and maxYaw
+ }
+ }
+ return y;
+ },
+
+ /**
+ * Sets the current camera yaw. The yaw is normalized between
+ * 0 <= y < 360.
+ *
+ * @param {number} y the yaw, in degrees
+ */
+ setYaw : function (y) {
+ this.state.rotation.y = this.snapYaw (y);
+ },
+
+ /**
+ * Gets the current camera yaw.
+ *
+ * @return {number} the yaw, in degrees
+ */
+ getYaw : function () {
+ return this.state.rotation.y;
+ },
+
+ /**
+ * Gets the current camera pitch.
+ *
+ * @return {number} the pitch, in degrees
+ */
+ getPitch : function () {
+ return this.state.rotation.p;
+ },
+
+ /**
+ * Unregisters event handlers and other page-level hooks. The client need not call this
+ * method unless bigshot images are created and removed from the page
+ * dynamically. In that case, this method must be called when the client wishes to
+ * free the resources allocated by the image. Otherwise the browser will garbage-collect
+ * all resources automatically.
+ * @public
+ */
+ dispose : function () {
+ this.disposed = true;
+ this.browser.unregisterListener (window, "resize", this.onresizeHandler, false);
+ this.browser.unregisterListener (document.body, "orientationchange", this.onresizeHandler, false);
+ this.removeEventListeners ();
+
+ for (var i = 0; i < this.vrFaces.length; ++i) {
+ this.vrFaces[i].dispose ();
+ }
+ this.renderer.dispose ();
+ },
+
+ /**
+ * Creates and initializes a {@link bigshot.VREvent} object.
+ * The {@link bigshot.VREvent#ray}, {@link bigshot.VREvent#yaw},
+ * {@link bigshot.VREvent#pitch}, {@link bigshot.Event#target} and
+ * {@link bigshot.Event#currentTarget} fields are set.
+ *
+ * @param {Object} data the data object for the event
+ * @param {number} data.clientX the client x-coordinate of the event
+ * @param {number} data.clientY the client y-coordinate of the event
+ * @returns the new event object
+ * @type bigshot.VREvent
+ */
+ createVREventData : function (data) {
+ var elementPos = this.browser.getElementPosition (this.container);
+ data.localX = data.clientX - elementPos.x;
+ data.localY = data.clientY - elementPos.y;
+
+ data.ray = this.screenToRay (data.localX, data.localY);
+
+ var polar = this.screenToPolar (data.localX, data.localY);
+ data.yaw = polar.yaw;
+ data.pitch = polar.pitch;
+ data.target = this;
+ data.currentTarget = this;
+
+ return new bigshot.VREvent (data);
+ },
+
+
+ /**
+ * Sets up transformation matrices etc. Calls all render listeners with a state parameter
+ * of {@link bigshot.VRPanorama.ONRENDER_BEGIN}.
+ *
+ * @private
+ *
+ * @param [cause] parameter for the {@link bigshot.VRPanorama.RenderListener}s.
+ * @param [data] parameter for the {@link bigshot.VRPanorama.RenderListener}s.
+ */
+ beginRender : function (cause, data) {
+ this.onrender (bigshot.VRPanorama.ONRENDER_BEGIN, cause, data);
+ this.renderer.beginRender (this.state.rotation, this.state.fov, this.state.translation, this.transformOffsets);
+ },
+
+
+ /**
+ * Add a function that will be called at various times during the render.
+ *
+ * @param {bigshot.VRPanorama.RenderListener} listener the listener function
+ */
+ addRenderListener : function (listener) {
+ var rl = new Array ();
+ rl = rl.concat (this.renderListeners);
+ rl.push (listener);
+ this.renderListeners = rl;
+ },
+
+ /**
+ * Removes a function that will be called at various times during the render.
+ *
+ * @param {bigshot.VRPanorama.RenderListener} listener the listener function
+ */
+ removeRenderListener : function (listener) {
+ var rl = new Array ();
+ rl = rl.concat (this.renderListeners);
+ for (var i = 0; i < rl.length; ++i) {
+ if (rl[i] === listener) {
+ rl.splice (i, 1);
+ break;
+ }
+ }
+ this.renderListeners = rl;
+ },
+
+ /**
+ * Called at the start and end of every render.
+ *
+ * @event
+ * @private
+ * @type function()
+ * @param {bigshot.VRPanorama.RenderState} state the current render state
+ */
+ onrender : function (state, cause, data) {
+ var rl = this.renderListeners;
+ for (var i = 0; i < rl.length; ++i) {
+ rl[i](state, cause, data);
+ }
+ },
+
+ /**
+ * Performs per-render cleanup. Calls all render listeners with a state parameter
+ * of {@link bigshot.VRPanorama.ONRENDER_END}.
+ *
+ * @private
+ *
+ * @param [cause] parameter for the {@link bigshot.VRPanorama.RenderListener}s.
+ * @param [data] parameter for the {@link bigshot.VRPanorama.RenderListener}s.
+ */
+ endRender : function (cause, data) {
+ for (var f in this.vrFaces) {
+ this.vrFaces[f].endRender ();
+ }
+ this.renderer.endRender ();
+ this.onrender (bigshot.VRPanorama.ONRENDER_END, cause, data);
+ },
+
+ /**
+ * Add a function that will be called to render any additional quads.
+ *
+ * @param {bigshot.VRPanorama.Renderable} renderable The renderable, a function responsible for
+ * rendering additional scene elements.
+ */
+ addRenderable : function (renderable) {
+ var rl = new Array ();
+ rl.concat (this.renderables);
+ rl.push (renderable);
+ this.renderables = rl;
+ },
+
+ /**
+ * Removes a function that will be called to render any additional quads.
+ *
+ * @param {bigshot.VRPanorama.Renderable} renderable The renderable added using
+ * {@link bigshot.VRPanorama#addRenderable}.
+ */
+ removeRenderable : function (renderable) {
+ var rl = new Array ();
+ rl.concat (this.renderables);
+ for (var i = 0; i < rl.length; ++i) {
+ if (rl[i] == listener) {
+ rl.splice (i, 1);
+ break;
+ }
+ }
+ this.renderables = rl;
+ },
+
+ /**
+ * Renders the VR cube.
+ *
+ * @param [cause] parameter for the {@link bigshot.VRPanorama.RenderListener}s.
+ * @param [data] parameter for the {@link bigshot.VRPanorama.RenderListener}s.
+ */
+ render : function (cause, data) {
+ if (!this.disposed) {
+ this.beginRender (cause, data);
+
+ var scene = this.renderer.createTexturedQuadScene ();
+
+ for (var f in this.vrFaces) {
+ this.vrFaces[f].render (scene);
+ }
+
+ for (var i = 0; i < this.renderables.length; ++i) {
+ this.renderables[i](this.renderer, scene);
+ }
+
+ scene.render ();
+
+ for (var i = 0; i < this.hotspots.length; ++i) {
+ this.hotspots[i].layout ();
+ }
+
+ this.endRender (cause, data);
+ }
+ },
+
+ /**
+ * Render updated faces. Called as tiles are loaded from the server.
+ *
+ * @param [cause] parameter for the {@link bigshot.VRPanorama.RenderListener}s.
+ * @param [data] parameter for the {@link bigshot.VRPanorama.RenderListener}s.
+ */
+ renderUpdated : function (cause, data) {
+ if (!this.disposed && this.renderer.supportsUpdate ()) {
+ this.beginRender (cause, data);
+
+ var scene = this.renderer.createTexturedQuadScene ();
+
+ for (var f in this.vrFaces) {
+ if (this.vrFaces[f].isUpdated ()) {
+ this.vrFaces[f].render (scene);
+ }
+ }
+
+ scene.render ();
+
+ for (var i = 0; i < this.hotspots.length; ++i) {
+ this.hotspots[i].layout ();
+ }
+
+ this.endRender (cause, data);
+ } else {
+ this.render (cause, data);
+ }
+ },
+
+ /**
+ * The current drag mode.
+ *
+ * @private
+ */
+ dragMode : bigshot.VRPanorama.DRAG_GRAB,
+
+ /**
+ * Sets the mouse dragging mode.
+ *
+ * @param mode one of {@link bigshot.VRPanorama.DRAG_PAN} or {@link bigshot.VRPanorama.DRAG_GRAB}.
+ */
+ setDragMode : function (mode) {
+ this.dragMode = mode;
+ },
+
+ addEventListeners : function () {
+ for (var k in this.allListeners) {
+ this.browser.registerListener (this.container, k, this.allListeners[k], false);
+ }
+ },
+
+ removeEventListeners : function () {
+ for (var k in this.allListeners) {
+ this.browser.unregisterListener (this.container, k, this.allListeners[k], false);
+ }
+ },
+
+ dragMouseDown : function (e) {
+ this.dragStart = {
+ clientX : e.clientX,
+ clientY : e.clientY
+ };
+ this.dragLast = {
+ clientX : e.clientX,
+ clientY : e.clientY,
+ dx : 0,
+ dy : 0,
+ dt : 1000000,
+ time : new Date ().getTime ()
+ };
+ this.dragDistance = 0;
+ },
+
+ dragMouseUp : function (e) {
+ // In case we got a mouse up with out a previous mouse down,
+ // for example, double-click on title bar to maximize the
+ // window
+ if (this.dragStart == null || this.dragLast == null) {
+ this.dragStart = null;
+ this.dragLast = null;
+ return;
+ }
+
+ this.dragStart = null;
+ var dx = this.dragLast.dx;
+ var dy = this.dragLast.dy;
+ var ds = Math.sqrt (dx * dx + dy * dy);
+ var dt = this.dragLast.dt;
+ var dtb = new Date ().getTime () - this.dragLast.time;
+ this.dragLast = null;
+
+ var v = dt > 0 ? (ds / dt) : 0;
+ if (v > 0.05 && dtb < 250 && dt > 20 && this.parameters.fling) {
+ var scale = this.state.fov / this.renderer.getViewportHeight ();
+
+ var t0 = new Date ().getTime ();
+
+ var flingScale = this.parameters.flingScale;
+
+ dx /= dt;
+ dy /= dt;
+
+ this.smoothRotate (function (dat) {
+ var dt = new Date ().getTime () - t0;
+ var fact = Math.pow (2, -dt * flingScale);
+ var d = (dx * dat * scale) * fact;
+ return fact > 0.01 ? d : null;
+ }, function (dat) {
+ var dt = new Date ().getTime () - t0;
+ var fact = Math.pow (2, -dt * flingScale);
+ var d = (dy * dat * scale) * fact;
+ return fact > 0.01 ? d : null;
+ }, function () {
+ return null;
+ });
+ return true;
+ } else {
+ this.smoothRotate ();
+ return false;
+ }
+ },
+
+ dragMouseMove : function (e) {
+ if (this.dragStart != null && this.currentGesture == null) {
+ if (this.dragMode == bigshot.VRPanorama.DRAG_GRAB) {
+ this.smoothRotate ();
+ var scale = this.state.fov / this.renderer.getViewportHeight ();
+ var dx = e.clientX - this.dragStart.clientX;
+ var dy = e.clientY - this.dragStart.clientY;
+ this.dragDistance += dx + dy;
+ this.setYaw (this.getYaw () - dx * scale);
+ this.setPitch (this.getPitch () - dy * scale);
+ this.renderAsap ();
+ this.dragStart = e;
+ var dt = new Date ().getTime () - this.dragLast.time;
+ if (dt > 20) {
+ this.dragLast = {
+ dx : this.dragLast.clientX - e.clientX,
+ dy : this.dragLast.clientY - e.clientY,
+ dt : dt,
+ clientX : e.clientX,
+ clientY : e.clientY,
+ time : new Date ().getTime ()
+ };
+ }
+ } else {
+ var scale = 0.1 * this.state.fov / this.renderer.getViewportHeight ();
+ var dx = e.clientX - this.dragStart.clientX;
+ var dy = e.clientY - this.dragStart.clientY;
+ this.dragDistance = dx + dy;
+ this.smoothRotate (
+ function () {
+ return dx * scale;
+ },
+ function () {
+ return dy * scale;
+ });
+ }
+ }
+ },
+
+ onMouseDoubleClick : function (e, x, y) {
+ var eventData = this.createVREventData ({
+ type : "dblclick",
+ clientX : e.clientX,
+ clientY : e.clientY
+ });
+ this.fireEvent ("dblclick", eventData);
+ if (!eventData.defaultPrevented) {
+ this.smoothRotateToXY (x, y);
+ }
+ },
+
+ mouseDoubleClick : function (e) {
+ var pos = this.browser.getElementPosition (this.container);
+ this.onMouseDoubleClick (e, e.clientX - pos.x, e.clientY - pos.y);
+ },
+
+ /**
+ * Begins a potential drag event.
+ *
+ * @private
+ */
+ gestureStart : function (event) {
+ this.currentGesture = {
+ startFov : this.getFov (),
+ scale : event.scale
+ };
+ },
+
+ /**
+ * Begins a potential drag event.
+ *
+ * @private
+ */
+ gestureEnd : function (event) {
+ this.currentGesture = null;
+ },
+
+ /**
+ * Begins a potential drag event.
+ *
+ * @private
+ */
+ gestureChange : function (event) {
+ if (this.currentGesture) {
+ var newFov = this.currentGesture.startFov / event.scale;
+ this.setFov (newFov);
+ this.renderAsap ();
+ }
+ },
+
+ /**
+ * Sets the maximum texture magnification.
+ *
+ * @param {number} v the maximum texture magnification
+ * @see bigshot.VRPanoramaParameters#maxTextureMagnification
+ */
+ setMaxTextureMagnification : function (v) {
+ this.maxTextureMagnification = v;
+ },
+
+ /**
+ * Gets the current maximum texture magnification.
+ *
+ * @type number
+ * @see bigshot.VRPanoramaParameters#maxTextureMagnification
+ */
+ getMaxTextureMagnification : function () {
+ return this.maxTextureMagnification;
+ },
+
+ /**
+ * Computes the minimum field of view where the resulting image will not
+ * have to stretch the textures more than given by the
+ * {@link bigshot.VRPanoramaParameters#maxTextureMagnification} parameter.
+ *
+ * @type number
+ * @return the minimum FOV, below which it is necessary to stretch the
+ * vr cube texture more than the given {@link bigshot.VRPanoramaParameters#maxTextureMagnification}
+ */
+ getMinFovFromViewportAndImage : function () {
+ var halfHeight = this.renderer.getViewportHeight () / 2;
+
+ var minFaceHeight = this.vrFaces[0].parameters.height;
+ for (var i in this.vrFaces) {
+ minFaceHeight = Math.min (minFaceHeight, this.vrFaces[i].parameters.height);
+ }
+
+ var edgeSizeY = this.maxTextureMagnification * minFaceHeight / 2;
+
+ var wy = halfHeight / edgeSizeY;
+
+ var mz = Math.atan (wy) * 180 / Math.PI;
+
+ return mz * 2;
+ },
+
+ /**
+ * Transforms screen coordinates to a world-coordinate ray.
+ * @private
+ */
+ screenToRay : function (x, y) {
+ var dray = this.screenToRayDelta (x, y);
+ var ray = this.renderer.transformToWorld (dray);
+ ray = Matrix.RotationY (-this.transformOffsets.y * Math.PI / 180.0).ensure4x4 ().xPoint3Dhom1 (ray);
+ ray = Matrix.RotationX (-this.transformOffsets.p * Math.PI / 180.0).ensure4x4 ().xPoint3Dhom1 (ray);
+ ray = Matrix.RotationZ (-this.transformOffsets.r * Math.PI / 180.0).ensure4x4 ().xPoint3Dhom1 (ray);
+ return ray;
+ },
+
+ /**
+ * @private
+ */
+ screenToRayDelta : function (x, y) {
+ var halfHeight = this.renderer.getViewportHeight () / 2;
+ var halfWidth = this.renderer.getViewportWidth () / 2;
+ var x = (x - halfWidth);
+ var y = (y - halfHeight);
+
+ var edgeSizeY = Math.tan ((this.state.fov / 2) * Math.PI / 180);
+ var edgeSizeX = edgeSizeY * this.renderer.getViewportWidth () / this.renderer.getViewportHeight ();
+
+ var wx = x * edgeSizeX / halfWidth;
+ var wy = y * edgeSizeY / halfHeight;
+ var wz = -1.0;
+
+ return {
+ x : wx,
+ y : wy,
+ z : wz
+ };
+ },
+
+ /**
+ * Smoothly rotates the panorama so that the
+ * point given by x and y, in pixels relative to the top left corner
+ * of the panorama, ends up in the center of the viewport.
+ *
+ * @param {int} x the x-coordinate, in pixels from the left edge
+ * @param {int} y the y-coordinate, in pixels from the top edge
+ */
+ smoothRotateToXY : function (x, y) {
+ var polar = this.screenToPolar (x, y);
+
+ this.smoothRotateTo (this.snapYaw (polar.yaw), this.snapPitch (polar.pitch), this.getFov (), this.state.fov / 200);
+ },
+
+ /**
+ * Gives the step to take to slowly approach the
+ * target value.
+ *
+ * @example
+ * current = current + this.ease (current, target, 1.0);
+ * @private
+ */
+ ease : function (current, target, speed, snapFrom) {
+ var easingFrom = speed * 40;
+ if (!snapFrom) {
+ snapFrom = speed / 5;
+ }
+ var ignoreFrom = speed / 1000;
+
+ var distance = current - target;
+ if (distance > easingFrom) {
+ distance = -speed;
+ } else if (distance < -easingFrom) {
+ distance = speed;
+ } else if (Math.abs (distance) < snapFrom) {
+ distance = -distance;
+ } else if (Math.abs (distance) < ignoreFrom) {
+ distance = 0;
+ } else {
+ distance = - (speed * distance) / (easingFrom);
+ }
+ return distance;
+ },
+
+ /**
+ * Resets the "idle" clock.
+ * @private
+ */
+ resetIdle : function () {
+ this.idleCounter = 0;
+ },
+
+ /**
+ * Idle clock.
+ * @private
+ */
+ idleTick : function () {
+ if (this.maxIdleCounter < 0) {
+ return;
+ }
+ ++this.idleCounter;
+ if (this.idleCounter == this.maxIdleCounter) {
+ this.autoRotate ();
+ }
+ var that = this;
+ setTimeout (function () {
+ that.idleTick ();
+ }, 1000);
+ },
+
+ /**
+ * Sets the panorama to auto-rotate after a certain time has
+ * elapsed with no user interaction. Default is disabled.
+ *
+ * @param {int} delay the delay in seconds. Set to < 0 to disable
+ * auto-rotation when idle
+ */
+ autoRotateWhenIdle : function (delay) {
+ this.maxIdleCounter = delay;
+ this.idleCounter = 0;
+ if (delay < 0) {
+ return;
+ } else if (this.maxIdleCounter > 0) {
+ var that = this;
+ setTimeout (function () {
+ that.idleTick ();
+ }, 1000);
+ }
+ },
+
+ /**
+ * Starts auto-rotation of the camera. If the yaw is constrained,
+ * will pan back and forth between the yaw endpoints. Call
+ * {@link #smoothRotate}() to stop the rotation.
+ */
+ autoRotate : function () {
+ var that = this;
+ var scale = this.state.fov / 400;
+
+ var speed = scale;
+ var dy = speed;
+ this.smoothRotate (
+ function () {
+ var nextPos = that.getYaw () + dy;
+ if (that.parameters.minYaw < that.parameters.maxYaw) {
+ if (nextPos > that.parameters.maxYaw || nextPos < that.parameters.minYaw) {
+ dy = -dy;
+ }
+ } else {
+ // The only time when minYaw > maxYaw is when the interval
+ // contains the 0 angle.
+ if (nextPos > that.parameters.minYaw) {
+ // ok, we're somewhere between minYaw and 0.0
+ } else if (nextPos > that.parameters.maxYaw) {
+ dy = -dy;
+ } else {
+ // ok, we're somewhere between 0.0 and maxYaw
+ }
+ }
+ return dy;
+ }, function () {
+ return that.ease (that.getPitch (), 0.0, speed);
+ }, function () {
+ return that.ease (that.getFov (), 45.0, 0.1);
+ });
+ },
+
+ /**
+ * Smoothly rotates the panorama to the given state.
+ *
+ * @param {number} yaw the target yaw
+ * @param {number} pitch the target pitch
+ * @param {number} fov the target vertical field of view
+ * @param {number} the speed to rotate with
+ */
+ smoothRotateTo : function (yaw, pitch, fov, speed) {
+ var that = this;
+ this.smoothRotate (
+ function () {
+ var distance = that.circleDistance (yaw, that.getYaw ());
+ var d = -that.ease (0, distance, speed);
+ return Math.abs (d) > 0.01 ? d : null;
+ }, function () {
+ var d = that.ease (that.getPitch (), pitch, speed);
+ return Math.abs (d) > 0.01 ? d : null;
+ }, function () {
+ var d = that.ease (that.getFov (), fov, speed);
+ return Math.abs (d) > 0.01 ? d : null;
+ }
+ );
+ },
+
+
+ /**
+ * Smoothly rotates the camera. If all of the dp, dy and df functions are null, stops
+ * any smooth rotation.
+ *
+ * @param {function()} [dy] function giving the yaw increment for the next frame
+ * or null if no further yaw movement is required
+ * @param {function()} [dp] function giving the pitch increment for the next frame
+ * or null if no further pitch movement is required
+ * @param {function()} [df] function giving the field of view (degrees) increment
+ * for the next frame or null if no further fov adjustment is required
+ */
+ smoothRotate : function (dy, dp, df) {
+ ++this.smoothrotatePermit;
+ var savedPermit = this.smoothrotatePermit;
+ if (!dp && !dy && !df) {
+ return;
+ }
+
+ var that = this;
+ var fs = {
+ dy : dy,
+ dp : dp,
+ df : df,
+ t : new Date ().getTime ()
+ };
+ var stepper = function () {
+ if (that.smoothrotatePermit == savedPermit) {
+ var now = new Date ().getTime ();
+ var dat = now - fs.t;
+ fs.t = now;
+
+ var anyFunc = false;
+ if (fs.dy) {
+ var d = fs.dy(dat);
+ if (d != null) {
+ anyFunc = true;
+ that.setYaw (that.getYaw () + d);
+ } else {
+ fs.dy = null;
+ }
+ }
+
+ if (fs.dp) {
+ var d = fs.dp(dat);
+ if (d != null) {
+ anyFunc = true;
+ that.setPitch (that.getPitch () + d);
+ } else {
+ fs.dp = null;
+ }
+ }
+
+ if (fs.df) {
+ var d = fs.df(dat);
+ if (d != null) {
+ anyFunc = true;
+ that.setFov (that.getFov () + d);
+ } else {
+ fs.df = null;
+ }
+ }
+ that.render ();
+ if (anyFunc) {
+ that.browser.requestAnimationFrame (stepper, that.renderer.getElement ());
+ }
+ }
+ };
+ stepper ();
+ },
+
+ /**
+ * Translates mouse wheel events.
+ * @private
+ */
+ mouseWheel : function (event){
+ var delta = 0;
+ if (!event) /* For IE. */
+ event = window.event;
+ if (event.wheelDelta) { /* IE/Opera. */
+ delta = event.wheelDelta / 120;
+ /*
+ * In Opera 9, delta differs in sign as compared to IE.
+ */
+ if (window.opera)
+ delta = -delta;
+ } else if (event.detail) { /* Mozilla case. */
+ /*
+ * In Mozilla, sign of delta is different than in IE.
+ * Also, delta is multiple of 3.
+ */
+ delta = -event.detail;
+ }
+
+ /*
+ * If delta is nonzero, handle it.
+ * Basically, delta is now positive if wheel was scrolled up,
+ * and negative, if wheel was scrolled down.
+ */
+ if (delta) {
+ this.mouseWheelHandler (delta);
+ }
+
+ /*
+ * Prevent default actions caused by mouse wheel.
+ * That might be ugly, but we handle scrolls somehow
+ * anyway, so don't bother here..
+ */
+ if (event.preventDefault) {
+ event.preventDefault ();
+ }
+ event.returnValue = false;
+ },
+
+ /**
+ * Utility function to interpret mouse wheel events.
+ * @private
+ */
+ mouseWheelHandler : function (delta) {
+ var that = this;
+ var target = null;
+ if (delta > 0) {
+ if (this.getFov () > this.parameters.minFov) {
+ target = this.getFov () * 0.9;
+ }
+ }
+ if (delta < 0) {
+ if (this.getFov () < this.parameters.maxFov) {
+ target = this.getFov () / 0.9;
+ }
+ }
+ if (target != null) {
+ this.smoothRotate (null, null, function () {
+ var df = (target - that.getFov ()) / 1.5;
+ return Math.abs (df) > 0.01 ? df : null;
+ });
+ }
+ },
+
+ /**
+ * Maximizes the image to cover the browser viewport.
+ * The container div is removed from its parent node upon entering
+ * full screen mode. When leaving full screen mode, the container
+ * is appended to its old parent node. To avoid rearranging the
+ * nodes, wrap the container in an extra div.
+ *
+ * <p>For unknown reasons (probably security), browsers will
+ * not let you open a window that covers the entire screen.
+ * Even when specifying "fullscreen=yes", all you get is a window
+ * that has a title bar and only covers the desktop (not any task
+ * bars or the like). For now, this is the best that I can do,
+ * but should the situation change I'll update this to be
+ * full-screen<i>-ier</i>.
+ *
+ * @param {function()} [onClose] function that is called when the user
+ * exits full-screen mode
+ * @public
+ */
+ fullScreen : function (onClose) {
+ if (this.fullScreenHandler) {
+ return;
+ }
+
+ var message = document.createElement ("div");
+ message.style.position = "absolute";
+ message.style.fontSize = "16pt";
+ message.style.top = "128px";
+ message.style.width = "100%";
+ message.style.color = "white";
+ message.style.padding = "16px";
+ message.style.zIndex = "9999";
+ message.style.textAlign = "center";
+ message.style.opacity = "0.75";
+ message.innerHTML = "<span style='border-radius: 16px; -moz-border-radius: 16px; padding: 16px; padding-left: 32px; padding-right: 32px; background:black'>Press Esc to exit full screen mode.</span>";
+
+ var that = this;
+
+ this.fullScreenHandler = new bigshot.FullScreen (this.container);
+ this.fullScreenHandler.restoreSize = this.sizeContainer == null;
+
+ this.fullScreenHandler.addOnResize (function () {
+ that.onresize ();
+ });
+
+ this.fullScreenHandler.addOnClose (function () {
+ if (message.parentNode) {
+ try {
+ div.removeChild (message);
+ } catch (x) {
+ }
+ }
+ that.fullScreenHandler = null;
+ });
+
+ if (onClose) {
+ this.fullScreenHandler.addOnClose (function () {
+ onClose ();
+ });
+ }
+
+ this.removeEventListeners ();
+ this.fullScreenHandler.open ();
+ this.addEventListeners ();
+ // Safari compatibility - must update after entering fullscreen.
+ // 1s should be enough so we enter FS, but not enough for the
+ // user to wonder if something is wrong.
+ var r = function () {
+ that.render ();
+ };
+ setTimeout (r, 1000);
+ setTimeout (r, 2000);
+ setTimeout (r, 3000);
+
+ if (this.fullScreenHandler.getRootElement ()) {
+ this.fullScreenHandler.getRootElement ().appendChild (message);
+
+ setTimeout (function () {
+ var opacity = 0.75;
+ var iter = function () {
+ opacity -= 0.02;
+ if (message.parentNode) {
+ if (opacity <= 0) {
+ message.style.display = "none";
+ try {
+ div.removeChild (message);
+ } catch (x) {}
+ } else {
+ message.style.opacity = opacity;
+ setTimeout (iter, 20);
+ }
+ }
+ };
+ setTimeout (iter, 20);
+ }, 3500);
+ }
+
+ return function () {
+ that.removeEventListeners ();
+ that.fullScreenHandler.close ();
+ that.addEventListeners ();
+ };
+ },
+
+ /**
+ * Right-sizes the canvas container.
+ * @private
+ */
+ onresize : function () {
+ if (this.fullScreenHandler == null || !this.fullScreenHandler.isFullScreen) {
+ if (this.sizeContainer) {
+ var s = this.browser.getElementSize (this.sizeContainer);
+ this.renderer.resize (s.w, s.h);
+ }
+ } else {
+ this.container.style.width = window.innerWidth + "px";
+ this.container.style.height = window.innerHeight + "px";
+ var s = this.browser.getElementSize (this.container);
+ this.renderer.resize (s.w, s.h);
+ }
+ this.renderer.onresize ();
+ this.renderAsap ();
+ },
+
+ /**
+ * Posts a render() call via a timeout or the requestAnimationFrame API.
+ * Use when the render call must be done as soon as possible, but
+ * can't be done in the current call context.
+ */
+ renderAsap : function () {
+ if (!this.renderAsapPermitTaken && !this.disposed) {
+ this.renderAsapPermitTaken = true;
+ var that = this;
+ this.browser.requestAnimationFrame (function () {
+ that.renderAsapPermitTaken = false;
+ that.render ();
+ }, this.renderer.getElement ());
+ }
+ },
+
+
+ /**
+ * Automatically resizes the canvas element to the size of the
+ * given element on resize.
+ *
+ * @param {HTMLElement} sizeContainer the element to use. Set to <code>null</code>
+ * to disable.
+ */
+ autoResizeContainer : function (sizeContainer) {
+ this.sizeContainer = sizeContainer;
+ }
+}
+
+/**
+ * Fired when the user double-clicks on the panorama.
+ *
+ * @name bigshot.VRPanorama#dblclick
+ * @event
+ * @param {bigshot.VREvent} event the event object
+ */
+
+bigshot.Object.extend (bigshot.VRPanorama, bigshot.EventDispatcher);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Abstract base class for panorama hotspots.
+ *
+ * @class Abstract base class for panorama hotspots.
+ *
+ * A Hotspot is simply an HTML element that is moved / hidden etc.
+ * to overlay a given position in the panorama.
+ *
+ * @param {bigshot.VRPanorama} panorama the panorama to attach this hotspot to
+ */
+bigshot.VRHotspot = function (panorama) {
+ this.panorama = panorama;
+
+ /**
+ * The method to use for dealing with hotspots that extend outside the
+ * viewport. Note that {@link #CLIP_ADJUST} et al are functions, not constants.
+ * To set the value, you must call the function to get a clipping strategy:
+ *
+ * @example
+ * var hotspot = ...;
+ * // note the function call below ---------------v
+ * hotspot.clippingStrategy = hotspot.CLIP_ADJUST ();
+ *
+ * @see bigshot.VRHotspot#CLIP_ADJUST
+ * @see bigshot.VRHotspot#CLIP_CENTER
+ * @see bigshot.VRHotspot#CLIP_FRACTION
+ * @see bigshot.VRHotspot#CLIP_ZOOM
+ * @see bigshot.VRHotspot#CLIP_FADE
+ * @see bigshot.VRHotspot#clip
+ * @type function(clipData)
+ * @default bigshot.VRHotspot#CLIP_ADJUST
+ */
+ this.clippingStrategy = bigshot.VRHotspot.CLIP_ADJUST (panorama);
+
+}
+
+/**
+ * Hides the hotspot if less than <code>frac</code> of its area is visible.
+ *
+ * @param {number} frac the fraction (0.0 - 1.0) of the hotspot that must be visible for
+ * it to be shown.
+ * @type function(clipData)
+ * @see bigshot.VRHotspot#clip
+ * @see bigshot.VRHotspot#clippingStrategy
+ */
+bigshot.VRHotspot.CLIP_FRACTION = function (panorama, frac) {
+ return function (clipData) {
+ var r = {
+ x0 : Math.max (clipData.x, 0),
+ y0 : Math.max (clipData.y, 0),
+ x1 : Math.min (clipData.x + clipData.w, panorama.renderer.getViewportWidth ()),
+ y1 : Math.min (clipData.y + clipData.h, panorama.renderer.getViewportHeight ())
+ };
+ var full = clipData.w * clipData.h;
+ var visibleWidth = (r.x1 - r.x0);
+ var visibleHeight = (r.y1 - r.y0);
+ if (visibleWidth > 0 && visibleHeight > 0) {
+ var visible = visibleWidth * visibleHeight;
+
+ return (visible / full) >= frac;
+ } else {
+ return false;
+ }
+ }
+};
+
+/**
+ * Hides the hotspot if its center is outside the viewport.
+ *
+ * @type function(clipData)
+ * @see bigshot.VRHotspot#clip
+ * @see bigshot.VRHotspot#clippingStrategy
+ */
+bigshot.VRHotspot.CLIP_CENTER = function (panorama) {
+ return function (clipData) {
+ var c = {
+ x : clipData.x + clipData.w / 2,
+ y : clipData.y + clipData.h / 2
+ };
+ return c.x >= 0 && c.x < panorama.renderer.getViewportWidth () &&
+ c.y >= 0 && c.y < panorama.renderer.getViewportHeight ();
+ }
+}
+
+/**
+ * Resizes the hotspot to fit in the viewport. Hides the hotspot if
+ * it is completely outside the viewport.
+ *
+ * @type function(clipData)
+ * @see bigshot.VRHotspot#clip
+ * @see bigshot.VRHotspot#clippingStrategy
+ */
+bigshot.VRHotspot.CLIP_ADJUST = function (panorama) {
+ return function (clipData) {
+ if (clipData.x < 0) {
+ clipData.w -= -clipData.x;
+ clipData.x = 0;
+ }
+ if (clipData.y < 0) {
+ clipData.h -= -clipData.y;
+ clipData.y = 0;
+ }
+ if (clipData.x + clipData.w > panorama.renderer.getViewportWidth ()) {
+ clipData.w = panorama.renderer.getViewportWidth () - clipData.x - 1;
+ }
+ if (clipData.y + clipData.h > panorama.renderer.getViewportHeight ()) {
+ clipData.h = panorama.renderer.getViewportHeight () - clipData.y - 1;
+ }
+
+ return clipData.w > 0 && clipData.h > 0;
+ }
+}
+
+/**
+ * Shrinks the hotspot as it approaches the viewport edges.
+ *
+ * @param s The full size of the hotspot.
+ * @param s.w The full width of the hotspot, in pixels.
+ * @param s.h The full height of the hotspot, in pixels.
+ * @see bigshot.VRHotspot#clip
+ * @see bigshot.VRHotspot#clippingStrategy
+ */
+bigshot.VRHotspot.CLIP_ZOOM = function (panorama, s, maxDistanceInViewportHeights) {
+ return function (clipData) {
+ if (clipData.x >= 0 && clipData.y >= 0 && (clipData.x + s.w) < panorama.renderer.getViewportWidth ()
+ && (clipData.y + s.h) < panorama.renderer.getViewportHeight ()) {
+ clipData.w = s.w;
+ clipData.h = s.h;
+ return true;
+ }
+
+ var distance = 0;
+ if (clipData.x < 0) {
+ distance = Math.max (-clipData.x, distance);
+ }
+ if (clipData.y < 0) {
+ distance = Math.max (-clipData.y, distance);
+ }
+ if (clipData.x + s.w > panorama.renderer.getViewportWidth ()) {
+ distance = Math.max (clipData.x + s.w - panorama.renderer.getViewportWidth (), distance);
+ }
+ if (clipData.y + s.h > panorama.renderer.getViewportHeight ()) {
+ distance = Math.max (clipData.y + s.h - panorama.renderer.getViewportHeight (), distance);
+ }
+
+ distance /= panorama.renderer.getViewportHeight ();
+ if (distance > maxDistanceInViewportHeights) {
+ return false;
+ }
+
+ var scale = 1 / (1 + distance);
+
+ clipData.w = s.w * scale;
+ clipData.h = s.w * scale;
+ if (clipData.x < 0) {
+ clipData.x = 0;
+ }
+ if (clipData.y < 0) {
+ clipData.y = 0;
+ }
+ if (clipData.x + clipData.w > panorama.renderer.getViewportWidth ()) {
+ clipData.x = panorama.renderer.getViewportWidth () - clipData.w;
+ }
+ if (clipData.y + clipData.h > panorama.renderer.getViewportHeight ()) {
+ clipData.y = panorama.renderer.getViewportHeight () - clipData.h;
+ }
+
+ return true;
+ }
+}
+
+/**
+ * Progressively fades the hotspot as it gets closer to the viewport edges.
+ *
+ * @param {number} borderSizeInPixels the distance from the edge, in pixels,
+ * where the hotspot is completely opaque.
+ * @see bigshot.VRHotspot#clip
+ * @see bigshot.VRHotspot#clippingStrategy
+ */
+bigshot.VRHotspot.CLIP_FADE = function (panorama, borderSizeInPixels) {
+ return function (clipData) {
+ var distance = Math.min (
+ clipData.x,
+ clipData.y,
+ panorama.renderer.getViewportWidth () - (clipData.x + clipData.w),
+ panorama.renderer.getViewportHeight () - (clipData.y + clipData.h));
+
+ if (distance <= 0) {
+ return false;
+ } else if (distance <= borderSizeInPixels) {
+ clipData.opacity = (distance / borderSizeInPixels);
+ return true;
+ } else {
+ clipData.opacity = 1.0;
+ return true;
+ }
+ }
+}
+
+bigshot.VRHotspot.prototype = {
+
+ /**
+ * Layout and resize the hotspot. Called by the panorama.
+ */
+ layout : function () {},
+
+ /**
+ * Helper function to rotate a point around an axis.
+ *
+ * @param {number} ang the angle
+ * @param {bigshot.Point3D} vector the vector to rotate around
+ * @param {Vector} point the point
+ * @type Vector
+ * @private
+ */
+ rotate : function (ang, vector, point) {
+ var arad = ang * Math.PI / 180.0;
+ var m = Matrix.Rotation(arad, $V([vector.x, vector.y, vector.z])).ensure4x4 ();
+ return m.xPoint3Dhom1 (point);
+ },
+
+ /**
+ * Converts the polar coordinates to world coordinates.
+ * The distance is assumed to be 1.0.
+ *
+ * @param yaw the yaw, in degrees
+ * @param pitch the pitch, in degrees
+ * @type bigshot.Point3D
+ */
+ toVector : function (yaw, pitch) {
+ var point = { x : 0, y : 0, z : -1 };
+ point = this.rotate (-pitch, { x : 1, y : 0, z : 0 }, point);
+ point = this.rotate (-yaw, { x : 0, y : 1, z : 0 }, point);
+ return point;
+ },
+
+ /**
+ * Converts the world-coordinate point p to screen coordinates.
+ *
+ * @param {bigshot.Point3D} p the world-coordinate point
+ * @type point
+ */
+ toScreen : function (p) {
+ var res = this.panorama.renderer.transformToScreen (p)
+ return res;
+ },
+
+ /**
+ * Clips the hotspot against the viewport. Both parameters
+ * are in/out. Clipping is done by adjusting the values of the
+ * parameters.
+ *
+ * @param clipData Information about the hotspot.
+ * @param {number} clipData.x the x-coordinate of the top-left corner of the hotspot, in pixels.
+ * @param {number} clipData.y the y-coordinate of the top-left corner of the hotspot, in pixels.
+ * @param {number} clipData.w the width of the hotspot, in pixels.
+ * @param {number} clipData.h the height of the hotspot, in pixels.
+ * @param {number} [clipData.opacity] the opacity of the hotspot, ranging from 0.0 (transparent)
+ * to 1.0 (opaque). If set, the opacity of the hotspot element is adjusted.
+ * @type boolean
+ * @return true if the hotspot is visible, false otherwise
+ */
+ clip : function (clipData) {
+ return this.clippingStrategy (clipData);
+ }
+}
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new point-hotspot and attaches it to a VR panorama.
+ *
+ * @class A VR panorama point-hotspot.
+ *
+ * A Hotspot is simply an HTML element that is moved / hidden etc.
+ * to overlay a given position in the panorama. The element is moved
+ * by setting its <code>style.top</code> and <code>style.left</code>
+ * values.
+ *
+ * @augments bigshot.VRHotspot
+ * @param {bigshot.VRPanorama} panorama the panorama to attach this hotspot to
+ * @param {number} yaw the yaw coordinate of the hotspot
+ * @param {number} pitch the pitch coordinate of the hotspot
+ * @param {HTMLElement} element the HTML element
+ * @param {number} offsetX the offset to add to the screen coordinate corresponding
+ * to the hotspot's polar coordinates. Use this to center the hotspot horizontally.
+ * @param {number} offsetY the offset to add to the screen coordinate corresponding
+ * to the hotspot's polar coordinates. Use this to center the hotspot vertically.
+ */
+bigshot.VRPointHotspot = function (panorama, yaw, pitch, element, offsetX, offsetY) {
+ bigshot.VRHotspot.call (this, panorama);
+ this.element = element;
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ this.point = this.toVector (yaw, pitch);
+}
+
+bigshot.VRPointHotspot.prototype = {
+ layout : function () {
+ var p = this.toScreen (this.point);
+
+ var visible = false;
+ if (p != null) {
+ var s = this.panorama.browser.getElementSize (this.element);
+ p.w = s.w;
+ p.h = s.h;
+
+ p.x += this.offsetX;
+ p.y += this.offsetY;
+
+ if (this.clip (p)) {
+ this.element.style.top = (p.y) + "px";
+ this.element.style.left = (p.x) + "px";
+ this.element.style.width = (p.w) + "px";
+ this.element.style.height = (p.h) + "px";
+ if (p.opacity) {
+ this.element.style.opacity = p.opacity;
+ }
+ this.element.style.visibility = "inherit";
+ visible = true;
+ }
+ }
+
+ if (!visible) {
+ this.element.style.visibility = "hidden";
+ }
+ }
+}
+
+bigshot.Object.extend (bigshot.VRPointHotspot, bigshot.VRHotspot);
+bigshot.Object.validate ("bigshot.VRPointHotspot", bigshot.VRHotspot);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new rectangular hotspot and attaches it to a VR panorama.
+ *
+ * @class A rectangular VR panorama hotspot.
+ *
+ * A rectangular hotspot is simply an HTML element that is moved / resized / hidden etc.
+ * to overlay a given rectangle in the panorama. The element is moved
+ * by setting its <code>style.top</code> and <code>style.left</code>
+ * values, and resized by setting its <code>style.width</code> and <code>style.height</code>
+ * values.
+ *
+ * @augments bigshot.VRHotspot
+ * @param {bigshot.VRPanorama} panorama the panorama to attach this hotspot to
+ * @param {number} yaw0 the yaw coordinate of the top-left corner of the hotspot
+ * @param {number} pitch0 the pitch coordinate of the top-left corner of the hotspot
+ * @param {number} yaw1 the yaw coordinate of the bottom-right corner of the hotspot
+ * @param {number} pitch1 the pitch coordinate of the bottom-right corner of the hotspot
+ * @param {HTMLElement} element the HTML element
+ */
+bigshot.VRRectangleHotspot = function (panorama, yaw0, pitch0, yaw1, pitch1, element) {
+ bigshot.VRHotspot.call (this, panorama);
+
+ this.element = element;
+ this.point0 = this.toVector (yaw0, pitch0);
+ this.point1 = this.toVector (yaw1, pitch1);
+}
+
+bigshot.VRRectangleHotspot.prototype = {
+ layout : function () {
+ var p = this.toScreen (this.point0);
+ var p1 = this.toScreen (this.point1);
+
+ var visible = false;
+ if (p != null && p1 != null) {
+ var cd = {
+ x : p.x,
+ y : p.y,
+ opacity : 1.0,
+ w : p1.x - p.x,
+ h : p1.y - p.y
+ };
+
+ if (this.clip (cd)) {
+ this.element.style.top = (cd.y) + "px";
+ this.element.style.left = (cd.x) + "px";
+ this.element.style.width = (cd.w) + "px";
+ this.element.style.height = (cd.h) + "px";
+ this.element.style.visibility = "inherit";
+ visible = true;
+ }
+ }
+
+ if (!visible) {
+ this.element.style.visibility = "hidden";
+ }
+ }
+}
+
+bigshot.Object.extend (bigshot.VRRectangleHotspot, bigshot.VRHotspot);
+bigshot.Object.validate ("bigshot.VRRectangleHotspot", bigshot.VRHotspot);
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new parameter block.
+ *
+ * @class Parameters for the adaptive LOD monitor.
+ */
+bigshot.AdaptiveLODMonitorParameters = function (values) {
+
+ /**
+ * The VR panorama to adjust.
+ *
+ * @type bigshot.VRPanorama
+ */
+ this.vrPanorama = null;
+
+ /**
+ * The target framerate in frames per second.
+ * The monitor will try to achieve an average frame render time
+ * of <i>1 / targetFps</i> seconds.
+ *
+ * @default 30
+ * @type float
+ */
+ this.targetFps = 30;
+
+ /**
+ * The tolerance for the rendering time. The monitor will adjust the
+ * level of detail if the average frame render time rises above
+ * <i>target frame render time * (1.0 + tolerance)</i> or falls below
+ * <i>target frame render time / (1.0 + tolerance)</i>.
+ *
+ * @default 0.3
+ * @type float
+ */
+ this.tolerance = 0.3;
+
+ /**
+ * The rate at which the level of detail is adjusted.
+ * For detail increase, the detail is multiplied with (1.0 + rate),
+ * for decrease divided.
+ *
+ * @default 0.1
+ * @type float
+ */
+ this.rate = 0.1;
+
+ /**
+ * Minimum texture magnification.
+ *
+ * @default 1.5
+ * @type float
+ */
+ this.minMag = 1.5;
+
+ /**
+ * Maximum texture magnification.
+ *
+ * @default 16
+ * @type float
+ */
+ this.maxMag = 16;
+
+ /**
+ * Texture magnification for HQ render passes.
+ *
+ * @default 1.5
+ * @type float
+ */
+ this.hqRenderMag = 1.5;
+
+ /**
+ * Delay in milliseconds before executing
+ * a HQ render pass.
+ *
+ * @default 2000
+ * @type int
+ */
+ this.hqRenderDelay = 2000;
+
+ /**
+ * Interval in milliseconds for the
+ * HQ render pass timer.
+ *
+ * @default 1000
+ * @type int
+ */
+ this.hqRenderInterval = 1000;
+
+ if (values) {
+ for (var k in values) {
+ this[k] = values[k];
+ }
+ }
+
+ this.merge = function (values, overwrite) {
+ for (var k in values) {
+ if (overwrite || !this[k]) {
+ this[k] = values[k];
+ }
+ }
+ }
+ return this;
+};
+/*
+ * Copyright 2010 - 2012 Leo Sutic <leo.sutic@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Creates a new adaptive level-of-detail monitor.
+ *
+ * @class An adaptive LOD monitor that adjusts the level of detail of a VR panorama
+ * to achieve a desired frame rate. To connect it to a VR panorama, use the
+ * {@link bigshot.AdaptiveLODMonitor#getListener} method to get a render listener
+ * that can be passed to {@link bigshot.VRPanorama#addRenderListener}.
+ *
+ * <p>The monitor maintains two render modes - a high quality one with a fixed
+ * level of detail, and a low(er) quality one with variable level of detail.
+ * If the panorama is idle for more than a set interval, a high-quality render is
+ * performed.
+ *
+ * @param {bigshot.AdaptiveLODMonitorParameters} parameters parameters for the LOD monitor.
+ *
+ * @see bigshot.AdaptiveLODMonitorParameters for a list of parameters
+ *
+ * @example
+ * var bvr = new bigshot.VRPanorama ( ... );
+ * var lodMonitor = new bigshot.AdaptiveLODMonitor (
+ * new bigshot.AdaptiveLODMonitorParameters ({
+ * vrPanorama : bvr,
+ * targetFps : 30,
+ * tolerance : 0.3,
+ * rate : 0.1,
+ * minMag : 1.5,
+ * maxMag : 16
+ * }));
+ * bvr.addRenderListener (lodMonitor.getListener ());
+ */
+bigshot.AdaptiveLODMonitor = function (parameters) {
+ this.setParameters (parameters);
+
+ /**
+ * The current adaptive detail level.
+ * @type float
+ * @private
+ */
+ this.currentAdaptiveMagnification = parameters.vrPanorama.getMaxTextureMagnification ();
+
+ /**
+ * The number of frames that have been rendered.
+ * @type int
+ * @private
+ */
+ this.frames = 0;
+
+ /**
+ * The total number of times we have sampled the render time.
+ * @type int
+ * @private
+ */
+ this.samples = 0;
+
+ /**
+ * The sum of sample times from all samples of render time in milliseconds.
+ * @type int
+ * @private
+ */
+ this.renderTimeTotal = 0;
+
+ /**
+ * The sum of sample times from the recent sample pass in milliseconds.
+ * @type int
+ * @private
+ */
+ this.renderTimeLast = 0;
+
+ /**
+ * The number of samples currently done in the recent sample pass.
+ * @type int
+ * @private
+ */
+ this.samplesLast = 0;
+
+ /**
+ * The start time, in milliseconds, of the last sample.
+ * @type int
+ * @private
+ */
+ this.startTime = 0;
+
+ /**
+ * The time, in milliseconds, when the panorama was last rendered.
+ * @type int
+ * @private
+ */
+ this.lastRender = 0;
+
+ this.hqRender = false;
+ this.hqMode = false;
+ this.hqRenderWaiting = false;
+
+ /**
+ * Flag to enable / disable the monitor.
+ * @type boolean
+ * @private
+ */
+ this.enabled = true;
+
+ var that = this;
+ this.listenerFunction = function (state, cause, data) {
+ that.listener (state, cause, data);
+ };
+};
+
+bigshot.AdaptiveLODMonitor.prototype = {
+ averageRenderTime : function () {
+ if (this.samples > 0) {
+ return this.renderTimeTotal / this.samples;
+ } else {
+ return -1;
+ }
+ },
+
+ /**
+ * @param {bigshot.AdaptiveLODMonitorParameters} parameters
+ */
+ setParameters : function (parameters) {
+ this.parameters = parameters;
+ this.targetTime = 1000 / this.parameters.targetFps;
+
+ this.lowerTime = this.targetTime / (1.0 + this.parameters.tolerance);
+ this.upperTime = this.targetTime * (1.0 + this.parameters.tolerance);
+ },
+
+ setEnabled : function (enabled) {
+ this.enabled = enabled;
+ },
+
+ averageRenderTimeLast : function () {
+ if (this.samples > 0) {
+ return this.renderTimeLast / this.samplesLast;
+ } else {
+ return -1;
+ }
+ },
+
+ getListener : function () {
+ return this.listenerFunction;
+ },
+
+ increaseDetail : function () {
+ this.currentAdaptiveMagnification = Math.max (this.parameters.minMag, this.currentAdaptiveMagnification / (1.0 + this.parameters.rate));
+ },
+
+ decreaseDetail : function () {
+ this.currentAdaptiveMagnification = Math.min (this.parameters.maxMag, this.currentAdaptiveMagnification * (1.0 + this.parameters.rate));
+ },
+
+ sample : function () {
+ var deltat = new Date ().getTime () - this.startTime;
+ this.samples++;
+ this.renderTimeTotal += deltat;
+
+ this.samplesLast++;
+ this.renderTimeLast += deltat;
+
+ if (this.samplesLast > 4) {
+ var averageLast = this.renderTimeLast / this.samplesLast;
+
+ if (averageLast < this.lowerTime) {
+ this.increaseDetail ();
+ } else if (averageLast > this.upperTime) {
+ this.decreaseDetail ();
+ }
+
+ this.samplesLast = 0;
+ this.renderTimeLast = 0;
+ }
+ },
+
+ hqRenderTick : function () {
+ if (this.lastRender < new Date ().getTime () - this.parameters.hqRenderDelay) {
+ this.hqRender = true;
+ this.hqMode = true;
+ if (this.enabled) {
+ this.parameters.vrPanorama.setMaxTextureMagnification (this.parameters.hqRenderMag);
+ this.parameters.vrPanorama.render ();
+ }
+
+ this.hqRender = false;
+ this.hqRenderWaiting = false;
+ } else {
+ var that = this;
+ setTimeout (function () {
+ that.hqRenderTick ();
+ }, this.parameters.hqRenderInterval);
+ }
+ },
+
+ listener : function (state, cause, data) {
+ if (!this.enabled) {
+ return;
+ }
+
+ if (this.hqRender) {
+ return;
+ }
+
+ if (this.hqMode && cause == bigshot.VRPanorama.ONRENDER_TEXTURE_UPDATE) {
+ this.parameters.vrPanorama.setMaxTextureMagnification (this.parameters.minMag);
+ return;
+ } else {
+ this.hqMode = false;
+ }
+
+ this.parameters.vrPanorama.setMaxTextureMagnification (this.currentAdaptiveMagnification);
+
+ this.frames++;
+ if ((this.frames < 20 || this.frames % 5 == 0) && state == bigshot.VRPanorama.ONRENDER_BEGIN) {
+ this.startTime = new Date ().getTime ();
+ this.lastRender = this.startTime;
+ var that = this;
+ setTimeout (function () {
+ that.sample ();
+ }, 1);
+ if (!this.hqRenderWaiting) {
+ this.hqRenderWaiting = true;
+ setTimeout (function () {
+ that.hqRenderTick ();
+ }, this.parameters.hqRenderInterval);
+ }
+ }
+ }
+};
+}
diff --git a/js/vendor/commonmark/LICENSE b/js/vendor/commonmark/LICENSE
new file mode 100644
index 00000000..e16d57a8
--- /dev/null
+++ b/js/vendor/commonmark/LICENSE
@@ -0,0 +1,115 @@
+Copyright (c) 2014, John MacFarlane
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+---
+
+lib/normalize-reference.js is a slightly modified version of
+https://github.com/dmoscrop/fold-case:
+
+Copyright Mathias Bynens <https://mathiasbynens.be/>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+---
+
+lib/from-code-point.js is derived from a polyfill
+Copyright Mathias Bynens <http://mathiasbynens.be/>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+---
+
+bench/samples/*.md:
+
+With the exception of `bench/samples/README.md`, the samples in
+`bench/samples` are taken from https://github.com/markdown-it/markdown-it.
+
+Copyright (c) 2014 Vitaly Puzrin, Alex Kocharin.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+---
+
+The CommonMark spec (spec.txt) in test/ is
+
+Copyright (C) 2014-15 John MacFarlane
+
+Released under the Creative Commons CC-BY-SA 4.0 license:
+<http://creativecommons.org/licenses/by-sa/4.0/>.
diff --git a/js/vendor/commonmark/README.md b/js/vendor/commonmark/README.md
new file mode 100644
index 00000000..89a69741
--- /dev/null
+++ b/js/vendor/commonmark/README.md
@@ -0,0 +1,326 @@
+commonmark.js
+=============
+
+[![Build Status](https://img.shields.io/travis/jgm/commonmark.js/master.svg?style=flat)](https://travis-ci.org/jgm/commonmark.js)
+[![NPM version](https://img.shields.io/npm/v/commonmark.svg?style=flat)](https://www.npmjs.org/package/commonmark)
+
+
+CommonMark is a rationalized version of Markdown syntax,
+with a [spec][the spec] and BSD-licensed reference
+implementations in C and JavaScript.
+
+ [the spec]: http://spec.commonmark.org
+
+For more information, see <http://commonmark.org>.
+
+This repository contains the JavaScript reference implementation.
+It provides a library with functions for parsing CommonMark
+documents to an abstract syntax tree (AST), manipulating the AST,
+and rendering the document to HTML or to an XML representation of the
+AST.
+
+To play with this library without installing it, see
+the live dingus at <http://try.commonmark.org/>.
+
+Installing
+----------
+
+You can install the library using `npm`:
+
+ npm install commonmark
+
+This package includes the commonmark library and a
+command-line executable, `commonmark`.
+
+For client-side use, you can do `make dist` to produce
+a standalone JavaScript file `js/dist/commonmark.js`,
+suitable for linking into a web page, or fetch the latest
+from <http://spec.commonmark.org/js/commonmark.js>, or
+`bower install commonmark`.
+
+To run tests for the JavaScript library:
+
+ npm install # if needed to fetch dependencies
+ make test
+
+To run benchmarks against some other JavaScript converters:
+
+ npm install benchmark showdown marked markdown-it
+ make bench
+
+To start an interactive dingus that you can use to try out
+the library:
+
+ make dingus
+
+Usage
+-----
+
+Instead of converting Markdown directly to HTML, as most converters
+do, `commonmark.js` parses Markdown to an AST (abstract syntax tree),
+and then renders this AST as HTML. This opens up the possibility of
+manipulating the AST between parsing and rendering. For example, one
+could transform emphasis into ALL CAPS.
+
+Here's a basic usage example:
+
+``` js
+var reader = new commonmark.Parser();
+var writer = new commonmark.HtmlRenderer();
+var parsed = reader.parse("Hello *world*"); // parsed is a 'Node' tree
+// transform parsed if you like...
+var result = writer.render(parsed); // result is a String
+```
+
+The constructors for `Parser` and `HtmlRenderer` take an optional
+`options` parameter:
+
+``` js
+var writer = new commonmark.HtmlRenderer({sourcepos: true});
+```
+
+The following options are currently supported:
+
+- `sourcepos`: if `true`, source position information for block-level
+ elements will be rendered in the `data-sourcepos` attribute (for
+ HTML) or the `sourcepos` attribute (for XML).
+- `smart`: if `true`, straight quotes will be made curly, `--` will
+ be changed to an en dash, `---` will be changed to an em dash, and
+ `...` will be changed to ellipses.
+- `safe`: if `true`, raw HTML will not be passed through to HTML
+ output (it will be replaced by comments), and potentially unsafe
+ URLs in links and images (those beginning with `javascript:`,
+ `vbscript:`, `file:`, and with a few exceptions `data:`) will
+ be replaced with empty strings.
+
+It is also possible to override the `escape` and `softbreak`
+properties of a renderer. So, to make soft breaks render as hard
+breaks in HTML:
+
+``` js
+var writer = new commonmark.HtmlRenderer;
+writer.softbreak = "<br />";
+```
+
+To make them render as spaces:
+
+``` js
+writer.softbreak = " ";
+```
+
+To override `escape`, pass it a function with two parameters:
+the first is the string to be escaped, the second is a boolean
+that is `true` if the escaped string is to be included in an
+attribute.
+
+In addition to the `HtmlRenderer`, there is an `XmlRenderer`, which
+will produce an XML representation of the AST:
+
+``` js
+var writer = new commonmark.XmlRenderer({sourcepos: true});
+```
+
+The parser returns a Node. The following public properties are defined
+(those marked "read-only" have only a getter, not a setter):
+
+- `type` (read-only): a String, one of
+ `Text`, `Softbreak`, `Hardbreak`, `Emph`, `Strong`,
+ `Html`, `Link`, `Image`, `Code`, `Document`, `Paragraph`,
+ `BlockQuote`, `Item`, `List`, `Header`, `CodeBlock`,
+ `HtmlBlock` `HorizontalRule`.
+- `firstChild` (read-only): a Node or null.
+- `lastChild` (read-only): a Node or null.
+- `next` (read-only): a Node or null.
+- `prev` (read-only): a Node or null.
+- `parent` (read-only): a Node or null.
+- `sourcepos` (read-only): an Array with the following form:
+ `[[startline, startcolumn], [endline, endcolumn]]`.
+- `isContainer` (read-only): `true` if the Node can contain other
+ Nodes as children.
+- `literal`: the literal String content of the node or null.
+- `destination`: link or image destination (String) or null.
+- `title`: link or image title (String) or null.
+- `info`: fenced code block info string (String) or null.
+- `level`: header level (Number).
+- `listType`: a String, either `Bullet` or `Ordered`.
+- `listTight`: `true` if list is tight.
+- `listStart`: a Number, the starting number of an ordered list.
+- `listDelimiter`: a String, either `)` or `.` for an ordered list.
+
+Nodes have the following public methods:
+
+- `appendChild(child)`: Append a Node `child` to the end of the
+ Node's children.
+- `prependChild(child)`: Prepend a Node `child` to the end of the
+ Node's children.
+- `unlink()`: Remove the Node from the tree, severing its links
+ with siblings and parents, and closing up gaps as needed.
+- `insertAfter(sibling)`: Insert a Node `sibling` after the Node.
+- `insertBefore(sibling)`: Insert a Node `sibling` before the Node.
+- `walker()`: Returns a NodeWalker that can be used to iterate through
+ the Node tree rooted in the Node.
+
+The NodeWalker returned by `walker()` has two methods:
+
+- `next()`: Returns an object with properties `entering` (a boolean,
+ which is `true` when we enter a Node from a parent or sibling, and
+ `false` when we reenter it from a child). Returns `null` when
+ we have finished walking the tree.
+- `resumeAt(node, entering)`: Resets the iterator to resume at the
+ specified node and setting for `entering`. (Normally this isn't
+ needed unless you do destructive updates to the Node tree.)
+
+Here is an example of the use of a NodeWalker to iterate through
+the tree, making transformations. This simple example converts
+the contents of all `Text` nodes to ALL CAPS:
+
+``` js
+var walker = parsed.walker();
+var event, node;
+
+while ((event = walker.next())) {
+ node = event.node;
+ if (event.entering && node.type === 'Text') {
+ node.literal = node.literal.toUpperCase();
+ }
+}
+```
+
+This more complex example converts emphasis to ALL CAPS:
+
+``` js
+var walker = parsed.walker();
+var event, node;
+var inEmph = false;
+
+while ((event = walker.next())) {
+ node = event.node;
+ if (node.type === 'Emph') {
+ if (event.entering) {
+ inEmph = true;
+ } else {
+ inEmph = false;
+ // add Emph node's children as siblings
+ while (node.firstChild) {
+ node.insertBefore(node.firstChild);
+ }
+ // remove the empty Emph node
+ node.unlink()
+ }
+ } else if (inEmph && node.type === 'Text') {
+ node.literal = node.literal.toUpperCase();
+ }
+}
+```
+
+Exercises for the reader: write a transform to
+
+1. De-linkify a document, transforming links to regular text.
+2. Remove all raw HTML (`Html` and `HtmlBlock` nodes).
+3. Run fenced code blocks marked with a language name through
+ a syntax highlighting library, replacing them with an `HtmlBlock`
+ containing the highlighted code.
+4. Print warnings to the console for images without image
+ descriptions or titles.
+
+A note on security
+------------------
+
+The library does not attempt to sanitize link attributes or
+raw HTML. If you use this library in applications that accept
+untrusted user input, you should either enable the `safe` option
+(see above) or run the output through an HTML sanitizer to protect against
+[XSS attacks](http://en.wikipedia.org/wiki/Cross-site_scripting).
+
+Performance
+-----------
+
+Performance is excellent, roughly on par with `marked`. On a benchmark
+converting an 11 MB Markdown file built by concatenating the Markdown
+sources of all localizations of the first edition of
+[*Pro Git*](https://github.com/progit/progit/tree/master/en) by Scott
+Chacon, the command-line tool, `commonmark` is just a bit slower than
+the C program `discount`, roughly ten times faster than PHP Markdown,
+a hundred times faster than Python Markdown, and more than
+a thousand times faster than `Markdown.pl`.
+
+Here are some focused benchmarks of four JavaScript libraries
+(using versions available on 24 Jan 2015). They test performance
+on different kinds of Markdown texts. (Most of these samples
+are taken from the
+[markdown-it](https://github.com/markdown-it/markdown-it)
+repository.) Results show a ratio of ops/second (higher is better)
+against showdown (which is usually the slowest implementation).
+Versions: commonmark.js 0.21.0, markdown-it 4.3.0,
+showdown 1.2.0, marked 0.3.3, with node 0.10.25.
+
+| Sample |showdown |commonmark|marked |markdown-it|
+|--------------------------|---------:|---------:|---------:|----------:|
+|[block-bq-flat.md] | 1| 4.2| 4.9| 4.9|
+|[block-bq-nested.md] | 1| 10.0| 7.6| 11.1|
+|[block-code.md] | 1| 3.8| 10.0| 14.5|
+|[block-fences.md] | 1| 6.0| 16.5| 14.3|
+|[block-heading.md] | 1| 3.9| 4.6| 5.5|
+|[block-hr.md] | 1| 2.6| 3.0| 3.8|
+|[block-html.md] | 1| 1.7| 0.8| 3.9|
+|[block-lheading.md] | 1| 3.6| 4.6| 3.1|
+|[block-list-flat.md] | 1| 4.3| 4.6| 6.5|
+|[block-list-nested.md] | 1| 7.1| 6.1| 13.2|
+|[block-ref-flat.md] | 1| 0.6| 0.4| 0.5|
+|[block-ref-nested.md] | 1| 0.5| 0.5| 0.7|
+|[inline-autolink.md] | 1| 2.0| 3.4| 2.5|
+|[inline-backticks.md] | 1| 5.6| 4.6| 7.9|
+|[inline-em-flat.md] | 1| 1.0| 1.0| 1.5|
+|[inline-em-nested.md] | 1| 1.2| 1.2| 1.4|
+|[inline-em-worst.md] | 1| 1.5| 1.3| 0.9|
+|[inline-entity.md] | 1| 1.0| 3.7| 2.5|
+|[inline-escape.md] | 1| 2.0| 1.3| 4.3|
+|[inline-html.md] | 1| 2.0| 3.7| 3.0|
+|[inline-links-flat.md] | 1| 2.4| 2.5| 2.4|
+|[inline-links-nested.md] | 1| 1.8| 0.5| 0.3|
+|[inline-newlines.md] | 1| 1.8| 1.8| 2.4|
+|[lorem1.md] | 1| 6.3| 3.4| 3.6|
+|[rawtabs.md] | 1| 4.3| 4.2| 4.9|
+|[README.md] | 1| 3.6| 3.3| 4.1|
+
+[block-lheading.md]: bench/samples/block-lheading.md
+[block-heading.md]: bench/samples/block-heading.md
+[inline-html.md]: bench/samples/inline-html.md
+[lorem1.md]: bench/samples/lorem1.md
+[block-list-flat.md]: bench/samples/block-list-flat.md
+[block-hr.md]: bench/samples/block-hr.md
+[block-fences.md]: bench/samples/block-fences.md
+[block-ref-flat.md]: bench/samples/block-ref-flat.md
+[block-bq-flat.md]: bench/samples/block-bq-flat.md
+[inline-escape.md]: bench/samples/inline-escape.md
+[rawtabs.md]: bench/samples/rawtabs.md
+[block-list-nested.md]: bench/samples/block-list-nested.md
+[block-ref-nested.md]: bench/samples/block-ref-nested.md
+[block-bq-nested.md]: bench/samples/block-bq-nested.md
+[inline-entity.md]: bench/samples/inline-entity.md
+[README.md]: bench/samples/README.md
+[block-html.md]: bench/samples/block-html.md
+[inline-newlines.md]: bench/samples/inline-newlines.md
+[inline-links-flat.md]: bench/samples/inline-links-flat.md
+[inline-em-flat.md]: bench/samples/inline-em-flat.md
+[inline-backticks.md]: bench/samples/inline-backticks.md
+[block-code.md]: bench/samples/block-code.md
+[inline-autolink.md]: bench/samples/inline-autolink.md
+[inline-links-nested.md]: bench/samples/inline-links-nested.md
+[inline-em-worst.md]: bench/samples/inline-em-worst.md
+[inline-em-nested.md]: bench/samples/inline-em-nested.md
+
+To generate this table,
+
+ npm install showdown marked markdown-it benchmark
+ make bench-detailed
+
+Authors
+-------
+
+John MacFarlane wrote the first version of the JavaScript
+implementation. The block parsing algorithm was worked out together
+with David Greenspan. Kārlis Gaņģis helped work out a better parsing
+algorithm for links and emphasis, eliminating several worst-case
+performance issues. Vitaly Puzrin has offered much good advice
+about optimization and other issues.
diff --git a/js/vendor/commonmark/dist/commonmark.js b/js/vendor/commonmark/dist/commonmark.js
new file mode 100644
index 00000000..95ccc80a
--- /dev/null
+++ b/js/vendor/commonmark/dist/commonmark.js
@@ -0,0 +1,3214 @@
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var o;"undefined"!=typeof window?o=window:"undefined"!=typeof global?o=global:"undefined"!=typeof self&&(o=self),o.commonmark=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+"use strict";
+
+var Node = require('./node');
+var unescapeString = require('./common').unescapeString;
+var OPENTAG = require('./common').OPENTAG;
+var CLOSETAG = require('./common').CLOSETAG;
+
+var CODE_INDENT = 4;
+
+var C_NEWLINE = 10;
+var C_GREATERTHAN = 62;
+var C_LESSTHAN = 60;
+var C_SPACE = 32;
+var C_OPEN_BRACKET = 91;
+
+var InlineParser = require('./inlines');
+
+var reHtmlBlockOpen = [
+ /./, // dummy for 0
+ /^<(?:script|pre|style)(?:\s|>|$)/i,
+ /^<!--/,
+ /^<[?]/,
+ /^<![A-Z]/,
+ /^<!\[CDATA\[/,
+ /^<[/]?(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|title|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?:\s|[/]?[>]|$)/i,
+ new RegExp('^(?:' + OPENTAG + '|' + CLOSETAG + ')\s*$', 'i')
+];
+
+var reHtmlBlockClose = [
+ /./, // dummy for 0
+ /<\/(?:script|pre|style)>/i,
+ /-->/,
+ /\?>/,
+ />/,
+ /\]\]>/
+];
+
+var reHrule = /^(?:(?:\* *){3,}|(?:_ *){3,}|(?:- *){3,}) *$/;
+
+var reMaybeSpecial = /^[#`~*+_=<>0-9-]/;
+
+var reNonSpace = /[^ \t\f\v\r\n]/;
+
+var reBulletListMarker = /^[*+-]( +|$)/;
+
+var reOrderedListMarker = /^(\d{1,9})([.)])( +|$)/;
+
+var reATXHeaderMarker = /^#{1,6}(?: +|$)/;
+
+var reCodeFence = /^`{3,}(?!.*`)|^~{3,}(?!.*~)/;
+
+var reClosingCodeFence = /^(?:`{3,}|~{3,})(?= *$)/;
+
+var reSetextHeaderLine = /^(?:=+|-+) *$/;
+
+var reLineEnding = /\r\n|\n|\r/;
+
+// Returns true if string contains only space characters.
+var isBlank = function(s) {
+ return !(reNonSpace.test(s));
+};
+
+var peek = function(ln, pos) {
+ if (pos < ln.length) {
+ return ln.charCodeAt(pos);
+ } else {
+ return -1;
+ }
+};
+
+// DOC PARSER
+
+// These are methods of a Parser object, defined below.
+
+// Returns true if block ends with a blank line, descending if needed
+// into lists and sublists.
+var endsWithBlankLine = function(block) {
+ while (block) {
+ if (block._lastLineBlank) {
+ return true;
+ }
+ var t = block.type;
+ if (t === 'List' || t === 'Item') {
+ block = block._lastChild;
+ } else {
+ break;
+ }
+ }
+ return false;
+};
+
+// Break out of all containing lists, resetting the tip of the
+// document to the parent of the highest list, and finalizing
+// all the lists. (This is used to implement the "two blank lines
+// break of of all lists" feature.)
+var breakOutOfLists = function(block) {
+ var b = block;
+ var last_list = null;
+ do {
+ if (b.type === 'List') {
+ last_list = b;
+ }
+ b = b._parent;
+ } while (b);
+
+ if (last_list) {
+ while (block !== last_list) {
+ this.finalize(block, this.lineNumber);
+ block = block._parent;
+ }
+ this.finalize(last_list, this.lineNumber);
+ this.tip = last_list._parent;
+ }
+};
+
+// Add a line to the block at the tip. We assume the tip
+// can accept lines -- that check should be done before calling this.
+var addLine = function() {
+ this.tip._string_content += this.currentLine.slice(this.offset) + '\n';
+};
+
+// Add block of type tag as a child of the tip. If the tip can't
+// accept children, close and finalize it and try its parent,
+// and so on til we find a block that can accept children.
+var addChild = function(tag, offset) {
+ while (!this.blocks[this.tip.type].canContain(tag)) {
+ this.finalize(this.tip, this.lineNumber - 1);
+ }
+
+ var column_number = offset + 1; // offset 0 = column 1
+ var newBlock = new Node(tag, [[this.lineNumber, column_number], [0, 0]]);
+ newBlock._string_content = '';
+ this.tip.appendChild(newBlock);
+ this.tip = newBlock;
+ return newBlock;
+};
+
+// Parse a list marker and return data on the marker (type,
+// start, delimiter, bullet character, padding) or null.
+var parseListMarker = function(ln, offset, indent) {
+ var rest = ln.slice(offset);
+ var match;
+ var spaces_after_marker;
+ var data = { type: null,
+ tight: true, // lists are tight by default
+ bulletChar: null,
+ start: null,
+ delimiter: null,
+ padding: null,
+ markerOffset: indent };
+ if ((match = rest.match(reBulletListMarker))) {
+ spaces_after_marker = match[1].length;
+ data.type = 'Bullet';
+ data.bulletChar = match[0][0];
+
+ } else if ((match = rest.match(reOrderedListMarker))) {
+ spaces_after_marker = match[3].length;
+ data.type = 'Ordered';
+ data.start = parseInt(match[1]);
+ data.delimiter = match[2];
+ } else {
+ return null;
+ }
+ var blank_item = match[0].length === rest.length;
+ if (spaces_after_marker >= 5 ||
+ spaces_after_marker < 1 ||
+ blank_item) {
+ data.padding = match[0].length - spaces_after_marker + 1;
+ } else {
+ data.padding = match[0].length;
+ }
+ return data;
+};
+
+// Returns true if the two list items are of the same type,
+// with the same delimiter and bullet character. This is used
+// in agglomerating list items into lists.
+var listsMatch = function(list_data, item_data) {
+ return (list_data.type === item_data.type &&
+ list_data.delimiter === item_data.delimiter &&
+ list_data.bulletChar === item_data.bulletChar);
+};
+
+// Finalize and close any unmatched blocks. Returns true.
+var closeUnmatchedBlocks = function() {
+ if (!this.allClosed) {
+ // finalize any blocks not matched
+ while (this.oldtip !== this.lastMatchedContainer) {
+ var parent = this.oldtip._parent;
+ this.finalize(this.oldtip, this.lineNumber - 1);
+ this.oldtip = parent;
+ }
+ this.allClosed = true;
+ }
+};
+
+// 'finalize' is run when the block is closed.
+// 'continue' is run to check whether the block is continuing
+// at a certain line and offset (e.g. whether a block quote
+// contains a `>`. It returns 0 for matched, 1 for not matched,
+// and 2 for "we've dealt with this line completely, go to next."
+var blocks = {
+ Document: {
+ continue: function() { return 0; },
+ finalize: function() { return; },
+ canContain: function(t) { return (t !== 'Item'); },
+ acceptsLines: false
+ },
+ List: {
+ continue: function() { return 0; },
+ finalize: function(parser, block) {
+ var item = block._firstChild;
+ while (item) {
+ // check for non-final list item ending with blank line:
+ if (endsWithBlankLine(item) && item._next) {
+ block._listData.tight = false;
+ break;
+ }
+ // recurse into children of list item, to see if there are
+ // spaces between any of them:
+ var subitem = item._firstChild;
+ while (subitem) {
+ if (endsWithBlankLine(subitem) &&
+ (item._next || subitem._next)) {
+ block._listData.tight = false;
+ break;
+ }
+ subitem = subitem._next;
+ }
+ item = item._next;
+ }
+ },
+ canContain: function(t) { return (t === 'Item'); },
+ acceptsLines: false
+ },
+ BlockQuote: {
+ continue: function(parser) {
+ var ln = parser.currentLine;
+ if (!parser.indented &&
+ peek(ln, parser.nextNonspace) === C_GREATERTHAN) {
+ parser.advanceNextNonspace();
+ parser.advanceOffset(1, false);
+ if (peek(ln, parser.offset) === C_SPACE) {
+ parser.offset++;
+ }
+ } else {
+ return 1;
+ }
+ return 0;
+ },
+ finalize: function() { return; },
+ canContain: function(t) { return (t !== 'Item'); },
+ acceptsLines: false
+ },
+ Item: {
+ continue: function(parser, container) {
+ if (parser.blank && container._firstChild !== null) {
+ parser.advanceNextNonspace();
+ } else if (parser.indent >=
+ container._listData.markerOffset +
+ container._listData.padding) {
+ parser.advanceOffset(container._listData.markerOffset +
+ container._listData.padding, true);
+ } else {
+ return 1;
+ }
+ return 0;
+ },
+ finalize: function() { return; },
+ canContain: function(t) { return (t !== 'Item'); },
+ acceptsLines: false
+ },
+ Header: {
+ continue: function() {
+ // a header can never container > 1 line, so fail to match:
+ return 1;
+ },
+ finalize: function() { return; },
+ canContain: function() { return false; },
+ acceptsLines: false
+ },
+ HorizontalRule: {
+ continue: function() {
+ // an hrule can never container > 1 line, so fail to match:
+ return 1;
+ },
+ finalize: function() { return; },
+ canContain: function() { return false; },
+ acceptsLines: false
+ },
+ CodeBlock: {
+ continue: function(parser, container) {
+ var ln = parser.currentLine;
+ var indent = parser.indent;
+ if (container._isFenced) { // fenced
+ var match = (indent <= 3 &&
+ ln.charAt(parser.nextNonspace) === container._fenceChar &&
+ ln.slice(parser.nextNonspace).match(reClosingCodeFence));
+ if (match && match[0].length >= container._fenceLength) {
+ // closing fence - we're at end of line, so we can return
+ parser.finalize(container, parser.lineNumber);
+ return 2;
+ } else {
+ // skip optional spaces of fence offset
+ var i = container._fenceOffset;
+ while (i > 0 && peek(ln, parser.offset) === C_SPACE) {
+ parser.advanceOffset(1, false);
+ i--;
+ }
+ }
+ } else { // indented
+ if (indent >= CODE_INDENT) {
+ parser.advanceOffset(CODE_INDENT, true);
+ } else if (parser.blank) {
+ parser.advanceNextNonspace();
+ } else {
+ return 1;
+ }
+ }
+ return 0;
+ },
+ finalize: function(parser, block) {
+ if (block._isFenced) { // fenced
+ // first line becomes info string
+ var content = block._string_content;
+ var newlinePos = content.indexOf('\n');
+ var firstLine = content.slice(0, newlinePos);
+ var rest = content.slice(newlinePos + 1);
+ block.info = unescapeString(firstLine.trim());
+ block._literal = rest;
+ } else { // indented
+ block._literal = block._string_content.replace(/(\n *)+$/, '\n');
+ }
+ block._string_content = null; // allow GC
+ },
+ canContain: function() { return false; },
+ acceptsLines: true
+ },
+ HtmlBlock: {
+ continue: function(parser, container) {
+ return ((parser.blank &&
+ (container._htmlBlockType === 6 ||
+ container._htmlBlockType === 7)) ? 1 : 0);
+ },
+ finalize: function(parser, block) {
+ block._literal = block._string_content.replace(/(\n *)+$/, '');
+ block._string_content = null; // allow GC
+ },
+ canContain: function() { return false; },
+ acceptsLines: true
+ },
+ Paragraph: {
+ continue: function(parser) {
+ return (parser.blank ? 1 : 0);
+ },
+ finalize: function(parser, block) {
+ var pos;
+ var hasReferenceDefs = false;
+
+ // try parsing the beginning as link reference definitions:
+ while (peek(block._string_content, 0) === C_OPEN_BRACKET &&
+ (pos =
+ parser.inlineParser.parseReference(block._string_content,
+ parser.refmap))) {
+ block._string_content = block._string_content.slice(pos);
+ hasReferenceDefs = true;
+ }
+ if (hasReferenceDefs && isBlank(block._string_content)) {
+ block.unlink();
+ }
+ },
+ canContain: function() { return false; },
+ acceptsLines: true
+ }
+};
+
+// block start functions. Return values:
+// 0 = no match
+// 1 = matched container, keep going
+// 2 = matched leaf, no more block starts
+var blockStarts = [
+ // block quote
+ function(parser) {
+ if (!parser.indented &&
+ peek(parser.currentLine, parser.nextNonspace) === C_GREATERTHAN) {
+ parser.advanceNextNonspace();
+ parser.advanceOffset(1, false);
+ // optional following space
+ if (peek(parser.currentLine, parser.offset) === C_SPACE) {
+ parser.advanceOffset(1, false);
+ }
+ parser.closeUnmatchedBlocks();
+ parser.addChild('BlockQuote', parser.nextNonspace);
+ return 1;
+ } else {
+ return 0;
+ }
+ },
+
+ // ATX header
+ function(parser) {
+ var match;
+ if (!parser.indented &&
+ (match = parser.currentLine.slice(parser.nextNonspace).match(reATXHeaderMarker))) {
+ parser.advanceNextNonspace();
+ parser.advanceOffset(match[0].length, false);
+ parser.closeUnmatchedBlocks();
+ var container = parser.addChild('Header', parser.nextNonspace);
+ container.level = match[0].trim().length; // number of #s
+ // remove trailing ###s:
+ container._string_content =
+ parser.currentLine.slice(parser.offset).replace(/^ *#+ *$/, '').replace(/ +#+ *$/, '');
+ parser.advanceOffset(parser.currentLine.length - parser.offset);
+ return 2;
+ } else {
+ return 0;
+ }
+ },
+
+ // Fenced code block
+ function(parser) {
+ var match;
+ if (!parser.indented &&
+ (match = parser.currentLine.slice(parser.nextNonspace).match(reCodeFence))) {
+ var fenceLength = match[0].length;
+ parser.closeUnmatchedBlocks();
+ var container = parser.addChild('CodeBlock', parser.nextNonspace);
+ container._isFenced = true;
+ container._fenceLength = fenceLength;
+ container._fenceChar = match[0][0];
+ container._fenceOffset = parser.indent;
+ parser.advanceNextNonspace();
+ parser.advanceOffset(fenceLength, false);
+ return 2;
+ } else {
+ return 0;
+ }
+ },
+
+ // HTML block
+ function(parser, container) {
+ if (!parser.indented &&
+ peek(parser.currentLine, parser.nextNonspace) === C_LESSTHAN) {
+ var s = parser.currentLine.slice(parser.nextNonspace);
+ var blockType;
+
+ for (blockType = 1; blockType <= 7; blockType++) {
+ if (reHtmlBlockOpen[blockType].test(s) &&
+ (blockType < 7 ||
+ container.type !== 'Paragraph')) {
+ parser.closeUnmatchedBlocks();
+ // We don't adjust parser.offset;
+ // spaces are part of the HTML block:
+ var b = parser.addChild('HtmlBlock',
+ parser.offset);
+ b._htmlBlockType = blockType;
+ return 2;
+ }
+ }
+ }
+
+ return 0;
+
+ },
+
+ // Setext header
+ function(parser, container) {
+ var match;
+ if (!parser.indented &&
+ container.type === 'Paragraph' &&
+ (container._string_content.indexOf('\n') ===
+ container._string_content.length - 1) &&
+ ((match = parser.currentLine.slice(parser.nextNonspace).match(reSetextHeaderLine)))) {
+ parser.closeUnmatchedBlocks();
+ var header = new Node('Header', container.sourcepos);
+ header.level = match[0][0] === '=' ? 1 : 2;
+ header._string_content = container._string_content;
+ container.insertAfter(header);
+ container.unlink();
+ parser.tip = header;
+ parser.advanceOffset(parser.currentLine.length - parser.offset, false);
+ return 2;
+ } else {
+ return 0;
+ }
+ },
+
+ // hrule
+ function(parser) {
+ if (!parser.indented &&
+ reHrule.test(parser.currentLine.slice(parser.nextNonspace))) {
+ parser.closeUnmatchedBlocks();
+ parser.addChild('HorizontalRule', parser.nextNonspace);
+ parser.advanceOffset(parser.currentLine.length - parser.offset, false);
+ return 2;
+ } else {
+ return 0;
+ }
+ },
+
+ // list item
+ function(parser, container) {
+ var data;
+ var i;
+ if ((data = parseListMarker(parser.currentLine,
+ parser.nextNonspace, parser.indent)) &&
+ (!parser.indented || container.type === 'List')) {
+ parser.closeUnmatchedBlocks();
+ parser.advanceNextNonspace();
+ // recalculate data.padding, taking into account tabs:
+ i = parser.column;
+ parser.advanceOffset(data.padding, false);
+ data.padding = parser.column - i;
+
+ // add the list if needed
+ if (parser.tip.type !== 'List' ||
+ !(listsMatch(container._listData, data))) {
+ container = parser.addChild('List', parser.nextNonspace);
+ container._listData = data;
+ }
+
+ // add the list item
+ container = parser.addChild('Item', parser.nextNonspace);
+ container._listData = data;
+ return 1;
+ } else {
+ return 0;
+ }
+ },
+
+ // indented code block
+ function(parser) {
+ if (parser.indented &&
+ parser.tip.type !== 'Paragraph' &&
+ !parser.blank) {
+ // indented code
+ parser.advanceOffset(CODE_INDENT, true);
+ parser.closeUnmatchedBlocks();
+ parser.addChild('CodeBlock', parser.offset);
+ return 2;
+ } else {
+ return 0;
+ }
+ }
+
+];
+
+var advanceOffset = function(count, columns) {
+ var i = 0;
+ var cols = 0;
+ var currentLine = this.currentLine;
+ while (columns ? (cols < count) : (i < count)) {
+ if (currentLine[this.offset + i] === '\t') {
+ cols += (4 - ((this.column + cols) % 4));
+ } else {
+ cols += 1;
+ }
+ i++;
+ }
+ this.offset += i;
+ this.column += cols;
+};
+
+var advanceNextNonspace = function() {
+ this.offset = this.nextNonspace;
+ this.column = this.nextNonspaceColumn;
+};
+
+var findNextNonspace = function() {
+ var currentLine = this.currentLine;
+ var i = this.offset;
+ var cols = this.column;
+ var c;
+
+ while ((c = currentLine.charAt(i)) !== '') {
+ if (c === ' ') {
+ i++;
+ cols++;
+ } else if (c === '\t') {
+ i++;
+ cols += (4 - (cols % 4));
+ } else {
+ break;
+ }
+ }
+ this.blank = (c === '\n' || c === '\r' || c === '');
+ this.nextNonspace = i;
+ this.nextNonspaceColumn = cols;
+ this.indent = this.nextNonspaceColumn - this.column;
+ this.indented = this.indent >= CODE_INDENT;
+};
+
+// Analyze a line of text and update the document appropriately.
+// We parse markdown text by calling this on each line of input,
+// then finalizing the document.
+var incorporateLine = function(ln) {
+ var all_matched = true;
+ var t;
+
+ var container = this.doc;
+ this.oldtip = this.tip;
+ this.offset = 0;
+ this.lineNumber += 1;
+
+ // replace NUL characters for security
+ if (ln.indexOf('\u0000') !== -1) {
+ ln = ln.replace(/\0/g, '\uFFFD');
+ }
+
+ this.currentLine = ln;
+
+ // For each containing block, try to parse the associated line start.
+ // Bail out on failure: container will point to the last matching block.
+ // Set all_matched to false if not all containers match.
+ var lastChild;
+ while ((lastChild = container._lastChild) && lastChild._open) {
+ container = lastChild;
+
+ this.findNextNonspace();
+
+ switch (this.blocks[container.type].continue(this, container)) {
+ case 0: // we've matched, keep going
+ break;
+ case 1: // we've failed to match a block
+ all_matched = false;
+ break;
+ case 2: // we've hit end of line for fenced code close and can return
+ this.lastLineLength = ln.length;
+ return;
+ default:
+ throw 'continue returned illegal value, must be 0, 1, or 2';
+ }
+ if (!all_matched) {
+ container = container._parent; // back up to last matching block
+ break;
+ }
+ }
+
+ this.allClosed = (container === this.oldtip);
+ this.lastMatchedContainer = container;
+
+ // Check to see if we've hit 2nd blank line; if so break out of list:
+ if (this.blank && container._lastLineBlank) {
+ this.breakOutOfLists(container);
+ }
+
+ var matchedLeaf = container.type !== 'Paragraph' &&
+ blocks[container.type].acceptsLines;
+ var starts = this.blockStarts;
+ var startsLen = starts.length;
+ // Unless last matched container is a code block, try new container starts,
+ // adding children to the last matched container:
+ while (!matchedLeaf) {
+
+ this.findNextNonspace();
+
+ // this is a little performance optimization:
+ if (!this.indented &&
+ !reMaybeSpecial.test(ln.slice(this.nextNonspace))) {
+ this.advanceNextNonspace();
+ break;
+ }
+
+ var i = 0;
+ while (i < startsLen) {
+ var res = starts[i](this, container);
+ if (res === 1) {
+ container = this.tip;
+ break;
+ } else if (res === 2) {
+ container = this.tip;
+ matchedLeaf = true;
+ break;
+ } else {
+ i++;
+ }
+ }
+
+ if (i === startsLen) { // nothing matched
+ this.advanceNextNonspace();
+ break;
+ }
+ }
+
+ // What remains at the offset is a text line. Add the text to the
+ // appropriate container.
+
+ // First check for a lazy paragraph continuation:
+ if (!this.allClosed && !this.blank &&
+ this.tip.type === 'Paragraph') {
+ // lazy paragraph continuation
+ this.addLine();
+
+ } else { // not a lazy continuation
+
+ // finalize any blocks not matched
+ this.closeUnmatchedBlocks();
+ if (this.blank && container.lastChild) {
+ container.lastChild._lastLineBlank = true;
+ }
+
+ t = container.type;
+
+ // Block quote lines are never blank as they start with >
+ // and we don't count blanks in fenced code for purposes of tight/loose
+ // lists or breaking out of lists. We also don't set _lastLineBlank
+ // on an empty list item, or if we just closed a fenced block.
+ var lastLineBlank = this.blank &&
+ !(t === 'BlockQuote' ||
+ (t === 'CodeBlock' && container._isFenced) ||
+ (t === 'Item' &&
+ !container._firstChild &&
+ container.sourcepos[0][0] === this.lineNumber));
+
+ // propagate lastLineBlank up through parents:
+ var cont = container;
+ while (cont) {
+ cont._lastLineBlank = lastLineBlank;
+ cont = cont._parent;
+ }
+
+ if (this.blocks[t].acceptsLines) {
+ this.addLine();
+ // if HtmlBlock, check for end condition
+ if (t === 'HtmlBlock' &&
+ container._htmlBlockType >= 1 &&
+ container._htmlBlockType <= 5 &&
+ reHtmlBlockClose[container._htmlBlockType].test(this.currentLine.slice(this.offset))) {
+ this.finalize(container, this.lineNumber);
+ }
+
+ } else if (this.offset < ln.length && !this.blank) {
+ // create paragraph container for line
+ container = this.addChild('Paragraph', this.offset);
+ this.advanceNextNonspace();
+ this.addLine();
+ }
+ }
+ this.lastLineLength = ln.length;
+};
+
+// Finalize a block. Close it and do any necessary postprocessing,
+// e.g. creating string_content from strings, setting the 'tight'
+// or 'loose' status of a list, and parsing the beginnings
+// of paragraphs for reference definitions. Reset the tip to the
+// parent of the closed block.
+var finalize = function(block, lineNumber) {
+ var above = block._parent;
+ block._open = false;
+ block.sourcepos[1] = [lineNumber, this.lastLineLength];
+
+ this.blocks[block.type].finalize(this, block);
+
+ this.tip = above;
+};
+
+// Walk through a block & children recursively, parsing string content
+// into inline content where appropriate.
+var processInlines = function(block) {
+ var node, event, t;
+ var walker = block.walker();
+ this.inlineParser.refmap = this.refmap;
+ this.inlineParser.options = this.options;
+ while ((event = walker.next())) {
+ node = event.node;
+ t = node.type;
+ if (!event.entering && (t === 'Paragraph' || t === 'Header')) {
+ this.inlineParser.parse(node);
+ }
+ }
+};
+
+var Document = function() {
+ var doc = new Node('Document', [[1, 1], [0, 0]]);
+ return doc;
+};
+
+// The main parsing function. Returns a parsed document AST.
+var parse = function(input) {
+ this.doc = new Document();
+ this.tip = this.doc;
+ this.refmap = {};
+ this.lineNumber = 0;
+ this.lastLineLength = 0;
+ this.offset = 0;
+ this.column = 0;
+ this.lastMatchedContainer = this.doc;
+ this.currentLine = "";
+ if (this.options.time) { console.time("preparing input"); }
+ var lines = input.split(reLineEnding);
+ var len = lines.length;
+ if (input.charCodeAt(input.length - 1) === C_NEWLINE) {
+ // ignore last blank line created by final newline
+ len -= 1;
+ }
+ if (this.options.time) { console.timeEnd("preparing input"); }
+ if (this.options.time) { console.time("block parsing"); }
+ for (var i = 0; i < len; i++) {
+ this.incorporateLine(lines[i]);
+ }
+ while (this.tip) {
+ this.finalize(this.tip, len);
+ }
+ if (this.options.time) { console.timeEnd("block parsing"); }
+ if (this.options.time) { console.time("inline parsing"); }
+ this.processInlines(this.doc);
+ if (this.options.time) { console.timeEnd("inline parsing"); }
+ return this.doc;
+};
+
+
+// The Parser object.
+function Parser(options){
+ return {
+ doc: new Document(),
+ blocks: blocks,
+ blockStarts: blockStarts,
+ tip: this.doc,
+ oldtip: this.doc,
+ currentLine: "",
+ lineNumber: 0,
+ offset: 0,
+ column: 0,
+ nextNonspace: 0,
+ nextNonspaceColumn: 0,
+ indent: 0,
+ indented: false,
+ blank: false,
+ allClosed: true,
+ lastMatchedContainer: this.doc,
+ refmap: {},
+ lastLineLength: 0,
+ inlineParser: new InlineParser(options),
+ findNextNonspace: findNextNonspace,
+ advanceOffset: advanceOffset,
+ advanceNextNonspace: advanceNextNonspace,
+ breakOutOfLists: breakOutOfLists,
+ addLine: addLine,
+ addChild: addChild,
+ incorporateLine: incorporateLine,
+ finalize: finalize,
+ processInlines: processInlines,
+ closeUnmatchedBlocks: closeUnmatchedBlocks,
+ parse: parse,
+ options: options || {}
+ };
+}
+
+module.exports = Parser;
+
+},{"./common":2,"./inlines":6,"./node":7}],2:[function(require,module,exports){
+"use strict";
+
+var encode = require('mdurl/encode');
+var decode = require('mdurl/decode');
+
+var C_BACKSLASH = 92;
+
+var decodeHTML = require('entities').decodeHTML;
+
+var ENTITY = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});";
+
+var TAGNAME = '[A-Za-z][A-Za-z0-9-]*';
+var ATTRIBUTENAME = '[a-zA-Z_:][a-zA-Z0-9:._-]*';
+var UNQUOTEDVALUE = "[^\"'=<>`\\x00-\\x20]+";
+var SINGLEQUOTEDVALUE = "'[^']*'";
+var DOUBLEQUOTEDVALUE = '"[^"]*"';
+var ATTRIBUTEVALUE = "(?:" + UNQUOTEDVALUE + "|" + SINGLEQUOTEDVALUE + "|" + DOUBLEQUOTEDVALUE + ")";
+var ATTRIBUTEVALUESPEC = "(?:" + "\\s*=" + "\\s*" + ATTRIBUTEVALUE + ")";
+var ATTRIBUTE = "(?:" + "\\s+" + ATTRIBUTENAME + ATTRIBUTEVALUESPEC + "?)";
+var OPENTAG = "<" + TAGNAME + ATTRIBUTE + "*" + "\\s*/?>";
+var CLOSETAG = "</" + TAGNAME + "\\s*[>]";
+var HTMLCOMMENT = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->";
+var PROCESSINGINSTRUCTION = "[<][?].*?[?][>]";
+var DECLARATION = "<![A-Z]+" + "\\s+[^>]*>";
+var CDATA = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>";
+var HTMLTAG = "(?:" + OPENTAG + "|" + CLOSETAG + "|" + HTMLCOMMENT + "|" +
+ PROCESSINGINSTRUCTION + "|" + DECLARATION + "|" + CDATA + ")";
+var reHtmlTag = new RegExp('^' + HTMLTAG, 'i');
+
+var reBackslashOrAmp = /[\\&]/;
+
+var ESCAPABLE = '[!"#$%&\'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]';
+
+var reEntityOrEscapedChar = new RegExp('\\\\' + ESCAPABLE + '|' + ENTITY, 'gi');
+
+var XMLSPECIAL = '[&<>"]';
+
+var reXmlSpecial = new RegExp(XMLSPECIAL, 'g');
+
+var reXmlSpecialOrEntity = new RegExp(ENTITY + '|' + XMLSPECIAL, 'gi');
+
+var unescapeChar = function(s) {
+ if (s.charCodeAt(0) === C_BACKSLASH) {
+ return s.charAt(1);
+ } else {
+ return decodeHTML(s);
+ }
+};
+
+// Replace entities and backslash escapes with literal characters.
+var unescapeString = function(s) {
+ if (reBackslashOrAmp.test(s)) {
+ return s.replace(reEntityOrEscapedChar, unescapeChar);
+ } else {
+ return s;
+ }
+};
+
+var normalizeURI = function(uri) {
+ try {
+ return encode(decode(uri));
+ }
+ catch(err) {
+ return uri;
+ }
+};
+
+var replaceUnsafeChar = function(s) {
+ switch (s) {
+ case '&':
+ return '&amp;';
+ case '<':
+ return '&lt;';
+ case '>':
+ return '&gt;';
+ case '"':
+ return '&quot;';
+ default:
+ return s;
+ }
+};
+
+var escapeXml = function(s, preserve_entities) {
+ if (reXmlSpecial.test(s)) {
+ if (preserve_entities) {
+ return s.replace(reXmlSpecialOrEntity, replaceUnsafeChar);
+ } else {
+ return s.replace(reXmlSpecial, replaceUnsafeChar);
+ }
+ } else {
+ return s;
+ }
+};
+
+module.exports = { unescapeString: unescapeString,
+ normalizeURI: normalizeURI,
+ escapeXml: escapeXml,
+ reHtmlTag: reHtmlTag,
+ OPENTAG: OPENTAG,
+ CLOSETAG: CLOSETAG,
+ ENTITY: ENTITY,
+ ESCAPABLE: ESCAPABLE
+ };
+
+},{"entities":10,"mdurl/decode":18,"mdurl/encode":19}],3:[function(require,module,exports){
+"use strict";
+
+// derived from https://github.com/mathiasbynens/String.fromCodePoint
+/*! http://mths.be/fromcodepoint v0.2.1 by @mathias */
+if (String.fromCodePoint) {
+ module.exports = function (_) {
+ try {
+ return String.fromCodePoint(_);
+ } catch (e) {
+ if (e instanceof RangeError) {
+ return String.fromCharCode(0xFFFD);
+ }
+ throw e;
+ }
+ };
+
+} else {
+
+ var stringFromCharCode = String.fromCharCode;
+ var floor = Math.floor;
+ var fromCodePoint = function() {
+ var MAX_SIZE = 0x4000;
+ var codeUnits = [];
+ var highSurrogate;
+ var lowSurrogate;
+ var index = -1;
+ var length = arguments.length;
+ if (!length) {
+ return '';
+ }
+ var result = '';
+ while (++index < length) {
+ var codePoint = Number(arguments[index]);
+ if (
+ !isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity`
+ codePoint < 0 || // not a valid Unicode code point
+ codePoint > 0x10FFFF || // not a valid Unicode code point
+ floor(codePoint) !== codePoint // not an integer
+ ) {
+ return String.fromCharCode(0xFFFD);
+ }
+ if (codePoint <= 0xFFFF) { // BMP code point
+ codeUnits.push(codePoint);
+ } else { // Astral code point; split in surrogate halves
+ // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
+ codePoint -= 0x10000;
+ highSurrogate = (codePoint >> 10) + 0xD800;
+ lowSurrogate = (codePoint % 0x400) + 0xDC00;
+ codeUnits.push(highSurrogate, lowSurrogate);
+ }
+ if (index + 1 === length || codeUnits.length > MAX_SIZE) {
+ result += stringFromCharCode.apply(null, codeUnits);
+ codeUnits.length = 0;
+ }
+ }
+ return result;
+ };
+ module.exports = fromCodePoint;
+}
+
+},{}],4:[function(require,module,exports){
+"use strict";
+
+var escapeXml = require('./common').escapeXml;
+
+// Helper function to produce an HTML tag.
+var tag = function(name, attrs, selfclosing) {
+ var result = '<' + name;
+ if (attrs && attrs.length > 0) {
+ var i = 0;
+ var attrib;
+ while ((attrib = attrs[i]) !== undefined) {
+ result += ' ' + attrib[0] + '="' + attrib[1] + '"';
+ i++;
+ }
+ }
+ if (selfclosing) {
+ result += ' /';
+ }
+
+ result += '>';
+ return result;
+};
+
+var reHtmlTag = /\<[^>]*\>/;
+var reUnsafeProtocol = /^javascript:|vbscript:|file:|data:/i;
+var reSafeDataProtocol = /^data:image\/(?:png|gif|jpeg|webp)/i;
+
+var potentiallyUnsafe = function(url) {
+ return reUnsafeProtocol.test(url) &&
+ !reSafeDataProtocol.test(url);
+};
+
+var renderNodes = function(block) {
+
+ var attrs;
+ var info_words;
+ var tagname;
+ var walker = block.walker();
+ var event, node, entering;
+ var buffer = "";
+ var lastOut = "\n";
+ var disableTags = 0;
+ var grandparent;
+ var out = function(s) {
+ if (disableTags > 0) {
+ buffer += s.replace(reHtmlTag, '');
+ } else {
+ buffer += s;
+ }
+ lastOut = s;
+ };
+ var esc = this.escape;
+ var cr = function() {
+ if (lastOut !== '\n') {
+ buffer += '\n';
+ lastOut = '\n';
+ }
+ };
+
+ var options = this.options;
+
+ if (options.time) { console.time("rendering"); }
+
+ while ((event = walker.next())) {
+ entering = event.entering;
+ node = event.node;
+
+ attrs = [];
+ if (options.sourcepos) {
+ var pos = node.sourcepos;
+ if (pos) {
+ attrs.push(['data-sourcepos', String(pos[0][0]) + ':' +
+ String(pos[0][1]) + '-' + String(pos[1][0]) + ':' +
+ String(pos[1][1])]);
+ }
+ }
+
+ switch (node.type) {
+ case 'Text':
+ out(esc(node.literal, false));
+ break;
+
+ case 'Softbreak':
+ out(this.softbreak);
+ break;
+
+ case 'Hardbreak':
+ out(tag('br', [], true));
+ cr();
+ break;
+
+ case 'Emph':
+ out(tag(entering ? 'em' : '/em'));
+ break;
+
+ case 'Strong':
+ out(tag(entering ? 'strong' : '/strong'));
+ break;
+
+ case 'Html':
+ if (options.safe) {
+ out('<!-- raw HTML omitted -->');
+ } else {
+ out(node.literal);
+ }
+ break;
+
+ case 'Link':
+ if (entering) {
+ if (!(options.safe && potentiallyUnsafe(node.destination))) {
+ attrs.push(['href', esc(node.destination, true)]);
+ }
+ if (node.title) {
+ attrs.push(['title', esc(node.title, true)]);
+ }
+ out(tag('a', attrs));
+ } else {
+ out(tag('/a'));
+ }
+ break;
+
+ case 'Image':
+ if (entering) {
+ if (disableTags === 0) {
+ if (options.safe &&
+ potentiallyUnsafe(node.destination)) {
+ out('<img src="" alt="');
+ } else {
+ out('<img src="' + esc(node.destination, true) +
+ '" alt="');
+ }
+ }
+ disableTags += 1;
+ } else {
+ disableTags -= 1;
+ if (disableTags === 0) {
+ if (node.title) {
+ out('" title="' + esc(node.title, true));
+ }
+ out('" />');
+ }
+ }
+ break;
+
+ case 'Code':
+ out(tag('code') + esc(node.literal, false) + tag('/code'));
+ break;
+
+ case 'Document':
+ break;
+
+ case 'Paragraph':
+ grandparent = node.parent.parent;
+ if (grandparent !== null &&
+ grandparent.type === 'List') {
+ if (grandparent.listTight) {
+ break;
+ }
+ }
+ if (entering) {
+ cr();
+ out(tag('p', attrs));
+ } else {
+ out(tag('/p'));
+ cr();
+ }
+ break;
+
+ case 'BlockQuote':
+ if (entering) {
+ cr();
+ out(tag('blockquote', attrs));
+ cr();
+ } else {
+ cr();
+ out(tag('/blockquote'));
+ cr();
+ }
+ break;
+
+ case 'Item':
+ if (entering) {
+ out(tag('li', attrs));
+ } else {
+ out(tag('/li'));
+ cr();
+ }
+ break;
+
+ case 'List':
+ tagname = node.listType === 'Bullet' ? 'ul' : 'ol';
+ if (entering) {
+ var start = node.listStart;
+ if (start !== null && start !== 1) {
+ attrs.push(['start', start.toString()]);
+ }
+ cr();
+ out(tag(tagname, attrs));
+ cr();
+ } else {
+ cr();
+ out(tag('/' + tagname));
+ cr();
+ }
+ break;
+
+ case 'Header':
+ tagname = 'h' + node.level;
+ if (entering) {
+ cr();
+ out(tag(tagname, attrs));
+ } else {
+ out(tag('/' + tagname));
+ cr();
+ }
+ break;
+
+ case 'CodeBlock':
+ info_words = node.info ? node.info.split(/\s+/) : [];
+ if (info_words.length > 0 && info_words[0].length > 0) {
+ attrs.push(['class', 'language-' + esc(info_words[0], true)]);
+ }
+ cr();
+ out(tag('pre') + tag('code', attrs));
+ out(esc(node.literal, false));
+ out(tag('/code') + tag('/pre'));
+ cr();
+ break;
+
+ case 'HtmlBlock':
+ cr();
+ if (options.safe) {
+ out('<!-- raw HTML omitted -->');
+ } else {
+ out(node.literal);
+ }
+ cr();
+ break;
+
+ case 'HorizontalRule':
+ cr();
+ out(tag('hr', attrs, true));
+ cr();
+ break;
+
+ default:
+ throw "Unknown node type " + node.type;
+ }
+
+ }
+ if (options.time) { console.timeEnd("rendering"); }
+ return buffer;
+};
+
+// The HtmlRenderer object.
+function HtmlRenderer(options){
+ return {
+ // default options:
+ softbreak: '\n', // by default, soft breaks are rendered as newlines in HTML
+ // set to "<br />" to make them hard breaks
+ // set to " " if you want to ignore line wrapping in source
+ escape: escapeXml,
+ options: options || {},
+ render: renderNodes
+ };
+}
+
+module.exports = HtmlRenderer;
+
+},{"./common":2}],5:[function(require,module,exports){
+"use strict";
+
+// commonmark.js - CommomMark in JavaScript
+// Copyright (C) 2014 John MacFarlane
+// License: BSD3.
+
+// Basic usage:
+//
+// var commonmark = require('commonmark');
+// var parser = new commonmark.Parser();
+// var renderer = new commonmark.HtmlRenderer();
+// console.log(renderer.render(parser.parse('Hello *world*')));
+
+module.exports.Node = require('./node');
+module.exports.Parser = require('./blocks');
+module.exports.HtmlRenderer = require('./html');
+module.exports.XmlRenderer = require('./xml');
+
+},{"./blocks":1,"./html":4,"./node":7,"./xml":9}],6:[function(require,module,exports){
+"use strict";
+
+var Node = require('./node');
+var common = require('./common');
+var normalizeReference = require('./normalize-reference');
+
+var normalizeURI = common.normalizeURI;
+var unescapeString = common.unescapeString;
+var fromCodePoint = require('./from-code-point.js');
+var decodeHTML = require('entities').decodeHTML;
+require('string.prototype.repeat'); // Polyfill for String.prototype.repeat
+
+// Constants for character codes:
+
+var C_NEWLINE = 10;
+var C_ASTERISK = 42;
+var C_UNDERSCORE = 95;
+var C_BACKTICK = 96;
+var C_OPEN_BRACKET = 91;
+var C_CLOSE_BRACKET = 93;
+var C_LESSTHAN = 60;
+var C_BANG = 33;
+var C_BACKSLASH = 92;
+var C_AMPERSAND = 38;
+var C_OPEN_PAREN = 40;
+var C_CLOSE_PAREN = 41;
+var C_COLON = 58;
+var C_SINGLEQUOTE = 39;
+var C_DOUBLEQUOTE = 34;
+
+// Some regexps used in inline parser:
+
+var ESCAPABLE = common.ESCAPABLE;
+var ESCAPED_CHAR = '\\\\' + ESCAPABLE;
+var REG_CHAR = '[^\\\\()\\x00-\\x20]';
+var IN_PARENS_NOSP = '\\((' + REG_CHAR + '|' + ESCAPED_CHAR + '|\\\\)*\\)';
+
+var ENTITY = common.ENTITY;
+var reHtmlTag = common.reHtmlTag;
+
+var rePunctuation = new RegExp(/^[\u2000-\u206F\u2E00-\u2E7F\\'!"#\$%&\(\)\*\+,\-\.\/:;<=>\?@\[\]\^_`\{\|\}~]/);
+
+var reLinkTitle = new RegExp(
+ '^(?:"(' + ESCAPED_CHAR + '|[^"\\x00])*"' +
+ '|' +
+ '\'(' + ESCAPED_CHAR + '|[^\'\\x00])*\'' +
+ '|' +
+ '\\((' + ESCAPED_CHAR + '|[^)\\x00])*\\))');
+
+var reLinkDestinationBraces = new RegExp(
+ '^(?:[<](?:[^<>\\n\\\\\\x00]' + '|' + ESCAPED_CHAR + '|' + '\\\\)*[>])');
+
+var reLinkDestination = new RegExp(
+ '^(?:' + REG_CHAR + '+|' + ESCAPED_CHAR + '|\\\\|' + IN_PARENS_NOSP + ')*');
+
+var reEscapable = new RegExp('^' + ESCAPABLE);
+
+var reEntityHere = new RegExp('^' + ENTITY, 'i');
+
+var reTicks = /`+/;
+
+var reTicksHere = /^`+/;
+
+var reEllipses = /\.\.\./g;
+
+var reDash = /--+/g;
+
+var reEmailAutolink = /^<([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/;
+
+var reAutolink = /^<(?:coap|doi|javascript|aaa|aaas|about|acap|cap|cid|crid|data|dav|dict|dns|file|ftp|geo|go|gopher|h323|http|https|iax|icap|im|imap|info|ipp|iris|iris.beep|iris.xpc|iris.xpcs|iris.lwz|ldap|mailto|mid|msrp|msrps|mtqp|mupdate|news|nfs|ni|nih|nntp|opaquelocktoken|pop|pres|rtsp|service|session|shttp|sieve|sip|sips|sms|snmp|soap.beep|soap.beeps|tag|tel|telnet|tftp|thismessage|tn3270|tip|tv|urn|vemmi|ws|wss|xcon|xcon-userid|xmlrpc.beep|xmlrpc.beeps|xmpp|z39.50r|z39.50s|adiumxtra|afp|afs|aim|apt|attachment|aw|beshare|bitcoin|bolo|callto|chrome|chrome-extension|com-eventbrite-attendee|content|cvs|dlna-playsingle|dlna-playcontainer|dtn|dvb|ed2k|facetime|feed|finger|fish|gg|git|gizmoproject|gtalk|hcp|icon|ipn|irc|irc6|ircs|itms|jar|jms|keyparc|lastfm|ldaps|magnet|maps|market|message|mms|ms-help|msnim|mumble|mvn|notes|oid|palm|paparazzi|platform|proxy|psyc|query|res|resource|rmi|rsync|rtmp|secondlife|sftp|sgn|skype|smb|soldat|spotify|ssh|steam|svn|teamspeak|things|udp|unreal|ut2004|ventrilo|view-source|webcal|wtai|wyciwyg|xfire|xri|ymsgr):[^<>\x00-\x20]*>/i;
+
+var reSpnl = /^ *(?:\n *)?/;
+
+var reWhitespaceChar = /^\s/;
+
+var reWhitespace = /\s+/g;
+
+var reFinalSpace = / *$/;
+
+var reInitialSpace = /^ */;
+
+var reSpaceAtEndOfLine = /^ *(?:\n|$)/;
+
+var reLinkLabel = new RegExp('^\\[(?:[^\\\\\\[\\]]|' + ESCAPED_CHAR +
+ '|\\\\){0,1000}\\]');
+
+// Matches a string of non-special characters.
+var reMain = /^[^\n`\[\]\\!<&*_'"]+/m;
+
+var text = function(s) {
+ var node = new Node('Text');
+ node._literal = s;
+ return node;
+};
+
+// INLINE PARSER
+
+// These are methods of an InlineParser object, defined below.
+// An InlineParser keeps track of a subject (a string to be
+// parsed) and a position in that subject.
+
+// If re matches at current position in the subject, advance
+// position in subject and return the match; otherwise return null.
+var match = function(re) {
+ var m = re.exec(this.subject.slice(this.pos));
+ if (m === null) {
+ return null;
+ } else {
+ this.pos += m.index + m[0].length;
+ return m[0];
+ }
+};
+
+// Returns the code for the character at the current subject position, or -1
+// there are no more characters.
+var peek = function() {
+ if (this.pos < this.subject.length) {
+ return this.subject.charCodeAt(this.pos);
+ } else {
+ return -1;
+ }
+};
+
+// Parse zero or more space characters, including at most one newline
+var spnl = function() {
+ this.match(reSpnl);
+ return true;
+};
+
+// All of the parsers below try to match something at the current position
+// in the subject. If they succeed in matching anything, they
+// return the inline matched, advancing the subject.
+
+// Attempt to parse backticks, adding either a backtick code span or a
+// literal sequence of backticks.
+var parseBackticks = function(block) {
+ var ticks = this.match(reTicksHere);
+ if (ticks === null) {
+ return false;
+ }
+ var afterOpenTicks = this.pos;
+ var matched;
+ var node;
+ while ((matched = this.match(reTicks)) !== null) {
+ if (matched === ticks) {
+ node = new Node('Code');
+ node._literal = this.subject.slice(afterOpenTicks,
+ this.pos - ticks.length)
+ .trim().replace(reWhitespace, ' ');
+ block.appendChild(node);
+ return true;
+ }
+ }
+ // If we got here, we didn't match a closing backtick sequence.
+ this.pos = afterOpenTicks;
+ block.appendChild(text(ticks));
+ return true;
+};
+
+// Parse a backslash-escaped special character, adding either the escaped
+// character, a hard line break (if the backslash is followed by a newline),
+// or a literal backslash to the block's children. Assumes current character
+// is a backslash.
+var parseBackslash = function(block) {
+ var subj = this.subject;
+ var node;
+ this.pos += 1;
+ if (this.peek() === C_NEWLINE) {
+ this.pos += 1;
+ node = new Node('Hardbreak');
+ block.appendChild(node);
+ } else if (reEscapable.test(subj.charAt(this.pos))) {
+ block.appendChild(text(subj.charAt(this.pos)));
+ this.pos += 1;
+ } else {
+ block.appendChild(text('\\'));
+ }
+ return true;
+};
+
+// Attempt to parse an autolink (URL or email in pointy brackets).
+var parseAutolink = function(block) {
+ var m;
+ var dest;
+ var node;
+ if ((m = this.match(reEmailAutolink))) {
+ dest = m.slice(1, m.length - 1);
+ node = new Node('Link');
+ node._destination = normalizeURI('mailto:' + dest);
+ node._title = '';
+ node.appendChild(text(dest));
+ block.appendChild(node);
+ return true;
+ } else if ((m = this.match(reAutolink))) {
+ dest = m.slice(1, m.length - 1);
+ node = new Node('Link');
+ node._destination = normalizeURI(dest);
+ node._title = '';
+ node.appendChild(text(dest));
+ block.appendChild(node);
+ return true;
+ } else {
+ return false;
+ }
+};
+
+// Attempt to parse a raw HTML tag.
+var parseHtmlTag = function(block) {
+ var m = this.match(reHtmlTag);
+ if (m === null) {
+ return false;
+ } else {
+ var node = new Node('Html');
+ node._literal = m;
+ block.appendChild(node);
+ return true;
+ }
+};
+
+// Scan a sequence of characters with code cc, and return information about
+// the number of delimiters and whether they are positioned such that
+// they can open and/or close emphasis or strong emphasis. A utility
+// function for strong/emph parsing.
+var scanDelims = function(cc) {
+ var numdelims = 0;
+ var char_before, char_after, cc_after;
+ var startpos = this.pos;
+ var left_flanking, right_flanking, can_open, can_close;
+ var after_is_whitespace, after_is_punctuation, before_is_whitespace, before_is_punctuation;
+
+ if (cc === C_SINGLEQUOTE || cc === C_DOUBLEQUOTE) {
+ numdelims++;
+ this.pos++;
+ } else {
+ while (this.peek() === cc) {
+ numdelims++;
+ this.pos++;
+ }
+ }
+
+ if (numdelims === 0) {
+ return null;
+ }
+
+ char_before = startpos === 0 ? '\n' : this.subject.charAt(startpos - 1);
+
+ cc_after = this.peek();
+ if (cc_after === -1) {
+ char_after = '\n';
+ } else {
+ char_after = fromCodePoint(cc_after);
+ }
+
+ after_is_whitespace = reWhitespaceChar.test(char_after);
+ after_is_punctuation = rePunctuation.test(char_after);
+ before_is_whitespace = reWhitespaceChar.test(char_before);
+ before_is_punctuation = rePunctuation.test(char_before);
+
+ left_flanking = !after_is_whitespace &&
+ !(after_is_punctuation && !before_is_whitespace && !before_is_punctuation);
+ right_flanking = !before_is_whitespace &&
+ !(before_is_punctuation && !after_is_whitespace && !after_is_punctuation);
+ if (cc === C_UNDERSCORE) {
+ can_open = left_flanking &&
+ (!right_flanking || before_is_punctuation);
+ can_close = right_flanking &&
+ (!left_flanking || after_is_punctuation);
+ } else if (cc === C_SINGLEQUOTE || cc === C_DOUBLEQUOTE) {
+ can_open = left_flanking && !right_flanking;
+ can_close = right_flanking;
+ } else {
+ can_open = left_flanking;
+ can_close = right_flanking;
+ }
+ this.pos = startpos;
+ return { numdelims: numdelims,
+ can_open: can_open,
+ can_close: can_close };
+};
+
+// Handle a delimiter marker for emphasis or a quote.
+var handleDelim = function(cc, block) {
+ var res = this.scanDelims(cc);
+ if (!res) {
+ return false;
+ }
+ var numdelims = res.numdelims;
+ var startpos = this.pos;
+ var contents;
+
+ this.pos += numdelims;
+ if (cc === C_SINGLEQUOTE) {
+ contents = "\u2019";
+ } else if (cc === C_DOUBLEQUOTE) {
+ contents = "\u201C";
+ } else {
+ contents = this.subject.slice(startpos, this.pos);
+ }
+ var node = text(contents);
+ block.appendChild(node);
+
+ // Add entry to stack for this opener
+ this.delimiters = { cc: cc,
+ numdelims: numdelims,
+ node: node,
+ previous: this.delimiters,
+ next: null,
+ can_open: res.can_open,
+ can_close: res.can_close,
+ active: true };
+ if (this.delimiters.previous !== null) {
+ this.delimiters.previous.next = this.delimiters;
+ }
+
+ return true;
+
+};
+
+var removeDelimiter = function(delim) {
+ if (delim.previous !== null) {
+ delim.previous.next = delim.next;
+ }
+ if (delim.next === null) {
+ // top of stack
+ this.delimiters = delim.previous;
+ } else {
+ delim.next.previous = delim.previous;
+ }
+};
+
+var removeDelimitersBetween = function(bottom, top) {
+ if (bottom.next !== top) {
+ bottom.next = top;
+ top.previous = bottom;
+ }
+};
+
+var processEmphasis = function(stack_bottom) {
+ var opener, closer, old_closer;
+ var opener_inl, closer_inl;
+ var tempstack;
+ var use_delims;
+ var tmp, next;
+ var opener_found;
+ var openers_bottom = [];
+
+ openers_bottom[C_UNDERSCORE] = stack_bottom;
+ openers_bottom[C_ASTERISK] = stack_bottom;
+ openers_bottom[C_SINGLEQUOTE] = stack_bottom;
+ openers_bottom[C_DOUBLEQUOTE] = stack_bottom;
+
+ // find first closer above stack_bottom:
+ closer = this.delimiters;
+ while (closer !== null && closer.previous !== stack_bottom) {
+ closer = closer.previous;
+ }
+ // move forward, looking for closers, and handling each
+ while (closer !== null) {
+ var closercc = closer.cc;
+ if (!(closer.can_close && (closercc === C_UNDERSCORE ||
+ closercc === C_ASTERISK ||
+ closercc === C_SINGLEQUOTE ||
+ closercc === C_DOUBLEQUOTE))) {
+ closer = closer.next;
+ } else {
+ // found emphasis closer. now look back for first matching opener:
+ opener = closer.previous;
+ opener_found = false;
+ while (opener !== null && opener !== stack_bottom &&
+ opener !== openers_bottom[closercc]) {
+ if (opener.cc === closer.cc && opener.can_open) {
+ opener_found = true;
+ break;
+ }
+ opener = opener.previous;
+ }
+ old_closer = closer;
+
+ if (closercc === C_ASTERISK || closercc === C_UNDERSCORE) {
+ if (!opener_found) {
+ closer = closer.next;
+ } else {
+ // calculate actual number of delimiters used from closer
+ if (closer.numdelims < 3 || opener.numdelims < 3) {
+ use_delims = closer.numdelims <= opener.numdelims ?
+ closer.numdelims : opener.numdelims;
+ } else {
+ use_delims = closer.numdelims % 2 === 0 ? 2 : 1;
+ }
+
+ opener_inl = opener.node;
+ closer_inl = closer.node;
+
+ // remove used delimiters from stack elts and inlines
+ opener.numdelims -= use_delims;
+ closer.numdelims -= use_delims;
+ opener_inl._literal =
+ opener_inl._literal.slice(0,
+ opener_inl._literal.length - use_delims);
+ closer_inl._literal =
+ closer_inl._literal.slice(0,
+ closer_inl._literal.length - use_delims);
+
+ // build contents for new emph element
+ var emph = new Node(use_delims === 1 ? 'Emph' : 'Strong');
+
+ tmp = opener_inl._next;
+ while (tmp && tmp !== closer_inl) {
+ next = tmp._next;
+ tmp.unlink();
+ emph.appendChild(tmp);
+ tmp = next;
+ }
+
+ opener_inl.insertAfter(emph);
+
+ // remove elts between opener and closer in delimiters stack
+ removeDelimitersBetween(opener, closer);
+
+ // if opener has 0 delims, remove it and the inline
+ if (opener.numdelims === 0) {
+ opener_inl.unlink();
+ this.removeDelimiter(opener);
+ }
+
+ if (closer.numdelims === 0) {
+ closer_inl.unlink();
+ tempstack = closer.next;
+ this.removeDelimiter(closer);
+ closer = tempstack;
+ }
+
+ }
+
+ } else if (closercc === C_SINGLEQUOTE) {
+ closer.node._literal = "\u2019";
+ if (opener_found) {
+ opener.node._literal = "\u2018";
+ }
+ closer = closer.next;
+
+ } else if (closercc === C_DOUBLEQUOTE) {
+ closer.node._literal = "\u201D";
+ if (opener_found) {
+ opener.node.literal = "\u201C";
+ }
+ closer = closer.next;
+
+ }
+ if (!opener_found) {
+ // Set lower bound for future searches for openers:
+ openers_bottom[closercc] = old_closer.previous;
+ if (!old_closer.can_open) {
+ // We can remove a closer that can't be an opener,
+ // once we've seen there's no matching opener:
+ this.removeDelimiter(old_closer);
+ }
+ }
+ }
+
+ }
+
+ // remove all delimiters
+ while (this.delimiters !== null && this.delimiters !== stack_bottom) {
+ this.removeDelimiter(this.delimiters);
+ }
+};
+
+// Attempt to parse link title (sans quotes), returning the string
+// or null if no match.
+var parseLinkTitle = function() {
+ var title = this.match(reLinkTitle);
+ if (title === null) {
+ return null;
+ } else {
+ // chop off quotes from title and unescape:
+ return unescapeString(title.substr(1, title.length - 2));
+ }
+};
+
+// Attempt to parse link destination, returning the string or
+// null if no match.
+var parseLinkDestination = function() {
+ var res = this.match(reLinkDestinationBraces);
+ if (res === null) {
+ res = this.match(reLinkDestination);
+ if (res === null) {
+ return null;
+ } else {
+ return normalizeURI(unescapeString(res));
+ }
+ } else { // chop off surrounding <..>:
+ return normalizeURI(unescapeString(res.substr(1, res.length - 2)));
+ }
+};
+
+// Attempt to parse a link label, returning number of characters parsed.
+var parseLinkLabel = function() {
+ var m = this.match(reLinkLabel);
+ if (m === null || m.length > 1001) {
+ return 0;
+ } else {
+ return m.length;
+ }
+};
+
+// Add open bracket to delimiter stack and add a text node to block's children.
+var parseOpenBracket = function(block) {
+ var startpos = this.pos;
+ this.pos += 1;
+
+ var node = text('[');
+ block.appendChild(node);
+
+ // Add entry to stack for this opener
+ this.delimiters = { cc: C_OPEN_BRACKET,
+ numdelims: 1,
+ node: node,
+ previous: this.delimiters,
+ next: null,
+ can_open: true,
+ can_close: false,
+ index: startpos,
+ active: true };
+ if (this.delimiters.previous !== null) {
+ this.delimiters.previous.next = this.delimiters;
+ }
+
+ return true;
+
+};
+
+// IF next character is [, and ! delimiter to delimiter stack and
+// add a text node to block's children. Otherwise just add a text node.
+var parseBang = function(block) {
+ var startpos = this.pos;
+ this.pos += 1;
+ if (this.peek() === C_OPEN_BRACKET) {
+ this.pos += 1;
+
+ var node = text('![');
+ block.appendChild(node);
+
+ // Add entry to stack for this opener
+ this.delimiters = { cc: C_BANG,
+ numdelims: 1,
+ node: node,
+ previous: this.delimiters,
+ next: null,
+ can_open: true,
+ can_close: false,
+ index: startpos + 1,
+ active: true };
+ if (this.delimiters.previous !== null) {
+ this.delimiters.previous.next = this.delimiters;
+ }
+ } else {
+ block.appendChild(text('!'));
+ }
+ return true;
+};
+
+// Try to match close bracket against an opening in the delimiter
+// stack. Add either a link or image, or a plain [ character,
+// to block's children. If there is a matching delimiter,
+// remove it from the delimiter stack.
+var parseCloseBracket = function(block) {
+ var startpos;
+ var is_image;
+ var dest;
+ var title;
+ var matched = false;
+ var reflabel;
+ var opener;
+
+ this.pos += 1;
+ startpos = this.pos;
+
+ // look through stack of delimiters for a [ or ![
+ opener = this.delimiters;
+
+ while (opener !== null) {
+ if (opener.cc === C_OPEN_BRACKET || opener.cc === C_BANG) {
+ break;
+ }
+ opener = opener.previous;
+ }
+
+ if (opener === null) {
+ // no matched opener, just return a literal
+ block.appendChild(text(']'));
+ return true;
+ }
+
+ if (!opener.active) {
+ // no matched opener, just return a literal
+ block.appendChild(text(']'));
+ // take opener off emphasis stack
+ this.removeDelimiter(opener);
+ return true;
+ }
+
+ // If we got here, open is a potential opener
+ is_image = opener.cc === C_BANG;
+
+ // Check to see if we have a link/image
+
+ // Inline link?
+ if (this.peek() === C_OPEN_PAREN) {
+ this.pos++;
+ if (this.spnl() &&
+ ((dest = this.parseLinkDestination()) !== null) &&
+ this.spnl() &&
+ // make sure there's a space before the title:
+ (reWhitespaceChar.test(this.subject.charAt(this.pos - 1)) &&
+ (title = this.parseLinkTitle()) || true) &&
+ this.spnl() &&
+ this.peek() === C_CLOSE_PAREN) {
+ this.pos += 1;
+ matched = true;
+ }
+ } else {
+
+ // Next, see if there's a link label
+ var savepos = this.pos;
+ this.spnl();
+ var beforelabel = this.pos;
+ var n = this.parseLinkLabel();
+ if (n === 0 || n === 2) {
+ // empty or missing second label
+ reflabel = this.subject.slice(opener.index, startpos);
+ } else {
+ reflabel = this.subject.slice(beforelabel, beforelabel + n);
+ }
+ if (n === 0) {
+ // If shortcut reference link, rewind before spaces we skipped.
+ this.pos = savepos;
+ }
+
+ // lookup rawlabel in refmap
+ var link = this.refmap[normalizeReference(reflabel)];
+ if (link) {
+ dest = link.destination;
+ title = link.title;
+ matched = true;
+ }
+ }
+
+ if (matched) {
+ var node = new Node(is_image ? 'Image' : 'Link');
+ node._destination = dest;
+ node._title = title || '';
+
+ var tmp, next;
+ tmp = opener.node._next;
+ while (tmp) {
+ next = tmp._next;
+ tmp.unlink();
+ node.appendChild(tmp);
+ tmp = next;
+ }
+ block.appendChild(node);
+ this.processEmphasis(opener.previous);
+
+ opener.node.unlink();
+
+ // processEmphasis will remove this and later delimiters.
+ // Now, for a link, we also deactivate earlier link openers.
+ // (no links in links)
+ if (!is_image) {
+ opener = this.delimiters;
+ while (opener !== null) {
+ if (opener.cc === C_OPEN_BRACKET) {
+ opener.active = false; // deactivate this opener
+ }
+ opener = opener.previous;
+ }
+ }
+
+ return true;
+
+ } else { // no match
+
+ this.removeDelimiter(opener); // remove this opener from stack
+ this.pos = startpos;
+ block.appendChild(text(']'));
+ return true;
+ }
+
+};
+
+// Attempt to parse an entity.
+var parseEntity = function(block) {
+ var m;
+ if ((m = this.match(reEntityHere))) {
+ block.appendChild(text(decodeHTML(m)));
+ return true;
+ } else {
+ return false;
+ }
+};
+
+// Parse a run of ordinary characters, or a single character with
+// a special meaning in markdown, as a plain string.
+var parseString = function(block) {
+ var m;
+ if ((m = this.match(reMain))) {
+ if (this.options.smart) {
+ block.appendChild(text(
+ m.replace(reEllipses, "\u2026")
+ .replace(reDash, function(chars) {
+ var enCount = 0;
+ var emCount = 0;
+ if (chars.length % 3 === 0) { // If divisible by 3, use all em dashes
+ emCount = chars.length / 3;
+ } else if (chars.length % 2 === 0) { // If divisible by 2, use all en dashes
+ enCount = chars.length / 2;
+ } else if (chars.length % 3 === 2) { // If 2 extra dashes, use en dash for last 2; em dashes for rest
+ enCount = 1;
+ emCount = (chars.length - 2) / 3;
+ } else { // Use en dashes for last 4 hyphens; em dashes for rest
+ enCount = 2;
+ emCount = (chars.length - 4) / 3;
+ }
+ return "\u2014".repeat(emCount) + "\u2013".repeat(enCount);
+ })));
+ } else {
+ block.appendChild(text(m));
+ }
+ return true;
+ } else {
+ return false;
+ }
+};
+
+// Parse a newline. If it was preceded by two spaces, return a hard
+// line break; otherwise a soft line break.
+var parseNewline = function(block) {
+ this.pos += 1; // assume we're at a \n
+ // check previous node for trailing spaces
+ var lastc = block._lastChild;
+ if (lastc && lastc.type === 'Text' && lastc._literal[lastc._literal.length - 1] === ' ') {
+ var hardbreak = lastc._literal[lastc._literal.length - 2] === ' ';
+ lastc._literal = lastc._literal.replace(reFinalSpace, '');
+ block.appendChild(new Node(hardbreak ? 'Hardbreak' : 'Softbreak'));
+ } else {
+ block.appendChild(new Node('Softbreak'));
+ }
+ this.match(reInitialSpace); // gobble leading spaces in next line
+ return true;
+};
+
+// Attempt to parse a link reference, modifying refmap.
+var parseReference = function(s, refmap) {
+ this.subject = s;
+ this.pos = 0;
+ var rawlabel;
+ var dest;
+ var title;
+ var matchChars;
+ var startpos = this.pos;
+
+ // label:
+ matchChars = this.parseLinkLabel();
+ if (matchChars === 0) {
+ return 0;
+ } else {
+ rawlabel = this.subject.substr(0, matchChars);
+ }
+
+ // colon:
+ if (this.peek() === C_COLON) {
+ this.pos++;
+ } else {
+ this.pos = startpos;
+ return 0;
+ }
+
+ // link url
+ this.spnl();
+
+ dest = this.parseLinkDestination();
+ if (dest === null || dest.length === 0) {
+ this.pos = startpos;
+ return 0;
+ }
+
+ var beforetitle = this.pos;
+ this.spnl();
+ title = this.parseLinkTitle();
+ if (title === null) {
+ title = '';
+ // rewind before spaces
+ this.pos = beforetitle;
+ }
+
+ // make sure we're at line end:
+ var atLineEnd = true;
+ if (this.match(reSpaceAtEndOfLine) === null) {
+ if (title === '') {
+ atLineEnd = false;
+ } else {
+ // the potential title we found is not at the line end,
+ // but it could still be a legal link reference if we
+ // discard the title
+ title = '';
+ // rewind before spaces
+ this.pos = beforetitle;
+ // and instead check if the link URL is at the line end
+ atLineEnd = this.match(reSpaceAtEndOfLine) !== null;
+ }
+ }
+
+ if (!atLineEnd) {
+ this.pos = startpos;
+ return 0;
+ }
+
+ var normlabel = normalizeReference(rawlabel);
+ if (normlabel === '') {
+ // label must contain non-whitespace characters
+ this.pos = startpos;
+ return 0;
+ }
+
+ if (!refmap[normlabel]) {
+ refmap[normlabel] = { destination: dest, title: title };
+ }
+ return this.pos - startpos;
+};
+
+// Parse the next inline element in subject, advancing subject position.
+// On success, add the result to block's children and return true.
+// On failure, return false.
+var parseInline = function(block) {
+ var res = false;
+ var c = this.peek();
+ if (c === -1) {
+ return false;
+ }
+ switch(c) {
+ case C_NEWLINE:
+ res = this.parseNewline(block);
+ break;
+ case C_BACKSLASH:
+ res = this.parseBackslash(block);
+ break;
+ case C_BACKTICK:
+ res = this.parseBackticks(block);
+ break;
+ case C_ASTERISK:
+ case C_UNDERSCORE:
+ res = this.handleDelim(c, block);
+ break;
+ case C_SINGLEQUOTE:
+ case C_DOUBLEQUOTE:
+ res = this.options.smart && this.handleDelim(c, block);
+ break;
+ case C_OPEN_BRACKET:
+ res = this.parseOpenBracket(block);
+ break;
+ case C_BANG:
+ res = this.parseBang(block);
+ break;
+ case C_CLOSE_BRACKET:
+ res = this.parseCloseBracket(block);
+ break;
+ case C_LESSTHAN:
+ res = this.parseAutolink(block) || this.parseHtmlTag(block);
+ break;
+ case C_AMPERSAND:
+ res = this.parseEntity(block);
+ break;
+ default:
+ res = this.parseString(block);
+ break;
+ }
+ if (!res) {
+ this.pos += 1;
+ block.appendChild(text(fromCodePoint(c)));
+ }
+
+ return true;
+};
+
+// Parse string content in block into inline children,
+// using refmap to resolve references.
+var parseInlines = function(block) {
+ this.subject = block._string_content.trim();
+ this.pos = 0;
+ this.delimiters = null;
+ while (this.parseInline(block)) {
+ }
+ block._string_content = null; // allow raw string to be garbage collected
+ this.processEmphasis(null);
+};
+
+// The InlineParser object.
+function InlineParser(options){
+ return {
+ subject: '',
+ delimiters: null, // used by handleDelim method
+ pos: 0,
+ refmap: {},
+ match: match,
+ peek: peek,
+ spnl: spnl,
+ parseBackticks: parseBackticks,
+ parseBackslash: parseBackslash,
+ parseAutolink: parseAutolink,
+ parseHtmlTag: parseHtmlTag,
+ scanDelims: scanDelims,
+ handleDelim: handleDelim,
+ parseLinkTitle: parseLinkTitle,
+ parseLinkDestination: parseLinkDestination,
+ parseLinkLabel: parseLinkLabel,
+ parseOpenBracket: parseOpenBracket,
+ parseCloseBracket: parseCloseBracket,
+ parseBang: parseBang,
+ parseEntity: parseEntity,
+ parseString: parseString,
+ parseNewline: parseNewline,
+ parseReference: parseReference,
+ parseInline: parseInline,
+ processEmphasis: processEmphasis,
+ removeDelimiter: removeDelimiter,
+ options: options || {},
+ parse: parseInlines
+ };
+}
+
+module.exports = InlineParser;
+
+},{"./common":2,"./from-code-point.js":3,"./node":7,"./normalize-reference":8,"entities":10,"string.prototype.repeat":20}],7:[function(require,module,exports){
+"use strict";
+
+function isContainer(node) {
+ switch (node._type) {
+ case 'Document':
+ case 'BlockQuote':
+ case 'List':
+ case 'Item':
+ case 'Paragraph':
+ case 'Header':
+ case 'Emph':
+ case 'Strong':
+ case 'Link':
+ case 'Image':
+ return true;
+ default:
+ return false;
+ }
+}
+
+var resumeAt = function(node, entering) {
+ this.current = node;
+ this.entering = (entering === true);
+};
+
+var next = function(){
+ var cur = this.current;
+ var entering = this.entering;
+
+ if (cur === null) {
+ return null;
+ }
+
+ var container = isContainer(cur);
+
+ if (entering && container) {
+ if (cur._firstChild) {
+ this.current = cur._firstChild;
+ this.entering = true;
+ } else {
+ // stay on node but exit
+ this.entering = false;
+ }
+
+ } else if (cur === this.root) {
+ this.current = null;
+
+ } else if (cur._next === null) {
+ this.current = cur._parent;
+ this.entering = false;
+
+ } else {
+ this.current = cur._next;
+ this.entering = true;
+ }
+
+ return {entering: entering, node: cur};
+};
+
+var NodeWalker = function(root) {
+ return { current: root,
+ root: root,
+ entering: true,
+ next: next,
+ resumeAt: resumeAt };
+};
+
+var Node = function(nodeType, sourcepos) {
+ this._type = nodeType;
+ this._parent = null;
+ this._firstChild = null;
+ this._lastChild = null;
+ this._prev = null;
+ this._next = null;
+ this._sourcepos = sourcepos;
+ this._lastLineBlank = false;
+ this._open = true;
+ this._string_content = null;
+ this._literal = null;
+ this._listData = null;
+ this._info = null;
+ this._destination = null;
+ this._title = null;
+ this._isFenced = false;
+ this._fenceChar = null;
+ this._fenceLength = 0;
+ this._fenceOffset = null;
+ this._level = null;
+};
+
+var proto = Node.prototype;
+
+Object.defineProperty(proto, 'isContainer', {
+ get: function () { return isContainer(this); }
+});
+
+Object.defineProperty(proto, 'type', {
+ get: function() { return this._type; }
+});
+
+Object.defineProperty(proto, 'firstChild', {
+ get: function() { return this._firstChild; }
+});
+
+Object.defineProperty(proto, 'lastChild', {
+ get: function() { return this._lastChild; }
+});
+
+Object.defineProperty(proto, 'next', {
+ get: function() { return this._next; }
+});
+
+Object.defineProperty(proto, 'prev', {
+ get: function() { return this._prev; }
+});
+
+Object.defineProperty(proto, 'parent', {
+ get: function() { return this._parent; }
+});
+
+Object.defineProperty(proto, 'sourcepos', {
+ get: function() { return this._sourcepos; }
+});
+
+Object.defineProperty(proto, 'literal', {
+ get: function() { return this._literal; },
+ set: function(s) { this._literal = s; }
+});
+
+Object.defineProperty(proto, 'destination', {
+ get: function() { return this._destination; },
+ set: function(s) { this._destination = s; }
+});
+
+Object.defineProperty(proto, 'title', {
+ get: function() { return this._title; },
+ set: function(s) { this._title = s; }
+});
+
+Object.defineProperty(proto, 'info', {
+ get: function() { return this._info; },
+ set: function(s) { this._info = s; }
+});
+
+Object.defineProperty(proto, 'level', {
+ get: function() { return this._level; },
+ set: function(s) { this._level = s; }
+});
+
+Object.defineProperty(proto, 'listType', {
+ get: function() { return this._listData.type; },
+ set: function(t) { this._listData.type = t; }
+});
+
+Object.defineProperty(proto, 'listTight', {
+ get: function() { return this._listData.tight; },
+ set: function(t) { this._listData.tight = t; }
+});
+
+Object.defineProperty(proto, 'listStart', {
+ get: function() { return this._listData.start; },
+ set: function(n) { this._listData.start = n; }
+});
+
+Object.defineProperty(proto, 'listDelimiter', {
+ get: function() { return this._listData.delimiter; },
+ set: function(delim) { this._listData.delimiter = delim; }
+});
+
+Node.prototype.appendChild = function(child) {
+ child.unlink();
+ child._parent = this;
+ if (this._lastChild) {
+ this._lastChild._next = child;
+ child._prev = this._lastChild;
+ this._lastChild = child;
+ } else {
+ this._firstChild = child;
+ this._lastChild = child;
+ }
+};
+
+Node.prototype.prependChild = function(child) {
+ child.unlink();
+ child._parent = this;
+ if (this._firstChild) {
+ this._firstChild._prev = child;
+ child._next = this._firstChild;
+ this._firstChild = child;
+ } else {
+ this._firstChild = child;
+ this._lastChild = child;
+ }
+};
+
+Node.prototype.unlink = function() {
+ if (this._prev) {
+ this._prev._next = this._next;
+ } else if (this._parent) {
+ this._parent._firstChild = this._next;
+ }
+ if (this._next) {
+ this._next._prev = this._prev;
+ } else if (this._parent) {
+ this._parent._lastChild = this._prev;
+ }
+ this._parent = null;
+ this._next = null;
+ this._prev = null;
+};
+
+Node.prototype.insertAfter = function(sibling) {
+ sibling.unlink();
+ sibling._next = this._next;
+ if (sibling._next) {
+ sibling._next._prev = sibling;
+ }
+ sibling._prev = this;
+ this._next = sibling;
+ sibling._parent = this._parent;
+ if (!sibling._next) {
+ sibling._parent._lastChild = sibling;
+ }
+};
+
+Node.prototype.insertBefore = function(sibling) {
+ sibling.unlink();
+ sibling._prev = this._prev;
+ if (sibling._prev) {
+ sibling._prev._next = sibling;
+ }
+ sibling._next = this;
+ this._prev = sibling;
+ sibling._parent = this._parent;
+ if (!sibling._prev) {
+ sibling._parent._firstChild = sibling;
+ }
+};
+
+Node.prototype.walker = function() {
+ var walker = new NodeWalker(this);
+ return walker;
+};
+
+module.exports = Node;
+
+
+/* Example of use of walker:
+
+ var walker = w.walker();
+ var event;
+
+ while (event = walker.next()) {
+ console.log(event.entering, event.node.type);
+ }
+
+ */
+
+},{}],8:[function(require,module,exports){
+"use strict";
+
+/* The bulk of this code derives from https://github.com/dmoscrop/fold-case
+But in addition to case-folding, we also normalize whitespace.
+
+fold-case is Copyright Mathias Bynens <https://mathiasbynens.be/>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+/*eslint-disable key-spacing, comma-spacing */
+
+var regex = /[ \t\r\n]+|[A-Z\xB5\xC0-\xD6\xD8-\xDF\u0100\u0102\u0104\u0106\u0108\u010A\u010C\u010E\u0110\u0112\u0114\u0116\u0118\u011A\u011C\u011E\u0120\u0122\u0124\u0126\u0128\u012A\u012C\u012E\u0130\u0132\u0134\u0136\u0139\u013B\u013D\u013F\u0141\u0143\u0145\u0147\u0149\u014A\u014C\u014E\u0150\u0152\u0154\u0156\u0158\u015A\u015C\u015E\u0160\u0162\u0164\u0166\u0168\u016A\u016C\u016E\u0170\u0172\u0174\u0176\u0178\u0179\u017B\u017D\u017F\u0181\u0182\u0184\u0186\u0187\u0189-\u018B\u018E-\u0191\u0193\u0194\u0196-\u0198\u019C\u019D\u019F\u01A0\u01A2\u01A4\u01A6\u01A7\u01A9\u01AC\u01AE\u01AF\u01B1-\u01B3\u01B5\u01B7\u01B8\u01BC\u01C4\u01C5\u01C7\u01C8\u01CA\u01CB\u01CD\u01CF\u01D1\u01D3\u01D5\u01D7\u01D9\u01DB\u01DE\u01E0\u01E2\u01E4\u01E6\u01E8\u01EA\u01EC\u01EE\u01F0-\u01F2\u01F4\u01F6-\u01F8\u01FA\u01FC\u01FE\u0200\u0202\u0204\u0206\u0208\u020A\u020C\u020E\u0210\u0212\u0214\u0216\u0218\u021A\u021C\u021E\u0220\u0222\u0224\u0226\u0228\u022A\u022C\u022E\u0230\u0232\u023A\u023B\u023D\u023E\u0241\u0243-\u0246\u0248\u024A\u024C\u024E\u0345\u0370\u0372\u0376\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03AB\u03B0\u03C2\u03CF-\u03D1\u03D5\u03D6\u03D8\u03DA\u03DC\u03DE\u03E0\u03E2\u03E4\u03E6\u03E8\u03EA\u03EC\u03EE\u03F0\u03F1\u03F4\u03F5\u03F7\u03F9\u03FA\u03FD-\u042F\u0460\u0462\u0464\u0466\u0468\u046A\u046C\u046E\u0470\u0472\u0474\u0476\u0478\u047A\u047C\u047E\u0480\u048A\u048C\u048E\u0490\u0492\u0494\u0496\u0498\u049A\u049C\u049E\u04A0\u04A2\u04A4\u04A6\u04A8\u04AA\u04AC\u04AE\u04B0\u04B2\u04B4\u04B6\u04B8\u04BA\u04BC\u04BE\u04C0\u04C1\u04C3\u04C5\u04C7\u04C9\u04CB\u04CD\u04D0\u04D2\u04D4\u04D6\u04D8\u04DA\u04DC\u04DE\u04E0\u04E2\u04E4\u04E6\u04E8\u04EA\u04EC\u04EE\u04F0\u04F2\u04F4\u04F6\u04F8\u04FA\u04FC\u04FE\u0500\u0502\u0504\u0506\u0508\u050A\u050C\u050E\u0510\u0512\u0514\u0516\u0518\u051A\u051C\u051E\u0520\u0522\u0524\u0526\u0528\u052A\u052C\u052E\u0531-\u0556\u0587\u10A0-\u10C5\u10C7\u10CD\u1E00\u1E02\u1E04\u1E06\u1E08\u1E0A\u1E0C\u1E0E\u1E10\u1E12\u1E14\u1E16\u1E18\u1E1A\u1E1C\u1E1E\u1E20\u1E22\u1E24\u1E26\u1E28\u1E2A\u1E2C\u1E2E\u1E30\u1E32\u1E34\u1E36\u1E38\u1E3A\u1E3C\u1E3E\u1E40\u1E42\u1E44\u1E46\u1E48\u1E4A\u1E4C\u1E4E\u1E50\u1E52\u1E54\u1E56\u1E58\u1E5A\u1E5C\u1E5E\u1E60\u1E62\u1E64\u1E66\u1E68\u1E6A\u1E6C\u1E6E\u1E70\u1E72\u1E74\u1E76\u1E78\u1E7A\u1E7C\u1E7E\u1E80\u1E82\u1E84\u1E86\u1E88\u1E8A\u1E8C\u1E8E\u1E90\u1E92\u1E94\u1E96-\u1E9B\u1E9E\u1EA0\u1EA2\u1EA4\u1EA6\u1EA8\u1EAA\u1EAC\u1EAE\u1EB0\u1EB2\u1EB4\u1EB6\u1EB8\u1EBA\u1EBC\u1EBE\u1EC0\u1EC2\u1EC4\u1EC6\u1EC8\u1ECA\u1ECC\u1ECE\u1ED0\u1ED2\u1ED4\u1ED6\u1ED8\u1EDA\u1EDC\u1EDE\u1EE0\u1EE2\u1EE4\u1EE6\u1EE8\u1EEA\u1EEC\u1EEE\u1EF0\u1EF2\u1EF4\u1EF6\u1EF8\u1EFA\u1EFC\u1EFE\u1F08-\u1F0F\u1F18-\u1F1D\u1F28-\u1F2F\u1F38-\u1F3F\u1F48-\u1F4D\u1F50\u1F52\u1F54\u1F56\u1F59\u1F5B\u1F5D\u1F5F\u1F68-\u1F6F\u1F80-\u1FAF\u1FB2-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD2\u1FD3\u1FD6-\u1FDB\u1FE2-\u1FE4\u1FE6-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2126\u212A\u212B\u2132\u2160-\u216F\u2183\u24B6-\u24CF\u2C00-\u2C2E\u2C60\u2C62-\u2C64\u2C67\u2C69\u2C6B\u2C6D-\u2C70\u2C72\u2C75\u2C7E-\u2C80\u2C82\u2C84\u2C86\u2C88\u2C8A\u2C8C\u2C8E\u2C90\u2C92\u2C94\u2C96\u2C98\u2C9A\u2C9C\u2C9E\u2CA0\u2CA2\u2CA4\u2CA6\u2CA8\u2CAA\u2CAC\u2CAE\u2CB0\u2CB2\u2CB4\u2CB6\u2CB8\u2CBA\u2CBC\u2CBE\u2CC0\u2CC2\u2CC4\u2CC6\u2CC8\u2CCA\u2CCC\u2CCE\u2CD0\u2CD2\u2CD4\u2CD6\u2CD8\u2CDA\u2CDC\u2CDE\u2CE0\u2CE2\u2CEB\u2CED\u2CF2\uA640\uA642\uA644\uA646\uA648\uA64A\uA64C\uA64E\uA650\uA652\uA654\uA656\uA658\uA65A\uA65C\uA65E\uA660\uA662\uA664\uA666\uA668\uA66A\uA66C\uA680\uA682\uA684\uA686\uA688\uA68A\uA68C\uA68E\uA690\uA692\uA694\uA696\uA698\uA69A\uA722\uA724\uA726\uA728\uA72A\uA72C\uA72E\uA732\uA734\uA736\uA738\uA73A\uA73C\uA73E\uA740\uA742\uA744\uA746\uA748\uA74A\uA74C\uA74E\uA750\uA752\uA754\uA756\uA758\uA75A\uA75C\uA75E\uA760\uA762\uA764\uA766\uA768\uA76A\uA76C\uA76E\uA779\uA77B\uA77D\uA77E\uA780\uA782\uA784\uA786\uA78B\uA78D\uA790\uA792\uA796\uA798\uA79A\uA79C\uA79E\uA7A0\uA7A2\uA7A4\uA7A6\uA7A8\uA7AA-\uA7AD\uA7B0\uA7B1\uFB00-\uFB06\uFB13-\uFB17\uFF21-\uFF3A]|\uD801[\uDC00-\uDC27]|\uD806[\uDCA0-\uDCBF]/g;
+
+var map = {'A':'a','B':'b','C':'c','D':'d','E':'e','F':'f','G':'g','H':'h','I':'i','J':'j','K':'k','L':'l','M':'m','N':'n','O':'o','P':'p','Q':'q','R':'r','S':'s','T':'t','U':'u','V':'v','W':'w','X':'x','Y':'y','Z':'z','\xB5':'\u03BC','\xC0':'\xE0','\xC1':'\xE1','\xC2':'\xE2','\xC3':'\xE3','\xC4':'\xE4','\xC5':'\xE5','\xC6':'\xE6','\xC7':'\xE7','\xC8':'\xE8','\xC9':'\xE9','\xCA':'\xEA','\xCB':'\xEB','\xCC':'\xEC','\xCD':'\xED','\xCE':'\xEE','\xCF':'\xEF','\xD0':'\xF0','\xD1':'\xF1','\xD2':'\xF2','\xD3':'\xF3','\xD4':'\xF4','\xD5':'\xF5','\xD6':'\xF6','\xD8':'\xF8','\xD9':'\xF9','\xDA':'\xFA','\xDB':'\xFB','\xDC':'\xFC','\xDD':'\xFD','\xDE':'\xFE','\u0100':'\u0101','\u0102':'\u0103','\u0104':'\u0105','\u0106':'\u0107','\u0108':'\u0109','\u010A':'\u010B','\u010C':'\u010D','\u010E':'\u010F','\u0110':'\u0111','\u0112':'\u0113','\u0114':'\u0115','\u0116':'\u0117','\u0118':'\u0119','\u011A':'\u011B','\u011C':'\u011D','\u011E':'\u011F','\u0120':'\u0121','\u0122':'\u0123','\u0124':'\u0125','\u0126':'\u0127','\u0128':'\u0129','\u012A':'\u012B','\u012C':'\u012D','\u012E':'\u012F','\u0132':'\u0133','\u0134':'\u0135','\u0136':'\u0137','\u0139':'\u013A','\u013B':'\u013C','\u013D':'\u013E','\u013F':'\u0140','\u0141':'\u0142','\u0143':'\u0144','\u0145':'\u0146','\u0147':'\u0148','\u014A':'\u014B','\u014C':'\u014D','\u014E':'\u014F','\u0150':'\u0151','\u0152':'\u0153','\u0154':'\u0155','\u0156':'\u0157','\u0158':'\u0159','\u015A':'\u015B','\u015C':'\u015D','\u015E':'\u015F','\u0160':'\u0161','\u0162':'\u0163','\u0164':'\u0165','\u0166':'\u0167','\u0168':'\u0169','\u016A':'\u016B','\u016C':'\u016D','\u016E':'\u016F','\u0170':'\u0171','\u0172':'\u0173','\u0174':'\u0175','\u0176':'\u0177','\u0178':'\xFF','\u0179':'\u017A','\u017B':'\u017C','\u017D':'\u017E','\u017F':'s','\u0181':'\u0253','\u0182':'\u0183','\u0184':'\u0185','\u0186':'\u0254','\u0187':'\u0188','\u0189':'\u0256','\u018A':'\u0257','\u018B':'\u018C','\u018E':'\u01DD','\u018F':'\u0259','\u0190':'\u025B','\u0191':'\u0192','\u0193':'\u0260','\u0194':'\u0263','\u0196':'\u0269','\u0197':'\u0268','\u0198':'\u0199','\u019C':'\u026F','\u019D':'\u0272','\u019F':'\u0275','\u01A0':'\u01A1','\u01A2':'\u01A3','\u01A4':'\u01A5','\u01A6':'\u0280','\u01A7':'\u01A8','\u01A9':'\u0283','\u01AC':'\u01AD','\u01AE':'\u0288','\u01AF':'\u01B0','\u01B1':'\u028A','\u01B2':'\u028B','\u01B3':'\u01B4','\u01B5':'\u01B6','\u01B7':'\u0292','\u01B8':'\u01B9','\u01BC':'\u01BD','\u01C4':'\u01C6','\u01C5':'\u01C6','\u01C7':'\u01C9','\u01C8':'\u01C9','\u01CA':'\u01CC','\u01CB':'\u01CC','\u01CD':'\u01CE','\u01CF':'\u01D0','\u01D1':'\u01D2','\u01D3':'\u01D4','\u01D5':'\u01D6','\u01D7':'\u01D8','\u01D9':'\u01DA','\u01DB':'\u01DC','\u01DE':'\u01DF','\u01E0':'\u01E1','\u01E2':'\u01E3','\u01E4':'\u01E5','\u01E6':'\u01E7','\u01E8':'\u01E9','\u01EA':'\u01EB','\u01EC':'\u01ED','\u01EE':'\u01EF','\u01F1':'\u01F3','\u01F2':'\u01F3','\u01F4':'\u01F5','\u01F6':'\u0195','\u01F7':'\u01BF','\u01F8':'\u01F9','\u01FA':'\u01FB','\u01FC':'\u01FD','\u01FE':'\u01FF','\u0200':'\u0201','\u0202':'\u0203','\u0204':'\u0205','\u0206':'\u0207','\u0208':'\u0209','\u020A':'\u020B','\u020C':'\u020D','\u020E':'\u020F','\u0210':'\u0211','\u0212':'\u0213','\u0214':'\u0215','\u0216':'\u0217','\u0218':'\u0219','\u021A':'\u021B','\u021C':'\u021D','\u021E':'\u021F','\u0220':'\u019E','\u0222':'\u0223','\u0224':'\u0225','\u0226':'\u0227','\u0228':'\u0229','\u022A':'\u022B','\u022C':'\u022D','\u022E':'\u022F','\u0230':'\u0231','\u0232':'\u0233','\u023A':'\u2C65','\u023B':'\u023C','\u023D':'\u019A','\u023E':'\u2C66','\u0241':'\u0242','\u0243':'\u0180','\u0244':'\u0289','\u0245':'\u028C','\u0246':'\u0247','\u0248':'\u0249','\u024A':'\u024B','\u024C':'\u024D','\u024E':'\u024F','\u0345':'\u03B9','\u0370':'\u0371','\u0372':'\u0373','\u0376':'\u0377','\u037F':'\u03F3','\u0386':'\u03AC','\u0388':'\u03AD','\u0389':'\u03AE','\u038A':'\u03AF','\u038C':'\u03CC','\u038E':'\u03CD','\u038F':'\u03CE','\u0391':'\u03B1','\u0392':'\u03B2','\u0393':'\u03B3','\u0394':'\u03B4','\u0395':'\u03B5','\u0396':'\u03B6','\u0397':'\u03B7','\u0398':'\u03B8','\u0399':'\u03B9','\u039A':'\u03BA','\u039B':'\u03BB','\u039C':'\u03BC','\u039D':'\u03BD','\u039E':'\u03BE','\u039F':'\u03BF','\u03A0':'\u03C0','\u03A1':'\u03C1','\u03A3':'\u03C3','\u03A4':'\u03C4','\u03A5':'\u03C5','\u03A6':'\u03C6','\u03A7':'\u03C7','\u03A8':'\u03C8','\u03A9':'\u03C9','\u03AA':'\u03CA','\u03AB':'\u03CB','\u03C2':'\u03C3','\u03CF':'\u03D7','\u03D0':'\u03B2','\u03D1':'\u03B8','\u03D5':'\u03C6','\u03D6':'\u03C0','\u03D8':'\u03D9','\u03DA':'\u03DB','\u03DC':'\u03DD','\u03DE':'\u03DF','\u03E0':'\u03E1','\u03E2':'\u03E3','\u03E4':'\u03E5','\u03E6':'\u03E7','\u03E8':'\u03E9','\u03EA':'\u03EB','\u03EC':'\u03ED','\u03EE':'\u03EF','\u03F0':'\u03BA','\u03F1':'\u03C1','\u03F4':'\u03B8','\u03F5':'\u03B5','\u03F7':'\u03F8','\u03F9':'\u03F2','\u03FA':'\u03FB','\u03FD':'\u037B','\u03FE':'\u037C','\u03FF':'\u037D','\u0400':'\u0450','\u0401':'\u0451','\u0402':'\u0452','\u0403':'\u0453','\u0404':'\u0454','\u0405':'\u0455','\u0406':'\u0456','\u0407':'\u0457','\u0408':'\u0458','\u0409':'\u0459','\u040A':'\u045A','\u040B':'\u045B','\u040C':'\u045C','\u040D':'\u045D','\u040E':'\u045E','\u040F':'\u045F','\u0410':'\u0430','\u0411':'\u0431','\u0412':'\u0432','\u0413':'\u0433','\u0414':'\u0434','\u0415':'\u0435','\u0416':'\u0436','\u0417':'\u0437','\u0418':'\u0438','\u0419':'\u0439','\u041A':'\u043A','\u041B':'\u043B','\u041C':'\u043C','\u041D':'\u043D','\u041E':'\u043E','\u041F':'\u043F','\u0420':'\u0440','\u0421':'\u0441','\u0422':'\u0442','\u0423':'\u0443','\u0424':'\u0444','\u0425':'\u0445','\u0426':'\u0446','\u0427':'\u0447','\u0428':'\u0448','\u0429':'\u0449','\u042A':'\u044A','\u042B':'\u044B','\u042C':'\u044C','\u042D':'\u044D','\u042E':'\u044E','\u042F':'\u044F','\u0460':'\u0461','\u0462':'\u0463','\u0464':'\u0465','\u0466':'\u0467','\u0468':'\u0469','\u046A':'\u046B','\u046C':'\u046D','\u046E':'\u046F','\u0470':'\u0471','\u0472':'\u0473','\u0474':'\u0475','\u0476':'\u0477','\u0478':'\u0479','\u047A':'\u047B','\u047C':'\u047D','\u047E':'\u047F','\u0480':'\u0481','\u048A':'\u048B','\u048C':'\u048D','\u048E':'\u048F','\u0490':'\u0491','\u0492':'\u0493','\u0494':'\u0495','\u0496':'\u0497','\u0498':'\u0499','\u049A':'\u049B','\u049C':'\u049D','\u049E':'\u049F','\u04A0':'\u04A1','\u04A2':'\u04A3','\u04A4':'\u04A5','\u04A6':'\u04A7','\u04A8':'\u04A9','\u04AA':'\u04AB','\u04AC':'\u04AD','\u04AE':'\u04AF','\u04B0':'\u04B1','\u04B2':'\u04B3','\u04B4':'\u04B5','\u04B6':'\u04B7','\u04B8':'\u04B9','\u04BA':'\u04BB','\u04BC':'\u04BD','\u04BE':'\u04BF','\u04C0':'\u04CF','\u04C1':'\u04C2','\u04C3':'\u04C4','\u04C5':'\u04C6','\u04C7':'\u04C8','\u04C9':'\u04CA','\u04CB':'\u04CC','\u04CD':'\u04CE','\u04D0':'\u04D1','\u04D2':'\u04D3','\u04D4':'\u04D5','\u04D6':'\u04D7','\u04D8':'\u04D9','\u04DA':'\u04DB','\u04DC':'\u04DD','\u04DE':'\u04DF','\u04E0':'\u04E1','\u04E2':'\u04E3','\u04E4':'\u04E5','\u04E6':'\u04E7','\u04E8':'\u04E9','\u04EA':'\u04EB','\u04EC':'\u04ED','\u04EE':'\u04EF','\u04F0':'\u04F1','\u04F2':'\u04F3','\u04F4':'\u04F5','\u04F6':'\u04F7','\u04F8':'\u04F9','\u04FA':'\u04FB','\u04FC':'\u04FD','\u04FE':'\u04FF','\u0500':'\u0501','\u0502':'\u0503','\u0504':'\u0505','\u0506':'\u0507','\u0508':'\u0509','\u050A':'\u050B','\u050C':'\u050D','\u050E':'\u050F','\u0510':'\u0511','\u0512':'\u0513','\u0514':'\u0515','\u0516':'\u0517','\u0518':'\u0519','\u051A':'\u051B','\u051C':'\u051D','\u051E':'\u051F','\u0520':'\u0521','\u0522':'\u0523','\u0524':'\u0525','\u0526':'\u0527','\u0528':'\u0529','\u052A':'\u052B','\u052C':'\u052D','\u052E':'\u052F','\u0531':'\u0561','\u0532':'\u0562','\u0533':'\u0563','\u0534':'\u0564','\u0535':'\u0565','\u0536':'\u0566','\u0537':'\u0567','\u0538':'\u0568','\u0539':'\u0569','\u053A':'\u056A','\u053B':'\u056B','\u053C':'\u056C','\u053D':'\u056D','\u053E':'\u056E','\u053F':'\u056F','\u0540':'\u0570','\u0541':'\u0571','\u0542':'\u0572','\u0543':'\u0573','\u0544':'\u0574','\u0545':'\u0575','\u0546':'\u0576','\u0547':'\u0577','\u0548':'\u0578','\u0549':'\u0579','\u054A':'\u057A','\u054B':'\u057B','\u054C':'\u057C','\u054D':'\u057D','\u054E':'\u057E','\u054F':'\u057F','\u0550':'\u0580','\u0551':'\u0581','\u0552':'\u0582','\u0553':'\u0583','\u0554':'\u0584','\u0555':'\u0585','\u0556':'\u0586','\u10A0':'\u2D00','\u10A1':'\u2D01','\u10A2':'\u2D02','\u10A3':'\u2D03','\u10A4':'\u2D04','\u10A5':'\u2D05','\u10A6':'\u2D06','\u10A7':'\u2D07','\u10A8':'\u2D08','\u10A9':'\u2D09','\u10AA':'\u2D0A','\u10AB':'\u2D0B','\u10AC':'\u2D0C','\u10AD':'\u2D0D','\u10AE':'\u2D0E','\u10AF':'\u2D0F','\u10B0':'\u2D10','\u10B1':'\u2D11','\u10B2':'\u2D12','\u10B3':'\u2D13','\u10B4':'\u2D14','\u10B5':'\u2D15','\u10B6':'\u2D16','\u10B7':'\u2D17','\u10B8':'\u2D18','\u10B9':'\u2D19','\u10BA':'\u2D1A','\u10BB':'\u2D1B','\u10BC':'\u2D1C','\u10BD':'\u2D1D','\u10BE':'\u2D1E','\u10BF':'\u2D1F','\u10C0':'\u2D20','\u10C1':'\u2D21','\u10C2':'\u2D22','\u10C3':'\u2D23','\u10C4':'\u2D24','\u10C5':'\u2D25','\u10C7':'\u2D27','\u10CD':'\u2D2D','\u1E00':'\u1E01','\u1E02':'\u1E03','\u1E04':'\u1E05','\u1E06':'\u1E07','\u1E08':'\u1E09','\u1E0A':'\u1E0B','\u1E0C':'\u1E0D','\u1E0E':'\u1E0F','\u1E10':'\u1E11','\u1E12':'\u1E13','\u1E14':'\u1E15','\u1E16':'\u1E17','\u1E18':'\u1E19','\u1E1A':'\u1E1B','\u1E1C':'\u1E1D','\u1E1E':'\u1E1F','\u1E20':'\u1E21','\u1E22':'\u1E23','\u1E24':'\u1E25','\u1E26':'\u1E27','\u1E28':'\u1E29','\u1E2A':'\u1E2B','\u1E2C':'\u1E2D','\u1E2E':'\u1E2F','\u1E30':'\u1E31','\u1E32':'\u1E33','\u1E34':'\u1E35','\u1E36':'\u1E37','\u1E38':'\u1E39','\u1E3A':'\u1E3B','\u1E3C':'\u1E3D','\u1E3E':'\u1E3F','\u1E40':'\u1E41','\u1E42':'\u1E43','\u1E44':'\u1E45','\u1E46':'\u1E47','\u1E48':'\u1E49','\u1E4A':'\u1E4B','\u1E4C':'\u1E4D','\u1E4E':'\u1E4F','\u1E50':'\u1E51','\u1E52':'\u1E53','\u1E54':'\u1E55','\u1E56':'\u1E57','\u1E58':'\u1E59','\u1E5A':'\u1E5B','\u1E5C':'\u1E5D','\u1E5E':'\u1E5F','\u1E60':'\u1E61','\u1E62':'\u1E63','\u1E64':'\u1E65','\u1E66':'\u1E67','\u1E68':'\u1E69','\u1E6A':'\u1E6B','\u1E6C':'\u1E6D','\u1E6E':'\u1E6F','\u1E70':'\u1E71','\u1E72':'\u1E73','\u1E74':'\u1E75','\u1E76':'\u1E77','\u1E78':'\u1E79','\u1E7A':'\u1E7B','\u1E7C':'\u1E7D','\u1E7E':'\u1E7F','\u1E80':'\u1E81','\u1E82':'\u1E83','\u1E84':'\u1E85','\u1E86':'\u1E87','\u1E88':'\u1E89','\u1E8A':'\u1E8B','\u1E8C':'\u1E8D','\u1E8E':'\u1E8F','\u1E90':'\u1E91','\u1E92':'\u1E93','\u1E94':'\u1E95','\u1E9B':'\u1E61','\u1EA0':'\u1EA1','\u1EA2':'\u1EA3','\u1EA4':'\u1EA5','\u1EA6':'\u1EA7','\u1EA8':'\u1EA9','\u1EAA':'\u1EAB','\u1EAC':'\u1EAD','\u1EAE':'\u1EAF','\u1EB0':'\u1EB1','\u1EB2':'\u1EB3','\u1EB4':'\u1EB5','\u1EB6':'\u1EB7','\u1EB8':'\u1EB9','\u1EBA':'\u1EBB','\u1EBC':'\u1EBD','\u1EBE':'\u1EBF','\u1EC0':'\u1EC1','\u1EC2':'\u1EC3','\u1EC4':'\u1EC5','\u1EC6':'\u1EC7','\u1EC8':'\u1EC9','\u1ECA':'\u1ECB','\u1ECC':'\u1ECD','\u1ECE':'\u1ECF','\u1ED0':'\u1ED1','\u1ED2':'\u1ED3','\u1ED4':'\u1ED5','\u1ED6':'\u1ED7','\u1ED8':'\u1ED9','\u1EDA':'\u1EDB','\u1EDC':'\u1EDD','\u1EDE':'\u1EDF','\u1EE0':'\u1EE1','\u1EE2':'\u1EE3','\u1EE4':'\u1EE5','\u1EE6':'\u1EE7','\u1EE8':'\u1EE9','\u1EEA':'\u1EEB','\u1EEC':'\u1EED','\u1EEE':'\u1EEF','\u1EF0':'\u1EF1','\u1EF2':'\u1EF3','\u1EF4':'\u1EF5','\u1EF6':'\u1EF7','\u1EF8':'\u1EF9','\u1EFA':'\u1EFB','\u1EFC':'\u1EFD','\u1EFE':'\u1EFF','\u1F08':'\u1F00','\u1F09':'\u1F01','\u1F0A':'\u1F02','\u1F0B':'\u1F03','\u1F0C':'\u1F04','\u1F0D':'\u1F05','\u1F0E':'\u1F06','\u1F0F':'\u1F07','\u1F18':'\u1F10','\u1F19':'\u1F11','\u1F1A':'\u1F12','\u1F1B':'\u1F13','\u1F1C':'\u1F14','\u1F1D':'\u1F15','\u1F28':'\u1F20','\u1F29':'\u1F21','\u1F2A':'\u1F22','\u1F2B':'\u1F23','\u1F2C':'\u1F24','\u1F2D':'\u1F25','\u1F2E':'\u1F26','\u1F2F':'\u1F27','\u1F38':'\u1F30','\u1F39':'\u1F31','\u1F3A':'\u1F32','\u1F3B':'\u1F33','\u1F3C':'\u1F34','\u1F3D':'\u1F35','\u1F3E':'\u1F36','\u1F3F':'\u1F37','\u1F48':'\u1F40','\u1F49':'\u1F41','\u1F4A':'\u1F42','\u1F4B':'\u1F43','\u1F4C':'\u1F44','\u1F4D':'\u1F45','\u1F59':'\u1F51','\u1F5B':'\u1F53','\u1F5D':'\u1F55','\u1F5F':'\u1F57','\u1F68':'\u1F60','\u1F69':'\u1F61','\u1F6A':'\u1F62','\u1F6B':'\u1F63','\u1F6C':'\u1F64','\u1F6D':'\u1F65','\u1F6E':'\u1F66','\u1F6F':'\u1F67','\u1FB8':'\u1FB0','\u1FB9':'\u1FB1','\u1FBA':'\u1F70','\u1FBB':'\u1F71','\u1FBE':'\u03B9','\u1FC8':'\u1F72','\u1FC9':'\u1F73','\u1FCA':'\u1F74','\u1FCB':'\u1F75','\u1FD8':'\u1FD0','\u1FD9':'\u1FD1','\u1FDA':'\u1F76','\u1FDB':'\u1F77','\u1FE8':'\u1FE0','\u1FE9':'\u1FE1','\u1FEA':'\u1F7A','\u1FEB':'\u1F7B','\u1FEC':'\u1FE5','\u1FF8':'\u1F78','\u1FF9':'\u1F79','\u1FFA':'\u1F7C','\u1FFB':'\u1F7D','\u2126':'\u03C9','\u212A':'k','\u212B':'\xE5','\u2132':'\u214E','\u2160':'\u2170','\u2161':'\u2171','\u2162':'\u2172','\u2163':'\u2173','\u2164':'\u2174','\u2165':'\u2175','\u2166':'\u2176','\u2167':'\u2177','\u2168':'\u2178','\u2169':'\u2179','\u216A':'\u217A','\u216B':'\u217B','\u216C':'\u217C','\u216D':'\u217D','\u216E':'\u217E','\u216F':'\u217F','\u2183':'\u2184','\u24B6':'\u24D0','\u24B7':'\u24D1','\u24B8':'\u24D2','\u24B9':'\u24D3','\u24BA':'\u24D4','\u24BB':'\u24D5','\u24BC':'\u24D6','\u24BD':'\u24D7','\u24BE':'\u24D8','\u24BF':'\u24D9','\u24C0':'\u24DA','\u24C1':'\u24DB','\u24C2':'\u24DC','\u24C3':'\u24DD','\u24C4':'\u24DE','\u24C5':'\u24DF','\u24C6':'\u24E0','\u24C7':'\u24E1','\u24C8':'\u24E2','\u24C9':'\u24E3','\u24CA':'\u24E4','\u24CB':'\u24E5','\u24CC':'\u24E6','\u24CD':'\u24E7','\u24CE':'\u24E8','\u24CF':'\u24E9','\u2C00':'\u2C30','\u2C01':'\u2C31','\u2C02':'\u2C32','\u2C03':'\u2C33','\u2C04':'\u2C34','\u2C05':'\u2C35','\u2C06':'\u2C36','\u2C07':'\u2C37','\u2C08':'\u2C38','\u2C09':'\u2C39','\u2C0A':'\u2C3A','\u2C0B':'\u2C3B','\u2C0C':'\u2C3C','\u2C0D':'\u2C3D','\u2C0E':'\u2C3E','\u2C0F':'\u2C3F','\u2C10':'\u2C40','\u2C11':'\u2C41','\u2C12':'\u2C42','\u2C13':'\u2C43','\u2C14':'\u2C44','\u2C15':'\u2C45','\u2C16':'\u2C46','\u2C17':'\u2C47','\u2C18':'\u2C48','\u2C19':'\u2C49','\u2C1A':'\u2C4A','\u2C1B':'\u2C4B','\u2C1C':'\u2C4C','\u2C1D':'\u2C4D','\u2C1E':'\u2C4E','\u2C1F':'\u2C4F','\u2C20':'\u2C50','\u2C21':'\u2C51','\u2C22':'\u2C52','\u2C23':'\u2C53','\u2C24':'\u2C54','\u2C25':'\u2C55','\u2C26':'\u2C56','\u2C27':'\u2C57','\u2C28':'\u2C58','\u2C29':'\u2C59','\u2C2A':'\u2C5A','\u2C2B':'\u2C5B','\u2C2C':'\u2C5C','\u2C2D':'\u2C5D','\u2C2E':'\u2C5E','\u2C60':'\u2C61','\u2C62':'\u026B','\u2C63':'\u1D7D','\u2C64':'\u027D','\u2C67':'\u2C68','\u2C69':'\u2C6A','\u2C6B':'\u2C6C','\u2C6D':'\u0251','\u2C6E':'\u0271','\u2C6F':'\u0250','\u2C70':'\u0252','\u2C72':'\u2C73','\u2C75':'\u2C76','\u2C7E':'\u023F','\u2C7F':'\u0240','\u2C80':'\u2C81','\u2C82':'\u2C83','\u2C84':'\u2C85','\u2C86':'\u2C87','\u2C88':'\u2C89','\u2C8A':'\u2C8B','\u2C8C':'\u2C8D','\u2C8E':'\u2C8F','\u2C90':'\u2C91','\u2C92':'\u2C93','\u2C94':'\u2C95','\u2C96':'\u2C97','\u2C98':'\u2C99','\u2C9A':'\u2C9B','\u2C9C':'\u2C9D','\u2C9E':'\u2C9F','\u2CA0':'\u2CA1','\u2CA2':'\u2CA3','\u2CA4':'\u2CA5','\u2CA6':'\u2CA7','\u2CA8':'\u2CA9','\u2CAA':'\u2CAB','\u2CAC':'\u2CAD','\u2CAE':'\u2CAF','\u2CB0':'\u2CB1','\u2CB2':'\u2CB3','\u2CB4':'\u2CB5','\u2CB6':'\u2CB7','\u2CB8':'\u2CB9','\u2CBA':'\u2CBB','\u2CBC':'\u2CBD','\u2CBE':'\u2CBF','\u2CC0':'\u2CC1','\u2CC2':'\u2CC3','\u2CC4':'\u2CC5','\u2CC6':'\u2CC7','\u2CC8':'\u2CC9','\u2CCA':'\u2CCB','\u2CCC':'\u2CCD','\u2CCE':'\u2CCF','\u2CD0':'\u2CD1','\u2CD2':'\u2CD3','\u2CD4':'\u2CD5','\u2CD6':'\u2CD7','\u2CD8':'\u2CD9','\u2CDA':'\u2CDB','\u2CDC':'\u2CDD','\u2CDE':'\u2CDF','\u2CE0':'\u2CE1','\u2CE2':'\u2CE3','\u2CEB':'\u2CEC','\u2CED':'\u2CEE','\u2CF2':'\u2CF3','\uA640':'\uA641','\uA642':'\uA643','\uA644':'\uA645','\uA646':'\uA647','\uA648':'\uA649','\uA64A':'\uA64B','\uA64C':'\uA64D','\uA64E':'\uA64F','\uA650':'\uA651','\uA652':'\uA653','\uA654':'\uA655','\uA656':'\uA657','\uA658':'\uA659','\uA65A':'\uA65B','\uA65C':'\uA65D','\uA65E':'\uA65F','\uA660':'\uA661','\uA662':'\uA663','\uA664':'\uA665','\uA666':'\uA667','\uA668':'\uA669','\uA66A':'\uA66B','\uA66C':'\uA66D','\uA680':'\uA681','\uA682':'\uA683','\uA684':'\uA685','\uA686':'\uA687','\uA688':'\uA689','\uA68A':'\uA68B','\uA68C':'\uA68D','\uA68E':'\uA68F','\uA690':'\uA691','\uA692':'\uA693','\uA694':'\uA695','\uA696':'\uA697','\uA698':'\uA699','\uA69A':'\uA69B','\uA722':'\uA723','\uA724':'\uA725','\uA726':'\uA727','\uA728':'\uA729','\uA72A':'\uA72B','\uA72C':'\uA72D','\uA72E':'\uA72F','\uA732':'\uA733','\uA734':'\uA735','\uA736':'\uA737','\uA738':'\uA739','\uA73A':'\uA73B','\uA73C':'\uA73D','\uA73E':'\uA73F','\uA740':'\uA741','\uA742':'\uA743','\uA744':'\uA745','\uA746':'\uA747','\uA748':'\uA749','\uA74A':'\uA74B','\uA74C':'\uA74D','\uA74E':'\uA74F','\uA750':'\uA751','\uA752':'\uA753','\uA754':'\uA755','\uA756':'\uA757','\uA758':'\uA759','\uA75A':'\uA75B','\uA75C':'\uA75D','\uA75E':'\uA75F','\uA760':'\uA761','\uA762':'\uA763','\uA764':'\uA765','\uA766':'\uA767','\uA768':'\uA769','\uA76A':'\uA76B','\uA76C':'\uA76D','\uA76E':'\uA76F','\uA779':'\uA77A','\uA77B':'\uA77C','\uA77D':'\u1D79','\uA77E':'\uA77F','\uA780':'\uA781','\uA782':'\uA783','\uA784':'\uA785','\uA786':'\uA787','\uA78B':'\uA78C','\uA78D':'\u0265','\uA790':'\uA791','\uA792':'\uA793','\uA796':'\uA797','\uA798':'\uA799','\uA79A':'\uA79B','\uA79C':'\uA79D','\uA79E':'\uA79F','\uA7A0':'\uA7A1','\uA7A2':'\uA7A3','\uA7A4':'\uA7A5','\uA7A6':'\uA7A7','\uA7A8':'\uA7A9','\uA7AA':'\u0266','\uA7AB':'\u025C','\uA7AC':'\u0261','\uA7AD':'\u026C','\uA7B0':'\u029E','\uA7B1':'\u0287','\uFF21':'\uFF41','\uFF22':'\uFF42','\uFF23':'\uFF43','\uFF24':'\uFF44','\uFF25':'\uFF45','\uFF26':'\uFF46','\uFF27':'\uFF47','\uFF28':'\uFF48','\uFF29':'\uFF49','\uFF2A':'\uFF4A','\uFF2B':'\uFF4B','\uFF2C':'\uFF4C','\uFF2D':'\uFF4D','\uFF2E':'\uFF4E','\uFF2F':'\uFF4F','\uFF30':'\uFF50','\uFF31':'\uFF51','\uFF32':'\uFF52','\uFF33':'\uFF53','\uFF34':'\uFF54','\uFF35':'\uFF55','\uFF36':'\uFF56','\uFF37':'\uFF57','\uFF38':'\uFF58','\uFF39':'\uFF59','\uFF3A':'\uFF5A','\uD801\uDC00':'\uD801\uDC28','\uD801\uDC01':'\uD801\uDC29','\uD801\uDC02':'\uD801\uDC2A','\uD801\uDC03':'\uD801\uDC2B','\uD801\uDC04':'\uD801\uDC2C','\uD801\uDC05':'\uD801\uDC2D','\uD801\uDC06':'\uD801\uDC2E','\uD801\uDC07':'\uD801\uDC2F','\uD801\uDC08':'\uD801\uDC30','\uD801\uDC09':'\uD801\uDC31','\uD801\uDC0A':'\uD801\uDC32','\uD801\uDC0B':'\uD801\uDC33','\uD801\uDC0C':'\uD801\uDC34','\uD801\uDC0D':'\uD801\uDC35','\uD801\uDC0E':'\uD801\uDC36','\uD801\uDC0F':'\uD801\uDC37','\uD801\uDC10':'\uD801\uDC38','\uD801\uDC11':'\uD801\uDC39','\uD801\uDC12':'\uD801\uDC3A','\uD801\uDC13':'\uD801\uDC3B','\uD801\uDC14':'\uD801\uDC3C','\uD801\uDC15':'\uD801\uDC3D','\uD801\uDC16':'\uD801\uDC3E','\uD801\uDC17':'\uD801\uDC3F','\uD801\uDC18':'\uD801\uDC40','\uD801\uDC19':'\uD801\uDC41','\uD801\uDC1A':'\uD801\uDC42','\uD801\uDC1B':'\uD801\uDC43','\uD801\uDC1C':'\uD801\uDC44','\uD801\uDC1D':'\uD801\uDC45','\uD801\uDC1E':'\uD801\uDC46','\uD801\uDC1F':'\uD801\uDC47','\uD801\uDC20':'\uD801\uDC48','\uD801\uDC21':'\uD801\uDC49','\uD801\uDC22':'\uD801\uDC4A','\uD801\uDC23':'\uD801\uDC4B','\uD801\uDC24':'\uD801\uDC4C','\uD801\uDC25':'\uD801\uDC4D','\uD801\uDC26':'\uD801\uDC4E','\uD801\uDC27':'\uD801\uDC4F','\uD806\uDCA0':'\uD806\uDCC0','\uD806\uDCA1':'\uD806\uDCC1','\uD806\uDCA2':'\uD806\uDCC2','\uD806\uDCA3':'\uD806\uDCC3','\uD806\uDCA4':'\uD806\uDCC4','\uD806\uDCA5':'\uD806\uDCC5','\uD806\uDCA6':'\uD806\uDCC6','\uD806\uDCA7':'\uD806\uDCC7','\uD806\uDCA8':'\uD806\uDCC8','\uD806\uDCA9':'\uD806\uDCC9','\uD806\uDCAA':'\uD806\uDCCA','\uD806\uDCAB':'\uD806\uDCCB','\uD806\uDCAC':'\uD806\uDCCC','\uD806\uDCAD':'\uD806\uDCCD','\uD806\uDCAE':'\uD806\uDCCE','\uD806\uDCAF':'\uD806\uDCCF','\uD806\uDCB0':'\uD806\uDCD0','\uD806\uDCB1':'\uD806\uDCD1','\uD806\uDCB2':'\uD806\uDCD2','\uD806\uDCB3':'\uD806\uDCD3','\uD806\uDCB4':'\uD806\uDCD4','\uD806\uDCB5':'\uD806\uDCD5','\uD806\uDCB6':'\uD806\uDCD6','\uD806\uDCB7':'\uD806\uDCD7','\uD806\uDCB8':'\uD806\uDCD8','\uD806\uDCB9':'\uD806\uDCD9','\uD806\uDCBA':'\uD806\uDCDA','\uD806\uDCBB':'\uD806\uDCDB','\uD806\uDCBC':'\uD806\uDCDC','\uD806\uDCBD':'\uD806\uDCDD','\uD806\uDCBE':'\uD806\uDCDE','\uD806\uDCBF':'\uD806\uDCDF','\xDF':'ss','\u0130':'i\u0307','\u0149':'\u02BCn','\u01F0':'j\u030C','\u0390':'\u03B9\u0308\u0301','\u03B0':'\u03C5\u0308\u0301','\u0587':'\u0565\u0582','\u1E96':'h\u0331','\u1E97':'t\u0308','\u1E98':'w\u030A','\u1E99':'y\u030A','\u1E9A':'a\u02BE','\u1E9E':'ss','\u1F50':'\u03C5\u0313','\u1F52':'\u03C5\u0313\u0300','\u1F54':'\u03C5\u0313\u0301','\u1F56':'\u03C5\u0313\u0342','\u1F80':'\u1F00\u03B9','\u1F81':'\u1F01\u03B9','\u1F82':'\u1F02\u03B9','\u1F83':'\u1F03\u03B9','\u1F84':'\u1F04\u03B9','\u1F85':'\u1F05\u03B9','\u1F86':'\u1F06\u03B9','\u1F87':'\u1F07\u03B9','\u1F88':'\u1F00\u03B9','\u1F89':'\u1F01\u03B9','\u1F8A':'\u1F02\u03B9','\u1F8B':'\u1F03\u03B9','\u1F8C':'\u1F04\u03B9','\u1F8D':'\u1F05\u03B9','\u1F8E':'\u1F06\u03B9','\u1F8F':'\u1F07\u03B9','\u1F90':'\u1F20\u03B9','\u1F91':'\u1F21\u03B9','\u1F92':'\u1F22\u03B9','\u1F93':'\u1F23\u03B9','\u1F94':'\u1F24\u03B9','\u1F95':'\u1F25\u03B9','\u1F96':'\u1F26\u03B9','\u1F97':'\u1F27\u03B9','\u1F98':'\u1F20\u03B9','\u1F99':'\u1F21\u03B9','\u1F9A':'\u1F22\u03B9','\u1F9B':'\u1F23\u03B9','\u1F9C':'\u1F24\u03B9','\u1F9D':'\u1F25\u03B9','\u1F9E':'\u1F26\u03B9','\u1F9F':'\u1F27\u03B9','\u1FA0':'\u1F60\u03B9','\u1FA1':'\u1F61\u03B9','\u1FA2':'\u1F62\u03B9','\u1FA3':'\u1F63\u03B9','\u1FA4':'\u1F64\u03B9','\u1FA5':'\u1F65\u03B9','\u1FA6':'\u1F66\u03B9','\u1FA7':'\u1F67\u03B9','\u1FA8':'\u1F60\u03B9','\u1FA9':'\u1F61\u03B9','\u1FAA':'\u1F62\u03B9','\u1FAB':'\u1F63\u03B9','\u1FAC':'\u1F64\u03B9','\u1FAD':'\u1F65\u03B9','\u1FAE':'\u1F66\u03B9','\u1FAF':'\u1F67\u03B9','\u1FB2':'\u1F70\u03B9','\u1FB3':'\u03B1\u03B9','\u1FB4':'\u03AC\u03B9','\u1FB6':'\u03B1\u0342','\u1FB7':'\u03B1\u0342\u03B9','\u1FBC':'\u03B1\u03B9','\u1FC2':'\u1F74\u03B9','\u1FC3':'\u03B7\u03B9','\u1FC4':'\u03AE\u03B9','\u1FC6':'\u03B7\u0342','\u1FC7':'\u03B7\u0342\u03B9','\u1FCC':'\u03B7\u03B9','\u1FD2':'\u03B9\u0308\u0300','\u1FD3':'\u03B9\u0308\u0301','\u1FD6':'\u03B9\u0342','\u1FD7':'\u03B9\u0308\u0342','\u1FE2':'\u03C5\u0308\u0300','\u1FE3':'\u03C5\u0308\u0301','\u1FE4':'\u03C1\u0313','\u1FE6':'\u03C5\u0342','\u1FE7':'\u03C5\u0308\u0342','\u1FF2':'\u1F7C\u03B9','\u1FF3':'\u03C9\u03B9','\u1FF4':'\u03CE\u03B9','\u1FF6':'\u03C9\u0342','\u1FF7':'\u03C9\u0342\u03B9','\u1FFC':'\u03C9\u03B9','\uFB00':'ff','\uFB01':'fi','\uFB02':'fl','\uFB03':'ffi','\uFB04':'ffl','\uFB05':'st','\uFB06':'st','\uFB13':'\u0574\u0576','\uFB14':'\u0574\u0565','\uFB15':'\u0574\u056B','\uFB16':'\u057E\u0576','\uFB17':'\u0574\u056D'};
+
+// Normalize reference label: collapse internal whitespace
+// to single space, remove leading/trailing whitespace, case fold.
+module.exports = function(string) {
+ return string.slice(1, string.length - 1).trim().replace(regex, function($0) {
+ // Note: there is no need to check `hasOwnProperty($0)` here.
+ // If character not found in lookup table, it must be whitespace.
+ return map[$0] || ' ';
+ });
+};
+
+},{}],9:[function(require,module,exports){
+"use strict";
+
+var escapeXml = require('./common').escapeXml;
+
+// Helper function to produce an XML tag.
+var tag = function(name, attrs, selfclosing) {
+ var result = '<' + name;
+ if (attrs && attrs.length > 0) {
+ var i = 0;
+ var attrib;
+ while ((attrib = attrs[i]) !== undefined) {
+ result += ' ' + attrib[0] + '="' + attrib[1] + '"';
+ i++;
+ }
+ }
+ if (selfclosing) {
+ result += ' /';
+ }
+
+ result += '>';
+ return result;
+};
+
+var reXMLTag = /\<[^>]*\>/;
+
+var toTagName = function(s) {
+ return s.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
+};
+
+var renderNodes = function(block) {
+
+ var attrs;
+ var tagname;
+ var walker = block.walker();
+ var event, node, entering;
+ var buffer = "";
+ var lastOut = "\n";
+ var disableTags = 0;
+ var indentLevel = 0;
+ var indent = ' ';
+ var unescapedContents;
+ var container;
+ var selfClosing;
+ var nodetype;
+
+ var out = function(s) {
+ if (disableTags > 0) {
+ buffer += s.replace(reXMLTag, '');
+ } else {
+ buffer += s;
+ }
+ lastOut = s;
+ };
+ var esc = this.escape;
+ var cr = function() {
+ if (lastOut !== '\n') {
+ buffer += '\n';
+ lastOut = '\n';
+ for (var i = indentLevel; i > 0; i--) {
+ buffer += indent;
+ }
+ }
+ };
+
+ var options = this.options;
+
+ if (options.time) { console.time("rendering"); }
+
+ buffer += '<?xml version="1.0" encoding="UTF-8"?>\n';
+ buffer += '<!DOCTYPE CommonMark SYSTEM "CommonMark.dtd">\n';
+
+ while ((event = walker.next())) {
+ entering = event.entering;
+ node = event.node;
+ nodetype = node.type;
+
+ container = node.isContainer;
+ selfClosing = nodetype === 'HorizontalRule' || nodetype === 'Hardbreak' ||
+ nodetype === 'Softbreak';
+ unescapedContents = nodetype === 'Html' || nodetype === 'HtmlInline';
+ tagname = toTagName(nodetype);
+
+ if (entering) {
+
+ attrs = [];
+
+ switch (nodetype) {
+ case 'List':
+ if (node.listType !== null) {
+ attrs.push(['type', node.listType.toLowerCase()]);
+ }
+ if (node.listStart !== null) {
+ attrs.push(['start', String(node.listStart)]);
+ }
+ if (node.listTight !== null) {
+ attrs.push(['tight', (node.listTight ? 'true' : 'false')]);
+ }
+ var delim = node.listDelimiter;
+ if (delim !== null) {
+ var delimword = '';
+ if (delim === '.') {
+ delimword = 'period';
+ } else {
+ delimword = 'paren';
+ }
+ attrs.push(['delimiter', delimword]);
+ }
+ break;
+ case 'CodeBlock':
+ if (node.info) {
+ attrs.push(['info', node.info]);
+ }
+ break;
+ case 'Header':
+ attrs.push(['level', String(node.level)]);
+ break;
+ case 'Link':
+ case 'Image':
+ attrs.push(['destination', node.destination]);
+ attrs.push(['title', node.title]);
+ break;
+ default:
+ break;
+ }
+ if (options.sourcepos) {
+ var pos = node.sourcepos;
+ if (pos) {
+ attrs.push(['sourcepos', String(pos[0][0]) + ':' +
+ String(pos[0][1]) + '-' + String(pos[1][0]) + ':' +
+ String(pos[1][1])]);
+ }
+ }
+
+ cr();
+ out(tag(tagname, attrs, selfClosing));
+ if (container) {
+ indentLevel += 1;
+ } else if (!container && !selfClosing) {
+ var lit = node.literal;
+ if (lit) {
+ out(unescapedContents ? lit : esc(lit));
+ }
+ out(tag('/' + tagname));
+ }
+ } else {
+ indentLevel -= 1;
+ cr();
+ out(tag('/' + tagname));
+ }
+
+
+ }
+ if (options.time) { console.timeEnd("rendering"); }
+ buffer += '\n';
+ return buffer;
+};
+
+// The XmlRenderer object.
+function XmlRenderer(options){
+ return {
+ // default options:
+ softbreak: '\n', // by default, soft breaks are rendered as newlines in HTML
+ // set to "<br />" to make them hard breaks
+ // set to " " if you want to ignore line wrapping in source
+ escape: escapeXml,
+ options: options || {},
+ render: renderNodes
+ };
+}
+
+module.exports = XmlRenderer;
+
+},{"./common":2}],10:[function(require,module,exports){
+var encode = require("./lib/encode.js"),
+ decode = require("./lib/decode.js");
+
+exports.decode = function(data, level){
+ return (!level || level <= 0 ? decode.XML : decode.HTML)(data);
+};
+
+exports.decodeStrict = function(data, level){
+ return (!level || level <= 0 ? decode.XML : decode.HTMLStrict)(data);
+};
+
+exports.encode = function(data, level){
+ return (!level || level <= 0 ? encode.XML : encode.HTML)(data);
+};
+
+exports.encodeXML = encode.XML;
+
+exports.encodeHTML4 =
+exports.encodeHTML5 =
+exports.encodeHTML = encode.HTML;
+
+exports.decodeXML =
+exports.decodeXMLStrict = decode.XML;
+
+exports.decodeHTML4 =
+exports.decodeHTML5 =
+exports.decodeHTML = decode.HTML;
+
+exports.decodeHTML4Strict =
+exports.decodeHTML5Strict =
+exports.decodeHTMLStrict = decode.HTMLStrict;
+
+exports.escape = encode.escape;
+
+},{"./lib/decode.js":11,"./lib/encode.js":13}],11:[function(require,module,exports){
+var entityMap = require("../maps/entities.json"),
+ legacyMap = require("../maps/legacy.json"),
+ xmlMap = require("../maps/xml.json"),
+ decodeCodePoint = require("./decode_codepoint.js");
+
+var decodeXMLStrict = getStrictDecoder(xmlMap),
+ decodeHTMLStrict = getStrictDecoder(entityMap);
+
+function getStrictDecoder(map){
+ var keys = Object.keys(map).join("|"),
+ replace = getReplacer(map);
+
+ keys += "|#[xX][\\da-fA-F]+|#\\d+";
+
+ var re = new RegExp("&(?:" + keys + ");", "g");
+
+ return function(str){
+ return String(str).replace(re, replace);
+ };
+}
+
+var decodeHTML = (function(){
+ var legacy = Object.keys(legacyMap)
+ .sort(sorter);
+
+ var keys = Object.keys(entityMap)
+ .sort(sorter);
+
+ for(var i = 0, j = 0; i < keys.length; i++){
+ if(legacy[j] === keys[i]){
+ keys[i] += ";?";
+ j++;
+ } else {
+ keys[i] += ";";
+ }
+ }
+
+ var re = new RegExp("&(?:" + keys.join("|") + "|#[xX][\\da-fA-F]+;?|#\\d+;?)", "g"),
+ replace = getReplacer(entityMap);
+
+ function replacer(str){
+ if(str.substr(-1) !== ";") str += ";";
+ return replace(str);
+ }
+
+ //TODO consider creating a merged map
+ return function(str){
+ return String(str).replace(re, replacer);
+ };
+}());
+
+function sorter(a, b){
+ return a < b ? 1 : -1;
+}
+
+function getReplacer(map){
+ return function replace(str){
+ if(str.charAt(1) === "#"){
+ if(str.charAt(2) === "X" || str.charAt(2) === "x"){
+ return decodeCodePoint(parseInt(str.substr(3), 16));
+ }
+ return decodeCodePoint(parseInt(str.substr(2), 10));
+ }
+ return map[str.slice(1, -1)];
+ };
+}
+
+module.exports = {
+ XML: decodeXMLStrict,
+ HTML: decodeHTML,
+ HTMLStrict: decodeHTMLStrict
+};
+},{"../maps/entities.json":15,"../maps/legacy.json":16,"../maps/xml.json":17,"./decode_codepoint.js":12}],12:[function(require,module,exports){
+var decodeMap = require("../maps/decode.json");
+
+module.exports = decodeCodePoint;
+
+// modified version of https://github.com/mathiasbynens/he/blob/master/src/he.js#L94-L119
+function decodeCodePoint(codePoint){
+
+ if((codePoint >= 0xD800 && codePoint <= 0xDFFF) || codePoint > 0x10FFFF){
+ return "\uFFFD";
+ }
+
+ if(codePoint in decodeMap){
+ codePoint = decodeMap[codePoint];
+ }
+
+ var output = "";
+
+ if(codePoint > 0xFFFF){
+ codePoint -= 0x10000;
+ output += String.fromCharCode(codePoint >>> 10 & 0x3FF | 0xD800);
+ codePoint = 0xDC00 | codePoint & 0x3FF;
+ }
+
+ output += String.fromCharCode(codePoint);
+ return output;
+}
+
+},{"../maps/decode.json":14}],13:[function(require,module,exports){
+var inverseXML = getInverseObj(require("../maps/xml.json")),
+ xmlReplacer = getInverseReplacer(inverseXML);
+
+exports.XML = getInverse(inverseXML, xmlReplacer);
+
+var inverseHTML = getInverseObj(require("../maps/entities.json")),
+ htmlReplacer = getInverseReplacer(inverseHTML);
+
+exports.HTML = getInverse(inverseHTML, htmlReplacer);
+
+function getInverseObj(obj){
+ return Object.keys(obj).sort().reduce(function(inverse, name){
+ inverse[obj[name]] = "&" + name + ";";
+ return inverse;
+ }, {});
+}
+
+function getInverseReplacer(inverse){
+ var single = [],
+ multiple = [];
+
+ Object.keys(inverse).forEach(function(k){
+ if(k.length === 1){
+ single.push("\\" + k);
+ } else {
+ multiple.push(k);
+ }
+ });
+
+ //TODO add ranges
+ multiple.unshift("[" + single.join("") + "]");
+
+ return new RegExp(multiple.join("|"), "g");
+}
+
+var re_nonASCII = /[^\0-\x7F]/g,
+ re_astralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
+
+function singleCharReplacer(c){
+ return "&#x" + c.charCodeAt(0).toString(16).toUpperCase() + ";";
+}
+
+function astralReplacer(c){
+ // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
+ var high = c.charCodeAt(0);
+ var low = c.charCodeAt(1);
+ var codePoint = (high - 0xD800) * 0x400 + low - 0xDC00 + 0x10000;
+ return "&#x" + codePoint.toString(16).toUpperCase() + ";";
+}
+
+function getInverse(inverse, re){
+ function func(name){
+ return inverse[name];
+ }
+
+ return function(data){
+ return data
+ .replace(re, func)
+ .replace(re_astralSymbols, astralReplacer)
+ .replace(re_nonASCII, singleCharReplacer);
+ };
+}
+
+var re_xmlChars = getInverseReplacer(inverseXML);
+
+function escapeXML(data){
+ return data
+ .replace(re_xmlChars, singleCharReplacer)
+ .replace(re_astralSymbols, astralReplacer)
+ .replace(re_nonASCII, singleCharReplacer);
+}
+
+exports.escape = escapeXML;
+
+},{"../maps/entities.json":15,"../maps/xml.json":17}],14:[function(require,module,exports){
+module.exports={"0":65533,"128":8364,"130":8218,"131":402,"132":8222,"133":8230,"134":8224,"135":8225,"136":710,"137":8240,"138":352,"139":8249,"140":338,"142":381,"145":8216,"146":8217,"147":8220,"148":8221,"149":8226,"150":8211,"151":8212,"152":732,"153":8482,"154":353,"155":8250,"156":339,"158":382,"159":376}
+},{}],15:[function(require,module,exports){
+module.exports={"Aacute":"\u00C1","aacute":"\u00E1","Abreve":"\u0102","abreve":"\u0103","ac":"\u223E","acd":"\u223F","acE":"\u223E\u0333","Acirc":"\u00C2","acirc":"\u00E2","acute":"\u00B4","Acy":"\u0410","acy":"\u0430","AElig":"\u00C6","aelig":"\u00E6","af":"\u2061","Afr":"\uD835\uDD04","afr":"\uD835\uDD1E","Agrave":"\u00C0","agrave":"\u00E0","alefsym":"\u2135","aleph":"\u2135","Alpha":"\u0391","alpha":"\u03B1","Amacr":"\u0100","amacr":"\u0101","amalg":"\u2A3F","amp":"&","AMP":"&","andand":"\u2A55","And":"\u2A53","and":"\u2227","andd":"\u2A5C","andslope":"\u2A58","andv":"\u2A5A","ang":"\u2220","ange":"\u29A4","angle":"\u2220","angmsdaa":"\u29A8","angmsdab":"\u29A9","angmsdac":"\u29AA","angmsdad":"\u29AB","angmsdae":"\u29AC","angmsdaf":"\u29AD","angmsdag":"\u29AE","angmsdah":"\u29AF","angmsd":"\u2221","angrt":"\u221F","angrtvb":"\u22BE","angrtvbd":"\u299D","angsph":"\u2222","angst":"\u00C5","angzarr":"\u237C","Aogon":"\u0104","aogon":"\u0105","Aopf":"\uD835\uDD38","aopf":"\uD835\uDD52","apacir":"\u2A6F","ap":"\u2248","apE":"\u2A70","ape":"\u224A","apid":"\u224B","apos":"'","ApplyFunction":"\u2061","approx":"\u2248","approxeq":"\u224A","Aring":"\u00C5","aring":"\u00E5","Ascr":"\uD835\uDC9C","ascr":"\uD835\uDCB6","Assign":"\u2254","ast":"*","asymp":"\u2248","asympeq":"\u224D","Atilde":"\u00C3","atilde":"\u00E3","Auml":"\u00C4","auml":"\u00E4","awconint":"\u2233","awint":"\u2A11","backcong":"\u224C","backepsilon":"\u03F6","backprime":"\u2035","backsim":"\u223D","backsimeq":"\u22CD","Backslash":"\u2216","Barv":"\u2AE7","barvee":"\u22BD","barwed":"\u2305","Barwed":"\u2306","barwedge":"\u2305","bbrk":"\u23B5","bbrktbrk":"\u23B6","bcong":"\u224C","Bcy":"\u0411","bcy":"\u0431","bdquo":"\u201E","becaus":"\u2235","because":"\u2235","Because":"\u2235","bemptyv":"\u29B0","bepsi":"\u03F6","bernou":"\u212C","Bernoullis":"\u212C","Beta":"\u0392","beta":"\u03B2","beth":"\u2136","between":"\u226C","Bfr":"\uD835\uDD05","bfr":"\uD835\uDD1F","bigcap":"\u22C2","bigcirc":"\u25EF","bigcup":"\u22C3","bigodot":"\u2A00","bigoplus":"\u2A01","bigotimes":"\u2A02","bigsqcup":"\u2A06","bigstar":"\u2605","bigtriangledown":"\u25BD","bigtriangleup":"\u25B3","biguplus":"\u2A04","bigvee":"\u22C1","bigwedge":"\u22C0","bkarow":"\u290D","blacklozenge":"\u29EB","blacksquare":"\u25AA","blacktriangle":"\u25B4","blacktriangledown":"\u25BE","blacktriangleleft":"\u25C2","blacktriangleright":"\u25B8","blank":"\u2423","blk12":"\u2592","blk14":"\u2591","blk34":"\u2593","block":"\u2588","bne":"=\u20E5","bnequiv":"\u2261\u20E5","bNot":"\u2AED","bnot":"\u2310","Bopf":"\uD835\uDD39","bopf":"\uD835\uDD53","bot":"\u22A5","bottom":"\u22A5","bowtie":"\u22C8","boxbox":"\u29C9","boxdl":"\u2510","boxdL":"\u2555","boxDl":"\u2556","boxDL":"\u2557","boxdr":"\u250C","boxdR":"\u2552","boxDr":"\u2553","boxDR":"\u2554","boxh":"\u2500","boxH":"\u2550","boxhd":"\u252C","boxHd":"\u2564","boxhD":"\u2565","boxHD":"\u2566","boxhu":"\u2534","boxHu":"\u2567","boxhU":"\u2568","boxHU":"\u2569","boxminus":"\u229F","boxplus":"\u229E","boxtimes":"\u22A0","boxul":"\u2518","boxuL":"\u255B","boxUl":"\u255C","boxUL":"\u255D","boxur":"\u2514","boxuR":"\u2558","boxUr":"\u2559","boxUR":"\u255A","boxv":"\u2502","boxV":"\u2551","boxvh":"\u253C","boxvH":"\u256A","boxVh":"\u256B","boxVH":"\u256C","boxvl":"\u2524","boxvL":"\u2561","boxVl":"\u2562","boxVL":"\u2563","boxvr":"\u251C","boxvR":"\u255E","boxVr":"\u255F","boxVR":"\u2560","bprime":"\u2035","breve":"\u02D8","Breve":"\u02D8","brvbar":"\u00A6","bscr":"\uD835\uDCB7","Bscr":"\u212C","bsemi":"\u204F","bsim":"\u223D","bsime":"\u22CD","bsolb":"\u29C5","bsol":"\\","bsolhsub":"\u27C8","bull":"\u2022","bullet":"\u2022","bump":"\u224E","bumpE":"\u2AAE","bumpe":"\u224F","Bumpeq":"\u224E","bumpeq":"\u224F","Cacute":"\u0106","cacute":"\u0107","capand":"\u2A44","capbrcup":"\u2A49","capcap":"\u2A4B","cap":"\u2229","Cap":"\u22D2","capcup":"\u2A47","capdot":"\u2A40","CapitalDifferentialD":"\u2145","caps":"\u2229\uFE00","caret":"\u2041","caron":"\u02C7","Cayleys":"\u212D","ccaps":"\u2A4D","Ccaron":"\u010C","ccaron":"\u010D","Ccedil":"\u00C7","ccedil":"\u00E7","Ccirc":"\u0108","ccirc":"\u0109","Cconint":"\u2230","ccups":"\u2A4C","ccupssm":"\u2A50","Cdot":"\u010A","cdot":"\u010B","cedil":"\u00B8","Cedilla":"\u00B8","cemptyv":"\u29B2","cent":"\u00A2","centerdot":"\u00B7","CenterDot":"\u00B7","cfr":"\uD835\uDD20","Cfr":"\u212D","CHcy":"\u0427","chcy":"\u0447","check":"\u2713","checkmark":"\u2713","Chi":"\u03A7","chi":"\u03C7","circ":"\u02C6","circeq":"\u2257","circlearrowleft":"\u21BA","circlearrowright":"\u21BB","circledast":"\u229B","circledcirc":"\u229A","circleddash":"\u229D","CircleDot":"\u2299","circledR":"\u00AE","circledS":"\u24C8","CircleMinus":"\u2296","CirclePlus":"\u2295","CircleTimes":"\u2297","cir":"\u25CB","cirE":"\u29C3","cire":"\u2257","cirfnint":"\u2A10","cirmid":"\u2AEF","cirscir":"\u29C2","ClockwiseContourIntegral":"\u2232","CloseCurlyDoubleQuote":"\u201D","CloseCurlyQuote":"\u2019","clubs":"\u2663","clubsuit":"\u2663","colon":":","Colon":"\u2237","Colone":"\u2A74","colone":"\u2254","coloneq":"\u2254","comma":",","commat":"@","comp":"\u2201","compfn":"\u2218","complement":"\u2201","complexes":"\u2102","cong":"\u2245","congdot":"\u2A6D","Congruent":"\u2261","conint":"\u222E","Conint":"\u222F","ContourIntegral":"\u222E","copf":"\uD835\uDD54","Copf":"\u2102","coprod":"\u2210","Coproduct":"\u2210","copy":"\u00A9","COPY":"\u00A9","copysr":"\u2117","CounterClockwiseContourIntegral":"\u2233","crarr":"\u21B5","cross":"\u2717","Cross":"\u2A2F","Cscr":"\uD835\uDC9E","cscr":"\uD835\uDCB8","csub":"\u2ACF","csube":"\u2AD1","csup":"\u2AD0","csupe":"\u2AD2","ctdot":"\u22EF","cudarrl":"\u2938","cudarrr":"\u2935","cuepr":"\u22DE","cuesc":"\u22DF","cularr":"\u21B6","cularrp":"\u293D","cupbrcap":"\u2A48","cupcap":"\u2A46","CupCap":"\u224D","cup":"\u222A","Cup":"\u22D3","cupcup":"\u2A4A","cupdot":"\u228D","cupor":"\u2A45","cups":"\u222A\uFE00","curarr":"\u21B7","curarrm":"\u293C","curlyeqprec":"\u22DE","curlyeqsucc":"\u22DF","curlyvee":"\u22CE","curlywedge":"\u22CF","curren":"\u00A4","curvearrowleft":"\u21B6","curvearrowright":"\u21B7","cuvee":"\u22CE","cuwed":"\u22CF","cwconint":"\u2232","cwint":"\u2231","cylcty":"\u232D","dagger":"\u2020","Dagger":"\u2021","daleth":"\u2138","darr":"\u2193","Darr":"\u21A1","dArr":"\u21D3","dash":"\u2010","Dashv":"\u2AE4","dashv":"\u22A3","dbkarow":"\u290F","dblac":"\u02DD","Dcaron":"\u010E","dcaron":"\u010F","Dcy":"\u0414","dcy":"\u0434","ddagger":"\u2021","ddarr":"\u21CA","DD":"\u2145","dd":"\u2146","DDotrahd":"\u2911","ddotseq":"\u2A77","deg":"\u00B0","Del":"\u2207","Delta":"\u0394","delta":"\u03B4","demptyv":"\u29B1","dfisht":"\u297F","Dfr":"\uD835\uDD07","dfr":"\uD835\uDD21","dHar":"\u2965","dharl":"\u21C3","dharr":"\u21C2","DiacriticalAcute":"\u00B4","DiacriticalDot":"\u02D9","DiacriticalDoubleAcute":"\u02DD","DiacriticalGrave":"`","DiacriticalTilde":"\u02DC","diam":"\u22C4","diamond":"\u22C4","Diamond":"\u22C4","diamondsuit":"\u2666","diams":"\u2666","die":"\u00A8","DifferentialD":"\u2146","digamma":"\u03DD","disin":"\u22F2","div":"\u00F7","divide":"\u00F7","divideontimes":"\u22C7","divonx":"\u22C7","DJcy":"\u0402","djcy":"\u0452","dlcorn":"\u231E","dlcrop":"\u230D","dollar":"$","Dopf":"\uD835\uDD3B","dopf":"\uD835\uDD55","Dot":"\u00A8","dot":"\u02D9","DotDot":"\u20DC","doteq":"\u2250","doteqdot":"\u2251","DotEqual":"\u2250","dotminus":"\u2238","dotplus":"\u2214","dotsquare":"\u22A1","doublebarwedge":"\u2306","DoubleContourIntegral":"\u222F","DoubleDot":"\u00A8","DoubleDownArrow":"\u21D3","DoubleLeftArrow":"\u21D0","DoubleLeftRightArrow":"\u21D4","DoubleLeftTee":"\u2AE4","DoubleLongLeftArrow":"\u27F8","DoubleLongLeftRightArrow":"\u27FA","DoubleLongRightArrow":"\u27F9","DoubleRightArrow":"\u21D2","DoubleRightTee":"\u22A8","DoubleUpArrow":"\u21D1","DoubleUpDownArrow":"\u21D5","DoubleVerticalBar":"\u2225","DownArrowBar":"\u2913","downarrow":"\u2193","DownArrow":"\u2193","Downarrow":"\u21D3","DownArrowUpArrow":"\u21F5","DownBreve":"\u0311","downdownarrows":"\u21CA","downharpoonleft":"\u21C3","downharpoonright":"\u21C2","DownLeftRightVector":"\u2950","DownLeftTeeVector":"\u295E","DownLeftVectorBar":"\u2956","DownLeftVector":"\u21BD","DownRightTeeVector":"\u295F","DownRightVectorBar":"\u2957","DownRightVector":"\u21C1","DownTeeArrow":"\u21A7","DownTee":"\u22A4","drbkarow":"\u2910","drcorn":"\u231F","drcrop":"\u230C","Dscr":"\uD835\uDC9F","dscr":"\uD835\uDCB9","DScy":"\u0405","dscy":"\u0455","dsol":"\u29F6","Dstrok":"\u0110","dstrok":"\u0111","dtdot":"\u22F1","dtri":"\u25BF","dtrif":"\u25BE","duarr":"\u21F5","duhar":"\u296F","dwangle":"\u29A6","DZcy":"\u040F","dzcy":"\u045F","dzigrarr":"\u27FF","Eacute":"\u00C9","eacute":"\u00E9","easter":"\u2A6E","Ecaron":"\u011A","ecaron":"\u011B","Ecirc":"\u00CA","ecirc":"\u00EA","ecir":"\u2256","ecolon":"\u2255","Ecy":"\u042D","ecy":"\u044D","eDDot":"\u2A77","Edot":"\u0116","edot":"\u0117","eDot":"\u2251","ee":"\u2147","efDot":"\u2252","Efr":"\uD835\uDD08","efr":"\uD835\uDD22","eg":"\u2A9A","Egrave":"\u00C8","egrave":"\u00E8","egs":"\u2A96","egsdot":"\u2A98","el":"\u2A99","Element":"\u2208","elinters":"\u23E7","ell":"\u2113","els":"\u2A95","elsdot":"\u2A97","Emacr":"\u0112","emacr":"\u0113","empty":"\u2205","emptyset":"\u2205","EmptySmallSquare":"\u25FB","emptyv":"\u2205","EmptyVerySmallSquare":"\u25AB","emsp13":"\u2004","emsp14":"\u2005","emsp":"\u2003","ENG":"\u014A","eng":"\u014B","ensp":"\u2002","Eogon":"\u0118","eogon":"\u0119","Eopf":"\uD835\uDD3C","eopf":"\uD835\uDD56","epar":"\u22D5","eparsl":"\u29E3","eplus":"\u2A71","epsi":"\u03B5","Epsilon":"\u0395","epsilon":"\u03B5","epsiv":"\u03F5","eqcirc":"\u2256","eqcolon":"\u2255","eqsim":"\u2242","eqslantgtr":"\u2A96","eqslantless":"\u2A95","Equal":"\u2A75","equals":"=","EqualTilde":"\u2242","equest":"\u225F","Equilibrium":"\u21CC","equiv":"\u2261","equivDD":"\u2A78","eqvparsl":"\u29E5","erarr":"\u2971","erDot":"\u2253","escr":"\u212F","Escr":"\u2130","esdot":"\u2250","Esim":"\u2A73","esim":"\u2242","Eta":"\u0397","eta":"\u03B7","ETH":"\u00D0","eth":"\u00F0","Euml":"\u00CB","euml":"\u00EB","euro":"\u20AC","excl":"!","exist":"\u2203","Exists":"\u2203","expectation":"\u2130","exponentiale":"\u2147","ExponentialE":"\u2147","fallingdotseq":"\u2252","Fcy":"\u0424","fcy":"\u0444","female":"\u2640","ffilig":"\uFB03","fflig":"\uFB00","ffllig":"\uFB04","Ffr":"\uD835\uDD09","ffr":"\uD835\uDD23","filig":"\uFB01","FilledSmallSquare":"\u25FC","FilledVerySmallSquare":"\u25AA","fjlig":"fj","flat":"\u266D","fllig":"\uFB02","fltns":"\u25B1","fnof":"\u0192","Fopf":"\uD835\uDD3D","fopf":"\uD835\uDD57","forall":"\u2200","ForAll":"\u2200","fork":"\u22D4","forkv":"\u2AD9","Fouriertrf":"\u2131","fpartint":"\u2A0D","frac12":"\u00BD","frac13":"\u2153","frac14":"\u00BC","frac15":"\u2155","frac16":"\u2159","frac18":"\u215B","frac23":"\u2154","frac25":"\u2156","frac34":"\u00BE","frac35":"\u2157","frac38":"\u215C","frac45":"\u2158","frac56":"\u215A","frac58":"\u215D","frac78":"\u215E","frasl":"\u2044","frown":"\u2322","fscr":"\uD835\uDCBB","Fscr":"\u2131","gacute":"\u01F5","Gamma":"\u0393","gamma":"\u03B3","Gammad":"\u03DC","gammad":"\u03DD","gap":"\u2A86","Gbreve":"\u011E","gbreve":"\u011F","Gcedil":"\u0122","Gcirc":"\u011C","gcirc":"\u011D","Gcy":"\u0413","gcy":"\u0433","Gdot":"\u0120","gdot":"\u0121","ge":"\u2265","gE":"\u2267","gEl":"\u2A8C","gel":"\u22DB","geq":"\u2265","geqq":"\u2267","geqslant":"\u2A7E","gescc":"\u2AA9","ges":"\u2A7E","gesdot":"\u2A80","gesdoto":"\u2A82","gesdotol":"\u2A84","gesl":"\u22DB\uFE00","gesles":"\u2A94","Gfr":"\uD835\uDD0A","gfr":"\uD835\uDD24","gg":"\u226B","Gg":"\u22D9","ggg":"\u22D9","gimel":"\u2137","GJcy":"\u0403","gjcy":"\u0453","gla":"\u2AA5","gl":"\u2277","glE":"\u2A92","glj":"\u2AA4","gnap":"\u2A8A","gnapprox":"\u2A8A","gne":"\u2A88","gnE":"\u2269","gneq":"\u2A88","gneqq":"\u2269","gnsim":"\u22E7","Gopf":"\uD835\uDD3E","gopf":"\uD835\uDD58","grave":"`","GreaterEqual":"\u2265","GreaterEqualLess":"\u22DB","GreaterFullEqual":"\u2267","GreaterGreater":"\u2AA2","GreaterLess":"\u2277","GreaterSlantEqual":"\u2A7E","GreaterTilde":"\u2273","Gscr":"\uD835\uDCA2","gscr":"\u210A","gsim":"\u2273","gsime":"\u2A8E","gsiml":"\u2A90","gtcc":"\u2AA7","gtcir":"\u2A7A","gt":">","GT":">","Gt":"\u226B","gtdot":"\u22D7","gtlPar":"\u2995","gtquest":"\u2A7C","gtrapprox":"\u2A86","gtrarr":"\u2978","gtrdot":"\u22D7","gtreqless":"\u22DB","gtreqqless":"\u2A8C","gtrless":"\u2277","gtrsim":"\u2273","gvertneqq":"\u2269\uFE00","gvnE":"\u2269\uFE00","Hacek":"\u02C7","hairsp":"\u200A","half":"\u00BD","hamilt":"\u210B","HARDcy":"\u042A","hardcy":"\u044A","harrcir":"\u2948","harr":"\u2194","hArr":"\u21D4","harrw":"\u21AD","Hat":"^","hbar":"\u210F","Hcirc":"\u0124","hcirc":"\u0125","hearts":"\u2665","heartsuit":"\u2665","hellip":"\u2026","hercon":"\u22B9","hfr":"\uD835\uDD25","Hfr":"\u210C","HilbertSpace":"\u210B","hksearow":"\u2925","hkswarow":"\u2926","hoarr":"\u21FF","homtht":"\u223B","hookleftarrow":"\u21A9","hookrightarrow":"\u21AA","hopf":"\uD835\uDD59","Hopf":"\u210D","horbar":"\u2015","HorizontalLine":"\u2500","hscr":"\uD835\uDCBD","Hscr":"\u210B","hslash":"\u210F","Hstrok":"\u0126","hstrok":"\u0127","HumpDownHump":"\u224E","HumpEqual":"\u224F","hybull":"\u2043","hyphen":"\u2010","Iacute":"\u00CD","iacute":"\u00ED","ic":"\u2063","Icirc":"\u00CE","icirc":"\u00EE","Icy":"\u0418","icy":"\u0438","Idot":"\u0130","IEcy":"\u0415","iecy":"\u0435","iexcl":"\u00A1","iff":"\u21D4","ifr":"\uD835\uDD26","Ifr":"\u2111","Igrave":"\u00CC","igrave":"\u00EC","ii":"\u2148","iiiint":"\u2A0C","iiint":"\u222D","iinfin":"\u29DC","iiota":"\u2129","IJlig":"\u0132","ijlig":"\u0133","Imacr":"\u012A","imacr":"\u012B","image":"\u2111","ImaginaryI":"\u2148","imagline":"\u2110","imagpart":"\u2111","imath":"\u0131","Im":"\u2111","imof":"\u22B7","imped":"\u01B5","Implies":"\u21D2","incare":"\u2105","in":"\u2208","infin":"\u221E","infintie":"\u29DD","inodot":"\u0131","intcal":"\u22BA","int":"\u222B","Int":"\u222C","integers":"\u2124","Integral":"\u222B","intercal":"\u22BA","Intersection":"\u22C2","intlarhk":"\u2A17","intprod":"\u2A3C","InvisibleComma":"\u2063","InvisibleTimes":"\u2062","IOcy":"\u0401","iocy":"\u0451","Iogon":"\u012E","iogon":"\u012F","Iopf":"\uD835\uDD40","iopf":"\uD835\uDD5A","Iota":"\u0399","iota":"\u03B9","iprod":"\u2A3C","iquest":"\u00BF","iscr":"\uD835\uDCBE","Iscr":"\u2110","isin":"\u2208","isindot":"\u22F5","isinE":"\u22F9","isins":"\u22F4","isinsv":"\u22F3","isinv":"\u2208","it":"\u2062","Itilde":"\u0128","itilde":"\u0129","Iukcy":"\u0406","iukcy":"\u0456","Iuml":"\u00CF","iuml":"\u00EF","Jcirc":"\u0134","jcirc":"\u0135","Jcy":"\u0419","jcy":"\u0439","Jfr":"\uD835\uDD0D","jfr":"\uD835\uDD27","jmath":"\u0237","Jopf":"\uD835\uDD41","jopf":"\uD835\uDD5B","Jscr":"\uD835\uDCA5","jscr":"\uD835\uDCBF","Jsercy":"\u0408","jsercy":"\u0458","Jukcy":"\u0404","jukcy":"\u0454","Kappa":"\u039A","kappa":"\u03BA","kappav":"\u03F0","Kcedil":"\u0136","kcedil":"\u0137","Kcy":"\u041A","kcy":"\u043A","Kfr":"\uD835\uDD0E","kfr":"\uD835\uDD28","kgreen":"\u0138","KHcy":"\u0425","khcy":"\u0445","KJcy":"\u040C","kjcy":"\u045C","Kopf":"\uD835\uDD42","kopf":"\uD835\uDD5C","Kscr":"\uD835\uDCA6","kscr":"\uD835\uDCC0","lAarr":"\u21DA","Lacute":"\u0139","lacute":"\u013A","laemptyv":"\u29B4","lagran":"\u2112","Lambda":"\u039B","lambda":"\u03BB","lang":"\u27E8","Lang":"\u27EA","langd":"\u2991","langle":"\u27E8","lap":"\u2A85","Laplacetrf":"\u2112","laquo":"\u00AB","larrb":"\u21E4","larrbfs":"\u291F","larr":"\u2190","Larr":"\u219E","lArr":"\u21D0","larrfs":"\u291D","larrhk":"\u21A9","larrlp":"\u21AB","larrpl":"\u2939","larrsim":"\u2973","larrtl":"\u21A2","latail":"\u2919","lAtail":"\u291B","lat":"\u2AAB","late":"\u2AAD","lates":"\u2AAD\uFE00","lbarr":"\u290C","lBarr":"\u290E","lbbrk":"\u2772","lbrace":"{","lbrack":"[","lbrke":"\u298B","lbrksld":"\u298F","lbrkslu":"\u298D","Lcaron":"\u013D","lcaron":"\u013E","Lcedil":"\u013B","lcedil":"\u013C","lceil":"\u2308","lcub":"{","Lcy":"\u041B","lcy":"\u043B","ldca":"\u2936","ldquo":"\u201C","ldquor":"\u201E","ldrdhar":"\u2967","ldrushar":"\u294B","ldsh":"\u21B2","le":"\u2264","lE":"\u2266","LeftAngleBracket":"\u27E8","LeftArrowBar":"\u21E4","leftarrow":"\u2190","LeftArrow":"\u2190","Leftarrow":"\u21D0","LeftArrowRightArrow":"\u21C6","leftarrowtail":"\u21A2","LeftCeiling":"\u2308","LeftDoubleBracket":"\u27E6","LeftDownTeeVector":"\u2961","LeftDownVectorBar":"\u2959","LeftDownVector":"\u21C3","LeftFloor":"\u230A","leftharpoondown":"\u21BD","leftharpoonup":"\u21BC","leftleftarrows":"\u21C7","leftrightarrow":"\u2194","LeftRightArrow":"\u2194","Leftrightarrow":"\u21D4","leftrightarrows":"\u21C6","leftrightharpoons":"\u21CB","leftrightsquigarrow":"\u21AD","LeftRightVector":"\u294E","LeftTeeArrow":"\u21A4","LeftTee":"\u22A3","LeftTeeVector":"\u295A","leftthreetimes":"\u22CB","LeftTriangleBar":"\u29CF","LeftTriangle":"\u22B2","LeftTriangleEqual":"\u22B4","LeftUpDownVector":"\u2951","LeftUpTeeVector":"\u2960","LeftUpVectorBar":"\u2958","LeftUpVector":"\u21BF","LeftVectorBar":"\u2952","LeftVector":"\u21BC","lEg":"\u2A8B","leg":"\u22DA","leq":"\u2264","leqq":"\u2266","leqslant":"\u2A7D","lescc":"\u2AA8","les":"\u2A7D","lesdot":"\u2A7F","lesdoto":"\u2A81","lesdotor":"\u2A83","lesg":"\u22DA\uFE00","lesges":"\u2A93","lessapprox":"\u2A85","lessdot":"\u22D6","lesseqgtr":"\u22DA","lesseqqgtr":"\u2A8B","LessEqualGreater":"\u22DA","LessFullEqual":"\u2266","LessGreater":"\u2276","lessgtr":"\u2276","LessLess":"\u2AA1","lesssim":"\u2272","LessSlantEqual":"\u2A7D","LessTilde":"\u2272","lfisht":"\u297C","lfloor":"\u230A","Lfr":"\uD835\uDD0F","lfr":"\uD835\uDD29","lg":"\u2276","lgE":"\u2A91","lHar":"\u2962","lhard":"\u21BD","lharu":"\u21BC","lharul":"\u296A","lhblk":"\u2584","LJcy":"\u0409","ljcy":"\u0459","llarr":"\u21C7","ll":"\u226A","Ll":"\u22D8","llcorner":"\u231E","Lleftarrow":"\u21DA","llhard":"\u296B","lltri":"\u25FA","Lmidot":"\u013F","lmidot":"\u0140","lmoustache":"\u23B0","lmoust":"\u23B0","lnap":"\u2A89","lnapprox":"\u2A89","lne":"\u2A87","lnE":"\u2268","lneq":"\u2A87","lneqq":"\u2268","lnsim":"\u22E6","loang":"\u27EC","loarr":"\u21FD","lobrk":"\u27E6","longleftarrow":"\u27F5","LongLeftArrow":"\u27F5","Longleftarrow":"\u27F8","longleftrightarrow":"\u27F7","LongLeftRightArrow":"\u27F7","Longleftrightarrow":"\u27FA","longmapsto":"\u27FC","longrightarrow":"\u27F6","LongRightArrow":"\u27F6","Longrightarrow":"\u27F9","looparrowleft":"\u21AB","looparrowright":"\u21AC","lopar":"\u2985","Lopf":"\uD835\uDD43","lopf":"\uD835\uDD5D","loplus":"\u2A2D","lotimes":"\u2A34","lowast":"\u2217","lowbar":"_","LowerLeftArrow":"\u2199","LowerRightArrow":"\u2198","loz":"\u25CA","lozenge":"\u25CA","lozf":"\u29EB","lpar":"(","lparlt":"\u2993","lrarr":"\u21C6","lrcorner":"\u231F","lrhar":"\u21CB","lrhard":"\u296D","lrm":"\u200E","lrtri":"\u22BF","lsaquo":"\u2039","lscr":"\uD835\uDCC1","Lscr":"\u2112","lsh":"\u21B0","Lsh":"\u21B0","lsim":"\u2272","lsime":"\u2A8D","lsimg":"\u2A8F","lsqb":"[","lsquo":"\u2018","lsquor":"\u201A","Lstrok":"\u0141","lstrok":"\u0142","ltcc":"\u2AA6","ltcir":"\u2A79","lt":"<","LT":"<","Lt":"\u226A","ltdot":"\u22D6","lthree":"\u22CB","ltimes":"\u22C9","ltlarr":"\u2976","ltquest":"\u2A7B","ltri":"\u25C3","ltrie":"\u22B4","ltrif":"\u25C2","ltrPar":"\u2996","lurdshar":"\u294A","luruhar":"\u2966","lvertneqq":"\u2268\uFE00","lvnE":"\u2268\uFE00","macr":"\u00AF","male":"\u2642","malt":"\u2720","maltese":"\u2720","Map":"\u2905","map":"\u21A6","mapsto":"\u21A6","mapstodown":"\u21A7","mapstoleft":"\u21A4","mapstoup":"\u21A5","marker":"\u25AE","mcomma":"\u2A29","Mcy":"\u041C","mcy":"\u043C","mdash":"\u2014","mDDot":"\u223A","measuredangle":"\u2221","MediumSpace":"\u205F","Mellintrf":"\u2133","Mfr":"\uD835\uDD10","mfr":"\uD835\uDD2A","mho":"\u2127","micro":"\u00B5","midast":"*","midcir":"\u2AF0","mid":"\u2223","middot":"\u00B7","minusb":"\u229F","minus":"\u2212","minusd":"\u2238","minusdu":"\u2A2A","MinusPlus":"\u2213","mlcp":"\u2ADB","mldr":"\u2026","mnplus":"\u2213","models":"\u22A7","Mopf":"\uD835\uDD44","mopf":"\uD835\uDD5E","mp":"\u2213","mscr":"\uD835\uDCC2","Mscr":"\u2133","mstpos":"\u223E","Mu":"\u039C","mu":"\u03BC","multimap":"\u22B8","mumap":"\u22B8","nabla":"\u2207","Nacute":"\u0143","nacute":"\u0144","nang":"\u2220\u20D2","nap":"\u2249","napE":"\u2A70\u0338","napid":"\u224B\u0338","napos":"\u0149","napprox":"\u2249","natural":"\u266E","naturals":"\u2115","natur":"\u266E","nbsp":"\u00A0","nbump":"\u224E\u0338","nbumpe":"\u224F\u0338","ncap":"\u2A43","Ncaron":"\u0147","ncaron":"\u0148","Ncedil":"\u0145","ncedil":"\u0146","ncong":"\u2247","ncongdot":"\u2A6D\u0338","ncup":"\u2A42","Ncy":"\u041D","ncy":"\u043D","ndash":"\u2013","nearhk":"\u2924","nearr":"\u2197","neArr":"\u21D7","nearrow":"\u2197","ne":"\u2260","nedot":"\u2250\u0338","NegativeMediumSpace":"\u200B","NegativeThickSpace":"\u200B","NegativeThinSpace":"\u200B","NegativeVeryThinSpace":"\u200B","nequiv":"\u2262","nesear":"\u2928","nesim":"\u2242\u0338","NestedGreaterGreater":"\u226B","NestedLessLess":"\u226A","NewLine":"\n","nexist":"\u2204","nexists":"\u2204","Nfr":"\uD835\uDD11","nfr":"\uD835\uDD2B","ngE":"\u2267\u0338","nge":"\u2271","ngeq":"\u2271","ngeqq":"\u2267\u0338","ngeqslant":"\u2A7E\u0338","nges":"\u2A7E\u0338","nGg":"\u22D9\u0338","ngsim":"\u2275","nGt":"\u226B\u20D2","ngt":"\u226F","ngtr":"\u226F","nGtv":"\u226B\u0338","nharr":"\u21AE","nhArr":"\u21CE","nhpar":"\u2AF2","ni":"\u220B","nis":"\u22FC","nisd":"\u22FA","niv":"\u220B","NJcy":"\u040A","njcy":"\u045A","nlarr":"\u219A","nlArr":"\u21CD","nldr":"\u2025","nlE":"\u2266\u0338","nle":"\u2270","nleftarrow":"\u219A","nLeftarrow":"\u21CD","nleftrightarrow":"\u21AE","nLeftrightarrow":"\u21CE","nleq":"\u2270","nleqq":"\u2266\u0338","nleqslant":"\u2A7D\u0338","nles":"\u2A7D\u0338","nless":"\u226E","nLl":"\u22D8\u0338","nlsim":"\u2274","nLt":"\u226A\u20D2","nlt":"\u226E","nltri":"\u22EA","nltrie":"\u22EC","nLtv":"\u226A\u0338","nmid":"\u2224","NoBreak":"\u2060","NonBreakingSpace":"\u00A0","nopf":"\uD835\uDD5F","Nopf":"\u2115","Not":"\u2AEC","not":"\u00AC","NotCongruent":"\u2262","NotCupCap":"\u226D","NotDoubleVerticalBar":"\u2226","NotElement":"\u2209","NotEqual":"\u2260","NotEqualTilde":"\u2242\u0338","NotExists":"\u2204","NotGreater":"\u226F","NotGreaterEqual":"\u2271","NotGreaterFullEqual":"\u2267\u0338","NotGreaterGreater":"\u226B\u0338","NotGreaterLess":"\u2279","NotGreaterSlantEqual":"\u2A7E\u0338","NotGreaterTilde":"\u2275","NotHumpDownHump":"\u224E\u0338","NotHumpEqual":"\u224F\u0338","notin":"\u2209","notindot":"\u22F5\u0338","notinE":"\u22F9\u0338","notinva":"\u2209","notinvb":"\u22F7","notinvc":"\u22F6","NotLeftTriangleBar":"\u29CF\u0338","NotLeftTriangle":"\u22EA","NotLeftTriangleEqual":"\u22EC","NotLess":"\u226E","NotLessEqual":"\u2270","NotLessGreater":"\u2278","NotLessLess":"\u226A\u0338","NotLessSlantEqual":"\u2A7D\u0338","NotLessTilde":"\u2274","NotNestedGreaterGreater":"\u2AA2\u0338","NotNestedLessLess":"\u2AA1\u0338","notni":"\u220C","notniva":"\u220C","notnivb":"\u22FE","notnivc":"\u22FD","NotPrecedes":"\u2280","NotPrecedesEqual":"\u2AAF\u0338","NotPrecedesSlantEqual":"\u22E0","NotReverseElement":"\u220C","NotRightTriangleBar":"\u29D0\u0338","NotRightTriangle":"\u22EB","NotRightTriangleEqual":"\u22ED","NotSquareSubset":"\u228F\u0338","NotSquareSubsetEqual":"\u22E2","NotSquareSuperset":"\u2290\u0338","NotSquareSupersetEqual":"\u22E3","NotSubset":"\u2282\u20D2","NotSubsetEqual":"\u2288","NotSucceeds":"\u2281","NotSucceedsEqual":"\u2AB0\u0338","NotSucceedsSlantEqual":"\u22E1","NotSucceedsTilde":"\u227F\u0338","NotSuperset":"\u2283\u20D2","NotSupersetEqual":"\u2289","NotTilde":"\u2241","NotTildeEqual":"\u2244","NotTildeFullEqual":"\u2247","NotTildeTilde":"\u2249","NotVerticalBar":"\u2224","nparallel":"\u2226","npar":"\u2226","nparsl":"\u2AFD\u20E5","npart":"\u2202\u0338","npolint":"\u2A14","npr":"\u2280","nprcue":"\u22E0","nprec":"\u2280","npreceq":"\u2AAF\u0338","npre":"\u2AAF\u0338","nrarrc":"\u2933\u0338","nrarr":"\u219B","nrArr":"\u21CF","nrarrw":"\u219D\u0338","nrightarrow":"\u219B","nRightarrow":"\u21CF","nrtri":"\u22EB","nrtrie":"\u22ED","nsc":"\u2281","nsccue":"\u22E1","nsce":"\u2AB0\u0338","Nscr":"\uD835\uDCA9","nscr":"\uD835\uDCC3","nshortmid":"\u2224","nshortparallel":"\u2226","nsim":"\u2241","nsime":"\u2244","nsimeq":"\u2244","nsmid":"\u2224","nspar":"\u2226","nsqsube":"\u22E2","nsqsupe":"\u22E3","nsub":"\u2284","nsubE":"\u2AC5\u0338","nsube":"\u2288","nsubset":"\u2282\u20D2","nsubseteq":"\u2288","nsubseteqq":"\u2AC5\u0338","nsucc":"\u2281","nsucceq":"\u2AB0\u0338","nsup":"\u2285","nsupE":"\u2AC6\u0338","nsupe":"\u2289","nsupset":"\u2283\u20D2","nsupseteq":"\u2289","nsupseteqq":"\u2AC6\u0338","ntgl":"\u2279","Ntilde":"\u00D1","ntilde":"\u00F1","ntlg":"\u2278","ntriangleleft":"\u22EA","ntrianglelefteq":"\u22EC","ntriangleright":"\u22EB","ntrianglerighteq":"\u22ED","Nu":"\u039D","nu":"\u03BD","num":"#","numero":"\u2116","numsp":"\u2007","nvap":"\u224D\u20D2","nvdash":"\u22AC","nvDash":"\u22AD","nVdash":"\u22AE","nVDash":"\u22AF","nvge":"\u2265\u20D2","nvgt":">\u20D2","nvHarr":"\u2904","nvinfin":"\u29DE","nvlArr":"\u2902","nvle":"\u2264\u20D2","nvlt":"<\u20D2","nvltrie":"\u22B4\u20D2","nvrArr":"\u2903","nvrtrie":"\u22B5\u20D2","nvsim":"\u223C\u20D2","nwarhk":"\u2923","nwarr":"\u2196","nwArr":"\u21D6","nwarrow":"\u2196","nwnear":"\u2927","Oacute":"\u00D3","oacute":"\u00F3","oast":"\u229B","Ocirc":"\u00D4","ocirc":"\u00F4","ocir":"\u229A","Ocy":"\u041E","ocy":"\u043E","odash":"\u229D","Odblac":"\u0150","odblac":"\u0151","odiv":"\u2A38","odot":"\u2299","odsold":"\u29BC","OElig":"\u0152","oelig":"\u0153","ofcir":"\u29BF","Ofr":"\uD835\uDD12","ofr":"\uD835\uDD2C","ogon":"\u02DB","Ograve":"\u00D2","ograve":"\u00F2","ogt":"\u29C1","ohbar":"\u29B5","ohm":"\u03A9","oint":"\u222E","olarr":"\u21BA","olcir":"\u29BE","olcross":"\u29BB","oline":"\u203E","olt":"\u29C0","Omacr":"\u014C","omacr":"\u014D","Omega":"\u03A9","omega":"\u03C9","Omicron":"\u039F","omicron":"\u03BF","omid":"\u29B6","ominus":"\u2296","Oopf":"\uD835\uDD46","oopf":"\uD835\uDD60","opar":"\u29B7","OpenCurlyDoubleQuote":"\u201C","OpenCurlyQuote":"\u2018","operp":"\u29B9","oplus":"\u2295","orarr":"\u21BB","Or":"\u2A54","or":"\u2228","ord":"\u2A5D","order":"\u2134","orderof":"\u2134","ordf":"\u00AA","ordm":"\u00BA","origof":"\u22B6","oror":"\u2A56","orslope":"\u2A57","orv":"\u2A5B","oS":"\u24C8","Oscr":"\uD835\uDCAA","oscr":"\u2134","Oslash":"\u00D8","oslash":"\u00F8","osol":"\u2298","Otilde":"\u00D5","otilde":"\u00F5","otimesas":"\u2A36","Otimes":"\u2A37","otimes":"\u2297","Ouml":"\u00D6","ouml":"\u00F6","ovbar":"\u233D","OverBar":"\u203E","OverBrace":"\u23DE","OverBracket":"\u23B4","OverParenthesis":"\u23DC","para":"\u00B6","parallel":"\u2225","par":"\u2225","parsim":"\u2AF3","parsl":"\u2AFD","part":"\u2202","PartialD":"\u2202","Pcy":"\u041F","pcy":"\u043F","percnt":"%","period":".","permil":"\u2030","perp":"\u22A5","pertenk":"\u2031","Pfr":"\uD835\uDD13","pfr":"\uD835\uDD2D","Phi":"\u03A6","phi":"\u03C6","phiv":"\u03D5","phmmat":"\u2133","phone":"\u260E","Pi":"\u03A0","pi":"\u03C0","pitchfork":"\u22D4","piv":"\u03D6","planck":"\u210F","planckh":"\u210E","plankv":"\u210F","plusacir":"\u2A23","plusb":"\u229E","pluscir":"\u2A22","plus":"+","plusdo":"\u2214","plusdu":"\u2A25","pluse":"\u2A72","PlusMinus":"\u00B1","plusmn":"\u00B1","plussim":"\u2A26","plustwo":"\u2A27","pm":"\u00B1","Poincareplane":"\u210C","pointint":"\u2A15","popf":"\uD835\uDD61","Popf":"\u2119","pound":"\u00A3","prap":"\u2AB7","Pr":"\u2ABB","pr":"\u227A","prcue":"\u227C","precapprox":"\u2AB7","prec":"\u227A","preccurlyeq":"\u227C","Precedes":"\u227A","PrecedesEqual":"\u2AAF","PrecedesSlantEqual":"\u227C","PrecedesTilde":"\u227E","preceq":"\u2AAF","precnapprox":"\u2AB9","precneqq":"\u2AB5","precnsim":"\u22E8","pre":"\u2AAF","prE":"\u2AB3","precsim":"\u227E","prime":"\u2032","Prime":"\u2033","primes":"\u2119","prnap":"\u2AB9","prnE":"\u2AB5","prnsim":"\u22E8","prod":"\u220F","Product":"\u220F","profalar":"\u232E","profline":"\u2312","profsurf":"\u2313","prop":"\u221D","Proportional":"\u221D","Proportion":"\u2237","propto":"\u221D","prsim":"\u227E","prurel":"\u22B0","Pscr":"\uD835\uDCAB","pscr":"\uD835\uDCC5","Psi":"\u03A8","psi":"\u03C8","puncsp":"\u2008","Qfr":"\uD835\uDD14","qfr":"\uD835\uDD2E","qint":"\u2A0C","qopf":"\uD835\uDD62","Qopf":"\u211A","qprime":"\u2057","Qscr":"\uD835\uDCAC","qscr":"\uD835\uDCC6","quaternions":"\u210D","quatint":"\u2A16","quest":"?","questeq":"\u225F","quot":"\"","QUOT":"\"","rAarr":"\u21DB","race":"\u223D\u0331","Racute":"\u0154","racute":"\u0155","radic":"\u221A","raemptyv":"\u29B3","rang":"\u27E9","Rang":"\u27EB","rangd":"\u2992","range":"\u29A5","rangle":"\u27E9","raquo":"\u00BB","rarrap":"\u2975","rarrb":"\u21E5","rarrbfs":"\u2920","rarrc":"\u2933","rarr":"\u2192","Rarr":"\u21A0","rArr":"\u21D2","rarrfs":"\u291E","rarrhk":"\u21AA","rarrlp":"\u21AC","rarrpl":"\u2945","rarrsim":"\u2974","Rarrtl":"\u2916","rarrtl":"\u21A3","rarrw":"\u219D","ratail":"\u291A","rAtail":"\u291C","ratio":"\u2236","rationals":"\u211A","rbarr":"\u290D","rBarr":"\u290F","RBarr":"\u2910","rbbrk":"\u2773","rbrace":"}","rbrack":"]","rbrke":"\u298C","rbrksld":"\u298E","rbrkslu":"\u2990","Rcaron":"\u0158","rcaron":"\u0159","Rcedil":"\u0156","rcedil":"\u0157","rceil":"\u2309","rcub":"}","Rcy":"\u0420","rcy":"\u0440","rdca":"\u2937","rdldhar":"\u2969","rdquo":"\u201D","rdquor":"\u201D","rdsh":"\u21B3","real":"\u211C","realine":"\u211B","realpart":"\u211C","reals":"\u211D","Re":"\u211C","rect":"\u25AD","reg":"\u00AE","REG":"\u00AE","ReverseElement":"\u220B","ReverseEquilibrium":"\u21CB","ReverseUpEquilibrium":"\u296F","rfisht":"\u297D","rfloor":"\u230B","rfr":"\uD835\uDD2F","Rfr":"\u211C","rHar":"\u2964","rhard":"\u21C1","rharu":"\u21C0","rharul":"\u296C","Rho":"\u03A1","rho":"\u03C1","rhov":"\u03F1","RightAngleBracket":"\u27E9","RightArrowBar":"\u21E5","rightarrow":"\u2192","RightArrow":"\u2192","Rightarrow":"\u21D2","RightArrowLeftArrow":"\u21C4","rightarrowtail":"\u21A3","RightCeiling":"\u2309","RightDoubleBracket":"\u27E7","RightDownTeeVector":"\u295D","RightDownVectorBar":"\u2955","RightDownVector":"\u21C2","RightFloor":"\u230B","rightharpoondown":"\u21C1","rightharpoonup":"\u21C0","rightleftarrows":"\u21C4","rightleftharpoons":"\u21CC","rightrightarrows":"\u21C9","rightsquigarrow":"\u219D","RightTeeArrow":"\u21A6","RightTee":"\u22A2","RightTeeVector":"\u295B","rightthreetimes":"\u22CC","RightTriangleBar":"\u29D0","RightTriangle":"\u22B3","RightTriangleEqual":"\u22B5","RightUpDownVector":"\u294F","RightUpTeeVector":"\u295C","RightUpVectorBar":"\u2954","RightUpVector":"\u21BE","RightVectorBar":"\u2953","RightVector":"\u21C0","ring":"\u02DA","risingdotseq":"\u2253","rlarr":"\u21C4","rlhar":"\u21CC","rlm":"\u200F","rmoustache":"\u23B1","rmoust":"\u23B1","rnmid":"\u2AEE","roang":"\u27ED","roarr":"\u21FE","robrk":"\u27E7","ropar":"\u2986","ropf":"\uD835\uDD63","Ropf":"\u211D","roplus":"\u2A2E","rotimes":"\u2A35","RoundImplies":"\u2970","rpar":")","rpargt":"\u2994","rppolint":"\u2A12","rrarr":"\u21C9","Rrightarrow":"\u21DB","rsaquo":"\u203A","rscr":"\uD835\uDCC7","Rscr":"\u211B","rsh":"\u21B1","Rsh":"\u21B1","rsqb":"]","rsquo":"\u2019","rsquor":"\u2019","rthree":"\u22CC","rtimes":"\u22CA","rtri":"\u25B9","rtrie":"\u22B5","rtrif":"\u25B8","rtriltri":"\u29CE","RuleDelayed":"\u29F4","ruluhar":"\u2968","rx":"\u211E","Sacute":"\u015A","sacute":"\u015B","sbquo":"\u201A","scap":"\u2AB8","Scaron":"\u0160","scaron":"\u0161","Sc":"\u2ABC","sc":"\u227B","sccue":"\u227D","sce":"\u2AB0","scE":"\u2AB4","Scedil":"\u015E","scedil":"\u015F","Scirc":"\u015C","scirc":"\u015D","scnap":"\u2ABA","scnE":"\u2AB6","scnsim":"\u22E9","scpolint":"\u2A13","scsim":"\u227F","Scy":"\u0421","scy":"\u0441","sdotb":"\u22A1","sdot":"\u22C5","sdote":"\u2A66","searhk":"\u2925","searr":"\u2198","seArr":"\u21D8","searrow":"\u2198","sect":"\u00A7","semi":";","seswar":"\u2929","setminus":"\u2216","setmn":"\u2216","sext":"\u2736","Sfr":"\uD835\uDD16","sfr":"\uD835\uDD30","sfrown":"\u2322","sharp":"\u266F","SHCHcy":"\u0429","shchcy":"\u0449","SHcy":"\u0428","shcy":"\u0448","ShortDownArrow":"\u2193","ShortLeftArrow":"\u2190","shortmid":"\u2223","shortparallel":"\u2225","ShortRightArrow":"\u2192","ShortUpArrow":"\u2191","shy":"\u00AD","Sigma":"\u03A3","sigma":"\u03C3","sigmaf":"\u03C2","sigmav":"\u03C2","sim":"\u223C","simdot":"\u2A6A","sime":"\u2243","simeq":"\u2243","simg":"\u2A9E","simgE":"\u2AA0","siml":"\u2A9D","simlE":"\u2A9F","simne":"\u2246","simplus":"\u2A24","simrarr":"\u2972","slarr":"\u2190","SmallCircle":"\u2218","smallsetminus":"\u2216","smashp":"\u2A33","smeparsl":"\u29E4","smid":"\u2223","smile":"\u2323","smt":"\u2AAA","smte":"\u2AAC","smtes":"\u2AAC\uFE00","SOFTcy":"\u042C","softcy":"\u044C","solbar":"\u233F","solb":"\u29C4","sol":"/","Sopf":"\uD835\uDD4A","sopf":"\uD835\uDD64","spades":"\u2660","spadesuit":"\u2660","spar":"\u2225","sqcap":"\u2293","sqcaps":"\u2293\uFE00","sqcup":"\u2294","sqcups":"\u2294\uFE00","Sqrt":"\u221A","sqsub":"\u228F","sqsube":"\u2291","sqsubset":"\u228F","sqsubseteq":"\u2291","sqsup":"\u2290","sqsupe":"\u2292","sqsupset":"\u2290","sqsupseteq":"\u2292","square":"\u25A1","Square":"\u25A1","SquareIntersection":"\u2293","SquareSubset":"\u228F","SquareSubsetEqual":"\u2291","SquareSuperset":"\u2290","SquareSupersetEqual":"\u2292","SquareUnion":"\u2294","squarf":"\u25AA","squ":"\u25A1","squf":"\u25AA","srarr":"\u2192","Sscr":"\uD835\uDCAE","sscr":"\uD835\uDCC8","ssetmn":"\u2216","ssmile":"\u2323","sstarf":"\u22C6","Star":"\u22C6","star":"\u2606","starf":"\u2605","straightepsilon":"\u03F5","straightphi":"\u03D5","strns":"\u00AF","sub":"\u2282","Sub":"\u22D0","subdot":"\u2ABD","subE":"\u2AC5","sube":"\u2286","subedot":"\u2AC3","submult":"\u2AC1","subnE":"\u2ACB","subne":"\u228A","subplus":"\u2ABF","subrarr":"\u2979","subset":"\u2282","Subset":"\u22D0","subseteq":"\u2286","subseteqq":"\u2AC5","SubsetEqual":"\u2286","subsetneq":"\u228A","subsetneqq":"\u2ACB","subsim":"\u2AC7","subsub":"\u2AD5","subsup":"\u2AD3","succapprox":"\u2AB8","succ":"\u227B","succcurlyeq":"\u227D","Succeeds":"\u227B","SucceedsEqual":"\u2AB0","SucceedsSlantEqual":"\u227D","SucceedsTilde":"\u227F","succeq":"\u2AB0","succnapprox":"\u2ABA","succneqq":"\u2AB6","succnsim":"\u22E9","succsim":"\u227F","SuchThat":"\u220B","sum":"\u2211","Sum":"\u2211","sung":"\u266A","sup1":"\u00B9","sup2":"\u00B2","sup3":"\u00B3","sup":"\u2283","Sup":"\u22D1","supdot":"\u2ABE","supdsub":"\u2AD8","supE":"\u2AC6","supe":"\u2287","supedot":"\u2AC4","Superset":"\u2283","SupersetEqual":"\u2287","suphsol":"\u27C9","suphsub":"\u2AD7","suplarr":"\u297B","supmult":"\u2AC2","supnE":"\u2ACC","supne":"\u228B","supplus":"\u2AC0","supset":"\u2283","Supset":"\u22D1","supseteq":"\u2287","supseteqq":"\u2AC6","supsetneq":"\u228B","supsetneqq":"\u2ACC","supsim":"\u2AC8","supsub":"\u2AD4","supsup":"\u2AD6","swarhk":"\u2926","swarr":"\u2199","swArr":"\u21D9","swarrow":"\u2199","swnwar":"\u292A","szlig":"\u00DF","Tab":"\t","target":"\u2316","Tau":"\u03A4","tau":"\u03C4","tbrk":"\u23B4","Tcaron":"\u0164","tcaron":"\u0165","Tcedil":"\u0162","tcedil":"\u0163","Tcy":"\u0422","tcy":"\u0442","tdot":"\u20DB","telrec":"\u2315","Tfr":"\uD835\uDD17","tfr":"\uD835\uDD31","there4":"\u2234","therefore":"\u2234","Therefore":"\u2234","Theta":"\u0398","theta":"\u03B8","thetasym":"\u03D1","thetav":"\u03D1","thickapprox":"\u2248","thicksim":"\u223C","ThickSpace":"\u205F\u200A","ThinSpace":"\u2009","thinsp":"\u2009","thkap":"\u2248","thksim":"\u223C","THORN":"\u00DE","thorn":"\u00FE","tilde":"\u02DC","Tilde":"\u223C","TildeEqual":"\u2243","TildeFullEqual":"\u2245","TildeTilde":"\u2248","timesbar":"\u2A31","timesb":"\u22A0","times":"\u00D7","timesd":"\u2A30","tint":"\u222D","toea":"\u2928","topbot":"\u2336","topcir":"\u2AF1","top":"\u22A4","Topf":"\uD835\uDD4B","topf":"\uD835\uDD65","topfork":"\u2ADA","tosa":"\u2929","tprime":"\u2034","trade":"\u2122","TRADE":"\u2122","triangle":"\u25B5","triangledown":"\u25BF","triangleleft":"\u25C3","trianglelefteq":"\u22B4","triangleq":"\u225C","triangleright":"\u25B9","trianglerighteq":"\u22B5","tridot":"\u25EC","trie":"\u225C","triminus":"\u2A3A","TripleDot":"\u20DB","triplus":"\u2A39","trisb":"\u29CD","tritime":"\u2A3B","trpezium":"\u23E2","Tscr":"\uD835\uDCAF","tscr":"\uD835\uDCC9","TScy":"\u0426","tscy":"\u0446","TSHcy":"\u040B","tshcy":"\u045B","Tstrok":"\u0166","tstrok":"\u0167","twixt":"\u226C","twoheadleftarrow":"\u219E","twoheadrightarrow":"\u21A0","Uacute":"\u00DA","uacute":"\u00FA","uarr":"\u2191","Uarr":"\u219F","uArr":"\u21D1","Uarrocir":"\u2949","Ubrcy":"\u040E","ubrcy":"\u045E","Ubreve":"\u016C","ubreve":"\u016D","Ucirc":"\u00DB","ucirc":"\u00FB","Ucy":"\u0423","ucy":"\u0443","udarr":"\u21C5","Udblac":"\u0170","udblac":"\u0171","udhar":"\u296E","ufisht":"\u297E","Ufr":"\uD835\uDD18","ufr":"\uD835\uDD32","Ugrave":"\u00D9","ugrave":"\u00F9","uHar":"\u2963","uharl":"\u21BF","uharr":"\u21BE","uhblk":"\u2580","ulcorn":"\u231C","ulcorner":"\u231C","ulcrop":"\u230F","ultri":"\u25F8","Umacr":"\u016A","umacr":"\u016B","uml":"\u00A8","UnderBar":"_","UnderBrace":"\u23DF","UnderBracket":"\u23B5","UnderParenthesis":"\u23DD","Union":"\u22C3","UnionPlus":"\u228E","Uogon":"\u0172","uogon":"\u0173","Uopf":"\uD835\uDD4C","uopf":"\uD835\uDD66","UpArrowBar":"\u2912","uparrow":"\u2191","UpArrow":"\u2191","Uparrow":"\u21D1","UpArrowDownArrow":"\u21C5","updownarrow":"\u2195","UpDownArrow":"\u2195","Updownarrow":"\u21D5","UpEquilibrium":"\u296E","upharpoonleft":"\u21BF","upharpoonright":"\u21BE","uplus":"\u228E","UpperLeftArrow":"\u2196","UpperRightArrow":"\u2197","upsi":"\u03C5","Upsi":"\u03D2","upsih":"\u03D2","Upsilon":"\u03A5","upsilon":"\u03C5","UpTeeArrow":"\u21A5","UpTee":"\u22A5","upuparrows":"\u21C8","urcorn":"\u231D","urcorner":"\u231D","urcrop":"\u230E","Uring":"\u016E","uring":"\u016F","urtri":"\u25F9","Uscr":"\uD835\uDCB0","uscr":"\uD835\uDCCA","utdot":"\u22F0","Utilde":"\u0168","utilde":"\u0169","utri":"\u25B5","utrif":"\u25B4","uuarr":"\u21C8","Uuml":"\u00DC","uuml":"\u00FC","uwangle":"\u29A7","vangrt":"\u299C","varepsilon":"\u03F5","varkappa":"\u03F0","varnothing":"\u2205","varphi":"\u03D5","varpi":"\u03D6","varpropto":"\u221D","varr":"\u2195","vArr":"\u21D5","varrho":"\u03F1","varsigma":"\u03C2","varsubsetneq":"\u228A\uFE00","varsubsetneqq":"\u2ACB\uFE00","varsupsetneq":"\u228B\uFE00","varsupsetneqq":"\u2ACC\uFE00","vartheta":"\u03D1","vartriangleleft":"\u22B2","vartriangleright":"\u22B3","vBar":"\u2AE8","Vbar":"\u2AEB","vBarv":"\u2AE9","Vcy":"\u0412","vcy":"\u0432","vdash":"\u22A2","vDash":"\u22A8","Vdash":"\u22A9","VDash":"\u22AB","Vdashl":"\u2AE6","veebar":"\u22BB","vee":"\u2228","Vee":"\u22C1","veeeq":"\u225A","vellip":"\u22EE","verbar":"|","Verbar":"\u2016","vert":"|","Vert":"\u2016","VerticalBar":"\u2223","VerticalLine":"|","VerticalSeparator":"\u2758","VerticalTilde":"\u2240","VeryThinSpace":"\u200A","Vfr":"\uD835\uDD19","vfr":"\uD835\uDD33","vltri":"\u22B2","vnsub":"\u2282\u20D2","vnsup":"\u2283\u20D2","Vopf":"\uD835\uDD4D","vopf":"\uD835\uDD67","vprop":"\u221D","vrtri":"\u22B3","Vscr":"\uD835\uDCB1","vscr":"\uD835\uDCCB","vsubnE":"\u2ACB\uFE00","vsubne":"\u228A\uFE00","vsupnE":"\u2ACC\uFE00","vsupne":"\u228B\uFE00","Vvdash":"\u22AA","vzigzag":"\u299A","Wcirc":"\u0174","wcirc":"\u0175","wedbar":"\u2A5F","wedge":"\u2227","Wedge":"\u22C0","wedgeq":"\u2259","weierp":"\u2118","Wfr":"\uD835\uDD1A","wfr":"\uD835\uDD34","Wopf":"\uD835\uDD4E","wopf":"\uD835\uDD68","wp":"\u2118","wr":"\u2240","wreath":"\u2240","Wscr":"\uD835\uDCB2","wscr":"\uD835\uDCCC","xcap":"\u22C2","xcirc":"\u25EF","xcup":"\u22C3","xdtri":"\u25BD","Xfr":"\uD835\uDD1B","xfr":"\uD835\uDD35","xharr":"\u27F7","xhArr":"\u27FA","Xi":"\u039E","xi":"\u03BE","xlarr":"\u27F5","xlArr":"\u27F8","xmap":"\u27FC","xnis":"\u22FB","xodot":"\u2A00","Xopf":"\uD835\uDD4F","xopf":"\uD835\uDD69","xoplus":"\u2A01","xotime":"\u2A02","xrarr":"\u27F6","xrArr":"\u27F9","Xscr":"\uD835\uDCB3","xscr":"\uD835\uDCCD","xsqcup":"\u2A06","xuplus":"\u2A04","xutri":"\u25B3","xvee":"\u22C1","xwedge":"\u22C0","Yacute":"\u00DD","yacute":"\u00FD","YAcy":"\u042F","yacy":"\u044F","Ycirc":"\u0176","ycirc":"\u0177","Ycy":"\u042B","ycy":"\u044B","yen":"\u00A5","Yfr":"\uD835\uDD1C","yfr":"\uD835\uDD36","YIcy":"\u0407","yicy":"\u0457","Yopf":"\uD835\uDD50","yopf":"\uD835\uDD6A","Yscr":"\uD835\uDCB4","yscr":"\uD835\uDCCE","YUcy":"\u042E","yucy":"\u044E","yuml":"\u00FF","Yuml":"\u0178","Zacute":"\u0179","zacute":"\u017A","Zcaron":"\u017D","zcaron":"\u017E","Zcy":"\u0417","zcy":"\u0437","Zdot":"\u017B","zdot":"\u017C","zeetrf":"\u2128","ZeroWidthSpace":"\u200B","Zeta":"\u0396","zeta":"\u03B6","zfr":"\uD835\uDD37","Zfr":"\u2128","ZHcy":"\u0416","zhcy":"\u0436","zigrarr":"\u21DD","zopf":"\uD835\uDD6B","Zopf":"\u2124","Zscr":"\uD835\uDCB5","zscr":"\uD835\uDCCF","zwj":"\u200D","zwnj":"\u200C"}
+},{}],16:[function(require,module,exports){
+module.exports={"Aacute":"\u00C1","aacute":"\u00E1","Acirc":"\u00C2","acirc":"\u00E2","acute":"\u00B4","AElig":"\u00C6","aelig":"\u00E6","Agrave":"\u00C0","agrave":"\u00E0","amp":"&","AMP":"&","Aring":"\u00C5","aring":"\u00E5","Atilde":"\u00C3","atilde":"\u00E3","Auml":"\u00C4","auml":"\u00E4","brvbar":"\u00A6","Ccedil":"\u00C7","ccedil":"\u00E7","cedil":"\u00B8","cent":"\u00A2","copy":"\u00A9","COPY":"\u00A9","curren":"\u00A4","deg":"\u00B0","divide":"\u00F7","Eacute":"\u00C9","eacute":"\u00E9","Ecirc":"\u00CA","ecirc":"\u00EA","Egrave":"\u00C8","egrave":"\u00E8","ETH":"\u00D0","eth":"\u00F0","Euml":"\u00CB","euml":"\u00EB","frac12":"\u00BD","frac14":"\u00BC","frac34":"\u00BE","gt":">","GT":">","Iacute":"\u00CD","iacute":"\u00ED","Icirc":"\u00CE","icirc":"\u00EE","iexcl":"\u00A1","Igrave":"\u00CC","igrave":"\u00EC","iquest":"\u00BF","Iuml":"\u00CF","iuml":"\u00EF","laquo":"\u00AB","lt":"<","LT":"<","macr":"\u00AF","micro":"\u00B5","middot":"\u00B7","nbsp":"\u00A0","not":"\u00AC","Ntilde":"\u00D1","ntilde":"\u00F1","Oacute":"\u00D3","oacute":"\u00F3","Ocirc":"\u00D4","ocirc":"\u00F4","Ograve":"\u00D2","ograve":"\u00F2","ordf":"\u00AA","ordm":"\u00BA","Oslash":"\u00D8","oslash":"\u00F8","Otilde":"\u00D5","otilde":"\u00F5","Ouml":"\u00D6","ouml":"\u00F6","para":"\u00B6","plusmn":"\u00B1","pound":"\u00A3","quot":"\"","QUOT":"\"","raquo":"\u00BB","reg":"\u00AE","REG":"\u00AE","sect":"\u00A7","shy":"\u00AD","sup1":"\u00B9","sup2":"\u00B2","sup3":"\u00B3","szlig":"\u00DF","THORN":"\u00DE","thorn":"\u00FE","times":"\u00D7","Uacute":"\u00DA","uacute":"\u00FA","Ucirc":"\u00DB","ucirc":"\u00FB","Ugrave":"\u00D9","ugrave":"\u00F9","uml":"\u00A8","Uuml":"\u00DC","uuml":"\u00FC","Yacute":"\u00DD","yacute":"\u00FD","yen":"\u00A5","yuml":"\u00FF"}
+},{}],17:[function(require,module,exports){
+module.exports={"amp":"&","apos":"'","gt":">","lt":"<","quot":"\""}
+
+},{}],18:[function(require,module,exports){
+
+'use strict';
+
+
+/* eslint-disable no-bitwise */
+
+var decodeCache = {};
+
+function getDecodeCache(exclude) {
+ var i, ch, cache = decodeCache[exclude];
+ if (cache) { return cache; }
+
+ cache = decodeCache[exclude] = [];
+
+ for (i = 0; i < 128; i++) {
+ ch = String.fromCharCode(i);
+ cache.push(ch);
+ }
+
+ for (i = 0; i < exclude.length; i++) {
+ ch = exclude.charCodeAt(i);
+ cache[ch] = '%' + ('0' + ch.toString(16).toUpperCase()).slice(-2);
+ }
+
+ return cache;
+}
+
+
+// Decode percent-encoded string.
+//
+function decode(string, exclude) {
+ var cache;
+
+ if (typeof exclude !== 'string') {
+ exclude = decode.defaultChars;
+ }
+
+ cache = getDecodeCache(exclude);
+
+ return string.replace(/(%[a-f0-9]{2})+/gi, function(seq) {
+ var i, l, b1, b2, b3, b4, char,
+ result = '';
+
+ for (i = 0, l = seq.length; i < l; i += 3) {
+ b1 = parseInt(seq.slice(i + 1, i + 3), 16);
+
+ if (b1 < 0x80) {
+ result += cache[b1];
+ continue;
+ }
+
+ if ((b1 & 0xE0) === 0xC0 && (i + 3 < l)) {
+ // 110xxxxx 10xxxxxx
+ b2 = parseInt(seq.slice(i + 4, i + 6), 16);
+
+ if ((b2 & 0xC0) === 0x80) {
+ char = ((b1 << 6) & 0x7C0) | (b2 & 0x3F);
+
+ if (char < 0x80) {
+ result += '\ufffd\ufffd';
+ } else {
+ result += String.fromCharCode(char);
+ }
+
+ i += 3;
+ continue;
+ }
+ }
+
+ if ((b1 & 0xF0) === 0xE0 && (i + 6 < l)) {
+ // 1110xxxx 10xxxxxx 10xxxxxx
+ b2 = parseInt(seq.slice(i + 4, i + 6), 16);
+ b3 = parseInt(seq.slice(i + 7, i + 9), 16);
+
+ if ((b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) {
+ char = ((b1 << 12) & 0xF000) | ((b2 << 6) & 0xFC0) | (b3 & 0x3F);
+
+ if (char < 0x800 || (char >= 0xD800 && char <= 0xDFFF)) {
+ result += '\ufffd\ufffd\ufffd';
+ } else {
+ result += String.fromCharCode(char);
+ }
+
+ i += 6;
+ continue;
+ }
+ }
+
+ if ((b1 & 0xF8) === 0xF0 && (i + 9 < l)) {
+ // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx
+ b2 = parseInt(seq.slice(i + 4, i + 6), 16);
+ b3 = parseInt(seq.slice(i + 7, i + 9), 16);
+ b4 = parseInt(seq.slice(i + 10, i + 12), 16);
+
+ if ((b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80 && (b4 & 0xC0) === 0x80) {
+ char = ((b1 << 18) & 0x1C0000) | ((b2 << 12) & 0x3F000) | ((b3 << 6) & 0xFC0) | (b4 & 0x3F);
+
+ if (char < 0x10000 || char > 0x10FFFF) {
+ result += '\ufffd\ufffd\ufffd\ufffd';
+ } else {
+ char -= 0x10000;
+ result += String.fromCharCode(0xD800 + (char >> 10), 0xDC00 + (char & 0x3FF));
+ }
+
+ i += 9;
+ continue;
+ }
+ }
+
+ result += '\ufffd';
+ }
+
+ return result;
+ });
+}
+
+
+decode.defaultChars = ';/?:@&=+$,#';
+decode.componentChars = '';
+
+
+module.exports = decode;
+
+},{}],19:[function(require,module,exports){
+
+'use strict';
+
+
+var encodeCache = {};
+
+
+// Create a lookup array where anything but characters in `chars` string
+// and alphanumeric chars is percent-encoded.
+//
+function getEncodeCache(exclude) {
+ var i, ch, cache = encodeCache[exclude];
+ if (cache) { return cache; }
+
+ cache = encodeCache[exclude] = [];
+
+ for (i = 0; i < 128; i++) {
+ ch = String.fromCharCode(i);
+
+ if (/^[0-9a-z]$/i.test(ch)) {
+ // always allow unencoded alphanumeric characters
+ cache.push(ch);
+ } else {
+ cache.push('%' + ('0' + i.toString(16).toUpperCase()).slice(-2));
+ }
+ }
+
+ for (i = 0; i < exclude.length; i++) {
+ cache[exclude.charCodeAt(i)] = exclude[i];
+ }
+
+ return cache;
+}
+
+
+// Encode unsafe characters with percent-encoding, skipping already
+// encoded sequences.
+//
+// - string - string to encode
+// - exclude - list of characters to ignore (in addition to a-zA-Z0-9)
+// - keepEscaped - don't encode '%' in a correct escape sequence (default: true)
+//
+function encode(string, exclude, keepEscaped) {
+ var i, l, code, nextCode, cache,
+ result = '';
+
+ if (typeof exclude !== 'string') {
+ // encode(string, keepEscaped)
+ keepEscaped = exclude;
+ exclude = encode.defaultChars;
+ }
+
+ if (typeof keepEscaped === 'undefined') {
+ keepEscaped = true;
+ }
+
+ cache = getEncodeCache(exclude);
+
+ for (i = 0, l = string.length; i < l; i++) {
+ code = string.charCodeAt(i);
+
+ if (keepEscaped && code === 0x25 /* % */ && i + 2 < l) {
+ if (/^[0-9a-f]{2}$/i.test(string.slice(i + 1, i + 3))) {
+ result += string.slice(i, i + 3);
+ i += 2;
+ continue;
+ }
+ }
+
+ if (code < 128) {
+ result += cache[code];
+ continue;
+ }
+
+ if (code >= 0xD800 && code <= 0xDFFF) {
+ if (code >= 0xD800 && code <= 0xDBFF && i + 1 < l) {
+ nextCode = string.charCodeAt(i + 1);
+ if (nextCode >= 0xDC00 && nextCode <= 0xDFFF) {
+ result += encodeURIComponent(string[i] + string[i + 1]);
+ i++;
+ continue;
+ }
+ }
+ result += '%EF%BF%BD';
+ continue;
+ }
+
+ result += encodeURIComponent(string[i]);
+ }
+
+ return result;
+}
+
+encode.defaultChars = ";/?:@&=+$,-_.!~*'()#";
+encode.componentChars = "-_.!~*'()";
+
+
+module.exports = encode;
+
+},{}],20:[function(require,module,exports){
+/*! http://mths.be/repeat v0.2.0 by @mathias */
+if (!String.prototype.repeat) {
+ (function() {
+ 'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
+ var defineProperty = (function() {
+ // IE 8 only supports `Object.defineProperty` on DOM elements
+ try {
+ var object = {};
+ var $defineProperty = Object.defineProperty;
+ var result = $defineProperty(object, object, object) && $defineProperty;
+ } catch(error) {}
+ return result;
+ }());
+ var repeat = function(count) {
+ if (this == null) {
+ throw TypeError();
+ }
+ var string = String(this);
+ // `ToInteger`
+ var n = count ? Number(count) : 0;
+ if (n != n) { // better `isNaN`
+ n = 0;
+ }
+ // Account for out-of-bounds indices
+ if (n < 0 || n == Infinity) {
+ throw RangeError();
+ }
+ var result = '';
+ while (n) {
+ if (n % 2 == 1) {
+ result += string;
+ }
+ if (n > 1) {
+ string += string;
+ }
+ n >>= 1;
+ }
+ return result;
+ };
+ if (defineProperty) {
+ defineProperty(String.prototype, 'repeat', {
+ 'value': repeat,
+ 'configurable': true,
+ 'writable': true
+ });
+ } else {
+ String.prototype.repeat = repeat;
+ }
+ }());
+}
+
+},{}]},{},[5])(5)
+}); \ No newline at end of file
diff --git a/js/vendor/dompurify/LICENSE b/js/vendor/dompurify/LICENSE
new file mode 100644
index 00000000..e099aad0
--- /dev/null
+++ b/js/vendor/dompurify/LICENSE
@@ -0,0 +1,378 @@
+DOMPurify
+Copyright 2015 Mario Heiderich
+
+DOMPurify is free software; you can redistribute it and/or modify it under the
+terms of either:
+
+a) the Apache License Version 2.0, or
+b) the Mozilla Public License Version 2.0
+
+-----------------------------------------------------------------------------
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-----------------------------------------------------------------------------
+Mozilla Public License, version 2.0
+
+1. Definitions
+
+1.1. “Contributor”
+
+ means each individual or legal entity that creates, contributes to the
+ creation of, or owns Covered Software.
+
+1.2. “Contributor Version”
+
+ means the combination of the Contributions of others (if any) used by a
+ Contributor and that particular Contributor’s Contribution.
+
+1.3. “Contribution”
+
+ means Covered Software of a particular Contributor.
+
+1.4. “Covered Software”
+
+ means Source Code Form to which the initial Contributor has attached the
+ notice in Exhibit A, the Executable Form of such Source Code Form, and
+ Modifications of such Source Code Form, in each case including portions
+ thereof.
+
+1.5. “Incompatible With Secondary Licenses”
+ means
+
+ a. that the initial Contributor has attached the notice described in
+ Exhibit B to the Covered Software; or
+
+ b. that the Covered Software was made available under the terms of version
+ 1.1 or earlier of the License, but not also under the terms of a
+ Secondary License.
+
+1.6. “Executable Form”
+
+ means any form of the work other than Source Code Form.
+
+1.7. “Larger Work”
+
+ means a work that combines Covered Software with other material, in a separate
+ file or files, that is not Covered Software.
+
+1.8. “License”
+
+ means this document.
+
+1.9. “Licensable”
+
+ means having the right to grant, to the maximum extent possible, whether at the
+ time of the initial grant or subsequently, any and all of the rights conveyed by
+ this License.
+
+1.10. “Modifications”
+
+ means any of the following:
+
+ a. any file in Source Code Form that results from an addition to, deletion
+ from, or modification of the contents of Covered Software; or
+
+ b. any new file in Source Code Form that contains any Covered Software.
+
+1.11. “Patent Claims” of a Contributor
+
+ means any patent claim(s), including without limitation, method, process,
+ and apparatus claims, in any patent Licensable by such Contributor that
+ would be infringed, but for the grant of the License, by the making,
+ using, selling, offering for sale, having made, import, or transfer of
+ either its Contributions or its Contributor Version.
+
+1.12. “Secondary License”
+
+ means either the GNU General Public License, Version 2.0, the GNU Lesser
+ General Public License, Version 2.1, the GNU Affero General Public
+ License, Version 3.0, or any later versions of those licenses.
+
+1.13. “Source Code Form”
+
+ means the form of the work preferred for making modifications.
+
+1.14. “You” (or “Your”)
+
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, “You” includes any entity that controls, is
+ controlled by, or is under common control with You. For purposes of this
+ definition, “control” means (a) the power, direct or indirect, to cause
+ the direction or management of such entity, whether by contract or
+ otherwise, or (b) ownership of more than fifty percent (50%) of the
+ outstanding shares or beneficial ownership of such entity.
+
+
+2. License Grants and Conditions
+
+2.1. Grants
+
+ Each Contributor hereby grants You a world-wide, royalty-free,
+ non-exclusive license:
+
+ a. under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or as
+ part of a Larger Work; and
+
+ b. under Patent Claims of such Contributor to make, use, sell, offer for
+ sale, have made, import, and otherwise transfer either its Contributions
+ or its Contributor Version.
+
+2.2. Effective Date
+
+ The licenses granted in Section 2.1 with respect to any Contribution become
+ effective for each Contribution on the date the Contributor first distributes
+ such Contribution.
+
+2.3. Limitations on Grant Scope
+
+ The licenses granted in this Section 2 are the only rights granted under this
+ License. No additional rights or licenses will be implied from the distribution
+ or licensing of Covered Software under this License. Notwithstanding Section
+ 2.1(b) above, no patent license is granted by a Contributor:
+
+ a. for any code that a Contributor has removed from Covered Software; or
+
+ b. for infringements caused by: (i) Your and any other third party’s
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+ c. under Patent Claims infringed by Covered Software in the absence of its
+ Contributions.
+
+ This License does not grant any rights in the trademarks, service marks, or
+ logos of any Contributor (except as may be necessary to comply with the
+ notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+ No Contributor makes additional grants as a result of Your choice to
+ distribute the Covered Software under a subsequent version of this License
+ (see Section 10.2) or under the terms of a Secondary License (if permitted
+ under the terms of Section 3.3).
+
+2.5. Representation
+
+ Each Contributor represents that the Contributor believes its Contributions
+ are its original creation(s) or it has sufficient rights to grant the
+ rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+ This License is not intended to limit any rights You have under applicable
+ copyright doctrines of fair use, fair dealing, or other equivalents.
+
+2.7. Conditions
+
+ Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
+ Section 2.1.
+
+
+3. Responsibilities
+
+3.1. Distribution of Source Form
+
+ All distribution of Covered Software in Source Code Form, including any
+ Modifications that You create or to which You contribute, must be under the
+ terms of this License. You must inform recipients that the Source Code Form
+ of the Covered Software is governed by the terms of this License, and how
+ they can obtain a copy of this License. You may not attempt to alter or
+ restrict the recipients’ rights in the Source Code Form.
+
+3.2. Distribution of Executable Form
+
+ If You distribute Covered Software in Executable Form then:
+
+ a. such Covered Software must also be made available in Source Code Form,
+ as described in Section 3.1, and You must inform recipients of the
+ Executable Form how they can obtain a copy of such Source Code Form by
+ reasonable means in a timely manner, at a charge no more than the cost
+ of distribution to the recipient; and
+
+ b. You may distribute such Executable Form under the terms of this License,
+ or sublicense it under different terms, provided that the license for
+ the Executable Form does not attempt to limit or alter the recipients’
+ rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+ You may create and distribute a Larger Work under terms of Your choice,
+ provided that You also comply with the requirements of this License for the
+ Covered Software. If the Larger Work is a combination of Covered Software
+ with a work governed by one or more Secondary Licenses, and the Covered
+ Software is not Incompatible With Secondary Licenses, this License permits
+ You to additionally distribute such Covered Software under the terms of
+ such Secondary License(s), so that the recipient of the Larger Work may, at
+ their option, further distribute the Covered Software under the terms of
+ either this License or such Secondary License(s).
+
+3.4. Notices
+
+ You may not remove or alter the substance of any license notices (including
+ copyright notices, patent notices, disclaimers of warranty, or limitations
+ of liability) contained within the Source Code Form of the Covered
+ Software, except that You may alter any license notices to the extent
+ required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+ You may choose to offer, and to charge a fee for, warranty, support,
+ indemnity or liability obligations to one or more recipients of Covered
+ Software. However, You may do so only on Your own behalf, and not on behalf
+ of any Contributor. You must make it absolutely clear that any such
+ warranty, support, indemnity, or liability obligation is offered by You
+ alone, and You hereby agree to indemnify every Contributor for any
+ liability incurred by such Contributor as a result of warranty, support,
+ indemnity or liability terms You offer. You may include additional
+ disclaimers of warranty and limitations of liability specific to any
+ jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+
+ If it is impossible for You to comply with any of the terms of this License
+ with respect to some or all of the Covered Software due to statute, judicial
+ order, or regulation then You must: (a) comply with the terms of this License
+ to the maximum extent possible; and (b) describe the limitations and the code
+ they affect. Such description must be placed in a text file included with all
+ distributions of the Covered Software under this License. Except to the
+ extent prohibited by statute or regulation, such description must be
+ sufficiently detailed for a recipient of ordinary skill to be able to
+ understand it.
+
+5. Termination
+
+5.1. The rights granted under this License will terminate automatically if You
+ fail to comply with any of its terms. However, if You become compliant,
+ then the rights granted under this License from a particular Contributor
+ are reinstated (a) provisionally, unless and until such Contributor
+ explicitly and finally terminates Your grants, and (b) on an ongoing basis,
+ if such Contributor fails to notify You of the non-compliance by some
+ reasonable means prior to 60 days after You have come back into compliance.
+ Moreover, Your grants from a particular Contributor are reinstated on an
+ ongoing basis if such Contributor notifies You of the non-compliance by
+ some reasonable means, this is the first time You have received notice of
+ non-compliance with this License from such Contributor, and You become
+ compliant prior to 30 days after Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+ infringement claim (excluding declaratory judgment actions, counter-claims,
+ and cross-claims) alleging that a Contributor Version directly or
+ indirectly infringes any patent, then the rights granted to You by any and
+ all Contributors for the Covered Software under Section 2.1 of this License
+ shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
+ license agreements (excluding distributors and resellers) which have been
+ validly granted by You or Your distributors under this License prior to
+ termination shall survive termination.
+
+6. Disclaimer of Warranty
+
+ Covered Software is provided under this License on an “as is” basis, without
+ warranty of any kind, either expressed, implied, or statutory, including,
+ without limitation, warranties that the Covered Software is free of defects,
+ merchantable, fit for a particular purpose or non-infringing. The entire
+ risk as to the quality and performance of the Covered Software is with You.
+ Should any Covered Software prove defective in any respect, You (not any
+ Contributor) assume the cost of any necessary servicing, repair, or
+ correction. This disclaimer of warranty constitutes an essential part of this
+ License. No use of any Covered Software is authorized under this License
+ except under this disclaimer.
+
+7. Limitation of Liability
+
+ Under no circumstances and under no legal theory, whether tort (including
+ negligence), contract, or otherwise, shall any Contributor, or anyone who
+ distributes Covered Software as permitted above, be liable to You for any
+ direct, indirect, special, incidental, or consequential damages of any
+ character including, without limitation, damages for lost profits, loss of
+ goodwill, work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses, even if such party shall have been
+ informed of the possibility of such damages. This limitation of liability
+ shall not apply to liability for death or personal injury resulting from such
+ party’s negligence to the extent applicable law prohibits such limitation.
+ Some jurisdictions do not allow the exclusion or limitation of incidental or
+ consequential damages, so this exclusion and limitation may not apply to You.
+
+8. Litigation
+
+ Any litigation relating to this License may be brought only in the courts of
+ a jurisdiction where the defendant maintains its principal place of business
+ and such litigation shall be governed by laws of that jurisdiction, without
+ reference to its conflict-of-law provisions. Nothing in this Section shall
+ prevent a party’s ability to bring cross-claims or counter-claims.
+
+9. Miscellaneous
+
+ This License represents the complete agreement concerning the subject matter
+ hereof. If any provision of this License is held to be unenforceable, such
+ provision shall be reformed only to the extent necessary to make it
+ enforceable. Any law or regulation which provides that the language of a
+ contract shall be construed against the drafter shall not be used to construe
+ this License against a Contributor.
+
+
+10. Versions of the License
+
+10.1. New Versions
+
+ Mozilla Foundation is the license steward. Except as provided in Section
+ 10.3, no one other than the license steward has the right to modify or
+ publish new versions of this License. Each version will be given a
+ distinguishing version number.
+
+10.2. Effect of New Versions
+
+ You may distribute the Covered Software under the terms of the version of
+ the License under which You originally received the Covered Software, or
+ under the terms of any subsequent version published by the license
+ steward.
+
+10.3. Modified Versions
+
+ If you create software not governed by this License, and you want to
+ create a new license for such software, you may create and use a modified
+ version of this License if you rename the license and remove any
+ references to the name of the license steward (except to note that such
+ modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
+ If You choose to distribute Source Code Form that is Incompatible With
+ Secondary Licenses under the terms of this version of the License, the
+ notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+
+ This Source Code Form is subject to the
+ terms of the Mozilla Public License, v.
+ 2.0. If a copy of the MPL was not
+ distributed with this file, You can
+ obtain one at
+ http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular file, then
+You may include the notice in a location (such as a LICENSE file in a relevant
+directory) where a recipient would be likely to look for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - “Incompatible With Secondary Licenses” Notice
+
+ This Source Code Form is “Incompatible
+ With Secondary Licenses”, as defined by
+ the Mozilla Public License, v. 2.0.
+
diff --git a/js/vendor/dompurify/README.md b/js/vendor/dompurify/README.md
new file mode 100644
index 00000000..a5e0da51
--- /dev/null
+++ b/js/vendor/dompurify/README.md
@@ -0,0 +1,185 @@
+# DOMPurify [![Bower version](https://badge.fury.io/bo/dompurify.svg)](http://badge.fury.io/bo/dompurify) · [![npm version](https://badge.fury.io/js/dompurify.svg)](http://badge.fury.io/js/dompurify) · [![Build Status](https://travis-ci.org/cure53/DOMPurify.svg?branch=master)](https://travis-ci.org/cure53/DOMPurify)
+
+[![NPM](https://nodei.co/npm/dompurify.png)](https://nodei.co/npm/dompurify/)
+
+DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG.
+
+It's written in JavaScript and works in all modern browsers (Safari, Opera (15+), Internet Explorer (10+), Spartan, Firefox and Chrome - as well as almost anything else using Blink or WebKit). It doesn't break on IE6 or other legacy browsers. It simply does nothing there. Our automated tests cover 8 different browsers right now.
+
+DOMPurify is written by security people who have vast background in web attacks and XSS. Fear not. For more details please also read about our [Security Goals & Threat Model](https://github.com/cure53/DOMPurify/wiki/Security-Goals-&-Threat-Model)
+
+## What does it do?
+
+DOMPurify sanitizes HTML and prevents XSS attacks. You can feed DOMPurify with string full of dirty HTML and it will return a string with clean HTML. DOMPurify will strip out everything that contains dangerous HTML and thereby prevent XSS attacks and other nastiness. It's also damn bloody fast. We use the technologies the browser provides and turn them into an XSS filter. The faster your browser, the faster DOMPurify will be.
+
+## How do I use it?
+
+It's easy. Just include DOMPurify on your website.
+
+### Using the unminified development version
+
+```html
+<script type="text/javascript" src="src/purify.js"></script>
+```
+
+### Using the minified and tested production version (source-map available)
+
+```html
+<script type="text/javascript" src="dist/purify.min.js"></script>
+```
+
+Afterwards you can sanitize strings by executing the following code:
+
+```javascript
+var clean = DOMPurify.sanitize(dirty);
+```
+
+The resulting HTML can be written into a DOM element using `innerHTML` or the DOM using `document.write()`. That is fully up to you. But keep in mind, if you use the sanitized HTML with jQuery's very insecure `elm.html()` method, then the `SAFE_FOR_JQUERY` flag has to be set to make sure it's safe! Other than that, all is fine.
+
+If you're using an [AMD](https://github.com/amdjs/amdjs-api/wiki/AMD) module loader like [Require.js](http://requirejs.org/), you can load this script asynchronously as well:
+
+```javascript
+require(['dompurify'], function(DOMPurify) {
+ var clean = DOMPurify.sanitize(dirty);
+});
+```
+
+You can also grab the files straight from npm (requires either [io.js](https://iojs.org) or [Browserify](http://browserify.org/), **Node.js 0.x is not supported**):
+
+```bash
+npm install dompurify
+```
+
+```javascript
+var DOMPurify = require('dompurify');
+var clean = DOMPurify.sanitize(dirty);
+```
+
+## Is there a demo?
+
+Of course there is a demo! [Play with DOMPurify](https://cure53.de/purify)
+
+## What if I find a bypass?
+
+If that happens, you probably qualify for a juicy bug bounty! The fine folks over at [FastMail](https://www.fastmail.com/) use DOMPurify for their services and added our library to their bug bounty scope. So, if you find a way to bypass or weaken DOMPurify, please have a look at their website and the [bug bounty info](https://www.fastmail.com/about/bugbounty.html).
+
+## Some purification samples please?
+
+How does purified markup look like? Well, [the demo](https://cure53.de/purify) shows it for a big bunch of nasty elements. But let's also show some smaller examples!
+
+```javascript
+DOMPurify.sanitize('<img src=x onerror=alert(1)//>'); // becomes <img src="x">
+DOMPurify.sanitize('<svg><g/onload=alert(2)//<p>'); // becomes <svg><g></g></svg>
+DOMPurify.sanitize('<p>abc<iframe/\/src=jAva&Tab;script:alert(3)>def'); // becomes <p>abc</p>
+DOMPurify.sanitize('<math><mi//xlink:href="data:x,<script>alert(4)</script>">'); // becomes <math></math>
+DOMPurify.sanitize('<TABLE><tr><td>HELLO</tr></TABL>'); // becomes <table><tbody><tr><td>HELLO</td></tr></tbody></table>
+DOMPurify.sanitize('<UL><li><A HREF=//google.com>click</UL>'); // becomes <ul><li><a href="//google.com">click</a></li></ul>
+```
+
+## What is supported?
+
+DOMPurify currently supports HTML5, SVG and MathML. DOMPurify per default allows CSS, HTML custom data attributes. DOMPurify also supports the Shadow DOM - and sanitizes DOM templates recursively. DOMPurify also allows you to sanitize HTML for being used with the jQuery `$()` and `elm.html()` methods.
+
+
+## Can I configure it?
+
+Yes. The included default configuration values are pretty good already - but you can of course override them. Check out the `/demos` folder to see a bunch of examples on how you can customize DOMPurify.
+
+```javascript
+// allow only <b>
+var clean = DOMPurify.sanitize(dirty, {ALLOWED_TAGS: ['b']});
+
+// allow only <b> and <q> with style attributes (for whatever reason)
+var clean = DOMPurify.sanitize(dirty, {ALLOWED_TAGS: ['b', 'q'], ALLOWED_ATTR: ['style']});
+
+// leave all as it is but forbid <style>
+var clean = DOMPurify.sanitize(dirty, {FORBID_TAGS: ['style']});
+
+// leave all as it is but forbid style attributes
+var clean = DOMPurify.sanitize(dirty, {FORBID_ATTR: ['style']});
+
+// extend the existing array of allowed tags
+var clean = DOMPurify.sanitize(dirty, {ADD_TAGS: ['my-tag']});
+
+// extend the existing array of attributes
+var clean = DOMPurify.sanitize(dirty, {ADD_ATTR: ['my-attr']});
+
+// prohibit HTML5 data attributes (default is true)
+var clean = DOMPurify.sanitize(dirty, {ALLOW_DATA_ATTR: false});
+
+// return a DOM HTMLBodyElement instead of an HTML string (default is false)
+var clean = DOMPurify.sanitize(dirty, {RETURN_DOM: true});
+
+// return a DOM DocumentFragment instead of an HTML string (default is false)
+var clean = DOMPurify.sanitize(dirty, {RETURN_DOM_FRAGMENT: true});
+
+// return a DOM DocumentFragment instead of an HTML string (default is false)
+// also import it into the current document (default is false).
+// RETURN_DOM_IMPORT must be set if you would like to append
+// the returned node to the current document
+var clean = DOMPurify.sanitize(dirty, {RETURN_DOM_FRAGMENT: true, RETURN_DOM_IMPORT: true});
+document.body.appendChild(clean);
+
+// return entire document including <html> tags (default is false)
+var clean = DOMPurify.sanitize(dirty, {WHOLE_DOCUMENT: true});
+
+// make output safe for usage in jQuery's $()/html() method (default is false)
+var clean = DOMPurify.sanitize(dirty, {SAFE_FOR_JQUERY: true});
+
+// disable DOM Clobbering protection on output (default is true, handle with care!)
+var clean = DOMPurify.sanitize(dirty, {SANITIZE_DOM: false});
+
+// discard an element's content when the element is removed (default is true)
+var clean = DOMPurify.sanitize(dirty, {KEEP_CONTENT: false});
+```
+There is even [more examples here](https://github.com/cure53/DOMPurify/tree/master/demos#what-it-this), showing how you can run, customize and configure DOMPurify to fit your needs.
+
+## Hooks
+
+DOMPurify allows you to augment its functionality by attaching one or more functions with the `DOMPurify.addHook` method to one of the following hooks:
+
+- `beforeSanitizeElements`
+- `uponSanitizeElement`
+- `afterSanitizeElements`
+- `beforeSanitizeAttributes`
+- `uponSanitizeAttribute`
+- `afterSanitizeAttributes`
+- `beforeSanitizeShadowDOM`
+- `uponSanitizeShadowNode`
+- `afterSanitizeShadowDOM`
+
+It passes the currently processed DOM node, when needed a literal with verified node and attribute data and the DOMPurify configuration to the callback. Check out the [MentalJS hook demo](https://github.com/cure53/DOMPurify/blob/master/demos/hooks-mentaljs-demo.html) to see how the API can be used nicely.
+
+_Example_:
+
+```javascript
+DOMPurify.addHook('beforeSanitizeElements', function(currentNode, data, config) {
+ // Do something with the current node and return it
+ return currentNode;
+});
+```
+
+## Continuous Integration
+
+We are currently using Travis CI in combination with BrowserStack. This gives us the possibility to confirm for each and every commit that all is going according to plan in all supported browsers. Check out the build logs here: https://travis-ci.org/cure53/DOMPurify
+
+You can further run local tests by executing `npm run-script local-test` or, in case you have a BrowserStack account with automation available, run the tests using `npm run-script ci-test`.
+
+## Security Mailing List
+
+We maintain a mailing list that notifies whenever a security-critical release of DOMPurify was published. This means, if someone found a bypass and we fixed it with a release (which always happens when a bypass was found) a mail will go out to that list. This usually happens within minutes or few hours after learning about a bypass. The list can be subscribed to here:
+
+[https://lists.ruhr-uni-bochum.de/mailman/listinfo/dompurify-security](https://lists.ruhr-uni-bochum.de/mailman/listinfo/dompurify-security)
+
+
+## What's on the road-map?
+
+We recently implemented a Hook-API allowing developers to create their own DOMPurify plugins and customize its functionality without changing the core. Thus, we are looking forward for plugins and extensions - pull requests are welcome! Oh, and we will increase the amount of browsers and HTML-mappings in our automates tests to make sure nothing slips through.
+
+## Who contributed?
+
+Several people need to be listed here! [@garethheyes](https://twitter.com/garethheyes) for invaluable help, [@shafigullin](https://twitter.com/shafigullin) for breaking the library multiple times and thereby strengthening it, [@mmrupp](https://twitter.com/mmrupp) and [@irsdl](https://twitter.com/irsdl) for doing the same.
+
+Big thanks also go to [@asutherland](https://twitter.com/asutherland), [@mathias](https://twitter.com/mathias), [@cgvwzq](https://twitter.com/cgvwzq), [@robbertatwork](https://twitter.com/robbertatwork), [@giutro](https://twitter.com/giutro) and [@fhemberger](https://twitter.com/fhemberger)! Further, thanks [@neilj](https://twitter.com/neilj) for his code review and countless small optimizations, fixes and beautifications. Big thanks also go to [@tdeekens](https://twitter.com/tdeekens) for doing all the hard work and getting us on track with Travis CI and BrowserStack.
+
+And last but not least, thanks to [BrowserStack](https://browserstack.com) for supporting this project with their services for free!
diff --git a/js/vendor/eventsource-polyfill/LICENSE b/js/vendor/eventsource-polyfill/LICENSE
new file mode 100644
index 00000000..62758d3f
--- /dev/null
+++ b/js/vendor/eventsource-polyfill/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 AmvTek
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE. \ No newline at end of file
diff --git a/js/vendor/eventsource-polyfill/README.md b/js/vendor/eventsource-polyfill/README.md
new file mode 100644
index 00000000..c949243e
--- /dev/null
+++ b/js/vendor/eventsource-polyfill/README.md
@@ -0,0 +1,75 @@
+EventSource Polyfill
+====================
+
+Provide polyfill to support EventSource in browser where it is not available.
+
+> - Used in production
+> - Tested in Internet Explorer 8 +
+> - Tested in Android browser 2.1 +
+> - [Documented][]
+> - Run the [Browser test suite][]
+
+Installing
+----------
+
+### from source
+
+Download suitable project archive (zip or tar.gz) from [release page][]
+
+Include in your html documents one of the following javascript file:
+
+> - *dist/eventsource.js*
+> - *dist/eventsource.min.js* (minified version)
+
+### Using bower package manager
+
+To install package from **bower registry**, type :
+
+ bower install eventsource-polyfill
+
+Include in your html documents one of the following javascript file:
+
+> - *bower\_components/eventsource-polyfill/dist/eventsource.js*
+> - *bower\_components/eventsource-polyfill/dist/eventsource.min.js* (minified version)
+
+### Using npm package manager
+
+To install package from **npm registry**, type :
+
+ npm install eventsource-polyfill
+
+Note that this package may only be used with in **browser application**.
+
+If you are using [browserify][] , you just have to require this package in your main module…
+
+``` sourceCode
+// load (Polyfill) EventSource, in case browser does not support it...
+require('eventsource-polyfill');
+```
+
+Run the tests now
+-----------------
+
+With your web browser visit this [test site][Browser test suite]
+
+Allow **sufficient time** ( ~ 5 minutes) for the full Test Suite to run…
+
+Project content
+---------------
+
+dist/
+ built version of javascript modules
+
+javascript/
+ Contains polyfill module and related unit tests
+
+test_server/
+ python server which generates *easy to test* **event stream**
+
+docs/
+ documentation wiki
+
+ [Documented]: https://github.com/amvtek/EventSource/wiki
+ [Browser test suite]: http://testevs.amvtek.com/
+ [release page]: https://github.com/amvtek/EventSource/releases/latest
+ [browserify]: http://browserify.org
diff --git a/js/vendor/eventsource-polyfill/dist/eventsource.js b/js/vendor/eventsource-polyfill/dist/eventsource.js
new file mode 100644
index 00000000..d3b563b3
--- /dev/null
+++ b/js/vendor/eventsource-polyfill/dist/eventsource.js
@@ -0,0 +1,621 @@
+/*
+ * EventSource polyfill version 0.9.7
+ * Supported by sc AmvTek srl
+ * :email: devel@amvtek.com
+ */
+;(function (global) {
+
+ if (global.EventSource && !global._eventSourceImportPrefix){
+ return;
+ }
+
+ var evsImportName = (global._eventSourceImportPrefix||'')+"EventSource";
+
+ var EventSource = function (url, options) {
+
+ if (!url || typeof url != 'string') {
+ throw new SyntaxError('Not enough arguments');
+ }
+
+ this.URL = url;
+ this.setOptions(options);
+ var evs = this;
+ setTimeout(function(){evs.poll()}, 0);
+ };
+
+ EventSource.prototype = {
+
+ CONNECTING: 0,
+
+ OPEN: 1,
+
+ CLOSED: 2,
+
+ defaultOptions: {
+
+ loggingEnabled: false,
+
+ loggingPrefix: "eventsource",
+
+ interval: 500, // milliseconds
+
+ bufferSizeLimit: 256*1024, // bytes
+
+ silentTimeout: 300000, // milliseconds
+
+ getArgs:{
+ 'evs_buffer_size_limit': 256*1024
+ },
+
+ xhrHeaders:{
+ 'Accept': 'text/event-stream',
+ 'Cache-Control': 'no-cache',
+ 'X-Requested-With': 'XMLHttpRequest'
+ }
+ },
+
+ setOptions: function(options){
+
+ var defaults = this.defaultOptions;
+ var option;
+
+ // set all default options...
+ for (option in defaults){
+
+ if ( defaults.hasOwnProperty(option) ){
+ this[option] = defaults[option];
+ }
+ }
+
+ // override with what is in options
+ for (option in options){
+
+ if (option in defaults && options.hasOwnProperty(option)){
+ this[option] = options[option];
+ }
+ }
+
+ // if getArgs option is enabled
+ // ensure evs_buffer_size_limit corresponds to bufferSizeLimit
+ if (this.getArgs && this.bufferSizeLimit) {
+
+ this.getArgs['evs_buffer_size_limit'] = this.bufferSizeLimit;
+ }
+
+ // if console is not available, force loggingEnabled to false
+ if (typeof console === "undefined" || typeof console.log === "undefined") {
+
+ this.loggingEnabled = false;
+ }
+ },
+
+ log: function(message) {
+
+ if (this.loggingEnabled) {
+
+ console.log("[" + this.loggingPrefix +"]:" + message)
+ }
+ },
+
+ poll: function() {
+
+ try {
+
+ if (this.readyState == this.CLOSED) {
+ return;
+ }
+
+ this.cleanup();
+ this.readyState = this.CONNECTING;
+ this.cursor = 0;
+ this.cache = '';
+ this._xhr = new this.XHR(this);
+ this.resetNoActivityTimer();
+
+ }
+ catch (e) {
+
+ // in an attempt to silence the errors
+ this.log('There were errors inside the pool try-catch');
+ this.dispatchEvent('error', { type: 'error', data: e.message });
+ }
+ },
+
+ pollAgain: function (interval) {
+
+ // schedule poll to be called after interval milliseconds
+ var evs = this;
+ evs.readyState = evs.CONNECTING;
+ evs.dispatchEvent('error', {
+ type: 'error',
+ data: "Reconnecting "
+ });
+ this._pollTimer = setTimeout(function(){evs.poll()}, interval||0);
+ },
+
+
+ cleanup: function() {
+
+ this.log('evs cleaning up')
+
+ if (this._pollTimer){
+ clearInterval(this._pollTimer);
+ this._pollTimer = null;
+ }
+
+ if (this._noActivityTimer){
+ clearInterval(this._noActivityTimer);
+ this._noActivityTimer = null;
+ }
+
+ if (this._xhr){
+ this._xhr.abort();
+ this._xhr = null;
+ }
+ },
+
+ resetNoActivityTimer: function(){
+
+ if (this.silentTimeout){
+
+ if (this._noActivityTimer){
+ clearInterval(this._noActivityTimer);
+ }
+ var evs = this;
+ this._noActivityTimer = setTimeout(
+ function(){ evs.log('Timeout! silentTImeout:'+evs.silentTimeout); evs.pollAgain(); },
+ this.silentTimeout
+ );
+ }
+ },
+
+ close: function () {
+
+ this.readyState = this.CLOSED;
+ this.log('Closing connection. readyState: '+this.readyState);
+ this.cleanup();
+ },
+
+ _onxhrdata: function() {
+
+ var request = this._xhr;
+
+ if (request.isReady() && !request.hasError() ) {
+ // reset the timer, as we have activity
+ this.resetNoActivityTimer();
+
+ // move this EventSource to OPEN state...
+ if (this.readyState == this.CONNECTING) {
+ this.readyState = this.OPEN;
+ this.dispatchEvent('open', { type: 'open' });
+ }
+
+ var buffer = request.getBuffer();
+
+ if (buffer.length > this.bufferSizeLimit) {
+ this.log('buffer.length > this.bufferSizeLimit');
+ this.pollAgain();
+ }
+
+ if (this.cursor == 0 && buffer.length > 0){
+
+ // skip byte order mark \uFEFF character if it starts the stream
+ if (buffer.substring(0,1) == '\uFEFF'){
+ this.cursor = 1;
+ }
+ }
+
+ var lastMessageIndex = this.lastMessageIndex(buffer);
+ if (lastMessageIndex[0] >= this.cursor){
+
+ var newcursor = lastMessageIndex[1];
+ var toparse = buffer.substring(this.cursor, newcursor);
+ this.parseStream(toparse);
+ this.cursor = newcursor;
+ }
+
+ // if request is finished, reopen the connection
+ if (request.isDone()) {
+ this.log('request.isDone(). reopening the connection');
+ this.pollAgain(this.interval);
+ }
+ }
+ else if (this.readyState !== this.CLOSED) {
+
+ this.log('this.readyState !== this.CLOSED');
+ this.pollAgain(this.interval);
+
+ //MV: Unsure why an error was previously dispatched
+ }
+ },
+
+ parseStream: function(chunk) {
+
+ // normalize line separators (\r\n,\r,\n) to \n
+ // remove white spaces that may precede \n
+ chunk = this.cache + this.normalizeToLF(chunk);
+
+ var events = chunk.split('\n\n');
+
+ var i, j, eventType, datas, line, retry;
+
+ for (i=0; i < (events.length - 1); i++) {
+
+ eventType = 'message';
+ datas = [];
+ parts = events[i].split('\n');
+
+ for (j=0; j < parts.length; j++) {
+
+ line = this.trimWhiteSpace(parts[j]);
+
+ if (line.indexOf('event') == 0) {
+
+ eventType = line.replace(/event:?\s*/, '');
+ }
+ else if (line.indexOf('retry') == 0) {
+
+ retry = parseInt(line.replace(/retry:?\s*/, ''));
+ if(!isNaN(retry)) {
+ this.interval = retry;
+ }
+ }
+ else if (line.indexOf('data') == 0) {
+
+ datas.push(line.replace(/data:?\s*/, ''));
+ }
+ else if (line.indexOf('id:') == 0) {
+
+ this.lastEventId = line.replace(/id:?\s*/, '');
+ }
+ else if (line.indexOf('id') == 0) { // this resets the id
+
+ this.lastEventId = null;
+ }
+ }
+
+ if (datas.length) {
+ // dispatch a new event
+ var event = new MessageEvent(eventType, datas.join('\n'), window.location.origin, this.lastEventId);
+ this.dispatchEvent(eventType, event);
+ }
+ }
+
+ this.cache = events[events.length - 1];
+ },
+
+ dispatchEvent: function (type, event) {
+ var handlers = this['_' + type + 'Handlers'];
+
+ if (handlers) {
+
+ for (var i = 0; i < handlers.length; i++) {
+ handlers[i].call(this, event);
+ }
+ }
+
+ if (this['on' + type]) {
+ this['on' + type].call(this, event);
+ }
+
+ },
+
+ addEventListener: function (type, handler) {
+ if (!this['_' + type + 'Handlers']) {
+ this['_' + type + 'Handlers'] = [];
+ }
+
+ this['_' + type + 'Handlers'].push(handler);
+ },
+
+ removeEventListener: function (type, handler) {
+ var handlers = this['_' + type + 'Handlers'];
+ if (!handlers) {
+ return;
+ }
+ for (var i = handlers.length - 1; i >= 0; --i) {
+ if (handlers[i] === handler) {
+ handlers.splice(i, 1);
+ break;
+ }
+ }
+ },
+
+ _pollTimer: null,
+
+ _noactivityTimer: null,
+
+ _xhr: null,
+
+ lastEventId: null,
+
+ cache: '',
+
+ cursor: 0,
+
+ onerror: null,
+
+ onmessage: null,
+
+ onopen: null,
+
+ readyState: 0,
+
+ // ===================================================================
+ // helpers functions
+ // those are attached to prototype to ease reuse and testing...
+
+ urlWithParams: function (baseURL, params) {
+
+ var encodedArgs = [];
+
+ if (params){
+
+ var key, urlarg;
+ var urlize = encodeURIComponent;
+
+ for (key in params){
+ if (params.hasOwnProperty(key)) {
+ urlarg = urlize(key)+'='+urlize(params[key]);
+ encodedArgs.push(urlarg);
+ }
+ }
+ }
+
+ if (encodedArgs.length > 0){
+
+ if (baseURL.indexOf('?') == -1)
+ return baseURL + '?' + encodedArgs.join('&');
+ return baseURL + '&' + encodedArgs.join('&');
+ }
+ return baseURL;
+ },
+
+ lastMessageIndex: function(text) {
+
+ var ln2 =text.lastIndexOf('\n\n');
+ var lr2 = text.lastIndexOf('\r\r');
+ var lrln2 = text.lastIndexOf('\r\n\r\n');
+
+ if (lrln2 > Math.max(ln2, lr2)) {
+ return [lrln2, lrln2+4];
+ }
+ return [Math.max(ln2, lr2), Math.max(ln2, lr2) + 2]
+ },
+
+ trimWhiteSpace: function(str) {
+ // to remove whitespaces left and right of string
+
+ var reTrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g;
+ return str.replace(reTrim, '');
+ },
+
+ normalizeToLF: function(str) {
+
+ // replace \r and \r\n with \n
+ return str.replace(/\r\n|\r/g, '\n');
+ }
+
+ };
+
+ if (!isOldIE()){
+
+ EventSource.isPolyfill = "XHR";
+
+ // EventSource will send request using XMLHttpRequest
+ EventSource.prototype.XHR = function(evs) {
+
+ request = new XMLHttpRequest();
+ this._request = request;
+ evs._xhr = this;
+
+ // set handlers
+ request.onreadystatechange = function(){
+ if (request.readyState > 1 && evs.readyState != evs.CLOSED) {
+ if (request.status == 200 || (request.status>=300 && request.status<400)){
+ evs._onxhrdata();
+ }
+ else {
+ request._failed = true;
+ evs.readyState = evs.CLOSED;
+ evs.dispatchEvent('error', {
+ type: 'error',
+ data: "The server responded with "+request.status
+ });
+ evs.close();
+ }
+ }
+ };
+
+ request.onprogress = function () {
+ };
+
+ request.open('GET', evs.urlWithParams(evs.URL, evs.getArgs), true);
+
+ var headers = evs.xhrHeaders; // maybe null
+ for (var header in headers) {
+ if (headers.hasOwnProperty(header)){
+ request.setRequestHeader(header, headers[header]);
+ }
+ }
+ if (evs.lastEventId) {
+ request.setRequestHeader('Last-Event-Id', evs.lastEventId);
+ }
+
+ request.send();
+ };
+
+ EventSource.prototype.XHR.prototype = {
+
+ useXDomainRequest: false,
+
+ _request: null,
+
+ _failed: false, // true if we have had errors...
+
+ isReady: function() {
+
+
+ return this._request.readyState >= 2;
+ },
+
+ isDone: function() {
+
+ return (this._request.readyState == 4);
+ },
+
+ hasError: function() {
+
+ return (this._failed || (this._request.status >= 400));
+ },
+
+ getBuffer: function() {
+
+ var rv = '';
+ try {
+ rv = this._request.responseText || '';
+ }
+ catch (e){}
+ return rv;
+ },
+
+ abort: function() {
+
+ if ( this._request ) {
+ this._request.abort();
+ }
+ }
+ };
+ }
+ else {
+
+ EventSource.isPolyfill = "IE_8-9";
+
+ // patch EventSource defaultOptions
+ var defaults = EventSource.prototype.defaultOptions;
+ defaults.xhrHeaders = null; // no headers will be sent
+ defaults.getArgs['evs_preamble'] = 2048 + 8;
+
+ // EventSource will send request using Internet Explorer XDomainRequest
+ EventSource.prototype.XHR = function(evs) {
+
+ request = new XDomainRequest();
+ this._request = request;
+
+ // set handlers
+ request.onprogress = function(){
+ request._ready = true;
+ evs._onxhrdata();
+ };
+
+ request.onload = function(){
+ this._loaded = true;
+ evs._onxhrdata();
+ };
+
+ request.onerror = function(){
+ this._failed = true;
+ evs.readyState = evs.CLOSED;
+ evs.dispatchEvent('error', {
+ type: 'error',
+ data: "XDomainRequest error"
+ });
+ };
+
+ request.ontimeout = function(){
+ this._failed = true;
+ evs.readyState = evs.CLOSED;
+ evs.dispatchEvent('error', {
+ type: 'error',
+ data: "XDomainRequest timed out"
+ });
+ };
+
+ // XDomainRequest does not allow setting custom headers
+ // If EventSource has enabled the use of GET arguments
+ // we add parameters to URL so that server can adapt the stream...
+ var reqGetArgs = {};
+ if (evs.getArgs) {
+
+ // copy evs.getArgs in reqGetArgs
+ var defaultArgs = evs.getArgs;
+ for (var key in defaultArgs) {
+ if (defaultArgs.hasOwnProperty(key)){
+ reqGetArgs[key] = defaultArgs[key];
+ }
+ }
+ if (evs.lastEventId){
+ reqGetArgs['evs_last_event_id'] = evs.lastEventId;
+ }
+ }
+ // send the request
+
+ request.open('GET', evs.urlWithParams(evs.URL,reqGetArgs));
+ request.send();
+ };
+
+ EventSource.prototype.XHR.prototype = {
+
+ useXDomainRequest: true,
+
+ _request: null,
+
+ _ready: false, // true when progress events are dispatched
+
+ _loaded: false, // true when request has been loaded
+
+ _failed: false, // true if when request is in error
+
+ isReady: function() {
+
+ return this._request._ready;
+ },
+
+ isDone: function() {
+
+ return this._request._loaded;
+ },
+
+ hasError: function() {
+
+ return this._request._failed;
+ },
+
+ getBuffer: function() {
+
+ var rv = '';
+ try {
+ rv = this._request.responseText || '';
+ }
+ catch (e){}
+ return rv;
+ },
+
+ abort: function() {
+
+ if ( this._request){
+ this._request.abort();
+ }
+ }
+ };
+ }
+
+ function MessageEvent(type, data, origin, lastEventId) {
+
+ this.bubbles = false;
+ this.cancelBubble = false;
+ this.cancelable = false;
+ this.data = data || null;
+ this.origin = origin || '';
+ this.lastEventId = lastEventId || '';
+ this.type = type || 'message';
+ }
+
+ function isOldIE () {
+
+ //return true if we are in IE8 or IE9
+ return (window.XDomainRequest && (window.XMLHttpRequest && new XMLHttpRequest().responseType === undefined)) ? true : false;
+ }
+
+ global[evsImportName] = EventSource;
+})(this);
diff --git a/js/vendor/image-scale/LICENSE b/js/vendor/image-scale/LICENSE
new file mode 100644
index 00000000..2e89bce3
--- /dev/null
+++ b/js/vendor/image-scale/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 GestiXi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/js/vendor/image-scale/README.md b/js/vendor/image-scale/README.md
new file mode 100644
index 00000000..85239ba3
--- /dev/null
+++ b/js/vendor/image-scale/README.md
@@ -0,0 +1,210 @@
+Image Scale
+===========
+
+**Scale images to fit or fill any target container via two simple properties: scale and align.**
+
+*This plugin is greatly inspired from Sproutcore SC.ImageView.*
+
+------
+
+## Installation
+
+image-scale depends on jQuery. To use it, include this in your page :
+
+ <script src="jquery.js" type="text/javascript"></script>
+ <script src="image-scale.js" type="text/javascript"></script>
+
+------
+
+## Usage
+
+If you want to identify the images that you want to scale, you can add a class to them. In this example we are adding a class call `scale`.
+
+You can also set the `data-scale` and `data-align` attributes directly to the images if you want to override the default setting.
+
+ <div class="image-container">
+ <img class="scale" data-scale="best-fit-down" data-align="center" src="img/example.jpg">
+ </div>
+
+Now add this JavaScript code to your page :
+
+ $(function() {
+ $("img.scale").imageScale();
+ });
+
+You're done.
+
+------
+
+## Properties
+
+
+### scale
+
+Determines how the image will scale to fit within its containing space. Possible values:
+
+* **fill** - stretches or compresses the source image to fill the target frame
+* **best-fill** - fits the shortest side of the source image within the target frame while maintaining the original aspect ratio
+* **best-fit** - fits the longest edge of the source image within the target frame while maintaining the original aspect ratio
+* **best-fit-down** - same as *best-fit* but will not stretch the source if it is smaller than the target
+* **none** - the source image is left unscaled
+
+<!-- -->
+
+ Type: String
+ Default: best-fill
+
+
+### align
+
+Align the image within its frame. Possible values:
+
+* **left**
+* **right**
+* **center**
+* **top**
+* **bottom**
+* **top-left**
+* **top-right**
+* **bottom-left**
+* **bottom-right**
+
+<!-- -->
+
+ Type: String
+ Default: center
+
+
+### parent
+
+A jQuery Object against which the image size will be calculated.
+If null, the parent of the image will be used.
+
+ Type: jQuery Object
+ Default: null
+
+
+### hideParentOverflow
+
+A boolean determining if the parent should hide its overflow.
+
+ Type: Boolean
+ Default: true
+
+
+### fadeInDuration
+
+A duration in milliseconds determining how long the fadeIn animation
+will run when your image is scale for the firstTime.
+
+Set it to 0 if you don't want any animation.
+
+ Type: Number or String
+ Default: 0
+
+
+### rescaleOnResize
+
+A boolean indicating if the image size should be rescaled when the window is resized.
+
+The window size is checked using requestAnimationFrame for good performance.
+
+ Type: Boolean
+ Default: false
+
+
+### didScale
+
+A function that will be call each time the receiver is scaled.
+
+Example:
+
+ $images.imageScale({
+ didScale: function(firstTime, options) {
+ console.log('did scale img: ', this.element);
+ }
+ });
+
+<!-- -->
+
+ Type: Function
+ Parameters:
+ - firstTime {Boolean} true if the image was scale for the first time.
+ - options {Object} the options passed to the scale method.
+
+
+### debug
+
+A number indicating the debug level :
+
+0. silent
+1. error
+2. error & warning
+3. error & warning & notice
+
+<!-- -->
+
+ Type: Number
+ Default: 0
+
+------
+
+## Methods
+
+
+### scale
+
+Main method. Used to scale the images.
+
+When `rescaleOnResize` is set to true, this method is executed each time the
+windows size changes.
+
+If `rescaleOnResize` is set to false, you may want to call it manually. Here is an
+example on how you should do it:
+
+ $image.imageScale('scale');
+
+
+### destroy
+
+Removes the data for the element.
+
+Here is an example on how you can call the destroy method:
+
+ $image.imageScale('destroy');
+
+
+------
+
+## Demo
+
+See it in action on our [home page](https://www.gestixi.com).
+
+
+You can also check out the Sproutcore [Automatic Image Scaling demo](http://showcase.sproutcore.com/#demos/Automatic%20Image%20Scaling) to understand the difference between all the different options.
+
+
+------
+
+## Size
+
+Original Size: 3.91KB gzipped (13.99KB uncompressed)
+
+Compiled Size: **1.8KB gzipped** (4.42KB uncompressed)
+
+
+------
+
+## Author
+
+**Nicolas Badia**
+
++ [https://twitter.com/@nicolas_badia](https://twitter.com/@nicolas_badia)
++ [https://github.com/nicolasbadia](https://github.com/nicolasbadia)
+
+
+------
+
+## Copyright and license
+
+Copyright 2013-2015 GestiXi under [The MIT License (MIT)](LICENSE).
diff --git a/js/vendor/image-scale/image-scale.js b/js/vendor/image-scale/image-scale.js
new file mode 100644
index 00000000..bbe182f7
--- /dev/null
+++ b/js/vendor/image-scale/image-scale.js
@@ -0,0 +1,494 @@
+// ==========================================================================
+// Project: Image Scale
+// Description: Scale images to fit or fill any target size via two simple properties: scale and align.
+// Copyright: ©2012-2015 GestiXi
+// License: Licensed under the MIT license (see LICENCE)
+// Version: 2.1
+// Author: Nicolas BADIA
+// ==========================================================================
+
+!function($) { "use strict";
+
+ // ..........................................................
+ // IMAGE SCALE PLUGIN DEFINITION
+ //
+
+ $.fn.imageScale = function( options ) {
+
+ return this.each(function() {
+ var that = this,
+ $this = $(this),
+ data = $this.data('imageScale'),
+ $img = this.tagName === 'IMG' ? $this : $this.find("img");
+
+ if (!data) {
+ var didLoad = $img[0].complete,
+ formattedOpt = $.extend({}, $.fn.imageScale.defaults, typeof options == 'object' && options),
+
+ loadFunc = function() {
+ $this.data('imageScale', (data = new ImageScale(that, formattedOpt)));
+
+ data.scale(true, formattedOpt);
+ };
+
+ if (didLoad) {
+ loadFunc.apply($this[0]);
+ }
+ else {
+ $img.on("load", loadFunc).attr("src", $img.attr("src"));
+ }
+ }
+ else {
+ if (typeof options == 'string') data[options]();
+ else if (typeof options == 'object') {
+ var method = options.method || 'scale';
+ data[method](false, options);
+ }
+ else data.scale();
+ }
+ })
+ }
+
+ $.fn.imageScale.defaults = {
+ /**
+ Determines how the image will scale to fit within its containing space. Possible values:
+
+ * **fill** - stretches or compresses the source image to fill the target frame
+ * **best-fill** - fits the shortest side of the source image within the target frame while maintaining the original aspect ratio
+ * **best-fit** - fits the longest edge of the source image within the target frame while maintaining the original aspect ratio
+ * **best-fit-down** - same as *best-fit* but will not stretch the source if it is smaller than the target
+ * **none** - the source image is left unscaled
+
+ @type String
+ @default best-fill
+ @since Version 1.2
+ */
+ scale: 'best-fill',
+
+ /**
+ Align the image within its frame. Possible values:
+
+ * **left**
+ * **right**
+ * **center**
+ * **top**
+ * **bottom**
+ * **top-left**
+ * **top-right**
+ * **bottom-left**
+ * **bottom-right**
+
+ @type String
+ @default center
+ @since Version 1.2
+ */
+ align: 'center',
+
+ /**
+ A jQuery Object against which the image size will be calculated.
+ If null, the parent of the image will be used.
+
+ @type jQuery Object
+ @default null
+ @since Version 1.0
+ */
+ parent: null,
+
+ /**
+ A boolean determining if the parent should hide its overflow.
+
+ @type Boolean
+ @default true
+ @since Version 1.0
+ */
+ hideParentOverflow: true,
+
+ /**
+ A duration in milliseconds determining how long the fadeIn animation will run when your image is scale for the firstTime.
+
+ Set it to 0 if you don't want any animation.
+
+ @type Number|String
+ @default 0
+ @since Version 1.1
+ */
+ fadeInDuration: 0,
+
+ /**
+ A boolean indicating if the image size should be rescaled when the window is resized.
+
+ The window size is checked using requestAnimationFrame for good performance.
+
+ @type Boolean
+ @default false
+ @since Version 1.0
+ */
+ rescaleOnResize: false,
+
+ /**
+ A function that will be call each time the receiver is scaled.
+
+ Example:
+
+ $images.imageScale({
+ didScale: function() {
+ console.log('did scale img: ', this.element);
+ }
+ });
+
+ @type Function
+ @param firstTime {Boolean} true if the image was scale for the first time.
+ @param options {Object} the options passed to the scale method.
+ @since Version 2.0
+ */
+ didScale: function(firstTime, options) {},
+
+ /**
+ A number indicating the log level :
+
+ 0: silent
+ 1: error
+ 2: error & warning
+ 3: error & warning & notice
+
+ @type Number
+ @default 0
+ @since Version 1.0
+ */
+ logLevel: 0
+ }
+
+ // ..........................................................
+ // IMAGE SCALE PUBLIC CLASS DEFINITION
+ //
+
+ var ImageScale = function(element, options) {
+ var that = this;
+ that.options = options;
+ that.element = element;
+
+ var $element = that.$element = $(element),
+ $img = that.$img = element.tagName === 'IMG' ? $element : $element.find("img"),
+ img = that.img = $img[0];
+
+ that.src = $img.attr('src');
+
+ that.imgWidth = img.naturalWidth || img.width;
+ that.imgHeight = img.naturalHeight || img.height;
+
+ var $parent = that.$parent = options.parent?options.parent:$($element.parent()[0]);
+ that.parent = $parent[0];
+
+ // Fixes: https://github.com/gestixi/image-scale/issues/1
+ if ($parent.css('position') === 'static') {
+ $parent.css('position', 'relative');
+ }
+
+ if (options.rescaleOnResize) {
+ $(window).resize(function(e) { that.scheduleScale(); });
+ }
+ }
+
+ $.fn.imageScale.Constructor = ImageScale;
+
+ ImageScale.prototype = {
+
+ NONE: "none",
+ FILL: "fill",
+ BEST_FILL: "best-fill",
+ BEST_FIT: "best-fit",
+ BEST_FIT_DOWN_ONLY: "best-fit-down",
+
+ ALIGN_LEFT: 'left',
+ ALIGN_RIGHT: 'right',
+ ALIGN_CENTER: 'center',
+ ALIGN_TOP: 'top',
+ ALIGN_BOTTOM: 'bottom',
+ ALIGN_TOP_LEFT: 'top-left',
+ ALIGN_TOP_RIGHT: 'top-right',
+ ALIGN_BOTTOM_LEFT: 'bottom-left',
+ ALIGN_BOTTOM_RIGHT: 'bottom-right',
+
+ constructor: ImageScale,
+
+ /**
+ The initial element.
+
+ @type DOM Element
+ */
+ element: null,
+
+ /**
+ The passed options.
+
+ @type Object
+ */
+ options: null,
+
+ /**
+ Main method. Used to scale the images.
+
+ When `rescaleOnResize` is set to true, this method is executed each time the
+ windows size changes.
+
+ If `rescaleOnResize` is set to false, you may want to call it manually. Here is an
+ example on how you should do it:
+
+ $image.imageScale('scale');
+
+
+ @param {Boolean} firstTime
+ */
+ scale: function(firstTime, opt) {
+ if (this._isDestroyed || this._canScale === false) return;
+
+ var that = this,
+ options = this.options,
+ $parent = this.$parent,
+ element = this.element,
+ $element = this.$element,
+ img = this.img,
+ $img = this.$img;
+
+ if (firstTime) {
+ if (options.hideParentOverflow) {
+ $parent.css({ overflow: 'hidden' });
+ }
+ }
+ else {
+ // If the source of the image has changed
+ if (this.src !== $img.attr('src')) {
+ this.destroy();
+ $element.data('imageScale', null);
+ $element.imageScale(options);
+ return;
+ }
+ }
+
+ this._didScheduleScale = false;
+
+ if (options.rescaleOnResize && !opt) {
+ if (!this._needUpdate(this.parent)) return;
+ }
+ opt = opt ? opt : {};
+
+ var transition = opt.transition;
+ if (transition) {
+ this._canScale = false;
+ $element.css('transition', 'all '+transition+'ms');
+
+ setTimeout(function() {
+ that._canScale = null;
+ $element.css('transition', 'null');
+ }, transition);
+ }
+
+ var destWidth = opt.destWidth ? opt.destWidth : $parent.outerWidth(),
+ destHeight = opt.destHeight ? opt.destHeight : $parent.outerHeight(),
+
+ destInnerWidth = opt.destWidth ? opt.destWidth : $parent.innerWidth(),
+ destInnerHeight = opt.destHeight ? opt.destHeight : $parent.innerHeight(),
+
+ widthOffset = destWidth - destInnerWidth,
+ heightOffset = destHeight - destInnerHeight,
+
+ scaleData = $element.attr('data-scale'),
+ alignData = $element.attr('data-align'),
+
+ scale = scaleData?scaleData:options.scale,
+ align = alignData?alignData:options.align,
+
+ fadeInDuration = options.fadeInDuration;
+
+ if (!scale) {
+ if (options.logLevel > 2) {
+ console.log("imageScale - DEBUG NOTICE: The scale property is null.", element);
+ }
+ return;
+ }
+
+ if (this._cacheDestWidth === destWidth && this._cacheDestHeight === destHeight) {
+ if (options.logLevel > 2) {
+ console.log("imageScale - DEBUG NOTICE: The parent size hasn't changed: dest width: '"+destWidth+"' - dest height: '"+destHeight+"'.", element);
+ }
+ }
+
+ var sourceWidth = this.imgWidth,
+ sourceHeight = this.imgHeight;
+
+ if (!(destWidth && destHeight && sourceWidth && sourceHeight)) {
+ if (options.logLevel > 0) {
+ console.error("imageScale - DEBUG ERROR: The dimensions are incorrect: source width: '"+sourceWidth+"' - source height: '"+sourceHeight+"' - dest width: '"+destWidth+"' - dest height: '"+destHeight+"'.", element);
+ }
+ return;
+ }
+
+ this._cacheDestWidth = destWidth;
+ this._cacheDestHeight = destHeight;
+
+ var layout = this._innerFrameForSize(scale, align, sourceWidth, sourceHeight, destWidth, destHeight);
+
+ if (widthOffset) layout.x -= widthOffset/2;
+ if (heightOffset) layout.y -= heightOffset/2;
+
+ $element.css({ position: 'absolute', top: layout.y+'px', left: layout.x+'px', width: layout.width+'px', height: layout.height+'px', 'max-width': 'none' });
+
+ if (firstTime && fadeInDuration) {
+ $element.css({ display: 'none' });
+ $element.fadeIn(fadeInDuration);
+ }
+
+ options.didScale.call(this, firstTime, opt);
+ },
+
+ /**
+ Removes the data for the element.
+
+ Here is an example on how you can call the destroy method:
+
+ $image.imageScale('destroy');
+
+ */
+ destroy: function() {
+ this._isDestroyed = true;
+ this.$element.removeData('imageScale');
+ },
+
+ /**
+ @private
+
+ Returns a frame (x, y, width, height) fitting the source size (sourceWidth & sourceHeight) within the
+ destination size (destWidth & destHeight) according to the align and scale properties.
+
+ @param {String} scale
+ @param {String} align
+ @param {Number} sourceWidth
+ @param {Number} sourceHeight
+ @param {Number} destWidth
+ @param {Number} destHeight
+ @returns {Object} the inner frame with properties: { x: value, y: value, width: value, height: value }
+ */
+ _innerFrameForSize: function(scale, align, sourceWidth, sourceHeight, destWidth, destHeight) {
+ var scaleX,
+ scaleY,
+ result;
+
+ // Fast path
+ result = { x: 0, y: 0, width: destWidth, height: destHeight };
+ if (scale === this.FILL) return result;
+
+ // Determine the appropriate scale
+ scaleX = destWidth / sourceWidth;
+ scaleY = destHeight / sourceHeight;
+
+ switch (scale) {
+ case this.BEST_FIT_DOWN_ONLY:
+ if (scale !== this.BEST_FIT_DOWN_ONLY && this.options.logLevel > 1) {
+ console.warn("imageScale - DEBUG WARNING: The scale '"+scale+"' was not understood.");
+ }
+
+ if ((sourceWidth > destWidth) || (sourceHeight > destHeight)) {
+ scale = scaleX < scaleY ? scaleX : scaleY;
+ } else {
+ scale = 1.0;
+ }
+ break;
+ case this.BEST_FIT:
+ scale = scaleX < scaleY ? scaleX : scaleY;
+ break;
+ case this.NONE:
+ scale = 1.0;
+ break;
+ //case this.BEST_FILL:
+ default:
+ scale = scaleX > scaleY ? scaleX : scaleY;
+ break;
+ }
+
+ sourceWidth *= scale;
+ sourceHeight *= scale;
+ result.width = Math.round(sourceWidth);
+ result.height = Math.round(sourceHeight);
+
+ // Align the image within its frame
+ switch (align) {
+ case this.ALIGN_LEFT:
+ result.x = 0;
+ result.y = (destHeight / 2) - (sourceHeight / 2);
+ break;
+ case this.ALIGN_RIGHT:
+ result.x = destWidth - sourceWidth;
+ result.y = (destHeight / 2) - (sourceHeight / 2);
+ break;
+ case this.ALIGN_TOP:
+ result.x = (destWidth / 2) - (sourceWidth / 2);
+ result.y = 0;
+ break;
+ case this.ALIGN_BOTTOM:
+ result.x = (destWidth / 2) - (sourceWidth / 2);
+ result.y = destHeight - sourceHeight;
+ break;
+ case this.ALIGN_TOP_LEFT:
+ result.x = 0;
+ result.y = 0;
+ break;
+ case this.ALIGN_TOP_RIGHT:
+ result.x = destWidth - sourceWidth;
+ result.y = 0;
+ break;
+ case this.ALIGN_BOTTOM_LEFT:
+ result.x = 0;
+ result.y = destHeight - sourceHeight;
+ break;
+ case this.ALIGN_BOTTOM_RIGHT:
+ result.x = destWidth - sourceWidth;
+ result.y = destHeight - sourceHeight;
+ break;
+ default: // this.ALIGN_CENTER
+ if (align !== this.ALIGN_CENTER && this.options.logLevel > 1) {
+ console.warn("imageScale - DEBUG WARNING: The align '"+align+"' was not understood.");
+ }
+ result.x = (destWidth / 2) - (sourceWidth / 2);
+ result.y = (destHeight / 2) - (sourceHeight / 2);
+ }
+
+ return result;
+ },
+
+ /**
+ @private
+
+ Determines if the windows size has changed since the last update.
+
+ @returns {Boolean}
+ */
+ _needUpdate: function(parent) {
+ var size = parent.clientHeight + ' ' + parent.clientWidth;
+
+ if (this._lastParentSize !== size) {
+ this._lastParentSize = size;
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ @private
+
+ Schedule a scale update.
+ */
+ scheduleScale: function() {
+ if (this._didScheduleScale) return;
+
+ if (window.requestAnimationFrame) {
+ var that = this;
+ this._didScheduleScale = true;
+ // setTimeout important when resizing down if the scrollbar were visible
+ requestAnimationFrame(function() { setTimeout(function() { that.scale(); }, 0); });
+ }
+ else {
+ this.scale();
+ }
+ }
+ }
+}(window.jQuery);