[med-svn] r23870 - in trunk/packages/orthanc-webviewer: tags tags/2.2-3 tags/2.2-3/JS tags/2.2-3/JS/cornerstone-0.8.4 tags/2.2-3/JS/js-url-1.8.6 tags/2.2-3/JS/jsPanel-2.3.3 tags/2.2-3/JS/jsPanel-2.3.3/fonts tags/2.2-3/JS/jsPanel-2.3.3/images tags/2.2-3/JS/pako-0.2.5 tags/2.2-3/configuration tags/2.2-3/patches tags/2.2-3/source tags/2.2-3/upstream trunk/debian

Sebastien Jodogne jodogne-guest at moszumanska.debian.org
Mon Jul 10 10:30:56 UTC 2017


Author: jodogne-guest
Date: 2017-07-10 10:30:56 +0000 (Mon, 10 Jul 2017)
New Revision: 23870

Added:
   trunk/packages/orthanc-webviewer/tags/2.2-3/
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/README
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/LICENSE
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/README.md
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/cornerstone.css
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/cornerstone.js
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/js-url-1.8.6/
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/js-url-1.8.6/README.md
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/js-url-1.8.6/url.jquery.json
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/js-url-1.8.6/url.js
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/LICENSE.TXT
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.eot
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.svg
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.ttf
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.woff
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.woff2
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/close-20-333.png
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/close-20.png
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/icon-sprite-16x16.jpg
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/icon-sprite-20x20.jpg
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/icon-sprite-32x32.jpg
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/resize-handle.png
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/ui-icons_454545_256x240.png
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jquery.jspanel.css
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jquery.jspanel.js
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jquery.ui.touch-punch.js
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jsglyph.css
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/mobile-detect.js
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/HISTORY.md
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/LICENSE
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/README.md
   trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/pako_inflate.js
   trunk/packages/orthanc-webviewer/tags/2.2-3/README.Debian
   trunk/packages/orthanc-webviewer/tags/2.2-3/changelog
   trunk/packages/orthanc-webviewer/tags/2.2-3/compat
   trunk/packages/orthanc-webviewer/tags/2.2-3/configuration/
   trunk/packages/orthanc-webviewer/tags/2.2-3/configuration/webviewer.json
   trunk/packages/orthanc-webviewer/tags/2.2-3/control
   trunk/packages/orthanc-webviewer/tags/2.2-3/copyright
   trunk/packages/orthanc-webviewer/tags/2.2-3/install
   trunk/packages/orthanc-webviewer/tags/2.2-3/patches/
   trunk/packages/orthanc-webviewer/tags/2.2-3/patches/cmake
   trunk/packages/orthanc-webviewer/tags/2.2-3/patches/series
   trunk/packages/orthanc-webviewer/tags/2.2-3/patches/sqlite
   trunk/packages/orthanc-webviewer/tags/2.2-3/postinst
   trunk/packages/orthanc-webviewer/tags/2.2-3/postrm
   trunk/packages/orthanc-webviewer/tags/2.2-3/rules
   trunk/packages/orthanc-webviewer/tags/2.2-3/source.lintian-overrides
   trunk/packages/orthanc-webviewer/tags/2.2-3/source/
   trunk/packages/orthanc-webviewer/tags/2.2-3/source/format
   trunk/packages/orthanc-webviewer/tags/2.2-3/source/include-binaries
   trunk/packages/orthanc-webviewer/tags/2.2-3/upstream/
   trunk/packages/orthanc-webviewer/tags/2.2-3/upstream/metadata
   trunk/packages/orthanc-webviewer/tags/2.2-3/watch
Modified:
   trunk/packages/orthanc-webviewer/trunk/debian/changelog
Log:
orthanc-webviewer 2.2-3

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/README
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/README	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/README	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,13 @@
+Source of the JavaScript libraries that are required for Orthanc Web Viewer:
+
+* Cornerstone by Chris Hafey (MIT license):
+  https://github.com/chafey/cornerstone
+
+* jsPanel by Stefan Sträßer (MIT license):
+  http://jspanel.de/
+
+* js-url by Websanova (MIT license):
+  https://github.com/websanova/js-url
+
+* pako by Andrey Tupitsin and Vitaly Puzrin (MIT license):
+  https://github.com/nodeca/pako

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/LICENSE
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/LICENSE	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/LICENSE	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Chris Hafey (chafey at gmail.com)
+
+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

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/README.md
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/README.md	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/README.md	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,252 @@
+Cornerstone Core
+================
+
+Cornerstone is an open source project with a goal to deliver a complete web based medical imaging platform.  This
+repository contains the Cornerstone Core component which is a lightweight JavaScript library for displaying
+medical images in modern web browsers that support the HTML5 canvas element.
+Cornerstone Core is not meant to be a complete application itself, but instead a component
+that can be used as part of larger more complex applications.  See the
+[CornerstoneDemo](http://chafey.github.io/cornerstoneDemo/) for an example of using the various Cornerstone
+libraries to build a simple study viewer.
+
+Cornerstone Core is agnostic to the actual container used to store image pixels as well as the transport mechanism
+used to get the image data.  In fact, Cornerstone Core itself has no ability to read/parse or load images and instead
+depends on one or more [ImageLoaders](https://github.com/chafey/cornerstone/wiki/ImageLoader) to function.
+The goal here is to avoid constraining developers to work within a single container and transport (e.g. DICOM) since
+images are stored in a variety of formats (including proprietary).  By providing flexibility with respect to the
+container and transport, the highest performance image display may be obtained as no conversion to an alternate
+container or transport is required.  It is hoped that developers feel empowered to load images from any type of image
+container using any kind of transport.  See the
+[CornerstoneWADOImageLoader](https://github.com/chafey/cornerstoneWADOImageLoader) project for an example
+of a DICOM WADO based Image Loader.
+
+Cornerstone Core is agnostic to the exact interaction paradigm being used.  It does not include any mouse, touch or
+keyboard bindings to manipulate the various image properties such as scale, translation or ww/wc.  The goal here
+is to avoid constraining developers using this library to fit into a given ui paradigm.  It is hoped that developers
+are empowered to create new paradigms possibly using new input mechanisms to interact with medical images (e.g.
+[Kinect](http://en.wikipedia.org/wiki/Kinect) or [Accelerometer](http://en.wikipedia.org/wiki/Accelerometer).
+Cornerstone does provide a set of API's allowing manipulation of the image properties via javascript.
+See the [CornerstoneTools](https://github.com/chafey/cornerstoneTools) library for an example of common tools built on top of
+Cornerstone.
+
+Community
+---------
+
+Have questions?  Try posting on our [google groups forum](https://groups.google.com/forum/#!forum/cornerstone-platform).
+
+Live Examples
+---------------
+The best way to see the power of this library is to actually see it in use.
+
+[Click here for a list of examples of using the Cornerstone library.](https://rawgit.com/chafey/cornerstone/master/example/index.html)
+
+Install
+-------
+
+Get a packaged source file:
+
+* [cornerstone.js](https://raw.githubusercontent.com/chafey/cornerstone/master/dist/cornerstone.js)
+* [cornerstone.min.js](https://raw.githubusercontent.com/chafey/cornerstone/master/dist/cornerstone.min.js)
+
+Or install via [Bower](http://bower.io/):
+
+> bower install cornerstone
+
+
+Key Features
+------------
+
+ * HTML5/Javascript based library to easily add interactive medical images to web applications
+ * Serves as a foundation to build more complex medical imaging applications from - enterprise viewer, report viewer, etc.
+ * Supports all HTML5 based browsers including mobile, tablet and desktop
+ * Displays all common medical image formats (e.g. 8 bit grayscale, 16 bit grayscale, RGB color)
+ * High performance image display
+ * Retrieval of images from different systems with different protocols via Image Loader plugin design
+ * API support for changing viewport properties (e.g. ww/wc, zoom, pan, invert)
+
+Build System
+============
+
+This project uses grunt to build the software.
+
+Pre-requisites:
+---------------
+
+NodeJs - [click to visit web site for installation instructions](http://nodejs.org).
+
+grunt-cli
+
+> npm install -g grunt-cli
+
+bower
+
+> npm install -g bower
+
+Common Tasks
+------------
+
+Update dependencies (after each pull):
+> npm install
+
+> bower install
+
+Running the build:
+> grunt
+
+Automatically running the build and unit tests after each source change:
+> grunt watch
+
+Links
+=====
+
+[View the wiki for documentation on the concepts and APIs](https://github.com/chafey/cornerstone/wiki)
+
+[View Roadmap](docs/roadmap.md)
+
+[View Backlog](docs/backlog.md)
+
+[comp.protocols.dicom thread](https://groups.google.com/forum/#!topic/comp.protocols.dicom/_2fMh69GdAM)
+
+[Trello](https://trello.com/b/tGTDIyt4/cornerstone)
+
+[CornerstoneTools](https://github.com/chafey/cornerstoneTools) - A library of common tools that can be used with Cornerstone
+
+[CornerstoneWADOImageLoader ](https://github.com/chafey/cornerstoneWADOImageLoader) - A Cornerstone Image Loader that works with WADO
+
+[dicomParser ](https://github.com/chafey/dicomParser) - A JavaScript library designed to parse DICOM for web browsers
+
+Code Contributors
+=================
+
+I welcome pull requests, please see FAQ below for guidance on this.
+
+* @simonmd - CSS improvements in the cornerstoneDemo application
+* @doncharkowsky - The angle tool in cornerstoneTools
+* @prasath-rasterimages - Touch event bindings in cornerstoneTools
+* @jpamburn - Performance optimizations for signed data, fixes for image caching
+* @jmhmd - for getPixels() implementation
+* @devishree-raster - for flip and rotate implementation
+
+FAQ
+===
+
+_Why did you decide to license this library using the open source MIT license?_
+
+The main reason this library is released as [open source](http://en.wikipedia.org/wiki/Open_source) is
+that I believe that medical imaging in particular can do a lot more to improve patient outcomes
+but the cost of doing so is prohibitive.  Making this library open source removes the cost barrier and will
+hopefully usher in a new set of medical imaging based applications.
+
+The old adage [a picture is worth a thousand words](http://en.wikipedia.org/wiki/A_picture_is_worth_a_thousand_words)
+is very true in medical imaging.  When a patient is going through a disease process, they often face fear
+and confusion.  Medical terminology amplifies these issues as it is hard to understand and therefore
+disempowering.  Medical imaging allows a mysterious health issue to be visualized and therefore brings a
+level of understanding that just can't be accomplished via textual information found in lab or radiology
+reports.  By helping a patient (and its supporting friends/family) connect with the disease visually through
+images, it is believed that fear, anxiety and confusion will all be reduced which
+will increase optimism and therefore patient outcomes.
+
+It is my hope that this library be used to build a variety of applications and experiences
+to deliver on this vision.  The MIT license allows this library to be used in any type of application - personal,
+open source and commercial and is therefore appropriate to support this vision.  If you are reading this,
+I hope you can join me in this mission as there is still a lot to be done.
+
+_Why doesn't Cornerstone natively support the display of DICOM images?_
+
+While DICOM has support for just about every type of medical image, there are many cases where medical images
+are not stored in DICOM format.  In many cases, a PACS may receive DICOM images but store them in a proprietary
+format on disk.  In this case, it can be faster to access images by having an image loader that works with
+a proprietary PACS interface that would not require conversion from the proprietary format into a standard format
+like DICOM.  Another example of this is is dermatology where images are often taken using standard
+digital cameras and are stored as JPEG not DICOM.
+
+The main reason this library is not based around DICOM is that it wants to reach the widest possible adoption
+and that will be accomplished by supporting as many types of image containers and transports possible.
+Another side effect of this approach is that the code base is smaller and easier to understand since
+it is focused on doing exactly one thing.  That being said, it is is expected that the majority of images
+displayed using this library will have originated as DICOM images.  It is therefore important to make sure
+that there are no limitations with respect to displaying the different types of DICOM images and have robust
+supporting libraries for DICOM.  Separate libraries to add DICOM specific support already exist, check out the
+[CornerstoneWADOImageLoader](https://github.com/chafey/cornerstoneWADOImageLoader) library and
+the (dicomParser)[https://github.com/chafey/dicomParser] library.
+
+_Why doesn't Cornerstone include basic tools like ww/wc using the mouse?_
+
+There is no standard for user interaction in medical imaging and a wide variety of interaction paradigms exist.
+For example, one medical imaging application may use the left mouse button to adjust ww/wc and another may use the
+right mouse button.  The main reason this library does not include tools is that it wants to reach the widest possible
+adoption and that will only be accomplished by making any interaction paradigm possible.  No tools
+are therefore provided with this library allowing users of the library to choose
+whatever interaction paradigm they like.  It is also hoped that this approach will make it easier for developers
+to experiment with new user input mechanisms like [Kinect](http://en.wikipedia.org/wiki/Kinect) or
+[Accelerometer](http://en.wikipedia.org/wiki/Accelerometer).  Another side effect of this
+approach is that the code base is smaller and easier to understand since it is focused on doing exactly one
+thing.  Tools are provided using the separate [CornerstoneTools](https://github.com/chafey/cornerstoneTools)
+if desired.
+
+_Why does this library require HTML5 canvas when IE8 is the main browser used in healthcare?_
+
+The fact that IE8 is the most popular commonly used web browser in healthcare right now is a temporary
+situation.  It is expected that 50% of the industry will have HTML5 based web browsers deployed by the end
+of 2015 and 90% by 2017.  The library made a tradeoff to focus on the future platform to keep the code base
+simple and avoid compromises related to older browser technology.  Note that it may be possible to get
+this library to work on IE8 using [excanvas](https://code.google.com/p/explorercanvas/) but I haven't tried
+it yet.
+
+_Why doesn't this library support stacks of images?_
+
+Images stack functionality such as a CT series or MRI series can actually be quite complex.  Regardless of
+what stack functionality is desired, all stacks ultimately need to be able to display a single image and that
+is what this library is focused on doing.  Stack functionality is therefore pushed up to a higher layer.  The
+[CornerstoneTools](https://github.com/chafey/cornerstoneTools) contains stack functionality and is a good place
+to look to see how various stack related functionality is implemented.
+
+_How do you envision this library supporting 3D functionality such as MPR, MIP and VR?_
+
+This library would be responsible for displaying the rendered image to the user.  The rendering of the
+3D image would be done by some other library - perhaps on the server side.  This library is purely 2D and has
+no knowledge of 3D image space.  It will probably make sense to have several layers on top of this library
+to provide 3D functionality.  For example, one layer that has a 3D viewport with properties such as transformation
+matrix, slice thickness, transfer function/LUT, segmentation masks, etc.  And another 3D tools layer that provides
+various tools on top of the 3d viewport (rotate, zoom, segment, scroll, etc).  I do have a working 3D server that
+is integrated with cornerstone but am keeping the code closed for now (this may change in the future - TBD).
+
+_Why did you add jQuery as a dependency?_
+
+Primarily for its custom event handling.
+
+_I would like to contribute code - how do I do this?_
+
+Fork the repository, make your change and submit a pull request.
+
+_Any guidance on submitting changes?_
+
+While I do appreciate code contributions, I will not merge it unless it meets the following criteria:
+
+* Functionality is appropriate for the repository.  Consider posting on the forum if you are not sure
+* Code quality is acceptable.  I don't have coding standards defined, but make sure it passes jshint and looks like
+   the rest of the code in the repository.
+* Quality of design is acceptable.  This is a bit subjective so you should consider posting on the forum for specific guidance
+
+I will provide feedback on your pull request if it fails to meet any of the above.
+
+Please consider separate pull requests for each feature as big pull requests are very time consuming to understand.
+It is highly probably that I will reject a large pull request due to the time it would take to comprehend.
+
+_Will you add feature XYZ for me?_
+
+If it is in the roadmap, I intend to implement it some day - probably when I actually need it.  If you really need
+something now and are willing to pay for it, try posting on the cornerstone platform google group
+
+_How mature is Cornerstone?_
+
+Each repository is at a different level of maturity.  There are at least 50 image viewer projects using Cornerstone
+and the feedback has been consistently strong about the architecture, design and quality.  The cornerstoneTools library
+is the least mature from a framework and breadth of functionality perspective - but the implemented features work well.
+
+Copyright
+=========
+
+Copyright 2015 Chris Hafey [chafey at gmail.com](mailto:chafey at gmail.com)
+
+

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/cornerstone.css
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/cornerstone.css	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/cornerstone.css	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,17 @@
+/*! cornerstone - v0.8.4 - 2015-09-12 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstone */
+.cornerstone-enabled-image {
+
+    /* prevent text selection from occurring when dragging the mouse on the div */
+    /* http://stackoverflow.com/questions/826782/css-rule-to-disable-text-selection-highlighting */
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+
+    /* force the cursor to always be the default arrow.  This prevents it from changing to an ibar when it is
+       over HTML based text overlays (four corners */
+    cursor:default;
+}
+

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/cornerstone.js
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/cornerstone.js	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/cornerstone-0.8.4/cornerstone.js	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,2022 @@
+/*! cornerstone - v0.8.4 - 2015-09-12 | (c) 2014 Chris Hafey | https://github.com/chafey/cornerstone */
+if(typeof cornerstone === 'undefined'){
+    cornerstone = {
+        internal : {},
+        rendering: {}
+    };
+}
+
+(function (cornerstone) {
+
+    "use strict";
+
+    function disable(element) {
+        if(element === undefined) {
+            throw "disable: element element must not be undefined";
+        }
+
+        // Search for this element in this list of enabled elements
+        var enabledElements = cornerstone.getEnabledElements();
+        for(var i=0; i < enabledElements.length; i++) {
+            if(enabledElements[i].element === element) {
+                // We found it!
+
+                // Fire an event so dependencies can cleanup
+                var eventData = {
+                    element : element
+                };
+                $(element).trigger("CornerstoneElementDisabled", eventData);
+
+                // remove the child dom elements that we created (e.g.canvas)
+                enabledElements[i].element.removeChild(enabledElements[i].canvas);
+
+                // remove this element from the list of enabled elements
+                enabledElements.splice(i, 1);
+                return;
+            }
+        }
+    }
+
+    // module/private exports
+    cornerstone.disable = disable;
+
+}(cornerstone));
+/**
+ * This module is responsible for enabling an element to display images with cornerstone
+ */
+(function ($, cornerstone) {
+
+    "use strict";
+
+    /**
+     * sets a new image object for a given element
+     * @param element
+     * @param image
+     */
+    function displayImage(element, image, viewport) {
+        if(element === undefined) {
+            throw "displayImage: parameter element cannot be undefined";
+        }
+        if(image === undefined) {
+            throw "displayImage: parameter image cannot be undefined";
+        }
+
+        var enabledElement = cornerstone.getEnabledElement(element);
+
+        enabledElement.image = image;
+
+        if(enabledElement.viewport === undefined) {
+            enabledElement.viewport = cornerstone.internal.getDefaultViewport(enabledElement.canvas, image);
+        }
+
+        // merge viewport
+        if(viewport) {
+            for(var attrname in viewport)
+            {
+                if(viewport[attrname] !== null) {
+                    enabledElement.viewport[attrname] = viewport[attrname];
+                }
+            }
+        }
+
+        var now = new Date();
+        var frameRate;
+        if(enabledElement.lastImageTimeStamp !== undefined) {
+            var timeSinceLastImage = now.getTime() - enabledElement.lastImageTimeStamp;
+            frameRate = (1000 / timeSinceLastImage).toFixed();
+        } else {
+        }
+        enabledElement.lastImageTimeStamp = now.getTime();
+
+        var newImageEventData = {
+            viewport : enabledElement.viewport,
+            element : enabledElement.element,
+            image : enabledElement.image,
+            enabledElement : enabledElement,
+            frameRate : frameRate
+        };
+
+        $(enabledElement.element).trigger("CornerstoneNewImage", newImageEventData);
+
+        cornerstone.updateImage(element);
+    }
+
+    // module/private exports
+    cornerstone.displayImage = displayImage;
+}($, cornerstone));
+/**
+ * This module is responsible for immediately drawing an enabled element
+ */
+
+(function ($, cornerstone) {
+
+    "use strict";
+
+    /**
+     * Immediately draws the enabled element
+     *
+     * @param element
+     */
+    function draw(element) {
+        var enabledElement = cornerstone.getEnabledElement(element);
+
+        if(enabledElement.image === undefined) {
+            throw "draw: image has not been loaded yet";
+        }
+
+        cornerstone.drawImage(enabledElement);
+    }
+
+    // Module exports
+    cornerstone.draw = draw;
+
+}($, cornerstone));
+/**
+ * This module is responsible for drawing invalidated enabled elements
+ */
+
+(function ($, cornerstone) {
+
+    "use strict";
+
+    /**
+     * Draws all invalidated enabled elements and clears the invalid flag after drawing it
+     */
+    function drawInvalidated()
+    {
+        var enabledElements = cornerstone.getEnabledElements();
+        for(var i=0;i < enabledElements.length; i++) {
+            var ee = enabledElements[i];
+            if(ee.invalid === true) {
+                cornerstone.drawImage(ee);
+            }
+        }
+    }
+
+    // Module exports
+    cornerstone.drawInvalidated = drawInvalidated;
+}($, cornerstone));
+/**
+ * This module is responsible for enabling an element to display images with cornerstone
+ */
+(function (cornerstone) {
+
+    "use strict";
+
+    function enable(element) {
+        if(element === undefined) {
+            throw "enable: parameter element cannot be undefined";
+        }
+
+        var canvas = document.createElement('canvas');
+        element.appendChild(canvas);
+
+        var el = {
+            element: element,
+            canvas: canvas,
+            image : undefined, // will be set once image is loaded
+            invalid: false, // true if image needs to be drawn, false if not
+            data : {}
+        };
+        cornerstone.addEnabledElement(el);
+
+        cornerstone.resize(element, true);
+
+        return element;
+    }
+
+    // module/private exports
+    cornerstone.enable = enable;
+}(cornerstone));
+(function (cornerstone) {
+
+    "use strict";
+
+    function getElementData(el, dataType) {
+        var ee = cornerstone.getEnabledElement(el);
+        if(ee.data.hasOwnProperty(dataType) === false)
+        {
+            ee.data[dataType] = {};
+        }
+        return ee.data[dataType];
+    }
+
+    function removeElementData(el, dataType) {
+        var ee = cornerstone.getEnabledElement(el);
+        delete ee.data[dataType];
+    }
+
+    // module/private exports
+    cornerstone.getElementData = getElementData;
+    cornerstone.removeElementData = removeElementData;
+
+}(cornerstone));
+(function (cornerstone) {
+
+    "use strict";
+
+    var enabledElements = [];
+
+    function getEnabledElement(element) {
+        if(element === undefined) {
+            throw "getEnabledElement: parameter element must not be undefined";
+        }
+        for(var i=0; i < enabledElements.length; i++) {
+            if(enabledElements[i].element == element) {
+                return enabledElements[i];
+            }
+        }
+
+        throw "element not enabled";
+    }
+
+    function addEnabledElement(enabledElement) {
+        if(enabledElement === undefined) {
+            throw "getEnabledElement: enabledElement element must not be undefined";
+        }
+
+        enabledElements.push(enabledElement);
+    }
+
+    function getEnabledElementsByImageId(imageId) {
+        var ees = [];
+        enabledElements.forEach(function(enabledElement) {
+            if(enabledElement.image && enabledElement.image.imageId === imageId) {
+                ees.push(enabledElement);
+            }
+        });
+        return ees;
+    }
+
+    function getEnabledElements() {
+        return enabledElements;
+    }
+
+    // module/private exports
+    cornerstone.getEnabledElement = getEnabledElement;
+    cornerstone.addEnabledElement = addEnabledElement;
+    cornerstone.getEnabledElementsByImageId = getEnabledElementsByImageId;
+    cornerstone.getEnabledElements = getEnabledElements;
+}(cornerstone));
+/**
+ * This module will fit an image to fit inside the canvas displaying it such that all pixels
+ * in the image are viewable
+ */
+(function (cornerstone) {
+
+    "use strict";
+
+    function getImageSize(enabledElement) {
+      if(enabledElement.viewport.rotation === 0 ||enabledElement.viewport.rotation === 180) {
+        return {
+          width: enabledElement.image.width,
+          height: enabledElement.image.height
+        };
+      } else {
+        return {
+          width: enabledElement.image.height,
+          height: enabledElement.image.width
+        };
+      }
+    }
+
+    /**
+     * Adjusts an images scale and center so the image is centered and completely visible
+     * @param element
+     */
+    function fitToWindow(element)
+    {
+        var enabledElement = cornerstone.getEnabledElement(element);
+        var imageSize = getImageSize(enabledElement);
+
+        var verticalScale = enabledElement.canvas.height / imageSize.height;
+        var horizontalScale= enabledElement.canvas.width / imageSize.width;
+        if(horizontalScale < verticalScale) {
+          enabledElement.viewport.scale = horizontalScale;
+        }
+        else
+        {
+          enabledElement.viewport.scale = verticalScale;
+        }
+        enabledElement.viewport.translation.x = 0;
+        enabledElement.viewport.translation.y = 0;
+        cornerstone.updateImage(element);
+    }
+
+    cornerstone.fitToWindow = fitToWindow;
+}(cornerstone));
+
+/**
+ * This file is responsible for returning the default viewport for an image
+ */
+
+(function ($, cornerstone) {
+
+    "use strict";
+
+    /**
+     * returns a default viewport for display the specified image on the specified
+     * enabled element.  The default viewport is fit to window
+     *
+     * @param element
+     * @param image
+     */
+    function getDefaultViewportForImage(element, image) {
+        var enabledElement = cornerstone.getEnabledElement(element);
+        var viewport = cornerstone.internal.getDefaultViewport(enabledElement.canvas, image);
+        return viewport;
+    }
+
+    // Module exports
+    cornerstone.getDefaultViewportForImage = getDefaultViewportForImage;
+}($, cornerstone));
+/**
+ * This module is responsible for returning the currently displayed image for an element
+ */
+
+(function ($, cornerstone) {
+
+    "use strict";
+
+    /**
+     * returns the currently displayed image for an element or undefined if no image has
+     * been displayed yet
+     *
+     * @param element
+     */
+    function getImage(element) {
+        var enabledElement = cornerstone.getEnabledElement(element);
+        return enabledElement.image;
+    }
+
+    // Module exports
+    cornerstone.getImage = getImage;
+}($, cornerstone));
+/**
+ * This module returns a subset of the stored pixels of an image
+ */
+(function (cornerstone) {
+
+    "use strict";
+
+    /**
+     * Returns array of pixels with modality LUT transformation applied
+     */
+    function getPixels(element, x, y, width, height) {
+
+        var storedPixels = cornerstone.getStoredPixels(element, x, y, width, height);
+        var ee = cornerstone.getEnabledElement(element);
+
+        var mlutfn = cornerstone.internal.getModalityLUT(ee.image.slope, ee.image.intercept, ee.viewport.modalityLUT);
+
+        var modalityPixels = storedPixels.map(mlutfn);
+
+        return modalityPixels;
+    }
+
+    // module exports
+    cornerstone.getPixels = getPixels;
+}(cornerstone));
+/**
+ * This module returns a subset of the stored pixels of an image
+ */
+(function (cornerstone) {
+
+    "use strict";
+
+    /**
+     * Returns an array of stored pixels given a rectangle in the image
+     * @param element
+     * @param x
+     * @param y
+     * @param width
+     * @param height
+     * @returns {Array}
+     */
+    function getStoredPixels(element, x, y, width, height) {
+        if(element === undefined) {
+            throw "getStoredPixels: parameter element must not be undefined";
+        }
+
+        x = Math.round(x);
+        y = Math.round(y);
+        var ee = cornerstone.getEnabledElement(element);
+        var storedPixels = [];
+        var index = 0;
+        var pixelData = ee.image.getPixelData();
+        for(var row=0; row < height; row++) {
+            for(var column=0; column < width; column++) {
+                var spIndex = ((row + y) * ee.image.columns) + (column + x);
+                storedPixels[index++] = pixelData[spIndex];
+            }
+        }
+        return storedPixels;
+    }
+
+    // module exports
+    cornerstone.getStoredPixels = getStoredPixels;
+}(cornerstone));
+/**
+ * This module contains functions to deal with getting and setting the viewport for an enabled element
+ */
+(function (cornerstone) {
+
+    "use strict";
+
+    /**
+     * Returns the viewport for the specified enabled element
+     * @param element
+     * @returns {*}
+     */
+    function getViewport(element) {
+        var enabledElement = cornerstone.getEnabledElement(element);
+
+        var viewport = enabledElement.viewport;
+        if(viewport === undefined) {
+            return undefined;
+        }
+        return {
+            scale : viewport.scale,
+            translation : {
+                x : viewport.translation.x,
+                y : viewport.translation.y
+            },
+            voi : {
+                windowWidth: viewport.voi.windowWidth,
+                windowCenter : viewport.voi.windowCenter
+            },
+            invert : viewport.invert,
+            pixelReplication: viewport.pixelReplication,
+            rotation: viewport.rotation, 
+            hflip: viewport.hflip,
+            vflip: viewport.vflip,
+            modalityLUT: viewport.modalityLUT,
+            voiLUT: viewport.voiLUT
+        };
+    }
+
+    // module/private exports
+    cornerstone.getViewport = getViewport;
+
+}(cornerstone));
+
+/**
+ * This module deals with caching images
+ */
+
+(function (cornerstone) {
+
+    "use strict";
+
+    var imageCache = {};
+
+    var cachedImages = [];
+
+    var maximumSizeInBytes = 1024 * 1024 * 1024; // 1 GB
+    var cacheSizeInBytes = 0;
+
+    function setMaximumSizeBytes(numBytes) {
+        if (numBytes === undefined) {
+            throw "setMaximumSizeBytes: parameter numBytes must not be undefined";
+        }
+        if (numBytes.toFixed === undefined) {
+            throw "setMaximumSizeBytes: parameter numBytes must be a number";
+        }
+
+        maximumSizeInBytes = numBytes;
+        purgeCacheIfNecessary();
+    }
+
+    function purgeCacheIfNecessary() {
+        // if max cache size has not been exceeded, do nothing
+        if (cacheSizeInBytes <= maximumSizeInBytes) {
+            return;
+        }
+
+        // cache size has been exceeded, create list of images sorted by timeStamp
+        // so we can purge the least recently used image
+        function compare(a,b) {
+            if (a.timeStamp > b.timeStamp) {
+                return -1;
+            }
+            if (a.timeStamp < b.timeStamp) {
+                return 1;
+            }
+            return 0;
+        }
+        cachedImages.sort(compare);
+
+        // remove images as necessary
+        while(cacheSizeInBytes > maximumSizeInBytes) {
+            var lastCachedImage = cachedImages[cachedImages.length - 1];
+            cacheSizeInBytes -= lastCachedImage.sizeInBytes;
+            delete imageCache[lastCachedImage.imageId];
+            lastCachedImage.imagePromise.reject();
+            cachedImages.pop();
+            $(cornerstone).trigger('CornerstoneImageCachePromiseRemoved', {imageId: lastCachedImage.imageId});
+        }
+
+        var cacheInfo = cornerstone.imageCache.getCacheInfo();
+        $(cornerstone).trigger('CornerstoneImageCacheFull', cacheInfo);
+    }
+
+    function putImagePromise(imageId, imagePromise) {
+        if (imageId === undefined) {
+            throw "getImagePromise: imageId must not be undefined";
+        }
+        if (imagePromise === undefined) {
+            throw "getImagePromise: imagePromise must not be undefined";
+        }
+
+        if (imageCache.hasOwnProperty(imageId) === true) {
+            throw "putImagePromise: imageId already in cache";
+        }
+
+        var cachedImage = {
+            loaded : false,
+            imageId : imageId,
+            imagePromise : imagePromise,
+            timeStamp : new Date(),
+            sizeInBytes: 0
+        };
+
+        imageCache[imageId] = cachedImage;
+        cachedImages.push(cachedImage);
+
+        imagePromise.then(function(image) {
+            cachedImage.loaded = true;
+
+            if (image.sizeInBytes === undefined) {
+                throw "putImagePromise: image does not have sizeInBytes property or";
+            }
+            if (image.sizeInBytes.toFixed === undefined) {
+                throw "putImagePromise: image.sizeInBytes is not a number";
+            }
+            cachedImage.sizeInBytes = image.sizeInBytes;
+            cacheSizeInBytes += cachedImage.sizeInBytes;
+            purgeCacheIfNecessary();
+        });
+    }
+
+    function getImagePromise(imageId) {
+        if (imageId === undefined) {
+            throw "getImagePromise: imageId must not be undefined";
+        }
+        var cachedImage = imageCache[imageId];
+        if (cachedImage === undefined) {
+            return undefined;
+        }
+
+        // bump time stamp for cached image
+        cachedImage.timeStamp = new Date();
+        return cachedImage.imagePromise;
+    }
+
+    function removeImagePromise(imageId) {
+        if (imageId === undefined) {
+            throw "removeImagePromise: imageId must not be undefined";
+        }
+        var cachedImage = imageCache[imageId];
+        if (cachedImage === undefined) {
+            throw "removeImagePromise: imageId must not be undefined";
+        }
+        cachedImages.splice( cachedImages.indexOf(cachedImage), 1);
+        cacheSizeInBytes -= cachedImage.sizeInBytes;
+        delete imageCache[imageId];
+
+        return cachedImage.imagePromise;
+    }
+
+    function getCacheInfo() {
+        return {
+            maximumSizeInBytes : maximumSizeInBytes,
+            cacheSizeInBytes : cacheSizeInBytes,
+            numberOfImagesCached: cachedImages.length
+        };
+    }
+
+    function purgeCache() {
+        while (cachedImages.length > 0) {
+            var removedCachedImage = cachedImages.pop();
+            delete imageCache[removedCachedImage.imageId];
+            removedCachedImage.imagePromise.reject();
+        }
+        cacheSizeInBytes = 0;
+    }
+
+    // module exports
+    cornerstone.imageCache = {
+        putImagePromise : putImagePromise,
+        getImagePromise: getImagePromise,
+        removeImagePromise: removeImagePromise,
+        setMaximumSizeBytes: setMaximumSizeBytes,
+        getCacheInfo : getCacheInfo,
+        purgeCache: purgeCache,
+        cachedImages: cachedImages
+    };
+
+}(cornerstone));
+
+/**
+ * This module deals with ImageLoaders, loading images and caching images
+ */
+
+(function ($, cornerstone) {
+
+    "use strict";
+
+    var imageLoaders = {};
+
+    var unknownImageLoader;
+
+    function loadImageFromImageLoader(imageId) {
+        var colonIndex = imageId.indexOf(":");
+        var scheme = imageId.substring(0, colonIndex);
+        var loader = imageLoaders[scheme];
+        var imagePromise;
+        if(loader === undefined || loader === null) {
+            if(unknownImageLoader !== undefined) {
+                imagePromise = unknownImageLoader(imageId);
+                return imagePromise;
+            }
+            else {
+                return undefined;
+            }
+        }
+        imagePromise = loader(imageId);
+
+        // broadcast an image loaded event once the image is loaded
+        // This is based on the idea here: http://stackoverflow.com/questions/3279809/global-custom-events-in-jquery
+        imagePromise.then(function(image) {
+            $(cornerstone).trigger('CornerstoneImageLoaded', {image: image});
+        });
+
+        return imagePromise;
+    }
+
+    // Loads an image given an imageId and returns a promise which will resolve
+    // to the loaded image object or fail if an error occurred.  The loaded image
+    // is not stored in the cache
+    function loadImage(imageId) {
+        if(imageId === undefined) {
+            throw "loadImage: parameter imageId must not be undefined";
+        }
+
+        var imagePromise = cornerstone.imageCache.getImagePromise(imageId);
+        if(imagePromise !== undefined) {
+            return imagePromise;
+        }
+
+        imagePromise = loadImageFromImageLoader(imageId);
+        if(imagePromise === undefined) {
+            throw "loadImage: no image loader for imageId";
+        }
+
+        return imagePromise;
+    }
+
+    // Loads an image given an imageId and returns a promise which will resolve
+    // to the loaded image object or fail if an error occurred.  The image is
+    // stored in the cache
+    function loadAndCacheImage(imageId) {
+        if(imageId === undefined) {
+            throw "loadAndCacheImage: parameter imageId must not be undefined";
+        }
+
+        var imagePromise = cornerstone.imageCache.getImagePromise(imageId);
+        if(imagePromise !== undefined) {
+            return imagePromise;
+        }
+
+        imagePromise = loadImageFromImageLoader(imageId);
+        if(imagePromise === undefined) {
+            throw "loadAndCacheImage: no image loader for imageId";
+        }
+
+        cornerstone.imageCache.putImagePromise(imageId, imagePromise);
+
+        return imagePromise;
+    }
+
+
+    // registers an imageLoader plugin with cornerstone for the specified scheme
+    function registerImageLoader(scheme, imageLoader) {
+        imageLoaders[scheme] = imageLoader;
+    }
+
+    // Registers a new unknownImageLoader and returns the previous one (if it exists)
+    function registerUnknownImageLoader(imageLoader) {
+        var oldImageLoader = unknownImageLoader;
+        unknownImageLoader = imageLoader;
+        return oldImageLoader;
+    }
+
+    // module exports
+
+    cornerstone.loadImage = loadImage;
+    cornerstone.loadAndCacheImage = loadAndCacheImage;
+    cornerstone.registerImageLoader = registerImageLoader;
+    cornerstone.registerUnknownImageLoader = registerUnknownImageLoader;
+
+}($, cornerstone));
+
+(function (cornerstone) {
+
+    "use strict";
+
+    function calculateTransform(enabledElement, scale) {
+
+        var transform = new cornerstone.internal.Transform();
+        transform.translate(enabledElement.canvas.width/2, enabledElement.canvas.height / 2);
+
+        //Apply the rotation before scaling for non square pixels
+        var angle = enabledElement.viewport.rotation;
+        if(angle!==0) {
+            transform.rotate(angle*Math.PI/180);
+        }
+
+        // apply the scale
+        var widthScale = enabledElement.viewport.scale;
+        var heightScale = enabledElement.viewport.scale;
+        if(enabledElement.image.rowPixelSpacing < enabledElement.image.columnPixelSpacing) {
+            widthScale = widthScale * (enabledElement.image.columnPixelSpacing / enabledElement.image.rowPixelSpacing);
+        }
+        else if(enabledElement.image.columnPixelSpacing < enabledElement.image.rowPixelSpacing) {
+            heightScale = heightScale * (enabledElement.image.rowPixelSpacing / enabledElement.image.columnPixelSpacing);
+        }
+        transform.scale(widthScale, heightScale);
+
+        // unrotate to so we can translate unrotated
+        if(angle!==0) {
+            transform.rotate(-angle*Math.PI/180);
+        }
+
+        // apply the pan offset
+        transform.translate(enabledElement.viewport.translation.x, enabledElement.viewport.translation.y);
+
+        // rotate again so we can apply general scale
+        if(angle!==0) {
+            transform.rotate(angle*Math.PI/180);
+        }
+
+        if(scale !== undefined) {
+            // apply the font scale
+            transform.scale(scale, scale);
+        }
+
+        //Apply Flip if required
+        if(enabledElement.viewport.hflip) {
+            transform.scale(-1,1);
+        }
+
+        if(enabledElement.viewport.vflip) {
+            transform.scale(1,-1);
+        }
+
+        // translate the origin back to the corner of the image so the event handlers can draw in image coordinate system
+        transform.translate(-enabledElement.image.width / 2 , -enabledElement.image.height/ 2);
+        return transform;
+    }
+
+    // Module exports
+    cornerstone.internal.calculateTransform = calculateTransform;
+}(cornerstone));
+/**
+ * This module is responsible for drawing an image to an enabled elements canvas element
+ */
+
+(function ($, cornerstone) {
+
+    "use strict";
+
+    /**
+     * Internal API function to draw an image to a given enabled element
+     * @param enabledElement
+     * @param invalidated - true if pixel data has been invalidated and cached rendering should not be used
+     */
+    function drawImage(enabledElement, invalidated) {
+
+        var start = new Date();
+
+        enabledElement.image.render(enabledElement, invalidated);
+
+        var context = enabledElement.canvas.getContext('2d');
+
+        var end = new Date();
+        var diff = end - start;
+        //console.log(diff + ' ms');
+
+        var eventData = {
+            viewport : enabledElement.viewport,
+            element : enabledElement.element,
+            image : enabledElement.image,
+            enabledElement : enabledElement,
+            canvasContext: context,
+            renderTimeInMs : diff
+        };
+
+        $(enabledElement.element).trigger("CornerstoneImageRendered", eventData);
+        enabledElement.invalid = false;
+    }
+
+    // Module exports
+    cornerstone.internal.drawImage = drawImage;
+    cornerstone.drawImage = drawImage;
+
+}($, cornerstone));
+/**
+ * This module generates a lut for an image
+ */
+
+(function (cornerstone) {
+
+  "use strict";
+
+  function generateLutNew(image, windowWidth, windowCenter, invert, modalityLUT, voiLUT)
+  {
+    if(image.lut === undefined) {
+      image.lut =  new Int16Array(image.maxPixelValue - Math.min(image.minPixelValue,0)+1);
+    }
+    var lut = image.lut;
+    var maxPixelValue = image.maxPixelValue;
+    var minPixelValue = image.minPixelValue;
+
+    var mlutfn = cornerstone.internal.getModalityLUT(image.slope, image.intercept, modalityLUT);
+    var vlutfn = cornerstone.internal.getVOILUT(windowWidth, windowCenter, voiLUT);
+
+    var offset = 0;
+    if(minPixelValue < 0) {
+      offset = minPixelValue;
+    }
+    var storedValue;
+    var modalityLutValue;
+    var voiLutValue;
+    var clampedValue;
+
+    for(storedValue = image.minPixelValue; storedValue <= maxPixelValue; storedValue++)
+    {
+      modalityLutValue = mlutfn(storedValue);
+      voiLutValue = vlutfn(modalityLutValue);
+      clampedValue = Math.min(Math.max(voiLutValue, 0), 255);
+      if(!invert) {
+        lut[storedValue+ (-offset)] = Math.round(clampedValue);
+      } else {
+        lut[storedValue + (-offset)] = Math.round(255 - clampedValue);
+      }
+    }
+    return lut;
+  }
+
+
+
+  /**
+   * Creates a LUT used while rendering to convert stored pixel values to
+   * display pixels
+   *
+   * @param image
+   * @returns {Array}
+   */
+  function generateLut(image, windowWidth, windowCenter, invert, modalityLUT, voiLUT)
+  {
+    if(modalityLUT || voiLUT) {
+      return generateLutNew(image, windowWidth, windowCenter, invert, modalityLUT, voiLUT);
+    }
+
+    if(image.lut === undefined) {
+      image.lut =  new Int16Array(image.maxPixelValue - Math.min(image.minPixelValue,0)+1);
+    }
+    var lut = image.lut;
+
+    var maxPixelValue = image.maxPixelValue;
+    var minPixelValue = image.minPixelValue;
+    var slope = image.slope;
+    var intercept = image.intercept;
+    var localWindowWidth = windowWidth;
+    var localWindowCenter = windowCenter;
+    var modalityLutValue;
+    var voiLutValue;
+    var clampedValue;
+    var storedValue;
+
+    // NOTE: As of Nov 2014, most javascript engines have lower performance when indexing negative indexes.
+    // We improve performance by offsetting the pixel values for signed data to avoid negative indexes
+    // when generating the lut and then undo it in storedPixelDataToCanvasImagedata.  Thanks to @jpambrun
+    // for this contribution!
+
+    var offset = 0;
+    if(minPixelValue < 0) {
+      offset = minPixelValue;
+    }
+
+    if(invert === true) {
+      for(storedValue = image.minPixelValue; storedValue <= maxPixelValue; storedValue++)
+      {
+        modalityLutValue =  storedValue * slope + intercept;
+        voiLutValue = (((modalityLutValue - (localWindowCenter)) / (localWindowWidth) + 0.5) * 255.0);
+        clampedValue = Math.min(Math.max(voiLutValue, 0), 255);
+        lut[storedValue + (-offset)] = Math.round(255 - clampedValue);
+      }
+    }
+    else {
+      for(storedValue = image.minPixelValue; storedValue <= maxPixelValue; storedValue++)
+      {
+        modalityLutValue = storedValue * slope + intercept;
+        voiLutValue = (((modalityLutValue - (localWindowCenter)) / (localWindowWidth) + 0.5) * 255.0);
+        clampedValue = Math.min(Math.max(voiLutValue, 0), 255);
+        lut[storedValue+ (-offset)] = Math.round(clampedValue);
+      }
+    }
+  }
+
+
+  // Module exports
+  cornerstone.internal.generateLutNew = generateLutNew;
+  cornerstone.internal.generateLut = generateLut;
+  cornerstone.generateLutNew = generateLutNew;
+  cornerstone.generateLut = generateLut;
+}(cornerstone));
+
+/**
+ * This module contains a function to get a default viewport for an image given
+ * a canvas element to display it in
+ *
+ */
+(function (cornerstone) {
+
+    "use strict";
+
+    /**
+     * Creates a new viewport object containing default values for the image and canvas
+     * @param canvas
+     * @param image
+     * @returns viewport object
+     */
+    function getDefaultViewport(canvas, image) {
+        if(canvas === undefined) {
+            throw "getDefaultViewport: parameter canvas must not be undefined";
+        }
+        if(image === undefined) {
+            throw "getDefaultViewport: parameter image must not be undefined";
+        }
+        var viewport = {
+            scale : 1.0,
+            translation : {
+                x : 0,
+                y : 0
+            },
+            voi : {
+                windowWidth: image.windowWidth,
+                windowCenter: image.windowCenter,
+            },
+            invert: image.invert,
+            pixelReplication: false,
+            rotation: 0,
+            hflip: false,
+            vflip: false,
+            modalityLUT: image.modalityLUT,
+            voiLUT: image.voiLUT
+        };
+
+        // fit image to window
+        var verticalScale = canvas.height / image.rows;
+        var horizontalScale= canvas.width / image.columns;
+        if(horizontalScale < verticalScale) {
+            viewport.scale = horizontalScale;
+        }
+        else {
+            viewport.scale = verticalScale;
+        }
+        return viewport;
+    }
+
+    // module/private exports
+    cornerstone.internal.getDefaultViewport = getDefaultViewport;
+    cornerstone.getDefaultViewport = getDefaultViewport;
+}(cornerstone));
+
+(function (cornerstone) {
+
+    "use strict";
+
+    function getTransform(enabledElement)
+    {
+        // For now we will calculate it every time it is requested.  In the future, we may want to cache
+        // it in the enabled element to speed things up
+        var transform = cornerstone.internal.calculateTransform(enabledElement);
+        return transform;
+    }
+
+    // Module exports
+    cornerstone.internal.getTransform = getTransform;
+
+}(cornerstone));
+/**
+ * This module is responsible for drawing an image to an enabled elements canvas element
+ */
+
+(function ($, cornerstone) {
+
+    "use strict";
+
+    cornerstone.drawImage = cornerstone.internal.drawImage;
+    cornerstone.generateLut = cornerstone.internal.generateLut;
+    cornerstone.storedPixelDataToCanvasImageData = cornerstone.internal.storedPixelDataToCanvasImageData;
+    cornerstone.storedColorPixelDataToCanvasImageData = cornerstone.internal.storedColorPixelDataToCanvasImageData;
+
+}($, cornerstone));
+/**
+ * This module generates a Modality LUT
+ */
+
+(function (cornerstone) {
+
+  "use strict";
+
+
+  function generateLinearModalityLUT(slope, intercept) {
+    var localSlope = slope;
+    var localIntercept = intercept;
+    return function(sp) {
+      return sp * localSlope + localIntercept;
+    }
+  }
+
+  function generateNonLinearModalityLUT(modalityLUT) {
+    var minValue = modalityLUT.lut[0];
+    var maxValue = modalityLUT.lut[modalityLUT.lut.length -1];
+    var maxValueMapped = modalityLUT.firstValueMapped + modalityLUT.lut.length;
+    return function(sp) {
+      if(sp < modalityLUT.firstValueMapped) {
+        return minValue;
+      }
+      else if(sp >= maxValueMapped)
+      {
+        return maxValue;
+      }
+      else
+      {
+        return modalityLUT.lut[sp];
+      }
+    }
+  }
+
+  function getModalityLUT(slope, intercept, modalityLUT) {
+    if (modalityLUT) {
+      return generateNonLinearModalityLUT(modalityLUT);
+    } else {
+      return generateLinearModalityLUT(slope, intercept);
+    }
+  }
+
+    // Module exports
+    cornerstone.internal.getModalityLUT = getModalityLUT;
+
+}(cornerstone));
+
+/**
+ * This module contains a function to convert stored pixel values to display pixel values using a LUT
+ */
+(function (cornerstone) {
+
+    "use strict";
+
+    function storedColorPixelDataToCanvasImageData(image, lut, canvasImageDataData)
+    {
+        var minPixelValue = image.minPixelValue;
+        var canvasImageDataIndex = 0;
+        var storedPixelDataIndex = 0;
+        var numPixels = image.width * image.height * 4;
+        var storedPixelData = image.getPixelData();
+        var localLut = lut;
+        var localCanvasImageDataData = canvasImageDataData;
+        // NOTE: As of Nov 2014, most javascript engines have lower performance when indexing negative indexes.
+        // We have a special code path for this case that improves performance.  Thanks to @jpambrun for this enhancement
+        if(minPixelValue < 0){
+            while(storedPixelDataIndex < numPixels) {
+                localCanvasImageDataData[canvasImageDataIndex++] = localLut[storedPixelData[storedPixelDataIndex++] + (-minPixelValue)]; // red
+                localCanvasImageDataData[canvasImageDataIndex++] = localLut[storedPixelData[storedPixelDataIndex++] + (-minPixelValue)]; // green
+                localCanvasImageDataData[canvasImageDataIndex] = localLut[storedPixelData[storedPixelDataIndex] + (-minPixelValue)]; // blue
+                storedPixelDataIndex+=2;
+                canvasImageDataIndex+=2;
+            }
+        }else{
+            while(storedPixelDataIndex < numPixels) {
+                localCanvasImageDataData[canvasImageDataIndex++] = localLut[storedPixelData[storedPixelDataIndex++]]; // red
+                localCanvasImageDataData[canvasImageDataIndex++] = localLut[storedPixelData[storedPixelDataIndex++]]; // green
+                localCanvasImageDataData[canvasImageDataIndex] = localLut[storedPixelData[storedPixelDataIndex]]; // blue
+                storedPixelDataIndex+=2;
+                canvasImageDataIndex+=2;
+            }
+        }
+    }
+
+    // Module exports
+    cornerstone.internal.storedColorPixelDataToCanvasImageData = storedColorPixelDataToCanvasImageData;
+    cornerstone.storedColorPixelDataToCanvasImageData = storedColorPixelDataToCanvasImageData;
+
+}(cornerstone));
+
+/**
+ * This module contains a function to convert stored pixel values to display pixel values using a LUT
+ */
+(function (cornerstone) {
+
+    "use strict";
+
+    /**
+     * This function transforms stored pixel values into a canvas image data buffer
+     * by using a LUT.  This is the most performance sensitive code in cornerstone and
+     * we use a special trick to make this go as fast as possible.  Specifically we
+     * use the alpha channel only to control the luminance rather than the red, green and
+     * blue channels which makes it over 3x faster.  The canvasImageDataData buffer needs
+     * to be previously filled with white pixels.
+     *
+     * NOTE: Attribution would be appreciated if you use this technique!
+     *
+     * @param pixelData the pixel data
+     * @param lut the lut
+     * @param canvasImageDataData a canvasImgageData.data buffer filled with white pixels
+     */
+    function storedPixelDataToCanvasImageData(image, lut, canvasImageDataData)
+    {
+        var pixelData = image.getPixelData();
+        var minPixelValue = image.minPixelValue;
+        var canvasImageDataIndex = 3;
+        var storedPixelDataIndex = 0;
+        var localNumPixels = pixelData.length;
+        var localPixelData = pixelData;
+        var localLut = lut;
+        var localCanvasImageDataData = canvasImageDataData;
+        // NOTE: As of Nov 2014, most javascript engines have lower performance when indexing negative indexes.
+        // We have a special code path for this case that improves performance.  Thanks to @jpambrun for this enhancement
+        if(minPixelValue < 0){
+            while(storedPixelDataIndex < localNumPixels) {
+                localCanvasImageDataData[canvasImageDataIndex] = localLut[localPixelData[storedPixelDataIndex++] + (-minPixelValue)]; // alpha
+                canvasImageDataIndex += 4;
+            }
+        }else{
+            while(storedPixelDataIndex < localNumPixels) {
+                localCanvasImageDataData[canvasImageDataIndex] = localLut[localPixelData[storedPixelDataIndex++]]; // alpha
+                canvasImageDataIndex += 4;
+            }
+        }
+    }
+
+    // Module exports
+    cornerstone.internal.storedPixelDataToCanvasImageData = storedPixelDataToCanvasImageData;
+    cornerstone.storedPixelDataToCanvasImageData = storedPixelDataToCanvasImageData;
+
+}(cornerstone));
+
+// Last updated November 2011
+// By Simon Sarris
+// www.simonsarris.com
+// sarris at acm.org
+//
+// Free to use and distribute at will
+// So long as you are nice to people, etc
+
+// Simple class for keeping track of the current transformation matrix
+
+// For instance:
+//    var t = new Transform();
+//    t.rotate(5);
+//    var m = t.m;
+//    ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]);
+
+// Is equivalent to:
+//    ctx.rotate(5);
+
+// But now you can retrieve it :)
+
+(function (cornerstone) {
+
+    "use strict";
+
+
+    // Remember that this does not account for any CSS transforms applied to the canvas
+    function Transform() {
+        this.reset();
+    }
+
+    Transform.prototype.reset = function() {
+        this.m = [1,0,0,1,0,0];
+    };
+
+    Transform.prototype.clone = function() {
+        var transform = new Transform();
+        transform.m[0] = this.m[0];
+        transform.m[1] = this.m[1];
+        transform.m[2] = this.m[2];
+        transform.m[3] = this.m[3];
+        transform.m[4] = this.m[4];
+        transform.m[5] = this.m[5];
+        return transform;
+    };
+
+
+    Transform.prototype.multiply = function(matrix) {
+        var m11 = this.m[0] * matrix.m[0] + this.m[2] * matrix.m[1];
+        var m12 = this.m[1] * matrix.m[0] + this.m[3] * matrix.m[1];
+
+        var m21 = this.m[0] * matrix.m[2] + this.m[2] * matrix.m[3];
+        var m22 = this.m[1] * matrix.m[2] + this.m[3] * matrix.m[3];
+
+        var dx = this.m[0] * matrix.m[4] + this.m[2] * matrix.m[5] + this.m[4];
+        var dy = this.m[1] * matrix.m[4] + this.m[3] * matrix.m[5] + this.m[5];
+
+        this.m[0] = m11;
+        this.m[1] = m12;
+        this.m[2] = m21;
+        this.m[3] = m22;
+        this.m[4] = dx;
+        this.m[5] = dy;
+    };
+
+    Transform.prototype.invert = function() {
+        var d = 1 / (this.m[0] * this.m[3] - this.m[1] * this.m[2]);
+        var m0 = this.m[3] * d;
+        var m1 = -this.m[1] * d;
+        var m2 = -this.m[2] * d;
+        var m3 = this.m[0] * d;
+        var m4 = d * (this.m[2] * this.m[5] - this.m[3] * this.m[4]);
+        var m5 = d * (this.m[1] * this.m[4] - this.m[0] * this.m[5]);
+        this.m[0] = m0;
+        this.m[1] = m1;
+        this.m[2] = m2;
+        this.m[3] = m3;
+        this.m[4] = m4;
+        this.m[5] = m5;
+    };
+
+    Transform.prototype.rotate = function(rad) {
+        var c = Math.cos(rad);
+        var s = Math.sin(rad);
+        var m11 = this.m[0] * c + this.m[2] * s;
+        var m12 = this.m[1] * c + this.m[3] * s;
+        var m21 = this.m[0] * -s + this.m[2] * c;
+        var m22 = this.m[1] * -s + this.m[3] * c;
+        this.m[0] = m11;
+        this.m[1] = m12;
+        this.m[2] = m21;
+        this.m[3] = m22;
+    };
+
+    Transform.prototype.translate = function(x, y) {
+        this.m[4] += this.m[0] * x + this.m[2] * y;
+        this.m[5] += this.m[1] * x + this.m[3] * y;
+    };
+
+    Transform.prototype.scale = function(sx, sy) {
+        this.m[0] *= sx;
+        this.m[1] *= sx;
+        this.m[2] *= sy;
+        this.m[3] *= sy;
+    };
+
+    Transform.prototype.transformPoint = function(px, py) {
+        var x = px;
+        var y = py;
+        px = x * this.m[0] + y * this.m[2] + this.m[4];
+        py = x * this.m[1] + y * this.m[3] + this.m[5];
+        return {x: px, y: py};
+    };
+
+    cornerstone.internal.Transform = Transform;
+}(cornerstone));
+/**
+ * This module generates a VOI LUT
+ */
+
+(function (cornerstone) {
+
+  "use strict";
+
+  function generateLinearVOILUT(windowWidth, windowCenter) {
+    return function(modalityLutValue) {
+      return (((modalityLutValue - (windowCenter)) / (windowWidth) + 0.5) * 255.0);
+    }
+  }
+
+  function generateNonLinearVOILUT(voiLUT) {
+    var shift = voiLUT.numBitsPerEntry - 8;
+    var minValue = voiLUT.lut[0] >> shift;
+    var maxValue = voiLUT.lut[voiLUT.lut.length -1] >> shift;
+    var maxValueMapped = voiLUT.firstValueMapped + voiLUT.lut.length - 1;
+    return function(modalityLutValue) {
+      if(modalityLutValue < voiLUT.firstValueMapped) {
+        return minValue;
+      }
+      else if(modalityLutValue >= maxValueMapped)
+      {
+        return maxValue;
+      }
+      else
+      {
+        return voiLUT.lut[modalityLutValue - voiLUT.firstValueMapped] >> shift;
+      }
+    }
+  }
+
+  function getVOILUT(windowWidth, windowCenter, voiLUT) {
+    if(voiLUT) {
+      return generateNonLinearVOILUT(voiLUT);
+    } else {
+      return generateLinearVOILUT(windowWidth, windowCenter);
+    }
+  }
+
+  // Module exports
+  cornerstone.internal.getVOILUT = getVOILUT;
+}(cornerstone));
+
+/**
+ * This module contains a function to make an image is invalid
+ */
+(function (cornerstone) {
+
+    "use strict";
+
+    /**
+     * Sets the invalid flag on the enabled element and fire an event
+     * @param element
+     */
+    function invalidate(element) {
+        var enabledElement = cornerstone.getEnabledElement(element);
+        enabledElement.invalid = true;
+        var eventData = {
+            element: element
+        };
+        $(enabledElement.element).trigger("CornerstoneInvalidated", eventData);
+    }
+
+    // module exports
+    cornerstone.invalidate = invalidate;
+}(cornerstone));
+/**
+ * This module contains a function to immediately invalidate an image
+ */
+(function (cornerstone) {
+
+    "use strict";
+
+    /**
+     * Forces the image to be updated/redrawn for the specified enabled element
+     * @param element
+     */
+    function invalidateImageId(imageId) {
+
+        var enabledElements = cornerstone.getEnabledElementsByImageId(imageId);
+        enabledElements.forEach(function(enabledElement) {
+            cornerstone.drawImage(enabledElement, true);
+        });
+    }
+
+    // module exports
+    cornerstone.invalidateImageId = invalidateImageId;
+}(cornerstone));
+/**
+ * This module contains a helper function to covert page coordinates to pixel coordinates
+ */
+(function (cornerstone) {
+
+    "use strict";
+
+    /**
+     * Converts a point in the page coordinate system to the pixel coordinate
+     * system
+     * @param element
+     * @param pageX
+     * @param pageY
+     * @returns {{x: number, y: number}}
+     */
+    function pageToPixel(element, pageX, pageY) {
+        var enabledElement = cornerstone.getEnabledElement(element);
+
+        if(enabledElement.image === undefined) {
+            throw "image has not been loaded yet";
+        }
+
+        var image = enabledElement.image;
+
+        // convert the pageX and pageY to the canvas client coordinates
+        var rect = element.getBoundingClientRect();
+        var clientX = pageX - rect.left - window.pageXOffset;
+        var clientY = pageY - rect.top - window.pageYOffset;
+
+        var pt = {x: clientX, y: clientY};
+        var transform = cornerstone.internal.getTransform(enabledElement);
+        transform.invert();
+        return transform.transformPoint(pt.x, pt.y);
+    }
+
+    // module/private exports
+    cornerstone.pageToPixel = pageToPixel;
+
+}(cornerstone));
+
+(function (cornerstone) {
+
+    "use strict";
+
+    /**
+     * Converts a point in the pixel coordinate system to the canvas coordinate system
+     * system.  This can be used to render using canvas context without having the weird
+     * side effects that come from scaling and non square pixels
+     * @param element
+     * @param pt
+     * @returns {x: number, y: number}
+     */
+    function pixelToCanvas(element, pt) {
+        var enabledElement = cornerstone.getEnabledElement(element);
+        var transform = cornerstone.internal.getTransform(enabledElement);
+        return transform.transformPoint(pt.x, pt.y);
+    }
+
+    // module/private exports
+    cornerstone.pixelToCanvas = pixelToCanvas;
+
+}(cornerstone));
+
+/**
+ * This module is responsible for drawing an image to an enabled elements canvas element
+ */
+
+(function (cornerstone) {
+
+    "use strict";
+
+    var colorRenderCanvas = document.createElement('canvas');
+    var colorRenderCanvasContext;
+    var colorRenderCanvasData;
+
+    var lastRenderedImageId;
+    var lastRenderedViewport = {};
+
+    function initializeColorRenderCanvas(image)
+    {
+        // Resize the canvas
+        colorRenderCanvas.width = image.width;
+        colorRenderCanvas.height = image.height;
+
+        // get the canvas data so we can write to it directly
+        colorRenderCanvasContext = colorRenderCanvas.getContext('2d');
+        colorRenderCanvasContext.fillStyle = 'white';
+        colorRenderCanvasContext.fillRect(0,0, colorRenderCanvas.width, colorRenderCanvas.height);
+        colorRenderCanvasData = colorRenderCanvasContext.getImageData(0,0,image.width, image.height);
+    }
+
+
+    function getLut(image, viewport)
+    {
+        // if we have a cached lut and it has the right values, return it immediately
+        if(image.lut !== undefined &&
+            image.lut.windowCenter === viewport.voi.windowCenter &&
+            image.lut.windowWidth === viewport.voi.windowWidth &&
+            image.lut.invert === viewport.invert) {
+            return image.lut;
+        }
+
+        // lut is invalid or not present, regenerate it and cache it
+        cornerstone.generateLut(image, viewport.voi.windowWidth, viewport.voi.windowCenter, viewport.invert);
+        image.lut.windowWidth = viewport.voi.windowWidth;
+        image.lut.windowCenter = viewport.voi.windowCenter;
+        image.lut.invert = viewport.invert;
+        return image.lut;
+    }
+
+    function doesImageNeedToBeRendered(enabledElement, image)
+    {
+        if(image.imageId !== lastRenderedImageId ||
+            lastRenderedViewport.windowCenter !== enabledElement.viewport.voi.windowCenter ||
+            lastRenderedViewport.windowWidth !== enabledElement.viewport.voi.windowWidth ||
+            lastRenderedViewport.invert !== enabledElement.viewport.invert ||
+            lastRenderedViewport.rotation !== enabledElement.viewport.rotation ||  
+            lastRenderedViewport.hflip !== enabledElement.viewport.hflip ||
+            lastRenderedViewport.vflip !== enabledElement.viewport.vflip
+            )
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    function getRenderCanvas(enabledElement, image, invalidated)
+    {
+        // apply the lut to the stored pixel data onto the render canvas
+
+        if(enabledElement.viewport.voi.windowWidth === enabledElement.image.windowWidth &&
+            enabledElement.viewport.voi.windowCenter === enabledElement.image.windowCenter &&
+            enabledElement.viewport.invert === false)
+        {
+            // the color image voi/invert has not been modified, request the canvas that contains
+            // it so we can draw it directly to the display canvas
+            return image.getCanvas();
+        }
+        else
+        {
+            if(doesImageNeedToBeRendered(enabledElement, image) === false && invalidated !== true) {
+                return colorRenderCanvas;
+            }
+
+            // If our render canvas does not match the size of this image reset it
+            // NOTE: This might be inefficient if we are updating multiple images of different
+            // sizes frequently.
+            if(colorRenderCanvas.width !== image.width || colorRenderCanvas.height != image.height) {
+                initializeColorRenderCanvas(image);
+            }
+
+            // get the lut to use
+            var colorLut = getLut(image, enabledElement.viewport);
+
+            // the color image voi/invert has been modified - apply the lut to the underlying
+            // pixel data and put it into the renderCanvas
+            cornerstone.storedColorPixelDataToCanvasImageData(image, colorLut, colorRenderCanvasData.data);
+            colorRenderCanvasContext.putImageData(colorRenderCanvasData, 0, 0);
+            return colorRenderCanvas;
+        }
+    }
+
+    /**
+     * API function to render a color image to an enabled element
+     * @param enabledElement
+     * @param invalidated - true if pixel data has been invaldiated and cached rendering should not be used
+     */
+    function renderColorImage(enabledElement, invalidated) {
+
+        if(enabledElement === undefined) {
+            throw "drawImage: enabledElement parameter must not be undefined";
+        }
+        var image = enabledElement.image;
+        if(image === undefined) {
+            throw "drawImage: image must be loaded before it can be drawn";
+        }
+
+        // get the canvas context and reset the transform
+        var context = enabledElement.canvas.getContext('2d');
+        context.setTransform(1, 0, 0, 1, 0, 0);
+
+        // clear the canvas
+        context.fillStyle = 'black';
+        context.fillRect(0,0, enabledElement.canvas.width, enabledElement.canvas.height);
+
+        // turn off image smooth/interpolation if pixelReplication is set in the viewport
+        if(enabledElement.viewport.pixelReplication === true) {
+            context.imageSmoothingEnabled = false;
+            context.mozImageSmoothingEnabled = false; // firefox doesn't support imageSmoothingEnabled yet
+        }
+        else {
+            context.imageSmoothingEnabled = true;
+            context.mozImageSmoothingEnabled = true;
+        }
+
+        // save the canvas context state and apply the viewport properties
+        context.save();
+        cornerstone.setToPixelCoordinateSystem(enabledElement, context);
+
+        var renderCanvas = getRenderCanvas(enabledElement, image, invalidated);
+
+        context.drawImage(renderCanvas, 0,0, image.width, image.height, 0, 0, image.width, image.height);
+
+        context.restore();
+
+        lastRenderedImageId = image.imageId;
+        lastRenderedViewport.windowCenter = enabledElement.viewport.voi.windowCenter;
+        lastRenderedViewport.windowWidth = enabledElement.viewport.voi.windowWidth;
+        lastRenderedViewport.invert = enabledElement.viewport.invert;
+        lastRenderedViewport.rotation = enabledElement.viewport.rotation;
+        lastRenderedViewport.hflip = enabledElement.viewport.hflip;
+        lastRenderedViewport.vflip = enabledElement.viewport.vflip;
+    }
+
+    // Module exports
+    cornerstone.rendering.colorImage = renderColorImage;
+    cornerstone.renderColorImage = renderColorImage;
+}(cornerstone));
+
+/**
+ * This module is responsible for drawing a grayscale imageß
+ */
+
+(function (cornerstone) {
+
+    "use strict";
+
+    var grayscaleRenderCanvas = document.createElement('canvas');
+    var grayscaleRenderCanvasContext;
+    var grayscaleRenderCanvasData;
+
+    var lastRenderedImageId;
+    var lastRenderedViewport = {};
+
+    function initializeGrayscaleRenderCanvas(image)
+    {
+        // Resize the canvas
+        grayscaleRenderCanvas.width = image.width;
+        grayscaleRenderCanvas.height = image.height;
+
+        // NOTE - we need to fill the render canvas with white pixels since we control the luminance
+        // using the alpha channel to improve rendering performance.
+        grayscaleRenderCanvasContext = grayscaleRenderCanvas.getContext('2d');
+        grayscaleRenderCanvasContext.fillStyle = 'white';
+        grayscaleRenderCanvasContext.fillRect(0,0, grayscaleRenderCanvas.width, grayscaleRenderCanvas.height);
+        grayscaleRenderCanvasData = grayscaleRenderCanvasContext.getImageData(0,0,image.width, image.height);
+    }
+
+    function lutMatches(a, b) {
+      // if undefined, they are equal
+      if(!a && !b) {
+        return true;
+      }
+      // if one is undefined, not equal
+      if(!a || !b) {
+        return false;
+      }
+      // check the unique ids
+      return (a.id !== b.id)
+    }
+
+    function getLut(image, viewport, invalidated)
+    {
+        // if we have a cached lut and it has the right values, return it immediately
+        if(image.lut !== undefined &&
+            image.lut.windowCenter === viewport.voi.windowCenter &&
+            image.lut.windowWidth === viewport.voi.windowWidth &&
+            lutMatches(image.lut.modalityLUT, viewport.modalityLUT) &&
+            lutMatches(image.lut.voiLUT, viewport.voiLUT) &&
+            image.lut.invert === viewport.invert &&
+            invalidated !== true) {
+            return image.lut;
+        }
+
+        // lut is invalid or not present, regenerate it and cache it
+        cornerstone.generateLut(image, viewport.voi.windowWidth, viewport.voi.windowCenter, viewport.invert, viewport.modalityLUT, viewport.voiLUT);
+        image.lut.windowWidth = viewport.voi.windowWidth;
+        image.lut.windowCenter = viewport.voi.windowCenter;
+        image.lut.invert = viewport.invert;
+        image.lut.voiLUT = viewport.voiLUT;
+        image.lut.modalityLUT = viewport.modalityLUT;
+        return image.lut;
+    }
+
+    function doesImageNeedToBeRendered(enabledElement, image)
+    {
+        if(image.imageId !== lastRenderedImageId ||
+            lastRenderedViewport.windowCenter !== enabledElement.viewport.voi.windowCenter ||
+            lastRenderedViewport.windowWidth !== enabledElement.viewport.voi.windowWidth ||
+            lastRenderedViewport.invert !== enabledElement.viewport.invert ||
+            lastRenderedViewport.rotation !== enabledElement.viewport.rotation ||
+            lastRenderedViewport.hflip !== enabledElement.viewport.hflip ||
+            lastRenderedViewport.vflip !== enabledElement.viewport.vflip ||
+            lastRenderedViewport.modalityLUT !== enabledElement.viewport.modalityLUT ||
+            lastRenderedViewport.voiLUT !== enabledElement.viewport.voiLUT
+            )
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+    function getRenderCanvas(enabledElement, image, invalidated)
+    {
+        // apply the lut to the stored pixel data onto the render canvas
+
+        if(doesImageNeedToBeRendered(enabledElement, image) === false && invalidated !== true) {
+            return grayscaleRenderCanvas;
+        }
+
+        // If our render canvas does not match the size of this image reset it
+        // NOTE: This might be inefficient if we are updating multiple images of different
+        // sizes frequently.
+        if(grayscaleRenderCanvas.width !== image.width || grayscaleRenderCanvas.height != image.height) {
+            initializeGrayscaleRenderCanvas(image);
+        }
+
+        // get the lut to use
+        var lut = getLut(image, enabledElement.viewport, invalidated);
+        // gray scale image - apply the lut and put the resulting image onto the render canvas
+        cornerstone.storedPixelDataToCanvasImageData(image, lut, grayscaleRenderCanvasData.data);
+        grayscaleRenderCanvasContext.putImageData(grayscaleRenderCanvasData, 0, 0);
+        return grayscaleRenderCanvas;
+    }
+
+    /**
+     * API function to draw a grayscale image to a given enabledElement
+     * @param enabledElement
+     * @param invalidated - true if pixel data has been invaldiated and cached rendering should not be used
+     */
+    function renderGrayscaleImage(enabledElement, invalidated) {
+
+        if(enabledElement === undefined) {
+            throw "drawImage: enabledElement parameter must not be undefined";
+        }
+        var image = enabledElement.image;
+        if(image === undefined) {
+            throw "drawImage: image must be loaded before it can be drawn";
+        }
+
+        // get the canvas context and reset the transform
+        var context = enabledElement.canvas.getContext('2d');
+        context.setTransform(1, 0, 0, 1, 0, 0);
+
+        // clear the canvas
+        context.fillStyle = 'black';
+        context.fillRect(0,0, enabledElement.canvas.width, enabledElement.canvas.height);
+
+        // turn off image smooth/interpolation if pixelReplication is set in the viewport
+        if(enabledElement.viewport.pixelReplication === true) {
+            context.imageSmoothingEnabled = false;
+            context.mozImageSmoothingEnabled = false; // firefox doesn't support imageSmoothingEnabled yet
+        }
+        else {
+            context.imageSmoothingEnabled = true;
+            context.mozImageSmoothingEnabled = true;
+        }
+
+        // save the canvas context state and apply the viewport properties
+        cornerstone.setToPixelCoordinateSystem(enabledElement, context);
+
+        var renderCanvas = getRenderCanvas(enabledElement, image, invalidated);
+
+        // Draw the render canvas half the image size (because we set origin to the middle of the canvas above)
+        context.drawImage(renderCanvas, 0,0, image.width, image.height, 0, 0, image.width, image.height);
+
+        lastRenderedImageId = image.imageId;
+        lastRenderedViewport.windowCenter = enabledElement.viewport.voi.windowCenter;
+        lastRenderedViewport.windowWidth = enabledElement.viewport.voi.windowWidth;
+        lastRenderedViewport.invert = enabledElement.viewport.invert;
+        lastRenderedViewport.rotation = enabledElement.viewport.rotation;
+        lastRenderedViewport.hflip = enabledElement.viewport.hflip;
+        lastRenderedViewport.vflip = enabledElement.viewport.vflip;
+        lastRenderedViewport.modalityLUT = enabledElement.viewport.modalityLUT;
+        lastRenderedViewport.voiLUT = enabledElement.viewport.voiLUT;
+    }
+
+    // Module exports
+    cornerstone.rendering.grayscaleImage = renderGrayscaleImage;
+    cornerstone.renderGrayscaleImage = renderGrayscaleImage;
+
+}(cornerstone));
+
+/**
+ * This module is responsible for drawing an image to an enabled elements canvas element
+ */
+
+(function (cornerstone) {
+
+    "use strict";
+
+    /**
+     * API function to draw a standard web image (PNG, JPG) to an enabledImage
+     *
+     * @param enabledElement
+     * @param invalidated - true if pixel data has been invaldiated and cached rendering should not be used
+     */
+    function renderWebImage(enabledElement, invalidated) {
+
+        if(enabledElement === undefined) {
+            throw "drawImage: enabledElement parameter must not be undefined";
+        }
+        var image = enabledElement.image;
+        if(image === undefined) {
+            throw "drawImage: image must be loaded before it can be drawn";
+        }
+
+        // get the canvas context and reset the transform
+        var context = enabledElement.canvas.getContext('2d');
+        context.setTransform(1, 0, 0, 1, 0, 0);
+
+        // clear the canvas
+        context.fillStyle = 'black';
+        context.fillRect(0,0, enabledElement.canvas.width, enabledElement.canvas.height);
+
+        // turn off image smooth/interpolation if pixelReplication is set in the viewport
+        if(enabledElement.viewport.pixelReplication === true) {
+            context.imageSmoothingEnabled = false;
+            context.mozImageSmoothingEnabled = false; // firefox doesn't support imageSmoothingEnabled yet
+        }
+        else {
+            context.imageSmoothingEnabled = true;
+            context.mozImageSmoothingEnabled = true;
+        }
+
+        // save the canvas context state and apply the viewport properties
+        cornerstone.setToPixelCoordinateSystem(enabledElement, context);
+
+        // if the viewport ww/wc and invert all match the initial state of the image, we can draw the image
+        // directly.  If any of those are changed, we call renderColorImage() to apply the lut
+        if(enabledElement.viewport.voi.windowWidth === enabledElement.image.windowWidth &&
+            enabledElement.viewport.voi.windowCenter === enabledElement.image.windowCenter &&
+            enabledElement.viewport.invert === false)
+        {
+            context.drawImage(image.getImage(), 0, 0, image.width, image.height, 0, 0, image.width, image.height);
+        } else {
+            cornerstone.renderColorImage(enabledElement, invalidated);
+        }
+
+    }
+
+    // Module exports
+    cornerstone.rendering.webImage = renderWebImage;
+    cornerstone.renderWebImage = renderWebImage;
+
+}(cornerstone));
+/**
+ */
+(function (cornerstone) {
+
+  "use strict";
+
+  /**
+   * Resets the viewport to the default settings
+   *
+   * @param element
+   */
+  function reset(element)
+  {
+    var enabledElement = cornerstone.getEnabledElement(element);
+    var defaultViewport = cornerstone.internal.getDefaultViewport(enabledElement.canvas, enabledElement.image);
+    enabledElement.viewport = defaultViewport;
+    cornerstone.updateImage(element);
+  }
+
+  cornerstone.reset = reset;
+}(cornerstone));
+
+/**
+ * This module is responsible for enabling an element to display images with cornerstone
+ */
+(function (cornerstone) {
+
+    "use strict";
+
+    function setCanvasSize(element, canvas)
+    {
+        // the device pixel ratio is 1.0 for normal displays and > 1.0
+        // for high DPI displays like Retina
+        /*
+
+        This functionality is disabled due to buggy behavior on systems with mixed DPI's.  If the canvas
+        is created on a display with high DPI (e.g. 2.0) and then the browser window is dragged to
+        a different display with a different DPI (e.g. 1.0), the canvas is not recreated so the pageToPixel
+        produces incorrect results.  I couldn't find any way to determine when the DPI changed other than
+        by polling which is not very clean.  If anyone has any ideas here, please let me know, but for now
+        we will disable this functionality.  We may want
+        to add a mechanism to optionally enable this functionality if we can determine it is safe to do
+        so (e.g. iPad or iPhone or perhaps enumerate the displays on the system.  I am choosing
+        to be cautious here since I would rather not have bug reports or safety issues related to this
+        scenario.
+
+        var devicePixelRatio = window.devicePixelRatio;
+        if(devicePixelRatio === undefined) {
+            devicePixelRatio = 1.0;
+        }
+        */
+
+        canvas.width = element.clientWidth;
+        canvas.height = element.clientHeight;
+        canvas.style.width = element.clientWidth + "px";
+        canvas.style.height = element.clientHeight + "px";
+    }
+
+    /**
+     * resizes an enabled element and optionally fits the image to window
+     * @param element
+     * @param fitToWindow true to refit, false to leave viewport parameters as they are
+     */
+    function resize(element, fitToWindow) {
+
+        var enabledElement = cornerstone.getEnabledElement(element);
+
+        setCanvasSize(element, enabledElement.canvas);
+
+        if(enabledElement.image === undefined ) {
+            return;
+        }
+
+        if(fitToWindow === true) {
+            cornerstone.fitToWindow(element);
+        }
+        else {
+            cornerstone.updateImage(element);
+        }
+    }
+
+    // module/private exports
+    cornerstone.resize = resize;
+
+}(cornerstone));
+/**
+ * This module contains a function that will set the canvas context to the pixel coordinates system
+ * making it easy to draw geometry on the image
+ */
+
+(function (cornerstone) {
+
+    "use strict";
+
+    /**
+     * Sets the canvas context transformation matrix to the pixel coordinate system.  This allows
+     * geometry to be driven using the canvas context using coordinates in the pixel coordinate system
+     * @param ee
+     * @param context
+     * @param scale optional scaler to apply
+     */
+    function setToPixelCoordinateSystem(enabledElement, context, scale)
+    {
+        if(enabledElement === undefined) {
+            throw "setToPixelCoordinateSystem: parameter enabledElement must not be undefined";
+        }
+        if(context === undefined) {
+            throw "setToPixelCoordinateSystem: parameter context must not be undefined";
+        }
+
+        var transform = cornerstone.internal.calculateTransform(enabledElement, scale);
+        context.setTransform(transform.m[0],transform.m[1],transform.m[2],transform.m[3],transform.m[4],transform.m[5],transform.m[6]);
+    }
+
+    // Module exports
+    cornerstone.setToPixelCoordinateSystem = setToPixelCoordinateSystem;
+}(cornerstone));
+/**
+ * This module contains functions to deal with getting and setting the viewport for an enabled element
+ */
+(function (cornerstone) {
+
+    "use strict";
+
+    /**
+     * Sets the viewport for an element and corrects invalid values
+     *
+     * @param element - DOM element of the enabled element
+     * @param viewport - Object containing the viewport properties
+     * @returns {*}
+     */
+    function setViewport(element, viewport) {
+
+        var enabledElement = cornerstone.getEnabledElement(element);
+
+        enabledElement.viewport.scale = viewport.scale;
+        enabledElement.viewport.translation.x = viewport.translation.x;
+        enabledElement.viewport.translation.y = viewport.translation.y;
+        enabledElement.viewport.voi.windowWidth = viewport.voi.windowWidth;
+        enabledElement.viewport.voi.windowCenter = viewport.voi.windowCenter;
+        enabledElement.viewport.invert = viewport.invert;
+        enabledElement.viewport.pixelReplication = viewport.pixelReplication;
+        enabledElement.viewport.rotation = viewport.rotation;
+        enabledElement.viewport.hflip = viewport.hflip;
+        enabledElement.viewport.vflip = viewport.vflip;
+        enabledElement.viewport.modalityLUT = viewport.modalityLUT;
+        enabledElement.viewport.voiLUT = viewport.voiLUT;
+
+        // prevent window width from being too small (note that values close to zero are valid and can occur with
+        // PET images in particular)
+        if(enabledElement.viewport.voi.windowWidth < 0.000001) {
+            enabledElement.viewport.voi.windowWidth = 0.000001;
+        }
+        // prevent scale from getting too small
+        if(enabledElement.viewport.scale < 0.0001) {
+            enabledElement.viewport.scale = 0.25;
+        }
+
+        if(enabledElement.viewport.rotation===360 || enabledElement.viewport.rotation===-360) {
+            enabledElement.viewport.rotation = 0;
+        }
+
+        // Force the image to be updated since the viewport has been modified
+        cornerstone.updateImage(element);
+    }
+
+
+    // module/private exports
+    cornerstone.setViewport = setViewport;
+
+}(cornerstone));
+
+/**
+ * This module contains a function to immediately redraw an image
+ */
+(function (cornerstone) {
+
+    "use strict";
+
+    /**
+     * Forces the image to be updated/redrawn for the specified enabled element
+     * @param element
+     */
+    function updateImage(element, invalidated) {
+        var enabledElement = cornerstone.getEnabledElement(element);
+
+        if(enabledElement.image === undefined) {
+            throw "updateImage: image has not been loaded yet";
+        }
+
+        cornerstone.drawImage(enabledElement, invalidated);
+    }
+
+    // module exports
+    cornerstone.updateImage = updateImage;
+
+}(cornerstone));
\ No newline at end of file

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/js-url-1.8.6/README.md
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/js-url-1.8.6/README.md	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/js-url-1.8.6/README.md	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,89 @@
+# url()
+
+A simple, lightweight url parser for JavaScript (~1.6 Kb minified, ~0.6Kb gzipped).
+
+* [View the url demo](http://url.websanova.com)
+* [Download the lastest version of url](https://github.com/websanova/url/tags)
+
+
+## Notes
+
+For path(1) and path(-1) will always act as if the path is in the form `/some/path/` regardless of whether the original path was `/some/path` or `/some/path/`.
+
+
+## Examples
+
+```html
+http://rob:abcd1234@www.example.com/path/index.html?query1=test&silly=willy#test=hash&chucky=cheese
+```
+
+```js
+url();            // http://rob:abcd1234@www.example.com/path/index.html?query1=test&silly=willy#test=hash&chucky=cheese
+url('domain');    // example.com
+url('hostname');  // www.example.com
+url('sub');       // www
+url('.0')         // (an empty string)
+url('.1')         // www
+url('.2')         // example
+url('.-1')        // com
+url('auth')       // rob:abcd1234
+url('user')       // rob
+url('pass')       // abcd1234
+url('port');      // 80
+url('protocol');  // http
+url('path');      // /path/index.html
+url('file');      // index.html
+url('filename');  // index
+url('fileext');   // html
+url('1');         // path
+url('2');         // index.html
+url('3');         // (an empty string)
+url('-1');        // index.html
+url(1);           // path
+url(2);           // index.html
+url(-1);          // index.html
+url('?');         // query1=test&silly=willy
+url('?silly');    // willy
+url('?poo');      // null
+url('#');         // test=hash&chucky=cheese
+url('#chucky');   // cheese
+url('#poo');      // null
+```
+
+We can also pass a url in and use all the same options on it:
+
+```js
+url('domain', 'test.www.example.com/path/here');            // example.com
+url('hostname', 'test.www.example.com/path/here');          // test.www.example.com
+url('sub', 'test.www.example.com/path/here');               // test.www
+url('protocol', 'www.example.com/path/here');               // http
+url('path', 'http://www.example.com:8080/some/path');       // /some/path
+url('port', 'http://www.example.com:8080/some/path');       // 8080
+url('protocol', 'https://www.example.com:8080/some/path');  // https
+etc...
+```
+
+
+## jQuery
+
+Also include is a jQuery version of the plugin that can be called via $.url() with all the same options.  If you're already using jQuery it may be better to use the jQuery version to avoid namespacing issues.
+
+
+## Grunt.js
+
+If you want to use Grunt you will need to install the required plugins locally using `npm install` in the root folder of your project.  If you need to setup Grunt on your system you can follow my [Setting up Grunt.js](http://www.websanova.com/blog/javascript/how-to-setup-grunt) guide.
+
+
+## Resources
+
+* [More jQuery plugins by Websanova](http://websanova.com/plugins)
+* [Websanova JavaScript Extensions Project](http://websanova.com/extensions)
+* [jQuery Plugin Development Boilerplate](http://wboiler.websanova.com)
+* [The Ultimate Guide to Writing jQuery Plugins](http://www.websanova.com/blog/jquery/the-ultimate-guide-to-writing-jquery-plugins)
+
+
+## License
+
+MIT licensed
+
+Copyright (C) 2011-2012 Websanova http://www.websanova.com
\ No newline at end of file

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/js-url-1.8.6/url.jquery.json
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/js-url-1.8.6/url.jquery.json	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/js-url-1.8.6/url.jquery.json	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,35 @@
+{
+  "name": "url",
+  "title": "url jQuery Plugin",
+  "description": "A simple, lightweight url parser for JavaScript (~1.6 Kb minified, ~0.6Kb gzipped).",
+  "keywords": [
+    "websanova",
+    "url"
+  ],
+  "version": "1.8.6",
+  "author": {
+    "name": "Websanova",
+    "email": "rob at websanova.com",
+    "url": "http://websanova.com"
+  },
+  "maintainers": [
+    {
+      "name": "Websanova",
+      "email": "rob at websanova.com",
+      "url": "http://websanova.com"
+    }
+  ],
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "https://github.com/websanova/js-url#license"
+    }
+  ],
+  "bugs": "https://github.com/websanova/js-url/issues",
+  "homepage": "http://url.websanova.com",
+  "docs": "https://github.com/websanova/js-url#url",
+  "download": "https://github.com/websanova/js-url/tags",
+  "dependencies": {
+    "jquery": ">=1.5"
+  }
+}

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/js-url-1.8.6/url.js
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/js-url-1.8.6/url.js	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/js-url-1.8.6/url.js	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,81 @@
+window.url = (function() {
+    function isNumeric(arg) {
+      return !isNaN(parseFloat(arg)) && isFinite(arg);
+    }
+    
+    return function(arg, url) {
+        var _ls = url || window.location.toString();
+
+        if (!arg) { return _ls; }
+        else { arg = arg.toString(); }
+
+        if (_ls.substring(0,2) === '//') { _ls = 'http:' + _ls; }
+        else if (_ls.split('://').length === 1) { _ls = 'http://' + _ls; }
+
+        url = _ls.split('/');
+        var _l = {auth:''}, host = url[2].split('@');
+
+        if (host.length === 1) { host = host[0].split(':'); }
+        else { _l.auth = host[0]; host = host[1].split(':'); }
+
+        _l.protocol=url[0];
+        _l.hostname=host[0];
+        _l.port=(host[1] || ((_l.protocol.split(':')[0].toLowerCase() === 'https') ? '443' : '80'));
+        _l.pathname=( (url.length > 3 ? '/' : '') + url.slice(3, url.length).join('/').split('?')[0].split('#')[0]);
+        var _p = _l.pathname;
+
+        if (_p.charAt(_p.length-1) === '/') { _p=_p.substring(0, _p.length-1); }
+        var _h = _l.hostname, _hs = _h.split('.'), _ps = _p.split('/');
+
+        if (arg === 'hostname') { return _h; }
+        else if (arg === 'domain') {
+            if (/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/.test(_h)) { return _h; }
+            return _hs.slice(-2).join('.'); 
+        }
+        //else if (arg === 'tld') { return _hs.slice(-1).join('.'); }
+        else if (arg === 'sub') { return _hs.slice(0, _hs.length - 2).join('.'); }
+        else if (arg === 'port') { return _l.port; }
+        else if (arg === 'protocol') { return _l.protocol.split(':')[0]; }
+        else if (arg === 'auth') { return _l.auth; }
+        else if (arg === 'user') { return _l.auth.split(':')[0]; }
+        else if (arg === 'pass') { return _l.auth.split(':')[1] || ''; }
+        else if (arg === 'path') { return _l.pathname; }
+        else if (arg.charAt(0) === '.')
+        {
+            arg = arg.substring(1);
+            if(isNumeric(arg)) {arg = parseInt(arg, 10); return _hs[arg < 0 ? _hs.length + arg : arg-1] || ''; }
+        }
+        else if (isNumeric(arg)) { arg = parseInt(arg, 10); return _ps[arg < 0 ? _ps.length + arg : arg] || ''; }
+        else if (arg === 'file') { return _ps.slice(-1)[0]; }
+        else if (arg === 'filename') { return _ps.slice(-1)[0].split('.')[0]; }
+        else if (arg === 'fileext') { return _ps.slice(-1)[0].split('.')[1] || ''; }
+        else if (arg.charAt(0) === '?' || arg.charAt(0) === '#')
+        {
+            var params = _ls, param = null;
+
+            if(arg.charAt(0) === '?') { params = (params.split('?')[1] || '').split('#')[0]; }
+            else if(arg.charAt(0) === '#') { params = (params.split('#')[1] || ''); }
+
+            if(!arg.charAt(1)) { return params; }
+
+            arg = arg.substring(1);
+            params = params.split('&');
+
+            for(var i=0,ii=params.length; i<ii; i++)
+            {
+                param = params[i].split('=');
+                if(param[0] === arg) { return param[1] || ''; }
+            }
+
+            return null;
+        }
+
+        return '';
+    };
+})();
+
+if(typeof jQuery !== 'undefined') {
+    jQuery.extend({
+        url: function(arg, url) { return window.url(arg, url); }
+    });
+}
\ No newline at end of file

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/LICENSE.TXT
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/LICENSE.TXT	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/LICENSE.TXT	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,39 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Stefan Sträßer, http://stefanstraesser.eu/
+
+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.
+
+Hiermit wird unentgeltlich jeder Person, die eine Kopie der Software und der
+zugehörigen Dokumentationen (die "Software") erhält, die Erlaubnis erteilt,
+sie uneingeschränkt zu benutzen, inklusive und ohne Ausnahme dem Recht, sie
+zu verwenden, kopieren, ändern, fusionieren, verlegen, verbreiten, unterlizenzieren
+und/oder zu verkaufen, und Personen, die diese Software erhalten, diese Rechte zu
+geben, unter den folgenden Bedingungen:
+
+Der obige Urheberrechtsvermerk und dieser Erlaubnisvermerk sind in allen Kopien
+oder Teilkopien der Software beizulegen.
+
+DIE SOFTWARE WIRD OHNE JEDE AUSDRÜCKLICHE ODER IMPLIZIERTE GARANTIE BEREITGESTELLT,
+EINSCHLIESSLICH DER GARANTIE ZUR BENUTZUNG FÜR DEN VORGESEHENEN ODER EINEM BESTIMMTEN
+ZWECK SOWIE JEGLICHER RECHTSVERLETZUNG, JEDOCH NICHT DARAUF BESCHRÄNKT. IN KEINEM
+FALL SIND DIE AUTOREN ODER COPYRIGHTINHABER FÜR JEGLICHEN SCHADEN ODER SONSTIGE
+ANSPRÜCHE HAFTBAR ZU MACHEN, OB INFOLGE DER ERFÜLLUNG EINES VERTRAGES, EINES DELIKTES
+ODER ANDERS IM ZUSAMMENHANG MIT DER SOFTWARE ODER SONSTIGER VERWENDUNG DER SOFTWARE
+ENTSTANDEN.
\ No newline at end of file

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.eot
===================================================================
(Binary files differ)


Property changes on: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.eot
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.svg
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.svg	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.svg	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,57 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="jsglyphregular" horiz-adv-x="2048" >
+<font-face units-per-em="2048" ascent="1638" descent="-410" />
+<missing-glyph horiz-adv-x="500" />
+<glyph unicode="&#xd;" />
+<glyph unicode="&#x25fc;" horiz-adv-x="500" d="M0 0v0v0v0v0z" />
+<glyph unicode="&#xe001;" d="M1405 1739l-514 -811l-139 80l-107 -585l454 385l-139 80zM208 398q-20 0 -34 -14t-14 -34v-304q0 -20 14 -34t34 -14h1584q20 0 34 14t14 34v304q0 20 -14 34t-34 14h-1584z" />
+<glyph unicode="&#xe002;" d="M208 1678q-20 0 -34 -14t-14 -34v-232v-72v-1193v-90v-5q1 -17 14.5 -28.5t30.5 -11.5h1590q17 0 30.5 11.5t14.5 28.5v5v90v1193v72v232q0 20 -14 34t-34 14h-1584zM340 1278h1320v-1100h-1320v1100z" />
+<glyph unicode="&#xe003;" d="M994 1156q-35 -2 -64 -20l-708 -441q-27 -16 -45.5 -49t-18.5 -65q0 -55 38.5 -94t93.5 -39q40 0 73 22l637 397l637 -397q33 -22 73 -22q55 0 93.5 39t38.5 94q0 32 -18.5 65t-45.5 49l-708 441q-33 20 -70 20h-6z" />
+<glyph unicode="&#xe004;" d="M994 448q-35 2 -64 20l-708 441q-27 16 -45.5 49t-18.5 65q0 55 38.5 94t93.5 39q42 0 73 -21l637 -398l637 398q31 21 73 21q55 0 93.5 -39t38.5 -94q0 -32 -18.5 -65t-45.5 -49l-708 -441q-33 -20 -70 -20h-6z" />
+<glyph unicode="&#xe005;" d="M374 1563q-19 0 -32.5 -10.5t-13.5 -26.5v-150h115v56q0 16 13.5 27t32.5 11h1291q19 0 32.5 -11t13.5 -27v-1043q0 -16 -13.5 -26.5t-32.5 -10.5h-65v-94h183q17 0 29.5 9.5t13.5 22.5v3v70v926v56v181q0 16 -13.5 26.5t-32.5 10.5h-1521zM113 1349q-19 0 -32.5 -11 t-13.5 -27v-180v-56v-926v-70v-4q1 -13 13.5 -22t29.5 -9h1527q17 0 29.5 9t13.5 22v4v70v926v56v180q0 16 -13.5 27t-32.5 11h-1521zM240 1038h1267v-854h-1267v854z" />
+<glyph unicode="&#xe006;" d="M1658 1637h-2q-35 0 -72 -22t-54 -53l-654 -1132l-270 468q-17 30 -54 51t-72 21q-60 0 -102.5 -42.5t-42.5 -102.5q0 -37 20 -72l395 -685q17 -30 54 -51.5t72 -21.5t72 21.5t54 51.5l779 1349q20 35 20 75q0 59 -42 101.5t-101 43.5z" />
+<glyph unicode="&#xe007;" d="M291 1456q-18 -19 -30.5 -49.5t-12.5 -56.5q0 -27 13.5 -59t32.5 -51l491 -491l-491 -490q-19 -19 -32 -50.5t-13 -57.5q0 -63 45 -108t108 -45q26 0 57.5 13t50.5 32l491 490l490 -490q19 -19 50.5 -32t57.5 -13q63 0 108 45t45 108q0 26 -13 57.5t-32 50.5l-490 490 l490 491q19 19 32 50.5t13 57.5q0 63 -45 108t-108 45q-26 0 -57.5 -13t-50.5 -32l-490 -490l-491 490q-19 19 -50.5 32.5t-58.5 13.5t-59 -13.5t-51 -32.5z" />
+<glyph unicode="&#xe008;" d="M1354 896q-2 35 -20 64l-441 708q-16 27 -49 45.5t-65 18.5q-55 0 -94 -38.5t-39 -93.5q0 -42 21 -73l398 -637l-398 -637q-21 -31 -21 -72q0 -55 39 -94t94 -39q32 0 65 19t49 46l441 707q20 33 20 70v6z" />
+<glyph unicode="&#xe009;" d="M646 896q2 35 20 64l441 708q16 27 49 45.5t65 18.5q55 0 94 -38.5t39 -93.5q0 -42 -21 -73l-398 -637l398 -637q21 -32 21 -72q0 -55 -39 -94t-94 -39q-32 0 -65 19t-49 46l-441 707q-20 33 -20 70v6z" />
+<glyph unicode="&#xe00a;" d="M998 1623q-53 -1 -90.5 -39t-37.5 -91v-2v-485h-485h-2q-54 0 -92 -38t-38 -92t38 -92t92 -38h2h485v-485v-2q0 -54 38 -92t92 -38t92 38t38 92v2v485h485h2q54 0 92 38t38 92t-38 92t-92 38h-2h-485v485v2q0 54 -38 92t-92 38h-2z" />
+<glyph unicode="&#xe00b;" d="M385 1006h-2q-54 0 -92 -38t-38 -92t38 -92t92 -38h2h1230h2q54 0 92 38t38 92t-38 92t-92 38h-2h-1230z" />
+<glyph unicode="&#xe00c;" d="M1000 1709q-77 0 -131.5 -19.5t-54.5 -46.5q0 -24 44 -42.5t109 -22.5l-224 -53l-258 -60h515h515l-258 60l-224 53q65 4 109 22.5t44 42.5q0 27 -54.5 46.5t-131.5 19.5zM485 1464v-62h1030v62h-1030zM493 1283l89 -1284h840l85 1284h-1014z" />
+<glyph unicode="&#xe00d;" d="M992 210q-169 2 -312 86.5t-226 228.5t-83 314q0 128 50 244.5t134 200.5t200.5 134t244.5 50t244.5 -50t200.5 -134t134 -200.5t50 -244.5t-50 -244.5t-134 -200.5t-200.5 -134t-244.5 -50h-8zM900 334h200q8 0 14 6t6 14v640q0 8 -6 14t-14 6h-200q-8 0 -14 -6t-6 -14 v-640q0 -8 6 -14t14 -6zM902 1122h196q9 0 15.5 6.5t6.5 15.5v156q0 9 -6.5 15.5t-15.5 6.5h-196q-9 0 -15.5 -6.5t-6.5 -15.5v-156q0 -9 6.5 -15.5t15.5 -6.5zM280 1679q-50 0 -85 -35.5t-35 -84.5v-1440q0 -49 35 -84.5t85 -35.5h1440q50 0 85 35.5t35 84.5v1440 q0 49 -35 84.5t-85 35.5h-1440zM1000 1599q155 0 295.5 -60t242.5 -162t162 -242.5t60 -295.5t-60 -295.5t-162 -242.5t-242.5 -162t-295.5 -60t-295.5 60t-242.5 162t-162 242.5t-60 295.5t60 295.5t162 242.5t242.5 162t295.5 60z" />
+<glyph unicode="&#xe00e;" d="M280 1679q-50 0 -85 -35.5t-35 -84.5v-1440q0 -49 35 -84.5t85 -35.5h1440q50 0 85 35.5t35 84.5v1440q0 49 -35 84.5t-85 35.5h-1440zM1000 1624v1v-1q17 0 34.5 -11t25.5 -26l707 -1412q7 -14 7 -31q0 -28 -20 -47.5t-48 -19.5h-1412q-28 0 -48 19.5t-20 47.5 q0 17 7 31l707 1412q8 15 25.5 26t34.5 11zM1000 1406l-597 -1194h1194zM933 1075h134q22 0 37.5 -15.5t15.5 -37.5v-426q0 -22 -15.5 -37.5t-37.5 -15.5h-134q-22 0 -37.5 15.5t-15.5 37.5v426q0 22 15.5 37.5t37.5 15.5zM920 459h160q16 0 28 -11.5t12 -28.5v-80 q0 -17 -12 -28.5t-28 -11.5h-160q-16 0 -28 11.5t-12 28.5v80q0 17 12 28.5t28 11.5z" />
+<glyph unicode="&#xe00f;" d="M662 1305q-55 0 -93 -38l-184 -185q-38 -38 -38 -92t38 -92l246 -246l-246 -247q-38 -38 -38 -92t38 -92l184 -185q38 -38 92.5 -38t92.5 38l246 246l246 -246q38 -38 92.5 -38t92.5 38l184 185q38 38 38 92t-38 92l-246 247l246 246q38 38 38 92t-38 92l-184 185 q-38 38 -92.5 38t-92.5 -38l-246 -246l-246 246q-38 38 -92 38z" />
+<glyph unicode="&#xe010;" d="M160 1038v-1040h1680v1040h-1680zM322 975h2q24 0 40 -13l636 -505l636 505q16 13 40 13q26 0 45 -19t19 -45v-786v-64h-128v64v654l-572 -454q-18 -14 -40 -14t-40 14l-572 454v-654v-64h-128v64v786q0 26 18.5 44.5t43.5 19.5z" />
+<glyph unicode="&#xe011;" d="M227 1045l750 -643l750 643h-1500zM138 989v-894l501 465zM1816 989l-502 -429l502 -465v894zM715 494l-524 -487h1571l-524 487l-229 -196l-32 -28l-33 28zM138 12v-5h4zM1816 12l-4 -5h4v5z" />
+<glyph unicode="&#xe012;" d="M357 875v741l7 -4q14 -7 641.5 -370t629.5 -365l-15 -9q-14 -10 -41 -26t-58 -34q-528 -305 -630 -364q-121 -70 -254 -147t-200.5 -116t-69.5 -40l-10 -6v740z" />
+<glyph unicode="&#xe013;" d="M1000 871v740l4 -4q6 -8 280.5 -371t275.5 -364t-50 -69q-173 -228 -276 -364q-224 -294 -229 -302l-5 -7v741zM440 871v740l4 -4q6 -8 280.5 -371t275.5 -364t-50 -69q-173 -228 -276 -364q-224 -294 -229 -302l-5 -7v741z" />
+<glyph unicode="&#xe014;" d="M1000 871v740l-4 -4q-6 -8 -280.5 -371t-275.5 -364t50 -69q173 -228 276 -364q224 -294 229 -302l5 -7v741zM1560 871v740l-4 -4q-6 -8 -280.5 -371t-275.5 -364t50 -69q173 -228 276 -364q224 -294 229 -302l5 -7v741z" />
+<glyph unicode="&#xe015;" d="M620 1611v-741v-740l5 6q11 16 229 303q103 136 276 364q51 67 49 69q-1 1 -275.5 364t-280.5 371zM1180 1608v-1480h200v1480h-200z" />
+<glyph unicode="&#xe016;" d="M1380 1611v-741v-740l-5 6q-11 16 -229 303q-103 136 -276 364q-51 67 -49 69q1 1 275.5 364t280.5 371zM820 1608v-1480h-200v1480h200z" />
+<glyph unicode="&#xe017;" d="M337 1611v-741v-740l5 6q11 16 229 303q103 136 276 364q51 67 49 69q-1 1 -275.5 364t-280.5 371zM897 1611v-741v-740l5 6q11 16 229 303q103 136 276 364q51 67 49 69q-1 1 -275.5 364t-280.5 371zM1457 1608v-1480h206v1480h-206z" />
+<glyph unicode="&#xe018;" d="M1663 1611v-741v-740l-5 6q-11 16 -229 303q-103 136 -276 364q-51 67 -49 69q1 1 275.5 364t280.5 371zM1103 1611v-741v-740l-5 6q-11 16 -229 303q-103 136 -276 364q-51 67 -49 69q1 1 275.5 364t280.5 371zM543 1608v-1480h-206v1480h206z" />
+<glyph unicode="&#xe019;" d="M261 363h1480v200h-1480v-200zM259 676h740h740l-6 5q-1 1 -40.5 38.5t-115.5 109t-147 139.5q-136 129 -364 345q-67 64 -69 62q-2 -1 -365 -344t-370 -351z" />
+<glyph unicode="&#xe01a;" d="M374 1502h1252v-1252h-1252v1252z" />
+<glyph unicode="&#xe01b;" d="M680 1502v-1252h240v1252h-240zM1080 1502v-1252h240v1252h-240z" />
+<glyph unicode="&#xe01c;" d="M1022 517v358v358l310 -179l310 -179l-310 -179zM358 1019h851v-288h-851v288z" />
+<glyph unicode="&#xe01d;" d="M978 517v358v358l-310 -179l-310 -179l310 -179zM1642 1019h-851v-288h851v288z" />
+<glyph unicode="&#xe01e;" d="M642 897h358h358l-179 310l-179 310l-179 -310zM1144 233v851h-288v-851h288z" />
+<glyph unicode="&#xe01f;" d="M642 853h358h358l-179 -310l-179 -310l-179 310zM1144 1517v-851h-288v851h288z" />
+<glyph unicode="&#xe020;" d="M1001 1531l-37 -125l-110 -382l-397 14l-131 5l108 -73l330 -223l-137 -374l-44 -123l103 80l313 245l314 -245l102 -80l-44 122l-136 374l331 222l108 73l-131 -4l-397 -14l-110 383zM1000 1260l82 -285l8 -28l29 1l297 10l-193 -130h-446l-193 130l297 -10l29 -1l8 28z " />
+<glyph unicode="&#xe021;" d="M1416 251l-416 324l-415 -325l180 496l-438 294l528 -18l145 507l145 -506l528 18l-437 -295z" />
+<glyph unicode="&#xe022;" d="M1001 1531l-37 -125l-110 -382l-397 14l-131 5l108 -73l330 -223l-137 -374l-44 -123l102 81l314 244l314 -245l102 -81l-44 123l-136 374l330 222l109 73l-131 -4l-397 -14l-110 383zM1000 1260l82 -285l8 -28l29 1l297 10l-247 -166l-24 -17l10 -27l101 -279l-234 183 l-22 18l-24 -18l-234 -182l102 278l10 28l-24 16l-246 166l297 -10l29 -1l8 28z" />
+<glyph unicode="&#xe023;" d="M950 1373v-85v-190h-630h-50v-50v-320v-50h50h630v-190v-85l74 41l720 400l79 44l-79 44l-720 400zM1050 1203l567 -315l-567 -315v155v50h-50h-630v220h630h50v50v155z" />
+<glyph unicode="&#xe024;" d="M1143 1373v-85v-190h630h50v-50v-320v-50h-50h-630v-190v-85l-74 41l-720 400l-79 44l79 44l720 400zM1043 1203l-567 -315l567 -315v155v50h50h630v220h-630h-50v50v155z" />
+<glyph unicode="&#xe025;" d="M1485 792h-85h-190v-630v-50h-50h-320h-50v50v630h-190h-85l41 74l400 720l44 78l44 -78l400 -720zM1315 892l-315 567l-315 -567h155h50v-50v-630h220v630v50h50h155z" />
+<glyph unicode="&#xe026;" d="M1485 984h-85h-190v630v50h-50h-320h-50v-50v-630h-190h-85l41 -74l400 -720l44 -78l44 78l400 720zM1315 884l-315 -567l-315 567h155h50v50v630h220v-630v-50h50h155z" />
+<glyph unicode="&#xe027;" d="M1000 1468q-128 0 -244.5 -50t-200.5 -134t-134 -200.5t-50 -244.5t50 -244.5t134 -200.5t200.5 -134t244.5 -50t244.5 50t200.5 134t134 200.5t50 244.5t-50 244.5t-134 200.5t-200.5 134t-244.5 50zM831 1328h1h336q25 0 42.5 -17.5t17.5 -42.5v-303q0 -25 -17.5 -42.5 t-42.5 -17.5h-277v-185h277h1q25 0 42.5 -17t17.5 -42t-17.5 -42.5t-42.5 -17.5h-1h-336q-25 0 -42.5 17.5t-17.5 42.5v304q0 25 17.5 42t42.5 17h277v185h-277h-3q-25 0 -42 17t-17 42t17 42.5t42 17.5h2zM1000 524q46 0 78 -32t32 -78t-32 -78t-78 -32t-78 32t-32 78 t32 78t78 32zM280 1679q-50 0 -85 -35.5t-35 -84.5v-1440q0 -49 35 -84.5t85 -35.5h1440q50 0 85 35.5t35 84.5v1440q0 49 -35 84.5t-85 35.5h-1440zM1000 1599q155 0 295.5 -60t242.5 -162t162 -242.5t60 -295.5t-60 -295.5t-162 -242.5t-242.5 -162t-295.5 -60t-295.5 60 t-242.5 162t-162 242.5t-60 295.5t60 295.5t162 242.5t242.5 162t295.5 60z" />
+<glyph unicode="&#xe028;" d="M989 -1q-169 2 -322.5 69.5t-264.5 180t-176.5 267t-65.5 323.5q0 171 66.5 326.5t179 268t268 179t326.5 66.5t326.5 -66.5t268 -179t179 -268t66.5 -326.5t-66.5 -326.5t-179 -268t-268 -179t-326.5 -66.5h-11zM867 165h266q11 0 19 8t8 19v854q0 11 -8 19t-19 8h-266 q-11 0 -19 -8t-8 -19v-854q0 -11 8 -19t19 -8zM870 1217h260q12 0 21 9t9 21v207q0 12 -9 21t-21 9h-260q-12 0 -21 -9t-9 -21v-207q0 -12 9 -21t21 -9z" />
+<glyph unicode="&#xe029;" d="M989 1679q-169 -2 -322.5 -69.5t-264.5 -180t-176.5 -267t-65.5 -323.5q0 -171 66.5 -326.5t179 -268t268 -179t326.5 -66.5t326.5 66.5t268 179t179 268t66.5 326.5t-66.5 326.5t-179 268t-268 179t-326.5 66.5h-11zM867 1513h266q11 0 19 -8t8 -19v-854q0 -11 -8 -19 t-19 -8h-266q-11 0 -19 8t-8 19v854q0 11 8 19t19 8zM870 461h260q12 0 21 -9t9 -21v-207q0 -12 -9 -21t-21 -9h-260q-12 0 -21 9t-9 21v207q0 12 9 21t21 9z" />
+<glyph unicode="&#xe02a;" d="M989 1679q-169 -2 -322.5 -69.5t-264.5 -180t-176.5 -267t-65.5 -323.5q0 -171 66.5 -326.5t179 -268t268 -179t326.5 -66.5t326.5 66.5t268 179t179 268t66.5 326.5t-66.5 326.5t-179 268t-268 179t-326.5 66.5h-11zM775 1511h450q33 0 56 -23.5t23 -56.5v-405 q0 -33 -23 -56t-56 -23h-370v-247h370h1q33 0 56 -23t23 -56t-23 -56.5t-56 -23.5h-1h-450q-33 0 -56 23.5t-23 56.5v405q0 33 23 56t56 23h370v247h-370h-1q-33 0 -56 23t-23 56t23 56.5t56 23.5h1zM1000 439q61 0 104 -43t43 -104t-43 -104t-104 -43t-104 43t-43 104 t43 104t104 43z" />
+<glyph unicode="&#xe02b;" d="M1000 -2q-168 0 -321 65.5t-263.5 176t-176 263.5t-65.5 321t65.5 321t176 264t263.5 176.5t321 65.5t321 -65.5t263.5 -176.5t176 -264t65.5 -321t-65.5 -321t-176 -263.5t-263.5 -176t-321 -65.5zM1000 133q141 0 269 54.5t221 147t147.5 220.5t54.5 269 q0 188 -92.5 347.5t-252 252t-347.5 92.5t-347.5 -92.5t-252 -252t-92.5 -347.5q0 -141 54.5 -269t147.5 -220.5t221 -147t269 -54.5zM900 308q-8 0 -14 6t-6 14v640q0 8 6 14t14 6h200q8 0 14 -6t6 -14v-640q0 -8 -6 -14t-14 -6h-200zM902 1140q-9 0 -15.5 7t-6.5 16v155 q0 9 6.5 15.5t15.5 6.5h196q9 0 15.5 -6.5t6.5 -15.5v-155q0 -9 -6.5 -16t-15.5 -7h-196z" />
+<glyph unicode="&#xe02c;" d="M1000 1651q-168 0 -321 -65.5t-263.5 -176.5t-176 -264t-65.5 -321t65.5 -321t176 -263.5t263.5 -176t321 -65.5t321 65.5t263.5 176t176 263.5t65.5 321t-65.5 321t-176 264t-263.5 176.5t-321 65.5zM1000 1516q188 0 347.5 -92.5t252 -252t92.5 -347.5 q0 -141 -54.5 -269t-147.5 -220.5t-221 -147t-269 -54.5t-269 54.5t-221 147t-147.5 220.5t-54.5 269q0 188 92.5 347.5t252 252t347.5 92.5zM900 1340q-8 0 -14 -6t-6 -14v-640q0 -8 6 -14t14 -6h200q8 0 14 6t6 14v640q0 8 -6 14t-14 6h-200zM902 508q-9 0 -15.5 -6.5 t-6.5 -15.5v-155q0 -9 6.5 -16t15.5 -7h196q9 0 15.5 7t6.5 16v155q0 9 -6.5 15.5t-15.5 6.5h-196z" />
+<glyph unicode="&#xe02d;" d="M1110 391q0 -46 -32 -78.5t-78 -32.5t-78 32.5t-32 78.5t32 78t78 32t78 -32t32 -78zM832 1335h-1q-25 0 -42.5 -17t-17.5 -42t17.5 -42.5t42.5 -17.5h1h277v-184h-277q-25 0 -42.5 -17.5t-17.5 -42.5v-304q0 -25 17.5 -42t42.5 -17h336h3q25 0 42 17t17 42t-17 42.5 t-42 17.5h-3h-277v185h277q25 0 42.5 17t17.5 42v304q0 25 -17.5 42t-42.5 17h-336zM1000 1651q-168 0 -321 -65.5t-263.5 -176.5t-176 -264t-65.5 -321t65.5 -321t176 -263.5t263.5 -176t321 -65.5t321 65.5t263.5 176t176 263.5t65.5 321t-65.5 321t-176 264t-263.5 176.5 t-321 65.5zM1000 1516q141 0 269 -54.5t220.5 -147.5t147 -221t54.5 -269t-54.5 -269t-147 -220.5t-220.5 -147t-269 -54.5t-269 54.5t-220.5 147t-147 220.5t-54.5 269t54.5 269t147 221t220.5 147.5t269 54.5z" />
+</font>
+</defs></svg> 
\ No newline at end of file

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.ttf
===================================================================
(Binary files differ)


Property changes on: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.ttf
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.woff
===================================================================
(Binary files differ)


Property changes on: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.woff
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.woff2
===================================================================
(Binary files differ)


Property changes on: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/fonts/jsglyph.woff2
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/close-20-333.png
===================================================================
(Binary files differ)


Property changes on: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/close-20-333.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/close-20.png
===================================================================
(Binary files differ)


Property changes on: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/close-20.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/icon-sprite-16x16.jpg
===================================================================
(Binary files differ)


Property changes on: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/icon-sprite-16x16.jpg
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/icon-sprite-20x20.jpg
===================================================================
(Binary files differ)


Property changes on: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/icon-sprite-20x20.jpg
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/icon-sprite-32x32.jpg
===================================================================
(Binary files differ)


Property changes on: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/icon-sprite-32x32.jpg
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/resize-handle.png
===================================================================
(Binary files differ)


Property changes on: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/resize-handle.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/ui-icons_454545_256x240.png
===================================================================
(Binary files differ)


Property changes on: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/images/ui-icons_454545_256x240.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jquery.jspanel.css
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jquery.jspanel.css	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jquery.jspanel.css	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,524 @@
+/* jsPanel.css version 2.3.0 - 2014-12-17 09:22 */
+ at font-face {
+    font-family: 'jsglyphregular';
+    src: url('fonts/jsglyph.eot');
+    src: url('fonts/jsglyph.eot?#iefix') format('embedded-opentype'),
+         url('fonts/jsglyph.woff2') format('woff2'),
+         url('fonts/jsglyph.woff') format('woff'),
+         url('fonts/jsglyph.ttf') format('truetype'),
+         url('fonts/jsglyph.svg#jsglyphregular') format('svg');
+    font-weight: normal;
+    font-style: normal;
+}
+.jsglyph{ font-family: "jsglyphregular"; }
+
+.jsglyph-remove:before{ content: "\e007"; } /* unicode &#xe007; */
+.jsglyph-minimize:before{ content: "\e001"; }
+.jsglyph-maximize:before{ content: "\e002"; }
+.jsglyph-chevron-up:before{ content: "\e003"; }
+.jsglyph-chevron-down:before{ content: "\e004"; }
+.jsglyph-normalize:before{ content: "\e005"; }
+.jsglyph-circle2-exclamationmark:before{ content:"\e02c"; color:red; font-size:14px; }
+
+.jsPanel, .jsPanel-hdr-r, .jsPanel-hdr-l, .jsPanel-hdr-r span img, .jsPanel-content{
+    margin: 0; padding: 0; border: 0; font-size: 100%; line-height: 1.5em; vertical-align: baseline;
+}
+.jsPanel{
+    display: none;
+    overflow: hidden;
+    position: absolute;
+    border-radius: 3px;
+}
+.jsPanel-hdr{
+    min-height: 26px;
+    padding: 2px 0 4px 2px;
+}
+.jsPanel-hdr *{
+    font-size: 16px;
+}
+.jsPanel-hdr-r{
+    position: absolute;
+    top: 0;
+    right: 0;
+}
+h3.jsPanel-title{
+    float: left;
+    width: calc(100% - 90px);
+    white-space: nowrap;
+    overflow: hidden;
+    text-align: left;
+    text-overflow: ellipsis;
+    margin: 0;
+    font-variant: small-caps;
+    font-weight: normal;
+    cursor: move;
+    min-height: 20px;
+    padding: 0 5px;
+}
+.jsPanel-hdr-r div{
+    float: right;
+    cursor: pointer;
+	margin-left: 1px;
+    width: auto;
+    height: auto;
+    min-width: 20px;
+    min-height: 20px;
+    overflow: hidden;
+}
+
+.jsPanel-btn-close{ background: url("images/icon-sprite-20x20.jpg") 20px 0 repeat no-repeat; }
+.jsPanel-btn-max{ background: url("images/icon-sprite-20x20.jpg") 40px 0 repeat no-repeat; }
+.jsPanel-btn-norm{ background: url("images/icon-sprite-20x20.jpg") 60px 0 repeat no-repeat; display: none; }
+.jsPanel-btn-min{ background: url("images/icon-sprite-20x20.jpg") 80px 0 repeat no-repeat; }
+.jsPanel-btn-small{ background: url("images/icon-sprite-20x20.jpg") 100px 0 repeat no-repeat; }
+.jsPanel-btn-smallrev{ background: url("images/icon-sprite-20x20.jpg") 120px 0 repeat no-repeat; display: none; }
+
+
+.jsPanel-hdr-toolbar {
+    clear: both;
+    font-size: 16px;
+    margin-top: 23px;
+    height: auto;
+    padding: 0 5px;
+    width: auto;
+}
+.jsPanel-hdr-toolbar img{
+    cursor: pointer;
+    margin-bottom: 4px;
+    margin-right: 5px;
+}
+.jsPanel-content {
+    font-size: 12px;
+    position: relative;
+    width: 100%;
+    overflow: hidden;
+}
+.jsPanel-ftr{
+    cursor: move;
+    display: none;
+    font-size: 12px;
+    height: auto;
+    padding: 0;
+    text-align: right;
+}
+.jsPanel-ftr button{
+    margin: 5px;
+}
+/* change the default jQuery-UI resize icon */
+.ui-icon, .ui-widget-content .ui-icon {
+    background-image: url("images/ui-icons_454545_256x240.png");
+}
+.ui-icon-gripsmall-diagonal-se {
+    background-position: -81px -224px;
+}
+
+/* clearfix */
+.jsPanel-clearfix:after {
+    content: ".";
+    display: block;
+    height: 0;
+    clear: both;
+    visibility: hidden;
+}
+
+/* container that takes the minified jsPanels */
+#jsPanel-min-container{
+    position:fixed;
+    left:0;
+    bottom:0;
+    width:auto;
+    height:28px;
+    background:transparent;
+    z-index:1000;
+}
+
+/* css for the modal backdrop --------------------------- */
+.jsPanel-backdrop{
+    position:absolute;
+    top:0;
+    left:0;
+    z-index:1001;
+    width:100%;
+    background:rgba(0,0,0,0.8);
+}
+.jsPanel-backdrop-inner{
+    position:absolute;
+    width:100%;
+}
+/* THEMES ------------------------------------------------------------------------- */
+
+/* default ------------------------------------------- */
+.jsPanel.jsPanel-theme-default{
+    background: none repeat scroll 0 0 #fff;
+    box-shadow: 0 0 6px rgba(0, 33, 50, 0.1), 0 7px 25px rgba(17, 38, 60, 0.4);
+}
+.jsPanel-hdr.jsPanel-theme-default{
+    color: #000;
+    font-family: tahoma,arial,verdana,sans-serif;
+    font-weight: normal;
+}
+.jsPanel-hdr.jsPanel-theme-default h3{
+    color: #000;
+}
+.jsPanel-hdr.jsPanel-theme-default h3 small{
+    color: #000;
+    font-size: 65%;
+}
+.jsPanel-content.jsPanel-theme-default{
+    background: none repeat scroll 0 0 #FFFFFF;
+    color: #000;
+    font-family: tahoma,arial,verdana,sans-serif;
+    outline: 1px solid #bbb;
+}
+.jsPanel-ftr.jsPanel-theme-default{
+    font-size: 12px;
+    height: auto;
+    max-height: 40px;
+    min-height: 3px;
+    padding-right: 25px;
+    text-align: right;
+}
+
+/* light ------------------------------------------- */
+.jsPanel.jsPanel-theme-light{ background: #f1f1f1; box-shadow: 0 0 6px rgba(0, 33, 50, 0.1), 0 7px 25px rgba(17, 38, 60, 0.4); }
+.jsPanel-hdr.jsPanel-theme-light{ color: #000; font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: #f1f1f1; }
+.jsPanel-hdr.jsPanel-theme-light h3{ color: #000; }
+.jsPanel-hdr.jsPanel-theme-light h3 small{ color: #000; font-size: 65%; }
+.jsPanel-content.jsPanel-theme-light { background: none repeat scroll 0 0 #FFFFFF; color: #000; font-family: tahoma,arial,verdana,sans-serif; outline: 1px solid #bbb; }
+.jsPanel-ftr.jsPanel-theme-light{ color: #000; font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: #f1f1f1; }
+
+/* medium -------------------------------------------- */
+.jsPanel.jsPanel-theme-medium{ background: #c2c2c2; box-shadow: 0 0 6px rgba(0, 33, 50, 0.1), 0 7px 25px rgba(17, 38, 60, 0.4); }
+.jsPanel-hdr.jsPanel-theme-medium{ color: #000; font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: #c2c2c2; }
+.jsPanel-hdr.jsPanel-theme-medium h3{ color: #000; }
+.jsPanel-hdr.jsPanel-theme-medium h3 small{ color: #000; font-size: 65%; }
+.jsPanel-content.jsPanel-theme-medium { background: none repeat scroll 0 0 #fff; color: #000; font-family: tahoma,arial,verdana,sans-serif; outline: 1px solid #bbb; }
+.jsPanel-ftr.jsPanel-theme-medium{ color: #000; font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: #c2c2c2; }
+
+/* dark ---------------------------------------------- */
+.jsPanel.jsPanel-theme-dark{ background: #828282; box-shadow: 0 0 6px rgba(0, 33, 50, 0.1), 0 7px 25px rgba(17, 38, 60, 0.4); }
+.jsPanel-hdr.jsPanel-theme-dark{ color: #fff; font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: #828282; }
+.jsPanel-hdr.jsPanel-theme-dark h3{ color: #fff; }
+.jsPanel-hdr.jsPanel-theme-dark h3 small{ color: #fff; font-size: 65%; }
+.jsPanel-content.jsPanel-theme-dark { background: none repeat scroll 0 0 #fff; color: #000; font-family: tahoma,arial,verdana,sans-serif; outline: 1px solid #bbb; }
+.jsPanel-ftr.jsPanel-theme-dark{ color: #fff; font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: #828282; }
+
+/* black ---------------------------------------------- */
+/* .jsPanel.jsPanel-theme-black{ background: -moz-linear-gradient(center top , #4a4a4a 0px, black 100%) repeat scroll 0 0 #4a4a4a; box-shadow: 0 0 3px #333; } */
+.jsPanel.jsPanel-theme-black{ background: linear-gradient(#4a4a4a 0px, black 100%) repeat scroll 0 0 #4a4a4a; background: -ms-linear-gradient(#4a4a4a 0px, black 100%) repeat scroll 0 0 #4a4a4a; box-shadow: 0 0 3px #333; }
+.jsPanel-hdr.jsPanel-theme-black{ color: #fff; font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: transparent; }
+.jsPanel-hdr.jsPanel-theme-black h3{ color: #fff; }
+.jsPanel-hdr.jsPanel-theme-black h3 small{ color: #fff; font-size: 65%; }
+.jsPanel-content.jsPanel-theme-black { background: transparent; color: #c3c3c3; }
+.jsPanel-ftr.jsPanel-theme-black{ color: #fff; font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: transparent; }
+
+/* primary ---------------------------------------------- */
+.jsPanel.jsPanel-theme-primary{ background: none repeat scroll 0 0 #fff; box-shadow: 0 0 6px rgba(0, 33, 50, 0.1), 0 7px 25px rgba(17, 38, 60, 0.4); }
+.jsPanel-hdr.jsPanel-theme-primary{ color: #ffffff; font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: #2FA4E7; }
+.jsPanel-hdr.jsPanel-theme-primary h3{ color: #fff; }
+.jsPanel-hdr.jsPanel-theme-primary h3 small{ color: #fff; font-size: 65%; }
+.jsPanel-content.jsPanel-theme-primary { background: none repeat scroll 0 0 #ffffff; color: #000000; font-family: tahoma,arial,verdana,sans-serif; outline: 1px solid #bbb; }
+.jsPanel-ftr.jsPanel-theme-primary{}
+
+/* success --------------------------------------------- */
+.jsPanel.jsPanel-theme-success{ background: none repeat scroll 0 0 #fff; box-shadow: 0 0 6px rgba(0, 33, 50, 0.1), 0 7px 25px rgba(17, 38, 60, 0.4); }
+.jsPanel-hdr.jsPanel-theme-success{ color: #fff; font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: #73A839; }
+.jsPanel-hdr.jsPanel-theme-success h3{ color: #fff; }
+.jsPanel-hdr.jsPanel-theme-success h3 small{ color: #fff; font-size: 65%; }
+.jsPanel-content.jsPanel-theme-success { background: none repeat scroll 0 0 #ffffff; color: #000000; font-family: tahoma,arial,verdana,sans-serif; outline: 1px solid #bbb; }
+.jsPanel-ftr.jsPanel-theme-success{}
+
+/* info ---------------------------------------------- */
+.jsPanel.jsPanel-theme-info{ background: none repeat scroll 0 0 #fff; box-shadow: 0 0 6px rgba(0, 33, 50, 0.1), 0 7px 25px rgba(17, 38, 60, 0.4); }
+.jsPanel-hdr.jsPanel-theme-info{ color: #fff; font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: #033C73; }
+.jsPanel-hdr.jsPanel-theme-info h3{ color: #fff; }
+.jsPanel-hdr.jsPanel-theme-info h3 small{ color: #fff; font-size: 65%; }
+.jsPanel-content.jsPanel-theme-info { background: none repeat scroll 0 0 #ffffff; color: #000000; font-family: tahoma,arial,verdana,sans-serif; outline: 1px solid #bbb; }
+.jsPanel-ftr.jsPanel-theme-info{}
+
+/* warning ----------------------------------------------- */
+.jsPanel.jsPanel-theme-warning{ background: none repeat scroll 0 0 #fff; box-shadow: 0 0 6px rgba(0, 33, 50, 0.1), 0 7px 25px rgba(17, 38, 60, 0.4); }
+.jsPanel-hdr.jsPanel-theme-warning{ color: #fff; font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: #DD5600; }
+.jsPanel-hdr.jsPanel-theme-warning h3{ color: #fff; }
+.jsPanel-hdr.jsPanel-theme-warning h3 small{ color: #fff; font-size: 65%; }
+.jsPanel-content.jsPanel-theme-warning { background: none repeat scroll 0 0 #fff; color: #000; font-family: tahoma,arial,verdana,sans-serif; outline: 1px solid #bbb; }
+.jsPanel-ftr.jsPanel-theme-warning{}
+
+/* danger ----------------------------------------------- */
+.jsPanel.jsPanel-theme-danger{ background: none repeat scroll 0 0 #fff; box-shadow: 0 0 6px rgba(0, 33, 50, 0.1), 0 7px 25px rgba(17, 38, 60, 0.4); }
+.jsPanel-hdr.jsPanel-theme-danger{ color: #fff; font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: #C71C22; }
+.jsPanel-hdr.jsPanel-theme-danger h3{ color: #fff; }
+.jsPanel-hdr.jsPanel-theme-danger h3 small{ color: #fff; font-size: 65%; }
+.jsPanel-content.jsPanel-theme-danger { background: none repeat scroll 0 0 #ffffff; color: #000000; font-family: tahoma,arial,verdana,sans-serif; outline: 1px solid #bbb; }
+.jsPanel-ftr.jsPanel-theme-danger{}
+
+/* autumngreen ------------------------------------------- */
+.jsPanel.jsPanel-theme-autumngreen{ background: none repeat scroll 0 0 #eee; box-shadow: 0 0 6px rgba(0, 33, 50, 0.1), 0 7px 25px rgba(17, 38, 60, 0.4); }
+.jsPanel-hdr.jsPanel-theme-autumngreen{ color: rgb(196, 189, 142); font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: rgb(125, 126, 81); }
+.jsPanel-hdr.jsPanel-theme-autumngreen h3{ color: rgb(196, 189, 142); }
+.jsPanel-hdr.jsPanel-theme-autumngreen h3 small{ color: #fff; font-size: 65%; }
+.jsPanel-content.jsPanel-theme-autumngreen { background: none repeat scroll 0 0 #FFFFFF; color: rgb(110, 100, 73); font-family: tahoma,arial,verdana,sans-serif; outline: 1px solid #bbb; }
+.jsPanel-ftr.jsPanel-theme-autumngreen{ background: linear-gradient(to bottom,  rgba(125, 126, 81,0.65) 0%,rgba(0,0,0,0) 100%); background: -ms-linear-gradient(top,  rgba(125, 126, 81,0.65) 0%,rgba(0,0,0,0) 100%); color: rgb(110, 100, 73); }
+
+/* autumnbrown ------------------------------------------- */
+.jsPanel.jsPanel-theme-autumnbrown{ background: none repeat scroll 0 0 #eee; box-shadow: 0 0 6px rgba(0, 33, 50, 0.1), 0 7px 25px rgba(17, 38, 60, 0.4); }
+.jsPanel-hdr.jsPanel-theme-autumnbrown{ color: rgb(233, 206, 135); font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: rgb(138, 91, 51); }
+.jsPanel-hdr.jsPanel-theme-autumnbrown h3{ color: rgb(233, 206, 135); }
+.jsPanel-hdr.jsPanel-theme-autumnbrown h3 small{ color: #fff; font-size: 65%; }
+.jsPanel-content.jsPanel-theme-autumnbrown { background: none repeat scroll 0 0 #FFFFFF; color: rgb(110, 100, 73); font-family: tahoma,arial,verdana,sans-serif; outline: 1px solid #bbb; }
+.jsPanel-ftr.jsPanel-theme-autumnbrown{ background: linear-gradient(to bottom,  rgba(138, 91, 51,0.65) 0%,rgba(0,0,0,0) 100%); background: -ms-linear-gradient(top,  rgba(138, 91, 51,0.65) 0%,rgba(0,0,0,0) 100%); color: rgb(110, 100, 73); }
+
+/* autumnred ------------------------------------------- */
+.jsPanel.jsPanel-theme-autumnred{ background: none repeat scroll 0 0 #eee; box-shadow: 0 0 6px rgba(0, 33, 50, 0.1), 0 7px 25px rgba(17, 38, 60, 0.4); }
+.jsPanel-hdr.jsPanel-theme-autumnred{ color: rgb(233, 206, 135); font-family: tahoma,arial,verdana,sans-serif; font-weight: normal; background: rgb(198, 113, 10); }
+.jsPanel-hdr.jsPanel-theme-autumnred h3{ color: rgb(233, 206, 135); }
+.jsPanel-hdr.jsPanel-theme-autumnred h3 small{ color: #fff; font-size: 65%; }
+.jsPanel-content.jsPanel-theme-autumnred { background: none repeat scroll 0 0 #FFFFFF; color: rgb(110, 100, 73); font-family: tahoma,arial,verdana,sans-serif; outline: 1px solid #bbb; }
+.jsPanel-ftr.jsPanel-theme-autumnred{ background: linear-gradient(to bottom,  rgba(198, 113, 10,0.65) 0%,rgba(0,0,0,0) 100%); background: -ms-linear-gradient(top,  rgba(198, 113, 10,0.65) 0%,rgba(0,0,0,0) 100%); color: rgb(110, 100, 73); }
+
+/* bootstrap adjustments */
+.jsPanel.panel-default, .jsPanel.panel-primary, .jsPanel.panel-info, .jsPanel.panel-success, .jsPanel.panel-warning, .jsPanel.panel-danger{
+    box-shadow: 0 0 6px rgba(0, 33, 50, 0.1), 0 7px 25px rgba(17, 38, 60, 0.4);
+}
+
+/* css for the tooltip wrapper ---------------------------- */
+.jsPanel-tooltip-wrapper{
+    position: relative;
+    display: inline-block;
+    margin: 0;
+    padding: 0;
+    border: none;
+    background: transparent;
+}
+.jsPanel-tooltip-wrapper .jsPanel{
+    border-radius: 4px;
+}
+
+/* css for tooltip corners */
+.jsPanel-corner-top, .jsPanel-corner-right, .jsPanel-corner-bottom, .jsPanel-corner-left{
+    width: 0;
+    height: 0;
+    border: 12px solid transparent;
+    position: absolute;
+}
+.jsPanel-corner-top{
+    border-top-width: 10px;
+}
+.jsPanel-corner-right{
+    border-right-width: 10px;
+}
+.jsPanel-corner-bottom{
+    border-bottom-width: 10px;
+}
+.jsPanel-corner-left{
+    border-left-width: 10px;
+}
+
+
+
+
+/* css for the hints ----------------------------------------------------------- */
+.jsPanel-hint{
+    margin-bottom: 4px;
+}
+.jsPanel-hint-close-dark, .jsPanel-hint-close-white{
+    float:right;
+    margin:5px;
+    cursor: pointer;
+    width: 20px;
+    height: 20px;
+}
+.jsPanel-hint-close-dark{
+    background: url('images/close-20-333.png');
+}
+.jsPanel-hint-close-white{
+    background: url('images/close-20.png');
+}
+.jsPanel-hint-content.jsPanel-hint-default{
+    background: none repeat scroll 0 0 #fff;
+    color: #000;
+}
+.jsPanel-hint-content.jsPanel-hint-light{
+    background: linear-gradient(to bottom,  rgba(0,0,0,0) 0%,rgba(250,250,250,0.65) 100%); background: -ms-linear-gradient(top,  rgba(0,0,0,0) 0%,rgba(250,250,250,0.65) 100%);
+    color: #000;
+}
+.jsPanel-hint-content.jsPanel-hint-medium{
+    background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(130, 130, 130, 1) 100%); background: -ms-linear-gradient(top, rgba(0, 0, 0, 0) 0%, rgba(130, 130, 130, 1) 100%);
+    color: #fff;
+}
+.jsPanel-hint-content.jsPanel-hint-dark{
+    background: linear-gradient(to bottom, rgba(181,189,200,1) 0%,rgba(130,140,149,1) 36%,rgba(40,52,59,1) 100%); background: -ms-linear-gradient(top, rgba(181,189,200,1) 0%,rgba(130,140,149,1) 36%,rgba(40,52,59,1) 100%);
+    color: #fff;
+}
+.jsPanel-hint-content.jsPanel-hint-autumngreen{
+    background: linear-gradient(to bottom,  rgba(125, 126, 81,0.65) 0%,rgba(0,0,0,0) 100%); background: -ms-linear-gradient(top,  rgba(125, 126, 81,0.65) 0%,rgba(0,0,0,0) 100%);
+    color: #fff;
+}
+.jsPanel-hint-content.jsPanel-hint-autumnbrown{
+    background: linear-gradient(to bottom,  rgba(138, 91, 51,0.65) 0%,rgba(0,0,0,0) 100%); background: -ms-linear-gradient(top,  rgba(138, 91, 51,0.65) 0%,rgba(0,0,0,0) 100%);
+    color: #fff;
+}
+.jsPanel-hint-content.jsPanel-hint-autumnred{
+    background: linear-gradient(to bottom,  rgba(198, 113, 10,0.65) 0%,rgba(0,0,0,0) 100%); background: -ms-linear-gradient(top,  rgba(198, 113, 10,0.65) 0%,rgba(0,0,0,0) 100%);
+    color: #fff;
+}
+.jsPanel-hint-content.jsPanel-hint-primary{
+    background: none repeat scroll 0 0 #2FA4E7;
+    color: #fff;
+}
+.jsPanel-hint-content.jsPanel-hint-info{
+    background: none repeat scroll 0 0 #033C73;
+    color: #fff;
+}
+.jsPanel-hint-content.jsPanel-hint-success{
+    background: none repeat scroll 0 0 #73A839;
+    color: #fff;
+}
+.jsPanel-hint-content.jsPanel-hint-warning{
+    background: none repeat scroll 0 0 #DD5600;
+    color: #fff;
+}
+.jsPanel-hint-content.jsPanel-hint-danger{
+    background: none repeat scroll 0 0 #C71C22;
+    color: #fff;
+}
+
+/**
+ * CSS3 Tips v1.0.1
+ * A stylesheet for creating tooltips without using anything other than CSS3.
+ * created by c.bavota
+ * released under GPL v2
+ * March 21st, 2014
+ * HTML example code for the tooltip
+ * <a href="http://bavotasan.com" class="top-tip" data-tips="Go to bavotasan.com">bavotasan.com</a>
+*/
+[data-tips] {
+    position: relative;
+    text-decoration: none;
+}
+[data-tips]:after,
+[data-tips]:before {
+    position: absolute;
+    z-index: 100;
+    opacity: 0;
+}
+[data-tips]:after {
+    content: attr(data-tips);
+    height: 25px;
+    line-height: 25px;
+    padding: 0 10px;
+    font-size: 12px;
+    text-align: center;
+    color: #fff;
+    background: #3276B1;
+    border-radius: 4px;
+    /*text-shadow: 0 0 5px #000;*/
+    -moz-box-shadow: 0 0 5px rgba(0,0,0,0.3);
+    -webkit-box-shadow: 0 0 5px rgba(0,0,0,0.3);
+    box-shadow: 0 0 5px rgba(0,0,0,0.3);
+    white-space: nowrap;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+}
+[data-tips]:before {
+    content: "";
+    width: 0;
+    height: 0;
+    border-width: 6px;
+    border-style: solid;
+}
+
+[data-tips]:hover:after,
+[data-tips]:hover:before {
+    opacity: 1;
+}
+/* Top tips */
+[data-tips].top-tip:after,
+[data-tips].top-tip:before {
+    -webkit-transition: bottom 0.25s ease-in-out, opacity 0.25s ease-in-out;
+    -moz-transition: bottom 0.25s ease-in-out, opacity 0.25s ease-in-out;
+    transition: bottom 0.25s ease-in-out, opacity 0.25s ease-in-out;
+    bottom: 90%;
+    left: -9999px;
+    margin-bottom: 12px;
+}
+[data-tips].top-tip:before {
+    border-color: #3276B1 transparent transparent transparent;
+    margin-bottom: 0;
+}
+[data-tips].top-tip:hover:after,
+[data-tips].top-tip:hover:before {
+    bottom: 100%;
+    left: 0;
+}
+[data-tips].top-tip:hover:before {
+    left: 15px;
+}
+/* Bottom tip */
+[data-tips].bottom-tip:after,
+[data-tips].bottom-tip:before {
+    -webkit-transition: top 0.25s ease-in-out, opacity 0.25s ease-in-out;
+    -moz-transition: top 0.25s ease-in-out, opacity 0.25s ease-in-out;
+    transition: top 0.25s ease-in-out, opacity 0.25s ease-in-out;
+    top: 90%;
+    left: -9999px;
+    margin-top: 12px;
+}
+[data-tips].bottom-tip:before {
+    border-color: transparent transparent #3276B1 transparent;
+    margin-top: 0;
+}
+[data-tips].bottom-tip:hover:after,
+[data-tips].bottom-tip:hover:before {
+    top: 100%;
+    left: 0;
+}
+[data-tips].bottom-tip:hover:before {
+    left: 15px;
+}
+/* Right tip */
+[data-tips].right-tip:after,
+[data-tips].right-tip:before {
+    -webkit-transition: left 0.25s ease-in-out, opacity 0.25s ease-in-out;
+    -moz-transition: left 0.25s ease-in-out, opacity 0.25s ease-in-out;
+    transition: left 0.25s ease-in-out, opacity 0.25s ease-in-out;
+    top: -9999px;
+    left: 96%;
+    margin-left: 12px;
+}
+[data-tips].right-tip:before {
+    border-color: transparent #3276B1 transparent transparent;
+    margin-left: 0;
+}
+[data-tips].right-tip:hover:after,
+[data-tips].right-tip:hover:before {
+    left: 100%;
+    top: 0;
+}
+
+[data-tips].right-tip:hover:before {
+    top: 7px;
+}
+/* Left tip */
+[data-tips].left-tip:after,
+[data-tips].left-tip:before {
+    -webkit-transition: right 0.25s ease-in-out, opacity 0.25s ease-in-out;
+    -moz-transition: right 0.25s ease-in-out, opacity 0.25s ease-in-out;
+    transition: right 0.25s ease-in-out, opacity 0.25s ease-in-out;
+    top: -9999px;
+    right: 96%;
+    margin-right: 12px;
+}
+[data-tips].left-tip:before {
+    border-color: transparent transparent transparent #3276B1;
+    margin-right: 0;
+}
+[data-tips].left-tip:hover:after,
+[data-tips].left-tip:hover:before {
+    right: 100%;
+    top: 0;
+}
+[data-tips].left-tip:hover:before {
+    top: 7px;
+}

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jquery.jspanel.js
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jquery.jspanel.js	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jquery.jspanel.js	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,3050 @@
+/* global console, MobileDetect */
+/* jQuery Plugin jsPanel
+ Dependencies:
+     jQuery library ( > 1.7.0 incl. 2.1.1 )
+     jQuery.UI library ( > 1.9.0 ) - (at least UI Core, Mouse, Widget, Draggable, Resizable)
+     mobile-detect.js for the responsive features <https://github.com/hgoebl/mobile-detect.js>
+     bootstrap (required only when using the bootstrap features)
+     HTML5/CSS3 compatible browser
+
+ Copyright (c) 2014 Stefan Sträßer, <http://stefanstraesser.eu/>
+
+ 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.
+
+ You should have received a copy of the MIT License along with this program.  If not, see <http://opensource.org/licenses/MIT>.
+ */
+
+/*
+ ### changes in 2.3.3 ###
+ + bugfix in handling of z-index values
+ ### changes in 2.3.2 ###
+ + bugfix in handling of z-index values for modals
+ ### changes in 2.3.1 ###
+ + bugfix in handling of z-index values
+ ### changes in 2.3.0 ###
+ + new method .resize(width, height) to resize an exsisting jsPanel by code
+ + new jsPanel property "device" return NULL if device is not a mobile
+ + new jsPanel.getMargins() to calculate panel margins either relative to browser viewport or panel.parent
+ + functions to shift tooltips horizontally or vertically depending on position used
+ + rewriteOPaneltype slightly adjusted
+ + Tooltips: option.paneltype NEW property "shiftwithin" to set the element witin a tooltip is to be repositioned; default "body"
+ + jsPanel function calcPosTooltipLeft und calcPosTooltipTop improved to consider possible margins of tooltrip trigger
+*/
+
+var jsPanel;
+
+(function($){
+    "use strict";
+    jsPanel = {
+        version: '2.3.3 2015-02-06 09:17',
+        device: (function(){
+            try {
+                // requires "mobile-detect.js" to be loaded
+                var md = new MobileDetect(window.navigator.userAgent),
+                    mobile = md.mobile(),
+                    phone = md.phone(),
+                    tablet = md.tablet(),
+                    os = md.os(),
+                    userAgent = md.userAgent();
+                return {mobile: mobile, tablet: tablet, phone: phone, os: os, userAgent: userAgent};
+            } catch (e) {
+                console.log(e + "; Seems like mobile-detect.js is not loaded");
+                return false;
+            }
+        })(),
+        ID: 0,                  // kind of a counter to add to automatically generated id attribute
+        widthForMinimized: 150, // default width of minimized panels
+        hintsTc: [],            // arrays that log hints for option.position 'top center', 'top left' and 'top right'
+        hintsTl: [],
+        hintsTr: [],
+
+        template: '<div class="jsPanel jsPanel-theme-default">' +
+                    '<div class="jsPanel-hdr jsPanel-theme-default">' +
+                        '<h3 class="jsPanel-title"></h3>' +
+                        '<div class="jsPanel-hdr-r">' +
+                            '<div class="jsPanel-btn-close"><span class="jsglyph jsglyph-remove"></span></div>' +
+                            '<div class="jsPanel-btn-max"><span class="jsglyph jsglyph-maximize"></span></div>' +
+                            '<div class="jsPanel-btn-norm"><span class="jsglyph jsglyph-normalize"></span></div>' +
+                            '<div class="jsPanel-btn-min"><span class="jsglyph jsglyph-minimize"></span></div>' +
+                            '<div class="jsPanel-btn-small"><span class="jsglyph jsglyph-chevron-up"></span></div>' +
+                            '<div class="jsPanel-btn-smallrev"><span class="jsglyph jsglyph-chevron-down"></span></div>' +
+                        '</div>' +
+                        '<div class="jsPanel-hdr-toolbar jsPanel-clearfix"></div>' +
+                    '</div>' +
+                    '<div class="jsPanel-content jsPanel-theme-default"></div>' +
+                    '<div class="jsPanel-ftr jsPanel-theme-default jsPanel-clearfix"></div>' +
+                  '</div>',
+
+        // add toolbar
+        addToolbar: function (panel, place, items) {
+
+            if (place === 'header') {
+
+                jsPanel.configToolbar(items, panel.header.toolbar, panel);
+
+            } else if (place === 'footer') {
+
+                panel.footer.css({
+                    display: 'block',
+                    padding: '5px 20px 5px 5px'
+                });
+
+                jsPanel.configToolbar(items, panel.footer, panel);
+
+            }
+
+            return panel;
+
+        },
+
+        // used in option.autoclose and checks prior use of .close() whether the panel is still there
+        autoclose: function (panel, id, optionAutoclose) {
+
+                window.setTimeout(function () {
+
+                    var elmt = $('#' + id);
+
+                        if (elmt.length > 0) {
+
+                            elmt.fadeOut('slow', function () {
+
+                                panel.close(); // elmt geht hier nicht weil .close() nicht für elmt definiert ist
+
+                            });
+
+                        }
+
+                }, optionAutoclose);
+
+        },
+
+        calcPos: function (prop, option, count, panel) {
+
+            if (option.position[prop] === 'auto') {
+
+                option.position[prop] = count * 30 + 'px';
+
+            } else if ($.isFunction(option.position[prop])) {
+
+                option.position[prop] = option.position[prop](panel);
+
+            } else if (option.position[prop] === 0) {
+
+                option.position[prop] = '0';
+
+            } else {
+
+                option.position[prop] = parseInt(option.position[prop], 10) + 'px';
+
+            }
+
+            // corrections if jsPanel is appended to the body element
+            if (option.selector === 'body') {
+
+                if (prop === 'top') {
+
+                    option.position.top = parseInt(option.position.top, 10) + jsPanel.winscrollTop() + 'px';
+
+                }
+
+                if (prop === 'bottom') {
+
+                    option.position.bottom = parseInt(option.position.bottom, 10) - jsPanel.winscrollTop() + 'px';
+
+                }
+
+                if (prop === 'left') {
+
+                    option.position.left = parseInt(option.position.left, 10) + jsPanel.winscrollLeft() + 'px';
+
+                }
+
+                if (prop === 'right') {
+
+                    option.position.right = parseInt(option.position.right, 10) - jsPanel.winscrollLeft() + 'px';
+
+                }
+            }
+
+            return option.position[prop];
+
+        },
+
+        // calculate position center for option.position == 'center'
+        calcPosCenter: function (option) {
+
+            var posL = ($(option.selector).outerWidth() / 2) - ((parseInt(option.size.width, 10) / 2)),
+                posT;
+
+            if (option.selector === 'body') {
+
+                posT = ($(window).outerHeight() / 2) - ((parseInt(option.size.height, 10) / 2) - jsPanel.winscrollTop());
+
+            } else {
+
+                posT = ($(option.selector).outerHeight() / 2) - ((parseInt(option.size.height, 10) / 2));
+
+            }
+
+            return {top: posT + 'px', left: posL + 'px'};
+
+        },
+
+        // calculate position for maximized panels using option.controls.maxtoScreen (for devmondo)
+        calcPosmaxtoScreen: function(panel) {
+
+            var offset = panel.offset(),
+                newPos = {
+                    top: parseInt(panel.css('top')) - (offset.top - $(document).scrollTop()) + 5 + 'px',
+                    left: parseInt(panel.css('left')) - (offset.left - $(document).scrollLeft()) + 5 + 'px'
+                };
+
+            return newPos;
+
+        },
+
+        // calculates css left for tooltips
+        calcPosTooltipLeft: function (pos, jsPparent, option) {
+
+            // width of element serving as trigger for the tooltip
+            var parW = jsPparent.outerWidth(),
+                // check possible margins of  trigger
+                mL = parseInt(jsPparent.css('margin-left')),
+                // check whether offset is set
+                oX = option.offset.left || 0;
+
+            if (pos === 'top' || pos === 'bottom') {
+
+                return (parW - option.size.width) / 2 + mL + oX + 'px';
+
+            }
+
+            if (pos === 'left') {
+
+                return -(option.size.width) + mL + oX + 'px';
+
+            }
+
+            if (pos === 'right') {
+
+                return parW + mL + oX + 'px';
+
+            }
+
+            return false;
+
+        },
+
+        // calculates css top for tooltips
+        calcPosTooltipTop: function (pos, jsPparent, option) {
+
+            var parH = jsPparent.innerHeight(),
+                mT = parseInt(jsPparent.css('margin-top')),
+                oY = option.offset.top || 0;
+
+            if (pos === 'left' || pos === 'right') {
+
+                return -(option.size.height / 2) + (parH / 2) + mT + oY + 'px';
+
+            }
+
+            if (pos === 'top') {
+
+                return -(option.size.height + oY) + mT + 'px';
+
+            }
+
+            if (pos === 'bottom') {
+
+                return parH + mT + oY + 'px';
+
+            }
+
+            return false;
+
+        },
+
+        // calculate final tooltip position
+        calcToooltipPosition: function(jsPparent, option) {
+
+            if (option.paneltype.position === 'top') {
+
+                option.position = {
+                    top: jsPanel.calcPosTooltipTop('top', jsPparent, option),
+                    left: jsPanel.calcPosTooltipLeft('top', jsPparent, option)
+                };
+
+            } else if (option.paneltype.position === 'bottom') {
+
+                option.position = {
+                    top: jsPanel.calcPosTooltipTop('bottom', jsPparent, option),
+                    left: jsPanel.calcPosTooltipLeft('bottom', jsPparent, option)
+                };
+
+            } else if (option.paneltype.position === 'left') {
+
+                option.position = {
+                    top: jsPanel.calcPosTooltipTop('left', jsPparent, option),
+                    left: jsPanel.calcPosTooltipLeft('left', jsPparent, option)
+                };
+
+            } else if (option.paneltype.position === 'right') {
+
+                option.position = {
+                    top: jsPanel.calcPosTooltipTop('right', jsPparent, option),
+                    left: jsPanel.calcPosTooltipLeft('right', jsPparent, option)
+                };
+
+            }
+
+            return option.position;
+
+        },
+
+        calcVerticalOffset: function (panel) {
+
+            return panel.offset().top - jsPanel.winscrollTop();
+
+        },
+
+        // closes a jsPanel and removes it from the DOM
+        close: function (panel, optionPaneltypeType, jsPparentTagname) {
+
+            // get parent-element of jsPanel
+            var context = panel.parent(),
+                panelID = panel.attr('id'),
+                ind;
+
+            // delete childpanels ...
+            jsPanel.closeChildpanels(panel);
+
+            // if present remove tooltip wrapper
+            if (context.hasClass('jsPanel-tooltip-wrapper')) {
+
+                panel.unwrap();
+
+            }
+
+            // remove the jsPanel itself
+            panel.remove();
+
+            $('body').trigger('jspanelclosed', panelID);
+
+            // remove backdrop only when modal jsPanel is closed
+            if (optionPaneltypeType === 'modal') {
+
+                $('.jsPanel-backdrop').remove();
+
+            }
+            // reposition minimized panels
+            jsPanel.reposMinimized(jsPanel.widthForMinimized);
+
+            // update arrays with hints
+            if (optionPaneltypeType === 'hint') {
+
+                ind = jsPanel.hintsTc.indexOf(panelID);
+
+                if (ind !== -1) {
+
+                    jsPanel.hintsTc.splice(ind, 1);
+                    // reposition hints
+                    jsPanel.reposHints(jsPanel.hintsTc, jsPparentTagname);
+
+                }
+
+                ind = jsPanel.hintsTl.indexOf(panelID);
+
+                if (ind !== -1) {
+
+                    jsPanel.hintsTl.splice(ind, 1);
+                    jsPanel.reposHints(jsPanel.hintsTl, jsPparentTagname);
+
+                }
+
+                ind = jsPanel.hintsTr.indexOf(panelID);
+
+                if (ind !== -1) {
+
+                    jsPanel.hintsTr.splice(ind, 1);
+                    jsPanel.reposHints(jsPanel.hintsTr, jsPparentTagname);
+
+                }
+
+            }
+
+            return context;
+
+        },
+
+        // close all tooltips
+        closeallTooltips: function () {
+
+            var pID;
+
+            $('.jsPanel-tt').each(function () {
+
+                pID = $(this).attr('id');
+                // if present remove tooltip wrapper and than remove tooltip
+                $('#' + pID).unwrap().remove();
+                $('body').trigger('jspanelclosed', pID);
+
+            });
+
+        },
+
+        // closes/removes all childpanels within the parent jsPanel
+        closeChildpanels: function (panel) {
+
+            var pID;
+
+            $('.jsPanel', panel).each(function () {
+
+                pID = $(this).attr('id');
+                $('#' + pID).remove();
+                $('body').trigger('jspanelclosed', pID);
+
+            });
+
+            return panel;
+
+        },
+
+        // configure controls
+        configControls: function(optionControls, panel) {
+
+            if (optionControls.buttons === 'closeonly') {
+
+                $(".jsPanel-btn-min, .jsPanel-btn-norm, .jsPanel-btn-max, .jsPanel-btn-small, .jsPanel-btn-smallrev", panel.header.controls).remove();
+                panel.header.title.css("width", "calc(100% - 30px)");
+
+            } else if (optionControls.buttons === 'none') {
+
+                $('*', panel.header.controls).remove();
+                panel.header.title.css("width", "100%");
+
+            }
+            // disable controls individually
+            if (optionControls.close) {panel.control('disable', 'close');}
+            if (optionControls.maximize) {panel.control('disable', 'maximize');}
+            if (optionControls.minimize) {panel.control('disable', 'minimize');}
+            if (optionControls.normalize) {panel.control('disable', 'normalize');}
+            if (optionControls.smallify) {panel.control('disable', 'smallify');}
+
+        },
+
+        // configure iconfonts
+        configIconfont: function(optionControlsIconfont, panel) {
+
+            // remove icon sprites
+            $('*', panel.header.controls).css('background-image', 'none');
+
+            if (optionControlsIconfont === 'bootstrap') {
+
+                $('.jsPanel-btn-close', panel.header.controls).empty().append('<span class="glyphicon glyphicon-remove"></span>');
+                $('.jsPanel-btn-max', panel.header.controls).empty().append('<span class="glyphicon glyphicon-fullscreen"></span>');
+                $('.jsPanel-btn-norm', panel.header.controls).empty().append('<span class="glyphicon glyphicon-resize-full"></span>');
+                $('.jsPanel-btn-min', panel.header.controls).empty().append('<span class="glyphicon glyphicon-minus"></span>');
+                $('.jsPanel-btn-small', panel.header.controls).empty().append('<span class="glyphicon glyphicon-chevron-up"></span>');
+                $('.jsPanel-btn-smallrev', panel.header.controls).empty().append('<span class="glyphicon glyphicon-chevron-down"></span>');
+
+            } else if (optionControlsIconfont === 'font-awesome') {
+
+                $('.jsPanel-btn-close', panel.header.controls).empty().append('<i class="fa fa-times"></i>');
+                $('.jsPanel-btn-max', panel.header.controls).empty().append('<i class="fa fa-arrows-alt"></i>');
+                $('.jsPanel-btn-norm', panel.header.controls).empty().append('<i class="fa fa-expand"></i>');
+                $('.jsPanel-btn-min', panel.header.controls).empty().append('<i class="fa fa-minus"></i>');
+                $('.jsPanel-btn-small', panel.header.controls).empty().append('<i class="fa fa-chevron-up"></i>');
+                $('.jsPanel-btn-smallrev', panel.header.controls).empty().append('<i class="fa fa-chevron-down"></i>');
+
+            }
+
+        },
+
+        // builds toolbar
+        configToolbar: function (toolbaritems, toolbarplace, panel) {
+
+            var i,
+                el,
+                type,
+                max = toolbaritems.length;
+
+            for (i = 0; i < max; i += 1) {
+
+                if (typeof toolbaritems[i] === 'object') {
+
+                    el = $(toolbaritems[i].item);
+                    type = el.prop('tagName').toLowerCase();
+
+                    if (type === 'button') {
+
+                        // set text of button
+                        el.append(toolbaritems[i].btntext);
+
+                        // add class to button
+                        if (typeof toolbaritems[i].btnclass === 'string') {
+
+                            el.addClass(toolbaritems[i].btnclass);
+
+                        }
+
+                    }
+
+                    toolbarplace.append(el);
+
+                    // bind handler to the item
+                    if ($.isFunction(toolbaritems[i].callback)) {
+
+                        el.on(toolbaritems[i].event, panel, toolbaritems[i].callback);
+                        // jsP is accessible in the handler as "event.data"
+
+                    }
+
+                }
+
+            }
+
+        },
+
+        // disable/enable individual controls
+        control: function (panel, action, btn) {
+
+            if (arguments.length === 3) {
+
+                if (arguments[1] === 'disable') {
+
+                    if (btn === 'close') {
+
+                        btn = $('.jsPanel-btn-close', panel.header.controls);
+
+                    }
+
+                    if (btn === 'maximize') {
+
+                        btn = $('.jsPanel-btn-max', panel.header.controls);
+
+                    }
+
+                    if (btn === 'minimize') {
+
+
+                        btn = $('.jsPanel-btn-min', panel.header.controls);
+                    }
+
+                    if (btn === 'normalize') {
+
+                        btn = $('.jsPanel-btn-norm', panel.header.controls);
+
+                    }
+
+                    if (btn === 'smallify') {
+
+                        btn = $('.jsPanel-btn-small', panel.header.controls);
+
+                    }
+
+                    // unbind handler and set styles
+                    btn.off().css({opacity:0.5, cursor: 'default'});
+
+                } else if (arguments[1] === 'enable') {
+
+                    var controlbtn;
+
+                    if (btn === 'close') {
+
+                        controlbtn = $('.jsPanel-btn-close', panel.header.controls);
+
+                    }
+
+                    if (btn === 'maximize') {
+
+                        controlbtn = $('.jsPanel-btn-max', panel.header.controls);
+
+                    }
+
+                    if (btn === 'minimize') {
+
+                        controlbtn = $('.jsPanel-btn-min', panel.header.controls);
+
+                    }
+
+                    if (btn === 'normalize') {
+
+                        controlbtn = $('.jsPanel-btn-norm', panel.header.controls);
+
+                    }
+
+                    if (btn === 'smallify') {
+
+                        controlbtn = $('.jsPanel-btn-small', panel.header.controls);
+
+                    }
+
+                    // enable control and reset styles
+                    controlbtn.on('click', function (e) {
+
+                        e.preventDefault();
+                        panel[btn]();
+
+                    }).css({opacity: 1, cursor: 'pointer'});
+                }
+
+            }
+
+            return panel;
+
+        },
+
+        // helper function for the doubleclick handlers (title, content, footer)
+        dblclickhelper: function (odcs, panel) {
+
+            if (typeof odcs === 'string') {
+
+                if (odcs === "maximize" || odcs === "normalize") {
+
+                    if (panel.status !== "maximized") {
+
+                        panel.maximize();
+
+                    } else {
+
+                        panel.normalize();
+
+                    }
+
+                } else if (odcs === "minimize" || odcs === "smallify" || odcs === "close") {
+
+                    panel[odcs]();
+
+                }
+
+            }
+
+        },
+
+        docOuterHeight: function () {
+
+            return $(document).outerHeight();
+
+        },
+
+        // maintains panel position relative to window on scroll of page
+        fixPosition: function (panel) {
+
+            var jspaneldiff = panel.offset().top - jsPanel.winscrollTop();
+
+            panel.jsPanelfixPos = function () {
+
+                panel.css('top', jsPanel.winscrollTop() + jspaneldiff + 'px');
+
+            };
+
+            $(window).on('scroll', panel.jsPanelfixPos);
+
+        },
+
+        // calculate panel margins
+        getMargins: function(panel, selector) {
+
+            var off, elmtOff, mR, mL, mB, mT;
+
+            if(!selector || selector === "body") {
+
+                // panel margins relative to browser viewport
+                off = panel.offset();
+                mR = jsPanel.winouterWidth() - off.left - panel.outerWidth() + jsPanel.winscrollLeft();
+                mL = jsPanel.winouterWidth() - panel.outerWidth() - mR;
+                mB = jsPanel.winouterHeight() - off.top - panel.outerHeight() + jsPanel.winscrollTop();
+                mT = jsPanel.winouterHeight() - panel.outerHeight() - mB;
+
+            } else {
+
+                // panel margins relative to element matching selector "selector"
+                elmtOff = $(selector).offset();
+                off = panel.offset();
+                mR = $(selector).outerWidth() - parseInt(panel.css('width')) - (off.left - elmtOff.left);
+                mL = off.left - elmtOff.left;
+                mB = $(selector).outerHeight() - (off.top - elmtOff.top) - parseInt(panel.css('height'));
+                mT = off.top - elmtOff.top;
+
+            }
+
+            return {marginTop: parseInt(mT), marginRight: parseInt(mR), marginBottom: parseInt(mB), marginLeft: parseInt(mL)};
+
+        },
+
+        // calculate max horizontal and vertical tooltip shift
+        getMaxpanelshift: function(panel) {
+
+            var horiz = parseInt( panel.outerWidth()/2 ) + parseInt( panel.parent().outerWidth()/2 ) - 20,
+                vert = parseInt( panel.outerHeight()/2 ) + parseInt( panel.parent().outerHeight()/2 ) - 20,
+                cornerHoriz = parseInt( panel.outerWidth()/2 ) - 16,
+                cornerVert = parseInt( panel.outerHeight()/2 ) - 16;
+
+            return {maxshiftH: horiz, maxshiftV: vert, maxCornerH: cornerHoriz, maxCornerV: cornerVert};
+
+        },
+
+        // shift tooltip left/right if it overflows window
+        // when using horizontal offsets of panel and/or corner result might be not as expected
+        shiftTooltipHorizontal: function(panel, optionPaneltypeshiftwithin){
+
+            var margins = jsPanel.getMargins(panel, optionPaneltypeshiftwithin),
+                leftShiftRequired = 0,
+                maxShift = jsPanel.getMaxpanelshift(panel),
+                maxLeftShift = maxShift.maxshiftH,
+                shift = 0,
+                maxCornerLeft = maxShift.maxCornerH,
+                cornerShift = 0,
+                newPanelLeft = 0,
+                newCornerLeft = 0;
+
+            if (margins.marginLeft < 0 && margins.marginRight > 0) {
+                // if panel overflows left window border
+                leftShiftRequired = Math.abs(margins.marginLeft) + 5;
+                shift = Math.min(leftShiftRequired, maxLeftShift);
+                cornerShift = Math.min(maxCornerLeft, shift);
+                newPanelLeft = parseInt(panel.css('left')) + shift + "px";
+                newCornerLeft = parseInt($('.jsPanel-corner', panel).css('left')) - cornerShift + "px";
+
+            } else if (margins.marginRight < 0 && margins.marginLeft > 0) {
+                // if panel overflows right window border
+                leftShiftRequired = Math.abs(margins.marginRight) + 5;
+                shift = Math.min(leftShiftRequired, maxLeftShift);
+                cornerShift = Math.min(maxCornerLeft, shift);
+                newPanelLeft = parseInt(panel.css('left')) - shift + "px";
+                newCornerLeft = parseInt($('.jsPanel-corner', panel).css('left')) + cornerShift + "px";
+
+            }
+
+            if ((margins.marginLeft < 0 && margins.marginRight > 0) || (margins.marginRight < 0 && margins.marginLeft > 0)) {
+                // shift panel
+                panel.animate({
+                    "left": newPanelLeft
+                },{ queue: false /* to have both animation run simultaneously */ });
+
+                // shift corner if present
+                if ($('.jsPanel-corner', panel)) {
+                    $('.jsPanel-corner', panel).animate({
+                        "left": newCornerLeft
+                    },{ queue: false /* to have both animation run simultaneously */ });
+                }
+            }
+
+        },
+
+        // shift tooltip up/down if it overflows window
+        // when using vertical offsets of panel and/or corner result might be not as expected
+        shiftTooltipVertical: function(panel, optionPaneltypeshiftwithin){
+
+            //console.log( parseInt($('*:first-child', panel.parent()).css('margin-left')) );
+
+            var margins = jsPanel.getMargins(panel, optionPaneltypeshiftwithin),
+                topShiftRequired = 0,
+                maxShift = jsPanel.getMaxpanelshift(panel),
+                maxTopShift = maxShift.maxshiftV,
+                shift = 0,
+                maxCornerTop = maxShift.maxCornerV,
+                cornerShift = 0,
+                newPanelTop = 0,
+                newCornerTop = 0;
+
+            if (margins.marginTop < 0 && margins.marginBottom > 0) {
+                // if panel overflows top window border
+                topShiftRequired = Math.abs(margins.marginTop) + 5;
+                shift = Math.min(topShiftRequired, maxTopShift);
+                cornerShift = Math.min(maxCornerTop, shift);
+                newPanelTop = parseInt(panel.css('top')) + shift + "px";
+                newCornerTop = parseInt($('.jsPanel-corner', panel).css('top')) - cornerShift + "px";
+
+            } else if (margins.marginBottom < 0 && margins.marginTop > 0) {
+                // if panel overflows bottom window border
+                topShiftRequired = Math.abs(margins.marginBottom) + 5;
+                shift = Math.min(topShiftRequired, maxTopShift);
+                cornerShift = Math.min(maxCornerTop, shift);
+                newPanelTop = parseInt(panel.css('top')) - shift + "px";
+                newCornerTop = parseInt($('.jsPanel-corner', panel).css('top')) + cornerShift + "px";
+
+            }
+
+            if ((margins.marginTop < 0 && margins.marginBottom > 0) || (margins.marginBottom < 0 && margins.marginTop > 0)) {
+                // shift panel
+                panel.animate({
+                    "top": newPanelTop
+                },{ queue: false /* to have both animation run simultaneously */ });
+
+                // shift corner if present
+                if ($('.jsPanel-corner', panel)) {
+                    $('.jsPanel-corner', panel).animate({
+                        "top": newCornerTop
+                    },{ queue: false /* to have both animation run simultaneously */ });
+                }
+            }
+
+        },
+
+        // get title text
+        getTitle: function (panel) {
+
+            return panel.header.title.html();
+
+        },
+
+        // hide controls specified by param "sel" of the jsPanel "panel"
+        hideControls: function (sel, panel) {
+
+            var controls = ".jsPanel-btn-close, .jsPanel-btn-norm, .jsPanel-btn-min, .jsPanel-btn-max, .jsPanel-btn-small, .jsPanel-btn-smallrev";
+
+            $(controls, panel.header.controls).css('display', 'block');
+
+            $(sel, panel.header.controls).css('display', 'none');
+
+        },
+
+        // calculates option.position for hints using 'top left', 'top center' or 'top right'
+        hintTop: function (hintGroup) {
+
+            var i,
+                hintH = 0,
+                max = hintGroup.length;
+
+            for (i = 0; i < max; i += 1) {
+
+                hintH += $('#' + hintGroup[i]).outerHeight(true);
+
+            }
+
+            if (hintGroup === jsPanel.hintsTr) {
+
+                return {top: hintH, right: 0};
+
+            }
+
+            if (hintGroup === jsPanel.hintsTl) {
+
+                return {top: hintH, left: 0};
+
+            }
+
+            if (hintGroup === jsPanel.hintsTc) {
+
+                return {top: hintH, left: 'center'};
+
+            }
+
+            return {top: 0, left: 0};
+
+        },
+
+        // append modal backdrop
+        insertModalBackdrop: function () {
+
+            var backdrop = '<div class="jsPanel-backdrop" style="height:' + jsPanel.docOuterHeight() + 'px;"></div>';
+
+            $('body').append(backdrop);
+
+        },
+
+        // check whether a bootstrap compatible theme is used
+        isBootstrapTheme: function(optionBootstrap) {
+
+            var arr = ["default", "primary", "info", "success", "warning", "danger"];
+
+            if ($.inArray(optionBootstrap, arr) > -1) {
+
+                return optionBootstrap;
+
+            } else {
+
+                return "default";
+
+            }
+
+        },
+
+        // check for positive integer
+        isPosInt: function(arg) {
+
+            var regex = /^\+?[1-9]\d*$/;
+
+            if (arg.toString().match(regex) !== null) {
+
+                return true;
+
+            } else {
+
+                return false;
+
+            }
+        },
+
+        // maximizes a panel within the body element
+        maxWithinBody: function (panel, option) {
+
+            if (panel.status !== "maximized" && option.paneltype.mode !== 'default') {
+
+                var newPos, newTop, newLeft;
+
+                // remove window.scroll handler, is added again later in this function
+                $(window).off('scroll', panel.jsPanelfixPos);
+
+                // restore minimized panel to initial container
+                jsPanel.restoreFromMinimized(panel, option);
+
+                // test to enable fullscreen maximize for panels in a parent other than body
+                if (option.controls.maxtoScreen === true) {
+
+                    newPos = jsPanel.calcPosmaxtoScreen(panel);
+                    newTop = newPos.top;
+                    newLeft = newPos.left;
+
+                } else {
+
+                    newTop = jsPanel.winscrollTop() + 5 + 'px';
+                    newLeft = jsPanel.winscrollLeft() + 5 + 'px';
+
+                }
+                panel.animate({
+
+                    top: newTop,
+                    left: newLeft,
+                    width: jsPanel.winouterWidth() - 10 + 'px',
+                    height: jsPanel.winouterHeight() - 10 + 'px'
+
+                }, {
+                    done: function () {
+
+                        jsPanel.resizeContent(panel);
+                        panel.animate({opacity: 1}, {duration: 150});
+
+                        // hier kein fadeIn() einbauen, funktioniert nicht mit jsPanel.fixPosition(jsP)
+                        jsPanel.hideControls(".jsPanel-btn-max, .jsPanel-btn-smallrev", panel);
+                        panel.status = "maximized";
+                        $(panel).trigger('jspanelmaximized', panel.attr('id'));
+                        $(panel).trigger('jspanelstatechange', panel.attr('id'));
+
+                        if (!option.controls.maxtoScreen || (option.controls.maxtoScreen && option.selector === 'body')) {
+
+                            // test to enable fullscreen maximize for panels in a parent other than body
+                            jsPanel.fixPosition(panel, option);
+
+                        }
+
+                        //jsPanel.fixPosition(panel);
+                        jsPanel.resizeTitle(panel);
+
+                    }
+                });
+
+            }
+
+        },
+
+        // maximizes a panel within an element other than body
+        maxWithinElement: function (panel, option) {
+
+            if (panel.status !== "maximized" && option.paneltype.mode !== 'default') {
+
+                var width,
+                    height;
+
+                // restore minimized panel to initial container
+                jsPanel.restoreFromMinimized(panel, option);
+
+                width = parseInt(panel.parent().outerWidth(), 10) - 10 + 'px';
+                height = parseInt(panel.parent().outerHeight(), 10) - 10 + 'px';
+
+                panel.animate({
+
+                    top: '5px',
+                    left: '5px',
+                    width: width,
+                    height: height
+
+                }, {
+                    done: function () {
+
+                        jsPanel.resizeContent(panel);
+                        panel.animate({opacity: 1}, {duration: 150});
+                        jsPanel.hideControls(".jsPanel-btn-max, .jsPanel-btn-smallrev", panel);
+                        panel.status = "maximized";
+
+                        $(panel).trigger('jspanelmaximized', panel.attr('id'));
+                        $(panel).trigger('jspanelstatechange', panel.attr('id'));
+
+                        jsPanel.resizeTitle(panel);
+
+                    }
+
+                });
+
+            }
+
+        },
+
+        // calls functions to maximize a jsPanel
+        maximize: function (panel, jsPparentTagname, option) {
+
+            //  || option.controls.maxtoScreen === true - test to enable fullscreen maximize for panels in a parent other than body
+            if (jsPparentTagname === 'body' || option.controls.maxtoScreen === true) {
+
+                jsPanel.maxWithinBody(panel, option);
+
+            } else {
+
+                jsPanel.maxWithinElement(panel, option);
+
+            }
+
+            return panel;
+
+        },
+
+        // minimizes a jsPanel to the lower left corner of the browser viewport
+        minimize: function (panel, optionSize) {
+
+            // update panel size to have correct values when normalizing again
+            if (panel.status === "normalized") {
+
+                optionSize.width = panel.outerWidth() + 'px';
+                optionSize.height = panel.outerHeight() + 'px';
+
+            }
+            panel.animate({
+
+                opacity: 0
+
+            }, {
+                duration: 400, // fade out speed when minimizing
+                complete: function () {
+
+                    panel.animate({
+
+                        width: '150px',
+                        height: '28px'
+
+                    }, {
+                        duration: 100,
+                        complete: function () {
+
+                            jsPanel.movetoMinified(panel);
+                            jsPanel.resizeTitle(panel);
+                            panel.css('opacity', 1);
+
+                        }
+                    });
+
+                }
+
+            });
+
+            return panel;
+
+        },
+
+        // moves a panel to the minimized container
+        movetoMinified: function (panel) {
+
+            var mincount = $('#jsPanel-min-container > .jsPanel').length;
+
+            // wenn der Container für die minimierten jsPanels noch nicht existiert -> erstellen
+            if ($('#jsPanel-min-container').length === 0) {
+
+                $('body').append('<div id="jsPanel-min-container"></div>');
+
+            }
+            if (panel.status !== "minimized") {
+
+                // jsPanel in vorgesehenen Container verschieben
+                panel.css({
+
+                    left: (mincount * jsPanel.widthForMinimized) + 'px',
+                    top: 0,
+                    opacity: 1
+
+                })
+                    .appendTo('#jsPanel-min-container')
+                    .resizable({disabled: true})
+                    .draggable({disabled: true});
+
+                // buttons show or hide
+                jsPanel.hideControls(".jsPanel-btn-min, .jsPanel-btn-small, .jsPanel-btn-smallrev", panel);
+
+                $(panel).trigger('jspanelminimized', panel.attr('id'));
+                $(panel).trigger('jspanelstatechange', panel.attr('id'));
+
+                panel.status = "minimized";
+
+                $(window).off('scroll', panel.jsPanelfixPos);
+
+            }
+
+        },
+
+        // restores a panel to its "normalized" (not minimized, maximized or smallified) position & size
+        normalize: function (panel, option, jsPparentTagname, verticalOffset) {
+
+            var panelTop;
+
+            // remove window.scroll handler, is added again later in this function
+            $(window).off('scroll', panel.jsPanelfixPos);
+
+            // restore minimized panel to initial container
+            jsPanel.restoreFromMinimized(panel, option);
+
+            // correction for panels maximized in body after page was scrolled
+            if (jsPparentTagname === 'body') {
+
+                panelTop = jsPanel.winscrollTop() + verticalOffset + 'px';
+
+            } else {
+
+                panelTop = option.position.top;
+
+            }
+
+            panel.animate({
+
+                width: option.size.width,
+                height: option.size.height,
+                top: panelTop,
+                left: option.position.left
+
+            }, {
+                done: function () {
+
+                    // hier kein fadeIn() einbauen, funktioniert nicht mit jsPanel.fixPosition(jsP);
+                    panel.animate({opacity: 1}, {duration: 150});
+
+                    jsPanel.hideControls(".jsPanel-btn-norm, .jsPanel-btn-smallrev", panel);
+                    jsPanel.resizeTitle(panel);
+
+                    if (option.resizable !== 'disabled') {
+
+                        panel.resizable("enable");
+
+                    }
+
+                    if (option.draggable !== 'disabled') {
+
+                        panel.draggable("enable");
+
+                    }
+
+                    panel.status = "normalized";
+
+                    $(panel).trigger('jspanelnormalized', panel.attr('id'));
+                    $(panel).trigger('jspanelstatechange', panel.attr('id'));
+
+                    if (jsPparentTagname === 'body') {
+
+                        jsPanel.fixPosition(panel);
+
+                    }
+
+                    jsPanel.resizeContent(panel); // to get the scrollbars back
+
+                }
+
+            });
+
+            return panel;
+
+        },
+
+        // replace bottom/right values with corresponding top/left values if necessary and update option.position
+        replaceCSSBottomRight: function (panel, optionPosition) {
+
+            var panelPosition = panel.position(),
+                panelPosTop = parseInt(panelPosition.top, 10),
+                panelPosLeft = parseInt(panelPosition.left, 10);
+
+            if (panel.css('bottom')) {
+
+                panel.css({
+
+                    'top': parseInt(panelPosition.top, 10) + 'px',
+                    'bottom': ''
+
+                });
+
+                optionPosition.top = panelPosTop;
+
+            }
+            if (panel.css('right')) {
+
+                panel.css({
+
+                    'left': parseInt(panelPosition.left, 10) + 'px',
+                    'right': ''
+
+                });
+
+                optionPosition.left = panelPosLeft;
+
+            }
+
+        },
+
+        // reposition hint upon closing
+        reposHints: function (hintGroup, jsPtagname) {
+
+            var hintH,
+                el,
+                i,
+                max = hintGroup.length;
+
+            if (jsPtagname === 'body') {
+
+                hintH = jsPanel.winscrollTop();
+
+            } else {
+
+                hintH = 0;
+
+            }
+
+            for (i = 0; i < max; i += 1) {
+
+                el = $('#' + hintGroup[i]);
+
+                el.animate({
+
+                    top: hintH + 'px'
+
+                });
+
+                hintH += el.outerHeight(true);
+
+            }
+
+        },
+
+        // reposition hints on window scroll
+        reposHintsScroll: function(panel) {
+
+            var dif = panel.offset().top - jsPanel.winscrollTop();
+
+            // with window.onscroll only the last added hint would stay in position
+            $(window).scroll(function () {
+
+                panel.css('top', jsPanel.winscrollTop() + dif + 'px');
+
+            });
+
+        },
+
+        // repositions minimized jsPanels
+        reposMinimized: function () {
+
+            var minimized = $('#jsPanel-min-container > .jsPanel'),
+                minimizedCount = minimized.length,
+                i;
+
+            for (i = 0; i < minimizedCount; i += 1) {
+
+                minimized.eq(i).animate({
+
+                    left: (i * jsPanel.widthForMinimized) + 'px'
+
+                });
+
+            }
+
+        },
+
+        // reset dimensions of content section after resize and so on
+        resizeContent: function (panel) {
+
+            var hdrftr,
+                poh = panel.outerHeight();
+
+            if (panel.footer.css('display') === 'none') {
+
+                hdrftr = panel.header.outerHeight();
+
+            } else {
+
+                hdrftr = panel.header.outerHeight() + panel.footer.outerHeight();
+
+            }
+
+            panel.content.css({
+
+                height: (poh - hdrftr) + 'px',
+                width: '100%'
+
+            });
+
+            return panel;
+
+        },
+
+        // resize the title h3 to use full width minus controls width (and prevent being longer than panel)
+        resizeTitle: function(panel) {
+
+            var contWidth = $(panel.header.controls).outerWidth(),
+                panelWidth = $(panel).outerWidth(),
+                titleWidth = (panelWidth - contWidth - 15) + 'px';
+
+            panel.header.title.css('width', titleWidth);
+
+        },
+
+        // restores minimized panels to their initial container, reenables resizable and draggable, repositions minimized panels
+        restoreFromMinimized: function (panel, option) {
+
+            // restore minimized panel to initial container
+            if (panel.status === "minimized") {
+
+                // hier kein fadeOut() einbauen, funktioniert nicht mit jsPanel.fixPosition(jsP)
+                panel.animate({opacity: 0}, {duration: 50});
+                panel.appendTo(option.selector);
+
+            }
+
+            if (option.resizable !== 'disabled') {
+
+                panel.resizable("enable");
+
+            }
+
+            if (option.draggable !== 'disabled') {
+
+                panel.draggable("enable");
+
+            }
+
+            // reposition minimized panels
+            jsPanel.reposMinimized(jsPanel.widthForMinimized);
+
+        },
+
+        // rewrite option.paneltype strings to objects and set defaults for option.paneltype
+        rewriteOPaneltype: function (optionPaneltype) {
+
+            var op = optionPaneltype;
+
+            if (op === 'modal') {
+
+                return {type: 'modal', mode: 'default'};
+
+            } else if (op === 'tooltip') {
+
+                return {type: 'tooltip', position: 'top'};
+
+            } else if (op === 'hint') {
+
+                return {type: 'hint'};
+
+            } else if (op.type === 'modal') {
+
+                op.mode = op.mode || 'default';
+                return {type: 'modal', mode: op.mode};
+
+            } else if (op.type === 'tooltip') {
+
+                op.mode = op.mode || false;
+                op.position = op.position || false;
+                op.shiftwithin = op.shiftwithin || "body";
+                op.solo = op.solo || false;
+                op.cornerBG = op.cornerBG || false;
+                op.cornerOX = op.cornerOX || false;
+                op.cornerOY = op.cornerOY || false;
+
+                return {
+                    type: 'tooltip',
+                    mode: op.mode,
+                    position: op.position,
+                    shiftwithin: op.shiftwithin,
+                    solo: op.solo,
+                    cornerBG: op.cornerBG,
+                    cornerOX: op.cornerOX,
+                    cornerOY: op.cornerOY
+                };
+
+            } else {
+
+                return {paneltype: false};
+
+            }
+
+        },
+
+        // converts option.position string to object
+        rewriteOPosition: function (optionPosition) {
+
+            var op = optionPosition;
+
+            if (op === 'center') {
+
+                return {top: 'center', left: 'center'};
+
+
+            }
+            if (op === 'auto') {
+
+                return {top: 'auto', left: 'auto'};
+
+            }
+
+            if (op === 'top left') {
+
+                return {top: '0', left: '0'};
+
+            }
+
+
+            if (op === 'top center') {
+                return {top: '0', left: 'center'};
+
+            }
+
+            if (op === 'top right') {
+
+                return {top: '0', right: '0'};
+
+            }
+
+            if (op === 'center right') {
+
+                return {top: 'center', right: '0'};
+
+            }
+
+            if (op === 'bottom right') {
+
+                return {bottom: '0', right: '0'};
+
+            }
+
+            if (op === 'bottom center') {
+
+                return {bottom: '0', left: 'center'};
+
+            }
+
+            if (op === 'bottom left') {
+
+                return {bottom: '0', left: '0'};
+
+            }
+
+            if (op === 'center left') {
+
+                return {top: 'center', left: '0'};
+
+            }
+
+            return optionPosition;
+
+        },
+
+        // set default options for hints and add necessary classes
+        setHintDefaults: function(option, panel) {
+
+            option.resizable = false;
+            option.draggable = false;
+            option.removeHeader = true;
+            option.toolbarFooter = false;
+            option.show = 'fadeIn';
+
+            panel.addClass('jsPanel-hint');
+            panel.content.addClass('jsPanel-hint-content');
+
+            // autoclose default 8 sec | or -1 to deactivate
+            if (!option.autoclose) {
+
+                option.autoclose = 8000;
+
+            } else if (option.autoclose < 0) {
+
+                option.autoclose = false;
+
+            }
+            // add class according option.theme to color the hint background
+            panel.content.addClass('jsPanel-hint-' + option.theme);
+
+            // add class according option.theme to color the hint background
+            if (option.theme === 'default' || option.theme === 'light') {
+
+                panel.content.append('<div class="jsPanel-hint-close-dark"></div>');
+
+            } else {
+
+                panel.content.append('<div class="jsPanel-hint-close-white"></div>');
+
+            }
+
+        },
+
+        // set default options for standard modal
+        setModalDefaults: function (option, panel) {
+
+            option.selector = 'body';
+            option.show = 'fadeIn';
+            panel.addClass('jsPanel-modal');
+
+            if (option.paneltype.mode === 'default') {
+
+                option.resizable = false;
+                option.draggable = false;
+                option.removeHeader = false;
+                option.position = {top: 'center', left: 'center'};
+                option.offset = {top: 0, left: 0};
+                option.controls.buttons = 'closeonly'; //do not delete else "modal" with no close button possible
+
+                $(".jsPanel-btn-min, .jsPanel-btn-norm, .jsPanel-btn-max, .jsPanel-btn-small, .jsPanel-btn-smallrev", panel).remove();
+                $(panel.header, panel.header.title, panel.footer).css('cursor', 'default');
+                $('.jsPanel-title', panel).css('cursor', 'inherit');
+
+            }
+
+        },
+
+        // set panel id
+        setPanelId: function (panel, optionID) {
+
+            if (typeof optionID === 'string') {
+
+                // id doesn't exist yet -> use it
+                if ($('#' + optionID).length < 1) {
+
+                    panel.attr('id', optionID);
+
+                } else {
+
+                    jsPanel.ID += 1;
+                    panel.attr('id', 'jsPanel-' + jsPanel.ID);
+
+                    // write new id as notification in title
+                    $('.jsPanel-title', panel).html($('.jsPanel-title', panel).text() + ' AUTO-ID: ' + panel.attr('id'));
+
+                }
+
+            } else if ($.isFunction(optionID)) {
+
+                panel.attr('id', optionID);
+
+            }
+
+        },
+
+        // set right-to-left text direction and language; set styles and reoorder controls for rtl
+        setRTL: function(panel, optionRtlLang) {
+
+            var elmts = [ panel.header.title, panel.content, panel.header.toolbar, panel.footer ],
+                i,
+                max = elmts.length;
+
+            for (i = 0; i < max; i += 1) {
+
+                elmts[i].prop('dir', 'rtl');
+
+                if (optionRtlLang) {
+
+                    elmts[i].prop('lang', optionRtlLang);
+
+                }
+
+            }
+
+            panel.header.title.css('text-align', 'right');
+            $('.jsPanel-btn-close', panel.header.controls).insertAfter($('.jsPanel-btn-min', panel.header.controls));
+            $('.jsPanel-btn-max', panel.header.controls).insertAfter($('.jsPanel-btn-min', panel.header.controls));
+            $('.jsPanel-btn-small', panel.header.controls).insertBefore($('.jsPanel-btn-min', panel.header.controls));
+            $('.jsPanel-btn-smallrev', panel.header.controls).insertBefore($('.jsPanel-btn-min', panel.header.controls));
+            $('.jsPanel-hdr-r', panel).css({left: '0px', right: '', position: 'relative', 'float': 'left'});
+            $('.jsPanel-hint-close-dark, .jsPanel-hint-close-white', panel).css('float', 'left');
+            $('.jsPanel-title', panel).css('float', 'right');
+            $('.jsPanel-ftr', panel).append('<div style="clear:both;height:0;"></div>');
+            $('button', panel.footer).css('float', 'left');
+
+        },
+
+        // set title text
+        setTitle: function (panel, text) {
+
+            if (text && typeof text === "string") {
+
+                panel.header.title.html(text);
+
+                return panel;
+
+            }
+
+            return panel;
+
+        },
+
+        // set default options for tooltips
+        setTooltipDefaults: function(option) {
+
+            option.position = {};
+            option.resizable = false;
+            option.draggable = false;
+            option.show = 'fadeIn';
+            option.controls.buttons = 'closeonly';
+
+        },
+
+        setZi: function () {
+            var zi = 100, elmtzi;
+            $('.jsPanel').each(function () {
+                elmtzi = $(this).zIndex();
+                if (elmtzi >= zi) {
+                    zi = elmtzi + 1;
+                }
+            });
+            return zi;
+        },
+
+        // toggles jsPanel height between header height and normalized/maximized height
+        smallify: function (panel) {
+
+            if (panel.status !== "smallified" && panel.status !== "smallifiedMax") {
+
+                var statusNew;
+
+                if (panel.status === "maximized") {
+
+                    statusNew = "smallifiedMax";
+
+                } else {
+
+                    statusNew = "smallified";
+
+                }
+
+                // store panel height in function property
+                panel.smallify.height = panel.outerHeight() + 'px';
+                panel.panelheaderheight = panel.header.outerHeight() - 2;
+                panel.panelfooterheight = panel.footer.outerHeight();
+                panel.panelcontentheight = panel.content.outerHeight();
+                panel.animate({
+
+                        height: panel.panelheaderheight + 'px'
+
+                    },
+                    {
+                        done: function () {
+
+                            if (panel.status === 'maximized') {
+
+                                jsPanel.hideControls(".jsPanel-btn-max, .jsPanel-btn-small", panel);
+
+                            } else {
+
+                                jsPanel.hideControls(".jsPanel-btn-norm, .jsPanel-btn-small", panel);
+
+                            }
+
+                            panel.status = statusNew;
+
+                            $(panel).trigger('jspanelsmallified', panel.attr('id'));
+                            $(panel).trigger('jspanelstatechange', panel.attr('id'));
+
+                        }
+
+                    });
+
+            } else {
+                panel.animate({
+
+                        height: panel.smallify.height
+
+                    },
+                    {
+                        done: function () {
+
+                            if (panel.status === 'smallified') {
+
+                                jsPanel.hideControls(".jsPanel-btn-norm, .jsPanel-btn-smallrev", panel);
+                                panel.status = "normalized";
+                                $(panel).trigger('jspanelnormalized', panel.attr('id'));
+                                $(panel).trigger('jspanelstatechange', panel.attr('id'));
+
+                            } else {
+
+                                jsPanel.hideControls(".jsPanel-btn-max, .jsPanel-btn-smallrev", panel);
+                                panel.status = "maximized";
+                                $(panel).trigger('jspanelmaximized', panel.attr('id'));
+                                $(panel).trigger('jspanelstatechange', panel.attr('id'));
+
+                            }
+
+                        }
+
+                    }
+
+                );
+
+            }
+
+            return panel;
+
+        },
+
+        // updates option.position to hold actual values
+        updateOptionPosition: function(panel, optionPosition) {
+
+            optionPosition.top = panel.css('top');
+            optionPosition.left = panel.css('left');
+
+        },
+
+        // updates option.size to hold actual values
+        updateOptionSize: function(panel, optionSize) {
+
+            optionSize.width = panel.css('width');
+            optionSize.height = panel.css('height');
+
+        },
+
+        winouterHeight: function () {
+
+            return $(window).outerHeight();
+
+        },
+
+        winouterWidth: function () {
+
+            return $(window).outerWidth();
+
+        },
+
+        winscrollLeft: function () {
+
+            return $(window).scrollLeft();
+
+        },
+
+        winscrollTop: function () {
+
+            return $(window).scrollTop();
+
+        }
+
+    };
+
+}(jQuery));
+
+(function ($) {
+    "use strict";
+
+    $.jsPanel = function (config) {
+
+        var jsP = $(jsPanel.template),
+            // Extend our default config with those provided.
+            // Note that the first arg to extend is an empty object - this is to keep from overriding our "defaults" object.
+            option = $.extend(true, {}, $.jsPanel.defaults, config),
+            anim = option.show,
+            verticalOffset = 0,
+            jsPparent,
+            jsPparentTagname,
+            count;
+
+        try {
+
+            jsPparent = $(option.selector).first();
+            jsPparentTagname = jsPparent[0].tagName.toLowerCase();
+            count = jsPparent.children('.jsPanel').length;
+
+        } catch (e) {
+
+            console.log(e);
+            console.log('The element you want to append the jsPanel to does not exist!');
+            option.selector = 'body';
+            jsPparent = $('body');
+            jsPparentTagname = 'body';
+            count = jsPparent.children('.jsPanel').length;
+
+        }
+
+        jsP.status = "initialized";
+
+        jsP.header = $('.jsPanel-hdr', jsP);
+
+        jsP.header.title = $('.jsPanel-title', jsP.header);
+
+        jsP.header.controls = $('.jsPanel-hdr-r', jsP.header);
+
+        jsP.header.toolbar = $('.jsPanel-hdr-toolbar', jsP.header);
+
+        jsP.content = $('.jsPanel-content', jsP);
+
+        jsP.footer = $('.jsPanel-ftr', jsP);
+
+        jsP.normalize = function() {
+
+            jsPanel.normalize(jsP, option, jsPparentTagname, verticalOffset);
+            return jsP;
+
+        };
+
+        jsP.close = function () {
+
+            jsPanel.close(jsP, option.paneltype.type, jsPparentTagname);
+            // no need to return something
+
+        };
+
+        jsP.closeChildpanels = function () {
+
+            jsPanel.closeChildpanels(jsP);
+            return jsP;
+
+        };
+
+        jsP.minimize = function () {
+
+            jsPanel.minimize(jsP, option.size);
+            return jsP;
+
+        };
+
+        jsP.maximize = function () {
+
+            jsPanel.maximize(jsP, jsPparentTagname, option);
+            return jsP;
+
+        };
+
+        jsP.smallify = function () {
+
+            jsPanel.smallify(jsP);
+            return jsP;
+
+        };
+
+        jsP.front = function () {
+
+            jsP.css('z-index', jsPanel.setZi());
+            return jsP;
+
+        };
+
+        jsP.title = function (text) {
+
+            if (text && typeof text === "string") {
+
+                jsPanel.setTitle(jsP, text);
+                return jsP;
+
+            } else if (arguments.length === 0) {
+
+                return jsPanel.getTitle(jsP);
+
+            }
+
+        };
+
+        jsP.addToolbar = function (place, items) {
+
+            jsPanel.addToolbar(jsP, place, items);
+            return jsP;
+
+        };
+        
+        jsP.control = function (action, btn) {
+
+            jsPanel.control(jsP, action, btn);
+            return jsP;
+
+        };
+
+        jsP.resize = function (width, height) {
+            // method resizes the full panel (not content section only)
+
+            if(width && width !== null) {
+                jsP.css("width", width);
+            } else {
+                jsP.css("width", jsP.content.css("width"));
+            }
+            if(height && height !== null) {
+                jsP.css("height", height);
+            }
+            jsPanel.resizeContent(jsP);
+            jsPanel.resizeTitle(jsP);
+            return jsP;
+
+        };
+
+        /*
+         * handlers for the controls -----------------------------------------------------------------------------------
+         */
+        // handler to move panel to foreground on
+        jsP.on('click', function () {
+
+            if (!jsP.hasClass("jsPanel-modal")) {
+                jsP.css('z-index', jsPanel.setZi());
+            }
+
+        });
+
+        // jsPanel close
+        $('.jsPanel-btn-close', jsP).on('click', function (e) {
+
+            e.preventDefault();
+            jsPanel.close(jsP, option.paneltype.type, jsPparentTagname);
+
+        });
+
+        // jsPanel minimize
+        $('.jsPanel-btn-min', jsP).on('click', function (e) {
+
+            e.preventDefault();
+            jsPanel.minimize(jsP, option.size);
+
+        });
+
+        // jsPanel maximize
+        $('.jsPanel-btn-max', jsP).on('click', function (e) {
+
+            e.preventDefault();
+            jsPanel.maximize(jsP, jsPparentTagname, option);
+
+        });
+
+        // jsPanel normalize
+        $('.jsPanel-btn-norm', jsP).on('click', function (e) {
+
+            e.preventDefault();
+            jsPanel.normalize(jsP, option, jsPparentTagname, verticalOffset);
+
+        });
+
+        // jsPanel smallify
+        $('.jsPanel-btn-small, .jsPanel-btn-smallrev', jsP).on('click', function (e) {
+
+            e.preventDefault();
+            jsPanel.smallify(jsP);
+
+        });
+
+        // rewrite option.paneltype strings to objects and set defaults for option.paneltype
+        option.paneltype = jsPanel.rewriteOPaneltype(option.paneltype);
+
+        // converts option.position string to object
+        option.position = jsPanel.rewriteOPosition(option.position);
+
+        /* option.id ------------------------------------------------------------------------------------------------ */
+        // wenn option.id -> string oder function?
+        jsPanel.setPanelId(jsP, option.id);
+
+        /* option.paneltype - override or set various settings depending on option.paneltype ------------------------ */
+        if (option.paneltype.type === 'modal') {
+
+            // set defaults for standard modal
+            jsPanel.setModalDefaults(option, jsP);
+
+            // insert backdrop
+            if ($('.jsPanel-backdrop').length < 1) {
+
+                jsPanel.insertModalBackdrop();
+
+            }
+
+        } else if (option.paneltype.type === 'tooltip') {
+
+            jsPanel.setTooltipDefaults(option);
+
+            // optionally remove all other tooltips
+            if (option.paneltype.solo) {
+
+                jsPanel.closeallTooltips();
+
+            }
+
+            // calc top & left for the various tooltip positions
+            option.position = jsPanel.calcToooltipPosition(jsPparent, option);
+
+            // position the tooltip & add tooltip class
+            jsP.css({
+                top: option.position.top,
+                left: option.position.left
+            }).addClass('jsPanel-tt');
+
+            if (!jsPparent.parent().hasClass('jsPanel-tooltip-wrapper')) {
+
+                // wrap element serving as trigger in a div - will take the tooltip
+                jsPparent.wrap('<div class="jsPanel-tooltip-wrapper">');
+
+                // append tooltip (jsPanel) to the wrapper div
+                jsPparent.parent().append(jsP);
+
+                if (option.paneltype.mode === 'semisticky') {
+
+                    jsP.hover(
+                        function () {
+                            $.noop();
+                        },
+                        function () {
+                            jsPanel.close(jsP, option.paneltype.type, jsPparentTagname);
+                        }
+                    );
+
+                } else if (option.paneltype.mode === 'sticky') {
+
+                    $.noop();
+
+                } else {
+
+                    option.controls.buttons = 'none';
+
+                    // tooltip will be removed whenever mouse leaves trigger
+                    jsPparent.off('mouseout'); // to prevent mouseout from firing several times
+                    jsPparent.mouseout(function () {
+
+                        jsPanel.close(jsP, option.paneltype.type, jsPparentTagname);
+
+                    });
+
+                }
+            }
+
+            // corners
+            jsP.css('overflow', 'visible');
+
+            if (option.paneltype.cornerBG) {
+
+                var corner = $("<div></div>"),
+                    cornerLoc = "jsPanel-corner-" + option.paneltype.position,
+                    cornerPos,
+                    cornerOX = parseInt(option.paneltype.cornerOX) || 0,
+                    cornerOY = parseInt(option.paneltype.cornerOY) || 0,
+                    cornerBG = option.paneltype.cornerBG;
+
+                if (option.paneltype.position !== "bottom") {
+
+                    corner.addClass("jsPanel-corner " + cornerLoc).appendTo(jsP);
+
+                } else {
+
+                    corner.addClass("jsPanel-corner " + cornerLoc).prependTo(jsP);
+
+                }
+
+                if (option.paneltype.position === "top") {
+
+                    cornerPos = parseInt(option.size.width)/2 - 12 + (cornerOX) + "px";
+                    corner.css({borderTopColor: cornerBG, left: cornerPos});
+
+                } else if (option.paneltype.position === "right") {
+
+                    cornerPos = parseInt(option.size.height)/2 - 12 + (cornerOY) + "px";
+                    corner.css({borderRightColor: cornerBG, left: "-22px", top: cornerPos});
+
+                } else if (option.paneltype.position === "bottom") {
+
+                    cornerPos = parseInt(option.size.width)/2 - 12 + (cornerOX) + "px";
+                    corner.css({borderBottomColor: cornerBG, left: cornerPos, top: "-22px"});
+
+                } else if (option.paneltype.position === "left") {
+
+                    cornerPos = parseInt(option.size.height)/2 - 12 + (cornerOY) + "px";
+                    corner.css({borderLeftColor: cornerBG, left: option.size.width, top: cornerPos});
+
+                }
+
+            }
+        } else if (option.paneltype.type === 'hint') {
+
+            jsPanel.setHintDefaults(option, jsP);
+
+            // bind callback for close button
+            $('.jsPanel-hint-close-dark, .jsPanel-hint-close-white', jsP).on('click', jsP, function (event) {
+
+                event.data.close(jsP, option.paneltype.type, jsPparentTagname);
+
+            });
+
+            // set option.position for hints using 'top left', 'top center' or 'top right'
+            if (option.position.top === '0' && option.position.left === 'center') {
+                // Schleife über alle hints in jsPanel.hintsTc, Höhen aufsummieren und als top für option.position verwenden
+                if (jsPanel.hintsTc.length > 0) {
+
+                    option.position = jsPanel.hintTop(jsPanel.hintsTc);
+
+                }
+                // populate array with hints
+                jsPanel.hintsTc.push(jsP.attr('id'));
+
+            } else if (option.position.top === '0' && option.position.left === '0') {
+
+                if (jsPanel.hintsTl.length > 0) {
+
+                    option.position = jsPanel.hintTop(jsPanel.hintsTl);
+
+                }
+                jsPanel.hintsTl.push(jsP.attr('id'));
+
+            } else if (option.position.top === '0' && option.position.right === '0') {
+
+                if (jsPanel.hintsTr.length > 0) {
+
+                    option.position = jsPanel.hintTop(jsPanel.hintsTr);
+
+                }
+                jsPanel.hintsTr.push(jsP.attr('id'));
+
+            }
+        }
+
+        /* option.selector - append jsPanel only to the first object in selector ------------------------------------ */
+        if (option.paneltype.type !== 'tooltip') {
+
+            jsP.appendTo(jsPparent);
+
+        }
+        if (option.paneltype.type === 'modal') {
+
+            jsP.css('zIndex', '1100');
+
+            if (option.paneltype.mode === 'extended') {
+
+                $('.jsPanel-backdrop').css('z-index', '999');
+
+            }
+
+        } else {
+
+            if (!jsP.hasClass("jsPanel-modal")) {
+                jsP.css('z-index', jsPanel.setZi());
+            }
+
+        }
+
+        /* option.bootstrap & option.theme -------------------------------------------------------------------------- */
+        if (option.bootstrap) {
+
+            // check whether a bootstrap compatible theme is used and set option.theme accordingly
+            option.theme = jsPanel.isBootstrapTheme(option.bootstrap);
+            option.controls.iconfont = 'bootstrap';
+
+            jsP.alterClass('jsPanel-theme-*', 'panel panel-' + option.theme);
+            jsP.header.alterClass('jsPanel-theme-*', 'panel-heading');
+            jsP.header.title.addClass('panel-title');
+            jsP.content.alterClass('jsPanel-theme-*', 'panel-body');
+            jsP.footer.addClass('panel-footer');
+
+            // fix css problems for panels nested in other bootstrap panels
+            jsP.header.title.css('color', function () {
+
+                return jsP.header.css('color');
+
+            });
+            jsP.content.css('border-top-color', function () {
+
+                return jsP.header.css('border-top-color');
+            });
+
+        } else {
+
+            // activate normal non bootstrap themes
+            jsP.alterClass('jsPanel-theme-*', 'jsPanel-theme-' + option.theme);
+            jsP.header.alterClass('jsPanel-theme-*', 'jsPanel-theme-' + option.theme);
+            jsP.content.alterClass('jsPanel-theme-*', 'jsPanel-theme-' + option.theme);
+            jsP.footer.alterClass('jsPanel-theme-*', 'jsPanel-theme-' + option.theme);
+
+        }
+
+        /* option.removeHeader -------------------------------------------------------------------------------------- */
+        if (option.removeHeader) {
+
+            jsP.header.remove();
+
+        }
+
+        /* option.controls (buttons in header right) | default: object ---------------------------------------------- */
+        if (!option.removeHeader) {
+
+            jsPanel.configControls(option.controls, jsP);
+
+        }
+
+        /* insert iconfonts if option.iconfont set (default is "jsglyph") */
+        if (option.controls.iconfont) {
+
+            jsPanel.configIconfont(option.controls.iconfont, jsP);
+
+        }
+
+        // if option.controls.iconfont === false restore old icon sprite
+        if (!option.controls.iconfont) {
+
+            $('.jsPanel-btn-close, .jsPanel-btn-max, .jsPanel-btn-norm, .jsPanel-btn-min, .jsPanel-btn-small, .jsPanel-btn-smallrev', jsP.header.controls).empty();
+
+        }
+
+        /* option.toolbarHeader | default: false -------------------------------------------------------------------- */
+        if (option.toolbarHeader && option.removeHeader === false) {
+
+            if (typeof option.toolbarHeader === 'string') {
+
+                jsP.header.toolbar.append(option.toolbarHeader);
+
+            } else if ($.isFunction(option.toolbarHeader)) {
+
+                jsP.header.toolbar.append(option.toolbarHeader(jsP.header));
+
+            } else if ($.isArray(option.toolbarHeader)) {
+
+                jsPanel.configToolbar(option.toolbarHeader, jsP.header.toolbar, jsP);
+
+            }
+        }
+
+        /* option.toolbarFooter | default: false -------------------------------------------------------------------- */
+        if (option.toolbarFooter) {
+
+            jsP.footer.css({
+
+                display: 'block',
+                padding: '0 20px 0 5px'
+
+            });
+
+            if (typeof option.toolbarFooter === 'string') {
+
+                jsP.footer.append(option.toolbarFooter);
+
+            } else if ($.isFunction(option.toolbarFooter)) {
+
+                jsP.footer.append(option.toolbarFooter(jsP.footer));
+
+            } else if ($.isArray(option.toolbarFooter)) {
+
+                jsPanel.configToolbar(option.toolbarFooter, jsP.footer, jsP);
+
+            }
+        }
+
+        /* option.rtl | default: false ------------------------------------------------------------------------------ */
+        if (option.rtl.rtl === true) {
+
+            jsPanel.setRTL(jsP, option.rtl.lang);
+
+        }
+
+        /* option.overflow  | default: 'hidden' --------------------------------------------------------------------- */
+        if (typeof option.overflow === 'string') {
+
+            jsP.content.css('overflow', option.overflow);
+
+        } else if ($.isPlainObject(option.overflow)) {
+
+            jsP.content.css({
+
+                'overflow-y': option.overflow.vertical,
+                'overflow-x': option.overflow.horizontal
+
+            });
+
+        }
+
+        /* option.draggable ----------------------------------------------------------------------------------------- */
+        if ($.isPlainObject(option.draggable)) {
+
+            // if jsPanel is childpanel
+            if (jsP.parent().hasClass('jsPanel-content')) {
+
+                option.draggable.containment = 'parent';
+
+            }
+
+            // merge draggable settings and apply
+            option.customdraggable = $.extend(true, {}, $.jsPanel.defaults.draggable, option.draggable);
+            jsP.draggable(option.customdraggable);
+
+        } else if (option.draggable === 'disabled') {
+
+            // reset cursor, draggable deactivated
+            $('.jsPanel-title', jsP).css('cursor', 'inherit');
+
+            // jquery ui draggable initialize disabled to allow to query status
+            jsP.draggable({ disabled: true });
+
+        }
+
+        /* option.resizable ----------------------------------------------------------------------------------------- */
+        if ($.isPlainObject(option.resizable)) {
+
+            option.customresizable = $.extend(true, {}, $.jsPanel.defaults.resizable, option.resizable);
+            jsP.resizable(option.customresizable);
+
+        } else if (option.resizable === 'disabled') {
+
+            // jquery ui resizable initialize disabled to allow to query status
+            jsP.resizable({ disabled: true });
+            $('.ui-icon-gripsmall-diagonal-se', jsP).css('background-image', 'none');
+
+        }
+
+        /* option.content ------------------------------------------------------------------------------------------- */
+        // option.content can be any valid argument for jQuery.append()
+        if (option.content) {
+
+            jsP.content.append(option.content);
+
+
+        }
+
+        /* option.load ---------------------------------------------------------------------------------------------- */
+        if ($.isPlainObject(option.load) && option.load.url) {
+
+            if (!option.load.data) {
+
+                option.load.data = undefined;
+
+            }
+
+            jsP.content.load(option.load.url, option.load.data, function (responseText, textStatus, jqXHR) {
+
+                if (option.load.complete) {
+                    option.load.complete(responseText, textStatus, jqXHR, jsP);
+                }
+
+                // title h3 might be to small: load() is async!
+                jsPanel.resizeTitle(jsP);
+
+                // update option.size (content might come delayed)
+                jsPanel.updateOptionSize(jsP, option.size);
+
+                // fix for a bug in jQuery-UI draggable? that causes the jsPanel to reduce width when dragged beyond boundary of containing element and option.size.width is 'auto'
+                jsP.content.css('width', function () {
+
+                    return jsP.content.outerWidth() + 'px';
+
+                });
+
+            });
+
+        }
+
+        /* option.ajax ---------------------------------------------------------------------------------------------- */
+        if ($.isPlainObject(option.ajax)) {
+
+            $.ajax(option.ajax)
+
+                .done(function (data, textStatus, jqXHR) {
+
+                    jsP.content.empty().append(data);
+
+                    if (option.ajax.done && $.isFunction(option.ajax.done)) {
+
+
+                        option.ajax.done(data, textStatus, jqXHR, jsP);
+                    }
+
+                })
+
+                .fail(function (jqXHR, textStatus, errorThrown) {
+
+                    if (option.ajax.fail && $.isFunction(option.ajax.fail)) {
+
+                        option.ajax.fail(jqXHR, textStatus, errorThrown, jsP);
+
+                    }
+
+                })
+
+                .always(function (arg1, textStatus, arg3) {
+                    //In response to a successful request, the function's arguments are the same as those of .done(): data(hier: arg1), textStatus, and the jqXHR object(hier: arg3)
+                    //For failed requests the arguments are the same as those of .fail(): the jqXHR object(hier: arg1), textStatus, and errorThrown(hier: arg3)
+                    // fix for a bug in jQuery-UI draggable? that causes the jsPanel to reduce width when dragged beyond boundary of containing element and option.size.width is 'auto'
+
+                    jsP.content.css('width', function () {
+
+                        return jsP.content.outerWidth() + 'px';
+
+                    });
+
+                    if (option.ajax.always && $.isFunction(option.ajax.always)) {
+
+                        option.ajax.always(arg1, textStatus, arg3, jsP);
+
+                    }
+
+                    // title h3 might be to small: load() is async!
+                    jsPanel.resizeTitle(jsP);
+
+                    // update option.size (content might come delayed)
+                    jsPanel.updateOptionSize(jsP, option.size);
+
+                })
+
+                .then(function (data, textStatus, jqXHR) {
+
+                    if (option.ajax.then && $.isArray(option.ajax.then)) {
+
+                        if (option.ajax.then[0] && $.isFunction(option.ajax.then[0])) {
+
+                            option.ajax.then[0](data, textStatus, jqXHR, jsP);
+
+                        }
+
+                        // title h3 might be to small: load() is async!
+                        jsPanel.resizeTitle(jsP);
+
+                        // update option.size (content might come delayed)
+                        jsPanel.updateOptionSize(jsP, option.size);
+
+                    }
+                }, function (jqXHR, textStatus, errorThrown) {
+
+                    if (option.ajax.then && $.isArray(option.ajax.then)) {
+
+                        if (option.ajax.then[1] && $.isFunction(option.ajax.then[1])) {
+
+                            option.ajax.then[1](jqXHR, textStatus, errorThrown, jsP);
+
+                        }
+
+                        // title h3 might be to small: load() is async!
+                        jsPanel.resizeTitle(jsP);
+
+                    }
+
+                }
+
+            );
+
+        }
+
+        /* option.size ---------------------------------------------------------------------------------------------- */
+        if (typeof option.size === 'string' && option.size === 'auto') {
+
+            option.size = {
+
+                width: 'auto',
+                height: 'auto'
+
+            };
+
+        } else if ($.isPlainObject(option.size)) {
+
+            if (!jsPanel.isPosInt(option.size.width)) {
+
+                if (typeof option.size.width === 'string' && option.size.width !== 'auto') {
+
+                    option.size.width = parseInt(option.size.width, 10);
+
+                    if (option.size.width < 1 || !$.isNumeric(option.size.width)) {
+
+                        option.size.width = $.jsPanel.defaults.size.width;
+
+                    }
+
+                } else if ($.isFunction(option.size.width)) {
+
+                    option.size.width = parseInt(option.size.width(), 10);
+
+                } else if (option.size.width !== 'auto') {
+
+                    option.size.width = $.jsPanel.defaults.size.width;
+
+                }
+
+            }
+
+            if (!jsPanel.isPosInt(option.size.height)) {
+
+                if (typeof option.size.height === 'string' && option.size.height !== 'auto') {
+
+                    option.size.height = parseInt(option.size.height, 10);
+
+                    if (option.size.height < 1 || !$.isNumeric(option.size.height)) {
+
+                        option.size.height = $.jsPanel.defaults.size.height;
+
+                    }
+
+                } else if ($.isFunction(option.size.height)) {
+
+                    option.size.height = parseInt(option.size.height(), 10);
+
+                } else if (option.size.height !== 'auto') {
+
+                    option.size.height = $.jsPanel.defaults.size.height;
+
+                }
+
+            }
+
+        }
+
+        jsP.content.css({
+
+            width: option.size.width + 'px',
+            height: option.size.height + 'px'
+
+        });
+
+        // Important! limit title width; final adjustment follows later; otherwise title might be longer than panel width
+        jsP.header.title.css('width', jsP.content.width()-90);
+
+        /* option.iframe -------------------------------------------------------------------------------------------- */
+        // implemented after option.size because option.size.width/height are either "auto" or pixel values already
+        if ($.isPlainObject(option.iframe) && (option.iframe.src || option.iframe.srcdoc)) {
+
+            var iFrame = $("<iframe></iframe>");
+
+            // iframe content
+            if (option.iframe.srcdoc) {
+
+                iFrame.prop("srcdoc", option.iframe.srcdoc);
+
+            }
+
+            if (option.iframe.src) {
+
+                iFrame.prop("src", option.iframe.src);
+
+            }
+
+            //iframe size
+            if (option.size.width !== "auto" && !option.iframe.width) {
+
+                iFrame.prop("width", option.size.width);
+
+            } else if (typeof option.iframe.width === 'string' && option.iframe.width.slice(-1) === '%') {
+
+                iFrame.prop("width", option.iframe.width);
+
+            } else {
+
+                iFrame.prop("width", parseInt(option.iframe.width) + 'px');
+
+            }
+
+            if (option.size.height !== "auto" && !option.iframe.height) {
+
+                iFrame.prop("height", option.size.height);
+
+            } else if (typeof option.iframe.height === 'string' && option.iframe.height.slice(-1) === '%') {
+
+                iFrame.prop("height", option.iframe.height);
+
+            } else {
+
+                iFrame.prop("height", parseInt(option.iframe.height) + 'px');
+
+            }
+
+            //iframe name
+            if (typeof option.iframe.name === 'string') {
+
+                iFrame.prop("name", option.iframe.name);
+
+            }
+
+            //iframe id
+            if (typeof option.iframe.id === 'string') {
+
+                iFrame.prop("id", option.iframe.id);
+
+            }
+
+            //iframe seamless (not yet supported by any browser)
+            if (option.iframe.seamless) {
+
+                iFrame.prop("seamless", "seamless");
+
+            }
+
+            //iframe sandbox
+            if (typeof option.iframe.sandbox === 'string') {
+
+                iFrame.prop("sandox", option.iframe.sandbox);
+
+            }
+
+            //iframe style
+            if ($.isPlainObject(option.iframe.style)) {
+
+                iFrame.css(option.iframe.style);
+
+            }
+
+            //iframe css classes
+            if (typeof option.iframe.classname === 'string') {
+
+                iFrame.addClass(option.iframe.classname);
+
+            } else if ($.isFunction(option.iframe.classname)) {
+
+                iFrame.addClass(option.iframe.classname());
+
+            }
+
+            jsP.content.append(iFrame);
+        }
+
+        /* option.position ------------------------------------------------------------------------------------------ */
+        if (option.paneltype.type !== 'tooltip') {
+            // when using option.size = 'auto' and option.position = 'center' consider use of option.ajax with
+            // async: false -> size will be known when position is calculated
+            // value "center" not allowed for option.position.bottom & option.position.right -> use top and/or left
+
+            jsP.calcPanelposition = function (jsP, selector) {
+
+                var panelpos = {};
+
+                // get px values for panel size in case option.size is 'auto' - results will be incorrect whenever content
+                // is not loaded yet ( e.g. option.load, option.ajax ) -> centering can't work correctly
+                option.size.width = $(jsP).outerWidth();
+                option.size.height = $(jsP).innerHeight();
+
+                // delete op.top and/or left if option.position.bottom and/or right
+                if (option.position.bottom) {
+
+                    delete option.position.top;
+
+                }
+                if (option.position.right) {
+
+                    delete option.position.left;
+
+                }
+
+                // calculate top | bottom values != center
+                // if not checked for 0 as well code would not be executed!
+                if (option.position.bottom || option.position.bottom === 0) {
+
+                    delete option.position.top;
+                    jsPanel.calcPos('bottom', option, count, jsP);
+
+                } else if (option.position.top || option.position.top === 0) {
+
+                    if (option.position.top === 'center') {
+
+                        option.position.top = jsPanel.calcPosCenter(option).top;
+
+                    } else {
+
+                        jsPanel.calcPos('top', option, count, jsP);
+
+                    }
+                }
+
+                // calculate left | right values != center
+                if (option.position.right || option.position.right === 0) {
+
+                    delete option.position.left;
+                    jsPanel.calcPos('right', option, count, jsP);
+
+                } else if (option.position.left || option.position.left === 0) {
+
+                    if (option.position.left === 'center') {
+
+                        option.position.left = jsPanel.calcPosCenter(option).left;
+
+                    } else {
+
+                        jsPanel.calcPos('left', option, count, jsP);
+
+                    }
+
+                }
+                if (option.position.top) {
+
+                    panelpos.top = parseInt(option.position.top, 10) + option.offset.top + 'px';
+
+                } else {
+
+                    panelpos.bottom = parseInt(option.position.bottom, 10) + option.offset.top + 'px';
+
+                }
+                if (option.position.left) {
+
+                    panelpos.left = parseInt(option.position.left, 10) + option.offset.left + 'px';
+
+                } else {
+
+                    panelpos.right = parseInt(option.position.right, 10) + option.offset.left + 'px';
+
+                }
+
+                jsP.css(panelpos);
+                option.position = {
+
+                    top: jsP.css('top'),
+                    left: jsP.css('left')
+
+                };
+            };
+
+            // finally calculate & position the jsPanel
+            jsP.calcPanelposition(jsP, option.selector);
+
+        }
+
+        /* option.addClass ------------------------------------------------------------------------------------------ */
+        if (typeof option.addClass.header === 'string') {
+
+            jsP.header.addClass(option.addClass.header);
+
+        }
+
+        if (typeof option.addClass.content === 'string') {
+
+            jsP.content.addClass(option.addClass.content);
+
+        }
+
+        if (typeof option.addClass.footer === 'string') {
+
+            jsP.footer.addClass(option.addClass.footer);
+
+        }
+
+        // handlers for doubleclicks -------------------------------------------------------------------------------- */
+        // dblclicks disabled for normal modals, hints and tooltips
+        if (option.paneltype.mode !== "default") {
+
+            if (option.dblclicks) {
+
+                if (option.dblclicks.title) {
+
+                    jsP.header.title.on('dblclick', function (e) {
+
+                        e.preventDefault();
+                        jsPanel.dblclickhelper(option.dblclicks.title, jsP);
+
+                    });
+
+                }
+
+                if (option.dblclicks.content) {
+
+                    jsP.content.on('dblclick', function (e) {
+
+                        e.preventDefault();
+                        jsPanel.dblclickhelper(option.dblclicks.content, jsP);
+
+                    });
+
+                }
+
+                if (option.dblclicks.footer) {
+
+                    jsP.footer.on('dblclick', function (e) {
+
+                        e.preventDefault();
+                        jsPanel.dblclickhelper(option.dblclicks.footer, jsP);
+
+                    });
+
+                }
+
+            }
+
+        }
+
+        /* option.show ---------------------------------------------------------------------------------------------- */
+        if (anim.indexOf(" ") === -1) {
+
+            // if no space is found in "anim" -> function anwenden
+            jsP[anim]({
+
+                done: function () {
+
+                    jsP.status = "normalized";
+
+                    // trigger custom event
+                    $(jsP).trigger('jspanelloaded', jsP.attr('id'));
+                    $(jsP).trigger('jspanelstatechange', jsP.attr('id'));
+
+                    option.size = {
+
+                        width: jsP.outerWidth() + 'px',
+                        height: jsP.outerHeight() + 'px'
+
+                    };
+
+                }
+
+            });
+
+        } else {
+
+            jsP.status = "normalized";
+
+            // does not work with certain combinations of type of animation and positioning
+            jsP.css({
+
+                display: 'block',
+                opacity: 1
+
+            })
+                .addClass(option.show)
+                .trigger('jspanelloaded', jsP.attr('id'))
+                .trigger('jspanelstatechange', jsP.attr('id'));
+
+            option.size = {
+
+                width: jsP.outerWidth() + 'px',
+                height: jsP.outerHeight() + 'px'
+
+            };
+
+        }
+
+        /* needed if a maximized panel in body is normalized again -------------------------------------------------- */
+        // don't put this under $('body').on('jspanelloaded', function () { ... }
+        verticalOffset = jsPanel.calcVerticalOffset(jsP);
+
+        /* replace bottom/right values with corresponding top/left values if necessary ------------------------------ */
+        jsPanel.replaceCSSBottomRight(jsP, option.position);
+
+        /* option.title | needs to be late in the file! ------------------------------------------------------------- */
+        jsP.header.title.prepend(option.title);
+        jsPanel.resizeTitle(jsP);
+
+        /* reposition hints while scrolling window, must be after normalization of position ------------------------- */
+        if (option.paneltype.type === 'hint') {
+
+            jsPanel.reposHintsScroll(jsP);
+
+        }
+
+        /* reposition jsPanel appended to body while scrolling window ----------------------------------------------- */
+        if (jsPparentTagname === 'body' && (option.paneltype.type !== 'tooltip' || option.paneltype.type !== 'hint')) {
+
+            jsPanel.fixPosition(jsP);
+
+        }
+
+        /* option.callback & option.autoclose and a jQuery-UI draggable bugfix -------------------------------------- */
+        /* resizestart & resizestop & dragstop callbacks ------------------------------------------------------------ */
+        // features activated for extended modals only for @genachka
+        if (!option.paneltype || option.paneltype.mode !== 'default') {
+            // not needed for modals, hints and tooltips
+
+            $(jsP).on("resize", function () {
+
+                jsPanel.resizeContent(jsP);
+                jsPanel.resizeTitle(jsP);
+
+            });
+
+            $(jsP).on("resizestop", function () {
+
+                option.size = {
+
+                    width: jsP.outerWidth() + 'px',
+                    height: jsP.outerHeight() + 'px'
+
+                };
+
+                jsP.status = "normalized";
+                $(jsP).trigger('jspanelnormalized', jsP.attr('id'));
+                $(jsP).trigger('jspanelstatechange', jsP.attr('id'));
+
+                // controls und title zurücksetzen
+                jsPanel.hideControls(".jsPanel-btn-norm, .jsPanel-btn-smallrev", jsP);
+
+            });
+
+            $(jsP).on("dragstart", function () {
+
+                // remove window.scroll handler, is added again on dragstop
+                $(window).off('scroll', jsP.jsPanelfixPos);
+
+                if (option.paneltype.mode === 'extended') {
+
+                    jsP.css('z-index', '1100');
+
+                }
+
+            });
+
+            $(jsP).on("dragstop", function () {
+
+                option.position = {
+
+                    top: jsP.css('top'),
+                    left: jsP.css('left')
+
+                };
+
+                verticalOffset = jsPanel.calcVerticalOffset(jsP);
+
+                if (jsPparentTagname === 'body') {
+
+                    jsPanel.fixPosition(jsP);
+
+                }
+
+            });
+        }
+
+        /* option.autoclose | default: false --------------------------------------- */
+        if (typeof option.autoclose === 'number' && option.autoclose > 0) {
+
+            jsPanel.autoclose(jsP, jsP.attr('id'), option.autoclose);
+
+        }
+
+        /* tooltip corrections ----------------------------------------------------- */
+        if (option.paneltype.type === "tooltip" && (option.paneltype.position === "top" || option.paneltype.position === "bottom")) {
+
+            jsPanel.shiftTooltipHorizontal(jsP, option.paneltype.shiftwithin);
+
+        } else if (option.paneltype.position === "left" || option.paneltype.position === "right") {
+
+            jsPanel.shiftTooltipVertical(jsP, option.paneltype.shiftwithin);
+
+        }
+
+        /* option.callback --------------------------------------------------------- */
+        if (option.callback && $.isFunction(option.callback)) {
+
+            option.callback(jsP);
+
+        } else if ($.isArray(option.callback)) {
+            var j,
+                max = option.callback.length;
+
+            for (j = 0; j < max; j += 1) {
+
+                if ($.isFunction(option.callback[j])) {
+
+                    option.callback[j](jsP);
+
+                }
+            }
+
+        }
+
+        return jsP;
+    };
+
+    /* jsPanel.defaults */
+    $.jsPanel.defaults = {
+        "addClass": {
+            header: false,
+            content: false,
+            footer: false
+        },
+        "ajax": false,
+        "autoclose": false,
+        "bootstrap": false,
+        "callback": undefined,
+        "content": false,
+        "controls": {
+            buttons: true,
+            iconfont: 'jsglyph',
+            close: false,
+            maximize: false,
+            minimize: false,
+            normalize: false,
+            smallify: false,
+            maxtoScreen: false
+        },
+        "dblclicks": false,
+        "draggable": {
+            handle: 'div.jsPanel-hdr, div.jsPanel-ftr',
+            stack: '.jsPanel',
+            opacity: 0.7
+        },
+        "id": function () {
+            jsPanel.ID += 1;
+            return 'jsPanel-' + jsPanel.ID;
+        },
+        "iframe": false,
+        "load": false,
+        "offset": {
+            top: 0,
+            left: 0
+        },
+        "paneltype": false,
+        "overflow": 'hidden',
+        "position": 'auto',
+        "removeHeader": false,
+        "resizable": {
+            handles: 'e, s, w, se, sw',
+            autoHide: false,
+            minWidth: 150,
+            minHeight: 93
+        },
+        "rtl": false,
+        "selector": 'body',
+        "show": 'fadeIn',
+        "size": {
+            width: 400,
+            height: 222
+        },
+        "theme": 'default',
+        "title": 'jsPanel',
+        "toolbarFooter": false,
+        "toolbarHeader": false
+    };
+
+    /*
+     * jQuery alterClass plugin
+     * Remove element classes with wildcard matching. Optionally add classes:
+     * $( '#foo' ).alterClass( 'foo-* bar-*', 'foobar' )
+     * Copyright (c) 2011 Pete Boere (the-echoplex.net)
+     * Free under terms of the MIT license: http://www.opensource.org/licenses/mit-license.php
+     */
+    $.fn.alterClass = function (removals, additions) {
+        var self = this,
+            patt;
+        if (removals.indexOf('*') === -1) {
+            // Use native jQuery methods if there is no wildcard matching
+            self.removeClass(removals);
+            return !additions ? self : self.addClass(additions);
+        }
+        patt = new RegExp('\\s' +
+            removals.replace(/\*/g, '[A-Za-z0-9-_]+').split(' ').join('\\s|\\s') +
+            '\\s', 'g');
+
+        self.each(function (i, it) {
+            var cn = ' ' + it.className + ' ';
+            while (patt.test(cn)) {
+                cn = cn.replace(patt, ' ');
+            }
+            it.className = $.trim(cn);
+        });
+        return !additions ? self : self.addClass(additions);
+    };
+
+    /* body click handler: remove all tooltips on click in body except click is inside tooltip */
+    $('body').click(function (e) {
+        var pID,
+            isTT = $(e.target).closest('.jsPanel-tt' ).length;
+
+        if (isTT < 1) {
+
+            $('.jsPanel-tt').each(function () {
+
+                pID = $(this).attr('id');
+
+                // if present remove tooltip wrapper and than remove tooltip
+                $('#' + pID).unwrap().remove();
+                $('body').trigger('jspanelclosed', pID);
+
+            });
+
+        }
+
+    });
+
+}(jQuery));

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jquery.ui.touch-punch.js
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jquery.ui.touch-punch.js	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jquery.ui.touch-punch.js	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,180 @@
+/*!
+ * jQuery UI Touch Punch 0.2.3
+ *
+ * Copyright 2011–2014, Dave Furfero
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
+ * Depends:
+ *  jquery.ui.widget.js
+ *  jquery.ui.mouse.js
+ */
+(function ($) {
+
+    // Detect touch support
+    $.support.touch = 'ontouchend' in document;
+
+    // Ignore browsers without touch support
+    if (!$.support.touch) {
+        return;
+    }
+
+    var mouseProto = $.ui.mouse.prototype,
+        _mouseInit = mouseProto._mouseInit,
+        _mouseDestroy = mouseProto._mouseDestroy,
+        touchHandled;
+
+    /**
+     * Simulate a mouse event based on a corresponding touch event
+     * @param {Object} event A touch event
+     * @param {String} simulatedType The corresponding mouse event
+     */
+    function simulateMouseEvent (event, simulatedType) {
+
+        // Ignore multi-touch events
+        if (event.originalEvent.touches.length > 1) {
+            return;
+        }
+
+        event.preventDefault();
+
+        var touch = event.originalEvent.changedTouches[0],
+            simulatedEvent = document.createEvent('MouseEvents');
+
+        // Initialize the simulated mouse event using the touch event's coordinates
+        simulatedEvent.initMouseEvent(
+            simulatedType,    // type
+            true,             // bubbles
+            true,             // cancelable
+            window,           // view
+            1,                // detail
+            touch.screenX,    // screenX
+            touch.screenY,    // screenY
+            touch.clientX,    // clientX
+            touch.clientY,    // clientY
+            false,            // ctrlKey
+            false,            // altKey
+            false,            // shiftKey
+            false,            // metaKey
+            0,                // button
+            null              // relatedTarget
+        );
+
+        // Dispatch the simulated event to the target element
+        event.target.dispatchEvent(simulatedEvent);
+    }
+
+    /**
+     * Handle the jQuery UI widget's touchstart events
+     * @param {Object} event The widget element's touchstart event
+     */
+    mouseProto._touchStart = function (event) {
+
+        var self = this;
+
+        // Ignore the event if another widget is already being handled
+        if (touchHandled || !self._mouseCapture(event.originalEvent.changedTouches[0])) {
+            return;
+        }
+
+        // Set the flag to prevent other widgets from inheriting the touch event
+        touchHandled = true;
+
+        // Track movement to determine if interaction was a click
+        self._touchMoved = false;
+
+        // Simulate the mouseover event
+        simulateMouseEvent(event, 'mouseover');
+
+        // Simulate the mousemove event
+        simulateMouseEvent(event, 'mousemove');
+
+        // Simulate the mousedown event
+        simulateMouseEvent(event, 'mousedown');
+    };
+
+    /**
+     * Handle the jQuery UI widget's touchmove events
+     * @param {Object} event The document's touchmove event
+     */
+    mouseProto._touchMove = function (event) {
+
+        // Ignore event if not handled
+        if (!touchHandled) {
+            return;
+        }
+
+        // Interaction was not a click
+        this._touchMoved = true;
+
+        // Simulate the mousemove event
+        simulateMouseEvent(event, 'mousemove');
+    };
+
+    /**
+     * Handle the jQuery UI widget's touchend events
+     * @param {Object} event The document's touchend event
+     */
+    mouseProto._touchEnd = function (event) {
+
+        // Ignore event if not handled
+        if (!touchHandled) {
+            return;
+        }
+
+        // Simulate the mouseup event
+        simulateMouseEvent(event, 'mouseup');
+
+        // Simulate the mouseout event
+        simulateMouseEvent(event, 'mouseout');
+
+        // If the touch interaction did not move, it should trigger a click
+        if (!this._touchMoved) {
+
+            // Simulate the click event
+            simulateMouseEvent(event, 'click');
+        }
+
+        // Unset the flag to allow other widgets to inherit the touch event
+        touchHandled = false;
+    };
+
+    /**
+     * A duck punch of the $.ui.mouse _mouseInit method to support touch events.
+     * This method extends the widget with bound touch event handlers that
+     * translate touch events to mouse events and pass them to the widget's
+     * original mouse event handling methods.
+     */
+    mouseProto._mouseInit = function () {
+
+        var self = this;
+
+        // Delegate the touch handlers to the widget's element
+        self.element.bind({
+            touchstart: $.proxy(self, '_touchStart'),
+            touchmove: $.proxy(self, '_touchMove'),
+            touchend: $.proxy(self, '_touchEnd')
+        });
+
+        // Call the original $.ui.mouse init method
+        _mouseInit.call(self);
+    };
+
+    /**
+     * Remove the touch event handlers
+     */
+    mouseProto._mouseDestroy = function () {
+
+        var self = this;
+
+        // Delegate the touch handlers to the widget's element
+        self.element.unbind({
+            touchstart: $.proxy(self, '_touchStart'),
+            touchmove: $.proxy(self, '_touchMove'),
+            touchend: $.proxy(self, '_touchEnd')
+        });
+
+        // Call the original $.ui.mouse destroy method
+        _mouseDestroy.call(self);
+    };
+
+})(jQuery);
\ No newline at end of file

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jsglyph.css
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jsglyph.css	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/jsglyph.css	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,64 @@
+ at font-face {
+    font-family: 'jsglyphregular';
+    src: url('fonts/jsglyph.eot');
+    src: url('fonts/jsglyph.eot?#iefix') format('embedded-opentype'),
+         url('fonts/jsglyph.woff2') format('woff2'),
+         url('fonts/jsglyph.woff') format('woff'),
+         url('fonts/jsglyph.ttf') format('truetype'),
+         url('fonts/jsglyph.svg#jsglyphregular') format('svg');
+    font-weight: normal;
+    font-style: normal;
+}
+.jsglyph{ font-family: "jsglyphregular"; }
+
+.jsglyph-minimize:before{ content: "\e001"; } /* unicode &#xe001; */
+.jsglyph-maximize:before{ content: "\e002"; }
+.jsglyph-chevron-up:before{ content: "\e003"; }
+.jsglyph-chevron-down:before{ content: "\e004"; }
+.jsglyph-normalize:before{ content: "\e005"; }
+.jsglyph-tick:before{ content: "\e006"; }
+.jsglyph-remove:before{ content: "\e007"; }
+.jsglyph-chevron-right:before{ content: "\e008"; }
+.jsglyph-chevron-left:before{ content: "\e009"; }
+.jsglyph-plus:before{ content: "\e00a"; }
+.jsglyph-minus:before{ content: "\e00b"; }
+.jsglyph-trashcan:before{ content: "\e00c"; }
+.jsglyph-square-info:before{ content: "\e00d"; }
+.jsglyph-square-exclamationmark:before{ content: "\e00e"; }
+.jsglyph-delete:before{ content: "\e00f"; }
+
+.jsglyph-mail:before{ content: "\e010"; }
+.jsglyph-envelope:before{ content: "\e011"; }
+.jsglyph-play:before{ content: "\e012"; }
+.jsglyph-forward:before{ content: "\e013"; }
+.jsglyph-backward:before{ content: "\e014"; }
+.jsglyph-step-forward:before{ content: "\e015"; }
+.jsglyph-step-backwardbefore{ content: "\e016"; }
+.jsglyph-fast-forward:before{ content: "\e017"; }
+.jsglyph-fast-backward:before{ content: "\e018"; }
+.jsglyph-eject:before{ content: "\e019"; }
+.jsglyph-stop:before{ content: "\e01a"; }
+.jsglyph-pause:before{ content: "\e01b"; }
+.jsglyph-arrow-right:before{ content: "\e01c"; }
+.jsglyph-arrow-left:before{ content: "\e01d"; }
+.jsglyph-arrow-up:before{ content: "\e01e"; }
+.jsglyph-arrow-down:before{ content: "\e01f"; }
+
+.jsglyph-star-halffull:before{ content: "\e020"; }
+.jsglyph-star:before{ content: "\e021"; }
+.jsglyph-star-empty:before{ content: "\e022"; }
+.jsglyph-arrow-right-hollow:before{ content: "\e023"; }
+.jsglyph-arrow-left-hollow:before{ content: "\e024"; }
+.jsglyph-arrow-up-hollow:before{ content: "\e025"; }
+.jsglyph-arrow-down-hollow:before{ content: "\e026"; }
+.jsglyph-square-questionmark:before{ content: "\e027"; }
+.jsglyph-circle-info:before{ content: "\e028"; }
+.jsglyph-circle-exclamationmark:before{ content: "\e029"; }
+.jsglyph-circle-qustionmark:before{ content: "\e02a"; }
+.jsglyph-circle2-info:before{ content: "\e02b"; }
+.jsglyph-circle2-exclamationmark:before{ content: "\e02c"; }
+.jsglyph-circle2-questionmark:before{ content: "\e02d"; }
+/*.jsglyph-:before{ content: "\e02e"; }*/
+/*.jsglyph-:before{ content: "\e02f"; }*/
+
+

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/mobile-detect.js
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/mobile-detect.js	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/jsPanel-2.3.3/mobile-detect.js	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,871 @@
+// THIS FILE IS GENERATED - DO NOT EDIT!
+/*global module:false, define:false*/
+
+(function (define, undefined) {
+define(function () {
+    'use strict';
+
+    var mobileDetectRules = {
+    "phones": {
+        "iPhone": "\\biPhone\\b|\\biPod\\b",
+        "BlackBerry": "BlackBerry|\\bBB10\\b|rim[0-9]+",
+        "HTC": "HTC|HTC.*(Sensation|Evo|Vision|Explorer|6800|8100|8900|A7272|S510e|C110e|Legend|Desire|T8282)|APX515CKT|Qtek9090|APA9292KT|HD_mini|Sensation.*Z710e|PG86100|Z715e|Desire.*(A8181|HD)|ADR6200|ADR6400L|ADR6425|001HT|Inspire 4G|Android.*\\bEVO\\b|T-Mobile G1|Z520m",
+        "Nexus": "Nexus One|Nexus S|Galaxy.*Nexus|Android.*Nexus.*Mobile|Nexus 4|Nexus 5|Nexus 6",
+        "Dell": "Dell.*Streak|Dell.*Aero|Dell.*Venue|DELL.*Venue Pro|Dell Flash|Dell Smoke|Dell Mini 3iX|XCD28|XCD35|\\b001DL\\b|\\b101DL\\b|\\bGS01\\b",
+        "Motorola": "Motorola|DROIDX|DROID BIONIC|\\bDroid\\b.*Build|Android.*Xoom|HRI39|MOT-|A1260|A1680|A555|A853|A855|A953|A955|A956|Motorola.*ELECTRIFY|Motorola.*i1|i867|i940|MB200|MB300|MB501|MB502|MB508|MB511|MB520|MB525|MB526|MB611|MB612|MB632|MB810|MB855|MB860|MB861|MB865|MB870|ME501|ME502|ME511|ME525|ME600|ME632|ME722|ME811|ME860|ME863|ME865|MT620|MT710|MT716|MT720|MT810|MT870|MT917|Motorola.*TITANIUM|WX435|WX445|XT300|XT301|XT311|XT316|XT317|XT319|XT320|XT390|XT502|XT530|XT531|XT532|XT535|XT603|XT610|XT611|XT615|XT681|XT701|XT702|XT711|XT720|XT800|XT806|XT860|XT862|XT875|XT882|XT883|XT894|XT901|XT907|XT909|XT910|XT912|XT928|XT926|XT915|XT919|XT925",
+        "Samsung": "Samsung|SGH-I337|BGT-S5230|GT-B2100|GT-B2700|GT-B2710|GT-B3210|GT-B3310|GT-B3410|GT-B3730|GT-B3740|GT-B5510|GT-B5512|GT-B5722|GT-B6520|GT-B7300|GT-B7320|GT-B7330|GT-B7350|GT-B7510|GT-B7722|GT-B7800|GT-C3010|GT-C3011|GT-C3060|GT-C3200|GT-C3212|GT-C3212I|GT-C3262|GT-C3222|GT-C3300|GT-C3300K|GT-C3303|GT-C3303K|GT-C3310|GT-C3322|GT-C3330|GT-C3350|GT-C3500|GT-C3510|GT-C3530|GT-C3630|GT-C3780|GT-C5010|GT-C5212|GT-C6620|GT-C6625|GT-C6712|GT-E1050|GT-E1070|GT-E1075|GT-E1080|GT-E1081|GT-E1085|GT-E1087|GT-E1100|GT-E1107|GT-E1110|GT-E1120|GT-E1125|GT-E1130|GT-E1160|GT-E1170|GT-E1175|GT-E1180|GT-E1182|GT-E1200|GT-E1210|GT-E1225|GT-E1230|GT-E1390|GT-E2100|GT-E2120|GT-E2121|GT-E2152|GT-E2220|GT-E2222|GT-E2230|GT-E2232|GT-E2250|GT-E2370|GT-E2550|GT-E2652|GT-E3210|GT-E3213|GT-I5500|GT-I5503|GT-I5700|GT-I5800|GT-I5801|GT-I6410|GT-I6420|GT-I7110|GT-I7410|GT-I7500|GT-I8000|GT-I8150|GT-I8160|GT-I8190|GT-I8320|GT-I8330|GT-I8350|GT-I8530|GT-I8700|GT-I8703|GT-I8910|GT-I9000|GT-I9001|GT-I9003|GT-I9010|GT-I9020|GT-I9023|GT-I9070|GT-I9082|GT-I9100|GT-I9103|GT-I9220|GT-I9250|GT-I9300|GT-I9305|GT-I9500|GT-I9505|GT-M3510|GT-M5650|GT-M7500|GT-M7600|GT-M7603|GT-M8800|GT-M8910|GT-N7000|GT-S3110|GT-S3310|GT-S3350|GT-S3353|GT-S3370|GT-S3650|GT-S3653|GT-S3770|GT-S3850|GT-S5210|GT-S5220|GT-S5229|GT-S5230|GT-S5233|GT-S5250|GT-S5253|GT-S5260|GT-S5263|GT-S5270|GT-S5300|GT-S5330|GT-S5350|GT-S5360|GT-S5363|GT-S5369|GT-S5380|GT-S5380D|GT-S5560|GT-S5570|GT-S5600|GT-S5603|GT-S5610|GT-S5620|GT-S5660|GT-S5670|GT-S5690|GT-S5750|GT-S5780|GT-S5830|GT-S5839|GT-S6102|GT-S6500|GT-S7070|GT-S7200|GT-S7220|GT-S7230|GT-S7233|GT-S7250|GT-S7500|GT-S7530|GT-S7550|GT-S7562|GT-S7710|GT-S8000|GT-S8003|GT-S8500|GT-S8530|GT-S8600|SCH-A310|SCH-A530|SCH-A570|SCH-A610|SCH-A630|SCH-A650|SCH-A790|SCH-A795|SCH-A850|SCH-A870|SCH-A890|SCH-A930|SCH-A950|SCH-A970|SCH-A990|SCH-I100|SCH-I110|SCH-I400|SCH-I405|SCH-I500|SCH-I510|SCH-I515|SCH-I600|SCH-I730|SCH-I760|SCH-I770|SCH-I830|SCH-I910|SCH-I920|SCH-I959|SCH-LC11|SCH-N150|SCH-N300|SCH-R100|SCH-R300|SCH-R351|SCH-R400|SCH-R410|SCH-T300|SCH-U310|SCH-U320|SCH-U350|SCH-U360|SCH-U365|SCH-U370|SCH-U380|SCH-U410|SCH-U430|SCH-U450|SCH-U460|SCH-U470|SCH-U490|SCH-U540|SCH-U550|SCH-U620|SCH-U640|SCH-U650|SCH-U660|SCH-U700|SCH-U740|SCH-U750|SCH-U810|SCH-U820|SCH-U900|SCH-U940|SCH-U960|SCS-26UC|SGH-A107|SGH-A117|SGH-A127|SGH-A137|SGH-A157|SGH-A167|SGH-A177|SGH-A187|SGH-A197|SGH-A227|SGH-A237|SGH-A257|SGH-A437|SGH-A517|SGH-A597|SGH-A637|SGH-A657|SGH-A667|SGH-A687|SGH-A697|SGH-A707|SGH-A717|SGH-A727|SGH-A737|SGH-A747|SGH-A767|SGH-A777|SGH-A797|SGH-A817|SGH-A827|SGH-A837|SGH-A847|SGH-A867|SGH-A877|SGH-A887|SGH-A897|SGH-A927|SGH-B100|SGH-B130|SGH-B200|SGH-B220|SGH-C100|SGH-C110|SGH-C120|SGH-C130|SGH-C140|SGH-C160|SGH-C170|SGH-C180|SGH-C200|SGH-C207|SGH-C210|SGH-C225|SGH-C230|SGH-C417|SGH-C450|SGH-D307|SGH-D347|SGH-D357|SGH-D407|SGH-D415|SGH-D780|SGH-D807|SGH-D980|SGH-E105|SGH-E200|SGH-E315|SGH-E316|SGH-E317|SGH-E335|SGH-E590|SGH-E635|SGH-E715|SGH-E890|SGH-F300|SGH-F480|SGH-I200|SGH-I300|SGH-I320|SGH-I550|SGH-I577|SGH-I600|SGH-I607|SGH-I617|SGH-I627|SGH-I637|SGH-I677|SGH-I700|SGH-I717|SGH-I727|SGH-i747M|SGH-I777|SGH-I780|SGH-I827|SGH-I847|SGH-I857|SGH-I896|SGH-I897|SGH-I900|SGH-I907|SGH-I917|SGH-I927|SGH-I937|SGH-I997|SGH-J150|SGH-J200|SGH-L170|SGH-L700|SGH-M110|SGH-M150|SGH-M200|SGH-N105|SGH-N500|SGH-N600|SGH-N620|SGH-N625|SGH-N700|SGH-N710|SGH-P107|SGH-P207|SGH-P300|SGH-P310|SGH-P520|SGH-P735|SGH-P777|SGH-Q105|SGH-R210|SGH-R220|SGH-R225|SGH-S105|SGH-S307|SGH-T109|SGH-T119|SGH-T139|SGH-T209|SGH-T219|SGH-T229|SGH-T239|SGH-T249|SGH-T259|SGH-T309|SGH-T319|SGH-T329|SGH-T339|SGH-T349|SGH-T359|SGH-T369|SGH-T379|SGH-T409|SGH-T429|SGH-T439|SGH-T459|SGH-T469|SGH-T479|SGH-T499|SGH-T509|SGH-T519|SGH-T539|SGH-T559|SGH-T589|SGH-T609|SGH-T619|SGH-T629|SGH-T639|SGH-T659|SGH-T669|SGH-T679|SGH-T709|SGH-T719|SGH-T729|SGH-T739|SGH-T746|SGH-T749|SGH-T759|SGH-T769|SGH-T809|SGH-T819|SGH-T839|SGH-T919|SGH-T929|SGH-T939|SGH-T959|SGH-T989|SGH-U100|SGH-U200|SGH-U800|SGH-V205|SGH-V206|SGH-X100|SGH-X105|SGH-X120|SGH-X140|SGH-X426|SGH-X427|SGH-X475|SGH-X495|SGH-X497|SGH-X507|SGH-X600|SGH-X610|SGH-X620|SGH-X630|SGH-X700|SGH-X820|SGH-X890|SGH-Z130|SGH-Z150|SGH-Z170|SGH-ZX10|SGH-ZX20|SHW-M110|SPH-A120|SPH-A400|SPH-A420|SPH-A460|SPH-A500|SPH-A560|SPH-A600|SPH-A620|SPH-A660|SPH-A700|SPH-A740|SPH-A760|SPH-A790|SPH-A800|SPH-A820|SPH-A840|SPH-A880|SPH-A900|SPH-A940|SPH-A960|SPH-D600|SPH-D700|SPH-D710|SPH-D720|SPH-I300|SPH-I325|SPH-I330|SPH-I350|SPH-I500|SPH-I600|SPH-I700|SPH-L700|SPH-M100|SPH-M220|SPH-M240|SPH-M300|SPH-M305|SPH-M320|SPH-M330|SPH-M350|SPH-M360|SPH-M370|SPH-M380|SPH-M510|SPH-M540|SPH-M550|SPH-M560|SPH-M570|SPH-M580|SPH-M610|SPH-M620|SPH-M630|SPH-M800|SPH-M810|SPH-M850|SPH-M900|SPH-M910|SPH-M920|SPH-M930|SPH-N100|SPH-N200|SPH-N240|SPH-N300|SPH-N400|SPH-Z400|SWC-E100|SCH-i909|GT-N7100|GT-N7105|SCH-I535|SM-N900A|SGH-I317|SGH-T999L|GT-S5360B|GT-I8262|GT-S6802|GT-S6312|GT-S6310|GT-S5312|GT-S5310|GT-I9105|GT-I8510|GT-S6790N|SM-G7105|SM-N9005|GT-S5301|GT-I9295|GT-I9195|SM-C101|GT-S7392|GT-S7560|GT-B7610|GT-I5510|GT-S7582|GT-S7530E|GT-I8750",
+        "LG": "\\bLG\\b;|LG[- ]?(C800|C900|E400|E610|E900|E-900|F160|F180K|F180L|F180S|730|855|L160|LS740|LS840|LS970|LU6200|MS690|MS695|MS770|MS840|MS870|MS910|P500|P700|P705|VM696|AS680|AS695|AX840|C729|E970|GS505|272|C395|E739BK|E960|L55C|L75C|LS696|LS860|P769BK|P350|P500|P509|P870|UN272|US730|VS840|VS950|LN272|LN510|LS670|LS855|LW690|MN270|MN510|P509|P769|P930|UN200|UN270|UN510|UN610|US670|US740|US760|UX265|UX840|VN271|VN530|VS660|VS700|VS740|VS750|VS910|VS920|VS930|VX9200|VX11000|AX840A|LW770|P506|P925|P999|E612|D955|D802)",
+        "Sony": "SonyST|SonyLT|SonyEricsson|SonyEricssonLT15iv|LT18i|E10i|LT28h|LT26w|SonyEricssonMT27i|C5303|C6902|C6903|C6906|C6943|D2533",
+        "Asus": "Asus.*Galaxy|PadFone.*Mobile",
+        "Micromax": "Micromax.*\\b(A210|A92|A88|A72|A111|A110Q|A115|A116|A110|A90S|A26|A51|A35|A54|A25|A27|A89|A68|A65|A57|A90)\\b",
+        "Palm": "PalmSource|Palm",
+        "Vertu": "Vertu|Vertu.*Ltd|Vertu.*Ascent|Vertu.*Ayxta|Vertu.*Constellation(F|Quest)?|Vertu.*Monika|Vertu.*Signature",
+        "Pantech": "PANTECH|IM-A850S|IM-A840S|IM-A830L|IM-A830K|IM-A830S|IM-A820L|IM-A810K|IM-A810S|IM-A800S|IM-T100K|IM-A725L|IM-A780L|IM-A775C|IM-A770K|IM-A760S|IM-A750K|IM-A740S|IM-A730S|IM-A720L|IM-A710K|IM-A690L|IM-A690S|IM-A650S|IM-A630K|IM-A600S|VEGA PTL21|PT003|P8010|ADR910L|P6030|P6020|P9070|P4100|P9060|P5000|CDM8992|TXT8045|ADR8995|IS11PT|P2030|P6010|P8000|PT002|IS06|CDM8999|P9050|PT001|TXT8040|P2020|P9020|P2000|P7040|P7000|C790",
+        "Fly": "IQ230|IQ444|IQ450|IQ440|IQ442|IQ441|IQ245|IQ256|IQ236|IQ255|IQ235|IQ245|IQ275|IQ240|IQ285|IQ280|IQ270|IQ260|IQ250",
+        "iMobile": "i-mobile (IQ|i-STYLE|idea|ZAA|Hitz)",
+        "SimValley": "\\b(SP-80|XT-930|SX-340|XT-930|SX-310|SP-360|SP60|SPT-800|SP-120|SPT-800|SP-140|SPX-5|SPX-8|SP-100|SPX-8|SPX-12)\\b",
+        "Wolfgang": "AT-B24D|AT-AS50HD|AT-AS40W|AT-AS55HD|AT-AS45q2|AT-B26D|AT-AS50Q",
+        "Alcatel": "Alcatel",
+        "Nintendo": "Nintendo 3DS",
+        "Amoi": "Amoi",
+        "INQ": "INQ",
+        "GenericPhone": "Tapatalk|PDA;|SAGEM|\\bmmp\\b|pocket|\\bpsp\\b|symbian|Smartphone|smartfon|treo|up.browser|up.link|vodafone|\\bwap\\b|nokia|Series40|Series60|S60|SonyEricsson|N900|MAUI.*WAP.*Browser"
+    },
+    "tablets": {
+        "iPad": "iPad|iPad.*Mobile",
+        "NexusTablet": "Android.*Nexus[\\s]+(7|9|10)|^.*Android.*Nexus(?:(?!Mobile).)*$",
+        "SamsungTablet": "SAMSUNG.*Tablet|Galaxy.*Tab|SC-01C|GT-P1000|GT-P1003|GT-P1010|GT-P3105|GT-P6210|GT-P6800|GT-P6810|GT-P7100|GT-P7300|GT-P7310|GT-P7500|GT-P7510|SCH-I800|SCH-I815|SCH-I905|SGH-I957|SGH-I987|SGH-T849|SGH-T859|SGH-T869|SPH-P100|GT-P3100|GT-P3108|GT-P3110|GT-P5100|GT-P5110|GT-P6200|GT-P7320|GT-P7511|GT-N8000|GT-P8510|SGH-I497|SPH-P500|SGH-T779|SCH-I705|SCH-I915|GT-N8013|GT-P3113|GT-P5113|GT-P8110|GT-N8010|GT-N8005|GT-N8020|GT-P1013|GT-P6201|GT-P7501|GT-N5100|GT-N5105|GT-N5110|SHV-E140K|SHV-E140L|SHV-E140S|SHV-E150S|SHV-E230K|SHV-E230L|SHV-E230S|SHW-M180K|SHW-M180L|SHW-M180S|SHW-M180W|SHW-M300W|SHW-M305W|SHW-M380K|SHW-M380S|SHW-M380W|SHW-M430W|SHW-M480K|SHW-M480S|SHW-M480W|SHW-M485W|SHW-M486W|SHW-M500W|GT-I9228|SCH-P739|SCH-I925|GT-I9200|GT-I9205|GT-P5200|GT-P5210|GT-P5210X|SM-T311|SM-T310|SM-T310X|SM-T210|SM-T210R|SM-T211|SM-P600|SM-P601|SM-P605|SM-P900|SM-P901|SM-T217|SM-T217A|SM-T217S|SM-P6000|SM-T3100|SGH-I467|XE500|SM-T110|GT-P5220|GT-I9200X|GT-N5110X|GT-N5120|SM-P905|SM-T111|SM-T2105|SM-T315|SM-T320|SM-T320X|SM-T321|SM-T520|SM-T525|SM-T530NU|SM-T230NU|SM-T330NU|SM-T900|XE500T1C|SM-P605V|SM-P905V|SM-P600X|SM-P900X|SM-T210X|SM-T230|SM-T230X|SM-T325|GT-P7503|SM-T531|SM-T330|SM-T530|SM-T705C|SM-T535|SM-T331|SM-T800|SM-T700|SM-T537|SM-T807|SM-P907A|SM-T337A|SM-T707A|SM-T807A|SM-T237P|SM-T807P|SM-P607T|SM-T217T|SM-T337T",
+        "Kindle": "Kindle|Silk.*Accelerated|Android.*\\b(KFOT|KFTT|KFJWI|KFJWA|KFOTE|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|WFJWAE|KFSAWA|KFSAWI|KFASWI)\\b",
+        "SurfaceTablet": "Windows NT [0-9.]+; ARM;.*(Tablet|ARMBJS)",
+        "HPTablet": "HP Slate (7|8|10)|HP ElitePad 900|hp-tablet|EliteBook.*Touch|HP 8|Slate 21|HP SlateBook 10",
+        "AsusTablet": "^.*PadFone((?!Mobile).)*$|Transformer|TF101|TF101G|TF300T|TF300TG|TF300TL|TF700T|TF700KL|TF701T|TF810C|ME171|ME301T|ME302C|ME371MG|ME370T|ME372MG|ME172V|ME173X|ME400C|Slider SL101|\\bK00F\\b|\\bK00C\\b|\\bK00E\\b|\\bK00L\\b|TX201LA|ME176C|ME102A|\\bM80TA\\b|ME372CL|ME560CG|ME372CG",
+        "BlackBerryTablet": "PlayBook|RIM Tablet",
+        "HTCtablet": "HTC_Flyer_P512|HTC Flyer|HTC Jetstream|HTC-P715a|HTC EVO View 4G|PG41200|PG09410",
+        "MotorolaTablet": "xoom|sholest|MZ615|MZ605|MZ505|MZ601|MZ602|MZ603|MZ604|MZ606|MZ607|MZ608|MZ609|MZ615|MZ616|MZ617",
+        "NookTablet": "Android.*Nook|NookColor|nook browser|BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2",
+        "AcerTablet": "Android.*; \\b(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700|A701|W500|W500P|W501|W501P|W510|W511|W700|G100|G100W|B1-A71|B1-710|B1-711|A1-810|A1-811|A1-830)\\b|W3-810|\\bA3-A10\\b",
+        "ToshibaTablet": "Android.*(AT100|AT105|AT200|AT205|AT270|AT275|AT300|AT305|AT1S5|AT500|AT570|AT700|AT830)|TOSHIBA.*FOLIO",
+        "LGTablet": "\\bL-06C|LG-V909|LG-V900|LG-V700|LG-V510|LG-V500|LG-V410|LG-V400|LG-VK810\\b",
+        "FujitsuTablet": "Android.*\\b(F-01D|F-02F|F-05E|F-10D|M532|Q572)\\b",
+        "PrestigioTablet": "PMP3170B|PMP3270B|PMP3470B|PMP7170B|PMP3370B|PMP3570C|PMP5870C|PMP3670B|PMP5570C|PMP5770D|PMP3970B|PMP3870C|PMP5580C|PMP5880D|PMP5780D|PMP5588C|PMP7280C|PMP7280C3G|PMP7280|PMP7880D|PMP5597D|PMP5597|PMP7100D|PER3464|PER3274|PER3574|PER3884|PER5274|PER5474|PMP5097CPRO|PMP5097|PMP7380D|PMP5297C|PMP5297C_QUAD",
+        "LenovoTablet": "Idea(Tab|Pad)( A1|A10| K1|)|ThinkPad([ ]+)?Tablet|Lenovo.*(S2109|S2110|S5000|S6000|K3011|A3000|A3500|A1000|A2107|A2109|A1107|A5500|A7600|B6000|B8000|B8080)(-|)(FL|F|HV|H|)",
+        "DellTablet": "Venue 11|Venue 8|Venue 7|Dell Streak 10|Dell Streak 7",
+        "YarvikTablet": "Android.*\\b(TAB210|TAB211|TAB224|TAB250|TAB260|TAB264|TAB310|TAB360|TAB364|TAB410|TAB411|TAB420|TAB424|TAB450|TAB460|TAB461|TAB464|TAB465|TAB467|TAB468|TAB07-100|TAB07-101|TAB07-150|TAB07-151|TAB07-152|TAB07-200|TAB07-201-3G|TAB07-210|TAB07-211|TAB07-212|TAB07-214|TAB07-220|TAB07-400|TAB07-485|TAB08-150|TAB08-200|TAB08-201-3G|TAB08-201-30|TAB09-100|TAB09-211|TAB09-410|TAB10-150|TAB10-201|TAB10-211|TAB10-400|TAB10-410|TAB13-201|TAB274EUK|TAB275EUK|TAB374EUK|TAB462EUK|TAB474EUK|TAB9-200)\\b",
+        "MedionTablet": "Android.*\\bOYO\\b|LIFE.*(P9212|P9514|P9516|S9512)|LIFETAB",
+        "ArnovaTablet": "AN10G2|AN7bG3|AN7fG3|AN8G3|AN8cG3|AN7G3|AN9G3|AN7dG3|AN7dG3ST|AN7dG3ChildPad|AN10bG3|AN10bG3DT|AN9G2",
+        "IntensoTablet": "INM8002KP|INM1010FP|INM805ND|Intenso Tab|TAB1004",
+        "IRUTablet": "M702pro",
+        "MegafonTablet": "MegaFon V9|\\bZTE V9\\b|Android.*\\bMT7A\\b",
+        "EbodaTablet": "E-Boda (Supreme|Impresspeed|Izzycomm|Essential)",
+        "AllViewTablet": "Allview.*(Viva|Alldro|City|Speed|All TV|Frenzy|Quasar|Shine|TX1|AX1|AX2)",
+        "ArchosTablet": "\\b(101G9|80G9|A101IT)\\b|Qilive 97R|Archos5|\\bARCHOS (70|79|80|90|97|101|FAMILYPAD|)(b|)(G10| Cobalt| TITANIUM(HD|)| Xenon| Neon|XSK| 2| XS 2| PLATINUM| CARBON|GAMEPAD)\\b",
+        "AinolTablet": "NOVO7|NOVO8|NOVO10|Novo7Aurora|Novo7Basic|NOVO7PALADIN|novo9-Spark",
+        "SonyTablet": "Sony.*Tablet|Xperia Tablet|Sony Tablet S|SO-03E|SGPT12|SGPT13|SGPT114|SGPT121|SGPT122|SGPT123|SGPT111|SGPT112|SGPT113|SGPT131|SGPT132|SGPT133|SGPT211|SGPT212|SGPT213|SGP311|SGP312|SGP321|EBRD1101|EBRD1102|EBRD1201|SGP351|SGP341|SGP511|SGP512|SGP521|SGP541|SGP551",
+        "PhilipsTablet": "\\b(PI2010|PI3000|PI3100|PI3105|PI3110|PI3205|PI3210|PI3900|PI4010|PI7000|PI7100)\\b",
+        "CubeTablet": "Android.*(K8GT|U9GT|U10GT|U16GT|U17GT|U18GT|U19GT|U20GT|U23GT|U30GT)|CUBE U8GT",
+        "CobyTablet": "MID1042|MID1045|MID1125|MID1126|MID7012|MID7014|MID7015|MID7034|MID7035|MID7036|MID7042|MID7048|MID7127|MID8042|MID8048|MID8127|MID9042|MID9740|MID9742|MID7022|MID7010",
+        "MIDTablet": "M9701|M9000|M9100|M806|M1052|M806|T703|MID701|MID713|MID710|MID727|MID760|MID830|MID728|MID933|MID125|MID810|MID732|MID120|MID930|MID800|MID731|MID900|MID100|MID820|MID735|MID980|MID130|MID833|MID737|MID960|MID135|MID860|MID736|MID140|MID930|MID835|MID733",
+        "MSITablet": "MSI \\b(Primo 73K|Primo 73L|Primo 81L|Primo 77|Primo 93|Primo 75|Primo 76|Primo 73|Primo 81|Primo 91|Primo 90|Enjoy 71|Enjoy 7|Enjoy 10)\\b",
+        "SMiTTablet": "Android.*(\\bMID\\b|MID-560|MTV-T1200|MTV-PND531|MTV-P1101|MTV-PND530)",
+        "RockChipTablet": "Android.*(RK2818|RK2808A|RK2918|RK3066)|RK2738|RK2808A",
+        "FlyTablet": "IQ310|Fly Vision",
+        "bqTablet": "bq.*(Elcano|Curie|Edison|Maxwell|Kepler|Pascal|Tesla|Hypatia|Platon|Newton|Livingstone|Cervantes|Avant)|Maxwell.*Lite|Maxwell.*Plus",
+        "HuaweiTablet": "MediaPad|MediaPad 7 Youth|IDEOS S7|S7-201c|S7-202u|S7-101|S7-103|S7-104|S7-105|S7-106|S7-201|S7-Slim",
+        "NecTablet": "\\bN-06D|\\bN-08D",
+        "PantechTablet": "Pantech.*P4100",
+        "BronchoTablet": "Broncho.*(N701|N708|N802|a710)",
+        "VersusTablet": "TOUCHPAD.*[78910]|\\bTOUCHTAB\\b",
+        "ZyncTablet": "z1000|Z99 2G|z99|z930|z999|z990|z909|Z919|z900",
+        "PositivoTablet": "TB07STA|TB10STA|TB07FTA|TB10FTA",
+        "NabiTablet": "Android.*\\bNabi",
+        "KoboTablet": "Kobo Touch|\\bK080\\b|\\bVox\\b Build|\\bArc\\b Build",
+        "DanewTablet": "DSlide.*\\b(700|701R|702|703R|704|802|970|971|972|973|974|1010|1012)\\b",
+        "TexetTablet": "NaviPad|TB-772A|TM-7045|TM-7055|TM-9750|TM-7016|TM-7024|TM-7026|TM-7041|TM-7043|TM-7047|TM-8041|TM-9741|TM-9747|TM-9748|TM-9751|TM-7022|TM-7021|TM-7020|TM-7011|TM-7010|TM-7023|TM-7025|TM-7037W|TM-7038W|TM-7027W|TM-9720|TM-9725|TM-9737W|TM-1020|TM-9738W|TM-9740|TM-9743W|TB-807A|TB-771A|TB-727A|TB-725A|TB-719A|TB-823A|TB-805A|TB-723A|TB-715A|TB-707A|TB-705A|TB-709A|TB-711A|TB-890HD|TB-880HD|TB-790HD|TB-780HD|TB-770HD|TB-721HD|TB-710HD|TB-434HD|TB-860HD|TB-840HD|TB-760HD|TB-750HD|TB-740HD|TB-730HD|TB-722HD|TB-720HD|TB-700HD|TB-500HD|TB-470HD|TB-431HD|TB-430HD|TB-506|TB-504|TB-446|TB-436|TB-416|TB-146SE|TB-126SE",
+        "PlaystationTablet": "Playstation.*(Portable|Vita)",
+        "TrekstorTablet": "ST10416-1|VT10416-1|ST70408-1|ST702xx-1|ST702xx-2|ST80208|ST97216|ST70104-2|VT10416-2|ST10216-2A|SurfTab",
+        "PyleAudioTablet": "\\b(PTBL10CEU|PTBL10C|PTBL72BC|PTBL72BCEU|PTBL7CEU|PTBL7C|PTBL92BC|PTBL92BCEU|PTBL9CEU|PTBL9CUK|PTBL9C)\\b",
+        "AdvanTablet": "Android.* \\b(E3A|T3X|T5C|T5B|T3E|T3C|T3B|T1J|T1F|T2A|T1H|T1i|E1C|T1-E|T5-A|T4|E1-B|T2Ci|T1-B|T1-D|O1-A|E1-A|T1-A|T3A|T4i)\\b ",
+        "DanyTechTablet": "Genius Tab G3|Genius Tab S2|Genius Tab Q3|Genius Tab G4|Genius Tab Q4|Genius Tab G-II|Genius TAB GII|Genius TAB GIII|Genius Tab S1",
+        "GalapadTablet": "Android.*\\bG1\\b",
+        "MicromaxTablet": "Funbook|Micromax.*\\b(P250|P560|P360|P362|P600|P300|P350|P500|P275)\\b",
+        "KarbonnTablet": "Android.*\\b(A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2)\\b",
+        "AllFineTablet": "Fine7 Genius|Fine7 Shine|Fine7 Air|Fine8 Style|Fine9 More|Fine10 Joy|Fine11 Wide",
+        "PROSCANTablet": "\\b(PEM63|PLT1023G|PLT1041|PLT1044|PLT1044G|PLT1091|PLT4311|PLT4311PL|PLT4315|PLT7030|PLT7033|PLT7033D|PLT7035|PLT7035D|PLT7044K|PLT7045K|PLT7045KB|PLT7071KG|PLT7072|PLT7223G|PLT7225G|PLT7777G|PLT7810K|PLT7849G|PLT7851G|PLT7852G|PLT8015|PLT8031|PLT8034|PLT8036|PLT8080K|PLT8082|PLT8088|PLT8223G|PLT8234G|PLT8235G|PLT8816K|PLT9011|PLT9045K|PLT9233G|PLT9735|PLT9760G|PLT9770G)\\b",
+        "YONESTablet": "BQ1078|BC1003|BC1077|RK9702|BC9730|BC9001|IT9001|BC7008|BC7010|BC708|BC728|BC7012|BC7030|BC7027|BC7026",
+        "ChangJiaTablet": "TPC7102|TPC7103|TPC7105|TPC7106|TPC7107|TPC7201|TPC7203|TPC7205|TPC7210|TPC7708|TPC7709|TPC7712|TPC7110|TPC8101|TPC8103|TPC8105|TPC8106|TPC8203|TPC8205|TPC8503|TPC9106|TPC9701|TPC97101|TPC97103|TPC97105|TPC97106|TPC97111|TPC97113|TPC97203|TPC97603|TPC97809|TPC97205|TPC10101|TPC10103|TPC10106|TPC10111|TPC10203|TPC10205|TPC10503",
+        "GUTablet": "TX-A1301|TX-M9002|Q702|kf026",
+        "PointOfViewTablet": "TAB-P506|TAB-navi-7-3G-M|TAB-P517|TAB-P-527|TAB-P701|TAB-P703|TAB-P721|TAB-P731N|TAB-P741|TAB-P825|TAB-P905|TAB-P925|TAB-PR945|TAB-PL1015|TAB-P1025|TAB-PI1045|TAB-P1325|TAB-PROTAB[0-9]+|TAB-PROTAB25|TAB-PROTAB26|TAB-PROTAB27|TAB-PROTAB26XL|TAB-PROTAB2-IPS9|TAB-PROTAB30-IPS9|TAB-PROTAB25XXL|TAB-PROTAB26-IPS10|TAB-PROTAB30-IPS10",
+        "OvermaxTablet": "OV-(SteelCore|NewBase|Basecore|Baseone|Exellen|Quattor|EduTab|Solution|ACTION|BasicTab|TeddyTab|MagicTab|Stream|TB-08|TB-09)",
+        "HCLTablet": "HCL.*Tablet|Connect-3G-2.0|Connect-2G-2.0|ME Tablet U1|ME Tablet U2|ME Tablet G1|ME Tablet X1|ME Tablet Y2|ME Tablet Sync",
+        "DPSTablet": "DPS Dream 9|DPS Dual 7",
+        "VistureTablet": "V97 HD|i75 3G|Visture V4( HD)?|Visture V5( HD)?|Visture V10",
+        "CrestaTablet": "CTP(-)?810|CTP(-)?818|CTP(-)?828|CTP(-)?838|CTP(-)?888|CTP(-)?978|CTP(-)?980|CTP(-)?987|CTP(-)?988|CTP(-)?989",
+        "MediatekTablet": "\\bMT8125|MT8389|MT8135|MT8377\\b",
+        "ConcordeTablet": "Concorde([ ]+)?Tab|ConCorde ReadMan",
+        "GoCleverTablet": "GOCLEVER TAB|A7GOCLEVER|M1042|M7841|M742|R1042BK|R1041|TAB A975|TAB A7842|TAB A741|TAB A741L|TAB M723G|TAB M721|TAB A1021|TAB I921|TAB R721|TAB I720|TAB T76|TAB R70|TAB R76.2|TAB R106|TAB R83.2|TAB M813G|TAB I721|GCTA722|TAB I70|TAB I71|TAB S73|TAB R73|TAB R74|TAB R93|TAB R75|TAB R76.1|TAB A73|TAB A93|TAB A93.2|TAB T72|TAB R83|TAB R974|TAB R973|TAB A101|TAB A103|TAB A104|TAB A104.2|R105BK|M713G|A972BK|TAB A971|TAB R974.2|TAB R104|TAB R83.3|TAB A1042",
+        "ModecomTablet": "FreeTAB 9000|FreeTAB 7.4|FreeTAB 7004|FreeTAB 7800|FreeTAB 2096|FreeTAB 7.5|FreeTAB 1014|FreeTAB 1001 |FreeTAB 8001|FreeTAB 9706|FreeTAB 9702|FreeTAB 7003|FreeTAB 7002|FreeTAB 1002|FreeTAB 7801|FreeTAB 1331|FreeTAB 1004|FreeTAB 8002|FreeTAB 8014|FreeTAB 9704|FreeTAB 1003",
+        "VoninoTablet": "\\b(Argus[ _]?S|Diamond[ _]?79HD|Emerald[ _]?78E|Luna[ _]?70C|Onyx[ _]?S|Onyx[ _]?Z|Orin[ _]?HD|Orin[ _]?S|Otis[ _]?S|SpeedStar[ _]?S|Magnet[ _]?M9|Primus[ _]?94[ _]?3G|Primus[ _]?94HD|Primus[ _]?QS|Android.*\\bQ8\\b|Sirius[ _]?EVO[ _]?QS|Sirius[ _]?QS|Spirit[ _]?S)\\b",
+        "ECSTablet": "V07OT2|TM105A|S10OT1|TR10CS1",
+        "StorexTablet": "eZee[_']?(Tab|Go)[0-9]+|TabLC7|Looney Tunes Tab",
+        "VodafoneTablet": "SmartTab([ ]+)?[0-9]+|SmartTabII10|SmartTabII7",
+        "EssentielBTablet": "Smart[ ']?TAB[ ]+?[0-9]+|Family[ ']?TAB2",
+        "RossMoorTablet": "RM-790|RM-997|RMD-878G|RMD-974R|RMT-705A|RMT-701|RME-601|RMT-501|RMT-711",
+        "iMobileTablet": "i-mobile i-note",
+        "TolinoTablet": "tolino tab [0-9.]+|tolino shine",
+        "AudioSonicTablet": "\\bC-22Q|T7-QC|T-17B|T-17P\\b",
+        "AMPETablet": "Android.* A78 ",
+        "SkkTablet": "Android.* (SKYPAD|PHOENIX|CYCLOPS)",
+        "TecnoTablet": "TECNO P9",
+        "JXDTablet": "Android.*\\b(F3000|A3300|JXD5000|JXD3000|JXD2000|JXD300B|JXD300|S5800|S7800|S602b|S5110b|S7300|S5300|S602|S603|S5100|S5110|S601|S7100a|P3000F|P3000s|P101|P200s|P1000m|P200m|P9100|P1000s|S6600b|S908|P1000|P300|S18|S6600|S9100)\\b",
+        "iJoyTablet": "Tablet (Spirit 7|Essentia|Galatea|Fusion|Onix 7|Landa|Titan|Scooby|Deox|Stella|Themis|Argon|Unique 7|Sygnus|Hexen|Finity 7|Cream|Cream X2|Jade|Neon 7|Neron 7|Kandy|Scape|Saphyr 7|Rebel|Biox|Rebel|Rebel 8GB|Myst|Draco 7|Myst|Tab7-004|Myst|Tadeo Jones|Tablet Boing|Arrow|Draco Dual Cam|Aurix|Mint|Amity|Revolution|Finity 9|Neon 9|T9w|Amity 4GB Dual Cam|Stone 4GB|Stone 8GB|Andromeda|Silken|X2|Andromeda II|Halley|Flame|Saphyr 9,7|Touch 8|Planet|Triton|Unique 10|Hexen 10|Memphis 4GB|Memphis 8GB|Onix 10)",
+        "FX2Tablet": "FX2 PAD7|FX2 PAD10",
+        "XoroTablet": "KidsPAD 701|PAD[ ]?712|PAD[ ]?714|PAD[ ]?716|PAD[ ]?717|PAD[ ]?718|PAD[ ]?720|PAD[ ]?721|PAD[ ]?722|PAD[ ]?790|PAD[ ]?792|PAD[ ]?900|PAD[ ]?9715D|PAD[ ]?9716DR|PAD[ ]?9718DR|PAD[ ]?9719QR|PAD[ ]?9720QR|TelePAD1030|Telepad1032|TelePAD730|TelePAD731|TelePAD732|TelePAD735Q|TelePAD830|TelePAD9730|TelePAD795|MegaPAD 1331|MegaPAD 1851|MegaPAD 2151",
+        "ViewsonicTablet": "ViewPad 10pi|ViewPad 10e|ViewPad 10s|ViewPad E72|ViewPad7|ViewPad E100|ViewPad 7e|ViewSonic VB733|VB100a",
+        "OdysTablet": "LOOX|XENO10|ODYS[ -](Space|EVO|Xpress|NOON)|\\bXELIO\\b|Xelio10Pro|XELIO7PHONETAB|XELIO10EXTREME|XELIOPT2|NEO_QUAD10",
+        "CaptivaTablet": "CAPTIVA PAD",
+        "IconbitTablet": "NetTAB|NT-3702|NT-3702S|NT-3702S|NT-3603P|NT-3603P|NT-0704S|NT-0704S|NT-3805C|NT-3805C|NT-0806C|NT-0806C|NT-0909T|NT-0909T|NT-0907S|NT-0907S|NT-0902S|NT-0902S",
+        "TeclastTablet": "T98 4G|\\bP80\\b|\\bX90HD\\b|X98 Air|X98 Air 3G|\\bX89\\b|P80 3G|\\bX80h\\b|P98 Air|\\bX89HD\\b|P98 3G|\\bP90HD\\b|P89 3G|X98 3G|\\bP70h\\b|P79HD 3G|G18d 3G|\\bP79HD\\b|\\bP89s\\b|\\bA88\\b|\\bP10HD\\b|\\bP19HD\\b|G18 3G|\\bP78HD\\b|\\bA78\\b|\\bP75\\b|G17s 3G|G17h 3G|\\bP85t\\b|\\bP90\\b|\\bP11\\b|\\bP98t\\b|\\bP98HD\\b|\\bG18d\\b|\\bP85s\\b|\\bP11HD\\b|\\bP88s\\b|\\bA80HD\\b|\\bA80se\\b|\\bA10h\\b|\\bP89\\b|\\bP78s\\b|\\bG18\\b|\\bP85\\b|\\bA70h\\b|\\bA70\\b|\\bG17\\b|\\bP18\\b|\\bA80s\\b|\\bA11s\\b|\\bP88HD\\b|\\bA80h\\b|\\bP76s\\b|\\bP76h\\b|\\bP98\\b|\\bA10HD\\b|\\bP78\\b|\\bP88\\b|\\bA11\\b|\\bA10t\\b|\\bP76a\\b|\\bP76t\\b|\\bP76e\\b|\\bP85HD\\b|\\bP85a\\b|\\bP86\\b|\\bP75HD\\b|\\bP76v\\b|\\bA12\\b|\\bP75a\\b|\\bA15\\b|\\bP76Ti\\b|\\bP81HD\\b|\\bA10\\b|\\bT760VE\\b|\\bT720HD\\b|\\bP76\\b|\\bP73\\b|\\bP71\\b|\\bP72\\b|\\bT720SE\\b|\\bC520Ti\\b|\\bT760\\b|\\bT720VE\\b|T720-3GE|T720-WiFi",
+        "JaytechTablet": "TPC-PA762",
+        "BlaupunktTablet": "Endeavour 800NG|Endeavour 1010",
+        "DigmaTablet": "\\b(iDx10|iDx9|iDx8|iDx7|iDxD7|iDxD8|iDsQ8|iDsQ7|iDsQ8|iDsD10|iDnD7|3TS804H|iDsQ11|iDj7|iDs10)\\b",
+        "EvolioTablet": "ARIA_Mini_wifi|Aria[ _]Mini|Evolio X10|Evolio X7|Evolio X8|\\bEvotab\\b|\\bNeura\\b",
+        "LavaTablet": "QPAD E704|\\bIvoryS\\b|E-TAB IVORY",
+        "CelkonTablet": "CT695|CT888|CT[\\s]?910|CT7 Tab|CT9 Tab|CT3 Tab|CT2 Tab|CT1 Tab|C820|C720|\\bCT-1\\b",
+        "MiTablet": "\\bMI PAD\\b|\\bHM NOTE 1W\\b",
+        "NibiruTablet": "Nibiru M1|Nibiru Jupiter One",
+        "NexoTablet": "NEXO NOVA|NEXO 10|NEXO AVIO|NEXO FREE|NEXO GO|NEXO EVO|NEXO 3G|NEXO SMART|NEXO KIDDO|NEXO MOBI",
+        "UbislateTablet": "UbiSlate[\\s]?7C",
+        "PocketBookTablet": "Pocketbook",
+        "Hudl": "Hudl HT7S3",
+        "TelstraTablet": "T-Hub2",
+        "GenericTablet": "Android.*\\b97D\\b|Tablet(?!.*PC)|BNTV250A|MID-WCDMA|LogicPD Zoom2|\\bA7EB\\b|CatNova8|A1_07|CT704|CT1002|\\bM721\\b|rk30sdk|\\bEVOTAB\\b|M758A|ET904|ALUMIUM10|Smartfren Tab|Endeavour 1010|Tablet-PC-4|Tagi Tab|\\bM6pro\\b|CT1020W|arc 10HD|\\bJolla\\b"
+    },
+    "oss": {
+        "AndroidOS": "Android",
+        "BlackBerryOS": "blackberry|\\bBB10\\b|rim tablet os",
+        "PalmOS": "PalmOS|avantgo|blazer|elaine|hiptop|palm|plucker|xiino",
+        "SymbianOS": "Symbian|SymbOS|Series60|Series40|SYB-[0-9]+|\\bS60\\b",
+        "WindowsMobileOS": "Windows CE.*(PPC|Smartphone|Mobile|[0-9]{3}x[0-9]{3})|Window Mobile|Windows Phone [0-9.]+|WCE;",
+        "WindowsPhoneOS": "Windows Phone 8.0|Windows Phone OS|XBLWP7|ZuneWP7|Windows NT 6.[23]; ARM;",
+        "iOS": "\\biPhone.*Mobile|\\biPod|\\biPad",
+        "MeeGoOS": "MeeGo",
+        "MaemoOS": "Maemo",
+        "JavaOS": "J2ME\/|\\bMIDP\\b|\\bCLDC\\b",
+        "webOS": "webOS|hpwOS",
+        "badaOS": "\\bBada\\b",
+        "BREWOS": "BREW"
+    },
+    "uas": {
+        "Chrome": "\\bCrMo\\b|CriOS|Android.*Chrome\/[.0-9]* (Mobile)?",
+        "Dolfin": "\\bDolfin\\b",
+        "Opera": "Opera.*Mini|Opera.*Mobi|Android.*Opera|Mobile.*OPR\/[0-9.]+|Coast\/[0-9.]+",
+        "Skyfire": "Skyfire",
+        "IE": "IEMobile|MSIEMobile",
+        "Firefox": "fennec|firefox.*maemo|(Mobile|Tablet).*Firefox|Firefox.*Mobile",
+        "Bolt": "bolt",
+        "TeaShark": "teashark",
+        "Blazer": "Blazer",
+        "Safari": "Version.*Mobile.*Safari|Safari.*Mobile|MobileSafari",
+        "Tizen": "Tizen",
+        "UCBrowser": "UC.*Browser|UCWEB",
+        "baiduboxapp": "baiduboxapp",
+        "baidubrowser": "baidubrowser",
+        "DiigoBrowser": "DiigoBrowser",
+        "Puffin": "Puffin",
+        "Mercury": "\\bMercury\\b",
+        "ObigoBrowser": "Obigo",
+        "NetFront": "NF-Browser",
+        "GenericBrowser": "NokiaBrowser|OviBrowser|OneBrowser|TwonkyBeamBrowser|SEMC.*Browser|FlyFlow|Minimo|NetFront|Novarra-Vision|MQQBrowser|MicroMessenger"
+    },
+    "props": {
+        "Mobile": "Mobile\/[VER]",
+        "Build": "Build\/[VER]",
+        "Version": "Version\/[VER]",
+        "VendorID": "VendorID\/[VER]",
+        "iPad": "iPad.*CPU[a-z ]+[VER]",
+        "iPhone": "iPhone.*CPU[a-z ]+[VER]",
+        "iPod": "iPod.*CPU[a-z ]+[VER]",
+        "Kindle": "Kindle\/[VER]",
+        "Chrome": [
+            "Chrome\/[VER]",
+            "CriOS\/[VER]",
+            "CrMo\/[VER]"
+        ],
+        "Coast": [
+            "Coast\/[VER]"
+        ],
+        "Dolfin": "Dolfin\/[VER]",
+        "Firefox": "Firefox\/[VER]",
+        "Fennec": "Fennec\/[VER]",
+        "IE": [
+            "IEMobile\/[VER];",
+            "IEMobile [VER]",
+            "MSIE [VER];"
+        ],
+        "NetFront": "NetFront\/[VER]",
+        "NokiaBrowser": "NokiaBrowser\/[VER]",
+        "Opera": [
+            " OPR\/[VER]",
+            "Opera Mini\/[VER]",
+            "Version\/[VER]"
+        ],
+        "Opera Mini": "Opera Mini\/[VER]",
+        "Opera Mobi": "Version\/[VER]",
+        "UC Browser": "UC Browser[VER]",
+        "MQQBrowser": "MQQBrowser\/[VER]",
+        "MicroMessenger": "MicroMessenger\/[VER]",
+        "baiduboxapp": "baiduboxapp\/[VER]",
+        "baidubrowser": "baidubrowser\/[VER]",
+        "Iron": "Iron\/[VER]",
+        "Safari": [
+            "Version\/[VER]",
+            "Safari\/[VER]"
+        ],
+        "Skyfire": "Skyfire\/[VER]",
+        "Tizen": "Tizen\/[VER]",
+        "Webkit": "webkit[ \/][VER]",
+        "Gecko": "Gecko\/[VER]",
+        "Trident": "Trident\/[VER]",
+        "Presto": "Presto\/[VER]",
+        "iOS": " \\bOS\\b [VER] ",
+        "Android": "Android [VER]",
+        "BlackBerry": [
+            "BlackBerry[\\w]+\/[VER]",
+            "BlackBerry.*Version\/[VER]",
+            "Version\/[VER]"
+        ],
+        "BREW": "BREW [VER]",
+        "Java": "Java\/[VER]",
+        "Windows Phone OS": [
+            "Windows Phone OS [VER]",
+            "Windows Phone [VER]"
+        ],
+        "Windows Phone": "Windows Phone [VER]",
+        "Windows CE": "Windows CE\/[VER]",
+        "Windows NT": "Windows NT [VER]",
+        "Symbian": [
+            "SymbianOS\/[VER]",
+            "Symbian\/[VER]"
+        ],
+        "webOS": [
+            "webOS\/[VER]",
+            "hpwOS\/[VER];"
+        ]
+    },
+    "utils": {
+        "DesktopMode": "WPDesktop",
+        "TV": "SonyDTV|HbbTV",
+        "WebKit": "(webkit)[ \/]([\\w.]+)",
+        "Bot": "Googlebot|YandexBot|bingbot|ia_archiver|AhrefsBot|Ezooms|GSLFbot|WBSearchBot|Twitterbot|TweetmemeBot|Twikle|PaperLiBot|Wotbox|UnwindFetchor|facebookexternalhit",
+        "MobileBot": "Googlebot-Mobile|YahooSeeker\/M1A1-R2D2",
+        "Console": "\\b(Nintendo|Nintendo WiiU|Nintendo 3DS|PLAYSTATION|Xbox)\\b",
+        "Watch": "SM-V700"
+    }
+};
+
+    // following patterns come from http://detectmobilebrowsers.com/
+    var detectMobileBrowsers = {
+        fullPattern: /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i,
+        shortPattern: /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i
+    };
+
+    var hasOwnProp = Object.prototype.hasOwnProperty,
+        isArray,
+        FALLBACK_PHONE = 'UnknownPhone',
+        FALLBACK_TABLET = 'UnknownTablet',
+        FALLBACK_MOBILE = 'UnknownMobile';
+
+    isArray = ('isArray' in Array) ?
+        Array.isArray : function (value) { return Object.prototype.toString.call(value) === '[object Array]'; };
+
+    (function init() {
+        var key, values, value, i, len, verPos;
+        for (key in mobileDetectRules.props) {
+            if (hasOwnProp.call(mobileDetectRules.props, key)) {
+                values = mobileDetectRules.props[key];
+                if (!isArray(values)) {
+                    values = [values];
+                }
+                len = values.length;
+                for (i = 0; i < len; ++i) {
+                    value = values[i];
+                    verPos = value.indexOf('[VER]');
+                    if (verPos >= 0) {
+                        value = value.substring(0, verPos) + '([\\w._\\+]+)' + value.substring(verPos + 5);
+                    }
+                    values[i] = new RegExp(value, 'i');
+                }
+                mobileDetectRules.props[key] = values;
+            }
+        }
+        convertPropsToRegExp(mobileDetectRules.oss);
+        convertPropsToRegExp(mobileDetectRules.phones);
+        convertPropsToRegExp(mobileDetectRules.tablets);
+        convertPropsToRegExp(mobileDetectRules.uas);
+        convertPropsToRegExp(mobileDetectRules.utils);
+
+        // copy some patterns to oss0 which are tested first (see issue#15)
+        mobileDetectRules.oss0 = {
+            WindowsPhoneOS: mobileDetectRules.oss.WindowsPhoneOS,
+            WindowsMobileOS: mobileDetectRules.oss.WindowsMobileOS
+        };
+    }());
+
+    function convertPropsToRegExp(object) {
+        for (var key in object) {
+            if (hasOwnProp.call(object, key)) {
+                object[key] = new RegExp(object[key], 'i');
+            }
+        }
+    }
+
+    /**
+     * Test userAgent string against a set of rules and find the matched key.
+     * @param {Object} rules (key is String, value is RegExp)
+     * @param {String} userAgent the navigator.userAgent (or HTTP-Header 'User-Agent').
+     * @returns {String|null} the matched key if found, otherwise <tt>null</tt>
+     * @private
+     */
+    function findMatch(rules, userAgent) {
+        for (var key in rules) {
+            if (hasOwnProp.call(rules, key)) {
+                if (rules[key].test(userAgent)) {
+                    return key;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Check the version of the given property in the User-Agent.
+     *
+     * @param {String} propertyName
+     * @param {String} userAgent
+     * @return {String} version or <tt>null</tt> if version not found
+     * @private
+     */
+    function getVersionStr(propertyName, userAgent) {
+        var props = mobileDetectRules.props, patterns, i, len, match;
+        if (hasOwnProp.call(props, propertyName)) {
+            patterns = props[propertyName];
+            len = patterns.length;
+            for (i = 0; i < len; ++i) {
+                match = patterns[i].exec(userAgent);
+                if (match !== null) {
+                    return match[1];
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Check the version of the given property in the User-Agent.
+     * Will return a float number. (eg. 2_0 will return 2.0, 4.3.1 will return 4.31)
+     *
+     * @param {String} propertyName
+     * @param {String} userAgent
+     * @return {Number} version or <tt>NaN</tt> if version not found
+     * @private
+     */
+    function getVersion(propertyName, userAgent) {
+        var version = getVersionStr(propertyName, userAgent);
+        return version ? prepareVersionNo(version) : NaN;
+    }
+
+    /**
+     * Prepare the version number.
+     *
+     * @param {String} version
+     * @return {Number} the version number as a floating number
+     * @private
+     */
+    function prepareVersionNo(version) {
+        var numbers;
+
+        numbers = version.split(/[a-z._ \/\-]/i);
+        if (numbers.length === 1) {
+            version = numbers[0];
+        }
+        if (numbers.length > 1) {
+            version = numbers[0] + '.';
+            numbers.shift();
+            version += numbers.join('');
+        }
+        return Number(version);
+    }
+
+    function equalIC(a, b) {
+        return a != null && b != null && a.toLowerCase() === b.toLowerCase();
+    }
+
+    function isMobileFallback(userAgent) {
+        return detectMobileBrowsers.fullPattern.test(userAgent) ||
+            detectMobileBrowsers.shortPattern.test(userAgent.substr(0,4));
+    }
+
+    function prepareDetectionCache(cache, userAgent, maxPhoneWidth) {
+        if (cache.mobile !== undefined) {
+            return;
+        }
+        var phone, tablet, phoneSized;
+
+        // first check for stronger tablet rules, then phone (see issue#5)
+        tablet = findMatch(mobileDetectRules.tablets, userAgent);
+        if (tablet) {
+            cache.mobile = cache.tablet = tablet;
+            cache.phone = null;
+            return; // unambiguously identified as tablet
+        }
+
+        phone = findMatch(mobileDetectRules.phones, userAgent);
+        if (phone) {
+            cache.mobile = cache.phone = phone;
+            cache.tablet = null;
+            return; // unambiguously identified as phone
+        }
+
+        // our rules haven't found a match -> try more general fallback rules
+        if (isMobileFallback(userAgent)) {
+            phoneSized = MobileDetect.isPhoneSized(maxPhoneWidth);
+            if (phoneSized === undefined) {
+                cache.mobile = cache.tablet = cache.phone = FALLBACK_MOBILE;
+            } else if (phoneSized) {
+                cache.mobile = cache.phone = FALLBACK_PHONE;
+                cache.tablet = null;
+            } else {
+                cache.mobile = cache.tablet = FALLBACK_TABLET;
+                cache.phone = null;
+            }
+        } else {
+            // not mobile at all!
+            cache.mobile = cache.tablet = cache.phone = null;
+        }
+    }
+
+    // t is a reference to a MobileDetect instance
+    function mobileGrade(t) {
+        // impl note:
+        // To keep in sync w/ Mobile_Detect.php easily, the following code is tightly aligned to the PHP version.
+        // When changes are made in Mobile_Detect.php, copy this method and replace:
+        //     $this-> / t.
+        //     self::MOBILE_GRADE_(.) / '$1'
+        //     , self::VERSION_TYPE_FLOAT / (nothing)
+        //     isIOS() / os('iOS')
+        //     [reg] / (nothing)   <-- jsdelivr complaining about unescaped unicode character U+00AE
+        var $isMobile = t.mobile() !== null;
+
+        if (
+            // Apple iOS 3.2-5.1 - Tested on the original iPad (4.3 / 5.0), iPad 2 (4.3), iPad 3 (5.1), original iPhone (3.1), iPhone 3 (3.2), 3GS (4.3), 4 (4.3 / 5.0), and 4S (5.1)
+            t.os('iOS') && t.version('iPad')>=4.3 ||
+            t.os('iOS') && t.version('iPhone')>=3.1 ||
+            t.os('iOS') && t.version('iPod')>=3.1 ||
+
+            // Android 2.1-2.3 - Tested on the HTC Incredible (2.2), original Droid (2.2), HTC Aria (2.1), Google Nexus S (2.3). Functional on 1.5 & 1.6 but performance may be sluggish, tested on Google G1 (1.5)
+            // Android 3.1 (Honeycomb)  - Tested on the Samsung Galaxy Tab 10.1 and Motorola XOOM
+            // Android 4.0 (ICS)  - Tested on a Galaxy Nexus. Note: transition performance can be poor on upgraded devices
+            // Android 4.1 (Jelly Bean)  - Tested on a Galaxy Nexus and Galaxy 7
+            ( t.version('Android')>2.1 && t.is('Webkit') ) ||
+
+            // Windows Phone 7-7.5 - Tested on the HTC Surround (7.0) HTC Trophy (7.5), LG-E900 (7.5), Nokia Lumia 800
+            t.version('Windows Phone OS')>=7.0 ||
+
+            // Blackberry 7 - Tested on BlackBerry Torch 9810
+            // Blackberry 6.0 - Tested on the Torch 9800 and Style 9670
+            t.is('BlackBerry') && t.version('BlackBerry')>=6.0 ||
+            // Blackberry Playbook (1.0-2.0) - Tested on PlayBook
+            t.match('Playbook.*Tablet') ||
+
+            // Palm WebOS (1.4-2.0) - Tested on the Palm Pixi (1.4), Pre (1.4), Pre 2 (2.0)
+            ( t.version('webOS')>=1.4 && t.match('Palm|Pre|Pixi') ) ||
+            // Palm WebOS 3.0  - Tested on HP TouchPad
+            t.match('hp.*TouchPad') ||
+
+            // Firefox Mobile (12 Beta) - Tested on Android 2.3 device
+            ( t.is('Firefox') && t.version('Firefox')>=12 ) ||
+
+            // Chrome for Android - Tested on Android 4.0, 4.1 device
+            ( t.is('Chrome') && t.is('AndroidOS') && t.version('Android')>=4.0 ) ||
+
+            // Skyfire 4.1 - Tested on Android 2.3 device
+            ( t.is('Skyfire') && t.version('Skyfire')>=4.1 && t.is('AndroidOS') && t.version('Android')>=2.3 ) ||
+
+            // Opera Mobile 11.5-12: Tested on Android 2.3
+            ( t.is('Opera') && t.version('Opera Mobi')>11 && t.is('AndroidOS') ) ||
+
+            // Meego 1.2 - Tested on Nokia 950 and N9
+            t.is('MeeGoOS') ||
+
+            // Tizen (pre-release) - Tested on early hardware
+            t.is('Tizen') ||
+
+            // Samsung Bada 2.0 - Tested on a Samsung Wave 3, Dolphin browser
+            // @todo: more tests here!
+            t.is('Dolfin') && t.version('Bada')>=2.0 ||
+
+            // UC Browser - Tested on Android 2.3 device
+            ( (t.is('UC Browser') || t.is('Dolfin')) && t.version('Android')>=2.3 ) ||
+
+            // Kindle 3 and Fire  - Tested on the built-in WebKit browser for each
+            ( t.match('Kindle Fire') ||
+                t.is('Kindle') && t.version('Kindle')>=3.0 ) ||
+
+            // Nook Color 1.4.1 - Tested on original Nook Color, not Nook Tablet
+            t.is('AndroidOS') && t.is('NookTablet') ||
+
+            // Chrome Desktop 11-21 - Tested on OS X 10.7 and Windows 7
+            t.version('Chrome')>=11 && !$isMobile ||
+
+            // Safari Desktop 4-5 - Tested on OS X 10.7 and Windows 7
+            t.version('Safari')>=5.0 && !$isMobile ||
+
+            // Firefox Desktop 4-13 - Tested on OS X 10.7 and Windows 7
+            t.version('Firefox')>=4.0 && !$isMobile ||
+
+            // Internet Explorer 7-9 - Tested on Windows XP, Vista and 7
+            t.version('MSIE')>=7.0 && !$isMobile ||
+
+            // Opera Desktop 10-12 - Tested on OS X 10.7 and Windows 7
+            // @reference: http://my.opera.com/community/openweb/idopera/
+            t.version('Opera')>=10 && !$isMobile
+
+            ){
+            return 'A';
+        }
+
+        if (
+            t.os('iOS') && t.version('iPad')<4.3 ||
+            t.os('iOS') && t.version('iPhone')<3.1 ||
+            t.os('iOS') && t.version('iPod')<3.1 ||
+
+            // Blackberry 5.0: Tested on the Storm 2 9550, Bold 9770
+            t.is('Blackberry') && t.version('BlackBerry')>=5 && t.version('BlackBerry')<6 ||
+
+            //Opera Mini (5.0-6.5) - Tested on iOS 3.2/4.3 and Android 2.3
+            ( t.version('Opera Mini')>=5.0 && t.version('Opera Mini')<=6.5 &&
+                (t.version('Android')>=2.3 || t.is('iOS')) ) ||
+
+            // Nokia Symbian^3 - Tested on Nokia N8 (Symbian^3), C7 (Symbian^3), also works on N97 (Symbian^1)
+            t.match('NokiaN8|NokiaC7|N97.*Series60|Symbian/3') ||
+
+            // @todo: report this (tested on Nokia N71)
+            t.version('Opera Mobi')>=11 && t.is('SymbianOS')
+            ){
+            return 'B';
+        }
+
+        if (
+        // Blackberry 4.x - Tested on the Curve 8330
+            t.version('BlackBerry')<5.0 ||
+            // Windows Mobile - Tested on the HTC Leo (WinMo 5.2)
+            t.match('MSIEMobile|Windows CE.*Mobile') || t.version('Windows Mobile')<=5.2
+
+            ){
+            return 'C';
+        }
+
+        //All older smartphone platforms and featurephones - Any device that doesn't support media queries
+        //will receive the basic, C grade experience.
+        return 'C';
+    }
+
+    /**
+     * Constructor for MobileDetect object.
+     * <br>
+     * Such an object will keep a reference to the given user-agent string and cache most of the detect queries.<br>
+     * <div style="background-color: #d9edf7; border: 1px solid #bce8f1; color: #3a87ad; padding: 14px; border-radius: 2px; margin-top: 20px">
+     *     <strong>Find information how to download and install:</strong>
+     *     <a href="https://github.com/hgoebl/mobile-detect.js/">github.com/hgoebl/mobile-detect.js/</a>
+     * </div>
+     *
+     * @example <pre>
+     *     var md = new MobileDetect(window.navigator.userAgent);
+     *     if (md.mobile()) {
+     *         location.href = (md.mobileGrade() === 'A') ? '/mobile/' : '/lynx/';
+     *     }
+     * </pre>
+     *
+     * @param {string} userAgent typically taken from window.navigator.userAgent or http_header['User-Agent']
+     * @param {number} [maxPhoneWidth=650] <strong>only for browsers</strong> specify a value for the maximum
+     *        width (in logical "CSS" pixels) until a device detected as mobile will be handled as phone.
+     *        This is only used in cases where the device cannot be classified as phone or tablet.<br>
+     *        See <a href="http://www.html5rocks.com/en/mobile/cross-device/">A non-responsive approach to
+     *        building cross-device webapps</a>.<br>
+     *        If you provide a value < 0, then this "fuzzy" check is disabled.
+     * @constructor
+     * @global
+     */
+    function MobileDetect(userAgent, maxPhoneWidth) {
+        this.ua = userAgent || '';
+        this._cache = {};
+        this.maxPhoneWidth = maxPhoneWidth || 650;
+    }
+
+    MobileDetect.prototype = {
+        constructor: MobileDetect,
+
+        /**
+         * Returns the detected phone or tablet type or <tt>null</tt> if it is not a mobile device.
+         * <br>
+         * For a list of possible return values see {@link MobileDetect#phone} and {@link MobileDetect#tablet}.<br>
+         * <br>
+         * If the device is not detected by the regular expressions from Mobile-Detect, a test is made against
+         * the patterns of <a href="http://detectmobilebrowsers.com/">detectmobilebrowsers.com</a>. If this test
+         * is positive, a value of <code>UnknownPhone</code>, <code>UnknownTablet</code> or
+         * <code>UnknownMobile</code> is returned.<br>
+         * When used in browser, the decision whether phone or tablet is made based on <code>screen.width</code>.<br>
+         * When used server-side (node.js), there is no way to tell the difference between <code>UnknownPhone</code>
+         * and <code>UnknownTablet</code>, so you will only get <code>UnknownMobile</code>.<br>
+         * <br>
+         * In most cases you will use the return value just as a boolean.
+         *
+         * @returns {String} the key for the phone family or tablet family, e.g. "Nexus".
+         * @function MobileDetect#mobile
+         */
+        mobile: function () {
+            prepareDetectionCache(this._cache, this.ua, this.maxPhoneWidth);
+            return this._cache.mobile;
+        },
+
+        /**
+         * Returns the detected phone type/family string or <tt>null</tt>.
+         * <br>
+         * The returned tablet (family or producer) is one of following keys:<br>
+         * <br><tt>iPhone, BlackBerry, HTC, Nexus, Dell, Motorola, Samsung, LG, Sony, Asus,
+         * Micromax, Palm, Vertu, Pantech, Fly, iMobile, SimValley, Wolfgang, Alcatel,
+         * Nintendo, Amoi, INQ, GenericPhone</tt><br>
+         * <br>
+         * If the device is not detected by the regular expressions from Mobile-Detect, a test is made against
+         * the patterns of <a href="http://detectmobilebrowsers.com/">detectmobilebrowsers.com</a>. If this test
+         * is positive, a value of <code>UnknownPhone</code> or <code>UnknownMobile</code> is returned.<br>
+         * When used in browser, the decision whether phone or tablet is made based on <code>screen.width</code>.<br>
+         * When used server-side (node.js), there is no way to tell the difference between <code>UnknownPhone</code>
+         * and <code>UnknownMobile</code>, so you will only get <code>UnknownMobile</code>.<br>
+         * <br>
+         * In most cases you will use the return value just as a boolean.
+         *
+         * @returns {String} the key of the phone family or producer, e.g. "iPhone"
+         * @function MobileDetect#phone
+         */
+        phone: function () {
+            prepareDetectionCache(this._cache, this.ua, this.maxPhoneWidth);
+            return this._cache.phone;
+        },
+
+        /**
+         * Returns the detected tablet type/family string or <tt>null</tt>.
+         * <br>
+         * The returned tablet (family or producer) is one of following keys:<br>
+         * <br><tt>iPad, NexusTablet, SamsungTablet, Kindle, SurfaceTablet, HPTablet, AsusTablet,
+         * BlackBerryTablet, HTCtablet, MotorolaTablet, NookTablet, AcerTablet,
+         * ToshibaTablet, LGTablet, FujitsuTablet, PrestigioTablet, LenovoTablet,
+         * DellTablet, YarvikTablet, MedionTablet, ArnovaTablet, IntensoTablet, IRUTablet,
+         * MegafonTablet, EbodaTablet, AllViewTablet, ArchosTablet, AinolTablet,
+         * SonyTablet, PhilipsTablet, CubeTablet, CobyTablet, MIDTablet, MSITablet,
+         * SMiTTablet, RockChipTablet, FlyTablet, bqTablet, HuaweiTablet, NecTablet,
+         * PantechTablet, BronchoTablet, VersusTablet, ZyncTablet, PositivoTablet,
+         * NabiTablet, KoboTablet, DanewTablet, TexetTablet, PlaystationTablet,
+         * TrekstorTablet, PyleAudioTablet, AdvanTablet, DanyTechTablet, GalapadTablet,
+         * MicromaxTablet, KarbonnTablet, AllFineTablet, PROSCANTablet, YONESTablet,
+         * ChangJiaTablet, GUTablet, PointOfViewTablet, OvermaxTablet, HCLTablet,
+         * DPSTablet, VistureTablet, CrestaTablet, MediatekTablet, ConcordeTablet,
+         * GoCleverTablet, ModecomTablet, VoninoTablet, ECSTablet, StorexTablet,
+         * VodafoneTablet, EssentielBTablet, RossMoorTablet, iMobileTablet, TolinoTablet,
+         * AudioSonicTablet, AMPETablet, SkkTablet, TecnoTablet, JXDTablet, iJoyTablet,
+         * FX2Tablet, XoroTablet, ViewsonicTablet, OdysTablet, CaptivaTablet,
+         * IconbitTablet, TeclastTablet, JaytechTablet, BlaupunktTablet, DigmaTablet,
+         * EvolioTablet, LavaTablet, CelkonTablet, MiTablet, NibiruTablet, NexoTablet,
+         * UbislateTablet, PocketBookTablet, Hudl, TelstraTablet, GenericTablet</tt><br>
+         * <br>
+         * If the device is not detected by the regular expressions from Mobile-Detect, a test is made against
+         * the patterns of <a href="http://detectmobilebrowsers.com/">detectmobilebrowsers.com</a>. If this test
+         * is positive, a value of <code>UnknownTablet</code> or <code>UnknownMobile</code> is returned.<br>
+         * When used in browser, the decision whether phone or tablet is made based on <code>screen.width</code>.<br>
+         * When used server-side (node.js), there is no way to tell the difference between <code>UnknownTablet</code>
+         * and <code>UnknownMobile</code>, so you will only get <code>UnknownMobile</code>.<br>
+         * <br>
+         * In most cases you will use the return value just as a boolean.
+         *
+         * @returns {String} the key of the tablet family or producer, e.g. "SamsungTablet"
+         * @function MobileDetect#tablet
+         */
+        tablet: function () {
+            prepareDetectionCache(this._cache, this.ua, this.maxPhoneWidth);
+            return this._cache.tablet;
+        },
+
+        /**
+         * Returns the detected user-agent string or <tt>null</tt>.
+         * <br>
+         * The returned user-agent is one of following keys:<br>
+         * <br><tt>Chrome, Dolfin, Opera, Skyfire, IE, Firefox, Bolt, TeaShark, Blazer, Safari,
+         * Tizen, UCBrowser, baiduboxapp, baidubrowser, DiigoBrowser, Puffin, Mercury,
+         * ObigoBrowser, NetFront, GenericBrowser</tt><br>
+         *
+         * @returns {String} the key for the detected user-agent or <tt>null</tt>
+         * @function MobileDetect#userAgent
+         */
+        userAgent: function () {
+            if (this._cache.userAgent === undefined) {
+                this._cache.userAgent = findMatch(mobileDetectRules.uas, this.ua);
+            }
+            return this._cache.userAgent;
+        },
+
+        /**
+         * Returns the detected operating system string or <tt>null</tt>.
+         * <br>
+         * The operating system is one of following keys:<br>
+         * <br><tt>AndroidOS, BlackBerryOS, PalmOS, SymbianOS, WindowsMobileOS, WindowsPhoneOS,
+         * iOS, MeeGoOS, MaemoOS, JavaOS, webOS, badaOS, BREWOS</tt><br>
+         *
+         * @returns {String} the key for the detected operating system.
+         * @function MobileDetect#os
+         */
+        os: function () {
+            if (this._cache.os === undefined) {
+                this._cache.os = findMatch(mobileDetectRules.oss0, this.ua) ||
+                                 findMatch(mobileDetectRules.oss, this.ua);
+            }
+            return this._cache.os;
+        },
+
+        /**
+         * Get the version (as Number) of the given property in the User-Agent.
+         * <br>
+         * Will return a float number. (eg. 2_0 will return 2.0, 4.3.1 will return 4.31)
+         *
+         * @param {String} key a key defining a thing which has a version.<br>
+         *        You can use one of following keys:<br>
+         * <br><tt>Mobile, Build, Version, VendorID, iPad, iPhone, iPod, Kindle, Chrome, Coast,
+         * Dolfin, Firefox, Fennec, IE, NetFront, NokiaBrowser, Opera, Opera Mini, Opera
+         * Mobi, UC Browser, MQQBrowser, MicroMessenger, baiduboxapp, baidubrowser, Iron,
+         * Safari, Skyfire, Tizen, Webkit, Gecko, Trident, Presto, iOS, Android,
+         * BlackBerry, BREW, Java, Windows Phone OS, Windows Phone, Windows CE, Windows
+         * NT, Symbian, webOS</tt><br>
+         *
+         * @returns {Number} the version as float or <tt>NaN</tt> if User-Agent doesn't contain this version.
+         *          Be careful when comparing this value with '==' operator!
+         * @function MobileDetect#version
+         */
+        version: function (key) {
+            return getVersion(key, this.ua);
+        },
+
+        /**
+         * Get the version (as String) of the given property in the User-Agent.
+         * <br>
+         *
+         * @param {String} key a key defining a thing which has a version.<br>
+         *        You can use one of following keys:<br>
+         * <br><tt>Mobile, Build, Version, VendorID, iPad, iPhone, iPod, Kindle, Chrome, Coast,
+         * Dolfin, Firefox, Fennec, IE, NetFront, NokiaBrowser, Opera, Opera Mini, Opera
+         * Mobi, UC Browser, MQQBrowser, MicroMessenger, baiduboxapp, baidubrowser, Iron,
+         * Safari, Skyfire, Tizen, Webkit, Gecko, Trident, Presto, iOS, Android,
+         * BlackBerry, BREW, Java, Windows Phone OS, Windows Phone, Windows CE, Windows
+         * NT, Symbian, webOS</tt><br>
+         *
+         * @returns {String} the "raw" version as String or <tt>null</tt> if User-Agent doesn't contain this version.
+         *
+         * @function MobileDetect#versionStr
+         */
+        versionStr: function (key) {
+            return getVersionStr(key, this.ua);
+        },
+
+        /**
+         * Global test key against userAgent, os, phone, tablet and some other properties of userAgent string.
+         *
+         * @param {String} key the key (case-insensitive) of a userAgent, an operating system, phone or
+         *        tablet family.<br>
+         *        For a complete list of possible values, see {@link MobileDetect#userAgent},
+         *        {@link MobileDetect#os}, {@link MobileDetect#phone}, {@link MobileDetect#tablet}.<br>
+         *        Additionally you have following keys:<br>
+         * <br><tt>DesktopMode, TV, WebKit, Bot, MobileBot, Console, Watch</tt><br>
+         *
+         * @returns {boolean} <tt>true</tt> when the given key is one of the defined keys of userAgent, os, phone,
+         *                    tablet or one of the listed additional keys, otherwise <tt>false</tt>
+         * @function MobileDetect#is
+         */
+        is: function(key) {
+            return equalIC(key, this.userAgent()) ||
+                   equalIC(key, this.os()) ||
+                   equalIC(key, this.phone()) ||
+                   equalIC(key, this.tablet()) ||
+                   equalIC(key, findMatch(mobileDetectRules.utils, this.ua));
+        },
+
+        /**
+         * Do a quick test against navigator::userAgent.
+         *
+         * @param {String|RegExp} pattern the pattern, either as String or RegExp
+         *                        (a string will be converted to a case-insensitive RegExp).
+         * @returns {boolean} <tt>true</tt> when the pattern matches, otherwise <tt>false</tt>
+         * @function MobileDetect#match
+         */
+        match: function (pattern) {
+            if (!(pattern instanceof RegExp)) {
+                pattern = new RegExp(pattern, 'i');
+            }
+            return pattern.test(this.ua);
+        },
+
+        /**
+         * Checks whether the mobile device can be considered as phone regarding <code>screen.width</code>.
+         * <br>
+         * Obviously this method makes sense in browser environments only (not for Node.js)!
+         * @param {number} [maxPhoneWidth] the maximum logical pixels (aka. CSS-pixels) to be considered as phone.<br>
+         *        The argument is optional and if not present or falsy, the value of the constructor is taken.
+         * @returns {boolean|undefined} <code>undefined</code> if screen size wasn't detectable, else <code>true</code>
+         *          when screen.width is less or equal to maxPhoneWidth, otherwise <code>false</code>.<br>
+         *          Will always return <code>undefined</code> server-side.
+         */
+        isPhoneSized: function (maxPhoneWidth) {
+            return MobileDetect.isPhoneSized(maxPhoneWidth || this.maxPhoneWidth);
+        },
+
+        /**
+         * Returns the mobile grade ('A', 'B', 'C').
+         *
+         * @returns {String} one of the mobile grades ('A', 'B', 'C').
+         * @function MobileDetect#mobileGrade
+         */
+        mobileGrade: function () {
+            if (this._cache.grade === undefined) {
+                this._cache.grade = mobileGrade(this);
+            }
+            return this._cache.grade;
+        }
+    };
+
+    // environment-dependent
+    if (typeof window !== 'undefined' && window.screen && window.screen.width) {
+        MobileDetect.isPhoneSized = function (maxPhoneWidth) {
+            if (maxPhoneWidth < 0) {
+                return undefined;
+            }
+            var physicalPixelWidth = window.screen.width,
+                pixelRatio = window.devicePixelRatio || 1,
+                cssPixelWidth = physicalPixelWidth / pixelRatio;
+
+            return cssPixelWidth <= maxPhoneWidth;
+        };
+    } else {
+        MobileDetect.isPhoneSized = function () {};
+    }
+
+    return MobileDetect;
+}); // end of call of define()
+})((function (undefined) {
+    if (typeof define === 'function' && define.amd) {
+        return define;
+    } else if (typeof module !== 'undefined' && module.exports) {
+        return function (factory) { module.exports = factory(); };
+    } else if (typeof window !== 'undefined') {
+        return function (factory) { window.MobileDetect = factory(); };
+    } else {
+        // please file a bug if you get this error!
+        throw new Error('unknown environment');
+    }
+})());

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/HISTORY.md
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/HISTORY.md	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/HISTORY.md	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,50 @@
+0.2.5 / 2014-07-19
+------------------
+
+- Workaround for Chrome 38.0.2096.0 script parser bug, #30
+
+
+0.2.4 / 2014-07-07
+------------------
+
+- Fixed bug in inflate wrapper, #29
+
+
+0.2.3 / 2014-06-09
+------------------
+
+- Maintenance release, dependencies update.
+
+
+0.2.2 / 2014-06-04
+------------------
+
+- Fixed iOS 5.1 Safary issue with `apply(typed_array)`, #26.
+
+
+0.2.1 / 2014-05-01
+------------------
+
+- Fixed collision on switch dynamic/fixed tables.
+
+
+0.2.0 / 2014-04-18
+------------------
+
+- Added custom gzip headers support.
+- Added strings support.
+- Improved memory allocations for small chunks.
+- ZStream properties rename/cleanup.
+- More coverage tests.
+
+
+0.1.1 / 2014-03-20
+------------------
+
+- Bugfixes for inflate/deflate.
+
+
+0.1.0 / 2014-03-15
+------------------
+
+- First release.

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/LICENSE
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/LICENSE	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/LICENSE	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,21 @@
+(The MIT License)
+
+Copyright (C) 2014 by Vitaly Puzrin
+
+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.

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/README.md
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/README.md	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/README.md	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,172 @@
+pako - zlib port to javascript, very fast!
+==========================================
+
+[![Build Status](https://travis-ci.org/nodeca/pako.svg?branch=master)](https://travis-ci.org/nodeca/pako)
+[![NPM version](https://img.shields.io/npm/v/pako.svg)](https://www.npmjs.org/package/pako)
+
+__Why pako is cool:__
+
+- Almost as fast in modern JS engines as C implementation (see benchmarks).
+- Works in browsers, you can browserify any separate component.
+- Chunking support for big blobs.
+- Results are binary equal to well known [zlib](http://www.zlib.net/) (now v1.2.8 ported).
+
+This project was done to understand how fast JS can be and is it necessary to
+develop native C modules for CPU-intensive tasks. Enjoy the result!
+
+
+__Famous projects, using pako:__
+
+- [browserify](http://browserify.org/) (via [browserify-zlib](https://github.com/devongovett/browserify-zlib))
+- [JSZip](http://stuk.github.io/jszip/)
+- [mincer](https://github.com/nodeca/mincer)
+- [JS-Git](https://github.com/creationix/js-git) and
+  [Tedit](https://chrome.google.com/webstore/detail/tedit-development-environ/ooekdijbnbbjdfjocaiflnjgoohnblgf)
+  by [@creatronix](https://github.com/creationix)
+
+ 
+
+__Benchmarks:__
+
+```
+node v0.10.26, 1mb sample:
+
+   deflate-dankogai x 4.73 ops/sec ±0.82% (15 runs sampled)
+   deflate-gildas x 4.58 ops/sec ±2.33% (15 runs sampled)
+   deflate-imaya x 3.22 ops/sec ±3.95% (12 runs sampled)
+ ! deflate-pako x 6.99 ops/sec ±0.51% (21 runs sampled)
+   deflate-pako-string x 5.89 ops/sec ±0.77% (18 runs sampled)
+   deflate-pako-untyped x 4.39 ops/sec ±1.58% (14 runs sampled)
+ * deflate-zlib x 14.71 ops/sec ±4.23% (59 runs sampled)
+   inflate-dankogai x 32.16 ops/sec ±0.13% (56 runs sampled)
+   inflate-imaya x 30.35 ops/sec ±0.92% (53 runs sampled)
+ ! inflate-pako x 69.89 ops/sec ±1.46% (71 runs sampled)
+   inflate-pako-string x 19.22 ops/sec ±1.86% (49 runs sampled)
+   inflate-pako-untyped x 17.19 ops/sec ±0.85% (32 runs sampled)
+ * inflate-zlib x 70.03 ops/sec ±1.64% (81 runs sampled)
+
+node v0.11.12, 1mb sample:
+
+   deflate-dankogai x 5.60 ops/sec ±0.49% (17 runs sampled)
+   deflate-gildas x 5.06 ops/sec ±6.00% (16 runs sampled)
+   deflate-imaya x 3.52 ops/sec ±3.71% (13 runs sampled)
+ ! deflate-pako x 11.52 ops/sec ±0.22% (32 runs sampled)
+   deflate-pako-string x 9.53 ops/sec ±1.12% (27 runs sampled)
+   deflate-pako-untyped x 5.44 ops/sec ±0.72% (17 runs sampled)
+ * deflate-zlib x 14.05 ops/sec ±3.34% (63 runs sampled)
+   inflate-dankogai x 42.19 ops/sec ±0.09% (56 runs sampled)
+   inflate-imaya x 79.68 ops/sec ±1.07% (68 runs sampled)
+ ! inflate-pako x 97.52 ops/sec ±0.83% (80 runs sampled)
+   inflate-pako-string x 45.19 ops/sec ±1.69% (57 runs sampled)
+   inflate-pako-untyped x 24.35 ops/sec ±2.59% (40 runs sampled)
+ * inflate-zlib x 60.32 ops/sec ±1.36% (69 runs sampled)
+```
+
+zlib's test is partialy afferted by marshling (that make sense for inflate only).
+You can change deflate level to 0 in benchmark source, to investigate details.
+For deflate level 6 results can be considered as correct.
+
+__Install:__
+
+node.js:
+
+```
+npm install pako
+```
+
+browser:
+
+```
+bower install pako
+```
+
+
+Example & API
+-------------
+
+Full docs - http://nodeca.github.io/pako/
+
+```javascript
+var pako = require('pako');
+
+// Deflate
+//
+var input = new Uint8Array();
+//... fill input data here
+var output = pako.deflate(input);
+
+// Inflate (simple wrapper can throw exception on broken stream)
+//
+var compressed = new Uint8Array();
+//... fill data to uncompress here
+try {
+  var result = pako.inflate(compressed);
+catch (err) {
+  console.log(err);
+}
+
+//
+// Alternate interface for chunking & without exceptions
+//
+
+var inflator = new pako.Inflate();
+
+inflator.push(chunk1, false);
+inflator.push(chunk2, false);
+...
+inflator.push(chunkN, true); // true -> last chunk
+
+if (inflator.err) {
+  console.log(inflator.msg);
+}
+
+var output = inflator.result;
+
+```
+
+Sometime you can wish to work with strings. For example, to send
+big objects as json to server. Pako detects input data type. You can
+force output to be string with option `{ to: 'string' }`.
+
+```javascript
+var pako = require('pako');
+
+var test = { my: 'super', puper: [456, 567], awesome: 'pako' };
+
+var binaryString = pako.deflate(JSON.stringify(test), { to: 'string' });
+
+//
+// Here you can do base64 encode, make xhr requests and so on.
+//
+
+var restored = JSON.parse(pako.inflate(binaryString, { to: 'string' }));
+```
+
+
+Notes
+-----
+
+Pako does not contain some specific zlib functions:
+
+- __deflate__ -  methods `deflateCopy`, `deflateBound`, `deflateParams`,
+  `deflatePending`, `deflatePrime`, `deflateSetDictionary`, `deflateTune`.
+- __inflate__ - `inflateGetDictionary`, `inflateCopy`, `inflateMark`,
+  `inflatePrime`, `inflateSetDictionary`, `inflateSync`, `inflateSyncPoint`,
+  `inflateUndermine`.
+
+
+Authors
+-------
+
+- Andrey Tupitsin [@anrd83](https://github.com/andr83)
+- Vitaly Puzrin [@puzrin](https://github.com/puzrin)
+
+Personal thanks to Vyacheslav Egorov ([@mraleph](https://github.com/mraleph))
+for his awesome tutoruals about optimising JS code for v8,
+[IRHydra](http://mrale.ph/irhydra/) tool and his advices.
+
+
+License
+-------
+
+MIT

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/pako_inflate.js
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/pako_inflate.js	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/JS/pako-0.2.5/pako_inflate.js	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,3025 @@
+/* pako 0.2.5 nodeca/pako */!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.pako=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);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.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(_dereq_,module,exports){
+'use strict';
+
+
+var zlib_inflate = _dereq_('./zlib/inflate.js');
+var utils = _dereq_('./utils/common');
+var strings = _dereq_('./utils/strings');
+var c = _dereq_('./zlib/constants');
+var msg = _dereq_('./zlib/messages');
+var zstream = _dereq_('./zlib/zstream');
+var gzheader = _dereq_('./zlib/gzheader');
+
+
+/**
+ * class Inflate
+ *
+ * Generic JS-style wrapper for zlib calls. If you don't need
+ * streaming behaviour - use more simple functions: [[inflate]]
+ * and [[inflateRaw]].
+ **/
+
+/* internal
+ * inflate.chunks -> Array
+ *
+ * Chunks of output data, if [[Inflate#onData]] not overriden.
+ **/
+
+/**
+ * Inflate.result -> Uint8Array|Array|String
+ *
+ * Uncompressed result, generated by default [[Inflate#onData]]
+ * and [[Inflate#onEnd]] handlers. Filled after you push last chunk
+ * (call [[Inflate#push]] with `Z_FINISH` / `true` param).
+ **/
+
+/**
+ * Inflate.err -> Number
+ *
+ * Error code after inflate finished. 0 (Z_OK) on success.
+ * Should be checked if broken data possible.
+ **/
+
+/**
+ * Inflate.msg -> String
+ *
+ * Error message, if [[Inflate.err]] != 0
+ **/
+
+
+/**
+ * new Inflate(options)
+ * - options (Object): zlib inflate options.
+ *
+ * Creates new inflator instance with specified params. Throws exception
+ * on bad params. Supported options:
+ *
+ * - `windowBits`
+ *
+ * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
+ * for more information on these.
+ *
+ * Additional options, for internal needs:
+ *
+ * - `chunkSize` - size of generated data chunks (16K by default)
+ * - `raw` (Boolean) - do raw inflate
+ * - `to` (String) - if equal to 'string', then result will be converted
+ *   from utf8 to utf16 (javascript) string. When string output requested,
+ *   chunk length can differ from `chunkSize`, depending on content.
+ *
+ * By default, when no options set, autodetect deflate/gzip data format via
+ * wrapper header.
+ *
+ * ##### Example:
+ *
+ * ```javascript
+ * var pako = require('pako')
+ *   , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9])
+ *   , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]);
+ *
+ * var inflate = new pako.Inflate({ level: 3});
+ *
+ * inflate.push(chunk1, false);
+ * inflate.push(chunk2, true);  // true -> last chunk
+ *
+ * if (inflate.err) { throw new Error(inflate.err); }
+ *
+ * console.log(inflate.result);
+ * ```
+ **/
+var Inflate = function(options) {
+
+  this.options = utils.assign({
+    chunkSize: 16384,
+    windowBits: 0,
+    to: ''
+  }, options || {});
+
+  var opt = this.options;
+
+  // Force window size for `raw` data, if not set directly,
+  // because we have no header for autodetect.
+  if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) {
+    opt.windowBits = -opt.windowBits;
+    if (opt.windowBits === 0) { opt.windowBits = -15; }
+  }
+
+  // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate
+  if ((opt.windowBits >= 0) && (opt.windowBits < 16) &&
+      !(options && options.windowBits)) {
+    opt.windowBits += 32;
+  }
+
+  // Gzip header has no info about windows size, we can do autodetect only
+  // for deflate. So, if window size not set, force it to max when gzip possible
+  if ((opt.windowBits > 15) && (opt.windowBits < 48)) {
+    // bit 3 (16) -> gzipped data
+    // bit 4 (32) -> autodetect gzip/deflate
+    if ((opt.windowBits & 15) === 0) {
+      opt.windowBits |= 15;
+    }
+  }
+
+  this.err    = 0;      // error code, if happens (0 = Z_OK)
+  this.msg    = '';     // error message
+  this.ended  = false;  // used to avoid multiple onEnd() calls
+  this.chunks = [];     // chunks of compressed data
+
+  this.strm   = new zstream();
+  this.strm.avail_out = 0;
+
+  var status  = zlib_inflate.inflateInit2(
+    this.strm,
+    opt.windowBits
+  );
+
+  if (status !== c.Z_OK) {
+    throw new Error(msg[status]);
+  }
+
+  this.header = new gzheader();
+
+  zlib_inflate.inflateGetHeader(this.strm, this.header);
+};
+
+/**
+ * Inflate#push(data[, mode]) -> Boolean
+ * - data (Uint8Array|Array|String): input data
+ * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes.
+ *   See constants. Skipped or `false` means Z_NO_FLUSH, `true` meansh Z_FINISH.
+ *
+ * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with
+ * new output chunks. Returns `true` on success. The last data block must have
+ * mode Z_FINISH (or `true`). That flush internal pending buffers and call
+ * [[Inflate#onEnd]].
+ *
+ * On fail call [[Inflate#onEnd]] with error code and return false.
+ *
+ * We strongly recommend to use `Uint8Array` on input for best speed (output
+ * format is detected automatically). Also, don't skip last param and always
+ * use the same type in your code (boolean or number). That will improve JS speed.
+ *
+ * For regular `Array`-s make sure all elements are [0..255].
+ *
+ * ##### Example
+ *
+ * ```javascript
+ * push(chunk, false); // push one of data chunks
+ * ...
+ * push(chunk, true);  // push last chunk
+ * ```
+ **/
+Inflate.prototype.push = function(data, mode) {
+  var strm = this.strm;
+  var chunkSize = this.options.chunkSize;
+  var status, _mode;
+  var next_out_utf8, tail, utf8str;
+
+  if (this.ended) { return false; }
+  _mode = (mode === ~~mode) ? mode : ((mode === true) ? c.Z_FINISH : c.Z_NO_FLUSH);
+
+  // Convert data if needed
+  if (typeof data === 'string') {
+    // Only binary strings can be decompressed on practice
+    strm.input = strings.binstring2buf(data);
+  } else {
+    strm.input = data;
+  }
+
+  strm.next_in = 0;
+  strm.avail_in = strm.input.length;
+
+  do {
+    if (strm.avail_out === 0) {
+      strm.output = new utils.Buf8(chunkSize);
+      strm.next_out = 0;
+      strm.avail_out = chunkSize;
+    }
+
+    status = zlib_inflate.inflate(strm, c.Z_NO_FLUSH);    /* no bad return value */
+
+    if (status !== c.Z_STREAM_END && status !== c.Z_OK) {
+      this.onEnd(status);
+      this.ended = true;
+      return false;
+    }
+
+    if (strm.next_out) {
+      if (strm.avail_out === 0 || status === c.Z_STREAM_END || (strm.avail_in === 0 && _mode === c.Z_FINISH)) {
+
+        if (this.options.to === 'string') {
+
+          next_out_utf8 = strings.utf8border(strm.output, strm.next_out);
+
+          tail = strm.next_out - next_out_utf8;
+          utf8str = strings.buf2string(strm.output, next_out_utf8);
+
+          // move tail
+          strm.next_out = tail;
+          strm.avail_out = chunkSize - tail;
+          if (tail) { utils.arraySet(strm.output, strm.output, next_out_utf8, tail, 0); }
+
+          this.onData(utf8str);
+
+        } else {
+          this.onData(utils.shrinkBuf(strm.output, strm.next_out));
+        }
+      }
+    }
+  } while ((strm.avail_in > 0) && status !== c.Z_STREAM_END);
+
+  if (status === c.Z_STREAM_END) {
+    _mode = c.Z_FINISH;
+  }
+  // Finalize on the last chunk.
+  if (_mode === c.Z_FINISH) {
+    status = zlib_inflate.inflateEnd(this.strm);
+    this.onEnd(status);
+    this.ended = true;
+    return status === c.Z_OK;
+  }
+
+  return true;
+};
+
+
+/**
+ * Inflate#onData(chunk) -> Void
+ * - chunk (Uint8Array|Array|String): ouput data. Type of array depends
+ *   on js engine support. When string output requested, each chunk
+ *   will be string.
+ *
+ * By default, stores data blocks in `chunks[]` property and glue
+ * those in `onEnd`. Override this handler, if you need another behaviour.
+ **/
+Inflate.prototype.onData = function(chunk) {
+  this.chunks.push(chunk);
+};
+
+
+/**
+ * Inflate#onEnd(status) -> Void
+ * - status (Number): inflate status. 0 (Z_OK) on success,
+ *   other if not.
+ *
+ * Called once after you tell inflate that input stream complete
+ * or error happenned. By default - join collected chunks,
+ * free memory and fill `results` / `err` properties.
+ **/
+Inflate.prototype.onEnd = function(status) {
+  // On success - join
+  if (status === c.Z_OK) {
+    if (this.options.to === 'string') {
+      // Glue & convert here, until we teach pako to send
+      // utf8 alligned strings to onData
+      this.result = this.chunks.join('');
+    } else {
+      this.result = utils.flattenChunks(this.chunks);
+    }
+  }
+  this.chunks = [];
+  this.err = status;
+  this.msg = this.strm.msg;
+};
+
+
+/**
+ * inflate(data[, options]) -> Uint8Array|Array|String
+ * - data (Uint8Array|Array|String): input data to decompress.
+ * - options (Object): zlib inflate options.
+ *
+ * Decompress `data` with inflate/ungzip and `options`. Autodetect
+ * format via wrapper header by default. That's why we don't provide
+ * separate `ungzip` method.
+ *
+ * Supported options are:
+ *
+ * - windowBits
+ *
+ * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
+ * for more information.
+ *
+ * Sugar (options):
+ *
+ * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify
+ *   negative windowBits implicitly.
+ * - `to` (String) - if equal to 'string', then result will be converted
+ *   from utf8 to utf16 (javascript) string. When string output requested,
+ *   chunk length can differ from `chunkSize`, depending on content.
+ *
+ *
+ * ##### Example:
+ *
+ * ```javascript
+ * var pako = require('pako')
+ *   , input = pako.deflate([1,2,3,4,5,6,7,8,9])
+ *   , output;
+ *
+ * try {
+ *   output = pako.inflate(input);
+ * } catch (err)
+ *   console.log(err);
+ * }
+ * ```
+ **/
+function inflate(input, options) {
+  var inflator = new Inflate(options);
+
+  inflator.push(input, true);
+
+  // That will never happens, if you don't cheat with options :)
+  if (inflator.err) { throw inflator.msg; }
+
+  return inflator.result;
+}
+
+
+/**
+ * inflateRaw(data[, options]) -> Uint8Array|Array|String
+ * - data (Uint8Array|Array|String): input data to decompress.
+ * - options (Object): zlib inflate options.
+ *
+ * The same as [[inflate]], but creates raw data, without wrapper
+ * (header and adler32 crc).
+ **/
+function inflateRaw(input, options) {
+  options = options || {};
+  options.raw = true;
+  return inflate(input, options);
+}
+
+
+/**
+ * ungzip(data[, options]) -> Uint8Array|Array|String
+ * - data (Uint8Array|Array|String): input data to decompress.
+ * - options (Object): zlib inflate options.
+ *
+ * Just shortcut to [[inflate]], because it autodetects format
+ * by header.content. Done for convenience.
+ **/
+
+
+exports.Inflate = Inflate;
+exports.inflate = inflate;
+exports.inflateRaw = inflateRaw;
+exports.ungzip  = inflate;
+
+},{"./utils/common":2,"./utils/strings":3,"./zlib/constants":5,"./zlib/gzheader":7,"./zlib/inflate.js":9,"./zlib/messages":11,"./zlib/zstream":12}],2:[function(_dereq_,module,exports){
+'use strict';
+
+
+var TYPED_OK =  (typeof Uint8Array !== 'undefined') &&
+                (typeof Uint16Array !== 'undefined') &&
+                (typeof Int32Array !== 'undefined');
+
+
+exports.assign = function (obj /*from1, from2, from3, ...*/) {
+  var sources = Array.prototype.slice.call(arguments, 1);
+  while (sources.length) {
+    var source = sources.shift();
+    if (!source) { continue; }
+
+    if (typeof(source) !== 'object') {
+      throw new TypeError(source + 'must be non-object');
+    }
+
+    for (var p in source) {
+      if (source.hasOwnProperty(p)) {
+        obj[p] = source[p];
+      }
+    }
+  }
+
+  return obj;
+};
+
+
+// reduce buffer size, avoiding mem copy
+exports.shrinkBuf = function (buf, size) {
+  if (buf.length === size) { return buf; }
+  if (buf.subarray) { return buf.subarray(0, size); }
+  buf.length = size;
+  return buf;
+};
+
+
+var fnTyped = {
+  arraySet: function (dest, src, src_offs, len, dest_offs) {
+    if (src.subarray && dest.subarray) {
+      dest.set(src.subarray(src_offs, src_offs+len), dest_offs);
+      return;
+    }
+    // Fallback to ordinary array
+    for(var i=0; i<len; i++) {
+      dest[dest_offs + i] = src[src_offs + i];
+    }
+  },
+  // Join array of chunks to single array.
+  flattenChunks: function(chunks) {
+    var i, l, len, pos, chunk, result;
+
+    // calculate data length
+    len = 0;
+    for (i=0, l=chunks.length; i<l; i++) {
+      len += chunks[i].length;
+    }
+
+    // join chunks
+    result = new Uint8Array(len);
+    pos = 0;
+    for (i=0, l=chunks.length; i<l; i++) {
+      chunk = chunks[i];
+      result.set(chunk, pos);
+      pos += chunk.length;
+    }
+
+    return result;
+  }
+};
+
+var fnUntyped = {
+  arraySet: function (dest, src, src_offs, len, dest_offs) {
+    for(var i=0; i<len; i++) {
+      dest[dest_offs + i] = src[src_offs + i];
+    }
+  },
+  // Join array of chunks to single array.
+  flattenChunks: function(chunks) {
+    return [].concat.apply([], chunks);
+  }
+};
+
+
+// Enable/Disable typed arrays use, for testing
+//
+exports.setTyped = function (on) {
+  if (on) {
+    exports.Buf8  = Uint8Array;
+    exports.Buf16 = Uint16Array;
+    exports.Buf32 = Int32Array;
+    exports.assign(exports, fnTyped);
+  } else {
+    exports.Buf8  = Array;
+    exports.Buf16 = Array;
+    exports.Buf32 = Array;
+    exports.assign(exports, fnUntyped);
+  }
+};
+
+exports.setTyped(TYPED_OK);
+},{}],3:[function(_dereq_,module,exports){
+// String encode/decode helpers
+'use strict';
+
+
+var utils = _dereq_('./common');
+
+
+// Quick check if we can use fast array to bin string conversion
+//
+// - apply(Array) can fail on Android 2.2
+// - apply(Uint8Array) can fail on iOS 5.1 Safary
+//
+var STR_APPLY_OK = true;
+var STR_APPLY_UIA_OK = true;
+
+try { String.fromCharCode.apply(null, [0]); } catch(__) { STR_APPLY_OK = false; }
+try { String.fromCharCode.apply(null, new Uint8Array(1)); } catch(__) { STR_APPLY_UIA_OK = false; }
+
+
+// Table with utf8 lengths (calculated by first byte of sequence)
+// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS,
+// because max possible codepoint is 0x10ffff
+var _utf8len = new utils.Buf8(256);
+for (var i=0; i<256; i++) {
+  _utf8len[i] = (i >= 252 ? 6 : i >= 248 ? 5 : i >= 240 ? 4 : i >= 224 ? 3 : i >= 192 ? 2 : 1);
+}
+_utf8len[254]=_utf8len[254]=1; // Invalid sequence start
+
+
+// convert string to array (typed, when possible)
+exports.string2buf = function (str) {
+  var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0;
+
+  // count binary size
+  for (m_pos = 0; m_pos < str_len; m_pos++) {
+    c = str.charCodeAt(m_pos);
+    if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) {
+      c2 = str.charCodeAt(m_pos+1);
+      if ((c2 & 0xfc00) === 0xdc00) {
+        c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
+        m_pos++;
+      }
+    }
+    buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;
+  }
+
+  // allocate buffer
+  buf = new utils.Buf8(buf_len);
+
+  // convert
+  for (i=0, m_pos = 0; i < buf_len; m_pos++) {
+    c = str.charCodeAt(m_pos);
+    if ((c & 0xfc00) === 0xd800 && (m_pos+1 < str_len)) {
+      c2 = str.charCodeAt(m_pos+1);
+      if ((c2 & 0xfc00) === 0xdc00) {
+        c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
+        m_pos++;
+      }
+    }
+    if (c < 0x80) {
+      /* one byte */
+      buf[i++] = c;
+    } else if (c < 0x800) {
+      /* two bytes */
+      buf[i++] = 0xC0 | (c >>> 6);
+      buf[i++] = 0x80 | (c & 0x3f);
+    } else if (c < 0x10000) {
+      /* three bytes */
+      buf[i++] = 0xE0 | (c >>> 12);
+      buf[i++] = 0x80 | (c >>> 6 & 0x3f);
+      buf[i++] = 0x80 | (c & 0x3f);
+    } else {
+      /* four bytes */
+      buf[i++] = 0xf0 | (c >>> 18);
+      buf[i++] = 0x80 | (c >>> 12 & 0x3f);
+      buf[i++] = 0x80 | (c >>> 6 & 0x3f);
+      buf[i++] = 0x80 | (c & 0x3f);
+    }
+  }
+
+  return buf;
+};
+
+// Helper (used in 2 places)
+function buf2binstring(buf, len) {
+  // use fallback for big arrays to avoid stack overflow
+  if (len < 65537) {
+    if ((buf.subarray && STR_APPLY_UIA_OK) || (!buf.subarray && STR_APPLY_OK)) {
+      return String.fromCharCode.apply(null, utils.shrinkBuf(buf, len));
+    }
+  }
+
+  var result = '';
+  for(var i=0; i < len; i++) {
+    result += String.fromCharCode(buf[i]);
+  }
+  return result;
+}
+
+
+// Convert byte array to binary string
+exports.buf2binstring = function(buf) {
+  return buf2binstring(buf, buf.length);
+};
+
+
+// Convert binary string (typed, when possible)
+exports.binstring2buf = function(str) {
+  var buf = new utils.Buf8(str.length);
+  for(var i=0, len=buf.length; i < len; i++) {
+    buf[i] = str.charCodeAt(i);
+  }
+  return buf;
+};
+
+
+// convert array to string
+exports.buf2string = function (buf, max) {
+  var i, out, c, c_len;
+  var len = max || buf.length;
+
+  // Reserve max possible length (2 words per char)
+  // NB: by unknown reasons, Array is significantly faster for
+  //     String.fromCharCode.apply than Uint16Array.
+  var utf16buf = new Array(len*2);
+
+  for (out=0, i=0; i<len;) {
+    c = buf[i++];
+    // quick process ascii
+    if (c < 0x80) { utf16buf[out++] = c; continue; }
+
+    c_len = _utf8len[c];
+    // skip 5 & 6 byte codes
+    if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len-1; continue; }
+
+    // apply mask on first byte
+    c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07;
+    // join the rest
+    while (c_len > 1 && i < len) {
+      c = (c << 6) | (buf[i++] & 0x3f);
+      c_len--;
+    }
+
+    // terminated by end of string?
+    if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; }
+
+    if (c < 0x10000) {
+      utf16buf[out++] = c;
+    } else {
+      c -= 0x10000;
+      utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff);
+      utf16buf[out++] = 0xdc00 | (c & 0x3ff);
+    }
+  }
+
+  return buf2binstring(utf16buf, out);
+};
+
+
+// Calculate max possible position in utf8 buffer,
+// that will not break sequence. If that's not possible
+// - (very small limits) return max size as is.
+//
+// buf[] - utf8 bytes array
+// max   - length limit (mandatory);
+exports.utf8border = function(buf, max) {
+  var pos;
+
+  max = max || buf.length;
+  if (max > buf.length) { max = buf.length; }
+
+  // go back from last position, until start of sequence found
+  pos = max-1;
+  while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; }
+
+  // Fuckup - very small and broken sequence,
+  // return max, because we should return something anyway.
+  if (pos < 0) { return max; }
+
+  // If we came to start of buffer - that means vuffer is too small,
+  // return max too.
+  if (pos === 0) { return max; }
+
+  return (pos + _utf8len[buf[pos]] > max) ? pos : max;
+};
+
+},{"./common":2}],4:[function(_dereq_,module,exports){
+'use strict';
+
+// Note: adler32 takes 12% for level 0 and 2% for level 6.
+// It doesn't worth to make additional optimizationa as in original.
+// Small size is preferable.
+
+function adler32(adler, buf, len, pos) {
+  var s1 = (adler & 0xffff) |0
+    , s2 = ((adler >>> 16) & 0xffff) |0
+    , n = 0;
+
+  while (len !== 0) {
+    // Set limit ~ twice less than 5552, to keep
+    // s2 in 31-bits, because we force signed ints.
+    // in other case %= will fail.
+    n = len > 2000 ? 2000 : len;
+    len -= n;
+
+    do {
+      s1 = (s1 + buf[pos++]) |0;
+      s2 = (s2 + s1) |0;
+    } while (--n);
+
+    s1 %= 65521;
+    s2 %= 65521;
+  }
+
+  return (s1 | (s2 << 16)) |0;
+}
+
+
+module.exports = adler32;
+},{}],5:[function(_dereq_,module,exports){
+module.exports = {
+
+  /* Allowed flush values; see deflate() and inflate() below for details */
+  Z_NO_FLUSH:         0,
+  Z_PARTIAL_FLUSH:    1,
+  Z_SYNC_FLUSH:       2,
+  Z_FULL_FLUSH:       3,
+  Z_FINISH:           4,
+  Z_BLOCK:            5,
+  Z_TREES:            6,
+
+  /* Return codes for the compression/decompression functions. Negative values
+  * are errors, positive values are used for special but normal events.
+  */
+  Z_OK:               0,
+  Z_STREAM_END:       1,
+  Z_NEED_DICT:        2,
+  Z_ERRNO:           -1,
+  Z_STREAM_ERROR:    -2,
+  Z_DATA_ERROR:      -3,
+  //Z_MEM_ERROR:     -4,
+  Z_BUF_ERROR:       -5,
+  //Z_VERSION_ERROR: -6,
+
+  /* compression levels */
+  Z_NO_COMPRESSION:         0,
+  Z_BEST_SPEED:             1,
+  Z_BEST_COMPRESSION:       9,
+  Z_DEFAULT_COMPRESSION:   -1,
+
+
+  Z_FILTERED:               1,
+  Z_HUFFMAN_ONLY:           2,
+  Z_RLE:                    3,
+  Z_FIXED:                  4,
+  Z_DEFAULT_STRATEGY:       0,
+
+  /* Possible values of the data_type field (though see inflate()) */
+  Z_BINARY:                 0,
+  Z_TEXT:                   1,
+  //Z_ASCII:                1, // = Z_TEXT (deprecated)
+  Z_UNKNOWN:                2,
+
+  /* The deflate compression method */
+  Z_DEFLATED:               8
+  //Z_NULL:                 null // Use -1 or null inline, depending on var type
+};
+},{}],6:[function(_dereq_,module,exports){
+'use strict';
+
+// Note: we can't get significant speed boost here.
+// So write code to minimize size - no pregenerated tables
+// and array tools dependencies.
+
+
+// Use ordinary array, since untyped makes no boost here
+function makeTable() {
+  var c, table = [];
+
+  for(var n =0; n < 256; n++){
+    c = n;
+    for(var k =0; k < 8; k++){
+      c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
+    }
+    table[n] = c;
+  }
+
+  return table;
+}
+
+// Create table on load. Just 255 signed longs. Not a problem.
+var crcTable = makeTable();
+
+
+function crc32(crc, buf, len, pos) {
+  var t = crcTable
+    , end = pos + len;
+
+  crc = crc ^ (-1);
+
+  for (var i = pos; i < end; i++ ) {
+    crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF];
+  }
+
+  return (crc ^ (-1)); // >>> 0;
+}
+
+
+module.exports = crc32;
+},{}],7:[function(_dereq_,module,exports){
+'use strict';
+
+
+function GZheader() {
+  /* true if compressed data believed to be text */
+  this.text       = 0;
+  /* modification time */
+  this.time       = 0;
+  /* extra flags (not used when writing a gzip file) */
+  this.xflags     = 0;
+  /* operating system */
+  this.os         = 0;
+  /* pointer to extra field or Z_NULL if none */
+  this.extra      = null;
+  /* extra field length (valid if extra != Z_NULL) */
+  this.extra_len  = 0; // Actually, we don't need it in JS,
+                       // but leave for few code modifications
+
+  //
+  // Setup limits is not necessary because in js we should not preallocate memory 
+  // for inflate use constant limit in 65536 bytes
+  //
+
+  /* space at extra (only when reading header) */
+  // this.extra_max  = 0;
+  /* pointer to zero-terminated file name or Z_NULL */
+  this.name       = '';
+  /* space at name (only when reading header) */
+  // this.name_max   = 0;
+  /* pointer to zero-terminated comment or Z_NULL */
+  this.comment    = '';
+  /* space at comment (only when reading header) */
+  // this.comm_max   = 0;
+  /* true if there was or will be a header crc */
+  this.hcrc       = 0;
+  /* true when done reading gzip header (not used when writing a gzip file) */
+  this.done       = false;
+}
+
+module.exports = GZheader;
+},{}],8:[function(_dereq_,module,exports){
+'use strict';
+
+// See state defs from inflate.js
+var BAD = 30;       /* got a data error -- remain here until reset */
+var TYPE = 12;      /* i: waiting for type bits, including last-flag bit */
+
+/*
+   Decode literal, length, and distance codes and write out the resulting
+   literal and match bytes until either not enough input or output is
+   available, an end-of-block is encountered, or a data error is encountered.
+   When large enough input and output buffers are supplied to inflate(), for
+   example, a 16K input buffer and a 64K output buffer, more than 95% of the
+   inflate execution time is spent in this routine.
+
+   Entry assumptions:
+
+        state.mode === LEN
+        strm.avail_in >= 6
+        strm.avail_out >= 258
+        start >= strm.avail_out
+        state.bits < 8
+
+   On return, state.mode is one of:
+
+        LEN -- ran out of enough output space or enough available input
+        TYPE -- reached end of block code, inflate() to interpret next block
+        BAD -- error in block data
+
+   Notes:
+
+    - The maximum input bits used by a length/distance pair is 15 bits for the
+      length code, 5 bits for the length extra, 15 bits for the distance code,
+      and 13 bits for the distance extra.  This totals 48 bits, or six bytes.
+      Therefore if strm.avail_in >= 6, then there is enough input to avoid
+      checking for available input while decoding.
+
+    - The maximum bytes that a single length/distance pair can output is 258
+      bytes, which is the maximum length that can be coded.  inflate_fast()
+      requires strm.avail_out >= 258 for each loop to avoid checking for
+      output space.
+ */
+module.exports = function inflate_fast(strm, start) {
+  var state;
+  var _in;                    /* local strm.input */
+  var last;                   /* have enough input while in < last */
+  var _out;                   /* local strm.output */
+  var beg;                    /* inflate()'s initial strm.output */
+  var end;                    /* while out < end, enough space available */
+//#ifdef INFLATE_STRICT
+  var dmax;                   /* maximum distance from zlib header */
+//#endif
+  var wsize;                  /* window size or zero if not using window */
+  var whave;                  /* valid bytes in the window */
+  var wnext;                  /* window write index */
+  var window;                 /* allocated sliding window, if wsize != 0 */
+  var hold;                   /* local strm.hold */
+  var bits;                   /* local strm.bits */
+  var lcode;                  /* local strm.lencode */
+  var dcode;                  /* local strm.distcode */
+  var lmask;                  /* mask for first level of length codes */
+  var dmask;                  /* mask for first level of distance codes */
+  var here;                   /* retrieved table entry */
+  var op;                     /* code bits, operation, extra bits, or */
+                              /*  window position, window bytes to copy */
+  var len;                    /* match length, unused bytes */
+  var dist;                   /* match distance */
+  var from;                   /* where to copy match from */
+  var from_source;
+
+
+  var input, output; // JS specific, because we have no pointers
+
+  /* copy state to local variables */
+  state = strm.state;
+  //here = state.here;
+  _in = strm.next_in;
+  input = strm.input;
+  last = _in + (strm.avail_in - 5);
+  _out = strm.next_out;
+  output = strm.output;
+  beg = _out - (start - strm.avail_out);
+  end = _out + (strm.avail_out - 257);
+//#ifdef INFLATE_STRICT
+  dmax = state.dmax;
+//#endif
+  wsize = state.wsize;
+  whave = state.whave;
+  wnext = state.wnext;
+  window = state.window;
+  hold = state.hold;
+  bits = state.bits;
+  lcode = state.lencode;
+  dcode = state.distcode;
+  lmask = (1 << state.lenbits) - 1;
+  dmask = (1 << state.distbits) - 1;
+
+
+  /* decode literals and length/distances until end-of-block or not enough
+     input data or output space */
+
+  top:
+  do {
+    if (bits < 15) {
+      hold += input[_in++] << bits;
+      bits += 8;
+      hold += input[_in++] << bits;
+      bits += 8;
+    }
+
+    here = lcode[hold & lmask];
+
+    dolen:
+    for (;;) { // Goto emulation
+      op = here >>> 24/*here.bits*/;
+      hold >>>= op;
+      bits -= op;
+      op = (here >>> 16) & 0xff/*here.op*/;
+      if (op === 0) {                          /* literal */
+        //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+        //        "inflate:         literal '%c'\n" :
+        //        "inflate:         literal 0x%02x\n", here.val));
+        output[_out++] = here & 0xffff/*here.val*/;
+      }
+      else if (op & 16) {                     /* length base */
+        len = here & 0xffff/*here.val*/;
+        op &= 15;                           /* number of extra bits */
+        if (op) {
+          if (bits < op) {
+            hold += input[_in++] << bits;
+            bits += 8;
+          }
+          len += hold & ((1 << op) - 1);
+          hold >>>= op;
+          bits -= op;
+        }
+        //Tracevv((stderr, "inflate:         length %u\n", len));
+        if (bits < 15) {
+          hold += input[_in++] << bits;
+          bits += 8;
+          hold += input[_in++] << bits;
+          bits += 8;
+        }
+        here = dcode[hold & dmask];
+
+        dodist:
+        for (;;) { // goto emulation
+          op = here >>> 24/*here.bits*/;
+          hold >>>= op;
+          bits -= op;
+          op = (here >>> 16) & 0xff/*here.op*/;
+
+          if (op & 16) {                      /* distance base */
+            dist = here & 0xffff/*here.val*/;
+            op &= 15;                       /* number of extra bits */
+            if (bits < op) {
+              hold += input[_in++] << bits;
+              bits += 8;
+              if (bits < op) {
+                hold += input[_in++] << bits;
+                bits += 8;
+              }
+            }
+            dist += hold & ((1 << op) - 1);
+//#ifdef INFLATE_STRICT
+            if (dist > dmax) {
+              strm.msg = 'invalid distance too far back';
+              state.mode = BAD;
+              break top;
+            }
+//#endif
+            hold >>>= op;
+            bits -= op;
+            //Tracevv((stderr, "inflate:         distance %u\n", dist));
+            op = _out - beg;                /* max distance in output */
+            if (dist > op) {                /* see if copy from window */
+              op = dist - op;               /* distance back in window */
+              if (op > whave) {
+                if (state.sane) {
+                  strm.msg = 'invalid distance too far back';
+                  state.mode = BAD;
+                  break top;
+                }
+
+// (!) This block is disabled in zlib defailts,
+// don't enable it for binary compatibility
+//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+//                if (len <= op - whave) {
+//                  do {
+//                    output[_out++] = 0;
+//                  } while (--len);
+//                  continue top;
+//                }
+//                len -= op - whave;
+//                do {
+//                  output[_out++] = 0;
+//                } while (--op > whave);
+//                if (op === 0) {
+//                  from = _out - dist;
+//                  do {
+//                    output[_out++] = output[from++];
+//                  } while (--len);
+//                  continue top;
+//                }
+//#endif
+              }
+              from = 0; // window index
+              from_source = window;
+              if (wnext === 0) {           /* very common case */
+                from += wsize - op;
+                if (op < len) {         /* some from window */
+                  len -= op;
+                  do {
+                    output[_out++] = window[from++];
+                  } while (--op);
+                  from = _out - dist;  /* rest from output */
+                  from_source = output;
+                }
+              }
+              else if (wnext < op) {      /* wrap around window */
+                from += wsize + wnext - op;
+                op -= wnext;
+                if (op < len) {         /* some from end of window */
+                  len -= op;
+                  do {
+                    output[_out++] = window[from++];
+                  } while (--op);
+                  from = 0;
+                  if (wnext < len) {  /* some from start of window */
+                    op = wnext;
+                    len -= op;
+                    do {
+                      output[_out++] = window[from++];
+                    } while (--op);
+                    from = _out - dist;      /* rest from output */
+                    from_source = output;
+                  }
+                }
+              }
+              else {                      /* contiguous in window */
+                from += wnext - op;
+                if (op < len) {         /* some from window */
+                  len -= op;
+                  do {
+                    output[_out++] = window[from++];
+                  } while (--op);
+                  from = _out - dist;  /* rest from output */
+                  from_source = output;
+                }
+              }
+              while (len > 2) {
+                output[_out++] = from_source[from++];
+                output[_out++] = from_source[from++];
+                output[_out++] = from_source[from++];
+                len -= 3;
+              }
+              if (len) {
+                output[_out++] = from_source[from++];
+                if (len > 1) {
+                  output[_out++] = from_source[from++];
+                }
+              }
+            }
+            else {
+              from = _out - dist;          /* copy direct from output */
+              do {                        /* minimum length is three */
+                output[_out++] = output[from++];
+                output[_out++] = output[from++];
+                output[_out++] = output[from++];
+                len -= 3;
+              } while (len > 2);
+              if (len) {
+                output[_out++] = output[from++];
+                if (len > 1) {
+                  output[_out++] = output[from++];
+                }
+              }
+            }
+          }
+          else if ((op & 64) === 0) {          /* 2nd level distance code */
+            here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
+            continue dodist;
+          }
+          else {
+            strm.msg = 'invalid distance code';
+            state.mode = BAD;
+            break top;
+          }
+
+          break; // need to emulate goto via "continue"
+        }
+      }
+      else if ((op & 64) === 0) {              /* 2nd level length code */
+        here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
+        continue dolen;
+      }
+      else if (op & 32) {                     /* end-of-block */
+        //Tracevv((stderr, "inflate:         end of block\n"));
+        state.mode = TYPE;
+        break top;
+      }
+      else {
+        strm.msg = 'invalid literal/length code';
+        state.mode = BAD;
+        break top;
+      }
+
+      break; // need to emulate goto via "continue"
+    }
+  } while (_in < last && _out < end);
+
+  /* return unused bytes (on entry, bits < 8, so in won't go too far back) */
+  len = bits >> 3;
+  _in -= len;
+  bits -= len << 3;
+  hold &= (1 << bits) - 1;
+
+  /* update state and return */
+  strm.next_in = _in;
+  strm.next_out = _out;
+  strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last));
+  strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end));
+  state.hold = hold;
+  state.bits = bits;
+  return;
+};
+
+},{}],9:[function(_dereq_,module,exports){
+'use strict';
+
+
+var utils = _dereq_('../utils/common');
+var adler32 = _dereq_('./adler32');
+var crc32   = _dereq_('./crc32');
+var inflate_fast = _dereq_('./inffast');
+var inflate_table = _dereq_('./inftrees');
+
+var CODES = 0;
+var LENS = 1;
+var DISTS = 2;
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+
+/* Allowed flush values; see deflate() and inflate() below for details */
+//var Z_NO_FLUSH      = 0;
+//var Z_PARTIAL_FLUSH = 1;
+//var Z_SYNC_FLUSH    = 2;
+//var Z_FULL_FLUSH    = 3;
+var Z_FINISH        = 4;
+var Z_BLOCK         = 5;
+var Z_TREES         = 6;
+
+
+/* Return codes for the compression/decompression functions. Negative values
+ * are errors, positive values are used for special but normal events.
+ */
+var Z_OK            = 0;
+var Z_STREAM_END    = 1;
+var Z_NEED_DICT     = 2;
+//var Z_ERRNO         = -1;
+var Z_STREAM_ERROR  = -2;
+var Z_DATA_ERROR    = -3;
+var Z_MEM_ERROR     = -4;
+var Z_BUF_ERROR     = -5;
+//var Z_VERSION_ERROR = -6;
+
+/* The deflate compression method */
+var Z_DEFLATED  = 8;
+
+
+/* STATES ====================================================================*/
+/* ===========================================================================*/
+
+
+var    HEAD = 1;       /* i: waiting for magic header */
+var    FLAGS = 2;      /* i: waiting for method and flags (gzip) */
+var    TIME = 3;       /* i: waiting for modification time (gzip) */
+var    OS = 4;         /* i: waiting for extra flags and operating system (gzip) */
+var    EXLEN = 5;      /* i: waiting for extra length (gzip) */
+var    EXTRA = 6;      /* i: waiting for extra bytes (gzip) */
+var    NAME = 7;       /* i: waiting for end of file name (gzip) */
+var    COMMENT = 8;    /* i: waiting for end of comment (gzip) */
+var    HCRC = 9;       /* i: waiting for header crc (gzip) */
+var    DICTID = 10;    /* i: waiting for dictionary check value */
+var    DICT = 11;      /* waiting for inflateSetDictionary() call */
+var        TYPE = 12;      /* i: waiting for type bits, including last-flag bit */
+var        TYPEDO = 13;    /* i: same, but skip check to exit inflate on new block */
+var        STORED = 14;    /* i: waiting for stored size (length and complement) */
+var        COPY_ = 15;     /* i/o: same as COPY below, but only first time in */
+var        COPY = 16;      /* i/o: waiting for input or output to copy stored block */
+var        TABLE = 17;     /* i: waiting for dynamic block table lengths */
+var        LENLENS = 18;   /* i: waiting for code length code lengths */
+var        CODELENS = 19;  /* i: waiting for length/lit and distance code lengths */
+var            LEN_ = 20;      /* i: same as LEN below, but only first time in */
+var            LEN = 21;       /* i: waiting for length/lit/eob code */
+var            LENEXT = 22;    /* i: waiting for length extra bits */
+var            DIST = 23;      /* i: waiting for distance code */
+var            DISTEXT = 24;   /* i: waiting for distance extra bits */
+var            MATCH = 25;     /* o: waiting for output space to copy string */
+var            LIT = 26;       /* o: waiting for output space to write literal */
+var    CHECK = 27;     /* i: waiting for 32-bit check value */
+var    LENGTH = 28;    /* i: waiting for 32-bit length (gzip) */
+var    DONE = 29;      /* finished check, done -- remain here until reset */
+var    BAD = 30;       /* got a data error -- remain here until reset */
+var    MEM = 31;       /* got an inflate() memory error -- remain here until reset */
+var    SYNC = 32;      /* looking for synchronization bytes to restart inflate() */
+
+/* ===========================================================================*/
+
+
+
+var ENOUGH_LENS = 852;
+var ENOUGH_DISTS = 592;
+//var ENOUGH =  (ENOUGH_LENS+ENOUGH_DISTS);
+
+var MAX_WBITS = 15;
+/* 32K LZ77 window */
+var DEF_WBITS = MAX_WBITS;
+
+
+function ZSWAP32(q) {
+  return  (((q >>> 24) & 0xff) +
+          ((q >>> 8) & 0xff00) +
+          ((q & 0xff00) << 8) +
+          ((q & 0xff) << 24));
+}
+
+
+function InflateState() {
+  this.mode = 0;             /* current inflate mode */
+  this.last = false;          /* true if processing last block */
+  this.wrap = 0;              /* bit 0 true for zlib, bit 1 true for gzip */
+  this.havedict = false;      /* true if dictionary provided */
+  this.flags = 0;             /* gzip header method and flags (0 if zlib) */
+  this.dmax = 0;              /* zlib header max distance (INFLATE_STRICT) */
+  this.check = 0;             /* protected copy of check value */
+  this.total = 0;             /* protected copy of output count */
+  // TODO: may be {}
+  this.head = null;           /* where to save gzip header information */
+
+  /* sliding window */
+  this.wbits = 0;             /* log base 2 of requested window size */
+  this.wsize = 0;             /* window size or zero if not using window */
+  this.whave = 0;             /* valid bytes in the window */
+  this.wnext = 0;             /* window write index */
+  this.window = null;         /* allocated sliding window, if needed */
+
+  /* bit accumulator */
+  this.hold = 0;              /* input bit accumulator */
+  this.bits = 0;              /* number of bits in "in" */
+
+  /* for string and stored block copying */
+  this.length = 0;            /* literal or length of data to copy */
+  this.offset = 0;            /* distance back to copy string from */
+
+  /* for table and code decoding */
+  this.extra = 0;             /* extra bits needed */
+
+  /* fixed and dynamic code tables */
+  this.lencode = null;          /* starting table for length/literal codes */
+  this.distcode = null;         /* starting table for distance codes */
+  this.lenbits = 0;           /* index bits for lencode */
+  this.distbits = 0;          /* index bits for distcode */
+
+  /* dynamic table building */
+  this.ncode = 0;             /* number of code length code lengths */
+  this.nlen = 0;              /* number of length code lengths */
+  this.ndist = 0;             /* number of distance code lengths */
+  this.have = 0;              /* number of code lengths in lens[] */
+  this.next = null;              /* next available space in codes[] */
+
+  this.lens = new utils.Buf16(320); /* temporary storage for code lengths */
+  this.work = new utils.Buf16(288); /* work area for code table building */
+
+  /*
+   because we don't have pointers in js, we use lencode and distcode directly
+   as buffers so we don't need codes
+  */
+  //this.codes = new utils.Buf32(ENOUGH);       /* space for code tables */
+  this.lendyn = null;              /* dynamic table for length/literal codes (JS specific) */
+  this.distdyn = null;             /* dynamic table for distance codes (JS specific) */
+  this.sane = 0;                   /* if false, allow invalid distance too far */
+  this.back = 0;                   /* bits back of last unprocessed length/lit */
+  this.was = 0;                    /* initial length of match */
+}
+
+function inflateResetKeep(strm) {
+  var state;
+
+  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+  state = strm.state;
+  strm.total_in = strm.total_out = state.total = 0;
+  strm.msg = ''; /*Z_NULL*/
+  if (state.wrap) {       /* to support ill-conceived Java test suite */
+    strm.adler = state.wrap & 1;
+  }
+  state.mode = HEAD;
+  state.last = 0;
+  state.havedict = 0;
+  state.dmax = 32768;
+  state.head = null/*Z_NULL*/;
+  state.hold = 0;
+  state.bits = 0;
+  //state.lencode = state.distcode = state.next = state.codes;
+  state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS);
+  state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS);
+
+  state.sane = 1;
+  state.back = -1;
+  //Tracev((stderr, "inflate: reset\n"));
+  return Z_OK;
+}
+
+function inflateReset(strm) {
+  var state;
+
+  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+  state = strm.state;
+  state.wsize = 0;
+  state.whave = 0;
+  state.wnext = 0;
+  return inflateResetKeep(strm);
+
+}
+
+function inflateReset2(strm, windowBits) {
+  var wrap;
+  var state;
+
+  /* get the state */
+  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+  state = strm.state;
+
+  /* extract wrap request from windowBits parameter */
+  if (windowBits < 0) {
+    wrap = 0;
+    windowBits = -windowBits;
+  }
+  else {
+    wrap = (windowBits >> 4) + 1;
+    if (windowBits < 48) {
+      windowBits &= 15;
+    }
+  }
+
+  /* set number of window bits, free window if different */
+  if (windowBits && (windowBits < 8 || windowBits > 15)) {
+    return Z_STREAM_ERROR;
+  }
+  if (state.window !== null && state.wbits !== windowBits) {
+    state.window = null;
+  }
+
+  /* update state and reset the rest of it */
+  state.wrap = wrap;
+  state.wbits = windowBits;
+  return inflateReset(strm);
+}
+
+function inflateInit2(strm, windowBits) {
+  var ret;
+  var state;
+
+  if (!strm) { return Z_STREAM_ERROR; }
+  //strm.msg = Z_NULL;                 /* in case we return an error */
+
+  state = new InflateState();
+
+  //if (state === Z_NULL) return Z_MEM_ERROR;
+  //Tracev((stderr, "inflate: allocated\n"));
+  strm.state = state;
+  state.window = null/*Z_NULL*/;
+  ret = inflateReset2(strm, windowBits);
+  if (ret !== Z_OK) {
+    strm.state = null/*Z_NULL*/;
+  }
+  return ret;
+}
+
+function inflateInit(strm) {
+  return inflateInit2(strm, DEF_WBITS);
+}
+
+
+/*
+ Return state with length and distance decoding tables and index sizes set to
+ fixed code decoding.  Normally this returns fixed tables from inffixed.h.
+ If BUILDFIXED is defined, then instead this routine builds the tables the
+ first time it's called, and returns those tables the first time and
+ thereafter.  This reduces the size of the code by about 2K bytes, in
+ exchange for a little execution time.  However, BUILDFIXED should not be
+ used for threaded applications, since the rewriting of the tables and virgin
+ may not be thread-safe.
+ */
+var virgin = true;
+
+var lenfix, distfix; // We have no pointers in JS, so keep tables separate
+
+function fixedtables(state) {
+  /* build fixed huffman tables if first call (may not be thread safe) */
+  if (virgin) {
+    var sym;
+
+    lenfix = new utils.Buf32(512);
+    distfix = new utils.Buf32(32);
+
+    /* literal/length table */
+    sym = 0;
+    while (sym < 144) { state.lens[sym++] = 8; }
+    while (sym < 256) { state.lens[sym++] = 9; }
+    while (sym < 280) { state.lens[sym++] = 7; }
+    while (sym < 288) { state.lens[sym++] = 8; }
+
+    inflate_table(LENS,  state.lens, 0, 288, lenfix,   0, state.work, {bits: 9});
+
+    /* distance table */
+    sym = 0;
+    while (sym < 32) { state.lens[sym++] = 5; }
+
+    inflate_table(DISTS, state.lens, 0, 32,   distfix, 0, state.work, {bits: 5});
+
+    /* do this just once */
+    virgin = false;
+  }
+
+  state.lencode = lenfix;
+  state.lenbits = 9;
+  state.distcode = distfix;
+  state.distbits = 5;
+}
+
+
+/*
+ Update the window with the last wsize (normally 32K) bytes written before
+ returning.  If window does not exist yet, create it.  This is only called
+ when a window is already in use, or when output has been written during this
+ inflate call, but the end of the deflate stream has not been reached yet.
+ It is also called to create a window for dictionary data when a dictionary
+ is loaded.
+
+ Providing output buffers larger than 32K to inflate() should provide a speed
+ advantage, since only the last 32K of output is copied to the sliding window
+ upon return from inflate(), and since all distances after the first 32K of
+ output will fall in the output data, making match copies simpler and faster.
+ The advantage may be dependent on the size of the processor's data caches.
+ */
+function updatewindow(strm, src, end, copy) {
+  var dist;
+  var state = strm.state;
+
+  /* if it hasn't been done already, allocate space for the window */
+  if (state.window === null) {
+    state.wsize = 1 << state.wbits;
+    state.wnext = 0;
+    state.whave = 0;
+
+    state.window = new utils.Buf8(state.wsize);
+  }
+
+  /* copy state->wsize or less output bytes into the circular window */
+  if (copy >= state.wsize) {
+    utils.arraySet(state.window,src, end - state.wsize, state.wsize, 0);
+    state.wnext = 0;
+    state.whave = state.wsize;
+  }
+  else {
+    dist = state.wsize - state.wnext;
+    if (dist > copy) {
+      dist = copy;
+    }
+    //zmemcpy(state->window + state->wnext, end - copy, dist);
+    utils.arraySet(state.window,src, end - copy, dist, state.wnext);
+    copy -= dist;
+    if (copy) {
+      //zmemcpy(state->window, end - copy, copy);
+      utils.arraySet(state.window,src, end - copy, copy, 0);
+      state.wnext = copy;
+      state.whave = state.wsize;
+    }
+    else {
+      state.wnext += dist;
+      if (state.wnext === state.wsize) { state.wnext = 0; }
+      if (state.whave < state.wsize) { state.whave += dist; }
+    }
+  }
+  return 0;
+}
+
+function inflate(strm, flush) {
+  var state;
+  var input, output;          // input/output buffers
+  var next;                   /* next input INDEX */
+  var put;                    /* next output INDEX */
+  var have, left;             /* available input and output */
+  var hold;                   /* bit buffer */
+  var bits;                   /* bits in bit buffer */
+  var _in, _out;              /* save starting available input and output */
+  var copy;                   /* number of stored or match bytes to copy */
+  var from;                   /* where to copy match bytes from */
+  var from_source;
+  var here = 0;               /* current decoding table entry */
+  var here_bits, here_op, here_val; // paked "here" denormalized (JS specific)
+  //var last;                   /* parent table entry */
+  var last_bits, last_op, last_val; // paked "last" denormalized (JS specific)
+  var len;                    /* length to copy for repeats, bits to drop */
+  var ret;                    /* return code */
+  var hbuf = new utils.Buf8(4);    /* buffer for gzip header crc calculation */
+  var opts;
+
+  var n; // temporary var for NEED_BITS
+
+  var order = /* permutation of code lengths */
+    [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
+
+
+  if (!strm || !strm.state || !strm.output ||
+      (!strm.input && strm.avail_in !== 0)) {
+    return Z_STREAM_ERROR;
+  }
+
+  state = strm.state;
+  if (state.mode === TYPE) { state.mode = TYPEDO; }    /* skip check */
+
+
+  //--- LOAD() ---
+  put = strm.next_out;
+  output = strm.output;
+  left = strm.avail_out;
+  next = strm.next_in;
+  input = strm.input;
+  have = strm.avail_in;
+  hold = state.hold;
+  bits = state.bits;
+  //---
+
+  _in = have;
+  _out = left;
+  ret = Z_OK;
+
+  inf_leave: // goto emulation
+  for (;;) {
+    switch (state.mode) {
+    case HEAD:
+      if (state.wrap === 0) {
+        state.mode = TYPEDO;
+        break;
+      }
+      //=== NEEDBITS(16);
+      while (bits < 16) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      if ((state.wrap & 2) && hold === 0x8b1f) {  /* gzip header */
+        state.check = 0/*crc32(0L, Z_NULL, 0)*/;
+        //=== CRC2(state.check, hold);
+        hbuf[0] = hold & 0xff;
+        hbuf[1] = (hold >>> 8) & 0xff;
+        state.check = crc32(state.check, hbuf, 2, 0);
+        //===//
+
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+        state.mode = FLAGS;
+        break;
+      }
+      state.flags = 0;           /* expect zlib header */
+      if (state.head) {
+        state.head.done = false;
+      }
+      if (!(state.wrap & 1) ||   /* check if zlib header allowed */
+        (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) {
+        strm.msg = 'incorrect header check';
+        state.mode = BAD;
+        break;
+      }
+      if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) {
+        strm.msg = 'unknown compression method';
+        state.mode = BAD;
+        break;
+      }
+      //--- DROPBITS(4) ---//
+      hold >>>= 4;
+      bits -= 4;
+      //---//
+      len = (hold & 0x0f)/*BITS(4)*/ + 8;
+      if (state.wbits === 0) {
+        state.wbits = len;
+      }
+      else if (len > state.wbits) {
+        strm.msg = 'invalid window size';
+        state.mode = BAD;
+        break;
+      }
+      state.dmax = 1 << len;
+      //Tracev((stderr, "inflate:   zlib header ok\n"));
+      strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
+      state.mode = hold & 0x200 ? DICTID : TYPE;
+      //=== INITBITS();
+      hold = 0;
+      bits = 0;
+      //===//
+      break;
+    case FLAGS:
+      //=== NEEDBITS(16); */
+      while (bits < 16) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      state.flags = hold;
+      if ((state.flags & 0xff) !== Z_DEFLATED) {
+        strm.msg = 'unknown compression method';
+        state.mode = BAD;
+        break;
+      }
+      if (state.flags & 0xe000) {
+        strm.msg = 'unknown header flags set';
+        state.mode = BAD;
+        break;
+      }
+      if (state.head) {
+        state.head.text = ((hold >> 8) & 1);
+      }
+      if (state.flags & 0x0200) {
+        //=== CRC2(state.check, hold);
+        hbuf[0] = hold & 0xff;
+        hbuf[1] = (hold >>> 8) & 0xff;
+        state.check = crc32(state.check, hbuf, 2, 0);
+        //===//
+      }
+      //=== INITBITS();
+      hold = 0;
+      bits = 0;
+      //===//
+      state.mode = TIME;
+      /* falls through */
+    case TIME:
+      //=== NEEDBITS(32); */
+      while (bits < 32) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      if (state.head) {
+        state.head.time = hold;
+      }
+      if (state.flags & 0x0200) {
+        //=== CRC4(state.check, hold)
+        hbuf[0] = hold & 0xff;
+        hbuf[1] = (hold >>> 8) & 0xff;
+        hbuf[2] = (hold >>> 16) & 0xff;
+        hbuf[3] = (hold >>> 24) & 0xff;
+        state.check = crc32(state.check, hbuf, 4, 0);
+        //===
+      }
+      //=== INITBITS();
+      hold = 0;
+      bits = 0;
+      //===//
+      state.mode = OS;
+      /* falls through */
+    case OS:
+      //=== NEEDBITS(16); */
+      while (bits < 16) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      if (state.head) {
+        state.head.xflags = (hold & 0xff);
+        state.head.os = (hold >> 8);
+      }
+      if (state.flags & 0x0200) {
+        //=== CRC2(state.check, hold);
+        hbuf[0] = hold & 0xff;
+        hbuf[1] = (hold >>> 8) & 0xff;
+        state.check = crc32(state.check, hbuf, 2, 0);
+        //===//
+      }
+      //=== INITBITS();
+      hold = 0;
+      bits = 0;
+      //===//
+      state.mode = EXLEN;
+      /* falls through */
+    case EXLEN:
+      if (state.flags & 0x0400) {
+        //=== NEEDBITS(16); */
+        while (bits < 16) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        state.length = hold;
+        if (state.head) {
+          state.head.extra_len = hold;
+        }
+        if (state.flags & 0x0200) {
+          //=== CRC2(state.check, hold);
+          hbuf[0] = hold & 0xff;
+          hbuf[1] = (hold >>> 8) & 0xff;
+          state.check = crc32(state.check, hbuf, 2, 0);
+          //===//
+        }
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+      }
+      else if (state.head) {
+        state.head.extra = null/*Z_NULL*/;
+      }
+      state.mode = EXTRA;
+      /* falls through */
+    case EXTRA:
+      if (state.flags & 0x0400) {
+        copy = state.length;
+        if (copy > have) { copy = have; }
+        if (copy) {
+          if (state.head) {
+            len = state.head.extra_len - state.length;
+            if (!state.head.extra) {
+              // Use untyped array for more conveniend processing later
+              state.head.extra = new Array(state.head.extra_len);
+            }
+            utils.arraySet(
+              state.head.extra,
+              input,
+              next,
+              // extra field is limited to 65536 bytes
+              // - no need for additional size check
+              copy,
+              /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/
+              len
+            );
+            //zmemcpy(state.head.extra + len, next,
+            //        len + copy > state.head.extra_max ?
+            //        state.head.extra_max - len : copy);
+          }
+          if (state.flags & 0x0200) {
+            state.check = crc32(state.check, input, copy, next);
+          }
+          have -= copy;
+          next += copy;
+          state.length -= copy;
+        }
+        if (state.length) { break inf_leave; }
+      }
+      state.length = 0;
+      state.mode = NAME;
+      /* falls through */
+    case NAME:
+      if (state.flags & 0x0800) {
+        if (have === 0) { break inf_leave; }
+        copy = 0;
+        do {
+          // TODO: 2 or 1 bytes?
+          len = input[next + copy++];
+          /* use constant limit because in js we should not preallocate memory */
+          if (state.head && len &&
+              (state.length < 65536 /*state.head.name_max*/)) {
+            state.head.name += String.fromCharCode(len);
+          }
+        } while (len && copy < have);
+
+        if (state.flags & 0x0200) {
+          state.check = crc32(state.check, input, copy, next);
+        }
+        have -= copy;
+        next += copy;
+        if (len) { break inf_leave; }
+      }
+      else if (state.head) {
+        state.head.name = null;
+      }
+      state.length = 0;
+      state.mode = COMMENT;
+      /* falls through */
+    case COMMENT:
+      if (state.flags & 0x1000) {
+        if (have === 0) { break inf_leave; }
+        copy = 0;
+        do {
+          len = input[next + copy++];
+          /* use constant limit because in js we should not preallocate memory */
+          if (state.head && len &&
+              (state.length < 65536 /*state.head.comm_max*/)) {
+            state.head.comment += String.fromCharCode(len);
+          }
+        } while (len && copy < have);
+        if (state.flags & 0x0200) {
+          state.check = crc32(state.check, input, copy, next);
+        }
+        have -= copy;
+        next += copy;
+        if (len) { break inf_leave; }
+      }
+      else if (state.head) {
+        state.head.comment = null;
+      }
+      state.mode = HCRC;
+      /* falls through */
+    case HCRC:
+      if (state.flags & 0x0200) {
+        //=== NEEDBITS(16); */
+        while (bits < 16) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        if (hold !== (state.check & 0xffff)) {
+          strm.msg = 'header crc mismatch';
+          state.mode = BAD;
+          break;
+        }
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+      }
+      if (state.head) {
+        state.head.hcrc = ((state.flags >> 9) & 1);
+        state.head.done = true;
+      }
+      strm.adler = state.check = 0 /*crc32(0L, Z_NULL, 0)*/;
+      state.mode = TYPE;
+      break;
+    case DICTID:
+      //=== NEEDBITS(32); */
+      while (bits < 32) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      strm.adler = state.check = ZSWAP32(hold);
+      //=== INITBITS();
+      hold = 0;
+      bits = 0;
+      //===//
+      state.mode = DICT;
+      /* falls through */
+    case DICT:
+      if (state.havedict === 0) {
+        //--- RESTORE() ---
+        strm.next_out = put;
+        strm.avail_out = left;
+        strm.next_in = next;
+        strm.avail_in = have;
+        state.hold = hold;
+        state.bits = bits;
+        //---
+        return Z_NEED_DICT;
+      }
+      strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
+      state.mode = TYPE;
+      /* falls through */
+    case TYPE:
+      if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; }
+      /* falls through */
+    case TYPEDO:
+      if (state.last) {
+        //--- BYTEBITS() ---//
+        hold >>>= bits & 7;
+        bits -= bits & 7;
+        //---//
+        state.mode = CHECK;
+        break;
+      }
+      //=== NEEDBITS(3); */
+      while (bits < 3) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      state.last = (hold & 0x01)/*BITS(1)*/;
+      //--- DROPBITS(1) ---//
+      hold >>>= 1;
+      bits -= 1;
+      //---//
+
+      switch ((hold & 0x03)/*BITS(2)*/) {
+      case 0:                             /* stored block */
+        //Tracev((stderr, "inflate:     stored block%s\n",
+        //        state.last ? " (last)" : ""));
+        state.mode = STORED;
+        break;
+      case 1:                             /* fixed block */
+        fixedtables(state);
+        //Tracev((stderr, "inflate:     fixed codes block%s\n",
+        //        state.last ? " (last)" : ""));
+        state.mode = LEN_;             /* decode codes */
+        if (flush === Z_TREES) {
+          //--- DROPBITS(2) ---//
+          hold >>>= 2;
+          bits -= 2;
+          //---//
+          break inf_leave;
+        }
+        break;
+      case 2:                             /* dynamic block */
+        //Tracev((stderr, "inflate:     dynamic codes block%s\n",
+        //        state.last ? " (last)" : ""));
+        state.mode = TABLE;
+        break;
+      case 3:
+        strm.msg = 'invalid block type';
+        state.mode = BAD;
+      }
+      //--- DROPBITS(2) ---//
+      hold >>>= 2;
+      bits -= 2;
+      //---//
+      break;
+    case STORED:
+      //--- BYTEBITS() ---// /* go to byte boundary */
+      hold >>>= bits & 7;
+      bits -= bits & 7;
+      //---//
+      //=== NEEDBITS(32); */
+      while (bits < 32) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) {
+        strm.msg = 'invalid stored block lengths';
+        state.mode = BAD;
+        break;
+      }
+      state.length = hold & 0xffff;
+      //Tracev((stderr, "inflate:       stored length %u\n",
+      //        state.length));
+      //=== INITBITS();
+      hold = 0;
+      bits = 0;
+      //===//
+      state.mode = COPY_;
+      if (flush === Z_TREES) { break inf_leave; }
+      /* falls through */
+    case COPY_:
+      state.mode = COPY;
+      /* falls through */
+    case COPY:
+      copy = state.length;
+      if (copy) {
+        if (copy > have) { copy = have; }
+        if (copy > left) { copy = left; }
+        if (copy === 0) { break inf_leave; }
+        //--- zmemcpy(put, next, copy); ---
+        utils.arraySet(output, input, next, copy, put);
+        //---//
+        have -= copy;
+        next += copy;
+        left -= copy;
+        put += copy;
+        state.length -= copy;
+        break;
+      }
+      //Tracev((stderr, "inflate:       stored end\n"));
+      state.mode = TYPE;
+      break;
+    case TABLE:
+      //=== NEEDBITS(14); */
+      while (bits < 14) {
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+      }
+      //===//
+      state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257;
+      //--- DROPBITS(5) ---//
+      hold >>>= 5;
+      bits -= 5;
+      //---//
+      state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1;
+      //--- DROPBITS(5) ---//
+      hold >>>= 5;
+      bits -= 5;
+      //---//
+      state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4;
+      //--- DROPBITS(4) ---//
+      hold >>>= 4;
+      bits -= 4;
+      //---//
+//#ifndef PKZIP_BUG_WORKAROUND
+      if (state.nlen > 286 || state.ndist > 30) {
+        strm.msg = 'too many length or distance symbols';
+        state.mode = BAD;
+        break;
+      }
+//#endif
+      //Tracev((stderr, "inflate:       table sizes ok\n"));
+      state.have = 0;
+      state.mode = LENLENS;
+      /* falls through */
+    case LENLENS:
+      while (state.have < state.ncode) {
+        //=== NEEDBITS(3);
+        while (bits < 3) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        state.lens[order[state.have++]] = (hold & 0x07);//BITS(3);
+        //--- DROPBITS(3) ---//
+        hold >>>= 3;
+        bits -= 3;
+        //---//
+      }
+      while (state.have < 19) {
+        state.lens[order[state.have++]] = 0;
+      }
+      // We have separate tables & no pointers. 2 commented lines below not needed.
+      //state.next = state.codes;
+      //state.lencode = state.next;
+      // Switch to use dynamic table
+      state.lencode = state.lendyn;
+      state.lenbits = 7;
+
+      opts = {bits: state.lenbits};
+      ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts);
+      state.lenbits = opts.bits;
+
+      if (ret) {
+        strm.msg = 'invalid code lengths set';
+        state.mode = BAD;
+        break;
+      }
+      //Tracev((stderr, "inflate:       code lengths ok\n"));
+      state.have = 0;
+      state.mode = CODELENS;
+      /* falls through */
+    case CODELENS:
+      while (state.have < state.nlen + state.ndist) {
+        for (;;) {
+          here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/
+          here_bits = here >>> 24;
+          here_op = (here >>> 16) & 0xff;
+          here_val = here & 0xffff;
+
+          if ((here_bits) <= bits) { break; }
+          //--- PULLBYTE() ---//
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+          //---//
+        }
+        if (here_val < 16) {
+          //--- DROPBITS(here.bits) ---//
+          hold >>>= here_bits;
+          bits -= here_bits;
+          //---//
+          state.lens[state.have++] = here_val;
+        }
+        else {
+          if (here_val === 16) {
+            //=== NEEDBITS(here.bits + 2);
+            n = here_bits + 2;
+            while (bits < n) {
+              if (have === 0) { break inf_leave; }
+              have--;
+              hold += input[next++] << bits;
+              bits += 8;
+            }
+            //===//
+            //--- DROPBITS(here.bits) ---//
+            hold >>>= here_bits;
+            bits -= here_bits;
+            //---//
+            if (state.have === 0) {
+              strm.msg = 'invalid bit length repeat';
+              state.mode = BAD;
+              break;
+            }
+            len = state.lens[state.have - 1];
+            copy = 3 + (hold & 0x03);//BITS(2);
+            //--- DROPBITS(2) ---//
+            hold >>>= 2;
+            bits -= 2;
+            //---//
+          }
+          else if (here_val === 17) {
+            //=== NEEDBITS(here.bits + 3);
+            n = here_bits + 3;
+            while (bits < n) {
+              if (have === 0) { break inf_leave; }
+              have--;
+              hold += input[next++] << bits;
+              bits += 8;
+            }
+            //===//
+            //--- DROPBITS(here.bits) ---//
+            hold >>>= here_bits;
+            bits -= here_bits;
+            //---//
+            len = 0;
+            copy = 3 + (hold & 0x07);//BITS(3);
+            //--- DROPBITS(3) ---//
+            hold >>>= 3;
+            bits -= 3;
+            //---//
+          }
+          else {
+            //=== NEEDBITS(here.bits + 7);
+            n = here_bits + 7;
+            while (bits < n) {
+              if (have === 0) { break inf_leave; }
+              have--;
+              hold += input[next++] << bits;
+              bits += 8;
+            }
+            //===//
+            //--- DROPBITS(here.bits) ---//
+            hold >>>= here_bits;
+            bits -= here_bits;
+            //---//
+            len = 0;
+            copy = 11 + (hold & 0x7f);//BITS(7);
+            //--- DROPBITS(7) ---//
+            hold >>>= 7;
+            bits -= 7;
+            //---//
+          }
+          if (state.have + copy > state.nlen + state.ndist) {
+            strm.msg = 'invalid bit length repeat';
+            state.mode = BAD;
+            break;
+          }
+          while (copy--) {
+            state.lens[state.have++] = len;
+          }
+        }
+      }
+
+      /* handle error breaks in while */
+      if (state.mode === BAD) { break; }
+
+      /* check for end-of-block code (better have one) */
+      if (state.lens[256] === 0) {
+        strm.msg = 'invalid code -- missing end-of-block';
+        state.mode = BAD;
+        break;
+      }
+
+      /* build code tables -- note: do not change the lenbits or distbits
+         values here (9 and 6) without reading the comments in inftrees.h
+         concerning the ENOUGH constants, which depend on those values */
+      state.lenbits = 9;
+
+      opts = {bits: state.lenbits};
+      ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts);
+      // We have separate tables & no pointers. 2 commented lines below not needed.
+      // state.next_index = opts.table_index;
+      state.lenbits = opts.bits;
+      // state.lencode = state.next;
+
+      if (ret) {
+        strm.msg = 'invalid literal/lengths set';
+        state.mode = BAD;
+        break;
+      }
+
+      state.distbits = 6;
+      //state.distcode.copy(state.codes);
+      // Switch to use dynamic table
+      state.distcode = state.distdyn;
+      opts = {bits: state.distbits};
+      ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts);
+      // We have separate tables & no pointers. 2 commented lines below not needed.
+      // state.next_index = opts.table_index;
+      state.distbits = opts.bits;
+      // state.distcode = state.next;
+
+      if (ret) {
+        strm.msg = 'invalid distances set';
+        state.mode = BAD;
+        break;
+      }
+      //Tracev((stderr, 'inflate:       codes ok\n'));
+      state.mode = LEN_;
+      if (flush === Z_TREES) { break inf_leave; }
+      /* falls through */
+    case LEN_:
+      state.mode = LEN;
+      /* falls through */
+    case LEN:
+      if (have >= 6 && left >= 258) {
+        //--- RESTORE() ---
+        strm.next_out = put;
+        strm.avail_out = left;
+        strm.next_in = next;
+        strm.avail_in = have;
+        state.hold = hold;
+        state.bits = bits;
+        //---
+        inflate_fast(strm, _out);
+        //--- LOAD() ---
+        put = strm.next_out;
+        output = strm.output;
+        left = strm.avail_out;
+        next = strm.next_in;
+        input = strm.input;
+        have = strm.avail_in;
+        hold = state.hold;
+        bits = state.bits;
+        //---
+
+        if (state.mode === TYPE) {
+          state.back = -1;
+        }
+        break;
+      }
+      state.back = 0;
+      for (;;) {
+        here = state.lencode[hold & ((1 << state.lenbits) -1)];  /*BITS(state.lenbits)*/
+        here_bits = here >>> 24;
+        here_op = (here >>> 16) & 0xff;
+        here_val = here & 0xffff;
+
+        if (here_bits <= bits) { break; }
+        //--- PULLBYTE() ---//
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+        //---//
+      }
+      if (here_op && (here_op & 0xf0) === 0) {
+        last_bits = here_bits;
+        last_op = here_op;
+        last_val = here_val;
+        for (;;) {
+          here = state.lencode[last_val +
+                  ((hold & ((1 << (last_bits + last_op)) -1))/*BITS(last.bits + last.op)*/ >> last_bits)];
+          here_bits = here >>> 24;
+          here_op = (here >>> 16) & 0xff;
+          here_val = here & 0xffff;
+
+          if ((last_bits + here_bits) <= bits) { break; }
+          //--- PULLBYTE() ---//
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+          //---//
+        }
+        //--- DROPBITS(last.bits) ---//
+        hold >>>= last_bits;
+        bits -= last_bits;
+        //---//
+        state.back += last_bits;
+      }
+      //--- DROPBITS(here.bits) ---//
+      hold >>>= here_bits;
+      bits -= here_bits;
+      //---//
+      state.back += here_bits;
+      state.length = here_val;
+      if (here_op === 0) {
+        //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+        //        "inflate:         literal '%c'\n" :
+        //        "inflate:         literal 0x%02x\n", here.val));
+        state.mode = LIT;
+        break;
+      }
+      if (here_op & 32) {
+        //Tracevv((stderr, "inflate:         end of block\n"));
+        state.back = -1;
+        state.mode = TYPE;
+        break;
+      }
+      if (here_op & 64) {
+        strm.msg = 'invalid literal/length code';
+        state.mode = BAD;
+        break;
+      }
+      state.extra = here_op & 15;
+      state.mode = LENEXT;
+      /* falls through */
+    case LENEXT:
+      if (state.extra) {
+        //=== NEEDBITS(state.extra);
+        n = state.extra;
+        while (bits < n) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        state.length += hold & ((1 << state.extra) -1)/*BITS(state.extra)*/;
+        //--- DROPBITS(state.extra) ---//
+        hold >>>= state.extra;
+        bits -= state.extra;
+        //---//
+        state.back += state.extra;
+      }
+      //Tracevv((stderr, "inflate:         length %u\n", state.length));
+      state.was = state.length;
+      state.mode = DIST;
+      /* falls through */
+    case DIST:
+      for (;;) {
+        here = state.distcode[hold & ((1 << state.distbits) -1)];/*BITS(state.distbits)*/
+        here_bits = here >>> 24;
+        here_op = (here >>> 16) & 0xff;
+        here_val = here & 0xffff;
+
+        if ((here_bits) <= bits) { break; }
+        //--- PULLBYTE() ---//
+        if (have === 0) { break inf_leave; }
+        have--;
+        hold += input[next++] << bits;
+        bits += 8;
+        //---//
+      }
+      if ((here_op & 0xf0) === 0) {
+        last_bits = here_bits;
+        last_op = here_op;
+        last_val = here_val;
+        for (;;) {
+          here = state.distcode[last_val +
+                  ((hold & ((1 << (last_bits + last_op)) -1))/*BITS(last.bits + last.op)*/ >> last_bits)];
+          here_bits = here >>> 24;
+          here_op = (here >>> 16) & 0xff;
+          here_val = here & 0xffff;
+
+          if ((last_bits + here_bits) <= bits) { break; }
+          //--- PULLBYTE() ---//
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+          //---//
+        }
+        //--- DROPBITS(last.bits) ---//
+        hold >>>= last_bits;
+        bits -= last_bits;
+        //---//
+        state.back += last_bits;
+      }
+      //--- DROPBITS(here.bits) ---//
+      hold >>>= here_bits;
+      bits -= here_bits;
+      //---//
+      state.back += here_bits;
+      if (here_op & 64) {
+        strm.msg = 'invalid distance code';
+        state.mode = BAD;
+        break;
+      }
+      state.offset = here_val;
+      state.extra = (here_op) & 15;
+      state.mode = DISTEXT;
+      /* falls through */
+    case DISTEXT:
+      if (state.extra) {
+        //=== NEEDBITS(state.extra);
+        n = state.extra;
+        while (bits < n) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        state.offset += hold & ((1 << state.extra) -1)/*BITS(state.extra)*/;
+        //--- DROPBITS(state.extra) ---//
+        hold >>>= state.extra;
+        bits -= state.extra;
+        //---//
+        state.back += state.extra;
+      }
+//#ifdef INFLATE_STRICT
+      if (state.offset > state.dmax) {
+        strm.msg = 'invalid distance too far back';
+        state.mode = BAD;
+        break;
+      }
+//#endif
+      //Tracevv((stderr, "inflate:         distance %u\n", state.offset));
+      state.mode = MATCH;
+      /* falls through */
+    case MATCH:
+      if (left === 0) { break inf_leave; }
+      copy = _out - left;
+      if (state.offset > copy) {         /* copy from window */
+        copy = state.offset - copy;
+        if (copy > state.whave) {
+          if (state.sane) {
+            strm.msg = 'invalid distance too far back';
+            state.mode = BAD;
+            break;
+          }
+// (!) This block is disabled in zlib defailts,
+// don't enable it for binary compatibility
+//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+//          Trace((stderr, "inflate.c too far\n"));
+//          copy -= state.whave;
+//          if (copy > state.length) { copy = state.length; }
+//          if (copy > left) { copy = left; }
+//          left -= copy;
+//          state.length -= copy;
+//          do {
+//            output[put++] = 0;
+//          } while (--copy);
+//          if (state.length === 0) { state.mode = LEN; }
+//          break;
+//#endif
+        }
+        if (copy > state.wnext) {
+          copy -= state.wnext;
+          from = state.wsize - copy;
+        }
+        else {
+          from = state.wnext - copy;
+        }
+        if (copy > state.length) { copy = state.length; }
+        from_source = state.window;
+      }
+      else {                              /* copy from output */
+        from_source = output;
+        from = put - state.offset;
+        copy = state.length;
+      }
+      if (copy > left) { copy = left; }
+      left -= copy;
+      state.length -= copy;
+      do {
+        output[put++] = from_source[from++];
+      } while (--copy);
+      if (state.length === 0) { state.mode = LEN; }
+      break;
+    case LIT:
+      if (left === 0) { break inf_leave; }
+      output[put++] = state.length;
+      left--;
+      state.mode = LEN;
+      break;
+    case CHECK:
+      if (state.wrap) {
+        //=== NEEDBITS(32);
+        while (bits < 32) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          // Use '|' insdead of '+' to make sure that result is signed
+          hold |= input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        _out -= left;
+        strm.total_out += _out;
+        state.total += _out;
+        if (_out) {
+          strm.adler = state.check =
+              /*UPDATE(state.check, put - _out, _out);*/
+              (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out));
+
+        }
+        _out = left;
+        // NB: crc32 stored as signed 32-bit int, ZSWAP32 returns signed too
+        if ((state.flags ? hold : ZSWAP32(hold)) !== state.check) {
+          strm.msg = 'incorrect data check';
+          state.mode = BAD;
+          break;
+        }
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+        //Tracev((stderr, "inflate:   check matches trailer\n"));
+      }
+      state.mode = LENGTH;
+      /* falls through */
+    case LENGTH:
+      if (state.wrap && state.flags) {
+        //=== NEEDBITS(32);
+        while (bits < 32) {
+          if (have === 0) { break inf_leave; }
+          have--;
+          hold += input[next++] << bits;
+          bits += 8;
+        }
+        //===//
+        if (hold !== (state.total & 0xffffffff)) {
+          strm.msg = 'incorrect length check';
+          state.mode = BAD;
+          break;
+        }
+        //=== INITBITS();
+        hold = 0;
+        bits = 0;
+        //===//
+        //Tracev((stderr, "inflate:   length matches trailer\n"));
+      }
+      state.mode = DONE;
+      /* falls through */
+    case DONE:
+      ret = Z_STREAM_END;
+      break inf_leave;
+    case BAD:
+      ret = Z_DATA_ERROR;
+      break inf_leave;
+    case MEM:
+      return Z_MEM_ERROR;
+    case SYNC:
+      /* falls through */
+    default:
+      return Z_STREAM_ERROR;
+    }
+  }
+
+  // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave"
+
+  /*
+     Return from inflate(), updating the total counts and the check value.
+     If there was no progress during the inflate() call, return a buffer
+     error.  Call updatewindow() to create and/or update the window state.
+     Note: a memory error from inflate() is non-recoverable.
+   */
+
+  //--- RESTORE() ---
+  strm.next_out = put;
+  strm.avail_out = left;
+  strm.next_in = next;
+  strm.avail_in = have;
+  state.hold = hold;
+  state.bits = bits;
+  //---
+
+  if (state.wsize || (_out !== strm.avail_out && state.mode < BAD &&
+                      (state.mode < CHECK || flush !== Z_FINISH))) {
+    if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) {
+      state.mode = MEM;
+      return Z_MEM_ERROR;
+    }
+  }
+  _in -= strm.avail_in;
+  _out -= strm.avail_out;
+  strm.total_in += _in;
+  strm.total_out += _out;
+  state.total += _out;
+  if (state.wrap && _out) {
+    strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/
+      (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out));
+  }
+  strm.data_type = state.bits + (state.last ? 64 : 0) +
+                    (state.mode === TYPE ? 128 : 0) +
+                    (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0);
+  if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) {
+    ret = Z_BUF_ERROR;
+  }
+  return ret;
+}
+
+function inflateEnd(strm) {
+
+  if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) {
+    return Z_STREAM_ERROR;
+  }
+
+  var state = strm.state;
+  if (state.window) {
+    state.window = null;
+  }
+  strm.state = null;
+  return Z_OK;
+}
+
+function inflateGetHeader(strm, head) {
+  var state;
+
+  /* check state */
+  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+  state = strm.state;
+  if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; }
+
+  /* save header structure */
+  state.head = head;
+  head.done = false;
+  return Z_OK;
+}
+
+
+exports.inflateReset = inflateReset;
+exports.inflateReset2 = inflateReset2;
+exports.inflateResetKeep = inflateResetKeep;
+exports.inflateInit = inflateInit;
+exports.inflateInit2 = inflateInit2;
+exports.inflate = inflate;
+exports.inflateEnd = inflateEnd;
+exports.inflateGetHeader = inflateGetHeader;
+exports.inflateInfo = 'pako inflate (from Nodeca project)';
+
+/* Not implemented
+exports.inflateCopy = inflateCopy;
+exports.inflateGetDictionary = inflateGetDictionary;
+exports.inflateMark = inflateMark;
+exports.inflatePrime = inflatePrime;
+exports.inflateSetDictionary = inflateSetDictionary;
+exports.inflateSync = inflateSync;
+exports.inflateSyncPoint = inflateSyncPoint;
+exports.inflateUndermine = inflateUndermine;
+*/
+},{"../utils/common":2,"./adler32":4,"./crc32":6,"./inffast":8,"./inftrees":10}],10:[function(_dereq_,module,exports){
+'use strict';
+
+
+var utils = _dereq_('../utils/common');
+
+var MAXBITS = 15;
+var ENOUGH_LENS = 852;
+var ENOUGH_DISTS = 592;
+//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);
+
+var CODES = 0;
+var LENS = 1;
+var DISTS = 2;
+
+var lbase = [ /* Length codes 257..285 base */
+  3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+  35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
+];
+
+var lext = [ /* Length codes 257..285 extra */
+  16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
+  19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78
+];
+
+var dbase = [ /* Distance codes 0..29 base */
+  1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+  257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+  8193, 12289, 16385, 24577, 0, 0
+];
+
+var dext = [ /* Distance codes 0..29 extra */
+  16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
+  23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
+  28, 28, 29, 29, 64, 64
+];
+
+module.exports = function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts)
+{
+  var bits = opts.bits;
+      //here = opts.here; /* table entry for duplication */
+
+  var len = 0;               /* a code's length in bits */
+  var sym = 0;               /* index of code symbols */
+  var min = 0, max = 0;          /* minimum and maximum code lengths */
+  var root = 0;              /* number of index bits for root table */
+  var curr = 0;              /* number of index bits for current table */
+  var drop = 0;              /* code bits to drop for sub-table */
+  var left = 0;                   /* number of prefix codes available */
+  var used = 0;              /* code entries in table used */
+  var huff = 0;              /* Huffman code */
+  var incr;              /* for incrementing code, index */
+  var fill;              /* index for replicating entries */
+  var low;               /* low bits for current root entry */
+  var mask;              /* mask for low root bits */
+  var next;             /* next available space in table */
+  var base = null;     /* base value table to use */
+  var base_index = 0;
+//  var shoextra;    /* extra bits table to use */
+  var end;                    /* use base and extra for symbol > end */
+  var count = new utils.Buf16(MAXBITS+1); //[MAXBITS+1];    /* number of codes of each length */
+  var offs = new utils.Buf16(MAXBITS+1); //[MAXBITS+1];     /* offsets in table for each length */
+  var extra = null;
+  var extra_index = 0;
+
+  var here_bits, here_op, here_val;
+
+  /*
+   Process a set of code lengths to create a canonical Huffman code.  The
+   code lengths are lens[0..codes-1].  Each length corresponds to the
+   symbols 0..codes-1.  The Huffman code is generated by first sorting the
+   symbols by length from short to long, and retaining the symbol order
+   for codes with equal lengths.  Then the code starts with all zero bits
+   for the first code of the shortest length, and the codes are integer
+   increments for the same length, and zeros are appended as the length
+   increases.  For the deflate format, these bits are stored backwards
+   from their more natural integer increment ordering, and so when the
+   decoding tables are built in the large loop below, the integer codes
+   are incremented backwards.
+
+   This routine assumes, but does not check, that all of the entries in
+   lens[] are in the range 0..MAXBITS.  The caller must assure this.
+   1..MAXBITS is interpreted as that code length.  zero means that that
+   symbol does not occur in this code.
+
+   The codes are sorted by computing a count of codes for each length,
+   creating from that a table of starting indices for each length in the
+   sorted table, and then entering the symbols in order in the sorted
+   table.  The sorted table is work[], with that space being provided by
+   the caller.
+
+   The length counts are used for other purposes as well, i.e. finding
+   the minimum and maximum length codes, determining if there are any
+   codes at all, checking for a valid set of lengths, and looking ahead
+   at length counts to determine sub-table sizes when building the
+   decoding tables.
+   */
+
+  /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
+  for (len = 0; len <= MAXBITS; len++) {
+    count[len] = 0;
+  }
+  for (sym = 0; sym < codes; sym++) {
+    count[lens[lens_index + sym]]++;
+  }
+
+  /* bound code lengths, force root to be within code lengths */
+  root = bits;
+  for (max = MAXBITS; max >= 1; max--) {
+    if (count[max] !== 0) { break; }
+  }
+  if (root > max) {
+    root = max;
+  }
+  if (max === 0) {                     /* no symbols to code at all */
+    //table.op[opts.table_index] = 64;  //here.op = (var char)64;    /* invalid code marker */
+    //table.bits[opts.table_index] = 1;   //here.bits = (var char)1;
+    //table.val[opts.table_index++] = 0;   //here.val = (var short)0;
+    table[table_index++] = (1 << 24) | (64 << 16) | 0;
+
+
+    //table.op[opts.table_index] = 64;
+    //table.bits[opts.table_index] = 1;
+    //table.val[opts.table_index++] = 0;
+    table[table_index++] = (1 << 24) | (64 << 16) | 0;
+
+    opts.bits = 1;
+    return 0;     /* no symbols, but wait for decoding to report error */
+  }
+  for (min = 1; min < max; min++) {
+    if (count[min] !== 0) { break; }
+  }
+  if (root < min) {
+    root = min;
+  }
+
+  /* check for an over-subscribed or incomplete set of lengths */
+  left = 1;
+  for (len = 1; len <= MAXBITS; len++) {
+    left <<= 1;
+    left -= count[len];
+    if (left < 0) {
+      return -1;
+    }        /* over-subscribed */
+  }
+  if (left > 0 && (type === CODES || max !== 1)) {
+    return -1;                      /* incomplete set */
+  }
+
+  /* generate offsets into symbol table for each length for sorting */
+  offs[1] = 0;
+  for (len = 1; len < MAXBITS; len++) {
+    offs[len + 1] = offs[len] + count[len];
+  }
+
+  /* sort symbols by length, by symbol order within each length */
+  for (sym = 0; sym < codes; sym++) {
+    if (lens[lens_index + sym] !== 0) {
+      work[offs[lens[lens_index + sym]]++] = sym;
+    }
+  }
+
+  /*
+   Create and fill in decoding tables.  In this loop, the table being
+   filled is at next and has curr index bits.  The code being used is huff
+   with length len.  That code is converted to an index by dropping drop
+   bits off of the bottom.  For codes where len is less than drop + curr,
+   those top drop + curr - len bits are incremented through all values to
+   fill the table with replicated entries.
+
+   root is the number of index bits for the root table.  When len exceeds
+   root, sub-tables are created pointed to by the root entry with an index
+   of the low root bits of huff.  This is saved in low to check for when a
+   new sub-table should be started.  drop is zero when the root table is
+   being filled, and drop is root when sub-tables are being filled.
+
+   When a new sub-table is needed, it is necessary to look ahead in the
+   code lengths to determine what size sub-table is needed.  The length
+   counts are used for this, and so count[] is decremented as codes are
+   entered in the tables.
+
+   used keeps track of how many table entries have been allocated from the
+   provided *table space.  It is checked for LENS and DIST tables against
+   the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
+   the initial root table size constants.  See the comments in inftrees.h
+   for more information.
+
+   sym increments through all symbols, and the loop terminates when
+   all codes of length max, i.e. all codes, have been processed.  This
+   routine permits incomplete codes, so another loop after this one fills
+   in the rest of the decoding tables with invalid code markers.
+   */
+
+  /* set up for code type */
+  // poor man optimization - use if-else instead of switch,
+  // to avoid deopts in old v8
+  if (type === CODES) {
+      base = extra = work;    /* dummy value--not used */
+      end = 19;
+  } else if (type === LENS) {
+      base = lbase;
+      base_index -= 257;
+      extra = lext;
+      extra_index -= 257;
+      end = 256;
+  } else {                    /* DISTS */
+      base = dbase;
+      extra = dext;
+      end = -1;
+  }
+
+  /* initialize opts for loop */
+  huff = 0;                   /* starting code */
+  sym = 0;                    /* starting code symbol */
+  len = min;                  /* starting code length */
+  next = table_index;              /* current table to fill in */
+  curr = root;                /* current table index bits */
+  drop = 0;                   /* current bits to drop from code for index */
+  low = -1;                   /* trigger new sub-table when len > root */
+  used = 1 << root;          /* use root table entries */
+  mask = used - 1;            /* mask for comparing low */
+
+  /* check available table space */
+  if ((type === LENS && used > ENOUGH_LENS) ||
+    (type === DISTS && used > ENOUGH_DISTS)) {
+    return 1;
+  }
+
+  var i=0;
+  /* process all codes and make table entries */
+  for (;;) {
+    i++;
+    /* create table entry */
+    here_bits = len - drop;
+    if (work[sym] < end) {
+      here_op = 0;
+      here_val = work[sym];
+    }
+    else if (work[sym] > end) {
+      here_op = extra[extra_index + work[sym]];
+      here_val = base[base_index + work[sym]];
+    }
+    else {
+      here_op = 32 + 64;         /* end of block */
+      here_val = 0;
+    }
+
+    /* replicate for those indices with low len bits equal to huff */
+    incr = 1 << (len - drop);
+    fill = 1 << curr;
+    min = fill;                 /* save offset to next table */
+    do {
+      fill -= incr;
+      table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0;
+    } while (fill !== 0);
+
+    /* backwards increment the len-bit code huff */
+    incr = 1 << (len - 1);
+    while (huff & incr) {
+      incr >>= 1;
+    }
+    if (incr !== 0) {
+      huff &= incr - 1;
+      huff += incr;
+    } else {
+      huff = 0;
+    }
+
+    /* go to next symbol, update count, len */
+    sym++;
+    if (--count[len] === 0) {
+      if (len === max) { break; }
+      len = lens[lens_index + work[sym]];
+    }
+
+    /* create new sub-table if needed */
+    if (len > root && (huff & mask) !== low) {
+      /* if first time, transition to sub-tables */
+      if (drop === 0) {
+        drop = root;
+      }
+
+      /* increment past last table */
+      next += min;            /* here min is 1 << curr */
+
+      /* determine length of next table */
+      curr = len - drop;
+      left = 1 << curr;
+      while (curr + drop < max) {
+        left -= count[curr + drop];
+        if (left <= 0) { break; }
+        curr++;
+        left <<= 1;
+      }
+
+      /* check for enough space */
+      used += 1 << curr;
+      if ((type === LENS && used > ENOUGH_LENS) ||
+        (type === DISTS && used > ENOUGH_DISTS)) {
+        return 1;
+      }
+
+      /* point entry in root table to sub-table */
+      low = huff & mask;
+      /*table.op[low] = curr;
+      table.bits[low] = root;
+      table.val[low] = next - opts.table_index;*/
+      table[low] = (root << 24) | (curr << 16) | (next - table_index) |0;
+    }
+  }
+
+  /* fill in remaining table entry if code is incomplete (guaranteed to have
+   at most one remaining entry, since if the code is incomplete, the
+   maximum code length that was allowed to get this far is one bit) */
+  if (huff !== 0) {
+    //table.op[next + huff] = 64;            /* invalid code marker */
+    //table.bits[next + huff] = len - drop;
+    //table.val[next + huff] = 0;
+    table[next + huff] = ((len - drop) << 24) | (64 << 16) |0;
+  }
+
+  /* set return parameters */
+  //opts.table_index += used;
+  opts.bits = root;
+  return 0;
+};
+
+},{"../utils/common":2}],11:[function(_dereq_,module,exports){
+'use strict';
+
+module.exports = {
+  '2':    'need dictionary',     /* Z_NEED_DICT       2  */
+  '1':    'stream end',          /* Z_STREAM_END      1  */
+  '0':    '',                    /* Z_OK              0  */
+  '-1':   'file error',          /* Z_ERRNO         (-1) */
+  '-2':   'stream error',        /* Z_STREAM_ERROR  (-2) */
+  '-3':   'data error',          /* Z_DATA_ERROR    (-3) */
+  '-4':   'insufficient memory', /* Z_MEM_ERROR     (-4) */
+  '-5':   'buffer error',        /* Z_BUF_ERROR     (-5) */
+  '-6':   'incompatible version' /* Z_VERSION_ERROR (-6) */
+};
+},{}],12:[function(_dereq_,module,exports){
+'use strict';
+
+
+function ZStream() {
+  /* next input byte */
+  this.input = null; // JS specific, because we have no pointers
+  this.next_in = 0;
+  /* number of bytes available at input */
+  this.avail_in = 0;
+  /* total number of input bytes read so far */
+  this.total_in = 0;
+  /* next output byte should be put there */
+  this.output = null; // JS specific, because we have no pointers
+  this.next_out = 0;
+  /* remaining free space at output */
+  this.avail_out = 0;
+  /* total number of bytes output so far */
+  this.total_out = 0;
+  /* last error message, NULL if no error */
+  this.msg = ''/*Z_NULL*/;
+  /* not visible by applications */
+  this.state = null;
+  /* best guess about the data type: binary or text */
+  this.data_type = 2/*Z_UNKNOWN*/;
+  /* adler32 value of the uncompressed data */
+  this.adler = 0;
+}
+
+module.exports = ZStream;
+},{}]},{},[1])
+(1)
+});
\ No newline at end of file

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/README.Debian
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/README.Debian	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/README.Debian	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,29 @@
+Orthanc Web Viewer is a plugin to Orthanc, a lightweight, RESTful Vendor
+Neutral Archive for medical imaging. It extends Orthanc with an integrated
+Web viewer of DICOM images.
+
+Homepage: http://www.orthanc-server.com/static.php?page=web-viewer
+
+The plugin is accessible from Orthanc Explorer, the built-in Web
+interface of Orthanc. To open Orthanc Explorer with Mozilla Firefox,
+use the following command:
+
+$ firefox http://localhost:8042/app/explorer.html
+
+Browse to your series of interest. You will see a yellow button
+entitled "Orthanc Web Viewer". Clicking on it will open the Web viewer
+on the series.
+
+
+Update notes
+------------
+
+If after installing this plugin, the Web viewer does not properly
+display some series of images, this is most probably due to the fact
+that the cache of the Web viewer needs to be rebuilt after an
+update. Here are the commands to do so, provided the cache is located
+at the default location:
+
+# sudo /etc/init.d/orthanc stop
+# sudo rm -rf /var/lib/orthanc/db-v6/WebViewerCache/
+# sudo /etc/init.d/orthanc start

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/changelog
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/changelog	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/changelog	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,44 @@
+orthanc-webviewer (2.2-3) unstable; urgency=medium
+
+  * Fix FTBFS with libsqlite3-dev >= 3.19.0. Closes: #867789
+
+ -- Sebastien Jodogne <s.jodogne at gmail.com>  Mon, 10 Jul 2017 11:57:21 +0200
+
+orthanc-webviewer (2.2-2) unstable; urgency=medium
+
+  [ Tiago Sturmer Daitx <tiago.daitx at canonical.com> ]
+  * Fix linking of libraries. Closes: #857355
+
+ -- Sebastien Jodogne <s.jodogne at gmail.com>  Fri, 17 Mar 2017 16:47:23 +0100
+
+orthanc-webviewer (2.2-1) unstable; urgency=medium
+
+  * New upstream version.
+
+ -- Sebastien Jodogne <s.jodogne at gmail.com>  Tue, 28 Jun 2016 15:20:00 +0200
+
+orthanc-webviewer (2.1-1) unstable; urgency=medium
+
+  * New upstream version.
+  * Upgrade Cornerstone version: 0.7.1 to 0.8.4
+
+ -- Sebastien Jodogne <s.jodogne at gmail.com>  Thu, 10 Dec 2015 15:01:19 +0100
+
+orthanc-webviewer (2.0-1) unstable; urgency=medium
+
+  * New upstream version.
+
+ -- Sebastien Jodogne <s.jodogne at gmail.com>  Fri, 04 Dec 2015 17:13:12 +0100
+
+orthanc-webviewer (1.2-1) unstable; urgency=medium
+
+  * New upstream version.
+
+ -- Sebastien Jodogne <s.jodogne at gmail.com>  Mon, 03 Aug 2015 09:49:56 +0200
+
+orthanc-webviewer (1.1-1) unstable; urgency=medium
+
+  * Initial release. (Closes: #779325)
+  * Fix of debian/copyright.
+
+ -- Sebastien Jodogne <s.jodogne at gmail.com>  Fri, 03 Jul 2015 13:42:36 +0200

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/compat
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/compat	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/compat	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1 @@
+9

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/configuration/webviewer.json
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/configuration/webviewer.json	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/configuration/webviewer.json	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,26 @@
+{
+  /**
+   * The following options control the configuration of the Orthanc
+   * Web Viewer plugin.
+   **/
+
+  "WebViewer" : {
+      /**
+       * The location of the cache of the Web viewer. By default, the
+       * cache is located inside the storage directory of Orthanc.
+       **/
+      // "CachePath" : "/tmp/WebViewerCache",
+      
+      /**
+       * The maximum size for the cached images, in megabytes. By
+       * default, a cache of 100 MB is used.
+       **/
+      // "CacheSize" : 10,
+
+      /**
+       * The number of threads that are used by the plugin to decode
+       * the DICOM images.
+       **/
+      // "Threads" : 4
+  }
+}

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/control
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/control	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/control	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,34 @@
+Source: orthanc-webviewer
+Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
+Uploaders: Sebastien Jodogne <s.jodogne at gmail.com>,
+           Andreas Tille <tille at debian.org>
+Section: science
+Priority: optional
+Standards-Version: 4.0.0
+Build-Depends: cmake (>= 2.8),
+               debhelper (>= 9),
+               libboost-all-dev,
+               libgdcm2-dev,
+               libgtest-dev (>= 1.6.0),
+               libjpeg-dev,
+               libjs-jquery-ui,
+               libjs-jquery-ui-theme-base,
+               libjsoncpp-dev,
+               libpng-dev,
+               libsqlite3-dev,
+               orthanc-dev (>= 0.9.5),
+               unzip,
+               uuid-dev,
+               yui-compressor,
+               zlib1g-dev
+Vcs-Browser: https://anonscm.debian.org/viewvc/debian-med/trunk/packages/orthanc-webviewer/trunk/
+Vcs-Svn: svn://anonscm.debian.org/debian-med/trunk/packages/orthanc-webviewer/trunk/
+Homepage: http://www.orthanc-server.com/static.php?page=web-viewer
+
+Package: orthanc-webviewer
+Architecture: any
+Depends: ${misc:Depends}, ${shlibs:Depends}, orthanc (>= 0.9.5)
+Description: Web viewer of medical images for Orthanc
+ Orthanc Web Viewer is a plugin to Orthanc, a lightweight, RESTful Vendor
+ Neutral Archive for medical imaging. It extends Orthanc with an integrated
+ Web viewer of DICOM images.

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/copyright
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/copyright	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/copyright	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,199 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: OrthancWebViewer
+Upstream-Contact: Sebastien Jodogne <s.jodogne at gmail.com>
+Source: https://bitbucket.org/sjodogne/orthanc-webviewer/
+
+Files: *
+Copyright: 2012-2015 Sebastien Jodogne <s.jodogne at gmail.com>, University Hospital of Liege, Belgium
+License: AGPL-3
+
+Files: Orthanc/*
+Copyright: 2012-2015 Sebastien Jodogne <s.jodogne at gmail.com>, University Hospital of Liege, Belgium
+License: GPL-3 with OpenSSL exception
+
+Files: Orthanc/Resources/ThirdParty/base64/*
+Copyright: 2004-2008 Rene Nyffenegger
+License: zlib
+
+Files: Orthanc/Resources/ThirdParty/VisualStudio/*
+Copyright: 2006-2008 Alexander Chemeris
+License: BSD-3-clause
+
+Files: WebApplication/jpeg-decoder.js
+Copyright: 2011 notmasteryet
+License: Apache-2
+
+Files: debian/JS/cornerstone-0.8.4/*
+Copyright: 2014 Chris Hafey <chafey at gmail.com>
+License: MIT
+
+Files: debian/JS/jsPanel-2.3.3/*
+Copyright: 2014 Stefan Sträßer
+License: MIT
+
+Files: debian/JS/jsPanel-2.3.3/jquery.ui.touch-punch.js
+Copyright: 2011-2014 Dave Furfero
+License: MIT or GPL-2
+
+Files: debian/JS/js-url-1.8.6/*
+Copyright: 2011-2012 Websanova
+License: MIT
+
+Files: debian/JS/pako-0.2.5/*
+Copyright: 2014 Vitaly Puzrin
+License: MIT
+
+
+License: AGPL-3
+ This program is free software: you can redistribute it and/or
+ modify it under the terms of the GNU Affero General Public License
+ as published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+ .
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ Affero General Public License for more details.
+ . 
+ You should have received a copy of the GNU Affero General Public License
+ along with this package; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+License: GPL-2
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of version 2 of the GNU General Public License as
+ published by the Free Software Foundation.
+ .
+ This program is distributed in the hope that it will be
+ useful, but WITHOUT ANY WARRANTY; without even the implied
+ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ PURPOSE.  See the GNU General Public License for more
+ details.
+ .
+ You should have received a copy of the GNU General Public
+ License along with this package; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ Boston, MA  02110-1301 USA
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 2 can be found in the file
+ `/usr/share/common-licenses/GPL-2'.
+
+
+License: GPL-3 with OpenSSL exception
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or (at
+ your option) any later version.
+ .
+ In addition, as a special exception, the copyright holders of this
+ program give permission to link the code of its release with the
+ OpenSSL project's "OpenSSL" library (or with modified versions of it
+ that use the same license as the "OpenSSL" library), and distribute
+ the linked executables. You must obey the GNU General Public License
+ in all respects for all of the code used other than "OpenSSL". If you
+ modify file(s) with this exception, you may extend this exception to
+ your version of the file(s), but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from your
+ version. If you delete this exception statement from all source files
+ in the program, then also delete it here.
+ .
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License along
+ with this package; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ .
+ On Debian systems, the full text of the GNU General Public License
+ version 3 can be found in the file
+ `/usr/share/common-licenses/GPL-3'.
+
+
+License: zlib
+ This software is provided 'as-is', without any express or implied
+ warranty.  In no event will the authors be held liable for any
+ damages arising from the use of this software.
+ .
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+ .
+ 1. The origin of this software must not be misrepresented; you must
+ not claim that you wrote the original software. If you use this
+ software in a product, an acknowledgment in the product documentation
+ would be appreciated but is not required.
+ .
+ 2. Altered source versions must be plainly marked as such, and must
+ not be misrepresented as being the original software.
+ .
+ 3. This notice may not be removed or altered from any source
+ distribution.
+
+
+License: Apache-2
+ 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.
+
+
+License: BSD-3-clause
+ 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.
+ .
+ * The name of the autors may be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+ .
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+License: MIT
+ 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.

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/install
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/install	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/install	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1 @@
+debian/configuration/webviewer.json etc/orthanc

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/patches/cmake
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/patches/cmake	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/patches/cmake	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,31 @@
+Description: Fix the linking of the shared library and the inclusion of the JavaScript libraries
+Author: Sebastien Jodogne <s.jodogne at gmail.com>
+---
+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
+Index: OrthancWebViewer-2.2/CMakeLists.txt
+===================================================================
+--- OrthancWebViewer-2.2.orig/CMakeLists.txt
++++ OrthancWebViewer-2.2/CMakeLists.txt
+@@ -54,7 +54,7 @@ include(${CMAKE_SOURCE_DIR}/Orthanc/Reso
+ include(${CMAKE_SOURCE_DIR}/Orthanc/Resources/CMake/SQLiteConfiguration.cmake)
+ 
+ include(${CMAKE_SOURCE_DIR}/Resources/CMake/GdcmConfiguration.cmake)
+-include(${CMAKE_SOURCE_DIR}/Resources/CMake/JavaScriptLibraries.cmake)
++set(JAVASCRIPT_LIBS_DIR ${CMAKE_CURRENT_BINARY_DIR}/javascript-libs/)
+ 
+ 
+ # Check that the Orthanc SDK headers are available or download them
+@@ -183,9 +183,10 @@ message("Setting the version of the libr
+ 
+ add_definitions(-DORTHANC_WEBVIEWER_VERSION="${ORTHANC_WEBVIEWER_VERSION}")
+ 
+-set_target_properties(OrthancWebViewer PROPERTIES 
+-  VERSION ${ORTHANC_WEBVIEWER_VERSION} 
+-  SOVERSION ${ORTHANC_WEBVIEWER_VERSION})
++set_target_properties(OrthancWebViewer PROPERTIES
++  NO_SONAME ON
++  LINK_FLAGS "-Wl,-soname,libOrthancWebViewer.so.${ORTHANC_WEBVIEWER_VERSION}"
++  )
+ 
+ install(
+   TARGETS OrthancWebViewer

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/patches/series
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/patches/series	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/patches/series	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,2 @@
+cmake
+sqlite

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/patches/sqlite
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/patches/sqlite	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/patches/sqlite	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,47 @@
+Description: Fix FTBFS with libsqlite3-dev 3.19.3-3
+Author: Sebastien Jodogne <s.jodogne at gmail.com>
+---
+This patch header follows DEP-3: http://dep.debian.net/deps/dep3/
+Index: OrthancWebViewer-2.2/Orthanc/Core/SQLite/FunctionContext.cpp
+===================================================================
+--- OrthancWebViewer-2.2.orig/Orthanc/Core/SQLite/FunctionContext.cpp
++++ OrthancWebViewer-2.2/Orthanc/Core/SQLite/FunctionContext.cpp
+@@ -49,7 +49,7 @@ namespace Orthanc
+   {
+     FunctionContext::FunctionContext(struct sqlite3_context* context,
+                                      int argc,
+-                                     struct ::Mem** argv)
++                                     struct sqlite3_value** argv)
+     {
+       assert(context != NULL);
+       assert(argc >= 0);
+Index: OrthancWebViewer-2.2/Orthanc/Core/SQLite/FunctionContext.h
+===================================================================
+--- OrthancWebViewer-2.2.orig/Orthanc/Core/SQLite/FunctionContext.h
++++ OrthancWebViewer-2.2/Orthanc/Core/SQLite/FunctionContext.h
+@@ -37,7 +37,7 @@
+ #include "Statement.h"
+ 
+ struct sqlite3_context;
+-struct Mem;  // This corresponds to the opaque type "sqlite3_value"
++struct sqlite3_value;
+  
+ namespace Orthanc
+ {
+@@ -50,14 +50,14 @@ namespace Orthanc
+     private:
+       struct sqlite3_context* context_;
+       unsigned int argc_;
+-      struct ::Mem** argv_;
++      struct sqlite3_value** argv_;
+ 
+       void CheckIndex(unsigned int index) const;
+ 
+     public:
+       FunctionContext(struct sqlite3_context* context,
+                       int argc,
+-                      struct ::Mem** argv);
++                      struct sqlite3_value** argv);
+ 
+       ColumnType GetColumnType(unsigned int index) const;
+  

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/postinst
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/postinst	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/postinst	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+set -e
+
+case $1 in
+    configure)
+        # Configure the permissions of the configuration file
+	chown -R orthanc:orthanc /etc/orthanc/webviewer.json
+	chmod 0664 /etc/orthanc/webviewer.json
+
+	# Restart the Orthanc service
+	# https://www.debian.org/doc/debian-policy/ch-opersys.html#s9.3.3.2
+	if [ -x /etc/init.d/orthanc ]; then
+	    if which invoke-rc.d >/dev/null 2>&1; then
+     		invoke-rc.d orthanc restart
+     	    else
+     		/etc/init.d/orthanc restart
+     	    fi
+     	fi
+	;;
+
+    abort-upgrade|abort-remove|abort-deconfigure)
+	;;
+
+    *)
+        echo "prerm called with unknown argument \`$1'" >&2
+        exit 1
+        ;;
+esac
+
+#DEBHELPER#


Property changes on: trunk/packages/orthanc-webviewer/tags/2.2-3/postinst
___________________________________________________________________
Added: svn:executable
   + *

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/postrm
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/postrm	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/postrm	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+set -e
+
+case $1 in
+    purge|remove|upgrade)
+	# Restart the Orthanc service
+	# https://www.debian.org/doc/debian-policy/ch-opersys.html#s9.3.3.2
+	if [ -x /etc/init.d/orthanc ]; then
+	    if which invoke-rc.d >/dev/null 2>&1; then
+     		invoke-rc.d orthanc restart
+     	    else
+     		/etc/init.d/orthanc restart
+     	    fi
+     	fi
+	;;
+
+    failed-upgrade|abort-install|abort-upgrade|disappear)
+	;;
+
+    *)
+        echo "prerm called with unknown argument \`$1'" >&2
+        exit 1
+        ;;
+esac
+
+#DEBHELPER#


Property changes on: trunk/packages/orthanc-webviewer/tags/2.2-3/postrm
___________________________________________________________________
Added: svn:executable
   + *

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/rules
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/rules	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/rules	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,73 @@
+#!/usr/bin/make -f
+
+include /usr/share/dpkg/pkg-info.mk   # To access the "DEB_VERSION" variable
+
+export JAVASCRIPT_LIBS := Build/javascript-libs
+export UPSTREAM_VERSION := $(shell echo "$(DEB_VERSION)" | cut -d '-' -f 1)
+export TARGET := libOrthancWebViewer.so
+
+export DEB_BUILD_MAINT_OPTIONS := hardening=+all
+
+%:
+	dh $@ --parallel --builddirectory=Build
+
+CMAKE_EXTRA_FLAGS += \
+	-DCMAKE_SKIP_RPATH:BOOL=ON \
+	-DSTATIC_BUILD:BOOL=OFF \
+	-DSTANDALONE_BUILD:BOOL=ON \
+        -DUSE_GTEST_DEBIAN_SOURCE_PACKAGE:BOOL=ON \
+	-DCMAKE_BUILD_TYPE=""   # The build type must be left empty, see #711515
+
+override_dh_auto_configure:
+	mkdir -p ${JAVASCRIPT_LIBS}/fonts
+	mkdir -p ${JAVASCRIPT_LIBS}/images
+
+        # Place the jquery and jquery-ui libraries from Debian
+	cp /usr/share/javascript/jquery/jquery.min.js ${JAVASCRIPT_LIBS}/jquery.js
+	cp /usr/share/javascript/jquery-ui/jquery-ui.min.js ${JAVASCRIPT_LIBS}
+	cp /usr/share/javascript/jquery-ui-themes/base/jquery-ui.min.css ${JAVASCRIPT_LIBS}
+	cp /usr/share/javascript/jquery-ui-themes/base/images/* ${JAVASCRIPT_LIBS}/images
+	touch  ${JAVASCRIPT_LIBS}/jquery-ui.theme.min.css
+
+        # Place the third-party JavaScript libraries at the location
+        # expected by CMake
+	yui-compressor debian/JS/cornerstone-0.8.4/cornerstone.js > \
+		${JAVASCRIPT_LIBS}/cornerstone.min.js
+	yui-compressor debian/JS/cornerstone-0.8.4/cornerstone.css > \
+		${JAVASCRIPT_LIBS}/cornerstone.min.css
+
+	yui-compressor debian/JS/jsPanel-2.3.3/jquery.jspanel.js > \
+		${JAVASCRIPT_LIBS}/jquery.jspanel.min.js
+	yui-compressor debian/JS/jsPanel-2.3.3/jquery.jspanel.css > \
+		${JAVASCRIPT_LIBS}/jquery.jspanel.min.css
+	yui-compressor debian/JS/jsPanel-2.3.3/jquery.ui.touch-punch.js > \
+		${JAVASCRIPT_LIBS}/jquery.ui.touch-punch.min.js
+	yui-compressor debian/JS/jsPanel-2.3.3/mobile-detect.js > \
+		${JAVASCRIPT_LIBS}/mobile-detect.min.js
+	cp debian/JS/jsPanel-2.3.3/images/* ${JAVASCRIPT_LIBS}/images/
+	cp debian/JS/jsPanel-2.3.3/fonts/* ${JAVASCRIPT_LIBS}/fonts/
+
+	yui-compressor debian/JS/js-url-1.8.6/url.js > \
+		${JAVASCRIPT_LIBS}/url.min.js
+
+	yui-compressor debian/JS/pako-0.2.5/pako_inflate.js > \
+		${JAVASCRIPT_LIBS}/pako_inflate.min.js
+
+        # Launch the original CMake script
+	dh_auto_configure -- $(CMAKE_EXTRA_FLAGS)
+
+override_dh_auto_test:
+	( cd Build; ./UnitTests )
+
+override_dh_auto_install:
+	cp Build/${TARGET} Build/${TARGET}.${UPSTREAM_VERSION}
+	dh_install Build/${TARGET}.${UPSTREAM_VERSION} usr/lib
+
+override_dh_link:
+	dh_link usr/lib/${TARGET}.${UPSTREAM_VERSION} usr/share/orthanc/plugins/${TARGET}
+
+override_dh_installchangelogs:
+	dh_installchangelogs -k NEWS
+
+get-orig-source:
+	uscan --verbose --force-download --repack --compression xz


Property changes on: trunk/packages/orthanc-webviewer/tags/2.2-3/rules
___________________________________________________________________
Added: svn:executable
   + *

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/source/format
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/source/format	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/source/format	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1 @@
+3.0 (quilt)

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/source/include-binaries
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/source/include-binaries	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/source/include-binaries	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,11 @@
+debian/JS/jsPanel-2.3.3/fonts/jsglyph.ttf
+debian/JS/jsPanel-2.3.3/fonts/jsglyph.eot
+debian/JS/jsPanel-2.3.3/fonts/jsglyph.woff2
+debian/JS/jsPanel-2.3.3/fonts/jsglyph.woff
+debian/JS/jsPanel-2.3.3/images/ui-icons_454545_256x240.png
+debian/JS/jsPanel-2.3.3/images/resize-handle.png
+debian/JS/jsPanel-2.3.3/images/close-20-333.png
+debian/JS/jsPanel-2.3.3/images/close-20.png
+debian/JS/jsPanel-2.3.3/images/icon-sprite-20x20.jpg
+debian/JS/jsPanel-2.3.3/images/icon-sprite-32x32.jpg
+debian/JS/jsPanel-2.3.3/images/icon-sprite-16x16.jpg

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/source.lintian-overrides
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/source.lintian-overrides	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/source.lintian-overrides	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,5 @@
+# The following files are wrongly interpreted as minimified
+# JavaScript by lintian, which is not the case.
+# cf. https://lists.debian.org/debian-med/2015/09/msg00135.html
+orthanc-webviewer source: source-is-missing debian/JS/jsPanel-2.3.3/mobile-detect.js line length is 659 characters (>512)
+orthanc-webviewer source: source-is-missing debian/JS/pako-0.2.5/pako_inflate.js line length is 768 characters (>512)

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/upstream/metadata
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/upstream/metadata	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/upstream/metadata	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,6 @@
+Reference:
+  Author: S. Jodogne and C. Bernard and M. Devillers and E. Lenaerts and P. Coucke
+  Title: "Orthanc -- A Lightweight, RESTful DICOM Server for Healthcare and Medical Research"
+  Type: book
+  Booktitle: Proc. of the International Symposium on Biomedical Imaging
+  Year: 2013

Added: trunk/packages/orthanc-webviewer/tags/2.2-3/watch
===================================================================
--- trunk/packages/orthanc-webviewer/tags/2.2-3/watch	                        (rev 0)
+++ trunk/packages/orthanc-webviewer/tags/2.2-3/watch	2017-07-10 10:30:56 UTC (rev 23870)
@@ -0,0 +1,3 @@
+version=3
+opts="downloadurlmangle=s/\/browse\.php\?path=//g" \
+http://www.orthanc-server.com/browse.php?path=/plugin-webviewer downloads/get\.php\?path=/plugin-webviewer/OrthancWebViewer-(\d\S*)\.tar\.gz

Modified: trunk/packages/orthanc-webviewer/trunk/debian/changelog
===================================================================
--- trunk/packages/orthanc-webviewer/trunk/debian/changelog	2017-07-10 10:09:05 UTC (rev 23869)
+++ trunk/packages/orthanc-webviewer/trunk/debian/changelog	2017-07-10 10:30:56 UTC (rev 23870)
@@ -1,4 +1,4 @@
-orthanc-webviewer (2.2-3) UNRELEASED; urgency=medium
+orthanc-webviewer (2.2-3) unstable; urgency=medium
 
   * Fix FTBFS with libsqlite3-dev >= 3.19.0. Closes: #867789
 




More information about the debian-med-commit mailing list